#!/usr/bin/perl
#
# $Revision: 1.330 $
# Copyright 2009-2012 Teleflora
#
# Script to perform backups and restores of an RTI and/or Daisy system.
#

use strict;
use warnings;
use POSIX;
use IO::Socket;
use Getopt::Long;
use Digest::MD5;
use English;
use Net::SMTP;
use File::Basename;

my $CVS_REVISION = '$Revision: 1.330 $';
my $TIMESTAMP = strftime("%Y%m%d%H%M%S", localtime());
my $PROGNAME = basename($0);

my $HELP = 0;
my $VERSION = 0;

my $OS = "";
my $ALTOS = "";
my $ALTROOT = "";

my @BACKUP = ();
my @RESTORE = ();
my @RESTORE_EXCLUDES = ();
my @EMAIL = ();
my @PRINTER = ();
my @USERFILES = ();
my @EXCLUDES = ();
my $EMAIL_SERVER = "";
my $EMAIL_USER = "";
my $EMAIL_PASS = "";
my $VERIFY = 0;
my $VERBOSE = 0;
my $FORCE = 0;
my $FORMAT = 0;
my $MOUNT = 0;
my $UNMOUNT = 0;
my $FINDDEV = 0;
my $INSTALL = 0;
my $SHOWKEY = 0;
my $CHECKMEDIA = 0;
my $DVD = 0;
my $EJECT = 0;
my $EJECTDAYS = "mon,tue,wed,thu,fri";
my $CURRENT_ARCH = "";
my $CONSOLE =  0;
my $NOCC =  0;
my $DAISY =  0;
my $DDBACKUP =  0;
my $RTI =  0;
my @LIST = ();
my $CHECKFILE = 0;
my $CONFIGFILE = "";
my $REPORT_CONFIGFILE = 0;
my $DEVICE = "";
my $DEVICE_IMAGE_FILE_MIN = 1048576; # 1 MB in bytes
my $DEVICE_VENDOR = "WD";
my $DEVICE_MODEL = "My Passport";
my $USB_DEVICE = 0;
my $DVDDEV = "";
my $CRYPTKEY = "";
my $VALIDATE_CRYPTKEY = 0;
my $ROOTDIR = "/";
my $SENDTO = "";
my @SENDTO_EXCLUDES = ();
my $SENDTO_ENCRYPT = 0;
my $SECONDARY = 0;
my $RCVFROM = "";
my $RCVFROM_DECRYPT = 0;
my $TOOLSDIR = "";
my $RTIDIR = "/usr2/bbx"; # This could be changed based on 'rootdir' value.
my $DAISYDIR = "/d"; # This could be different based on 'rootdir' value.
my $MOUNTPOINT = "/mnt/backups";
my $LOGFILE = "";
my $LOGFILE_DEF = "rtibackup.log";
my $DEBUGLOGFILE = "";
my $DEBUGMODE = 0;
my $NEED_USAGE = 0; #Surely there's a better way to do this.
my $backup_returnval = -1;
my $DSYSAVEALL = 0;
my @ARGV_ORIG = @ARGV;
my $VERIFY_FAILED = 0;
my $AUTO_CHECKMEDIA = 0;
my $COMPRESS_BU = 0;
my $DECOMPRESS_BU = 0;
my $KEEP_OLD_FILES = 0;
my $GETINFO = 0;
my $DRY_RUN = 0;
my $UPGRADE = 0;
my $HARDEN_LINUX = 1;
my $BACKUP_SUMMARY_INFO = "";

#
# Constants
#

# script exit status
my $EXIT_OK = 0;
my $EXIT_COMMAND_LINE = 1;
my $EXIT_PLATFORM = 2;
my $EXIT_ROOTDIR = 3;
my $EXIT_TOOLSDIR = 4;
my $EXIT_BACKUP_DEVICE_NOT_FOUND = 5;
my $EXIT_USB_DEVICE_NOT_FOUND = 6;
my $EXIT_BACKUP_TYPE = 7;
my $EXIT_RESTORE = 9;
my $EXIT_LIST = 10;
my $EXIT_DEVICE_VERIFY = 11;
my $EXIT_USB_DEVICE_UNSUPPORTED = 12;
my $EXIT_SAMBA_CONF = 23;
my $EXIT_SENDTO_CONNECT = 30;
my $EXIT_SENDTO_IPADDR = 31;
my $EXIT_SENDTO_SVCTAG = 32;
my $EXIT_SENDTO_READ_ERR = 33;
my $EXIT_SENDTO_WRITE_ERR = 34;
my $EXIT_SECONDARY_IN_USE = 40;
my $EXIT_SECONDARY_IPADDR = 41;
my $EXIT_SECONDARY_SVCTAG = 42;
my $EXIT_SECONDARY_READ_ERR = 43;
my $EXIT_SECONDARY_WRITE_ERR = 44;

# wait time for DVD writes
my $MAX_WAIT_TIME = 3 * 60 * 60; # Seconds

# port used to communicate from primary to secondary
my $SECONDARY_SVR_PORT = 15020;

# the lockfile used for --checkmedia
my $LOCKFILE_NAME = "rtibackup.lock";
my $LOCKFILE_DIR = "";
my $LOCKFILE = "";

# Device types are:
# 1) "passport" - a Western Digital Passport USB disk
# 2) "rev"      - an IOmega Rev drive
# 3) "usb"      - a USB disk with Teleflora label
my $DEVICE_TYPE = "";

my @BACKUP_TYPES = (
	"all",
	"usr2",
	"daisy",
	"printconfigs",
	"rticonfigs",
	"daisyconfigs",
	"osconfigs",
	"netconfigs",
	"altiris",
	"userconfigs",
	"userfiles",
	"logfiles"
);
my @RESTORE_TYPES = (@BACKUP_TYPES, "bbxd", "bbxps", "singlefiles");

#
# If a particular restore type needs to have the perms fixed, 
# it should set one of these global flags and then the perms
# will be fixed just before exit.
#
my $RUN_RTI_PERMS = 0;
my $RUN_DAISY_PERMS = 0;

#
# If the user permits it via the "--harden_linux" command line option,
# run the harden_linux.pl script after restoration of some files.
#
my $RUN_HARDEN_LINUX = 0;

#
# The name of the crypt key validation file - it is put onto a
# backup device every time a successful backup is completed.
#
my $VALIDATION_FILE = "teleflora-cryptkey-validation-20111130.dat";

#
# The name of the rtibacukp.pl setup file - it is written to the
# backup device any time it is formatted via "--format".
#
my $FORMAT_FILE = "teleflora-formatted.txt";

#
# The command line must be recorded before the GetOptions modules
# is called or any options will be removed.
#
my $command_line = get_command_line();

#Getopt::Long::Configure("pass_through");
GetOptions(
	"help" => \$HELP,
	"version" => \$VERSION,
	"configfile=s" => \$CONFIGFILE,
	"report-configfile" => \$REPORT_CONFIGFILE,
	"checkmedia" => \$CHECKMEDIA,
	"email=s" => \@EMAIL,
	"printer=s" => \@PRINTER,
	"eject" => \$EJECT,
	"console" => \$CONSOLE,
	"dvd" => \$DVD,
	"finddev" => \$FINDDEV,
	"format" => \$FORMAT,
	"install" => \$INSTALL,
	"showkey" => \$SHOWKEY,
	"mount" => \$MOUNT,
	"unmount|umount" => \$UNMOUNT,
	"nocc" => \$NOCC,
	"backup=s" => \@BACKUP,
	"restore=s" => \@RESTORE,
	"restore-exclude=s" => \@RESTORE_EXCLUDES,
	"rootdir=s" => \$ROOTDIR,
	"sendto=s" => \$SENDTO,
	"sendto-excludes=s" => \@SENDTO_EXCLUDES,
	"sendto-encrypt" => \$SENDTO_ENCRYPT,
	"secondary" => \$SECONDARY,
	"receivefrom=s" => \$RCVFROM,
	"receivefrom-decrypt" => \$RCVFROM_DECRYPT,
	"daisy" => \$DAISY,
	"ddbackup" => \$DDBACKUP,
	"rti" => \$RTI,
	"list=s" => \@LIST,
	"checkfile" => \$CHECKFILE,
	"verbose" => \$VERBOSE,
	"force" => \$FORCE,
	"verify" => \$VERIFY,
	"logfile=s" => \$LOGFILE,
	"device=s" => \$DEVICE,
	"device-vendor=s" => \$DEVICE_VENDOR,
	"device-model=s" => \$DEVICE_MODEL,
	"usb-device" => \$USB_DEVICE,
	"dvddev=s" => \$DVDDEV,
	"cryptkey=s" => \$CRYPTKEY,
	"validate-cryptkey" => \$VALIDATE_CRYPTKEY,
	"saveall" => \$DSYSAVEALL,
	"autocheckmedia!" => \$AUTO_CHECKMEDIA,
	"compress" => \$COMPRESS_BU,
	"decompress" => \$DECOMPRESS_BU,
	"keep-old-files" => \$KEEP_OLD_FILES,
	"getinfo" => \$GETINFO,
	"dry-run" => \$DRY_RUN,
	"upgrade" => \$UPGRADE,
	"harden-linux!" => \$HARDEN_LINUX,
	"debugmode" => \$DEBUGMODE,
) || die "Error: invalid command line option, exiting...\n";


# --version
if($VERSION != 0) {
	print("OSTools Version: 1.13.0\n");
	print("$CVS_REVISION\n");
	exit($EXIT_OK);
}

# --help
if($HELP != 0) {
	usage();
	exit($EXIT_OK);
}


# --logfile
#
# There are several possibilities:
#   1) path to the logfile specified on the command line
#   2) the default name for a normal rtibackup.pl invocation
#   3) the name when "rtibackup.pl --sendto" specified
#   4) the name when "rtibackup.pl --receivefrom" specified
#
if ($LOGFILE eq "") {
	my $logfiledir = "/tmp";
	my $logfilename = "rtibackup-Day_%d.log";

	if (-d "$RTIDIR/log") {
		$logfiledir= "$RTIDIR/log";
	}
	if (-d "$DAISYDIR/daisy/log") {
		$logfiledir = "$DAISYDIR/daisy/log";
	}

	if ($DEBUGMODE) {

	    $DEBUGLOGFILE = "$logfiledir/rtibackup-debug.log";

	    debuglog("Starting $PROGNAME $CVS_REVISION");
	    debuglog("Invoked with command line:");
	    debuglog("$command_line");

	    debuglog("\$SENDTO = <$SENDTO>");
	    debuglog("\$SECONDARY = <$SECONDARY>");
	}

	if ($SENDTO ne "") {
		$logfilename = "rtibackup-sendto-Day_%d.log";
	}
	elsif ($SECONDARY != 0) {
		$logfilename = "rtibackup-receivefrom-Day_%d.log";
	}

	$LOGFILE = strftime("$logfiledir/$logfilename", localtime());

	if ($DEBUGMODE) {
	    debuglog("\$LOGFILE = <$LOGFILE>");
	}
}

# should never get here with $LOGFILE undefined, but...
if ($LOGFILE eq "") {
	$LOGFILE = "/tmp/$LOGFILE_DEF";
}


# Rotate the log file now that we know it's name
$LOGFILE = logrotate($LOGFILE);


$OS = determine_os();
if ($OS eq "") {
	logerror("Unknown operating system.");
	exit($EXIT_PLATFORM);
}


# --rootdir=/some/path
if (! -d "$ROOTDIR") {
	logerror("Specifed directory not found: rootdir=\"$ROOTDIR\".");
	exit($EXIT_ROOTDIR);
}


# Where do our "OSTOOLS" typically reside?
my @ostools_dir_paths = qw(
    /teleflora/ostools
    /usr2/ostools
    /d/ostools
);

foreach (@ostools_dir_paths) {
    if (-e $_) {
	$TOOLSDIR = $_;
	last;
    }
}

unless ($TOOLSDIR) {
    logerror("OSTools directory does not exist at: @ostools_dir_paths");
    exit($EXIT_TOOLSDIR);
}


loginfo("");
loginfo("===========================================");
loginfo("Starting $PROGNAME $CVS_REVISION");
loginfo("===========================================");
loginfo("");
loginfo("Invoked with command line:");
loginfo("$command_line");


# Read our settings from --configfile=xxxx
if ($CONFIGFILE eq "") {
	if (-e "$RTIDIR/config/backups.config") {
		$CONFIGFILE = "$RTIDIR/config/backups.config";
	}
	if (-e "$DAISYDIR/daisy/config/backups.config") {
		$CONFIGFILE = "$DAISYDIR/daisy/config/backups.config";
	}
}
if ($CONFIGFILE ne "") {
    if (read_configfile($CONFIGFILE) != 0) {
	loginfo("Can't read config file... proceeding without it");
    }
}


# --report-configfile
# report only, then exit
if ($REPORT_CONFIGFILE) {
    exit($EXIT_OK);
}


# --daisy --rti
# --daisy
# --rti
# (Nothing specified)
# Guess which system we are on if we have not been explicitly told.
# Note that if both /usr2 and /d/daisy exist, then, we'll work with both.
if ( ($RTI == 0) && ($DAISY == 0) ) {
    if (-d "/d/daisy") {
	$DAISY = 1;
    }
    if (-d "/usr2/bbx") {
	$RTI = 1;
    }
}
# System type still not determined... this could be in the case of
# a fresh install and, no "--daisy" and no "--rti" specified
# on the commandline.
if ( ($RTI == 0) && ($DAISY == 0) ) {
    $RTI = 1;
}

if ($DAISY != 0) {
    loginfo("Assuming --daisy");
}
if ($RTI != 0) {
    loginfo("Assuming --rti");
}

#
# Determine the lockfile for --checkmedia
#
$LOCKFILE_DIR = ($RTI) ? "$RTIDIR/config" : "$DAISYDIR/daisy/config";
if (! -d $LOCKFILE_DIR) {
    $LOCKFILE_DIR = "/tmp";
}
$LOCKFILE = "$LOCKFILE_DIR/$LOCKFILE_NAME";


# Are we 32 bit or 64 bit?
open(ARCH, "uname -i |");
while(<ARCH>) {
	chomp;
	$CURRENT_ARCH = $_;
}
close(ARCH);


#
# The crypt key can be obtained from 1 of 3 ways as outlined here,
# listed in order of precedence:
#
# 1) from the command line
# 2) from the system serial number (aka Dell Service Tag)
# 3) a default value
#
if($CRYPTKEY eq "" and -x '/usr/sbin/dmidecode') {
	open(PIPE, "/usr/sbin/dmidecode | grep -A 5 \"System Information\" |");
	while(<PIPE>) {
		chomp;
		if(/(Serial Number:)(\s+)(\S+)/) {
			$CRYPTKEY = uc($3);
			last;
		}
	}
	close(PIPE);
}

# Set a default crypt key if need be.
if($CRYPTKEY eq "") {
	$CRYPTKEY = "Pwklk+82rT";
}


# --sendto=secondary_ipaddr:secondary_svctag
if ($SENDTO ne "") {
	if ($SENDTO !~ /:/) {
		logerror("Incorrect format for --sendto commandline argument: $SENDTO");
		usage();
		exit($EXIT_COMMAND_LINE);
	}

	if (@SENDTO_EXCLUDES) {
		@SENDTO_EXCLUDES = split(/,/, join(',', @SENDTO_EXCLUDES));
	}
	exit(send_to_secondary_server($SENDTO, \@SENDTO_EXCLUDES));
}

# --secondary --configfile=path
# --secondary --receivefrom=primary_ipaddr:primary_svctag
if ($SECONDARY) {
	if ($RCVFROM eq "" || $RCVFROM !~ /:/) {
		logerror("Incorrect format for --receivefrom commandline argument.");
		usage();
		exit($EXIT_COMMAND_LINE);
	}

	exit(receive_from_primary_server());
}



# --ddbackup
if($DDBACKUP != 0) {
	ddbackup();
	exit($EXIT_OK);
}



# --saveall
if($DSYSAVEALL != 0) {
	loginfo("Saving encrypted /d to /save.");
	daisy_saveall();
	exit($EXIT_OK);
}

# --install
if($INSTALL != 0) {
	install_rtibackup();
	exit($EXIT_OK);
}


#
# If the backup device is an image file, make sure that it exists and
# is at least $DEVICE_IMAGE_SIZE in size - arbitrary yes, but at least
# some measure of defense.
#
if ($DEVICE ne "") {
    unless (device_is_verified($DEVICE)) {
	logerror("Backup device verification error for device: $DEVICE");
	exit($EXIT_DEVICE_VERIFY);
    }
}

#
# --usb-device
#
# If specifed, either find and verify a USB device, or exit.
#
if ($USB_DEVICE) {
    if ($OS eq "RHEL4") {
	showerror("USB devices other than WD Passports are not supported on $OS");
	exit($EXIT_USB_DEVICE_UNSUPPORTED);
    }
    else {
	$DEVICE = find_usb_device();
	if ($DEVICE) {
	    unless (device_is_verified($DEVICE)) {
		showerror("Can't verify USB device: $DEVICE");
		exit($EXIT_DEVICE_VERIFY);
	    }
	}
	else {
	    showerror("Can't find USB backup device");
	    exit($EXIT_USB_DEVICE_NOT_FOUND);
	}
    }
}

