/[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

Annotation of /psinib.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.19 - (hide annotations)
Mon Oct 27 19:07:32 2003 UTC (20 years, 6 months ago) by dpavlin
Branch: MAIN
Changes since 1.18: +6 -2 lines
File MIME type: text/plain
remove non-valid latest links

1 dpavlin 1.1 #!/usr/bin/perl -w
2     #
3     # psinib - Perl Snapshot Is Not Incremental Backup
4     #
5     # written by Dobrica Pavlinusic <dpavlin@rot13.org> 2003-01-03
6     # released under GPL v2 or later.
7     #
8     # Backup SMB directories using file produced by LinNeighbourhood (or some
9     # other program [vi :-)] which produces file in format:
10     #
11     # smbmount service mountpoint options
12     #
13     #
14     # usage:
15 dpavlin 1.4 # $ psinib.pl mountscript
16 dpavlin 1.1
17     use strict 'vars';
18     use Data::Dumper;
19     use Net::Ping;
20     use POSIX qw(strftime);
21     use List::Compare;
22     use Filesys::SmbClient;
23     #use Taint;
24 dpavlin 1.2 use Fcntl qw(LOCK_EX LOCK_NB);
25 dpavlin 1.4 use Digest::MD5;
26     use File::Basename;
27 dpavlin 1.14 use Getopt::Long;
28 dpavlin 1.1
29     # configuration
30     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
32    
33 dpavlin 1.16 # define timeout for ping
34     my $PING_TIMEOUT = 5;
35    
36 dpavlin 1.1 my $LOG = '/var/log/backup.log'; # add path here...
37 dpavlin 1.5 #$LOG = '/tmp/backup.log';
38 dpavlin 1.1
39     # store backups in which directory
40 dpavlin 1.13 my $BACKUP_DEST = '/backup/isis_backup';
41     #my $BACKUP_DEST = '/tmp/backup/';
42 dpavlin 1.1
43     # files to ignore in backup
44     my @ignore = ('.md5sum', '.backupignore', 'backupignore.txt');
45    
46     # open log
47 dpavlin 1.5 open(L, ">> $LOG") || die "can't open log $LOG: $!";
48 dpavlin 1.1 select((select(L), $|=1)[0]); # flush output
49 dpavlin 1.2
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 dpavlin 1.12 xlog("ABORT","can't take lock on $LOG -- another $0 running?");
59 dpavlin 1.2 exit 1;
60     }
61 dpavlin 1.1
62     # taint path: nmblookup should be there!
63     $ENV{'PATH'} = "/usr/bin:/bin";
64    
65 dpavlin 1.17 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 dpavlin 1.14
69     my $result = GetOptions(
70     "ping!" => \$use_ping, "backupdest!" => \$BACKUP_DEST,
71 dpavlin 1.17 "verbose+" => \$verbose, "quiet+" => \$quiet,
72 dpavlin 1.14 );
73    
74 dpavlin 1.17 $verbose -= $quiet;
75    
76 dpavlin 1.1 my $mounts = shift @ARGV ||
77     'mountscript';
78     # die "usage: $0 mountscript";
79    
80    
81     my @in_backup; # shares which are backeduped this run
82    
83 dpavlin 1.16 # init Net::Ping object
84 dpavlin 1.14 my $ping;
85     if ($use_ping) {
86 dpavlin 1.16 $ping = new Net::Ping->new("syn", 2);
87 dpavlin 1.14 # ping will try tcp connect to netbios-ssn (139)
88     $ping->{port_num} = getservbyname("netbios-ssn", "tcp");
89     }
90 dpavlin 1.1
91 dpavlin 1.16 # do syn ping to cifs port
92     sub host_up {
93     my $ping = shift || return;
94     my $host_ip = shift || xlog("host_up didn't get IP");
95     my $timeout = shift;
96     return 1 if (! $use_ping);
97    
98     $ping->ping($host_ip,$timeout);
99     my $return = 0;
100    
101     while (my ($host,$rtt,$ip) = $ping->ack) {
102     xlog("","HOST: $host [$ip] ACKed in $rtt seconds");
103     $return = 1 if ($ip eq $host_ip);
104     }
105     return $return;
106     }
107    
108 dpavlin 1.1 my $backup_ok = 0;
109    
110     my $smb;
111     my %smb_atime;
112     my %smb_mtime;
113 dpavlin 1.4 my %file_md5;
114 dpavlin 1.1
115     open(M, $mounts) || die "can't open $mounts: $!";
116     while(<M>) {
117     chomp;
118     next if !/^\s*smbmount\s/;
119     my (undef,$share,undef,$opt) = split(/\s+/,$_,4);
120    
121 dpavlin 1.11 my ($user,$passwd,$workgroup,$ip);
122 dpavlin 1.1
123     foreach (split(/,/,$opt)) {
124     my ($n,$v) = split(/=/,$_,2);
125     if ($n =~ m/username/i) {
126     if ($v =~ m#^(.+)/(.+)%(.+)$#) {
127     ($user,$passwd,$workgroup) = ($1,$2,$3);
128     } elsif ($v =~ m#^(.+)/(.+)$#) {
129     ($user,$workgroup) = ($1,$2);
130     } elsif ($v =~ m#^(.+)%(.+)$#) {
131     ($user,$passwd) = ($1,$2);
132     } else {
133     $user = $v;
134     }
135     } elsif ($n =~ m#workgroup#i) {
136     $workgroup = $v;
137 dpavlin 1.11 } elsif ($n =~ m#ip#i) {
138     $ip = $v;
139 dpavlin 1.1 }
140     }
141    
142     push @in_backup,$share;
143    
144 dpavlin 1.4
145     my ($host,$dir,$date_dir) = share2host_dir($share);
146     my $bl = "$BACKUP_DEST/$host/$dir/latest"; # latest backup
147     my $bc = "$BACKUP_DEST/$host/$dir/$date_dir"; # current one
148     my $real_bl;
149 dpavlin 1.9 if (-l $bl) {
150 dpavlin 1.4 $real_bl=readlink($bl) || die "can't read link $bl: $!";
151     $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
152 dpavlin 1.9 if (-l $bc && $real_bl eq $bc) {
153 dpavlin 1.17 xlog($share,"allready backuped...");
154 dpavlin 1.4 $backup_ok++;
155     next;
156     }
157    
158     }
159    
160    
161 dpavlin 1.17 xlog($share,"working on $share...");
162 dpavlin 1.1
163 dpavlin 1.11 # try to nmblookup IP
164     $ip = get_ip($share) if (! $ip);
165 dpavlin 1.1
166     if ($ip) {
167     xlog($share,"IP is $ip");
168 dpavlin 1.16 if (host_up($ping, $ip,$PING_TIMEOUT)) {
169 dpavlin 1.12 if (snap_share($share,$user,$passwd,$workgroup)) {
170     $backup_ok++;
171     }
172 dpavlin 1.1 }
173     }
174     }
175     close(M);
176    
177     xlog("","$backup_ok backups completed of total ".($#in_backup+1)." this time (".int($backup_ok*100/($#in_backup+1))." %)");
178    
179     1;
180    
181     #-------------------------------------------------------------------------
182    
183 dpavlin 1.4
184 dpavlin 1.1 # get IP number from share
185     sub get_ip {
186     my $share = shift;
187    
188     my $host = $1 if ($share =~ m#//([^/]+)/#);
189    
190     my $ip = `nmblookup $host`;
191     if ($ip =~ m/(\d+\.\d+\.\d+\.\d+)\s$host/i) {
192     return $1;
193     }
194     }
195    
196 dpavlin 1.4
197     # write entry to screen and log
198 dpavlin 1.1 sub xlog {
199     my $share = shift;
200     my $t = strftime $LOG_TIME_FMT, localtime;
201     my $m = shift || '[no log entry]';
202 dpavlin 1.18 my $l = shift;
203     $l = 1 if (! defined $l); # default verbosity is 1
204 dpavlin 1.17 print STDERR $m,"\n" if ($verbose >= $l);
205 dpavlin 1.1 print L "$t $share\t$m\n";
206     }
207    
208 dpavlin 1.7 # dump warn and dies into log
209 dpavlin 1.18 BEGIN { $SIG{'__WARN__'} = sub { xlog('WARN',$_[0],1) ; warn $_[0] } }
210     BEGIN { $SIG{'__DIE__'} = sub { xlog('DIE',$_[0],0) ; die $_[0] } }
211 dpavlin 1.7
212 dpavlin 1.1
213 dpavlin 1.4 # split share name to host, dir and currnet date dir
214     sub share2host_dir {
215 dpavlin 1.1 my $share = shift;
216     my ($host,$dir);
217     if ($share =~ m#//([^/]+)/(.+)$#) {
218     ($host,$dir) = ($1,$2);
219     $dir =~ s/\W/_/g;
220     $dir =~ s/^_+//;
221     $dir =~ s/_+$//;
222     } else {
223 dpavlin 1.17 xlog($share,"Can't parse share $share into host and directory!",1);
224 dpavlin 1.1 return;
225     }
226 dpavlin 1.4 return ($host,$dir,strftime $DIR_TIME_FMT, localtime);
227     }
228    
229 dpavlin 1.1
230 dpavlin 1.4 # make a snapshot of a share
231     sub snap_share {
232    
233     my $share = shift;
234    
235     my %param = ( debug => 0 );
236    
237 dpavlin 1.8 $param{username} = shift || warn "can't find username for share $share";
238     $param{password} = shift || warn "can't find passwod for share $share";
239     $param{workgroup} = shift || warn "can't find workgroup for share $share";
240 dpavlin 1.4
241     my ($host,$dir,$date_dir) = share2host_dir($share);
242 dpavlin 1.1
243     # latest backup directory
244     my $bl = "$BACKUP_DEST/$host/$dir/latest";
245     # current backup directory
246     my $bc = "$BACKUP_DEST/$host/$dir/$date_dir";
247    
248     my $real_bl;
249 dpavlin 1.9 if (-l $bl) {
250 dpavlin 1.1 $real_bl=readlink($bl) || die "can't read link $bl: $!";
251     $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
252 dpavlin 1.19 if (! -e $real_bl) {
253     xlog($share,"latest link $bl -> $real_bl not valid, removing it");
254     unlink $bl || die "can't remove link $bl: $!";
255     undef $real_bl;
256     }
257 dpavlin 1.18 }
258     if (! $real_bl) {
259 dpavlin 1.19 xlog($share,"no old backup, trying to find last backup");
260 dpavlin 1.7 if (opendir(BL_DIR, "$BACKUP_DEST/$host/$dir")) {
261     my @bl_dirs = sort grep { !/^\./ && -d "$BACKUP_DEST/$host/$dir/$_" } readdir(BL_DIR);
262     closedir(BL_DIR);
263     $real_bl=pop @bl_dirs;
264 dpavlin 1.17 xlog($share,"using $real_bl as latest...");
265 dpavlin 1.7 $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
266     if ($real_bl eq $bc) {
267     xlog($share,"latest from today (possible partial backup)");
268     rename $real_bl,$real_bl.".partial" || warn "can't reaname partial backup: $!";
269     $real_bl .= ".partial";
270     }
271     } else {
272 dpavlin 1.17 xlog($share,"this is first run...");
273 dpavlin 1.7 }
274 dpavlin 1.1 }
275    
276 dpavlin 1.9 if (-l $bc && $real_bl && $real_bl eq $bc) {
277 dpavlin 1.17 xlog($share,"allready backuped...");
278 dpavlin 1.12 return 1;
279 dpavlin 1.1 }
280    
281     die "You should really create BACKUP_DEST [$BACKUP_DEST] by hand! " if (!-e $BACKUP_DEST);
282    
283     if (! -e "$BACKUP_DEST/$host") {
284     mkdir "$BACKUP_DEST/$host" || die "can't make dir for host $host, $BACKUP_DEST/$host: $!";
285 dpavlin 1.17 xlog($share,"created host directory $BACKUP_DEST/$host...");
286 dpavlin 1.1 }
287    
288     if (! -e "$BACKUP_DEST/$host/$dir") {
289     mkdir "$BACKUP_DEST/$host/$dir" || die "can't make dir for share $share, $BACKUP_DEST/$host/$dir $!";
290 dpavlin 1.17 xlog($share,"created dir for this share $BACKUP_DEST/$host/$dir...");
291 dpavlin 1.1 }
292    
293     mkdir $bc || die "can't make dir for current backup $bc: $!";
294    
295     my @dirs = ( "/" );
296     my @smb_dirs = ( "/" );
297    
298     my $transfer = 0; # bytes transfered over network
299    
300     # this will store all available files and sizes
301     my @files;
302     my %file_size;
303     my %file_atime;
304     my %file_mtime;
305 dpavlin 1.4 #my %file_md5;
306 dpavlin 1.13 %file_md5 = ();
307 dpavlin 1.1
308     my @smb_files;
309     my %smb_size;
310     #my %smb_atime;
311     #my %smb_mtime;
312    
313     sub norm_dir {
314     my $foo = shift;
315     my $prefix = shift;
316     $foo =~ s#//+#/#g;
317     $foo =~ s#/+$##g;
318     $foo =~ s#^/+##g;
319     return $prefix.$foo if ($prefix);
320     return $foo;
321     }
322    
323     # read local filesystem
324     my $di = 0;
325     while ($di <= $#dirs && $real_bl) {
326     my $d=$dirs[$di++];
327 dpavlin 1.7 opendir(DIR,"$real_bl/$d") || warn "opendir($real_bl/$d): $!\n";
328 dpavlin 1.1
329     # read .backupignore if exists
330 dpavlin 1.7 if (-f "$real_bl/$d/.backupignore") {
331     open(I,"$real_bl/$d/.backupignore");
332 dpavlin 1.1 while(<I>) {
333     chomp;
334     push @ignore,norm_dir("$d/$_");
335     }
336     close(I);
337 dpavlin 1.9 #print STDERR "ignore: ",join("|",@ignore),"\n";
338 dpavlin 1.7 link "$real_bl/$d/.backupignore","$bc/$d/.backupignore" ||
339     warn "can't copy $real_bl/$d/.backupignore to current backup dir: $!\n";
340 dpavlin 1.1 }
341    
342     # read .md5sum if exists
343 dpavlin 1.7 if (-f "$real_bl/$d/.md5sum") {
344     open(I,"$real_bl/$d/.md5sum");
345 dpavlin 1.1 while(<I>) {
346     chomp;
347     my ($md5,$f) = split(/\s+/,$_,2);
348     $file_md5{$f}=$md5;
349     }
350     close(I);
351     }
352    
353     my @clutter = readdir(DIR);
354     foreach my $f (@clutter) {
355     next if ($f eq '.');
356     next if ($f eq '..');
357     my $pr = norm_dir("$d/$f"); # path relative
358 dpavlin 1.7 my $pf = norm_dir("$d/$f","$real_bl/"); # path full
359 dpavlin 1.1 if (grep(/^\Q$pr\E$/,@ignore) == 0) {
360     if (-f $pf) {
361     push @files,$pr;
362     $file_size{$pr}=(stat($pf))[7];
363     $file_atime{$pr}=(stat($pf))[8];
364     $file_mtime{$pr}=(stat($pf))[9];
365     } elsif (-d $pf) {
366     push @dirs,$pr;
367     } else {
368 dpavlin 1.17 xlog($share,"not file or directory: $pf",0);
369 dpavlin 1.1 }
370     } else {
371 dpavlin 1.17 xlog($share,"ignored: $pr");
372 dpavlin 1.1 }
373     }
374     }
375    
376 dpavlin 1.12 # local dir always include /
377     xlog($share,($#files+1)." files and ".($#dirs)." dirs on local disk before backup");
378 dpavlin 1.1
379     # read smb filesystem
380    
381     xlog($share,"smb to $share as $param{username}/$param{workgroup}");
382    
383     # FIX: how to aviod creation of ~/.smb/smb.conf ?
384     $smb = new Filesys::SmbClient(%param) || die "SmbClient :$!\n";
385    
386     $di = 0;
387     while ($di <= $#smb_dirs) {
388 dpavlin 1.9 my $d=$smb_dirs[$di];
389 dpavlin 1.1 my $pf = norm_dir($d,"smb:$share/"); # path full
390 dpavlin 1.9 my $D = $smb->opendir($pf);
391     if (! $D) {
392 dpavlin 1.18 xlog($share,"FATAL: $share [$pf] as $param{username}/$param{workgroup}: $!",0);
393 dpavlin 1.9 # remove failing dir
394     delete $smb_dirs[$di];
395 dpavlin 1.12 return 0; # failed
396 dpavlin 1.9 }
397     $di++;
398 dpavlin 1.1
399     my @clutter = $smb->readdir_struct($D);
400     foreach my $item (@clutter) {
401     my $f = $item->[1];
402     next if ($f eq '.');
403     next if ($f eq '..');
404     my $pr = norm_dir("$d/$f"); # path relative
405     my $pf = norm_dir("$d/$f","smb:$share/"); # path full
406     if (grep(/^\Q$pr\E$/,@ignore) == 0) {
407     if ($item->[0] == main::SMBC_FILE) {
408     push @smb_files,$pr;
409     $smb_size{$pr}=($smb->stat($pf))[7];
410     $smb_atime{$pr}=($smb->stat($pf))[10];
411     $smb_mtime{$pr}=($smb->stat($pf))[11];
412     } elsif ($item->[0] == main::SMBC_DIR) {
413     push @smb_dirs,$pr;
414     } else {
415 dpavlin 1.17 xlog($share,"not file or directory [".$item->[0]."]: $pf",0);
416 dpavlin 1.1 }
417     } else {
418 dpavlin 1.17 xlog($share,"smb ignored: $pr");
419 dpavlin 1.1 }
420     }
421     }
422    
423 dpavlin 1.12 xlog($share,($#smb_files+1)." files and ".($#smb_dirs)." dirs on remote share");
424 dpavlin 1.1
425     # sync dirs
426     my $lc = List::Compare->new(\@dirs, \@smb_dirs);
427    
428     my @dirs2erase = $lc->get_Lonly;
429     my @dirs2create = $lc->get_Ronly;
430     xlog($share,($#dirs2erase+1)." dirs to erase and ".($#dirs2create+1)." dirs to create");
431    
432     # create new dirs
433     foreach (sort @smb_dirs) {
434     mkdir "$bc/$_" || warn "mkdir $_: $!\n";
435     }
436    
437     # sync files
438     $lc = List::Compare->new(\@files, \@smb_files);
439    
440     my @files2erase = $lc->get_Lonly;
441     my @files2create = $lc->get_Ronly;
442     xlog($share,($#files2erase+1)." files to erase and ".($#files2create+1)." files to create");
443    
444     sub smb_copy {
445     my $smb = shift;
446    
447     my $from = shift;
448     my $to = shift;
449    
450    
451     my $l = 0;
452    
453     foreach my $f (@_) {
454     #print "smb_copy $from/$f -> $to/$f\n";
455 dpavlin 1.4 my $md5 = Digest::MD5->new;
456    
457 dpavlin 1.1 my $fd = $smb->open("$from/$f");
458     if (! $fd) {
459 dpavlin 1.12 xlog("WARNING","can't open smb file $from/$f: $!");
460     next;
461     }
462    
463     if (! open(F,"> $to/$f")) {
464     xlog("WARNING","can't open new file $to/$f: $!");
465 dpavlin 1.1 next;
466     }
467    
468     while (defined(my $b=$smb->read($fd,4096))) {
469     print F $b;
470     $l += length($b);
471 dpavlin 1.4 $md5->add($b);
472 dpavlin 1.1 }
473    
474     $smb->close($fd);
475     close(F);
476    
477 dpavlin 1.4 $file_md5{$f} = $md5->hexdigest;
478    
479 dpavlin 1.1 # FIX: this fails with -T
480     my ($a,$m) = ($smb->stat("$from/$f"))[10,11];
481     utime $a, $m, "$to/$f" ||
482     warn "can't update utime on $to/$f: $!\n";
483    
484     }
485     return $l;
486     }
487    
488     # copy new files
489     foreach (@files2create) {
490     $transfer += smb_copy($smb,"smb:$share",$bc,$_);
491     }
492    
493     my $size_sync = 0;
494     my $atime_sync = 0;
495     my $mtime_sync = 0;
496     my @sync_files;
497     my @ln_files;
498    
499     foreach ($lc->get_intersection) {
500    
501     my $f;
502    
503     if ($file_size{$_} != $smb_size{$_}) {
504     $f=$_;
505     $size_sync++;
506     }
507     if ($file_atime{$_} != $smb_atime{$_}) {
508     $f=$_;
509     $atime_sync++;
510     }
511     if ($file_mtime{$_} != $smb_mtime{$_}) {
512     $f=$_;
513     $mtime_sync++;
514     }
515    
516     if ($f) {
517     push @sync_files, $f;
518     } else {
519     push @ln_files, $_;
520     }
521     }
522    
523     xlog($share,($#sync_files+1)." files will be updated (diff: $size_sync size, $atime_sync atime, $mtime_sync mtime), ".($#ln_files+1)." will be linked.");
524    
525     foreach (@sync_files) {
526     $transfer += smb_copy($smb,"smb:$share",$bc,$_);
527     }
528    
529     xlog($share,"$transfer bytes transfered...");
530    
531     foreach (@ln_files) {
532 dpavlin 1.7 link "$real_bl/$_","$bc/$_" || warn "link $real_bl/$_ -> $bc/$_: $!\n";
533 dpavlin 1.1 }
534    
535     # remove files
536     foreach (sort @files2erase) {
537     unlink "$bc/$_" || warn "unlink $_: $!\n";
538 dpavlin 1.13 delete $file_md5{$_};
539 dpavlin 1.1 }
540    
541     # remove not needed dirs (after files)
542     foreach (sort @dirs2erase) {
543     rmdir "$bc/$_" || warn "rmdir $_: $!\n";
544     }
545    
546 dpavlin 1.4 # remove old .md5sum
547     foreach (sort @dirs) {
548     unlink "$bc/$_/.md5sum" if (-e "$bc/$_/.md5sum");
549     }
550    
551 dpavlin 1.13 # erase stale entries in .md5sum
552     my @md5_files = keys %file_md5;
553     $lc = List::Compare->new(\@md5_files, \@smb_files);
554     foreach my $file ($lc->get_Lonly) {
555     xlog("NOTICE","removing stale '$file' from .md5sum");
556     delete $file_md5{$file};
557     }
558    
559 dpavlin 1.4 # create .md5sum
560     my $last_dir = '';
561     my $md5;
562 dpavlin 1.7 foreach my $f (sort { $file_md5{$a} cmp $file_md5{$b} } keys %file_md5) {
563 dpavlin 1.4 my $dir = dirname($f);
564     my $file = basename($f);
565 dpavlin 1.10 #print "$f -- $dir / $file<--\n";
566 dpavlin 1.4 if ($dir ne $last_dir) {
567     close($md5) if ($md5);
568     open($md5, ">> $bc/$dir/.md5sum") || warn "can't create $bc/$dir/.md5sum: $!";
569     $last_dir = $dir;
570 dpavlin 1.7 #print STDERR "writing $last_dir/.md5sum\n";
571 dpavlin 1.4 }
572     print $md5 $file_md5{$f}," $file\n";
573     }
574 dpavlin 1.11 close($md5) if ($md5);
575 dpavlin 1.1
576     # create leatest link
577 dpavlin 1.7 #print "ln -s $bc $real_bl\n";
578 dpavlin 1.9 if (-l $bl) {
579 dpavlin 1.7 unlink $bl || warn "can't remove old latest symlink $bl: $!\n";
580     }
581     symlink $bc,$bl || warn "can't create latest symlink $bl -> $bc: $!\n";
582    
583     # FIX: sanity check -- remove for speedup
584 dpavlin 1.9 xlog($share,"failed to create latest symlink $bl -> $bc...") if (readlink($bl) ne $bc || ! -l $bl);
585 dpavlin 1.1
586     xlog($share,"backup completed...");
587 dpavlin 1.12
588     return 1;
589 dpavlin 1.1 }
590 dpavlin 1.3 __END__
591 dpavlin 1.1 #-------------------------------------------------------------------------
592    
593 dpavlin 1.3
594     =head1 NAME
595    
596     psinib - Perl Snapshot Is Not Incremental Backup
597    
598     =head1 SYNOPSIS
599    
600 dpavlin 1.17 ./psinib.pl [OPTION]... [mount script]
601 dpavlin 1.3
602     =head1 DESCRIPTION
603    
604 dpavlin 1.17 Option can be one of more of following:
605    
606     =over 8
607    
608     =item C<--backupdest=dir>
609    
610     Specify backup destination directory (defaults is /data/
611    
612     =item C<--noping>
613    
614     Don't use ping to check if host is up (default is ti use tcp syn to cifs
615     port)
616    
617     =item C<--verbose -v>
618    
619     Increase verbosity level. Defailt is 1 which prints moderate amount of data
620     on STDOUT and STDERR.
621    
622     =item C<--quiet -q>
623    
624     Decrease verbosity level
625    
626     =back
627    
628 dpavlin 1.3 This script in current version support just backup of Samba (or Micro$oft
629     Winblowz) shares to central disk space. Central disk space is organized in
630     multiple directories named after:
631    
632     =over 4
633    
634     =item *
635     server which is sharing files to be backed up
636    
637     =item *
638     name of share on server
639    
640     =item *
641     dated directory named like standard ISO date format (YYYYMMDD).
642    
643     =back
644    
645     In each dated directory you will find I<snapshot> of all files on
646     exported share on that particular date.
647    
648     You can also use symlink I<latest> which will lead you to
649     last completed backup. After that you can use some other backup
650     software to transfer I<snapshot> to tape, CD-ROM or some other media.
651    
652     =head2 Design considerations
653    
654     Since taking of share snapshot every day requires a lot of disk space and
655     network bandwidth, B<psinib> uses several techniques to keep disk usage and
656     network traffic at acceptable level:
657    
658     =over 3
659    
660     =item - usage of hard-links to provide same files in each snapshot (as opposed
661     to have multiple copies of same file)
662    
663     =item - usage of file size, atime and mtime to find changes of files without
664     transferring whole file over network (just share browsing is transfered
665     over network)
666    
667     =item - usage of C<.md5sum> files (compatible with command-line utility
668 dpavlin 1.6 C<md5sum>) to keep file between snapshots hard-linked
669 dpavlin 1.3
670     =back
671    
672     =head1 CONFIGURATION
673    
674     This section is not yet written.
675    
676 dpavlin 1.4 =head1 HACKS, TRICKS, BUGS and LIMITATIONS
677    
678     This chapter will have all content that doesn't fit anywhere else.
679    
680     =head2 Can snapshots be more frequent than daily?
681 dpavlin 1.3
682     There is not real reason why you can't take snapshot more often than
683 dpavlin 1.4 once a day. Actually, if you are using B<psinib> to backup Windows
684     workstations you already know that they tend to come-and-go during the day
685     (reboots probably ;-), so running B<psinib> several times a day increases
686     your chance of having up-to-date backup (B<psinib> will not make multiple
687     snapshots for same day, nor will it update snapshot for current day if
688     it already exists).
689 dpavlin 1.3
690 dpavlin 1.4 However, changing B<psinib> to produce snapshots which are, for example, hourly
691 dpavlin 1.3 is a simple change of C<$DIR_TIME_FMT> which is currently set to
692     C<'%Y%m%d'> (see I<strftime> documentation for explanation of that
693     format). If you change that to C<'%Y%m%d-%H> you can have hourly snapshots
694     (if your network is fast enough, that is...). Also, some of messages in
695     program will sound strange, but other than that it should work.
696     I<You have been warned>.
697 dpavlin 1.4
698     =head2 Do I really need to share every directory which I want to snapshot?
699    
700     Actually, no. Due to usage of C<Filesys::SmbClient> module, you can also
701     specify sub-directory inside your share that you want to backup. This feature
702     is most useful if you want to use administrative shares (but, have in mind
703     that you have to enter your Win administrator password in unencrypted file on
704     disk to do that) like this:
705    
706     smbmount //server/c$/WinNT/fonts /mnt -o username=administrator%win
707    
708     After that you will get directories with snapshots like:
709    
710     server/c_WinNT_fonts/yyyymmdd/....
711    
712 dpavlin 1.6 =head2 Won't I run out of disk space?
713    
714     Of course you will... Snapshots and logfiles will eventually fill-up your disk.
715     However, you can do two things to stop that:
716    
717     =head3 Clean snapshort older than x days
718    
719     You can add following command to your C<root> crontab:
720    
721     find /backup/isis_backup -type d -mindepth 3 -maxdepth 3 -mtime +11 -exec rm -Rf {} \;
722    
723     I assume that C</backup/isis_backup> is directory in which are your snapshots
724     and that you don't want to keep snapshots older than 11 days (that's
725     C<-mtime +11> part of command).
726    
727     =head3 Rotate your logs
728    
729     I will leave that to you. I relay on GNU/Debian's C<logrotate> to do it for me.
730 dpavlin 1.7
731     =head2 What are I<YYYYMMDD.partial> directories?
732    
733     If there isn't I<latest> symlink in snapshot directory, it's preatty safe to
734     assume that previous backup from that day failed. So, that directory will
735     be renamed to I<YYYYMMDD.partial> and snapshot will be performed again,
736     linking same files (other alternative would be to erase that dir and find
737     second-oldest directory, but this seemed like more correct approach).
738 dpavlin 1.3
739 dpavlin 1.15 =head2 I can't connect to any share
740    
741     Please verify that nmblookup (which is part of samba package) is in /bin or
742     /usr/bin. Also verify that nmblookup returns IP address for your server
743     using:
744    
745     $ nmblookup tvhouse
746     querying tvhouse on 192.168.34.255
747     192.168.34.30 tvhouse<00>
748    
749     If you don't get any output, your samba might not listen to correct interface
750     (see interfaces in smb.conf).
751    
752 dpavlin 1.3 =head1 AUTHOR
753    
754     Dobrica Pavlinusic <dpavlin@rot13.org>
755    
756 dpavlin 1.17 L<http:E<sol>E<sol>www.rot13.orgE<sol>~dpavlinE<sol>>
757 dpavlin 1.3
758     =head1 LICENSE
759    
760     This product is licensed under GNU Public License (GPL) v2 or later.
761    
762     =cut

  ViewVC Help
Powered by ViewVC 1.1.26