#!/usr/bin/perl
#
# $Revision: 1.265.2.1 $
# Copyright 2009 Teleflora
#
# Script to perform backups and restores of an RTI and/or Daisy system.
#
# todo:
#	add command line option that will identify the following
#	about the backup set on the backup device:
#	* backup type
#	* compressed or not
#	* RTI or Daisy
#
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.265.2.1 $';
my $TIMESTAMP = strftime("%Y%m%d%H%M%S", localtime());
my $PROGNAME = basename($0);

my $HELP = 0;
my $VERSION = 0;
my @BACKUP = ();
my @RESTORE = ();
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 $DEVICE = "";
my $DVDDEV = "";
my $CRYPTKEY = "";
my $ROOTDIR = "/";
my $SECONDARY = 0;
my $SENDTO = "";
my @SENDTO_EXCLUDES = ();
my $RCVFROM = "";
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 $DEBUGLOGFILE = "";
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;

#
# Constants
#
# 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;


my $DEVICE_TYPE = "";		# Device types are "passport" or "rev".

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

#
# 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,
	"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" => \$UNMOUNT,
	"nocc" => \$NOCC,
	"backup=s" => \@BACKUP,
	"restore=s" => \@RESTORE,
	"rootdir=s" => \$ROOTDIR,
	"secondary" => \$SECONDARY,
	"sendto=s" => \$SENDTO,
	"sendto-excludes=s" => \@SENDTO_EXCLUDES,
	"receivefrom=s" => \$RCVFROM,
	"umount" => \$UNMOUNT,
	"daisy" => \$DAISY,
	"ddbackup" => \$DDBACKUP,
	"rti" => \$RTI,
	"list=s" => \@LIST,
	"checkfile" => \$CHECKFILE,
	"verbose" => \$VERBOSE,
	"force" => \$FORCE,
	"verify" => \$VERIFY,
	"logfile=s" => \$LOGFILE,
	"device=s" => \$DEVICE,
	"dvddev=s" => \$DVDDEV,
	"cryptkey=s" => \$CRYPTKEY,
	"saveall" => \$DSYSAVEALL,
	"autocheckmedia!" => \$AUTO_CHECKMEDIA,
	"compress" => \$COMPRESS_BU,
	"decompress" => \$DECOMPRESS_BU,
	"keep-old-files" => \$KEEP_OLD_FILES,
	"getinfo" => \$GETINFO,
);


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

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


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

# Where do our "OSTOOLS" typically reside?
if (-e "/teleflora/ostools/bin") {
	$TOOLSDIR = "/teleflora/ostools";
} elsif (-e "/usr2/ostools/bin") {
	$TOOLSDIR = "/usr2/ostools";
} elsif (-e "/d/ostools/bin") {
	$TOOLSDIR = "/d/ostools";
} elsif (-e "/d") {
	system("mkdir -p /d/ostools/bin");
	$TOOLSDIR = "/d/ostools";
} elsif (-e "/usr2") {
	system("mkdir -p /usr2/ostools/bin");
	$TOOLSDIR = "/usr2/ostools";
} else {
	system("mkdir -p /teleflora/ostools/bin");
	$TOOLSDIR = "/teleflora/ostools";
}

unless (-d "$TOOLSDIR/bin") {
	logerror("Required directory does not exist: \"$TOOLSDIR/bin\"... can't continue");
	exit(1);
}


$RTIDIR = "/usr2/bbx";
$DAISYDIR = "/d";


# --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 = "";
	my $logfilename = "";

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

	$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";
	}
	else {
		$logfilename = "rtibackup-Day_%d.log";
	}

	if ( ($logfiledir ne "") && ($logfilename ne "") ) {
		$LOGFILE = strftime("$logfiledir/$logfilename", localtime());
	}

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

if ($LOGFILE eq "") {
	$LOGFILE = "/dev/stdout";
}


loginfo("===========================================");
loginfo("Starting $PROGNAME $CVS_REVISION");
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";
	}
}
read_configfile($CONFIGFILE);

logrotate($LOGFILE);

# --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;
	}

	# Sort of a hail mary here. This would 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");
}

# 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=host.ip.address:svctag
if ($SENDTO ne "") {
	if ($SENDTO !~ /:/) {
		logerror("Incorrect format for --sendto commandline argument: $SENDTO");
		usage();
		exit(1);
	}

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

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

	exit(receive_from_primary_server());
}



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



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

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


