Article 2827 of comp.lang.perl:
Xref: feenix.metronet.com comp.lang.perl:2827
Newsgroups: comp.lang.perl
Path: feenix.metronet.com!news.utdallas.edu!tamsun.tamu.edu!cs.utexas.edu!asuvax!ncar!tres
From: [email protected] (Tres Hofmeister)
Subject: Re: SOURCE: change UIDs thoughout your filesystems
Message-ID: <[email protected]>
Sender: [email protected] (USENET Maintenance)
Organization: NCAR, Research Applications Program
References: <[email protected]>
Date: Mon, 17 May 1993 21:50:55 GMT
Lines: 286

In article <[email protected]> [email protected]
   (Hal Pomeranz) writes:

> I had the fun of changing UIDs on my systems last week, so I wrote
> the following little proglet to descend through my directories and
> change ownerships everywhere.  The script expects one argument which
> is the name of a file containing lines with the old UID and the UID
> that it's changing to, separated by whitespace (one pair per line).
> There's an optional second argument if you want to start somewhere
> other than the root.  The script will not descend into NFS filesystems.


In article <[email protected]> [email protected]
   (Tom Christiansen) writes:

> From the keyboard of [email protected]:
> : P.S. How do I learn perl, where do I start?
>
> Read other people's code.  Understand it.  Copy it.  Change it.
> --tom


       With Tom's advice in mind, here's a modified version of his
`mvids' script, which performs the same function as Hal's, and more.

--
Tres Hofmeister
[email protected]

#!/local/bin/perl
# mvids: "moves" uids and gids
# Tom Christiansen <[email protected]>
# Modified by: Tres Hofmeister <[email protected]>
#   3/29/93
#   Use find.pl and `lstat' rather than reading a pipe from find(1):
#       For better portability, as not all finds support -fstype or -ls;
#       indexing into the find string breaks on device special files and
#       filenames containing whitespace; `split' was breaking up the
#       string inconsistently because of leading whitespace (using
#       split(' ') and changing the indexing could also fix this).
#   Fixed the non-existent user/group tests, which were using the wrong
#       array names;
#   Allow negative uid/gid's.
#   Delete temporary files if -n is specified.
#   Changed the name of the default directions file to `mvids.cf'.
#   Various minor or cosmetic changes, including addition of comments.
#
# Usage: mvids [-n] [-d] [-f dfile] [startdir]
#
# Takes a file of new user and group id's, creates and installs new
# passwd and group files, and traverses local filesystems starting with
# startdir (default is `/'), updating all changed id's.
#
# Read descriptions from `mvids.cf' file with format:
#     type name number
# e.g.:
#     user tom 1023
#     group staff 200
#
# Options:
# -n  Find files and create the new passwd and group files, but don't
#        actually change anything.
# -f  Specify an alternate description file than the default `mvids.cf'.
# -d  Use the passwd and group files in the current directory.

require 'find.pl';
require 'getopts.pl';

$| = 1;
$oops = 0;
($prog = $0) =~ s#.*/##;

&Getopts('dnf:');
$FILE = $opt_f || "mvids.cf";
$DIR  = $opt_d ? "." : "/etc";
$topdir = shift || '/';

die "Usage: $prog [-n] [-d] [-f dfile] [startdir]\n" if $#ARGV > -1;
die "$topdir: Not a directory" unless -d $topdir;

# Process directions file.
open FILE || die "Can't open directions file \"$FILE\": $!\n";
while (<FILE>) {
   s/\s*#.*//;
   next if /^$/;
   unless (/^(user|group)\s+(\w+)\s+(\d+)/) {
       print STDERR "malformed line at line $. of $FILE: $_";
       $oops++; next;
   }
   if ($3 > 32767) {
       print STDERR "$1 $2 has id that's too big ($3)\n";
       $oops++; next;
   }
   if ($3 == 0) {
       print STDERR "Too dangerous to move $1 $2 to 0\n";
       $oops++; next;
   }
   if ($2 eq 'root') {
       print STDERR "You don't really want to move root!\n";
       $oops++; $next;
   }
   if ($1 eq 'user') {
       if (defined $n_pwn2i{$2}) {
           print STDERR "Saw user $2 again at line $. of $FILE\n";
           $oops++; next;
       }
       if (defined $n_pwi2n{$3}) {
           print STDERR "Saw uid $3 again at line $. of $FILE\n";
           $oops++; next;
       }
       $uids++;

       # Build %n_pwn2i and %n_pwi2n.
       $n_pwn2i{$2} = $3;
       $n_pwi2n{$3} = $2;
   }
   else {
       if (defined $n_grn2i{$2}) {
           print STDERR "Saw group $2 again at line $. of $FILE\n";
           $oops++; next;
       }
       if (defined $n_gri2n{$3}) {
           print STDERR "Saw gid $3 again at line $. of $FILE\n";
           $oops++; next;
       }
       $gids++;

       # Build %n_grn2i and %n_gri2n.
       $n_grn2i{$2} = $3;
       $n_gri2n{$3} = $2;
   }
}

