/[psinib]/psinib.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /psinib.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.1.1.1 by dpavlin, Sat Jan 4 11:42:56 2003 UTC revision 1.22 by dpavlin, Mon Mar 1 19:38:16 2004 UTC
# Line 12  Line 12 
12  #  #
13  #  #
14  # usage:  # usage:
15  #       $ backup.pl mountscript  #       $ psinib.pl mountscript
16    
17  use strict 'vars';  use strict 'vars';
18  use Data::Dumper;  use Data::Dumper;
# Line 21  use POSIX qw(strftime); Line 21  use POSIX qw(strftime);
21  use List::Compare;  use List::Compare;
22  use Filesys::SmbClient;  use Filesys::SmbClient;
23  #use Taint;  #use Taint;
24    use Fcntl qw(LOCK_EX LOCK_NB);
25    use Digest::MD5;
26    use File::Basename;
27    use Getopt::Long;
28    
29  # configuration  # configuration
30  my $LOG_TIME_FMT = '%Y-%m-%d %H:%M:%S'; # strftime format for logfile  my $LOG_TIME_FMT = '%Y-%m-%d %H:%M:%S'; # strftime format for logfile
31  my $DIR_TIME_FMT = '%Y%m%d';            # strftime format for backup dir  my $DIR_TIME_FMT = '%Y%m%d';            # strftime format for backup dir
32    
33    # define timeout for ping
34    my $PING_TIMEOUT = 5;
35    
36  my $LOG = '/var/log/backup.log';        # add path here...  my $LOG = '/var/log/backup.log';        # add path here...
37  $LOG = '/tmp/backup.log';  #$LOG = '/tmp/backup.log';
38    
39  # store backups in which directory  # store backups in which directory
40  my $BACKUP_DEST = '/data/isis_backup';  my $BACKUP_DEST = '/backup/isis_backup';
41    #my $BACKUP_DEST = '/tmp/backup/';
42    
43  # files to ignore in backup  # files to ignore in backup
44  my @ignore = ('.md5sum', '.backupignore', 'backupignore.txt');  my @ignore = ('.md5sum', '.backupignore', 'backupignore.txt');
45    
46  # open log  # open log
47  open(L, "> $LOG") || die "can't open log $LOG: $!";  open(L, ">> $LOG") || die "can't open log $LOG: $!";
48  select((select(L), $|=1)[0]);   # flush output  select((select(L), $|=1)[0]);   # flush output
49    
50    # make a lock on logfile
51    
52    my $c = 0;
53    {
54            flock L, LOCK_EX | LOCK_NB and last;
55            sleep 1;
56            redo if ++$c < 10;
57            # no response for 10 sec, bail out
58            xlog("ABORT","can't take lock on $LOG -- another $0 running?");
59            exit 1;
60    }
61    
62  # taint path: nmblookup should be there!  # taint path: nmblookup should be there!
63  $ENV{'PATH'} = "/usr/bin:/bin";  $ENV{'PATH'} = "/usr/bin:/bin";
64    
65    my $use_ping = 1;       # default: use syn tcp ping to verify that host is up
66    my $verbose = 1;        # default verbosity level
67    my $quiet = 0;
68    my $email;
69    
70    my $result = GetOptions(
71            "ping!" => \$use_ping, "backupdest!" => \$BACKUP_DEST,
72            "verbose+" => \$verbose, "quiet+" => \$quiet,
73            "email=s" => \$email,
74    );
75    
76    $verbose -= $quiet;
77    
78  my $mounts = shift @ARGV ||  my $mounts = shift @ARGV ||
79          'mountscript';          'mountscript';
80  #       die "usage: $0 mountscript";  #       die "usage: $0 mountscript";
81    
82    my $basedir = $0;
83    $basedir =~ s,/?[^/]+$,,g;
84    
85    # default subject for e-mail messages
86    my @subjects = ('Backup needs your attention!');
87    my $sub_nr = 0;
88    my $email_body;
89    
90    my $home_dir=$ENV{'HOME'};
91    $home_dir = '/tmp' if (! -w $home_dir);
92    
93    if ($email) {
94            # It will use (and require) Tie::File only if --email=foo@bar.com
95            # arguement is used!
96            use Tie::File;
97            tie @subjects, 'Tie::File', "$basedir/subjects.txt" || xlog("CONFIG","Can't find $basedir/subjects.txt... using default (only one)");
98            chdir; # this will change directory to HOME
99            if (open(SN,"$home_dir/.psinib.subject")) {
100                    $sub_nr = <SN>;
101                    chomp($sub_nr);
102                    close(SN);
103            }
104            $sub_nr++;
105            # skip comments in subjects.txt
106            while($subjects[$sub_nr] && $subjects[$sub_nr] =~ m/^#/) {
107                    $sub_nr++;
108            }
109            $sub_nr = 0 if (! $subjects[$sub_nr]);
110    
111            if (open(SN,"> $home_dir/.psinib.subject")) {
112                    print SN "$sub_nr\n";
113                    close (SN);
114            } else {
115                    xlog("CONFIG","Can't open $home_dir/.psinib.subject -- I can't cycle subjects...");
116            };
117    }
118    
119  my @in_backup;  # shares which are backeduped this run  my @in_backup;  # shares which are backeduped this run
120    
121  my $p = new Net::Ping->new();  # init Net::Ping object
122    my $ping;
123    if ($use_ping) {
124            $ping = new Net::Ping->new("syn", 2);
125            # ping will try tcp connect to netbios-ssn (139)
126            $ping->{port_num} = getservbyname("netbios-ssn", "tcp");
127    }
128    
129    # do syn ping to cifs port
130    sub host_up {
131            my $ping = shift || return;
132            my $host_ip = shift || xlog("host_up didn't get IP");
133            my $timeout = shift;
134            return 1 if (! $use_ping);
135    
136            $ping->ping($host_ip,$timeout);
137            my $return = 0;
138    
139            while (my ($host,$rtt,$ip) = $ping->ack) {
140                    xlog("","HOST: $host [$ip] ACKed in $rtt seconds");
141                    $return = 1 if ($ip eq $host_ip);
142            }
143            return $return;
144    }
145    
146  my $backup_ok = 0;  my $backup_ok = 0;
147    
148  my $smb;  my $smb;
149  my %smb_atime;  my %smb_atime;
150  my %smb_mtime;  my %smb_mtime;
151    my %file_md5;
152    
153  open(M, $mounts) || die "can't open $mounts: $!";  open(M, $mounts) || die "can't open $mounts: $!";
154  while(<M>) {  while(<M>) {
# Line 63  while(<M>) { Line 156  while(<M>) {
156          next if !/^\s*smbmount\s/;          next if !/^\s*smbmount\s/;
157          my (undef,$share,undef,$opt) = split(/\s+/,$_,4);          my (undef,$share,undef,$opt) = split(/\s+/,$_,4);
158    
159          my ($user,$passwd,$workgroup);          my ($user,$passwd,$workgroup,$ip);
160    
161          foreach (split(/,/,$opt)) {          foreach (split(/,/,$opt)) {
162                  my ($n,$v) = split(/=/,$_,2);                  my ($n,$v) = split(/=/,$_,2);
# Line 79  while(<M>) { Line 172  while(<M>) {
172                          }                          }
173                  } elsif ($n =~ m#workgroup#i) {                  } elsif ($n =~ m#workgroup#i) {
174                          $workgroup = $v;                          $workgroup = $v;
175                    } elsif ($n =~ m#ip#i) {
176                            $ip = $v;
177                  }                  }
178          }          }
179    
180          push @in_backup,$share;          push @in_backup,$share;
181    
         print "working on $share\n";  