# 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 either the docking
station, or, a  USB cable. Verify that the small white light is illuminated on the bottom of the passport drive.

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

	exit(5);
}


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.
			format_device_ext2(1);
		}
	} else {

		# --force --format will not ask questions.
		if($FORCE != 0) {
			format_device_ext2(1);
		} else {
			format_device_ext2(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 $result = "";

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

	for my $i (0 .. $#find_dev_table) {
		$result = $find_dev_table[$i][0];
		$result = "(device not found)" if ($result eq "");
		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(1);
		}
	}


	# 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) {
		push(@EXCLUDES, "/d/*.iso");
		push(@EXCLUDES, "/d/*.tar.asc");
		push(@EXCLUDES, "/d/daisy-*");
		push(@EXCLUDES, "/d/daisy/*.iso");
		push(@EXCLUDES, "/d/daisy/log/*");
		push(@EXCLUDES, "/d/daisy/*.tar.asc");
		push(@EXCLUDES, "/d/build/*");
	}

	$backup_returnval = backup_files();
}


# --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(1);
			}
		}
	}

	restore_files();
}


# --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(1);
		}
	}
	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();
	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;

	# --backup --eject
	# ... and backups succeeded.
	if($#BACKUP >= 0) {
		if ($backup_returnval == 0) {
			eject_devices();
		}
	} else {
		eject_devices();
	}
}


# --backup  + --verify + "ejectdays"
# Make sure this block of code comes *after* the "VERIFY" block of code.
if("$EJECTDAYS" ne "") {

	if ( ($#BACKUP >= 0) && ($VERIFY != 0)) {
		if($EJECTDAYS =~ /always/i) {
			showinfo("ejectdays set to 'always.' Ejecting Media.");
			eject_devices();

		} elsif($backup_returnval < 0) {
			# Don't eject if the backup failed.
			showinfo("backup_files() returned error \"$backup_returnval\". Will not eject.");
		} else {
			# Eject if our "eject days" matches.
			if($EJECTDAYS =~ /never/i) {
				showinfo("ejectdays set to 'never'. Will not eject.");
			} else {
				my $today = lc(strftime("%a", localtime()));
				if($EJECTDAYS =~ /$today/i) {
					showinfo("Successful backup. Ejecting Media.");
					eject_devices();
				} else {
					showinfo("Successful backup. ejectdays set to not eject media.");
				}
			}
		}
	}
}




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

exit(0);
#####################################################################
#####################################################################
#####################################################################


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 --checkmedia [--email=user1\@foo.com,user2\@bar.com,...] [--printer=printer1,printer2,...] \n");
	print("$PROGNAME --[no]autocheckmedia\n");
	print("$PROGNAME --sendto=secondary.ip.address:secondary_svctag\n");
	print("$PROGNAME --receivefrom=master.ip.address:mastersvctag\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("\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--dvddev=/dev/xxxx\n");
	print("\t--cryptkey=xxxx\n");
	print("\t--compress\n");
	print("\t--decompress\n");
	print("\t--daisy\n");
	print("\t--rti\n");
}





#
# Format our "device"using Linux EXT2 Filesystem.
# Note that this supports either block devices, or "loopback" image files.
#
sub format_device_ext2
{
	my $force = $_[0];
	my $returnval = 0;
	my $datestamp = "";
	my $answer = "";
	my $uuid = "";

	loginfo("Formatting Device \"$DEVICE\" (force=$force)");

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


	if( (! -b "$DEVICE") && (! -f "$DEVICE") ) {
		logerror("Device \"$DEVICE\" is not a file or block device. Cannot format.");
		return(-3);
	}
	if( (-f "$DEVICE") && ((-s "$DEVICE") <= 0) ) {
		logerror("Image File \"$DEVICE\" is zero bytes. Please use \"dd\" to create an image file.");
		return(-4);
	}



	# Don't format a mounted drive.
	if (is_device_mounted() == 0) {
		logerror("Backup Device is still mounted. Please manually --unmount before formatting.");
		return(-3);
	}



	#
	# 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("Device \"$DEVICE\" is not a file or block device. Cannot format.");
				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)
	$uuid = get_tape_id();
	if("$uuid" eq "") {
		$uuid = "random";
	}


	#
	# Format the cartridge.
	# Note that, under RHWS5 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.
	#
	$datestamp = strftime("%Y-%m-%d", localtime());
	showinfo("Formatting Device \"$DEVICE\" with EXT2 Tape ID \"$uuid\"...");
	system("/sbin/mkfs.ext2 -F -L \"$datestamp\" $DEVICE 2>> $LOGFILE");
	$returnval = $?;
	if($returnval != 0) {
		logerror("mkfs.ext2 failed with error \"$!\". Disk was not formatted.");
	}
	system("/sbin/tune2fs -c 0 -e \"remount-ro\" -i \"12m\" -U \"$uuid\" $DEVICE 2>> $LOGFILE");


	# Make subdirectories.
	mount_device("rw");
	if(is_device_mounted() != 0) {
		logerror("Could not mount \"$DEVICE\" after formatting.");
		return(-4);
	}
	mkdir("$MOUNTPOINT/configs");

	unmount_device();

	return($returnval);
}


