#! /usr/bin/perl

#
#   Copyright (C) Dr. Heinz-Josef Claes (2004)
#                 hjclaes@web.de
#          sponsored by Deutscher Bundestag
#   
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#   
#   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., 675 Mass Ave, Cambridge, MA 02139, USA.
#


push @VERSION, '$Id: storeBackupMount.pl 330 2004-04-18 07:39:13Z  $ ';

use strict;
use Net::Ping;
use POSIX;


sub libPath
{
    my $file = shift;

    my $dir;

    # Falls Datei selbst ein symlink ist, solange folgen, bis aufgelst
    if (-f $file)
    {
	while (-l $file)
	{
	    my $link = readlink($file);

	    if (substr($link, 0, 1) ne "/")
	    {
		$file =~ s/[^\/]+$/$link/;
	    }
	    else
	    {
		$file = $link;
	    }
	}

	($dir, $file) = &splitFileDir($file);
	$file = "/$file";
    }
    else
    {
	print STDERR "<$file> does not exist, exiting!\n";
        POSIX::_exit 2;
    }

    $dir .= "/../lib";           # Pfad zu den Bibliotheken
    my $oldDir = `/bin/pwd`;
    chomp $oldDir;
    if (chdir $dir)
    {
	my $absDir = `/bin/pwd`;
	chop $absDir;
	chdir $oldDir;

	return (&splitFileDir("$absDir$file"));
    }
    else
    {
	print STDERR "<$dir> does not exist, exiting\n";
        POSIX::_exit 2;
    }
}
sub splitFileDir
{
    my $name = shift;

    return ('.', $name) unless ($name =~/\//);    # nur einfacher Dateiname

    my ($dir, $file) = $name =~ /^(.*)\/(.*)$/s;
    $dir = '/' if ($dir eq '');                   # gilt, falls z.B. /filename
    return ($dir, $file);
}
my ($req, $prog) = &libPath($0);
(@INC) = ($req, @INC);

require 'checkParam.pl';
require 'checkObjPar.pl';
require 'prLog.pl';
require 'forkProc.pl';
require 'dateTools.pl';
require 'version.pl';

$main::exit = 0;                               # exit status

my $Help = <<EOH;
This script does the following:
- checks an nfs server with ping
- mounts that server via a list of mount points
- starts storeBackup (with a config file)
- umounts that server

usage:
	$prog -c configFile [-s server] [-l logFile]
	[-p pathToStoreBackup] [-k killTime] [-m] mountPoints...

--server	-s  name or ip address of the server to backup
		    default is localhost
--configFile	-c  configuration file for storeBackup
--logFile	-l  logFile for this process
		    default is STDOUT
		    you can log into the same logfile as storeBackup
--pathStbu	-p  path to storeBackup
--killTime	-k  time until storeBackup will be killed
		    default is to kill never
    		    the time range has to be specified in format 'dhms', e.g.
		    10d4h means 10 days and 4 hours
--keppExistingMounts
		-m  if a mount already exists, do not umount after
		    running storeBackup
mountPoints	    List of mount points needed to perform the backup.
		    This must be a list of paths which have to be
		    defined in /etc/fstab.

exit status:
    0 -> all is ok
    1 -> error from storeBackup
    2 -> error from storeBackupMount
    3 -> error from both programs

Copyright (c) 2004 by Heinz-Josef Claes
Published under the GNU General Public License
EOH
    ;

&printVersions(\@ARGV, '-V');

my $CheckPar =
    CheckParam->new('-allowLists' => 'yes',
		    '-list' => [Option->new('-option' => '-s',
					    '-alias' => '--server',
					    '-param' => 'yes'),
				Option->new('-option' => '-c',
					    '-alias' => '--configFile',
					    '-param' => 'yes',
					    '-must_be' => 'yes'),
				Option->new('-option' => '-l',
					    '-alias' => '--logFile',
					    '-param' => 'yes'),
				Option->new('-option' => '-p',
					    '-alias' => '--pathStbu',
					    '-param' => 'yes'),
				Option->new('-option' => '-k',
					    '-alias' => '--killTime',
					    '-param' => 'yes'),
				Option->new('-option' => '-m',
					    '-alias' => '--keepExistingMounts')
				]);

$CheckPar->check('-argv' => \@ARGV,
                 '-help' => $Help
                 );

my $server = $CheckPar->getOptWithPar('-s');
my $configFile = $CheckPar->getOptWithPar('-c');
my $logFile = $CheckPar->getOptWithPar('-l');
my $pathStbu = $CheckPar->getOptWithPar('-p');
my $kt = $CheckPar->getOptWithPar('-k');
my $keepExistingMounts = $CheckPar->getOptWithoutPar('-m');
my (@mountPoints) = $CheckPar->getListPar();

my $prLog;
my ($prLogKind) = ['A:BEGIN',
		   'Z:END',
		   'I:INFO',
		   'W:WARNING',
		   'E:ERROR'];
if ($logFile)
{
    $prLog = printLog->new('-file' => $logFile,
			   '-multiprint' => 'yes',
			   '-kind' => $prLogKind);
}
else
{
    $prLog = printLog->new('-kind' => $prLogKind);
}

$prLog->print('-kind' => 'A',
	      '-str' => ["starting storeBackup -f $configFile"]);

# killTime in seconds:
my $killTime = &dateTools::strToSec('-str' => $kt);
unless (defined $killTime)
{
    $prLog->print('-kind' => 'E',
		  '-str' => ["wrong format of parameter --killTime: <$kt>"]);
    wait;
    POSIX::_exit 2;
}

#
# test ping to server
#
if ($server)
{
    my $p = Net::Ping->new('tcp', 5); # wait a maximum of 5 seconds for response
    my $ret = $p->ping($server);
    if ($ret == 1)
    {
	$prLog->print('-kind' => 'I',
		      '-str' => ["host <$server> reachable via tcp-ping"]);
    }
    else
    {
	$main::exit |= 2;
	$prLog->print('-kind' => 'E',
		      '-str' => ["host <$server> not reachable via tcp-ping"]);
	wait;
	POSIX::_exit $main::exit;
    }
}

#
# checking for already mounted filesystems
#
my (@aM) = `mount`;
my (%alreadyMounted, $m);
foreach $m (@aM)
{
    $m =~ /(.+?) on (\S+)/;
    $alreadyMounted{$2} = 1;
}

#
# mounting the file systems
#
my (@mounted) = ();
my $error = 0;
foreach $m (@mountPoints)
{
    if (exists $alreadyMounted{$m})
    {
	$prLog->print('-kind' => 'I',
		      '-str' => ["<$m> is already mounted"]);
	next;
    }

    $prLog->print('-kind' => 'I',
		  '-str' => ["trying to mount $m"]);
    my $fp = forkProc->new('-exec' => 'mount',
			   '-param' => [$m],
			   '-outRandom' => '/tmp/doStoreBackup-forkMount-',
			   '-prLog' => $prLog);

    # wait for a maximum of 10 seconds
    foreach (1..10)
    {
	sleep 1;
	if ($fp->processRuns() == 0)
	{
	    last;
	}
    }
    my $out1 = $fp->getSTDOUT();
    my $out2 = $fp->getSTDERR();
    $fp->DESTROY();
    if ($fp->get('-what' => 'status') != 0    # mount not successfull
	or @$out2 > 0)
    {
	$main::exit |= 2;
	$error = 1;
	$prLog->print('-kind' => 'E',
		      '-str' => ["could not mount $m"]);
	$fp->signal('-value' => 9);

	&umount(\@mounted, $keepExistingMounts, \%alreadyMounted);

	$prLog->print('-kind' => 'E',
		      '-str' => ["exiting"]);
	wait;
	POSIX::_exit $main::exit;
    }
    else
    {
	push @mounted, $m;
	$prLog->print('-kind' => 'I',
		      '-str' => ["<mount $m> successfull"]);
    }

    $prLog->print('-kind' => 'W',
		  '-str' => ["STDOUT of <mount $m>:", @$out1])
	if (@$out1 > 0);
    $prLog->print('-kind' => 'E',
		  '-str' => ["STDERR of <mount $m>:", @$out2])
	if (@$out2 > 0);

    if (@$out2)
    {
	$main::exit |= 2;
	$prLog->print('-kind' => 'E',
		      '-str' => ["exiting"]);
	wait;
	POSIX::_exit $main::exit;
    }
}
if ($error == 1)
{
    $prLog->print('-kind' => 'E',
		  '-str' => ["exiting"]);
    wait;
    POSIX::_exit $main::exit;
}

#
# starting storeBackup
#
my $storeBackup = $pathStbu ? "$pathStbu/storeBackup" : 'storeBackup';

my $stbu = forkProc->new('-exec' => $storeBackup,
			 '-param' => ['-f', $configFile],
			 '-outRandom' => '/tmp/doStoreBackup-stbu-',
			 '-prLog' => $prLog);
$prLog->print('-kind' => 'I',
	      '-str' => ["started <$storeBackup -f $configFile>, pid=" .
			 $stbu->get('-what' => 'pid')]);

if ($killTime)
{
    my $ready = 0;
    foreach (1..$killTime)
    {
	sleep 1;
	if ($stbu->processRuns() == 0)
	{
	    $ready = 1;
	    last;
	}
    }
    if ($ready == 0)      # duration too long
    {
	$prLog->print('-kind' => 'E',
		      '-str' => ["time limit <$kt> exceeded for " .
				 "<storeBackup -f $configFile>"]);
	$stbu->signal('-value' => 2);     # SIGINT
	$main::exit |= 1;
	sleep 10;          # time for storeBackup to finish
    }
}
else
{
    $stbu->wait();
}

if ($stbu->get('-what' => 'status') != 0)
{
    $main::exit |= 1;
    $prLog->print('-kind' => 'E',
		  '-str' => ["storeBackup exit status != 0"]);
}

my $out1 = $stbu->getSTDOUT();
$prLog->__reallyPrint($out1) if (@$out1 > 0);

my $out2 = $stbu->getSTDERR();
if (@$out2 > 0)
{
    $main::exit |= 1;
    $prLog->print('-kind' => 'E',
		  '-str' => ["STDERR of <storeBackup -f $configFile>:",
			     @$out2]);
}

sleep 2;

&umount(\@mounted, $keepExistingMounts, \%alreadyMounted);

$prLog->print('-kind' => 'Z',
	      '-str' => ["finished storeBackup -f $configFile"]);

wait;
POSIX::_exit $main::exit;

######################################################################
sub umount
{
    my ($mounted, $keepExistingMounts, $alreadyMounted) = @_;

    foreach $m (reverse @$mounted)
    {
	if (exists $alreadyMounted{$m})
	{
	    $prLog->print('-kind' => 'I',
			  '-str' =>
			  ["do not umount <$m>, was already mounted"]);
	    next;
	}
	$prLog->print('-kind' => 'I',
		      '-str' => ["trying to <umount $m>"]);
	sleep 5;
	my $um = forkProc->new('-exec' => 'umount',
			       '-param' => [$m],
			       '-outRandom' =>
			       '/tmp/doStoreBackup-forkMount-',
			       '-prLog' => $prLog);

	# wait for a maximum of 10 seconds
	foreach (1..10)
	{
	    sleep 1;
	    if ($um->processRuns() == 0)
	    {
		last;
	    }
	}
	$um->DESTROY();
	if ($um->get('-what' => 'status') != 0)    # umount not successfull
	{
	    $prLog->print('-kind' => 'E',
			  '-str' => ["could not <umount $m>"]);
	    $um->signal('-value' => 9);
	    $main::exit |= 2;
	}
	else
	{
	    $prLog->print('-kind' => 'I',
			  '-str' => ["<umount> $m successfull"]);
	}
    }
}
