From: [email protected] (Tom Christiansen)
#Subject: Re: Help with massive uid/gid change.
Date: 30 Nov 90 21:36:05 GMT
Reply-To: [email protected] (Tom Christiansen)
Organization: CONVEX Software Development, Richardson, TX

In article <[email protected]> <ofer%[email protected]> writes:
>Hello
>I have to change the uid/gid numbers for all the users on a system
>I'd like to do it doing one pass over the file system and not by
>doing n times 'find ....' for n=the number of users.
>I have an old /etc/passwd file and a new one and wish to use them
>as the base for this number swapping process.
>I have perl and all standard unix available.

I believe that UofCO has a compiled version of this kind of thing that's
quite quick by going under the file system, which may or may not bother
you.  I don't have a copy though, so wrote my own.

This is how it works.  You make a file, called "howtomv" by default.
In it you put new mappings, for example:

   #comment
   user jimbob 10002      # more comment
   user stevebob 854
   group bobbros 200
   user daemon 1854
   user notes 1

Then you run the script.  It will give you a passwd.new and a group.new
file that you can inspect, and it won't really do the work if you use -n.
I've checked for a lot of blunders, but not all.  One thing I don't check
for is if userid A wants to move to userid B and B is occupied, it won't
let you.  It should really check to see whether B is moving and leaving a
vacancy, but it doesn't.  By all means inspect the code closely first, run
with -n to see what it wants to do, and keep a backup of your disk.  No
warranties, etc etc.  Still, it worked well enough for me when I needed it.

--tom

#/usr/bin/perl
#
# mvids - "moves" uids and gids
# Tom Christiansen <[email protected]>
#
# usage: mvids [-n] [-f howtomvfile] [starting-dir]
#
# Takes list of new user and group ids.
# Fixes passwd and group files.
# Traverses local file system starting with starting-dir
#       updating all changed ids
#
# -n means don't really change anything
# -f is if you don't like the default description file name of howtomv


# read descriptions from howtomv file with format:
#       type name number
# e.g.:
#       user tom 1023
#       group staff 200


$| = 1;
$oops = 0;

require 'getopts.pl';

do Getopts('dnf:');

$FILE = $opt_f || "howtomv";

$DIR = $opt_d ? "." : "/etc";

$topdir = shift || '/';

die "usage: $0 [-n] [-f howtomv] [starting-dir]\n"      if $#ARGV > -1;

die "$topdir: Not a directory"  unless -d $topdir;

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 > 32000) {
       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++;
       $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++;
       $n_grn2i{$2} = $3;
       $n_gri2n{$3} = $2;
   }
}

$PWD  = "$DIR/passwd";
$NPWD = "$PWD.new";

if ($uids) {
   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;
       }
       $pwn2i{$name} = $uid;
       s/:$uid:/:$n_pwn2i{$name}:/     if defined $n_pwn2i{$name};
       print NPWD;
   }
   close PWD;
   close NPWD;

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

}

if ($gids) {
   $GRP = "$DIR/group";
   $NGRP = "$GRP.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;
       }
       $grn2i{$name} = $gid;
       s/:$gid:/:$n_grn2i{$name}:/     if defined $n_grn2i{$name};
       print NGRP;
   }
   close GRP;
   close NGRP;

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

}

die "$0: $oops error" . ($oops > 1 ? "s" : "").
       " in remapping directions.\n"           if $oops;


die "$0: no ids to move\n" unless $uids || $gids;

# ok, now do it

open(FIND, "find $topdir \\( -fstype nfs -prune \\) -o -ls |")
   || die "Can't open find pipe";

while (<FIND>) {
   split;
   $uid = $gid = -1;
   ($file, $user, $group) = ($_[11], $_[5], $_[6]);

   if (defined $n_pwn2i{$user}) {
       $uid = $n_pwn2i{$user};
       print "changing owner $user of $file from ",
               "$pwn2i{$user} to $n_pwn2i{$user}\n";
   }
   if (defined $n_grn2i{$group}) {
       $gid = $n_grn2i{$group};
       print "changing group $group of $file from ",
               "$grn2i{$group} to $n_grn2i{$group}\n";
   }

   if (!$opt_n && ($uid != -1 || $gid != -1)) {
       if (!chown $uid, $gid, $file) {
           printf STDERR "couldn't chown $file to $uid.$gid: $!\n";
           $oops++;
       }
   }
}

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";
   }
}

exit ($oops != 0);