sub form_tmpfile_name
{
        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]);
}


#
# 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 (is_device_mounted() != 0) {
		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");

	#
	# Read up to the first 1MB of the backup file so we can determine
	# whether it is a compressed file or not.
	#
	my $tmpfile = qx(mktemp /d/backup/rtibackup.pl.XXXXXXX);
	chomp($tmpfile);
	if ($tmpfile eq "") {
		my $timestamp = strftime("%Y%m%d%H%M%S", localtime());
		$tmpfile = "/d/backup/rtibackup.pl.$timestamp";
	}

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

	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
	);

	my $rc;

	foreach my $thisfile (@backup_files) {

		my $bu_file = "$MOUNTPOINT/$thisfile";
		unless (-f $bu_file) {
			next;
		}

		my $undo_cmd = "cat $bu_file | " . $decrypt_cmd;
		open(SRC, "$undo_cmd |");	

		open(DST, "> $tmpfile");

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

		open(PIPE, "file $tmpfile |");
		while(<PIPE>) {
			my $is_compressed = 0;
			if (/compressed data/) {
				$is_compressed = 1;
			}
			printf("$thisfile: %scompressed\n", ($is_compressed) ? "" : "not ");
		}
		close(PIPE);

		system("rm -f $tmpfile");
	}

	unmount_device();

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

	return(0);
}


#
# 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
{
	my $pid = -1;
	my $subject = "";
	my $results = "";
	my $tapeid = "";
	my @array = ();


	loginfo("Check Backup Media");
	# Verify that we are not already running in the background.
	open(PS, "ps wwaux |");
	while(<PS>) {
		chomp;
		# Look only for "rtibackup.pl --checkmedia" script invocations.
		next until(/($PROGNAME)(\s+)(--checkmedia)/);
		next if(/(\s+)($$)(\s+)/); # Don't count ourselves.
		@array =  split(/\s+/);
		$pid = int($array[1]);
	}
	close(PS);
	if($pid > -1) {
		showinfo("Checkmedia is already running as PID $pid");
		return(0);
	}



	# Don't format a mounted drive.
	if (is_device_mounted() == 0) {
		unmount_device();
	}
	if (is_device_mounted() == 0) {
		logerror("Error: could not unmount backup device. Please manually --unmount first.");
		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. 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.
	if($#BACKUP >= 0) {
		$pid = 0;
	} else {
		$pid = fork();
	}


	if($pid > 0) {
		showinfo("Checkdisk background process started as PID $pid");
	} elsif($pid == 0) {

		$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");
		}

	}
}


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

	if ( ($DEVICE ne "") && ($DEVICE_TYPE eq "passport") ) {
		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($?);
}





#
# Grab "important" OS settings and put them in the /usr2 tree. By doing so, we only backup /usr2
# and are able to restore everything needed.
#
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);
	}

	# Make sure we can mount our backup device.
	mount_device("rw");
	if(is_device_mounted() != 0) {
		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;




	# 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 make for an easier bare-metal restore.
	# This is relatively safe from the PCI perspective, as, we use a service tag number for the cryptkey
	# which is not 'hard coded' into the backup script.
	system("cp $0 $MOUNTPOINT");


	# Once we are done backing up, the end-user proabbly expects to be able to hit the eject button
	# on the drive in order to eject the cartridge. Unmounting the drive will ensure that user capability.
	unmount_device();

	return($total_returnval);
}


