#! /usr/bin/perl -w use strict; use Getopt::Mixed; use vars qw/$VERSION/; BEGIN { $VERSION = '1.0' } die "Not running as root!\n" if $<; my( $IS_UID, $OLD_ID, $NEW_ID, $RECURSE, $QUIET, $VERBOSE ) = ( 0, undef, undef, undef, undef, undef ); Getopt::Mixed::init( q{g=s gid>g group>g u=s uid>u user>u n=s new>n r recurse>r R>r q quiet>q v verbose>v V version>V }); OPTS: { # switch -u and -g are mutually exclusive my( $u_seen, $g_seen ) = (undef, undef); while( my( $option, $value, $pretty ) = Getopt::Mixed::nextOption() ) { $u_seen = 1 if $option eq 'u'; $g_seen = 1 if $option eq 'g'; $OLD_ID = $value if $option eq 'g' or $option eq 'u'; $IS_UID = 1 if $option eq 'u'; $NEW_ID = $value if $option eq 'n'; $RECURSE = 1 if $option eq 'r'; $QUIET = 1 if $option eq 'q'; $VERBOSE = 1 if $option eq 'v'; if( $option eq 'V' ) { print "$VERSION\n"; exit; } die "Cannot specify both -u and -g at the same time.\n" if defined $u_seen and defined $g_seen; } } my $id_name = $IS_UID ? 'user' : 'group'; defined $OLD_ID or die "Source ID not specified via -u/--user or via -g/--group.\n"; $OLD_ID = resolve_id( $OLD_ID ) if( $OLD_ID !~ /^\d+$/ ); defined $NEW_ID or die "New $id_name ID not specified via -n/--new.\n"; $NEW_ID = resolve_id( $NEW_ID ) if( $NEW_ID !~ /^\d+$/ ); if( $RECURSE ) { require File::Find; File::Find->import(); } local @ARGV = $RECURSE ? qw/./ : (do { opendir D, '.' or die "Cannot open directory . for input: $!\n"; my @files = readdir D; close D; @files }) unless @ARGV; foreach my $spec ( @ARGV ) { if( $RECURSE ) { $VERBOSE and -d $spec and print "$spec\n"; find( sub { return if $_ eq '..'; try_chown( $_ ); }, $spec ); } else { try_chown( $spec ); } } sub resolve_id { my $text_id = shift; my $numeric_id = $IS_UID ? (getpwnam($text_id))[2] : (getgrnam($text_id))[2]; die "Can't determine $id_name ID for $text_id.\n" unless defined $numeric_id; $VERBOSE and print "numeric $id_name ID for $text_id resolves to $numeric_id.\n"; $numeric_id; } sub try_chown { my $file = shift; die "$file: no such file or directory.\n" unless -e $file; my( $uid, $gid ) = (stat $file)[4,5]; return unless defined $uid and defined $gid; if( $IS_UID and $uid == $OLD_ID) { print "\tchown $uid->$NEW_ID, $gid, $file\n" unless $QUIET; if( !chown $NEW_ID, $gid, $file ) { $QUIET or warn "Cannot change uid for $file from $uid to $NEW_ID: $!\n"; } } elsif( !$IS_UID and $gid == $OLD_ID ) { print "\tchown $uid, $gid->$NEW_ID, $file\n" unless $QUIET; if( !chown $uid, $NEW_ID, $file ) { $QUIET or warn "Cannot change gid for $file from $gid to $NEW_ID: $!\n"; } } } =head1 NAME nugid - new user/group id =head1 SYNOPSIS B [B<-qrvV>] {B<-g> group|B<-u> user} -n ID [filespec] [filespec...] =head1 DESCRIPTION Selectively modify user and group ownerships of files and directories depending on current ownerships. All files that match the given group id or user id will be changed to the new specified id. Numeric and symbolic ids are recognised. =head1 OPTIONS =over 5 =item B<-g> B<--group> B<--gid> Group. Specifiy the existing group id (gid) to match. The id may either be numeric, or symbolic. In the latter case, it is an error to specify a non-existant group name. This switch and the [B<-u>] switch are mutually exclusive. =item B<-u> B<--uid> B<--user> User. Specifiy the existing user id (uid) to match. The id may either be numeric, or symbolic. In the latter case, it is an error to specify a non-existant user name. This switch and the [B<-g>] switch are mutually exclusive. =item B<-n> B<--new> New. Specify the new id that will be applied to the files that are owned by the group or user specified via the B<-g> or B<-u> switches. =item B<-r> B<-R> B<--recurse> Recurse. All subdirectories of the directories specified on the command line will be examined. =item B<-q> B<--quiet> Quiet. By default, the script will print out every change made as well as errors resulting from the inability to perform the change. This switch will cause the script to run silently. =item B<-v> B<--verbose> Verbose. The script will print out a little more information than usual, notably the name of each directory root given when performing a recursive search, and the resolved numeric id when a symbolic group or user is given. =item B<-V> B<--version> Version. Print out the current version of the script and exit with performing any changes. =back If no file specifications are given on the command line, one of two actions will be taken. If recursion is specified then the current directory `.' will be used as a starting point. If recursion is not specified, the current directory is globbed and all entries are processed. The script must be run by the superuser, and will exit immediately if this condition is not met. =head1 EXAMPLES C Starting from the root directory, find all files who are owned by user 1245 (which may be an account that has been deleted from the host) and change the ownership to that of the `operator' account. Nothing will be printed. C Change the files in the current directory that are owned by the group `staff' to the group `wheel'. The output will look something like: chown 410, 5->15, ./porpentine chown 203, 5->15, ./slothrop chown 600, 5->15, ./stencil There may be many more files than these three in the current directory, however, these would be the only ones belonging to the group `staff'. In this example, the numeric id of the staff group is 5, and the id of the `wheel' group is 15. C Change all files owned by group 45 to group 85. Note that neither of these ids necessarily equate to a known group name (as per C or equivalent). This may have dire consequences on the health of your host, use with caution. =head1 INSTALLATION As this this script is useable only by the superuser, it makes sense to install it with no permissions for anyone other than root. The following commands are thus suitable for installing the script: cp thisfile /usr/local/bin/nugid chown root /usr/local/bin/nugid chmod 700 /usr/local/bin/nugid =head1 COPYRIGHT Copyright (c) 2001 David Landgren. This script is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR David "grinder" Landgren grinder on perlmonks (http://www.perlmonks.org/) eval {join chr(64) => qw[landgren bpinet.com]} =cut