smeserver-affa/root/sbin/affa

5265 lines
182 KiB
Perl

#!/usr/bin/perl -w
#----------------------------------------------------------------------
# Affa is a rsync based backup program for Linux.
# It remotely backups Linux or other systems, which have either the rsync
# program and the sshd service or the rsyncd service installed.
# Please see http://affa.sf.net for full documentation.
# Copyright (C) 2004-2012 by Michael Weinberger
# This program comes with ABSOLUTELY NO WARRANTY; for details type 'affa --warranty'.
# This is free software, and you are welcome to redistribute it
# under certain conditions; type 'affa --license' for details.
#----------------------------------------------------------------------
# ToDO
# re-order the getSourceDirs
# Line 820
#2969; my $needed = int( $report->val( 'Report', 'TotalFileSize' ) / 1024 );
# Fix the CLI
#1629; my $archive = $ARGV[1] eq '' ? $job{'Archive'} : $ARGV[1] ; #Overide archive from the CLI if set
# Need to fix the 'selected' rise version here
# sub riseFromDeath() {
# not ExecCmd( @cmd, 0 ) or affaErrorExit("writing list of installed RPMs failed."); # reetp fix this around 1580
# Restore will fail if contribs are not installed first. rsycn < 3.3.0 cannot make the directories.
# This will be fixed in rsync 3.3.0+ and v11
# 19-08-24
# RPM version
my $VERSION='4.0-2';
# Script Sub Version j
# This will remove the trailing -1 release number for docs
my $shortVersion = $VERSION;
$shortVersion =~ s/-[0-9]+//;
# Development code to find which routines take time
# my $develTimer = 0;
# use Devel::Timer;
# my $timer = Devel::Timer->new();
#----------------------------------------------------------------------
#
# A few notes - 21/09/2021
#
# This is a huge script and probably needs breaking up
# It probably has lots that can be cleaned or removed
# It is starting to become systemd aware, but
# SME is still not fully systemd manageable and still relies
# on some old init scripts
#
# freedup for SME v10 is in smetest or smecontribs
#
# reetp - what if this is backing up a remote linux box but non SME?
# This has become more and more SME specific and certain config settings
# may trigger unusual behavior.
#----------------------------------------------------------------------
use strict;
use Date::Format;
use Errno;
use File::Path;
use File::stat;
use File::Copy;
use Digest::MD5;
use Cwd;
use Filesys::DiskFree;
use Getopt::Long;
use Mail::Send;
use Time::Local;
use Sys::Hostname;
use Config::IniFiles;
use Proc::ProcessTable;
##########################
#ajoute
use esmith::Backup;
use esmith::ConfigDB;
# ajout pour V3.2.2-2
use File::Basename;
# Added for 3.3.1
use esmith::Service;
#use Affa::lib qw(ExecCmd setlog lg dbg);
########use Affa::lib qw(ExecCmd setlog lg dbg);
#fin ajout
#########################
$| = 1; # flush output
#$ARGV[0]
# prototypes
sub affaErrorExit($);
sub affaExit($);
sub checkArchiveExists($$);
sub checkConfig();
sub checkConnection($);
sub checkConnectionsAll();
sub checkConnection_silent($$$);
# ajout pour V3.2.2-2
sub checkForDiskLabel($);
# fin ajout
sub checkCrossFS($$);
sub cleanup();
sub convertReportV2toV3($$);
sub cronSetup();
# sub dbg($); reetp - duplicate
sub dbg($);
sub deduplicate($);
sub deleteJob();
sub df($);
sub DiskSpaceWarn();
sub DiskUsage();
sub DiskUsageRaw();
sub ExecCmd( \@$ );
sub ExecCmdBackupList( \@$ );
sub execJobCommand($$);
sub execJobCommandRemote($$);
sub execPostJobCommand($);
sub execPreJobCommand($);
sub findProcessId($$);
sub fullRestore();
sub getChildProcess($$);
sub getConfigFile($);
sub getConfigFileList();
sub getIncExc($);
sub getExcludedString();
sub getIncludedString();
sub getJobConfig($);
sub getJobs();
sub getLinkdest($$);
sub getLock($);
sub getMultivalueKeys();
sub getProcessState($);
sub getReportVal($$);
sub getSourceDirs($);
sub getSourceDirsString($);
sub getStatus();
sub getStatusRaw();
sub isMounted($$);
sub jobsnrpe($);
sub killall();
sub killJob($);
sub killProcessGroup($$);
sub lg($);
sub listArchives();
sub listArchivesRaw($);
sub listJobs();
sub logTail();
sub mailTest();
###### ligne suivante modifiée umount diskusage - bug 9147
# sub mount($$$); # reetp - which mount?
sub mount($$$%);
sub moveArchive();
sub moveFileorDir($$);
sub nrpe();
sub remoteCopy($$$);
sub removeDir($$);
sub removeLock();
sub renameConfigKey($$$);
sub renameJob();
sub resumeInterrupted();
sub revokeKeys($);
sub sanityCheckConfFiles();
sub sendErrorMesssage();
sub sendKeys();
sub sendStatus();
sub sendSuccessMesssage();
sub setLock();
sub setlog($);
sub setupSamba();
sub shiftArchives();
sub showConfigPathes();
sub showConfigPathesRaw();
sub showDefaults();
sub showHelp($);
sub showProperty();
sub showSchedule();
sub showTextfile($);
sub showVersion();
sub SignalHandler();
sub trim($);
sub unmount($$);
sub writeConfigFile($);
####################################################################################
########### ajouts
sub compareRPMLists($);
sub imapIndexFilesDelete();
sub imapIndexFilesDeleteCommand();
sub installedRPMsList($);
sub installWatchdog($);
sub mailTestWatchdogRemote($);
sub riseFromDeath();
sub runRiseRsync($$$);
sub saveMySoul();
sub undoRise();
########## fin ajouts
###################################################################################
###################################################################################
################ ajouts
my $configDB = esmith::ConfigDB->open or die "Error: Couldn't open config db.";
my $LocalIP = $configDB->get("LocalIP")->value;
my $DomainName = $configDB->get("DomainName")->value;
my $SystemName = $configDB->get("SystemName")->value;
my $rpmlist = "/home/e-smith/db/affa-rpmlist"; # list of installed RPMs
my $backupList = "BackupDirectoryList.txt";
my @services = ( 'crond', 'smbd', 'qmail', 'qpsmtpd', 'sqpsmtpd' );
my $ServicesStopped = 0;
my $sshQuiet = "-q";
my $ServerBasename = "AFFA.$SystemName.$DomainName-$LocalIP";
my $allow_retry = 0;
my $defaultEmail = 'admin';
my $affaTitle = "Affa version $VERSION on $SystemName";
my $smbconf = '/etc/samba/smb.conf';
#############
# reetp
# So we could change this to check the samba status as this is ONLY checking
# For the script existence OR /sbin/e-smith/service smb status | grep Active
my $systemD = new esmith::Service();
# Service command << not required now
# $serviceCommand = '/sbin/e-smith/service'
my $SambaStartScript = '/usr/lib/systemd/system/smbd.service';
#my $smbStatus = $configDB->get_prop( 'smb', 'status' ) || 'disabled';
$systemD->{'serviceName'} = 'smbd';
my $smbdStatus = ( $systemD->is_enabled() ) ? "enabled" : "disabled";
# nrpe too
my $NRPEStartScript = '/usr/lib/systemd/system/nrpe.service';
#my $nrpeStatus = $configDB->get_prop( 'nrpe', 'status' ) || 'disabled';
$systemD->{'serviceName'} = 'nrpe';
my $nrpeStatus = ( $systemD->is_enabled() ) ? "enabled" : "disabled";
#############
# Test binary built in reetpTest repo
# No idea if it works on v10
my $dedupBinary = '/usr/bin/freedup';
my $cfg; # ini file
my $curtime = time(); # now timestamp
my $thisDay = time2str( "%Y%j", $curtime ); # day of year 1-366
my $thisWeek = time2str( "%Y%W", $curtime ); # week of year 1-53
my $thisMonth = time2str( "%Y%m", $curtime ); # month 1-12
my $thisYear = time2str( "%Y", $curtime ); # 4-digit year
my $process_id = $$; # my PID
my $jobname = 'NONE';
my $Command = '';
my $interactive = 0;
my %autoMounted = ();
my $interrupt = '';
my $ExecCmdOut = '';
my @Messages = (); # List of all log messages
my $logdir = '/var/log/affa'; # Logdir
my $logfile = "$logdir/affa-GLOBAL-LOGFILE.log"; # Logfile
my $scriptdir = '/etc/affa/scripts';
my $lockdir = '/var/lock/affa'; # Process lock
my $archive = '';
my $restoreVer = '';
File::Path::mkpath( $lockdir, 0, 0700 ) if not -d $lockdir;
# reetp this was not set anywhere at this point so added it right at the start
# otherwise when you initially run you get a dir not found
# it does get set in getDefaultConfig()
# 'RootDir' => '/var/affa',
# Maybe should be called earlier?
my $storagedir = '/var/affa'; # Storage directory
File::Path::mkpath( $storagedir, 0, 0700 ) if not -d $storagedir;
if ( not $ARGV[0] ) {
showHelp(1);
exit;
}
my %opts;
my $runninglog = "Affa $VERSION: Running $0 @ARGV";
my $getRes = GetOptions(
'15' => \$opts{'15'},
'30' => \$opts{'30'},
'all' => \$opts{'all'},
'backup' => \$opts{'run'}, # same as --run
'check-connections' => \$opts{'check-connections'},
'cleanup' => \$opts{'cleanup'},
'csv' => \$opts{'csv'},
'configcheck' => \$opts{'configcheck'},
'nrpe' => \$opts{'nrpe'},
'debug' => \$opts{'debug'},
'cli-debug' => \$opts{'cli-debug'},
'delete-job' => \$opts{'delete-job'},
'disk-usage' => \$opts{'disk-usage'},
'full-restore' => \$opts{'full-restore'},
'help' => \$opts{'help'},
'_jobs' => \$opts{'jobs'},
'_jobsnrpe' => \$opts{'jobsnrpe'},
'init-nrpe' => \$opts{'init-nrpe'},
'_cronupdate' => \$opts{'cronupdate'},
'killall' => \$opts{'killall'},
'kill' => \$opts{'kill'},
'resume=s' => \$opts{'resume'},
'list-archives' => \$opts{'list-archives'},
'preserve-newer=s' => \$opts{'preserve-newer'},
'delete=s' => \$opts{'delete'},
'_delay=s' => \$opts{'delay'},
'log-tail' => \$opts{'log-tail'},
'mailtest' => \$opts{'mailtest'},
'make-cronjobs' => \$opts{'make-cronjobs'},
'move-archive' => \$opts{'move-archive'},
'outfile=s' => \$opts{'outfile'},
'rename-job' => \$opts{'rename-job'},
'RetryAfter=s' => \$opts{'RetryAfter'},
'RetryAttempts=s' => \$opts{'RetryAttempts'},
'revoke-key' => \$opts{'revoke-key'},
'run' => \$opts{'run'},
'send-keys' => \$opts{'send-keys'},
'send-status' => \$opts{'send-status'},
'_shorthelp' => \$opts{'shorthelp'},
'show-config-pathes' => \$opts{'show-config-pathes'},
'show-property' => \$opts{'show-property'},
'show-schedule' => \$opts{'show-schedule'},
'show-default-config' => \$opts{'show-default-config'},
'status' => \$opts{'status'},
'resume-interrupted' => \$opts{'resume-interrupted'},
'version' => \$opts{'version'},
'warranty' => \$opts{'warranty'},
'license' => \$opts{'license'},
'silent' => \$opts{'silent'},
'watchdog=s' => \$opts{'watchdog'},
"rise" => \$opts{'rise'},
"undo-rise" => \$opts{'undo-rise'},
"sanity-check" => \$opts{'sanityheck'},
);
####################################################################################
#### ajouté paramètre watchdog=s + rise + undo-rise 2 lignes plus haut
my $configfile = "/tmp/affa-config-$curtime-$$";
unlink($configfile);
# Check the config files are OK
sanityCheckConfFiles();
my $cliDebug = 0;
if ( $opts{'cli-debug'} ) {
$cliDebug = 1;
}
# get default config - no job specificed
my %job = getJobConfig('');
if ( $opts{'nrpe'} ) {
exit nrpe();
}
if ( $opts{'cronupdate'} ) {
$interactive = 1;
cronSetup();
exit 0;
}
if ( $opts{'version'} ) {
showVersion();
exit 0;
}
if ( $opts{'license'} ) {
showTextfile("/usr/share/doc/smeserver-affa-$shortVersion/LICENSE");
exit 0;
}
if ( $opts{'warranty'} ) {
showTextfile("/usr/share/doc/smeserver-affa-$shortVersion/WARRANTY");
exit 0;
}
if ( $opts{'help'} ) {
showHelp(0);
exit 0;
}
if ( $opts{'jobs'} ) {
listJobs();
exit 0;
}
if ( $opts{'init-nrpe'} ) {
jobsnrpe(1);
exit 0;
}
if ( $opts{'jobsnrpe'} ) {
exit jobsnrpe(0);
}
if ( $opts{'shorthelp'} ) {
showHelp(1);
exit 0;
}
#lg( $runninglog ); $runninglog='';
if ( $opts{'list-archives'} ) {
print listArchives();
affaExit('Done.');
}
elsif ( $opts{'show-config-pathes'} ) {
undef $jobname;
showConfigPathes();
affaExit('Done.');
}
elsif ( $opts{'show-default-config'} ) {
undef $jobname;
showDefaults();
affaExit('Done.');
}
elsif ( $opts{'log-tail'} ) {
undef $jobname;
logTail();
affaExit('Done.');
}
elsif ( $opts{'configcheck'} ) {
undef $jobname;
checkConfig();
affaExit('Done.');
}
elsif ( $opts{'send-keys'} ) {
$jobname = 'send keys';
sendKeys();
affaExit('Done.');
}
elsif ( $opts{'disk-usage'} ) {
undef $jobname;
print DiskUsage();
affaExit('Done.');
}
elsif ( $opts{'status'} ) {
undef $jobname;
print getStatus();
affaExit('Done.');
}
elsif ( $opts{'send-status'} ) {
undef $jobname;
sendStatus();
affaExit('Done.');
}
elsif ( $opts{'mailtest'} ) {
mailTest();
affaExit('Done.');
}
elsif ( $opts{'full-restore'} ) {
$jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Full-Restore Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
# Don't untaint as it removes the digit
# regex to check [0-9] ?
$restoreVer = $ARGV[1] || 'scheduled.0';
fullRestore();
affaExit('Done.');
}
elsif ( $opts{'resume-interrupted'} ) {
resumeInterrupted();
affaExit('Done.');
}
elsif ( $opts{'cleanup'} ) {
cleanup();
affaExit('Done.');
}
elsif ( $opts{'delete-job'} ) {
deleteJob();
affaExit('Done.');
}
elsif ( $opts{'rename-job'} ) {
renameJob();
affaExit('Done.');
}
elsif ( $opts{'move-archive'} ) {
moveArchive();
affaExit('Done.');
}
elsif ( $opts{'revoke-key'} ) {
$jobname = 'revoke key';
revokeKeys( $ARGV[0] || '' );
affaExit('Done.');
}
elsif ( $opts{'check-connections'} ) {
$jobname = 'check-connections';
checkConnectionsAll();
affaExit('Done.');
}
elsif ( $opts{'kill'} ) {
killJob( $ARGV[0] || '' );
affaExit('Done.');
}
elsif ( $opts{'killall'} ) {
killall();
affaExit('Done.');
}
elsif ( $opts{'show-schedule'} ) {
showSchedule();
affaExit('Done.');
}
elsif ( $opts{'show-property'} ) {
showProperty();
affaExit('Done.');
}
####################################################################################
#### début des modifs: ajout de
elsif ( $opts{'rise'} ) {
#$jobname = 'rising from archive';
$jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Rise Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
# Don't untaint as it removes the digit
# regex to check [0-9] ?
$restoreVer = $ARGV[1] || 'scheduled.0';
riseFromDeath();
affaExit('Done.');
}
elsif ( $opts{'undo-rise'} ) {
$jobname = 'undo-rise';
$jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
undoRise();
affaExit('Done.');
}
#### fin des modifs
####################################################################################
if ( $opts{'make-cronjobs'} ) {
$jobname = 'make cronjobs';
cronSetup();
affaExit('Done.');
}
if ( not $opts{'run'} ) {
print "Run affa --help for help.\n";
affaErrorExit("Unkown option.");
}
# With all other options tested we now run a backup job
my $StartTime = time();
$jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
$Command = defined $ARGV[1] ? lc( $ARGV[1] ) : 'scheduled';
$Command =~ /([a-z]*)/i;
$Command = $1; # untaint
if ( not $Command =~ /^(scheduled|daily|weekly|monthly|yearly)$/ ) {
affaErrorExit("Unknown command '$Command'");
}
%job = getJobConfig($jobname);
setlog("$jobname.log");
{ my $txt = "# Affa $VERSION #"; lg( '#' x length($txt) ); lg($txt); lg( '#' x length($txt) ) }
lg("Starting job $jobname $Command ($job{'remoteHostName'})");
lg( "Description: " . $job{'Description'} ) if defined $job{'Description'};
lg("Bandwidth limit: $job{'BandwidthLimit'} KBytes/sec") if $job{'BandwidthLimit'};
# check whether the job is already running
$Command eq "scheduled"
and getLock($jobname)
and affaErrorExit( "Lock found. Another job (pid=" . getLock($jobname) . ") is still running." );
setLock() if $Command eq "scheduled";
$SIG{'TERM'} = 'SignalHandler';
$SIG{'INT'} = 'SignalHandler';
if ( $opts{'delay'} ) {
lg( "Delayed run. Starting at " . Date::Format::time2str( "%T", time() + $opts{'delay'} ) );
sleep( $opts{'delay'} );
}
if ( $opts{'RetryAfter'} ) {
lg( "Waiting $opts{'RetryAfter'} seconds. Continuing at "
. Date::Format::time2str( "%T", time() + $opts{'RetryAfter'} ) );
sleep( $opts{'RetryAfter'} );
$job{'chattyOnSuccess'}++ if not $job{'chattyOnSuccess'} and $job{'RetryNotification'} eq 'yes';
}
$allow_retry = 1;
checkConnection($jobname); # and exit on error
# mount root dir
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
###### ligne suivante modifiée umount diskusage - bug 9147
# mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'} );
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
}
affaErrorExit("RootDir $job{'RootDir'} does not exist") unless -d $job{'RootDir'};
File::Path::mkpath( "$job{'RootDir'}/$jobname", 0, 0700 ) unless -d "$job{'RootDir'}/$jobname";
my $ddf = "$job{'RootDir'}/$jobname/.doneDates";
if ( not -f $ddf ) {
open( DF, ">$ddf" );
print DF "[doneDates]\n";
print DF "daily=-1\n";
print DF "weekly=-1\n";
print DF "monthly=-1\n";
print DF "yearly=-1\n";
close(DF);
}
$job{'_doneDates'} = Config::IniFiles->new( -file => "$job{'RootDir'}/$jobname/.doneDates", -nocase => 0 );
# run daily, weekly, monthly or yearly if not already done;
if ( $Command eq "scheduled" and -f "$job{'RootDir'}/$jobname/scheduled.0/.AFFA4-REPORT" ) {
$0 =~ /(.*)/; # untaint
my @cmd = ( $1, '--run', $jobname, 'yearly' );
ExecCmd( @cmd, 1 ) if ( $job{'_doneDates'}->val( 'doneDates', 'yearly' ) ne $thisYear and $job{'yearlyKeep'} > 0 );
$cmd[3] = 'monthly';
ExecCmd( @cmd, 1 ) if ( $job{'_doneDates'}->val( 'doneDates', 'monthly' ) ne $thisMonth and $job{'monthlyKeep'} > 0 );
$cmd[3] = 'weekly';
ExecCmd( @cmd, 1 ) if ( $job{'_doneDates'}->val( 'doneDates', 'weekly' ) ne $thisWeek and $job{'weeklyKeep'} > 0 );
$cmd[3] = 'daily';
ExecCmd( @cmd, 1 ) if ( $job{'_doneDates'}->val( 'doneDates', 'daily' ) ne $thisDay and $job{'dailyKeep'} > 0 );
}
### hier geht's wirklich los ###
execPreJobCommand($jobname);
####################################################################################
#### debut des modifs
installWatchdog( $opts{'watchdog'} || 0 );
if ( $job{'SMEServer'} ne 'no' ) {
# Here only for SME machines
my $prebackup = 'SME/signal-pre-backup';
if ( $job{'remoteHostName'} eq 'localhost' ) {
execJobCommand( $jobname, $prebackup );
}
else {
execJobCommandRemote( $jobname, $prebackup );
}
}
installedRPMsList(0) if $job{'SMEServer'} ne 'no' and $job{'remoteHostName'} ne 'localhost';
#### ligne ci-dessus: suppression de "!$ESX and " entre if et $job
#### fin modifs
####################################################################################
# reetp here we start the business end by retrieving the list of directories that need backing up
# Note if we are not 'scheduled' we drop right through to after shiftArchives below
my $linkDest = '';
my @cmd;
my $status = 0;
my $rsyncOutput = '';
if ( $Command eq 'scheduled' ) {
my $exclude = getExcludedString();
my $include = getIncludedString();
$linkDest = getLinkdest( $jobname, 0 );
dbg("Using link destination $linkDest") if $linkDest;
# my @cmd; moved up
#my $status = 0;
#my $rsyncOutput = '';
File::Path::mkpath( "$job{'RootDir'}/$jobname/scheduled.running",
0, 0700 )
unless -d "$job{'RootDir'}/$jobname/scheduled.running";
if ( $job{'_rsyncd'} ) {
# e.g. Windows Server with rsyncd installed
# reetp pretend we didnt see this part - Windows needs Cygwin/Rysncd to work
# This will likely not work and may get removed
my @SourceDirs = getSourceDirs($jobname);
my $source = '';
foreach my $src (@SourceDirs) {
$src = "/$src" if not $src =~ /^\//;
$src =~ s/'/'\\''/g; # escape single quotes
$source
.= ( $job{'rsyncdUser'} ? $job{'rsyncdUser'} . '@' : '' )
. $job{'remoteHostName'} . "::'"
. $job{'rsyncdModule'}
. $src . "' ";
}
@cmd = (
$job{'_rsyncLocal'},
"--archive",
"--hard-links",
"--stats",
"--delete-during",
"--ignore-errors",
"--delete-excluded",
"--relative",
"--partial",
$job{'BandwidthLimit'} ? "--bwlimit=$job{'BandwidthLimit'}" : '',
$job{'rsync--modify-window'} > 0
? "--modify-window=$job{'rsync--modify-window'}"
: '',
$job{'rsync--inplace'} ne 'no' ? "--inplace" : "",
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
"--numeric-ids",
( $linkDest
? "--link-dest='$job{'RootDir'}/$jobname/$linkDest'"
: ''
),
$include, $exclude,
$job{'rsyncOptions'},
$source,
"$job{'RootDir'}/$jobname/scheduled.running/"
);
}
elsif ( $job{'SMEServer'} ne 'no' ) {
# SME specific
# reetp - remote or local? Remote should not be just '/'
# We fix Include/Exclude in the getSourceDir subs below
# (Except we don't fix the excludes yet)
#my $SourceDirs = getSourceDirsString($jobname) || '/';
my ( $local, $remote ) = getSourceDirsString($jobname);
# $remote is the destination - it could be local
my $SourceDirs = $remote;
####################################################################################
#### debut des modifs: ajout du "if........." ditto V2
# escape single quotes
#$SourceDirs =~ s/'/'\\''/g if $job{'remoteHostName'} ne 'localhost';
#### fin des modifs
####################################################################################
# reetp here is where we set up the backup command
# The Include/Exclude parts may be problematic UNLESS they are a local box
# Removed 'default' ssh options for localhost
@cmd = (
$job{'_rsyncLocal'},
"--archive",
"--hard-links",
"--stats",
"--delete-during",
"--ignore-errors",
"--delete-excluded",
"--relative",
"--partial",
$job{'BandwidthLimit'} ? "--bwlimit=$job{'BandwidthLimit'}" : '',
$job{'rsync--modify-window'} > 0
? "--modify-window=$job{'rsync--modify-window'}"
: '',
$job{'rsync--inplace'} ne 'no' ? "--inplace" : "",
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
"--numeric-ids",
"--rsync-path=\"$job{'_rsyncRemote'}\"",
$job{'remoteHostName'} ne 'localhost'
? "--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\""
: '',
( $linkDest
? "--link-dest='$job{'RootDir'}/$jobname/$linkDest'"
: ''
),
# Remove old include/exclude
#$job{'remoteHostName'} eq 'localhost' ? "$include" : '',
#$job{'remoteHostName'} eq 'localhost' ? "$exclude" : '',
# For now just add exclude
$exclude,
$job{'rsyncOptions'},
"$SourceDirs",
"$job{'RootDir'}/$jobname/scheduled.running/"
);
####################################################################################
#### debut des modifs faites dans le bas de la commande @cmd precedente
# beginning of the modifications made at the bottom of the previous @cmd command
#
#
# $job{'remoteHostName'} eq 'localhost' ? "$SourceDirs" : $job{'remoteUser'}.'@'.$job{'remoteHostName'}.":'$SourceDirs'",
#
##### fin des modifs
####################################################################################
}
else {
# Generic linux
my $SourceDirs = getSourceDirsString($jobname) || '/';
# my ($local, $remote) = getSourceDirsString($jobname);
@cmd = (
$job{'_rsyncLocal'},
"--archive",
"--hard-links",
"--stats",
"--delete-during",
"--ignore-errors",
"--delete-excluded",
"--relative",
"--partial",
$job{'BandwidthLimit'} ? "--bwlimit=$job{'BandwidthLimit'}" : '',
$job{'rsync--modify-window'} > 0
? "--modify-window=$job{'rsync--modify-window'}"
: '',
$job{'rsync--inplace'} ne 'no' ? "--inplace" : "",
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
"--numeric-ids",
"--rsync-path=\"$job{'_rsyncRemote'}\"",
$job{'remoteHostName'} ne 'localhost'
? "--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\""
: '',
( $linkDest
? "--link-dest='$job{'RootDir'}/$jobname/$linkDest'"
: ''
),
$job{'remoteHostName'} eq 'localhost' ? "$include" : '',
$job{'remoteHostName'} eq 'localhost' ? "$exclude" : '',
$job{'rsyncOptions'},
"$SourceDirs",
"$job{'RootDir'}/$jobname/scheduled.running/"
);
}
#else {
# What if we are not 'scheduled??
#affaErrorExit("Backup Command not equal 'scheduled'");
#}
# And here is where we actually start to backup
lg("Running Backup $jobname rsync...");
if ( $cliDebug == 1 ) {
# reetp remove empty array keys
@cmd = grep { $_ ne '' } @cmd;
printf "ExecCMD = @cmd\n";
}
$status = ExecCmd( @cmd, 0 );
$rsyncOutput = $ExecCmdOut;
if ( $cliDebug == 1 ) {
printf($rsyncOutput);
}
# if nothing was transferred, scheduled.running does not exist
if ( not -d "$job{'RootDir'}/$jobname/scheduled.running" ) {
lg("Error: No data transferred. Check include/exclude settings.");
$status += 1000;
}
if ( $status eq '0' or $status eq '23' or $status eq '24' ) {
# write report file
my $reportfile
= "$job{'RootDir'}/$jobname/scheduled.running/.AFFA4-REPORT";
lg("writing $reportfile");
my $rpt = Config::IniFiles->new();
( my $used, my $avail ) = df( $job{'RootDir'} );
$rpt->AddSection('Report');
$rpt->newval( 'Report', 'Date',
Date::Format::time2str( "%Y%m%d%H%M", time() ) );
$rpt->newval( 'Report', 'ExecutionTime', time() - $StartTime );
foreach my $s ( split( /[\n]/, $rsyncOutput ) ) {
#next if ( $s =~ /(^rsync)|(^sent)|(^total size is)|(^File list)|(^[^:]*$)/ );
#next if ( $s =~ /(^rsync)|(^sent)|(^total size is)|(^File list)|(?=:(?:\s|$))/ );
#next if ( $s =~ /(^Number)|(^Total)|(^Literal)|(^Matched)|(^File list)|(?=:(?:\s|$))/ );
# next - doesn't like next
if (( $s
=~ /(^Number of)|(^Total file)|(^Total transferred)|(^Literal)|(^Matched)|(^Total bytes)/
)
&& ( $s =~ /(?=:(?:\s|$))/ )
) {
my @p = split( /:/, $s );
( my $key = $p[0] ) =~ s/\b(\w)/\U$1/g; # first char uppercase
$key =~ s/ //g;
( my $val = trim( $p[1] ) ) =~ s/ .*$//;
#### ajout AG pour enlever les virgules générées par rsync3
#### Remove commas generated by Rsync 3
$val =~ s/,//g;
##### fin des modifs AG
$rpt->newval( 'Report', $key, $val );
}
}
$rpt->newval( 'Report', 'RootDirFilesystemUsed', $used ); # kbytes
$rpt->newval( 'Report', 'RootDirFilesystemAvail', $avail ); # kbytes
$rpt->newval( 'Report', 'RootDirFilesystemUsage',
sprintf( '%.1f', $used / ( $avail + $used ) * 100 ) ); # percent
$rpt->newval( 'Report', 'ExitStatus', $status );
$rpt->newval( 'Report', 'Dedup',
$job{"dedup"} ne 'yes' || not $linkDest ? 'no' : 'yes' );
$rpt->WriteConfig($reportfile);
# save used setup
my $usfile
= "$job{'RootDir'}/$jobname/scheduled.running/.$jobname-setup.ini";
my $js = open( JS, ">$usfile" )
or lg("Error: Couldn't write $usfile");
if ($js) {
print JS "[$jobname]\n";
print JS "; Created on "
. trim( Date::Format::ctime( time() ) ) . "\n";
foreach my $key ( sort { lc($a) cmp lc($b) } keys %job ) {
next if $key =~ /^_/;
if ( ref $job{$key} eq 'ARRAY' ) {
foreach my $r ( sort @{ $job{$key} } ) {
print JS "$key=$r\n";
}
}
else {
print JS "$key=$job{$key}\n" if $job{$key};
}
}
close(JS);
}
####################################################################################
#### ajout de modifs
compareRPMLists("$job{'RootDir'}/$jobname/scheduled.running");
#### fin ajout modifs
####################################################################################
}
else {
affaErrorExit("rsync failed with status $status.");
}
}
# End of if ( $Command eq 'scheduled' ) {
shiftArchives();
# We should get here if ne scheduled
if ( $Command ne 'scheduled' ) {
my %dd = ( 'daily' => $thisDay, 'weekly' => $thisWeek, 'monthly' => $thisMonth, 'yearly' => $thisYear );
$job{'_doneDates'}->setval( 'doneDates', $Command, $dd{$Command} );
$job{'_doneDates'}->RewriteConfig();
}
execPostJobCommand($jobname);
####################################################################################
#### debut des modifs
if ( $job{'SMEServer'} ne 'no' ) {
my $postbackup = 'SME/signal-post-backup';
if ( $job{'remoteHostName'} eq 'localhost' ) {
execJobCommand( $jobname, $postbackup );
}
else {
execJobCommandRemote( $jobname, $postbackup );
}
}
#### fin des modifs
####################################################################################
deduplicate($jobname);
DiskSpaceWarn();
sendSuccessMesssage();
affaExit("Completed job '$jobname $Command ($job{'remoteHostName'})'");
exit 0;
####################################################################################
sub sanityCheckConfFiles () {
my @cfgfiles = getConfigFileList();
my $error = 0;
my $count = 0;
my $line = '';
foreach my $cfgFile (@cfgfiles) {
open( INFO, $cfgFile ) or die("Could not open file.");
$count = 0;
while ( my $line = <INFO> ) {
if ( not( $line =~ /\r$/ | $line =~ /\r\n/ | $line =~ /\n$/ ) ) {
lg("Please check $cfgFile $line and add a LF or CR/LF");
print "Please check $cfgFile $line and add a LF or CR/LF\n";
$error = 1;
last if ++$count == 2;
}
}
}
close(INFO);
if ($error) {
print "Sanity file check failed. Exiting - see your logs";
affaErrorExit(
"Your config file MUST end with a line LF or CR/LF - check your log for details"
);
} else {
return;
}
}
sub getDefaultConfig() {
my %job = (
'AutomountDevice' => '',
'AutomountOptions' => '',
'AutomountPoint' => '',
'AutoUnmount' => '',
'BandwidthLimit' => 0,
'chattyOnSuccess' => 0,
'ConnectionCheckTimeout' => 120,
'dailyKeep' => 7,
'Debug' => 'no',
'dedup' => 'no',
'dedupKill' => 'no',
'Description' => '',
'DiskSpaceWarn' => 'strict', # strict | normal | risky | none
'_doneDates' => 0,
'EmailAddress' => ['root'], # multivalue
'Exclude' => [], # multivalue
'Include' => [], # multivalue
'killAt' => '',
'localNice' => 0,
'localNiceBinary' => 'nice',
'localRsyncBinary' => 'rsync',
'localSSHBinary' => 'ssh',
'_lockfile' => '',
'_LockIsSet' => 0,
'monthlyKeep' => 12,
'NRPEtrigger' => 24,
'postJobCommand' => [],
'postJobCommandRemote' => [],
'preJobCommand' => [],
'preJobCommandRemote' => [],
'RemoteAuthorizedKeysFile' => '.ssh/authorized_keys2', # relative to remoteUser Homedir
'remoteHostName' => '',
'remoteNice' => 0,
'remoteNiceBinary' => '/bin/nice',
'remoteRsyncBinary' => '/usr/bin/rsync',
'remoteUser' => 'root',
'resumeKilledAt' => '',
'resumeAfterBoot' => 'yes',
'RetryAfter' => 600,
'RetryAttempts' => 4,
'RetryNotification' => 'yes',
'RootDir' => '/var/affa',
'rsyncCompress' => 'yes',
'_rsyncd' => 0,
'rsyncdMode' => 'no',
'rsyncdModule' => 'AFFA',
'rsyncdPassword' => '',
'rsyncdUser' => 'affa',
'rsync--inplace' => 'yes',
'_rsyncLocal' => '/usr/bin/rsync',
'rsync--modify-window' => 0,
'rsyncOptions' => '-v ',
'_rsyncRemote' => '/usr/bin/rsync',
'rsyncTimeout' => 900,
'SambaShare' => 'no',
'SambaValidUser' => ['affa'], # multivalue
'scheduledKeep' => 1,
'_sshOpts' => '',
'sshPort' => 22,
'status' => 'enabled',
'TimeSchedule' => ['2230'], # multivalue
'weeklyKeep' => 4,
'yearlyKeep' => 2,
'SMEServer' => 'no',
'RPMCheck' => 'no',
'Watchdog' => 'no',
);
###########################################################################
#### ajoute 'SMEServer'=>'no', 3 lignes plus haut
#### ajoute 'RPMCheck'=>'no', 3 lignes plus haut
#### ajoute 'Watchdog'=>'no', 3 lignes plus haut
###########################################################################
return %job;
}
sub getMultivalueKeys() {
my %multi = (
'EmailAddress' => 'string',
'Exclude' => 'yes',
'Include' => 'yes',
'SambaValidUser' => 'string',
'TimeSchedule' => 'yes',
'preJobCommand' => 'yes',
'preJobCommandRemote' => 'yes',
'postJobCommand' => 'yes',
'postJobCommandRemote' => 'yes',
);
return %multi;
}
sub showDefaults() {
%job = getJobConfig('');
foreach my $key ( sort { lc($a) cmp lc($b) } keys %job ) {
next if $key =~ /^_/;
if ( ref $job{$key} eq 'ARRAY' ) {
foreach my $r ( sort @{ $job{$key} } ) {
print "$key=$r\n";
}
}
else {
print "$key=$job{$key}\n";
}
}
}
sub getJobConfig( $ ) {
my $jobname = shift || '';
my %job = getDefaultConfig();
if ( not -f $configfile ) {
my @cfgfiles = getConfigFileList(); # only valid ones
my @cmd = ( 'echo', '-n', '>', $configfile, ';', 'chmod', '400', $configfile, ';', 'cat' );
# mod my @cmd = ( 'echo', '-n', '>', $configfile, ';', 'chmod', '400', $configfile, ';');
foreach my $s (@cfgfiles) {
# mod push(@cmd, ('cat', $s,'>>',$configfile,';', 'echo','-n' ,'>>',$configfile, ';' ));
push( @cmd, '"'.$s.'"' );
}
push( @cmd, '>' );
push( @cmd, $configfile );
ExecCmd( @cmd, 0 );
$cfg = Config::IniFiles->new( -file => $configfile, -default => 'GlobalAffaConfig', -nocase => 0 );
}
my %multi = getMultivalueKeys();
# configured global Defaults
my @p = $cfg->Parameters('GlobalAffaConfig');
foreach my $k (@p) {
if ( $multi{$k} ) {
my @m = $cfg->val( $jobname, $k );
$job{$k} = [@m];
$job{"_$k"} = join( ',', @m ) if $multi{$k} eq 'string';
}
else {
$job{$k} = $cfg->val( $jobname, $k );
}
}
# configured Job - reetp needs a fix
@p = $cfg->Parameters($jobname);
foreach my $k (@p) {
if ( not defined $job{$k} ) {
my @val = $cfg->val( $jobname, $k );
my $txt = "Unknown parameter '$k=$val[0]' in configuration of job '$jobname'";
print "$txt\n";
affaExit($txt);
}
if ( $multi{$k} ) {
my @m = $cfg->val( $jobname, $k );
######### following 1 line added by Arnaud to solve bug#9449: blank characters into file job.conf##
map( {
s/^\s+|\s+$//g;
$_
} @m );
$job{$k} = [@m];
$job{"_$k"} = join( ',', @m ) if $multi{$k} eq 'string';
}
else {
$job{$k} = $cfg->val( $jobname, $k );
lg("** Error in job config $jobname: Multivalues are not allowed for key $k") if $job{$k} =~ /\n/;
$job{$k} =~ s/\n.*//;
######### following 1 line added by Arnaud to solve bug#9449: blank characters into file job.conf##
$job{$k} =~ s/^\s+|\s+$//g;
}
}
# globalStatus overides job status
$job{'status'} = $job{'globalStatus'} if ( ( $job{'globalStatus'} || '' ) =~ /^(enabled|disabled)$/ );
# check and set save keep settings if needed
$job{'scheduledKeep'} = 1 if ( $job{'scheduledKeep'} < 1 );
$job{'dailyKeep'} = 0 if ( $job{'dailyKeep'} < 0 );
$job{'weeklyKeep'} = 0 if ( $job{'weeklyKeep'} < 0 );
$job{'monthlyKeep'} = 0 if ( $job{'monthlyKeep'} < 0 );
$job{'yearlyKeep'} = 0 if ( $job{'yearlyKeep'} < 0 );
$job{'_rsyncLocal'} = $job{'localRsyncBinary'};
$job{'_rsyncLocal'} = "$job{'localNiceBinary'} --adjustment=$job{'localNice'} $job{'localRsyncBinary'}"
if $job{'localNice'};
$job{'_rsyncRemote'} = $job{'remoteRsyncBinary'};
$job{'_rsyncRemote'} = "$job{'remoteNiceBinary'} --adjustment=$job{'remoteNice'} $job{'remoteRsyncBinary'}"
if $job{'remoteNice'};
$job{'_rsyncd'} = $job{'rsyncdMode'} eq 'yes' ? 1 : 0;
$ENV{'RSYNC_PASSWORD'} = $job{'rsyncdPassword'};
$job{'rsyncdPassword'} = $job{'rsyncdPassword'} ? '<not shown>' : '';
$job{'Debug'} = 'yes' if $opts{'debug'};
$job{'Archive'} = $job{'Archive'} ? $job{'Archive'} : 'scheduled.0';
####################################################################################
#### ajout modifs
$sshQuiet = $job{'Debug'} eq 'yes' ? '' : '-q';
#### fin modifs
####################################################################################
# get Done Dates
if ($jobname) {
# reetp add -i id_rsa_affa here
$job{'_sshOpts'} = "-p $job{'sshPort'} -i /root/.ssh/id_rsa_affa -o CheckHostIP=no -o StrictHostKeyChecking=no -o HostKeyAlias=$jobname -o UserKnownHostsFile=/root/.ssh/knownhosts-$jobname" . ( $job{'Debug'} ne 'yes' ? ' -q' : '' );
$job{'_lockfile'} = "$lockdir/$jobname";
}
return %job;
}
sub checkConfig() {
my $errcnt = 0;
my $casecnt = 0;
my %multi = getMultivalueKeys();
my %defaults = getDefaultConfig();
my %defaults_lc = map { lc $_ => $_ } keys %defaults;
foreach my $k ( keys %defaults ) {
$defaults_lc{ lc $k } = $k;
}
my @cfiles = getConfigFileList();
foreach my $f (@cfiles) {
my $cfg = Config::IniFiles->new( -file => $f, -nocase => 0 );
foreach my $s ( $cfg->Sections() ) {
$s =~ /([a-z0-9_\.-]*)/i;
print "Checking job section $s ($f)\n";
if ( $s ne $1 ) {
print "*** ERROR: Illegal characters in job name\n";
$errcnt++;
}
my @parms = $cfg->Parameters($s);
foreach my $p (@parms) {
my @v = $cfg->val( $s, $p );
my $val = $v[0];
if ( $p =~ /(sendStatus|globalStatus)/ ) {
if ( $s ne 'GlobalAffaConfig' ) {
print "*** ERROR: Key $p only allowed in GlobalAffaConfig section\n";
$errcnt++;
}
elsif ( $p eq 'globalStatus' and not $val =~ /(enabled|disabled|jobs)/ ) {
print "*** ERROR: Bad value: $p=$val\n";
$errcnt++;
}
elsif ( $p eq 'sendStatus' and not $val =~ /(daily|weekly|monthly|never)/ ) {
print "*** ERROR: Bad value: $p=$val\n";
$errcnt++;
}
next;
}
if ( $p =~ /^_/ or not defined $defaults{$p} and not defined $defaults_lc{ lc $p } ) {
print "*** ERROR: Unknown key: $p\n";
$errcnt++;
next;
}
if ( not defined $defaults{$p} and defined $defaults_lc{ lc $p } ) {
my $err = renameConfigKey( $s, $p, $defaults_lc{ lc $p } );
print "*** CASE ERROR: $p -> $defaults_lc{lc $p} FIXED.\n";
$p = $defaults_lc{ lc $p };
$casecnt++;
}
if ( scalar @v > 1 and not $multi{$p} ) {
print "*** ERROR: Multivalues are not allowed for key $p\n";
$errcnt++;
next;
}
if ( $p eq 'killAt' or $p eq 'resumeKilledAt' ) {
if ( not $val =~ /^\d\d\d\d$/ or $val > 2359 ) {
print "*** ERROR: Bad value: $p=$val\n";
$errcnt++;
}
}
if ( $p eq 'TimeSchedule' ) {
foreach my $t (@v) {
if ( not $t =~ /^\d\d\d\d$/ or $t > 2359 ) {
print "*** ERROR: Bad value: $p=$t\n";
$errcnt++;
}
}
}
if ( $p eq 'RootDir' ) {
if ( not -d $val ) {
print "*** ERROR: Directory does not exist: $p=$val\n";
$errcnt++;
}
}
if ( $p eq 'DiskSpaceWarn' ) {
if ( not $val =~ /^(strict|normal|risky|none)$/ ) {
print "*** ERROR: Bad value: $p=$val\n";
$errcnt++;
}
}
if ( $p =~ /JobCommand/ ) {
foreach my $s (@v) {
if ( not -x "/etc/affa/scripts/$s" ) {
print "*** ERROR: No executable script found: $p=$s\n";
$errcnt++;
}
}
}
}
$cfg = Config::IniFiles->new( -file => $f, -nocase => 0 ) if $casecnt;
if ( $s ne 'GlobalAffaConfig' ) {
if ( not $cfg->val( $s, 'remoteHostName' ) ) {
print "*** ERROR: Key remoteHostName is mandatory\n";
$errcnt++;
next;
}
}
}
}
if ( not $errcnt and not $casecnt ) {
print "Configuration is ok.\n";
}
else {
print $casecnt ? "Fixed $casecnt case errors\n" : '';
print $errcnt ? "Configuration has $errcnt errors\n" : '';
}
}
sub getJobs() {
my @joblist;
foreach my $job ( $cfg->Sections() ) {
next if $job eq 'GlobalAffaConfig';
push( @joblist, $job );
}
return @joblist;
}
sub getConfigFileList() {
dbg('Fetching list of all config files');
my @dirList = qw (/etc/affa/ /etc/affa/conf.d/);
my @ls = ();
my @list = ();
foreach my $dir (@dirList) {
if ( -d $dir ) {
my @cmd = (
'find', "$dir", '-maxdepth 1', '-type', 'f', '-name', '"*.conf"'
);
ExecCmd( @cmd, 0 );
@ls = split( /[\r\n]/, $ExecCmdOut );
@list = (@list, @ls);
}
}
my @ret = ();
foreach my $s (@list) {
my $c = Config::IniFiles->new( -file => $s );
if ( not $c ) {
foreach my $e (@Config::IniFiles::errors) {
$e =~ s/[\r\n][\t ]*/ /g;
my $txt = "CONFIG ERROR: $e";
print "$txt\n";
lg($txt);
}
my $txt = "CONFIG ERROR: File '$s' ignored!";
print "$txt\n";
lg($txt);
}
else {
push( @ret, $s );
}
}
return sort @ret;
}
sub rewriteConfigVal($$$) {
( my $jobname, my $key, my $newval ) = @_;
my $err = 1;
my $c = openOrgConfig($jobname);
if ($c) {
$c->setval( $jobname, $key, $newval );
writeConfigFile($c);
$err = 0;
}
return $err;
}
sub renameConfigKey($$$) {
( my $jobname, my $oldkey, my $newkey ) = @_;
my $err = 1;
my $c = openOrgConfig($jobname);
if ($c) {
my @val = $c->val( $jobname, $oldkey );
$c->delval( $jobname, $oldkey );
my @nv = $c->val( $jobname, $newkey );
if ( scalar @nv ) {
push( @val, @nv );
}
$c->newval( $jobname, $newkey, @val );
writeConfigFile($c);
$err = 0;
}
return $err;
}
sub openOrgConfig($) {
my $jobname = shift;
my $cfg;
my @list = getConfigFileList();
foreach my $s (@list) {
$cfg = Config::IniFiles->new( -file => $s );
if ( $cfg->SectionExists($jobname) ) {
last;
}
undef $cfg;
}
return $cfg;
}
sub setLock() {
open( LOCK, ">$job{'_lockfile'}" ) or die "Error: Couldn't create lockfile $job{'_lockfile'}\n";
print LOCK "$process_id\n";
close(LOCK) or warn "Error: Couldn't close lockfile $job{'_lockfile'}\n";
$job{'_LockIsSet'} = 1;
}
sub removeLock() {
unlink( $job{'_lockfile'} ) if ( -f $job{'_lockfile'} && $job{'_LockIsSet'} );
$job{'_LockIsSet'} = 0;
}
sub getLock($) {
my $jobname = shift;
%job = getJobConfig($jobname);
my $lockpid = 0;
if ( open( LOCK, "<$job{'_lockfile'}" ) ) {
$lockpid = <LOCK>;
chomp($lockpid);
close(LOCK);
}
if ($lockpid) {
if ( -f "/proc/$lockpid/stat" ) {
if ( open( STAT, "</proc/$lockpid/stat" ) ) {
my $stat = <STAT>;
chomp($stat);
close(STAT);
if ( not( $stat =~ /^$lockpid \(affa\)/ ) ) {
$lockpid = 0;
unlink $job{'_lockfile'};
lg("Orphaned lock found and removed.");
}
}
}
else {
$lockpid = 0;
unlink $job{'_lockfile'};
lg("Orphaned lock found and removed.");
}
}
return $lockpid;
}
# returns process id if dedup is running
sub findProcessId($$) {
( my $treepid, my $pattern ) = @_;
my %pt;
my $ret = '';
getChildProcess( $treepid, \%pt );
foreach my $p ( keys %pt ) {
if ( $pt{$p} =~ /$pattern/ ) {
$ret = $p;
last;
}
}
return $ret;
}
sub getChildProcess($$) {
( my $parent, my $pt ) = @_;
my $proc_table = Proc::ProcessTable->new();
foreach my $proc ( @{ $proc_table->table() } ) {
if ( $proc->ppid == $parent ) {
my $cmd = '';
if ( open( CMD, "</proc/" . $proc->pid . "/cmdline" ) ) {
$cmd = <CMD> || '';
close(CMD);
$cmd =~ s/\0/ /g;
}
$pt->{ $proc->pid } = $cmd;
getChildProcess( $proc->pid, $pt );
}
}
return $pt;
}
sub getProcessState($) {
my $jobname = shift;
my $lockpid = getLock($jobname);
my $state = '';
if ($lockpid) {
my $job = getJobConfig($jobname);
if ( findProcessId( $lockpid, "$job{'localRsyncBinary'}.*scheduled.running" ) ) {
$state = "running rsync";
}
elsif ( findProcessId( $lockpid, $dedupBinary ) ) {
$state = "deduplicating";
}
else {
$state = 'waiting';
}
}
else {
my $job = getJobConfig($jobname);
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
if ( -d "$job{'RootDir'}/$jobname/scheduled.running" ) {
$state = 'rsync interrupted';
}
elsif ( -f $rptfile ) {
my $rpt = Config::IniFiles->new( -file => $rptfile );
if ( $rpt->exists( 'Report', 'Dedup' )
&& $rpt->val( 'Report', 'Dedup' ) eq 'yes'
&& !$rpt->exists( 'Report', 'DedupDate' ) ) {
$state = 'dedup interrupted';
}
}
}
return $state;
}
sub checkConnection($) {
my $jobname = shift;
return checkConnection_silent( $jobname, 0, 0 );
}
sub checkConnection_silent($$$) {
####################################################################################
#### debut modifs
return 0 if ( $job{'remoteHostName'} eq 'localhost' );
#### fin modifs
####################################################################################
my ( $jobname, $viapi, $silent ) = @_;
my %job = getJobConfig($jobname);
my $status = 0;
my @cmd;
if ( $job{'_rsyncd'} ) {
lg( "Checking rsyncd connection to " . $job{'remoteHostName'} );
@cmd = (
$job{'_rsyncLocal'}, '-dq',
( $job{'rsyncdUser'} ? $job{'rsyncdUser'} . '@' : '' )
. $job{'remoteHostName'} . "::'"
. $job{'rsyncdModule'} . "'"
);
not ExecCmd( @cmd, 0 )
or affaErrorExit( "Rsyncd connection to "
. $job{'remoteHostName'}
. " failed. Did you set the rsyncdUser, rsyncdPassword and rsyncdModule properties correctly?" );
}
else {
lg( "Checking SSH connection to " . $job{'remoteUser'} . '@' . $job{'remoteHostName'} );
@cmd = ( '/usr/bin/ssh', '-o', "ConnectTimeout=$job{'ConnectionCheckTimeout'}", '-o', 'PasswordAuthentication=no', $job{'_sshOpts'}, $job{'remoteUser'} . '@' . $job{'remoteHostName'}, 'echo OK'
);
ExecCmd( @cmd, 0 );
chomp($ExecCmdOut);
if ( $ExecCmdOut ne "OK" ) {
$status = -1;
if ( !$silent ) {
affaErrorExit(
"SSH connection to " . $job{'remoteHostName'} . " failed. Did you send the public key?" );
}
}
}
return $status;
}
####################################################################################
#### début ajout modifs
sub installedRPMsList($) {
return if $job{'SMEServer'} eq 'no' or $job{'RPMCheck'} ne 'yes';
my $forceLocal = shift(@_);
# requires bash shell on remote host
my @cmd;
if ( $job{'remoteHostName'} eq 'localhost' or $forceLocal ) {
lg("writing list of installed RPMs on localhost ($rpmlist)");
@cmd = ( "/sbin/e-smith/affa-rpmlist.sh", ">$rpmlist" );
}
else {
lg( "writing list of installed RPMs on " . $job{'remoteHostName'} . " ($rpmlist)" );
#### ligne suivante: rajouté $jobname
remoteCopy( $jobname, "/sbin/e-smith/affa-rpmlist.sh", "/sbin/e-smith/affa-rpmlist.sh" );
@cmd = ( '/usr/bin/ssh', '-p', $job{'sshPort'}, '-i', '/root/.ssh/id_rsa_affa', '-o', "HostKeyAlias=$jobname", '-o',
"UserKnownHostsFile=/root/.ssh/knownhosts-$jobname", $sshQuiet, $job{'remoteHostName'}, "'/sbin/e-smith/affa-rpmlist.sh>$rpmlist'"
);
}
# not ExecCmd( @cmd, 0 ) or affaErrorExit("writing list of installed RPMs failed.");
my $listStatus = ExecCmd (@cmd, 0);
if ($listStatus ne 0) {
affaErrorExit("Status = $listStatus - Error writing list of installed RPMs");
}
}
sub compareRPMLists($) {
return if $job{'SMEServer'} eq 'no' or $job{'remoteHostName'} eq 'localhost' or $job{'RPMCheck'} ne 'yes';
my $localRPMList = shift;
lg("Comparing installed RPMs on backup und remote host");
my $RPMPath = "home/e-smith/db/affa-rpmlist";
my %remoteRPM;
lg ("Reading remote RPM list Local = $localRPMList RPMPath = $RPMPath");
my $newRPMPath = "$localRPMList/$RPMPath";
lg ("New path = $newRPMPath");
open( RP, "$newRPMPath" ) or affaErrorExit("Couldn't open $localRPMList/$RPMPath");
while (<RP>) {
my @z = split( " ", $_ );
$remoteRPM{ $z[0] } = $z[1];
}
close(RP);
installedRPMsList(1);
my %localRPM;
dbg("Reading local RPM list");
open( RP, "</$RPMPath" ) or lg("Error: Couldn't open /$RPMPath");
while (<RP>) {
my @z = split( " ", $_ );
$localRPM{ $z[0] } = $z[1];
}
close(RP);
# vergleichen
my ( @missing, @VersionMismatch );
my $md5 = Digest::MD5->new;
foreach my $p ( keys %remoteRPM ) {
if ( not $localRPM{$p} ) {
push( @missing, "$p-" . $remoteRPM{$p} );
$md5->add( "$p-" . $remoteRPM{$p} );
}
elsif ( $localRPM{$p} ne $remoteRPM{$p} ) {
push( @VersionMismatch, "$p-" . $remoteRPM{$p} );
$md5->add( "$p-" . $remoteRPM{$p} );
}
}
my $RPMFilename = "$job{'RootDir'}/$jobname/rpms-missing.txt";
my $MD5Filename = "$job{'RootDir'}/$jobname/.md5-rpms-missing-" . $md5->hexdigest;
dbg("RPMFilename=$RPMFilename");
dbg("MD5Filename=$MD5Filename");
if ( not -f $MD5Filename ) {
open( RP, ">$RPMFilename" ) or affaErrorExit("Couldn't open $RPMFilename for writing.");
my $out = '';
foreach my $k (@missing) {
$out .= "$k\n";
}
if ($out) {
print RP "* \n";
print RP "* The following packages are installed on " . $job{'remoteHostName'} . ",\n";
print RP "* but they are missing on this backup host:\n";
print RP "* \n";
print RP $out;
}
$out = '';
foreach my $k (@VersionMismatch) {
$out .= "$k\n";
}
if ($out) {
print RP "\n* \n";
print RP "* The following packages are installed on both,\n";
print RP "* the source " . $job{'remoteHostName'} . " and on this backup host,\n";
print RP "* but the version does not match:\n";
print RP "* \n";
print RP $out;
}
close(RP);
if ( $job{'RPMCheck'} eq 'yes' and not -f $MD5Filename and $job{'_EmailAddress'} ) {
my $msg = new Mail::Send;
$msg->subject(
"Missing RPMs on $SystemName.$DomainName ($LocalIP) compared with " . $job{'remoteHostName'} );
$msg->to( $job{'_EmailAddress'} );
$msg->set( "From", "\"Affa Backup Server\" <noreply\@$SystemName.$DomainName>" );
my $fh = $msg->open;
open( RP, "<$RPMFilename" ) or affaErrorExit("Couldn't open $RPMFilename.");
#### attention dans AffaV2, c'est $job{'EmailAdresses'} donc modifié aussi dans
#### la déclaration des variables et paramètres
while (<RP>) {
print $fh $_;
}
close(RP);
$fh->close;
lg( "RPM check message sent to " . $job{'_EmailAddress'} );
}
unlink( glob("$job{'RootDir'}/$jobname/.md5-rpms-missing-*") );
open( RP, ">$MD5Filename" ) or affaErrorExit("Couldn't open $MD5Filename for writing.");
print RP "md5sum of content of file $RPMFilename\n";
close(RP);
}
}
###### fin ajout modifs
####################################################################################
# get directories and files to backup from db
# Add standard dirs if SME.
sub getSourceDirs($) {
my $jobname = shift; # From ($)
my %job = getJobConfig($jobname);
my @result = ();
my @remoteDirectories = ();
my @remoteDirList = ();
my @remoteDirs = ();
my @localDirList = ();
my @localDirs = ();
my $remoteDir = '';
my $backupListScript = "SME/backupList.sh";
my $remoteHost = $job{'remoteHostName'};
####################################################
# This is to rise a specific backup
if ( $opts{'rise'} || $opts{'full-restore'} ) {
$archive = $restoreVer eq '' ? $job{'Archive'} : $restoreVer; #Overide archive from the CLI if set
printf ("getSourceDirs Archive $archive\n");
}
# For non SME
my $exclude = getExcludedString();
my $include = getIncludedString()
; #--include="/etc/sudoers" --include="/media/tmp" --include="/usr/share/nextcloud"
####################################################################################
#### debut des modifs: recopie a partir de la version 2rc5
if ( $job{'SMEServer'} ne 'no' ) {
# reetp for reference here we use the esmith standard backup routine
# to get the standard dirs for backup
# Includes & Excludes
my @include = getIncExc("Include");
my @exclude = getIncExc("Exclude");
# For a job run we have to create the local and remote directory lists
# For restore we only need to read the lists that are stored
# for rise ??
# Only continue here if both are 0 ie this is a backup operation
if ( !( ( $opts{'full-restore'} ) || ( $opts{'rise'} ) ) ) {
# If we are SME remote host - not localhost
if ( $remoteHost ne 'localhost' ) {
# SME Remote routine
lg("remote copy backupList.pl to target");
remoteCopy( $jobname, "/etc/affa/scripts/SME/backupList.pl",
"/tmp/backupList.pl" );
@remoteDirectories
= execJobCommandRemote( $jobname, $backupListScript );
@remoteDirList = split( ',', $remoteDirectories[0] );
# We have the remote directories in @remoteDirs
# We have the additional includes in @includes
# For full restore we will have to save both src and destination dirs
foreach my $dir (@remoteDirList) {
$dir = "/$dir" if not $dir =~ /^\//;
chomp $dir;
trim($dir);
push( @localDirs, $dir );
my $remoteHostDir
= $job{'remoteUser'} . '@' . "$remoteHost:$dir";
push( @remoteDirs, $remoteHostDir );
}
foreach my $dir (@include) {
$dir = "/$dir" if not $dir =~ /^\//;
chomp($dir);
push( @localDirs, $dir );
my $remoteHostDir
= $job{'remoteUser'} . '@' . "$remoteHost:$dir";
push( @remoteDirs, $remoteHostDir );
}
}
else {
# SME local routine - use SME Backup to discover dirs
# Slightly duplicated as it should produce identical local and remote strings
my $b = new esmith::Backup
or die "Error: Couldn't create Backup object\n";
foreach my $k ( $b->restore_list ) {
$k = "/$k" if not $k =~ /^\//;
chomp $k;
trim($k);
push( @localDirs, $k );
push( @remoteDirs, $k );
}
push( @localDirs, "/etc/affa" );
foreach my $dir (@include) {
$dir = "/$dir" if not $dir =~ /^\//;
chomp($dir);
trim($dir);
push( @localDirs, $dir );
push( @remoteDirs, $dir );
}
push( @remoteDirs, "/etc/affa" );
}
# Remove duplicates
# This is done in the calling sub getSourceDirs but we need to do it before
@remoteDirs = sort ( keys { map { $_ => 1 } @remoteDirs } );
@localDirs = sort ( keys { map { $_ => 1 } @localDirs } );
# Write @SrcDirList to file
open my $fd, '>',
"$job{'RootDir'}/$jobname/scheduled.running/src-$backupList"
or die
"Cannot open $job{'RootDir'}/$jobname/scheduled.running/src-$backupList : $!";
print $fd ( join ',', @localDirs );
close $fd;
# Write @remoteDirList to file
open my $fh, '>',
"$job{'RootDir'}/$jobname/scheduled.running/dest-$backupList"
or die
"Cannot open $job{'RootDir'}/$jobname/scheduled.running/dest-$backupList : $!";
print $fh ( join ',', @remoteDirs );
close $fh;
}
# This is restore and rise(?)
else {
# read @localDirList and @remoteDirList from file
# Which dir does it restore - from Argv above in full-restore
open my $fd, '<',
"$job{'RootDir'}/$jobname/$archive/src-$backupList"
or die
"Cannot open $job{'RootDir'}/$jobname/$archive/src-$backupList : $!";
chomp( @localDirList = <$fd> );
@localDirs = split( ',', $localDirList[0] );
close $fd;
open my $fh, '<',
"$job{'RootDir'}/$jobname/$archive/dest-$backupList"
or die
"Cannot open $job{'RootDir'}/$jobname/$archive/dest-$backupList : $!";
chomp( @remoteDirList = <$fh> );
@remoteDirs = split( ',', $remoteDirList[0] );
close $fh;
# Check if the two arrays match
my $err = 0;
my @splitRemote = ();
foreach my $localDir (@localDirs) {
my $answer = grep /\Q$localDir\E$/, @remoteDirs;
if ($answer) {
#print "Local $localDir found in remote \n";
}
else {
$err = 1;
lg( "Local directory $localDir not found in remote. Check the backup directory lists"
);
}
}
foreach my $remote (@remoteDirs) {
@splitRemote = split( ':', $remote );
my $remoteDir = $splitRemote[1];
my $answer = grep /\Q$remoteDir\E$/, @localDirs;
if ($answer) {
# print "Remote $remote found in local \n";
}
else {
$err = 1;
lg( "Remote directory $remoteDir not found in local. Check the backup directory lists"
);
}
}
# Exit on any errors
if ($err == 1) {
affaErrorExit ("Check the global log for details, Check directory lists match");
}
}
}
# Else this is not SME
# In which case you need routines for localhost & remote host
else {
# Oh dear we are standard linux and I have no solution yet
# $job{'SMEServer'} ne no
print "This is not a SME server - no configuration available";
affaExit("Standard Linux - we should not be here");
}
return ( \@localDirs, \@remoteDirs );
}
sub getSourceDirsString($) {
my $jobname = shift;
my ($local, $remote) = getSourceDirs($jobname);
my @localDirs = @$local;
my @remoteDirs = @$remote;
my $localresult = '';
my $remoteresult = '';
# @SourceDirs = keys {map {$_ => 1} @SourceDirs};
foreach my $k (@localDirs) {
$localresult .= '"' . $k . '" ';
}
foreach my $k (@remoteDirs) {
$remoteresult .= '"' . $k . '" ';
}
trim ($localresult);
trim ($remoteresult);
return ($localresult, $remoteresult);
}
# Get Include & Exclude directories for SME
sub getIncExc ($) {
my $IncExc = shift;
my @result = ();
foreach my $i ( @{ $job{$IncExc} } ) {
$i =~ s/^\///;
$i =~ s/\/$//;
$i =~ s/"/\\"/g;
$i = "/$i";
chomp ($i);
trim ($i);
push (@result, $i) # Have to leave the / there now;
#print $i;
}
return @result;
}
# get files to include
sub getIncludedString() {
my $result = '';
foreach my $k ( @{ $job{'Include'} } ) {
$k =~ s/^\///;
$k =~ s/\/$//;
$k =~ s/"/\\"/g;
$result .= '--include="/' . $k . '" ' if $k and not $k =~ /^\//;
}
chomp($result);
return trim($result);
}
# get directories and files to exclude
sub getExcludedString() {
my $result = '';
foreach my $k ( @{ $job{'Exclude'} } ) {
$k =~ s/^\///;
$k =~ s/\/$//;
$k =~ s/"/\\"/g;
$result .= '--exclude="/' . $k . '" ' if $k;
}
chomp($result);
return trim($result);
}
sub getLinkdest($$) {
( my $jobname, my $prev ) = @_;
my %job = getJobConfig($jobname);
my %timestamps;
my @st;
my $dir = opendir( DIR, "$job{'RootDir'}/$jobname" );
my $ar;
while ( defined( $ar = readdir(DIR) ) ) {
next if not $ar =~ /^(scheduled|daily|weekly|monthly|yearly)\.[0-9]+$/;
my $d = getReportVal( $jobname, $ar );
if ( $d =~ /^\d{12}$/ ) {
$timestamps{$ar} = $d;
}
}
foreach my $k ( sort { $timestamps{$b} cmp $timestamps{$a} } keys %timestamps ) {
push( @st, $k );
}
return $prev ? ( $st[1] || '' ) : ( $st[0] || '' );
}
sub shiftArchives() {
lg("Shifting backup archives...");
my $nothingDone = "Nothing to be done.";
my $JobDir = "$job{'RootDir'}/$jobname";
my $basename = "$JobDir/$Command";
if ( -d "$basename.0" and ( $Command ne 'scheduled' or -f "$JobDir/scheduled.running/.AFFA4-REPORT" ) ) {
my $keep = $job{ $Command . 'Keep' } - 1;
if ( -d "$basename.$keep" ) {
File::Path::mkpath( "$job{'RootDir'}/$jobname/.AFFA-TRASH", 0, 0700 )
unless -d "$job{'RootDir'}/$jobname/.AFFA-TRASH";
$nothingDone = '';
moveFileorDir( "$basename.$keep", "$job{'RootDir'}/$jobname/.AFFA-TRASH/$Command.$keep-$curtime-$$" );
}
for ( my $i = $keep; $i > 0; $i-- ) {
if ( -d "$basename." . ( $i - 1 ) ) {
$nothingDone = '';
moveFileorDir( "$basename." . ( $i - 1 ), "$basename.$i" );
chmod( 0700, "$basename.$i" );
}
}
}
if ( $Command eq 'scheduled' and not -d "$JobDir/scheduled.0" and -f "$JobDir/scheduled.running/.AFFA4-REPORT" ) {
moveFileorDir( "$JobDir/scheduled.running", "$JobDir/scheduled.0" );
$nothingDone = '';
}
if ( $Command eq 'yearly' ) {
my $src = "$JobDir/monthly." . ( $job{'monthlyKeep'} - 1 );
$src = "$JobDir/weekly." . ( $job{'weeklyKeep'} - 1 ) if ( not -d $src ) && !$job{'monthlyKeep'};
$src = "$JobDir/daily." . ( $job{'dailyKeep'} - 1 )
if ( not -d $src ) && !$job{'monthlyKeep'} && !$job{'weeklyKeep'};
$src = "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 )
if ( not -d $src ) && !$job{'monthlyKeep'} && !$job{'weeklyKeep'} && !$job{'dailyKeep'};
if ( -d $src ) {
moveFileorDir( $src, "$basename.0" );
chmod( 0700, "$basename.0" );
$nothingDone = '';
}
}
elsif ( $Command eq 'monthly' ) {
my $src = "$JobDir/weekly." . ( $job{'weeklyKeep'} - 1 );
$src = "$JobDir/daily." . ( $job{'dailyKeep'} - 1 ) if ( not -d $src ) && !$job{'weeklyKeep'};
$src = "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 )
if ( not -d $src ) && !$job{'weeklyKeep'} && !$job{'scheduledKeep'};
if ( -d $src ) {
moveFileorDir( $src, "$basename.0" );
chmod( 0700, "$basename.0" );
$nothingDone = '';
}
}
elsif ( $Command eq 'weekly' ) {
my $src = "$JobDir/daily." . ( $job{'dailyKeep'} - 1 );
$src = "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 ) if ( not -d $src ) && !$job{'dailyKeep'};
if ( -d $src ) {
moveFileorDir( $src, "$basename.0" );
chmod( 0700, "$basename.0" );
$nothingDone = '';
}
}
elsif ( $Command eq 'daily' and -d "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 ) ) {
moveFileorDir( "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 ), "$basename.0" );
chmod( 0700, "$basename.0" );
$nothingDone = '';
}
removeDir( "$job{'RootDir'}/$jobname/.AFFA-TRASH", 1 );
lg($nothingDone) if $nothingDone;
}
####################################################################################
#### début des modifs: copié à partir de AffaV2
sub installWatchdog($) {
return if $job{'Watchdog'} ne 'yes' or $job{'SMEServer'} ne 'yes' or $job{'remoteHostName'} eq 'localhost';
#### suppression de "$ESX or " entre le if et $job{'Watchdog'}
my $t = shift;
my $nextScheduled = ( $t || '86400' ) + 600;
lg( "Installing watchdog on " . $job{'remoteHostName'} . " with dt=$nextScheduled seconds" );
my $scheduled = Date::Format::ctime( time() );
chomp($scheduled);
my $WDName = "affa-watchdog-$jobname-$LocalIP";
my $trigger = Date::Format::time2str( "%Y%m%d%H%M", time() + $nextScheduled );
open( WD, "</usr/lib/affa/watchdog.template" ) or warn "Error: Couldn't open /usr/lib/affa/watchdog.template\n";
open( WDS, ">/tmp/$$.$WDName" ) or warn "Error: Couldn't open /tmp/$$.$WDName for writing\n";
dbg("Watchdog parameters:");
while (<WD>) {
#### modifs 3 lignes plus bas: dans AffaV2, c'est $job{'EmailAddresses'}
$_ =~ s/^use constant _TRIGGER=>.*$/use constant _TRIGGER=>$trigger;/;
$_ =~ s/^use constant _JOBNAME=>.*$/use constant _JOBNAME=>'$jobname';/;
$_ =~ s/^use constant _EMAIL=>.*$/use constant _EMAIL=>\'$job{'_EmailAddress'}\';/;
$_ =~ s/^use constant _BACKUPHOST=>.*$/use constant _BACKUPHOST=>'$SystemName.$DomainName ($LocalIP)';/;
$_ =~ s/^use constant _SCHEDULED=>.*$/use constant _SCHEDULED=>'$scheduled';/;
$_ =~ s/^use constant _WDSCRIPT=>.*$/use constant _WDSCRIPT=>'$WDName';/;
print WDS $_;
if ( $_ =~ /^use constant/ ) {
( my $p = $_ ) =~ s/^use constant(.*);/$1/;
chomp($p);
dbg( " " . trim($p) );
}
}
close(WDS);
close(WD);
chmod( 0700, "/tmp/$$.$WDName" );
my @cmd = ( '/usr/bin/ssh', '-p', $job{'sshPort'}, '-i', '/root/.ssh/id_rsa_affa', '-o', "HostKeyAlias=$jobname", '-o', "UserKnownHostsFile=/root/.ssh/knownhosts-$jobname", $job{'remoteHostName'}, "/bin/rm", "-f", "/etc/cron.hourly/$WDName-reminder"
);
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't delete /etc/cron.hourly/$WDName-reminder on remote host.");
##### ligne suivante: ajouté $jobname
remoteCopy( $jobname, "/tmp/$$.$WDName", "/etc/cron.hourly/$WDName" );
unlink("/tmp/$$.$WDName");
}
sub mailTestWatchdogRemote($) {
#### ligne suivante ajoutée
my $jobname = shift;
my %job = getJobConfig($jobname);
####################################################################################
checkConnection($jobname);
my $WDName = "affa-watchdog-mailtest-$jobname-$LocalIP";
open( WD, "</usr/lib/affa/watchdog-mailtest.template" )
or warn "Error: Couldn't open /usr/lib/affa/watchdog.template\n";
open( WDS, ">/usr/lib/affa/$WDName" ) or warn "Error: Couldn't open /usr/lib/affa/$WDName for writing\n";
dbg("Watchdog parameters:");
while (<WD>) {
#### modifs 2 lignes plus bas: dans AffaV2, c'est $job{'EmailAddresses'}
$_ =~ s/^use constant _JOBNAME=>.*$/use constant _JOBNAME=>'$jobname';/;
$_ =~ s/^use constant _EMAIL=>.*$/use constant _EMAIL=>\'$job{'_EmailAddress'}\';/;
$_ =~ s/^use constant _BACKUPHOST=>.*$/use constant _BACKUPHOST=>'$SystemName.$DomainName ($LocalIP)';/;
$_ =~ s/^use constant _WDSCRIPT=>.*$/use constant _WDSCRIPT=>'$WDName';/;
print WDS $_;
}
close(WDS);
close(WD);
chmod( 0700, "/usr/lib/affa/$WDName" );
#### ligne suivante: ajouté $jobname
remoteCopy( $jobname, "/usr/lib/affa/$WDName", "/tmp/" );
my @cmd = ( '/usr/bin/ssh', '-p', $job{'sshPort'}, '-i', '/root/.ssh/id_rsa_affa', '-o', "HostKeyAlias=$jobname", '-o', "UserKnownHostsFile=/root/.ssh/knownhosts-$jobname", $sshQuiet, $job{'remoteHostName'}, "/tmp/$WDName"
);
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't run /usr/lib/affa/$WDName on remote host.");
}
#### fin des modifs
####################################################################################
sub execJobCommandRemote($$) {
( my $jobname, my $scrname ) = @_;
####################################################################################
#### début des modifs: en 'remoteHostName'=localhost, il n'y a pas de execJobCommandRemote --> renvoi vers execJobCommand
if ( $job{'remoteHostName'} eq 'localhost' ) {
lg("Executing script $scrname on $job{'remoteHostName'}");
execJobCommand( $jobname, $scrname );
}
else {
#### fin des modifs ////// "}" rajouté à la fin de la fonction à cause du "else"
my $script = "$scriptdir/$scrname";
my $remoteScript = "/tmp/affa-$jobname-$$-$curtime";
if ( not -x $script ) {
my $txt = "Error: Script $scrname not found or not executable.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
lg("Copying script $scrname to $job{'remoteHostName'}");
my @cmd = (
$job{'_rsyncLocal'},
"--archive",
$job{'rsync--modify-window'} > 0 ? "--modify-window=$job{'rsync--modify-window'}" : '',
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
"--rsync-path=\"$job{'_rsyncRemote'}\"",
"--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\"",
$job{'rsyncOptions'},
$script,
( $job{'remoteUser'} ? $job{'remoteUser'} . '@' : '' ) . $job{'remoteHostName'} . ":$remoteScript",
);
not ExecCmd( @cmd, 0 ) or affaErrorExit("Copying $remoteScript to $job{'remoteHostName'} failed.");
lg("Executing script $scrname on $job{'remoteHostName'}");
@cmd = (
"$job{'localSSHBinary'} $job{'_sshOpts'}",
( $job{'remoteUser'} ? $job{'remoteUser'} . '@' : '' ) . $job{'remoteHostName'},
$remoteScript, $job{'remoteHostName'}, $jobname,
);
# Retrieve the remote backup directories
if ($scrname eq "SME/backupList.sh") {
# not ExecCmdBackupList( @cmd, 0 ) or affaErrorExit("Executing script $scrname on $job{'remoteHostName'} failed.");
my @list = ExecCmdBackupList( @cmd, 0);
#my $result = $list [0];
my $string = $list [1];
not ($list[0]) or affaErrorExit("Executing script $scrname on $job{'remoteHostName'} failed.");
return $string;
#not ExecCmdBackupList( @cmd, 0 ) or affaErrorExit("Executing script $scrname on $job{'remoteHostName'} failed.");
} else {
not ExecCmd( @cmd, 0 ) or affaErrorExit("Executing script $scrname on $job{'remoteHostName'} failed.");
}
}
}
sub execJobCommand($$) {
( my $jobname, my $scrname ) = @_;
my $script = "$scriptdir/$scrname";
if ( not -x $script ) {
my $txt = "Error: Script $scrname not found or not executable.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
lg("Executing script $scrname");
$allow_retry = 0;
my @cmd = ();
affaErrorExit("Script '$script' not found or not executable") if not -x $script;
if ($job{'remoteHostName'} eq 'localhost') {
@cmd = ( $script );
} else {
@cmd = ( $script, $job{'remoteHostName'}, $jobname, "'$job{'localSSHBinary'} $job{'_sshOpts'}'" );
}
not ExecCmd( @cmd, 0 ) or affaErrorExit("Executing script $scrname failed.");
}
sub execPreJobCommand($) {
return if $Command ne "scheduled";
my $jobname = shift;
my %job = getJobConfig($jobname);
my %ps = map { $_ => 0 } @{ $job{"preJobCommand"} };
%ps = ( %ps, map { $_ => 1 } @{ $job{"preJobCommandRemote"} } );
foreach my $p ( sort keys %ps ) {
if ( $ps{$p} ) {
execJobCommandRemote( $jobname, $p );
}
else {
execJobCommand( $jobname, $p );
}
}
}
sub execPostJobCommand($) {
return if $Command ne "scheduled";
my $jobname = shift;
my %job = getJobConfig($jobname);
my %ps = map { $_ => 0 } @{ $job{"postJobCommand"} };
%ps = ( %ps, map { $_ => 1 } @{ $job{"postJobCommandRemote"} } );
foreach my $p ( sort keys %ps ) {
if ( $ps{$p} ) {
execJobCommandRemote( $jobname, $p );
}
else {
execJobCommand( $jobname, $p );
}
}
}
sub deduplicate($) {
return if $Command ne "scheduled";
my $jobname = shift;
my %job = getJobConfig($jobname);
return if $job{"dedup"} ne 'yes';
lg('Starting deduplicating');
if ( not -x $dedupBinary ) {
lg("Executable $dedupBinary not found. Skipping deduplicating");
return -1;
}
my $ar1 = getLinkdest( $jobname, 0 );
my $ar2 = getLinkdest( $jobname, 1 );
if ( $ar1 ne 'scheduled.0' || not $ar2 ) {
lg('No archives found to deduplicate');
return -1;
}
lg("Deduplicating $ar1 and $ar2 archives.");
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
my $rpt = Config::IniFiles->new( -file => $rptfile ) if ( -f $rptfile );
my $exectime = time();
my @cmd = (
$dedupBinary, '-upg',
"$job{'RootDir'}/$jobname/$ar1",
"$job{'RootDir'}/$jobname/$ar2",
'2>&1', '|', '/bin/egrep', '"(size of replaced files was [0-9]* bytes|[0-9]+ replaced by links)\.$"'
);
my $stat = ExecCmd( @cmd, 0 );
$exectime = time() - $exectime;
$ExecCmdOut =~ s/\n//;
$ExecCmdOut =~ /([0-9]+) files of ([0-9]+) replaced by links.*?replaced files was ([0-9]+) bytes/;
my $replacedFiles = defined $1 ? $1 : -1;
my $totalFiles = defined $2 ? $2 : -1;
my $savedBytes = defined $3 ? $3 : -1;
if ( $stat == 0
&& $replacedFiles >= 0
&& $totalFiles >= 0
&& $savedBytes >= 0
&& !$rpt->exists( 'Report', 'DedupDate' ) ) {
$rpt->newval( 'Report', 'DedupTotalFiles', $totalFiles );
$rpt->newval( 'Report', 'DedupReplacedFiles', $replacedFiles );
$rpt->newval( 'Report', 'DedupSavedBytes', $savedBytes );
$rpt->newval( 'Report', 'DedupExectime', $exectime );
$rpt->newval( 'Report', 'DedupDate', Date::Format::time2str( "%Y%m%d%H%M", time() ) );
$rpt->RewriteConfig();
lg( 'Deduplicating completed. Yield is ' . sizeUnit($savedBytes) . ' Bytes.' );
}
else {
lg('Deduplicating failed with bad status');
}
}
sub listJobs() {
$interactive = 1;
foreach my $job ( $cfg->Sections() ) {
next if $job eq 'GlobalAffaConfig';
print "$job\n";
}
}
sub jobsnrpe($) {
my $init = shift;
$interactive = 1;
my $af;
my $nf;
if ( -f "/etc/nagios/nrpe.cfg" ) {
$af = "/etc/nagios/affa-nrpe.cfg";
$nf = "/etc/nagios/nrpe.cfg";
}
elsif ( -f "/etc/icinga/nrpe.cfg" ) {
$af = "/etc/icinga/affa-nrpe.cfg";
$nf = "/etc/icinga/nrpe.cfg";
}
else {
print "# NRPE is not installed on the Affa server\n";
return -1;
}
if ( $nrpeStatus ne 'enabled' ) {
lg("NRPE service is not running.") ;
return;
}
open( FO, ">$af" );
print FO "command[check_affa]=sudo /usr/sbin/affa --nrpe\n";
print FO "command[affa_jobsnrpe]=sudo /usr/sbin/affa --_jobsnrpe\n";
print FO "command[affa_diskusagenrpe]=sudo /usr/sbin/affa --disk-usage --csv\n";
foreach my $jobname ( $cfg->Sections() ) {
next if $jobname eq 'GlobalAffaConfig';
my %job = getJobConfig($jobname);
next if ( $job{'NRPEtrigger'} < 0 || $job{'status'} ne 'enabled' );
print "$jobname\n" if not $init;
print FO "command[check_affa_$jobname]=sudo /usr/sbin/affa --nrpe $jobname\n";
}
print "#OK\n" if not $init;
close(FO);
system(
"grep -v 'include=$af' $nf > $nf-$$ && echo include=$af>>$nf-$$ && mv -f $nf-$$ $nf && /etc/init.d/nrpe restart"
);
}
sub listArchives() {
$interactive = 1;
if ( not $ARGV[0] ) {
foreach my $job ( sort $cfg->Sections() ) {
next if $job eq 'GlobalAffaConfig';
push( @ARGV, $job );
}
}
my $out = '';
foreach my $jobname (@ARGV) {
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
#$timer->mark('startlistArchivesRaw');
my @csv = listArchivesRaw($jobname);
#$timer->mark('endListArchives->Raw');
#$timer->mark('startgetJobConfig');
my %job = getJobConfig($jobname);
#$timer->mark('endgetJobConfig');
if ( $opts{'csv'} ) {
$out = join( "\n", @csv ) . "\n";
}
else {
$out .= $out ? "\n" : "$affaTitle\n";
shift(@csv);
my $h;
##### début modifs
# ($h = sprintf "+-%076s-+\n", '-') =~ s/0/-/g;
( $h = sprintf "+-%0110s-+\n", '-' ) =~ s/0/-/g;
$out .= $h;
# $out .= sprintf( "| Job: %-71s |\n", $jobname );
# $out .= sprintf( "| Description: %-63s |\n", $job{'Description'} ) if $job{'Description'};
# $out .= sprintf( "| Directory: %-65s |\n", $job{'RootDir'}."/$jobname/" );
# $out .= sprintf( "| Hostname: %-66s |\n", $job{'remoteHostName'} );
$out .= sprintf( "| Job: %-105s |\n", $jobname );
$out .= sprintf( "| Description: %-97s |\n", $job{'Description'} ) if $job{'Description'};
$out .= sprintf( "| Directory: %-99s |\n", $job{'RootDir'} . "/$jobname/" );
$out .= sprintf( "| Hostname: %-100s |\n", $job{'remoteHostName'} );
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
$out .= sprintf( "| AutomountDevice: %-93s |\n", $job{'AutomountDevice'} );
$out .= sprintf( "| AutomountPoint: %-94s |\n", $job{'AutomountPoint'} );
$out .= sprintf( "| AutoUnmount: %-97s |\n", $job{'AutoUnmount'} );
}
my $etxt = "Email:";
foreach my $s ( @{ $job{'EmailAddress'} } ) {
# $out .= sprintf( "| %6s %-69s |\n", $etxt, trim($s) );
$out .= sprintf( "| %6s %-103s |\n", $etxt, trim($s) );
$etxt = '';
}
# ligne d'origine ($h = sprintf "+%05s+%022s+%08s+%08s+%07s+%07s+%07s+%07s+\n", '-','-','-','-','-','-','-','-') =~ s/0/-/g;
# ligne du patch affa2 ($h = sprintf "+-%05s-+-%021s-+-%09s-+-%040s-+-%06s-+-%06s-+\n", '-','-','-','-','-','-') =~ s/0/-/g;
# ligne modif AG
# ($h = sprintf "+%05s+%022s+%08s+%08s+%07s+%07s+%07s+%07s+%25s+\n", '-','-','-','-','-','-','-','-','-') =~ s/0/-/g;
# fin modif
my $out2 = '';
my $lastArchive = '';
my $tag = 0;
foreach my $k (@csv) {
my @c = split( ";", $k );
my $valid = $c[7] ne "yes" ? 0 : 1;
my $date = ( $valid and $c[2] ne '197001010000' ) ? formatHTime( $c[2] ) : "Incomplete!";
my $NumberOfFiles = ( $valid and $c[3] ) ? countUnit( $c[3] ) : '-';
# ligne du patch affa2 my $NumberOfFiles = ($valid and $c[3]) ? ($c[3]) : '-';
# ligne du patch affa2 $NumberOfFiles =~ s/,//g;
my $TotalFileSize = ( $valid and $c[4] ) ? sizeUnit( $c[4] ) : '-';
my $TotalBytesSent = ( $valid and $c[8] >= 0 ) ? sizeUnit( $c[8] ) : '-';
my $TotalBytesReceived = ( $valid and $c[9] >= 0 ) ? sizeUnit( $c[9] ) : '-';
my $DiskUsage =
( $valid and $c[5] and $c[6] )
? sizeUnit( $c[6] * 1024 ) . "/" . int( $c[6] / ( $c[5] + $c[6] ) * 100 ) . "%"
: '-';
my $idx = sprintf '%s%2d', uc( substr( $c[0], 0, 1 ) ), $c[1];
my $ExecTime = !$valid || $c[10] eq '-' ? '-' : timeUnit( $c[10] );
if ( $c[1] >= $job{ $c[0] . "Keep" } ) {
$idx = "*$idx";
$tag++;
}
else {
$idx = " $idx";
}
if ( $lastArchive ne $c[0] ) {
$lastArchive = $c[0];
$out2 .= $h;
}
my $DedupSavedBytes = '-';
my $DedupExectime = '-';
if ( defined $c[14] && $c[14] ne '' ) {
$DedupSavedBytes = sizeUnit( $c[13] );
$DedupExectime = timeUnit( $c[14] );
}
if ( $idx =~ /S.0/ && getProcessState($jobname) =~ /deduplicating/ ) {
$DedupSavedBytes = 'busy';
$DedupExectime = 'busy';
}
# lignes d'origine $out2 .= sprintf( "|%-4s | %-19s | %6s | %6s | %5s | %5s | %5s | %5s |\n",
# $idx,$date,$ExecTime,$DedupExectime,$DedupSavedBytes,
# $NumberOfFiles,$TotalFileSize,$TotalBytesReceived );
# ligne du patch Affa2 $out2 .= sprintf( "| %-5s | %-21s | %9s | %40s | %6s | %6s |\n", $idx,$date,$ExecTime,$NumberOfFiles,$TotalFileSize,$TotalBytesReceived );
# lignes modifiée AG
$out2 .= sprintf(
"|%-5s | %-42s | %9s | %6s | %5s | %5s | %5s | %5s | %5s |\n",
$idx, $date, $ExecTime, $DedupExectime, $DedupSavedBytes,
$NumberOfFiles, $TotalFileSize, $TotalBytesSent, $TotalBytesReceived
);
}
if ($lastArchive) {
$out .= sprintf "| %-76s |\n", "Archives with an index greater than the Keep value are tagged with '*'"
if $tag;
# ligne du patch Affa2 $out .= sprintf "| %-102s |\n", "Archives with an index greater than the Keep value are tagged with '*'" if $tag;
$out .= $h;
# ligne d'origine $out .= sprintf "| %-3s | %-20s | %6s | %6s | %5s | %5s | %5s | %5s |\n", 'Run','Completion date', 'buTime', 'ddTime', 'ddYld', 'Files', 'Size', 'Recvd';
# ligne du patch Affa2 $out .= sprintf "| %-5s | %-21s | %9s | %-40s | %6s | %6s |\n", "Run","Completion date", "Exec Time", "Files", "Size", "Recvd";
# ligne modifiée AG ci-dessous
$out .= sprintf "| %-5s | %-41s | %9s | %6s | %5s | %5s | %5s | %5s | %5s |\n", 'Run',
'Completion date', 'buTime', 'ddTime', 'ddYld', 'Files', 'Size', 'Sent', 'Recvd';
}
else {
# ($h = sprintf "+-%076s-+\n", '-') =~ s/0/-/g;
# ligne du patch Affa2 ci dessous
( $h = sprintf "+-%0110s-+\n", '-' ) =~ s/0/-/g;
}
$out2 .= $h;
$out = $out . $out2;
}
}
#$timer->mark('END');
#$timer->report();
return $out;
}
sub listArchivesRaw($) {
my $jobname = shift @_ || '';
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
#$timer->mark('startautomountlistArchivesRaw');
# automount root dir - fixing bug 9147
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
}
#$timer->mark('endautomountlistArchivesRaw');
affaErrorExit("RootDir $job{'RootDir'} does not exist") unless -d $job{'RootDir'};
my @out = (
'Archive:Count;Date;Files;Size;RootDirFilesystemAvail;RootDirFilesystemUsed;valid;TotalBytesReceived;ExecutionTime;DedupTotalFiles;DedupReplacedFiles;DedupSavedBytes;DedupExectime;DedupDate'
);
my %js;
my %js2;
my @jobdirs;
#$timer->mark('start job-RootDir-jobName');
foreach my $k (<$job{'RootDir'}/$jobname/*>) {
push( @jobdirs, $k ) if $k =~ /\/(scheduled|daily|weekly|monthly|yearly)\.[0-9]/;
}
foreach my $record ( sort @jobdirs ) {
( my $k = $record ) =~ s/.*\///;
$k =~ s/scheduled/A-scheduled/;
$k =~ s/daily/B-daily/;
$k =~ s/weekly/C-weekly/;
$k =~ s/monthly/D-monthly/;
$k =~ s/yearly/E-yearly/;
( my $a, my $b ) = split( /\./, $k );
$js{ $a . sprintf( "-%05d", $b ) } = "$record/.AFFA4-REPORT";
$js2{ $a . sprintf( "-%05d", $b ) } = "$record/.AFFA-REPORT";
}
#$timer->mark('end job-RootDir-jobName');
foreach my $k ( reverse sort keys %js ) {
#$timer->mark('start for');
( my $a, my $b, my $c ) = split( /-/, $k );
my $Date = 0;
my $NumberOfFiles = 0;
my $TotalFileSize = 0;
my $TotalBytesSent = 0;
my $TotalBytesReceived = 0;
my $RootDirFilesystemAvail = 0;
my $valid = "no";
my $RootDirFilesystemUsed = 0;
my $ExecutionTime = 0;
my $DedupTotalFiles = '';
my $DedupReplacedFiles = '';
my $DedupSavedBytes = '';
my $DedupExectime = '';
my $DedupDate = '';
if ( -f $js{$k} or -f $js2{$k} ) {
#$timer->mark('start if');
if ( not -f $js{$k} ) {
convertReportV2toV3( $js{$k}, $js2{$k} );
}
#$timer->mark('go Ini');
my $rpt = Config::IniFiles->new( -file => $js{$k} );
#$timer->mark('returned ini');
$Date = $rpt->val( 'Report', 'Date' );
$NumberOfFiles = $rpt->val( 'Report', 'NumberOfFiles' );
$TotalFileSize = $rpt->val( 'Report', 'TotalFileSize' );
$TotalBytesSent = $rpt->val( 'Report', 'TotalBytesSent' );
$TotalBytesReceived = $rpt->val( 'Report', 'TotalBytesReceived' );
# ligne du patch affa2 (my $TotalFileSize=$v->prop('TotalFileSize')||0) =~ s/(\D*)*/$1/g;
# ligne du patch affa2 (my $TotalBytesReceived=$v->prop('TotalBytesReceived')||0) =~ s/(\D*)*/$1/g;
$RootDirFilesystemAvail = $rpt->val( 'Report', 'RootDirFilesystemAvail' );
$RootDirFilesystemUsed = $rpt->val( 'Report', 'RootDirFilesystemUsed' );
$ExecutionTime = $rpt->val( 'Report', 'ExecutionTime' );
$valid = "yes";
#$timer->mark('start DedupExec');
if ( $rpt->exists( 'Report', 'DedupExectime' ) ) {
$DedupTotalFiles = $rpt->val( 'Report', 'DedupTotalFiles' );
$DedupReplacedFiles = $rpt->val( 'Report', 'DedupReplacedFiles' );
$DedupSavedBytes = $rpt->val( 'Report', 'DedupSavedBytes' );
$DedupExectime = $rpt->val( 'Report', 'DedupExectime' );
$DedupDate = $Date;
$DedupDate = $rpt->val( 'Report', 'DedupDate' ) if $rpt->exists( 'Report', 'DedupDate' );
}
#$timer->mark('end DedupExec');
}
push( @out,
"$b;$c" . ";"
. $Date . ";"
. $NumberOfFiles . ";"
. $TotalFileSize . ";"
. $RootDirFilesystemAvail . ";"
. $RootDirFilesystemUsed . ";"
. $valid . ";"
. $TotalBytesSent . ";"
. $TotalBytesReceived . ";"
. $ExecutionTime . ";"
. $DedupTotalFiles . ";"
. $DedupReplacedFiles . ";"
. $DedupSavedBytes . ";"
. $DedupExectime . ";"
. $DedupDate );
}
# auto-unmount - fixing bug 9147
my $dev = $job{'AutomountDevice'};
my $mountPoint = $job{'AutomountPoint'};
unmount( $dev, $mountPoint ) if $dev and $mountPoint;
#$timer->mark('returning ListArchivesRaw');
return @out;
}
sub nrpe() {
my $exitstat = 0;
my @failed;
my $jobcnt = 0;
my $jobckcnt = 0;
my $STATUS = 'OK';
my $total_exectime = 0;
my $total_size = 0;
if ( not $ARGV[0] ) {
foreach my $job ( sort $cfg->Sections() ) {
next if $job eq 'GlobalAffaConfig';
push( @ARGV, $job );
}
}
my $lastrun;
my $done = 0;
my $state;
foreach my $jobname (@ARGV) {
my %job = getJobConfig($jobname);
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
$rptfile = -f $rptfile ? $rptfile : $job{'RootDir'} . "/$jobname/daily.0/.AFFA4-REPORT";
my $rpt;
$rpt = Config::IniFiles->new( -file => $rptfile ) if ( -f $rptfile );
my $last = $rpt ? $rpt->val( 'Report', 'Date' ) : '19700101000000';
$lastrun = formatHTime($last);
$state = getProcessState($jobname);
my $exectime = $rpt ? $rpt->val( 'Report', 'ExecutionTime' ) : 0;
my $size = $rpt ? $rpt->val( 'Report', 'TotalFileSize' ) : 0;
$exectime = $exectime ? $exectime : 0;
next if $job{'NRPEtrigger'} <= 0;
$jobcnt++;
next if $job{'status'} ne 'enabled';
$jobckcnt++;
$total_exectime += $exectime if $exectime =~ /\d/;
$total_size += $size if $size =~ /\d/;
$last = hTime2Timestamp($last);
if ( !$state && $last + 3600 * $job{'NRPEtrigger'} < $curtime || $state =~ /interrupted/ ) {
push( @failed, $jobname );
$STATUS = 'CRITICAL';
$exitstat = 2;
}
$done++;
}
return 0 if !$done;
print "$STATUS: ";
if ( scalar @ARGV == 1 ) {
print $state? $state : $lastrun;
}
else {
if ( $STATUS eq 'CRITICAL' ) {
print "Failed jobs: " . join( ' ', @failed ) . ' ';
}
else {
print "$jobckcnt of $jobcnt jobs are enabled. ";
}
}
print '|';
my $totaltxt = '';
if ( scalar @ARGV > 1 ) {
printf( "Jobs failed=%d;;;; ", scalar @failed );
printf( "Jobs total=%d;;;; ", $jobcnt );
printf( "Jobs checked=%d;;;; ", $jobckcnt );
$totaltxt = "Total ";
}
printf( "$totaltxt" . "Execution Time=%0.1fmin;;;; ", $total_exectime / 60 );
printf( "$totaltxt" . "Size=%0.3fGByte;;;; ", $total_size / 1024 / 1024 / 1024 );
print "\n";
return $exitstat;
}
sub getStatus() {
$interactive = 1;
my @csv = getStatusRaw();
my $out = "$affaTitle\n";
if ( $opts{'csv'} ) {
$out = join( "\n", @csv ) . "\n";
}
else {
shift(@csv);
my $jobcolw = 2;
foreach my $k (@csv) {
my @c = split( ";", $k );
$jobcolw = length( $c[0] ) if $jobcolw < length( $c[0] );
}
$jobcolw = 3 if ( $jobcolw < 3 );
# $jobcolw=12 if( $jobcolw>12 );
#ligne suivante modifiée AG
$jobcolw = 38 if ( $jobcolw > 38 );
( my $h = sprintf "+%0" . ( $jobcolw + 2 ) . "s+%05s+%07s+%08s+%07s+%07s+%07s+%016s+\n",
'-', '-', '-', '-', '-', '-', '-', '-'
) =~ s/0/-/g;
$out .= $h;
$out .= sprintf "| %-" . $jobcolw . "s | %3s | %5s | %6s | %5s | %5s | %5s | %14s |\n", 'Job', 'ENA', 'Last',
'Time', 'Next', 'Size', 'ddYld', 'N of S,D,W,M,Y';
$out .= $h;
my $eo = '';
my $do = '';
my $disabled = 0;
foreach my $k ( sort @csv ) {
( my $thisjob,
my $status,
my $lastrun,
my $netxrun,
my $TotalFileSize,
my $avail,
my $used,
my $nof,
my $lock,
my $state,
my $BackupTime,
my $DedupTotalFiles,
my $DedupReplacedFiles,
my $DedupSavedBytes,
my $DedupExectime,
my $DedupDate
) = split( ";", $k );
if ( $DedupExectime ne '' ) {
$DedupSavedBytes = sizeUnit($DedupSavedBytes);
$lastrun = $DedupDate if defined $DedupDate;
}
else {
$DedupSavedBytes = '-';
$DedupExectime = 0;
}
$lastrun = $status eq "yes" ? ( $lastrun eq '19700101000000' ? 'never' : 'ERROR' ) : '-'
if $curtime - hTime2Timestamp($lastrun) > 86400;
$lastrun =~ s/\d{8}(\d\d)(\d\d)/$1:$2/;
$thisjob = length($thisjob) > $jobcolw ? substr( $thisjob, 0, $jobcolw - 2 ) . ".." : $thisjob;
$TotalFileSize = $TotalFileSize ? sizeUnit($TotalFileSize) : '-';
my $line = '';
my $TotalExecTime = '-';
if ( $BackupTime ne '' ) {
$TotalExecTime = timeUnit( $BackupTime + $DedupExectime );
}
if ( !$lock && not $state =~ 'interrupted' ) {
$line = sprintf "| %-" . $jobcolw . "s | %3s | %5s | %6s | %5s | %5s | %5s | %14s |\n",
$thisjob, $status, $lastrun, $TotalExecTime, $netxrun, $TotalFileSize, $DedupSavedBytes, $nof;
}
else {
my $ps = $lock ? "(pid $lock)" : '';
$line = sprintf "| %-" . $jobcolw . "s | %3s | %-38s | %14s |\n", $thisjob, $status, "$state $ps", $nof;
}
if ( $status eq "no" && not $state ) {
$do .= $line;
$disabled++;
}
else {
$eo .= $line;
}
}
if ( $opts{'all'} ) {
$do = $h . $do if $do && $eo;
$out .= $eo . $do . $h;
}
else {
$out .= $eo . $h;
$out .= sprintf "%d disabled jobs not listed. Use --all to display.\n", $disabled if $disabled;
}
}
return $out;
}
sub getStatusRaw() {
my $txt;
my $lock;
my $lockpid = 0;
my @out =
"Job;Enabled;Last;Next;Size;RootDirFilesystemAvail;RootDirFilesystemUsed;NumberOfArchives;lockpid;ProcessState;BackupTime;DedupTotalFiles;DedupReplacedFiles;DedupSavedBytes;DedupExectime;DedupDate";
my @sections = $cfg->Sections();
foreach my $jobname ( reverse sort @sections ) {
next if $jobname eq 'GlobalAffaConfig';
my %job = getJobConfig($jobname);
# automount root dir - fixing bug 9147
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
}
affaErrorExit("RootDir $job{'RootDir'} does not exist") unless -d $job{'RootDir'};
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
my $rpt;
$rpt = Config::IniFiles->new( -file => $rptfile ) if ( -f $rptfile );
# lockpid
my $lock = getLock($jobname);
# process state
my $state = getProcessState($jobname);
# status
my $status = $job{'status'} eq "enabled" ? "yes" : "no";
# Number of archives
my $total = 0;
my %acnt = ( 'scheduled' => 0, 'daily' => 0, 'weekly' => 0, 'monthly' => 0, 'yearly' => 0 );
my $jobpath = $job{'RootDir'} . "/$jobname";
foreach my $k (<$jobpath/*>) {
$k =~ s/.*\/(.*?)\.[0-9]$/$1/;
$acnt{$k}++;
$total++;
}
# Last and next run
my $nowTime = Date::Format::time2str( "%H%M", time() );
my @s;
my @u = @{ $job{'TimeSchedule'} };
foreach my $z ( sort @u ) {
$z = trim($z);
push( @s, $z ) if ( length($z) == 4 and $z == sprintf( "%04d", int($z) ) );
}
@u = sort { $a <=> $b } @s;
push( @u, $u[0] );
( my $netxrun = $u[0] ) =~ s/(..)(..)/$1:$2/;
for ( my $i = 0; $i < @u; $i++ ) {
if ( $nowTime < $u[$i] ) {
( $netxrun = $u[$i] ) =~ s/(\d\d)(\d\d)/$1:$2/;
last;
}
}
my $lastrun = $rpt ? $rpt->val( 'Report', 'Date' ) : '19700101000000';
# Size
( my $TotalFileSize = $rpt ? $rpt->val( 'Report', 'TotalFileSize' ) : 0 ) =~ s/.*?(\d*).*/$1/;
# ligne du patch affa2 (my $TotalFileSize=$props{'TotalFileSize'}||0) =~ s/(\D*)*/$1/g;
# ligne modifiée AG (my $TotalFileSize=$rpt ? $rpt->val('Report','TotalFileSize'):0) =~ s/(\D*)*/$1/g;
$TotalFileSize = int($TotalFileSize);
# Disk usage
( my $used = $rpt ? $rpt->val( 'Report', 'RootDirFilesystemUsed' ) : 0 ) =~ s/ .*//;
( my $avail = $rpt ? $rpt->val( 'Report', 'RootDirFilesystemAvail' ) : 0 ) =~ s/ .*//;
# Backup execution time
my $BackupTime = $rpt ? $rpt->val( 'Report', 'ExecutionTime' ) : '';
my $ExecutionTime = 0;
my $DedupTotalFiles = '';
my $DedupReplacedFiles = '';
my $DedupSavedBytes = '';
my $DedupExectime = '';
my $DedupDate = '';
if ( $rpt && $rpt->exists( 'Report', 'DedupExectime' ) ) {
$DedupTotalFiles = $rpt->val( 'Report', 'DedupTotalFiles' );
$DedupReplacedFiles = $rpt->val( 'Report', 'DedupReplacedFiles' );
$DedupSavedBytes = $rpt->val( 'Report', 'DedupSavedBytes' );
$DedupExectime = $rpt->val( 'Report', 'DedupExectime' );
$DedupDate = $lastrun;
$DedupDate = $rpt->val( 'Report', 'DedupDate' ) if $rpt->exists( 'Report', 'DedupDate' );
}
my $nof = sprintf "%2d,%2d,%2d,%2d,%2d", $acnt{'scheduled'}, $acnt{'daily'}, $acnt{'weekly'}, $acnt{'monthly'},
$acnt{'yearly'};
push( @out,
"$jobname;$status;$lastrun;$netxrun;$TotalFileSize;$avail;$used;$nof;$lock;$state;$BackupTime;$DedupTotalFiles;$DedupReplacedFiles;$DedupSavedBytes;$DedupExectime;$DedupDate"
);
}
# auto-unmount - fixing bug 9147
my $dev = $job{'AutomountDevice'};
my $mountPoint = $job{'AutomountPoint'};
unmount( $dev, $mountPoint ) if $dev and $mountPoint;
return @out;
}
sub convertReportV2toV3($$) {
( my $fn, my $f ) = @_;
open( DF, ">$fn" );
print DF "; converted from .AFFA-REPORT (Affa V2)\n";
lg("converting report file $f to $fn");
print DF "[Report]\n";
open( AS, "<$f" );
while (<AS>) {
next if /^[ \t]*#/ or not /: /;
chomp($_);
$_ =~ /(.*?):(.*)/;
my $prop = trim($1);
next if $prop =~ /^File/;
my $val = trim($2);
next if not $val =~ /^[\-0-9]/ and not $prop =~ /^Date/;
$val =~ /([\-0-9.]+)(.*)/;
$val = $1;
my @ps = split( " ", $prop );
$prop = '';
foreach my $k (@ps) {
$prop .= ucfirst($k);
}
print DF "$prop=$val\n";
}
close(AS);
close(DF);
( my $d = $f ) =~ s/\.AFFA-REPORT//;
system("touch -r $f $d");
}
sub df($) {
my $dir = shift;
my $df = new Filesys::DiskFree;
$df->df();
return ( int( $df->used($dir) / 1024 ), int( $df->avail($dir) / 1024 ) );
}
sub DiskUsage() {
my @csv = DiskUsageRaw();
my $out = "$affaTitle\n";
if ( $opts{'csv'} ) {
$out = join( "\n", @csv ) . "\n";
}
else {
( my $h = sprintf "+-%04s-+-%06s-+-%06s-+-%050s-+\n", '-', '-', '-', '-' ) =~ s/0/-/g;
$out .= $h;
$out .= sprintf "| %4s | %6s | %6s | %-50s |\n", "Use%", "Used", "Avail", "Root Dir";
$out .= $h;
shift(@csv);
foreach my $k (@csv) {
my @c = split( ";", $k );
$c[0] =~ s/"//g;
$c[0] =~ s/.*(.{47})$/...$1/ if length( $c[0] ) > 50;
if ( $c[1] eq '-' or $c[2] eq '-' ) {
$out .= sprintf "| %4s | %6s | %6s | %-50s |\n", '-', '-', '-', $c[0];
}
else {
$out .= sprintf "| %4s | %6s | %6s | %-50s |\n", int( $c[2] / ( $c[1] + $c[2] ) * 100 ) . "%", # Use%
sizeUnit( $c[2] * 1024 ), # Used
sizeUnit( $c[1] * 1024 ), # Avail
$c[0];
}
}
$out .= $h;
}
return $out;
}
sub DiskUsageRaw() {
my @out = ('RootDir;RootDirFilesystemAvail;RootDirFilesystemUsed');
my %done;
foreach my $jobname ( reverse sort $cfg->Sections() ) {
next if $jobname eq 'GlobalAffaConfig';
my %job = getJobConfig($jobname);
my $RootDir = $job{'RootDir'};
next if $done{$RootDir};
$done{$RootDir} = 1;
my $used = '-';
my $avail = '-';
my $dev = $job{'AutomountDevice'};
my $mountPoint = $job{'AutomountPoint'};
my $mountOptions = $job{'AutomountOptions'};
if ( $RootDir && $mountPoint && $RootDir =~ /$mountPoint/ ) {
###### ligne suivante modifiée umount diskusage - bug 9147
# mount($dev,$mountPoint, $mountOptions) if $dev and $mountPoint;
mount( $dev, $mountPoint, $mountOptions, %job ) if $dev and $mountPoint;
( $used, $avail ) = df($RootDir) if isMounted( $dev, $mountPoint );
unmount( $dev, $mountPoint ) if $dev and $mountPoint;
}
elsif ( $RootDir && -x $RootDir ) {
( $used, $avail ) = df($RootDir);
}
push( @out, "\"$RootDir\";$avail;$used" );
}
return @out;
}
# ajout pour V3.2.2-2
sub checkForDiskLabel($) {
my $dev = $_[0];
my $result = $dev;
if ( $dev =~ "/by-label/" ) {
$result = "/dev/" . basename( readlink($dev) );
lg("Mapping disk label to device: $dev links to $result\n");
}
return $result;
}
# fin ajout
sub isMounted($$) {
( my $dev, my $AutomountPoint ) = @_;
$AutomountPoint =~ s/\/$//;
# ajout pour V3.2.2-2
$dev = checkForDiskLabel($dev);
# fin ajout
$dev =~ s/\/$//;
my $txt = "Check mounted: $dev $AutomountPoint";
my $df = new Filesys::DiskFree;
my $result = 0;
$df->df();
( my $df_AutomountPoint = $df->device($AutomountPoint) ) =~ s/\/$//;
if ( $df_AutomountPoint =~ "^/dev/mapper/" && not( $dev =~ "^/dev/mapper" ) ) {
# convert /dev/mapper/VG-LV to /dev/VG/LV
( my $d = $df_AutomountPoint ) =~ s;^/dev/mapper/(.*)-(.*)$;/dev/$1/$2;;
$result = 1 if ( $d eq $dev );
}
$result |= $df_AutomountPoint eq $dev;
dbg( "$txt. Result: " . ( $result ? 'yes' : 'no ' ) );
return $result;
}
###### 2 lignes suivantes modifiées umount diskusage - bug 9147
# sub mount($$$) {
# (my $dev, my $AutomountPoint, my $options) = @_;
sub mount($$$%) {
( my $dev, my $AutomountPoint, my $options, my %job ) = @_;
$AutomountPoint =~ s/\/$//;
return if isMounted( $dev, $AutomountPoint );
File::Path::mkpath( $AutomountPoint, 0, 0700 ) if not -d $AutomountPoint;
lg("Mounting $dev to $AutomountPoint");
my @cmd = ( '/bin/mount', $options, $dev, $AutomountPoint );
if ( ExecCmd( @cmd, 0 ) ) {
my $s = "Couldn't mount $dev $AutomountPoint";
if ($Command) {
affaErrorExit($s);
}
else {
lg($s);
}
}
$autoMounted{$AutomountPoint} = $dev if $job{'AutoUnmount'} eq 'yes';
}
sub unmount($$) {
( my $dev, my $AutomountPoint ) = @_;
$AutomountPoint =~ s/\/$//;
return
if not $autoMounted{$AutomountPoint}
or $autoMounted{$AutomountPoint} ne $dev
or not isMounted( $dev, $AutomountPoint );
my @cmd = ( '/bin/umount', '-l', $AutomountPoint );
lg("Unmounting $AutomountPoint");
not ExecCmd( @cmd, 0 ) or lg("Couldn't unmount $AutomountPoint");
}
sub unmountAll() {
while ( ( my $AutomountPoint, my $dev ) = each(%autoMounted) ) {
unmount( $dev, $AutomountPoint );
}
}
sub checkCrossFS($$) {
( my $fs1, my $fs2 ) = @_;
my $fn = ".affa.checkCrossFS.$$";
unlink("$fs1/$fn") if -e "$fs1/$fn";
unlink("$fs2/$fn") if -e "$fs2/$fn";
open( OUT, ">$fs1/$fn" );
print OUT "test";
close(OUT);
link( "$fs1/$fn", "$fs2/$fn" );
my $res = -e "$fs2/$fn" ? 0 : 1;
unlink("$fs1/$fn") if -e "$fs1/$fn";
unlink("$fs2/$fn") if -e "$fs2/$fn";
return $res;
}
sub DiskSpaceWarn() {
return if $Command ne 'scheduled' or $job{'DiskSpaceWarn'} eq 'none';
lg("Checking disk space.");
( my $used, my $avail ) = df( $job{'RootDir'} );
my $report = Config::IniFiles->new( -file => "$job{'RootDir'}/$jobname/scheduled.0/.AFFA4-REPORT" );
my $needed = int( $report->val( 'Report', 'TotalFileSize' ) / 1024 );
$needed = int( $needed * 0.5 ) if $job{'DiskSpaceWarn'} eq 'normal';
$needed = int( $needed * 0.1 ) if $job{'DiskSpaceWarn'} eq 'risky';
if ( $avail < $needed ) {
my $msg = new Mail::Send;
$msg->subject("Warning: Affa server $SystemName running out of disk space!");
$msg->to( $job{'_EmailAddress'} );
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
my $s;
my $fh = $msg->open;
print $fh "This message was sent by job '$jobname'.\n";
$s = "Configured threshold type: $job{'DiskSpaceWarn'}\n";
print $fh $s;
lg($s);
$s = "Disk space left: " . sizeUnit( int( $avail * 1024 ) ) . "\n";
print $fh $s;
lg($s);
$s = "Used disk space: " . sizeUnit( int( $used * 1024 ) ) . "\n";
print $fh $s;
lg($s);
$s = "Disk size: " . sizeUnit( int( ( $avail + $used ) * 1024 ) ) . "\n";
print $fh $s;
lg($s);
close($fh);
lg( "Running out of disk space message sent to " . $job{'_EmailAddress'} );
}
}
sub getReportVal($$) {
( my $jobname, my $archive ) = @_;
my $rptfile = $job{'RootDir'} . "/$jobname/$archive/.AFFA4-REPORT";
my $rpt = Config::IniFiles->new( -file => $rptfile ) if ( -f $rptfile );
my $val = '';
if ( $rpt && $rpt->exists( 'Report', 'Date' ) ) {
$val = $rpt->val( 'Report', 'Date' );
}
return $val;
}
sub checkArchiveExists($$) {
( my $jobname, my $archive ) = @_;
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
if ( not -f "$job{'RootDir'}/$jobname/$archive/.AFFA4-REPORT" ) {
$interactive = 0;
my $dir = opendir( DIR, "$job{'RootDir'}/$jobname" );
my $ar;
my %va;
while ( defined( $ar = readdir(DIR) ) ) {
next if not $ar =~ /^(scheduled|daily|weekly|monthly|yearly)\.[0-9]+$/;
my $d = getReportVal( $jobname, $ar );
if ($d) {
$va{$ar} = $d;
}
}
my $txt = "Error: Archive $archive not found.";
print "$txt\n";
lg($txt);
$txt = "The following Affa v4 archives exist:";
print "$txt\n";
lg($txt);
foreach my $k ( sort { $va{$a} cmp $va{$b} } keys %va ) {
$txt = sprintf " %-12s: %s", $k, formatHTime( $va{$k} );
print "$txt\n";
lg($txt);
}
print "Note there may be Affa 3 archives available but Affa v4 cannot restore them\n";
affaErrorExit(".");
}
}
# remove archives which have indices greater than the Keep value
sub cleanup() {
my $jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
my $txt;
my @cmd;
if ( not $cfg->SectionExists($jobname) ) {
$txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
my $dir = opendir( DIR, "$job{'RootDir'}/$jobname" );
my %archives;
my $cnt = 0;
if ($dir) {
my $af;
while ( defined( $af = readdir(DIR) ) ) {
next if not $af =~ /^(scheduled|daily|weekly|monthly|yearly)\.[0-9]+$/;
( my $k, my $b ) = split( /\./, $af );
next if ( $b < $job{ $k . "Keep" } );
$k =~ s/scheduled/A/;
$k =~ s/daily/B/;
$k =~ s/weekly/C/;
$k =~ s/monthly/D/;
$k =~ s/yearly/E/;
$archives{ "$k" . sprintf( "%05d", $b ) } = $af;
$cnt++;
}
}
if ($cnt) {
print "\nWARNING: The following $cnt archives will be deleted!\n";
foreach my $k ( reverse sort keys %archives ) {
print "$archives{$k}\n";
}
my $input = '';
print "Type 'proceed' to confirm or <ENTER> to cancel: ";
$input = lc(<STDIN>);
chomp($input);
if ( $input ne 'proceed' ) {
affaErrorExit("Terminated by user") if $input ne 'proceed';
}
File::Path::mkpath( "$job{'RootDir'}/$jobname/.AFFA-TRASH", 0, 0700 )
unless -d "$job{'RootDir'}/$jobname/.AFFA-TRASH";
foreach my $k ( reverse sort keys %archives ) {
print "deleting archive $archives{$k} in background... ";
$|++;
moveFileorDir( "$job{'RootDir'}/$jobname/$archives{$k}",
"$job{'RootDir'}/$jobname/.AFFA-TRASH/$archives{$k}.$curtime-$$" );
print " Done.\n";
}
removeDir( "$job{'RootDir'}/$jobname/.AFFA-TRASH", 1 );
}
}
sub moveArchive() {
$jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
my $txt;
my @cmd;
if ( not $cfg->SectionExists($jobname) ) {
$txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
( my $newdir = $ARGV[1] ) =~ s/\/$//;
if ( not $newdir ) {
$txt = "Error: New RootDir not given.";
print("$txt\n");
affaErrorExit("$txt");
}
if ( not $newdir =~ /^\// or $newdir =~ /\.\./ ) {
$txt = "Error: Full path required for NEWROOTDIR.";
print("$txt\n");
affaErrorExit("$txt");
}
if ( not -d $newdir ) {
my $input = '';
while ( not $input =~ /^(yes|no)$/ ) {
print "Directory $newdir does not exist. Create? (yes|no) ";
$input = lc(<STDIN>);
chomp($input);
}
if ( $input ne 'yes' ) {
$interactive = 0;
affaErrorExit("Terminated by user") if $input ne 'proceed';
}
File::Path::mkpath( $newdir, 0, 0700 );
}
my $err = 0;
if ( "$job{'RootDir'}/$jobname" ne "$newdir/$jobname" and -d "$job{'RootDir'}/$jobname" ) {
if ( checkCrossFS( $job{'RootDir'} . "/$jobname", $newdir ) ) {
$txt = "Warning: Cannot move across filesystems. Using copy and delete.";
print("$txt\n");
lg($txt);
$txt = "Please wait...";
print("$txt\n");
lg($txt);
my @cmd = (
"/bin/tar", "--remove-files", "-C", $job{'RootDir'}, "-cf", '-',
$jobname, "|", "/bin/tar", "-C", $newdir, "-xf",
'-'
);
$err = ExecCmd( @cmd, 0 );
if ($err) {
$txt = "Error: Copying failed.";
print("$txt\n");
lg($txt);
}
else {
removeDir( $job{'RootDir'} . "/$jobname", 0 );
}
}
else {
moveFileorDir( $job{'RootDir'} . "/$jobname", "$newdir/$jobname" );
}
}
if ( not $err ) {
my $err = rewriteConfigVal( $jobname, 'RootDir', $newdir );
lg("Changed 'RootDir' value to '$newdir' in config file of job '$jobname'") if ( not $err );
}
}
sub renameJob() {
$jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
my $txt;
my @cmd;
if ( not $cfg->SectionExists($jobname) ) {
$txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
my $newname = $ARGV[1] || '';
$newname =~ s/\//_/g;
if ( not $newname ) {
$txt = "Error: No new jobname given.";
print("$txt\n");
affaErrorExit("$txt");
}
if ( $cfg->SectionExists($newname) ) {
$txt = "Error: A job '$newname' already exists.";
print("$txt\n");
affaErrorExit("$txt");
}
if ( -e "$job{'RootDir'}/$newname" ) {
$txt = "Error: $job{'RootDir'}/$newname already exists.";
print("$txt\n");
affaErrorExit("$txt");
}
if ( -d "$job{'RootDir'}/$jobname" ) {
moveFileorDir( $job{'RootDir'} . "/$jobname", $job{'RootDir'} . "/$newname" );
}
# rename ssh HostKeyAlias
if ( -f "/root/.ssh/knownhosts-$jobname" ) {
open( LF, "</root/.ssh/knownhosts-$jobname" );
$_ = <LF>;
s/^$jobname /$newname /;
close(LF);
open( LF, ">/root/.ssh/knownhosts-$newname" );
print LF;
close(LF);
unlink("/root/.ssh/knownhosts-$jobname");
}
# rename Section in config file
my $cfg = openOrgConfig($jobname);
$cfg->AddSection($newname);
my @p = $cfg->Parameters($jobname);
foreach my $k (@p) {
$cfg->newval( $newname, $k, $cfg->val( $jobname, $k ) );
}
$cfg->DeleteSection($jobname);
writeConfigFile($cfg);
# rename logfile
moveFileorDir( "$logdir/$jobname.log", "$logdir/$newname.log" );
lg("Renamed job '$jobname' to '$newname'");
$jobname = $newname;
cronSetup();
}
sub writeConfigFile($) {
my $cfg = shift;
$cfg->RewriteConfig();
# replace 'here documents' by multiline values
my $file = $cfg->GetFileName();
my $key = '';
open( HD, "<$file" );
open( MP, ">$file.$$.$curtime" );
while (<HD>) {
my $e = trim($_);
$e =~ s/(.*?)(= <<)(EOT)$//;
if ( not $key and not $e ) {
$key = $1;
next;
}
if ( $key and $e ne 'EOT' ) {
print MP "$key=$e\n";
next;
}
if ( $e eq 'EOT' ) {
$key = '';
next;
}
print MP "$e\n";
}
close(MP);
close(HD);
rename( "$file.$$.$curtime", $file );
# force config re-read
unlink($configfile) if $configfile;
getJobConfig('');
}
# entirely remove a job
sub deleteJob() {
my $jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
my $txt;
my @cmd;
if ( not $cfg->SectionExists($jobname) ) {
$txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
my $dir = opendir( DIR, "$job{'RootDir'}/$jobname" );
my %archives;
if ($dir) {
my $af;
while ( defined( $af = readdir(DIR) ) ) {
next if not $af =~ /^(scheduled|hourly|daily|weekly|monthly|yearly])\.[0-9]+$/;
( my $k, my $b ) = split( /\./, $af );
$k =~ s/scheduled/A/;
$k =~ s/daily/B/;
$k =~ s/weekly/C/;
$k =~ s/monthly/D/;
$k =~ s/yearly/E/;
$archives{ "$k" . sprintf( "%05d", $b ) } = $af;
}
}
print "Job '$jobname' has the following archives:\n";
foreach my $k ( sort keys %archives ) {
print " $archives{$k}\n";
}
print "WARNING: All archives and logfiles of the job '$jobname' will be deleted!\n";
my $input = '';
print "Type 'proceed' to confirm or <ENTER> to cancel: ";
$input = lc(<STDIN>);
chomp($input);
if ( $input ne 'proceed' ) {
affaErrorExit("Terminated by user") if $input ne 'proceed';
}
rewriteConfigVal( $jobname, 'status', 'disabled' );
print("Set job '$jobname' status disabled.\n");
####################################################################################
#### début modifs
if ( $job{'remoteHostName'} ne 'localhost' ) {
#### fin modifs (penser à l'accolade fermante après le revokeKeys
####################################################################################
revokeKeys($jobname);
}
if ($dir) {
print "deleting $job{'RootDir'}/$jobname in background...";
$|++;
removeDir( "$job{'RootDir'}/$jobname", 1 );
print " Done.\n";
}
cronSetup();
print "deleting logfile '/var/log/affa/$jobname.log' ... ";
$|++;
unlink("/var/log/affa/$jobname.log");
print " Done.\n";
}
sub fullRestore() {
$interactive = 0;
$SIG{'INT'} = sub { };
$archive = $restoreVer;
my $txt = '';
my @cmd;
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
#affaExit("Ending Restore");
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
##### ligne suivante modifiée umount diskusage - bug 9147
# mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'} );
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
}
# check if a job is running
if ( getLock($jobname) ) {
print "Job '$jobname' is running. Wait for completion. Then run affa --full-restore again.\n";
affaErrorExit("affa job 'jobname' is running.");
}
# check if archive exists
checkArchiveExists( $jobname, $archive ); # and affaErrorExit() if archive does not exist
$interactive = 1;
checkConnection($jobname);
####################################################################################
#### debut des modifs
print "WARNING: After the restore is done, the server $job{'remoteHostName'} will reboot! \n"
if $job{'SMEServer'} ne 'no';
#### fin des modifs
####################################################################################
my $input = '';
print "Type 'proceed' to confirm or <ENTER> to cancel: ";
$input = lc(<STDIN>);
chomp($input);
#Modified the Exit line. Moved Rise subs nr
if ( $input ne 'proceed' ) {
$interactive = 0;
affaErrorExit("Terminated by user") if $input ne 'proceed';
}
####################################################################################
#### debut des modifs
if ( $job{'SMEServer'} ne 'no' ) {
#pour debbugage print 'SME-server= ' . $job{'SMEServer'} . "\n";
my $prerestore = 'SME/signal-pre-restore';
if ( $job{'remoteHostName'} eq 'localhost' ) {
execJobCommand( $jobname, $prerestore );
}
else {
execJobCommandRemote( $jobname, $prerestore );
}
#pour debbugage print 'pre-restore: $prerestore' . "\n";
}
#### fin des modifs
####################################################################################
# Archive version is set via at the top in full-restore or via CLI
# Old
#my @SourceDirs = getSourceDirsString($jobname);
my ($local, $remote) = getSourceDirs($jobname);
# $remote is the destination - it could be local
foreach my $k (@$remote) {
my @split = split(':', $k);
my $source = $split[1];
my $target = "\"$k\"";
my $src = "$job{'RootDir'}/$jobname/$archive$source";
next if not -e $src;
$txt = "Restoring Job $job{'remoteHostName'} Target $k ...";
lg($txt);
print "$txt\n";
@cmd = (
"/usr/bin/rsync",
"--archive",
"--stats",
"--ignore-errors",
$opts{'preserve-newer'} || 'yes' eq 'no' ? "" : "--update",
$opts{'delete'} || 'no' eq 'yes' ? "--delete-during" : "",
"--partial",
$job{'rsync--inplace'} ne 'no' ? "--inplace" : "",
"--numeric-ids",
$job{'remoteHostName'} ne 'localhost' ? "--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\"" : '',
( -d $src ? "$src/" : "$src" ),
# reetp fix here
$job{'remoteHostName'} eq 'localhost' ? "/$k" : $target
);
####################################################################################
#### debut des modifs faites dans la dernière ligne de la commande @cmd précédente
#
# ajout de $job{'remoteHostName'} eq 'localhost' ? ":/$k" :
#
#### fin des modifs
####################################################################################
ExecCmd( @cmd, 0 );
}
####################################################################################
#### debut des modifs
if ( $job{'SMEServer'} ne 'no' ) {
####################################################################################
#### penser à rajouter le imapIndexFilesDelete(); cf AffaV2
#pour debbugage print 'SME-server= ' . $job{'SMEServer'} . "\n";
my $postupgrade = 'SME/signal-post-upgrade-reboot';
if ( $job{'remoteHostName'} eq 'localhost' ) {
execJobCommand( $jobname, $postupgrade );
}
else {
execJobCommandRemote( $jobname, $postupgrade );
}
#pour debbugage print 'post-upgrade + reboot: $postupgrade' . "\n";
}
#### fin des modifs
####################################################################################
}
####################################################################################
#### début des modifs: ajout à partir de Affa2
sub riseFromDeath() {
$SIG{'INT'} = sub { };
$interactive = 1;
my @cmd;
my $bootstrap = $configDB->get_prop( "bootstrap-console", "Run" );
#my $jobname = $ARGV[0] || 'none'; # This will cause an exit below
my $archive = $restoreVer;
if ( $cliDebug == 1 ) {
print "Argv0: $ARGV[0] Argv1: $ARGV[1] Archive: $archive\n";
}
# check if archive exists
##### ligne d'origine: checkArchiveExists($job{'RootDir'},$jobname,$archive); remplacée par:
if ($jobname eq 'none') {
affaErrorExit("Please specify the job name to rise");
}
else {
####################################################################################
#### ligne d'origine d'Affa2: getJobConfig( $jobname );
%job = getJobConfig($jobname);
####################################################################################
}
if ( $job{'remoteHostName'} eq 'localhost' ) {
$interactive = 0;
my $txt;
$txt = "Error: A rise cannot be done for this server from it's own backup.";
lg($txt);
print "$txt\n";
$txt = "Try option --full-restore instead.";
lg($txt);
print "$txt\n";
affaErrorExit("Cannot rise from my own backup.");
}
$archive = $archive ne '' ? $archive : $job{'Archive'};
# This will exit in the subroutine if the archive does not exist
checkArchiveExists( $jobname, $archive );
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
###### ligne suivante modifiée umount diskusage - bug 9147
# mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'} );
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
}
# check if other affa jobs are running
##### ligne d'origine supprimée: my %all = $affa->as_hash();
my $havelocks = 0;
#### début des modifs // commentés avec un #: script d'origine de AffaV2
# foreach my $job( reverse sort keys %all )
# {
# next if $job eq $ServerBasename;
# my $v=$all{$job};
# next if $v->{'type'} ne 'job';
# my $lockpid=getLock("$lockdir/$job");
foreach my $jobname ( sort $cfg->Sections() ) {
next if $jobname eq $ServerBasename;
#### fin des modifs
my $lockpid = getLock($jobname);
if ($lockpid) {
# my $txt = "Job '$job' is running (pid $lockpid)";
my $txt = "Job '$jobname' is running (pid $lockpid)";
print "$txt\n";
lg($txt);
$havelocks++;
$interactive = 0;
}
}
if ( $interactive == 0 ) {
print "Wait for completion of the running jobs or kill them. Then run affa --rise again.\n";
affaErrorExit("affa jobs are running.");
}
# reetp test stop
if ( $cliDebug != 1 ) {
stopServices();
}
if ( $bootstrap ne 'no' ) {
print "*************************************************************\n";
print "* WARNING: *\n";
print "* Bootstrap-console Run flag is set. *\n";
print "* It appears as if affa --rise has already run. *\n";
print "* Skipping backup run of affa server base (this server) *\n";
print "*************************************************************\n";
}
else {
# reetp test backup
if ( $cliDebug != 1 ) {
saveMySoul();
}
}
####################################################################################
#### début modifs
# reetp why get it again?
%job = getJobConfig($jobname);
##### fin modifs
####################################################################################
if ( checkCrossFS( $job{'RootDir'}, "/usr/lib/affa" ) ) {
print "*************************************************************\n";
print "* WARNING: *\n";
print "* The archive is not on the local filesystem, therefore *\n";
print "* hardlinks cannot be used and all data need to be copied. *\n";
print "* Depending on the size of the archive this can take a long *\n";
print "* time. *\n";
print "*************************************************************\n";
}
my $input = '';
if ( $cliDebug != 1 ) {
while ( not $input =~ /^(proceed|quit|exit)$/ ) {
print "Type 'proceed' to continue or 'quit' to exit: ";
$input = lc(<STDIN>);
chomp($input);
}
if ( $input ne 'proceed' ) {
$interactive = 0;
affaErrorExit("Terminated by user") if $input ne 'proceed';
}
}
print "Signalling pre-restore event\n";
@cmd = ( "/sbin/e-smith/signal-event", "pre-restore" );
# reet test exec
if ( $cliDebug != 1 ) {
ExecCmd( @cmd, 0 );
}
$configDB = esmith::ConfigDB->close;
##### ligne d'origine AffaV2: runRiseRsync("$job{'RootDir'}/$jobname/$archive/", "/");
#my $archiveDir = "$job{'RootDir'}/$jobname/$job{'Archive'}";
my $archiveDir = "$job{'RootDir'}/$jobname/$archive";
if ( $cliDebug != 1 ) {
print "Running Rise $jobname ArchiveDir $archiveDir\n";
runRiseRsync( $jobname, $archiveDir, "/" );
}
else {
print "ArchiveDir $archiveDir\n";
print "Debug - exiting before rise\n";
exit;
}
$configDB = esmith::ConfigDB->open or die "Error: Couldn't open config db.";
imapIndexFilesDelete();
print "Signalling post-upgrade event\n";
@cmd = ( "/sbin/e-smith/signal-event", "post-upgrade" );
if ( $cliDebug != 1 ) {
ExecCmd( @cmd, 0 );
} else {
print "Debug - exit without post-upgrade\n";
exit;
}
####################################################################################
# We can't do the following unless saveMySoul has been run
####################################################################################
####################################################################################
# preserve ethernet driver configuration for this hardware.
# This allows us to run the --rise option remotely and connect to the restored server
my $srcconfigPath = "/var/affa/$ServerBasename/$archive/home/e-smith/db/configuration";
my $srcconfig = esmith::ConfigDB->open_ro($srcconfigPath)
or affaErrorExit("Couldn't open source config db $srcconfigPath");
$configDB->set_prop( "EthernetDriver1", "type", $srcconfig->get("EthernetDriver1")->value );
$configDB->set_prop( "EthernetDriver2", "type", $srcconfig->get("EthernetDriver2")->value );
$configDB->set_prop( "InternalInterface", "Driver", $srcconfig->get_prop( "InternalInterface", "Driver" ) );
$configDB->set_prop( "InternalInterface", "Name", $srcconfig->get_prop( "InternalInterface", "Name" ) );
$configDB->set_prop( "InternalInterface", "NICBondingOptions",
$srcconfig->get_prop( "InternalInterface", "NICBondingOptions" ) );
$configDB->set_prop( "InternalInterface", "NICBonding",
$srcconfig->get_prop( "InternalInterface", "NICBonding" ) || 'disabled' );
print "Please make sure that the server '" . $job{'remoteHostName'} . "' is not connected to your network.\n";
print "Otherwise you will not be able to connect to this server after the reboot.\n";
print "Please reboot this server now.\n";
}
sub runRiseRsync($$$) {
( my $jobname, my $archive, my $dest ) = @_;
# set $dest = /
#### ligne d'origine AffaV2: (my $archive,my $dest)=@_;
#my $b = new esmith::Backup or die "Error: Couldn't create Backup object\n";
#### ligne d'origine AffaV2: my @SourceDirs = $opts{'all'} ? getSourceDirs() : $b->restore_list;
#my @SourceDirs = $opts{'all'} ? getSourceDirs($jobname) : $b->restore_list;
#my @SourceDirs = $opts{'all'} ? getSourceDirs($jobname) : $b->restore_list;
#This should be scheduled.0 or the ARGv
my ( $local, $remote ) = getSourceDirs($jobname);
# $remote is the destination - it could be local
# Why? This is questionable
if ( $jobname eq 'undo-rise' ) {
push( @$remote, "/etc/affa" );
}
my $txt = 'Running Rise rsync...';
lg($txt);
print "$txt\n";
#my $src = "$job{'RootDir'}/$jobname/$archive$source";
# foreach my $src (@SourceDirs) {
my $linkDest = getLinkdest( $jobname, 0 ); # scheduled.0
affaErrorExit("No LinkDest $linkDest") unless $linkDest;
dbg("runRiseRsync using link destination $linkDest") if $linkDest;
foreach my $url (@$remote) {
my @split = split( ':', $url );
my $src = $split[1];
#my $target = "\"$k\"";
$src =~ s/"/\\"/g;
chomp($src);
my $archivesrc = $archive . $src;
# Call it dest and not source to save confusion
my $dest = "$src";
# Check is this is a dir and add a / if it is
if (-d $archivesrc) {
$archivesrc = $archive . $src . "/";
}
#my $linkDest = "$job{'RootDir'}/$jobname/$linkDest/$archivesrc";
my @cmd = (
"/usr/bin/rsync", "--archive",
"--stats", "--delete-during",
"--ignore-errors", "--delete-excluded",
"--partial", "--inplace",
"--numeric-ids",
# "--link-dest=$archive$src",
# We are trying to set say daily.0 instead of scheduled.0 if required
# ( $linkDest ? "--link-dest='$job{'RootDir'}/$jobname/$linkDest'" : '' ), "$archive$src", "$dest$src"
# $src is the destination $dest = '/' but no idea if that is useful - just ends with //some/dir
# --link-dest
# Unchanged files are hard linked from DIR to the destination directory
# rsync -av --link-dest=$PWD/prior_dir host:src_dir/ new_dir/
# If no LinkDest we should have already bailed?
"--link-dest=$archivesrc", "$archivesrc", "$dest"
);
print "Restoring LinkDest: $linkDest Src: $archivesrc Dest: $dest\n";
if ( $cliDebug == 1 ) {
print "What does the next line say?\n";
print "LinkDest: $linkDest Src: $archivesrc Dest: $dest\n";
print join( ',', @cmd );
print "\n";
}
# reetp - and run or not
if ( $cliDebug == 1 ) {
print " Debug - don't exec cmd\n";
} else {
lg("LinkDest: $linkDest Src: $archivesrc Dest: $dest\n");
my $status = ExecCmd( @cmd, 0 );
if ( $status == 0 or $status == 23 or $status == 24 ) {
# $Affa::lib::ExecCmdOut =~ /(Total file size: [0-9]* bytes)/gm;
$ExecCmdOut =~ /(Total file size: [0-9]* bytes)/gm;
# print "OK. $1\n"; ##### Bug 9139: valeur de $1 non définie.
print "OK. Restoring LinkDest: $linkDest Src: $archivesrc Dest: $dest\n";
}
else {
print "Failed. Restoring LinkDest: $linkDest Src: $archivesrc Dest: $dest\n";
}
}
}
if ( $cliDebug == 1 ) {
print " Debug - exiting\n";
exit;
}
}
sub undoRise() {
# Note that this will undo back to the last run - scheduled.0
# What if you had tried rise 'day.3' Which you can do?
# But... you were at 'latest' at the point you started the rise.
# So this is correct
my @cmd;
$interactive = 1;
# search server base backup
my $dir = opendir( DIR, "/var/affa/" );
affaErrorExit("Couldn't open directory /var/affa/") if not $dir;
my $archive;
my $a = '';
while ( defined( $archive = readdir(DIR) ) ) {
next if not $archive =~ /AFFA\.[a-z][a-z0-9\-]*\..*-\d*\.\d*\.\d*\.\d*/;
$a = $archive;
}
affaErrorExit("Server backup found.") if not $a and not -f "$a/scheduled.0";
stopServices();
print "\nWARNING: You will loose all data of your current server installation!\n";
my $input = '';
while ( not $input =~ /^(proceed|quit|exit)$/ ) {
print "Type 'proceed' to continue or 'quit' to exit: ";
$input = lc(<STDIN>);
chomp($input);
}
if ( $input ne 'proceed' ) {
$interactive = 0;
affaErrorExit("Terminated by user") if $input ne 'proceed';
}
print "Signalling pre-restore event\n";
@cmd = ( "/sbin/e-smith/signal-event", "pre-restore" );
ExecCmd( @cmd, 0 );
runRiseRsync( $jobname, "/var/affa/$a/scheduled.0/", "/" );
imapIndexFilesDelete();
print "Signalling post-upgrade event\n";
@cmd = ( "/sbin/e-smith/signal-event", "post-upgrade" );
ExecCmd( @cmd, 0 );
print "affa server base restored. Please reboot now.\n";
}
sub resumeInterrupted() {
my @csv = getStatusRaw();
my $delay = 15;
foreach my $line (@csv) {
my @c = split( ";", $line );
if ( $c[9] && $c[9] =~ 'interrupted' ) {
my $jobname = $c[0];
my %job = getJobConfig($jobname);
my $txt = "Interrupted Affa job '$jobname' ";
if ( $job{'resumeAfterBoot'} eq 'yes' ) {
system("/usr/sbin/affa --run --_delay=$delay $jobname &");
$txt .= "resumed. Start is delayed by $delay seconds.";
$delay += 30;
}
else {
$txt .= "was NOT resumed.";
}
lg($txt);
print "$txt\n";
}
}
}
sub sendKeys() {
$interactive = 1;
@ARGV = getJobs() if not $ARGV[0];
foreach my $jobname (@ARGV) {
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
my $kf = "/root/.ssh/id_rsa_affa.pub";
my $s;
my @cmd;
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
print "Job $jobname: " if ($jobname);
if ( not -f $kf or not -f "/root/.ssh/id_rsa_affa" ) {
$s = "Generating RSA affa keys...";
print "$s\n";
lg($s);
@cmd = ( "/usr/bin/ssh-keygen", "-t", "rsa", "-N ''", "-b 4096", "-f", "/root/.ssh/id_rsa_affa" );
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't generate RSA keys");
$s = "Successfully created RSA key pair.";
print "$s\n";
lg($s);
}
open( PUBK, $kf ) or affaErrorExit("Could not open $kf");
my $pubk = trim(<PUBK>);
close(PUBK);
unlink "/root/.ssh/knownhosts-$jobname";
my $ak = $job{'RemoteAuthorizedKeysFile'};
( my $kd = $ak ) =~ s/(.*?)\/.*/$1/;
my $mkdirstr = $kd ? "mkdir -p $kd && chmod 700 $kd &&" : '';
my $cmd =
"/bin/cat $kf | /usr/bin/ssh $job{'_sshOpts'} -q -p $job{'sshPort'} $job{'remoteUser'}\@$job{'remoteHostName'} 'cat - > /tmp/$SystemName.\$\$ && $mkdirstr touch $ak && grep -v \"$pubk\" < $ak >> /tmp/$SystemName.\$\$ ; mv -f /tmp/$SystemName.\$\$ $ak'";
dbg("Exec Cmd: $cmd");
my $err = system($cmd);
$s =
$err ? "Sending public key to $job{'remoteHostName'} failed." : "Public key sent to $job{'remoteHostName'}";
print "$s\n";
lg($s);
}
}
sub revokeKeys($) {
my $jobname = shift;
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
my $kf = "/root/.ssh/id_rsa_affa.pub";
return if not -f $kf;
my $s;
my @cmd;
if ( not $cfg->SectionExists($jobname) ) { # does job exist?
my $txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
open( PUBK, $kf ) or affaErrorExit("Could not open $kf");
my $pubk = trim(<PUBK>);
close(PUBK);
my $ak = $job{'RemoteAuthorizedKeysFile'};
my $cmd =
"/usr/bin/ssh $job{'_sshOpts'} -q -o PasswordAuthentication=no -o StrictHostKeyChecking=yes $job{'remoteUser'}\@$job{'remoteHostName'} 'touch $ak && grep -v \"$pubk\" < $ak > $ak.$SystemName.\$\$ ; mv -f $ak.$SystemName.\$\$ $ak'";
dbg("Exec Cmd: $cmd");
my $err = system($cmd);
if ($err) {
$s = "Deleting public key on $job{'remoteHostName'} failed.";
print "$s\n";
affaErrorExit($s);
}
unlink "/root/.ssh/knownhosts-$jobname";
$s = "Public key deleted on $job{'remoteHostName'}";
print "$s\n";
lg($s);
}
sub checkConnectionsAll() {
@ARGV = getJobs() if not $ARGV[0];
foreach my $jb ( sort @ARGV ) {
next if $jb eq 'GlobalAffaConfig';
my %job = getJobConfig($jb);
my @cmd;
printf "%-16s : ", $jb;
if ( $job{'_rsyncd'} ) {
print "Rsyncd connection ";
@cmd = ( $job{'_rsyncLocal'}, '-dq', ( $job{'rsyncdUser'} ? $job{'rsyncdUser'} . '@' : '' ) . $job{'remoteHostName'} . "::'" . $job{'rsyncdModule'} . "'"
);
print( ( ( ExecCmd( @cmd, 0 ) == 0 ) ? "ok" : "FAILED" ) . ". " );
}
if ($jb ne 'localhost') {
print "SSH connection ";
@cmd = ( '/usr/bin/ssh', '-p', $job{'sshPort'}, '-i', '/root/.ssh/id_rsa_affa', '-o', "HostKeyAlias=$jb", '-o', 'StrictHostKeyChecking=yes', '-o', "ConnectTimeout=$job{'ConnectionCheckTimeout'}", '-o', 'PasswordAuthentication=no', $job{'_sshOpts'}, $job{'remoteUser'} . '@' . $job{'remoteHostName'}, 'echo OK'
);
ExecCmd( @cmd, 0 );
chomp($ExecCmdOut);
print( $ExecCmdOut eq "OK" ? "ok\n" : "FAILED\n" );
} else {
print "Not tested\n";
}
}
}
sub showProperty() {
exit if not $ARGV[0];
my $pa = $ARGV[0];
my $prop = '';
my $cnt = 0;
my %def = getDefaultConfig();
for my $p ( sort keys %def ) {
next if $p =~ /^_/;
if ( $p =~ /^$pa/i ) {
if ( $p =~ /^$pa$/i ) {
$prop = $p;
last;
}
$prop .= " " if $cnt;
$prop .= $p;
$cnt++;
}
}
exit if not $prop;
if ( $cnt > 1 ) {
print "$pa is ambiguous: $prop\n";
exit;
}
print "Values of property $prop\n";
my $jobcolw = 17;
my %multi = getMultivalueKeys();
my @sections = $cfg->Sections();
foreach my $jobname ( sort @sections ) {
next if $jobname eq 'GlobalAffaConfig';
my %job = getJobConfig($jobname);
my $jn = length($jobname) > $jobcolw ? substr( $jobname, 0, $jobcolw - 2 ) . ".." : $jobname;
if ( $multi{$prop} ) {
my @v = @{ $job{$prop} };
my $out = '';
foreach my $p ( sort @v ) {
if ( $prop =~ /(TimeSchedule|SambaValidUser)/ ) {
$out .= $out ? " $p" : sprintf( "%-" . $jobcolw . "s: %s", $jn, $p );
}
else {
$out .= sprintf( "%-" . $jobcolw . "s: %s\n", $jn, $p );
}
}
$out =~ s/\n$//;
print "$out\n" if $out;
}
else {
printf( "%-" . $jobcolw . "s: %s\n", $jn, $job{$prop} );
}
}
}
sub showConfigPathes() {
$interactive = 1;
my @csv = showConfigPathesRaw();
my $out = "$affaTitle\n";
if ( $opts{'csv'} ) {
print join( "\n", @csv ) . "\n";
}
else {
shift(@csv);
my $jobcolw = 2;
foreach my $k (@csv) {
my @c = split( ";", $k );
$jobcolw = length( $c[0] ) if $jobcolw < length( $c[0] );
}
$jobcolw = 3 if ( $jobcolw < 3 );
$jobcolw = 17 if ( $jobcolw > 17 );
foreach my $k ( sort @csv ) {
( my $job, my $configpath ) = split( ";", $k );
$job = length($job) > $jobcolw ? substr( $job, 0, $jobcolw - 2 ) . ".." : $job;
printf( "%-" . $jobcolw . "s: %s\n", $job, $configpath );
}
}
}
sub showConfigPathesRaw() {
my @ret;
push( @ret, "job;configpath" );
if ( not $ARGV[0] ) {
foreach my $job ( sort $cfg->Sections() ) {
next if $job eq 'GlobalAffaConfig';
push( @ARGV, $job );
}
}
foreach my $j ( sort @ARGV ) {
my $c = openOrgConfig($j);
push( @ret, "$j;" . $c->GetFileName() );
}
return @ret;
}
sub logTail() {
my $txt;
my $jobname = $ARGV[0] || '';
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
my $log;
if ( not $jobname ) {
$log = $logfile; # global log
}
else {
$log = "$logdir/$jobname.log";
}
if ( not -f $log ) {
$txt = "Error: Job log file not found.";
print("$txt\n");
affaErrorExit("$txt");
}
open( LF, $log );
my $lc = 0;
while ( <LF> && $lc < 50 ) {
$lc++;
}
close(LF);
print "\nHit Ctrl-C to exit\n";
sleep(1);
if ( $lc < 50 ) { #current log file is too short
my $pl = `ls -r $log-* 2>/dev/null | head -1`;
system("tail -50 $pl") if $pl; # show last rotated log file
}
system("tail -n 50 -f $log");
}
sub showSchedule() {
my $res = $opts{15} ? 15 : 30;
my $vt = 240 / 30 * $res;
my %out;
my $maxlen = 0;
my $disabled = 0;
my @cols;
for ( my $i = 0, my $h = 12 * 60; $i < 24 * 60; $i += $res, $h += $res ) {
$h = 0 if $h >= 24 * 60;
push( @cols, $h );
}
foreach my $jobname ( sort $cfg->Sections() ) {
next if $jobname eq 'GlobalAffaConfig';
my %job = getJobConfig($jobname);
if ( $job{'status'} ne "enabled" && not $opts{'all'} ) {
$disabled++;
next;
}
my $rpt;
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
if ( -f $rptfile ) {
$rpt = Config::IniFiles->new( -file => $rptfile );
}
my $ExecTime = $rpt ? $rpt->val( 'Report', 'ExecutionTime' ) : 0;
$ExecTime = int( $ExecTime / 60 );
my $end = $rpt ? $rpt->val( 'Report', 'Date' ) : '0000';
$end =~ /.*(..)(..)$/;
$end = $1 * 60 + $2;
my $pid = getLock($jobname);
my $dedup = $rpt && $rpt->exists( 'Report', 'DedupDate' ) ? 1 : 0;
my $ddend = '0000';
if ( !$dedup ) {
if ( $job{'dedup'} && $job{'dedup'} eq 'yes' && $pid && findProcessId( $pid, $dedupBinary ) ) {
$ddend = Date::Format::time2str( "%Y%m%d%H%M", time() );
$dedup = 1;
}
}
else {
$ddend = $rpt->val( 'Report', 'DedupDate' );
}
$ddend =~ /.*(..)(..)$/;
$ddend = $1 * 60 + $2;
my $ddstart = $end;
my $start = $end - $ExecTime;
while ( $start < 0 ) { $start += 1440 }
my ( $s1, $e1, $s2, $e2 );
if ( $start > $end ) {
$s1 = 0;
$e1 = $end;
$s2 = $start;
$e2 = 1440;
}
else {
$s1 = $s2 = $start;
$e1 = $e2 = $end;
}
my ( $ds1, $de1, $ds2, $de2 );
if ( $ddstart > $ddend ) {
$ds1 = 0;
$de1 = $ddend;
$ds2 = $ddstart;
$de2 = 1440;
}
else {
$ds1 = $ds2 = $ddstart;
$de1 = $de2 = $ddend;
}
my $jn = substr( $jobname, 0, 26 );
$maxlen = length($jn) if length($jn) > $maxlen;
# Time Schedule
my @ts = sort $cfg->val( $jobname, 'TimeSchedule' );
for ( my $i = 0; $i < @ts; $i++ ) {
$ts[$i] =~ /(..)(..)/;
$ts[$i] = $1 * 60 + $2;
}
### fix bug 10196
my @am;
my @pm;
while (@ts) {
my $t = shift @ts;
if ( $t <= 720 ) {
push( @am, $t );
}
else {
push( @pm, $t );
}
}
push( @ts, @pm );
push( @ts, @am );
## end fix bug 10196
# Kill at
my $killat = -1;
if ( $job{'killAt'} ) {
$job{'killAt'} =~ /(..)(..)/;
$killat = $1 * 60 + $2;
}
# Resume at
my $resumeat = -1;
if ( $job{'resumeKilledAt'} ) {
$job{'resumeKilledAt'} =~ /(..)(..)/;
$resumeat = $1 * 60 + $2;
}
my $t = shift @ts if @ts;
for my $i (@cols) {
$out{$jn} .= " " if $i % $vt == 0;
if ( $killat >= $i && $killat < $i + $res ) {
$out{$jn} .= "K";
}
elsif ( $resumeat >= $i && $resumeat < $i + $res ) {
$out{$jn} .= "R";
####### next line modified: fix bug 10196
# } elsif( $t >= $i && $t < $i+$res ) {
}
elsif ( $t && $t >= $i && $t < $i + $res ) {
if ( $t >= $i && $t < $i + $res ) {
$out{$jn} .= "S";
###### 3 next lines: fix bug 10196
# while( @ts && $t < $i+$res ) {
# $t=shift @ts;
# }
##### replaced through:
while ( @ts && $t < 720 && $t < $i + $res ) {
$t = shift @ts;
}
while ( @ts && $t >= 720 && $t < $i + $res ) {
$t = shift @ts;
}
}
## end fix bug 10196
}
elsif ( $i >= $s1 && $i < $e1 or $i >= $s2 && $i < $e2 ) {
$out{$jn} .= "=";
}
elsif ( $dedup && ( $i >= $ds1 && $i < $de1 or $i >= $ds2 && $i < $de2 ) ) {
$out{$jn} .= "~";
}
else {
$out{$jn} .= "-";
}
}
$out{$jn} .= " busy" if $pid;
$out{$jn} .= sprintf "\n";
}
print "$affaTitle\n";
if (%out) {
printf "%" . $maxlen . "s", "TIME";
for my $i (@cols) {
my $h = int( $i / 60 );
my $m = sprintf "%02d", $i - $h * 60;
printf " %-8s", "$h:$m" if $i % $vt == 0;
}
print "\n";
foreach my $k ( sort { index( $out{$a}, 'S' ) cmp index( $out{$b}, 'S' ) } keys %out ) {
printf "%" . $maxlen . "s%s", $k, $out{$k};
}
}
printf "Symbols: S=scheduled K=kill R=resume '='=rsync '~'=dedup\n";
printf "%d disabled jobs not listed. Use --all to display.\n", $disabled if $disabled;
}
sub sendStatus() {
if ( $job{'sendStatus'} || '' =~ /^(daily|weekly|monthly)$/ ) {
$job{'sendStatus'} = ucfirst( $job{'sendStatus'} );
my $msg = new Mail::Send;
$msg->subject("$job{'sendStatus'} status from Affa server $SystemName");
$msg->to( $job{'_EmailAddress'} );
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
my $fh = $msg->open;
print $fh "Status:\n";
print $fh getStatus();
print $fh "\nDisk Usage:\n";
print $fh DiskUsage();
print $fh "\nArchive lists of all jobs:\n";
print $fh listArchives();
print $fh "\ngenerated on " . Date::Format::ctime( time() );
$fh->close;
lg( "$job{'sendStatus'} status message sent to " . $job{'_EmailAddress'} );
}
}
sub mailTest() {
my $jobname = $ARGV[0] || '';
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
my $msg = new Mail::Send;
$msg->subject("Testmail from job '$jobname' on Affa server $SystemName");
$msg->to( $job{'_EmailAddress'} );
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
my $fh = $msg->open;
print $fh "It works!\n\n";
$fh->close;
foreach my $k ( @{ $job{'EmailAddress'} } ) {
my $txt = "Testmail sent to $k";
lg($txt);
print "$txt\n";
}
####################################################################################
#### début des modifs: copié à partir de Affa V2 et ajouté "$jobname" à mailTestWatchdogRemote();
if ( $job{'Watchdog'} eq 'yes' and $job{'remoteHostName'} ne 'localhost' ) {
mailTestWatchdogRemote($jobname);
my $txt = "Watchdog testmail from $job{'remoteHostName'} sent to " . $job{'_EmailAddress'};
lg($txt);
print "$txt\n";
}
#### fin des modifs
####################################################################################
}
sub setupSamba() {
lg("Configuring Samba");
if ( $smbdStatus ne 'enabled' ) {
lg("Samba service is not installed or not properly configured.");
return;
}
my %inc;
foreach my $jobname ( $cfg->Sections() ) {
next if $jobname eq 'GlobalAffaConfig';
my %job = getJobConfig($jobname);
if ( not $job{'_SambaValidUser'} and $job{'SambaShare'} eq 'yes' ) {
lg( "Job $jobname: No Valid Users defined. Samba has been access disabled."
);
}
my $affasmb = "/etc/samba/Affa-Job-$jobname.conf";
if ( $job{'SambaShare'} eq 'yes' and $job{'_SambaValidUser'} ) {
open( FD, ">$affasmb" )
or affaErrorExit("Could not create $affasmb");
print FD "[$jobname]\n";
print FD "path = $job{'RootDir'}/$jobname\n";
print FD "comment = Affa archive: $job{'Description'}\n";
print FD "valid users = $job{'_SambaValidUser'}\n";
print FD "force user = root\n";
print FD "read only = yes\n";
print FD "writable = no\n";
print FD
"veto files = /.$jobname-setup.ini/,/.doneDates/,/.AFFA4-REPORT/,/.AFFA-REPORT/.AFFA-TRASH/\n\n";
close(FD);
$inc{"include = $affasmb"} = 1;
}
else {
unlink($affasmb);
}
}
open( FD, "<$smbconf" ) or affaErrorExit("Could not open $smbconf");
open( FW, ">$smbconf.$$" )
or affaErrorExit("Could not create $smbconf.$$");
while (<FD>) {
$_ = trim($_);
if ( $_ =~ /include = (\/etc\/samba\/Affa-Job.*conf)/
and not $inc{$_} )
{
unlink $1;
}
print FW "$_\n"
if not $_
=~ /(include = \/etc\/samba\/Affa-Job|# Affa archives. Updated on)/;
# ligne d'origine - Bug 9298 - if( $_ =~ /^\[global\]$/i ) {
if ( $_ =~ /^#\[Affa-jobs\]$/i ) {
printf FW "# Affa archives. Updated on "
. formatHTime(
Date::Format::time2str( "%Y%m%d%H%M", time() ) )
. "\n";
print FW join( "\n", sort keys %inc ) . "\n";
}
}
close(FD);
close(FW);
rename( "$smbconf.$$", "$smbconf" )
or affaErrorExit("Moving $smbconf.$$ to $smbconf failed");
#my @cmd = ( $servicecommand, 'smb', 'reload' ); # reetp changed to restart
#ExecCmd( @cmd, 0 ); # Reload Samba config
$systemD->{'serviceName'} = 'smbd';
my $smbdStatus = ( $systemD->reload() ); # reetp - failure??
print "Status $smbdStatus\n";
}
sub sendErrorMesssage() {
return if not $jobname or not $Command;
my %job;
if ( $cfg->SectionExists($jobname) ) {
%job = getJobConfig($jobname);
}
return if not $job{'_EmailAddress'};
my $msg = new Mail::Send;
$msg->subject("Error on Affa server $SystemName: Job '$jobname' failed.");
$msg->to( $job{'_EmailAddress'} );
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
my $fh = $msg->open;
print $fh "Excerpt from log file $logfile:\n";
foreach my $k (@Messages) {
chomp($k);
$k =~ s/ /_/g;
print $fh "$k\n";
}
$fh->close;
lg( "Failure message sent to " . $job{'_EmailAddress'} );
}
sub sendSuccessMesssage() {
return
if not $jobname
or $Command ne "scheduled"
or not $job{'_EmailAddress'}
or ( $job{'chattyOnSuccess'} || 0 ) <= 0;
if ( $job{'chattyOnSuccess'} > 0 ) {
$job{'chattyOnSuccess'}--;
my $err = rewriteConfigVal( $jobname, 'chattyOnSuccess', $job{'chattyOnSuccess'} );
dbg("Decremented 'chattyOnSuccess' value to $job{'chattyOnSuccess'} in config file of job '$jobname'")
if ( not $err );
}
my $msg = new Mail::Send;
$msg->subject("Success on Affa server $SystemName: Job '$jobname' completed.");
$msg->to( $job{'_EmailAddress'} );
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
my $fh = $msg->open;
print $fh "Affa job '$jobname' successfully completed.\n\n";
print $fh listArchives() . "\n";
print $fh "\nDisk Usage:\n";
print $fh DiskUsage();
print $fh "\nYou will receive "
. ( $job{'chattyOnSuccess'} ? $job{'chattyOnSuccess'} : 'no' )
. " further success notifications.\n";
$fh->close;
lg( "Success message sent to " . $job{'_EmailAddress'} );
}
sub cronSetup() {
my $md5 = Digest::MD5->new;
my $mt = -M '/etc/cron.d/affa';
foreach my $s ( getConfigFileList() ) {
$mt = -M $s if $mt > -M $s;
$md5->add($s);
}
open( IN, "</etc/cron.d/affa" );
( my $md5x = <IN> ) =~ s/.*MD5:(.*)$/$1/;
chomp($md5x);
close(IN);
my $md5n = $md5->hexdigest;
return if ( $md5x eq $md5n ) && ( $mt >= -M '/etc/cron.d/affa' );
my @sects = $cfg->Sections();
open( CF, ">/tmp/affa.$$.$curtime" );
printf CF "# updated on "
. formatHTime( Date::Format::time2str( "%Y%m%d%H%M", time() ) ) . " MD5:"
. $md5n . "\n\n";
foreach my $j ( sort @sects ) {
next if ( $j eq "GlobalAffaConfig" );
my %job = getJobConfig($j);
if ( $job{'status'} eq 'enabled' ) {
print CF "# Job $j; $job{'remoteHostName'}, $job{'Description'}\n";
for my $t ( sort @{ $job{'TimeSchedule'} } ) {
$t =~ /(\d\d)(\d\d)/;
printf CF "%02d %02d * * * root %s --silent --run %s\n", $2, $1, '/usr/sbin/affa', $j;
}
if ( $job{'killAt'} ) {
( my $t = $job{'killAt'} ) =~ /(\d\d)(\d\d)/;
my $nt = 3600 * $1 + 60 * $2;
my $r = '';
if ( $job{'resumeKilledAt'} ) {
$job{'resumeKilledAt'} =~ /(\d\d)(\d\d)/;
my $sec = ( 3600 * $1 + 60 * $2 ) - $nt;
$sec = $sec <= 0 ? $sec += 86400 : $sec;
$r = "--resume=$sec";
}
printf CF "%02d %02d * * * root %s --silent --kill %s %s\n", $2, $1, '/usr/sbin/affa', $r, $j;
}
}
else {
print CF "# DISABLED Job $j; $job{'remoteHostName'}, $job{'Description'}\n";
}
print CF "\n";
}
my %job = getJobConfig('');
print CF "# Send status\n";
if ( $job{'sendStatus'} =~ /(daily|weekly|monthly)/ ) {
print CF "00 5 * * *" if $job{'sendStatus'} eq 'daily';
print CF "00 5 * * 0" if $job{'sendStatus'} eq 'weekly';
print CF "00 5 1 * *" if $job{'sendStatus'} eq 'monthly';
print CF " root /usr/sbin/affa --silent --send-status\n\n";
}
print CF "# update this file if needed\n";
print CF "*/15 * * * * root /usr/sbin/affa --silent --_cronupdate\n\n";
close(CF);
move( "/tmp/affa.$$.$curtime", "/etc/cron.d/affa" );
lg("/etc/cron.d/affa updated");
setupSamba();
}
sub moveFileorDir($$) {
( my $src, my $dst ) = @_;
return if not -e $src;
dbg("Moving $src to $dst");
move( $src, $dst ) or affaErrorExit("Moving $src to $dst failed");
}
sub removeDir($$) {
( my $dir, my $bg ) = @_;
return if ( not -d $dir );
$bg = $bg ? '&' : '';
dbg("Deleting $dir");
# after the first rm run, do a chmod 777 and run rm again. This delete files with wrong permissions.
# This is an issue on mounted CIFS filesystems.
#my @cmd=('/bin/rm', '-rf', $dir, ';', '/bin/chmod', '-R', '777', "$dir/*", '&>', '/dev/null', ';', '/bin/rm', '-rf', $dir );
my @cmd = ("(/bin/rm -rf $dir;/bin/chmod -R 777 $dir/*;/bin/rm -rf $dir) &>/dev/null $bg");
ExecCmd( @cmd, 0 );
}
sub remoteCopy($$$) {
( my $jobname, my $src, my $dst ) = @_;
my %job = getJobConfig($jobname);
my @cmd = (
$job{'_rsyncLocal'},
"--archive",
$job{'BandwidthLimit'} ? "--bwlimit=$job{'BandwidthLimit'}" : '',
$job{'rsync--modify-window'} > 0 ? "--modify-window=$job{'rsync--modify-window'}" : '',
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
"--rsync-path=\"$job{'_rsyncRemote'}\"",
"--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\"",
$src,
$job{'remoteUser'} . '@' . $job{'remoteHostName'} . ":$dst"
);
lg ("sub RemoteCopy");
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't copy $src to remote host.");
}
sub showVersion() {
print "$affaTitle\n";
# Ony shows if script detected
printf " Samba service is%s installed.\n", -f $SambaStartScript ? '' : ' NOT';
printf " NRPE service is%s installed.\n", -f $NRPEStartScript ? '' : ' NOT';
printf " Freedup program is%s installed.\n", -x $dedupBinary ? '' : ' NOT';
}
sub showTextfile($) {
my $f = shift;
if ( not -f $f ) {
print "File $f not found.\n";
exit -1;
}
open( FI, "<$f" );
while (<FI>) {
print $_;
}
}
sub showHelp($) {
my $short = shift;
if ( not $short ) {
print "$affaTitle\n";
print "Affa is a rsync based backup program for Linux.\n";
print "It remotely backups Linux or other systems, which have either the rsync\n";
print "program and the sshd service or the rsyncd service installed.\n";
print "Please see http://affa.sf.net for full documentation.\n";
print "\n";
print "Copyright (C) 2004-2012 by Michael Weinberger\n";
print "This program comes with ABSOLUTELY NO WARRANTY; for details type 'affa --warranty'.\n";
print "This is free software, and you are welcome to redistribute it\n";
print "under certain conditions; type 'affa --license' for details.\n";
print "\n";
}
print "Usage: affa --run JOB\n";
print " or affa --configcheck\n";
print " or affa --make-cronjobs\n";
print " or affa --send-key [JOB]\n";
print " or affa --check-connections [JOB JOB ...]\n";
print " or affa --resume-interrupted\n";
print " or affa --full-restore [--preserve-newer=no] [--delete=yes] JOB [ARCHIVE]\n";
print " or affa --rise JOB [ARCHIVE]\n";
print " or affa --list-archives [--csv] [JOB JOB ...]\n";
print " or affa --status [--csv]\n";
print " or affa --show-config-pathes [--csv] [JOB JOB ...]\n";
print " or affa --show-default-config\n";
print " or affa --show-schedule [-15]\n";
print " or affa --show-property PROPERTY\n";
print " or affa --log-tail [JOB]\n";
print " or affa --send-status\n";
print " or affa --disk-usage [--csv]\n";
print " or affa --cleanup JOB\n";
print " or affa --rename-job JOB NEWNAME\n";
print " or affa --move-archive JOB NEWROOTDIR\n";
print " or affa --delete-job JOB\n";
print " or affa --revoke-key JOB\n";
print " or affa --kill JOB\n";
print " or affa --killall\n";
print " or affa --mailtest JOB\n";
print " or affa --nrpe [JOB JOB ...]\n";
print " or affa --init-nrpe\n";
print " or affa --version\n";
print " or affa --warranty\n";
print " or affa --license\n";
print " or affa --help\n";
}
sub SignalHandler() {
my $sig = shift;
killProcessGroup( 0, $sig );
}
sub killall() {
foreach my $job ( sort $cfg->Sections() ) {
next if $job eq 'GlobalAffaConfig';
killJob($job);
}
}
sub killJob($) {
$jobname = shift;
$jobname =~ /([a-z0-9_\.-]*)/i;
$jobname = $1; # untaint
$allow_retry = 0;
if ( not $cfg->SectionExists($jobname) ) {
my $txt = "Error: Job '$jobname' undefined.";
print("$txt\n");
affaErrorExit("$txt");
}
my %job = getJobConfig($jobname);
my $pid = getLock($jobname);
if ($pid) {
if ( $opts{'all'} || $job{'dedupKill'} eq 'yes' || !findProcessId( $pid, $dedupBinary ) ) {
killProcessGroup( $pid, 'TERM' );
print "Affa job $jobname killed (pid=$pid)\n" if not $opts{'silent'};
my $resume = $opts{'resume'};
if ($resume) {
system("/usr/sbin/affa --run --_delay=$resume $jobname &");
}
}
else {
print "Deduplication still running. Job $jobname was not killed. Use --all to force kill.\n"
if not $opts{'silent'};
}
}
}
sub killProcessGroup($$) {
( my $pid, my $sig ) = @_;
$allow_retry = 0;
$SIG{'TERM'} = sub { };
my $pgrp = getpgrp($pid);
kill 'TERM', -$pgrp;
if ( $pid == 0 ) { # current process
lg("$Command run killed");
if ( $Command eq "scheduled" ) {
affaErrorExit("Caught interrupt signal $sig");
}
else {
exit -1;
}
}
}
sub affaErrorExit($) {
( my $msg ) = @_;
my $package = (caller)[0];
my $err = (caller)[2];
my $sub = ( caller(1) )[3] || '';
my $txt = "Error ($err): $msg";
lg($txt);
print "$txt\n" if $interactive == 1;
unmountAll();
my $retry = ( defined $opts{'RetryAttempts'} ? $opts{'RetryAttempts'} : $job{'RetryAttempts'} ) - 1;
my $retryCmd = '';
if ( $allow_retry && $retry >= 0 && ( $Command eq 'scheduled' ) ) {
my $sleep = $opts{'RetryAfter'} || ( $job{'RetryAfter'} ) || 0;
$sleep = 0 if $sleep < 0;
$retry = int( 86400 / $sleep ) - 1 if $retry * $sleep > 86400;
$retryCmd = "/usr/sbin/affa --run $jobname --RetryAttempts=$retry --RetryAfter=$sleep &";
lg( "Starting re-run "
. ( $job{'RetryAttempts'} - $retry )
. " of $job{'RetryAttempts'} in $job{'RetryAfter'} seconds." );
}
lg( "Total execution time: " . timeUnit( time() - $StartTime ) ) if $StartTime;
sendErrorMesssage() if not $retryCmd or not $allow_retry or ( $job{'RetryNotification'} || '' ) eq 'yes';
removeLock();
####################################################################################
###################################################### début modifs ajouté comme dans Affa2
startServices() if $ServicesStopped; # reetp - note to self
###################################################### fin modifs
#######################################################################################
lg("Exiting.");
lg('.');
system("sleep 3 && $retryCmd") if $retryCmd;
exit -1;
}
sub affaExit( $ ) {
my $msg = shift(@_);
unmountAll();
lg($msg);
removeLock();
lg( "Total execution time: " . timeUnit( time() - $StartTime ) ) if $StartTime;
lg("Exiting.");
lg('.');
exit 0;
}
###
sub setlog( $ ) {
my $s = shift;
$logfile = "$logdir/$s";
}
sub lg( $ ) {
if ($runninglog) {
my $txt = $runninglog;
$runninglog = '';
lg($txt);
}
my $str = shift(@_);
chomp($str);
if ( defined($logfile) ) {
if ($interrupt) {
my $txt = $interrupt;
$interrupt = '';
lg($txt);
}
open( LOG, ">> $logfile" ) or die "Error: Couldn't open logfile $logfile for writing\n";
my $tag = Date::Format::time2str( "%a %b %e %T", time() ) . "[$process_id]:";
$tag .= " " if ( ( $Command || '' ) =~ /^(daily|weekly|monthly|yearly)$/ );
my @s = split( /[\r\n]+/, $str );
foreach my $se (@s) {
print LOG "$tag $se\n";
}
close(LOG) or warn "Error: Couldn't close logfile $logfile\n";
chown( 0, 101, $logfile );
chmod( 0640, $logfile );
}
push( @Messages, $str );
}
sub dbg( $ ) {
if ( ( $job{'Debug'} || '' ) eq 'yes' ) {
lg( shift(@_) );
}
else {
push( @Messages, @_ );
}
}
sub ExecCmd( \@$ ) {
( my $cmdRef, my $forcelog ) = @_;
my @cmd = @$cmdRef;
# reetp remove empty array keys
@cmd = grep { $_ ne '' } @cmd;
dbg "sub ExecCmd this CMD: @cmd\n";
my $pipestatus = '';
$forcelog = 1 if $job{'Debug'};
die "Fork failed: $!\n" unless defined( my $pid = open( RCHILD, "-|" ) );
if ($pid) {
$ExecCmdOut = '';
while (<RCHILD>) {
chomp($_);
next if $_ eq '';
dbg("Exec Out: $pipestatus") if $forcelog and $pipestatus; # one step offset
s/\e\[[0-9\;]+[A-Za-z]//g; # remove ANSI escape sequences
$ExecCmdOut .= "$_\n";
$pipestatus = $_;
}
close(RCHILD);
}
else {
dbg("Exec Cmd: @cmd");
exec("@cmd 2>&1; echo \${PIPESTATUS}") or die "exec failed: $!\n";
}
$ExecCmdOut =~ s/$pipestatus\n$//;
$pipestatus = $? if not $pipestatus;
dbg("Exec Out: exit status: $pipestatus - ExecCmdOut: $ExecCmdOut") if $forcelog;
return $pipestatus;
}
sub ExecCmdBackupList( \@$ ) {
( my $cmdRef, my $forcelog ) = @_;
my @cmd = @$cmdRef;
# reetp remove empty array keys
@cmd = grep { $_ ne '' } @cmd;
if ($cliDebug == 1) {
printf "ExecCMD = @cmd\n";
}
my $pipestatus = '';
$forcelog = 1 if $job{'Debug'};
die "Fork failed: $!\n" unless defined( my $pid = open( RCHILD, "-|" ) );
if ($pid) {
$ExecCmdOut = '';
while (<RCHILD>) {
chomp($_);
next if $_ eq '';
dbg("Exec Out: $pipestatus") if $forcelog and $pipestatus; # one step offset
s/\e\[[0-9\;]+[A-Za-z]//g; # remove ANSI escape sequences
$ExecCmdOut .= "$_\n";
$pipestatus = $_;
}
close(RCHILD);
}
else {
dbg("Exec Cmd: @cmd");
exec("@cmd 2>&1; echo \${PIPESTATUS}") or die "exec failed: $!\n";
}
$ExecCmdOut =~ s/$pipestatus\n$//;
$pipestatus = $? if not $pipestatus;
dbg("Exec Out: exitstatus=$pipestatus ExecCmdOut: $ExecCmdOut") if $forcelog;
return $pipestatus, $ExecCmdOut;
}
sub trim($) {
my $s = shift;
$s =~ s/^\s+//;
$s =~ s/\s+$//;
return $s;
}
sub formatHTime($) {
my $ts = shift(@_);
my ( $y, $m, $d, $H, $M ) = $ts =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
return Date::Format::time2str( "%a %Y-%m-%d %H:%M", ( timelocal( 0, $M, $H, $d, $m - 1, $y ) ) );
}
sub hTime2Timestamp($) {
my $ts = shift(@_);
my ( $y, $m, $d, $H, $M ) = $ts =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
return timelocal( 0, $M, $H, $d, $m - 1, $y );
}
sub countUnit($) {
my $count = shift(@_);
my $unit = "";
if ( $count > 9999E9 ) {
$count = int( ( $count + 0.5E12 ) / 1E12 );
$unit = "T";
}
elsif ( $count > 9999E6 ) {
$count = int( ( $count + 0.5E9 ) / 1E9 );
$unit = "G";
}
elsif ( $count > 9999E3 ) {
$count = int( ( $count + 0.5E6 ) / 1E6 );
$unit = "M";
}
elsif ( $count > 9999E0 ) {
$count = int( ( $count + 0.5E3 ) / 1E3 );
$unit = "k";
}
$count .= "$unit";
return $count;
}
sub timeUnit($) {
my $res = '';
my $t = shift(@_);
my $days = int( $t / 86400 );
$t -= $days * 86400;
my $hours = int( $t / 3600 );
$t -= $hours * 3600;
my $minutes = int( $t / 60 );
my $seconds = $t - $minutes * 60;
if ($days) {
$res = sprintf( "%dd%02dh", $days, $hours );
}
elsif ($hours) {
$res = sprintf( "%2dh%02dm", $hours, $minutes );
}
else {
$res = sprintf( "%2dm%02ds", $minutes, $seconds );
}
return $res;
}
sub sizeUnit($) {
my $size = shift(@_);
my $unit = '';
if ( $size > 1024**4 ) {
$size = $size / 1024**4;
$unit = "T";
}
elsif ( $size > 1024**3 ) {
$size = $size / 1024**3;
$unit = "G";
}
elsif ( $size > 1024**2 ) {
$size = $size / 1024**2;
$unit = "M";
}
elsif ( $size > 1024 ) {
$size = $size / 1024;
$unit = "k";
}
if ( $unit && length( sprintf( "%2.1f", $size ) ) <= 3 ) {
$size = sprintf( "%2.1f", $size );
}
else {
$size = sprintf( "%3.0f", $size );
}
return "$size$unit";
}
sub stopServices() {
lg("Stopping services...");
# my @cmd;
foreach my $svc (@services) {
#reetp stop services
$systemD->{'serviceName'} = $svc;
my $Status = ( $systemD->stop() );
# @cmd = ( $serviceCommand, $svc, 'stop' );
# ExecCmd( @cmd, 0 );
# print $Affa::lib::ExecCmdOut;
# print $ExecCmdOut;
print "Stop $svc $Status\n"; #reetp - status?
}
$ServicesStopped = 1;
}
sub startServices() {
lg("Starting services...");
#my @cmd;
foreach my $svc (@services) {
$systemD->{'serviceName'} = $svc;
my $Status = ( $systemD->start() );
# @cmd = ( $serviceCommand, $svc, 'start' );
# ExecCmd( @cmd, 0 );
# print $Affa::lib::ExecCmdOut;
# print $ExecCmdOut;
print "Start $svc $Status\n"; #reetp - status?
}
$ServicesStopped = 0;
}
sub saveMySoul() {
my $txt = 'Backing up the Affa server base (this server).';
print "$txt\n";
lg($txt);
####
#### début copie de sme-affa-conf2ini.perl avec modifs AG
#### faire attention: $ServerBasename.conf dans /root et /root est écrasé par sub runRiseRsync($$)
# File::Path::mkpath( "/root/affa3conf/", 0, 0755 );
open( CF, ">/etc/affa/$ServerBasename.conf" );
print CF "[$ServerBasename]\n";
# foreach my $k ( sort keys %job )
# {
# my $key=$k;
# if( $k=~/^Include\[/ ){
# $key="Include";
# } elsif( $k=~/^Exclude\[/ ){
# $key="Exclude";
# } elsif( $k=~/TimeSchedule/ ){
# my @ts = split(/,/, $job{$k});
# foreach my $s (sort @ts) {
# print CF "TimeSchedule=$s\n";
# }
# next;
# } elsif( $k=~/EmailAddresses/ ){
# my @ts = split(/,/, $job{$k});
# foreach my $s (sort @ts) {
# next if $s eq 'admin';
# print CF "EmailAddress=$s\n";
# $postprefix="WARNING: EmailAddress=admin skipped!";
# }
# next;
# } elsif( $k eq 'SMEServer' && $job{$k} eq 'yes' ) {
# print CF "Include=/etc/e-smith/templates-custom\n";
# print CF "Include=/etc/e-smith/templates-user-custom\n";
# print CF "Include=/etc/group\n";
# print CF "Include=/etc/gshadow\n";
# print CF "Include=/etc/passwd\n";
# print CF "Include=/etc/samba/secrets.tdb\n";
# print CF "Include=/etc/samba/smbpasswd\n";
# print CF "Include=/etc/shadow\n";
# print CF "Include=/etc/smbpasswd\n";
# print CF "Include=/etc/ssh\n";
# print CF "Include=/etc/sudoers\n";
# print CF "Include=/home/e-smith\n";
# print CF "Include=/root\n";
# print CF "preJobCommandRemote=SME/signal-pre-backup\n";
# print CF "postJobCommandRemote=SME/signal-post-backup\n";
# } elsif( $k =~ /JobCommand$/ && $job{$k} =~ /^\// ) {
# $postprefix="WARNING: preJobCommand/postJobCommand needs fix!";
# $job{$k} =~ s/.*\///;
# }
# next if( ! defined $defs{$key} );
# next if( $defs{$key} eq $job{$k} );
# print CF "$key=$job{$k}\n" if $job{$k};
# }
print CF "remoteHostName=localhost\n";
print CF "Description=for internal use only\n";
print CF "scheduledKeep=1\n";
print CF "dailyKeep=1\n";
print CF "weeklyKeep=1\n";
print CF "monthlyKeep=1\n";
print CF "yearlyKeep=1\n";
print CF "SMEServer=yes\n";
print CF "DiskSpaceWarn=none\n";
print CF "status=disabled\n";
print CF "RetryAfter=0\n";
print CF "RetryAttempts=0\n";
close(CF);
# File::Path::mkpath( "/var/affa/", 0, 0755 );
# open( DD, ">/var/affa/$ServerBasename/.doneDates.conf");
# print DD "[doneDates]\n";
# print DD "daily=$thisDay\n";
# print DD "weekly=$thisWeek\n";
# print DD "monthly=$thisMonth\n";
# print DD "yearly=$thisYear\n";
# close(DD);
# print "/root/affa3conf/$xjob.conf created. $postprefix\n";
print "/etc/affa/$ServerBasename.conf created.\n";
#### fin de la copie de sme-affa-conf2ini.perl et des modifs AG
####################################################################################
# my $asb = $affa->get($ServerBasename);
# $asb->delete() if $asb;
# my $me = $affa->new_record($ServerBasename);
# $me->set_prop('type','job');
# my %props=(
# 'remoteHostName'=>'localhost',
# 'RootDir'=>'/var/affa',
# 'TimeSchedule'=>'',
# 'Description'=>'for internal use only',
# 'scheduledKeep'=>1,
# 'dailyKeep'=>1,
# 'weeklyKeep'=>1,
# 'monthlyKeep'=>1,
# 'yearlyKeep'=>1,
# 'SMEServer'=>'yes',
# 'RPMCheck'=>'no',
# 'DiskSpaceWarn'=>'none',
# 'localNice'=>'+19',
# 'remoteNice'=>0,
# 'Watchdog'=>'no',
# 'ConnectionCheckTimeout'=>120,
# 'rsyncTimeout'=>900,
# 'rsyncCompress'=>'yes',
# 'EmailAddresses'=>'admin',
# 'postJobCommand'=>'',
# 'preJobCommand'=>'',
# 'doneDaily'=>$thisDay,
# 'doneMonthly'=>$thisMonth,
# 'doneWeekly'=>$thisWeek,
# 'doneYearly'=>$thisYear,
# 'status'=>'disabled',
# 'Debug'=>'no',
# );
# while( (my $p, my $v) = each %props )
# {
# $me->set_prop($p,$v);
# }
####################################################################################
##### début modifs
%job = getJobConfig($ServerBasename);
#### fin modifs
####################################################################################
if ( not -d $job{'RootDir'} ) {
my $input = '';
while ( not $input =~ /^(yes|no)$/ ) {
print "Directory $job{'RootDir'} does not exist. Create? (yes|no) ";
$input = lc(<STDIN>);
chomp($input);
}
if ( $input ne 'yes' ) {
$interactive = 0;
affaErrorExit("Terminated by user");
}
File::Path::mkpath( $job{'RootDir'}, 0, 0700 );
}
#### ligne d'origine de AffaV2: my @cmd=('/sbin/e-smith/affa','--run', $ServerBasename);
my @cmd = ( '/usr/sbin/affa', '--run', $ServerBasename );
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't backup myself");
print "Done.\n";
}
sub imapIndexFilesDeleteCommand() {
return
'USERS=`/usr/bin/find /home/e-smith/files/users -maxdepth 1 -type d`;USERS="/home/e-smith/ $USERS"; for u in $USERS ; do ! /usr/bin/test -d $u/Maildir && continue; /usr/bin/find $u/Maildir -maxdepth 2 -type f -name ".imap.index*" -exec /bin/rm -f \'{}\' \; ; /usr/bin/find $u/Maildir -maxdepth 2 -type f -name "dovecot.index*" -exec /bin/rm -f \'{}\' \; ; done';
}
sub imapIndexFilesDelete() {
# Sometimes Dovecot index files are corrupted after a restore
# It is save to delete them all. Dovecot will rebuild them when the mailbox is accessed.
print "Deleting Dovecot's index files\n";
my @cmd = ( imapIndexFilesDeleteCommand() );
ExecCmd( @cmd, 0 );
}
##### fin des modifs
####################################################################################
END {
unlink($configfile) if $configfile;
}