#!/usr/bin/perl
# Copyright (c) 2012 Apple Inc. All Rights Reserved.
#
# IMPORTANT NOTE: This file is licensed only for use on Apple-branded
# computers and is subject to the terms and conditions of the Apple Software
# License Agreement accompanying the package this file is a part of.
# You may not port this file to another platform without Apple's written consent.

#################################   Constants  #################################
$ECHO = "/bin/echo";
$MKDIR = "/bin/mkdir";
$MKTEMP_PATH = "/usr/bin/mktemp";
$PBUDDY = "/usr/libexec/PlistBuddy";
$SQLITE = "/usr/bin/sqlite3";
$TAR = "/usr/bin/gnutar";
$SBS_CONF = "/Applications/Server.app/Contents/ServerRoot/private/etc/server_backup";
$SBSProgressLogFile = "/private/var/log/server_backup/sbs_progress.log";
$tmpPath = "/private/var/jabberd/tmp";
$sqliteDefaultDbPath = "/Library/Server/Messages/Data/sqlite/jabberd2.db";
$sqliteDbPath = "";
$ServiceConf = "75-MessageServer.plist";
$servermgrJabberConfigPath = "/Library/Preferences/com.apple.messageserver.plist";
$ServiceName = "MessageServer";
%$BigList = "";
%dbinfo = {};
@MasterFilesList;

$VERBOSE = 0;
$DEBUG = 0;
$FUNCLOG = 0;
$PROGRESS = 0;

# Enum for deciding what to do with output
($OUTPUT_NONE, $OUTPUT_DEBUG, $OUTPUT_VERBOSE, $OUTPUT_PROGRESS, 
	$OUTPUT_FUNCLOG, $OUTPUT_PRINT) = (0..5);

$LOG_PATH = &_get_log_path(@ARGV);   # global, for service-specific log path

if($ENV{DEBUG}) {$DEBUG = '1'; print "Message Server backup DEBUG = 1\n";}
if($ENV{VERBOSE}) {$VERBOSE = '1'; print "Message Server backup VERBOSE = 1\n";}
if($ENV{FUNCLOG} && $DEBUG) {$FUNCLOG = '1'; print "Message Server backup FUNCLOG = 1\n";}
if($ENV{PROGRESS} && $DEBUG) {$PROGRESS = '1'; print "Message Server backup PROGRESS = 1\n";}

&_log($OUTPUT_VERBOSE, 	"MessageServer_backup was called.\n");

ParseOptions();

if ($DEBUG) 
	{ &dumpAssociativeArray(@ARGV); }