182    
183          my $ip = get_ip($share);          my ($host,$dir,$date_dir) = share2host_dir($share);
184            my $bl = "$BACKUP_DEST/$host/$dir/latest";      # latest backup
185            my $bc = "$BACKUP_DEST/$host/$dir/$date_dir";   # current one
186            my $real_bl;
187            if (-l $bl) {
188                    $real_bl=readlink($bl) || die "can't read link $bl: $!";
189                    $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
190                    if (-l $bc && $real_bl eq $bc) {
191                            xlog($share,"allready backuped...");
192                            $backup_ok++;
193                            next;
194                    }
195    
196            }
197    
198    
199            xlog($share,"working on $share...");
200    
201            # try to nmblookup IP
202            $ip = get_ip($share) if (! $ip);
203    
204          if ($ip) {          if ($ip) {
205                  xlog($share,"IP is $ip");                  xlog($share,"IP is $ip");
206                  if ($p->ping($ip)) {                  if (host_up($ping, $ip,$PING_TIMEOUT)) {
207                          snap_share($share,$user,$passwd,$workgroup);                          if (snap_share($share,$user,$passwd,$workgroup)) {
208                          $backup_ok++;                                  $backup_ok++;
209                            }
210                  }                  }
211          }          }
212  }  }
213  close(M);  close(M);
214    
215  xlog("","$backup_ok backups completed of total ".($#in_backup+1)." this time (".int($backup_ok*100/($#in_backup+1))." %)");  my $total = ($#in_backup + 1) || 0;
216    my $pcnt = "";
217    $pcnt = "(".int($backup_ok*100/$total)." %)" if ($total > 0);
218    xlog("","$backup_ok backups completed of total $total this time".$pcnt);
219    
220    send_email();
221    
222  1;  1;
223    
224  #-------------------------------------------------------------------------  #-------------------------------------------------------------------------
225    
226    
227  # get IP number from share  # get IP number from share
228  sub get_ip {  sub get_ip {
229          my $share = shift;          my $share = shift;
# Line 116  sub get_ip { Line 236  sub get_ip {
236          }          }
237  }  }
238    
239    # send e-mail with all messages
240    sub send_email {
241            return if (! $email || $email eq "" || !$email_body);
242            require Mail::Send;
243            my $msg = new Mail::Send;
244            $msg->to($email);
245            $msg->subject($subjects[$sub_nr]);
246            my $fn=$msg->open;
247            print $fn $email_body;
248            $fn->close;
249    }
250            
251    
252    # write entry to screen and log
253  sub xlog {  sub xlog {
254          my $share = shift;          my $share = shift;
255          my $t = strftime $LOG_TIME_FMT, localtime;          my $t = strftime $LOG_TIME_FMT, localtime;
256          my $m = shift || '[no log entry]';          my $m = shift || '[no log entry]';
257          print STDERR $m,"\n";          my $l = shift;
258            $l = 1 if (! defined $l);       # default verbosity is 1
259            if ($verbose >= $l) {
260                    if (! $email) {
261                            print STDERR $m,"\n";
262                    # don't e-mail mesages with verbosity < 1
263                    } elsif ($l < 1) {
264                            $email_body .= $m."\n";
265                    }
266            }
267          print L "$t $share\t$m\n";          print L "$t $share\t$m\n";
268  }  }
269    
270  sub snap_share {  # dump warn and dies into log
271    BEGIN { $SIG{'__WARN__'} = sub { xlog('WARN',$_[0],1) ; exit 1 } }
272          my $share = shift;  BEGIN { $SIG{'__DIE__'} = sub { xlog('DIE',$_[0],0) ; exit 1 } }
   
         my %param = ( debug => 0 );  
273    
         $param{username} = shift;  
         $param{password} = shift;  
         $param{workgroup} = shift;  
274    
275    # split share name to host, dir and currnet date dir
276    sub share2host_dir {
277            my $share = shift;
278          my ($host,$dir);          my ($host,$dir);
279          if ($share =~ m#//([^/]+)/(.+)$#) {          if ($share =~ m#//([^/]+)/(.+)$#) {
280                  ($host,$dir) = ($1,$2);                  ($host,$dir) = ($1,$2);
# Line 141  sub snap_share { Line 282  sub snap_share {
282                  $dir =~ s/^_+//;                  $dir =~ s/^_+//;
283                  $dir =~ s/_+$//;                  $dir =~ s/_+$//;
284          } else {          } else {
285                  print "Can't parse share $share into host and directory!\n";                  xlog($share,"Can't parse share $share into host and directory!",1);
286                  return;                  return;
287          }          }
288            return ($host,$dir,strftime $DIR_TIME_FMT, localtime);
289    }
290    
291          my $date_dir = strftime $DIR_TIME_FMT, localtime;  
292    # make a snapshot of a share
293    sub snap_share {
294    
295            my $share = shift;
296    
297            my %param = ( debug => 0 );
298    
299            $param{username} = shift || warn "can't find username for share $share";
300            $param{password} = shift || warn "can't find passwod for share $share";
301            $param{workgroup} = shift || warn "can't find workgroup for share $share";
302    
303            my ($host,$dir,$date_dir) = share2host_dir($share);
304    
305          # latest backup directory          # latest backup directory
306          my $bl = "$BACKUP_DEST/$host/$dir/latest";          my $bl = "$BACKUP_DEST/$host/$dir/latest";
# Line 153  sub snap_share { Line 308  sub snap_share {
308          my $bc = "$BACKUP_DEST/$host/$dir/$date_dir";          my $bc = "$BACKUP_DEST/$host/$dir/$date_dir";
309    
310          my $real_bl;          my $real_bl;
311          if (-e $bl) {          if (-l $bl) {
312                  $real_bl=readlink($bl) || die "can't read link $bl: $!";                  $real_bl=readlink($bl) || die "can't read link $bl: $!";
313                  $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");                  $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
314          } else {                  if (! -e $real_bl) {
315                  print "no old backup, this is first run...\n";                          xlog($share,"latest link $bl -> $real_bl not valid, removing it");
316                            unlink $bl || die "can't remove link $bl: $!";
317                            undef $real_bl;
318                    }
319            }
320            if (! $real_bl) {
321                    xlog($share,"no old backup, trying to find last backup");
322                    if (opendir(BL_DIR, "$BACKUP_DEST/$host/$dir")) {
323                            my @bl_dirs = sort grep { !/^\./ && -d "$BACKUP_DEST/$host/$dir/$_" } readdir(BL_DIR);
324                            closedir(BL_DIR);
325                            $real_bl=pop @bl_dirs;
326                            xlog($share,"using $real_bl as latest...");
327                            $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
328                            if ($real_bl eq $bc) {
329                                    xlog($share,"latest from today (possible partial backup)");
330                                    rename $real_bl,$real_bl.".partial" || warn "can't reaname partial backup: $!";
331                                    $real_bl .= ".partial";
332                            }
333                    } else {
334                            xlog($share,"this is first run...");
335                    }
336          }          }
337    
338          if (-e $bc && $real_bl && $real_bl eq $bc) {          if (-l $bc && $real_bl && $real_bl eq $bc) {
339                  print "$share allready backuped...\n";                  xlog($share,"allready backuped...");
340                  return;                  return 1;
341          }          }
342    
343          die "You should really create BACKUP_DEST [$BACKUP_DEST] by hand! " if (!-e $BACKUP_DEST);          die "You should really create BACKUP_DEST [$BACKUP_DEST] by hand! " if (!-e $BACKUP_DEST);
344    
345          if (! -e "$BACKUP_DEST/$host") {          if (! -e "$BACKUP_DEST/$host") {
346                  mkdir "$BACKUP_DEST/$host" || die "can't make dir for host $host, $BACKUP_DEST/$host: $!";                  mkdir "$BACKUP_DEST/$host" || die "can't make dir for host $host, $BACKUP_DEST/$host: $!";
347                  print "created host directory $BACKUP_DEST/$host...\n";                  xlog($share,"created host directory $BACKUP_DEST/$host...");
348          }          }
349    
350          if (! -e "$BACKUP_DEST/$host/$dir") {          if (! -e "$BACKUP_DEST/$host/$dir") {
351                  mkdir "$BACKUP_DEST/$host/$dir" || die "can't make dir for share $share, $BACKUP_DEST/$host/$dir $!";                  mkdir "$BACKUP_DEST/$host/$dir" || die "can't make dir for share $share, $BACKUP_DEST/$host/$dir $!";
352                  print "created dir for share $share, $BACKUP_DEST/$host/$dir...\n";                  xlog($share,"created dir for this share $BACKUP_DEST/$host/$dir...");
353          }          }
354    
355          mkdir $bc || die "can't make dir for current backup $bc: $!";          mkdir $bc || die "can't make dir for current backup $bc: $!";
# Line 189  sub snap_share { Line 364  sub snap_share {
364          my %file_size;          my %file_size;
365          my %file_atime;          my %file_atime;
366          my %file_mtime;          my %file_mtime;
367          my %file_md5;          #my %file_md5;
368            %file_md5 = ();
369    
370          my @smb_files;          my @smb_files;
371          my %smb_size;          my %smb_size;
372          #my %smb_atime;          #my %smb_atime;
373          #my %smb_mtime;          #my %smb_mtime;
         my %smb_md5;  
   
374    
375          sub norm_dir {          sub norm_dir {
376                  my $foo = shift;                  my $foo = shift;
# Line 212  sub snap_share { Line 386  sub snap_share {
386          my $di = 0;          my $di = 0;
387          while ($di <= $#dirs && $real_bl) {          while ($di <= $#dirs && $real_bl) {
388                  my $d=$dirs[$di++];                  my $d=$dirs[$di++];
389                  opendir(DIR,"$bl/$d") || warn "opendir($bl/$d): $!\n";                  opendir(DIR,"$real_bl/$d") || warn "opendir($real_bl/$d): $!\n";
390    
391                  # read .backupignore if exists                  # read .backupignore if exists
392                  if (-f "$bl/$d/.backupignore") {                  if (-f "$real_bl/$d/.backupignore") {
393                          open(I,"$bl/$d/.backupignore");                          open(I,"$real_bl/$d/.backupignore");
394                          while(<I>) {                          while(<I>) {
395                                  chomp;                                  chomp;
396                                  push @ignore,norm_dir("$d/$_");                                  push @ignore,norm_dir("$d/$_");
397                          }                          }
398                          close(I);                          close(I);
399  print STDERR "ignore: ",join("|",@ignore),"\n";  #print STDERR "ignore: ",join("|",@ignore),"\n";
400                          link "$bl/$d/.backupignore","$bc/$d/.backupignore" ||                          link "$real_bl/$d/.backupignore","$bc/$d/.backupignore" ||
401                                  warn "can't copy $bl/$d/.backupignore to current backup dir: $!\n";                                  warn "can't copy $real_bl/$d/.backupignore to current backup dir: $!\n";
402                  }                  }
403    
404                  # read .md5sum if exists                  # read .md5sum if exists
405                  if (-f "$bl/$d/.md5sum") {                  if (-f "$real_bl/$d/.md5sum") {
406                          open(I,"$bl/$d/.md5sum");                          open(I,"$real_bl/$d/.md5sum");
407                          while(<I>) {                          while(<I>) {
408                                  chomp;                                  chomp;
409                                  my ($md5,$f) = split(/\s+/,$_,2);                                  my ($md5,$f) = split(/\s+/,$_,2);
# Line 243  print STDERR "ignore: ",join("|",@ignore Line 417  print STDERR "ignore: ",join("|",@ignore
417                          next if ($f eq '.');                          next if ($f eq '.');
418                          next if ($f eq '..');                          next if ($f eq '..');
419                          my $pr = norm_dir("$d/$f");     # path relative                          my $pr = norm_dir("$d/$f");     # path relative
420                          my $pf = norm_dir("$d/$f","$bl/");      # path full                          my $pf = norm_dir("$d/$f","$real_bl/"); # path full
421                          if (grep(/^\Q$pr\E$/,@ignore) == 0) {                          if (grep(/^\Q$pr\E$/,@ignore) == 0) {
422                                  if (-f $pf) {                                  if (-f $pf) {
423                                          push @files,$pr;                                          push @files,$pr;
# Line 253  print STDERR "ignore: ",join("|",@ignore Line 427  print STDERR "ignore: ",join("|",@ignore
427                                  } elsif (-d $pf) {                                  } elsif (-d $pf) {
428                                          push @dirs,$pr;                                          push @dirs,$pr;
429                                  } else {                                  } else {
430                                          print STDERR "unknown type: $pf\n";                                          xlog($share,"not file or directory: $pf",0);
431                                  }                                  }
432                          } else {                          } else {
433                                  print STDERR "ignored: $pr\n";                                  xlog($share,"ignored: $pr");
434                          }                          }
435                  }                  }
436          }          }
437    
438          xlog($share,($#files+1)." files and ".($#dirs+1)." dirs on local disk before backup");          # local dir always include /
439            xlog($share,($#files+1)." files and ".($#dirs)." dirs on local disk before backup");
440    
441          # read smb filesystem          # read smb filesystem
442    
# Line 272  print STDERR "ignore: ",join("|",@ignore Line 447  print STDERR "ignore: ",join("|",@ignore
447    
448          $di = 0;          $di = 0;
449          while ($di <= $#smb_dirs) {          while ($di <= $#smb_dirs) {
450                  my $d=$smb_dirs[$di++];                  my $d=$smb_dirs[$di];
451                  my $pf = norm_dir($d,"smb:$share/");    # path full                  my $pf = norm_dir($d,"smb:$share/");    # path full
452                  my $D = $smb->opendir($pf) || warn "smb->opendir($pf): $!\n";                  my $D = $smb->opendir($pf);
453                    if (! $D) {
454                            xlog($share,"FATAL: $share [$pf] as $param{username}/$param{workgroup}: $!",0);
455                            # remove failing dir
456                            delete $smb_dirs[$di];
457                            return 0;                       # failed
458                    }
459                    $di++;
460    
461                  my @clutter = $smb->readdir_struct($D);                  my @clutter = $smb->readdir_struct($D);
462                  foreach my $item (@clutter) {                  foreach my $item (@clutter) {
# Line 292  print STDERR "ignore: ",join("|",@ignore Line 474  print STDERR "ignore: ",join("|",@ignore
474                                  } elsif ($item->[0] == main::SMBC_DIR) {                                  } elsif ($item->[0] == main::SMBC_DIR) {
475                                          push @smb_dirs,$pr;                                          push @smb_dirs,$pr;
476                                  } else {                                  } else {
477                                          print STDERR "unknown type: $pf\n";                                          xlog($share,"not file or directory [".$item->[0]."]: $pf",0);
478                                  }                                  }
479                          } else {                          } else {
480                                  print STDERR "smb ignored: $pr\n";                                  xlog($share,"smb ignored: $pr");
481                          }                          }
482                  }                  }
483          }          }
484    
485          xlog($share,($#smb_files+1)." files and ".($#smb_dirs+1)." dirs on remote share");          xlog($share,($#smb_files+1)." files and ".($#smb_dirs)." dirs on remote share");
486    
487          # sync dirs          # sync dirs
488          my $lc = List::Compare->new(\@dirs, \@smb_dirs);          my $lc = List::Compare->new(\@dirs, \@smb_dirs);
# Line 332  print STDERR "ignore: ",join("|",@ignore Line 514  print STDERR "ignore: ",join("|",@ignore
514                                    
515                  foreach my $f (@_) {                  foreach my $f (@_) {
516  #print "smb_copy $from/$f -> $to/$f\n";  #print "smb_copy $from/$f -> $to/$f\n";
517                          if (! open(F,"> $to/$f")) {                          my $md5 = Digest::MD5->new;
                                 print STDERR "can't open new file $to/$f: $!\n";  
                                 next;  
                         }  
518    
519                          my $fd = $smb->open("$from/$f");                          my $fd = $smb->open("$from/$f");
520                          if (! $fd) {                          if (! $fd) {
521                                  print STDERR "can't open smb file $from/$f: $!\n";                                  xlog("WARNING","can't open smb file $from/$f: $!");
522                                    next;
523                            }
524    
525                            if (! open(F,"> $to/$f")) {
526                                    xlog("WARNING","can't open new file $to/$f: $!");
527                                  next;                                  next;
528                          }                          }
529    
530                          while (defined(my $b=$smb->read($fd,4096))) {                          while (defined(my $b=$smb->read($fd,4096))) {
531                                  print F $b;                                  print F $b;
532                                  $l += length($b);                                  $l += length($b);
533                                    $md5->add($b);
534                          }                          }
535    
536                          $smb->close($fd);                          $smb->close($fd);
537                          close(F);                          close(F);
538    
539                            $file_md5{$f} = $md5->hexdigest;
540    
541                          # FIX: this fails with -T                          # FIX: this fails with -T
542                          my ($a,$m) = ($smb->stat("$from/$f"))[10,11];                          my ($a,$m) = ($smb->stat("$from/$f"))[10,11];
543                          utime $a, $m, "$to/$f" ||                          utime $a, $m, "$to/$f" ||
# Line 404  print STDERR "ignore: ",join("|",@ignore Line 591  print STDERR "ignore: ",join("|",@ignore
591          xlog($share,"$transfer bytes transfered...");          xlog($share,"$transfer bytes transfered...");
592    
593          foreach (@ln_files) {          foreach (@ln_files) {
594                  link "$bl/$_","$bc/$_" || warn "link $bl/$_ -> $bc/$_: $!\n";                  link "$real_bl/$_","$bc/$_" || warn "link $real_bl/$_ -> $bc/$_: $!\n";
595          }          }
596    
597          # remove files          # remove files
598          foreach (sort @files2erase) {          foreach (sort @files2erase) {
599                  unlink "$bc/$_" || warn "unlink $_: $!\n";                  unlink "$bc/$_" || warn "unlink $_: $!\n";
600                    delete $file_md5{$_};
601          }          }
602    
603          # remove not needed dirs (after files)          # remove not needed dirs (after files)
# Line 417  print STDERR "ignore: ",join("|",@ignore Line 605  print STDERR "ignore: ",join("|",@ignore
605                  rmdir "$bc/$_" || warn "rmdir $_: $!\n";                  rmdir "$bc/$_" || warn "rmdir $_: $!\n";
606          }          }
607    
608            # remove old .md5sum
609          # FIX: create .md5sum          foreach (sort @dirs) {
610                    unlink "$bc/$_/.md5sum" if (-e "$bc/$_/.md5sum");
611            }
612    
613            # erase stale entries in .md5sum
614            my @md5_files = keys %file_md5;
615            $lc = List::Compare->new(\@md5_files, \@smb_files);
616            foreach my $file ($lc->get_Lonly) {
617                    xlog("NOTICE","removing stale '$file' from .md5sum");
618                    delete $file_md5{$file};
619            }
620    
621            # create .md5sum
622            my $last_dir = '';
623            my $md5;
624            foreach my $f (sort { $file_md5{$a} cmp $file_md5{$b} } keys %file_md5) {
625                    my $dir = dirname($f);
626                    my $file = basename($f);
627    #print "$f -- $dir / $file<--\n";
628                    if ($dir ne $last_dir) {
629                            close($md5) if ($md5);
630                            open($md5, ">> $bc/$dir/.md5sum") || warn "can't create $bc/$dir/.md5sum: $!";
631                            $last_dir = $dir;
632    #print STDERR "writing $last_dir/.md5sum\n";
633                    }
634                    print $md5 $file_md5{$f},"  $file\n";
635            }
636            close($md5) if ($md5);
637    
638          # create leatest link          # create leatest link
639    #print "ln -s $bc $real_bl\n";
640            if (-l $bl) {
641                    unlink $bl || warn "can't remove old latest symlink $bl: $!\n";
642            }
643          symlink $bc,$bl || warn "can't create latest symlink $bl -> $bc: $!\n";          symlink $bc,$bl || warn "can't create latest symlink $bl -> $bc: $!\n";
644    
645            # FIX: sanity check -- remove for speedup
646            xlog($share,"failed to create latest symlink $bl -> $bc...") if (readlink($bl) ne $bc || ! -l $bl);
647    
648          xlog($share,"backup completed...");          xlog($share,"backup completed...");
 }  
649    
650            return 1;
651    }
652    __END__
653  #-------------------------------------------------------------------------  #-------------------------------------------------------------------------
654    
655    
656    =head1 NAME
657    
658    psinib - Perl Snapshot Is Not Incremental Backup
659    
660    =head1 SYNOPSIS
661    
662    ./psinib.pl [OPTION]... [mount script]
663    
664    =head1 DESCRIPTION
665    
666    Option can be one of more of following:
667    
668    =over 8
669    
670    =item C<--backupdest=dir>
671    
672    Specify backup destination directory (defaults is /data/
673    
674    =item C<--noping>
675    
676    Don't use ping to check if host is up (default is ti use tcp syn to cifs
677    port)
678    
679    =item C<--verbose -v>
680    
681    Increase verbosity level. Defailt is 1 which prints moderate amount of data
682    on STDOUT and STDERR.
683    
684    =item C<--quiet -q>
685    
686    Decrease verbosity level
687    
688    =item C<--email=email@domain>
689    
690    Send e-mails instead of dumping errors to STDERR. Useful for cron jobs.
691    
692    =back
693    
694    This script in current version support just backup of Samba (or Micro$oft
695    Winblowz) shares to central disk space. Central disk space is organized in
696    multiple directories named after:
697    
698    =over 4
699    
700    =item *
701    server which is sharing files to be backed up
702    
703    =item *
704    name of share on server
705    
706    =item *
707    dated directory named like standard ISO date format (YYYYMMDD).
708    
709    =back
710    
711    In each dated directory you will find I<snapshot> of all files on
712    exported share on that particular date.
713    
714    You can also use symlink I<latest> which will lead you to
715    last completed backup. After that you can use some other backup
716    software to transfer I<snapshot> to tape, CD-ROM or some other media.
717    
718    =head2 Design considerations
719    
720    Since taking of share snapshot every day requires a lot of disk space and
721    network bandwidth, B<psinib> uses several techniques to keep disk usage and
722    network traffic at acceptable level:
723    
724    =over 3
725    
726    =item - usage of hard-links to provide same files in each snapshot (as opposed
727    to have multiple copies of same file)
728    
729    =item - usage of file size, atime and mtime to find changes of files without
730    transferring whole file over network (just share browsing is transfered
731    over network)
732    
733    =item - usage of C<.md5sum> files (compatible with command-line utility
734    C<md5sum>) to keep file between snapshots hard-linked
735    
736    =back
737    
738    =head1 CONFIGURATION
739    
740    This section is not yet written.
741    
742    =head1 HACKS, TRICKS, BUGS and LIMITATIONS
743    
744    This chapter will have all content that doesn't fit anywhere else.
745    
746    =head2 Can snapshots be more frequent than daily?
747    
748    There is not real reason why you can't take snapshot more often than
749    once a day. Actually, if you are using B<psinib> to backup Windows
750    workstations you already know that they tend to come-and-go during the day
751    (reboots probably ;-), so running B<psinib> several times a day increases
752    your chance of having up-to-date backup (B<psinib> will not make multiple
753    snapshots for same day, nor will it update snapshot for current day if
754    it already exists).
755    
756    However, changing B<psinib> to produce snapshots which are, for example, hourly
757    is a simple change of C<$DIR_TIME_FMT> which is currently set to
758    C<'%Y%m%d'> (see I<strftime> documentation for explanation of that
759    format). If you change that to C<'%Y%m%d-%H> you can have hourly snapshots
760    (if your network is fast enough, that is...). Also, some of messages in
761    program will sound strange, but other than that it should work.
762    I<You have been warned>.
763    
764    =head2 Do I really need to share every directory which I want to snapshot?
765    
766    Actually, no. Due to usage of C<Filesys::SmbClient> module, you can also
767    specify sub-directory inside your share that you want to backup. This feature
768    is most useful if you want to use administrative shares (but, have in mind
769    that you have to enter your Win administrator password in unencrypted file on
770    disk to do that) like this:
771    
772            smbmount //server/c$/WinNT/fonts  /mnt  -o username=administrator%win  
773    
774    After that you will get directories with snapshots like:
775    
776            server/c_WinNT_fonts/yyyymmdd/....
777    
778    =head2 Won't I run out of disk space?
779    
780    Of course you will... Snapshots and logfiles will eventually fill-up your disk.
781    However, you can do two things to stop that:
782    
783    =head3 Clean snapshort older than x days
784    
785    You can add following command to your C<root> crontab:
786    
787            find /backup/isis_backup -type d -mindepth 3 -maxdepth 3 -mtime +11 -exec rm -Rf {} \;
788    
789    I assume that C</backup/isis_backup> is directory in which are your snapshots
790    and that you don't want to keep snapshots older than 11 days (that's
791    C<-mtime +11> part of command).
792    
793    =head3 Rotate your logs
794    
795    I will leave that to you. I relay on GNU/Debian's C<logrotate> to do it for me.
796    
797    =head2 What are I<YYYYMMDD.partial> directories?
798    
799    If there isn't I<latest> symlink in snapshot directory, it's preatty safe to
800    assume that previous backup from that day failed. So, that directory will
801    be renamed to I<YYYYMMDD.partial> and snapshot will be performed again,
802    linking same files (other alternative would be to erase that dir and find
803    second-oldest directory, but this seemed like more correct approach).
804    
805    =head2 I can't connect to any share
806    
807    Please verify that nmblookup (which is part of samba package) is in /bin or
808    /usr/bin. Also verify that nmblookup returns IP address for your server
809    using:
810    
811       $ nmblookup tvhouse
812       querying tvhouse on 192.168.34.255
813       192.168.34.30 tvhouse<00>
814    
815    If you don't get any output, your samba might not listen to correct interface
816    (see interfaces in smb.conf).
817    
818    =head2 Aren't backups boring?
819    
820    No! If you have subjects.txt in same directory as C<psinib.pl> you can get
821    various funny subjects in your mail. They change over time as long as you
822    ignore your backup.
823    
824    =head1 AUTHOR
825    
826    Dobrica Pavlinusic <dpavlin@rot13.org>
827    
828    L<http:E<sol>E<sol>www.rot13.orgE<sol>~dpavlinE<sol>>
829    
830    =head1 LICENSE
831    
832    This product is licensed under GNU Public License (GPL) v2 or later.
833    
834    =cut

Legend:
Removed from v.1.1.1.1  
changed lines
  Added in v.1.22

  ViewVC Help
Powered by ViewVC 1.1.26