#!/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 = ) { 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'} ? '' : ''; $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 = ; chomp($lockpid); close(LOCK); } if ($lockpid) { if ( -f "/proc/$lockpid/stat" ) { if ( open( 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, "pid . "/cmdline" ) ) { $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 () { my @z = split( " ", $_ ); $remoteRPM{ $z[0] } = $z[1]; } close(RP); installedRPMsList(1); my %localRPM; dbg("Reading local RPM list"); open( 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\" " ); 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 () { 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, "/tmp/$$.$WDName" ) or warn "Error: Couldn't open /tmp/$$.$WDName for writing\n"; dbg("Watchdog parameters:"); while () { #### 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/$WDName" ) or warn "Error: Couldn't open /usr/lib/affa/$WDName for writing\n"; dbg("Watchdog parameters:"); while () { #### 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 () { 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\" " ); 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 to cancel: "; $input = lc(); 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(); 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, "; 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 () { 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 to cancel: "; $input = lc(); 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 to cancel: "; $input = lc(); 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(); 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(); 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(); 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(); 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 ( && $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\" " ); 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\" " ); 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 () { $_ = 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\" " ); 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\" " ); 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, " ) =~ 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 () { 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 () { 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 () { 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(); 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; }