#
# 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 $cryptkey = "";
	my $command = "";
	my $secondary_ip =  "";
	my $secondary_svctag = "";
	my $buffer;
	my $returnval = 255;

	#
	# "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(6);
	}
	if ($secondary_svctag eq "") {
		logerror("Service tag of secondary server missing from --sendto commandline argument.");
		return(6);
	}

	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 = "";
	open(HOST, "hostname |");
	while(<HOST>) {
		chomp;
		$primary_hostname = $_;
	}
	close(HOST);

	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 = form_tmpfile_name("/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 ---");
	}

	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.
	$cryptkey = uc($CRYPTKEY) . uc($secondary_svctag);

	# Form the tar command line
	my $tar_cmd = "tar $exclude_opt -cvf - $backup_file_set";
	my $encrypt_cmd = "nice openssl aes-128-cbc -e -salt -k \"$cryptkey\"";
	$command = "$tar_cmd 2>> $LOGFILE | $encrypt_cmd 2>> $LOGFILE";

	# Connect to our master 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(7);
	}

	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 = -5;
			last;
		}
		if(! syswrite($remote, $buffer, $bytecount)) {
			$logerror_msg = "Error \"$!\" writing backup to socket.";
			$returnval = -6;
			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 $cryptkey = "";
	my $primary_ip =  "";
	my $primary_svctag =  "";
	my $buffer = "";
	my $pos_name = "";
	my $tar_dstdir = "";
	my $bytecount = -1;
	my $returnval = 255;
	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(11);
		}
	}
	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(6);
	}
	if ($primary_svctag eq "") {
		logerror("Service tag of primary server missing from --receivefrom commandline argument.");
		return(6);
	}
	$cryptkey = uc($primary_svctag) . uc("$CRYPTKEY");


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

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

		showinfo("Started 'receive' daemon as PID $pid");
		return(0);
	}

	#########
	# 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 tar command pipe
	my $encrypt_cmd = "openssl aes-128-cbc -d -salt -k \"$cryptkey\"";
	my $tar_cmd = "tar -C $tar_dstdir -xvf -";
	my $command = "$encrypt_cmd | $tar_cmd >> $LOGFILE 2>> $LOGFILE";

	while(1) {

		# 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("Listening for connection from incoming $pos_name primary server...");

		$connection = $remote->accept();
		if (! $connection) {
			loginfo("Error Accepting Connection. \"$!\"");
			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;
		}

		loginfo("Accepted connection from $peer_ip...");

		# Actually transact data here.
		if($connection) {
			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 = -5;
					last;
				}
				if(! syswrite(TAR, $buffer, $bytecount)) {
					logerror("Error \"$!\" writing backup to tar.");
					$returnval = -6;
					last;
				}
			}
			close($connection);
			close(TAR);
		}
	}

	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/*
/var/log/faillog
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

	# 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 backup_userconfigs
{
	my @toback = ();

	mount_device("rw");
	if(is_device_mounted() != 0) {
		return(-1);
	}

	showinfo("Saving POS User Configs...");

	# RTI Users.
	if (-f "$RTIDIR/bin/rtiuser.pl") {
		system("$RTIDIR/bin/rtiuser.pl --list > $MOUNTPOINT/configs/rtiusers.txt 2>> $LOGFILE");
	}
	# Daisy Users.
	if (-f "$DAISYDIR/daisy/bin/dsyuser.pl") {
		system("$DAISYDIR/daisy/bin/dsyuser.pl --list > $MOUNTPOINT/configs/daisyusers.txt 2>> $LOGFILE");
	}

	#
	# 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/log/faillog
		/var/spool/cron
		/home
		/root
	);

	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 Saving 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(is_device_mounted() != 0) {
		return(-1);
	}

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

	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/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 Saving 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(is_device_mounted() != 0) {
		return(-1);
	}

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

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


	# OS Configs
	@toback = ();
	foreach(
	"/etc/ssh", # tfremote
	"/etc/samba",
	"/etc/httpd/conf.d/rti.conf", # RTI v14
	"/etc/mail", # RTI v12 outbound emails
	"/etc/cron.d/rti",
	"/etc/cron.d/rti-service",
	"/etc/cron.d/daisy",
	"/etc/cron.d/daisy-service",
	"/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",
	"$RTIDIR/bin/tfremote.pl",
	"$DAISYDIR/daisy/bin/tfremote.pl",
	"/etc/inittab", # Digi portserver drivers
	"/usr/src/redhat", # Digi portserver drivers
	) {
		next if ( $DAISY && (/(httpd|rti|usr2|\/usr\/src\/redhat)/) );
		if(! -e $_) {
			logerror("Skipping backup of \"$_\". File not found.");
		} else {
			push(@toback, $_);
		}
	}
	$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");


	# Latest redhat kickstart file(s)
	# In case we need to kickstart without the internet available.
	logdebug("Saving Redhat Kickstart Files...");
	system("wget -O $MOUNTPOINT/onedisk.ks http://rtihardware.homelinux.com/ks/onedisk.ks");
	system("wget -O $MOUNTPOINT/fc5.ks http://rtihardware.homelinux.com/ks/fc5.ks");


	# "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 Saving 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(is_device_mounted() != 0) {
		return(-1);
	}

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

	showinfo("Saving 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");
	# this path does not exist on all platforms
	push(@toback, "/etc/sysconfig/networking/devices/ifcfg*");
	push(@toback, "/etc/sysconfig/network-scripts/ifcfg*");
	$returnval += create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/netconfigs.bak");


	loginfo("END Saving 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(is_device_mounted() != 0) {
		return(-1);
	}

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


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

	# RTI Users.
	system("$RTIDIR/bin/rtiuser.pl --list > $MOUNTPOINT/configs/rtiusers.txt 2>> $LOGFILE");


	# 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, "$RTIDIR/config/");
	push(@toback, "/var/spool/fax");
	push(@toback, "/usr/local/lib/BITMAPS");
	$returnval = create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/rticonfigs.bak");


	loginfo("END Saving 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(is_device_mounted() != 0) {
		return(-1);
	}

	showinfo("Saving Daisy Configs...");


	if(! -d "$MOUNTPOINT/configs") {
		mkdir("$MOUNTPOINT/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 Saving 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(is_device_mounted() != 0) {
		return(-1);
	}

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

	# 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.
	system("ls -l --inode --recursive /usr2/bbx/bbxd > $MOUNTPOINT/configs/bbxd-filelist.txt");
	system("ls -l --inode --recursive  /usr2/bbx/bbxps > $MOUNTPOINT/configs/bbxps-filelist.txt");

	loginfo("END Saving RTI Data ...");
	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(is_device_mounted() != 0) {
		return(-1);
	}

	loginfo("Invoked with command line: $0 @ARGV_ORIG\n");

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

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

	loginfo("Backing up files: @toback\n");

	$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 Saving 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(is_device_mounted() != 0) {
		return(-1);
	}

	showinfo("Saving Altiris Data ...");

	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);
	}


	# Altiris
	@toback = ();
	push(@toback, "/opt/altiris");
	push(@toback, "/etc/rc.d/init.d/altiris");
	create_tarfile(\@toback, \@EXCLUDES, "$MOUNTPOINT/configs/altiris.bak");


	loginfo("END Saving Altiris Data ...");
	return(0);
}

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

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

	mount_device("rw");
	if(is_device_mounted() != 0) {
		return(-1);
	}

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

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


	# User specified files.
	@emptyarray = ();
	create_tarfile(\@USERFILES, \@emptyarray,  "$MOUNTPOINT/userfiles.bak");


	loginfo("END Saving \"User 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 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(/WD/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 Wenstern
		# 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(/My Passport/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 0 means "yes, is mounted"
# Returnval non-zero means "no, is not mounted."
#
sub is_device_mounted
{
	my $returnval = 1;

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


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

	return($returnval);
}


#
# 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(is_device_mounted() != 0) {
			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(is_device_mounted() == 0) {
		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\" (Tape ID: \"$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
{
	my $tapeid = "";
	my $returnval = -1;


	if( "$DEVICE" ne "") {
		if(is_device_mounted() == 0) {
			$tapeid = get_tape_id();
			showinfo("Un-mounting \"$DEVICE\" (Tape ID: \"$tapeid\") ...");
			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 Creating \"$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?
		open(HOST, "hostname |");
		while(<HOST>) {
			print(INFO "# Hostname: ");
			print(INFO);
		}
		close(HOST);

		# 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 Creating $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.
		foreach("$tarpath", "$tarpath.info", "$tarpath.allmd5") {
			system("chown root:root $_");
			system("chmod 444 $_");
		}

		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");
	}
	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;

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

	while(<PIPE>) {
		chomp;
		loginfo("$_");
		if($VERBOSE != 0) {
			print($_);
		}
	}
	close(PIPE);
	unlink("/tmp/restore-excludes.$$");
	showinfo("===== END Restore $tarfile =====");
}


#
# 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(is_device_mounted() != 0) {
		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 = $2;
			}
		}
		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 .= "############################################################\r\n";
	if($VERBOSE != 0) {
		$status .= "######## Backup DEVICE: $DEVICE\r\n";
		$status .= "######## DVD Device: $DVDDEV\r\n";
	}

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


	# How much space is left?
	if(is_device_mounted() == 0) {
		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\r\n";
			} elsif ( int($array[3]) < 1000 ) {
				$status .= "wwwwwwww LOW SPACE AVAILABLE ($df_avail GB) on Backup Device\r\n";
			} else {
				$status .= "######## SPACE REMAINING: $df_avail / $df_size GB \r\n";
			}

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


	# Summary information about a particular backup.
	if (is_device_mounted() == 0) {

		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
		);

		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\r\n";
						$VERIFY_FAILED = 1;
					}

					if(! -f "$MOUNTPOINT/$thisfile.info") {
						$status .= "!!!!!!!! ERROR: Checksum File not Found: $MOUNTPOINT/$thisfile.info\r\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\r\n";
						$VERIFY_FAILED = 1;
					}

					if(! -f "$MOUNTPOINT/$thisfile.info") {
						$status .= "!!!!!!!! ERROR: Checksum File not Found: $MOUNTPOINT/$thisfile.info\r\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\r\n";
					}
					next;
				} else {
					if(! -f "$MOUNTPOINT/$thisfile") {
						$status .= "!!!!!!!! ERROR: Backup File not Found: $MOUNTPOINT/$thisfile\r\n";
						$VERIFY_FAILED = 1;
					}
					if(! -f "$MOUNTPOINT/$thisfile.info") {
						$status .= "!!!!!!!! ERROR: Checksum File not Found: $MOUNTPOINT/$thisfile.info\r\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 .= "#######$_\r\n";
				}
				if(/Backup Status:/) {
					if(! /(Backup Status:)(\s+)(SUCCESS)/) {
						$status .= "!!!!!!!! ERROR occurred during Backup of $thisfile\r\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)\r\n";
				$VERIFY_FAILED = 1;
			} else {
				$status .= "######## VERIFY SUCCEEDED: $thisfile ($backuptime)\r\n";
			}
		}
	}

	unmount_device();


	$status .= "############################################################\r\n";
	showinfo("---- END Verify ----");

	return($status);
}

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

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

	mount_device("ro");
	if (is_device_mounted() != 0) {
		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(/^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 $thisfile = "";
	my $returnval = 0;
	my $answer = "";
	my $tapeid = "";

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

	mount_device("ro");
	if(is_device_mounted() != 0) {
		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();
		}
	}

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

	# 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();
		}
	}


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

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


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

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


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


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


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


	# 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 $thisfile = "";
	my $returnval = -1;
	my @excludes = ();

	mount_device("ro");
	if(is_device_mounted() != 0) {
		return(-1);
	}

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


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

	# Bring down 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");
	}

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


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


	# Make correct symlink for tcc based on which OS we are on.
	open(PIPE, "uname -p |");
	while(<PIPE>) {
		if(/i686/) {
			system("ln -sf /usr2/bbx/bin/tcc_linux /usr2/bbx/bin/tcc");
		} elsif (/x86_64/) {
			system("ln -sf /usr2/bbx/bin/tcc_x64 /usr2/bbx/bin/tcc");
		}
	}
	close(PIPE);

	set_rti_perms();

	showinfo("*** Make sure to restart 'bbj', 'rti' and 'httpd' services.");
	loginfo("END Restore RTI Data...");

	return($returnval);
}



sub restore_rticonfigs
{
	my $thisfile = "";
	my $returnval = -1;
	my @excludes = ();


	mount_device("ro");
	if(is_device_mounted() != 0) {
		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", \@excludes);

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

		harden_pos($RTIDIR);

		set_rti_perms();
	}

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

	return($returnval);
}


sub restore_daisy
{
	my $thisfile = "";
	my $returnval = -1;
	my @excludes = ();
	my $clobber_daisy = 0;

	mount_device("ro");
	if(is_device_mounted() != 0) {
		return(-1);
	}

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


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

	#
	# 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 ($clobber_daisy) {
		system("/sbin/init 4");
		system("$DAISYDIR/daisy/utils/killemall 2>> $LOGFILE");
	}

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

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

		harden_pos("$DAISYDIR/daisy");

		set_daisy_perms();

	}

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

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

	return($returnval);
}

sub restore_dsyconfigs
{
	my $thisfile = "";
	my $returnval = -1;
	my @excludes = ();
	my $clobber_daisy = 0;

	mount_device("ro");
	if(is_device_mounted() != 0) {
		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.
	#
	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", \@excludes);

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

		harden_pos("$DAISYDIR/daisy");

		set_daisy_perms();

	}

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

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

	return($returnval);
}

sub restore_userconfigs
{
	my $thisfile = "";
	my $returnval = -1;
	my @excludes = ();

	mount_device("ro");
	if(is_device_mounted() != 0) {
		return(-1);
	}


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

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


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

	return($returnval);
}



sub restore_altiris
{
	my $thisfile = "";
	my $returnval = -1;
	my @excludes = ();

	mount_device("ro");
	if(is_device_mounted() != 0) {
		return(-1);
	}


	showinfo("Restore Altiris ...");

	# Restore Altiris
	$returnval = restore_tarfile("$MOUNTPOINT/configs/altiris.bak", \@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 $thisfile = ""; 
	my $returnval = -1;
	my @excludes = ();

	mount_device("ro");
	if(is_device_mounted() != 0) {
		return(-1);
	}


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

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

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

	return($returnval);
}


sub restore_osconfigs
{
	my $thisfile = "";
	my $returnval = -1;
	my $datestamp = "";
	my @excludes = ();

	mount_device("ro");
	if(is_device_mounted() != 0) {
		return(-1);
	}


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

	# What *not* to restore
	if(-f "/etc/httpd/conf.d/rti.conf") {
		push(@excludes, "etc/httpd/conf.d/rti.conf");
	}
	if(-f "/etc/sysconfig/rhn/systemid") {
		push(@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 Network Configurations
	$returnval = restore_tarfile("$MOUNTPOINT/configs/osconfigs.bak", \@excludes);
	if($returnval == 0) {
		system("/sbin/service rhnsd restart 2>> $LOGFILE");
		system("/sbin/service syslog restart 2>> $LOGFILE");
		system("/sbin/service sendmail restart 2>> $LOGFILE");
		system("/sbin/service samba 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");
		}

		harden_pos($RTIDIR);

	}

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

	return($returnval);
}


sub restore_netconfigs
{
	my $thisfile = "";
	my $returnval = -1;
	my $oldfile = "";
	my $newfile = "";
	my @excludes = ();

	mount_device("ro");
	if(is_device_mounted() != 0) {
		return(-1);
	}


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

	# Don't restore ifcfg-eth0 if a static IP address is already in place on this box.
	#open(CFG, "< /etc/sysconfig/ifcfg-eth0");
	#while(<CFG>) {
	#	if( (/^(\s*)(IPADDR)(\s*)(=)(\s*)(192)(\.)(168)/)
	#	or  (/^(\s*)(IPADDR)(\s*)(=)(\s*)(10)(\.)/) ) {
	#		push(@excludes, "/etc/sysconfig/ifcfg-eth0");
	#		last;
	#	}
	#}
	#close(CFG);


	# Restore Network Configurations
	$returnval = restore_tarfile("$MOUNTPOINT/configs/netconfigs.bak", \@excludes);
	if($returnval == 0) {
		system("/sbin/service network restart 2>> $LOGFILE");
	}

	# 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");
	}
	system("/sbin/service network restart");

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

	return($returnval);
}


sub restore_printconfigs
{
	my $thisfile = "";
	my $returnval = -1;
	my @excludes = ();

	mount_device("ro");
	if(is_device_mounted() != 0) {
		return(-1);
	}


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

	# Restore Printer Configurations
	$returnval = restore_tarfile("$MOUNTPOINT/configs/printconfigs.bak", \@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(is_device_mounted() != 0) {
		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
			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);
}


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

	# first decide which command to run
	if ($posdir eq $RTIDIR) {
	    $harden_cmd = "$posdir/bin/harden_rti.pl";
	}
	if ($posdir eq "$DAISYDIR/daisy") {
	    $harden_cmd = "$posdir/bin/harden_linux.pl";
	}

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


#
# 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 $timestamp = "";
	my $recipient = "";
	my $hostname = "";
	my $returnval = -1;

	if ($EMAIL_SERVER eq "") {
		# No SMTP email server defined; just use Mutt (and local sendmail.)
		$returnval = send_email_mutt(\@recipients, $subject, $message);
		if($returnval >= 0) {
			return($returnval);
		}
	}
	elsif ($EMAIL_SERVER eq 'sendmail') {
		# Use the sendmail program directly
		$returnval = send_email_sendmail(\@recipients, $subject, $message);
		if ($returnval >= 0) {
			return($returnval);
		}
	}
	else {
		# If we are confiured to do so, try sending emails 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);
		}
	}


	# Pick out our hostname and tack onto end of our subject.
	open(PIPE, "hostname |");
	while(<PIPE>) {
		chomp;
		$hostname = $_;
		last;
	}
	close(PIPE);
	$subject .= " $hostname";



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



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


#
# 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);
	}
}



#
# 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 = "";
	my $smtp = "";

	if($#recipients < 0) {
		return(-1);
	}
	if("$server" eq "") {
		return(-2);
	}
	if("$username" eq "") {
		return(-3);
	}
	if("$password" eq "") {
		return(-4);
	}



	# Pick out our hostname and tack onto end of our subject.
	open(PIPE, "hostname |");
	while(<PIPE>) {
		chomp;
		$hostname = $_;
		last;
	}
	close(PIPE);
	$subject .= " $hostname";


	foreach $recipient(@recipients) {

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

		# 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("Could not connect to SMTP server \"$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 = "";

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

	foreach $recipient(@recipients) {
		next if("$recipient" eq "");
		loginfo("Sending email to $recipient");
		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 = qx(hostname);
	chomp($hostname);
	unless ($hostname ne "") {
	    logerror("Can't send email: hostname is null");
	    return(1);
	}

	# 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")) {
		    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);
}


sub install_rtibackup
{
	loginfo("---- BEGIN Installation $PROGNAME $CVS_REVISION ----");

	#
	# Copy ourselves 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 we thus avoid a nasty diagnostic
	#
	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");
	}

	# 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");
	}

	# Make a new combined cron job file
	open(FILE, "> /etc/cron.d/nightly-backup");

	# 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 /etc/cron.d/nightly-backup");
	system("chmod 644 /etc/cron.d/nightly-backup");

	# 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.
	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 3 rtibackup-secondary off");
		system("/sbin/chkconfig --level 4 rtibackup-secondary off");
		system("/sbin/chkconfig --level 5 rtibackup-secondary off");
	}





	# Write a default config file if need be.
	if(-d "/usr2/bbx/config") {
		install_configfile("/usr2/bbx/config/backups.config");
		system ("chown tfsupport:rtiadmins /usr2/bbx/config/backups.config");
		system ("chmod 554 /usr2/bbx/config/backups.config");
	}
	if(-d "/d/daisy/config") {
		install_configfile("/d/daisy/config/backups.config");
		system ("chown tfsupport:dsyadmins /d/daisy/config/backups.config");
		system ("chmod 554 /d/daisy/config/backups.config");
	}

	loginfo("---- END rtibackup installation ----");
}


#
# 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

	# Add any email addresses which we found from ltar.cfg
	foreach(@email_addrs) {
		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


#
# 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


#
# 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, 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


#
# 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 ----");
}



#
# 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");
}


#
# Read config file.
#
sub read_configfile
{
	my $configfile = $_[0];

	if(! $configfile) {
		return(0);
	}
	if("$configfile" eq "") {
		return(0);
	}
	if(! -e "$configfile") {
		logerror("Config File \"$configfile\" does not exist.");
		return(0);
	}

	loginfo("Reading Configfile \"$configfile\"");
	open(CONFIG, "< $configfile");
	while(<CONFIG>) {


		# printer=cups_prn_name
		# Multiple of these are allowed.
		if(/^(\s*)(printer)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			push(@PRINTER, $6);
			loginfo("\tConfigfile: --printer=\"$6\"");
		}

		# email=someone@foo.com
		# Multiple of these are allowed.
		if(/^(\s*)(email)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			push(@EMAIL, $6);
			loginfo("\tConfigfile: --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;
			loginfo("\tConfigfile: Email Server =\"$6\"");
		}
		if(/^(\s*)(email_username)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$EMAIL_USER = $6;
			loginfo("\tConfigfile: Email Username=\"$6\"");
		}
		if(/^(\s*)(email_password)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$EMAIL_PASS = $6;
			loginfo("\tConfigfile: 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);
			loginfo("\tConfigfile: Will exclude backup 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);
			loginfo("\tConfigfile: userfile=\"$6\"");
		}


		# Which backup device to use.
		# device=/dev/whatever
		if(/^(\s*)(device)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$DEVICE = $6;
			loginfo("\tConfigfile: --device=$DEVICE");
		}



		# 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;
				loginfo("\tConfigfile: --dvd");
			}
		}



		# Which DVD backup device to use.
		# dvddev=/dev/whatever
		if(/^(\s*)(dvddev)(\s*)(=)(\s*)([[:print:]]*)$/i) {
			$DVDDEV = $6;
			loginfo("\tConfigfile: --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;
			}
			loginfo("\tConfigfile: --ejectdays=$EJECTDAYS");
		}


		# Specify sending machine (Server to server copy)
		# receivefrom=server.ip.address:SVCTAG
		if(/^(\s*)(receivefrom)(\s*)(=)(\s*)([[:print:]]+)$/i) {
			$RCVFROM = uc($6);
			loginfo("\tConfigfile: --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;
				loginfo("\tConfigfile: --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;
				loginfo("\tConfigfile: --decompress");
			}
		}

		# 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;
				loginfo("\tConfigfile: --autocheckmedia");
			}
		}

		# 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;
				loginfo("\tConfigfile: --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;
				loginfo("\tConfigfile: --daisy");
			}
		}
	}
	close(CONFIG);
}


sub get_command_line
{
	my $cmd_line = "";

	$cmd_line = $0;
	foreach my $i (@ARGV) {
		$cmd_line .= " ";
		if ($i =~/\s/) {
			$cmd_line .= "\"$i\"";
		} else {
			$cmd_line .= "$i";
		}
	}

	return($cmd_line);
}


# 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" which is defined to be
	# remove tomorrow's instance.
	#
	if ($logfile =~ /(.*)-(Day_\d\d)\.log/) {
		my $logfiletype = $1;
		my $tomorrow = strftime("Day_%d", localtime(time() + (60*60*24))) . ".log";
		if (unlink "$logfiletype-$tomorrow") {
			loginfo("Pseudo rotation: log file $logfiletype-$tomorrow removed");;
		}
	}
}


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);
}

