BuddhaNature has asked for the wisdom of the Perl Monks concerning the following question:

I have written a script which takes a "snapshot" of sorts of the permissions of a specified directory (or file) and spits it into a configuration file Apache-style. You can then use the script to test the permissions later on against the previous "snapshot." You can also use it to change the current permissions back to how they were in the snapshot.

My main drive for this was so that I would put something like /etc into Subversion and then do "svn update"s on other machines to maintain consistency across sytems' /etc's. But there are a number of files which have to have very specific permissions so that they work correctly, but Subversion does not keep permissions info, so I wrote this script "permmy."

This is one of my first large-ish scripts and would like any constructive criticism I could get on it regarding errors and streamlining, especially errors (screwing up permissons on /etc can be a BadThing(tm)...). If you are interested check out the readmore...

#!/usr/bin/perl -w ### LAME BUG ALERT - program will barf if there ### are repeated elements in the conf file ### so be careful you don't "add" the same directory ### to a conf file that already has it in it use strict; use diagnostics; use File::Find (); use Getopt::Std; #use Sys::Syslog; use Config::General; die "Usage: permmy -[itcad] [directory] -f config-file\n" unless @ARGV >= 3; use vars qw/ $opt_t $opt_c $opt_d $opt_i $opt_f $opt_a/; getopts('tcdi:f:a:') or die "Usable options are -tcifa: $!\n"; my $dir_tree = $opt_i; my $add_tree = $opt_a; my $perms_config_file = $opt_f; use vars qw/ $conf %config/; if ($opt_t || $opt_d || $opt_c) { $conf = new Config::General( -ConfigFile => "$perms_config_file", -UseApacheInclude => "yes", -InterPolateVars => "yes" ); %config = $conf->getall; } sub return_mode { sprintf "%04o", (shift) & 07777; } ## checks for validity. Takes a type argument (m for mode, u for user ## and g for group) and the test value sub validator ($$) { my($type, $value) = @_; if ($type =~ /.*mode/) { die "$value is not a valid mode: $!\n" unless ((oct($value)) & +& (oct($value) <= 4095)); } elsif ($type =~ /.*owner/) { die "$value is not a valid user: $!\n" unless ((getpwnam($valu +e)) || ($value eq "root")); } elsif ($type =~ /.*group/) { if (($value eq "root") && ($^O =~ /bsd|darwin/)) {print "There + is no $value group on $^O, fix your config file!\n";} if (($value eq "wheel") && ($^O =~ /linux/)) {print "There is +no $value group in $^O, fix your config file!\n";} if (($value eq "root") || ($value eq "wheel")) {$value = getgr +gid(0);} die "$value is not a valid group: $!\n" unless ((getgrnam($val +ue)) || (($value eq "root") || ($value eq "wheel"))); } else { die "Validator complaint: something wrong with type $type and/ +or value $value\n"; } } sub configurator ($$$$) { my($conf_type, $conf_file, $directory, $wanted_ref) = @_; if ($conf_type eq "i") { open CONFIG, ">$conf_file" or die "Could not open config file $conf_file for writing: + $!\n"; } elsif ($conf_type eq "a") { open CONFIG, ">>$conf_file" or die "Could not open config file $conf_file for writing: + $!\n"; } use vars qw/*name *dir *prune/; *name = *File::Find::name; *dir = *File::Find::dir; *prune = *File::Find::prune; # Traverse desired filesystems File::Find::find({wanted => $wanted_ref}, $directory); exit; close CONFIG or die "Could not close config file $conf_file: $!\n" +; } sub configtest { foreach my $pri ( keys %config ) { die "$pri is not a valid descriptor: $!\n" unless (($pri eq "d +ir") || ($pri eq "file") || ($pri eq "link") || ($pri eq "area")); if ($pri eq "area") { foreach my $sec ( keys %{ $config{$pri} } ) { die "$sec does not exist: $!\n" unless (-e $sec); foreach my $ter (keys %{ $config{$pri}{$sec} } ) { die "$ter is not a valid type: $!\n" unless (($ter + eq "dir-mode") || ($ter eq "dir-owner") || ($ter eq "dir-group") || +($ter eq "file-mode") || ($ter eq "file-owner") || ($ter eq "file-gro +up") || ($ter eq "link-mode") || ($ter eq "link-owner") || ($ter eq " +link-group")); &validator($ter, $config{$pri}{$sec}{$ter}); } } } else { foreach my $sec (keys %{ $config{$pri} } ) { die "$sec does not exist: $!\n" unless (-e $sec); foreach my $ter (keys %{ $config{$pri}{$sec} } ) { die "$ter is not a valid type: $!\n" unless (($ter + eq "mode") || ($ter eq "owner") || ($ter eq "group")); &validator($ter, $config{$pri}{$sec}{$ter}); } } } } print "Syntax OK\n"; } sub wanted_ia { ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($_)); my $pwuid = getpwuid $uid; my $grgid = getgrgid $gid; my $nice_mode = &return_mode($mode); if (-d $_) { print CONFIG "<dir $name>\n\tmode $nice_mode\n\towner $pwuid\n +\tgroup $grgid\n</dir>\n"; } elsif (-f $_) { print CONFIG "<file $name>\n\tmode $nice_mode\n\towner $pwuid\ +n\tgroup $grgid\n</file>\n"; } elsif (-l $_) { print CONFIG "<link $name>\n\tmode $nice_mode\n\towner $pwuid\ +n\tgroup $grgid\n</link>\n"; } } sub change_mode ($$) { my($the_file, $change_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_nice_mode = &return_mode($mode); if (($change_value) && (oct($change_value) != oct($c_nice_mode))) { my $o_mode = oct($change_value); chmod($o_mode, $the_file) or warn "Could not change mode of $t +he_file: $!\n"; print "Changed mode of $the_file from $c_nice_mode to $change_ +value\n"; } } sub change_owner ($$) { my($the_file, $change_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_pwuid = getpwuid($uid); if (($change_value) && ($change_value ne $c_pwuid)) { my $num_uid = getpwnam($change_value); chown($num_uid, -1, $the_file) or warn "Could not change owner + of $the_file: $!\n"; print "Changed owner of $the_file from $c_pwuid to $change_val +ue\n"; } } sub change_group ($$) { my($the_file, $change_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_grgid = getgrgid($gid); if (($change_value) && ($change_value ne $c_grgid)) { if (($change_value eq "root") || ($change_value eq "wheel")) { +$change_value = getgrgid(0)} my $num_gid = getgrnam($change_value); chown(-1, $num_gid, $the_file) or warn "Could not change group + of $the_file: $!\n"; print "Changed group of $the_file from $c_grgid to $change_val +ue\n"; } } sub test_mode ($$) { my($the_file, $test_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_nice_mode = return_mode($mode); print "The file $the_file has a mode of $c_nice_mode but should ha +ve a mode of $test_value\n" unless ($c_nice_mode eq $test_value); } sub test_owner ($$) { my($the_file, $test_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_pwuid = getpwuid($uid); print "The file $the_file has an owner of $c_pwuid but should have + an owner of $test_value\n" unless ($c_pwuid eq $test_value); } sub test_group ($$) { my($the_file, $test_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_grgid = getgrgid($gid); print "The file $the_file has a group of $c_grgid but should have +a group of $test_value\n" unless ($c_grgid eq $test_value); } if ($opt_d) { configtest(); } elsif ($opt_i) { configurator("i", $perms_config_file, $dir_tree, \&wanted_ia); } elsif ($opt_a) { configurator("a", $perms_config_file, $add_tree, \&wanted_ia); } elsif ($opt_t) { configtest(); foreach my $test ( keys %config ) { foreach my $tester ( keys %{ $config{$test} } ) { if (!-e $tester) { print "$tester does not seem to exist any more\n"; next; } foreach my $testy ( keys %{ $config{$test}{$tester} } ) { if ($testy eq "mode") { test_mode($tester, $config{$test}{$tester}{$testy} +); } elsif ($testy eq "owner") { test_owner($tester, $config{$test}{$tester}{$testy +}); } elsif ($testy eq "group") { test_group($tester, $config{$test}{$tester}{$testy +}); } } } } } elsif ($opt_c) { configtest(); foreach my $test ( keys %config ) { if ($test eq "area") { foreach my $tester ( keys %{ $config{$test} } ) { if (!-e $tester) { print "$tester does not seem to exist any more\n"; next; } use vars qw/$d_mode $f_mode $l_mode $d_own $f_own $l_o +wn $d_grp $f_grp $l_grp/; $d_mode=$f_mode=$l_mode=$d_own=$f_own=$l_own=$d_grp=$f +_grp=$l_grp=0; foreach my $testy ( keys %{ $config{$test}{$tester} } +) { if ($testy eq "dir-mode") { $d_mode = $config{$tes +t}{$tester}{$testy} } elsif ($testy eq "file-mode") { $f_mode = $config{ +$test}{$tester}{$testy} } elsif ($testy eq "link-mode") { $l_mode = $config{ +$test}{$tester}{$testy} } elsif ($testy eq "dir-owner") { $d_own = $config{$ +test}{$tester}{$testy} } elsif ($testy eq "file-owner") { $f_own = $config{ +$test}{$tester}{$testy} } elsif ($testy eq "link-owner") { $l_own = $config{ +$test}{$tester}{$testy} } elsif ($testy eq "dir-group") { $d_grp = $config{$ +test}{$tester}{$testy} } elsif ($testy eq "file-group") { $f_grp = $config{ +$test}{$te\ster}{$testy} } elsif ($testy eq "link-group") { $l_grp = $config{ +$test}{$tester}{$testy} } } use vars qw/*name *dir *prune/; *name = *File::Find::name; *dir = *File::Find::dir; *prune = *File::Find::prune; # Traverse desired filesystems File::Find::find({wanted => \&wanted_c}, $tester); exit; sub wanted_c { if ((-d $_) && ($d_mode)) { change_mode($_, $d_mode); } elsif ((-d $_) && ($d_own)) { change_owner($_, $d_own); } elsif ((-d $_) && ($d_grp)) { change_group($_, $d_grp); } elsif ((-f $_) && ($f_mode)) { change_mode($_, $f_mode); } elsif ((-f $_) && ($f_own)) { change_owner($_, $f_own); } elsif ((-f $_) && ($f_grp)) { change_group($_, $f_grp); } elsif ((-l $_) && ($l_mode)) { change_mode($_, $l_mode); } elsif ((-l $_) && ($l_own)) { change_owner($_, $l_own); } elsif ((-l $_) && ($l_grp)) { change_group($_, $l_grp); } } } } else { foreach my $tester ( keys %{ $config{$test} } ) { if (!-e $tester) { print "$tester does not seem to exist any more\n"; next; } foreach my $testy2 ( keys %{ $config{$test}{$tester} } + ) elsif ((-l $_) && ($l_mode)) { change_mode($_, $l_mode); } elsif ((-l $_) && ($l_own)) { change_owner($_, $l_own); } elsif ((-l $_) && ($l_grp)) { change_group($_, $l_grp); } } } } else { foreach my $tester ( keys %{ $config{$test} } ) { if (!-e $tester) { print "$tester does not seem to exist any more\n"; next; } foreach my $testy2 ( keys %{ $config{$test}{$tester} } + ) { if ($testy2 eq "mode") { change_mode($tester, $config{$test}{$tester}{$ +testy2}); } elsif ($testy2 eq "owner") { change_owner($tester, $config{$test}{$tester}{ +$testy2}); } elsif ($testy2 eq "group") { change_group($tester, $config{$test}{$tester}{ +$testy2}); } } } } } }

