File Coverage

blib/lib/Dir/Rocknroll.pm
Criterion Covered Total %
statement 86 400 21.5
branch 1 142 0.7
condition 0 33 0.0
subroutine 29 56 51.7
pod n/a
total 116 631 18.3


line stmt bran cond sub pod time code
1             package Dir::Rocknroll ;
2              
3             ####################################################
4             #
5             # rocknroll : Rsync fOr baCKup and Roll
6             #
7             # Jacquelin Charbonnel - CNRS/Mathrice/LAREMA - 2006-09-04
8             #
9             # $Id: Rocknroll.pm 488 2012-09-01 19:14:00Z jaclin $
10             #
11             ####################################################
12              
13             require Exporter ;
14             @ISA = qw(Exporter);
15             @EXPORT=qw() ;
16             @EXPORT_OK = qw( );
17              
18 1     1   21947 use 5.006;
  1         4  
  1         40  
19 1     1   5 use Carp;
  1         2  
  1         77  
20 1     1   5 use strict;
  1         12  
  1         41  
21              
22             #our $VERSION = "0.".eval{'$Rev: 488 $'=~/(\d+)/;$1;} ;
23             our $VERSION = 0.34 ;
24              
25 1     1   966 use Data::Dumper ;
  1         15905  
  1         83  
26 1     1   8282 use Sys::Syslog ;
  1         41323  
  1         105  
27 1     1   1136 use File::Path::Tiny;
  1         1090  
  1         37  
28 1     1   1830 use Getopt::Long ;
  1         29129  
  1         8  
29 1     1   324 use File::Basename ;
  1         2  
  1         63  
30 1     1   908 use FileHandle ;
  1         17921  
  1         7  
31 1     1   1488 use DirHandle ;
  1         524  
  1         24  
32 1     1   891 use FindBin ;
  1         1037  
  1         37  
33 1     1   1015 use Net::SMTP ;
  1         90320  
  1         51  
34 1     1   1056 use Sys::Hostname;
  1         1443  
  1         70  
35 1     1   1200 use Config::General ;
  1         17408  
  1         94  
36 1     1   1067 use Config::General::Extended ;
  1         2805  
  1         72  
37 1     1   1046 use Dir::Which q/which/;
  1         1014  
  1         232  
