#!/usr/bin/perl # ft-backup - backup using ftape and dump # # Usage: # ft-backup [-f] [filesystem ...] # -f for faster backup run # (i.e. don't do anything that requires extra tape movement) # If no filesystems listed on command line, backup all filesystems with # nonzero dump flag in /etc/fstab (second-to-last field). Otherwise # backup only filesystems given on command line. # configurable parameters $TAPE = "/dev/nzqft0"; # use compressed device $ratio = 1.7; # avg compression ratio, >= 1.0 $threshold = "1400"; # if tape is bigger than $threshold megs, do full dump $blocksize= "10"; # in KB, ftape defaults to 10 # don't forget to specify the blocksize to restore(8) using 'b' # program locations $date = "/bin/date"; $mt = "/usr/bin/ftmt"; $dump = "/sbin/dump"; $tar = "/bin/tar"; $logger = "/usr/bin/logger"; $swapout = "/sbin/swapout"; use Sys::Hostname; $host = hostname; sub log { # log message to syslog my($message,$loglevel) = @_; $loglevel = "info" unless ($loglevel); ! system "$logger -t ft-backup -p daemon.$loglevel $message"; } sub string2k { # get size as string, return size in K local($_) = @_; my($size,$mult); # initialize multiplier table values in K my(%multable) = ( "G" => 1024*1024, "M" => 1024, "K" => 1, "" => 1/1024, ); ($size,$mult) = /^\s*([\d.]+)\s+([a-z]?)[a-z]*bytes/i; $mult = uc($mult); return ($size * $multable{$mult}); } sub mt { my(@command) = @_; my($command) = "@command"; @command = split(/\s+/,$command); ! system($mt,"-f","$TAPE",@command); } sub status { my($verbose) = @_; my(@line,$rest,%stat); local($_); open(STATUS,"$mt -f $TAPE status|"); while () { print if $verbose; chomp; if (s/^This is a /This is a,/) { (@line[0,1],$rest) = split(/,/,$_); $stat{$line[0]} = $line[1]; $rest =~ /^ \(([^\)]*)\).*$/; $_ = $1; } if (/^\(/) { # catch: (In particular: * whatever * ) /^\(([^:]*): (.*) \)$/; $_ = "$1 = $2"; } next unless (/ = /); @line = split(/\s+=\s+/); $stat{$line[0]} = $line[1]; } close(STATUS); # return reference to %stat return \%stat; } sub getsize { # return size in K my $stat = &status(); return(string2k($$stat{'total bytes on tape'})); } sub fslist { my(@list) = @_; my(@fs); # for each possible filesystem, define whether to use dump, tar, or null my %backtype = ( "auto" => "tar", # tar can do anything "minix" => "tar", "ext" => "tar", "ext2" => "dump", "xiafs" => "tar", "ufs" => "tar", "sysv" => "tar", "xenix" => "tar", "coherent" => "tar", "msdos" => "tar", "fat" => "tar", "fat32" => "tar", "vfat" => "tar", "umsdos"=> "tar", "hpfs" => "tar", "iso9660" => "tar", # probably shouldn't backup CDs "nfs" => "tar", # ideally, NFS-mounts should backup there "smb" => "tar", "ncp" => "tar", "affs" => "tar", "swap" => "null", "proc" => "null", "ignore" => "null", "" => "null", ); open(FSTAB,"/etc/fstab"); while() { next unless (/^[^\#]/); @line=split; next if ($line[1] eq "none"); if (@list) { if (grep($_ eq $line[0],@list) or grep($_ eq $line[1],@list)) { push(@fs,"$line[1] $backtype{$line[2]}"); } } else { push(@fs,"$line[1] $backtype{$line[2]}") if ($line[4]); } } close(FSTAB); return(@fs); } sub dump { my($level,$fs) = @_; my $label; my $labelfile; my $retval; my @dumpargs; my $now = time; $label = "$host:$fs"; $label =~ s|/|,|g; # use "," instead of "/" $label =~ s/:,/:/; # but omit initial marker # first touch the label file if (-d $fs) { $labelfile = "$fs/.$label"; if (-e $labelfile) { utime($now,$now,$labelfile); } else { open(LABEL,">$labelfile"); close(LABEL); } } &log("Dumping $fs"); print "\n+ dumping $fs....\n"; @dumpargs = ($dump,"${level}ufbB", $TAPE, $blocksize, int($ratio*$size), # take compression ratio into account $fs ); print " @dumpargs\n"; $retval = system(@dumpargs); return !$retval; } sub tar { my($fs) = @_; my $retval,@tarargs; &log("Tarring $fs"); print "\n+ tarring $fs....\n"; @tarargs = ($tar,"cf", $TAPE, "--one-file-system", "--sparse", "--blocking-factor", 2*$blocksize, "--totals", "--atime-preserve", "--preserve-permissions", "--label", "$host:$fs", "--directory", $fs, "." ); print " @tarargs\n"; # emulate dump's verbosity print " TAR: Date of this tar: ", `$date '+%a %b %d %T %Y'`; print " TAR: Tarring $fs to $TAPE\n TAR: "; $retval = system(@tarargs); print " TAR: TAR IS DONE\n"; return !$retval; } sub null { my($fs) = @_; &log("Not backing up $fs"); print "+ Not backing up $fs\n"; } sub getratio { my $physspace = 0; my $realsize = 0; my $phystot = 0; my $realtot = 0; my $ratio = 0; my $stat; # reference to status hash &log("Calculating compression ratio"); &mt("rewind"); # back to beginning $stat = &status(); $physspace = string2k($$stat{'physical space used'}); $realsize = string2k($$stat{'real size of volume'}); while ($realsize) { # real size is zero when done $phystot += $physspace; $realtot += $realsize; &mt("fsf 1"); # next file $stat = &status(); $physspace = string2k($$stat{'physical space used'}); $realsize = string2k($$stat{'real size of volume'}); } &mt("rewind"); if ($phystot) { $ratio = ($realtot/$phystot); } else { $ratio = 0; } # round to 3 dec places $ratio = sprintf('%.3f',$ratio); &log("Compression ratio was 1:$ratio"); return $ratio; } ##### # unbuffer STDOUT and STDERR, and select STDERR (which dump uses) $|=1; select STDERR; $|=1; @opts = grep(/^-/,@ARGV); @argv = grep(!/^-/,@ARGV); $fast=1 if (grep(/^-f/,@opts)); $datenow = `$date`; &log("Starting backup"); print "Starting backup at $datenow\n\n"; system($swapout,"30"); if (${&status(1)}{"In particular"} =~ /(\(no tape\)|^\*?$)/) { &log("Tape offline or missing -- aborting"); print "\nTape offline or missing -- aborting\n"; exit 1; } if (${&status(1)}{"In particular"} =~ / tape write protected /) { &log("Tape write-protected -- aborting"); print "\nTape write-protected -- aborting\n"; exit 1; } unless ($fast) { &log("Retensioning tape"); print "\nRetensioning tape.\n"; &mt("reten") or die "Unable to retension tape -- No tape? Device permissions?\n"; } &log("Rewinding tape"); print "Rewinding tape.\n"; &mt("rewind"); # get real and compressed size of tape $size = &getsize; $ratio = 1 if ($device !~ /z/); # non-compressed device gets ratio 1:1 $csize = $ratio*$size; # allow for doing different kind of backup # depending on length of tape if ($size > $threshold*1024) { $level = 0; } elsif ($size > 0) { $level = 3; } else { &log("Tape is size $size? Aborting"); print "Tape size is $size ??? Aborting!\n"; exit 1; } $size_M = int(sprintf('%.0f',$size/1024)); $csize_M = int(sprintf('%.0f',$csize/1024)); print "Tape size is ${size_M}M"; print " (${csize_M}M with 1:${ratio} compression)" if ($ratio != 1); print "\n"; print "...doing level $level backup" if (defined ($dump)); print "\n\n"; # get filesystems to backup, either from command line or from fstab @fs = &fslist(@argv); # backup each one individually foreach (@fs) { ($fs,$type) = split; if ($type eq "dump") { &dump($level,$fs); } elsif ($type eq "tar") { &tar($fs); } elsif ($type eq "null") { &null($fs); } else { print "unknown type $type: "; &null($fs); } } print "\n"; # show new tape status &status(1); unless ($fast or ($ratio==1)) { # get (new) average compression ratio $ratio = &getratio(); print "\nActual compression ratio for this backup was 1:$ratio\n"; } # rewind and take tape offline &mt("rewoffl"); $datenow = `$date`; &log("Backup done"); print "\nDone at $datenow\n"; __END__