#!/usr/bin/perl # This script will parse the output of "ls -lR dir [dir...]" or # "ls -nR dir [dir...]" (the former preserves owner and group info # as names, the latter as numbers). It will duplicate the perm- # issions, owner, and group info it reads onto any existing files # and dirs (it silently skips non-existing items, and doesn't even # try to affect symlinks). use strict; our $verbose = 0; # Replace this with real option parsing... $" = ', '; # How to join arrays referenced in double-quotes. my($dir, %uid_hash, %gid_hash); my $detail_line = qr{ ^ ([-bcdlps]) # 1. File type ( [-r][-w][-xsS] # 2. owner-permissions [-r][-w][-xsS] # 3. group-permissions [-r][-w][-xtT] ) \s+ # 4. world-permissions \d+ \s+ # ignore number of links (\S+) \s+ # 5. owner (\S+) \s+ # 6. group \d+ (?: , \s+ \d+ )? \s+ # ignore size or device numbers \S+ \s+ \d+ \s+ # ignore month and date (?: \d+:\d+ | \d+ ) \s+ # ignore time or year ([^\r\n]+) $ # 7. name }x; while (<>) { if (defined($dir)) { if (/^$/) { undef $dir; next; } my($type, $perms, $owner, $group, $name) = /$detail_line/; die "Invalid input format at line $.:\n$_" unless defined $name; my $fn = "$dir/$name"; if ($type eq '-') { undef $type unless -f $fn; } elsif ($type eq 'd') { undef $type unless -d $fn; } elsif ($type eq 'b') { undef $type unless -b $fn; } elsif ($type eq 'c') { undef $type unless -c $fn; } elsif ($type eq 'p') { undef $type unless -p $fn; } elsif ($type eq 's') { undef $type unless -S $fn; } else { if ($type eq 'l') { $fn =~ s/ -> .*//; $type = 'symlinks'; } else { $type = "type '$type'"; } print "Skipping $fn ($type ignored)\n" if $verbose; next; } if (!defined $type) { if ($verbose) { if (!-e _) { print "Skipping $fn (missing)\n"; } else { print "Skipping $fn (types don't match)\n"; } } next; } my($cur_mode, $cur_uid, $cur_gid) = (stat(_))[2,4,5]; my $highs = join('', $perms =~ /..(.)..(.)..(.)/); $highs =~ tr/-rwxSTst/00001111/; $perms =~ tr/-STrwxst/00011111/; my $mode = oct('0b' . $highs . $perms); my $uid = $uid_hash{$owner}; if (!defined $uid) { if ($owner =~ /^\d+$/) { $uid = $owner; } else { $uid = getpwnam($owner); } $uid_hash{$owner} = $uid; } my $gid = $gid_hash{$group}; if (!defined $gid) { if ($group =~ /^\d+$/) { $gid = $group; } else { $gid = getgrnam($group); } $gid_hash{$group} = $gid; } my @changes; if ($mode != ($cur_mode & 07777)) { push(@changes, 'permissions'); chmod($mode, $fn); } if ($uid != $cur_uid || $gid != $cur_gid) { push(@changes, 'owner') if $uid != $cur_uid; push(@changes, 'group') if $gid != $cur_gid; chown($uid, $gid, $fn); chmod($mode, $fn) if $mode & 06000; } if (@changes) { print "$fn: changed @changes\n"; } elsif ($verbose) { print "$fn: OK\n"; } } elsif (/^(.*):$/) { $dir = $1; $_ = <>; die "Invalid input format at line $.:\n$_" unless /^total \d+$/; } else { die "Invalid input format at line $.:\n$_"; } }