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

Contents of /psinib.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.21 - (show annotations)
Sat Nov 8 01:46:53 2003 UTC (20 years, 5 months ago) by dpavlin
Branch: MAIN
Changes since 1.20: +1 -1 lines
File MIME type: text/plain
don't warn on undef if there isn't subjects.txt file

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

  ViewVC Help
Powered by ViewVC 1.1.26