#!/usr/bin/perl
#
# Copyright (c) 2006 Zmanda Inc.  All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Contact information: Zmanda Inc, 505 N Mathlida Ave, Suite 120
# Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
#
#

#This plugin uses ssh and tar to copy data between the localhost and remote host
# Command line parameters to the plugin are 
# --source-host <name>, 
# --source-file <filename>, 
# --destination-host <name>, 
# --destination-directory <destination directory>

use strict;
use warnings;
use Getopt::Long;
use File::Basename;
use File::Temp qw/ :POSIX /;
use File::Spec::Functions;

# Set remote-mysql-binpath in the mysql-zrm.conf if a different path is needed.
my $REMOTE_MYSQL_BINPATH="/usr/bin";

# Set ssh-user in the mysql-zrm.conf if a different user is needed.
# The user specified should be the user mysqld runs as or it should be root.
my $REMOTE_USER="mysql";

$ENV{"PATH"}="/usr/local/bin:/opt/csw/bin:/bin:/usr/bin:/sbin:/usr/sbin";

# This is the temporary directory on the remote host. 
# that is used to keep data before transferring to the backup host.
# This gets inherited from mysql-zrm. If that should not be used then
# uncomment and modify this if some other directory is to be used
#$ENV{'TMPDIR'}="/tmp";


my $SSH="ssh";
my @SSH_ARR;
my $TAR="gtar";
my $RM="rm";
my $MKDIR="mkdir";
my $LS="ls";
my $compress="";


my $MYSQL_BINPATH="/usr/bin";
my $MYSQLHOTCOPY="mysqlhotcopy";

# TMP_DIR is where mysqlhotcopy will copy the data 
# to on the remote host.
my $TMP_DIR=tmpnam();

my $srcHost = "localhost";
my $destHost = "localhost";
my $host;
my $srcDir;
my $srcFile;
my $destDir;
my $action;
my $params;
my @snapshotParamList;

$SIG{'TERM'} = sub { &removeTmpDir(); die "TERM broke\n"; };

# Parses the command line for all of the copy parameters
sub getCopyParameters()
{
	my %opt;

	my $ret = GetOptions( \%opt, 
			"source-host=s", 
			"source-file=s", 
			"create-link",
			"destination-host=s", 
			"ssh-user=s",
			"destination-directory=s" );

	unless( $ret ){
		die( "Invalid parameters" );
	}

	if( $opt{"source-host"} ){
		$srcHost = $opt{"source-host"};
	}
	if( $opt{"destination-host"} ){
		$destHost = $opt{"destination-host"};
	}

	if( !$opt{"destination-directory"} ){
		die( "No destination directory defined\n" );
	}else{
		$destDir = $opt{"destination-directory"};
	}

	if( !$opt{"source-file"} ){
		die( "No source file defined\n" );
	}else{
		$srcFile = $opt{"source-file"};
	}
	
	if( defined $opt{"ssh-user"} ){
		$REMOTE_USER = $opt{"ssh-user"};
	}
	
	my @suf;
	$srcFile =~ s/^"//g;
	$srcFile =~ s/"$//g;
        $srcDir = dirname( $srcFile );
        $srcFile = basename( $srcFile, @suf );
	$srcDir = "\"$srcDir\"";
	$srcFile = "\"$srcFile\"";
	if( defined $opt{"create-link"} ){
		$action = "create-link";
	}else{
		if( $srcHost ne "localhost" && $destHost ne "localhost" ){
			$action = "copy between";
		}else{
			$action = "copy";
		}
	}
	
}

