Steve Losen scl@sasha.acc.virginia.edu writes
Someone (sorry, deleted message) pointed out that perl's chown() call probably follows symlinks, and that is indeed the case. However, there is a very simple workaround for that:
find dir ! -type l -print | mychown.c
Also, as someone pointed out, if you use chown -R you also want to use -h (if your unix flavor supports that option) to change the owner of the symlink itself rather than what the symlink points to.
Perl's chown will normally be using the underlying chown(2) system call, and whether that follows symlinks or not depends on your Unix flavour: e.g. it didn't in SunOS4, but it does in Solaris 2+.
With some trepidation, I include the "multichown" Perl scipt that I use to make systematic changes to uids and gids in directory trees. It's quite old so please excuse all the Perl4'isms (the globs, no my variables, etc.) It's specifically programmed to run under Solaris: tweaks to the $type values might well be required on other systems. I did find one bug in it while I was looking at today, which I have fixed. :) It just leaves out symlinks, but I know that the right things to do is to write a C extension to get access to Solaris' lchown(2) call instead.
With all the methods discussed so far, there's another potential problem. Unless the directory trees and their contents are guaranteed unchanging, havoc can arise if changes are made between the stat() and chown() calls.
Chris Thompson University of Cambridge Computing Service, Email: cet1@ucs.cam.ac.uk New Museums Site, Cambridge CB2 3QG, Phone: +44 1223 334715 United Kingdom.
=== multichown script begins ===
#!/bin/perl
# This program does selective chown/chgrp operations on all inodes # in one or more directory trees. It will often be run as "root".
# There should be one or more option arguments of the form # -owner:NAME1:NAME2 (-o, -user, -u are synonyms of -owner) # -group:NAME1:NAME2 (-g is a synonym of -group) # to specify changing owner or group from NAME1 to NAME2. Users # and groups can be specified by name or by numeric uid/gid.
# The remaining arguments are the path names of the top-level directories # to be searched. (This directory itself is not included in the list of # inodes processed.) Relative path names are allowed provided the PWD # environment variable is set correctly on entry.
%OWNER = (); %GROUP = (); $OWNER = "user" ; $GROUP = "group"; %keys = ( "-owner", *OWNER, "-o", *OWNER, "-user", *OWNER, "-u", *OWNER, "-group", *GROUP, "-g", *GROUP);
while ($ARGV[0] =~ /^-/) { (@name = split(/:/,($arg = shift @ARGV),4)) == 3 && defined ($name = $keys{shift @name}) || die "$0: Invalid option $arg\n"; local(*KTYPE) = $name; for (@name) { next if /^\d+$/; defined($name = &KTYPE) || die "$0: Unknown $KTYPE $_\n"; $_ = $name; } $KTYPE{0+$name[0]} = 1+$name[1]; }
sub OWNER { return scalar getpwnam($_); } sub GROUP { return scalar getgrnam($_); }
keys (%OWNER) + keys (%GROUP) or die "$0: No owner or group substitutions specified\n";
# Prefix remaining arguments with $PWD if appropriate.
undef $prefix; for (@ARGV) { unless (m#^/#) { $prefix = &pwdpath."/" unless $prefix; $_ = $prefix.$_; } }
sub pwdpath { local($pwd) = $ENV{"PWD"}; local($deva,$inoa,$devb,$inob); (($deva,$inoa) = stat(".")) || die "$0: Cannot stat current directory: $!\n"; $pwd =~ m#^/# || die "$0: Invalid environment variable PWD = $pwd\n"; (($devb,$inob) = stat($pwd)) || die "$0: Cannot stat $pwd: $!\n"; ($deva==$devb && $inoa==$inob) || die "$0: Incorrect environment variable PWD = $pwd\n"; return $pwd; }
# Now do the real work passing over the directories.
while (defined ($name = shift @ARGV)) { unless (chdir $name) { warn "Cannot make $name current: $!\n" ; next } unless (opendir(DIR,".")) { warn "Cannot open $name: $!\n" ; next } while ($entry = readdir DIR) { next if $entry eq "." || $entry eq ".."; unless (@stat = lstat $entry) { warn "Unable to stat $name/$entry: $!\n" ; next } $perm = $stat[2]; $type = $perm&0xf000; if ($type==0x4000) { unshift (@ARGV,"$name/$entry"); } $olduid = $stat[4]; $newuid = $OWNER{$olduid}-1; $oldgid = $stat[5]; $newgid = $GROUP{$oldgid}-1; unless ($newuid < 0 && $newgid < 0) { if ($type==0x8000 && ($perm&06000)!=0) { warn "$name/$entry is setuid or setgid - not changed\n"; } elsif ($type==0xa000) { warn "$name/$entry is symlink - not changed\n" } elsif (chown ($newuid, $newgid, $entry)) { printf "%5d %5d -> %5d %5d ",$olduid,$oldgid,$newuid,$newgid; print "$name/$entry\n"; } else { warn "Unable to chown $name/$entry: $!\n" } } } closedir(DIR) }
# End of program
=== multichown script ends ===
Chris Thompson University of Cambridge Computing Service, Email: cet1@ucs.cam.ac.uk New Museums Site, Cambridge CB2 3QG, Phone: +44 1223 334715 United Kingdom.