;# ;# Copyright (c) 1995-1997 ;# Ikuo Nakagawa. All rights reserved. ;# ;# Redistribution and use in source and binary forms, with or without ;# modification, are permitted provided that the following conditions ;# are met: ;# ;# 1. Redistributions of source code must retain the above copyright ;# notice unmodified, this list of conditions, and the following ;# disclaimer. ;# 2. Redistributions in binary form must reproduce the above copyright ;# notice, this list of conditions and the following disclaimer in the ;# documentation and/or other materials provided with the distribution. ;# ;# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ;# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ;# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ;# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS ;# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, ;# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT ;# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR ;# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ;# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE ;# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, ;# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;# ;# $Id: Farm.pm,v 1.21 1997/09/26 04:48:32 ikuo Exp $ ;# package Fan::Farm; use strict; use vars qw($VERSION $LOG); use Carp; use Fan::MD5; use Fan::Cool; use Fan::Scan; use AutoLoader 'AUTOLOAD'; $VERSION = '0.04'; $LOG = 5; ;# A special marker for AutoSplit. 1; __END__ ;# ;# Destroy FTP Archive Revision Manager object. ;# sub DESTROY ($) { my $p = shift; # myself. my $dir = $p->{work_directory}; # farm directory. # Unlink all temporary files, including the lock file # for the working directory. for my $file (keys %{$p->{tempfiles}}) { warn("Fan::Farm unlink $file...\n") if $LOG > 5; unlink($file) if -e $file; } # Log message. carp("Fan::Farm DESTROYING $p") if $LOG > 5; } ;# ;# Creating FTP Archive Revision Managemer object. ;# ;# Usage: THIS::CLASS->new(directory_name); ;# ;# where directory_name is the directory who contains index ;# or step files. ;# ;# Index files are named as: index.1, index.2, ... ;# And step files: step.1, step.2, ... ;# step.n contains all differences between index.n and ;# index.(n+1), that is, we can generate index.(n+1) if we ;# have index.n and step.n. ;# ;# There may be also local index file, named `index.local' ;# which contains index of this work directory itself. ;# sub new ($$) { my $this = shift; my $class = ref($this) || $this; my $dir = shift; # Check directory definition. if ($dir eq '') { carp("Fan::Farm directory not defined") if $LOG > 5; return undef; } # Check directory existence. if (! -d $dir) { carp("Fan::Farm no directory: $dir") if $LOG > 4; return undef; } # Try bless this object before we lock this directory. my $p = bless { work_directory => $dir }, $class; ref($p) || croak("Fan::Farm can't bless object"); carp("Fan::Farm CREATING $p") if $LOG > 5; # Add the lock file to the hash of temporary files. my $lock = "$dir/.LOCK"; $p->{tempfiles}->{$lock}++; # Try to lock directory. unless (plock($lock, 30)) { carp("Fan::Farm can't lock directory: $dir") if $LOG > 4; return undef; } # Try to get revision. unless ($p->getrev) { carp("Fan::Farm can't get revision") if $LOG > 4; return undef; } # Return myself. $p; } ;# ;# farm_begin ... ;# initialize farm index updater. ;# sub d_begin ($) { my $p = shift; my $dir = $p->{work_directory}; my $fh; # clear $p->{pim_stack} = []; $p->{pim_depth} = 0; $p->{pim_modified} = 0; # Check revisions... exists($p->{pim_index_new}) || $p->getrev or return undef; # check revision numbers my $rev = $p->{pim_index_new}; $p->{pim_index} = "$dir/index.$rev"; $p->{pim_index_tmp} = $p->{pim_index}.'.tmp'; local *TMPINDEX; unless (open(TMPINDEX, ">$p->{pim_index_tmp}")) { carp("d_begin open($p->{pim_index_tmp}): $!"); return undef; } $p->{pim_index_handle} = *TMPINDEX; # information log... warn("Farm::begin: open $p->{pim_index_tmp}: o.k.\n") if $LOG > 5; # CAUTION: # WE CAN GENERATE STEP FILE EVEN IF WE ARE CREATING A NEW # INDEX FILE, BUT WE SHOULD GENERATE STEP FILE FROM INDEX # FILES... return 1; # shall we go step mode? $rev > 1 || return 1; # we are required step mode. $rev--; $p->{pim_step} = "$dir/step.$rev"; $p->{pim_step_tmp} = $p->{pim_step}.'.tmp'; # local *TMPSTEP; unless (open(TMPSTEP, ">$p->{pim_step_tmp}")) { carp("Farm::begin: open($p->{pim_step_tmp}): $!"); return undef; } $p->{pim_step_handle} = *TMPSTEP; # information log... warn("Farm::begin: open $p->{pim_step_tmp}: o.k.\n") if $LOG > 5; # success 1; } ;# ;# farm_add ;# add a file (Attrib object) to updater ;# sub d_add ($$) { my $p = shift; my $fh_index = $p->{pim_index_handle}; my $fh_step = $p->{pim_step_handle}; # check file handle first. unless (defined($fh_index)) { carp("Farm::add: has no file handle"); return undef; } my $y = shift; # Attribute. my $t = $y->type; # Abbrev for type of $y. my $f = $y->flag; # Abbrev for flag of $y. # At first, check the depth of the current tree. if ($t eq 'D') { warn("Farm::add: down to \"".$y->name."\"\n") if $LOG > 6; $p->{pim_depth}++; } elsif ($t eq 'U') { warn("Farm::add: up to \"..\"\n") if $LOG > 6; $p->{pim_depth}--; } else { warn("Farm::add: checking ".$y->name." (type=$t)...\n") if $LOG > 6; } # Check type/flag for given attribute. if ($t eq '.') { return $p->d_end; # terminator will be printed. } # check if we have any modification. if ($f ne '') { $p->{pim_modified} = 1; } # step mode ? if (!defined($fh_step)) { ; # no step mode } elsif ($t eq 'D' && $y->name eq '.') { print $fh_step $y->to_line."\n"; } elsif ($f eq '') { if ($t eq 'D') { push(@{$p->{pim_stack}}, $y); } elsif ($t eq 'U') { if (@{$p->{pim_stack}}) { pop(@{$p->{pim_stack}}); } else { print $fh_step "U\n"; } } } else { while (@{$p->{pim_stack}}) { my $a = shift(@{$p->{pim_stack}}); print $fh_step $a->to_line."\n"; } print $fh_step $y->to_line."\n"; } # index mode if ($f ne '-') { # ignore removed files. $y->flag(''); # clear flag print $fh_index $y->to_line."\n"; $y->flag($f); # restore. } # success 1; } ;# ;# farm_end ;# terminate updater ;# sub d_end ($) { my $p = shift; my $fh_index = $p->{pim_index_handle}; my $fh_step = $p->{pim_step_handle}; # Check file handle unless (defined($fh_index)) { carp("Farm::end: no file handle defined") if $LOG > 5; return undef; } # Check depth of working tree. if ($p->{pim_depth} < 1) { carp("Farm::end: ouch! pim_depth is too small") if $LOG > 4; close($fh_index); # We must close output file. delete($p->{pim_index_handle}); unlink($p->{pim_index_tmp}); warn("Farm::end: $p->{pim_index_tmp} unlinked.\n") if $LOG > 5; if (defined($fh_step)) { close($fh_step); delete($p->{pim_step_handle}); unlink($p->{pim_step_tmp}); warn("Farm::end: $p->{pim_step_tmp} unlinked.\n") if $LOG > 5; } return undef; } # Greater depth means "terminated abnormally" if ($p->{pim_depth} > 1) { carp("Farm::end: pim_depth > 1, index abort") if $LOG > 3; close($fh_index); # We must close output file. delete($p->{pim_index_handle}); unlink($p->{pim_index_tmp}); $fh_index = ''; warn("Farm::end: $p->{pim_index_tmp} unlinked.\n") if $LOG > 5; if (defined($fh_step)) { warn("Farm::end: try to fix step files.....\n") if $LOG > 5; while ($p->{pim_depth} > 1) { if (@{$p->{pim_stack}}) { pop(@{$p->{pim_stack}}); } else { print $fh_step "U\n"; } $p->{pim_depth}--; } } } # put terminator, and close output file. if (defined($fh_index)) { print $fh_index ".\n"; close($fh_index); delete($p->{pim_index_handle}); warn("Farm::end: $p->{pim_index_tmp} was closed.\n") if $LOG > 5; # modified flag my $mod = 1; # check modification if needed. if ($p->{pim_index_max} > 0) { # exists last one my $rev = $p->{pim_index_max}; my $dir = $p->{work_directory}; my $old = "$dir/index.$rev"; my $new = $p->{pim_index_tmp}; my $out = "$dir/step.$rev"; my $tmp = "$out.tmp"; $mod = &Fan::Scan::scan_mkdiff($tmp, $old, $new); if (!defined($mod)) { warn("Farm::end: can't generate step file" . ", use this index.\n") if $LOG >5; warn("Farm::end: unlink $tmp\n") if $LOG > 5; $mod = 1; } elsif ($mod == 0) { # no modification... warn("Farm::end: no change, $tmp removed.\n") if $LOG > 5; unlink($tmp); } elsif (!rename($tmp, $out)) { carp("Farm::end: rename $tmp -> $out: $!"); unlink($tmp); } } # check index modification... if ($mod == 0) { unlink($p->{pim_index_tmp}); warn("Farm::end: no change" . ", $p->{pim_index_tmp} removed.\n") if $LOG > 5; } elsif (rename($p->{pim_index_tmp}, $p->{pim_index})) { warn("Farm::end: rename to $p->{pim_index}: o.k.\n") if $LOG > 5; } else { carp("Farm::end: rename($p->{pim_index}): $!"); } } # step mode, skipped in this version. if (0 && defined($fh_step)) { print $fh_step ".\n"; close($fh_step); delete($p->{pim_step_handle}); warn("Farm::end: $p->{pim_step_tmp} was closed.\n") if $LOG > 5; if ($p->{pim_modified} == 0) { unlink($p->{pim_step_tmp}); warn("Farm::end: no chage" . ", $p->{pim_step_tmp} removed.\n") if $LOG > 5; } elsif (rename($p->{pim_step_tmp}, $p->{pim_step})) { warn("Farm::end: rename to $p->{pim_step}: o.k.\n") if $LOG > 5; } else { carp("Farm::end: rename($p->{pim_step}): $!"); } } # success, but really? 1; } ;# Master mode: ;# Generate full index of the given directory. ;# (as the newest index). ;# ;# this routine should be called after `update' routine. ;# ;# Usage: ;# $p->generate(directory); ;# where `directory' is the target directory. ;# sub generate ($$) { my $p = shift; my $dir = $p->{work_directory}; my $target = shift; # Check revisions... exists($p->{pim_index_new}) || $p->getrev or return undef; # Get revision... my $rev = $p->{pim_index_new}; my $outp = "$dir/index.$rev"; my $temp = "$outp.tmp"; # open temorary output file. unless (&Fan::Scan::scan_mklist($temp, $target)) { carp("generate:Fan:: Scan::mklist failure"); return undef; } # try compare... $rev--; if (exists($p->{pim_index_max}) && $p->{pim_index_max} == $rev) { my $old = "$dir/index.$rev"; my $step = "$dir/step.$rev"; my $tmps = "$step.tmp"; my $mod = &Fan::Scan::scan_mkdiff($tmps, $old, $temp); if (!defined($mod)) { warn("generate: scan_mkdiff failure, skipped.\n"); } elsif ($mod == 0) { unlink($tmps); warn("generate: no change, $tmps removed.\n") if $LOG > 5; unlink($temp); warn("generate: no change, $temp removed.\n") if $LOG > 5; return 1; # this is success case. } else { if (rename($tmps, $step)) { warn("generate: rename $tmps -> $step: o.k.\n") if $LOG > 5; } else { carp("generate: rename $tmps -> $step: $!"); unlink($tmps); } } } # now, try to rename. unless (rename($temp, $outp)) { carp("generate: rename $temp -> $outp: $!"); unlink($temp); return undef; } # warn("generate: rename $temp -> $outp: o.k.\n") if $LOG > 5; # success 1; } ;# Master and slave mode: ;# Normalize index directory. ;# (a) generate all step files. ;# (b) index files are removed except the newest one. ;# (but, show warning messages only, in this version.) ;# (c) all step files remain. ;# sub normalize ($;$) { my $p = shift; my $clean = shift; my $dir = $p->{work_directory}; # Force to check revisions... $p->getrev or return undef; # Check existence of index files... if (!exists($p->{pim_index_max})) { # we have no index file. carp("normalize: have no index file") if $LOG > 4; return undef; } # Update index files and calculate revisions again, if needed. if (exists($p->{pim_step_max})) { if ($p->{pim_step_max} >= $p->{pim_index_max}) { $p->update && $p->getrev or return undef; } } # Next, check step files. my $max_i = $p->{pim_index_max}; # DOES exist my $rev = $p->{pim_index_min}; # DOES exist $rev = $p->{pim_step_max} + 1 if exists($p->{pim_step_max}); # loop. while ($rev < $max_i) { my $out = "$dir/step.$rev"; my $tmp = "$out.tmp"; my $old = "$dir/index.$rev"; $rev++; my $new = "$dir/index.$rev"; unless (defined(&Fan::Scan::scan_mkdiff($tmp, $old, $new))) { carp("normalize: can't make diff"); unlink($tmp); return undef; } unless (rename($tmp, $out)) { carp("normalize: rename $tmp -> $out: $!"); unlink($tmp); return undef; } warn("normalize: rename $tmp -> $out: o.k.\n") if $LOG > 5; } # unlink redundant files... for ($rev = $p->{pim_index_min}; $rev < $max_i; $rev++) { if ($clean) { unlink("$dir/index.$rev"); warn("normalize: unlink $dir/index.$rev\n") if $LOG > 5; } else { warn("normalize: we should unlink $dir/index.$rev\n") if $LOG > 5; } } # get revision numbers once more. unless ($p->getrev) { carp("normalize: can't update revision numbers"); return undef; } # shall we clean up? $clean || return 1; # abbrev for revision numbers. my $min_s = 0; my $min_i = 0; # Initialize... $min_s = $p->{pim_step_min} if exists($p->{pim_step_min}); $min_i = $p->{pim_index_min} if exists($p->{pim_index_min}); # Open working directory local *DIR; unless (opendir(DIR, $dir)) { carp("normalize: opendir($dir): $!") if $LOG > 4; return undef; } # Search invalid step/index files my $e; while (defined($e = readdir(DIR))) { if ($e =~ /^step\.(\d+)(\.Z|\.gz)?$/) { if (!$min_s || $1 < $min_s) { warn("normalize: unlink $dir/$e\n") if $LOG > 5; # unlink("$dir/$e"); } } elsif ($e =~ /^index\.(\d+)(\.Z|\.gz)?$/) { if (!$min_i || $1 < $min_i) { warn("normalize: unlink $dir/$e\n") if $LOG > 5; # unlink("$dir/$e"); } } else { ; # simply ignored... } } closedir(DIR); # success code. 1; } ;# Master and slave mode: ;# Generate the newest index file from step files. ;# ;# Usage: ;# $p->updage; ;# sub update ($) { my $p = shift; my $dir = $p->{work_directory}; # Check revisions... exists($p->{pim_index_new}) || $p->getrev or return undef; # Check existence of index files... if (!exists($p->{pim_index_max})) { # we have no index file. carp("update: can't find base index file.\n") if $LOG > 4; return undef; } # Next, check step files. if (!exists($p->{pim_step_max})) { # no step file. warn("update: no step file.\n") if $LOG > 5; return 1; # seems good. } # Check revision numbers. if ($p->{pim_step_max} < $p->{pim_index_max}) { warn("update: revision check o.k.\n") if $LOG > 5; return 1; # seems good. } # Now, we can generate the newest index file. my $min = $p->{pim_index_max}; # we have... my $max = $p->{pim_step_max}; # we have... my $new = $max + 1; # Open the index who has maximum number. my $orig = "$dir/index.$min"; my @diff = (); while ($min <= $max) { push(@diff, "$dir/step.$max"); $min++; } my $outp = "$dir/index.$new"; my $temp = "$outp.tmp"; # update by Fan::Scan::scan_update. unless (&Fan::Scan::scan_update($temp, $orig, @diff)) { warn("update: Fan::Scan::scan_update failure\n"); unlink($temp); # unlink temporary file return undef; } # now try to rename... unless (rename($temp, $outp)) { carp("update: rename($outp): $!") if $LOG > 4; unlink($temp); # unlink temporary file return undef; } # debug log warn("update: rename $temp -> $outp: o.k.\n") if $LOG > 5; # success 1; } ;# ;# a fileter who pickup only step / index files. ;# sub farm_filter { my $y = shift; # Fan::Attrib object. my $t = $y->type; # type abbrev if ($t eq 'F') { my $n = $y->name; if ($n !~ /^(step|index)\.\d+(\.Z|\.gz)?$/) { warn("farm_filter: $n was skipped.\n") if $LOG > 6; return undef; } } 1; } ;# Master mode: ;# Generate local index of the index directory. ;# ;# Usage: ;# $p->genindex; ;# sub genindex ($) { my $p = shift; my $dir = $p->{work_directory}; my $scan = Fan::Scan->new( scan_type => 'LOCAL', scan_dir => $dir ); unless (ref($scan)) { carp("genindex: can't create Scan object"); return undef; } unless ($scan->add_filter(\&farm_filter)) { carp("genindex: can't add filter"); return undef; } my $local_index = "$dir/index.local"; my $tmp_index = "$local_index.tmp"; local *TEMP; unless (open(TEMP, ">$tmp_index")) { carp("genindex: open($tmp_index): $!"); return undef; } warn("genindex: open $tmp_index: o.k.\n") if $LOG > 5; my $y; while (defined($y = $scan->get)) { $y->fill_checksum; print TEMP $y->to_line."\n"; } close(TEMP); unless(rename($tmp_index, $local_index)) { carp("genindex: rename($local_index): $!"); unlink($tmp_index); warn("genindex: rename failed, unlink $tmp_index...\n") if $LOG > 5; return undef; } warn("genindex: rename to $local_index: o.k.\n"); 1; } ;# Slave mode: ;# Synchronize index directory to the master. ;# ;# Usage: ;# $p->synch('/ftp/db/foo/index.local', $ftp); ;# where $ftp supports $ftp->get(remote-file, local-file), and ;# '/db/foo/index.local' is the local-index filename in localhost. ;# sub synch ($$$$) { my $p = shift; # myself my $net = shift; # must support $net->get(remote, local). my $pre = shift; # prefix of remote files. my $start = shift; # file name we will start from. my $dir = $p->{work_directory}; # check local file. unless (-f $start) { carp("synch: file $start not found"); return undef; } # warn("synch: local file $start: o.k.\n") if $LOG > 5; # scanner my $scan = Fan::Scan->new( scan_type => 'INDEX', scan_index => $start, ); unless (ref($scan)) { carp("synch: can't create index scanner"); return undef; } # add filter unless ($scan->add_filter(\&farm_filter)) { carp("synch: can't add filter(index)"); return undef; } # local side scanner... my $ours = Fan::Scan->new( scan_type => 'LOCAL', scan_dir => $p->{work_directory} ); unless (ref($ours)) { carp("synch: can't create local scanner"); return undef; } # add filter unless ($ours->add_filter(\&farm_filter)) { carp("synch: can't add filter(local)"); return undef; } # parsing... # this is very simple mirror - only check size and checksum. my $max_y = undef; my $max_i = 0; my $a; my $b; while (($a, $b) = $ours->getcmp($scan)) { my $z; my $t; my $flag = 0; if (!defined($a) && !defined($b)) { confess("synch: UNEXPECTED CASE"); } elsif (!defined($a)) { $z = $b; $t = $z->type; $flag++; #warn("synch: local does not have $t $z->{y_name}.\n"); } elsif (!defined($b)) { $z = $a; $t = $z->type; $flag--; #warn("synch: remote does not have $t $z->{y_name}.\n"); } else { $z = $b; $t = $z->type; if ($t eq '.') { ; } elsif ($a->type ne $t) { $flag++; #warn("synch: type mismatch $t $z->{y_name}.\n"); } elsif ($t eq 'D') { ; } elsif ($t eq 'U') { ; } elsif ($t eq 'L') { $flag++ if $a->linkto ne $b->linkto; #warn("synch: linkto mismatch $t $z->{y_name}.\n"); } elsif ($t ne 'F') { carp("synch: UNKNOWN TYPE $t"); return undef; } elsif (!$a->fill_checksum) { carp("synch: can't get checksum of " . $a->realpath); return undef; } elsif ($a->size != $b->size) { $flag++; #warn("synch: size mismatch $t $z->{y_name}.\n"); } elsif ($b->checksum eq '') { carp("synch: NO CHECKSUM for ".$b->path); return undef; } elsif ($a->checksum ne $b->checksum) { $flag++; #warn("synch: checksum mismatch $t $z->{y_name}.\n"); } else { ; } } # check end. if ($t eq '.') { last; # done } # abbrev for path name my $path = "$dir/".$z->path; # check index file before $flag check. if ($t eq 'F' && $z->name =~ /^index\.(\d+)/) { ($max_y, $max_i) = ($z, $1) if $max_i < $1; next; } # check flag. we only check modified files. $flag > 0 or next; # check types... if ($t eq 'D') { unlink($path) if -e $path; unless (mkdir($path, 0755)) { carp("synch: mkdir($path): $!"); return undef; } warn("synch: mkdir($path, 0755): o.k.\n") if $LOG > 5; } elsif ($t eq 'U') { ; } elsif ($t eq 'L') { unlink($path) if -e $path; symlink($z->linkto, $path); warn("synch: symlink($path): o.k.\n") if $LOG > 5; } elsif ($t eq 'F' && $z->name =~ /^step\./) { unless ($net->get($pre.'/'.$z->path, $path)) { carp("synch: GET($path): ".$net->error); next; # skip this... } chmod((defined($z->perm) ? $z->perm : 0644), $path); my $m = $z->mtime; if ($m > 0) { utime($m, $m, $path); } warn("synch: get $path: o.k.\n") if $LOG > 5; } else { ; # what? } } # calculate revision numbers... $p->getrev || return undef; # try update. unless ($p->update) { warn("synch: can't update $dir, try continue...\n") if $LOG > 4; # continue... } # calculate revision numbers once more $p->getrev || return undef; # check remote side index file. unless (ref($max_y)) { carp("synch: no index file in remote"); return undef; } # relative path name my $path = $max_y->path; # check index number... # same index? if ($max_i == $p->{pim_index_max}) { if ($max_y->name !~ /^index\.(\d+)$/) { if ($LOG > 4) { warn("synch: remote index is compressed.\n"); warn("synch: skip checksum check.\n"); } return 1; } # or checksum test. if (MD5File("$dir/$path") eq $max_y->checksum) { warn("synch: checksum($dir/$path) ok, very good!\n") if $LOG > 5; return 1; } else { # checksum error warn("synch: checksum error, unlink $dir/$path.\n") if $LOG > 5; unlink("$dir/$path"); } } elsif ($max_i < $p->{pim_index_max}) { if ($LOG > 5) { warn("synch: local index($p->{pim_index_max}) was " . "greater than remote($max_i)\n"); warn("synch: this may be good...\n"); } return 1; } # remaining case is ($max_i > $p->{pim_index_max}), # or checksum error if (exists($p->{pim_index_max}) && $max_i > $p->{pim_index_max}) { warn("synch: remtoe has greater index($max_i)" ." than local($p->{pim_index_max}).\n") if $LOG > 4; } if ($LOG > 4) { warn("synch: try to get $path...\n"); } unless ($net->get("$pre/$path", "$dir/$path")) { carp("synch: GET($path): failed"); return undef; } unless ($max_y->checksum eq MD5File("$dir/$path")) { warn("synch: CHECKSUM($path) mismatch, unlink it.\n"); unlink("$dir/$path"); return undef; } chmod(0644, "$dir/$path"); my $m = $max_y->mtime; if ($m > 0) { utime($m, $m, "$dir/$path"); } # success to small mirror, get revisions again. $p->getrev; } ;# Get revision number for this package. ;# A file "step.i" is a diff file between "index.i" and ;# "index.(i+1)", that is, we can generate "index.12" ;# from "index.11" and "step.11". ;# ;# If this routine returns success code (== 1), you can ;# always access to $p->{pim_index_new}; ;# sub getrev ($) { my $p = shift; my $dir = $p->{work_directory}; # Clear old revision numbers. delete($p->{pim_index_max}); delete($p->{pim_index_min}); delete($p->{pim_index_new}); delete($p->{pim_step_max}); delete($p->{pim_step_min}); # try to open directory... local *DIR; unless (opendir(DIR, $dir)) { carp("getrev: opendir($dir): $!") if $LOG > 4; return undef; } # local variables to search revisions. my %steps = (); my %indexes = (); my $e; # read directory entries, and search `index.n'. while (defined($e = readdir(DIR))) { if ($e =~ /^step\.(\d+)(\.Z|\.gz)?$/) { $steps{$1 + 0}++; } elsif ($e =~ /^index\.(\d+)(\.Z|\.gz)?$/) { $indexes{$1 + 0}++; } else { ; # simply ignored. } } closedir(DIR); # sort steps in reverse order... my @steps = sort { $b <=> $a } keys %steps; my @indexes = sort { $b <=> $a } keys %indexes; # Maximum / minimum index of step files. my $max_s = 0; my $min_s = 0; # Check the chain of step files. # Search largest continuous block. if (@steps) { $min_s = $max_s = shift(@steps); while (@steps) { $min_s - 1 == shift(@steps) || last; $min_s--; } } # Indexes for index files. my $max_i = 0; my $min_i = 0; # Check the chain of step files. # Search largest continuous block. if (@indexes) { $min_i = $max_i = shift(@indexes); while (@indexes) { $min_i - 1 == shift(@indexes) || last; $min_i--; } } # Validation if ($max_i == 0 && $max_s == 0) { # nothing found. $p->{pim_index_new} = 1; } elsif ($max_i < $min_s) { # unexpected case... $p->{pim_index_new} = $max_s + 2; # skip one. } elsif ($max_s == 0 || $min_i > $max_s + 1) { # step has no meaning $p->{pim_index_max} = $max_i; # we have... $p->{pim_index_min} = $min_i; # we have... $p->{pim_index_new} = $max_i + 1; # we will... } else { # seems good. $p->{pim_step_max} = $max_s; # we have... $p->{pim_step_min} = $min_s; # we have... $p->{pim_index_max} = $max_i; # we have... $p->{pim_index_min} = $min_i; # we have... if ($max_i > $max_s) { # $max_i is maximum. $p->{pim_index_new} = $max_i + 1; # we will... } else { # we can generate ($max_s + 1). $p->{pim_index_new} = $max_s + 2; # we will... } } # return success code. 1; } ;# end of Fan::Farm module