# Parses the command line for the mysqlhotcopy parameters
sub getMySQLHotCopyParameters()
{
        my $y = shift @ARGV;
	my @x = split( /=/, $y );
	my $l = @x;
	if( $l > 1 ){
		$MYSQL_BINPATH = $x[1];
	}
        $destDir= pop @ARGV;
        my %opt;
        GetOptions( \%opt,
                        "host=s",
                        "user=s",
                        "password=s",
                        "socket=s",
                        "port=s",
                        "quiet" );
        $params = "";
        for( keys%opt ){
                if( $_ ne "quiet" ){
                        $params .= " --$_=\"$opt{$_}\"";
                }else{
                        $params .= " --$_";
                }
        }
        foreach( @ARGV ){
                $params .= " $_";
        }
	if( $opt{"host"} ){
		$host = $opt{"host"};
	}
	
        if( ! $host || $host eq "localhost" ){
                exit( system( "$MYSQL_BINPATH/mysqlhotcopy $params $destDir" ) );
        }
        $action = "mysqlhotcopy";
}

#gets parameters for remove backup data
sub getRemoveBackupParams()
{
        my $y = shift @ARGV;
        my %opt;
        GetOptions( \%opt,
                        "host=s",
                        "backup-dir=s",
			"type-of-dir=s",
                        "backup-id=i" );
	$host = $opt{"host"};
	my $dir = $opt{"backup-dir"};
	if( defined $opt{"backup-id"} ){
		my $id = $opt{"backup-id"};
        	$params = "$dir/BACKUP/BACKUP-$id";
	}else{
		my $r = "";
		if( $opt{"type-of-dir"} eq "LINKS" ){
			$r = "ZRM_LINKS";	
		}else{
			$r = "ZRM_MOUNTS";
		}
		$params = $dir."/".$r;
	}
	
	$action = "remove-backup-data";
}

sub getSnapshotParams()
{
	my $y = shift @ARGV;
	my %opt;
	GetOptions( \%opt,
			"host=s",
			"snapshot-parameters=s" );
	$host = $opt{"host"};
	$params = $opt{"snapshot-parameters"};
	$action = "snapshot";
}

# This will parse the command line arguments
sub getInputs()
{
	my $len = @ARGV;
	if( $len == 0 ){
		die "This plugin is meant to be invoked from mysql-zrm only\n";
	}
        if( $ARGV[0]=~/^--mysqlhotcopy/ ){
                getMySQLHotCopyParameters();
	}elsif( $ARGV[0]=~/^remove-backup-data/ ){
		getRemoveBackupParams();
        }elsif( $ARGV[0]=~/^--snapshot-command/ ){
		getSnapshotParams();
	}else{
                getCopyParameters();
        }
}

sub doCreateLinks()
{
	my $dir;
	my $link;
	my $sd = $srcDir;
	$sd =~ s/^"//g;
	$sd =~ s/"$//g;
	
	my $sf = $srcFile;
	$sf =~ s/^"//g;
	$sf =~ s/"$//g;
	my $tmpFile = "$sd/$sf";
	$host = $destHost;
	unless( open( TP, $tmpFile ) ){
		die "Unable to open link details file\n";
	}
	my @d = <TP>;
	close TP;
	chomp( @d );
	my $l = @d;
	my $i;
	unless( open( TP, ">$tmpFile" ) ){
		die "Unable to open link details file to write\n";
	}
	print TP "#!/bin/sh\n";
	for( $i = 0; $i < $l; $i += 2 ){
		print TP "mkdir -p $d[$i]\n";
		print TP "ln -s $d[$i+1]\n";
	}
	close TP;
	chmod( 0700, $tmpFile );
	my $r = system( @SSH_ARR, "$REMOTE_USER\@$host", $MKDIR, $TMP_DIR );
	if( $r > 0 ){
		unlink( $tmpFile );	
		die( "Could not create directory $TMP_DIR on host $host" );
	}

	my $f = basename($tmpFile);
	my $d = dirname( $tmpFile);
	$r = system( "$TAR --same-owner $compress -f- -cphsC $d $f | $SSH $REMOTE_USER\@$host $TAR --same-owner $compress -f- -xphsC $TMP_DIR" );

	unlink( $tmpFile );	
	if( $r > 0 ){
		die( "Unable to send conf file to $host\n" );
	}

	$r = system( "$SSH $REMOTE_USER\@$host $TMP_DIR/$f" );
	&removeTmpDir();
	if( $r > 0 ){
		unlink( $tmpFile );	
		die( "Could not create links on host $host" );
	}
}