38              
39             my $this_prog = 'rocknroll' ;
40             my $NEW_EXT = "_running_snapshot_" ;
41             my $OLD_EXT = "_snapshot_to_delete_" ;
42             my $CONFFILE = "$this_prog.conf" ;
43             my $CONFPATH = $FindBin::Bin.":".$FindBin::Bin."/../etc:/etc:/etc/${this_prog}.d" ;
44             my $FACILITY = "local7" ;
45             my %arg_conf ;
46              
47             my $default_conf = {
48             "continue" => 0
49             , "debug" => 0
50             , "dry-run" => 0
51             , "refresh" => 0
52             , "link-dest" => ""
53             , "mail_from" => 'root@localhost'
54             , "mail_to" => 'root@localhost'
55             , "max_runtime" => 360 # 6h
56             , "no-links" => 0
57             , "no-roll" => 0
58             , "rsync_path" => "/usr/bin/rsync"
59             , "rsync_retcode_ok" => 0
60             , "rsync_retcode_warn" => 24
61             , "ro" => ["--stats"]
62             , "ro_default" => "--hard-links --archive -e ssh"
63             , "send_warn" => 0
64             , "smtp_server" => "localhost"
65             , "update" => 0
66             , "use_syslog" => 1
67             , "verbose" => 0
68             } ;
69              
70             my ($log,@files,$init,@excludes,@rsync_options,$dry_run,$config,$config_file,$arg) ;
71              
72             ############################################################
73              
74             {
75             package _Log ;
76             require Exporter ;
77             our @ISA = qw(Exporter);
78             our @EXPORT=qw() ;
79              
80 1     1   7 use Carp ;
  1         2  
  1         61  
81 1     1   6 use Data::Dumper ;
  1         4  
  1         53  
82 1     1   6 use Dir::Which q/which/ ;
  1         2  
  1         46  
83 1     1   6 use Sys::Syslog ;
  1         2  
  1         1057  
84              
85             #my ($DEBUG,$INFO,$WARN,$ERR) = (1,2,3,4) ;
86              
87             #--------------------
88             sub new
89             {
90 0     0     my($type,$cmdline,$level) = @_ ;
91 0           my($this) ;
92              
93             # %h = map { /^-/ ? lc(substr($_,1)) : $_ ; } %h ;
94              
95 0           $this->{"level"} = $level ;
96 0           $this->{"cmdline"} = $cmdline ;
97 0           $this->{"log"} = [] ;
98 0           $this->{"criticity"} = 0 ;
99              
100 0           bless $this,$type ;
101              
102 0           openlog($this_prog,"ndelay,pid", "local0") ;
103 0           return $this ;
104             }
105              
106             sub set_level
107             {
108 0     0     my($this,$level) = @_ ;
109 0           $this->{"level"} = $level ;
110             }
111              
112             sub send
113             {
114 0     0     my ($this)=@_ ;
115              
116 0 0         return unless scalar(@{$this->{"log"}}>0) ;
  0            
117              
118 0           my $smtp_server = $config->get("smtp_server") ;
119 0           my $smtp ;
120              
121 0 0         if ($smtp = Net::SMTP->new($smtp_server))
122             {
123             #die Dumper $smtp_server ;
124 0           $smtp->mail($config->get("mail_from"));
125 0           $smtp->to($config->get("mail_to"));
126              
127 0           $smtp->data();
128 0           $smtp->datasend("From: ".$config->get("mail_from")."\n");
129 0           $smtp->datasend("To: ".$config->get("mail_to")."\n");
130 0           $smtp->datasend("Subject: $this_prog ".$this->{"criticity"}."\n");
131 0           $smtp->datasend("\n");
132 0           $smtp->datasend("running command : $this_prog ".$this->{"cmdline"}."\n");
133 0           $smtp->datasend("output :\n");
134 0           $smtp->datasend(join("\n",@{$this->{"log"}}));
  0            
135 0           $smtp->dataend();
136 0           $smtp->quit;
137             }
138             else
139             {
140 0           $this->warn("can't connect to the SMTP server '$smtp_server'") ;
141 0           print join("\n",@$log) ;
142             }
143             }
144             #--------------------
145             sub debug
146             {
147 0     0     my($this,$msg) = @_ ;
148 0 0         print "$msg\n" if $this->{"level"}==2 ;
149             }
150             sub info
151             {
152 0     0     my($this,$msg) = @_ ;
153 0 0         if ($this->{"level"}>=1)
154             {
155 0 0         if ($config->get("use_syslog")==1) { syslog("info",$msg) ; }
  0            
156 0           print "$msg\n" ;
157             }
158             }
159             sub warn
160             {
161 0     0     my($this,$msg) = @_ ;
162 0 0         if ($config->get("use_syslog")==1) { syslog("warn",$msg) ; }
  0            
163 0           else { print "$msg\n" ; }
164             }
165             sub crit
166             {
167 0     0     my($this,$msg) = @_ ;
168 0 0         if ($config->get("use_syslog")==1) { syslog("crit",$msg) ; }
  0            
169 0           die "$msg\nExecution aborted !\n" ;
170             }
171             sub warn_by_mail
172             {
173 0     0     my($this,$msg) = @_ ;
174              
175 0           $this->{"criticity"} = "warn" ;
176 0 0 0       if ($config->get("mail_to")=~/\S/ && $config->get("send_warn")==1)
177             {
178 0           push(@{$this->{"log"}},"WARN: $msg") ;
  0            
179 0 0         syslog("info",split(/\n/,$msg)) if $config->get("use_syslog")==1 ;
180             }
181             else
182 0           { $this->warn($msg) ; }
183             }
184             sub crit_by_mail
185             {
186 0     0     my($this,$msg) = @_ ;
187              
188 0           $this->{"criticity"} = "crit" ;
189 0 0         if ($config->get("mail_to")=~/\S/)
190             {
191 0           push(@{$this->{"log"}},"CRIT: $msg") ;
  0            
192 0 0         syslog("info",$msg) if $config->get("use_syslog")==1 ;
193             }
194 0           $this->crit($msg) ;
195             }
196             }
197 1 50   1   385 END { $log->send() if $log ; closelog() ; }
  1         10  