# find sqlite database path
my $pbuddyDbPath = qx(${PBUDDY} -c \"Print :jabberdDatabasePath:\" $servermgrJabberConfigPath);
chomp($pbuddyDbPath);
if (-e $pbuddyDbPath) {
    $sqliteDbPath = $pbuddyDbPath;
} else {
    $sqliteDbPath = $sqliteDefaultDbPath;
}

&validateOptionsAndDispatch(@ARGV);
exit();

################################################################################
sub validateOptionsAndDispatch()
{
	%BigList = @_;
	my $nothing = 0;
	SWITCH: {
		if (uc($BigList{"-cmd"}) eq uc("actions")) { if($DEBUG) {print("actions\n");} Actions(); last SWITCH; }
		if (uc($BigList{"-cmd"}) eq uc("backup"))  { if($DEBUG) {print("backup\n");} Backup(); last SWITCH; }
		if (uc($BigList{"-cmd"}) eq uc("help"))    { if($DEBUG) {print("help\n");} Usage(); last SWITCH; }
		if (uc($BigList{"-cmd"}) eq uc("size"))    { if($DEBUG) {print("size\n");} Size(); last SWITCH; }
		if (uc($BigList{"-cmd"}) eq uc("version")) { if($DEBUG) {print("version\n");} Version(); last SWITCH; }
		$nothing = 1;
	}
	if($nothing == 1)
		{print("Legal options were not supplied!\n");Usage();}
}

################################################################################
sub Actions() 
{
	&_log($OUTPUT_FUNCLOG, "Start Actions-------------------------------------------------------+\n");

	&_log($OUTPUT_VERBOSE, 	(qq(${PBUDDY} -c \"Print :BackupActions\" $SBS_CONF/$ServiceConf) . "\n"));

	$Version = qx(${PBUDDY} -c \"Print :BackupActions\" $SBS_CONF/$ServiceConf);
	&_log($OUTPUT_PRINT, $Version);

	&_log($OUTPUT_FUNCLOG, "End   Actions-------------------------------------------------------+\n");

}

################################################################################
sub Backup()
{
	my $OPT = $BigList{"-opt"};
	my $cmd = "";
	my $jabberDataTmpDir = "";

	umask(077);

	&_log($OUTPUT_FUNCLOG, "Start Backup-------------------------------------------------------+\n");

    if ($OPT ne "configuration" && $OPT ne "data" && $OPT ne "all") {
        &_log($OUTPUT_PRINT, "No valid -opt selected for backup operation.\n");
        return 1;
    }

	#Create our service named folder in the image.
	my $SBS_PATH=$BigList{"-path"};
	if ($SBS_PATH eq "") {
		&_log($OUTPUT_PRINT, "No -path given. Aborting.\n");
		return 1;
	}

	if(! -e $SBS_PATH) {
		&_log($OUTPUT_DEBUG, "${MKDIR} $SBS_PATH\n");
		qx(${MKDIR} $SBS_PATH);
	}

	## Configs ##
	if ($OPT eq "all" || $OPT eq "configuration") {
		&_log($OUTPUT_PROGRESS, "Backing up configuration files", 0);

		# backup configs
		my $configTarPath = $SBS_PATH."/"."MessageServer.conf.tar.gz";
		my @configurationFiles;
		my @configurationFilesPbuddy = qx(${PBUDDY} -c \"Print :ConfigurationFilesToTar:\" $SBS_CONF/$ServiceConf);
        if ($#configurationFilesPbuddy > 1) {
            for (my $i = 1; $i < $#configurationFilesPbuddy; $i++) {
                my $configTmp = $configurationFilesPbuddy[$i];
                chomp($configTmp);
                $configTmp =~ s/^\s*//;
                push(@configurationFiles, $configTmp);
            }
        }

		if ($VERBOSE) {
			$cmd = "$TAR --ignore-failed-read -cvzf \"$configTarPath\"";
		} else {
			$cmd = "$TAR --ignore-failed-read -czf \"$configTarPath\"";
		}
		foreach $confFile (@configurationFiles) {
			# Not quoting confFile- allow for wildcard expansion since there may be any number of sm.xml files
			$cmd .= " $confFile";
		}
		&_log($OUTPUT_PRINT, "Executing command: $cmd\n");
		system($cmd);

		&_log($OUTPUT_VERBOSE, "Backed up configuration files to $configTarPath\n");

		push(@MasterFilesList, "MessageServer.conf.tar.gz");
		&_log($OUTPUT_PROGRESS, "Backing up configuration files", 100);
	}
	
	## Data ##
	if ($OPT eq "all" || $OPT eq "data") {
		&_log($OUTPUT_PROGRESS, "Backing up data files", 0);

		# backup database
		my $dataTarPath = $SBS_PATH."/"."MessageServer.data.tar.gz";
		my $sqlFilePath = "MessageServer_data_backup.sql";
		# clear out tmp dir
		if (-e $tmpPath) {
			$cmd = "rm -Rf \"$tmpPath/*\"";
			system($cmd);
		} else {
			`$MKDIR -p $tmpPath`;
			chown(84, 84, $tmpPath);
			chmod(0700, $tmpPath);
		}
		for (my $i = 0; $i < 5; $i++) {
			$cmd = "$MKTEMP_PATH -d $tmpPath/jabber_backup.XXXXXXXXXXXXXXXXXXXXXXXX";
			$jabberDataTmpDir = `$cmd`;
			chomp($jabberDataTmpDir);
			if (-e $jabberDataTmpDir) {
				last;
			}
			if ($i == 4) {
				die "Error: Cannot create temporary file:\n$jabberDataTmpDir";
			}
		}

		qx(echo ".dump" | $SQLITE $sqliteDbPath > \"$jabberDataTmpDir/$sqlFilePath\");

		my $origCWD = $ENV{PWD};
		chdir("$jabberDataTmpDir");
		if ($VERBOSE) {
			$cmd = "$TAR -cvzf \"$dataTarPath\" \"$sqlFilePath\"";
			print "Executing command: $cmd\n";
		} else {
			$cmd = "$TAR -czf \"$dataTarPath\" \"$sqlFilePath\"";
		}
		system($cmd);
		chdir($origCWD);
	   	unlink("$jabberDataTmpDir/$sqlFilePath");
        qx(rm -Rf $jabberDataTmpDir);

		push(@MasterFilesList, "MessageServer.data.tar.gz");
		&_log($OUTPUT_VERBOSE, "Backed up database to $dataTarPath\n");

		my $roomsDirPath = qx(${PBUDDY} -c \"Print :RoomsDataDirectory:\" $SBS_CONF/$ServiceConf);
		chomp($roomsDirPath);
		my $roomsLogDirPath = qx(${PBUDDY} -c \"Print :RoomsLogDataDirectory:\" $SBS_CONF/$ServiceConf);
		chomp($roomsLogDirPath);
		if ($roomsDirPath ne "") {
			chdir($origCWD);
			my $roomsDataTarPath = $SBS_PATH."/"."MessageServer_Rooms.data.tar.gz";
			if ($VERBOSE) {
				$cmd = "$TAR --ignore-failed-read -cpvzf \"$roomsDataTarPath\" \"$roomsDirPath\" \"$roomsLogDirPath\"";
				print "Executing command: $cmd\n";
			} else {
				$cmd = "$TAR --ignore-failed-read -cpzf \"$roomsDataTarPath\" \"$roomsDirPath\" \"$roomsLogDirPath\"";
			}
			system($cmd);
			chdir($origCWD);
			push(@MasterFilesList, "MessageServer_Rooms.data.tar.gz");
			&_log($OUTPUT_VERBOSE, "Backed up database to $roomsDataTarPath\n");
		}

		&_log($OUTPUT_PROGRESS, "Backing up data files", 100);

	}

	CreateBrowsePlist();

	&_log($OUTPUT_FUNCLOG, "End  Backup-------------------------------------------------------+\n");
}

################################################################################
sub CreateBrowsePlist()
{
	my $SBS_PATH=$BigList{"-path"};
	my $browseFilePath=$SBS_PATH . "/$ServiceName.browse.plist";

	open(OUT, ">$browseFilePath") or die ("Couldn't open the $browseFilePath file for writing.\n");
	select(OUT);

#Header
	print ('<?xml version="1.0" encoding="UTF-8"?>' . "\n");
	print ('<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' . "\n");
	print ('<plist version="1.0">' . "\n");
	print ('    <array>' . "\n");

	for($i=0; $i<=$#MasterFilesList; $i++) {
		$LG=$MasterFilesList[$i];
		chomp($LG);
		print '        <string>'.$LG.'</string>'. "\n";
	}

#Footer
	print ('    </array>' . "\n");
    print ("</plist>" . "\n");

	close(OUT);
	select(STDOUT);  #If you don't do this you will never see output!!!
}

################################################################################
sub Size()
{
	my $OPT = $BigList{"-opt"};
	my $jabberDataTmpDir = "";

	if ($OPT ne "configuration" && $OPT ne "data" && $OPT ne "all") {
		&_log($OUTPUT_PRINT, "No valid -opt selected for size operation.\n");
		&Usage;
	}

	umask(077);
	&_log($OUTPUT_FUNCLOG, "Start Size-------------------------------------------------------+\n");


	$totalsize = 0; #bytes
	if ($OPT eq "all" || $OPT eq "configuration") {
		my @configurationFiles;
		my @configurationFilesPbuddy = qx(${PBUDDY} -c \"Print :ConfigurationFilesToTar:\" $SBS_CONF/$ServiceConf);
		if ($#configurationFilesPbuddy > 1) {
			for (my $i = 1; $i < $#configurationFilesPbuddy; $i++) {
				my $configTmp = $configurationFilesPbuddy[$i];
				chomp($configTmp);
				$configTmp =~ s/^\s*//;
				push(@configurationFiles, $configTmp);	
			}
		}
		foreach my $configFile (@configurationFiles) {
			my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($configFile);
			$totalsize += $size;
		}
	}	

	if ($OPT eq "all" || $OPT eq "data") {
        # clear out tmp dir
		if (-e $tmpPath) {
			$cmd = "rm -Rf \"$tmpPath/*\"";
			system($cmd);
		} else {
			`$MKDIR -p $tmpPath`;
			chown(84, 84, $tmpPath);
			chmod(0700, $tmpPath);
		}
		for (my $i = 0; $i < 5; $i++) {
			$jabberDataTmpDir = `$MKTEMP_PATH -d $tmpPath/jabber_backup.XXXXXXXXXXXXXXXXXXXXXXXX`;
			chomp($jabberDataTmpDir);
			if (-e $jabberDataTmpDir) {
				last;
			}
			if ($i == 4) {
				die "Error: Cannot create temporary file:\n$jabberDataTmpDir";
			}
		}
		qx(echo ".dump" | $SQLITE $sqliteDbPath > \"$jabberDataTmpDir/$sqlFilePath\");

		my $dataTarPath = "$jabberDataTmpDir/MessageServer_data_backup_tmp.tar.gz";
		my $roomsDirPath = qx(${PBUDDY} -c \"Print :RoomsDataDirectory:\" $SBS_CONF/$ServiceConf);
		chomp($roomsDirPath);
		my $roomsLogDirPath = qx(${PBUDDY} -c \"Print :RoomsLogDataDirectory:\" $SBS_CONF/$ServiceConf);
		chomp($roomsLogDirPath);

		if ($VERBOSE) {
			$cmd = "$TAR --ignore-failed-read -cvzf \"$dataTarPath\" \"$jabberDataTmpDir/$sqlFilePath\" \"$roomsDirPath\" \"$roomsLogDirPath\"";
			&_log($OUTPUT_PRINT, "Executing command: $cmd\n");
		} else {
			$cmd = "$TAR --ignore-failed-read -czf \"$dataTarPath\" \"$jabberDataTmpDir/$sqlFilePath\" \"$roomsDirPath\" \"$roomsLogDirPath\"";
		}
		system($cmd);
		my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($dataTarPath);
		unlink($dataTarPath);
		unlink("$jabberDataTmpDir/$sqlFilePath");
		qx(rm -Rf $jabberDataTmpDir);
		$totalsize += $size;
	}

	$totalsize /= 1024; #make kbytes
	
	&_log($OUTPUT_PRINT, "total := ".int($totalsize)."\n");

	&_log($OUTPUT_FUNCLOG, "End Size-------------------------------------------------------+\n");
}

################################################################################
sub Version() 
{
	&_log($OUTPUT_FUNCLOG, "Start Version-------------------------------------------------------+\n");

	$Version = qx(${PBUDDY} -c \"Print :Version\" $SBS_CONF/$ServiceConf);
	&_log($OUTPUT_PRINT, $Version);

	&_log($OUTPUT_FUNCLOG, "End Version-------------------------------------------------------+\n");
}

################################################################################
# ParseOptions takes a list of possible options and a boolean indicating
# whether the option has a value following, and sets up an associative array
# %opt of the values of the options given on the command line. It removes all
# the arguments it uses from @ARGV and returns them in @optArgs.
#
sub ParseOptions {
	local (@optval) = @_;
	local ($opt, @opts, %valFollows, @newargs);

	while (@optval) {
		$opt = shift(@optval);
		push(@opts,$opt);
		$valFollows{$opt} = shift(@optval);
	}

	@optArgs = ();
	%opt = ();

	arg: while (defined($arg = shift(@ARGV))) {
		foreach $opt (@opts) {
			if ($arg eq $opt) {
				push(@optArgs, $arg);
				if ($valFollows{$opt}) {
					$opt{$opt} = shift(@ARGV);
					push(@optArgs, $opt{$opt});
				} else {
					$opt{$opt} = 1;
				}
				next arg;
			}
		}
		push(@newargs,$arg);
	}
	@ARGV = @newargs;
}

################################################################################
sub dumpAssociativeArray()
{
	%BigList = @_;
	while(($theKey, $theVal) = each (%BigList))
		{ print "$theKey is the key for value $theVal\n"; }
	if($BigList{"-cmd"} eq "backup")
		{ print "cmd := ", $BigList{"-cmd"}, "\n"; }
}

################################################################################
sub timestamp()
{
	my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
	localtime(time);
	$year += 1900;
	$mon  += 1;
	if ( $hour =~ /^\d$/ ) { $hour = "0" . $hour; }
	if ( $min  =~ /^\d$/ ) { $min  = "0" . $min; }
	if ( $sec  =~ /^\d$/ ) { $sec  = "0" . $sec; }

	my $ret = $year."-".$mon."-".$mday."-$hour:$min:$sec";
	#my $ret = $year."-".$mon."-".$mday;
	return $ret;
}

################################################################################
sub Usage()
{
	print "Usage:\n";
	print "Message_backup supports the following options:\n";
	print " -cmd backup -path path -log logPath \n";
	print " -cmd backup -path path -opt option\n";
	print "    where path is the path to the mounted image where the data was backed-up.\n";
	print "    where option is one of: configuration, data, all\n";
	print "    where logPath is the path to the backup log file for this service, it can be found in property list file := $SBS_CONF$ServiceConf\n";
	print " -cmd size -opt option :       Calculate size of the storage requirements for MessageServer.\n";
	print "    where option is one of: configuration, data, all\n";
	print " -cmd help :                   Displays this usage.\n";
	print " -cmd version :                Prints out the version value from the property list := $SBS_CONF$ServiceConf\n";
	exit(0);
}

################################################################################
# Handle the various output modes, log to our file
sub _log {
	my $type = shift;
	my $msg = shift;
	my $progress_percent = shift;  

	# For the progress log, just append to that log file and return
	if ($PROGRESS && ($type == $OUTPUT_PROGRESS)) {
		if (! defined($progress_percent)) {
			&_log($OUTPUT_DEBUG, "ERROR: Missing progress percentage");
			return;
		}
		my $pString = sprintf("\"## PROGRESS :: :: Message Server :: $msg :: %%%f \"", $progress_percent);
		qx($ECHO $pString >> $SBSProgressLogFile);
		return;
	} 
	
	# For everything else, log the message to our service-specific log file
	if ($LOG_PATH ne "") {
		open(OUT, ">>$LOG_PATH") || die "ERROR: Cannot open log file at path: $LOG_PATH : $!";
		print OUT &timestamp."\t".$msg;
		close(OUT);
	}

	# print to STDOUT if appropriate
	if (($type == $OUTPUT_PRINT)  ||
			($FUNCLOG && ($type == $OUTPUT_FUNCLOG)) ||
			($DEBUG && ($type == $OUTPUT_DEBUG)) ||
			($VERBOSE && ($type == $OUTPUT_VERBOSE))) {
		print $msg;
	}
}

# Pull the -log arg from argv
sub _get_log_path {
	my ($theKey, $theVal);
	my %theArgList = @ARGV;
	while(($theKey, $theVal) = each (%theArgList)) {
		if ($theKey eq "-log") {
			return $theVal;
		}
	}
	return "";
}
