#!/usr/bin/perl -w
#
# amount
#
# Process a command, automatically mounting and unmounting any mount
# points in the command's arguments. Also re-globs arguments as
# needed after mounting.
#
# Usage: amount command args...
#
# This program is in the public domain. Use at your own risk.
# .
#
# Version 0.1.4
#
# -- Ed Avis, ed@membled.com, 2004-12-26
#
use strict;
use File::Basename;
# Subroutine prototypes
sub canon_dirname($);
sub under_dir($$);
my $FSTAB = '/etc/fstab';
my $MOUNT = '/proc/mounts'; # or 'mount |'
# Check usage
my ($cmd, @args) = @ARGV;
die "usage: $0 command [args]\n" if not defined $cmd;
# Get list of mount points
my %auto_mp;
open(FSTAB, $FSTAB) or die "cannot open $FSTAB: $!";
while () {
s/\#.*//; s/^\s+//; s/\s+$//; next if not length;
/^\S+\s+(\S+)\s+\S+\s+\S+\s+\d+\s+\d+$/
or die "$FSTAB:$.: bad line";
my $mp = $1;
if ($mp !~ m!^/!) {
warn "$FSTAB:$.: mount point $mp not absolute\n" unless $mp eq 'swap';
next;
}
$auto_mp{canon_dirname($mp)}++
&& warn "$FSTAB:$.: mount point $mp already seen";
}
# But leave alone those which are already mounted
open(MOUNT, $MOUNT) or die "cannot open $MOUNT: $!";
while () {
s/\#.*//; s/^\s+//; s/\s+$//; next if not length;
/^.+ (.+) .+ .+ \d \d$/
or /^.+ on (.+) type .+ \(.+\)\s*$/
or die "$MOUNT:$.:bad line";
delete $auto_mp{canon_dirname($1)};
}
# Now look at the command's arguments and do any mounting needed.
my %mounted;
my $fail = 0;
MP: foreach my $mp (keys %auto_mp) {
ARG: foreach (@args) {
if (under_dir($_, $mp)) {
if (system('mount', $mp) != 0) {
warn "could not mount $mp, aborting\n";
$fail = 1;
last MP;
} else {
$mounted{$mp} = 1;
last ARG;
}
}
}
}
# Exit status is from command - unless something went wrong, when we
# don't run the command but just return 1.
#
my $rc;
if ($fail) {
$rc = 1;
} else {
# Glob arguments which are on one of the auto-mounted places
my @globbed;
ARG: foreach my $arg (@args) {
foreach my $m (keys %mounted) {
if (under_dir($arg, $m)) {
# Okay, glob it - but if it doesn't match anything,
# push it on unchanged (like the shell does).
#
my @glob_arg = glob $arg;
push @globbed, (scalar @glob_arg) ? @glob_arg : $arg;
next ARG;
}
}
# Not under any of the places we mounted.
push @globbed, $arg;
}
# At last, run the command.
$rc = system($cmd, @globbed);
}
# Finally, put things back as they were before.
foreach (keys %mounted) {
system('umount', $_) && warn "could not umount $_\n";
}
exit($rc);
# canon_dirname()
#
# In Unix, directories are sometimes written with a trailing slash,
# and sometimes without. Furthermore several consecutive slashes are
# equivalent to just one.
#
# This function converts a directory name to a canonical form which
# always ends in a slash and doesn't have two consecutive slashes
# anywhere. It knows nothing about '.' and '..'.
#
# Parameters: directory name
# Returns: canonical form
#
sub canon_dirname($) {
die 'usage: canon_dirname(directory name)' if @_ != 1;
local $_ = shift;
s!/{2,}!/!g;
$_ .= '/' unless m!/$!;
return $_;
}
# under_dir()
#
# Is one file 'at or under' a certain directory? NB a directory is a
# type of 'file'.
#
# Parameters:
# filename
# directory name
#
# Returns: whether (file is under directory or file _is_ directory)
#
sub under_dir($$) {
die 'usage: under_dir(filename, dirname)' if @_ != 2;
my ($f, $d) = @_;
$d = canon_dirname($d);
if (canon_dirname($f) eq $d) {
# File and directory are the same
return 1;
}
if (index(canon_dirname(dirname($f)), $d) == 0) {
# File contained in directory
return 1;
}
return 0;
}