#======================================================================= # Require additional modules required use File::Compare; use Win32::File; use Win32::FileSecurity; use Win32::FileSecurity qw(Get EnumerateRights); use Win32::NetResource; use File::Basename; use File::Copy; use File::Find; use DirHandle; use Socket; { #======================================================================= $SIG{'INT'} = 'main::cleanup'; $SIG{'HUP'} = 'main::cleanup'; $SIG{'QUIT'} = 'main::cleanup'; $SIG{'PIPE'} = 'main::cleanup'; # $SIG{'ALARM'} = 'main::cleanup'; #======================================================================= # Set default values.... $VERSION = 0.16; # Script version number $VDATE = "29 July 1999"; # Script release date $binary = 0; $debug = 0; $noexe = 0; $quiet = 0; $verify = 0; $datecheck = 0; $rmextra = 0; $stats = 0; # Print host update statistics $ntfsupdate = 0; # Do not update NTFS permissions when set to 1 $ntfsignore = 0; # Do not read NTFS permissions when set to 1 $tolerance = 3; # Tolerance for file date/time checks for FAT file # systems - in seconds $dotposn = 0; # Dot Position for screen feedback undef $targmap ; # Memory for mapped drive to target host undef $srcmap ; # Memory for mapped drive to target host my(@os) = qw(Win32s, Win95, WinNT); my($update, $target_drive); &args(@ARGV); # Process command line arguments #======================================================================= # Open log file # unless ($log) { $tmp = $ENV{'TMP'}; # Identify TMP environment variable - $tmp = $ENV{'TEMP'} unless $tmp; # - or failing that TEMP $tmp = $ENV{'SystemDrive'} unless $tmp;# - or failing that TEMP $log = $tmp . '/rdist.log' ; # Create the &log file name } StartLog($log); # Open the log file &log( "win32rdist.pl version $VERSION - dated $VDATE\n"); $name = Win32::LoginName; $machine = Win32::NodeName; # Determine Machine (Computer) name $domain = Win32::DomainName; # Determine Domain Name (for &logon) &log( "Machine $machine - domain $domain\n"); # Determine the OS type and version details my($string,$major,$minor,$build,$id) = Win32::GetOSVersion(); &log( " $os[$id] $major\.$minor $string (Build $build)\n"); &log( " Logon Server $ENV{'LOGONSERVER'}\n"); if ( $os[$id] eq "WinNT" ) { use Win32::NetAdmin; Win32::NetAdmin::UserGetAttributes($ENV{'LOGONSERVER'},$name,$password, $passwordage,$privilege,$homedir,$comment,$flags,$scriptpath); }else{ $comment = '' } &log( " User $name ($comment)\n\n"); &log("log file : $log \n\n") if $debug; &log(" started at " . &datetime . "\n\n"); $start_time = time; # Test for incompatible switches.!!!!!!!!!!!!!!!!! if ( $noexe && $quiet ) { &log( " quiet option is invalid with no exe option\n"); undef $quiet; } &ReadDistfile($distfile) if $distfile; #======================================================================= # Print out configuration settings if ($debug & 2) { &log( " -b (binary test) : $binary\n"); &log( " -D (debug) : $debug\n"); &log( " -f distfile : $distfile\n"); &log( " -l logfile : $log\n") if $logset; &log( " -n (no exe) : $noexe\n"); &log( " -q (quiet) : $quiet\n"); &log( " -R (rm extra) : $rmextra\n"); &log( " -v (verify) : $verify\n"); &log( " Append Path : $appendpath\n"); &log( " -y (date check) : $datecheck\n"); foreach (@hostlimit) { &log( " hostlimit : $_\n"); } if ( @exceptions ) { &log("===========================Exceptions================================\n"); foreach (@exceptions){ &log(" $_\n"); } } if ( @except_pats ) { &log("========================Exception Patterns===========================\n"); foreach (@except_pats){ &log(" $_\n"); } } if ( @notify ) { &log("====================Notification Addresses===========================\n"); foreach (@notify){ &log(" $_\n"); } &log("=====================================================================\n"); } } if ($debug & 128) { &log("rdist configuration file variables\n"); foreach $key ( keys %variable ) { &log("\n name : $key\n"); &log(" values : "); foreach $value ( split('\|',$variable{$key} ) ) { &log("$value\n "); } } &log("\n"); } #======================================================================= $list = sub{ my($file) = $File::Find::name; my($mask)=$cwd; $mask =~ s/\\/\\\\/g; $mask =~ s/\//\\\//g; $file =~ s/$mask//g; push(@srcfiles, $cwd . ' ! .' . $file); }; #======================================================================= # Process each Destination #---------------------------------------------------------------------- &log("===============================TARGET================================\n") if ($debug>0); TARGET: foreach $target ( split('\|',$variable{$targets}) ) { &log("updating $target (at " . &datetime . ")\n" ); $targ_start_time = time; $kbupdate = 0; # Total size of files transferred $kbtotal = 0; # Total size of files maintained $ftotal = 0; # Total number of files $fupdate = 0; # Total number of files updated $fremove = 0; # Total number of files deleted $dtotal = 0; # Total number of directories $dupdate = 0; # Total number of directories created $dremove = 0; # Total number of files deleted # If Share is specified if ( $target =~ /^([A-Z]:)(\S*)/i ) { $hostdrive = $1; $hostpath = $2; if ( $debug > 0 ) { &log("Logical drive connection\n"); &log(" Drive : $hostdrive\n"); &log(" Path : $hostpath\n"); } }elsif( $target =~ /(\\\\|\/\/)([a-zA-Z0-9]+)(\\|\/)([a-zA-Z0-9]+[\$]?)(.*)$/ ) { &log(" Mapping target UNC - $target \n") if $debug; ($hostdrive,$hostpath,$hostshare) = &mapdrive($target); if ( $hostdrive =~ /[a-z]:/i ) { &log(" - mapped $target to use drive $hostdrive$hostpath\n") if $debug; $targmap = 1; }else{ &gripe(" failed to connect to $target\n"); next TARGET; } } # Check $hostpath exists - if not create it # - Return to TARGET on failure unless (Win32::SetCwd($hostdrive)) { &gripe("Unable to find $hostdrive\n"); next TARGET; } # Read Source Files/Directories if ( $debug & 64 ) { &log("\nstarting find process - looking for directories under $variable{$files}\n") ; &log(" Source directories are contained in variable : $files\n"); &log(" (use -D128 to list source directories)\n"); } $variable{$files} =~ s/\s+$//g; SRCDIR: foreach $cwd ( split('\|',$variable{$files} ) ) { $cwd =~ s/^"(.*)"$/$1/; # Remove any surrounding quotation marks &log (" CWD - initially set to \"$cwd\" \n") if $debug; # Call mapdrive routine here - but only if a UNC mapping is specified undef $cwddrive; undef $cwdpath; undef $cwdshare; if ( ! ( $cwd =~ /^([A-Z]:)/i ) ) { &log(" Mapping drive to cwd - $cwd \n") if $debug; ($cwddrive,$cwdpath,$cwdshare) = &mapdrive($cwd); if ( $cwddrive =~ /[a-z]:/i ) { &log(" - mapped $cwd to use drive") if $debug; $cwd = $cwddrive . '/' . $cwdpath; &log(" $hostdrive$hostpath\n") if $debug; $srcmap = 1; }else{ &gripe(" failed to connect to $cwd\n"); next SRCDIR; } }else{ &log(" No need to add drive mapping for CWD\n") if $debug; } if ( Win32::SetCwd($cwd) ){ undef @srcfiles; find($list,$cwd); if ( $debug & 64 ) { foreach (@srcfiles){ my ($path,$base) = split(/ ! \./,$dir); &log("$path$base\n"); } } # Process each directory in turn HOSTDIR: foreach $dir ( @srcfiles ) { &log("\n***********************************************************************\n") if $debug; ($path,$base) = split(/ ! \./,$dir); undef %f_ref; undef %f_test; $f_ref{name} = $path . $base; $f_ref{name} =~ s/\/\//\//g; if ( $appendpath ) { $f_test{name} = $hostdrive . $hostpath . $base; &log(" Append path option has changed destination from\n") if $debug; &log(" $f_test{name} to\n") if $debug; $f_test{name} = $path; $f_test{name} =~ s/^([a-zA-Z]:)/$hostdrive$hostpath/; # Strip Source #$f_test{name} = $hostdrive . $hostpath; $f_test{name} .= $base; &log(" $f_test{name}\n") if $debug; }else{ $f_test{name} = $hostdrive . $hostpath . $base; } $f_test{name} =~ s/\/\//\//g; # Remove and double forward slashes... $f_test{display_name} = $f_test{name}; if ( $targmap ) { $f_test{display_name} =~ s/$hostdrive/$hostshare/; $f_test{display_name} =~ s/\\/\//g; # Change \ for / $f_test{display_name} =~ s/(\S)\/\//$1\//g; # Sub any // for / } $f_ref{display_name} = $f_ref{name}; if ( $srcmap ) { $f_ref{display_name} =~ s/$cwddrive/$cwdshare/; $f_ref{display_name} =~ s/\\/\//g; $f_ref{display_name} =~ s/(\S)\/\//$1\//g; } &log(" $f_ref{display_name} --> $f_test{display_name}\n") if $debug; # Finish loop here if $f_ref{name} matches an exception entry if ( @exceptions ) { foreach (@exceptions){ if ( $_ eq $f_ref{name} ) { &log(" ignoring $_ (exception match)\n") if ($debug & 256) ; next HOSTDIR; } } } # Finish loop here if $f_ref{name} matches an except_pat entry if ( @except_pats ) { foreach (@except_pats){ if ( $f_ref{name} =~ /$_/ ) { &log(" ignoring $f_ref{display_name} (except_pat match with /$_/ )\n") if ($debug & 256) ; next HOSTDIR; } } } undef $update; undef $create; undef $ntfschange; #============================================================== # Check File Type (Dir or File) if ( -f $f_ref{name} ) { $f_ref{type} = "FILE"; $ftotal ++; } if ( -d $f_ref{name} ) { $f_ref{type} = "DIR" ; $dtotal ++; } if ( -f $f_test{name} ) { $f_test{type} = "FILE";}; if ( -d $f_test{name} ) { $f_test{type} = "DIR" ;}; #============================================================== # Check files exist unless ( $f_ref{type} ) { &log( "$f_ref{display_name} does not exist\n"); next HOSTDIR; } #============================================================== if (( $f_ref{type} eq "DIR" ) & ( $f_test{type} eq "FILE" ) ) { # Remove redundant file if ($noexe) { &log( " delete file $f_test{display_name}\n"); } elsif ( $verify ) { &log( " delete file $f_test{display_name}\n"); }elsif ( &_dfile($f_test{name},$f_test{display_name}) ) { undef $f_test{type}; } #============================================================== }elsif(( $f_ref{type} eq "FILE" ) & ( $f_test{type} eq "DIR" ) ) { # Remove redundant directory # Remove all files in directory tree &deltree($f_test{type}); if ( -d $f_test{name} ) { $f_test{type} = "DIR" ; }else{ undef $f_test{type}; } #============================================================== }elsif (( $f_ref{type} eq "DIR" ) & ( $f_test{type} eq "DIR" ) & ( $rmextra == 1 ) ) { $d = new DirHandle $f_test{name}; if (defined $d) { @dir = $d->read(); foreach (@dir){ next if ( $_ eq '.' ); next if ( $_ eq '..' ); my($t) = $f_test{name} . '/' . $_; my($s) = $f_ref{name} . '/' . $_; my($display_t) = $f_test{display_name} . '/' . $_; if ( ( -d $t) && ( ! -d $s ) ){ &log (" removing directory tree $display_t\n"); &deltree($t); }elsif ( -f $t ){ next if ( -f $s ); if ($verify) { &log(" remove $display_t\n"); }elsif ($noexe){ &log(" remove $display_t\n"); }else{ &_dfile($t,$display_t); } } } } #============================================================== }elsif (( $f_ref{type} eq "FILE" ) & ( $f_test{type} eq "FILE" ) ) { # Binary compare of files if ( $binary ){ if ( compare($f_ref{name},$f_test{name}) != 0) { if ( $verify ) { &log (" update $f_test{display_name} \n"); }else{ $update = 1; } } } } #============================================================== unless ( $f_test{type} ) { if ( $f_ref{type} eq "DIR" ) { if ( $verify ) { &log (" need to create $f_test{display_name} \n"); next HOSTDIR; }elsif ( $noexe ) { &log (" need to create $f_test{display_name} \n"); }else{ &log (" creating $f_test{display_name} \n"); mkdir($f_test{name}, 666) || &gripe( "Failed to create directory $f_test{display_name} ($!)\n"); $dupdate ++; if ( -d $f_test{name} ) { $f_test{type} = "DIR" ; } } } elsif ( $f_ref{type} eq "FILE" ) { if ( $verify ) { &log (" need to create $f_test{display_name} \n"); next HOSTDIR; }else{ $create = 1; } } } unless ( $f_test{type} || $create || $noexe ) { &gripe( "Failed to create $f_test{display_name}\n"); &gripe( "Type : $f_test{type} \n"); &gripe( "create: $create \n"); &gripe( "update: $update \n"); exit 1; } #============================================================== # Check Native File System (FAT, NTFS, NFS, Samba, etc) fileparse_set_fstype("MSWin32"); $f_ref{dir} = dirname($f_ref{name}); $f_test{dir} = dirname($f_test{name}); Win32::SetCwd($f_ref{dir}); $f_ref{fstype}=Win32::FsType(); Win32::SetCwd($f_test{dir}); $f_test{fstype}=Win32::FsType(); if ($debug & 16) { &log( "File System type:\n"); &log(sprintf " %s : %s\n",$f_ref{display_name},$f_ref{fstype}); &log(sprintf " %s : %s\n",$f_test{display_name},$f_test{fstype}); } #============================================================== # Read file size and time stamps ($f_ref{size} ,$f_ref{ctime} ,$f_ref{mtime} ,$f_ref{atime} ) = &fstat($f_ref{name}); ($f_test{size},$f_test{ctime},$f_test{mtime},$f_test{atime}) = &fstat($f_test{name}); #$kbtotal += $f_ref{size}; #======================================================================= # Update file if sizes differ if ( $f_ref{type} eq "FILE" ) { $kbtotal += $f_ref{size}; if ( ( $f_ref{mtime} < $f_test{mtime} ) & ( $datecheck == 1) ) { # Log remote copy is newer - and prevent update &log( " remote copy $f_test{display_name} is newer ! \n"); &log( " $f_ref{display_name} = $f_ref{mtime}\n"); &log( " $f_test{display_name} = $f_test{mtime}\n"); }else{ if ( ( ($f_ref{mtime} - $f_test{mtime}) > $fat_timestamp_tol ) & ( ($f_ref{fstype} eq "FAT" ) | ($f_test{fstype} eq "FAT" ) ) ) { # Log source copy is newer &log( " source copy $f_ref{display_name} is newer!\n") if $debug; if ( $verify ) { &log (" update $f_test{display_name} \n" ); }else{ $update = 1; } }elsif ( $f_ref{mtime} > $f_test{mtime} ) { # Update file if sizes differ or reference file modified more recently if ( $verify ) { &log (" update $f_test{display_name}\n"); }else{ $update = 1; } }elsif ( $f_ref{size} != $f_test{size} ) { # Update file if sizes differ or reference file modified more recently if ( $verify ) { &log (" update $f_test{display_name}\n"); }else{ $update = 1; } } } # This is where we install or update the file if ($update || $create) { &_cleardots; if ($noexe || $verify) { if ($create) { &log( " install $f_test{display_name}\n"); }elsif ($update) { &log( " update $f_test{display_name}\n") } }else{ undef $msg; if ( ! -r $f_ref{name} ) { $msg = " - unable to read $f_ref{display_name} - \n"; } elsif ( ( -e $f_test{name} ) & ( ! -w $f_test{name} ) ) { $msg = " - unable to write $f_test{display_name} - "; } else{ if ($create) { &log( " installing $f_test{display_name}\n"); }elsif ($update) { &log( " updating $f_test{display_name}\n") } if ( copy($f_ref{name}, $f_test{name}) ) { $kbupdate += $f_ref{size}; $fupdate ++; if ( $f_test{atime} < $f_ref{mtime} ) { $f_test{atime} = $f_ref{mtime} } utime($f_test{atime},$f_ref{mtime},$f_test{name}) || &gripe ( "Failed to set modification time on $f_test{display_name}\n"); }else{ &gripe( "Failed to copy file $f_test{display_name} ($?)\n"); } } if ( $msg ) { if ($create) { &log( "$msg unable to install $f_test{display_name}\n"); }elsif ($update) { &log( "$msg unable to update $f_test{display_name}\n") } } } } else { &_printdot; } } #============================================================== # Test file attributes (using Win32::File module) # Read Only = 1 # Hidden = 2 # = 4 # = 8 # = 16 # Archive = 32 # Normal =128 # Directory = ?? # System = ?? # Compressed=2048 Win32::File::GetAttributes($f_ref{name}, $f_ref{attrib}); #$f_ref{attrib} = $attrib; if ( -e $f_test{name} ) { Win32::File::GetAttributes($f_test{name}, $f_test{attrib}); }else{ undef $f_test{attrib}; # Use $f_test{attrib} to identify file exists } #$f_test{attrib} = $attrib; if ($debug & 4) { &log("\nAttribute value for $f_ref{display_name}: $f_ref{attrib}\n"); &print_attrib($f_ref{attrib}); if ( $f_test{attrib} ) { &log("\nAttribute value for $f_test{name}: $f_test{attrib}\n"); &print_attrib($f_test{attrib}); } } # Only if $f_test{name} exists can we try and set its attributes # if ( $f_test{attrib} ) { # As we can't set the file to be compressed mask out the compress attribute if ( $f_test{fstype} eq "Samba" ) { $attribmask = 1; # Samba File System can only set RO bit }else{ $attribmask = 2047; } if ( ( ($f_ref{attrib} & $attribmask) != ($f_test{attrib} & $attribmask) ) ) { &_cleardots; if ( $f_test{attrib} < 0 ){ if ( $verify ){ ; }elsif ( $noexe ) { &log( " check/update attributes of $f_test{display_name}\n"); }else{ # attribute of -1 occurs when directory deleted is held open !!!! &log( " - unable to set attributes for $f_test{display_name} - file open, locked or does not exist\n"); } }else{ if ( $noexe ) { &log( " update attributes of $f_test{display_name}\n"); }elsif ( ! $verify) { &log( " updating attributes on $f_test{display_name}\n"); Win32::File::SetAttributes($f_test{name},($f_ref{attrib} & 2047))|| &gripe (" - failed to set attributes on $f_test{display_name}\n"); Win32::File::GetAttributes($f_test{name}, $attrib); $f_test{attrib} = $attrib; if ($debug & 4) { &log("\nAttribute value for $f_test{display_name}: $f_test{attrib}\n"); &print_attrib($f_test{attrib}); } } } } #============================================================== # Gets the rights for all files listed on the command line. # Only if both files are on NTFS..... if ( ( $f_ref{fstype} eq "NTFS" ) & ( $f_test{fstype} eq "NTFS" ) & ( $ntfsignore == 0 ) ) { if ( ( $f_test{attrib} >= 0 ) & ( ! $verify) ) { %f_refperms = &readntfs($f_ref{name}); %f_testperms = &readntfs($f_test{name}); #============================================================== # Verify both files have same number of user names identified # - and if not then quit while ( ($name,$mask) = each %f_refperms) { next if ( $mask == $f_testperms{$name} ); $ntfschange = 1; $f_testperms{$name} = $mask; } while ( ($name,$mask) = each %f_testperms) { next if ($f_refperms{$name}); $ntfschange = 1; undef $f_testperms{$name}; } if ($ntfschange) { if ($noexe) { &_cleardots; &log( " update NTFS permissions on $f_test{display_name}\n"); } elsif ( $ntfsupdate != 1 ) { if ( $debug ) { &_cleardots; &log( " NTFS permissions on $f_test{display_name} require update - configured to make no change\n"); } }else{ &_cleardots; &log( " updating NTFS permissions on $f_test{display_name}\n"); Win32::FileSecurity::Set( $f_test{name}, \%f_testperms ) || &gripe (" - failed to update NTFS permissions\n"); } } }elsif ( ( $f_test{attrib} < 0 ) & $noexe ) { &log (" check/update NTFS permissions of $f_test{display_name}\n"); } } } } # End of HOSTDIR loop }else{ warn ("Failed to Set Cwd to $cwd\n "); } $srcmap=unmapdrive($cwddrive,$srcmap); } if ( $stats ) { $secs = time - $targ_start_time; $mins = int ( $secs/60 ); $secs -= $mins * 60; $hours = int ( $mins/60 ); $mins -= $hours * 60; $percent = 100*$kbupdate/$kbtotal if ( $kbtotal > 0 ); $kbupdate = &commas($kbupdate); $kbtotal = &commas($kbtotal); &log("\nUpdated $target with \n"); &log(" $kbupdate of a total $kbtotal bytes"); &log(sprintf "( %04.2f %% )\n",$percent); &log(" $fupdate of $ftotal files installed/updated\n"); &log(" $fremove files deleted\n"); &log(" $dupdate of $dtotal directories created\n"); &log(" $dremove directories deleted\n"); &log( sprintf " in %02d:%02d:%02d \n",$hours,$mins,$secs ); } $targmap=unmapdrive($hostdrive,$targmap); } # End of TARGET loop &log(" All hosts updated at " . &datetime . "\n"); $secs = time - $start_time; $mins = int ( $secs/60 ); $secs -= $mins * 60; $hours = int ( $mins/60 ); $mins -= $hours * 60; &log( sprintf " total elapsed time %02d:%02d:%02d \n",$hours,$mins,$secs ); # Send notifications if ( @notify ) { ¬ify; } &StopLog; #===================================================================== } exit 0; #===================================================================== sub notify { # Compile message my($from) = 'rdist'; my($reply) = 'Test Mail'; my($smtp) = $ENV{'MAILRELAY'}; my($subject) = "files updated by win32rdist to " . $variable{$targets}; my($message); open(READLOG,$log)||warn(" unable to open log file $log for notification\n"); # open(DEBUGLOG,">C:/rdistdebug.log")||warn(" unable to open debug log\n"); while () { $message .= $_; # printf DEBUGLOG $_; } # close(DEBUGLOG); close(READLOG); open (LOG,">>$log")|| gripe("Unable to open log file $log"); $mno = 0; foreach $to (@notify){ $mno ++; &log("\nnotify $to\n"); $message .= "\nnotify $to\n"; $status=sendmail($from, $reply, $to, $smtp, $subject, $message ); if ( $status == -1 ) { &gripe(" SMTP host ($smtp) unknown\n"); }elsif ( $status == -2) { &gripe(" socket() failed\n"); }elsif ( $status == -3) { &gripe(" connect() failed\n"); }elsif ( $status == -4) { &gripe(" service not available\n"); }elsif ( $status == -5) { &gripe(" unspecified communication error\n"); }elsif ( $status == -6) { &gripe(" local user ($to) unknown on SMTP host ($smtp)\n"); }elsif ( $status == -7) { &gripe(" transmission of message failed\n"); }elsif ( $status == -8) { &gripe(" argument \$to ($to) empty\n"); } } } #===================================================================== sub cleanup { &log("\nprocess interrupted - aborting now\n"); if ( $cwddrive & $srcmap ) { &log("\nDeleting the $cwddrive drive mapping\n",$0) if $debug; # Delete the mapping Win32::NetResource::CancelConnection($cwddrive,0,1); undef $srcmap; } if ($target_drive eq $hostdrive ) { &log("\nDeleting the $target_drive drive mapping\n",$0) if $debug; # Delete the mapping Win32::NetResource::CancelConnection($target_drive,0,1); undef $targmap; } &StopLog; ¬ify; } #===================================================================== sub deltree{ my($root)=@_; find(\&findfile,$root); find(\&finddir,$root); print "verify: $verify noexe: $noexe\n"; while ( @dirlist ) { $dir = pop(@dirlist); if ( $noexe ) { &log( " remove $dir\n"); }elsif ( $verify ) { &log( " remove $dir\n"); }else{ if ( rmdir($dir) ) { &log( " removed $dir\n"); $dremove ++; }else{ &gripe(" - unable to remove $dir\n"); } } } } #===================================================================== sub StartLog { my($logfile) = @_; open (LOG,">$logfile")|| die("Unable to open &log file $logfile"); } #===================================================================== sub StopLog { close LOG; } #===================================================================== sub log { my( $message ) = @_; local($oldfh) = select(LOG); print $message; $| = 1; select($oldfh); print $message unless $quiet; } #===================================================================== sub gripe { my( $message ) = @_; local($oldfh) = select(LOG); print $message; $| = 1; select($oldfh); warn($message); } #===================================================================== sub td { my($td) = @_; my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $td; $month = (January,February,March,April,May,June,July,August,September,October,November,December) [$mon]; return sprintf "%02d %s %4d %02d:%02d:%02d", $mday, $month, ($year+1900), $hour,$min,$sec; } #===================================================================== sub findfile { if ( -f ) { if ($noexe) { &log( " remove $File::Find::name\n"); }elsif ( $verify ) { &log(" remove $File::Find::name\n"); }else{ &_dfile($File::Find::name,$File::Find::name); } } } #===================================================================== sub _dfile { my($name,$display) =@_; if ( unlink $name ) { &log( " deleted $display\n"); $fremove ++; return 0; }else{ &gripe (" - unable to delete $display \n"); return 1; } } #===================================================================== sub finddir { if ( -d ) { push (@dirlist,$File::Find::name); } } #===================================================================== sub fstat{ my($filename) = @_; my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename); if ($debug & 32) { &log( "\nstat result for $filename\n"); &log( " dev : $dev\n"); &log( " ino : $ino\n"); &log( " mode : $mode\n"); &log( " nlink : $nlink\n"); &log( " uid : $uid\n"); &log( " gid : $gid\n"); &log( " rdev : $rdev\n"); &log( " size : $size\n"); &log( " ctime : " . &td($ctime) . "\n"); &log( " mtime : " . &td($mtime) . "\n"); &log( " atime : " . &td($atime) . "\n"); &log( " blksize: $blksize\n"); &log( " blocks : $blocks\n"); } return $size,$ctime,$mtime,$atime; } #===================================================================== sub print_attrib{ my ($attrib) = @_; if ( $attrib < 0 ) { &log( " UNKNOWN - PROBABLY OPEN OR DOES NOT EXIST \n"); }else{ &log( " READ ONLY \n") if $attrib & 1; &log( " HIDDEN \n") if $attrib & 2; &log( " SYSTEM \n") if $attrib & 4; &log( " VVVV \n") if $attrib & 8; &log( " DIRECTORY \n") if $attrib & 16; &log( " ARCHIVE \n") if $attrib & 32; &log( " WWWW \n") if $attrib & 64; &log( " NORMAL \n") if $attrib & 128; &log( " XXXX \n") if $attrib & 256; &log( " YYYY \n") if $attrib & 512; &log( " ZZZZ \n") if $attrib & 1024; &log( " COMPRESSED\n") if $attrib & 2048; } } #===================================================================== sub readntfs { my($filename) = @_; my(@happy); if ( Win32::FileSecurity::Get( $filename, \%perms ) ) { if ($debug & 8) { &log( "\n\nNTFS permissions for $filename\n"); while( ($name, $mask) = each %perms ) { &log( "\n $name:\n "); EnumerateRights( $mask, \@happy ) ; &log( join( "\n ", @happy ), "\n"); } &log("\n"); } }else{ &gripe( "Error #", int( $! ), ": $!" ) ; } return %perms; } #======================================================================= # Interpret Command Line Switches sub args { PARSE_ARGS: while( $_[ 0 ] =~ /^-/ ){ local( $arg ) = shift @_; if( $arg eq '-b' ){ # Performs a binary comparison and updates files if they differ. $binary = 1; next PARSE_ARGS; } if( $arg eq '-c' ){ # Directs the rdist command to interpret the remaining arguments # as a small distribution file. $distfile = @_; last; } if( $arg =~ /^-D([0-9]*)$/ ){ # Turns on the debugging output. if (defined $1) { $debug = $1; }else{ $debug = 1; } next PARSE_ARGS; } if( $arg =~ /^-f(.*)$/ ){ # Specifies the name of the distribution file. # the next arg is the distfile to get $distfile = $1; if( ! $distfile ){ # Must be -f space arg $distfile = shift @_; } next PARSE_ARGS; } if( $arg eq '-h' ){ # Copies the file that the link points to rather than the link itself. # This file type is invalid on Win32 systems and therefore this switch # has no function last; } if( $arg eq '-i' ){ # Ignores unresolved links. The rdist command maintains the link # structure of files being transferred and warns users if it cannot # find all the links. # This file type is invalid on Win32 systems and therefore this switch # has no function last; } if( $arg eq '-I' ){ # Ignores NTFS permission testing - useful when Read access only is available. $ntfsignore = 1; next PARSE_ARGS; } if( $arg =~ /^-l(.*)$/ ){ # Identify the log file to use - rather than the default. This is # deleted after the process is completed. $log = $1; $logset = 1; if( ! $log ){ # Must be -l space arg $log = shift @_; } next PARSE_ARGS; } if( $arg =~ /^-m(.*)$/ ){ # -m Host Limits which machines are to be updated. You can use the -m # Host option multiple times to limit updates to a subset of the hosts # listed in the distfile file. local( $hostlimit ) = $1; if( ! $hostlimit ){ # Must be -m space arg $hostlimit = shift @_; } push @hostlimit, $hostlimit; next PARSE_ARGS; } if( $arg eq '-n' ){ # Prints the subcommands without executing them. Use the -n flag # to debug the distfile file. $noexe = 1; next PARSE_ARGS; } if( $arg eq '-N' ){ # Update NTFS attributes (ownership/permissions) on destination # to debug the distfile file. $ntfsupdate = 1; next PARSE_ARGS; } if( $arg eq '-q' ){ # Operates in quiet mode. The -q option suppresses printing of modified # files on standard output. $quiet = 1; next PARSE_ARGS; } if( $arg eq '-R' ){ # Removes extraneous files. If a directory is being updated, any # files that exist on the remote host but not in the master directory # are removed. Use the -R flag to maintain identical copies of directories. $rmextra = 1; next PARSE_ARGS; } if( $arg eq '-s' ){ # Print statistics evaluated for each host ( qty data updated, files, dirs etc.) $stats = 1; next PARSE_ARGS; } if( $arg eq '-v' ){ # Verifies that the files are up-to-date on all hosts; files that # are out-of-date are then displayed. However, the rdist -v command # neither changes files nor sends mail. $verify = 1; next PARSE_ARGS; } if( $arg eq '-w' ){ # Appends the entire path name of the file to the destination directory # name. Normally, the rdist command uses only the last component of # a name for renaming files, preserving the directory structure of the # copied files. # # When the -w flag is used with a file name that begins with a ~ (tilde), # everything except the home directory is appended to the destination # name. File names that do not begin with a / (slash) or a ~ (tilde) # use the destination user's home directory as the root directory for # the rest of the file name. $appendpath = 1; next PARSE_ARGS; } if( $arg eq '-y' ){ # Prevents recent copies of files from being replaced by files that # are not as recent. Files are normally updated when their time stamp # and size differ. The -y flag prevents the rdist command from updating # files more recent than the master file. $datecheck = 1; next PARSE_ARGS; } gripe(" unknown arg $arg, skipping\n"); } # Switches not processed........................................ # #-d Argument=Value Defines the Argument variable as having the value #specified by the Value variable. The -d flag defines or overrides #variable definitions in the distfile file. The Value variable can #be specified as an empty string, one name, or a list of names surrounded #by parentheses and separated by tabs or spaces. # } #======================================================================= # Read the distribution file sub ReadDistfile { open (DIST, "@_") || die "Unable to open @_"; undef $getvar; undef @temp; while () { chop; #-------------------------------------------------------------- # Clean out unwanted characters # # Loose comments in distfile next if /^([\s]*)#/; s/#.*//g; # Loose leading and trailing white space s/^\s*//g; s/\s*$//g; #-------------------------------------------------------------- # Determine variables # if ( $getvar ) { if ( /([^\)]*)\)/ ) { push (@temp,$1); $variable{$getvar} = join('|',@temp); #print " Consolidated $getvar : $variable{$getvar}\n"; undef $getvar; undef @temp; next; } push (@temp,$_); next; } if ( /(\w+)\s*=\s*\(\s*(\w*)/ ) { # print "variable : $1\n"; # print "first word : $2\n"; @temp = $2 if $2; $getvar = $1; next; } #-------------------------------------------------------------- # Determine rdist instructions # if ($cseq) { $commd .= $_; } if ( /\$\{(\w+)\}\s*-\>\s*\$\{(\w+)\}\s*(.*)$/ ) { $files = $1; $targets = $2; $commd = $3; $cseq = 1; &log(" Found instruction\n") if ($debug & 2); } # Interpret command switch if ( $cseq && $commd =~ /;$/ ) { $commd =~ s/;$//g; if ($debug & 2 ) { &log("Command : $commd\n"); &log("files : $files\n"); &log("hosts : $targets\n"); } undef $cseq; $commd =~ s/install//g; #$commd =~ s/(\S+)/"$1"/g; $commd =~ s/\s+/ /g; $commd =~ s/^\s+//g; $commd =~ s/\s+$//g; #$commd =~ s/ /,/g; &args(split(/ /,$commd)); } #-------------------------------------------------------------- # Process any special instructions here if ($spcl) { $commd .= "\n"; $commd .= $_; } if ( /special\s+(\S*)\s+(\S*)/ ) { $file = $1; $commd = $2; $spcl = 1; #print "Found special\n"; #print " file : $file\n"; #print " command : $commd\n"; } if ( $spcl && $commd =~ /"\s*;\s*$/ ) { $commd =~ s/;$//g; print "===============================Special===============================\n"; print "Command : $commd\n"; print "file : $file\n"; print "=====================================================================\n"; $special{$file} = $commd; undef $spcl; } #-------------------------------------------------------------- # Process any notify instructions here if ($ntfy) { $addr .= $_; } if ( /notify\s+(\S*)/ ) { $addr = $1; $ntfy = 1; } if ( $ntfy && $addr =~ /;\s*$/ ) { $addr =~ s/;$//g; push (@notify,$addr); undef $ntfy; } #-------------------------------------------------------------- # Process except entries if ( /except\s+(\S*)\s*;\s*$/ ) { my $pattern = $1; if ( $pattern =~ /(\S+)\$\{(\S+)\}/ ) { if ( defined $variable{$2} ) { foreach $p1 (split (/ /,$variable{$2}) ) { push @exceptions, $k . $p1; } }else{ &log(" invalid exception argument $pattern, {$2} does not exist\n"); } }else{ push @exceptions, $pattern; } } #-------------------------------------------------------------- # Process except_pat entries if ( /except_pat\s+\(\s*(.*)\s*\)\s*;\s*$/ ) { foreach (split(/\s+/,$1)) { push @except_pats, $_; } } } close DIST; } #------------------------------------------------------------ # sub sendmail() # # send/fake email around the world ... # # Version : 1.21 # Environment: Hip Perl Build 105 NT 3.51 Server SP4 # Environment: Hip Perl Build 110 NT 4.00 # # arguments: # # $from email address of sender # $reply email address for replying mails # $to email address of reciever # (multiple recievers can be given separated with space) # $smtp name of smtp server (name or IP) # $subject subject line # $message (multiline) message # # return codes: # # 1 success # -1 $smtphost unknown # -2 socket() failed # -3 connect() failed # -4 service not available # -5 unspecified communication error # -6 local user $to unknown on host $smtp # -7 transmission of message failed # -8 argument $to empty # # usage examples: # # print # sendmail("Alice ", # "alice\@company.com", # "joe\@agency.com charlie\@agency.com", # $smtp, $subject, $message ); # # or # # print # sendmail($from, $reply, $to, $smtp, $subject, $message ); # # (sub changes $_) # #------------------------------------------------------------ sub sendmail { my ($from, $reply, $to, $smtp, $subject, $message) = @_; my ($fromaddr) = $from; my ($replyaddr) = $reply; $to =~ s/[ \t]+/, /g; # pack spaces and add comma $fromaddr =~ s/.*<([^\s]*?)>/$1/; # get from email address $replyaddr =~ s/.*<([^\s]*?)>/$1/; # get reply email address $replyaddr =~ s/^([^\s]+).*/$1/; # use first address $message =~ s/^\./\.\./gm; # handle . as first character $message =~ s/\r\n/\n/g; # handle line ending $message =~ s/\n/\r\n/g; $smtp =~ s/^\s+//g; # remove spaces around $smtp $smtp =~ s/\s+$//g; if (!$to) { return -8; } my($proto) = (getprotobyname('tcp'))[2]; my($port) = (getservbyname('smtp', 'tcp'))[2]; my($smtpaddr) = ($smtp =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) ? pack('C4',$1,$2,$3,$4) : (gethostbyname($smtp))[4]; if (!defined($smtpaddr)) { return -1; } if (!socket(S, AF_INET, SOCK_STREAM, $proto)) { return -2; } if (!connect(S, pack('Sna4x8', AF_INET, $port, $smtpaddr))) { return -3; } my($oldfh) = select(S); $| = 1; select($oldfh); $_ = ; if (/^[45]/) { close S; return -4; } print S "helo localhost\r\n"; $_ = ; if (/^[45]/) { close S; return -5; } print S "mail from: <$fromaddr>\r\n"; $_ = ; if (/^[45]/) { close S; return -5; } foreach (split(/, /, $to)) { print S "rcpt to: <$_>\r\n"; $_ = ; if (/^[45]/) { close S; return -6; } } print S "data\r\n"; $_ = ; if (/^[45]/) { close S; return -5; } print S "To: $to\r\n"; print S "From: $from\r\n"; print S "Reply-to: $replyaddr\r\n" if $replyaddr; print S "X-Mailer: Perl Sendmail Version 1.21 Christian Mallwitz Germany\r\n"; print S "Subject: $subject\r\n\r\n"; print S "$message"; print S "\r\n.\r\n"; $_ = ; if (/^[45]/) { close S; return -7; } print S "quit\r\n"; $_ = ; close S; return 1; } #===================================================================== sub mapdrive { my ($unc) = @_; my ($debug) = 0; my ($target,$share,$path,$share,%unc,$drive); if( $unc =~ /(\\\\|\/\/)([a-zA-Z0-9]+)(\\|\/)([a-zA-Z0-9]+[\$]?)(.*)$/ ) { # If Share is specified # Make Share Connection (if required) # - Return to TARGET on failure $target= $2; $share= $4; $path = $5; $path = '/' . $path; $path =~ s/\\/\//g; # Substitute all back slashes for forward slashes $path =~ s/\/\//\//g; # Substitute double forward slashes for single if ( $debug > 0 ) { &log("Verifying host connetion\n"); &log(" Host : $target\n"); &log(" Share : $share\n"); &log(" Path : $path\n"); } $share = '\\\\' . $target . '\\' . $share; $share =~ tr/a-z/A-Z/; # Determine UNC's already mapped to drives undef %unc; open (NETUSE, "net use|"); while () { chop; if ( /(OK|Disconnected)\s+([a-zA-Z]:)\s+(\S+)\s+.*/ ) { $unc = $3; $unc =~ tr/a-z/A-Z/; $unc{$unc} = $2; } } close NETUSE; if ( $unc{$share} ) { # Identify target drive - if its already mapped to the UNC $drive = $unc{$share}; }else{ # Add the new share - but only if we need to $drive = Win32::GetNextAvailDrive();# Temp drive for URL destination mapping &log(" mapdrive: Adding Drive $target_drive to share $share\n") if $debug; # call net use to add the drive/share if ( system "net use $drive $share /persistent:no" ) { &gripe(" mapdrive: Failed to map drive $drive to UNC $share ($!)\n",$0); return 1; }else{ &log(" mapdrive: Added Drive mapping OK\n") if $debug; } } return $drive,$path,$share; }else{ &gripe (" mapdrive: Invalid UNC supplied ($unc)\n"); return 1; } #===================================================================== } sub _cleardots { unless ( $dotposn == 0 ) { printf "\n"; $dotposn = 0; } } #===================================================================== sub _printdot { printf "."; $dotposn ++; if ( $dotposn > 77 ) { $dotposn = 0; printf "\n"; } } #===================================================================== sub commas { local($_) = @_; 1 while s/(.*\d)(\d\d\d)/$1,$2/; $_; } #===================================================================== # sub datetime { my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); my($month) = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')[$mon]; my($datetime) = sprintf "%2d.%02d.%02d %2d %s %02d", $hour,$min,$sec,$mday,$month,($year+0); return $datetime; } #===================================================================== sub unmapdrive { my($drive,$flag) = @_; if ( $drive && $flag ) { Win32::SetCwd($ENV{'SystemDrive'}) || &gripe("Failed to set CWD to " . $ENV{'SystemDrive'} . "\n",$0); &log("\nDeleting the $drive drive mapping\n") if $debug; if ( Win32::NetResource::CancelConnection($drive,0,1) ) { return undef; }else{ &gripe("\n Failed to Delete the $drive drive mapping\n",$0); return 1; } } } #===================================================================== 1; #===================================================================== __END__ =head1 NAME win32rdist - A Win32 based script that will maintain file structures between various file systems and servers. This is loosely based on the unix rdist utility. =head1 README This is the README file for the win32rdist script, a MSWin32 utility that attempts to mimic the unix based rdist utility. This is version 0.13 of win32rdist. This version is still considered alpha software. It has only been tested on Windows NT 4.0 (SP3 and SP4) with perl 5.005_02 - based on Activeware 507 and 509 releases. It has had limited testing on NTFS, FAT and Samba file systems. AVAILABILITY The latest version of the win32rdist script is available from the Comprehensive Perl Archive Network (CPAN), where it can be found in the Win32\Utilities directory. INSTALLATION Create an environment variable MAILRELAY, and set this to the local internet mail relay host name. This will be used if any notifications are to be sent. Should be ready to use wherever you save it on your MSWin32 machine SUPPORT You can send bug reports and suggestions for improvements on this module to me at . However, I can't promise to offer any other support for this script. COPYRIGHT This module is Copyright © 1999 Dave Roberts. All rights reserved. This script is free software; you can redistribute it and/or modify it under the same terms as Perl itself. This script is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The copyright holder of this script can not be held liable for any general, special, incidental or consequential damages arising out of the use of the script. CHANGE HISTORY =item 15 Jan 1999 - beefed up the README section =item 16 Jan 1999 - provided -l option for specifing logfile =item 18 Jan 1999 - modified creation logic for logfile location, using SystemDrive variable if TMP and TEMP not specified (typically when the NT schedule service is used to call the script). This fixes problems with the notify routine when logfile is not clearly specified. - Identified the MAILRELAY environment variable to contain the internet mail relay host used. - Modified output of UNC defined file structures to display UNC name (not the drive letter mapped to the UNC). =item 22 Jan 1999 - Fixed bug in display of UNC name (was dropping last character of the share name). - Changed print of log file location to appear only in debug mode. - Changed file timestamp tolerance variable from $tolerance to $fat_timestamp_tol, and code so that it only applies when comparisons to FAT based filesystems are made. - Added "TO DO" section to README - Set release version to 0.12 dated 22 JaN 1999 =item 25 Jan 1999 - Tidied documentation a little more. - Modified stats collection to prevent size counts being made when file copy fails. - Fixed bug in stats data collection count - @srcfiles was not being initialized correctly. - Changed indentation of error messages - to standardize and aid visibility =item 28 Jan 1999 - Changed notification message subject - to be similar to that used in unix utility. =item 6 Feb 1999 - Single files deleted are now displayed in a URL format. This does not affect files deleted when a directory tree deletion is in progress. - Modified calls to Win32::File::GetAttributes - Only trys to set file attributes or NTFS permissions if the destination file exists - should prevent "Failed to copy..." error then tries to set attributes - Fixed bug to correct counter for number of files deleted. - Corrected notify mail header. - Set release version to 0.13 =item 22 July 1999 - The mapdrive subroutine now ignores case - preventing the adding of duplicate shares. - Improved logging information for deleting of shares. - set release version to 0.14 =item 23 July 1999 - Fixed bug in deletion of shares - Set release version to 0.14a =item 26 July 1999 - set release to 0.15 - Added datetime subroutine - Added date/time timestamps to log file - Moved the disconnect of the $hostdrive - to outside of the TARGET loop (this was a bug)... =item 29 July 1999 - set release to 0.16 - Added unmapdrive subroutine, and corrected bitwise to logical AND statement in the drive mapping test for this logic. Added Win32::SetCwd to the unmapdrive to prevent unmapping the CWD. - moved both unmapdrive subroutines to correct locations (were incorrectly placed in loops. TO DO - ignore blank/null entries for $variable{$targets} and $variable{$files} and probably to generate error messages for unrecognized entries. - deltree subroutine needs change to display URL's rather than file names. - change file to allow pb.pl to work without indent errors - Improve the documentation. There are still some features that are not coded or tested. =head1 PREREQUISITES This script requires the following modules C C C C C C C C C C =pod OSNAMES MSWin32 =pod SCRIPT CATEGORIES Win32/Utilities =head1 DESCRIPTION Distributes identical copies of files on multiple hosts. Syntax To Use a Distribution File win32rdist [ -n ] [ -q ] [ -b ] [ -D ] [ -R ] [ -h ] [ -i ] [ -v ] [ -w ] [ -y ] [ -f FileName ] [ -d Argument=Value ] [ -m Host ] ... [ Name ] ... To Interpret Arguments as a Small Distribution File win32rdist [ -n ] [ -q ] [ -b ] [ -D ] [ -R ] [ -h ] [ -i ] [ -v ] [ -w ] [ -y ] -c Name ... [ Login@ ] Host [ Destination ] =head1 DESCRIPTION The win32rdist command maintains identical copies of files on multiple hosts. The win32rdist command preserves the DOS style attributes and modified time of files. It can optionally update NTFS style permissions. The win32rdist command can receive direction from the following sources: * A distribution file specified by the -f flag. * Command-line arguments that augment or override variable definitions in the distribution file. * Command-line arguments that serve as a small distribution file. The value specified by the Name parameter is read as the name of a file to be updated or a subcommand to execute. If you do not specify a value for the Name parameter on the command line, the win32rdist command updates all the files and directories listed in the distribution file. If you specify - (minus sign) for the Name parameter, the win32rdist command uses standard input. If the name of a file specified by the Name parameter is the same as the name of a subcommand, the win32rdist command interprets the Name parameter as a subcommand. The win32rdist command requires that a the owner of the win32rdist process has permission to establish connections to both source and destination file systems and has rights to create files and change permissions. head2= Flags -b Performs a binary comparison and updates files if they differ. -c Directs the win32rdist command to interpret the remaining arguments as a small distribution file. Available arguments are: Name Specifies single name or list of names separated by blanks. The value can be either a file or a subcommand. [Login@]Host Specifies the machine to be updated and, optionally, the login name to be notified of the update. The Login option is not yet implemented. Host can be specified as an existinf file share (H:) or as a machine name (\\desthost\). Destination Specifies a file on the remote machine if a single name is specified in the Name argument; specifies a directory if more than one name is specified. Note: Do not use the -c flag with the -f, -d, or -m flag. -d Argument=Value Defines the Argument variable as having the value specified by the Value variable. The -d flag defines or overrides variable definitions in the distfile file. The Value variable can be specified as an empty string, one name, or a list of names surrounded by parentheses and separated by tabs or spaces. -D Turns on the debugging output. This can also be used as -D#, where # is a number that specifies additional debugging information. Values are 1 normal debugging information 2 display configuration settings (taken from both command line and distribution file) 4 display Win32 style attributes 8 display NTFS permissions 16 display source and target file system types (FAT, NTFS, Samba etc) 32 display stat results 64 disply files in source tree(s) 128 display win32rdist file variables 256 display exception files These values may be combined. -f FileName Specifies the name of the distribution file. If you do not use the -f flag, the default value is the distfile or Distfile file in your $HOME directory. -h Copies the file that the link points to rather than the link itself. This file type is invalid on Win32 systems and therefore this switch has no function -i Ignores unresolved links. The win32rdist command maintains the link structure of files being transferred and warns users if it cannot find all the links.This file type is invalid on Win32 systems and therefore this switch has no function. -I Prevents NTFS permissions from being read - useful when only limited privilege is available to NTFS drive. -l FileName Specifies the name of the process log file. If you do not use the -l flag, the default value is $TMP\rdist.log, or if $TMP is not defined $TEMP\rdist.log (note if executed from the schedular $TMP and $TEMP may not be available). -m Host Limits which machines are to be updated. You can use the -m Host option multiple times to limit updates to a subset of the hosts listed in the distfile file. -n Prints the subcommands without executing them. Use the -n flag to debug the distfile file. -N Update NTFS attributes (ownership and permissions). Take care using this when transferring files across NT Domains. -q Operates in quiet mode. The -q option suppresses printing of modified files on standard output. -R Removes extraneous files. If a directory is being updated, any files that exist on the remote host but not in the master directory are removed. Use the -R flag to maintain identical copies of directories. -s Print statistics after processing each host. -v Verifies that the files are up-to-date on all hosts; files that are out-of-date are then displayed. However, the win32rdist -v command neither changes files nor sends mail. -w Appends the entire path name of the file to the destination directory name. Normally, the win32rdist command uses only the last component of a name for renaming files, preserving the directory structure of the copied files. This option allows the directory structure (including any share specified) to be included in the new file structure. -y Prevents recent copies of files from being replaced by files that are not as recent. Files are normally updated when their time stamp and size differ. The -y flag prevents the win32rdist command from updating files more recent than the master file. =head2 DESCRIPTION FILE Distribution File (distfile File) The distribution file specifies the files to copy, destination hosts for distribution, and operations to perform when updating files to be distributed with the win32rdist command. Normally, the win32rdist command uses the distfile file in your $HOME directory. You can specify a different file If you use the -f flag. Entry Formats Each entry in the distribution file has one of the following formats: VariableName = NameList Defines variables used in other entries of the distribution file (SourceList, DestinationList, or SubcommandList). [Label:] SourceList -> DestinationList SubcommandList Directs the win32rdist command to distribute files named in the SourceList variable to hosts named in the DestinationList variable. Distribution file commands perform additional functions. [Label:] SourceList :: TimeStampFile SubcommandList Directs the win32rdist command to update files that have changed since a given date. Distribution file subcommands perform additional functions. Each file specified with the SourceList variable is updated if the file is newer than the time-stamp file. This format is useful for restoring files. Labels are optional and used to identify a subcommand for partial updates. Entries VariableName Identifies the variable used in the distribution file. NameList Specifies a list of files and directories, hosts, or subcommands. SourceList Specifies files and directories on the local host for the win32rdist command to use as the master copy for distribution. DestinationList Indicates hosts to receive copies of the files. SubcommandList Lists distribution file subcommands to be executed. The win32rdist command treats new-line characters, tabs, and blanks as separators. Distribution file variables for expansion begin with a $ (dollar sign) followed by a single character or a name enclosed in {} (braces). Comments begin with a # (pound sign) and end with a new-line character. Source and Destination List Format The distribution file source and destination lists comprise zero or more names separated by blanks, as shown in the following format: [Name1] [Name2] [Name3] ... The win32rdist command recognizes and expands the following shell metacharacters on the local host in the same way as for the csh command. * [ (left bracket) * ] (right bracket) * { (left brace) * } (right brace) * ( (left parenthesis) * ) (right parenthesis) * * (asterisk) * ? (question mark) To prevent these characters from being expanded, precede them with a \ (backslash). Distribution File Subcommands Multiple commands to the shell must be separated by a ; (semicolon). Commands are executed in the user's home directory on the host being updated. The special subcommand can be used to rebuild private databases after a program has been updated. The distribution file subcommand list may contain zero or more of the following subcommands: install Options [OptionalDestName]; Copies out-of-date files and directories. The win32rdist command copies each source file or directory to each host in the destination list. The available options as specified by the Options variable are the win32rdist command flags -b, -h, -i, -R, -v, -w, and -y. These options only apply to the files specified by the SourceList variable. When you use the -R flag, nonempty directories are removed if the corresponding file name is absent on the master host. The OptionalDestName parameter renames files. If no install subcommand appears in the subcommand list or the destination name is not specified, the source file name is used. Directories in the path name are created if they do not exist on the remote host. The login name used on the destination host is the same as the local host unless the destination name is of the format login@host. notify NameList; Mails the list of updated files and any errors that may have occurred to the listed names (the NameList parameter). If no @ (at sign) appears in the name, the destination host is appended to the name (name@host). except NameList; Causes the win32rdist command to update all the files specified by the SourceList entry except for those files specified by the NameList variable. except_pat NameList; Prevents the win32rdist command from updating any files that contain a string that matches a member of the list specified by the NameList variable. special NameList "String"; Specifies shell commands (the "String" variable) to be executed on the remote host after the file specified by the NameList variable is updated or installed. If the NameList variable is omitted, the shell commands are executed for every file updated or installed. The shell variable FILE is set to the current file name before the win32rdist command executes the "String" variable. The "String" value must be enclosed in " " (double quotation marks) and can cross multiple lines in the distribution file. =head2 Exit Status This command returns the following exit values: 0 Specifies that the command completed successfully. >0 Specifies that an error occurred. =head1 Examples Examples of the Format: VariableName = NameList 1. To indicate which hosts' files to update, enter a line similar to the following: HOSTS =( H:\ \\arpa\dest_dir ) where the HOSTS variable is defined to be H: and \\arpa\ on the dest_dir share. The win32rdist command updates files on the hosts H: and \\arpa\dest_dir. You could use this variable as a destination list. 2. To indicate a name to use as a value for a SourceList entry, enter a line similar to the following: FILES = ( /bin /lib/usr/bin /usr/games /usr/include/{*.h,{stand,sys,vax*,pascal,machine}/*.h} /usr/lib /usr/man/man? /usr/ucb /usr/local/win32rdist ) where the FILES value is defined to be the files to be used for the SourceList entry. Note that a forward slash (/) is used to reprent the Microsoft standard backslash (\). 3. To indicate which files to exclude from the updating process, enter a line similar to the following: EXLIB = ( Mail.rc aliases aliases.dir aliases.pag crontab dshrc sendmail.cf sendmail.fc sendmail.hf sendmail.st uucp vfont) where the EXLIB value is defined as a list of files to exclude from the updating process. Examples of the Format: [label:] SourceList -> DestinationList SubcommandList 1. To copy a source list of files to a destination list of hosts, enter a line similar to the following: ${FILES} ->${HOSTS} install -R except /usr/lib/${EXLIB} ; except /usr/games/lib ; special /usr/sbin/sendmail "/usr/sbin/sendmail.bz" ; The [Label:] entry of the line is optional and not shown here. The $ (dollar sign) and the {} (braces) cause the file names FILES, HOSTS, and EXLIB to be expanded into the lists designated for them in the previous examples. The rest of the example comprises the subcommand list. 2. To use the [Label:] entry, enter the line as follows: srcsL: /usr/src/bin -> arpa except_pat (\e\e.o\e$ /SCCS\e$ ) ; The label is srcsL: and can be used to identify this entry for updating. The /usr/src/bin file is the source to be copied and host arpa is the destination of the copy. The third line contains a subcommand from the subcommand list. 3. To use a time-stamp file, enter a line similar to the following: ${FILES} :: stamp.cory notify root@cory The $ (dollar sign) and {} (braces) cause the name specified by FILES to be expanded into the list designated for it in example . The time-stamp file is stamp.cory. The last line is a subcommand from the subcommand list. =head1 Files =head1 Related Information =cut