/[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.18 - (hide annotations)
Mon Oct 27 18:58:41 2003 UTC (20 years, 5 months ago) by dpavlin
Branch: MAIN
Changes since 1.17: +8 -5 lines
File MIME type: text/plain
spit fatal errors on screen even if quiet (use -q -q to supress all output)

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.18 undef $real_bl if (! -e $real_bl);
253     }
254     if (! $real_bl) {
255 dpavlin 1.17 xlog($share,"no old backup, trying to find last backup,");
256 dpavlin 1.7 if (opendir(BL_DIR, "$BACKUP_DEST/$host/$dir")) {
257     my @bl_dirs = sort grep { !/^\./ && -d "$BACKUP_DEST/$host/$dir/$_" } readdir(BL_DIR);
258     closedir(BL_DIR);
259     $real_bl=pop @bl_dirs;
260 dpavlin 1.17 xlog($share,"using $real_bl as latest...");
261 dpavlin 1.7 $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
262     if ($real_bl eq $bc) {
263     xlog($share,"latest from today (possible partial backup)");
264     rename $real_bl,$real_bl.".partial" || warn "can't reaname partial backup: $!";
265     $real_bl .= ".partial";
266     }
267     } else {
268 dpavlin 1.17 xlog($share,"this is first run...");
269 dpavlin 1.7 }
270 dpavlin 1.1 }
271    
272 dpavlin 1.9 if (-l $bc && $real_bl && $real_bl eq $bc) {
273 dpavlin 1.17 xlog($share,"allready backuped...");
274 dpavlin 1.12 return 1;
275 dpavlin 1.1 }
276    
277     die "You should really create BACKUP_DEST [$BACKUP_DEST] by hand! " if (!-e $BACKUP_DEST);
278    
279     if (! -e "$BACKUP_DEST/$host") {
280     mkdir "$BACKUP_DEST/$host" || die "can't make dir for host $host, $BACKUP_DEST/$host: $!";
281 dpavlin 1.17 xlog($share,"created host directory $BACKUP_DEST/$host...");
282 dpavlin 1.1 }
283    
284     if (! -e "$BACKUP_DEST/$host/$dir") {
285     mkdir "$BACKUP_DEST/$host/$dir" || die "can't make dir for share $share, $BACKUP_DEST/$host/$dir $!";
286 dpavlin 1.17 xlog($share,"created dir for this share $BACKUP_DEST/$host/$dir...");
287 dpavlin 1.1 }
288    
289     mkdir $bc || die "can't make dir for current backup $bc: $!";
290    
291     my @dirs = ( "/" );
292     my @smb_dirs = ( "/" );
293    
294     my $transfer = 0; # bytes transfered over network
295    
296     # this will store all available files and sizes
297     my @files;
298     my %file_size;
299     my %file_atime;
300     my %file_mtime;
301 dpavlin 1.4 #my %file_md5;
302 dpavlin 1.13 %file_md5 = ();
303 dpavlin 1.1
304     my @smb_files;
305     my %smb_size;
306     #my %smb_atime;
307     #my %smb_mtime;
308    
309     sub norm_dir {
310     my $foo = shift;
311     my $prefix = shift;
312     $foo =~ s#//+#/#g;
313     $foo =~ s#/+$##g;
314     $foo =~ s#^/+##g;
315     return $prefix.$foo if ($prefix);
316     return $foo;
317     }
318    
319     # read local filesystem
320     my $di = 0;
321     while ($di <= $#dirs && $real_bl) {
322     my $d=$dirs[$di++];
323 dpavlin 1.7 opendir(DIR,"$real_bl/$d") || warn "opendir($real_bl/$d): $!\n";
324 dpavlin 1.1
325     # read .backupignore if exists
326 dpavlin 1.7 if (-f "$real_bl/$d/.backupignore") {
327     open(I,"$real_bl/$d/.backupignore");
328 dpavlin 1.1 while(<I>) {
329     chomp;
330     push @ignore,norm_dir("$d/$_");
331     }
332     close(I);
333 dpavlin 1.9 #print STDERR "ignore: ",join("|",@ignore),"\n";
334 dpavlin 1.7 link "$real_bl/$d/.backupignore","$bc/$d/.backupignore" ||
335     warn "can't copy $real_bl/$d/.backupignore to current backup dir: $!\n";
336 dpavlin 1.1 }
337    
338     # read .md5sum if exists
339 dpavlin 1.7 if (-f "$real_bl/$d/.md5sum") {
340     open(I,"$real_bl/$d/.md5sum");
341 dpavlin 1.1 while(<I>) {
342     chomp;
343     my ($md5,$f) = split(/\s+/,$_,2);
344     $file_md5{$f}=$md5;
345     }
346     close(I);
347     }
348    
349     my @clutter = readdir(DIR);
350     foreach my $f (@clutter) {
351     next if ($f eq '.');
352     next if ($f eq '..');
353     my $pr = norm_dir("$d/$f"); # path relative
354 dpavlin 1.7 my $pf = norm_dir("$d/$f","$real_bl/"); # path full
355 dpavlin 1.1 if (grep(/^\Q$pr\E$/,@ignore) == 0) {
356     if (-f $pf) {
357     push @files,$pr;
358     $file_size{$pr}=(stat($pf))[7];
359     $file_atime{$pr}=(stat($pf))[8];
360     $file_mtime{$pr}=(stat($pf))[9];
361     } elsif (-d $pf) {
362     push @dirs,$pr;
363     } else {
364 dpavlin 1.17 xlog($share,"not file or directory: $pf",0);
365 dpavlin 1.1 }
366     } else {
367 dpavlin 1.17 xlog($share,"ignored: $pr");
368 dpavlin 1.1 }
369     }
370     }
371    
372 dpavlin 1.12 # local dir always include /
373     xlog($share,($#files+1)." files and ".($#dirs)." dirs on local disk before backup");
374 dpavlin 1.1
375     # read smb filesystem
376    
377     xlog($share,"smb to $share as $param{username}/$param{workgroup}");
378    
379     # FIX: how to aviod creation of ~/.smb/smb.conf ?
380     $smb = new Filesys::SmbClient(%param) || die "SmbClient :$!\n";
381    
382     $di = 0;
383     while ($di <= $#smb_dirs) {
384 dpavlin 1.9 my $d=$smb_dirs[$di];
385 dpavlin 1.1 my $pf = norm_dir($d,"smb:$share/"); # path full
386 dpavlin 1.9 my $D = $smb->opendir($pf);
387     if (! $D) {
388 dpavlin 1.18 xlog($share,"FATAL: $share [$pf] as $param{username}/$param{workgroup}: $!",0);
389 dpavlin 1.9 # remove failing dir
390     delete $smb_dirs[$di];
391 dpavlin 1.12 return 0; # failed
392 dpavlin 1.9 }
393     $di++;
394 dpavlin 1.1
395     my @clutter = $smb->readdir_struct($D);
396     foreach my $item (@clutter) {
397     my $f = $item->[1];
398     next if ($f eq '.');
399     next if ($f eq '..');
400     my $pr = norm_dir("$d/$f"); # path relative
401     my $pf = norm_dir("$d/$f","smb:$share/"); # path full
402     if (grep(/^\Q$pr\E$/,@ignore) == 0) {
403     if ($item->[0] == main::SMBC_FILE) {
404     push @smb_files,$pr;
405     $smb_size{$pr}=($smb->stat($pf))[7];
406     $smb_atime{$pr}=($smb->stat($pf))[10];
407     $smb_mtime{$pr}=($smb->stat($pf))[11];
408     } elsif ($item->[0] == main::SMBC_DIR) {
409     push @smb_dirs,$pr;
410     } else {
411 dpavlin 1.17 xlog($share,"not file or directory [".$item->[0]."]: $pf",0);
412 dpavlin 1.1 }
413     } else {
414 dpavlin 1.17 xlog($share,"smb ignored: $pr");
415 dpavlin 1.1 }
416     }
417     }
418    
419 dpavlin 1.12 xlog($share,($#smb_files+1)." files and ".($#smb_dirs)." dirs on remote share");
420 dpavlin 1.1
421     # sync dirs
422     my $lc = List::Compare->new(\@dirs, \@smb_dirs);
423    
424     my @dirs2erase = $lc->get_Lonly;
425     my @dirs2create = $lc->get_Ronly;
426     xlog($share,($#dirs2erase+1)." dirs to erase and ".($#dirs2create+1)." dirs to create");
427    
428     # create new dirs
429     foreach (sort @smb_dirs) {
430     mkdir "$bc/$_" || warn "mkdir $_: $!\n";
431     }
432    
433     # sync files
434     $lc = List::Compare->new(\@files, \@smb_files);
435    
436     my @files2erase = $lc->get_Lonly;
437     my @files2create = $lc->get_Ronly;
438     xlog($share,($#files2erase+1)." files to erase and ".($#files2create+1)." files to create");
439    
440     sub smb_copy {
441     my $smb = shift;
442    
443     my $from = shift;
444     my $to = shift;
445    
446    
447     my $l = 0;
448    
449     foreach my $f (@_) {
450     #print "smb_copy $from/$f -> $to/$f\n";
451 dpavlin 1.4 my $md5 = Digest::MD5->new;
452    
453 dpavlin 1.1 my $fd = $smb->open("$from/$f");
454     if (! $fd) {
455 dpavlin 1.12 xlog("WARNING","can't open smb file $from/$f: $!");
456     next;
457     }
458    
459     if (! open(F,"> $to/$f")) {
460     xlog("WARNING","can't open new file $to/$f: $!");
461 dpavlin 1.1 next;
462     }
463    
464     while (defined(my $b=$smb->read($fd,4096))) {
465     print F $b;
466     $l += length($b);
467 dpavlin 1.4 $md5->add($b);
468 dpavlin 1.1 }
469    
470     $smb->close($fd);
471     close(F);
472    
473 dpavlin 1.4 $file_md5{$f} = $md5->hexdigest;
474    
475 dpavlin 1.1 # FIX: this fails with -T
476     my ($a,$m) = ($smb->stat("$from/$f"))[10,11];
477     utime $a, $m, "$to/$f" ||
478     warn "can't update utime on $to/$f: $!\n";
479    
480     }
481     return $l;
482     }
483    
484     # copy new files
485     foreach (@files2create) {
486     $transfer += smb_copy($smb,"smb:$share",$bc,$_);
487     }
488    
489     my $size_sync = 0;
490     my $atime_sync = 0;
491     my $mtime_sync = 0;
492     my @sync_files;
493     my @ln_files;
494    
495     foreach ($lc->get_intersection) {
496    
497     my $f;
498    
499     if ($file_size{$_} != $smb_size{$_}) {
500     $f=$_;
501     $size_sync++;
502     }
503     if ($file_atime{$_} != $smb_atime{$_}) {
504     $f=$_;
505     $atime_sync++;
506     }
507     if ($file_mtime{$_} != $smb_mtime{$_}) {
508     $f=$_;
509     $mtime_sync++;
510     }
511    
512     if ($f) {
513     push @sync_files, $f;
514     } else {
515     push @ln_files, $_;
516     }
517     }
518    
519     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.");
520    
521     foreach (@sync_files) {
522     $transfer += smb_copy($smb,"smb:$share",$bc,$_);
523     }
524    
525     xlog($share,"$transfer bytes transfered...");
526    
527     foreach (@ln_files) {
528 dpavlin 1.7 link "$real_bl/$_","$bc/$_" || warn "link $real_bl/$_ -> $bc/$_: $!\n";
529 dpavlin 1.1 }
530    
531     # remove files
532     foreach (sort @files2erase) {
533     unlink "$bc/$_" || warn "unlink $_: $!\n";
534 dpavlin 1.13 delete $file_md5{$_};
535 dpavlin 1.1 }
536    
537     # remove not needed dirs (after files)
538     foreach (sort @dirs2erase) {
539     rmdir "$bc/$_" || warn "rmdir $_: $!\n";
540     }
541    
542 dpavlin 1.4 # remove old .md5sum
543     foreach (sort @dirs) {
544     unlink "$bc/$_/.md5sum" if (-e "$bc/$_/.md5sum");
545     }
546    
547 dpavlin 1.13 # erase stale entries in .md5sum
548     my @md5_files = keys %file_md5;
549     $lc = List::Compare->new(\@md5_files, \@smb_files);
550     foreach my $file ($lc->get_Lonly) {
551     xlog("NOTICE","removing stale '$file' from .md5sum");
552     delete $file_md5{$file};
553     }
554    
555 dpavlin 1.4 # create .md5sum
556     my $last_dir = '';
557     my $md5;
558 dpavlin 1.7 foreach my $f (sort { $file_md5{$a} cmp $file_md5{$b} } keys %file_md5) {
559 dpavlin 1.4 my $dir = dirname($f);
560     my $file = basename($f);
561 dpavlin 1.10 #print "$f -- $dir / $file<--\n";
562 dpavlin 1.4 if ($dir ne $last_dir) {
563     close($md5) if ($md5);
564     open($md5, ">> $bc/$dir/.md5sum") || warn "can't create $bc/$dir/.md5sum: $!";
565     $last_dir = $dir;
566 dpavlin 1.7 #print STDERR "writing $last_dir/.md5sum\n";
567 dpavlin 1.4 }
568     print $md5 $file_md5{$f}," $file\n";
569     }
570 dpavlin 1.11 close($md5) if ($md5);
571 dpavlin 1.1
572     # create leatest link
573 dpavlin 1.7 #print "ln -s $bc $real_bl\n";
574 dpavlin 1.9 if (-l $bl) {
575 dpavlin 1.7 unlink $bl || warn "can't remove old latest symlink $bl: $!\n";
576     }
577     symlink $bc,$bl || warn "can't create latest symlink $bl -> $bc: $!\n";
578    
579     # FIX: sanity check -- remove for speedup
580 dpavlin 1.9 xlog($share,"failed to create latest symlink $bl -> $bc...") if (readlink($bl) ne $bc || ! -l $bl);
581 dpavlin 1.1
582     xlog($share,"backup completed...");
583 dpavlin 1.12
584     return 1;
585 dpavlin 1.1 }
586 dpavlin 1.3 __END__
587 dpavlin 1.1 #-------------------------------------------------------------------------
588    
589 dpavlin 1.3
590     =head1 NAME
591    
592     psinib - Perl Snapshot Is Not Incremental Backup
593    
594     =head1 SYNOPSIS
595    
596 dpavlin 1.17 ./psinib.pl [OPTION]... [mount script]
597 dpavlin 1.3
598     =head1 DESCRIPTION
599    
600 dpavlin 1.17 Option can be one of more of following:
601    
602     =over 8
603    
604     =item C<--backupdest=dir>
605    
606     Specify backup destination directory (defaults is /data/
607    
608     =item C<--noping>
609    
610     Don't use ping to check if host is up (default is ti use tcp syn to cifs
611     port)
612    
613     =item C<--verbose -v>
614    
615     Increase verbosity level. Defailt is 1 which prints moderate amount of data
616     on STDOUT and STDERR.
617    
618     =item C<--quiet -q>
619    
620     Decrease verbosity level
621    
622     =back
623    
624 dpavlin 1.3 This script in current version support just backup of Samba (or Micro$oft
625     Winblowz) shares to central disk space. Central disk space is organized in
626     multiple directories named after:
627    
628     =over 4
629    
630     =item *
631     server which is sharing files to be backed up
632    
633     =item *
634     name of share on server
635    
636     =item *
637     dated directory named like standard ISO date format (YYYYMMDD).
638    
639     =back
640    
641     In each dated directory you will find I<snapshot> of all files on
642     exported share on that particular date.
643    
644     You can also use symlink I<latest> which will lead you to
645     last completed backup. After that you can use some other backup
646     software to transfer I<snapshot> to tape, CD-ROM or some other media.
647    
648     =head2 Design considerations
649    
650     Since taking of share snapshot every day requires a lot of disk space and
651     network bandwidth, B<psinib> uses several techniques to keep disk usage and
652     network traffic at acceptable level:
653    
654     =over 3
655    
656     =item - usage of hard-links to provide same files in each snapshot (as opposed
657     to have multiple copies of same file)
658    
659     =item - usage of file size, atime and mtime to find changes of files without
660     transferring whole file over network (just share browsing is transfered
661     over network)
662    
663     =item - usage of C<.md5sum> files (compatible with command-line utility
664 dpavlin 1.6 C<md5sum>) to keep file between snapshots hard-linked
665 dpavlin 1.3
666     =back
667    
668     =head1 CONFIGURATION
669    
670     This section is not yet written.
671    
672 dpavlin 1.4 =head1 HACKS, TRICKS, BUGS and LIMITATIONS
673    
674     This chapter will have all content that doesn't fit anywhere else.
675    
676     =head2 Can snapshots be more frequent than daily?
677 dpavlin 1.3
678     There is not real reason why you can't take snapshot more often than
679 dpavlin 1.4 once a day. Actually, if you are using B<psinib> to backup Windows
680     workstations you already know that they tend to come-and-go during the day
681     (reboots probably ;-), so running B<psinib> several times a day increases
682     your chance of having up-to-date backup (B<psinib> will not make multiple
683     snapshots for same day, nor will it update snapshot for current day if
684     it already exists).
685 dpavlin 1.3
686 dpavlin 1.4 However, changing B<psinib> to produce snapshots which are, for example, hourly
687 dpavlin 1.3 is a simple change of C<$DIR_TIME_FMT> which is currently set to
688     C<'%Y%m%d'> (see I<strftime> documentation for explanation of that
689     format). If you change that to C<'%Y%m%d-%H> you can have hourly snapshots
690     (if your network is fast enough, that is...). Also, some of messages in
691     program will sound strange, but other than that it should work.
692     I<You have been warned>.
693 dpavlin 1.4
694     =head2 Do I really need to share every directory which I want to snapshot?
695    
696     Actually, no. Due to usage of C<Filesys::SmbClient> module, you can also
697     specify sub-directory inside your share that you want to backup. This feature
698     is most useful if you want to use administrative shares (but, have in mind
699     that you have to enter your Win administrator password in unencrypted file on
700     disk to do that) like this:
701    
702     smbmount //server/c$/WinNT/fonts /mnt -o username=administrator%win
703    
704     After that you will get directories with snapshots like:
705    
706     server/c_WinNT_fonts/yyyymmdd/....
707    
708 dpavlin 1.6 =head2 Won't I run out of disk space?
709    
710     Of course you will... Snapshots and logfiles will eventually fill-up your disk.
711     However, you can do two things to stop that:
712    
713     =head3 Clean snapshort older than x days
714    
715     You can add following command to your C<root> crontab:
716    
717     find /backup/isis_backup -type d -mindepth 3 -maxdepth 3 -mtime +11 -exec rm -Rf {} \;
718    
719     I assume that C</backup/isis_backup> is directory in which are your snapshots
720     and that you don't want to keep snapshots older than 11 days (that's
721     C<-mtime +11> part of command).
722    
723     =head3 Rotate your logs
724    
725     I will leave that to you. I relay on GNU/Debian's C<logrotate> to do it for me.
726 dpavlin 1.7
727     =head2 What are I<YYYYMMDD.partial> directories?
728    
729     If there isn't I<latest> symlink in snapshot directory, it's preatty safe to
730     assume that previous backup from that day failed. So, that directory will
731     be renamed to I<YYYYMMDD.partial> and snapshot will be performed again,
732     linking same files (other alternative would be to erase that dir and find
733     second-oldest directory, but this seemed like more correct approach).
734 dpavlin 1.3
735 dpavlin 1.15 =head2 I can't connect to any share
736    
737     Please verify that nmblookup (which is part of samba package) is in /bin or
738     /usr/bin. Also verify that nmblookup returns IP address for your server
739     using:
740    
741     $ nmblookup tvhouse
742     querying tvhouse on 192.168.34.255
743     192.168.34.30 tvhouse<00>
744    
745     If you don't get any output, your samba might not listen to correct interface
746     (see interfaces in smb.conf).
747    
748 dpavlin 1.3 =head1 AUTHOR
749    
750     Dobrica Pavlinusic <dpavlin@rot13.org>
751    
752 dpavlin 1.17 L<http:E<sol>E<sol>www.rot13.orgE<sol>~dpavlinE<sol>>
753 dpavlin 1.3
754     =head1 LICENSE
755    
756     This product is licensed under GNU Public License (GPL) v2 or later.
757    
758     =cut

  ViewVC Help
Powered by ViewVC 1.1.26