#!/usr/bin/perl use strict; ($main::progname = $0) =~ s/^.*\/([^\/]+)/$1/; $main::svn_id = '$HeadURL$ $LastChangedRevision$'; $main::version_scheme = 'svn'; $main::release_version = 'ADE_APP_TOKEN_RELEASEID'; ############################################################################### # # # CONFIGURABLE STUFF STARTS HERE # # # ############################################################################### my ($tapes_db_file, $backup_log_dir, $backup_lock_dir, $block_size); my ($this_host); my ($tmp_dir) = "/var/tmp"; my ($old_log_compression_age) = 7; ############################################################################### # # # CONFIGURABLE STUFF ENDS HERE # # # ############################################################################### sub main { my(@ARGV) = @_; ########################################################################## # # PROCESS OPTIONS # ########################################################################## # Default values for options $backup_conf_file = "$backup_etc_prefix/$main::progname.conf"; $opt_simulate = 0; $opt_verify = 1; $opt_eject = 1; $opt_tapetests = 1; $opt_mainmode = "dobackup"; $tapes_db_file = "$backup_state_prefix/tapes.ffd"; $backup_log_dir = "$backup_log_prefix"; $backup_lock_dir = "$backup_lock_prefix"; $opt_infosrc = "configfile"; $block_size = 10; # Loop over each option while (defined($ARGV[0]) && $ARGV[0] =~ /^-./) { $_ = shift @ARGV; # standard options if (/^(?:-V|--version)$/) { &ade_msg_version(\&app_version); } elsif (/^(?:-d|--debug)(.*)/) { $main::verboselevel = ($1 ? $1 : shift @ARGV); } elsif (/^(?:-v|--verbose)$/) { $main::verboselevel = 3; } elsif (/^(?:-h|--help)$/) { &ade_msg_usage(\&app_usage, 0); } elsif (/^(?:-p|--list-paths)$/) { &ade_msg_listpaths(\&app_listpaths); # application-specific options } elsif (/^(?:--conf-file)(.*)/) { $backup_conf_file = ($1 ? $1 : shift @ARGV); } elsif (/^(?:-n|--simulate)$/) { $opt_simulate = 1; } elsif (/^(?:--no-eject)$/) { $opt_eject = 0; } elsif (/^(?:--no-honest-backup)$/) { $do_trust_backup_exit_code = 0; } elsif (/^(?:-b|--backup-list)$/) { $opt_mainmode = "listbackups"; } elsif (/^(?:-a|--allocate-media)$/) { $opt_mainmode = "allocatemedia"; } elsif (/^(?:-u|--unallocate-media)$/) { $opt_mainmode = "unallocatemedia"; } elsif (/^(?:-m|--list-media)$/) { $opt_mainmode = "listmedia"; } elsif (/^(?:--no-verify)$/) { $opt_verify = 0; } elsif (/^(?:--no-tape-tests)$/) { $opt_tapetests = 0; } elsif (/^(?:--tapes-file)(.*)/) { $tapes_db_file = ($1 ? $1 : shift @ARGV); } elsif (/^(?:--log-dir)(.*)/) { $backup_log_dir = ($1 ? $1 : shift @ARGV); } elsif (/^(?:--lock-dir)(.*)/) { $backup_lock_dir = ($1 ? $1 : shift @ARGV); # --block-size-translator) MODE=blocksizetranslator ;; } elsif (/^(?:--block-size)(.*)/) { $block_size = ($1 ? $1 : shift @ARGV); } elsif (/^(?:-c|--command-line)$/) { $opt_infosrc = "commandline"; # bad options } else { &ade_msg_usage(\&app_usage, 1); } } ########################################################################## # # ERROR HANDLING AND GENERIC SANITY CHECKS # ########################################################################## &ade_msg_error("can't read config file: $backup_conf_file") if (! -r $backup_conf_file); chomp($this_host=`hostname`); if ($opt_mainmode eq "listbackups") { (!$ARGV[0]) && &ade_msg_usage(\&app_usage, 1); return(&listbackups); } elsif ($opt_mainmode eq "listmedia") { (!$ARGV[0] || $ARGV[1]) && &ade_msg_usage(\&app_usage, 1); return(&listmedia($ARGV[0])); } elsif ($opt_mainmode eq "allocatemedia") { (!$ARGV[2] || $ARGV[3]) && &ade_msg_usage(\&app_usage, 1); return(&allocatemedia($ARGV[0], $ARGV[1], $ARGV[2])); } elsif ($opt_mainmode eq "unallocatemedia") { (!$ARGV[0] || $ARGV[1]) && &ade_msg_usage(\&app_usage, 1); return(&unallocatemedia($ARGV[0])); } elsif ($opt_mainmode eq "dobackup" && $opt_infosrc eq "commandline") { (!$ARGV[4] || $ARGV[5]) && &ade_msg_usage(\&app_usage, 1); &ade_lck_lock("", \&ade_lck_getgenericlockfilename); $ade_msg_logfile = "$backup_log_dir/$main::progname-$media_id.$date_tag.log"; $rc = &dobackup("DUMMY", $media_id, $srcinfo_host, $srcinfo_user, $srcinfo_meth, $srcinfo_path); &ade_lck_unlock("", \&ade_lck_getgenericlockfilename); return($rc); } elsif ($opt_mainmode eq "dobackup" && $opt_infosrc eq "configfile") { (!$ARGV[1] || $ARGV[2]) && &ade_msg_usage(\&app_usage, 1); return(&dobackup($ARGV[0], $ARGV[1], undef, undef, undef)); } } sub listbackups { my ($backup_id, $run_host, $backup_desc); # Don't bother rationalising the config file, we don't refer to it much open(BACKUP_CONF_HANDLE, $backup_conf_file) || &ade_msg_error("can't open $backup_conf_file"); while () { chomp; next if (!($backup_id, $run_host, $backup_desc) = (/^choice:\s*(.*?)\s+(.*?)\s+(.*)$/)); printf "%3s %s\n", "$backup_id)", $backup_desc; } close(BACKUP_CONF_FILE); return(0); } sub app_version { my($version_ref) = @_; ${$version_ref} = $main::version; return(0); } sub app_usage { print "Usage: $main::progname [ ] \n"; print " $main::progname [ ] { -c | --command-line } \n"; print " $main::progname [ ] { -b | --backup-list }\n"; print " $main::progname [ ] { -m | --media-list } [ ]\n"; print " $main::progname [ ] { -a | --allocate-media } \n"; print " $main::progname [ ] { -u | --unallocate-media } \n"; print "\n"; # standard options print "Options: -V | --version display version information\n"; print " -v | --verbose verbose\n"; print " -d | --debug set debug level\n"; print " -h | --help display this text\n"; print " -p | --list-paths list paths used by this program\n"; # application-specific options print " -c | --command-line specify backup parameters on command line\n"; print " --conf-file override default config file\n"; print " --tapes-file override default tape information file\n"; print " --log-dir override default log directory\n"; print " --lock-dir override default lock directory\n"; print " --no-verify don't verify backup\n"; print " --no-eject don't eject tape at end\n"; print " --no-honest-backup for badly-behaved tars\n"; print " --no-tape-tests skip the lengthy tape access tests\n"; print ; return(0); } sub app_listpaths { printf "Config-File: $backup_conf_file\n"; printf "Tapes-File: $tapes_db_file\n"; printf "Lock-Directory: $backup_lock_dir\n"; printf "Log-Directory: $backup_log_dir\n"; return(0); } sub allocatemedia { my ($media_id, $data_host_regexp, $media_size) = @_;; return(&ade_ffd_queryinsert($tapes_db_file, $media_id, $data_host_regexp, $media_size)); } sub unallocatemedia { my ($media_id) = @_; return(&ade_ffd_querydelete($tapes_db_file, $media_id)); } sub listmedia { my ($backup_id) = @_; my ($media_id, @data_hosts, $data_host); # translate backup id into a set of data hosts open(BACKUP_CONF_HANDLE, $backup_conf_file) || &ade_msg_error("can't open $backup_conf_file"); while () { chomp; next if (!/^backup:\s*$backup_id\s+(.*?)\s+.*$/); push(@data_hosts, $1); } close(BACKUP_CONF_HANDLE); # Display the media ids which are acceptable for all the data hosts for $media_id (&ade_ffd_queryselect($tapes_db_file, 1, 1)) { # Assume media ok for backing up data on this host unless we determine otherwise $media_ok_for_data_host = 1; for $data_host (@data_hosts) { &ade_msg_error("can't verify media ok for data host") if (&verify_media_ok_for_data_host($media_id, $data_host \$media_ok_for_data_host) != 0); last if (!$media_ok_for_data_host); } print "$media_id\n" if ($media_ok_for_data_host); } } sub verify_media_ok_for_data_host { my ($media_id, $data_host, $media_is_ok_for_data_host_ref) = @_; my ($data_host_regexp, @data_host_regexps); &ade_msg_debug(10, "verify_media_ok_for_data_host: sof (\$media_id=$media_id, \$data_host=$data_host)"; return($rc) if (($rc=&ade_ffd_queryselect($tapes_db_file, 2, 2, "^$media_id:", \@data_host_regexps)) != 0); # ade_ffd_queryselect already ensures that there will only be one result for a search on the primary key. ($data_host_regexp) = @data_host_regexps; # first try the data host as it stands against the regexp if ($data_host =~ /^$data_host_regex$/) { ${$media_is_ok_for_data_host_ref} = 1; # Then try localhost ... } elsif ($data_host eq $this_host && "localhost" =~ /^$data_host_regex$/) { ${$media_is_ok_for_data_host_ref} = 1; # and non-localhost variants. } elsif ($data_host eq "localhost" && $this_host =~ /^$data_host_regex$/) { ${$media_is_ok_for_data_host_ref} = 1; # otherwise this media is not good. } else { ${$media_is_ok_for_data_host_ref} = 0; } # The function itself has succeeded in determining what it was to determine. return (0); } sub dobackup { my ($backup_id, $media_id, $srcinfo_host, $srcinfo_user, $srcinfo_meth, $srcinfo_path) = @_; my ($rc); &ade_msg_debug(10, "dobackup: sof (\$backup_id=$backup_id, \$media_id=$media_id, ..."); ########################################################################### # # SET UP LOGGING # ########################################################################### chomp($date_tag = `date '+%Y%m%d%H%M%S'`); # here we should do a test to check that the log file is writable but i don' know what the function call is yet. ########################################################################### # # PREAMBLE # ########################################################################### &ade_msg_info("initialising software ..."); # Rationalise config file &ade_tmp_register("$tmp_dir/$PROGNAME.$$.rcf"); &ade_fcm_normaliseconfigfile($backup_conf_file, "$tmp_dir/$PROGNAME.$$.rcf"); # Verify backup id is known and that we're on the right host if ($opt_infosrc eq "configfile") { open(RCF_HANDLE, "$tmp_dir/$main::progname.$$.rcf") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.rcf"); while () { chomp; next if (!/^choice:\s*$backup_id\s+(\S+)\s+.*/); $run_host_regexp = $1; } close(RCF_HANDLE); &ade_msg_error("$backup_id: no such backup id") if (!$run_host_regexp); &ade_msg_error("this backup can only be run on $run_host_regexp") if ($this_host !~ /^$run_host_regexp$/); } ########################################################################## # # COLLECT NAMES OF BACKUPS TO DO # ########################################################################## &ade_tmp_register("$tmp_dir/$PROGNAME.$$.bul"); open(BUL_HANDLE, ">$tmp_dir/$PROGNAME.$$.bul") || &ade_msg_error("can't open $tmp_dir/$PROGNAME.$$.bul"); if ($opt_infosrc eq "configfile") { &ade_msg_debug(10, "dobackup: extracting list of backups to do from config file ..."); open(RCF_HANDLE, "$tmp_dir/$main::progname.$$.rcf") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.rcf"); while () { chomp; next if (!/^backup:\s*$backup_id\s+(.*)$/); print BUL_HANDLE "$1\n"; } close(RCF_HANDLE); } elsif { $opt_infosrc eq "commandline") { &ade_msg_debug(10, "dobackup: generating list of backups to do from command line ..."); print BUL_HANDLE "$srcinfo_host $srcinfo_user $srcinfo_meth $srcinfo_path\n"; fi close(BUL_HANDLE); ########################################################################## # # COLLECT THE NAMES OF THE DATA HOSTS AND VERIFY AGAINST MEDIA # ########################################################################## open(BUL_HANDLE, "$tmp_dir/$main::progname.$$.bul") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.bul"); while () { chomp; /^(\S+)\s+.*$/; push(@data_hosts, $1); } close(BUL_HANDLE); for $data_host (@data_hosts) { &ade_msg_error("media '$media_id' may not be used to back up data on $data_host") if (&verify_media_ok_for_data_host($media_id, $data_host) != 0); } ########################################################################## # # COLLECT THE NAME OF THE TAPE HOST # ########################################################################## # If line matches 'device:', save the host regex and rest of line to $1 # and $2, $1 will get overwritten in a moment so copy it to $keep, then # if $THIS_HOST matches the regex (delimited in ^ and $) then display # the stuff in $keep. open(RCF_HANDLE, "$tmp_dir/$main::progname.$$.rcf") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.rcf"); while () { chomp; next if (!/^device:\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/); next if ($this_host != /^$1$/); &ade_msg_error("multiple tape hosts are available for $this_host") if ($tape_host); $tape_host = $1; $tape_rdev = $2; $tape_ndev = $3; $tape_user = $4; } close(RCF_HANDLE); &ade_msg_error("no tape host is available for $this_host") if (!$tape_host); ########################################################################## # # VERIFY ACCESS TO ALL REQUIRED HOSTS FROM ALL REQUIRED HOSTS # ########################################################################## # here -> tape host &ade_msg_debug(10, "dobackup: checking from here (`uname -n`) to tape host ($TAPE_HOST) ..."); &ade_msg_error("can't access tape host ($tape_host) from this host") if (&ade_net_remsh("$TAPE_HOST -l $TAPE_USER true") != 0); open(BUL_HANDLE, "$tmp_dir/$main::progname.$$.bul") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.bul"); while () { chomp; /^(\S+)\s+(\S+)\s+(\S+)\s+(.*)\s*?$/; $data_host = $1; $data_user = $2; $data_meth = $3; @data_paths = split(\s+, $4); # here -> data hosts &ade_msg_debug(10, "dobackup: checking from $this_host to data host $data_host ..."); &ade_msg_error "can'access data host $data_host from here") if (&ade_net_remsh("$data_host -l $data_user true") != 0); # data hosts -> tape host &ade_msg_error "can'access tape host $tape_host from data host $data_host") if &ade_net_remsh("-h $data_host -u $data_user $tape_host -l $tape_user true") != 0); } close(BUL_HANDLE); ########################################################################## # # COLLECT THE NAMES OF ALL REQUIRED METHODS # ########################################################################## &ade_msg_debug(10, "dobackup: collecting names of used backup methods ..."); open(BUL_HANDLE, "$tmp_dir/$main::progname.$$.bul") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.bul"); while () { chomp; /^(\S+)\s+(\S+)\s+(\S+)\s+(.*)\s*?$/; $data_host = $1; $data_user = $2; $data_meth = $3; @data_paths = split(\s+, $4); push(@required_methods, $data_meth); } close(BUL_HANDLE); &ade_msg_debug(10, sprintf("dobackup: used backup methods are %s", join(' ', @required_methods))); ########################################################################## # # CHECK SIZE, BACKUP AND VERIFY METHODS KNOWN # ########################################################################## &ade_msg_debug(10, "dobackup: verifying backup, verify and size methods exist for each used backup method ..."); open(RCF_HANDLE, "$tmp_dir/$main::progname.$$.rcf") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.rcf"); for $data_meth (@required_methods) { # rewind to beginning of file to begin scan again (saves closing and reopening) seek(RCF_HANDLE, 0, 0); while () { chomp; if (/^method:\s*$data_meth\s+(.*?)\s*$/) { &ade_msg_error("method command for '$data_meth' multiply defined") if ($method_cmdtpld); $method_cmdtpl = $1; } elsif (/^verify:\s*$data_meth\s+(.*?)\s*$/) { &ade_msg_error("verify command for '$data_meth' multiply defined") if ($verify_cmdtpld); $verify_cmdtpl = $1; } elsif (/^size:\s*$data_meth\s+(.*?)\s*$/) { &ade_msg_error("size command for '$data_meth' multiply defined") if ($size_cmdtpld); $size_cmdtpl = $1; } &ade_msg_error("method command for '$data_meth' not defined") if (!$method_cmdtpld); &ade_msg_error("verify command for '$data_meth' not defined") if (!$verify_cmdtpld); &ade_msg_error("size command for '$data_meth' not defined") if (!$size_cmdtpld); # undef so when looping round to check next required method won't think it's already defined undef($method_cmdtpl); undef($verify_cmdtpl); undef($size_cmdtpl); } close(RCF_HANDLE); ########################################################################## # # CONSTRUCT TAPE OPERATION COMMANDS # ########################################################################## &ade_msg_error("couldn't determine OS of $tape_host") if (&ade_net_remsh(\$tapeunames, "$tape_host -l $tape_user uname -s") != 0); chomp($tapeunames); if ($tapeunames eq "SunOS") { $mt_cmd = "/usr/bin/mt -f"; $mt_stat_cmd = "$mt_cmd $tape_ndev stat >/dev/null 2>&1"; $mt_eod_cmd = "$mt_cmd $tape_ndev eom"; $devval_test_cmd = "[ -c $tape_ndev -a -r $tape_ndev -a -w $tape_ndev ]"; $mt_fsf_cmd = "$mt_cmd $tape_ndev fsf 1"; $mt_eject_cmd = "$mt_cmd $taperdev offline"; $read_test_cmd = "{ < $tape_ndev; } 2>/dev/null"; $write_test_cmd = "{ $mt_cmd $tape_ndev eof; } 2>/dev/null"; # SunOS 1st rewind doesn't wait for completion, second does $mt_rewind_cmd = "$MT_CMD $TAPERDEV rewind && $MT_CMD $TAPERDEV rewind"; } elsif ($tapeunames eq "HP-UX") { $mt_cmd = "/usr/bin/mt -t" $mt_eod_cmd = "true"; $devval_test_cmd = "[ -c $tape_ndev -a -r $tape_ndev -a -w $tape_ndev ]"; $mt_fsf_cmd = "$mt_cmd $tape_ndev fsf 1"; $mt_eject_cmd = "$mt_cmd $taperdev offline"; $read_test_cmd = "{ < $tape_ndev; } 2>/dev/null"; $write_test_cmd = "{ $mt_cmd $tape_ndev eof; } 2>/dev/null"; $mt_stat_cmd = "true; $mt_rewind_cmd = "$mt_cmd $taperdev rewind"; } elsif ($tapeunames eq "Linux") { $mt_cmd = "/bin/mt -t" $mt_eod_cmd = "$mt_cmd $tape_ndev eom"; $devval_test_cmd = "[ -c $tape_ndev -a -r $tape_ndev -a -w $tape_ndev ]"; $mt_fsf_cmd = "$mt_cmd $tape_ndev fsf 1"; $mt_eject_cmd = "$mt_cmd $taperdev offline"; $read_test_cmd = "{ < $tape_ndev; } 2>/dev/null"; $write_test_cmd = "{ $mt_cmd $tape_ndev eof; } 2>/dev/null"; $mt_stat_cmd = "$mt_cmd $tape_ndev stat >/dev/null 2>&1"; $mt_rewind_cmd = "$mt_cmd $taperdev rewind"; } else { &ade_msg_error("sorry, no support for $tapeunames yet"); } ########################################################################## # # SIZE CHECKS # ########################################################################## &ade_msg_debug(10, "dobackup: checking backup(s) will fit on media ..."); # Get the size of the specified media (do it before the 'du' in case # *this* goes wrong, and all that 'du' time was wasted. $media_size = &ade_ffd_queryselect($TAPES_DB_FILE, 3, 3, "^$media_id:"); &ade_msg_error("invalid media size (media id '$MEDIA_ID', size '$MEDIA_SIZE')") if ($media_size" !~ /^[0-9]+$/); &ade_msg_debug(10, "dobackup: media size is $media_size Kb"); $total_size = 0; open(BUL_HANDLE, "$tmp_dir/$main::progname.$$.bul") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.bul"); open(RCF_HANDLE, "$tmp_dir/$main::progname.$$.rcf") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.rcf"); while () { chomp; /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/; $data_host = $1; $data_user = $2; $data_meth = $3; @data_paths = split(\s+, $4); push(@data_hosts, $1); ade_msg_debug 10 "dobackup: top of size loop, read DATA_HOST=$DATA_HOST, DATA_METH=$DATA_METH, DATA_PATHS=$DATA_PATHS" # rewind to beginning of file to begin scan again (saves closing and reopening) seek(RCF_HANDLE, 0, 0); while () { chomp; next if (!/^size:\s*$data_meth\s+(.*?)\s*$/); $size_cmdtpl = $1; } # Let the shell expand DATA_PATHS to it's full list. # e.g. if the user puts '/diskb/svn-repos.*' then # this cannot be passed to '[ -r ]' as it is. We # need to expand it and pass them one at a time. But # it doesn't work. for $data_path_unglobbed (@data_paths) { for $data_path (split(' ', glob($data_path_unglobbed))) { $size_cmd = ($size_cmdtpl =~ s/DATA_PATH/$data_path/g); &ade_msg_debug(10, "dobackup: size cmd is $SIZE_CMD"); # Data is *always* remote, even when local &ade_msgerrror("can't remsh") if (&ade_net_remsh(\$size, "$data_host -l $data_user "$size_cmd") != 0); &ade_msg_debug(10, "dobackup: size is $size Kb"); $total_size += $size; } } } close(BUL_HANDLE); close(RCF_HANDLE); # Probably need some overhead factor here for block size etc, but # for now it's a straight comparison &ade_msg_error("media $media_id is not big enough for this backup") if ($media_size< $total_size); ########################################################################## # # GENERATE LOG ENTRY # ########################################################################## chomp($date = `date`); $i = 0; if ($opt_infosrc eq "configfile") { open(RCF_HANDLE, "$tmp_dir/$main::progname.$$.rcf") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.rcf"); while () { chomp; next if (!/^backup:\s*$backup_id\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*/); $data_host = $1; $data_user = $2; $data_meth = $3; @data_paths = split(\s+, $data_paths); $log_line = sprintf("%-31s %-15s %-6s %-1s %s:%s", $date, "$media_id+$i", $data_meth, 0, $data_host, join(' ', @data_paths)); &ade_msg_info("backup record entry: $LOG_LINE"); $i++; } close(RCF_HANDLE); elif [ $opt_infosrc eq "commandline") { $log_line = sprintf("%-31s %-15s %-6s %-1s %s:%s", $date, "$media_id+$i", $srcinfo_meth, 0, $srcinfo_host, join(' ', @srcinfo_paths)); &ade_msg_info("backup record entry: $LOG_LINE"); fi ########################################################################## # # TAPE CHECKS AND POSITIONING # ########################################################################## if ($opt_tapetests) { &ade_msg_info("initialising hardware ..."); &ade_msg_debug(4, "dobackup: $devval_test_cmd"); &ade_msg_error("invalid device file (right device file?)") if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $devval_test_cmd) != 0); &ade_msg_debug(4, "dobackup: $mt_stat_cmd"); &ade_msg_error("invalid hardware (device on and connected?)") if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $mt_stat_cmd) != 0); &ade_msg_debug(4, "dobackup: $read_test_cmd"); if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $read_test_cmd) != 0) { &ade_msg_warning("read failed, retrying ..."); sleep 1; &ade_msg_error("read failed (tape missing/wrong device?)") if (&ade_net_remsh($tape_host, "-l", $tape_user, $read_test_cmd) != 0); } # check tape is writable &ade_msg_debug(4, "dobackup: $write_test_cmd"); &ade_msg_error("write failed (write-protected?)") if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $write_test_cmd) != 0); } # tape sanity tests complete, rewind tape &ade_msg_error("rewind failed") if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $mt_rewind_cmd) != 0); ########################################################################## # # PROCESS EACH BACKUP REQUEST # ########################################################################## $backup_idx = 0; open(BUL_HANDLE, "$tmp_dir/$main::progname.$$.bul") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.bul"); open(RCF_HANDLE, "$tmp_dir/$main::progname.$$.rcf") || &ade_msg_error("can't open $tmp_dir/$main::progname.$$.rcf"); while () { chomp; /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/; $data_host = $1; $data_user = $2; $data_meth = $3; $globbed_data_paths = glob($4); &ade_msg_debug(10, "dobackup: top of backup loop, read data_host=$data_host, data_meth=$data_meth, globbed_data_paths=$globbed_data_paths"); ###################################################################### # # CONSTRUCT THE BACKUP COMMAND # ###################################################################### seek(RCF_HANDLE, 0, 0); while () { chomp; next if (!/^method:\s*$data_meth\s+(.*?)\s*$/); method_cmdtpl = $1; } &ade_msg_debug(10, "dobackup: method cmd is $method_cmd"); # User substitutions $backup_cmd =~ s/DATA_PATHS/$globbed_data_paths/g; $backup_cmd =~ s/BLOCK_SIZE/$block_size/g; $backup_cmd =~ s/MEDIA_SIZE/$media_size/g; $backup_cmd =~ s/BACKUP_ID/$media_id/g; ###################################################################### # # DO THE BACKUP # ###################################################################### &ade_msg_info("backing up $DATA_HOST:$DATA_PATHS ..."); &ade_msg_debug(4, "dobackup: backup_cmd=$backup_cmd"); &ade_tmp_register("$tmp_dir/$PROGNAME.$$.backup"); open(BACKUP_HANDLE, ">$tmp_dir/$PROGNAME.$$.backup") || &ade_msg_error("can't open $tmp_dir/$PROGNAME.$$.backup"); if ($opt_simulate) { print BACKUP_HANDLE "this line is a simulated tape backup"; } else { # the backup command should *not* be verbose &ade_msg_error("can't open backup reader") if (&ade_net_remsh(undef, \*READER_OUTPUT, "-n", $data_host, "-l", $data_user, $backup_cmd) !- 0); &ade_msg_error("can't open backup writer") if (&ade_net_remsh(\*WRITER_INPUT, \*BACKUP_HANDLE, "-n", $tape_host, "-l", $tape_user, "dd obs=${block_size}k of=$tape_ndev"); } close(BACKUP_HANDLE); should print the first 20 lines of "$tmp_dir/$PROGNAME.$$.backup" to stdeout here unlink "$tmp_dir/$PROGNAME.$$.backup"; &ade_tmp_deregister("$tmp_dir/$PROGNAME.$$.backup"); if (!$opt_verify) { $backup_idx++; next; } ################################################################## # # PREPARE TAPE FOR VERIFY AND CONSTRUCT VERIFY COMMAND # ################################################################## # move tape back to beginning of backup $BACKUP_IDX &ade_msg_debug(10, "rewinding tape ..."); &ade_msg_debug(4, "dobackup: MT_REWIND_CMD=$MT_REWIND_CMD"); &ade_msg_error("rewind failed") if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $mt_rewind_cmd) != 0); &ade_msg_debug(10, "moving $BACKUP_IDX files forward ..."); for ($i=0; $i<$backup_idx; $i++) { &ade_msg_debug(4, "dobackup: MT_FSF_CMD=$MT_FSF_CMD"); &ade_msg_error("forward failed") if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $mt_fsf_cmd) != 0); } # make substitutions in verify method to make verify command seek(RCF_HANDLE, 0, 0); while () { chomp; next if (!/^verify:\s*$data_meth\s+(.*?)\s*$/); verify_cmdtpl = $1; } &ade_msg_debug(10, "dobackup: verify cmd is $verify_cmd"); # User substitutions $verify_cmd =~ s/DATA_PATHS/$globbed_data_paths/g; $verify_cmd =~ s/BLOCK_SIZE/$block_size/g; ################################################################## # # DO THE VERIFY # ################################################################## &ade_msg_info("verifying $data_host:$data_paths ..."); &ade_tmp_register("$tmp_dir/$PROGNAME.$$.verify"); open(VERIFY_OUTPUT, ">$tmp_dir/$PROGNAME.$$.verify") || &ade_msg_error("can't open $tmp_dir/$PROGNAME.$$.verify"); if (!$opt_simulate) { printf VERIFY_OUTPUT "this line is a simulated tape verify"; } else { # the verify command *should* be verbose &ade_msg_debug(8, "dobackup: verify_cmd=$verify_cmd"); &ade_net_remsh(DEVNULL_OUTPUT, TAPEREADER_OUTPUT, $tape_host, "-l", $tape_user, "dd ibs=${block_size}k if=$tape_ndev"); &ade_net_remsh(VERIFIER_INPUT, VERIFIER_OUTPUT, $data_host, "-l", $data_user, $verify_cmd); while() { print VERIFIER_INPUT; } } close(VERIFIER_OUTPUT); close(TAPEREADER_OUTPUT); close(VERIFIER_INPUT); close(DEVNULL_OUTPUT); should print the first 20 lines of "$tmp_dir/$PROGNAME.$$.verify" to stdeout here unlink "$tmp_dir/$PROGNAME.$$.verify"; &ade_tmp_deregister("$tmp_dir/$PROGNAME.$$.verify"); ################################################################## # # POST-VERIFY TAPE REPOSITIONING # ################################################################## # 'tar tvf' and 'ufsrestore' don't leave head at safe writable pos &ade_msg_debug(10, "moving to end of recorded media ..."); &ade_msg_error("end of data failed") if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $mt_eod_cmd) != 0); $backup_idx++; } close (BUL_HANDLE); close (RCF_HANDLE); unlink "$tmp_dir/$PROGNAME.$$.rcf $tmp_dir/$PROGNAME.$$.bul"; &ade_tmp_deregister("$tmp_dir/$PROGNAME.$$.rcf $tmp_dir/$PROGNAME.$$.bul"); &ade_msg_info("tidying up ..."); ########################################################################## # # LOG CLEANUP # ########################################################################## # Any previous backup on this tape has now been rendered unusable - so # we can now delete *all* old log files relating to the same tape. Strictly # this should be done *absolutely immediately* after the write-protection # test a few lines above, but in reality that means that we can't group # the tape rewind with the rest of the tape access commands, and the user # may be confused by seeing "wiping old log files" and then a *long* # silent pause. if (!$opt_simulate) { opendir(LOGDIR_HANDLE, $backup_log_dir) || &ade_msg_error("can't open directory $backup_log_dir"); while () { # Ignore non-log files next if (!/^$main::progname-.*?\..*\.log(|\.gz)$/); # Ignore *this* log file! next if ($_ eq $ade_msg_logfile); # If for this media delete it if (/^$main::progname-$media_id\..*\.log(|\.gz)$/) { unlink $_; # If for other media then compress if olde } elsif ((stat....)[?] + $old_log_compression_age*(3600*24) < time) { system "$compress_cmd -f $_"; } } close(LOGDIR_HANDLE); } ########################################################################## # # REWIND AND EJECT TAPE # ########################################################################## &ade_msg_debug(4, "dobackup: mt_rewind_cmd=$mt_rewind_cmd"); &ade_msg_error("rewind failed") if (!$opt_simulate && &ade_net_remsh($tape_host, "-l", $tape_user, $mt_rewind_cmd) != 0); &ade_msg_error("eject failed") if ($opt_eject && !$opt_simulate && &ade_net_remsh(undef, undef, $tape_host, "-l", $tape_user, $mt_eject_cmd) != 0); } sub app_make_lock_file_name { my ($lockwhat) = @_; return("$backup_lock_dir/$main::progname.pid"); } # converts a Kb value into a block size string appropriate to a particular program #blocksizetranslator() #{ # typeset PROGRAM VALUE # # PROGRAM=$1 # VALUE=$2 # # case $PROGRAM in # cpio) ade_msg_internalerror "blocksizetranslator: program parameter '$PROGRAM' not coded yet" ;; # tar) expr 2 \* $VALUE ;; # tar blocks are measured in 1/2 Kb # dump) echo $VALUE ;; # dump blocks are measured in Kb # *) ade_msg_internalerror "blocksizetranslator: called with illegal program parameter '$PROGRAM'" ;; # esac # # return 0 #} sub ade_net_remsh { my (@ARGV) = @_; my ($local_user, $src_host, $src_user, $dst_host, $dst_user, $cmd); # fascilitate traditional remsh syntax detection $got_host = 0; # option defaults $src_host = $this_host; # ade_usr_getmyloginname() is now an ADE compliant function # and so this call is simply wrong! I have no intention of # fixing it because this whole script is obsolete, but I put # this comment here in case I ever resurrect it. $src_user = &ade_usr_getmyloginname; $minus_n_string = ""; &ade_msg_debug(20, "ade_net_remsh: processing options ..."); while (defined($ARGV[0])) { $_ = shift @ARGV; if (/^-h(.*)/) { $src_host = ($1 ? $1 : shift @ARGV); } elsif (/^-u(.*)/) { $src_user = ($1 ? $1 : shift @ARGV); # ade_usr_getmyloginname() is now an ADE compliant function # and so this call is simply wrong! I have no intention of # fixing it because this whole script is obsolete, but I put # this comment here in case I ever resurrect it. $src_user = &ade_usr_getmyloginname if ($src_user eq "SELF"); } elsif (/^-n$/) { $minus_n_string = "-n" ;; } elsif (/^-l(.*)/) { $dst_user = ($1 ? $1 : shift @ARGV); # ade_usr_getmyloginname() is now an ADE compliant function # and so this call is simply wrong! I have no intention of # fixing it because this whole script is obsolete, but I put # this comment here in case I ever resurrect it. $dst_user = &ade_usr_getmyloginname if ($dst_user eq "SELF"); } else { if (!$got_host) { $dst_host = $_; $got_host = 1; } else { $cmd = join(' ', @ARGV); $got_cmd = 1; last; } } &ade_msg_debug(20, "ade_net_remsh: $src_host --> $dst_host"); if (($src_host eq $this_host || $src_host eq "localhost") && ($dst_host eq $this_host || $dst_host eq "localhost")) { system $cmd; } elsif (($src_host eq $this_host || $src_host eq "localhost") && ($dst_host ne $this_host && $dst_host ne "localhost")) { &remsh_wrapper($minus_n_string, $dst_host, "-l", $dst_user, $cmd); } elsif (($src_host ne $this_host && $src_host ne "localhost") && ($dst_host eq $src_host || $dst_host eq "localhost")) { &remsh_wrapper($minus_n_string, $dst_host, "-l", $dst_user, $cmd); } else { &remsh_remsh($minus_n_string, "-h", $src_user, "-u", $src_user, $dst_host, "-l", $dst_user, $cmd); } } sub remsh_remsh { &ade_msg_internalerror("remsh_remsh: not coded yet"); } sub remsh_wrapper { my ($input_handle, $output_handle, @ARGV) = @_; $got_host = 0; # default values for options $minus_n_string = ""; &ade_msg_debug(20, "remsh_wrapper: processing options ..."); while (defined($ARGV[0])) { $_ = shift @ARGV; if (/^-n/) { $minus_n_string = "-n"; } elsif (/^-l(.*)/) { $dst_user = ($1 ? $1 : shift @ARGV); } else { if (!$got_host) { $dst_host = $_; $got_host = 1; } else { $cmd = join(' ', @ARGV); $got_cmd = 1; last; } } } open(REMSH_HANDLE, "|$local_remsh_cmd $dst_host -l $dst_user \"cat > /tmp/$PROGNAME.$$.scr\"") || .. printf REMSH_HANDLE "#!/bin/sh\n"; printf REMSH_HANDLE "$cmd\n"; printf REMSH_HANDLE "echo \$? > /tmp/$PROGNAME.$$.rc\n"; close(REMSH_HANDLE); system "$local_remsh_cmd $dst_host -l $dst_user sh /TMP/$progname.$$.scr"; open(REMSH_HANDLE, "$local_remsh_cmd $dst_host -l $dst_user cat > /tmp/$PROGNAME.$$.rc|") || .. while () { chomp; # int acts on $_ by default; $rc = int; } system "$local_remsh_cmd $dst_host -l $dst_user rm /TMP/$progname.$$.scr"; return($rc); }