sub doTar()
{
	my $cmd;
	my $tarCmd = "$TAR --same-owner $compress -f- -phsC ";
	
	my $fileList = $srcFile;
	my $lsCmd = "";
	if( $srcFile =~ /\*/){ 
		$lsCmd = "cd $srcDir; $LS -1 $srcFile > $TMP_DIR 2>/dev/null;";
		$fileList = " -T $TMP_DIR";
	}

	my $srcCmd = "$lsCmd $tarCmd $srcDir -c $fileList";
	my $destCmd = "$tarCmd $destDir -x";
	if( $srcHost eq "localhost" && $destHost eq "localhost" ){
		$cmd = "$srcCmd|$destCmd";
	}elsif( $srcHost ne "localhost" ){
		$cmd = "$SSH $REMOTE_USER\@$srcHost '$srcCmd'| $destCmd";
	}else{
		$cmd = "$srcCmd | $SSH $REMOTE_USER\@$destHost $destCmd";
	}

	my $r = system( $cmd );
	if( $lsCmd ne "" ){
		if( $srcHost ne "localhost" ){
			$host = $srcHost;
			&removeTmpDir();
		}else{
			system( $RM, "-rf", $TMP_DIR );
		}
	}
	if( $r > 0 ){
		die "Could not copy data $!\n";
	}
}

# executes mysqlhotcopy on remote machine and streams the data back using tar
sub doMySQLHotCopy()
{
	my $r = system( @SSH_ARR, "$REMOTE_USER\@$host", $MKDIR, $TMP_DIR );
	if( $r > 0 ){
		die( "Could not create directory $TMP_DIR on host $host" );
	}
	$r = system( @SSH_ARR, "$REMOTE_USER\@$host", "$REMOTE_MYSQL_BINPATH/$MYSQLHOTCOPY", $params, $TMP_DIR );
	if( $r > 0 ){
		die( "mysqlhotcopy on host $host failed" );
	}
	
	$r = system( "$SSH $REMOTE_USER\@$host $TAR --same-owner $compress -f- -cphsC $TMP_DIR . | $TAR --same-owner $compress -f- -xphsC $destDir" );
	
	&removeTmpDir();
	if( $r > 0 ){
		die( "Unable to tar backup data from $host:$TMP_DIR\n" );
	}
}

sub removeTmpDir()
{	
	&removeDir( $TMP_DIR );
}

#$_[0] name of dir
sub removeDir()
{
	my $s = basename( $_[0] );
	my $d = dirname( $_[0] );
	my $r = system( @SSH_ARR, "$REMOTE_USER\@$host", $RM, "-rf", $_[0] );
	if( $r > 0 ){
		print STDERR "WARNING: Could not remove directory $_[0] on host $host\n";
		return;
	}
	if( $s eq "ZRM_MOUNTS" ){
		# try to remove the parent directory in case it is empty
		$r = system( @SSH_ARR, "$REMOTE_USER\@$host", "rmdir", $d );
	}
}	

my %config;
# Reads file the specified config file
# This reads the conf file that is prepared by mysql-zrm.
# Please note this does not do any validation of the config file
# pointed to by $ZRM_CONF in the enviornment
sub parseConfFile()
{
        my $fileName = $ENV{'ZRM_CONF'};
        unless( open( FH, "$fileName" ) ){
                die "Unable to open config file. This is only meant to be invoked from mysql-zrm\n";
        }
        my @tmparr = <FH>;
        close( FH );
        chomp( @tmparr );
        foreach( @tmparr ){
                my @v = split( /=/, $_ );
                my $v1 = shift @v;
                my $v2 = join( "=", @v );
                $config{$v1} = $v2;
        }
}