# If the user doesn't specify a specific device name, then, guess it.
# First, look for the "newer generation" RD1000 device, then, for the iomega rev.
if ($DEVICE eq "") {
	$DEVICE = find_passport();
	if ($DEVICE eq "") {
		$DEVICE = find_rd1000();
		if ($DEVICE eq "") {
			$DEVICE = find_revdevice();
			if ($DEVICE eq "") {
				$DEVICE = find_tapedrive();
			}
		}
	}
}
if ( ($DEVICE eq "") &&  ($DVD == 0) ) {
	logerror("No backup device found.");
	if( ( ($#EMAIL >= 0) || ($#PRINTER >= 0))
	&&  ($#BACKUP >= 0) ) {
		my $info = "";
		my $subject = "";
		my $timestamp = "";
		$timestamp = strftime("%Y-%m-%d", localtime());
		$subject = "Backup $timestamp FAILED - No Device";
		$info = << "EOF";

-- Backup Device Not Found --
A backup could not be performed because a device could not be found to
store your backup onto.

If you use an Iomega REV drive, check to verify that a cartridge is
snapped into the drive, and that the drive's "active" light is not
illuminated.

If you are using a Passport drive, verify that the drive is connected to
your server by a  USB cable, and verify that the small white light is
illuminated on the side of the Passport drive.

EOF
		if($#EMAIL >= 0) {
			send_email(\@EMAIL, $subject, $info);
		} 
		if($#PRINTER >= 0) {
			print_results(\@PRINTER, $subject, $info);
		}
	}

	exit($EXIT_BACKUP_DEVICE_NOT_FOUND);
}


showinfo("Using Backup Device \"$DEVICE\"");


# Guess our DVD Device only if "--dvd" was specified.
if($DVD == 0) {
	$DVDDEV = "";
} else {
	if("$DVDDEV" eq "") {
		$DVDDEV = find_dvddevice();
	}
	if("$DVDDEV" eq "") {
		showinfo("No DVD Device Found.");
	} else {
		showinfo("Using DVD Device \"$DVDDEV\"");
	}
}




# --unmount
# --umount
if($UNMOUNT != 0) {
	$NEED_USAGE = 0;
	if( ($DVD != 0) && ("$DVDDEV" ne "") ){
		system("umount $DVDDEV");
	}
	if("$DEVICE" ne "") {
		unmount_device();
	}
}

# --format
if($FORMAT != 0) {
	$NEED_USAGE = 0;
	if(! -d "/mnt/backups") {
		mkdir("/mnt/backups");
	}

	# Format the tape if we want to backup and can't find a tape ID.
	if($#BACKUP >= 0) {
		# Format any tapes which are still the older "UDF" filesystem.
		# Do this to make an easier transition from lonetar to rtibackup.pl
		if(get_tape_id() ne "") {
			showinfo("Device is already formatted. Will not re-format.");
		} else {
			# Unconditional format.
			device_format_ext2("format", 1);
		}
	} else {

		# --force --format will not ask questions.
		if($FORCE != 0) {
			device_format_ext2("format", 1);
		} else {
			device_format_ext2("format", 0);
		}
	}
}




# --mount
if($MOUNT != 0) {
	$NEED_USAGE = 0;
	if($DVD != 0) {
		system("mount -t iso9660 -o ro $DVDDEV $MOUNTPOINT 2>> $LOGFILE");
	} else {
		mount_device("rw");
	}
}


# --finddev
if ($FINDDEV != 0) {
	$NEED_USAGE = 0;

	my @find_dev_table = (
		[ &find_dvddevice,	'     DVD Device', ],
		[ &find_usb_device,	'     USB Device', ],
		[ &find_passport,	'Passport Device', ],
		[ &find_rd1000,		'  RD1000 Device', ],
		[ &find_revdevice,	'     REV Device', ],
		[ &find_tapedrive,	' Magtape Device', ],
		[ &get_tape_id,		'      Backup ID', ],
	);

	for my $i (0 .. $#find_dev_table) {
		my $result = "";

		if ($OS eq "RHEL4" && $find_dev_table[$i][1] =~ /USB Device/) {
		    $result = "(not supported on RHEL4)";
		}
		else {
		    $result = $find_dev_table[$i][0];
		}
		if ($result eq "") {
		    $result = "(device not found)";
		}

		showinfo("$find_dev_table[$i][1]: $result");
	}
}


# --showkey
if($SHOWKEY != 0) {
	# Do not log this to logfile.
	print("Using CryptKey: \"$CRYPTKEY\"\n");
}



# --checkmedia
if($CHECKMEDIA != 0) {
	checkmedia();
}

# --getinfo
if ($GETINFO != 0) {
	getinfo();
}



# --backup=blah
# Make sure this block of code comes *after* the "mount", "unmount" and "format" blocks of code.
if($#BACKUP >= 0) {
	$NEED_USAGE = 0;
	@BACKUP = split(/,/, join(',', @BACKUP));

	#
	# Check the backup types specified on the command line and let the user
	# know if there is an error and what the supported backup types are.
	#
	for my $backup_type (@BACKUP) {
		unless (grep(/^$backup_type$/, @BACKUP_TYPES)) {
			logerror("Error: backup type specified: $backup_type");
			logerror("Error: supported backup types: @BACKUP_TYPES");
			exit($EXIT_BACKUP_TYPE);
		}
	}


	# Default set of things to *not* backup.
	push(@EXCLUDES, "*.iso");
	push(@EXCLUDES, "*.tar.asc");
	push(@EXCLUDES, "lost+found");

	if ($RTI) {
		push(@EXCLUDES, "/usr2/*.iso");
		push(@EXCLUDES, "/usr2/*.tar.asc");
		push(@EXCLUDES, "/usr2/bbx-*");
		push(@EXCLUDES, "/usr2/bbx/*.iso");
		push(@EXCLUDES, "/usr2/bbx/log/*");
		push(@EXCLUDES, "/usr2/bbx/bbxt/*");
		push(@EXCLUDES, "/usr2/bbx/backups/*");
		push(@EXCLUDES, "/usr2/bbx/*.tar.asc");
		if($NOCC != 0) {
			showinfo("Will not include Cardholder Data in this backup.");
			push(@EXCLUDES, "CCXF01");
			push(@EXCLUDES, "CCXF02");
			push(@EXCLUDES, "ONCA01");
			push(@EXCLUDES, "bytefile");
			push(@EXCLUDES, "recv.fil");
		}
	} elsif ($DAISY) {

		# exclude some files commonly found in /d 
		push(@EXCLUDES, "/d/*.iso");
		push(@EXCLUDES, "/d/*.tar.asc");

		# exclude some files from within daisy database dirs
		determine_daisy_excludes(\@EXCLUDES);
	}

	my $begin_timestamp = strftime("%a %b %d %H:%M:%S %Y", localtime());

	$backup_returnval = backup_files();

	my $end_timestamp = strftime("%a %b %d %H:%M:%S %Y", localtime());

	$BACKUP_SUMMARY_INFO =
"#
#      Backup program: $PROGNAME $CVS_REVISION
#          Backup Set: @BACKUP
#      Backup Started: $begin_timestamp
#    Backup Completed: $end_timestamp
#
";

	#
	# if the backup was successful, write a new validation file
	#
	if ($backup_returnval == 0) {

	    my $ml = "backup";
	    if (mount_device_simple($ml, "rw")) {
		logerror("[$ml] Can't mount backup device for writing validation file: $DEVICE");
	    }

	    else {
		my $cat_cmd = "cat /etc/redhat-release";
		my $encrypt_cmd = "nice openssl aes-128-cbc -e -salt -k \"$CRYPTKEY\"";
		my $cmd = $cat_cmd . " | " . $encrypt_cmd . " > $MOUNTPOINT/$VALIDATION_FILE";

		system("$cmd");
		if ($? != 0) {
		    logerror("[$ml] Error writing validation file to backup device");
		}
		else {
		    loginfo("[$ml] Validation file written to backup device");
		}

		unmount_device_simple($ml);
	    }

	}
}


# --restore
if($#RESTORE >= 0) {
	$NEED_USAGE = 0;
	@RESTORE = split(/,/, join(',', @RESTORE));

	#
	# Check the backup types specified on the command line and let the user
	# know if there is an error and what the supported backup types are.
	#
	# One exception, if the string following "--restore" has a leading '/'
	# char, ie it specifies an absolute path, then assume the restore type
	# is "singlefiles".
	#
	for my $restore_type (@RESTORE) {
		unless ($restore_type =~ /^\//) {
			unless (grep(/^$restore_type$/, @RESTORE_TYPES)) {
				logerror("Error: restore type specified: $restore_type");
				logerror("Error: supported restore types: @RESTORE_TYPES");
				exit($EXIT_RESTORE);
			}
		}
	}

	if (@RESTORE_EXCLUDES) {
		@RESTORE_EXCLUDES = split(/,/, join(',', @RESTORE_EXCLUDES));
	}

	restore_files(\@RESTORE_EXCLUDES);

	if ($HARDEN_LINUX && $RUN_HARDEN_LINUX) {
	    run_harden_linux();
	}
}


# --list
if($#LIST >= 0) {
	$NEED_USAGE = 0;
	@LIST = split(/,/, join(',', @LIST));

	#
	# Check the backup types to list that were specified on the command line and
	# let the user know if there is an error and what the supported backup types are.
	#
	for my $backup_type (@LIST) {
		unless (grep(/^$backup_type$/, @BACKUP_TYPES)) {
			logerror("Error: backup type specified: $backup_type");
			logerror("Error: supported backup types: @BACKUP_TYPES");
			exit($EXIT_LIST);
		}
	}
	list_files();
}

# --checkfile
if($CHECKFILE != 0) {
	$NEED_USAGE = 0;
	checkfile(@ARGV);
}


# --verify
# --verify --console
# --backup --email=someone@somewhere
# Make sure this block of code comes *after* the "BACKUP" block of code.
#
if($VERIFY != 0) {
	my $info = "";
	my $subject = "";
	my $timestamp = "";
	my @termnames = ();
	my $termname = "";

	if ($RTI) {
		@termnames = qw( /dev/tty8 );
	}
	if ($DAISY) {
		@termnames = qw( /dev/tty11 );
	}

	$NEED_USAGE = 0;
	$info = verify_backup();

	# add the backup summary info
	$info = $BACKUP_SUMMARY_INFO . $info;

	showinfo($info);
	if($CONSOLE != 0) {
		foreach $termname (@termnames) {
			open(CON, "> $termname");
				print(CON "\n");
				print(CON "\n");
				print(CON "\n");
				print(CON "\n");
				print(CON "\n");
				print(CON "$info");
			close(CON);
		}
	}


	# If we specified "--backup" and "--verify" together, then, we'll email the verify results,
	# only, of course, if there are email addresses configured.
	if( (($#EMAIL >= 0) || ($#PRINTER >= 0))
	&&  ($#BACKUP >= 0) ) {

		#
		# Errors in our verify text always start with a bunch of exclamation marks.
		# note too that our email titles should be easily sortable, and, give a quick overview
		# for the user to decide "do I even need to open this email?" 
		#
		$timestamp = strftime("%Y-%m-%d", localtime());
		if($info =~ /[!]{7,}/) {
			$subject = "Backup $timestamp ERRORS"
		} else {
			$subject = "Backup $timestamp SUCCESS"
		}
		if($#EMAIL >= 0) {
			send_email(\@EMAIL, $subject, $info);
		}
		if($#PRINTER >= 0) {
			print_results(\@PRINTER, $subject, $info);
		}
	}

	#
	# If performing a "--backup" and a "--verify" and the "--verify" fails,
	# then do a "--checkmedia" unless "--noautocheckmedia" was specified.
	#
	if ($VERIFY_FAILED && ($#BACKUP >=0) && $AUTO_CHECKMEDIA) {
		checkmedia();
	}
}


# --eject
# Make sure this block of code comes *after* the "VERIFY" block of code.
if ($EJECT != 0) {
    $NEED_USAGE = 0;

    unless ($DEVICE_TYPE eq "passport" || $DEVICE_TYPE eq "usb") {

	# --eject + --backup
	if ($#BACKUP >= 0) {

	    # eject only if backup succeeded.
	    if ($backup_returnval == 0) {
		eject_devices();
	    }

	    else {
		showinfo("Media NOT ejected: backup error $backup_returnval.");
	    }
	}

	else {
	    eject_devices();
	}
    }
}


# ejectdays is a config file item only - it is not a command line option
# Make sure this block of code comes *after* the "VERIFY" block of code.
#
# Requirements to evaluate:
#   0) ejectdays has been specified in the config file
#   1) the backup device is not a USB disk drive
#   2) there must have been a backup attempted and a verification
#
if ($EJECTDAYS ne "") {

    unless ($DEVICE_TYPE eq "passport" || $DEVICE_TYPE eq "usb") {

	if ( ($#BACKUP >= 0) && ($VERIFY != 0) ) {
	    eject_days($EJECTDAYS, $backup_returnval);
	}
    }
}


if ($RUN_RTI_PERMS) {
    set_rti_perms();
}
if ($RUN_DAISY_PERMS) {
    set_daisy_perms();
}


#
# Validate the value of cryptkey
#
# --validate-cryptkey [--cryptkey=s]
if ($VALIDATE_CRYPTKEY) {
    my $rc = validate_crypt_key($CRYPTKEY);
    if ($rc == 0) {
	unless ($DRY_RUN) {
	    print("The crypt key is valid\n");
	}
    }
    elsif ($rc == 1) {
	unless ($DRY_RUN) {
	    print("The crypt key is invalid\n");
	}
    }
}


# Nothing specified on commandline.
if($NEED_USAGE != 0) {
	usage();
}

exit($EXIT_OK);

#####################################################################
#####################################################################
#####################################################################


sub usage
{
	my $temp = "";

	print("$PROGNAME $CVS_REVISION\n");
	print("$PROGNAME --help\n");
	print("$PROGNAME --version\n");
	print("$PROGNAME --eject\n");
	print("$PROGNAME --dvd\n");
	print("$PROGNAME --finddev\n");
	print("$PROGNAME --format [--force]\n");
	print("$PROGNAME --install\n");
	print("$PROGNAME --showkey\n");
	print("$PROGNAME --mount\n");
	print("$PROGNAME --unmount\n");
	print("$PROGNAME --ddbackup\n");
	print("$PROGNAME --verify [--verbose]\n");
	print("$PROGNAME --getinfo\n");
	print("$PROGNAME --report-configfile\n");
	print("$PROGNAME --checkmedia [--email=user1\@foo.com,user2\@bar.com,...] [--printer=printer1,printer2,...] \n");
	print("$PROGNAME --[no]autocheckmedia\n");
	print("$PROGNAME --validate-cryptkey [--cryptkey=s]\n");
	print("$PROGNAME --sendto=secondary_ipaddr:secondary_svctag [--sendto-encrypt]\n");
	print("$PROGNAME --secondary --receivefrom=primary_ipaddr:primary_svctag [--receivefrom-decrypt]\n");

	#
	# The following code assumes that @BACKUP_TYPES and @RESTORE_TYPES has
	# at least one element.
	#
	my $first_elem = shift(@BACKUP_TYPES);
	print("$PROGNAME --backup=$first_elem");
	foreach $temp (@BACKUP_TYPES) {
		print(",$temp");
	}
	unshift(@BACKUP_TYPES, $first_elem);
	print(" [--eject] [--console] [--nocc] [--email=user1\@foo.com,user2\@bar.com,...] [--printer=printer1,printer2,...] ");
	print("\n");

	$first_elem = shift(@BACKUP_TYPES);
	print("$PROGNAME --list=$first_elem");
	foreach $temp (@BACKUP_TYPES) {
		print(",$temp");
	}
	unshift(@BACKUP_TYPES, $first_elem);
	print("\n");

	$first_elem = shift(@RESTORE_TYPES);
	print("$PROGNAME --restore=$first_elem");
	foreach $temp (@RESTORE_TYPES) {
		print(",$temp");
	}
	unshift(@RESTORE_TYPES, $first_elem);
	print(" [--force] [--keep-old-files] [--rootdir=/some/path]");
	print(" [--restore-exclude=path,path,...] [--harden_linux]");
	print("\n");

	print("$PROGNAME --restore $RTIDIR/bin/killem $RTIDIR/bbxd /etc/sysconfig/network-scripts ...");
	print(" [--force] [--keep-old-files] [--rootdir=/some/path]");
	print("\n");


	print("$PROGNAME --checkfile /dir/file /dir/*.blah ...\n");
	print("\n");
	print("For any command above, you may also need one or many of the following:\n");
	print("\t--configfile=/usr2/bbx/config/backups.config\n");
	print("\t--configfile=/d/daisy/config/backups.config\n");
	print("\t--logfile=/path/to/logfile.log\n");
	print("\t--device=/dev/xxxx or --device=/path/to/imagefile.img\n");
	print("\t--device-vendor=name (from /sys/block/sd[a|b|c|d|e]/device/vendor)\n");
	print("\t--device-model=name (from /sys/block/sd[a|b|c|d|e]/device/model))\n");
	print("\t--dvddev=/dev/xxxx\n");
	print("\t--usb-device\n");
	print("\t--cryptkey=s\n");
	print("\t--compress\n");
	print("\t--decompress\n");
	print("\t--debugmode\n");
	print("\t--daisy\n");
	print("\t--rti\n");
	print("\n");
	print("The harden_linux.pl script will be run after any of these restore types:\n");
	print("\tall, rticonfigs, daisy, daisyconfigs, osconfigs and netconfigs\n");
	print("The script will only be run once after all restores are finished.\n");
	print("To prevent harden_linux.pl from running, specify the following option:\n");
	print("\t--noharden-linux\n");
	print("\n");
}


sub determine_os
{
	$OS = "";

	# We could also use /usr/bin/lsb-release
	open(FILE, "< /etc/redhat-release");
	while(<FILE>) {

		# Fedora Core 3
		if(/(Fedora)(\s+)(Core)(\s+)(release)(\s+)(3)/) {
			$OS = "FC3";
			last;
		}

		# Fedora Core 4
		if(/(Fedora)(\s+)(Core)(\s+)(release)(\s+)(4)/) {
			$OS = "FC4";
			last;
		}

		# Fedora Core 5
		if(/(Fedora)(\s+)(Core)(\s+)(release)(\s+)(5)/) {
			$OS = "FC5";
			last;
		}

		# Redhat Enterprise Linux Client Workstation 5
		if( /(Client release)(\s+)(5)/ ) {
			$OS = "RHWS5";
			last;
		}

		# ES 6
		# Redhat Enterprise Linux Server 6
		if( /(release)(\s+)(6)/ ) {
			$OS = "RHEL6";
			last;
		}

		# ES 5
		# Redhat Enterprise Linux Server 5
		if( (/(release)(\s+)(5)/)
		||  (/(CentOS)([[:print:]]+)(\s)(5)/) ) {
			$OS = "RHEL5";
			last;
		}

		# EL 4
		# Redhat Enterprise Linux Server 4
		if( (/(release)(\s+)(4)/) 
		||  (/(CentOS)([[:print:]]+)(\s)(4)/) ) {
			$OS = "RHEL4";
			last;
		}

		# EL 3
		# Redhat Enterprise Linux Server 3
		if (/(release)(\s+)(3)/) {
			$OS = "RHEL3";
			last;
		}

		# Redhat 7.2
		if(/(release)(\s+)(7\.2)/) {
			$OS = "RH72";
			last;
		}
	}
	close(FILE);

	#
	# Allow a last minute switcheroo.
	#
	if ($ALTOS) {
	    $OS = $ALTOS;
	}

	return($OS);
}


#
# Verify that the backup device path is either a block device or
# the path to an image file of at least a minimum size.
#
# Return TRUE if backup device path is verified
# Return FALSE if not
#
sub device_is_verified
{
    my $device_path = $_[0];

    if (-b $device_path) {
	return(1);
    }

    elsif (-f $device_path) {
	if ((-s $device_path) >= $DEVICE_IMAGE_FILE_MIN) {
	    return(1);
	}
	else {
	    logerror("Backup image file less then minimum size: $DEVICE_IMAGE_FILE_MIN bytes");
	}
    }

    else {
	logerror("Backup device must be a block device or an existing image file");
    }

    return(0);
}


#
# Format the backup device using Linux EXT2 Filesystem.
# Note that this supports either block devices, or image files.
#
sub device_format_ext2
{
    my ($ml, $force) = @_;
    my $returnval = 0;
    my $answer = "";
    my $uuid = "";

    if ($DEVICE eq "") {
	logerror("[$ml] No device specified to format.");
	return(-2);
    }

    unless (device_is_verified($DEVICE)) {
	logerror("[$ml] Backup device path verification error");
	return(-3);
    }

    # Don't format a mounted drive.
    if (device_is_mounted()) {
	logerror("[$ml] Backup Device is mounted. Please --unmount before formatting.");
	return(-3);
    }

    my $device_type = "device";
    $device_type = "image file" if (-f $DEVICE);

    my $begin_timestamp = strftime("%a %b %d %H:%M:%S %Y", localtime());

    showinfo("[$ml] Formatting $device_type: $DEVICE");

    #
    # Verify that the user wants to format the disk. If "force" is in place, then
    # just format.
    #
    if ($force == 0) {
	$answer = "";
	while (1) {
	    if (-b "$DEVICE") {
		print("Format Backup Device \"$DEVICE\"?\n");
	    }
	    elsif (-f "$DEVICE") {
		print("Format Backup Image File \"$DEVICE\"?\n");
	    }
	    else {
		logerror("[$ml] Can't format - device is not a file or block device: $DEVICE");
		return(-3);
	    }
	    print("(Y/N) >");
	    $answer = <STDIN>;
	    chomp($answer);
	    last if ("$answer" =~ /^n/i);
	    last if ("$answer" =~ /^y/i);
	}

	# anything but 'yes'.
	if ($answer !~ /^y/i) {
	    return(0);
	}
    }


    # Determine the "old" UUID (if any).  If there is no old UUID,
    # then by setting the value to "random", it will cause the
    # tune2fs command to generate a new UUID for the file system.

    $uuid = get_tape_id();
    if ($uuid eq "") {
	$uuid = "random";
    }

    #
    # Put a file system on the device.
    #
    # Note that, under RHEL5 at least, these same commands work equally
    # whether talking to a block device or a file. "mkfs.ext2" and "tune2fs"
    # transparently figure out whether loopback devices are needed.
    #
    # Make the disk label something that can be searched for and
    # recognized as a disk being used as a Teleflora backup device.
    # A disk label can be a maximum of 16 chars, so make it:
    #
    #       |<---- 16 ---->|
    #       |     chars    |
    #	    TFBUDSK-YYYYMMDD
    #
    # which would be the string "TFBUDSK" followed by a date stamp.
    # "TFBUDSK" stands for "Teleflora Backup Disk".  The disk label
    # can be read with the e2label command.

    my $label_brand = "TFBUDSK";
    my $label_datestamp = strftime("%Y%m%d", localtime());
    my $label_disk = $label_brand . "-" . $label_datestamp;

    showinfo("[$ml] Making an EXT2 file system on $device_type: $DEVICE");
    if ($VERBOSE) {
	showinfo("[$ml] CLI: /sbin/mkfs.ext2 -F -L $label_disk $DEVICE");
    }
    system("/sbin/mkfs.ext2 -F -L $label_disk $DEVICE 2>> $LOGFILE");
    $returnval = $?;
    showinfo("[$ml] Completed make of an EXT2 file system");
    if ($returnval != 0) {
	showerror("[$ml] The command to make an EXT2 file system exited with error: $!");
	showerror("[$ml] The $device_type was not formatted: $DEVICE");
    }
    else {
	showinfo("[$ml] Setting tuning parameters for $device_type: $DEVICE");
	if ($VERBOSE) {
	    showinfo("[$ml] Setting max mount count to 0, thus disabling it");
	    showinfo("[$ml] Setting error behavior to \"remount read-only on error\"");
	    showinfo("[$ml] Setting FSCK interval to 12 months");
	    showinfo("[$ml] Setting UUID of file system to: $uuid");
	    showinfo("[$ml] CLI: /sbin/tune2fs -c 0 -e remount-ro -i 12m -U $uuid $DEVICE");
	}
	system("/sbin/tune2fs -c 0 -e remount-ro -i 12m -U $uuid $DEVICE 2>> $LOGFILE");
	if ($returnval != 0) {
	    showerror("[$ml] Can't tune2fs $device_type: $DEVICE");
	}
	else {
	    showinfo("[$ml] Completed setting tuning parameters");
	}

	my $end_timestamp = strftime("%a %b %d %H:%M:%S %Y", localtime());

	showinfo("[$ml] Completed format");

	showinfo("[$ml] Setting up $PROGNAME framework on $device_type: $DEVICE");

	#
	# Write format info to newly formatted device and make framework
	# expected by script.
	#

	if (mount_device_simple($ml, "rw")) {
	    logerror("[$ml] Could not mount $device_type after formatting: $DEVICE");
	    $returnval = -4;
	}
	else {

	    showinfo("[$ml] Writing files to $device_type: $DEVICE");

	    my $format_info =
"#
#      Backup program: $PROGNAME $CVS_REVISION
#         Device type: $device_type $DEVICE
#      Format Started: $begin_timestamp
#    Format Completed: $end_timestamp
#
";
	    open(BUSIG, ">", "$MOUNTPOINT/$FORMAT_FILE");
	    print(BUSIG "$format_info");
	    close(BUSIG);

	    mkdir("$MOUNTPOINT/configs");

	    showinfo("[$ml] Completed writing files");

	    unmount_device_simple($ml);

	    showinfo("[$ml] Completed setup of $PROGNAME framework");
	}
    }

    return($returnval);
}


sub make_tempfile
{
        my $prefix = $_[0];

        my $tmpfile = qx(mktemp $prefix.XXXXXXX);
        chomp($tmpfile);
        if ($tmpfile eq "") {
                $tmpfile = "$prefix" . '.' . strftime("%Y%m%d%H%M%S", localtime());
        }

        return($tmpfile);
}


sub get_free_space
{
	my $mount_point = $_[0];
	my @fs_stats = ();

	# The output of the "df" command looks like this:
	#
	#Filesystem           1k-blocks          Used    Available Use% Mounted on
	#/dev/cciss/c0d0p1     26204060      22578308      2294656  91% /
	#
	# fs_stats[0]       fs_stats[1]   fs_stats[2]  fs_stats[3]
	#
	# How much space is left?
	open(PIPE, "df -k $mount_point |");
	while(<PIPE>) {
		next until(/$mount_point/);

		chomp;
		@fs_stats = split(/\s+/);

		unless (@fs_stats) {
			$fs_stats[3] = 0;
		}

		last;
	}
	close(PIPE);

	return($fs_stats[3]);
}


sub get_hostname
{
    my $hostname = "";

    open(PIPE, "hostname |");
    while(<PIPE>) {
	chomp;
	$hostname = $_;
	last;
    }
    close(PIPE);

    if ($hostname eq "") {
	if (-d "/usr2/bbx") {
	    $hostname = "rti-server";
	}
	elsif ("/d/daisy") {
	    $hostname = "daisy-server";
	}
	logerror("The hostname command did not return a hostname");
	loginfo("Using $hostname as The hostname");
    }

    return($hostname);
}


#
# Read up to the first 1MB of the backup file to determine
# whether it is a compressed file or not.
#
sub is_compressed
{
    my $bu_file = $_[0];
    my $prefix = "rtibackup.pl";

    #
    # Temp file location:
    # RTI: /usr2/bbx/backups
    # Daisy: /d/daisy/backups
    #
    if ($RTI) {
	$prefix = "/usr2/bbx/backups";
    }
    elsif ($DAISY) {
	$prefix = "/d/daisy/backups";
    }

    unless (-d $prefix) {
	$prefix = "/tmp";
    }

    unless (-d $prefix) {
	logerror("Missing temp file directory: $prefix");
	return(-1);
    }

    my $tmpfile = make_tempfile($prefix);

    my $decrypt_cmd = "openssl aes-128-cbc -d -salt -k \"$CRYPTKEY\"";

    my $undo_cmd = "cat $bu_file | " . $decrypt_cmd;

    my $bytes_read = 0;
    my $bytes_written = 0;
    my $sum = 0;
    my $buffer = "";
    my $maxread = 1000000;
    my $io_error = 0;

    open(SRC, "$undo_cmd |");	
    open(DST, "> $tmpfile");

    while(1) {
	$bytes_read = sysread(SRC, $buffer, $maxread);
	if ($bytes_read == 0) {
	    last;
	}
	if ($bytes_read < 0) {
	    logerror("Error \"$!\" reading from pipe process.");
	    $io_error = 1;
	    last;
	}
	$bytes_written = syswrite(DST, $buffer, $bytes_read);
	if (! defined($bytes_written)) {
	    logerror("Error \"$!\" writing to $tmpfile.");
	    $io_error = 1;
	    last;
	}
	$sum += $bytes_read;
	last if ($sum >= $maxread);
    }
    close(DST);

    # drain pipe to avoid error message
    while (<SRC>) {
    }
    close(SRC);

    if ($io_error) {
	return(-1);
    }

    my $is_compressed = 0;
    open(PIPE, "file $tmpfile |");
    while (<PIPE>) {
	if (/compressed data/) {
	    $is_compressed = 1;
	    last;
	}
    }
    close(PIPE);

    system("rm -f $tmpfile");

    return($is_compressed);
}


#
# Get info about the backup set on the backup device
#
sub getinfo
{
	loginfo("Get Info about Backups");

	# Verify there is a backup device.
	if ("$DEVICE" eq "") {
		logerror("Backup device not specified or unknown.");
		return(-2);
	}

	# Verify the backup device can be mounted.
	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}

	my $uuid = get_tape_id();

	print("Using config file: $CONFIGFILE\n");
	print("Backup device: $DEVICE\n");
	print("Backup UUID: $uuid\n");

	my $free_space = get_free_space($MOUNTPOINT);
	print("Backup free space: $free_space KB\n");

	my @backup_files = qw(
		configs/altiris.bak
		configs/printconfigs.bak
		configs/rticonfigs.bak
		configs/dsyconfigs.bak
		configs/osconfigs.bak
		configs/userconfigs.bak
		usr2.bak
		daisy.bak
		userfiles.bak
		logfiles.bak
	);

	foreach my $backup_file (@backup_files) {

		my $bu_file_path = "$MOUNTPOINT/$backup_file";

		unless (-f $bu_file_path) {
		    next;
		}

		my $file_is_compressed = is_compressed($bu_file_path);

		if ($file_is_compressed == -1) {
		    logerror("$backup_file: error determining compression");
		    next;
		}

		print "$backup_file: ";

		if ($file_is_compressed == 0) {
		    print "not ";
		}
		print "compressed\n";
	}

	unmount_device();

	loginfo("---- END Get Info about Backups ----");

	return(0);
}


sub set_signal_handlers
{
	my $handler = $_[0];

	$SIG{'STOP'} = $handler;
	$SIG{'TSTP'} = $handler;
	$SIG{'INT'} = $handler;
}


#
# Run filesystem check, and media check, to ensure the disk is OK.
# This is intended as a tool to run if we believe the cartridge, or
# perhaps, the rev drive, is "bad".
#
sub checkmedia
{
	loginfo("Check Backup Media");

	# Handle signals.
	set_signal_handlers('IGNORE');

	unless (sysopen(LFH, $LOCKFILE, O_EXCL|O_CREAT)) {
	    showinfo("Lockfile exists, resource in use, try again later: $LOCKFILE");
	    set_signal_handlers('DEFAULT');
	    return;
	}
	close(LFH);

	# Don't format a mounted drive.
	if (device_is_mounted()) {
		unmount_device();
	}
	if (device_is_mounted()) {
		logerror("Error: could not unmount backup device. Please --unmount first.");
		unlink($LOCKFILE);
		set_signal_handlers('DEFAULT');
		return(-3);
	}

	# On RHWS5, this works when "DEVICE" is either a block device or file.
	# "e2fsck" figures out whether to use a loopback device.
	showinfo("Checking Filesystem and disk for errors. This will take a long time.");
	showinfo("Results will be logged.");
	if($#EMAIL >= 0) {
		showinfo("Results will also be emailed to \"@EMAIL\"");
	}
	if($#PRINTER >= 0) {
		showinfo("Results will also be printed on printer(s): \"@PRINTER\"");
	}

	# If we run '--checkmedia' and '--backup=blah' at the same time, then,
	# don't fork 'checkmedia' into the background. This allows our cron tasks
	# to run a nightly "checkmedia" before running the backup. Doing so better
	# ensures that backups will always succeed.
	my $pid = -1;
	if ($#BACKUP >= 0) {
		$pid = 0;
	}
	else {
		$pid = fork();
	}

	# parent
	if ($pid > 0) {
		showinfo("Checkdisk background process started as PID $pid");
		set_signal_handlers('DEFAULT');
	}

	# child
	elsif ($pid == 0) {

		my $subject = "";
		my $results = "";
		my $tapeid = "";

		$tapeid = get_tape_id();
		$results =  "---------------------------------\n";
		$results .= "--  $PROGNAME --checkmedia Results\n";
		$results .= "--  PID: $$\n";
		$results .= "--  Tape ID: $tapeid\n";
		$results .= "--  Start Time:" . localtime() . "\n";
		$results .= "---------------------------------\n";
		$results .= "\n";

		open(FSCK, "/sbin/e2fsck -v -y -c $DEVICE 2>&1 |");
		while(<FSCK>) {
			$results .= <FSCK>;
		}
		close(FSCK);

		loginfo("Checkdisk (PID $$) Complete.\n$results\n");

		# Do not email checkmedia results in the event that we are also
		# backing up (nightly backups.)
		if ( ($#EMAIL >= 0) && ($#BACKUP < 0) ) {
			$subject = "Backup Media Checkdisk Results";
			send_email(\@EMAIL, $subject, $results);
			loginfo("Checkdisk email sent to @EMAIL");
		}

		# Do not email checkmedia results in the event that we are also
		# backing up (nightly backups.)
		if ( ($#PRINTER >= 0) && ($#BACKUP < 0) ) {
			$subject = "Backup Media Checkdisk Results";
			print_results(\@PRINTER, $subject, $results);
			loginfo("Checkdisk results sent to printer(s): @PRINTER");
		}

		unlink($LOCKFILE);

		#
		# If not run with "--backup", then this code was forked,
		# so don't return to mainline processing, just exit.
		#
		if ($#BACKUP < 0) {
		    exit(0);
		}
		set_signal_handlers('DEFAULT');
	}
}


sub eject_days
{
    my $eject_days = $_[0];
    my $backup_returnval = $_[1];

    if ($eject_days =~ /always/i) {
	my $backup_status = ($backup_returnval != 0) ? "Backup NOT succssful" : "Successful backup";
	showinfo("$backup_status... ejecting media: because ejectdays is set to 'always'.");
	eject_devices();
    }

    elsif ($backup_returnval != 0) {
	# Don't eject if the backup failed.
	showinfo("Backup NOT successful... media NOT ejected: backup error $backup_returnval.");
    }

    elsif ($eject_days =~ /never/i) {
	showinfo("Successful backup... media NOT ejected: because ejectdays is set to 'never'.");
    }

    else {
	# Eject if our "eject days" matches.
	my $today = lc(strftime("%a", localtime()));
	if ($eject_days =~ /$today/i) {
	    showinfo("Successful backup... ejecting media: because $today is an eject day.");
	    eject_devices();
	} else {
	    showinfo("Successful backup... media NOT ejected: because $today is NOT an eject day.");
	}
    }
}


#
# Eject DVD and/or rev devices.
#
sub eject_devices
{
	my $tapeid = "";

	if ( (($DEVICE ne "") && ($DEVICE_TYPE eq "passport")) ||
	     (($DEVICE ne "") && ($DEVICE_TYPE eq "usb")) ) {
		return(0);
	}

	if ( ($DEVICE ne "") &&  (-b $DEVICE) ) {

		$tapeid = get_tape_id();
		showinfo("Ejecting Device $DEVICE ($tapeid) ");
		system("eject $DEVICE > /dev/null 2>> $LOGFILE");
	}


	# Only eject the DVD if "--backup --eject"
	if( ($DVD != 0) 
	&& ($DVDDEV)
	&& (-b "$DVDDEV")
	&& ($#BACKUP >= 0)
	&& ($DVDDEV ne "") ) {
		showinfo("Ejecting DVD Device $DVDDEV");
		system("eject $DVDDEV > /dev/null 2>> $LOGFILE");
	}

	return($?);
}


#
# Backup Files.
#
# The backup of all backup types funnels through this function.
#
sub backup_files
{
	my @array = ();
	my $returnval = 0;
	my $total_returnval = 0;
	my $pid = -1;
	my $kid = -1;
	my $i = 0;

	# If we want to backup to DVD,kick that off in the background.
	# That backup is much smaller and more refined, as
	# there is only 4 GB available on that media.
	if($DVD != 0) {
		$pid = fork();
		if($pid == 0) {
			$returnval = backup_dvdfiles($DVDDEV);
			exit($returnval);
		}
	}
	# We are OK if the DVD backup fails, that's not our "primary" backup media.



	# No need to backup to the backup device if we don't even know what that is.
	if("$DEVICE" eq "") {
		return(-2);
	}

	# Verify the backup device is accessible via mount
	mount_device("rw");
	if (device_is_mounted()) {
		unmount_device();
	}
	else {
		showerror("Can't verify backup device is accessible via mount: $DEVICE");
		return(-1);
	}



	#RTI and (--backup=all or --backup=usr2)
	$returnval = 0;
	if(($total_returnval == 0)
	&& ($RTI != 0)
	&& ((grep(/^usr2$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_usr2();
		if($returnval != 0) {
			logerror("backup_usr2() returned $returnval");
		}
	}
	$total_returnval += $returnval;


	#DAISY and (--backup=all or --backup=daisy)
	$returnval = 0;
	if(($total_returnval == 0)
	&& ($DAISY != 0)
	&& ( (grep(/^daisy$/, @BACKUP)) || (grep(/^all$/, @BACKUP)) )) {
		$returnval = backup_daisy();
		if($returnval != 0) {
			logerror("backup_daisy() returned $returnval");
		}
	}
	$total_returnval += $returnval;


	#--backup=all
	#--backup=printconfigs
	$returnval = 0;
	if(($total_returnval == 0)
	&& ((grep(/^printconfigs$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_printconfigs();
		if($returnval != 0) {
			logerror("backup_printconfigs() returned $returnval");
		}
	}
	$total_returnval += $returnval;

	#RTI and (--backup=all or --backup=rticonfigs)
	$returnval = 0;
	if(($total_returnval == 0)
	&& ($RTI != 0)
	&& ((grep(/^rticonfigs$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_rticonfigs();
		if($returnval != 0) {
			logerror("backup_rticonfigs() returned $returnval");
		}
	}
	$total_returnval += $returnval;

	#DAISY and (--backup=all or --backup=daisyconfigs)
	$returnval = 0;
	if(($total_returnval == 0)
	&& ($DAISY != 0)
	&& ((grep(/^daisyconfigs$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_dsyconfigs();
		if($returnval != 0) {
			logerror("backup_dsyconfigs() returned $returnval");
		}
	}
	$total_returnval += $returnval;

	#--backup=all
	#--backup=userconfigs
	$returnval = 0;
	if(($total_returnval == 0)
	&& ((grep(/^userconfigs$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_userconfigs();
		if($returnval != 0) {
			logerror("backup_userconfigs() returned $returnval");
		}
	}
	$total_returnval += $returnval;


	#--backup=all
	#--backup=netconfigs
	$returnval = 0;
	if( ($total_returnval == 0)
	&& ((grep(/^netconfigs$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_netconfigs();
		if($returnval != 0) {
			logerror("backup_netconfigs() returned $returnval");
		}
	}
	$total_returnval += $returnval;


	#--backup=all
	#--backup=osconfigs
	$returnval = 0;
	if( ($total_returnval == 0)
	&& ((grep(/^osconfigs$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_osconfigs();
		if($returnval != 0) {
			logerror("backup_osconfigs() returned $returnval");
		}
	}
	$total_returnval += $returnval;


	#--backup=all
	#--backup=altiris
	$returnval = 0;
	if( ($total_returnval == 0)
	&& ((grep(/^altiris$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_altiris();
		if($returnval != 0) {
			logerror("backup_altiris() returned $returnval");
		}
	}
	$total_returnval += $returnval;


	#--backup=all
	#--backup=userfiles
	$returnval = 0;
	if( ($total_returnval == 0)
	&& ((grep(/^userfiles$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_userfiles();
		if($returnval != 0) {
			logerror("backup_userfiles() returned $returnval"); }
	}
	$total_returnval += $returnval;


	#--backup=all
	#--backup=logfiles
	$returnval = 0;
	if( ($total_returnval == 0)
	&& ((grep(/^logfiles$/, @BACKUP))
	|| (grep(/^all$/, @BACKUP)))) {
		$returnval = backup_logfiles();
		if($returnval != 0) {
			logerror("backup_logfiles() returned $returnval"); }
	}
	$total_returnval += $returnval;


	# Wait for our background DVD backup to finish.
	if($pid > 0) {
		for($i = 0; $i < $MAX_WAIT_TIME; $i++) {
			$kid = waitpid($pid, WNOHANG);
			last if($kid != 0);
			sleep(1);
		}
		if($kid < 0) {
			logerror("waitpid() on PID $pid returned $kid. This is odd.");
		}

		if($i >= $MAX_WAIT_TIME) {
			logerror("We waited long enough ($MAX_WAIT_TIME) for our background 'dvd tar' to finish. Killing PID $pid");
			system("kill -TERM $pid 2>> $LOGFILE");
		}
	}



	#
	# There was an error. Log information which could help post-mortem the problem.
	# Do this *before* we unmount the drive.
	#
	if($total_returnval != 0) {

		logerror("An error occurred during backup. Following is some system information to help troubleshoot this issue.");
		open(PIPE, "df -k |");
		while(<PIPE>) {
			logerror($_);
		}
		close(PIPE);

		open(PIPE, "dmesg | tail |");
		while(<PIPE>) {
			logerror($_);
		}
		close(PIPE);
	}

	# The output of the "df" command looks like this:
	#
	#Filesystem           1k-blocks      Used Available Use% Mounted on
	#/dev/cciss/c0d0p1     26204060  22578308   2294656  91% /
	#
	# array[0]            array[1]   array[2]   array[3]
	#
	# How much space is left?
	open(PIPE, "df -k $MOUNTPOINT |");
	while(<PIPE>) {
		next until(/$MOUNTPOINT/);

		chomp;
		@array = split(/\s+/);

		my $total_bu_space = int($array[1] / 1024);
		my $free_space_percent = "??";
		if ( ($array[1] > 0) && ($array[3] <= $array[1]) ) {
			$free_space_percent = int(100 * ($array[3]/$array[1]));
		}

		loginfo("Total Backup Space: $total_bu_space MB. (${free_space_percent}% Free)");

		last;
	}
	close(PIPE);


	# Copy the backup script onto our backup media, which will a
	# bare-metal restore possible but there is not workflow that
	# currently makes use of the capability.  This is relatively
	# safe from the PCI perspective, as the service tag number
	# is used for the cryptkey and thus 'hard coded' into the
	# backup script.
	system("cp $0 $MOUNTPOINT");


	# Once the backup is finished, umount the drive.
	unmount_device();

	return($total_returnval);
}


#
# Daisy POS can have one or more daisy db dirs.
#
sub get_daisy_db_dirs
{
    my @daisy_db_dirs = ();

    # if not a Daisy system, then list is empty, we are done
    unless (-d "/d/daisy") {
	return(@daisy_db_dirs);
    }

    my @d_dirs = glob("/d/*");

    for my $d_dir (@d_dirs) {

	# must be a directory
	next unless (-d $d_dir);

	# skip old daisy dirs
	next if ($d_dir =~ /.+-\d{12}$/);

	# must contain the magic files
	next unless(-e "$d_dir/flordat.tel");
	next unless(-e "$d_dir/control.dsy");

	# must be daisy 8.0+
	next unless (-d "$d_dir/bin");

	push(@daisy_db_dirs, $d_dir);
    }

    return(@daisy_db_dirs);
}


#
# Add excludes for all the daisy database dirs to the global
# list of excluded paths.
#
sub determine_daisy_excludes
{
    my $global_excludes = $_[0];

    my @daisy_db_dirs = get_daisy_db_dirs();

    foreach my $daisy_db_dir (@daisy_db_dirs) {
	push(@{$global_excludes}, "${daisy_db_dir}-*");
	push(@{$global_excludes}, "${daisy_db_dir}/*.iso");
	push(@{$global_excludes}, "${daisy_db_dir}/*.tar.asc");
	push(@{$global_excludes}, "${daisy_db_dir}/log/*");
    }
}


#
# Send select backup files to a specified "secondary" server.
# Note here, we are also specifying the secondary's service tag number,
# which will act as one half of the encryption key.
#
sub send_to_secondary_server
{
	my $sendto = $_[0];
	my @sendto_excludes = @{$_[1]};
	my $remote;
	my $bytecount = -1;
	my $secondary_ip =  "";
	my $secondary_svctag = "";
	my $buffer;
	my $returnval = 0;

	#
	# "sendto" should consist of the secondary's ip address and service tag number
	# formatted like: "host.ip.address:svctag"
	#
	# The default encryption key for the tar archive sent to the secondary will be the
	# concatenation of primary servicetag and secondary servicetag.  Instead of using
	# the primary service tag for the first half of encryption key, it may be specified
	# on the command line, which takes precedence.  If not specified on command line and
	# the service tag can not be determined, there is a default value supplied.
	#
	($secondary_ip, $secondary_svctag) = split(/:/, $sendto);
	if ($secondary_ip eq "") {
		logerror("IP address of secondary server missing from --sendto commandline argument.");
		return($EXIT_SENDTO_IPADDR);
	}
	if ($secondary_svctag eq "") {
		logerror("Service tag of secondary server missing from --sendto commandline argument.");
		return($EXIT_SENDTO_SVCTAG);
	}

	my $begin_timestamp = strftime("%a %b %d %H:%M:%S %Y", localtime());

	# Write extra info to logfile
	loginfo("#");
	loginfo("#      Backup Started: $begin_timestamp");
	loginfo("#      Backup Version: $CVS_REVISION");
	loginfo("#   Backup Process ID: $$");

	# What is our hostname?
	my $primary_hostname = get_hostname();

	if ($primary_hostname ne "") {
		loginfo("#      Primary Server: $primary_hostname");
	}
	loginfo("#    Secondary Server: $secondary_ip");

	# What type of POS are we backing up?
	my $pos_name = "UNK";
	my $pos_data_dir = "UNK";
	if ($RTI) {
		$pos_name = "RTI";
		$pos_data_dir = '/usr2/bbx';
	}
	elsif ($DAISY) {
		$pos_name = "Daisy";
		$pos_data_dir = '/d';
	}
	my $backup_file_set = "$pos_data_dir/* /etc";

	loginfo("#     Backup File Set: $backup_file_set");

	# Get path to a temp file name
	my $tmpfile = make_tempfile("/tmp/sendto-excludes");
	loginfo("#  Excluded File List: $tmpfile");

	# If specified, put list of excluded files in position for use by tar command.
	my $exclude_opt = "";
	if (@sendto_excludes) {
		loginfo("--- Begin Making Excluded File List ---");
		open(EXCLUDE, "> $tmpfile");
		foreach (@sendto_excludes) {
			print(EXCLUDE "$_\n");
			loginfo("$_");
		}
		close(EXCLUDE);
		$exclude_opt = "--exclude-from=$tmpfile";
		loginfo("--- End Making Excluded File List ---");
	}

	my $is_encrypting = ($SENDTO_ENCRYPT) ? "yes" : "no";
	loginfo("#     Encrypting data: $is_encrypting");

	loginfo("#");

	# the crypt key is made from the crypt key for this system -
	# usually the Dell Service Tag - combined with the service tag
	# from the secondary server.
	my $cryptkey = uc($CRYPTKEY) . uc($secondary_svctag);

	# Form the encrypt command line if any
	my $encrypt_cmd = "";
	if ($SENDTO_ENCRYPT) {
	    $encrypt_cmd = "nice openssl aes-128-cbc -e -salt -k \"$cryptkey\"";
	}

	# Form the tar command line
	my $tar_cmd = "tar $exclude_opt -cvf - $backup_file_set";
	my $command = "$tar_cmd 2>> $LOGFILE";
	if ($encrypt_cmd ne "") {
	    $command = "$tar_cmd 2>> $LOGFILE | $encrypt_cmd 2>> $LOGFILE";
	}

	# Connect to the secondary server.
	$remote = IO::Socket::INET->new(
		Proto => "tcp",
		PeerAddr => "$secondary_ip",
		PeerPort => "$SECONDARY_SVR_PORT",
		Blocking => 1,
		Timeout => 1
	);
	if (! $remote) {
		showinfo("Error: Could not connect to \"$secondary_ip:$SECONDARY_SVR_PORT\".");
		if ($#EMAIL >= 0) {
		    my $subject = "Error: Could not connect to secondary server";
		    my $info = "
#      Backup Started: $begin_timestamp
#      Backup Version: $CVS_REVISION
#   Backup Process ID: $$
#      Primary Server: $primary_hostname
#    Secondary Server: $secondary_ip:$SECONDARY_SVR_PORT";

		    send_email(\@EMAIL, $subject, $info);
		}
		return($EXIT_SENDTO_CONNECT);
	}

	loginfo("Sending $pos_name Data to Secondary Server");
	loginfo("Command Line: $command");

	my $logerror_msg = "";

	# Actually transact data here.
	open(TAR, "$command |");
	while(1) {
		$bytecount = sysread(TAR, $buffer, 8 * 1024);

		# Throttle transfer speeds such that we don't eat all bandwidth.
		#select(undef, undef, undef, 0.25);

		last if($bytecount == 0);

		if($bytecount < 0) {
			$logerror_msg = "Error \"$!\" reading tar (input) process.";
			$returnval = $EXIT_SENDTO_READ_ERR;
			last;
		}
		if(! syswrite($remote, $buffer, $bytecount)) {
			$logerror_msg = "Error \"$!\" writing backup to socket.";
			$returnval = $EXIT_SENDTO_WRITE_ERR;
			last;
		}
	}
	close($remote);
	close(TAR);

	my $completion_timestamp = strftime("%a %b %d %H:%M:%S %Y", localtime());

	if ($logerror_msg) {
		logerror($logerror_msg);
	}

        if ($#EMAIL >= 0) {
                my $subject = "Backup to Secondary Server Completed";
		my $info = "
#      Backup Started: $begin_timestamp
#      Backup Version: $CVS_REVISION
#   Backup Process ID: $$
#      Primary Server: $primary_hostname
#    Secondary Server: $secondary_ip
#     Backup File Set: $backup_file_set
#    Backup Exclusion: @sendto_excludes
#    Backup Completed: $completion_timestamp
#       Backup Status: Backup completed";

		if ($logerror_msg) {
			$subject .= " with Errors";
			$info .= " with errors.\n";
			$info .= $logerror_msg;
		}
		else {
			$subject .= " Successfully";
			$info .= " successfully";
		}

		send_email(\@EMAIL, $subject, $info);
	}

	unlink($tmpfile);

	loginfo("Completed Sending Data to Secondary Server.");

	return($returnval);
}


#
# Listen, and then receive content from a primary server (which is running "--sendto=").
# Note here, we are also specifying the secondary's service tag number, which
# will act as one half of the encryption key.
#
sub receive_from_primary_server
{
	my @array = ();
	my $remote;
	my $primary_ip =  "";
	my $primary_svctag =  "";
	my $buffer = "";
	my $pos_name = "";
	my $tar_dstdir = "";
	my $bytecount = -1;
	my $returnval = 0;
	my $connection;
	my $pid = -1;


	# Did we get the right commandline arguments?
	loginfo("Starting process to receive data from primary server.");

	# Are we already running?
	open(PIPE, "netstat -plan |");
	while (<PIPE>) {
		if (/((\d+).(\d+).(\d+).(\d+):$SECONDARY_SVR_PORT)(\s+)(LISTEN)/) {
			showinfo("TCP port /$SECONDARY_SVR_PORT already in use.");
			showinfo("Will not start background 'receive' process.");
			return($EXIT_SECONDARY_IN_USE);
		}
	}
	close(PIPE);

	#
	# "--receivefrom" argument should be of the form "host.ip.address:svctag".
	# The encryption key will be a concatenation of the primary (sender)
	# servicetag and the secondary (receiver) servicetag.
	#
	($primary_ip, $primary_svctag)  = split(/:/, $RCVFROM);
	if ($primary_ip eq "") {
		logerror("IP address of primary server missing from --receivefrom commandline argument.");
		return($EXIT_SECONDARY_IPADDR);
	}
	if ($primary_svctag eq "") {
		logerror("Service tag of primary server missing from --receivefrom commandline argument.");
		return($EXIT_SECONDARY_SVCTAG);
	}
	my $cryptkey = uc($primary_svctag) . uc("$CRYPTKEY");


	# Fork off a sub process.
	$pid = fork();
	if($pid > 0) {

		##########
		# parent #
		##########

		showinfo("Started 'receive' daemon as PID $pid");
		showinfo("Exiting from original rtibackup.pl process");
		return($EXIT_OK);
	}

	#########
	# child #
	#########

	# Give a little time for parent to write log message and return
	sleep(1);

	#
	# Put files into either the appropriate RTI or Daisy directory on the receiver.
	# This prevents us over-writing any sort of 'critical' files inadvertently, and
	# allows a "restore" to be as simple as "mv".
	#
	if ($RTI) {
		$pos_name = "RTI";
		$tar_dstdir = "/usr2/bbx/backups";
	}
	elsif ($DAISY) {
		$pos_name = "Daisy";
		$tar_dstdir = "/save";
	}

	# Form the decrypt command
	my $encrypt_cmd = "";
	if ($RCVFROM_DECRYPT) {
	    $encrypt_cmd = "openssl aes-128-cbc -d -salt -k \"$cryptkey\"";
	}

	while(1) {

		#
		# This could be a very long running process, so we need to
		# rotate the logfile.
		#
		$LOGFILE = logrotate($LOGFILE);

		loginfo("Secondary Server Receive Session");

		# Close our listening socket, if that exists.
		if($remote) {
			$remote->close();
			undef($remote);
		}

		sleep(2); # Prevent rapid-fire spinning of this script.

		# Open a listening socket.
		if(! $remote) {
			$remote = IO::Socket::INET->new(
				Proto => "tcp",
				LocalPort => "$SECONDARY_SVR_PORT",
				Listen => 1,
				Reuse => 1,
				Blocking => 1
			);
		}

		loginfo("Secondary Server: listening for connection from primary server");

		# Wait for connection
		$connection = $remote->accept();
		if (! $connection) {
			loginfo("Error Accepting Connection from primary server: \"$!\"");
			sleep(5);
			next;
		}

		# Make sure our remote client is who we were configured to accept from.
		@array = unpack_sockaddr_in($connection->peername());
		my $peer_ip = inet_ntoa($array[1]);
		if ($peer_ip ne $primary_ip) {
			logerror("Remote IP addr ($peer_ip) != configured IP addr ($primary_ip)...");
			logerror("Closing connection.");
			$connection->close();
			sleep(5); # Prevent rapid-fire DoS attacks.
			next;
		}

		# There could have been a long wait, rotate log file
		$LOGFILE = logrotate($LOGFILE);

		loginfo("Secondary Server: accepted connection from primary server: $peer_ip");

		# Form the tar command here so we pick up any new values for the
		# name of the log file
		my $tar_cmd = "tar -C $tar_dstdir -xvf - >> $LOGFILE 2>> $LOGFILE";
		my $command = $tar_cmd;
		if ($encrypt_cmd ne "") {
		    $command = "$encrypt_cmd | $tar_cmd";
		}

		# Actually transact data here.
		if($connection) {

			loginfo("Secondary Server: begin reading data from primary server");

			open(TAR, "| $command");
			while(1) {
				$bytecount = sysread($connection, $buffer, 4 * 1024);
				last if($bytecount == 0);
				if($bytecount < 0) {
					logerror("Error \"$!\" reading socket (input) process.");
					$returnval = $EXIT_SECONDARY_READ_ERR;
					last;
				}
				if(! syswrite(TAR, $buffer, $bytecount)) {
					logerror("Error \"$!\" writing backup to tar.");
					$returnval = $EXIT_SECONDARY_WRITE_ERR;
					last;
				}
			}
			close($connection);
			close(TAR);

			loginfo("Seondary Server: end reading data from primary server");
		}
	}

	return($returnval);
}



sub backup_dvdfiles
{
	my $devname = $_[0];
	my $returnval = 0;
	my $pid = -1;
	my $kid = -1;
	my $mediasize = -1;
	my $i = 0;

	logdebug("***** BEGIN Backup DVD Files (Background PID $$) *****");

	unless (-f "/usr/bin/dvdrecord") {
		logerror("dvdrecord executable not found. Could not backup to DVD.");
		return(1);
	}

	unless (-f "/usr/bin/mkisofs") {
		logerror("mkisofs executable not found. Could not backup to DVD.");
		return(2);
	}

	unless (-f "/usr/bin/growisofs") {
		logerror("growisofs executable not found. Could not backup to DVD.");
		return(3);
	}

	# These are things we do not want in our tarfile.
	open(EXCLUDE, "> /tmp/dvdexclude.$$");
	print(EXCLUDE << 'EOF');
*.patched*
*.prepatch.*
*.bak
*.old
*.tar
*.tgz
*.gz
*.bz2
*.zip
*.old
*.empty
*.log
/usr2/bbx/log/*
/usr2/bbx/bbxt/*
/usr2/bbx/bbxtmp/dvd*
/usr2/bbx/backups/*
/usr2/basis/blmgr/log/*
DICT01
ONZP02
FDAC01
FDCS02
FDCS03
FDEL02
FDEL03
FDEL04
ONZP01
ONPT01
ONHF01
FDPD01
FDPD02
FDPC01
FDCS01
FDCS04
FDDS01
FDEL01
FDMU01
FDNT01
FDOP01
FDOP02
FDRP01
FDTM01
FDTM02
FDTM03
FDZP01
EOF

	if ($OS eq "RHEL5") {
		push(@EXCLUDES, "/var/log/faillog");
	}
	if ($OS eq "RHEL6") {
		push(@EXCLUDES, "/var/log/tallylog");
	}

	# Exclusions taken from our config file.
	foreach(@EXCLUDES) {
		print(EXCLUDE);
	}


	# If we want to create a backup which excludes cardholder data,  then, don't include these two files.
	if($NOCC != 0) {
		print(EXCLUDE "$RTIDIR/bbxd/CCXF01\n");
		print(EXCLUDE "$RTIDIR/bbxd/CCXF02\n");
		print(EXCLUDE "$RTIDIR/bbxd/ONCA01\n");
		print(EXCLUDE "$RTIDIR/bbxd/bytefile\n");
		print(EXCLUDE "$RTIDIR/dove/work/recv.fil\n");
	}

	close(EXCLUDE);


	# Blank the DVD-RW in the background.
	# This takes a long time to do, as does the creation of our ISO image,
	# thus, we'll do both concurrently.
	$pid = fork();
	if($pid == 0) {
		exec("dvdrecord speed=4 blank=all dev=$devname 2> /dev/null");
		exit(255);
	}


	# Create an image which will be written to disk.
	# $mediasize = int((4.7 * 1024 * 1000 *1000) / 2048) - 200;
	# $mediasize = 2294921 - 200; # http://www.floobydust.com/dvd_backup/ and manpage for growisofs
	$mediasize = 2294000; # Trial and error.
	system("nice tar --exclude-from /tmp/dvdexclude.$$ -cf - $RTIDIR/bbxd $RTIDIR/config $RTIDIR/bbxps | nice bzip2 --best --quiet | nice openssl aes-128-cbc -e -salt -k \"$CRYPTKEY\" | mkisofs -stream-media-size $mediasize -o $RTIDIR/bbxtmp/dvd-$$.iso 2>> $LOGFILE");

	# Wait for our background DVD "blank" to finish.
	if($pid > 0) {
		for($i = 0; $i < $MAX_WAIT_TIME; $i++) {
			$kid = waitpid($pid, WNOHANG);
			last if($kid != 0);
			sleep(1);
		}
		if($kid < 0) {
			logerror("waitpid() on PID $pid returned $kid. This is odd.");
		}

		if($i >= $MAX_WAIT_TIME) {
			logerror("We waited long enough ($MAX_WAIT_TIME) for our background 'dvd erase' to finish. Killing PID $pid");
			system("kill -TERM $pid 2>> $LOGFILE");
		}
	}




	# Actually burn the DVD here.
	# Create an ad-hoc "wrapper" script to run growisofs.
	# This is a kludge for growisofs to run under sudo, see man page for 
	# growisofs.
	open(SCRIPT, "> /tmp/dvdburn-$$.sh");
	print(SCRIPT << "EOF");
#!/bin/bash
export SUDO_COMMAND=
unset SUDO_COMMAND
/usr/bin/growisofs -dvd-compat -speed=4 -Z $devname=$RTIDIR/bbxtmp/dvd-$$.iso
EOF
	close(SCRIPT);
	system("chmod +rx /tmp/dvdburn-$$.sh");
	system("/tmp/dvdburn-$$.sh 2>> $LOGFILE");
	$returnval = $?;


	unlink("/tmp/dvdburn-$$.sh");
	unlink("/tmp/dvdexclude.$$");
	unlink("$RTIDIR/bbxtmp/dvd-$$.iso");
	logdebug("***** END Backup DVD Files (Background PID $$) *****");

	return($returnval)
}


sub read_users_file
{
    my $users_file = $_[0];

    my %users_tab = ();
    my $line;

    open(UF, '<', $users_file);
    while ($line = <UF>) {
	if ($line =~ /^(\S+)\s/) {
	    $users_tab{$1} = 1;
	}
	if ($line =~ /^(\S+)\s.*RTI Admin/) {
	    $users_tab{$1} = 2;
	}
    }
    close(UF);

    return(%users_tab);
}


sub backup_users_info
{
    my $users_file;
    my $users_cmd;

    if (-f "$RTIDIR/bin/rtiuser.pl") {
	$users_file = "$MOUNTPOINT/configs/rtiusers.txt";
	$users_cmd = "$RTIDIR/bin/rtiuser.pl";
    }
    elsif (-f "$DAISYDIR/daisy/bin/dsyuser.pl") {
	$users_file = "$MOUNTPOINT/configs/dsyusers.txt";
	$users_cmd = "$DAISYDIR/daisy/bin/dsyuser.pl";
    }

    system("$users_cmd --list > $users_file 2>> $LOGFILE");

    my %users_tab = read_users_file($users_file);

    open(SF, '<', "/etc/shadow");
    open(OUT, '>', "$MOUNTPOINT/configs/usersinfo.txt");
    while (<SF>) {
	my $i = index($_, ":");
	my $username = substr($_, 0, $i);
	if (defined($users_tab{$username})) {
	    print OUT "$_";
	}
    }
    close(SF);
    close(OUT);
}


sub backup_userconfigs
{
	my @toback = ();

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	showinfo("BEGIN Backup of POS User Configs...");

	backup_users_info();

	#
	# Save files that have to do with users.
	#
	@toback = qw(
		/etc/pam.d
		/etc/shadow
		/etc/shadow-
		/etc/gshadow
		/etc/passwd
		/etc/passwd-
		/etc/group
		/etc/login.defs
		/etc/sudoers
		/etc/cron.d/nightly-backup
		/var/spool/cron
		/home
		/root
	);

	if ($OS eq "RHEL5") {
		push(@toback, "/var/log/faillog");
	}
	if ($OS eq "RHEL6") {
		push(@toback, "/var/log/tallylog");
	}

	if (-d "$RTIDIR") {
		push(@toback, "/etc/cron.d/rti*");
	}
	if (-d "$DAISYDIR") {
		push(@toback, "/etc/cron.d/daisy*");
	}

	create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/userconfigs.bak");


	loginfo("END Backup of POS User Configs...");

	return(0);
}


sub backup_printconfigs
{
	my @toback = ();

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup printconfigs.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/configs");
	}

	showinfo("BEGIN Backup of Printer Configs...");


	# Printer Configs
	@toback = ();
	push(@toback, "/etc/printcap ");
	push(@toback, "/etc/cups");
	if (-f "$RTIDIR/config/config.bbx") {
		push(@toback, "$RTIDIR/config/config.bbx");
	}

	create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/printconfigs.bak");


	loginfo("END Backup of Printer Configs...");

	return(0);
}



sub backup_osconfigs
{
	my @toback = ();
	my $returnval = 0;

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup osconfigs.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/configs");
	}

	showinfo("BEGIN Backup of OS Configs...");


	# OS Configs
	@toback = ();

	my @common_files = (
	    "/etc/ssh",		    # tfremote
	    "/etc/samba",
	    "/etc/mail",	    # RTI v12 outbound emails
	    "/etc/cron.d/nightly-backup",
	    "/etc/log.d",
	    "/etc/sysconfig/rhn",
	    "/etc/yum",
	    "/etc/yum.conf",
	    "/etc/yum.repos.d",
	    "/etc/rc.d/init.d/tfremote",
	    "/etc/inittab",
	);

	# contains setting for kernel log message priority for console
	if ($OS eq "RHEL6") {
	    push(@common_files, "/etc/rsyslog.conf");
	}
	else {
	    push(@common_files, "/etc/sysconfig/syslog");
	}

	foreach (@common_files) {
	    if (-e $_) {
		push(@toback, $_);
	    } else {
		loginfo("Skipping backup of \"$_\". File not found.");
	    }
	}

	if ($RTI) {
	    my @rti_files = (
		"/etc/httpd/conf.d/rti.conf",	# RTI v14
		"/etc/cron.d/rti",
		"/etc/cron.d/rti-service",
		"$RTIDIR/bin/tfremote.pl",
		"/usr/src/redhat"		# Digi portserver drivers
	    );
	    foreach (@rti_files) {
		if (-e $_) {
		    push(@toback, $_);
		} else {
		    loginfo("Skipping backup of \"$_\". File not found.");
		}
	    }
	}

	if ($DAISY) {
	    my @daisy_files = (
		"/etc/cron.d/daisy",
		"/etc/cron.d/daisy-service",
		"$DAISYDIR/daisy/bin/tfremote.pl"
	    );
	    foreach (@daisy_files) {
		if (-e $_) {
		    push(@toback, $_);
		} else {
		    loginfo("Skipping backup of \"$_\". File not found.");
		}
	    }
	}

	$returnval += create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/osconfigs.bak");


	# Master boot record.
	logdebug("Saving Master Boot Record...");
	system("dd if=/dev/sda of=$MOUNTPOINT/configs/sda-mbr.img bs=512 count=1 2>> $LOGFILE");
	system("/sbin/sfdisk -d > $MOUNTPOINT/configs/sfdisk-layout.txt 2>> $LOGFILE");
	system("mount > $MOUNTPOINT/configs/mount-layout.txt 2>> $LOGFILE");


	# "OS" configs. Just in case.
	# This is an advanced item in case we need it.
	# Restoring from this, you should know what you are doing.
	logdebug("Saving /etc...");
	@toback = ();
	push(@toback, "/etc"); # Just in case we miss something below.
	$returnval += create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/etc.bak");


	loginfo("END Backup of OS Configs...");

	return($returnval);
}



sub backup_netconfigs
{
	my @toback = ();
	my $returnval = 0;

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup netconfigs.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/configs");
	}

	showinfo("BEGIN Backup of Network Configs...");


	# Network Configs
	@toback = ();
	push(@toback, "/etc/hosts");
	push(@toback, "/etc/hosts.allow");
	push(@toback, "/etc/hosts.deny");
	push(@toback, "/etc/resolv.conf");
	push(@toback, "/etc/ssh");
	push(@toback, "/etc/sysconfig/iptables");
	push(@toback, "/etc/sysconfig/network");
	push(@toback, "/etc/sysconfig/networking/profiles");
	push(@toback, "/etc/sysconfig/network-scripts/ifcfg*");

	# this path does not exist on all platforms
	my @net_devices_dir = glob("/etc/sysconfig/networking/devices/ifcfg*");
	if (@net_devices_dir) {
	    push(@toback, "/etc/sysconfig/networking/devices/ifcfg*");
	}

	$returnval += create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/netconfigs.bak");


	loginfo("END Backup of Network Configs...");

	return($returnval);
}

sub backup_rticonfigs
{
	my @toback = ();
	my $returnval = -1;

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup rticonfigs.");
		return(0);
	}
	if($RTI == 0) {
		loginfo("--rti not specified. Will not backup RTI configs.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/configs");
	}

	showinfo("BEGIN Backup of RTI Configs...");

	backup_users_info();

	# RTI Configs
	@toback = ();
	push(@toback, "/etc/sysconfig/i18n");
	push(@toback, "/etc/inittab");
	push(@toback, "/etc/sudoers");
	push(@toback, "/etc/profile");
	push(@toback, "/etc/profile.d/rti.sh");
	push(@toback, "/etc/profile.d/pro5.sh");
	push(@toback, "/etc/rc.d/init.d/rti");
	push(@toback, "/etc/rc.d/init.d/blm");
	push(@toback, "/etc/rc.d/init.d/bbj");
	push(@toback, "$RTIDIR/config/");
	push(@toback, "/var/spool/fax");
	push(@toback, "/usr/local/lib/BITMAPS");

	$returnval = create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/rticonfigs.bak");


	loginfo("END Backup of RTI Configs...");

	return($returnval);
}

sub backup_dsyconfigs
{
	my @toback = ();
	my $returnval = -1;

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup dsyconfigs.");
		return(0);
	}
	if($DAISY == 0) {
		loginfo("--daisy not specified. Will not backup Daisy configs.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/configs");
	}

	showinfo("BEGIN Backup of Daisy Configs...");


	# Copy a list of the Daisy Users to the backup medium.
	system("$DAISYDIR/daisy/bin/dsyuser.pl --list > $MOUNTPOINT/configs/dsyusers.txt 2>> $LOGFILE");


	# Here is the list of files that constitute the Daisy Configs
	@toback = ();
	push(@toback, "/etc/inittab");
	push(@toback, "/etc/sudoers");
	push(@toback, "/etc/profile.d/daisy.sh");
	push(@toback, "/etc/rc.d/init.d/daisy");
	push(@toback, "$DAISYDIR/daisy/config/");

	$returnval = create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/dsyconfigs.bak");


	loginfo("END Backup of Daisy Configs...");

	return($returnval);
}

sub backup_usr2
{
	my @toback = ();
	my $returnval = -1;

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup usr2.");
		return(0);
	}
	if($RTI == 0) {
		loginfo("--rti not specified. Will not backup usr2 directory.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	showinfo("BEGIN Backup of RTI Data (/usr2)...");


	# RTI
	@toback = ();
	push(@toback, "/usr2");

	# Version 14
	foreach("/var/www/jnlp",
	"/var/www/lib",
	"/var/www/cgi-bin"
	) {
		if(-d $_) {
			push(@toback, $_);
		}
	}

	$returnval = create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/usr2.bak");

	# Stow away a list of inode numbers for each 'important' bbx file.
	# We have seen cases where fsck goes wrong and loses files. However,
	# these 'lost' files often show up in /usr2/lost+found as a file named
	# after the inode. If we were to know the inode mapping (as per these
	# items below), we would have a better idea of what the files are in "lost+found".
	# Again, this is only useful in very bad situations, but at least, gives us options.

	my @dir_records = (
	    [ "ls -l --inode --recursive", "/usr2/bbx/bbxd", "bbxd-filelist.txt" ] ,
	    [ "ls -l --inode --recursive", "/usr2/bbx/bbxps", "bbxps-filelist.txt" ],
	);

        foreach (@dir_records) {

	    my $cmd  = ${$_}[0];    # command to run
	    my $dir  = ${$_}[1];    # comand argument
	    my $dest = ${$_}[2];    # redirect output of command to this

	    next unless (-d $dir);

	    system("$cmd $dir > $MOUNTPOINT/configs/$dest");
	}

	loginfo("END Backup of RTI Data (/usr2)...");

	return($returnval);
}


sub backup_daisy
{
	my @toback = ();
	my $returnval = -1;

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup daisy.");
		return(0);
	}
	if($DAISY == 0) {
		loginfo("--daisy not specified. Will not backup daisy directory.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	showinfo("BEGIN Backup of Daisy Data...");


	# Daisy
	@toback = ();
	push(@toback, "/d");

	$returnval = create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/daisy.bak");

	# Mapping of "Inode to filenames"; in case we ever need to pick files out of /d/lost+found.
	system("ls -l --inode --recursive /d > $MOUNTPOINT/configs/daisy-filelist.txt");

	loginfo("END Backup of Daisy Data...");

	return($returnval);
}


#
# "Poor Man's RAID" for Daisy.
# Long ago, we used Linux Software RAID for Daisy, however, in the early Redhat 5.x days, we saw definite kernel panic issues 
# with the use of Redhat. Hence, we decided to move away from Redhat, and instead, towards a "poor man's RAID" model; two disks,
# re-synchronized nightly.
#
sub ddbackup
{
	my $found = 0;
	my $pid = -1;

	showinfo("Starting Daisy 'Disk Duplication'");


	# Daisy isn't even present.
	if(! -d "/d") {
		logerror("/d mountpoint not found. Will not proceed with disk duplication.");
		return;
	}

	# We are not already running software RAID.
	open(PIPE, "mount |");
	while(<PIPE>) {
		if(/\/dev\/md\d+/) {
			logerror("Software RAID is in use. Will not proceed with disk duplication backup.");
			return;
		}
		if(/\/dev\/sdb\d+/) {
			logerror("A partition on /dev/sdb is already mounted. Will not proceed with disk duplication backup.");
			return;
		}
	}
	close(PIPE);


	# We must be running a T3500.
	$found = 0;
	open(PIPE, "dmidecode |");
	while(<PIPE>) {
		if(/Precision WorkStation T3500/i) {
			loginfo("This is a Dell Precision T3500");
			$found = 1;
		}
	}
	close(PIPE);
	if($found == 0) {
		logerror("This is NOT a Dell Precision T3500. Will not perform Daisy 'disk duplication'");
		return;
	}



	# Make damned sure 'sda' and 'sdb' are both the same type of disk.
	system("grep 'WDC WD1600HLFS-7' /sys/block/sda/device/model > /dev/null 2> /dev/null");
	if($? != 0) {
		logerror("Did not find expected hard disk signature (sda). Will not perform partition backup.");
		return;
	}
	system("grep 'WDC WD1600HLFS-7' /sys/block/sdb/device/model > /dev/null 2> /dev/null");
	if($? != 0) {
		logerror("Did not find expected hard disk signature (sdb). Will not perform partition backup.");
		return;
	}

	$pid = fork();
	if($pid == 0) {
		my $sourcepartition = "";
		my $destpartition = "";
		system("sfdisk -d /dev/sda | sfdisk /dev/sdb");
		# Need to "rsync" each partition here.

		# Re-install grub on sdb disk.
		open(PIPE, "| /sbin/grub --batch");
			print(PIPE "device (hd0) /dev/sdb\n");
			print(PIPE "root (hd0,0)\n");
			print(PIPE "setup (hd0)\n");
			print(PIPE "quit\n");
		close(PIPE);

		# Spin down sdb disk, which should allow it to live a longer life.
		system("/sbin/hdparm -S 120 /dev/sdb");
		exit(0);
	}
	showinfo("Disk Duplication Backup Started as PID $pid");
}




sub backup_altiris
{
	my @toback = ();

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup altiris.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/configs");
	}
	if(! -d "/opt/altiris") {
		loginfo("/opt/altiris does not exist. Cannot backup what is not there.");
		return(0);
	}

	showinfo("BEGIN Backup of Altiris Data...");


	# Altiris
	@toback = ();
	push(@toback, "/opt/altiris");
	push(@toback, "/etc/rc.d/init.d/altiris");

	create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/altiris.bak");


	loginfo("END Backup of Altiris Data...");

	return(0);
}

#
# Backup user specified files.
#
sub backup_userfiles
{
	my @toback = ();
	my @emptyarray = ();

	if("$DEVICE" eq "") {
		loginfo("No device specified to backup user files.");
		return(0);
	}

	mount_device("rw");
	if (!device_is_mounted()) {
		return(-1);
	}

	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/configs");
	}

	# typically, there are none.
	unless (@USERFILES) {
		loginfo("There are no user specified files to backup... skipping");
		return(0);
	}

	showinfo("BEGIN Backup of User Specified Files...");


	create_tarfile(\@USERFILES, \@emptyarray,  "$MOUNTPOINT/userfiles.bak");


	loginfo("END Backup of User Specified Files...");

	return(0);
}


#
# Backup log files.
#
sub backup_logfiles
{
    my @toback = ();

    if ("$DEVICE" eq "") {
	loginfo("No device specified to backup log files.");
	return(0);
    }

    mount_device("rw");
    if (!device_is_mounted()) {
	return(-1);
    }

    if(! -d "$MOUNTPOINT/configs") {
	mkdir("$MOUNTPOINT/configs");
    }

    showinfo("BEGIN Backup of Log Files...");


    push(@toback, "/var/log");

    create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/logfiles.bak");


    loginfo("END Backup of Log Files...");

    return(0);
}




#
# Guess which device our rev drive is mapped to.
# We are looking for a fast "cdrom" device which can't write CD-Rs, can't read DVDs
# yet, can "write RAM".
#
sub find_revdevice
{
        my @array = ();
        my %hash  =();
        my $line = "";
        my $key = "";
        my $i = 0;


	if(! -e "/proc/sys/dev/cdrom/info") {
		logerror("Warning: Cannot look for Rev device: \"/proc/sys/dev/cdrom/info\" not present.");
		return("");
	}


        open(FILE, "< /proc/sys/dev/cdrom/info");
        while(<FILE>) {
                chomp;
                $line = $_;
                next if("$line" eq "");
                if($line =~ /^drive name:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
				#
				# Just add "/dev/" at the beginning of the device name
				# so it will work with mount.
				#
                                $hash{$i}{"name"} = "$array[$i]";
                        }
                }
                if($line =~ /^Can read DVD:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
                                $hash{$i}{"can_read_dvd"} = $array[$i];
                        }
                }

                if($line =~ /^Can write CD-R:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
                                $hash{$i}{"can_write_cdr"} = $array[$i];
                        }
                }
                if($line =~ /^drive speed:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
                                $hash{$i}{"speed"} = int($array[$i]);
                        }
                }


        }
        close(FILE);


	if(! %hash) {
		return("");
	}

        foreach $key(keys(%hash)) {
                if( ($hash{$key}{"can_write_cdr"} == 0)
                &&  ($hash{$key}{"can_read_dvd"} == 0) 
                &&  ($hash{$key}{"speed"} > 100) 
		) {

			$DEVICE_TYPE = "rev";

			# For reasons not yet understood, "/dev/scd0" seems to work "better"
			# than sr0 after extended periods of time under RTI.
			if($hash{$key}{"name"} eq "sr0") {
				return("/dev/scd0");
			}
			if($hash{$key}{"name"} eq "sr1") {
				return("/dev/scd1");
			}

                        return("/dev/" . $hash{$key}{"name"});
                }
        }

	return("");
}


#
# Locate a Dell RD1000 device.
#
sub find_rd1000
{
	my $thisdev = "";
	my $found = 0;

	if(! -d "/sys/block") {
		logerror("Warning: Cannot look for RD1000 device: \"/sys\" filesystem not present.");
		return("");
	}
	foreach $thisdev("sda", "sdb", "sdc", "sdd", "sde") {

		# Device is removable?
		$found = 0;
		if(-f "/sys/block/$thisdev/removable") {
			open(FILE, "< /sys/block/$thisdev/removable");
			while(<FILE>) {
				if(/1/) {
					$found = 1;
					last;
				}
			}
			close(FILE);
		}
		if($found == 0) {
			next;
		}


		# DELL is the vendor
		$found = 0;
		if(-f "/sys/block/$thisdev/device/vendor") {
			open(FILE, "< /sys/block/$thisdev/device/vendor");
			while(<FILE>) {
				if(/DELL/i) {
					$found = 1;
					last;
				}
			}
			close(FILE);

		}
		if($found == 0) {
			next;
		}

		# Model is correct.
		$found = 0;
		if(-f "/sys/block/$thisdev/device/model") {
			open(FILE, "< /sys/block/$thisdev/device/model");
			while(<FILE>) {
				if(/RD1000/i) {
					$found = 1;
					last;
				}
			}
			close(FILE);
		}
		if($found == 0) {
			next;
		}

		return("/dev/$thisdev");
	}

	return("");
}


#
# Locate a USB Device - either a disk or flash drive
#
# Scan the output of "udevinfo" and look for two patterns:
#   ID_BUS=usb
#   ID_FS_LABEL=TFBUDSK-nnnnnnnn
#
# If there is no output from "udevinfo" that contains the
# string "ID_FS_LABEL", then try the "/lib/udev/vol_id" command.
#
# Returns non-empty string if device found else empty string
#
sub find_usb_device
{
    my $returnval = "";
    my @dev_files = qw(
	/dev/sda
	/dev/sdb
	/dev/sdc
	/dev/sdd
	/dev/sde
	/dev/sdf
	/dev/sdg
    );

    my $udevcmd = "/usr/bin/udevinfo";

    if ($OS eq "RHEL6") {
	    $udevcmd = "/sbin/udevadm info";
    }

    foreach my $this_dev_file (@dev_files) {

	# verify there is a block device file
	next unless (-b $this_dev_file);

	# now verify it's on the USB bus and has a label made with rtibackup.pl
	unless(open(PIPE, "$udevcmd -q env -n $this_dev_file |")) {
	    showerror("Can't query device info for device file: $this_dev_file");
	    next;
	}

	my $is_on_usb_bus = 0;
	my $has_bu_label = 0;
	my $fs_label_seen = 0;

	while (<PIPE>) {
	    if (/ID_BUS=usb/) {
		$is_on_usb_bus = 1;
	    }
	    if (/^ID_FS_LABEL/) {
		$fs_label_seen = 1;
		if (/^ID_FS_LABEL=TFBUDSK-(\d{8})/) {
		    $has_bu_label = 1;
		    last;
		}
	    }
	}
	close(PIPE);

	#
	# If there wasn't any info on the file system from the
	# "udevinfo" command, then try another approach.
	#
	unless ($fs_label_seen) {
	    my $tmp_file = "/tmp/rtibackup.pl.$$";
	    system("/sbin/e2label $this_dev_file > $tmp_file 2> /dev/null");
	    if ($? == 0) {
		my $disk_label = qx(cat $tmp_file);
		if ($disk_label =~ /^TFBUDSK-(\d{8})/) {
		    $has_bu_label = 1;
		}
	    }

	    system("rm $tmp_file");
	}

	if ($is_on_usb_bus && $has_bu_label) {
	    $returnval = $this_dev_file;
	    $DEVICE_TYPE = "usb";
	    last;
	}
    }

    return($returnval);
}


#
# Locate a Western Digital Passport Device
#
sub find_passport
{
	my $thisdev = "";
	my $found = 0;

	if(! -d "/sys/block") {
		logerror("Warning: Cannot look for Passport device: \"/sys\" filesystem not present.");
		return("");
	}
	foreach $thisdev("sda", "sdb", "sdc", "sdd", "sde") {


		# WD (Western Digital) is the vendor
		$found = 0;
		if(-f "/sys/block/$thisdev/device/vendor") {
			open(FILE, "< /sys/block/$thisdev/device/vendor");
			while(<FILE>) {
				if(/$DEVICE_VENDOR/i) {
					$found = 1;
					last;
				}
			}
			close(FILE);
		}
		if($found == 0) {
			next;
		}

		# ... AND Model is correct.
		# Note it is important to look at the model. Some shops could have internal Western
		# digital HDDs. If we were to only look for "WD" drive, and not look for this particular
		# model, then, we wcould inadvertently use the "real" device as the "backup" device, which
		# would be catastrophic.
		$found = 0;
		if(-f "/sys/block/$thisdev/device/model") {
			open(FILE, "< /sys/block/$thisdev/device/model");
			while(<FILE>) {
				if(/$DEVICE_MODEL/i) {
					$found = 1;
					last;
				}
			}
			close(FILE);
		}
		if($found == 0) {
			next;
		}

		$DEVICE_TYPE = "passport";
		return("/dev/$thisdev");
	}

	return("");
}


# Is there a tape drive, and, do we see a tape in the drive?
sub find_tapedrive
{
	my $thisdev = "";
	my $found = 0;


	foreach $thisdev("st0", "st1") {

		next until(-e "/dev/$thisdev");

		open(MT, "mt -f /dev/$thisdev status |");
		while(<MT>) {
			if(/ONLINE/) {
				$found = 1;
			}
		}
		close(MT);

		return("/dev/$thisdev");
	}

	return("");
}

#
# Guess which device our rev drive is mapped to.
# We are looking for a fast "cdrom" device which can't write CD-Rs, can't read DVDs
# yet, can "write RAM".
#
sub find_dvddevice
{
        my @array = ();
        my %hash  =();
        my $line = "";
        my $key = "";
        my $i = 0;

        open(FILE, "< /proc/sys/dev/cdrom/info");
        while(<FILE>) {
                chomp;
                $line = $_;
                next if("$line" eq "");
                if($line =~ /^drive name:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
                                $hash{$i}{"name"} = $array[$i];
                                $hash{$i}{"name"} =~ s/sr/\/dev\/scd/g;
                        }
                }
                if($line =~ /^Can read DVD:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
                                $hash{$i}{"can_read_dvd"} = $array[$i];
                        }
                }

                if($line =~ /^Can write CD-R:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
                                $hash{$i}{"can_write_cdr"} = $array[$i];
                        }
                }
                if($line =~ /^Can write DVD-R:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
                                $hash{$i}{"can_write_dvdr"} = $array[$i];
                        }
                }
                if($line =~ /^drive speed:/i) {
                        @array = split(/\t+/);
                        for($i = 1; $i <= $#array; $i++) {
                                $hash{$i}{"speed"} = int($array[$i]);
                        }
                }


        }
        close(FILE);


        foreach $key(keys(%hash)) {
                if( ($hash{$key}{"can_write_cdr"} == 1)
                &&  ($hash{$key}{"can_write_dvdr"} == 1) 
                &&  ($hash{$key}{"can_read_dvd"} == 1) 
                &&  ($hash{$key}{"speed"} > 0) 
                &&  ($hash{$key}{"speed"} < 100) 
		) {
                        return($hash{$key}{"name"});
                }
        }

	return("");
}




#
# Is the backup device already mounted?
# Returnval of 1 means "yes, the device is mounted."
# Returnval 0 means "no, the device is not mounted."
#
sub device_is_mounted
{
	my $returnval = 0;

	# Does the backup device mount point exist?
	if (! -e "$MOUNTPOINT") {
		logerror("Backup Device mount point \"$MOUNTPOINT\" not found.");
		return(0);
	}


	open(FILE, "< /proc/mounts");
	while(<FILE>) {
		if(/$MOUNTPOINT/) {
			$returnval = 1;
			last;
		}
		if(/$DEVICE/) {
			$returnval = 1;
			last;
		}
	}
	close(FILE);

	return($returnval);
}


#
# Mount either a block device, or an "image file".
# Returnval of 0 means "yes, mount succeeded"
# Returnval non-zero means "no, the mount failed."
#
sub mount_device_simple
{
    my ($ml, $mount_opt) = @_;

    # Is our mountpoint present?
    unless (-d $MOUNTPOINT) {
	showinfo("[$ml] Making mountpoint: $MOUNTPOINT");
	mkdir("$MOUNTPOINT");
    }
    unless (-d $MOUNTPOINT) {
	showerror("[$ml] Could not find nor make mountpoint: $MOUNTPOINT");
	return(-3);
    }

    if (device_is_mounted()) {
	showinfo("[$ml] Device already mounted: $DEVICE");
	system("umount $DEVICE > /dev/null 2>&1");
	if ($? != 0) {
	    showerror("[$ml] Could not umount device: $DEVICE");
	    return(-3);
	}
    }

    my $device_type = "";
    if (-b $DEVICE) {
	$device_type = "device";
    }
    elsif (-f $DEVICE) {
	$device_type = "image file";
	$mount_opt .= ",loop";
    }
    else {
	showerror("[$ml] Unknown device: $DEVICE");
	return(-3);
    }

    if ($VERBOSE) {
	showinfo("[$ml] Mounting \"$DEVICE\" -> \"$MOUNTPOINT\"");
    }

    system("mount -t ext2 -o $mount_opt $DEVICE $MOUNTPOINT > /dev/null 2>&1");
    if ($? != 0) {
	showerror("[$ml] Could not mount $device_type: $DEVICE on mountpoint: $MOUNTPOINT");
	return(-3);
    }

    return(0);
}


#
# Mount either the physical rev device, or possibly, a "loopback image file"
# Returnval of 0 means "yes, mount succeeded"
# Returnval non-zero means "no, the mount failed."
#
sub mount_device
{
	my @options = @_;
	my $mounted = 0;
	my $tapeid = "";
	my $writemode = "ro";
	my $returnval = 0;


	# If we are a tape drive, then, just rewind the tape.
	if("$DEVICE" =~ /st[01]/) {
		if (!device_is_mounted()) {
			logerror("A Tape is not in the drive.");
			return(-2);
		}
		system("mt -f $DEVICE rewind");
	}

	# Is our mountpoint present?
	if(! -d "$MOUNTPOINT") {
		showinfo("Making mountpoint $MOUNTPOINT");
		mkdir("$MOUNTPOINT");
	}
	if(! -d "$MOUNTPOINT") {
		logerror("Could not find nor make mountpoint $MOUNTPOINT");
		return(-3);
	}
	if(! -d "$MOUNTPOINT-server") {
		showinfo("Making mountpoint $MOUNTPOINT-server");
		mkdir("$MOUNTPOINT-server");
	}
	if(! -d "$MOUNTPOINT-server") {
		logerror("Could not find nor make mountpoint $MOUNTPOINT-server");
		return(-4);
	}



	if (device_is_mounted()) {
		unmount_device();
	}

	# By default, mount read-only, but, if this sub is called with a parameter of "rw",
	# then, read-write.
	$writemode = "ro";
	foreach(@options) {
		if(/^rw$/) {
			$writemode = "rw";
		}
		if(/^ro$/) {
			$writemode = "ro";
		}
	}

	
	if (-b "$DEVICE") {
		# Mount a "real" device, typically the passport drive or Iomega Rev device.

		$tapeid = get_tape_id();

		# Run a quick filesystem check... if there is a problem, run it again and
		# save the output in the log file.
		system("/sbin/e2fsck -p $DEVICE 1> /dev/null 2>&1");
		if ($? != 0) {
			# get details for log file
			open(FSCK, "/sbin/e2fsck -p $DEVICE 2>&1 |");
			while(<FSCK>) {
				loginfo("$_");
			}
			close(FSCK);

			logerror("File system check of device $DEVICE returns non-zero status");
			print("Review log file for details\n");
			return(-5);
		}

		showinfo("Mounting \"$DEVICE\" -> \"$MOUNTPOINT\" (Tape ID: \"$tapeid\") ...");

		# Block device (eg '/dev/scd0'
		system("mount -t ext2 -o $writemode $DEVICE $MOUNTPOINT 1> /dev/null 2>&1");
		if ($? != 0) {
			# get details for log file
			open(MOUNT, "mount -t ext2 -o $writemode $DEVICE $MOUNTPOINT 2>&1 |");
			while(<MOUNT>) {
				loginfo("$_");
			}
			close(MOUNT);
			logerror("Mount of device $DEVICE returns non-zero status");
			print("Review log file for details\n");
			return(-6);
		} else {
			system("umount $MOUNTPOINT 2>&1");
			open(MOUNT, "mount -t ext2 -o $writemode $DEVICE $MOUNTPOINT 2>&1 |");
		} 

	} elsif ($DEVICE =~ /[[:alnum:]]+:\//) {
		# Mount an image file being hosted on a remote samba server.
		my @array = ();
		my $hostname = "";
		my $fullpath = "";
		my $dir = "";
		my $file = "";

		@array = split(/:/);
		$hostname = $array[0];
		$fullpath = $array[1];
		$dir = dirname($fullpath);
		$file = basename($fullpath);


		# Network device my.ip.addr:/path/to/file.img
		# First, mount the samba share which contains our image file. Then, mount the image file.
		showinfo("Mounting Backup Server on CIFS host \"$hostname\" directory \"$dir\" ...");
		open(MOUNT, "mount -t cifs -o $writemode cifs://$hostname/$dir $MOUNTPOINT-server 2>&1 |");
		while(<MOUNT>) {
			loginfo("$_");
		}
		close(MOUNT);

		if($? == 0) {
			$tapeid = get_tape_id();
			showinfo("Mounting \"$DEVICE\" -> \"$MOUNTPOINT\" (Tape ID: \"$tapeid\") ...");
			open(MOUNT, "mount -t ext2 -o $writemode,loop  $MOUNTPOINT-server/$file $MOUNTPOINT 2>&1 |");
		} else {
			showinfo("Error mounting Backup Server. \"$!\"");
		}

	} else {
		# Mount a local "loopback image" file.

		# FIXME: check that the path to the device file exists.
		$tapeid = get_tape_id();
		showinfo("Mounting \"$DEVICE\" -> \"$MOUNTPOINT\" (UUID: \"$tapeid\")");

		# Run a quick filesystem check.
		#open(FSCK, "/sbin/e2fsck -p $DEVICE 2>&1 |");
		#while(<FSCK>) {
		#	loginfo("$_");
		#}
		#close(FSCK);

		open(MOUNT, "mount -t ext2 -o $writemode,loop $DEVICE $MOUNTPOINT 2>&1 |");
	}
	while(<MOUNT>) {
		loginfo("$_");
	}
	close(MOUNT);


	return($returnval);
}


sub unmount_device_simple
{
    my ($ml) = @_;

    my $returnval = -1;

    if ($DEVICE ne "") {
	if (device_is_mounted()) {
	    if ($VERBOSE) {
		showinfo("[$ml] Un-mounting \"$DEVICE\"");
	    }

	    system("umount $DEVICE");
	    $returnval = $?;

	}
    }

    return($returnval);
}


sub unmount_device
{
    my $returnval = -1;

    if ($DEVICE ne "") {
	if (device_is_mounted()) {
	    if ($VERBOSE) {
		my $uuid = get_tape_id();
		showinfo("Un-mounting \"$DEVICE\" (UUID: \"$uuid\") ...");
	    }

	    open(UMOUNT, "umount $DEVICE 2>&1 |");
	    while (<UMOUNT>) {
		loginfo("$_");
	    }
	    close(UMOUNT);

	    system("mount | grep '/mnt/backup-server' > /dev/null 2> /dev/null");
	    if ($? == 0) {
		showinfo("Un-mounting Remote Backup Server...");
		open(UMOUNT, "umount /mnt/backup-server 2>&1 |");
		while (<UMOUNT>) {
		    loginfo("$_");
		}
		close(UMOUNT);
	    }
	}
	$returnval = $?;
    }

    return($returnval);
}


#
# Create a .bak of files or directories in "@tobackup".
# Also creates a .md5 file which we can use to md5 verify the tar's
# integrity.
#
sub create_tarfile
{
	my @tobackup = @{$_[0]};
	my @excludes = @{$_[1]};
	my $tarpath  = $_[2];
	my $tapeid = $_[3];
	my $bytecount = 0;
	my $buffer = "";
	my $pid_mdfind = -1;
	my $md5 = "";
	my $tarlabel = "";
	my $returnval = 0;
	my $i = 0;
	my $kid = -1;

	loginfo("##### BEGIN writing encrypted tar archive: \"$tarpath\" #####");

	if( (! $tarpath) 
	||  ("$tarpath" eq "") ){
		logerror("Destination tar filename not specified.");
		return(-1);
	}
	if($#tobackup < 0) {
		logerror("Nothing specified to backup.");
		return(-2);
	}


	#
	# In the background, kick off a process which takes md5sums of all files which we are about to tar up.
	# This background process will create a file called (for example) "whatever.bak.allmd5"
	# This resultant file is then used by this scripts's --checkfile functionality.
	#
	if($DEVICE ne "") {
		$pid_mdfind = fork();
		unlink("$tarpath.allmd5");
		if($pid_mdfind == 0) {
			logdebug("***** BEGIN Creating $tarpath.allmd5 (Background PID $$)");
			system("find @tobackup -type f -exec md5sum \\{\\} \\; >> $tarpath.allmd5 2>> $LOGFILE");
			$returnval = $?;
			logdebug("***** END Creating $tarpath.allmd5 (Background PID $$)(returnval=$?)");
			exit($returnval);
		}


		# Collect some information about this backup file.	
		open(INFO, "> $tarpath.info");
		print(INFO "#\n");
		print(INFO "# Backup Started: " . localtime(time()) . "\n");
		print(INFO "# Backup Version: $CVS_REVISION\n");
		print(INFO "# Backup PID: $$\n");

		# What is our hostname?
		my $hostname = get_hostname();
		print(INFO "# Hostname: $hostname\n");

		# Which version of redhat does this file come from?
		open(RHVER, "< /etc/redhat-release");
		while(<RHVER>) {
			print(INFO "# RHVersion: ");
			print(INFO);
		}
		close(RHVER);


		# 32 or 64 bit linux?
		print(INFO "# Architecture: $CURRENT_ARCH\n");


		# Which Linux Kernel?
		open(ARCH, "uname -r |");
		while(<ARCH>) {
			print(INFO "# Kernel: ");
			print(INFO);
		}
		close(ARCH);

		# are the backups compressed?
		printf(INFO "# Compressed archives: %s\n", ($COMPRESS_BU) ? "yes" : "no");

		# What are we backing up?
		foreach(@tobackup) {
			loginfo("ToBackup: $_");
			print(INFO "# ToBackup: $_\n");
		}

		# What are we excluding?
		foreach(@excludes) {
			loginfo("Exclude: $_");
			print(INFO "# Excludes: $_\n");
		}
	}


	# These are things we do not want in our tarfile.
	open(EXCLUDE, "> /tmp/exclude.$$");
	foreach(@excludes) {
		print(EXCLUDE "$_\n");
	}
	close(EXCLUDE);


	# In the case of magnetic tapes, or streaming to the network, we do not have a "filesystem" per-se.
	# Thus, put our "tape id" within the actual tar header itself.
	if(defined $tapeid) {
		$tarlabel = " '--label=$tapeid'";
	}



	# Note that we pipe our backup image to an internal (memory) MD5sum prior to sending the data
	# to disk. That way, if the disk is corrupted, our MD5 is "pure", as, the MD5 is created entirely in-memory.
	# This should safeguard us against any disk corruptions.
	$md5 = Digest::MD5->new;

	#
	# Form the tar command - the compression step is optional and can be specified
	# with a command line option.
	#
	my $tar_cmd = "nice tar --exclude-from=/tmp/exclude.$$ -cvf - @tobackup";
	my $compress_cmd = "nice bzip2 --quiet";
	my $encrypt_cmd = "nice openssl aes-128-cbc -e -salt -k \"$CRYPTKEY\"";

	if ($COMPRESS_BU) {
		$tar_cmd .= " | " . $compress_cmd;
	}
	$tar_cmd .= " | " . $encrypt_cmd;

	open(TAR, "$tar_cmd 2>> $LOGFILE |");

	open(FILE, "> $tarpath");
	while(1) {
		$bytecount = sysread(TAR, $buffer, 10 * 1024 * 1024);
		last if($bytecount == 0);
		if($bytecount < 0) {
			logerror("Error \"$!\" reading tar (input) process.");
			$returnval = -5;
			last;
		}
		if(! syswrite(FILE, $buffer, $bytecount)) {
			logerror("Error \"$!\" writing backup to media.");
			$returnval = -6;
			last;
		}
		$md5->add($buffer);
	}
	close(FILE);
	close(TAR);
	unlink("/tmp/exclude.$$");

	print(INFO "# Backup Finished: " . localtime(time()) . "\n");

	logdebug("##### END writing encrypted tar archive: $tarpath #####");
	loginfo("##### END writing encrypted tar archive: $tarpath #####");


	# Write error status.
	if("$DEVICE" ne "") {
		if($returnval == 0) {
			print(INFO "# Backup Status: SUCCESSFUL Backup.\n");
		} elsif($returnval == -6) {
			print(INFO "# Backup Status: FAILED. Error encountered while writing backup to media.\n");
			logerror("Error encountered while writing backup to media.");
		} else {
			print(INFO "# Backup Status: FAILED. Error encountered during Backup!!!\n");
			logerror("Error encountered during Backup!!!");
		}
		print(INFO "#\n");


		# Write the MD5sum of our backup image to disk.
		if($returnval != 0) {
			printf(INFO "%s  %s\n", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", $tarpath);
		} else {
			printf(INFO "%s  %s\n", $md5->hexdigest, $tarpath);
		}
		
		close(INFO);


		# Set permissions for the files on the backup media.
		my @files_on_media = (
		    "$tarpath",
		    "$tarpath.info",
		    "$tarpath.allmd5"
		);

		foreach (@files_on_media) {
		    if (-e $_) {
			system("chown root:root $_");
			system("chmod 444 $_");
			loginfo("File modes set for: $_");
		    }
		    else {
			loginfo("File modes NOT set because file does not exist: $_");
		    }
		}

		system("sync 2>> $LOGFILE");
	}


	# Wait for our "md5sum" process to finish if it is not already.
	if($pid_mdfind > 0) {
		for($i = 0; $i < $MAX_WAIT_TIME; $i++) {
			$kid =  waitpid($pid_mdfind, WNOHANG);
			last if($kid != 0);
			sleep(1);
		}
		if($kid < 0) {
			logerror("waitpid() on PID $pid_mdfind returned $kid. This is odd.");
		}
		if($i >= $MAX_WAIT_TIME) {
			logerror("We waited long enough ($MAX_WAIT_TIME) for our background 'find' to finish. Killing PID $pid_mdfind");
			system("kill -TERM $pid_mdfind 2>> $LOGFILE");
		}
	}

	return($returnval);
}



#
# Restore all files from "tarfile" back onto disk.
#
sub restore_tarfile
{
	my $tarfile = $_[0];
	my @excludes = @{$_[1]};
	
	showinfo("===== BEGIN Restore $tarfile =====");
	showinfo("Using Rootdir=\"$ROOTDIR\"");

	if(! $tarfile || ("$tarfile" eq "")) {
		return(-1);
	}
	if(! -f "$tarfile") {
		logerror("File not found: \"$tarfile\"");
		return(-2);
	}

	# These are the files which we do *not* want to restore.
	loginfo("--- Will NOT restore the following files due to exclusion list. ---");
	open(FILE, "> /tmp/restore-excludes.$$");
	foreach(@excludes) {
		print(FILE "$_\n");
		loginfo("$_");
	}
	close(FILE);
	loginfo("--- End Restore Exclusion List  ---");


	# If we don't use "--keep-old-files", then, overwrite files, otherwise, 
	# use "tar --keep-old-files" which should NOT overwrite.
	my $keepfiles = "";
	if ($KEEP_OLD_FILES) {
		$keepfiles = "--keep-old-files";
	}

	#
	# Form the untar command - the decompression step is optional and can be specified
	# with a command line option, but it's needed when trying to restore an old backup
	# file with the newer backup program... the compression of backup files as standard
	# was removed in version 1.192 of rtibackup.pl.
	#
	my $untar_cmd = "cat $tarfile";
	my $decrypt_cmd = "openssl aes-128-cbc -d -salt -k \"$CRYPTKEY\"";
	my $decompress_cmd = "bzip2 -dc";
	my $tar_cmd = "tar -C $ROOTDIR --exclude-from /tmp/restore-excludes.$$ $keepfiles -xvf -";

	$untar_cmd .= " | " . $decrypt_cmd;
	if ($DECOMPRESS_BU) {
		$untar_cmd .= " | " . $decompress_cmd;
	}
	$untar_cmd .= " | " . $tar_cmd;

	if ($DRY_RUN) {
		print "$untar_cmd\n";
	}

	else {
		open(PIPE, "$untar_cmd 2>&1 |");

		while(<PIPE>) {
			chomp;
			loginfo("$_");
			if($VERBOSE != 0) {
				print($_);
			}
		}
		close(PIPE);
	}

	unlink("/tmp/restore-excludes.$$");

	showinfo("===== END Restore $tarfile =====");

	return(0);
}


#
# Verifies md5sum of files on disk with md5sums taken during backup.
# If used with no commandline parameters, then, check all known files.
# If specified with commandline (file/dir) names, then, check only those.
#
sub checkfile
{
	my @searchlist = @_;
	my @md5files = ();
	my $thisfile = "";
	my $thisline = "";
	my $tofind = "";


	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}

	showinfo("===== Begin Checkfile =====");
	loginfo("Searchlist: @searchlist");

	# Just check everything we know about.
	if($#searchlist < 0) {
		loginfo("Checking All Known Files.");
		open(PIPE, "find $MOUNTPOINT -type f -iname *.allmd5 -exec md5sum --check \\{\\} \\; |");
		while(<PIPE>) {
			showinfo("$_");
		}
		close(PIPE);
		return($?);
	}

	# Our list of MD5sums which we are interested in.
	# This is a subset of content from all of our ".allmd5" files, that subset is
	# dictated by what we specified on the commandline.
	open(GREPLIST, "> /tmp/greplist.$$");


	# Step through each of our ".allmd5" files on the backup media.
	open(MD5LIST, "find $MOUNTPOINT -type f -iname *.allmd5 -print |");
	while(<MD5LIST>) {
		chomp;
		$thisfile = $_;

		# Open this .allmd5 file and start "grepping" for each item we had on the commandline.
		open(FILE, "< $thisfile");
		while(<FILE>) {
			chomp;
			$thisline = $_;
			foreach $tofind (@searchlist) {
				if($thisline =~ /$tofind/) {
					print(GREPLIST "$thisline\n");
				}
			}
		}
		close(FILE);
	}
	close(MD5LIST);
	close(GREPLIST);


	# Now, our "greplist.$$" file contains only files which we are interested in checking.
	open(PIPE, "md5sum --check /tmp/greplist.$$ 2>&1 |");
	while(<PIPE>) {
		chomp;
		showinfo("$_");
	}
	close(PIPE);
	unlink("/tmp/greplist.$$");


	unmount_device();
	showinfo("===== End Checkfile =====");
	return($?);
}




sub list_tarfile
{
	my $tarfile = $_[0];


	# Mount the fs image using loopback device.
	if(! -f "$tarfile") {
		logerror("File not found: \"$tarfile\".");
		return(-1);
	}

	showinfo("----- BEGIN $tarfile Contents -----");

	#
	# Form the untar command - the decompression step is optional and can be specified
	# with a command line option, but it's needed when trying to read an old backup
	# file with the newer backup program... the compression of backup files as standard
	# was removed in version 1.192 of rtibackup.pl.
	#
	my $untar_cmd = "cat $tarfile";
	my $decrypt_cmd = "nice openssl aes-128-cbc -d -salt -k \"$CRYPTKEY\"";
	my $decompress_cmd = "nice bzip2 -dc";
	my $tar_cmd = "nice tar -tvf -";

	$untar_cmd .= " | " . $decrypt_cmd;
	if ($DECOMPRESS_BU) {
		$untar_cmd .= " | " . $decompress_cmd;
	}
	$untar_cmd .= " | " . $tar_cmd;

	open(PIPE, "$untar_cmd 2>&1 |");

	while(<PIPE>) {
		chomp;
		showinfo("$_");
	}
	close(PIPE);

	showinfo("----- END $tarfile Contents -----");
	return($?);
}



sub get_tape_id
{
	my $tapeid  = "";

	# UUIDs are 16 bytes numbers encoded in a string as 2 hex digits
	# per byte, with 4 hyphens in the string, in the form 8-4-4-4-12,
	# thus 36 chars long.
	my $UUID_LENGTH_MAGIC = 36;

	if("$DEVICE" eq "") {
		return("");
	}

	# Get a unique identifier for this particular tape. This uuid changes whenever the tape is formatted.
	# This works on RHWS5 with both block devices and "file" images.
	if (-f "/sbin/dumpe2fs") {
		open(PIPE, "/sbin/dumpe2fs -h $DEVICE 2>&1 |");
		while(<PIPE>) {
			if(/(Filesystem UUID:)(\s+)(\S+)/) {
				$tapeid = $3;
			}
		}
		close(PIPE);
	}

	if (length($tapeid) == $UUID_LENGTH_MAGIC) {
		return($tapeid);
	}

	# Try another way to get the UUID.
	if (-f "/sbin/blkid") {
		open(PIPE, "/sbin/blkid -c /dev/null $DEVICE |");
		while(<PIPE>) {
			if(/(UUID=)(\")([^\"]+)(\")/) {
				$tapeid = $3;
			}
		}
		close(PIPE);
	}

	unless (length($tapeid) == $UUID_LENGTH_MAGIC) {
		$tapeid  = "";
	}

	return($tapeid);
}


#
# Verify any and all items backed up.
#
sub verify_backup
{
	my @array = ();
	my @errors = ();
	my $error_found = 0;
	my $status = "";
	my $thisfile = "";
	my $errors_present = 0;
	my $tapeid = "";
	my $backuptime = "";

	$VERIFY_FAILED = 0;

	showinfo("---- BEGIN Verify ----");
	$status .= "############################################################\n";
	if($VERBOSE != 0) {
		$status .= "######## Backup DEVICE: $DEVICE\n";
		$status .= "######## DVD Device: $DVDDEV\n";
	}

	mount_device("ro");
	if (!device_is_mounted()) {
		$status .= "!!!!!!!! $DEVICE cannot be mounted. (Is cartridge ejected?)\n";
		$status .= "!!!!!!!! BACKUP DEVICE NOT FOUND\n";
	} else {
		$status .= "######## TAPEID: " . get_tape_id() . "\n";
	}


	# How much space is left?
	if (device_is_mounted()) {
		open(PIPE, "df -m $MOUNTPOINT |");
		while(<PIPE>) {
			next until(/$MOUNTPOINT/);
			@array = split(/\s+/);
			my $df_size = sprintf("%.2f", $array[1] / 1024);
			my $df_avail = sprintf("%.2f", $array[3] / 1024);
			if ( int($array[3]) == 0 ) {
				$status .= "!!!!!!!! NO SPACE LEFT on Backup Device\n";
			} elsif ( int($array[3]) < 1000 ) {
				$status .= "wwwwwwww LOW SPACE AVAILABLE ($df_avail GB) on Backup Device\n";
			} else {
				$status .= "######## SPACE REMAINING: $df_avail / $df_size GB \n";
			}

		}
		close(PIPE);
	}
	$status .= "######## NOW: " . localtime(time()) . "\n";


	# Summary information about a particular backup.
	if (device_is_mounted()) {

		my @backup_files = qw(
			configs/altiris.bak
			configs/printconfigs.bak
			configs/rticonfigs.bak
			configs/dsyconfigs.bak
			configs/osconfigs.bak
			configs/userconfigs.bak
			usr2.bak
			daisy.bak
			userfiles.bak
			logfiles.bak
		);

		foreach $thisfile (@backup_files) {

			# Don't print error messages about backup files which we may not care about.
			if ( ("$thisfile" eq "usr2.bak") || ("$thisfile" eq "configs/rticonfigs.bak") ) {
				if( ($RTI == 0) && ($DAISY == 1) ) {
					# Don't report to daisy users errors related to "usr2 not found!",
					# since that info is not pertinant to daisy users.
					loginfo("POS type 'daisy', thus backup files for 'usr2' ignored during verify. Use '--rti' to change this behavior.");
					next;
				} else {
					if(! -f "$MOUNTPOINT/$thisfile") {
						$status .= "!!!!!!!! ERROR: Backup File not Found: $MOUNTPOINT/$thisfile\n";
						$VERIFY_FAILED = 1;
					}

					if(! -f "$MOUNTPOINT/$thisfile.info") {
						$status .= "!!!!!!!! ERROR: Checksum File not Found: $MOUNTPOINT/$thisfile.info\n";
						$VERIFY_FAILED = 1;
						next;
					}
				}


			} elsif ( ($thisfile eq "daisy.bak") || ($thisfile eq "configs/dsyconfigs.bak") ) {
				if( ($RTI == 1) && ($DAISY == 0) ) {
					# Don't report to RTI users errors related to "daisy not found!",
					# since that info is not pertinant to RTI users.
					loginfo("POS type 'rti', thus backup files for 'daisy' ignored during verify. Use '--daisy' to change this behavior.");
					next;
				} else {
					if(! -f "$MOUNTPOINT/$thisfile") {
						$status .= "!!!!!!!! ERROR: Backup File not Found: $MOUNTPOINT/$thisfile\n";
						$VERIFY_FAILED = 1;
					}

					if(! -f "$MOUNTPOINT/$thisfile.info") {
						$status .= "!!!!!!!! ERROR: Checksum File not Found: $MOUNTPOINT/$thisfile.info\n";
						$VERIFY_FAILED = 1;
						next;
					}
				}



			# If we don't see both the backup and checksum files, assume that is intentional,
			# just give a warning.
			# If we see one file, but not the other, there is a problem.
			} else {

				if( (! -f "$MOUNTPOINT/$thisfile")
				&&  (! -f "$MOUNTPOINT/$thisfile.info")) {
					loginfo("Backup and Checksum files not found: \"$MOUNTPOINT/$thisfile\".");
					if("$thisfile" ne "userfiles.bak") {
						$status .= "wwwwwwww WARN: Backup and Checksum Files not Found: $MOUNTPOINT/$thisfile\n";
					}
					next;
				} else {
					if(! -f "$MOUNTPOINT/$thisfile") {
						$status .= "!!!!!!!! ERROR: Backup File not Found: $MOUNTPOINT/$thisfile\n";
						$VERIFY_FAILED = 1;
					}
					if(! -f "$MOUNTPOINT/$thisfile.info") {
						$status .= "!!!!!!!! ERROR: Checksum File not Found: $MOUNTPOINT/$thisfile.info\n";
						$VERIFY_FAILED = 1;
						next;
					}
				}
			}


			# How did the backup process go?
			showinfo("Getting backup info for $thisfile");
			open(FILE, "< $MOUNTPOINT/$thisfile.info");
			while(<FILE>) {
				if($VERBOSE != 0) {
					$status .= "#######$_\n";
				}
				if(/Backup Status:/) {
					if(! /(Backup Status:)(\s+)(SUCCESS)/) {
						$status .= "!!!!!!!! ERROR occurred during Backup of $thisfile\n";
						$VERIFY_FAILED = 1;
					}
				}

				if(/(Backup Finished:)(\s+)([[:print:]]+)/){
					chomp;
					$backuptime = $3;
				}
			}
			close(FILE);


			# Verify our backup.
			showinfo("Verifying md5sums for $thisfile");
			system("md5sum --check $MOUNTPOINT/$thisfile.info > /dev/null 2>> $LOGFILE");
			if($? != 0) {
				$status .= "!!!!!!!! VERIFY FAILED: $thisfile ($backuptime)\n";
				$VERIFY_FAILED = 1;
			} else {
				$status .= "######## VERIFY SUCCEEDED: $thisfile ($backuptime)\n";
			}
		}
	}

	unmount_device();


	$status .= "############################################################\n";

	showinfo("---- END Verify ----");

	return($status);
}

# 
# List contents of various tarfiles.
#
sub list_files
{

	loginfo("---- BEGIN List Files ----");

	mount_device("ro");
	if (!device_is_mounted()) {
		logerror("Could not mount device \"$DEVICE\" for file listing purposes.");
		return(-1);
	}

	if ((grep(/^all$/, @LIST)) || (grep(/^usr2$/, @LIST))) {
		if ($RTI) {
			showinfo("List RTI Data...");
			list_tarfile("$MOUNTPOINT/usr2.bak");
		}
	}

	if ((grep(/^all$/, @LIST)) || (grep(/^rticonfigs$/, @LIST))) {
		if ($RTI) {
			showinfo("List RTI Configs ...");
			list_tarfile("$MOUNTPOINT/configs/rticonfigs.bak");
		}
	}

	if ((grep(/^all$/, @LIST)) || (grep(/^daisy$/, @LIST))) {
		if ($DAISY) {
			showinfo("List Daisy Data...");
			list_tarfile("$MOUNTPOINT/daisy.bak");
		}
	}

	if ((grep(/^all$/, @LIST)) || (grep(/^daisyconfigs$/, @LIST))) {
		if ($DAISY) {
			showinfo("List Daisy Configs ...");
			list_tarfile("$MOUNTPOINT/configs/dsyconfigs.bak");
		}
	}

	if((grep(/^all$/, @LIST))
	|| (grep(/^userconfigs$/, @LIST))) {
		showinfo("List User Configs ...");
		list_tarfile("$MOUNTPOINT/configs/userconfigs.bak");
	}

	if((grep(/^all$/, @LIST))
	|| (grep(/^altiris$/, @LIST))) {
		showinfo("List Altiris Data ...");
		list_tarfile("$MOUNTPOINT/configs/altiris.bak");
	}

	if((grep(/^all$/, @LIST))
	|| (grep(/^userfiles$/, @LIST))) {
		showinfo("List User Specified Data ...");
		list_tarfile("$MOUNTPOINT/userfiles.bak");
	}

	if((grep(/^all$/, @LIST))
	|| (grep(/^netconfigs$/, @LIST))) {
		showinfo("List Network Configs...");
		list_tarfile("$MOUNTPOINT/configs/netconfigs.bak");
	}

	if((grep(/^all$/, @LIST))
	|| (grep(/^printconfigs$/, @LIST))) {
		showinfo("List Printer Configs...");
		list_tarfile("$MOUNTPOINT/configs/printconfigs.bak");
	}

	if((grep(/^all$/, @LIST))
	|| (grep(/^logfiles$/, @LIST))) {
		showinfo("List Log Files ...");
		list_tarfile("$MOUNTPOINT/logfiles.bak");
	}

	if((grep(/^all$/, @LIST))
	|| (grep(/^osconfigs$/, @LIST))) {
		showinfo("List OS Configs...");
		list_tarfile("$MOUNTPOINT/configs/osconfigs.bak");
	}



	# List contents of DVD.
	if($DVD != 0) {
		showinfo("List DVD Files ...");
		list_tarfile("$DVDDEV");
	}


	unmount_device();
	loginfo("---- END List Files ----");
	return($?);
}


#
# Restore some or all files based on what "group" we specified on the commandline.
# Also restore individual files if so specified on the commandline.
#
sub restore_files
{
	my @restore_excludes = @{$_[0]};

	my $returnval = 0;
	my $answer = "";
	my $tapeid = "";

	# First make sure the crypt key is valid
	if (validate_crypt_key($CRYPTKEY) != 0) {
	    return(-1);
	}

	loginfo("---- BEGIN Restore Files ----");

	mount_device("ro");
	if (!device_is_mounted()) {
		logerror("Could not mount \"$DEVICE\". Cannot proceed with restore.");
		return(-1);
	}

	# Make sure the user has somehow acknowledged that they really want to restore.
	$tapeid = get_tape_id();
	if($FORCE != 0) {
		loginfo("--force specified. Will force restore \"@RESTORE\" to rootdir=\"$ROOTDIR\" (Tape ID:$tapeid)");
	} else {
		$answer = "";
		while(1) {
			print("Tape ID: \"$tapeid\"\n");
			print("Restore \"@RESTORE\" to rootdir=\"$ROOTDIR\"?");
			print("(Y/N) >");
			$answer = <STDIN>;
			chomp($answer);
			last if("$answer" =~ /^n/i);
			last if("$answer" =~ /^y/i);
		}

		# anything but 'yes'.
		if($answer !~ /^y/i) {
			loginfo("Restore cancelled by user.");
			return(0);
		}
		loginfo("User accepted restore of \"@RESTORE\" to rootdir=\"$ROOTDIR\" (Tape ID: $tapeid)");
	}


	if((grep(/^all$/, @RESTORE))
	|| (grep(/^daisy$/, @RESTORE))) {
		if($DAISY == 0) {
			loginfo("--daisy not specified. Will not restore daisy files.");
		} else {
			$returnval += restore_daisy(\@restore_excludes);
		}
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^usr2$/, @RESTORE))) {
		if($RTI == 0) {
			loginfo("--rti not specified. Will not restore RTI files.");
		} else {
			$returnval += restore_usr2(\@restore_excludes);
		}
	}

	# Restore only RTI "bbxd"
	# A sort of "pseudo-special" restore.
	if(grep(/^bbxd$/, @RESTORE)) {
		if($RTI == 0) {
			loginfo("--rti not specified. Will not restore bbxd files.");
		} else {
			system("cat $MOUNTPOINT/usr2.bak | openssl aes-128-cbc -d -salt -k \"$CRYPTKEY\" | tar -C $ROOTDIR -xvf - usr2/bbx/bbxd 2>> $LOGFILE");
			$returnval += $?;
		}
	}

	# Restore only RTI "bbxps" (Customs)
	# A sort of "pseudo-special" restore.
	if(grep(/^bbxps$/, @RESTORE)) {
		if($RTI == 0) {
			loginfo("--rti not specified. Will not restore bbxps files.");
		} else {
			system("cat $MOUNTPOINT/usr2.bak | openssl aes-128-cbc -d -salt -k \"$CRYPTKEY\" | tar -C $ROOTDIR -xvf - usr2/bbx/bbxps 2>> $LOGFILE");
			$returnval += $?;
		}
	}


	if((grep(/^all$/, @RESTORE))
	|| (grep(/^rticonfigs$/, @RESTORE))) {
		if($RTI == 0) {
			loginfo("--rti not specified. Will not restore rticonfig files.");
		} else {
			$returnval += restore_rticonfigs(\@restore_excludes);
		}
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^daisyconfigs$/, @RESTORE))) {
		if($DAISY == 0) {
			loginfo("--daisy not specified. Will not restore daisyconfig files.");
		} else {
			$returnval += restore_dsyconfigs(\@restore_excludes);
		}
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^userconfigs$/, @RESTORE))) {
		$returnval += restore_userconfigs(\@restore_excludes);
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^altiris$/, @RESTORE))) {
		$returnval += restore_altiris(\@restore_excludes);
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^userfiles$/, @RESTORE))) {
		$returnval += restore_userfiles(\@restore_excludes);
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^logfiles$/, @RESTORE))) {
		$returnval += restore_logfiles(\@restore_excludes);
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^printconfigs$/, @RESTORE))) {
		$returnval += restore_printconfigs(\@restore_excludes);
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^netconfigs$/, @RESTORE))) {
		$returnval += restore_netconfigs(\@restore_excludes);
	}

	if((grep(/^all$/, @RESTORE))
	|| (grep(/^osconfigs$/, @RESTORE))) {
		$returnval += restore_osconfigs(\@restore_excludes);
	}


	# If user specified "--restore /usr2/bbx/blah /etc/sysconfig/blah" or
	# "--restore=singlefiles" /usr2/bbx/blah /etc/sysconfig/blah then,
	# this subroutine restores those specific files.
	$returnval += restore_singlefiles();

	unmount_device();

	loginfo("---- END Restore Files ----");
	return($returnval);
}


sub restore_usr2
{
	my @restore_excludes = @{$_[0]};

	my $returnval = -1;

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}

	if($RTI == 0) {
		logerror("--rti not specified. Will not restore RTI data.");
		return(0);
	}


	showinfo("Restore RTI Data...");

	#
	# Only stop RTI if restoring on top of /usr2...  which
	# is true only if restoring to the top of the file system - the
	# default value for the restore root directory.
	#
	my $clobber_rti = 0;
	if ($ROOTDIR eq '/') {
		$clobber_rti = 1;
	}

	#
	# If /usr2 is being restored, then two things have to happen:
	# 1) RTI must be stopped
	# 2) the log file location must be moved
	#
	my $old_logfile_path = "";

	if ($clobber_rti) {

	    # stop RTI
	    if (-f "$RTIDIR/bin/killem") {
		system("$RTIDIR/bin/killem 2>> $LOGFILE");
	    }
	    if (-f "/etc/rc.d/init.d/httpd") {
		system("/sbin/service httpd stop 2>> $LOGFILE");
	    }
	    if (-f "/etc/rc.d/init.d/bbj") {
		system("/sbin/service bbj stop 2>> $LOGFILE");
	    }
	    if (-f "/etc/rc.d/init.d/rti") {
		system("/sbin/service rti stop 2>> $LOGFILE");
	    }

	    # move logfile location
	    $old_logfile_path = log_change_location();
	}

	# Should we *not* restore some files?
	if(-d "/usr2/basis") {
		push(@restore_excludes, "usr2/basis");
	}


	# /usr2
	$returnval = restore_tarfile("$MOUNTPOINT/usr2.bak", \@restore_excludes );

	#
	# Make correct symlink for tcc based on which OS we are on.
	# Assumption is that the payment processor is Elavon.
	#
	determine_os();

	loginfo("Making TCC links for: $OS");
	if ($OS eq "RHEL4") {
	    if (-e "/usr2/bbx/bin/tcc2_linux") {
		system("ln -sf /usr2/bbx/bin/tcc2_linux /usr2/bbx/bin/tcc");
		system("ln -sf /usr2/bbx/bin/tcc_linux /usr2/bbx/bin/tcc_tws");
	    }
	    else {
		system("ln -sf /usr2/bbx/bin/tcc_linux /usr2/bbx/bin/tcc");
	    }
	}

	elsif ($OS eq "RHEL5") {
	    if (-e "/usr2/bbx/bin/tcc2_x64") {
		system("ln -sf /usr2/bbx/bin/tcc2_x64 /usr2/bbx/bin/tcc");
		system("ln -sf /usr2/bbx/bin/tcc_x64 /usr2/bbx/bin/tcc_tws");
	    }
	    else {
		system("ln -sf /usr2/bbx/bin/tcc_x64 /usr2/bbx/bin/tcc");
	    }
	}

	elsif ($OS eq "RHEL6") {
	    system("ln -sf /usr2/bbx/bin/tcc2_rhel6 /usr2/bbx/bin/tcc");
	    system("ln -sf /usr2/bbx/bin/tcc_rhel6 /usr2/bbx/bin/tcc_tws");
	}

	else {
	    logerror("Can't make TCC links: unsupported platform: $OS");
	}

	if ($clobber_rti) {

	    $RUN_RTI_PERMS = 1;

	    showinfo("*** Make sure to restart 'bbj', 'rti' and 'httpd' services.");
	}

	loginfo("END Restore RTI Data...");

	if ($old_logfile_path ne "") {
	    log_restore_location($old_logfile_path);
	}

	return($returnval);
}



sub restore_rticonfigs
{
	my @restore_excludes = @{$_[0]};

	my $returnval = -1;


	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}

	if($RTI == 0) {
		logerror("--rti not specified. Will not restore RTI configs.");
		return(0);
	}


	showinfo("Restore RTI Configs...");

	# Bring down RTI.
	if(-f "$RTIDIR/bin/killem") {
		system("$RTIDIR/bin/killem 2>> $LOGFILE");
	}
	if(-f "/etc/rc.d/init.d/rti") {
		system("/sbin/service rti stop 2>> $LOGFILE");
	}

	# Restore RTI "configurations".
	$returnval = restore_tarfile("$MOUNTPOINT/configs/rticonfigs.bak", \@restore_excludes);

	# Make sure our system services are re-established.
	if($returnval == 0) {
		system("/sbin/chkconfig --add --level 35 rti 2>> $LOGFILE");
		system("/sbin/chkconfig --add --level 35 blm 2>> $LOGFILE");
		system("/sbin/chkconfig --add --level 35 bbj 2>> $LOGFILE");

		$RUN_HARDEN_LINUX = 1;

		$RUN_RTI_PERMS = 1;
	}

	loginfo("END Restore RTI Configs...");

	return($returnval);
}


sub restore_daisy
{
	my @restore_excludes = @{$_[0]};

	my $returnval = -1;
	my $clobber_daisy = 0;

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}

	if($DAISY == 0) {
		logerror("--daisy not specified. Will not restore Daisy data.");
		return(0);
	}

	#
	# Only bring down Daisy if restoring on top of daisy...  which
	# is true only if restoring to the top of the file system - the
	# default value for the restore root directory.
	#
	if ($ROOTDIR eq '/') {
		$clobber_daisy = 1;
	}

	#
	# if (/d is being restored) then
	#   the log file location must be moved
	#   if (/d/daisy exists) then
	#	daisy must be stopped
	#
	my $old_logfile_path = "";
	if ($clobber_daisy) {
	    # move logfile location
	    $old_logfile_path = log_change_location();

	    # stop daisy if necessary
	    if (-e "/d/daisy") {
		system("/sbin/init 4");
		system("$DAISYDIR/daisy/utils/killemall 2>> $LOGFILE");
	    }
	}

	showinfo("Restore Daisy Data...");

	# Restore "/d".
	$returnval = restore_tarfile("$MOUNTPOINT/daisy.bak", \@restore_excludes);

	# Make sure our system services are re-established.
	if ($returnval == 0 && $clobber_daisy) {

		$RUN_HARDEN_LINUX = 1;

		$RUN_DAISY_PERMS = 1;

	}

	# Bring Daisy back up if necessary.
	if ($clobber_daisy) {
		system("/sbin/init 3");
	}

	loginfo("END Restore Daisy Data...");

	if ($old_logfile_path ne "") {
	    log_restore_location($old_logfile_path);
	}

	return($returnval);
}

sub restore_dsyconfigs
{
	my @restore_excludes = @{$_[0]};

	my $returnval = -1;

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}

	if($DAISY == 0) {
		logerror("--daisy not specified. Will not restore Daisy configs.");
		return(0);
	}


	showinfo("Restore Daisy Config Files...");

	#
	# Only bring down Daisy if restoring on top of daisy...  which
	# is true only if restoring to the top of the file system - the
	# default value for the restore root directory.
	#
	my $clobber_daisy = 0;
	if ($ROOTDIR eq '/') {
		$clobber_daisy = 1;
	}

	if ($clobber_daisy) {
		system("/sbin/init 4");
		system("$DAISYDIR/daisy/utils/killemall 2>> $LOGFILE");
	}

	# Restore Daisy "configurations".
	$returnval = restore_tarfile("$MOUNTPOINT/configs/dsyconfigs.bak", \@restore_excludes);

	# Make sure our system services are re-established.
	if ($returnval == 0 && $clobber_daisy) {

		$RUN_HARDEN_LINUX = 1;

		$RUN_DAISY_PERMS = 1;

	}

	# Bring Daisy back up if necessary.
	if ($clobber_daisy) {
		system("/sbin/init 3");
	}

	loginfo("END Restore Daisy Configs...");

	return($returnval);
}


sub read_usersinfo_file
{
    my $info_file = "$MOUNTPOINT/configs/usersinfo.txt";

    my %usersinfo_tab = ();
    my $line;

    open(UIF, '<', $info_file);
    while ($line = <UIF>) {
	my $i = index($line, ":");
	my $username = substr($line, 0, $i);
	$usersinfo_tab{$username} = $line;
    }
    close(UIF);

    return(%usersinfo_tab);
}


#
# Re-create user accounts related to the POS.
#
sub restore_users_info
{
    my $users_file = "";
    my $users_cmd = "";

    if ( (-f "$RTIDIR/bin/rtiuser.pl") && (-f "$MOUNTPOINT/configs/rtiusers.txt") ) {
	$users_file = "$MOUNTPOINT/configs/rtiusers.txt";
	$users_cmd = "$RTIDIR/bin/rtiuser.pl";
    }

    elsif ( (-f "$DAISYDIR/daisy/bin/dsyuser.pl") && (-f "$MOUNTPOINT/configs/dsyusers.txt") ) {
	$users_file = "$MOUNTPOINT/configs/dsyusers.txt";
	$users_cmd = "$DAISYDIR/daisy/bin/dsyuser.pl";
    }

    if ($users_file) {

	my %users_tab = read_users_file($users_file);

	foreach my $key (keys(%users_tab)) {
	    loginfo("Restoring POS user $key...");
	    system("$users_cmd --add $key 2>> $LOGFILE");
	    if ($users_tab{$key} == 2) {
		loginfo("Restoring POS admin $key...");
		system("$users_cmd --enable-admin $key password 2>> $LOGFILE");
	    }
	}
    }

    my $info_file = "";
    if (-f "$MOUNTPOINT/configs/usersinfo.txt") {
	$info_file = "$MOUNTPOINT/configs/usersinfo.txt";
    }

    if ($info_file) {

	my %usersinfo_tab = read_usersinfo_file();

	my $conf_file = "/etc/shadow";
	my $new_conf_file = "$conf_file.$$";

	unless (-f "$conf_file") {
	    logerror("Can't happen: shadow password file does not exist: $conf_file");
	    return;
	}

	unless (open(OLD, '<', $conf_file)) {
	    logerror("Could not open old shadow password file: $conf_file");
	    return;
	}

	unless (open(NEW, '>', $new_conf_file)) {
	    logerror("Could not open new shadow password file: $new_conf_file");
	    close(OLD);
	    return;
	}

	my $line;
	while ($line = <OLD>) {
	    my $i = index($line, ":");
	    my $username = substr($line, 0, $i);
	    if (defined($usersinfo_tab{$username})) {
		print NEW "$usersinfo_tab{$username}";
	    }
	    else {
		print NEW "$line";
	    }
	}
	close(OLD);
	close(NEW);

	if (-z $new_conf_file) {
	    logerror("New shadow password file is zero length: $new_conf_file");
	    system("rm $new_conf_file");
	    return;
	}
	system("chmod --reference=$conf_file $new_conf_file");
	system("chown --reference=$conf_file $new_conf_file");
	system("mv $new_conf_file $conf_file");

	loginfo("POS users and shadown file info restored.");
    }
}


sub restore_userconfigs
{
	my @restore_excludes = @{$_[0]};

	my $returnval = -1;

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}

	# exclude /etc/passwd, /etc/group, /etc/shadow when upgrading
	# between os releases

	if ($UPGRADE) {

	    my @excludes = qw(
		etc/pam.d
		etc/login.defs
		etc/shadow
		etc/shadow-
		etc/gshadow
		etc/passwd
		etc/passwd-
		etc/group
	    );

	    foreach my $exclude (@excludes) {
		push(@restore_excludes, $exclude);
	    }
	}

	showinfo("BEGIN Restore POS User Configs...");

	# Restore user config Settings
	$returnval = restore_tarfile("$MOUNTPOINT/configs/userconfigs.bak", \@restore_excludes);
	if ($returnval == 0) {
	    loginfo("restarting crond...");
	    system("/sbin/service crond restart 2>> $LOGFILE");

	    if ($UPGRADE) {
		restore_users_info();
	    }
	}


	loginfo("END Restore POS User Configs...");

	return($returnval);
}



sub restore_altiris
{
	my @restore_excludes = @{$_[0]};

	my $returnval = -1;

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}


	showinfo("Restore Altiris ...");

	# Restore Altiris
	$returnval = restore_tarfile("$MOUNTPOINT/configs/altiris.bak", \@restore_excludes);

	if (-f "$RTIDIR/bin/altiriscontrol.pl") {
		system("perl $RTIDIR/bin/altiriscontrol.pl --install 2>> $LOGFILE");
	}

	if (-f "$DAISYDIR/daisy/bin/altiriscontrol.pl") {
		system("perl $DAISYDIR/daisy/bin/altiriscontrol.pl --install 2>> $LOGFILE");
	}

	# Make sure altiris system service restarts at boot time.
	if ($returnval == 0) {
		system("/sbin/chkconfig --add --level 35 altiris 2>> $LOGFILE");
	}

	loginfo("END Restore Altiris ...");

	return($returnval);
}


sub restore_userfiles
{ 
	my @restore_excludes = @{$_[0]};

	my $returnval = -1;

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}


	showinfo("Restore User Specified Files...");

	# Restore User Specified Files
	$returnval = restore_tarfile("$MOUNTPOINT/userfiles.bak", \@restore_excludes);

	loginfo("END Restore User Files ...");

	return($returnval);
}


sub restore_logfiles
{ 
	my @restore_excludes = @{$_[0]};

	#
	# do NOT restore log files if doing an upgrade
	#
	if ($UPGRADE) {
	    loginfo("Log Files NOT restored when --upgrade specified.");
	    return(0);
	}

	my $returnval = -1;

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}


	showinfo("Restore Log Files...");

	# Restore User Specified Files
	$returnval = restore_tarfile("$MOUNTPOINT/logfiles.bak", \@restore_excludes);

	loginfo("END Restore Log Files ...");

	return($returnval);
}


#
# Set the "passdb backend"  parameter to a value of "smbpasswd"
# in the samba config file.  This is a "global" section parameter.
# This must be done for RHEL6 systems to be backwards compatabile
# with the way the pre-RHEL6 systems were configured.
#
sub samba_set_passdb
{
    my $parameter = "passdb backend = smbpasswd";
    my $parameter2 = "smb passwd file = /etc/samba/smbpasswd";

    my $conf_file = "$ALTROOT/etc/samba/smb.conf";
    my $new_conf_file = "$conf_file.$$";

    unless (-f $conf_file) {
	showerror("Samba config file does not exist: $conf_file");
	return($EXIT_SAMBA_CONF);
    }

    # Do nothing if a modified conf file is already in place.
    system("grep '$parameter' $conf_file > /dev/null 2> /dev/null");
    if ($? == 0) {
	showinfo("Samba config file already appears to be modified: $conf_file");
	return($EXIT_OK);
    }

    unless (open(OLD, '<', $conf_file)) {
	showerror("Can't open existing Samba config file: $conf_file");
	return($EXIT_SAMBA_CONF);
    }
    unless (open(NEW, '>', $new_conf_file)) {
	showerror("Can't make new Samba config file: $new_conf_file");
	return($EXIT_SAMBA_CONF);
    }

    #
    # Copy all lines from old to new, but after the global section dec,
    # write the new parameter into the new conf file.
    #
    while (<OLD>) {
	if (/^\s*\[global\]/) {
	    print(NEW $_);
	    print(NEW "#Following lines added by $PROGNAME, $CVS_REVISION, $TIMESTAMP\n");
	    print(NEW "$parameter\n");
	    print(NEW "$parameter2\n");
	    next;
	}
	else {
	    print(NEW);
	}
    }

    close(OLD);
    close(NEW);

    # If we created a new conf file that is zero sized, that is bad.
    if (-z $new_conf_file) {
	showerror("The copy of the Samba $conf_file is a zero size file");
	showerror("Samba config file NOT modified with new paramter: $parameter");
	system("rm $new_conf_file");
	return($EXIT_SAMBA_CONF);
    }

    # Assume conf file was successfully transformed...
    # so replace the old one with the new.
    system("chmod --reference=$conf_file $new_conf_file");
    system("chown --reference=$conf_file $new_conf_file");
    system("cp $new_conf_file $conf_file");

    loginfo("Samba config file modified with new paramter: $parameter");

    return($EXIT_OK);
}


#
# Make the UIDs in the "smbpasswd" file match those in /etc/passwd.
#
sub samba_rebuild_passdb
{
    my $conf_file = "$ALTROOT/etc/samba/smbpasswd";
    if ($OS eq "RHEL6") {
	if (! -e $conf_file) {
	    my $alt_conf_file = "$ALTROOT/var/lib/samba/private/smbpasswd";
	    if (-e $alt_conf_file) {
		showerror("Expecting smbpasswd file to be in /etc/samba");
		return($EXIT_SAMBA_CONF);
	    }
	}
    }
    my $new_conf_file = "$conf_file.$$";

    unless (-f $conf_file) {
	showerror("Samba config file does not exist: $conf_file");
	return($EXIT_SAMBA_CONF);
    }

    unless (open(OLD, '<', $conf_file)) {
	showerror("Can't open existing Samba config file: $conf_file");
	return($EXIT_SAMBA_CONF);
    }
    unless (open(NEW, '>', $new_conf_file)) {
	showerror("Can't make new Samba config file: $new_conf_file");
	return($EXIT_SAMBA_CONF);
    }

    #
    # Copy all lines from old to new, but adjust the UID field if necessary.
    #
    # The structure of an smbpasswd entry is:
    #
    # username:uid:lanman_hash:nt_hash:flags:pw_lct
    #
    # username    - the user's login name.
    # uid         - the user's UID
    # lanman_hash - Windows stuff
    # nt_hash     - Windows stuff
    # flags       - Various single-character flags representing the type and
    #               state of the user's account.
    # pw_lct      - the timestamp of the user's last successful password change
    #
    my $file_modified = 0;
    while (<OLD>) {
	my $line = $_;
	if ($line =~ /^(\S+):(\d+):(.*)$/) {
	    my $username = $1;
	    my $uid = $2;
	    my $remainder = $3;

	    my $system_uid = getpwnam($username);
	    if (defined($system_uid)) {
		if ($uid ne $system_uid) {
		    $line = "$username" . ":" . "$system_uid" . ":" . "$remainder" . "\n";
		    $file_modified = 1;
		}
	    }
	}
	print(NEW $line);
    }

    close(OLD);
    close(NEW);

    if ($file_modified) {
	# If we created a new conf file that is zero sized, that is bad.
	if (-z $new_conf_file) {
	    showerror("The copy of the Samba $conf_file is a zero size file");
	    showerror("Samba $conf_file NOT modified with new UID fields");
	    system("rm $new_conf_file");
	    return($EXIT_SAMBA_CONF);
	}

	# Assume conf file was successfully transformed...
	# so replace the old one with the new.
	system("chmod --reference=$conf_file $new_conf_file");
	system("chown --reference=$conf_file $new_conf_file");
	system("cp $new_conf_file $conf_file");

	loginfo("Samba $conf_file modified with new UID fields");
    }
    else {
	system("rm $new_conf_file");
	loginfo("Samba $conf_file did not need any modification with new UID fields");
    }

    return($EXIT_OK);
}


sub restore_osconfigs
{
	my @restore_excludes = @{$_[0]};

	my $thisfile = "";
	my $returnval = -1;
	my $datestamp = "";

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}


	showinfo("Restore OS Configs...");

	# What *not* to restore
	if(-f "/etc/httpd/conf.d/rti.conf") {
		push(@restore_excludes, "etc/httpd/conf.d/rti.conf");
	}
	if(-f "/etc/sysconfig/rhn/systemid") {
		push(@restore_excludes, "etc/sysconfig/rhn");
	}

	# Copy critical files before blowing them away.
	$datestamp = strftime("%Y-%m-%d_%H%M%S", localtime());
	system("cp /etc/inittab /etc/inittab.$datestamp");

	# Restore OS Configurations
	$returnval = restore_tarfile("$MOUNTPOINT/configs/osconfigs.bak", \@restore_excludes);
	if($returnval == 0) {
		my $service_name = ($OS eq "RHEL6") ? 'rsyslog' : 'syslog';
		system("/sbin/service $service_name restart 2>> $LOGFILE");
		system("/sbin/service rhnsd restart 2>> $LOGFILE");
		system("/sbin/service sendmail restart 2>> $LOGFILE");

		if ($UPGRADE && ($OS eq "RHEL6")) {
		    samba_set_passdb();
		    samba_rebuild_passdb();
		}

		system("/sbin/service smb restart 2>> $LOGFILE");

		if ($RTI) {
			if(-f "/etc/rc.d/init.d/httpd 2>> $LOGFILE") {
				system("/sbin/service httpd restart 2>> $LOGFILE");
			}
		}

		# (Re) Setup tfremote
		if (-f "$RTIDIR/bin/tfremote.pl") {
			system("$RTIDIR/bin/tfremote.pl --install 2>> $LOGFILE");
		} else {
			logerror("Error: expecting script to exist: $RTIDIR/bin/tfremote.pl");
			logerror("Error: could not install: $RTIDIR/bin/tfremote.pl");
		}

		$RUN_HARDEN_LINUX = 1;

	}

	loginfo("END Restore OS Configs...");

	return($returnval);
}


sub restore_netconfigs
{
	my @restore_excludes = @{$_[0]};

	my $thisfile = "";
	my $returnval = -1;
	my $oldfile = "";
	my $newfile = "";

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}


	showinfo("Restore Network Configs...");

	# Restore Network Configurations
	$returnval = restore_tarfile("$MOUNTPOINT/configs/netconfigs.bak", \@restore_excludes);

	# Don't preserve the "HWADDR=" line in ifcfg-eth0. This causes issues
	# when migrating ethernet configs from an older server to a new server.
	$oldfile = "/etc/sysconfig/network-scripts/ifcfg-eth0";
	$newfile = "/etc/sysconfig/network-scripts/ifcfg-eth0.$$";
	open(OLD, "< $oldfile");
	open(NEW, "> $newfile");
	while(<OLD>) {
		if(/HWADDR/) {
			print NEW "# ";
		}
		print NEW;
	}
	close(OLD);
	close(NEW);
	if(-s "$newfile" > 0) {
		system("mv -v $newfile $oldfile");
	}

	$RUN_HARDEN_LINUX = 1;

	loginfo("END Restore Network Configs...");

	return($returnval);
}


sub restore_printconfigs
{
	my @restore_excludes = @{$_[0]};

	my $thisfile = "";
	my $returnval = -1;

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}


	showinfo("Restore Printer Configs...");

	# Restore Printer Configurations
	$returnval = restore_tarfile("$MOUNTPOINT/configs/printconfigs.bak", \@restore_excludes);
	if($returnval == 0) {
		system("/sbin/service cups restart 2>> $LOGFILE");
	}

	loginfo("END Restore Printer Configs...");

	return($returnval);
}


#
# Restore a single file from one of the backup tar files on the backup device.
# All backup tar files on the backup device are searched... last one wins.
#
# Examples of where this is used:
# --restore=singlefiles /usr2/bbx/bin/rtiperms.pl /usr/bbx/bin/harden_rti.pl
# --restore /usr2/bbx/bin/killem
# --restore /etc/sysconfig/network-scripts/ifcfg-eth0
#
sub restore_singlefiles
{
	my @files_to_restore = ();
	my $thisfile = "";

	mount_device("ro");
	if (!device_is_mounted()) {
		return(-1);
	}


	# If we specify a file "path" in ARGV, our first "path" shows up in @RESTORE,
	# with remaining paths showing up in @ARGV. 
	# Thus, for example, valid things to restore would be:
	# /usr2/bbx/
	# /usr2/bbx/bin/killem
	# ...
	foreach(@RESTORE, @ARGV) {
		chomp;
		$thisfile = $_;
		if(/^\//) {
			$thisfile =~ s/^\///g;
			push(@files_to_restore, "\"$thisfile\"");
		}
	}
	if($#files_to_restore < 0) {
		return(0);
	}


	if($DEVICE ne "") {

		#
		# Search though all available backup tarfiles.
		#
		open(TARFILES, "find $MOUNTPOINT -type f -iname '*.bak' -print |");
		while(<TARFILES>) {
			chomp;

			showinfo("Restoring @files_to_restore from \"$_\" into rootdir=\"$ROOTDIR\".");

			#
			# Form the untar command - the decompression step is optional and can be specified
			# with a command line option, but it's needed when trying to read an old backup
			# file with the newer backup program... the compression of backup files as standard
			# was removed in version 1.192 of rtibackup.pl.
			#
			# First, define the segments of the pipeline
			my $untar_cmd = "cat $_";
			my $decrypt_cmd = "openssl aes-128-cbc -d -salt -k \"$CRYPTKEY\"";
			my $decompress_cmd = "bzip2 -dc";
			my $tar_cmd = "tar -C $ROOTDIR -xvf - @files_to_restore";

			# Now put them together
			$untar_cmd .= " | " . $decrypt_cmd;
			if ($DECOMPRESS_BU) {
				$untar_cmd .= " | " . $decompress_cmd;
			}
			$untar_cmd .= " | " . $tar_cmd;

			# And then issue the command
			if ($DRY_RUN) {
			    print "$untar_cmd 2>> $LOGFILE\n\n";
			}
			else {
			    system("$untar_cmd 2>> $LOGFILE");
			}
		}
		close(TARFILES);
	}

	# Restore from DVD Device, if possible.
	if($DVD != 0) {
		showinfo("Restoring \"@files_to_restore\" from DVD \"$DVDDEV\" into rootdir=\"$ROOTDIR\".");
		system("cat $MOUNTPOINT/stream.img | openssl aes-128-cbc -d -salt -k \"$CRYPTKEY\" | tar -C $ROOTDIR -xvf - @files_to_restore 2>> $LOGFILE");
	}


	return(0);
}


#
# Validate a crypt key
#
# Returns:
#    0 --> crypt key is valid
#    1 --> crypt key is invalid
#   -1 --> crypt key is empty string
#   -2 --> could not mount backup device
#   -3 --> no encrypted files on backup device
#
sub validate_crypt_key
{
    my $cryptkey = $_[0];
    my $rc = 0;

    if ($cryptkey eq "") {
	logerror("Can't validate empty crypt key");
	return(-1);
    }

    mount_device("ro");
    unless (device_is_mounted()) {
	logerror("Could not mount backup device: $DEVICE");
	return(-2);
    }

    my $validation_file_path = "$MOUNTPOINT/$VALIDATION_FILE";

    # Pick a file to test decryption:
    # First, look for the file dedicated for this use:
    #	teleflora-cryptkey-validation-20111130.dat
    #
    # Next, look for the smallest file of:
    my @encrypted_files = qw(
    	configs/altiris.bak
    	configs/osconfigs.bak
    	configs/netconfigs.bak
    	configs/userconfigs.bak
    	configs/rticonfigs.bak
    	configs/dsyconfigs.bak
    	configs/printconfigs.bak
    	userfiles.bak
	logfiles.bak
    	daisy.bak
    	usr2.bak
    );

    my $tarfile = "";

    # if the special validation file exists, use it
    if (-e $validation_file_path) {
	$tarfile = $validation_file_path;
	loginfo("The validation file exists on backup device: $VALIDATION_FILE");
    }

    # otherwise look for the smallest encrypted file
    else {
	loginfo("The validation file does not exist on backup device: $VALIDATION_FILE");
	loginfo("The smallest encrypted file will be used instead");

	my $smallest_file_size = -1;
	my $smallest_file = "";
	my $file_size = 0;
	foreach (@encrypted_files) {
	    if (-e "$MOUNTPOINT/$_") {
		$file_size = -s "$MOUNTPOINT/$_";

		# first time through, use the current file's size
		if ($smallest_file_size == -1) {
		    $smallest_file_size = $file_size;
		    $smallest_file = $_;
		}

		# after that, actually look for a smaller file
		elsif ($file_size < $smallest_file_size) {
		    $smallest_file_size = $file_size;
		    $smallest_file = $_;
		}
	    }
	}

	if ($smallest_file ne "") {
	    $tarfile = "$MOUNTPOINT/$smallest_file";
	    loginfo("Smallest encrypted file: $smallest_file");
	}
    }

    if ($tarfile eq "") {
	showinfo("The backup device does not contain any encrypted files");
	logerror("The crypt key can not be validated");
	unmount_device();
	return(-3);
    }

    loginfo("===== BEGIN Validate Cryptkey =====");

    my $cat_cmd = "cat $tarfile";
    my $decrypt_cmd = "openssl aes-128-cbc -d -salt -k \"$cryptkey\" > /dev/null 2> /dev/null";
    my $validate_cmd = $cat_cmd . " | " . $decrypt_cmd;

    unless ($DRY_RUN) {
	loginfo("Attempting to validate crypt key by decrypting: $tarfile");
	system("$validate_cmd");
	if ($? != 0) {
	    loginfo("The crypt key is invalid");
	    $rc = 1;
	}
	else {
	    loginfo("The crypt key is valid");
	}
    }

    loginfo("===== END Validate Cryptkey =====");

    unmount_device();

    return($rc);
}

#
# Run the harden script on the POS, with an input argument of either
# "$DAISYDIR/daisy" or "$RTIDIR".
#
sub run_harden_linux
{
    my $posdir = "";

    if ($RTI) {
	$posdir = $RTIDIR;
    }
    elsif ($DAISY) {
	$posdir = "$DAISYDIR/daisy";
    }

    if (-f "$posdir/bin/harden_linux.pl") {
	showinfo("Running $posdir/bin/harden_linux.pl");
	system("perl $posdir/bin/harden_linux.pl 2>> $LOGFILE");
    } else {
	logerror("Error: expecting script to exist: $posdir/bin/harden_linux.pl");
	logerror("Error: could not run: $posdir/bin/harden_linux.pl");
    }
}


#
# Run rtiperms.pl on the RTI application files.
#
sub set_rti_perms
{
	if (-f "$RTIDIR/bin/rtiperms.pl") {
		showinfo("Running $RTIDIR/bin/rtiperms.pl /usr2/bbx");
		system("perl $RTIDIR/bin/rtiperms.pl /usr2/bbx 2>> $LOGFILE");
	} else {
		logerror("Error: expecting script to exist: $RTIDIR/bin/rtiperms.pl");
		logerror("Error: could not run: $RTIDIR/bin/rtiperms.pl /usr2/bbx");
	}
}


#
# Run dsyperms.pl on all possible daisy database dirs... always remember
# that there can be multiple daisy database dirs!
#
# Look for all directories in "/d" that contain files named
# "flordat.tel" and "control.dsy".  Skip old dirs.
#
sub set_daisy_perms
{
	my @daisy_db_dirs = glob("$DAISYDIR/*");
	my $dsyperms_cmd = "$DAISYDIR/daisy/bin/dsyperms.pl";

	for my $daisy_db_dir (@daisy_db_dirs) {

		# must be a directory
		next unless (-d $daisy_db_dir);

		# skip old daisy dirs
		next if ($daisy_db_dir =~ /.+-\d{12}$/);

		# must contain the magic files
		next unless(-e "$daisy_db_dir/flordat.tel");
		next unless(-e "$daisy_db_dir/control.dsy");

		if (-f "$DAISYDIR/daisy/bin/dsyperms.pl") {
			showinfo("Running $dsyperms_cmd $daisy_db_dir");
			system("perl $dsyperms_cmd $daisy_db_dir 2>> $LOGFILE");
		} else {
			logerror("Error: expecting script to exist: $dsyperms_cmd");
			logerror("Error: could not run: $dsyperms_cmd");
		}
	}
}

#
# Function to determine if an arbitrary path is a path to a
# daisy databse directory.
#
sub is_daisy_db_dir
{
	my $path = $_[0];

	# must begin with '/d/'
	return(0) unless ($path =~ /^\/d\//);

	# must be a directory
	return(0) unless (-d $path);

	# skip old daisy dirs
	return(0) if ($path =~ /^\/d\/.+-\d{12}$/);

	# must contain the magic files
	return(0) unless (-e "$path/flordat.tel");
	return(0) unless (-e "$path/control.dsy");

	# must be daisy 8.0+
	return(0) unless (-d "$path/bin");

	return(1);
}

sub send_email
{
	my @recipients = @{$_[0]};
	my $subject = $_[1];
	my $message = $_[2];
	my $returnval = -1;

	# Get the hostname and tack onto end of the subject.
	my $hostname = get_hostname();
	$subject .= " $hostname";

	if ($EMAIL_SERVER eq 'sendmail') {
		# Use the sendmail program directly
		$returnval = send_email_sendmail(\@recipients, $subject, $message);
		if ($returnval >= 0) {
			return($returnval);
		}
	}
	elsif ($EMAIL_SERVER ne ""){
		# If we are configured to do so, try sending email via smtp server,
		# instead of using sendmail.
		$returnval = send_email_smtp(\@recipients, $subject, $message, $EMAIL_SERVER, $EMAIL_USER, $EMAIL_PASS);
		if ($returnval >= 0) {
			return($returnval);
		}
	}

	# If there is no SMTP email server defined or the other methods failed,
	# try to use Mutt (and local sendmail.)
	$returnval = send_email_mutt(\@recipients, $subject, $message);

	return($returnval);
}


#
# Send email via direct connect from this script, to an SMTP(s) server.
# This "way" of sending email totally bypasses the use of sendmail, and
# could also resolve issues with email blacklisting.
#
sub send_email_smtp
{
	my @recipients = @{$_[0]};
	my $subject = $_[1];
	my $message = $_[2];
	my $server = $_[3];
	my $username = $_[4];
	my $password = $_[5];
	my $timestamp = "";
	my $recipient = "";
	my $hostname = get_hostname();
	my $smtp = "";

	if($#recipients < 0) {
		logerror("Email transport SMTP server: email recepients is zero");
		return(-1);
	}
	if("$server" eq "") {
		logerror("Email transport SMTP server: server name required but NULL");
		return(-2);
	}
	if("$username" eq "") {
		logerror("Email transport SMTP server: username required but NULL");
		return(-3);
	}
	if("$password" eq "") {
		logerror("Email transport SMTP server: password required but NULL");
		return(-4);
	}

	foreach $recipient(@recipients) {

		loginfo("Sending email to \"$recipient\" via SMTP server.");

		# Connect to an SMTPs (SSL + SMTP) server.
		undef $smtp;
		#$smtp = Net::SMTP::SSL->new($server, Port=> 465);
		if(! $smtp) {
			# Try unencrypted smtp.
			$smtp = Net::SMTP->new($server, Port=> 25);
			if(! $smtp) {
				logerror("Email transport SMTP server: could not connect to $server");
				return(-1);
			}
		}

		# Username + password which the user would usually
		# use to login to their webmail.
		$smtp->auth($username, $password);

		$smtp->mail("backups\@$hostname");
		$smtp->to("$recipient\n", {SkipBad => 1} );

		$smtp->data();
		$smtp->datasend("From: backups\@$hostname\n");
		$smtp->datasend("To: $recipient\n");
		$smtp->datasend("Subject: $subject\n");
		$smtp->datasend("\n");
		$smtp->datasend("$message\n");
		$smtp->dataend();
		$smtp->quit;
	}

	return(0);
}

sub send_email_mutt
{
	my @recipients = @{$_[0]};
	my $subject = $_[1];
	my $message = $_[2];
	my $recipient = "";

	if ($OS eq "FC3") {
		logerror("Fedora Core 3 does not support sending email via Mutt");
		logerror("Configure a different email transport for Fedora Core 3");
		return(-1);
	}

	#["foo,bar,fee"] -> ["foo", "bar", "fee"]
	@recipients = split(/,/, join(',', @recipients));

	foreach $recipient(@recipients) {
		next if("$recipient" eq "");
		loginfo("Sending email to $recipient via MUTT");
		open(MUTT, "| mutt -s \"$subject\" $recipient");
		print(MUTT $message);
		close(MUTT);
	}

	return(0);
}

sub send_email_sendmail
{
	my @recipients = @{$_[0]};
	my $subject = $_[1];
	my $message = $_[2];
 	my $sendmail_cmd = '/usr/lib/sendmail';
	my $recipient = "";

	unless (-x $sendmail_cmd) {
	    logerror("Can't send email: $sendmail_cmd: $!");
	    return(-1);
	}

	my $hostname = get_hostname();

	# any recipients from the command line are a comma separated string but
	# the recipients from the config file are not.
	@recipients = split(/,/, join(',', @recipients));

 	my $from = "rtibackup.pl\@${hostname}.teleflora.com";

	my $cmd = "$sendmail_cmd -oi -t";

	foreach $recipient (@recipients) {
	    if (open(MAIL, "| $cmd")) {
		    loginfo("Sending email to $recipient via sendmail");
		    print MAIL "From: $from\n";
		    print MAIL "To: $recipient\n";
		    print MAIL "Subject: $subject\n\n";
		    print MAIL "$message\n";
		    close(MAIL);
	    }
	    else {
		    logerror("Can't send email: recipient: $recipient");
		    return(-1);
	    }
	}

	return(0);
}


#
# Send backup results to a named printer.
#
sub print_results
{
	my @printers = @{$_[0]};
	my $subject = $_[1];
	my $message = $_[2];
	my $printer = "";
	my $timestamp = "";
	my $returnval = -1;


	#["foo,bar,fee"] -> ["foo", "bar", "fee"]
	@printers = split(/,/, join(',', @printers));

	# Plain Jane printing.
	$timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime());
	foreach $printer(@printers) {
		next if("$printer" eq "");
		open(PIPE, "| lp -d $printer");
		print(PIPE "=============================================================\n");
		print(PIPE "$subject\n");
		print(PIPE "Begin Printout: $timestamp\n");
		print(PIPE "=============================================================\n");
		print(PIPE "\n");
		print(PIPE "\n");
		print(PIPE "\n");
		print(PIPE "\n");
		print(PIPE "\n");
		print(PIPE "$message");
		print(PIPE "\n");
		print(PIPE "\n");
		print(PIPE "\n");
		print(PIPE "\n");
		print(PIPE "\n");
		print(PIPE "=============================================================\n");
		print(PIPE "End Printout: $timestamp\n");
		print(PIPE "$subject\n");
		print(PIPE "=============================================================\n");
		close(PIPE);
	}
}


#
# Copy script to bin directory, set modes and owners.
# If the path to this script is the same as the destination,
# there is no need to copy and thus a nasty diagnostic is avoided.
#
sub install_script
{
	if ($0 ne "$TOOLSDIR/bin/$PROGNAME") {
		system("cp $0 $TOOLSDIR/bin/");
	}
	system("chown root:root $TOOLSDIR/bin/rtibackup.pl");
	system("chmod 555 $TOOLSDIR/bin/rtibackup.pl");

	# Now remove old versions in $POSDIR/bin and make symlinks
	if (-d "/usr2/bbx/bin") {
		system("rm -f /usr2/bbx/bin/rtibackup.pl");
		system("ln -sf $TOOLSDIR/bin/rtibackup.pl /usr2/bbx/bin");
	}
	if (-d "/d/daisy/bin/") {
		system("rm -f /d/daisy/bin/rtibackup.pl");
		system("ln -sf $TOOLSDIR/bin/rtibackup.pl /d/daisy/bin");
	}
}


sub install_ostools_config_dir
{
    my ($config_dir) = @_;
    my $retval = 0;

    unless (-d $config_dir) {

	if (-f $config_dir) {
	    system("rm -f $config_dir");
	}

	system("mkdir $config_dir");

	unless (-d $config_dir) {
	    showerror("Can not make OSTools config dir: $config_dir");
	    $retval = 1;
	}

	else {

	    my $owner = "root";
	    my $group = "root";

	    if (-d "/usr2/bbx") {
		$owner = "tfsupport";
		$group = "rtiadmins";
	    }
	    elsif (-d "/d/daisy") {
		$owner = "tfsupport";
		$group = "dsyadmins";
	    }
	    system("chown $owner:$group $config_dir");
	    system("chmod 775 $config_dir");
	}
    }

    return($retval);
}


sub install_cron_job
{
	# Get rid of old backup cron job files
	if (-f "/etc/cron.d/rti-backup") {
		system("rm -f /etc/cron.d/rti-backup");
	}
	if (-f "/etc/cron.d/daisy-backup") {
		system("rm -f /etc/cron.d/daisy-backup");
	}

	my $cron_job_file = "/etc/cron.d/nightly-backup";

	# If a cron job file already exists, don't overwrite it since
	# it might have site dependent contents.  Put the new version
	# into the ostools config directory.

	if (-e $cron_job_file) {
	    if (install_ostools_config_dir("$TOOLSDIR/config") != 0) {
		showerror("OSTools config dir doesn't exist: $TOOLSDIR/config");
		return;
	    }
	    $cron_job_file = "$TOOLSDIR/config/nightly-backup.new";
	}

	# Make a new combined cron job file
	unless (open(FILE, ">", $cron_job_file)) {
	    showerror("Can not make new cron job file: $cron_job_file");
	    return;
	}

	# write first line normal way so we can inject variable values
	print(FILE "#\n");
	print(FILE "# Generated by $PROGNAME $CVS_REVISION $TIMESTAMP\n");
	print(FILE "#\n");

	# now the bulk of the file contents
	print(FILE << 'EOF');
# Do not edit this file.
#
# Automated Nightly backups.
# See also config file /usr2/bbx/config/backups.config
# See also config file /d/daisy/config/backups.config
#

# RTI - Nightly Backups.
30 01 * * * root (/usr/bin/test -e /usr2/bbx && . /etc/profile.d/rti.sh && /usr2/bbx/bin/rtibackup.pl --rti --configfile=/usr2/bbx/config/backups.config --format --backup=all --verify --console)

# Daisy - Nightly Backups.
35 01 * * * root (/usr/bin/test -e /d/daisy && /d/daisy/bin/rtibackup.pl --daisy --format --backup=all --verify --console)

# Daisy - Daily encrypted backup of /d to /save/backups
#01 01 * * * root (/usr/bin/test -e /d/daisy && /d/daisy/bin/rtibackup.pl --daisy --saveall)

EOF
	close(FILE);

	system("chown root:root $cron_job_file");
	system("chmod 644 $cron_job_file");
}


sub install_edit_root_crontab
{
	# Only need to edit crontab if root crontab exists
	system("crontab -u root -l > /dev/null 2>&1");
	if ($? == 0) {

		# Remove from root crontab entries for RTI or Daisy
		open(OLD, "crontab -u root -l |");
		open(NEW, "> /tmp/crontab.$$");
		while(<OLD>) {
			if (/lone-tar/) {
				if(! /^(\s*)(#)/) {
					# lone-tar entry has not already been pounded out.
					print(NEW "# $_");
				}
				next;

			} elsif (/dsyclean.pl/ || /daisyback/ || /dback/ || /dayback/) {
				unless (/^(\s*)(#)/) {
					# this entry has not already been pounded out.
					print(NEW "# $_");
				}
				next;

			} else {
				print(NEW);
			}
		}
		close(OLD);
		close(NEW);
		if(-s "/tmp/crontab.$$" > 0) {
			system("crontab -u root /tmp/crontab.$$");
		}
		unlink("/tmp/crontab.$$");
	}
}


# Add an init.d entry for the "rtibackup data secondary server".
# Make sure *not* to enable this by default, though.
# This option is rarely used, especially in daisy, and,
# we do not want it running by default, as, it exposes a
# listening TCP port.  However, we do want this service
# to be available for those shops who are performing
# backups to a secondary server.

sub install_secondary_server
{
	open(FILE, "> /etc/init.d/rtibackup-secondary");
	print(FILE << 'xxxEOFxxx');
#!/bin/bash
#
# rtibackup-secondary  Act as a POS Secondary Server for Backup Data.
#
# chkconfig: 345 25 75
# description: Starts daemon which listens for incoming POS data, and places that onto local disk.
### BEGIN INIT INFO
# Provides: $rti-datasync
### END INIT INFO


case "$1" in
 start)
	if [ -x /usr2/bbx/bin/rtibackup.pl ] ; then
        	/usr2/bbx/bin/rtibackup.pl --secondary --configfile=/usr2/bbx/config/backups.config
	elif [ -x /d/daisy/bin/rtibackup.pl ] ; then
        	/d/daisy/bin/rtibackup.pl --secondary --configfile=/d/daisy/config/backups.config
	else
		echo "rtibackup script not found."
	fi
	;;
	
 stop)
        killall rtibackup.pl
	;;

 status)
        ps wwaux | grep rtibackup.pl
	;;

 *)
	echo "usage: $0 start|stop|status"
	exit 1
esac

exit $?
xxxEOFxxx
	close(FILE);

	system("chown root:root /etc/init.d/rtibackup-secondary");
	system("chmod 555 /etc/init.d/rtibackup-secondary");

	system("/sbin/chkconfig --list | grep rtibackup-secondary > /dev/null 2> /dev/null");
	if ($? != 0) {
		system("/sbin/chkconfig --add rtibackup-secondary");
		system("/sbin/chkconfig --level 345 rtibackup-secondary off");
	}
}


#
# If one does not already exist, write a "template" config file.
#
sub install_configfile
{
	my $filepath = $_[0];
	my @email_addrs = ();
	my $text = "";

	unless (defined($filepath)) {
		loginfo("can't happen: install_configfile() called with undefined arg");
		return;
	}

	if ($filepath eq "") {
		loginfo("can't happen: install_configfile() called with null arg");
		return;
	}

	#
	# If config file exists, write a new one with ".new" suffix and
	# leave the old one in place.
	#
	if (-f $filepath) {
		$filepath = "$filepath.new"
	}

	loginfo("---- BEGIN Install Config File $PROGNAME $CVS_REVISION ----");


	#
	# (RTI)
	# Pick any existing email addresses from current "ltar.cfg" file and
	# add those into our newly generated config file.
	#
	if(-f "/usr/lone-tar/ltar.cfg") {
		loginfo("---- Looking for Email Addresses in ltar.cfg ----");
		open(LTAR, "< /usr/lone-tar/ltar.cfg");
		while(<LTAR>) {
			if(/^(\s*)(MAIL_TO)(\s*)(=)(\s*)(\S+)/) {
				chomp;
				$text = $6;
				$text =~ s/\"//g; #strip quotes.
				$text =~ s/,//g; # Strip commas.
				# John Simon is no longer employed by Teleflora.
				$text =~ s/jsimon\@teleflora\.com//g;
				@email_addrs = split(/\s+/,$text);
				last;
			}
		}
		close(LTAR);
		foreach(@email_addrs) {
			loginfo("Found Lonetar Email Address: $_");
		}
		loginfo("---- Done with ltar.cfg ----");
	}

	open(CONFIG, "> $filepath");

	print(CONFIG "#\n");
	print(CONFIG "# $PROGNAME Config File\n");
	print(CONFIG "# Generated by $PROGNAME $CVS_REVISION $TIMESTAMP\n");
	print(CONFIG "#\n");

	print(CONFIG << 'EOB');
#
# email=xxxxx
# When backups complete, send an email to the user(s) specified in an "email=" line (below).
# Use multiple "email=" lines, one email address per line.
# Note that the system "mutt" utility is used to send these mails.
#
#email=user@somewhere.com
#email=user2@elsewhere.com
EOB

	# The Long Tar config file uses a value of "off" as a special
	# email address that means "do not send email".
	my $lone_tar_special_addr = "off";

	# Add any email addresses which we found from ltar.cfg
	foreach(@email_addrs) {
		if (! /$lone_tar_special_addr/i) {
			print(CONFIG "email=$_\n");
		}
	}

	print(CONFIG << 'EOB');


#
# emailserver=smtp.isp.com
# In the event that email does not "just work", this option allows us to send an email via
# a 3rd party. For example, sending "through" gmail, yahoo, or your ISP.
# Using this requires you have a 3rd party email "username" and "password" as well
# as the "smtp" host which your provider uses (eg "smtp.google.com", "smtp.emailsrvr.com", etc)
# Note that your password will be stored here in cleartext.
#
#email_server=smtp.google.com
#email_username=someone@gmail.com
#email_password=gmailpassword
#
# Use the sendmail program
#email_server=sendmail


#
# printer=xxxxx
# When backups complete, send verify results to one of the printers listed here.
# Multiple "printer=xxxxxxx" lines are allowed, in which case, results will be sent to multiple printers.
# "xxxxxx" represents the printer queue name; typically the "cups printer name."
#
#printer=printer11
#printer=order1


#
# ejectdays=never
# ejectdays=always
# ejectdays=weekdays
# ejectdays=mon,tue,wed,thu,fri,sat,sun
# ejectdays=mon,wed,fri
# ejectdays=xxx,xxx,xxx...
#
# When a backup succeeds, on which days of the week should the tape be ejected?
# Some shops never want their tape ejected, other shops want the tape ejected
# with every successful backup.
#
# Most shops are not in the office on Sunday, thus, would not want the tape
# ejected on Sunday. Many shops may not want the tape ejected over the weekend.
#
# This config option enables some control over when the tape is ejected.
# Note that the tape is never ejected if the backup failed.
#
ejectdays=weekdays


#
# receivefrom=xxx.xxx.xxx.xxx:SVCTAG
#
# Usually, you will leave this option commented.
# If we wish for this server to act as a secondary server, then, running
# rtibackup.pl --secondary --configfile=thisfile will look for this particular configuration value.
# This value is a colon separated string consisting of:
# xxx.xxx.xxx.xxx: IP Address (not hostname) of the primary server which will be sending the data.
# SVCTAG: Dell Service Tag Number of the primary server which will be sending the data.
#
# receivefrom=xxx.xxx.xxx.xxx:SVCTAG


#
# exclude=/path/to/somewhere
# Exclude files or paths from the configured backups.
# Note that multiple "exclude=" lines are allowed, and results are passed
# directly into a tar "exclude" file (see tar man page.)
#
#exclude=/path/to/file
#exclude=filename


#
# restore-exclude=/path/to/directory
# restore-exclude=/path/to/file
# Multiple of these are allowed.
# Only used during restore.
# Note: if there were values specified on the command line via the
# "--restore-exclude=" option, then the values specified in the
# config file will be added to those specified on the command line.
#restore-exclude=/path/to/directory
#restore-exclude=/path/to/file
#restore-exclude=filename

#
# userfile=/path/to/somewhere
# userfile=/path/to/somewhere
# ...
# Include "user specified" files for backup into the "--backup=include"
# option.
# Note that multiple "include=" lines are allowed.
# Note that "exclude" rules (above) are ignored when these "userfiles" are
# backed up.
#
#userfile=/path/to/file
#userfile=filename


#
# usedvd=true
# usedvd=false
# usedvd=yes
# usedvd=no
# Equivalent to "--dvd". 
# Should the backups attempt to "also backup" to optical media?
#
usedvd=false


#
# Device to use for DVD-R, DVD-RW, DVD+RW, DVD+R, etc.
# This is the device used when "--dvd" is specified on the commandline.
# Usually, this device can be auto-detected. 
#
# This is *not* the device for a Rev drive, RD1000 or USB thumb drive.
# See "--device" for that.
#
# If "--dvd" is specified during backup time, this device will "also"
# be used to backup a limited (but critical) subset of data.
#
#dvddev=/dev/scd1


#
# Specify which device we will use to backup.
# Note that this device is normally auto-detected, however, by specifying a default,
# one could use "non-standard" devices such as external hard disks, USB thumb drives
# or even "loopback" image files.
#
# Use this option with care!
# Especially when working with removable media. If you specify some device here
# which turns out to be your primary partition, you will wind up wiping out your primary partition.
#
# To create a "loopback" image file, some examples are below. Make sure to --format the device
# before use:
# dd if=/dev/zero of=/path/to/file.img bs=1M count=30000 #30 GB image file.
#
#device=/dev/scd0
#device=/path/to/file.img

#
# Identify the vendor and model of an external disk.  These are the
# strings that appear in the following files:
#
#   /sys/block/sd[a|b|c|d|e]/device/vendor
#   /sys/block/sd[a|b|c|d|e]/device/model
#
# Default values:
#device-vendor=WD
#device-model="My Passport"
#
# Example for other block devices:
#device-vendor=Seagate
#device-model="FreeAgent GoFlex"


#
# If set to true, the tar files written to the backup device will be
# compressed with bzip2.  The default is to not compress.
#
#compress=false


#
# If set to true, the tar files read from the backup device are
# decompressed with bzip2.  The default is to not decompress.
#
#decompress=false


#
# If set to true, encrypt the files sent to the secondary server.
# Default: false.
#
#sendto-encrypt=false


#
# If set to true, decrypt the files received from the primary server.
# Default: false.
#
#receivefrom-decrypt=false


# If set to true, run in debug mode
# Default: false.
#
#debugmode=false


# If set to true, then if there is a verify error on a backup device,
# and a specific backup type was specified on the command line,
# then an fsck of the backup device will be performed.
#
#autocheckmedia=false


# If set to true, then look for a device on the USB bus that
# has a file system label of "TFBUDSK-yyyymmdd".
#
#usb-device=false


#
# rti=true
# rti=yes
# Hint the backup script that this is an "rti" system.
# This is normally auto-detected.
#
#rti=true
#rti=false


#
# daisy=true
# daisy=false
# daisy=yes
# daisy=no
# Hint the backup script that this is a "daisy" system.
# This is normally auto-detected.
#
#daisy=true
#daisy=false
EOB

	close(CONFIG);
	loginfo("---- END Install Configfile ----");
}


# Write a default config file if need be.
sub install_default_config_file
{
	my $owner = "root";
	my $group = "root";
	my $conf_file = "$TOOLSDIR/config/backups.config";

	if (-d "/usr2/bbx/config") {
		$conf_file = "/usr2/bbx/config/backups.config";
		$owner = "tfsupport";
		$group = "rtiadmins";
	}
	elsif (-d "/d/daisy/config") {
		$conf_file = "/d/daisy/config/backups.config";
		$owner = "tfsupport";
		$group = "dsyadmins";
	}

	install_configfile($conf_file);
	system ("chown $owner:$group $conf_file");
	system ("chmod 554 $conf_file");
}


sub install_rtibackup
{
	loginfo("---- BEGIN Installation $PROGNAME $CVS_REVISION ----");

	install_script();

	install_cron_job();

	install_edit_root_crontab();

	install_secondary_server();

	install_default_config_file();

	loginfo("---- END rtibackup installation ----");
}



#
# Daisy Backup To /save.  This will backup all of a daisy tree (/d).
# Meant to be called from the crontab file /etc/cron.d/daisy-backup.
#
sub daisy_saveall
{
	my $bucmd = '/d/daisy/bin/rtibackup.pl';
	my $cfgfile = '/d/daisy/config/backups.config';
	my $dayname = strftime "%A", localtime;
	my $imgfile = "/save/backups/$dayname-backup.img";

	return if(! -d "/d/daisy");

	#fixme - size should be dynamic
	system("dd if=/dev/zero of=$imgfile bs=1M count=3000");
	if ($?) {
		logerror("Could not make new backup image file $imgfile: $!");
		return;
	}
	exec("$bucmd --daisy --configfile=$cfgfile --device=$imgfile --format --backup=daisy --verify");
}


sub report_configfile_entry
{
    my $configfile_entry = $_[0];

    loginfo("\tConfigfile: $configfile_entry");
    if ($REPORT_CONFIGFILE) {
	print "$configfile_entry\n";
    }
}


#
# Read config file.
#
# Returns 0 on success, 1 on error.
#
sub read_configfile
{
	my $configfile = $_[0];

	if ($configfile eq "") {
		logerror("Can't happen: config file value is the empty string");
		return(1);
	}
	unless (-e $configfile) {
		logerror("Config file does not exist: $configfile");
		return(1);
	}

	loginfo("Reading config file: $configfile");

	unless (open(CONFIG, "<", $configfile)) {
		logerror("Can't open config file: $configfile");
		return(1);
	}

	while(<CONFIG>) {


		# printer=cups_prn_name
		# Multiple of these are allowed.
		if(/^(\s*)(printer)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			push(@PRINTER, $6);
			report_configfile_entry("--printer=\"$6\"");
		}

		# email=someone@foo.com
		# Multiple of these are allowed.
		if(/^(\s*)(email)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			push(@EMAIL, $6);
			report_configfile_entry("--email=\"$6\"");
		}


		# If we want to try sending emails via, say, gmail or yahoo.
		if(/^(\s*)(email_server)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$EMAIL_SERVER = $6;
			report_configfile_entry("Email Server =\"$6\"");
		}
		if(/^(\s*)(email_username)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$EMAIL_USER = $6;
			report_configfile_entry("Email Username=\"$6\"");
		}
		if(/^(\s*)(email_password)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$EMAIL_PASS = $6;
			report_configfile_entry("Email Password=\"xxxxxxx\"");
		}


		# exclude=/path/to/directory
		# exclude=/path/to/file
		# Multiple of these are allowed.
		# Only used during backup.
		if(/^(\s*)(exclude)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			push(@EXCLUDES, $6);
			report_configfile_entry("Will exclude backup of \"$6\"");
		}

		# restore-exclude=/path/to/directory
		# restore-exclude=/path/to/file
		# Multiple of these are allowed.
		# Only used during restore.
		# Note: if there were values specified on the command line via the
		# "--restore-exclude=" option, then the values specified in the
		# config file will be added to those specified on the command line.
		if(/^(\s*)(restore-exclude)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			push(@RESTORE_EXCLUDES, $6);
			report_configfile_entry("Will exclude restore of \"$6\"");
		}

		# userfile=/path/to/directory
		# userfile=/path/to/file
		# Multiple of these are allowed.
		# Only used during backup.
		# Backs up into "other" category.
		if(/^(\s*)(userfile)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			push(@USERFILES, $6);
			report_configfile_entry("userfile=\"$6\"");
		}


		# Which backup device to use.
		# device=/dev/whatever
		if(/^(\s*)(device)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$DEVICE = $6;
			report_configfile_entry("--device=$DEVICE");
		}


		# Which vendor for external backup device to use.
		# The default value is:
		# device-vendor=WD
		if (/^(\s*)(device-vendor)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$DEVICE_VENDOR = $6;
			$DEVICE_VENDOR =~ s/"//g;
			report_configfile_entry("--device-vendor=\"$DEVICE_VENDOR\"");
		}


		# Which model for external backup device to use.
		# The default value is:
		# device-model=My Passport
		if (/^(\s*)(device-model)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$DEVICE_MODEL = $6;
			$DEVICE_MODEL =~ s/"//g;
			report_configfile_entry("--device-model=\"$DEVICE_MODEL\"");
		}


		# Use the DVD Device?
		# Configfile equivalent to "--dvd"
		# usedvd=yes
		# usedvd=no
		# usedvd=true
		# usedvd=false
		if(/^(\s*)(usedvd)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if($6 =~ /[YyTt1]/) {
				$DVD = 1;
				report_configfile_entry("--dvd");
			}
		}



		# Which DVD backup device to use.
		# dvddev=/dev/whatever
		if(/^(\s*)(dvddev)(\s*)(=)(\s*)([[:print:]]*)$/i) {
			$DVDDEV = $6;
			report_configfile_entry("--dvddev=$DVDDEV");
		}

		# When to eject the backup media
		# eject=never
		# eject=always
		# eject=weekdays
		# eject=mon,tue,wed,thu,fri,sat,sun
		if(/^(\s*)(ejectdays)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$EJECTDAYS = lc($6);
			$EJECTDAYS =~ s/(\s+)//g; # Strip out spaces.
			if($EJECTDAYS =~ /weekdays/i) {
				$EJECTDAYS =~ s/weekdays/mon,tue,wed,thu,fri/g;
			}
			if($EJECTDAYS =~ /weekend/i) {
				$EJECTDAYS =~ s/weekend/sat,sun/g;
			}
			report_configfile_entry("ejectdays=$EJECTDAYS");
		}


		# Specify sending machine (Server to server copy)
		# receivefrom=server.ip.address:SVCTAG
		if(/^(\s*)(receivefrom)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$RCVFROM = uc($6);
			report_configfile_entry("--receivefrom=$RCVFROM");
		}


		# Use compression when writing tar files?
		# compress=1
		# compress=True / compress=true
		# compress=Yes / compress=yes
		if(/^(\s*)(compress)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if($6 =~ /[YyTt1]/) {
				$COMPRESS_BU = 1;
				report_configfile_entry("--compress");
			}
		}

		# Use decompression when reading tar files?
		# decompress=1
		# decompress=True / decompress=true
		# decompress=Yes / decompress=yes
		if(/^(\s*)(decompress)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if($6 =~ /[YyTt1]/) {
				$DECOMPRESS_BU = 1;
				report_configfile_entry("--decompress");
			}
		}

		# Encrypt files sent to secondary server
		# sendto-encrypt=1
		# sendto-encrypt=True / sendto-encrypt=true
		# sendto-encrypt=Yes / sendto-encrypt=yes
		if (/^(\s*)(sendto-encrypt)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if ($6 =~ /[YyTt1]/) {
				$SENDTO_ENCRYPT = 1;
				report_configfile_entry("--sendto-encrypt");
			}
		}

		# Decrypt files received from primary server
		# receivefrom-decrypt=1
		# receivefrom-decrypt=True / receivefrom-decrypt=true
		# receivefrom-decrypt=Yes / receivefrom-decrypt=yes
		if (/^(\s*)(receivefrom-decrypt)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if ($6 =~ /[YyTt1]/) {
				$RCVFROM_DECRYPT = 1;
				report_configfile_entry("--receivefrom-decrypt");
			}
		}

		# Run in debug mode
		# debugmode=1
		# debugmode=True
		# debugmode=Yes
		if (/^(\s*)(debugmode)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if ($6 =~ /[YyTt1]/) {
				$DEBUGMODE = 1;
				report_configfile_entry("--debugmode");
			}
		}

		# Do autocheckmedia?
		# autocheckmedia=1
		# autocheckmedia=True / autocheckmedia=true
		# autocheckmedia=Yes / autocheckmedia=yes
		if(/^(\s*)(autocheckmedia)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if($6 =~ /[YyTt1]/) {
				$AUTO_CHECKMEDIA = 1;
				report_configfile_entry("--autocheckmedia");
			}
		}

		# Use a USB device?
		# usb-device=1
		# usb-device=True / usb-device=true
		# usb-device=Yes / usb-device=yes
		if(/^(\s*)(usb-device)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if($6 =~ /[YyTt1]/) {
				$USB_DEVICE = 1;
				report_configfile_entry("--usb-device");
			}
		}

		# Is this an RTI system?
		# rti=1
		# rti=True / rti=true
		# rti=Yes / rti=yes
		if(/^(\s*)(rti)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if($6 =~ /[YyTt1]/) {
				$RTI = 1;
				report_configfile_entry("--rti");
			}
		}

		# Is this a daisy system?
		# daisy=1
		# daisy=True / daisy=true
		# daisy=Yes / daisy=yes
		if(/^(\s*)(daisy)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			if($6 =~ /[YyTt1]/) {
				$DAISY = 1;
				report_configfile_entry("--daisy");
			}
		}
	}
	close(CONFIG);

	return(0);
}


sub get_command_line
{
	my $cmd_line = "";

	$cmd_line = $0;
	foreach my $i (@ARGV) {
		$cmd_line .= " ";
		if ($i =~ /\s/) {
			if ($i =~ /(--[[:print:]]+)(=)(.+)$/) {
				$cmd_line .= "$1$2\"$3\"";
			}

			else {
				$cmd_line .= "\"$i\"";
			}
		}

		else {
			$cmd_line .= "$i";
		}
	}

	return($cmd_line);
}

#
# Change the location of the logfile - when doing a "--restore=all" or
# a "--restore=rti" on an RTI system, or a "--restore=daisy" on a Daisy
# system, the logfile location is going to be replaced.
#
# The only real choice for a new temporary location is /tmp.  After
# the restore is done, copy the temporary logfile back to the normal
# location.
#
sub log_change_location
{
    return if ($LOGFILE eq "");

    # First, make a temp logfile based on name of standard logfile
    my $logfile_basename = basename($LOGFILE);
    my $tmp_dir = "/tmp";
    my $tmp_logfile = make_tempfile("$tmp_dir/$logfile_basename");

    # save path to old logfile
    my $old_logfile_path = $LOGFILE;

    # switch to temp logfile after writing message to old logfile
    loginfo("#");
    loginfo("# Switching to temp logfile: $tmp_logfile");
    loginfo("#");
    $LOGFILE = $tmp_logfile;

    # write message to new temp logfile
    loginfo("#");
    loginfo("# Logfile location switched");
    loginfo("#");
    loginfo("#\tTemporary logfile: $LOGFILE");
    loginfo("#\tPrevious logfile: $old_logfile_path");

    return($old_logfile_path);
}

sub log_restore_location
{
    my $orig_logfile_path = $_[0];

    # save path to temp logfile
    my $tmp_logfile = $LOGFILE;

    # switch to standard logfile after writing message to temp logfile
    loginfo("#");
    loginfo("# Switching to original logfile: $orig_logfile_path");
    loginfo("#");
    $LOGFILE = $orig_logfile_path;

    # write status message to standard logfile
    loginfo("#");
    loginfo("# Logfile location switched");
    loginfo("#");
    loginfo("#\tCurrent logfile: $LOGFILE");
    loginfo("#\tTemporary logfile: $tmp_logfile");
    loginfo("#");

    # concatenate temp logfile to the standard log file
    loginfo("#");
    loginfo("# BEGIN including contents of temp logfile");
    loginfo("#");
    system("cat $tmp_logfile >> $LOGFILE");
    my $cat_status = $?;
    loginfo("#");
    loginfo("# END including contents of temp logfile");
    loginfo("#");

    if ($cat_status != 0) {
	# could not copy contents of temp logfile
	logerror("Concatenation of temp logfile to standard log file failed");
	loginfo("Contents of temp logfile preserved: $tmp_logfile");
	loginfo("Please remove when no longer needed");
    }
    else {
	# success, so rm previous logfile
	unlink "$tmp_logfile";
	loginfo("Temp logfile removed: $tmp_logfile");
    }
}


# Remove tomorrow's logfile.
sub logrotate
{
	my $logfile = $_[0];

	#
	# If the format of the log file is one that can be rotated, that is,
	# it ends with "-Day_nn.log", then "rotate it":
	#   - remove tomorrow's instance.
	#   - form file name with todays day of the month
	#
	if ($logfile =~ /(.*)-(Day_\d\d)\.log/) {
		my $logfiletype = $1;
		my $today = strftime("Day_%d", localtime(time())) . ".log";
		my $tomorrow = strftime("Day_%d", localtime(time() + (60*60*24))) . ".log";
		if (unlink "$logfiletype-$tomorrow") {
			loginfo("Logfile Rotation: tomorrow's logfile removed: $logfiletype-$tomorrow");;
		}
		my $new_logfile_name = "$logfiletype-$today";
			loginfo("Logfile Rotation: current logfile: $logfiletype-$today");;
		return($new_logfile_name);
	}

	return($logfile);
}


sub showerror
{
	my $message = $_[0];

	print("$message\n");
	return(loginfo("<E>  $message"));
}

sub logerror
{
	my $message = $_[0];

	print("$message\n");
	return(loginfo("<E> $message"));
}

sub logdebug
{
	my $message = $_[0];
	if($VERBOSE != 0) {
		print("$message\n");
		return(loginfo("<D> $message"));
	}
}


# Print to screen, and log to logfile.
sub showinfo
{
	my $message = $_[0];

	print("$message\n");
	return(loginfo("<I>  $message"));
}


#
# Write message to logfile.
#
sub loginfo
{
	my $message = $_[0];
	my $timestamp = "";

	# Is a log directory in place? If so, keep a logfile as well.
	unless (-d "$RTIDIR/log" || -d "$DAISYDIR/daisy/log") {
		return(0);
	}

	# Logfile
	$timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime(time()));
	open(LOG, ">> $LOGFILE");
	print(LOG "$timestamp");
	if( ($message !~ /<E>/)
	&&  ($message !~ /<D>/)
	&&  ($message !~ /<I>/) ) {
		print(LOG " <I> ");
	}
	print(LOG " $message\n");
	close(LOG);

	# insurance that processes can access the log file
	system "chmod 666 $LOGFILE";

	return(0);
}

sub debuglog
{
	my $message = $_[0];
	my $timestamp = "";

	$timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime(time()));
	open(LOG, ">> $DEBUGLOGFILE");
	print(LOG "$timestamp $message\n");
	close(LOG);
}


__END__

=pod

=head1 NAME

rtibackup.pl - OSTools backup script for RTI and Daisy


=head1 VERSION

This documenation refers to version: $Revision: 1.330 $



=head1 OPTIONS

=over 4

=item B<--version>

Output the version number of the script and exit.

=item B<--help>

Output a short help message and exit.

=item B<--install>

Install the script.

=item B<--backup=s>

Perform a backup.

Modifiers: B<--device=s>, B<--usb-device>, B<--compress>, B<--nocc>,
B<--cryptkey=s>, B<--rti>, and B<--daisy>.

=item B<--restore=s>

Restore files from a backup.

Modifiers: B<--device>, B<--usb-device>, B<--restore-exclude=s>, B<--decompress>,
B<--cryptkey=s>, B<--force>, B<--rootdir=s>, B<--[no]harden-linux>,
B<--dry-run> and B<--upgrade>.

=item B<--list=s>

List the files in a backup.

Modifiers: B<--device>, B<--usb-device>, B<--rti> and B<--daisy>.

=item B<--verify>

Verify a backup.

Modifiers: B<--console>, B<--[no]autocheckmedia>, B<--verbose>, B<--rti>, and B<--daisy>.

=item B<--checkmedia>

Check the media of a backup device.
May be used with B<--backup=s>.

Modifiers: B<--checkmedia>,  B<--email=s>, and B<--printer=s>.

=item B<--format>

Format the backup device.
May be used with B<--backup=s>.

Modifiers: B<--force>, and B<--verbose>.

=item B<--eject>

Eject the media from a backup device.

=item B<--finddev>

Find a backup device and report it's device special file.

=item B<--getinfo>

Get and report info about the backups on a backup device.

=item B<--showkey>

Output the encryption key.

Modifers: B<--cryptkey=s>.

=item B<--validate-cryptkey>

Verify that the encryption key will actually decrypt the backup files.

Modifers: B<--cryptkey=s>.

=item B<--mount>

Mount a backup device.

=item B<--unmount|--umount>

Unmount a backup device.

Modifers: B<--verbose>.

=item B<--report-configfile>

Parse the config file, and report it's contents.

=item B<--checkfile args [args ...]>

Verify the files listed as command arguments.

=item B<--rti>

Modifier: specify that the system is a RTI system.

=item B<--daisy>

Modifier: specify that the system is a Daisy system.

=item B<--email=s>

Modifier: specifies a list of email addresses.

=item B<--printer=s>

Modifier: specifies a list of printer names.

=item B<--rootdir=s>

Modifier: specifies the destination directory for restore;
used as the B<-C=dir> option for the tar(1) command.

=item B<--configfile=s>

Modifier: specifies the path to the config file.

=item B<--logfile=s>

Modifier: specifies the path to the logfile.

=item B<--restore-exclude=s>

Modifier: specifiesy a list of files to exclude from a restore.

=item B<--device=s>

Modifier: specifies the path to the device special file for the backup device.

=item B<--device-vendor=s>

Modifier: specifies the vendor name of the backup device.

=item B<--device-model=s>

Modifier: specifies the model name of the backup device.

=item B<--usb-device>

Modifier: use a disk plugged into the USB bus which has been
formatted with B<--format> as the backup device.

=item B<--compress>

Modifier: compress the files when writing to the backup device.

=item B<--decompress>

Modifier: decompress the files when reading from the backup device.

=item B<--keep-old-files>

Modifer: don't overwrite files when doing a restore.

=item B<--upgrade>

Modifier: perform extra operations when doing a restore.

=item B<--dry-run>

Modifer: report what an operation would do but don't actually do it.

=item B<--verbose>

Modifier: report extra information.

=item B<--[no]autocheckmedia>

Modifier: don't check media on errors during a backup.

=item B<--[no]harden-linux>

Modifier: don't run the harden_linux.pl script.



=back


=head1 DESCRIPTION

This I<rtibackup.pl> script is used to backup and restore data
for a Teleflora RTI or Daisy Point of Sale system.
It is essentially an elaborate front end to the tar(1) command which
does the real work of reading and writing the data files.
Due to the complexity of the requirements, there are many options and
many ways that the script can be used.

=head2 COMMAND LINE OPTIONS

The C<--install> command line option performs all the steps
necessary to install the "rtibackup.pl" script onto the system.
First, the script is copied to the OSTools bin directory,
and it's file owner, group, and perms are set.
Then, a symlink is made from the POS bin directory pointing to the script
in the OSTools bin directory.
Next, the cron job file is installed.
If any old style cron job files exist, they are removed.
The new cron job file named "nightly-backup" is generated and
copied into directory "/etc/cron.d".
However, if there is an existing cron job file in "/etc/cron.d", then
the cron job file is copied to the OSTools config directory instead.

The C<--restore=s> command line option restores file from a backup.
The options C<--restore-exclude=s>,  C<--cryptkey=s>, C<--force>, and
C<--rootdir=s> may be used with C<--restore>.

The C<--harden-linux|--noharden-linux> command line option provides
a way to specify whether or not the "harden_linux.pl" script
should be run by the "rtibackup.pl" script.
The default behavior for "rtibackup.pl" is to run "harden_linux.pl"
after performing any of the following restore types:
"all", "rticonfigs", "daisy", "daisyconfigs", "osconfigs" and "netconfigs".
The "harden" script will only be run once after all restores are finished.
To prevent harden_linux.pl from running, specify the following option:
"--noharden-linux".


=head1 FILES

=over 4

=item B</usr2/ostools/config>

The location of the OSTools config directory for an RTI system.

=item B</d/ostools/config>

The location of the OSTools config directory for a Daisy system.

=item B</mnt/backups>

Mount point for backup device.

=item B</etc/redhat-release>

Contents determines OS type, and is used for validating crypt key.

=item B</sys/block/{sda,sdb,sdc,sde,sdd}/device/vendor>

This file contains the vendor string for the block device, ie disk,
that has special device file "/dev/sda", or "/dev/sdb", etc.

=item B</sys/block/{sda,sdb,sdc,sde,sdd}/device/model>

This file contains the model string for the block device, ie disk,
that has special device file "/dev/sda", or "/dev/sdb", etc.

=back


=head1 DIAGNOSTICS

=over 4

=item Exit status 0 ($EXIT_OK)

Successful completion.

=item Exit status 1 ($EXIT_COMMAND_LINE)

In general, there was an issue with the syntax of the command line.

=item Exit status 2 ($EXIT_PLATFORM)

Unknown operating system.

=item Exit status 3 ($EXIT_ROOTDIR)

The directory specified for B<--rootdir> does not exist.

=item Exit status 4 ($EXIT_TOOLSDIR)

The OSTools directory does not exist.

=item Exit status 5 ($EXIT_BACKUP_DEVICE_NOT_FOUND)

A backup device of any kind was not found.

=item Exit status 6 ($EXIT_USB_DEVICE_NOT_FOUND)

The B<--usb-device> option was specified but a USB backup device
was not found.

=item Exit status 7 ($EXIT_BACKUP_TYPE)

The backup type specified with B<--backup> is not supported.

=item Exit status 11 ($EXIT_DEVICE_VERIFY)

The specified backup device is either not a block device or
is not an image file of minimum size.

=item Exit status 12 ($EXIT_USB_DEVICE_UNSUPPORTED)

USB devices other than WD Passports are not supported on RHEL4.

=item Exit status 30 ($EXIT_SENDTO_CONNECT)

Could not connect to the secondary server.

=item Exit status 31 ($EXIT_SENDTO_IPADDR)

The IP address of the secondary server was missing from the B<--sendto>
commandline argument.

=item Exit status 32 ($EXIT_SENDTO_SVCTAG)

The service tag of the secondary server was missing from the B<--sendto>
commandline argument.

=item Exit status 33 ($EXIT_SENDTO_READ_ERR)

There was a read error while reading data from list of files to backup.

=item Exit status 34 ($EXIT_SENDTO_WRITE_ERR)

There was a write error while writing data to the secondary server. 

=item Exit status 40 ($EXIT_SECONDARY_IN_USE)

The TCP port used by the secondary server for receiving data is already in use.

=item Exit status 41 ($EXIT_SECONDARY_IPADDR)

The IP address of the primary server was missing from the B<--receivefrom>
commandline argument.

=item Exit status 42 ($EXIT_SECONDARY_SVCTAG)

The service tag of the primary server was missing from the B<--receivefrom>
commandline argument.

=item Exit status 43 ($EXIT_SECONDARY_READ_ERR)

There was a read error while reading data from primary server socket.

=item Exit status 44 ($EXIT_SECONDARY_WRITE_ERR)

There was a write error while writing data to the secondary server file system. 

=back


=head1 SEE ALSO

tar(1), openssl(1)


=cut