Below is a sample config file, showing all the options:

<area /etc> dir-perms 0755 file-perms 0644 link-perms 0777 dir-owner root file-owner root link-owner root dir-group root file-group root link-group root </area> <dir /etc/foo> perms 0700 owner shane group shane </dir> <file /etc/bar.cfg> perms 0744 owner joe group baz </file> <link /etc/baz> perms 0777 owner follow group me </link>

The one kinda "weird" part is the "area" tag - this basically just means to give default permissions (recursively) to the directory and its contents.

Thanks for any help.

Replies are listed 'Best First'.
Re: Permissions Utility Script for sysadmins - seeking comments
by hv (Prior) on Apr 19, 2004 at 21:43 UTC

    (I should stress before I start that I don't find this code horrible, but you asked for any constructive criticism. :)

    die "Usage: permmy -[itcad] [directory] -f config-file\n" unless @ARGV >= 3; use vars qw/ $opt_t $opt_c $opt_d $opt_i $opt_f $opt_a/; getopts('tcdi:f:a:') or die "Usable options are -tcifa: $!\n";

    Hmm, nothing explains what the options mean; the usage message implies that you can supply just 2 arguments but the code requires 3; the second die() gives the wrong list of supported arguments.

    I prefix most of my scripts with a usage() routine something like:

    sub usage { my $text = <<USAGE; $0 [ -i | -c | -t ] directory [, directory ... ] Frobnicate all the files in the specified directory/ies -t test only, and warn of discrepancies -i inclusive: unknown files ignored -c conclusive: unknown files deleted (default is to defrobnicate unknown files) USAGE die "$text\n@_\n" if @_; print $text; exit 0; }

    I then call usage() without parameters if I see -h or -? options, and with an error message as a parameter if for some other reason the arguments were not acceptable. As well as being useful for the user, having this usage statement at the beginning of the script can be very helpful for the maintainer - these days I tend to write it first, and use it as a guide for development of the rest of the script.

    if ($opt_t || $opt_d || $opt_c) { ... } [ snip 11 subroutines covering some 180 lines ] if ($opt_d) { ... }

    I find this layout confusing; I prefer instead to keep the main code at the top together and with a clear

    exit 0; # --- subroutines
    (or something) separating the main code from the subroutine declarations - it is quite difficult, either by eye or programmatically, to find the next active line of code when it is so far away. If the main code starts to become long (eg more than a screenful) I'll move stuff into otherwise unnecessary subroutines to minimise that, so that I can end up with:
    if ($opt_d) { configtest(); } elsif ($opt_i) { configurator("i", $perms_config_file, $dir_tree, \&wanted_ia); } elsif ($opt_a) { configurator("a", $perms_config_file, $add_tree, \&wanted_ia); } elsif ($opt_t) { configtest(); fulltest(); } elsif ($opt_c) { configtest(); changeperms(); # or whatever this actually does }
    since when maintaining it 6 months later, I usually want the main code to give me a clear overview of the structure, to make it as easy as possible to drill down to the actual bit of code I'm looking for.

    Sideroad: I try to name subroutines appropriately to help me think with them, and part of that is getting the right part of speech - if a subroutine returns TRUE when the argument is valid I'll call it valid or is_valid and use it like:

    if (valid($foo)) { ... }
    or, more likely:
    if ($obj->valid) { ... }
    whereas if it does its validity checks but doesn't return a result I'll name it imperatively:
    validate($foo); # dies if invalid

    sub configurator ($$$$) { ... exit; close CONFIG or die "Could not close config file $conf_file: $!\n" +; }

    That looks wrong - perhaps the exit was a temporary diagnostic? I tend to put such things in the first column to highlight their (intended) temporary nature.

    Sideroad: I assume there is documentation somewhere for the format of the config file.

    my $nice_mode = &return_mode($mode);

    The & is not needed there, and it makes me do a double-take - usually I only expect to see the & specifed when taking a reference to a subroutine.

    sub change_mode ($$) { ...(oct($change_value) != oct($c_nice_mode))... }

    Hmm, one day someone'll call change_mode($file, 0777) and everything will go horribly wrong. We have the underlying mode (a number) and the common external representation (an octal string) - I would normally prefer to use the underlying value throughout in the code, and convert to the external representation only when it is needed for output. This also makes things more consistent with perl builtins such as mkdir or sysopen which take a raw number for their mode arguments.

    chown($num_uid, -1, $the_file) or warn "Could not change owner + of $the_file: $!\n"; print "Changed owner of $the_file from $c_pwuid to $change_val +ue\n";

    After the warning is printed, the success message is printed anyway. Two common alternatives are:

    if (chown(...)) { print "success"; } else { warn "failure"; }
    or:
    chown(...) or do { warn "failure"; return }; print ...

    if ($testy eq "dir-mode") { $d_mode = $config{$tes +t}{$tester}{$testy} } elsif ($testy eq "file-mode") { $f_mode = $config{ +$test}{$tester}{$testy} } ...

    A string of tests like this immediately makes me think "use a hash". In this case, I think you want to end up with a hunk of code to replace sub wanted_c, by specifying a code block explicitly for each hash value or by encoding parameters to either build up some evalled code or to bind to a closure.

    I note also that if the tests specify (say) both 'dir-mode' and 'dir-owner', the latter will be silently ignored: in such cases it is helpful to test and warn. You also need to be careful about booleans - I guess a mode of 000 is unlikely, but if you were to make a natural extension to allow owners to be specified by uid, an attempt to specify root as uid 0 would fail, since the

    elsif ((-d $_) && ($d_uid))
    would always be false.

    If you keep the wanted_c() subroutine as is, note also that it is doing a lot of unnecessary work repeatedly calling stat on the same file as each elsif test fails - that is probably most easily fixed by doing a single bare stat($_) at the beginning of the routine and using the filetests on _ (underscore) thereafter.

    That's probably enough to be getting on with for now. :)

    Hugo

      First off, thanks for al the constructive criticism. I have embarked and a number of the changes you mentioned.

      I am getting a bit confused with one or two things and was hoping you could elucidate a bit more on what might be going on and how I might make it better. Specifically where you mention "use a hash" - I am a bit unsure on how to best impliment this in this context. Also, do you have any insight into why (as you mentioned) if there are both dir-mode and dir-owner that the latter will be silently ignored. Thanks in advance.

      -Shane

        Ok, here's a simplistic way to replace one of the string of elsifs with a hash:

        my %vars = ( 'dir-mode' => \$d_mode, 'file-mode' => \$f_mode, 'link-mode' => \$l_mode, 'dir-owner' => \$d_own, 'file-owner' => \$f_own, 'link-owner' => \$l_own, 'dir-group' => \$d_grp, 'file-group' => \$f_grp, 'link-group' => \$l_grp, ); for my $testy (keys %{ $config{$test}{$tester} }) { unless ($vars{$testy}) { warn "Unexpected test key '$testy'"; next; } ${ $vars{$testy} } = $config{$test}{$tester}{$testy}; }

        This is simply saying: we have a string of (els)ifs, and whichever string we match we're going to execute almost the same code, so let's abstract out the one bit that varies (the variable name in this case) and let the hash associate the string to match with the variable name to modify, leaving the rest of the code constant.

        However, we also have a series of similar variable names, which is also often a sign that a hash is needed. So let's look at the point we're using these variables: I'd characterise the wanted_c subroutine as doing something like:

        • where specified, update any of the mode, owner or group of the file
        • but select a different set of updates depending on whether the file to update is a directory, a link or a plain file
        so I'd be inclined to map that to a data structure that looks like:
        my $wanted_this_time = { mode => $wanted_mode, owner => $wanted_owner, group => $wanted_group, };
        but pick one of three such structures depending on the type of file:
        my $wanted = { if_directory => { mode => $dir_mode, owner => $dir_owner, group => $dir_group, }, if_link => { ... } if_file => { ... } };
        and having made that conceptual transformation, we can now get rid of the matrix of individually named variables:
        my %wanted; my %validfile = map +($_ => 1), qw/ dir file link /; my %validupdate = map +($_ => 1), qw/ mode owner group /; for (keys %{ $config{$test}{$tester} }) { my($filetype, $updatetype} = split /-/, $_, 2; unless ($validfile{$filetype} && $validupdate($updatetype}) { warn "Unexpected directive '$_'"; next; } $wanted{$filetype}{$updatetype} = $config{$test}{$tester}{$_}; } [...] sub wanted_c { stat($_); my $wanted = (-d _ ? $wanted{'dir'}) : (-l _ ? $wanted{'link'}) : (-f _ ? $wanted{'file'}) : return; # ?? warn unexpected type? return unless $wanted; # nothing to change if (defined $wanted->{'mode'}) { change_mode($_, $wanted->{'mode'}); } if (defined $wanted->{'owner'}) { change_owner($_, $wanted->{'owner'}); } if (defined $wanted->{'group'}) { change_group($_, $wanted->{'group'}); } }

        Note that you could as easily represent this data using arrays rather than hashes, but it then becomes much less self-documenting unless you also use named constants for the indexes into each array.

        In general, whenever I see code that needs to cope with several different cases, my instinct is to ask: what effort will be required when another new case needs to be added to the list? How far can I reduce that effort? How can I minimise the danger of getting it wrong when I need to add a new case, for example by adding it in one place and not in another?

        Usually, the answer is: use data structures to minimise code duplication; where possible, encapsulate all the information for the cases in a single data structure, so all the work involved in adding a new case is reduced to adding a new entry into that data structure.

        It may be overkill for this script, but we can take that extra step by taking advantage of code references. The data structures might then look something like this:

        my %filetypes = ( 'dir' => sub { -d _ }, 'link' => sub { -l _ }, 'file' => sub { -f _ }, ); my %updatetypes = ( 'mode' => \&change_mode, 'owner' => \&change_owner, 'group' => \&change_group, ); sub wanted_c { stat($_); for my $filetype (keys %wanted) { next unless &{ $filetypes{$filetype} }; my $wanted = $wanted{$filetype}; for my $update (keys %$wanted) { &{ $updatetypes{$update} }($_, $wanted->{$update}); } } }

        However, I suspect that would not be desirable, at least for the update types, since it makes it much harder to add interdependencies between the update types such as (for example) updating ownership before mode.

        Hugo

        Also, do you have any insight into why (as you mentioned) if there are both dir-mode and dir-owner that the latter will be silently ignored.

        Looking again, this was an error on my part - I'd missed the surrounding loop. Apologies for the misdirection.

        Update: bother, I was right the first time. See Re^5: Permissions Utility Script for sysadmins - seeking comments instead.

        I'll try tomorrow to come up with a couple of examples of how you might use a hash to improve things.

        Hugo

Re: Permissions Utility Script for sysadmins - seeking comments
by sgifford (Prior) on Apr 19, 2004 at 21:25 UTC
    Looks like generally good code. A few comments:
    • Your function prototypes probably aren't that useful.
    • Why use pattern matching for $type in validator instead of just eq?
    • Why the system-specific tests for root and wheel?
    • configurator can easily leave a partial config file if something goes wrong. You may want to write to a temp file then rename it if everything goes well.
    • I would explicitly set permissions on the config file in configurator. If your script is ever used to set permissions, a user with write permissions to the config file effectively 0wns the system, so extra care is warranted.
    • Why do you exit before close CONFIG in configurator?
    • Using a global variable for the CONFIG filehandle is a little messy. Maybe better to use a FileHandle object.
    • If you can't change the owner/group, you probably shouldn't change the mode.
    • In test_mode, == may be a better way to compare modes than eq.
    • In the handler for $opt{t} and in other places, using while(my($key,$val)each(%hash)) may be cleaner than foreach my $key (keys %hash).
    A more general comment: with some files in /etc it's dangerous to have bad permissions even briefly. You may want to consider checking files out into a "staging" directory, then copy them into the real area simultaneously setting their permissions.
Re: Permissions Utility Script for sysadmins - seeking comments
by coec (Chaplain) on Apr 19, 2004 at 23:07 UTC
    Am I correct in assuming that this should be able to be run on any *NIX? If so, under AIX using the octal notation of the file modes will destroy any extended ACLs that have been setup (not sure on other *NIX). It is much safer to use the long notation (u=rwx,go=rx for example).

    My 2 cents

    CC

    PS: I like the idea and its good to see people thinking about consistancy across the systems they manage.

      Well I am writing it to be run on Linux servers I admin, but am writing _on_ a darwin machine (Powerbook G4) - so the whole bsd/linux thing is mostly for convenience more than anything else.
OT (almost) Re: Permissions Utility Script for sysadmins - seeking comments
by kappa (Chaplain) on Apr 19, 2004 at 21:44 UTC
    There's a nice utility in BSDs that does exactly what you want. It is called mtree(8). Check whether you have one.
      Will mtree alter the permissions and such to get them "back in line?" Skimming through the man page it doesn't look as though it does...
        mtree -U
      Is there a version on linux, or a perl implimentation somewhere?

Re: Permissions Utility Script for sysadmins - seeking comments
by Thelonius (Priest) on Apr 19, 2004 at 21:07 UTC
    Your code got screwed up when you posted it. E.g. there's a backslash in the middle of a variable name on line 286 and another gross syntax error around line 350.

    Copy and paste again.

      Okay, I fixed the one on line 286, but don't see the one on 350... Could you be more specific?
Re: Permissions Utility Script for sysadmins - seeking comments
by idsfa (Vicar) on Apr 20, 2004 at 18:03 UTC

    Actually, subversion can be used for this task. Just store the permissions as properties. This also allows you to store ACLs, have version control (and check-in notes) on the permission changes, etc, etc...

    A useful resource would probably be the various API bindings. From this you can use the check-in/check-out hooks to do something like:

    use SVN::Client; my $svn = new SVN::Client(); $svn->propset("acl:joe", "rw", $target, 0, 0); # Where $target is the local (checked out) copy

    NB. The SVN perl bindings are NOT in CPAN. You need to get them along with subversion. (Discussion)


    If anyone needs me I'll be in the Angry Dome.