# __ # /\ \ From the mind of # / \ \ # / /\ \ \_____ Lee Eakin # / \ \ \______\ or # / /\ \ \/____ / # \ \ \ \____\/ / Wrapper module for the rsync program # \ \ \/____ / rsync can be found at http://rsync.samba.org/rsync/ # \ \____\/ / # \/______/ package Rsync; require 5.004; use FileHandle; use IPC::Open3 qw(open3); use Carp; use strict; =head1 NAME Rsync - perl module interface to B http://rsync.samba.org/rsync/ =head1 SYNOPSIS use Rsync; $obj = Rsync->new(qw(-az C<-e> /usr/local/bin/ssh --rsync-path /usr/local/bin/rsync)); $obj->exec(qw(localdir rhost:remdir)) or warn "rsync failed\n"; =head1 DESCRIPTION Perl Convenience wrapper for B program. Written for B 2.3.1 but should perform properly with most versions. =cut # options from the rsync man pae ###### Boolean flags ###### # -h, --help show this help screen # -v, --verbose increase verbosity # -q, --quiet decrease verbosity # -c, --checksum always checksum # -a, --archive archive mode # -r, --recursive recurse into directories # -R, --relative use relative path names # -b, --backup make backups (default ~ suffix) # -u, --update update only (don't overwrite newer files) # -l, --links preserve soft links # -L, --copy-links treat soft links like regular files # --copy-unsafe-links copy links outside the source tree # --safe-links ignore links outside the destination tree # -H, --hard-links preserve hard links # -p, --perms preserve permissions # -o, --owner preserve owner (root only) # -g, --group preserve group # -D, --devices preserve devices (root only) # -t, --times preserve times # -S, --sparse handle sparse files efficiently # -n, --dry-run show what would have been transferred # -W, --whole-file copy whole files, no incremental checks # -x, --one-file-system don't cross filesystem boundaries # -C, --cvs-exclude auto ignore files in the same way CVS does # RCS SCCS CVS CVS.adm RCSLOG cvslog.* tags TAGS # .make.state .nse_depinfo *~ #* .#* ,* *.old *.bak # *.BAK *.orig *.rej .del-* *.a *.o *.obj *.so *.Z # *.elc *.ln core # --delete delete files that don't exist on the sending side # --delete-excluded also delete excluded files on the receiving side # --partial keep partially transferred files # --force force deletion of directories even if not empty # --numeric-ids don't map uid/gid values by user/group name # -I, --ignore-times don't exclude files that match length and time # --size-only only use file size when determining if a file # should be transferred # -z, --compress compress file data # --version print version number # --daemon run as a rsync daemon # --stats give some file transfer stats # --progress show progress during transfer ###### scalar values ###### # --csum-length=LENGTH <=16 bit md4 checksum size # -B, --block-size=SIZE checksum blocking size (default 700) # --timeout=TIME set IO timeout in seconds # --port=PORT specify alternate rsyncd port number # -e, --rsh=COMMAND specify rsh replacement # -T, --temp-dir=DIR create temporary files in directory DIR # --compare-dest=DIR also compare destination files relative to DIR # --exclude-from=FILE exclude patterns listed in FILE # --include-from=FILE don't exclude patterns listed in FILE # --config=FILE specify alternate rsyncd.conf file # --password-file=FILE get password from FILE # --log-format=FORMAT log file transfers using specified format # --suffix=SUFFIX override backup suffix # --rsync-path=PATH specify path to rsync on the remote machine ###### array values ###### # --exclude=PATTERN exclude files matching PATTERN # --include=PATTERN don't exclude files matching PATTERN # =over 4 =item Rsync::new $obj = new Rsync; or $obj = Rsync->new; or $obj = Rsync->new(@options); =back Create an Rsync object. Any options passed at creation are stored in the object as defaults for all future exec call on that object. Options are the same as those in L with the addition of --path-to-rsync which can be used to override the hardcoded default of /usr/local/bin/rsync, and --debug which causes the module functions to print some debugging information to STDERR. =cut sub new { my $class=shift; # seed the options hash, booleans, scalars, excludes, data, # status, stderr/stdout storage for last exec my $self={ # the full path name to the rsync binary 'path-to-rsync' => '/usr/local/bin/rsync', # these are the boolean flags to rsync, all default off, including them # in the args list turns them on 'flag' => {qw( --archive 0 --backup 0 --checksum 0 --compress 0 --copy-links 0 --copy-unsafe-links 0 --cvs-exclude 0 --daemon 0 --delete 0 --delete-excluded 0 --devices 0 --dry-run 0 --force 0 --group 0 --hard-links 0 --help 0 --ignore-times 0 --links 0 --numeric-ids 0 --one-file-system 0 --owner 0 --partial 0 --perms 0 --progress 0 --quiet 0 --recursive 0 --relative 0 --safe-links 0 --size-only 0 --sparse 0 --stats 0 --times 0 --update 0 --verbose 0 --version 0 --whole-file 0 )}, # these have simple scalar args we cannot easily check 'scalar' => {qw( --block-size 0 --compare-dest 0 --config 0 --csum-length 0 --exclude-from 0 --include-from 0 --log-format 0 --password-file 0 --port 0 --rsh 0 --rsync-path 0 --suffix 0 --temp-dir 0 --timeout 0 )}, # these can be specified multiple times and are additive, the doc also # specifies that it is an ordered list so we must preserve that order 'exclude' => [], # source/destination path names and hostnames 'data' => [], # return status from last exec 'status' => 0, 'realstatus' => 0, # whether or not to print debug statements 'debug' => 0, # stderr from last exec in array format (messages from remote rsync proc) 'err' => [], # stdout from last exec in array format (messages from local rsync proc) 'out' => [], }; if (@_) { &defopts($self,@_) or return undef; } bless $self, $class; } =over 4 =item Rsync::defopts defopts $obj @options; or $obj->defopts(@options); =back Set default options for future exec calls for the object. See L for a complete list of valid options. This is really the internal function that B calls but you can use it too. Presently there is no way to turn off the boolean options short of creating another object, but if it is needed and the B guys don't use it, I may add hooks to let + and ++ or a leading no- toggle it back off similar to B (the GNU way). =cut sub defopts { my $self=shift; my @opts=@_; # need a conversion table in case someone uses the short options my %short=(qw( -B --block-size -C --cvs-exclude -D --devices -H --hard-links -I --ignore-times -L --copy-links -R --relative -T --temp-dir -W --whole-file -a --archive -b --backup -c --checksum -e --rsh -g --group -h --help -l --links -n --dry-run -o --owner -p --perms -q --quiet -r --recursive -s --sparse -t --times -u --update -v --verbose -x --one-file-system -z --compress )); while (my $opt=shift @opts) { my $arg; print(STDERR "setting debug flag\n"),$self->{debug}=1,next if $opt eq '--debug'; print STDERR "processing option: $opt\n" if $self->{debug}; if ($opt=~/^-/) { # handle short opts first if ($opt=~/^-(\w+)$/) { foreach (split '',$1) { print STDERR "short option: -$_\n" if $self->{debug}; if (exists $short{'-'.$_}) { $opt=$short{'-'.$_}; # convert it to the long form $opt=$short{$opt} if exists $short{$opt}; # handle the 3 short opts that require args $self->{scalar}{$opt}=shift(@opts),next if (/^[BeT]$/); # handle the rest $self->{flag}{$opt}=1,next if exists $self->{flag}{$opt}; } carp "$opt - unknown option\n"; return undef; } } # handle long opts with = args if ($opt=~/^(--\w+[\w-]*)=(.*)$/) { print STDERR "splitting longopt: $opt ($1 $2)\n" if $self->{debug}; ($opt,$arg)=($1,$2); } # handle boolean flags $self->{flag}{$opt}=1,next if exists $self->{flag}{$opt}; # handle simple scalars $self->{scalar}{$opt}=($arg || shift @opts),next if exists $self->{scalar}{$opt}; # handle excludes if ($opt eq '--exclude') { $arg||=shift @opts; # if they sent a reset, we will too $self->{exclude}=[],next if $arg eq '!'; # otherwise add it to the list push @{$self->{exclude}},$arg; next; } # to preserve order we store both includes and excludes in the same # array. We use the leading '+ ' (plus space) trick from the man # page to accomplish this. if ($opt eq '--include') { $arg||=shift @opts; # first check to see if this is really an exclude push(@{$self->{exclude}},$arg),next if $arg=~s/^- //; # next see if they sent a reset, if they did, we will too $self->{exclude}=[],next if $arg eq '!'; # if it really is an include, fix it first, since we use exclude $arg='+ '.$arg unless $arg=~/^\+ /; push @{$self->{exclude}},$arg; next; } # handle our special case to override hard-coded path to rsync $self->{'path-to-rsync'}=($arg || shift @opts),next if $opt eq '--path-to-rsync'; # if we get this far nothing matched so it must be an error carp "$opt - unknown option\n"; return undef; } else { # must be data (source/destination info) print STDERR "adding to data array: $opt\n" if $self->{debug}; push(@{$self->{data}},$opt); } } 1; } =over 4 =item Rsunc::exec exec $obj @options or warn "rsync failed\n"; or $obj->exec(@options) or warn "rsync failed\n"; =back This is the function that does the real work. Any options passed to this routine are appended to any pre-set options and are not saved. They effect the current execution of B only. It returns 1 if the return status was zero (or true), if the B return status was non-zero it returns undef and stores the return status. You can examine the return status from rsync and any output to stdout and stderr with the functions listed below. =cut sub exec { my $self=shift; my @cmd=($self->{'path-to-rsync'}); foreach (sort keys %{$self->{flag}}) { push @cmd,$_ if $self->{flag}{$_}; } foreach (sort keys %{$self->{scalar}}) { push @cmd,$_.'='.$self->{scalar}{$_} if $self->{scalar}{$_}; } foreach (@{$self->{exclude}}) { push @cmd,'--exclude='.$_; } foreach (@{$self->{data}}) { push @cmd,$_; } push @cmd,@_ if @_; print STDERR "exec: @cmd\n" if $self->{debug}; my $in=FileHandle->new; my $out=FileHandle->new; my $err=FileHandle->new; my $pid=eval{ open3 $in,$out,$err,@cmd }; if ($@) { $self->{realstatus}=0; $self->{status}=255; $self->{err}=[$@,"Execution of rsync failed.\n"]; return undef; } $in->close; # we don't use it and neither should rsync (at least not yet) $self->{err}=[ $err->getlines ]; $self->{out}=[ $out->getlines ]; $err->close; $out->close; waitpid $pid,0; $self->{realstatus}=$?; $self->{status}=$?>>8; return undef if $self->{status}; return 1; } =over 4 =item Rsync::status $rval = status $obj; or $rval = $obj->status; =back Returns the status from last B call right shifted 8 bits. =cut sub status { my $self=shift; return $self->{status}; } =over 4 =item Rsync::realstatus $rval = realstatus $obj; or $rval = $obj->realstatus; =back Returns the real status from last B call (not right shifted). =cut sub realstatus { my $self=shift; return $self->{realstatus}; } =over 4 =item Rsync::err $aref = err $obj; or $aref = $obj->err; =back Returns an array or a reference to the array containing all output to stderr from the last B call. B sends all messages from the remote B process to stderr. This functions purpose is to make it easier for you to parse that output for appropriate information. =cut sub err { my $self=shift; return(wantarray ? @{$self->{err}} : $self->{err}); } =over 4 =item Rsync::out $aref = out $obj; or $aref = $obj->out; =back Similar to the B function, this returns an array or a reference to the array containing all output to stdout from the last B call. B sends all messages from the local B process to stdout. =cut sub out { my $self=shift; return(wantarray ? @{$self->{out}} : $self->{out}); } =head1 Author Lee Eakin Eleakin@nostrum.comE =head1 Credits Gerard Hickey C Russ Allbery C Graham Barr C Andrew Tridgell and Paul Mackerras C John Steele Esteele@nostrum.comE Philip Kizer Epckizer@nostrum.comE Larry Wall C I borrowed many clues on wrapping an external program from the PGP modules, and I would not have had such a useful tool to wrap except for the great work of the B authors. Thanks also to Graham Barr, the author of the libnet modules and many others, for looking over this code. Of course I must mention the other half of my brain, John Steele, and his good friend Philip Kizer for finding B and bringing it to my attention. And I would not have been able to enjoy writing useful tools if not for the creator of the B language. =head1 Copyrights Copyleft (l) 1999, by Lee Eakin =cut 1;