#Sets up relavent config parameters
sub setUpConfParams()
{
	if( $config{"ssh-user"} ){
		$REMOTE_USER = $config{"ssh-user"};
	}
	if( $config{"remote-mysql-binpath"} ){
		$REMOTE_MYSQL_BINPATH=$config{"remote-mysql-binpath"};
	}
	if( defined $ENV{'SNAPSHOT_CONF'} ){
        	my $fName = $ENV{'SNAPSHOT_CONF'};
        	unless( open( TMP, $fName ) ){
			return;
        	}
        	@snapshotParamList = <TMP>;
        	chomp( @snapshotParamList );
        	close TMP;
	}
	if( defined $config{"compress-data-during-transport"} && $config{"compress-data-during-transport"} == 1 ){
		$compress = "-z";
	}

	push @SSH_ARR, $SSH;
	if( defined $config{"ssh-identity-file"} ){
		$SSH .= " -i ".$config{"ssh-identity-file"};
		push @SSH_ARR, "-i";
		push @SSH_ARR, $config{"ssh-identity-file"};
	}

}

sub doSnapshotCommand()
{
	my $fName = $ENV{'SNAPSHOT_CONF'};

	chmod( 0600, $fName );
	unless( open( TMP, ">$fName" ) ){
		die "Unable to open snapshot conf to write\n";	
	}
	foreach( @snapshotParamList ){
		print TMP "$_=$config{$_}\n";
	}
	close TMP;
	my $tmp = File::Spec->tmpdir();
	
	my $r = system( @SSH_ARR, "$REMOTE_USER\@$host", $MKDIR, $TMP_DIR );
	if( $r > 0 ){
		unlink( $fName );	
		die( "Could not create directory $TMP_DIR on host $host" );
	}

	my $f = basename($fName);
	my $d = dirname( $fName);
	$r = system( "$TAR --same-owner $compress -f- -cphsC $d $f | $SSH $REMOTE_USER\@$host $TAR --same-owner $compress -f- -xphsC $TMP_DIR" );

	unlink( $fName );	
	if( $r > 0 ){
		die( "Unable to send conf file to $host\n" );
	}
	my $cn = "$TMP_DIR/$f";
	
	my $cmd = $config{'snapshot-plugin'}." ".$params;
	$cmd = "$SSH $REMOTE_USER\@$host 'ZRM_CONF=$cn; export ZRM_CONF; $cmd'";
	$r = system( $cmd );
	&removeTmpDir();
	if( $r > 0 ){
		if( !defined $ENV{"ZRM_NO_ERROR"} ){
			die( "Command failed: $cmd \n" );
		}else{
			die( "\n" );
		}
		#die();
	}
}

sub doCopyBetween()
{
	my $tmp = File::Spec->tmpdir();
	$host = $srcHost;
	my $fName = tmpnam();
	my $r = system( @SSH_ARR, "$REMOTE_USER\@$host", $MKDIR, $TMP_DIR );
	if( $r > 0 ){
		unlink( $fName );	
		die( "Could not create directory $TMP_DIR on host $host" );
	}

	my $f = basename($fName);
	
	my $cn = "$TMP_DIR/$f";
	$r = system( @SSH_ARR, "$REMOTE_USER\@$host", "touch", $cn );

	if( $r > 0 ){
		&removeTmpDir();
		die( "Unable to create conf file to $host\n" );
	}
	my $d = $destHost;
	if( $srcHost eq $destHost ){
		$d = "localhost";
	}
	my $cmd = "$SSH $REMOTE_USER\@$host \"ZRM_CONF=$cn; export ZRM_CONF; $config{'copy-plugin'} --source-file '$srcDir/$srcFile' --source-host localhost --destination-host $destHost --destination-directory '$destDir' --ssh-user $REMOTE_USER\"";
	$r = system( $cmd );
	&removeTmpDir();
	
	if( $r > 0 ){
		die( "Copy data failed. Command used was $cmd." );	
	}
}

&parseConfFile();
&setUpConfParams();
&getInputs();
if( $action eq "copy" ){
	&doTar();
}elsif( $action eq "copy between" ){
	&doCopyBetween();	
}elsif( $action eq "remove-backup-data" ){
	&removeDir( $params );
}elsif( $action eq "snapshot" ){
	&doSnapshotCommand( $params );
}elsif( $action eq "create-link" ){
	&doCreateLinks( $params );
}else{
	&doMySQLHotCopy();
}
exit( 0 );


