From: [email-masked] (Tom Christiansen)
Date: 30 Nov 90 21:36:05 GMT

In article <[email-masked]> <[email-masked]> 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-masked]>
#
# 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);