# Process the existing passwd file, build the new one.
if ($uids) {
   $PWD  = "$DIR/passwd";
   $NPWD = "$DIR/passwd.new";
   open PWD                    || die "Can't open $PWD: $!\n";
   open (NPWD, ">$NPWD")       || die "Can't create $NPWD: $!\n";

   while (<PWD>) {
       ((($name, $uid) = /^(\w+):[^:]*:(-?[\d]+):/))
           || die "Bad passwd entry at line $.\n";
       if (defined $n_pwi2n{$uid} && !defined $n_pwn2i{$name}) {
           printf STDERR "Can't move user %s to uid %d, %s already has it\n",
                   $n_pwi2n{$uid}, $uid, $name;
           $oops++;
           next;
       }

       # Build %pwn2i.
       $pwn2i{$name} = $uid;

       # Edit the current line if necessary.
       s/:$uid:/:$n_pwn2i{$name}:/ if defined $n_pwn2i{$name};
       print NPWD;
   }
   close PWD;
   close NPWD;

   foreach $user (keys %n_pwn2i) {
       unless (defined $pwn2i{$user}) {
           print STDERR "Can't move non-existent user $user\n";
           $oops++;
       }
   }
}

# Process the existing group file, build the new one.
if ($gids) {
   $GRP  = "$DIR/group";
   $NGRP = "$DIR/group.new";
   open GRP                    || die "Can't open $GRP: $!\n";
   open (NGRP, ">$NGRP")       || die "Can't create $NGRP: $!\n";

   while (<GRP>) {
       ((($name, $gid) = /^(\w+):[^:]*:(-?[\d]+):/))
           || die "Bad group entry at line $.\n";
       if (defined $n_gri2n{$gid} && !defined $n_grn2i{$name}) {
           printf STDERR "Can't move gid %s to %d, %s already has it\n",
                   $n_gri2n{$gid}, $gid, $name;
           $oops++;
           next;
       }

       # Build %grn2i.
       $grn2i{$name} = $gid;

       # Edit the current line if necessary.
       s/:$gid:/:$n_grn2i{$name}:/ if defined $n_grn2i{$name};
       print NGRP;
   }
   close GRP;
   close NGRP;

   foreach $group (keys %n_grn2i) {
       unless (defined $grn2i{$group}) {
           print STDERR "Can't move non-existent group $group\n";
           $oops++;
       }
   }
}

# Exit if there were errors processing files, or if there's nothing to do.
die "$prog: no ids to move\n" unless $uids || $gids;
die "$prog: $oops error" . ($oops > 1 ? "s" : "") .
       " in remapping directions.\n" if $oops;

# Ok, now do it.

# Build %pwi2n from %pwn2i.
foreach $key (keys %pwn2i) {
   $pwi2n{$pwn2i{$key}} = $key;
}
# Build %gri2n from %grn2i.
foreach $key (keys %grn2i) {
   $gri2n{$grn2i{$key}} = $key;
}

&find("$topdir");

sub wanted {
   # Called by &find.  $name contains the current pathname,
   # $_ contains the filename component of the pathname.

   # Prune NFS filesystems.
   unless ((($dev, $user, $group) = (lstat($_))[0,4,5])
       && $dev < 0 && ($prune = 1)) {

       $uid = $gid = -1;
       $file = $name;

       # Convert numeric id's to names.
       $user = $pwi2n{$user};
       $group = $gri2n{$group};

       # If this file is owned by a user to be changed...
       if (defined $n_pwn2i{$user}) {
           $uid = $n_pwn2i{$user};
           print "changing owner $user of $file from ",
                   "$pwn2i{$user} to $n_pwn2i{$user}\n";
       }

       # If this file is in a group to be changed...
       if (defined $n_grn2i{$group}) {
           $gid = $n_grn2i{$group};
           print "changing group $group of $file from ",
                   "$grn2i{$group} to $n_grn2i{$group}\n";
       }

       # Change the uid and/or gid of the file.  If both are still -1,
       # this file doesn't need changing.  Passing chown -1 for the
       # uid or gid leaves it unchanged.
       if (!$opt_n && ($uid != -1 || $gid != -1)) {
           if (!chown $uid, $gid, $_) {
               print STDERR "couldn't chown $file to $uid.$gid: $!\n";
               $oops++;
           }
       }
   }
}

# Install the new passwd and group files...
unless ($opt_n) {
   if ($uids) {
       rename($PWD, "$PWD.bak")
           || die "Can't mv $PWD to $PWD.bak: $!\n";
       rename($NPWD, $PWD)
           || die "Can't mv $NPWD to $PWD: $!\n";
   }
   if ($gids) {
       rename($GRP, "$GRP.bak")
           || die "Can't mv $GRP to $GRP.bak: $!\n";
       rename($NGRP, $GRP)
           || die "Can't mv $NGRP to $GRP: $!\n";
   }
}
else {
   # Clean up if we were just testing.
   unlink("$NPWD", "$NGRP");
}

exit ($oops != 0);
--
Tres Hofmeister
[email protected]