#!/usr/bin/perl -w # $Date: 2010/05/16 11:46:03 $ # simple script that acts as a glorified form of cp(1), allowing to # keep the working directory of a version control system separate from # the place where the files under version control actually reside. # # see rvc(1) and rvc-files(5) for a documentation # # If you have received this file without a man page, see # http://www.linta.de/~aehlig/rvc/ use strict; use Getopt::Long qw(:config require_order); use File::Copy; use File::Compare; use Cwd 'realpath'; my $home=$ENV{'HOME'}; defined $home and -d $home and $home = realpath($home); defined $home or $home = ""; my $rvc_directory = $ENV{'RVC_DIRECTORY'}; defined($rvc_directory) or $rvc_directory = "$home/remote-vc"; $rvc_directory=realpath($rvc_directory); -d $rvc_directory or die "Invalid remote version control directory $rvc_directory.\n"; $rvc_directory =~ /\/$/ and chop($rvc_directory); my $rvc_places = $ENV{'RVC_PLACES_FILE'}; defined($rvc_places) or $rvc_places="rvc-places"; my $rvc_dirs = $ENV{'RVC_DIRS'}; defined($rvc_dirs) or $rvc_dirs="rvc-dirs"; ## shared format strings my $status_linef = $ENV{'RVC_STATUS_FORMATSTRING'}; defined($status_linef) or $status_linef = " %-10s %-35s %-20s %-17s %s"; $status_linef .= "\n"; my $dirstatus_linef = $ENV{'RVC_DIRSTATUS_FORMATSTRING'}; defined($dirstatus_linef) or $dirstatus_linef = "%1s %-60s %-23s %-23s"; $dirstatus_linef .= "\n"; ## Ensure, all the needed directories are present. foreach my $dir (qw! rvc-installed !) { -d "$rvc_directory/$dir" and next; mkdir "$rvc_directory/$dir", 0700 or die "Unable to create directory `$rvc_directory/$dir' ($!)\n"; set_owner("$rvc_directory/$dir"); } my $force = 0; my $partial = 0; my $part = 0; my $observer = 0; GetOptions( 'observer' => sub {$observer =1}, 'force' => sub {$force =1}, 'partial' => sub {$partial =1}, 'part=i' => \$part, ); $observer and $partial=1; my $command; @ARGV !=0 or die "A command has to be specified"; for ($ARGV[0]) { /^install$/ && do { @ARGV == 2 or $ARGV[1] = guess_module(); defined($ARGV[1]) or die "usage: rvc install \n"; do_install($ARGV[1]); last } ; /^installfile$/ && do { @ARGV == 3 or $ARGV[2] = guess_module(); defined($ARGV[2]) or die "usage: rvc installfile \n"; do_install_file($ARGV[1],$ARGV[2]); last } ; /^appendfile$/ && do { @ARGV == 3 or $ARGV[2] = guess_module(); defined($ARGV[2]) or die "usage: rvc appendfile \n"; do_append_file($ARGV[1],$ARGV[2]); last } ; /^update$/ && do { @ARGV == 2 or $ARGV[1] = guess_module(); defined($ARGV[1]) or die "usage: rvc update \n"; do_update($ARGV[1]); last } ; /^dirstatus$/ && do { @ARGV == 2 or $ARGV[1] = guess_module(); defined($ARGV[1]) or die "usage: rvc dirstatus \n"; do_dirstatus($ARGV[1]); last }; /^status$/ && do { @ARGV == 2 or $ARGV[1] = guess_module(); defined($ARGV[1]) or die "usage: rvc status \n"; do_status($ARGV[1]); last }; /^ls$/ && do { @ARGV <= 2 or die "usage: rvc ls []\n"; @ARGV == 2 or $ARGV[1] = realpath(); do_ls(realpath($ARGV[1])); last }; /^do$/ && do { shift(@ARGV); do_vcs(@ARGV); last }; /^diff$/ && do { @ARGV == 2 or die "usage: rvc diff \n"; do_diff($ARGV[1]); last }; /^add$/ && do { @ARGV == 4 or die "usage: rvc add \n"; do_add($ARGV[1],$ARGV[2],$ARGV[3]); last }; /^addmultifile/ && do { shift(@ARGV); do_add_multifile(@ARGV); last }; /^notedir$/ && do {@ARGV == 3 or die "usage: rvc notedir \n"; do_notedir($ARGV[1],$ARGV[2]); last }; /^store$/ && do { @ARGV == 2 or die "usage: rvc store \n"; do_store($ARGV[1]); last }; /^release$/ && do { @ARGV == 2 or die "usage: rvc release \n"; do_release($ARGV[1]); last }; /^restore$/ && do { @ARGV == 2 or die "usage: rvc restore \n"; do_restore($ARGV[1]); last }; /^ldiff$/ && do { @ARGV == 3 or (@ARGV == 2 and $ARGV[2] = guess_module()) or die "usage: rvc ldiff \n"; do_ldiff($ARGV[2],$ARGV[1]); last }; /^lrestore$/ && do { @ARGV == 3 or (@ARGV == 2 and $ARGV[2] = guess_module()) or die "usage: rvc lrestore \n"; do_lrestore($ARGV[2],$ARGV[1]); last }; die "rvc command $_ not supported.\n"; } exit 0; ###################################################################### sub install_a_file { my ($module,$file,$owner,$group,$perms,$place) = @_; print "\n* file >$file<, to reside with permissions $owner:$group $perms at $place\n"; my $place_quoted = quote($place); $partial and -d "$rvc_directory/rvc-installed/$place_quoted" and do { print " SKIPPING\n"; my $other_module = contents_of("$rvc_directory/rvc-installed/$place_quoted/module"); my $other_file = contents_of("$rvc_directory/rvc-installed/$place_quoted/file"); print " $place belongs to module $other_module and is $file there.\n"; return; }; -e "$rvc_directory/rvc-installed/$place_quoted" and die "File seems to be installed already.\n" . "Remove $rvc_directory/rvc_installed/$place_quoted if this is not the case.\n" . "Use the option -partial to skip files already under rvc control."; !$observer and $force and -f $place and do { print " WARNING! By option force, removing $place\n"; unlink $place or die "Failed to remove $place ($!).\n"; }; -e $place and !$observer and die "The remote place $place is already occupied; giving up.\n"; mkdir "$rvc_directory/rvc-installed/$place_quoted", 0700 or die "Couldn't create $rvc_directory/rvc-installed/$place_quoted ($!)\n"; set_owner("$rvc_directory/rvc-installed/$place_quoted"); create_file("$rvc_directory/rvc-installed/$place_quoted/module",$module); create_file("$rvc_directory/rvc-installed/$place_quoted/file",$file); create_file("$rvc_directory/rvc-installed/$place_quoted/perms","$owner $group $perms"); do_cp("$rvc_directory/$module/$file", "$rvc_directory/rvc-installed/$place_quoted/data"); set_owner("$rvc_directory/rvc-installed/$place_quoted/data"); $observer and return; do_cp( "$rvc_directory/rvc-installed/$place_quoted/data", $place); do_chown($owner,$group,$place); do_chmod($perms,$place); } sub append_a_file { my ($module,$file,$place) = @_; print "\n* file >$file<, to be appended at $place\n"; my $place_quoted = quote($place); -e "$rvc_directory/rvc-installed/$place_quoted" or die "Primary file seems no to be installed yet.\n"; my $partnumber = get_part_number("$rvc_directory/rvc-installed/$place_quoted"); print "This file will be part Number $partnumber.\n"; create_file("$rvc_directory/rvc-installed/$place_quoted/module.$partnumber",$module); create_file("$rvc_directory/rvc-installed/$place_quoted/file.$partnumber",$file); do_cp("$rvc_directory/$module/$file", "$rvc_directory/rvc-installed/$place_quoted/part.$partnumber"); set_owner("$rvc_directory/rvc-installed/$place_quoted/part.$partnumber"); $partnumber == 1 and do { do_cp("$rvc_directory/rvc-installed/$place_quoted/data", "$rvc_directory/rvc-installed/$place_quoted/part.0"); set_owner("$rvc_directory/rvc-installed/$place_quoted/part.0"); }; combine_data("$rvc_directory/rvc-installed/$place_quoted"); my $permissions = contents_of("$rvc_directory/rvc-installed/$place_quoted/perms"); $permissions =~ /(\w+) (\w+) (\S+)/ or die "Can't parse permissions $permissions.\n"; my ($owner,$group,$perms) = ($1,$2,$3); do_cp("$rvc_directory/rvc-installed/$place_quoted/data", $place); do_chown($owner,$group,$place); do_chmod($perms,$place); } sub do_install_file { my $file = shift; my $module = shift; print "Installing a file...\n"; print " rvc directory: $rvc_directory\n"; print " module: $module\n"; print " file: $file\n"; print " rvc places: $rvc_places\n"; print " rvc dirs: $rvc_dirs\n\n"; open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; my ($lfile,$owner,$group,$perms,$place) = ($1,$2,$3,$4,$5); $file eq $lfile or next; !$observer and create_path($place,$module); install_a_file($module,$file,$owner,$group,$perms,$place); } close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; } sub do_append_file { my ($file,$module) = @_; print "Appending a file...\n"; print " rvc directory: $rvc_directory\n"; print " module: $module\n"; print " file: $file\n"; print " rvc places: $rvc_places\n\n"; open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; my ($lfile,$owner,$group,$perms,$place) = ($1,$2,$3,$4,$5); $file eq $lfile or next; $force or not compare($place, "$rvc_directory/rvc-installed/" . quote($place) . "/data") or die "File changed remotely, not building a multi-file.\n"; append_a_file($module,$file,$place); } close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; } sub do_install { my $module = shift; print "Installing...\n"; print " rvc directory: $rvc_directory\n"; print " module: $module\n"; print " rvc places: $rvc_places\n"; print " rvc dirs: $rvc_dirs\n\n"; open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; my ($file,$owner,$group,$perms,$place) = ($1,$2,$3,$4,$5); !$observer and create_path($place,$module); install_a_file($module,$file,$owner,$group,$perms,$place); } close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; } sub do_add { my ($place,$module,$file) = @_; my $pwd = realpath(); $place =~ /^\.\// and substr($place,0,1)= $pwd; $place =~ /^\// or $place = $pwd ."/" . $place; -e $place or die "Cannot find $place.\n"; -f $place or die "$place is not a regular file.\n"; my $record = $rvc_directory . "/rvc-installed/" . quote($place); -e $record and die "$record exists, file already installed.\n"; -d "$rvc_directory/$module" or die "Module directory $rvc_directory/$module not found.\n"; -e "$rvc_directory/$module/$file" and die "$rvc_directory/$module/$file exists already\n"; mkdir "$record", 0700 or die "Couldn't create $record ($!)\n"; set_owner("$record"); create_file("$record/module",$module); create_file("$record/file",$file); do_cp($place, "$record/data"); set_owner("$record/data"); do_cp("$record/data", "$rvc_directory/$module/$file"); set_owner("$rvc_directory/$module/$file"); my $perms = mystat($place); create_file("$record/perms",$perms); open(PLACES, ">> $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; print PLACES "$file $perms $place\n"; close(PLACES) or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; my $vc_call=$ENV{'RVC_ADD_COMMAND'}; defined($vc_call) or return; $vc_call =~ s/RVCWORKINGDIR/$rvc_directory\/$module/g; $vc_call =~ s/RVCFILE/$file/g; print "\n\n$vc_call\n"; system "/bin/sh", "-c", "$vc_call"; printf "[return value %s]\n", $? >> 8; } sub do_add_multifile { my ($place,$module,$file,@splits) = @_; defined($file) && @splits % 3 == 0 or die "usage: rvc addmultifile ( )*\n"; @splits > 0 or return do_add($place,$module,$file); my $pwd = realpath(); $place =~ /^\.\// and substr($place,0,1)= $pwd; $place =~ /^\// or $place = $pwd ."/" . $place; -e $place or die "Cannot find $place.\n"; -f $place or die "$place is not a regular file.\n"; my $record = $rvc_directory . "/rvc-installed/" . quote($place); -e $record and die "$record exists, file already installed.\n"; -d "$rvc_directory/$module" or die "Module directory $rvc_directory/$module not found.\n"; -e "$rvc_directory/$module/$file" and die "$rvc_directory/$module/$file exists already\n"; for (my $i=0; 3 * $i < @splits; $i++) { -d "$rvc_directory/$splits[3*$i+1]" or die "Module directory $rvc_directory/$splits[3*$i+1] not found.\n"; -e "$rvc_directory/$splits[3*$i+1]/$splits[3*$i+2]" and die "$rvc_directory/$splits[3*$i+1]/$splits[3*$i+2] exists already\n"; } mkdir "$record", 0700 or die "Couldn't create $record ($!)\n"; set_owner("$record"); do_cp($place, "$record/data"); set_owner("$record/data"); create_file("$record/module",$module); create_file("$record/file",$file); my $perms = mystat($place); create_file("$record/perms",$perms); open(DATAFILE, "< $record/data") or die "Failed to read self-created file $record/data ($!)\n"; my $regexp = shift(@splits); my $new_module = shift(@splits); my $new_file = shift(@splits); my $i = 0; open(PARTFILE, "> $record/part.$i") or die "Failed to write $record/part.$i ($!)\n"; foreach my $line () { defined($regexp) and $line =~ /$regexp/ and do { close(PARTFILE) or die "Failed to close $record/part.$i ($!)\n"; do_cp("$record/part.$i","$rvc_directory/$module/$file"); set_owner("$rvc_directory/$module/$file"); open(PLACES, ">> $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; print PLACES "$file $perms $place\n"; close(PLACES) or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; my $vc_call=$ENV{'RVC_ADD_COMMAND'}; defined($vc_call) and do { $vc_call =~ s/RVCWORKINGDIR/$rvc_directory\/$module/g; $vc_call =~ s/RVCFILE/$file/g; print "\n\n$vc_call\n"; system "/bin/sh", "-c", "$vc_call"; printf "[return value %s]\n", $? >> 8; }; $module=$new_module; $file=$new_file; $i++; create_file("$record/file.$i",$file); create_file("$record/module.$i",$module); open(PARTFILE, "> $record/part.$i") or die "Failed to write $record/part.$i ($!)\n"; $regexp = shift(@splits); $new_module = shift(@splits); $new_file = shift(@splits); }; print PARTFILE $line; } close(PARTFILE) or die "Failed to close $record/part.$i ($!)\n"; do_cp("$record/part.$i","$rvc_directory/$module/$file"); set_owner("$rvc_directory/$module/$file"); open(PLACES, ">> $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; print PLACES "$file $perms $place\n"; close(PLACES) or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; my $vc_call=$ENV{'RVC_ADD_COMMAND'}; defined($vc_call) and do { $vc_call =~ s/RVCWORKINGDIR/$rvc_directory\/$module/g; $vc_call =~ s/RVCFILE/$file/g; print "\n\n$vc_call\n"; system "/bin/sh", "-c", "$vc_call"; printf "[return value %s]\n", $? >> 8; }; } sub do_update { my $module = shift; print "Updateing...\n"; print " rvc directory: $rvc_directory\n"; print " module: $module\n"; print " rvc places: $rvc_places\n\n"; open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; my ($file,$owner,$group,$perms,$place) = ($1,$2,$3,$4,$5); print "\n* file >$file<, to reside with permissions $owner:$group $perms at $place\n"; my $place_quoted = quote($place); -d "$rvc_directory/rvc-installed/$place_quoted" or do { print " SKIPPING\n"; print " $file is not installed, hence not updating it.\n"; next; }; -e $place or do { print " SKIPPING\n"; print " $file is lost remotely, hence not updating it.\n"; next; }; if (-e "$rvc_directory/rvc-installed/$place_quoted/part.0") { print "$file is a multi-file\n"; my $record = "$rvc_directory/rvc-installed/" . quote ($place); my $part = 0; my $i; for ($i=1; -e "$record/part.$i"; $i++) { my $part_module = contents_of("$record/module.$i"); my $part_file = contents_of("$record/file.$i"); $part_module eq $module and $part_file eq $file and $part = $i; } split_directory($record,$place); compare("$record/part.$part", "$record/split.$part") and do { print " SKIPPING\n"; print " part $part changed remotely\n"; }; do_cp("$rvc_directory/$module/$file", "$record/split.$part"); do_cp("$record/split.$part", "$record/part.$part"); combine_split_data($record); my $permissions = contents_of("$rvc_directory/rvc-installed/$place_quoted/perms"); $permissions =~ /(\w+) (\w+) (\S+)/ or die "Can't parse permissions $permissions.\n"; my ($owner,$group,$perms) = ($1,$2,$3); do_cp("$rvc_directory/rvc-installed/$place_quoted/data", $place); do_chown($owner,$group,$place); do_chmod($perms,$place); } else { my $other_module = contents_of("$rvc_directory/rvc-installed/$place_quoted/module"); my $other_file = contents_of("$rvc_directory/rvc-installed/$place_quoted/file"); ($module eq $other_module and $file eq $other_file) or do { print " SKIPPING\n"; print " $place belongs to module $other_module and is $file there.\n"; next }; compare("$rvc_directory/rvc-installed/$place_quoted/data", $place) and do { print " SKIPPING\n"; print " Contents changed remotely.\n"; next }; compare("$rvc_directory/rvc-installed/$place_quoted/data", "$rvc_directory/$module/$file") or do { print " NOTHING TO DO\n"; print " File unchanged locally.\n"; next }; my $the_perms = contents_of("$rvc_directory/rvc-installed/$place_quoted/perms"); my $actual_perms = mystat($place); $actual_perms eq $the_perms or do { print " SKIPPING\n"; print " permissions changed remotely form $the_perms to $actual_perms\n"; next }; open(PERMS, "> $rvc_directory/rvc-installed/$place_quoted/perms") or die "Couldn't open $rvc_directory/rvc-installed/$place_quoted/perms ($!)\n"; print PERMS "$owner $group $perms"; close(PERMS) or die "Couldn't close $rvc_directory/rvc-installed/$place_quoted/perms ($!)\n"; set_owner("$rvc_directory/rvc-installed/$place_quoted/perms"); unlink "$rvc_directory/rvc-installed/$place_quoted/data" or die "Couldn't remove $rvc_directory/rvc-installed/$place_quoted/data ($!)\n"; do_cp("$rvc_directory/$module/$file", "$rvc_directory/rvc-installed/$place_quoted/data"); set_owner("$rvc_directory/rvc-installed/$place_quoted/data"); unlink $place or die "Couldn't remove $place ($!)\n"; do_cp("$rvc_directory/rvc-installed/$place_quoted/data", $place); do_chown($owner,$group,$place); do_chmod($perms,$place); } } close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; } sub do_restore { my $place = shift; my $pwd = realpath(); $place =~ /^\.\// and substr($place,0,1)= $pwd; $place =~ /^\// or $place = $pwd ."/" . $place; print "retstoring $place ...\n"; -e $place and die "$place exists already.\n"; my $record = $rvc_directory . "/rvc-installed/" . quote($place); -d $record or die "Couldn't find record directory $record\n"; do_cp($record . "/data", $place); my $perms = contents_of($record . "/perms"); $perms =~ /^(\S+)\s+(\S+)\s+(\S+)/ or die "Couldn't parse permissions string"; my ($owner,$group,$mod) = ($1,$2,$3); do_chown($owner,$group,$place); do_chmod($mod,$place); } sub do_diff { my $place = shift; my $pwd = realpath(); $place =~ /^\.\// and substr($place,0,1)= $pwd; $place =~ /^\// or $place = $pwd ."/" . $place; -e $place or die "Couldn't find $place.\n"; my $record = $rvc_directory . "/rvc-installed/" . quote($place); -d $record or die "Couldn't find record directory $record\n"; my $count = split_directory($record,$place); if (!$count) { system "diff", $record . "/data", $place; } else { my $i; for ($i=0; $i<$count; $i++) { print "\n############# Changes in Part $i:\n"; system "diff", $record . "/part." . $i, $record . "/split." . $i; } } my $perms = contents_of($record . "/perms"); my $oldperms = mystat($place); $perms eq $oldperms and return; print "\n\n"; print "stored permissions : " . $perms . "\n"; print "current permissions: " . $oldperms . "\n"; } sub do_vcs { my $place = pop; my $pwd = realpath(); $place =~ /^\.\// and substr($place,0,1)= $pwd; $place =~ /^\// or $place = $pwd ."/" . $place; -e $place or die "Couldn't find $place.\n"; $place = realpath($place); my $record = $rvc_directory . "/rvc-installed/" . quote($place); -d $record or die "Couldn't find record directory $record\n"; $force or not compare($record . "/data", $place) or die "Remote file changed, aborting. You can use -force to do it anyway.\n"; my $count = split_directory($record,$place); $part and ($part >= $count) and die "Part $part not existent for $place, which only has $count parts.\n"; my $vcs = $ENV{'RVC_VCS'}; defined($vcs) or $vcs="rcs"; my ($localname,$module); if ($part == 0) { $localname = contents_of($record . "/file"); $module = contents_of($record . "/module"); } else { $localname = contents_of($record . "/file." . $part); $module = contents_of($record . "/module." . $part); } my $data = "/data"; $data = "/part.$part" if $count > 0; print "Module: $module, Local Name: $localname\n"; my $workingdir = $rvc_directory . "/" . $module; -d $workingdir or die "Directory $workingdir does not exist.\n"; $workingdir = realpath($workingdir); print "Working directory: $workingdir\n"; $force or not compare($record . "/$data", $workingdir . "/" . $localname) or die "Local file changed, aborting. You can use -force to do it anyway.\n"; chdir($workingdir); print "\n" . $vcs . " "; foreach(@_) { print $_ . " "; } print $localname . "\n"; system $vcs, @_, $localname; printf "[return value %s]\n", $? >> 8; compare($workingdir . "/" . $localname, $record . "/$data") or return; unlink $record . "/$data" or die "Failed to unlink $record/$data ($!).\n"; do_cp($workingdir . "/" . $localname, $record . "/$data"); set_owner($record . "/$data"); combine_data($record) if $count > 1; unlink $place or die "Failed to unlink $place ($!).\n"; do_cp($record . "/data", $place); my $perms = contents_of($record . "/perms"); $perms =~ /(\w+) (\w+) (\w+)/ or die "Failed to parse permsions >$perms<\n"; my ($owner,$group,$perm) = ($1,$2,$3); do_chown($owner,$group,$place); do_chmod($perm,$place); } sub do_notedir { my ($dir,$module) = @_; $dir = realpath($dir); -d $dir or die "Not a directory $dir.\n"; -d "$rvc_directory/$module" or die "Module directory $rvc_directory/$module not found.\n"; -f "$rvc_directory/$module/$rvc_dirs" or system "touch \Q$rvc_directory/$module/$rvc_dirs\E"; my %dirs = (); open(DIRS, "< $rvc_directory/$module/$rvc_dirs") or die "failed opening $rvc_directory/$module/$rvc_dirs ($!).\n"; foreach() { chomp; /^(\S+) (.*)$/ or die "Failed to parse dirs line $_\n"; $dirs{$1}=$2; } close(DIRS) or die "failed to close $rvc_directory/$module/$rvc_dirs after reading ($!).\n"; while ($dir) { $dirs{$dir} = mystat($dir); $dir =~ /(.*)\/[^\/]*$/ or die "Malformed directory name $dir\n"; $dir = $1; }; open(DIRS, "> $rvc_directory/$module/$rvc_dirs") or die "failed opening $rvc_directory/$module/$rvc_dirs for writing ($!).\n"; foreach(sort(keys(%dirs))) { print DIRS "$_ $dirs{$_}\n"; }; close(DIRS) or die "failed opening $rvc_directory/$module/$rvc_dirs ($!).\n"; my $vc_call=$ENV{'RVC_NOTE_DIR_COMMAND'}; defined($vc_call) or return; $vc_call =~ s/RVCWORKINGDIR/$rvc_directory\/$module/g; print "\n\n$vc_call\n"; system "/bin/sh", "-c", "$vc_call"; printf "[return value %s]\n", $? >> 8; } sub do_store { my $place = shift; my $pwd = realpath(); $place =~ /^\.\// and substr($place,0,1)= $pwd; $place =~ /^\// or $place = $pwd ."/" . $place; print "storing changes to $place ...\n"; -e $place or die "Cannot file $place\n"; my $record = $rvc_directory . "/rvc-installed/" . quote($place); -d $record or die "Couldn't find record directory $record\n"; my $module = contents_of($record . "/module"); my $file = contents_of($record . "/file"); print " module: $module\n"; print " file: $file\n"; my $count = split_directory($record,$place); $count = 1 unless $count; my $i; for ($i=0; $i<$count; $i++) { my $data = "/data"; if ($count > 1) { print "######## Processing Part $i\n"; $data = "/part.$i"; $place = $record . "/split.$i"; } if ($i > 0) { $module = contents_of($record . "/module.$i"); $file = contents_of($record . "/file.$i"); print " module: $module\n"; print " file: $file\n"; } compare($record . "/$data", "$rvc_directory/$module/$file") and do { print "File changed locally.\n Skipping.\n"; next; }; unlink "$record" . "/$data" or die "Couldn't remove $record/$data ($!)\n"; do_cp($place, $record . "/$data"); set_owner($record . "/$data"); unlink "$rvc_directory/$module/$file" or die "Couldn't remove $rvc_directory/$module/$file ($?)\n"; do_cp($record . "/$data", "$rvc_directory/$module/$file"); set_owner("$rvc_directory/$module/$file"); if ($i==0) { my $perms = mystat($place); create_file("$record/perms",$perms); open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; my $new_rvc_places = ""; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; my ($lfile,$lowner,$lgroup,$lperms,$lplace) = ($1,$2,$3,$4,$5); if ($lfile eq $file and $lplace eq $place) { $new_rvc_places .= "$file $perms $place\n"; } else { $new_rvc_places .= $_; } } close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; open(PLACES, "> $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; print PLACES $new_rvc_places; close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; set_owner("$rvc_directory/$module/$rvc_places"); }; my $vc_call=$ENV{'RVC_STORE_COMMAND'}; defined($vc_call) and do { $vc_call =~ s/RVCWORKINGDIR/$rvc_directory\/$module/g; print "\n\n$vc_call\n"; system "/bin/sh", "-c", "$vc_call"; printf "[return value %s]\n", $? >> 8; }; } combine_data($record) if $count > 1; } sub do_release { my $place = shift; my $pwd = realpath(); my $is_single_file = 1; $place =~ /^\.\// and substr($place,0,1)= $pwd; $place =~ /^\// or $place = $pwd ."/" . $place; print "Releasing $place ...\n"; -e $place or die "Cannot find file $place\n"; my $record = $rvc_directory . "/rvc-installed/" . quote($place); -d $record or die "Couldn't find record directory $record\n"; my $module = contents_of($record . "/module"); my $file = contents_of($record . "/file"); print " module: $module\n"; print " file: $file\n"; compare($record . "/data", "$place") and die "File changed remotely.\n"; my $data = "data"; -e "$record/part.0" and do { $data = "part.0"; $is_single_file = 0; }; for (my $i=1; -e "$record/part.$i"; $i++) { my $part_module = contents_of("$record/module.$i"); my $part_file = contents_of("$record/file.$i"); $part_module eq $module and $part_file eq $file and $data = "part." . $i; compare($record . "/part.$i", "$rvc_directory/$part_module/$part_file") and die "File changed locally (part $i, $part_module/$part_file).\n"; } compare($record . "/$data", "$rvc_directory/$module/$file") and die "File changed locally ($module/$file).\n"; unlink "$record" . "/perms" or die "Couldn't remove $record/perms ($!)\n"; unlink "$record" . "/data" or die "Couldn't remove $record/data ($!)\n"; unlink "$record" . "/file" or die "Couldn't remove $record/file ($!)\n"; unlink "$record" . "/module" or die "Couldn't remove $record/module ($!)\n"; -e "$record/part.0" and do { unlink "$record/part.0" or die "Couldn't remove $record/part.0 ($!)\n"; }; for (my $i=1; -e "$record/part.$i"; $i++) { unlink "$record/part.$i" or die "Couldn't remove $record/part.$i ($!)\n"; unlink "$record/module.$i" or die "Couldn't remove $record/module.$i ($!)\n"; unlink "$record/file.$i" or die "Couldn't remove $record/file.$i ($!)\n"; -e "$record/split.$i" and do { unlink "$record/split.$i" or die "Couldn't remove $record/split.$i ($!)\n"; }; }; -e "$record/split.0" and do { unlink "$record/split.0" or die "Couldn't remove $record/split.0 ($!)\n"; }; rmdir "$record" or die "Couldn't remove $record ($!)\n"; $is_single_file and !$observer and do { open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; my $new_rvc_places = ""; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; my ($lfile,$lowner,$lgroup,$lperms,$lplace) = ($1,$2,$3,$4,$5); if ($lfile eq $file and $lplace eq $place) { $new_rvc_places .= ""; ## remove the entry in the list } else { $new_rvc_places .= $_; } } close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; open(PLACES, "> $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; print PLACES $new_rvc_places; close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; set_owner("$rvc_directory/$module/$rvc_places"); my $vc_call=$ENV{'RVC_RELEASE_COMMAND'}; defined($vc_call) or return; $vc_call =~ s/RVCWORKINGDIR/$rvc_directory\/$module/g; print "\n\n$vc_call\n"; system "/bin/sh", "-c", "$vc_call"; } } sub do_lrestore { my ($module,$file) = @_; -e "$rvc_directory/$module/$file" and die "$rvc_directory/$module/$file exists already\n"; open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; $file eq $1 and do { my $place = $5; print "restoring $file in module $module as installed at $place\n"; my $record = "$rvc_directory/rvc-installed/" . quote ($place); my $data="data"; my $i; -e "$record/part.0" and $data = "part.0"; for ($i=1; -e "$record/part.$i"; $i++) { my $part_module = contents_of("$record/module.$i"); my $part_file = contents_of("$record/file.$i"); $part_module eq $module and $part_file eq $file and $data = "part." . $i; } system "cp","-p","$record/$data", "$rvc_directory/$module/$file" and die "cp $record/$data $rvc_directory/$module/$file failed ($?)\n"; last; } } close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; } sub do_ldiff { my ($module,$file) = @_; -e "$rvc_directory/$module/$file" or die "$rvc_directory/$module/$file does not exist.\n"; open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; $file eq $1 and do { my $place = $5; my $record = "$rvc_directory/rvc-installed/" . quote ($place); my $data="data"; my $i; -e "$record/part.0" and $data = "part.0"; for ($i=1; -e "$record/part.$i"; $i++) { my $part_module = contents_of("$record/module.$i"); my $part_file = contents_of("$record/file.$i"); $part_module eq $module and $part_file eq $file and $data = "part." . $i; } system "diff","$record/$data", "$rvc_directory/$module/$file"; last; }; } close(PLACES) or die "failed closing $rvc_directory/$module/$rvc_places ($!).\n"; } sub do_dirstatus { my $module = shift; print "Directory Status...\n"; print " rvc directory: $rvc_directory\n"; print " module: $module\n"; print " rvc dirs: $rvc_dirs\n\n"; printf $dirstatus_linef, "X", "directory", "stored", "actual"; printf $dirstatus_linef, "-", "---------", "------", "------"; open(DIRS, "< $rvc_directory/$module/$rvc_dirs") or die "failed opening $rvc_directory/$module/$rvc_dirs ($!).\n"; while() { /^(\S+) (.*)$/ or die "cannot parse line >$_<\n"; my ($dir,$stored) = ($1,$2); if (-d $dir) { my $actual = mystat($dir); my $flag; if ($actual eq $stored) { $flag = " "; } else { $flag = "!"; }; printf $dirstatus_linef, $flag, $dir, $stored, $actual; } else { printf $dirstatus_linef, "M", $dir, $stored, "[missing]"; }; } printf "\n"; close(DIRS); } sub do_status { my $module = shift; print "Status...\n"; print " rvc directory: $rvc_directory\n"; print " module: $module\n"; print " rvc places: $rvc_places\n\n"; printf $status_linef, "module", "file", "status", "changed", "remote"; printf $status_linef, "------", "----", "------", "-------", "------"; open(PLACES, "< $rvc_directory/$module/$rvc_places") or die "failed opening $rvc_directory/$module/$rvc_places ($!).\n"; while() { /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/ or die "cannot parse line >$_<\n"; my ($file,$owner,$group,$perms,$place) = ($1,$2,$3,$4,$5); short_status($module,$file,$place); } printf "\n"; close(PLACES); } sub do_ls { my ($pwd) = @_; opendir my $curdir, $pwd or die "Unable to read current directory ($!)\n"; printf $status_linef, "module", "file", "status", "changed", "remote"; printf $status_linef, "------", "----", "------", "-------", "------"; while (defined(my $file = readdir $curdir)) { my $place = "$pwd/$file"; my $place_quoted = quote($place); my $record = "$rvc_directory/rvc-installed/$place_quoted"; -e $record and short_status(contents_of($record . "/module"), contents_of($record . "/file"), $place); } closedir $curdir; print "\n"; } sub quote { my ($name) = @_; $name =~ s/([^-a-zA-Z0-9])/"_" . ord ($1) . "_" /eg; return $name; } sub contents_of { my ($name) = @_; open(FILETOREAD, "< $name") or die "Failed to open $name ($!)\n"; my $contents = do {local $/; }; close(FILETOREAD) or die "Failed to close $name ($!)\n"; return $contents; } sub create_file { my ($name,$content) = @_; open(FILETOWRITE, "> $name") or die "Failed to create $name ($!)\n"; print FILETOWRITE $content; close(FILETOWRITE) or die "Failed to close $name ($!)\n"; set_owner($name); } sub guess_module { my $pwd = realpath(); length($pwd) > length($rvc_directory) or return undef; substr($pwd, 0, length($rvc_directory)) eq $rvc_directory or return undef; my $module = substr($pwd, length($rvc_directory)+1); $module =~/\/$/ and chop($module); length($module) or return undef; $module =~ /\// and return undef; return $module; } sub set_owner { my $file = shift; my $rvc_admin_user = $ENV{'RVC_ADMIN_USER'}; defined ($rvc_admin_user) or return; $rvc_admin_user =~ /(.*):(.*)/ or die "Can't parse user:group '$rvc_admin_user'\n"; my ($username,$groupname) = ($1,$2); my $uid = getpwnam($username); my $gid = getgrnam($groupname); chown $uid, $gid, $file or die "chown $rvc_admin_user $file failed [uid=$uid] ($?)\n"; } sub short_status { my ($module,$file,$place) = @_; my $place_quoted = quote($place); my $status =""; my $changed =""; if (-d "$rvc_directory/rvc-installed/$place_quoted") { my $installed_module =contents_of("$rvc_directory/rvc-installed/$place_quoted/module"); my $installed_file =contents_of("$rvc_directory/rvc-installed/$place_quoted/file"); my $data = "data"; my $i; -e "$rvc_directory/rvc-installed/$place_quoted/part.0" and $data = "part.0"; for ($i=1; -e "$rvc_directory/rvc-installed/$place_quoted/part.$i"; $i++) { my $part_module = contents_of("$rvc_directory/rvc-installed/$place_quoted/module.$i"); my $part_file = contents_of("$rvc_directory/rvc-installed/$place_quoted/file.$i"); $part_module eq $module and $part_file eq $file and do { $data = "part." . $i; $installed_module = $part_module; $installed_file = $part_file; }; } if ($installed_module eq $module and $installed_file eq $file) { $status = "installed"; if (-e "$rvc_directory/$module/$file") { if (-e $place) { if (compare("$rvc_directory/rvc-installed/$place_quoted/data",$place)) { if (compare("$rvc_directory/rvc-installed/$place_quoted/$data", "$rvc_directory/$module/$file")) { $changed = "CONFLICT"; } else { $changed = "changed remotely"; } } else { if (compare("$rvc_directory/rvc-installed/$place_quoted/$data", "$rvc_directory/$module/$file")) { $changed = "changed locally"; } else { $changed = "OK"; } } } else { $changed = "lost remote"; } } else { $changed = "lost locally"; } } else { $status = "[$installed_module:$installed_file]"; } } else { if (-e $place) { $status = "(space occupied)"; if (-e "$rvc_directory/$module/$file") { if (compare("$rvc_directory/$module/$file",$place)) { $changed = "(differ)"; } else { $changed = "(identical)"; } } else { $changed = "lost locally"; } } else { $status = "uninstalled"; } } printf $status_linef, $module,$file,$status,$changed,$place; } sub mystat { my ($file) = @_; my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($file); my $perms = sprintf "%04o", $mode & 07777; my $uname = getpwuid($uid); defined($uname) or $uname="root"; $uname eq "" and $uname = "root"; my $gname = getgrgid($gid); defined($gname) or $gname="root"; $gname eq "" and $gname = "root"; return "$uname $gname $perms"; } ## The heuristics for split_directory (see there) sub uncat { my ($composed,$firstpartorig,$first,$rest) = @_; print "Splitting $composed into $first and $rest\n"; open(DIFF,'-|',"diff -e \Q$composed\E \Q$firstpartorig\E") or die "diff failed\n"; my $firstcmd = ; close(DIFF); system 'cp',$composed,$first and die "cp $composed $first failed\n"; open(ED,'|-',"ed -s \Q$first\E") or die "ed failed\n"; chomp($firstcmd); print " First ed command: >$firstcmd<\n"; $firstcmd =~ /^(\d+),(\d+)d$/ or die "Unexpected first edit command\n"; my $start = $1; my $end = $2; print ED "$start,\$d\n"; print ED "w\nq\n"; close(ED) or die "ed failed\n"; system 'cp',$composed,$rest and die "cp $composed $rest failed\n"; open(ED,'|-',"ed -s \Q$rest\E") or die "ed failed\n"; print ED "1," . ($start - 1) . "d\n"; print ED "w\nq\n"; close(ED) or die "ed failed\n"; } sub get_part_number { my $dir = shift; my $i=1; for (;-e "$dir/file.$i"; $i++){}; return $i; } sub combine_split_data { my $dir = shift; my @files = (); my $i=0; for(;-e "$dir/part.$i"; $i++) { push @files, "\Q$dir\E/split.$i"; }; system "cat @files > $dir/data" and die "Combining split files in $dir failed.\n"; set_owner("$dir/data"); } sub combine_data { my $dir = shift; my @files = (); my $i=0; for(;-e "$dir/part.$i"; $i++) { push @files, "\Q$dir\E/part.$i"; }; system "cat @files > $dir/data" and die "Combining files in $dir failed.\n"; set_owner("$dir/data"); } ## This routine should find that splitting of the remote places ## that minimies the sum of the edit distances between the part.i ## files and their corresponding split.i file ## ## Currently we just use a wild heuristics that seems to work OK in ## practise. sub split_directory { my ($dir,$remote) = @_; my ($i,$count); for ($count=0; -e "$dir/part.$count"; $count++) {}; $count < 2 and return 0; ## Not a multi file print "== Preparing record by splititng file...\n"; uncat($remote,"$dir/part.0","$dir/split.0","$dir/split.1"); for ($i=1; $i < $count -1; $i++) { uncat("$dir/split.$i","$dir/part.$i","$dir/split." . ($count+1),"$dir/split." . ($i+1)); move("$dir/split." . ($count+1),"$dir/split." . $i); } print "== done\n\n"; return $count; } ### copy 'from' to 'to' or die horribly sub do_cp { my ($from,$to) = @_; copy($from,$to) or die "Failed to copy $from to $to ($?)\n"; } ## dito for chown sub do_chown { my ($username,$groupname,$file) = @_; my $uid = getpwnam($username); my $gid = getgrnam($groupname); chown $uid,$gid,$file or die "chown $uid:$gid $file failed (i.e., user $username and group $groupname) ($?)\n"; } ## ..and for chmod sub do_chmod { my ($perms,$file) = @_; chmod oct($perms), $file or die "chmod $perms $file failed ($?)\n"; } sub create_path { my ($place,$module) = @_; open(DIRS, "< $rvc_directory/$module/$rvc_dirs") or die "failed opening $rvc_directory/$module/$rvc_dirs ($!).\n"; my %dirs = (); foreach() { chomp; /^(\S+) (.*)$/ or die "Failed to parse dirs line $_\n"; $dirs{$1}=$2; } close(DIRS) or die "failed to close $rvc_directory/$module/$rvc_dirs after reading ($!).\n"; my @dirstocheck = (); $place =~ /^(.*)\/[^\/]*/ or die "Not an absolute path $place\n"; $place = $1; while($place) { unshift @dirstocheck, $place; $place =~ /(.*)\/[^\/]*$/ or die "Malformed directory name $place\n"; $place = $1; } foreach(@dirstocheck) { -d $_ or do { defined($dirs{$_}) or die "$_ does not exist and is not mentioned in the directory file of module $module\n"; $dirs{$_} =~ /^(\S+)\s+(\S+)\s+(\S+)/ or die "Couldn't parse permissions string"; my ($owner,$group,$mod) = ($1,$2,$3); print " Creating $_ as $owner:$group $mod\n"; mkdir $_ or die "Failed to create $_\n"; do_chown($owner,$group,$_); do_chmod($mod,$_); } } }