/[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.17 - (hide annotations)
Sun Oct 26 14:04:17 2003 UTC (20 years, 6 months ago) by dpavlin
Branch: MAIN
Changes since 1.16: +47 -17 lines
File MIME type: text/plain
verbose, quiet options and added documentation for command line options

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

  ViewVC Help
Powered by ViewVC 1.1.26