198              
199             ############################################################
200              
201             {
202             package _Config ;
203             require Exporter ;
204             our @ISA = qw(Exporter);
205             our @EXPORT=qw() ;
206              
207 1     1   7 use Carp ;
  1         2  
  1         52  
208 1     1   4 use Data::Dumper ;
  1         3  
  1         41  
209 1     1   4 use Dir::Which q/which/ ;
  1         2  
  1         641  
210              
211             #--------------------
212             sub new
213             {
214 0     0     my($type,%h) = @_ ;
215 0           my($this) ;
216              
217 0 0         %h = map { /^-/ ? lc(substr($_,1)) : $_ ; } %h ;
  0            
218              
219 0           $this->{"args"} = \%h ;
220              
221 0           bless $this,$type ;
222 0           return $this ;
223             }
224              
225             #--------------------
226             sub init
227             {
228 0     0     my($this,$default,$arg) = @_ ;
229              
230 0           $this->{"default"} = $default ;
231 0           $this->{"arg"} = $arg ;
232             }
233              
234             #--------------------
235             sub get_arg
236             {
237 0     0     my($this,$arg) = @_ ;
238 0 0         carp "arg '$arg' undefined" unless exists($this->{"args"}{$arg}) ;
239 0           return $this->{"args"}{$arg} ;
240             }
241             #--------------------
242             sub load
243             {
244 0     0     my($this) = @_ ;
245 0           my $file = $this->get_arg("file") ;
246 0           my $path = $this->get_arg("path") ;
247              
248 0           my $confname = which(-entry=>$file
249             ,-defaultpath=>$path
250             ) ;
251 0 0         return unless defined $confname ;
252            
253 0 0         my $conffile=new Config::General(-ConfigFile=>$confname,-ExtendedAccess => 1) or $log->warn("can't read config file $confname") ;
254 0           my %conf ;
255             {
256 0           my %this_conf = $conffile->getall() ;
  0            
257 0           for my $k (keys %this_conf)
258             {
259 0 0         die "unknown parameter '$k' in $file\n" unless exists $this->{"default"}{$k} ;
260 0           $conf{$k} = $this_conf{$k} ;
261             }
262             }
263             # die Dumper \%conf ;
264 0           $this->{"conf"} = \%conf ;
265 0           $this->{"confname"} = $confname ;
266             }
267             sub get
268             {
269 0     0     my($this,$var) = @_ ;
270              
271 0 0         return $this->{"arg"}{$var} if exists($this->{"arg"}{$var}) ;
272 0 0         return $this->{"conf"}{$var} if exists($this->{"conf"}{$var}) ;
273 0 0         return $this->{"default"}{$var} if exists($this->{"default"}{$var}) ;
274 0           die "'$var' not found in config\n" ;
275             }
276             }
277              
278             ############################################################
279              
280             {
281             package _RocknRoll ;
282             require Exporter ;
283             our @ISA = qw(Exporter);
284             our @EXPORT=qw() ;
285              
286 1     1   6 use Carp ;
  1         3  
  1         67  
287 1     1   5 use Data::Dumper ;
  1         14  
  1         49  
288 1     1   5 use Dir::Which q/which/ ;
  1         7  
  1         44  
289 1     1   1015 use File::stat ;
  1         35302  
  1         19  
290              
291             #--------------------
292             sub new
293             {
294 0     0     my($type,$dstdir,$interval) = @_ ;
295 0           my($this) ;
296              
297 0           $this->{"dstdir"} = $dstdir ;
298 0           $this->{"interval"} = $interval ;
299 0           $this->{NORMAL} = 1 ;
300 0           @{$this->{rsync_opt}} = () ;
  0            
301              
302             # get files
303 0 0         my $d = new DirHandle($dstdir) or $log->crit("can't read directory $dstdir: $!\n") ;
304 0           @{$this->{"files"}} = $d->read() ;
  0            
305              
306 0           bless $this,$type ;
307              
308 0 0         $this->check_if_running() unless $config->get("continue") ;
309              
310 0           return $this ;
311             }
312             #--------------------
313             sub get_archives
314             {
315 0     0     my($this) = @_ ;
316 0           my $interval = $this->{"interval"} ;
317              
318 0           return grep /^$interval.\d+$/,@{$this->{"files"}} ;
  0            
319             }
320             #--------------------
321             sub check_if_running
322             {
323 0     0     my($this) = @_ ;
324 0           my $dstdir = $this->{"dstdir"} ;
325 0           my $interval = $this->{"interval"} ;
326 0           my @files = @{$this->{"files"}} ;
  0            
327 0           my $running = ".$interval.running" ;
328              
329 0 0         if ( grep /^$running$/,@files )
330             {
331 0           $this->{"running_exists"} = 1 ;
332 0           my $st = stat($this->{"dstdir"}."/$running") ;
333 0 0         if (time()-$st->ctime < $config->get("max_runtime")*60)
334             {
335 0           $log->crit_by_mail(sprintf("a directory '$dstdir/$running' (with ctime<%dmin) found.",$config->get("max_runtime"))) ;
336             }
337             else
338             {
339 0           $log->warn_by_mail("a directory '$running' already exists in '$dstdir', it will be overwritten.") ;
340 0           push(@{$this->{rsync_opt}},"--delete") ;
  0            
341             }
342             }
343             }
344              
345             #--------------------
346             sub read_control
347             {
348 0     0     my($this,$nb) = @_ ;
349              
350 0           my $dstdir = $this->{"dstdir"} ;
351 0           my $interval = $this->{"interval"} ;
352              
353 0 0         if (open F,"$dstdir/.$interval.ctl")
354             {
355 0           my $line = ;
356 0           close(F) ;
357 0 0         if ($line=~/^\s*nb_archives\s*:\s*\d+\s*$/)
358             {
359 0           my ($nb) = $line=~/^\s*nb_archives\s*:\s*(\d+)\s*$/ ;
360 0           return $nb ;
361             }
362 0           else { return -1 ; }
363             }
364 0           else { return -1 ; }
365             }
366             #--------------------
367             sub write_control
368             {
369 0     0     my($this,$nb) = @_ ;
370              
371 0           my $dstdir = $this->{"dstdir"} ;
372 0           my $interval = $this->{"interval"} ;
373              
374 0 0         open F,">$dstdir/.$interval.ctl" or die "$!" ;
375 0           print F "nb_archives:$nb" ;
376 0           close(F) ;
377             }
378             #--------------------
379             sub check_if_complete
380             {
381 0     0     my($this) = @_ ;
382              
383 0           my $dstdir = $this->{"dstdir"} ;
384 0           my $interval = $this->{"interval"} ;
385              
386 0           my @archives = $this->get_archives() ;
387 0 0         die("No archive found in $dstdir for interval $interval !\n"
388             ."(you must do '$this_prog --init n $interval $dstdir' first),\n\twhere n is the number of archives expected\n")
389             if @archives==0 ;
390              
391 0           my $found = @archives ;
392 0           my $require = $this->read_control() ;
393              
394 0 0         if ($require!=-1)
395             {
396 0 0         if ($require > $found)
    0          
397             {
398 0           my $diff = $require-$found ;
399 0           my $un = $diff==1 ;
400 0 0         $log->warn_by_mail(sprintf("%s archive%s missing in %s",$diff,$un?"":"s",$this->{"dstdir"})) ;
401 0           $this->{NORMAL} = 0 ;
402             }
403             elsif ($require < $found)
404             {
405 0           $log->warn_by_mail("$require archives required, but $found found !!") ;
406 0           $this->{NORMAL} = 0 ;
407             # que faire ?
408             }
409             }
410             else
411             {
412 0           $log->warn_by_mail("no control file found in ".$this->{"dstdir"}.", $found required archives supposed") ;
413 0           $this->write_control($found) ;
414             }
415 0           $this->{"require"} = $require ;
416 0           $this->{"found"} = $found ;
417             }
418             #--------------------
419             sub mkdirs
420             {
421 0     0     my($this,$nb) = @_ ;
422              
423 0           my $dstdir = $this->{"dstdir"} ;
424 0           my $interval = $this->{"interval"} ;
425 0           my @files = grep /^$interval.\d+$/,@{$this->{"files"}} ;
  0            
426 0           my $running = ".$interval.running" ;
427              
428 0 0         die("'$interval' archives already exist on $dstdir, abort !\n") if @files!=0 ;
429              
430 0           for my $i (1..$nb)
431             {
432 0           my $dir = "$dstdir/$interval.$i" ;
433 0 0         mkdir $dir or die("can't create $dir, abort !\n") ;
434             }
435 0           $this->write_control($nb) ;
436             }
437             #--------------------
438             sub rock
439             {
440 0     0     my($this,$srcdir) = @_ ;
441              
442 0           my $dstdir = $this->{"dstdir"} ;
443 0           my $interval = $this->{"interval"} ;
444 0           my @files = @{$this->{"files"}} ;
  0            
445 0 0 0       my $running = ($config->get("refresh") || $config->get("update"))
446             ? "$interval.1"
447             : ".$interval.running" ;
448              
449 0           my $jro = join(" ",@{$config->get("ro")}) ;
  0            
450 0 0         push(@{$this->{rsync_opt}},"--delete") if $config->get("update") ;
  0            
451 0           my $require = $this->{"require"} ;
452              
453             # options
454 0           my $ld ;
455 0 0         if ($config->get("no-links")==1)
456             {
457 0           $ld = "" ;
458             }
459             else
460             {
461 0 0         $ld = exists($this->{"linkdest"})
462             ? "--link-dest=$this->{'linkdest'}"
463             : "--link-dest=../${interval}.1" ;
464             }
465              
466 0           my $cmd = sprintf("%s %s %s %s $ld $srcdir $dstdir/$running"
467             , $config->get("rsync_path")
468 0           , join(" ",@{$this->{rsync_opt}})
469             , $config->get("ro_default")
470             , $jro
471             ) ;
472              
473 0           my $retcode = main::_myexec($cmd) ;
474 0 0         $log->crit_by_mail("$dstdir/$running not found after rsync execution") unless -d "$dstdir/$running" ;
475 0           return $retcode ;
476             }
477             #--------------------
478             sub roll
479             {
480 0     0     my($this) = @_ ;
481              
482 0           my $dstdir = $this->{"dstdir"} ;
483 0           my $interval = $this->{"interval"} ;
484 0           my $require = $this->{"require"} ;
485 0           my @files = grep /^$interval.\d+$/,@{$this->{"files"}} ;
  0            
486 0           my $running = ".$interval.running" ;
487 0           my $found = $this->{"found"} ;
488 0           my (@to_roll,@exist,@next_rank,@steps,@roll,@place) ;
489              
490             # determine the future rank of each archive
491 0           my ($i,$j) = (1,2) ;
492              
493 0           while ($found>0)
494             {
495 0 0         if (scalar(grep(/^$interval.$i$/,@files)) == 1)
496             {
497             # this archive exists
498 0           $found-- ;
499 0 0 0       $j=-1 if $j>$require || $j==0 ;
500 0           push @to_roll,$i ;
501 0           $exist[$i] = 1 ;
502 0           $place[$i] = 1 ;
503 0           $next_rank[$i] = $j ;
504 0           $j++ ;
505             }
506             else
507             {
508 0           $exist[$i] = 0 ;
509             }
510 0           $i++ ;
511             }
512              
513             # determine the order of future rename operation
514 0           my $again ;
515             do
516 0           {
517 0           $again = 0 ;
518 0           for $i (reverse @to_roll)
519             {
520 0 0         next unless $exist[$i]==1 ; # this archive doesn't exist
521 0 0 0       next if defined($roll[$i]) && $roll[$i]==1 ; # this archive has already rolled
522 0 0 0       next if $next_rank[$i]!=-1 && defined($place[$next_rank[$i]]) && $place[$next_rank[$i]]==1 ; # next place is not free
      0        
523 0           $log->debug("exist: ".Dumper \@exist) ;
524 0           $log->debug("roll: ".Dumper \@roll) ;
525 0           $log->debug("next_rank: ".Dumper \@next_rank) ;
526 0           $log->debug("place: ".Dumper \@place) ;
527 0           $again = 1 ; # at least one archive performed
528              
529             # oldest(s) archives to delete
530 0 0         if ($next_rank[$i]==-1)
531             {
532 0           push @steps,[$i,-1] ;
533 0           $place[$i] = 0 ;
534 0           $roll[$i] = 1 ;
535 0           next ;
536             }
537              
538             # is the next rank free ?
539             #if (! (defined($next_rank[$i]) && defined($place[$next_rank[$i]]) && $place[$next_rank[$i]]==1))
540 0 0 0       if (! (defined($place[$next_rank[$i]]) && $place[$next_rank[$i]]==1))
541             {
542 0           push @steps,[$i,$next_rank[$i]] ;
543 0           $place[$next_rank[$i]] = 1 ;
544 0           $place[$i] = 0 ;
545 0           $roll[$i] = 1 ;
546             }
547             }
548             } while $again ;
549 0           $log->debug("NORMAL: ".$this->{NORMAL}) ;
550 0           $log->debug("steps: ".Dumper \@steps) ;
551 0           for my $s (@steps)
552             {
553 0           my($old,$new) = @$s ;
554 0           my $msg ;
555              
556 0 0         if ($new==-1)
557             {
558 0           my $path = sprintf("%s/%s.%d"
559             ,$this->{"dstdir"}
560             ,$this->{"interval"}
561             ,$old
562             ) ;
563 0           $msg = sprintf("delete $path") ;
564 0 0         File::Path::Tiny::rm($path) or $log->crit("can't remove $path: $!") ;
565             }
566             else
567             {
568 0           my $src = sprintf("%s/%s.%d",
569             ,$this->{"dstdir"}
570             ,$this->{"interval"}
571             ,$old
572             ) ;
573 0           my $dst = sprintf("%s/%s.%d"
574             ,$this->{"dstdir"}
575             ,$this->{"interval"}
576             ,$new
577             ) ;
578 0           $msg = "rename $src $dst" ;
579 0 0         rename($src,$dst) or $log->crit("can't rename $src to $dst: $!") ;
580             }
581 0           $log->info($msg) ;
582 0 0         $log->warn_by_mail($msg) if $this->{NORMAL}==0 ;
583             }
584             {
585 0           my $src = sprintf("%s/.%s.running",$this->{"dstdir"},$this->{"interval"}) ;
  0            
586 0           my $dst = sprintf("%s/%s.1",$this->{"dstdir"},$this->{"interval"}) ;
587 0           my $msg = sprintf("rename $src $dst") ;
588 0           $log->info($msg) ;
589 0 0         $log->warn_by_mail($msg) if $this->{NORMAL}==0 ;
590 0 0         rename($src,$dst) or $log->crit("can't rename $src to $dst: $!") ;
591             }
592             }
593             }
594              
595             ############################################################
596              
597             #--------------------
598             sub _man
599             {
600 1     1   4016 use Pod::Perldoc ;
  1         37618  
  1         1326  
601              
602 0     0     @ARGV = ($this_prog) ;
603 0           exit(Pod::Perldoc->run($this_prog,undef)) ;
604             }
605              
606             #--------------------
607             sub _usage
608             {
609 0     0     my($msg) = @_ ;
610              
611 0           print(<< "EOF") ;
612             Rsync fOr baCKup (and roll) - v$VERSION
613              
614             $msg
615              
616             Usage: $this_prog --init n tag dstdir (1)
617             $this_prog options tag srcdir dstdir (2)
618             $this_prog --man
619              
620             common options :
621             --debug (1)(2)
622             --excludes=DIR (2)
623             --ro rsync_option (2)
624             --link-dest=DIR (2)
625             --no-roll (2)
626             --refresh (2)
627             --update (2)
628             --continue (2)
629              
630             example :
631             $this_prog --init 7 daily /var/snapshots/home
632             initialize a set of 7 archives, named 'daily'
633              
634             $this_prog daily /home /var/snapshots/home
635             rsync a new archive of /home to the set 'daily' located into
636             /var/snapshots/home, and roll the existing archives in the set
637              
638             EOF
639 0           exit(0) ;
640             }
641              
642             #--------------------
643             sub _myexec
644             {
645 0     0     my ($cmd) = @_ ;
646              
647 0           $log->info("exec: $cmd") ;
648 0           my @output ;
649 0 0         my $fcmd = new FileHandle("$cmd 2>&1|") or $log->crit_by_mail("can't execure $cmd: $!") ;
650 0           my $output ;
651 0           while (<$fcmd>)
652             {
653 0           chomp ;
654 0           $log->info("> $_") ;
655 0           push(@output,"> $_") ;
656             }
657 0           my $rc = $fcmd->close() ;
658              
659 0 0         if ($rc)
660             {
661             # all rights
662 0           $log->info(sprintf("return: %d (rc=$rc)",$?>>8)) ;
663 0           return 2 ;
664             }
665              
666 0 0         $log->crit_by_mail(sprintf("can't execure $cmd (rc=$rc, \$!==$!)")) if ($!) ;
667              
668             # some issues remains...
669 0           my $retcode = $?>>8 ;
670 0           $log->info(sprintf("return: %d",$retcode)) ;
671 0           my %ok_codes = map { $_ => 1 ; } split(/[^\d]+/,$config->get("rsync_retcode_ok")) ;
  0            
672 0           my %warn_codes = map { $_ => 1 ; } split(/[^\d]+/,$config->get("rsync_retcode_warn")) ;
  0            
673              
674             # case known as OK
675 0 0 0       return 2 if (exists($ok_codes{$retcode}) && $ok_codes{$retcode}==1) ;
676              
677             # case not known as WARNING -> ERROR
678 0 0 0       unless (exists($warn_codes{$retcode}) && $warn_codes{$retcode}==1)
679             {
680 0           $log->crit_by_mail(sprintf("'$cmd' returns %d (not found in OK et WARN codes)\n%s",$retcode,join("\n",@output))) ;
681 0           return 0 ;
682             }
683              
684             # other case : WARNING
685 0           $log->warn_by_mail(sprintf("'$cmd' returns %d\n%s",$retcode,join("\n",@output))) ;
686 0           return 1 ;
687             }
688              
689             #--------------------
690              
691             sub _run
692             {
693 0     0     my $cmdline = join(" ",@ARGV) ;
694 0           $Data::Dumper::Terse = 1;
695              
696 0 0         GetOptions(
697             \%arg_conf
698             , "init=s" => \$init
699             , "c=s" => \$config_file
700             , "continue" # start again with an existing .running archive
701             , "help" => \&_usage
702             , "man" => \&_man
703             , "debug"
704             , "dry-run"
705             , "link-dest=s"
706             , "mail_from=s"
707             , "mail_to=s"
708             , "max_runtime=i"
709             , "no-links"
710             , "no-roll"
711             , "refresh" # update archive .1, without deleting any files
712             , "rsync_retcode_ok=s"
713             , "rsync_retcode_warn=s"
714             , "ro=s@"
715             , "ro_default=s"
716             , "send_warn=i"
717             , "smtp_server=s"
718             , "update" # update archive .1, and delete obsolete files
719             , "use_syslog=i"
720             , "verbose"
721             ) or _usage("") ;
722              
723             # load conf file
724 0 0         if ($config_file)
725             {
726 0           $config = new _Config(-file=>$config_file,-path=>"") ;
727             }
728             else
729             {
730 0           $config = new _Config(
731             -file=>"$this_prog.conf"
732             , -path=>$CONFPATH
733             ) ;
734             }
735 0           $config->init($default_conf,\%arg_conf) ;
736 0           $config->load() ;
737              
738 0 0         $log = new _Log($cmdline,$config->get("debug")?2:$config->get("verbose")?1:0) ;
    0          
739 0           $log->debug("loading config file ".$config->{"confname"}) ;
740 0           $log->debug("config: ".Dumper $config) ;
741              
742 0 0         my $interval = shift(@ARGV) or _usage("wrong number of arguments") ;
743              
744 0           my ($srcdir,$dstdir) ;
745              
746 0 0         if ($init)
747             {
748 0 0         $init =~ /^\d+$/ or _usage("'$init' isn't numeric") ;
749 0 0         $dstdir = shift(@ARGV) or _usage("dstdir is missing") ;
750             }
751             else
752             {
753 0 0         $srcdir = shift(@ARGV) or _usage("srcdir is missing") ;
754 0 0         $dstdir = shift(@ARGV) or _usage("dstdir is missing") ;
755             }
756              
757 0 0         _usage("wrong number of arguments") if scalar(@ARGV)!=0 ;
758              
759 0           my $rocknRoll = new _RocknRoll($dstdir,$interval) ;
760              
761 0 0         if ($init)
762             {
763 0           $rocknRoll->mkdirs($init) ;
764             }
765             else
766             {
767 0           $rocknRoll->check_if_complete() ;
768              
769 0 0         if ($rocknRoll->rock($srcdir)==0)
770             {
771 0           $log->crit_by_mail("aborted before rolling archives") ;
772 0           exit 1 ;
773             }
774 0 0 0       $rocknRoll->roll() unless $config->get("no-roll")
      0        
775             || $config->get("refresh")
776             || $config->get("update")
777             ;
778             }
779             }
780              
781             1;
782             __END__