#!/bin/perl # make_tape_backup.pl # Meryll Larkin # December 2020 # January 28, 2021 total refactoring because new HBA H241 # February 1, 2021 added check for "screen" as well as "tmux" # February 3, 2021 changed log file naming for better sorting. Date before tape label. # February 3, 2021 Added BlockSize to top of log file. # February 17, 2021 Added choices of backup command type. # use Cwd; use Cwd qw(getcwd); # use Cwd 'abs_path'; # my $abs_path = abs_path($file); use strict; # Populate Constants for script chomp (my $SCRIPT = $0); my $DEBUG = 0; $SCRIPT =~ s/^.*\///; print " Welcome to $SCRIPT\n"; print " $SCRIPT creates tape backups using a command you select\n"; chomp (my $BKUP_SOURCE=$ARGV[0] || "zzzzzzzzz" ); chomp (my $CWD=getcwd); ############################################# ## ## Set PATH to your Log Directory Here ## ############################################# my $LOG_DIR = "/root/BKUP_TAPE_LOGS"; if ( ! -d $LOG_DIR ) { mkdir $LOG_DIR, 0755; } chomp (my $PID=$$); ############################################# ## ## The File Named exclude_from_tar_bkup.txt ## must exist, even if it is empty ## ############################################# my $EXCLUDEFILE="${CWD}/exclude_from_tar_bkup.txt"; #date and time chomp (my $NOW=`(date +"%Y%m%d_%H:%M")`); my $TODAY = $NOW; # date only $TODAY =~ s/_.*$//; my $CHANGER = &get_autoloader_address; my $DEVICE = &get_tape_address; my $DEST = $DEVICE; $DEST =~ s/st/nst/; print " Destination = $DEST\n" if ($DEBUG); print " Checking status of Tape in Tape Drive..."; # Before engaging Tape, make sure it isn't "busy" - like with another backup... my $device_status = `/bin/mt -f $DEVICE status`; my $status_return_code = $?; print " Status return code = $status_return_code\n" if ($DEBUG); if ( $status_return_code > 0 ) { print "\n"; print " Device is busy! Do not proceed until this is addressed!\n"; exit; } else { print " Okay.\n"; } my $LABEL = &get_tape_label($CHANGER); print " LABEL = $LABEL\n" if ($DEBUG); if ( $LABEL !~ /ABH/ ) { print " tape label = $LABEL does not contain ABH.\n" if ($DEBUG); print " Labeled Tape does not appear to be in Drive Slot. Quitting.\n"; exit; } print " Checking for File of strings to Exclude..."; my $EXCLUDE; my @EXCLUDES; if ( -f $EXCLUDEFILE ) { # we have located the exclusion list file print " Okay.\n"; print " Creating Exclude list.\n"; ($EXCLUDE, @EXCLUDES) = &create_exclude_list($EXCLUDEFILE); } else { print "\n"; print " Unable to find the exclusion list file $EXCLUDEFILE. Exiting.\n"; exit; } # Determine if script is running inside tmux print " Checking to ensure script is being run inside tmux or screen...\n"; my $SESSION; chomp (my $tmux_run = `env | grep TMUX`); chomp (my $screen_run = `env | grep STY`); if ( $tmux_run =~ /\w+/ ) { print " Confirmed running in tmux.\n"; print " TMUX = $tmux_run\n\n" if ($DEBUG); $SESSION = "TMUX"; } elsif ( $screen_run =~ /\w+/ ) { print " Confirmed running in screen.\n"; print " SCREEN = $screen_run\n\n" if ($DEBUG); $SESSION = "SCREEN"; } else { print " This script \"$SCRIPT\" is NOT running in tmux and it needs to be.\n"; print " Quitting.\n\n"; exit; } my %BLOCK = ( 'A' => { 'BF' => '0', 'BS' => '0', }, 'B' => { 'BF' => '64', 'BS' => '32768', }, 'C' => { 'BF' => '128', 'BS' => '65536', }, 'D' => { 'BF' => '256', 'BS' => '131072', }, ); print " Checking BlockSize...\n"; my $BlockSize = &get_blocksize($DEVICE); my $BF_number; while ( 1 ) { print " Optimal BlockSize is '131072' for most backups.\n"; print " Current BlockSize is $BlockSize.\n\n"; print " Your choices are:\n"; print " 1) Keep current BlockSize $BlockSize.\n"; print " 2) Change BlockSize.\n"; print " Please type \"1\" or \"2\": "; chomp (my $answer = ); if ( $answer == "1" ) { print " Keeping BlockSize $BlockSize and proceeding\n"; foreach my $index ( sort keys %BLOCK ) { if ($BlockSize == $BLOCK{$index}{'BS'} ) { $BF_number = $BLOCK{$index}{'BF'}; } } last; } else { print "\n Your options for BlockSize are:\n"; foreach my $index ( sort keys %BLOCK ) { print " $index" . '.' . " $BLOCK{$index}{'BS'} \tAND blocking-factor $BLOCK{$index}{'BF'}\n"; } print "\n Please indicate your preferred BlockSize: "; chomp (my $BSs = ); $BSs = uc $BSs; if (($BSs =~ /^A$/) || ($BSs =~ /^B$/) || ($BSs =~ /^C$/) || ($BSs =~ /^D$/)) { $BlockSize = $BLOCK{$BSs}{'BS'}; print "\n Changing BlockSize to $BlockSize\n\n"; `mt -f $DEVICE defblksize $BlockSize`; $BF_number = $BLOCK{$BSs}{'BF'}; } else { print " Response $BSs not understood. Try again.\n"; } $BlockSize = &get_blocksize($DEVICE); } } print " Setting blocking-factor to $BF_number\n"; my $BF; if ( $BF_number == 0 ) { $BF = ""; } else { $BF = "-b" . $BF_number; } print " Creating a Logfile for the Backup Job...\n"; my $LOG = $LOG_DIR . "/" . "backup_${TODAY}_${LABEL}_${PID}.log"; open (LOG, ">>$LOG") || die "Unable to open log $LOG. Quitting.\n" . $! . "\n"; print "\n Logfile name is $LOG\n"; my @BKUP_SOURCES; if ( -d $BKUP_SOURCE ) { # a single backup source has been provided as an argument to the script. print " Backup dir $BKUP_SOURCE exists!\n" if ($DEBUG); push (@BKUP_SOURCES, $BKUP_SOURCE); } else { my $i = 1; $BKUP_SOURCE = ""; # Put this in a loop so multiple directories can be added to Backup Job while ( $i > 0 ) { print " Please provide absolute paths of directories to be backed-up (or q for complete) : "; chomp (my $bup_dir = ); # if response was a directory, not "quit" if ( $bup_dir !~ /^q\s*$/i ) { # remove final slash if one was entered: $bup_dir =~ s/\/$//; if ( ! -d $bup_dir ) { print "$bup_dir is not a directory, try again.\n"; next; } push (@BKUP_SOURCES, $bup_dir); $BKUP_SOURCE = $BKUP_SOURCE . " " . $bup_dir; } else { # response was "q" for quit - we have all our directories $i = 0; } } } sleep 2; print " Final checks before backup....\n"; print " These are the backup sources:\n"; foreach my $source (@BKUP_SOURCES) { print " $source\n"; } print "\n LAST CHANCE FOR SIZE CHECK....\n"; print " Would you like to do a size check on the Backup Sources? [y|N] "; chomp (my $size_check = ); my $total_size; if (( $size_check eq "y" ) || ( $size_check eq "Y")) { print " Please be patient, this may take a while\n"; $total_size = &do_size_check (@BKUP_SOURCES); my $megabytes = $1 if ($total_size =~ /^(\d+)M/); # 11,000,000 M = 11 T # 11000000 M = 11 T if ($total_size > 11000000 ) { print " Total size $total_size is greater than 11T. Quitting\n"; exit; } else { print " Total size is $total_size. Less than 11T. Okay to proceed.\n"; } } print "\n Proceeding...\n"; sleep 2; print "\n"; print " These are the files/directories/name strings to Exclude:\n"; if ( $EXCLUDES[0] ) { foreach my $exclude (@EXCLUDES) { print " $exclude\n"; } } else { print " $EXCLUDE\n"; } my %BKUP_COMMANDS = &create_backup_command_selection($BF,$DEST,$DEVICE,$EXCLUDE,$BKUP_SOURCE); print "\n Select your backup command:\n"; foreach my $index (sort keys %BKUP_COMMANDS) { print " $index" . '.' . " $BKUP_COMMANDS{$index}\n"; } print "\n Choice? Default is 'A': "; chomp (my $BKUP_CMD = ); $BKUP_CMD = uc $BKUP_CMD; if (($BKUP_CMD !~ /^A$/) && ($BKUP_CMD !~ /^B$/) && ($BKUP_CMD !~ /^C$/) && ($BKUP_CMD !~ /^D$/)) { $BKUP_CMD = $BKUP_COMMANDS{A}; } else { $BKUP_CMD = $BKUP_COMMANDS{$BKUP_CMD}; } print "\n\n Here is your Backup Command:\n"; print "\n $BKUP_CMD\n"; print "\n\n Are you ready to proceed? [y|N] "; chomp (my $proceed = ); if (( $proceed ne "y" ) && ( $proceed ne "Y")) { print " Quitting due to user response\n"; exit; } ############################################################## ######## ######## ACTUAL WORK ON TAPE STARTS HERE ######## ############################################################## sleep 5; print "mt -f $DEVICE status\n"; my $status = `mt -f $DEVICE status`; print $status; print "\n\nRewinding the tape to the beginning:\n"; print "mt -f $DEVICE rewind\n"; `mt -f $DEVICE rewind`; `sleep 10`; # flush buffer and set it $status=`sync; echo 3 >/proc/sys/vm/drop_caches`; $status = `mt -f $DEVICE drvbuffer 0`; $status = `mt -f $DEVICE drvbuffer 1`; $status = `mt -f $DEVICE status`; print $status; chomp (my $NOW=`(date +"%Y%m%d_%H:%M")`); print LOG "Begin Backup: $LABEL $NOW\n"; print LOG "Total Data Size: $total_size\n" if ($total_size > 1); print LOG "Using BlockSize: $BlockSize\n"; print LOG "Using Session: $SESSION\n"; print LOG " $BKUP_CMD\n\n"; print LOG "******************************\n"; if ( $BKUP_CMD =~ /cvf/ ) { `$BKUP_CMD >> $LOG`; } if ( $BKUP_CMD =~ /cf/ ) { print LOG "\nThis backup Command does not provide output\n"; `$BKUP_CMD`; } chomp ($NOW=`(date +"%Y%m%d_%H:%M")`); print LOG "END Backup $NOW\n"; close LOG; exit; # ************************************************* sub create_backup_command_selection { my ($BF,$DEST,$DEVICE,$EXCLUDE,$BKUP_SOURCE) = @_; my %BKUP_COMMANDS; $BKUP_COMMANDS{A} = "tar $BF -cvf $DEST $EXCLUDE $BKUP_SOURCE"; $BKUP_COMMANDS{B} = "tar $BF $EXCLUDE -cf - $BKUP_SOURCE | cat > $DEVICE"; $BKUP_COMMANDS{C} = "tar $BF $EXCLUDE -cf - $BKUP_SOURCE | cat > $DEVICE"; $BKUP_COMMANDS{D} = "tar $BF $EXCLUDE -cf - $BKUP_SOURCE | cat > $DEVICE"; return %BKUP_COMMANDS; } # ************************************************* sub get_autoloader_address { my $changer_count = `/bin/lsscsi -g |grep mediumx|wc -l`; my $changer; if ($changer_count > 1) { my @many_changers =`/bin/lsscsi -g |grep mediumx`; # Take the 2nd listing because the first one is probably horked $changer = $many_changers[1]; } else { $changer = `/bin/lsscsi -g |grep mediumx`; } my @changers = split /\s+/, $changer; chomp (my $autoloader = $changers[8]); return $autoloader; } # ************************************************* sub get_tape_address { my $tape_lines = `/bin/lsscsi |grep tape |wc`; my $tape; if ( $tape_lines > 1 ) { # take the first line # $tape = `/bin/lsscsi |grep tape| head -n1`; # take the second line $tape = `/bin/lsscsi |grep tape| tail -n1`; } else { $tape = `/bin/lsscsi |grep tape`; } my @tapes = split /\s+/, $tape; chomp (my $tape_addr = $tapes[-1]); return $tape_addr; } # ************************************************* sub get_tape_label { my $changer = shift; my $tape_drive = `/sbin/mtx -f $changer status |grep Transfer`; chomp (my $volume_tag = $tape_drive); # remove all characters up the the equal sign and the space after it $volume_tag =~ s/^.*=\s+//; # remove any trailing characters that are not numbers or letters $volume_tag =~ s/\W+$//; # remove any trailing spaces $volume_tag =~ s/s+$//; return $volume_tag; } # ************************************************* sub get_blocksize { my $device = shift; my $blocksize =`/sbin/tapeinfo -f $device |grep BlockSize`; # remove anything that is not a number from $blocksize $blocksize =~ s/\D+//g; # print "blocksize = $blocksize\n"; return $blocksize; } # ************************************************* sub create_exclude_list { my ($EXCLUDEFILE) = @_; my @EXCLUDEARRAY; my $EXCLUDESTRING = "--exclude='swapfile' "; open (INPUT, $EXCLUDEFILE) || die "unable to open input $EXCLUDEFILE " . $! . "\n"; while () { chomp (my $line = $_); # ignore comment lines next if ($line =~ m/^\s*#/); # ignore empty lines next if ($line =~ m/^\s*$/); push (@EXCLUDEARRAY, $line); my $EXCLUDE = "--exclude=\"" . $line . '"'; $EXCLUDESTRING = $EXCLUDESTRING . " " . $EXCLUDE; } close INPUT; return ($EXCLUDESTRING,@EXCLUDEARRAY); } # ************************************************* sub do_size_check { my @directories = @_; my $size; my $total_sizek; my $total_size; foreach my $dir (@directories) { $size = `du -s $dir`; print " $size"; $size =~ s/\s+.*$//; $total_sizek += $size; } { use integer; $total_size = ${total_sizek}/1024; } return "${total_size}M"; } # *************************************************