| Line 56... |
Line 56... |
| 56 |
# makeReplicateCommands: build zfs send/receive command lists from snapshot lists and prior status
|
56 |
# makeReplicateCommands: build zfs send/receive command lists from snapshot lists and prior status
|
| 57 |
# sendReport: helper to deliver replication reports (email/file) — exported for scripts to implement
|
57 |
# sendReport: helper to deliver replication reports (email/file) — exported for scripts to implement
|
| 58 |
# fatalError: helper to log a fatal condition and die (convenience wrapper)
|
58 |
# fatalError: helper to log a fatal condition and die (convenience wrapper)
|
| 59 |
# getDirectoryList: utility to list directory contents with optional filters
|
59 |
# getDirectoryList: utility to list directory contents with optional filters
|
| 60 |
# cleanDirectory: safe directory cleaning utility used by snapshot pruning helpers
|
60 |
# cleanDirectory: safe directory cleaning utility used by snapshot pruning helpers
|
| 61 |
# exported package variables: $logFileName, $displayLogsOnConsole, $lastRunError
|
61 |
# exported package variables: $logFileName, $displayLogsOnConsole, $lastRunError, $verboseLoggingLevel
|
| 62 |
|
62 |
#
|
| 63 |
# v1.0 RWR 20251215
|
63 |
# v1.0 RWR 20251215
|
| 64 |
# This is the initial, tested release
|
64 |
# This is the initial, tested release
|
| 65 |
|
65 |
#
|
| - |
|
66 |
# v1.0.1 RWR 20251215
|
| - |
|
67 |
# Added verbose logging control to logMsg calls, controlled by $verboseLoggingLevel
|
| 66 |
|
68 |
|
| 67 |
# Exported functions and variables
|
69 |
# Exported functions and variables
|
| 68 |
|
70 |
|
| 69 |
our @EXPORT_OK = qw(loadConfig shredFile mountDriveByLabel unmountDriveByLabel mountGeli logMsg runCmd makeReplicateCommands sendReport fatalError getDirectoryList cleanDirectory $logFileName $displayLogsOnConsole $lastRunError);
|
71 |
our @EXPORT_OK = qw(loadConfig shredFile mountDriveByLabel unmountDriveByLabel mountGeli logMsg runCmd makeReplicateCommands sendReport fatalError getDirectoryList cleanDirectory $logFileName $displayLogsOnConsole $lastRunError $verboseLoggingLevel);
|
| 70 |
|
72 |
|
| 71 |
our $VERSION = '1.0';
|
73 |
our $VERSION = '1.0';
|
| 72 |
|
74 |
|
| 73 |
# these are variables which affect the flow of the program and are exported so they can be modified by the caller
|
75 |
# these are variables which affect the flow of the program and are exported so they can be modified by the caller
|
| 74 |
our $logFileName = '/tmp/zfs_utils.log'; # this can be overridden by the caller, and turned off with empty string
|
76 |
our $logFileName = '/tmp/zfs_utils.log'; # this can be overridden by the caller, and turned off with empty string
|
| 75 |
our $displayLogsOnConsole = 1; # if non-zero, log messages are also printed to console
|
77 |
our $displayLogsOnConsole = 1; # if non-zero, log messages are also printed to console
|
| 76 |
our $merge_stderr = 0; # if set to 1, stderr is captured in runCmd
|
78 |
our $merge_stderr = 0; # if set to 1, stderr is captured in runCmd
|
| 77 |
our $lastRunError = 0; # tracks the last error code from runCmd
|
79 |
our $lastRunError = 0; # tracks the last error code from runCmd
|
| - |
|
80 |
our $verboseLoggingLevel = 0; # if non-zero, logMsg will include more verbose output
|
| 78 |
|
81 |
|
| 79 |
# Execute a command and return its output.
|
82 |
# Execute a command and return its output.
|
| 80 |
# If called in scalar context, returns the full output as a single string.
|
83 |
# If called in scalar context, returns the full output as a single string.
|
| 81 |
# If called in list context, returns the output split into lines.
|
84 |
# If called in list context, returns the output split into lines.
|
| 82 |
# If $merge_stderr is true (default), stderr is merged into stdout (only for scalar commands).
|
85 |
# If $merge_stderr is true (default), stderr is merged into stdout (only for scalar commands).
|
| Line 84... |
Line 87... |
| 84 |
sub runCmd {
|
87 |
sub runCmd {
|
| 85 |
my $cmd = join( ' ', @_ );
|
88 |
my $cmd = join( ' ', @_ );
|
| 86 |
$merge_stderr = 1 unless defined $merge_stderr;
|
89 |
$merge_stderr = 1 unless defined $merge_stderr;
|
| 87 |
my $output = '';
|
90 |
my $output = '';
|
| 88 |
|
91 |
|
| 89 |
logMsg( "Running command [$cmd]" );
|
92 |
logMsg( "Running command [$cmd]" ) if $verboseLoggingLevel >= 2;
|
| 90 |
$cmd .= ' 2>&1' if $merge_stderr;
|
93 |
$cmd .= ' 2>&1' if $merge_stderr;
|
| 91 |
$output = `$cmd`;
|
94 |
$output = `$cmd`;
|
| 92 |
$lastRunError = $?;
|
95 |
$lastRunError = $?;
|
| 93 |
if ( $lastRunError ) {
|
96 |
if ( $lastRunError ) {
|
| 94 |
if ($? == -1) {
|
97 |
if ($? == -1) {
|
| Line 150... |
Line 153... |
| 150 |
unless ( $driveInfo->{label} =~ /^[a-zA-Z0-9_\-]+$/ ) {
|
153 |
unless ( $driveInfo->{label} =~ /^[a-zA-Z0-9_\-]+$/ ) {
|
| 151 |
logMsg("mountDriveByLabel: Invalid label '$driveInfo->{label}'");
|
154 |
logMsg("mountDriveByLabel: Invalid label '$driveInfo->{label}'");
|
| 152 |
return '';
|
155 |
return '';
|
| 153 |
}
|
156 |
}
|
| 154 |
|
157 |
|
| 155 |
logMsg("mountDriveByLabel: Looking for drive with label '$driveInfo->{label}'");
|
158 |
logMsg("mountDriveByLabel: Looking for drive with label '$driveInfo->{label}'") if $verboseLoggingLevel >= 1;
|
| 156 |
# default to /mnt/label if not provided
|
159 |
# default to /mnt/label if not provided
|
| 157 |
$driveInfo->{mountPath} //= "/mnt/$driveInfo->{label}"; # this is where we'll mount it if we find it
|
160 |
$driveInfo->{mountPath} //= "/mnt/$driveInfo->{label}"; # this is where we'll mount it if we find it
|
| 158 |
$driveInfo->{fstype} //= 'ufs'; # default to mounting ufs
|
161 |
$driveInfo->{fstype} //= 'ufs'; # default to mounting ufs
|
| 159 |
# The location for the label depends on filesystem. Only providing access to ufs and msdos here for safety.
|
162 |
# The location for the label depends on filesystem. Only providing access to ufs and msdos here for safety.
|
| 160 |
# gpt labeled drives for ufs are in /dev/gpt/, for msdosfs in /dev/msdosfs/
|
163 |
# gpt labeled drives for ufs are in /dev/gpt/, for msdosfs in /dev/msdosfs/
|
| Line 184... |
Line 187... |
| 184 |
return '';
|
187 |
return '';
|
| 185 |
}
|
188 |
}
|
| 186 |
# mount device
|
189 |
# mount device
|
| 187 |
runCmd( "mount -t $driveInfo->{fstype} $labelPath $driveInfo->{mountPath}" );
|
190 |
runCmd( "mount -t $driveInfo->{fstype} $labelPath $driveInfo->{mountPath}" );
|
| 188 |
if ( $lastRunError ) {
|
191 |
if ( $lastRunError ) {
|
| 189 |
logMsg("Failed to mount $labelPath on $driveInfo->{mountPath}: $!");
|
192 |
logMsg("Failed to mount $labelPath on $driveInfo->{mountPath}: $!") if $verboseLoggingLevel >= 0;
|
| 190 |
return '';
|
193 |
return '';
|
| 191 |
}
|
194 |
}
|
| 192 |
return $driveInfo->{mountPath};
|
195 |
return $driveInfo->{mountPath};
|
| 193 |
} else {
|
196 |
} else {
|
| 194 |
return '';
|
197 |
return '';
|
| Line 206... |
Line 209... |
| 206 |
unless ( $driveInfo->{label} =~ /^[a-zA-Z0-9_\-]+$/ ) {
|
209 |
unless ( $driveInfo->{label} =~ /^[a-zA-Z0-9_\-]+$/ ) {
|
| 207 |
logMsg("unmountDriveByLabel: Invalid label '$driveInfo->{label}'");
|
210 |
logMsg("unmountDriveByLabel: Invalid label '$driveInfo->{label}'");
|
| 208 |
return '';
|
211 |
return '';
|
| 209 |
}
|
212 |
}
|
| 210 |
|
213 |
|
| 211 |
logMsg("unmountDriveByLabel: Looking for drive with label '$driveInfo->{label}'");
|
214 |
logMsg("unmountDriveByLabel: Looking for drive with label '$driveInfo->{label}'") if $verboseLoggingLevel >= 1;
|
| 212 |
# default to /mnt/label if not provided
|
215 |
# default to /mnt/label if not provided
|
| 213 |
$driveInfo->{mountPath} //= "/mnt/$driveInfo->{label}"; # this is where we'll mount it if we find it
|
216 |
$driveInfo->{mountPath} //= "/mnt/$driveInfo->{label}"; # this is where we'll mount it if we find it
|
| 214 |
|
217 |
|
| 215 |
runCmd( "mount | grep '$driveInfo->{mountPath}'" );
|
218 |
runCmd( "mount | grep '$driveInfo->{mountPath}'" );
|
| 216 |
if ( $lastRunError ) {
|
219 |
if ( $lastRunError ) {
|
| 217 |
logMsg("Drive with label '$driveInfo->{label}' is not mounted");
|
220 |
logMsg("Drive with label '$driveInfo->{label}' is not mounted") if $verboseLoggingLevel >= 2;
|
| 218 |
return '';
|
221 |
return '';
|
| 219 |
}
|
222 |
}
|
| 220 |
|
223 |
|
| 221 |
# unmount device
|
224 |
# unmount device
|
| 222 |
runCmd( "umount $driveInfo->{mountPath}" );
|
225 |
runCmd( "umount $driveInfo->{mountPath}" );
|
| Line 274... |
Line 277... |
| 274 |
# Try YAML::XS first, fall back to YAML::Tiny
|
277 |
# Try YAML::XS first, fall back to YAML::Tiny
|
| 275 |
eval {
|
278 |
eval {
|
| 276 |
require YAML::XS;
|
279 |
require YAML::XS;
|
| 277 |
YAML::XS->import();
|
280 |
YAML::XS->import();
|
| 278 |
$yaml = YAML::XS::LoadFile($filename);
|
281 |
$yaml = YAML::XS::LoadFile($filename);
|
| 279 |
logMsg("using YAML::XS to load $filename");
|
282 |
logMsg("using YAML::XS to load $filename") if $verboseLoggingLevel >= 3;
|
| 280 |
1;
|
283 |
1;
|
| 281 |
} or do {
|
284 |
} or do {
|
| 282 |
eval {
|
285 |
eval {
|
| 283 |
require YAML::Tiny;
|
286 |
require YAML::Tiny;
|
| 284 |
YAML::Tiny->import();
|
287 |
YAML::Tiny->import();
|
| 285 |
$yaml = YAML::Tiny->read($filename);
|
288 |
$yaml = YAML::Tiny->read($filename);
|
| 286 |
$yaml = $yaml->[0] if $yaml; # YAML::Tiny returns an arrayref of documents
|
289 |
$yaml = $yaml->[0] if $yaml; # YAML::Tiny returns an arrayref of documents
|
| 287 |
logMsg("using YAML::Tiny to load $filename");
|
290 |
logMsg("using YAML::Tiny to load $filename") if $verboseLoggingLevel >= 3;
|
| 288 |
1;
|
291 |
1;
|
| 289 |
} or do {
|
292 |
} or do {
|
| 290 |
logMsg("No YAML parser installed (YAML::XS or YAML::Tiny). Skipping config load from $filename");
|
293 |
logMsg("No YAML parser installed (YAML::XS or YAML::Tiny). Skipping config load from $filename");
|
| 291 |
return ($default && ref $default eq 'HASH') ? $default : {};
|
294 |
return ($default && ref $default eq 'HASH') ? $default : {};
|
| 292 |
};
|
295 |
};
|
| Line 314... |
Line 317... |
| 314 |
## Returns:
|
317 |
## Returns:
|
| 315 |
## Pool name (string) on success, empty string on error.
|
318 |
## Pool name (string) on success, empty string on error.
|
| 316 |
sub mountGeli {
|
319 |
sub mountGeli {
|
| 317 |
my $geliConfig = shift;
|
320 |
my $geliConfig = shift;
|
| 318 |
|
321 |
|
| 319 |
logMsg( "geli config detected, attempting to mount geli disks" );
|
322 |
logMsg( "geli config detected, attempting to mount geli disks" ) if $verboseLoggingLevel >= 0;
|
| 320 |
# Can't continue at all if no pool name
|
323 |
# Can't continue at all if no pool name
|
| 321 |
unless ( $geliConfig->{'poolname'} ) {
|
324 |
unless ( $geliConfig->{'poolname'} ) {
|
| 322 |
logMsg "Could not find pool name in configuration file\n";
|
325 |
logMsg "Could not find pool name in configuration file\n";
|
| 323 |
return '';
|
326 |
return '';
|
| 324 |
}
|
327 |
}
|
| Line 343... |
Line 346... |
| 343 |
##
|
346 |
##
|
| 344 |
## Returns an array of device names (eg: qw( ada0 ada1 )) that appear free for use.
|
347 |
## Returns an array of device names (eg: qw( ada0 ada1 )) that appear free for use.
|
| 345 |
## The routine collects all disks, excludes disks with existing partitions and those
|
348 |
## The routine collects all disks, excludes disks with existing partitions and those
|
| 346 |
## referenced by active zpools.
|
349 |
## referenced by active zpools.
|
| 347 |
sub findGeliDisks {
|
350 |
sub findGeliDisks {
|
| 348 |
logMsg("Finding available disks for GELI/ZFS use");
|
351 |
logMsg("Finding available disks for GELI/ZFS use") if $verboseLoggingLevel >= 2;
|
| 349 |
# get all disks in system
|
352 |
# get all disks in system
|
| 350 |
my %allDisks = map{ chomp $_ ; $_ => 1 } runCmd( "geom disk list | grep 'Geom name:' | rev | cut -d' ' -f1 | rev" );
|
353 |
my %allDisks = map{ chomp $_ ; $_ => 1 } runCmd( "geom disk list | grep 'Geom name:' | rev | cut -d' ' -f1 | rev" );
|
| 351 |
# get the disks with partitions
|
354 |
# get the disks with partitions
|
| 352 |
my @temp = runCmd( "gpart show -p | grep '^=>'"); # -p prints just the disks without partitions
|
355 |
my @temp = runCmd( "gpart show -p | grep '^=>'"); # -p prints just the disks without partitions
|
| 353 |
# remove them from the list
|
356 |
# remove them from the list
|
| Line 411... |
Line 414... |
| 411 |
|
414 |
|
| 412 |
# Derive the decrypted device name (.eli suffix on FreeBSD)
|
415 |
# Derive the decrypted device name (.eli suffix on FreeBSD)
|
| 413 |
my $decrypted = $disk . '.eli';
|
416 |
my $decrypted = $disk . '.eli';
|
| 414 |
|
417 |
|
| 415 |
# Decrypt using geli attach with the keyfile
|
418 |
# Decrypt using geli attach with the keyfile
|
| 416 |
logMsg("Decrypting $disk with keyfile $keyfile");
|
419 |
logMsg("Decrypting $disk with keyfile $keyfile") if $verboseLoggingLevel >= 2;
|
| 417 |
runCmd("geli attach -p -k $geliConfig->{target} $disk");
|
420 |
runCmd("geli attach -p -k $geliConfig->{target} $disk");
|
| 418 |
if ( $lastRunError) {
|
421 |
if ( $lastRunError) {
|
| 419 |
logMsg "Failed to decrypt $disk (exit $lastRunError)\n";
|
422 |
logMsg "Failed to decrypt $disk (exit $lastRunError)\n" if $verboseLoggingLevel >= 3;
|
| 420 |
next; # ignore failed disks and continue to see if we can import the pool
|
423 |
next; # ignore failed disks and continue to see if we can import the pool
|
| 421 |
}
|
424 |
}
|
| 422 |
|
425 |
|
| 423 |
unless ( -e $decrypted ) {
|
426 |
unless ( -e $decrypted ) {
|
| 424 |
logMsg "Decrypted device $decrypted does not exist after geli attach\n";
|
427 |
logMsg "Decrypted device $decrypted does not exist after geli attach\n" if $verboseLoggingLevel >= 0;
|
| 425 |
return '';
|
428 |
return '';
|
| 426 |
}
|
429 |
}
|
| 427 |
push @decrypted_devices, $decrypted;
|
430 |
push @decrypted_devices, $decrypted;
|
| 428 |
}
|
431 |
}
|
| 429 |
|
432 |
|
| 430 |
# Import the ZFS pool
|
433 |
# Import the ZFS pool
|
| 431 |
logMsg("Importing ZFS pool $poolname");
|
434 |
logMsg("Importing ZFS pool $poolname") if $verboseLoggingLevel >= 0;
|
| 432 |
my @import_cmd = ('zpool', 'import');
|
435 |
my @import_cmd = ('zpool', 'import');
|
| 433 |
# If decrypted devices exist, add their directories to -d list
|
- |
|
| 434 |
#foreach my $dev (@decrypted_devices) {
|
- |
|
| 435 |
# my $dir = $dev;
|
- |
|
| 436 |
# $dir =~ s!/[^/]+$!!; # Remove filename to get directory
|
- |
|
| 437 |
# push @import_cmd, '-d', $dir;
|
- |
|
| 438 |
#}
|
- |
|
| 439 |
|
436 |
|
| 440 |
push @import_cmd, $poolname;
|
437 |
push @import_cmd, $poolname;
|
| 441 |
|
438 |
|
| 442 |
runCmd("zpool import $poolname" );
|
439 |
runCmd("zpool import $poolname" );
|
| 443 |
unless ( $lastRunError == 0 ) {
|
440 |
unless ( $lastRunError == 0 ) {
|
| 444 |
logMsg("Failed to import zfs pool $poolname (exit $lastRunError)\n");
|
441 |
logMsg("Failed to import zfs pool $poolname (exit $lastRunError)\n");
|
| 445 |
return '';
|
442 |
return '';
|
| 446 |
}
|
443 |
}
|
| 447 |
|
444 |
|
| 448 |
# Mount the ZFS pool (zfs mount -a mounts all filesystems in the pool)
|
445 |
# Mount the ZFS pool (zfs mount -a mounts all filesystems in the pool)
|
| 449 |
logMsg("Mounting ZFS pool $poolname");
|
446 |
logMsg("Mounting ZFS pool $poolname") if $verboseLoggingLevel >= 1;
|
| 450 |
runCmd('zfs mount -a');
|
447 |
runCmd('zfs mount -a');
|
| 451 |
unless ( $lastRunError == 0 ) {
|
448 |
unless ( $lastRunError == 0 ) {
|
| 452 |
logMsg("Failed to mount zfs pool $poolname (exit $lastRunError)\n");
|
449 |
logMsg("Failed to mount zfs pool $poolname (exit $lastRunError)\n");
|
| 453 |
return '';
|
450 |
return '';
|
| 454 |
}
|
451 |
}
|
| 455 |
|
452 |
|
| 456 |
logMsg("Successfully decrypted and mounted pool $poolname");
|
453 |
logMsg("Successfully decrypted and mounted pool $poolname") if $verboseLoggingLevel >= 2;
|
| 457 |
return $poolname;
|
454 |
return $poolname;
|
| 458 |
}
|
455 |
}
|
| 459 |
|
456 |
|
| 460 |
## Create a GELI key by XOR'ing a remote binary keyfile and a local key (hex string).
|
457 |
## Create a GELI key by XOR'ing a remote binary keyfile and a local key (hex string).
|
| 461 |
##
|
458 |
##
|
| Line 477... |
Line 474... |
| 477 |
$geliConfig->{secureKey}->{keyfile} //= '';
|
474 |
$geliConfig->{secureKey}->{keyfile} //= '';
|
| 478 |
$geliConfig->{localKey} //= '';
|
475 |
$geliConfig->{localKey} //= '';
|
| 479 |
$geliConfig->{target} //= '';
|
476 |
$geliConfig->{target} //= '';
|
| 480 |
|
477 |
|
| 481 |
if ( $geliConfig->{target} && -f $geliConfig->{target} ) {
|
478 |
if ( $geliConfig->{target} && -f $geliConfig->{target} ) {
|
| 482 |
logMsg "GELI target keyfile $geliConfig->{target} already exists. Not overwriting.\n";
|
479 |
logMsg "GELI target keyfile $geliConfig->{target} already exists. Not overwriting.\n" if $verboseLoggingLevel >= 2;
|
| 483 |
return 1;
|
480 |
return 1;
|
| 484 |
}
|
481 |
}
|
| 485 |
|
482 |
|
| 486 |
my $remote_keyfile = "$geliConfig->{secureKey}->{path}/$geliConfig->{secureKey}->{keyfile}";
|
483 |
my $remote_keyfile = "$geliConfig->{secureKey}->{path}/$geliConfig->{secureKey}->{keyfile}";
|
| 487 |
my $localKeyHexOrPath = $geliConfig->{localKey};
|
484 |
my $localKeyHexOrPath = $geliConfig->{localKey};
|
| Line 489... |
Line 486... |
| 489 |
|
486 |
|
| 490 |
if ( $geliConfig->{secureKey}->{keyfile} && $geliConfig->{localKey} ) {
|
487 |
if ( $geliConfig->{secureKey}->{keyfile} && $geliConfig->{localKey} ) {
|
| 491 |
# we have what we need to proceed
|
488 |
# we have what we need to proceed
|
| 492 |
|
489 |
|
| 493 |
if ( -f $remote_keyfile ) {
|
490 |
if ( -f $remote_keyfile ) {
|
| 494 |
logMsg "Creating GELI keyfile at $geliConfig->{target} using remote keyfile " . $geliConfig->{secureKey}->{keyfile} . " and local key\n";
|
491 |
logMsg "Creating GELI keyfile at $geliConfig->{target} using remote keyfile " . $geliConfig->{secureKey}->{keyfile} . " and local key\n"
|
| - |
|
492 |
if $verboseLoggingLevel >= 2;
|
| 495 |
} else {
|
493 |
} else {
|
| 496 |
die "Remote keyfile " . $geliConfig->{secureKey}->{keyfile} . " does not exist\n";
|
494 |
die "Remote keyfile " . $geliConfig->{secureKey}->{keyfile} . " does not exist\n";
|
| 497 |
}
|
495 |
}
|
| 498 |
}
|
496 |
}
|
| 499 |
|
497 |
|
| Line 690... |
Line 688... |
| 690 |
# $logFile is the path to the log file to include in the report
|
688 |
# $logFile is the path to the log file to include in the report
|
| 691 |
sub sendReport {
|
689 |
sub sendReport {
|
| 692 |
my ( $reportConfig, $message, $logFile ) = @_;
|
690 |
my ( $reportConfig, $message, $logFile ) = @_;
|
| 693 |
return unless defined $reportConfig;
|
691 |
return unless defined $reportConfig;
|
| 694 |
$logFile //= $reportConfig->{logFile};
|
692 |
$logFile //= $reportConfig->{logFile};
|
| 695 |
logMsg( "Beginning sendReport" );
|
693 |
logMsg( "Beginning sendReport" ) if $verboseLoggingLevel >= 0;
|
| 696 |
# if targetDrive defined and there is a valid label for it, try to mount it and write the report there
|
694 |
# if targetDrive defined and there is a valid label for it, try to mount it and write the report there
|
| 697 |
if ( defined $reportConfig->{targetDrive} && defined $reportConfig->{targetDrive}->{label} && $reportConfig->{targetDrive}->{label} ) {
|
695 |
if ( defined $reportConfig->{targetDrive} && defined $reportConfig->{targetDrive}->{label} && $reportConfig->{targetDrive}->{label} ) {
|
| 698 |
logMsg( "Saving report to disk with label $reportConfig->{targetDrive}->{label}" );
|
696 |
logMsg( "Saving report to disk with label $reportConfig->{targetDrive}->{label}" ) if $verboseLoggingLevel >= 2;
|
| 699 |
if ( $reportConfig->{targetDrive}->{mountPath} = mountDriveByLabel( $reportConfig->{targetDrive} ) ) {
|
697 |
if ( $reportConfig->{targetDrive}->{mountPath} = mountDriveByLabel( $reportConfig->{targetDrive} ) ) {
|
| 700 |
copyReportToDrive( $logFile, $reportConfig->{targetDrive}->{mountPath} );
|
698 |
copyReportToDrive( $logFile, $reportConfig->{targetDrive}->{mountPath} );
|
| 701 |
unmountDriveByLabel( $reportConfig->{targetDrive} );
|
699 |
unmountDriveByLabel( $reportConfig->{targetDrive} );
|
| 702 |
} else {
|
700 |
} else {
|
| 703 |
logMsg( "Warning: could not mount report target drive with label '$reportConfig->{targetDrive}->{label}'" );
|
701 |
logMsg( "Warning: could not mount report target drive with label '$reportConfig->{targetDrive}->{label}'" ) if $verboseLoggingLevel >= 1;
|
| 704 |
}
|
702 |
}
|
| 705 |
}
|
703 |
}
|
| 706 |
# if they have set an e-mail address, try to e-mail the report
|
704 |
# if they have set an e-mail address, try to e-mail the report
|
| 707 |
if ( defined $reportConfig->{email} && $reportConfig->{email} ne '' ) {
|
705 |
if ( defined $reportConfig->{email} && $reportConfig->{email} ne '' ) {
|
| 708 |
logMsg( "Sending report via e-mail to $reportConfig->{email}" );
|
706 |
logMsg( "Sending report via e-mail to $reportConfig->{email}" ) if $verboseLoggingLevel >= 1;
|
| 709 |
$reportConfig->{subject} //= 'Replication Report from ' . `hostname`;
|
707 |
$reportConfig->{subject} //= 'Replication Report from ' . `hostname`;
|
| 710 |
sendEmailReport( $reportConfig->{email}, $reportConfig->{subject}, $message, $logFile );
|
708 |
sendEmailReport( $reportConfig->{email}, $reportConfig->{subject}, $message, $logFile );
|
| 711 |
}
|
709 |
}
|
| 712 |
}
|
710 |
}
|
| 713 |
|
711 |
|
| Line 724... |
Line 722... |
| 724 |
my ( $logFile, $mountPoint ) = @_;
|
722 |
my ( $logFile, $mountPoint ) = @_;
|
| 725 |
return unless defined $logFile && -e $logFile;
|
723 |
return unless defined $logFile && -e $logFile;
|
| 726 |
return unless defined $mountPoint && -d $mountPoint;
|
724 |
return unless defined $mountPoint && -d $mountPoint;
|
| 727 |
|
725 |
|
| 728 |
my $targetFile = "$mountPoint/" . ( split( /\//, $logFile ) )[-1];
|
726 |
my $targetFile = "$mountPoint/" . ( split( /\//, $logFile ) )[-1];
|
| 729 |
logMsg( "Copying report log file $logFile to drive at $mountPoint" );
|
727 |
logMsg( "Copying report log file $logFile to drive at $mountPoint" ) if $verboseLoggingLevel >= 2;
|
| 730 |
use File::Copy;
|
728 |
use File::Copy;
|
| 731 |
unless ( copy( $logFile, $targetFile ) ) {
|
729 |
unless ( copy( $logFile, $targetFile ) ) {
|
| 732 |
logMsg( "Could not copy report log file to target drive: $!" );
|
730 |
logMsg( "Could not copy report log file to target drive: $!" ) if $verboseLoggingLevel >= 0;
|
| 733 |
}
|
731 |
}
|
| 734 |
}
|
732 |
}
|
| 735 |
|
733 |
|
| 736 |
## Send an email report with an attached log body.
|
734 |
## Send an email report with an attached log body.
|
| 737 |
##
|
735 |
##
|
| Line 750... |
Line 748... |
| 750 |
return unless defined $to && $to ne '';
|
748 |
return unless defined $to && $to ne '';
|
| 751 |
$subject //= 'Sneakernet Replication Report from ' . `hostname`;
|
749 |
$subject //= 'Sneakernet Replication Report from ' . `hostname`;
|
| 752 |
$message //= '';
|
750 |
$message //= '';
|
| 753 |
$logFile //= '';
|
751 |
$logFile //= '';
|
| 754 |
|
752 |
|
| 755 |
logMsg( "Sending email report to $to with subject '$subject'" );
|
753 |
logMsg( "Sending email report to $to with subject '$subject'" ) if $verboseLoggingLevel >= 2;
|
| 756 |
open my $mailfh, '|-', '/usr/sbin/sendmail -t' or do {
|
754 |
open my $mailfh, '|-', '/usr/sbin/sendmail -t' or do {
|
| 757 |
logMsg( "Could not open sendmail: $!" );
|
755 |
logMsg( "Could not open sendmail: $!" ) if $verboseLoggingLevel >= 0;
|
| 758 |
return;
|
756 |
return;
|
| 759 |
};
|
757 |
};
|
| 760 |
print $mailfh "To: $to\n";
|
758 |
print $mailfh "To: $to\n";
|
| 761 |
print $mailfh "Subject: $subject\n";
|
759 |
print $mailfh "Subject: $subject\n";
|
| 762 |
print $mailfh "MIME-Version: 1.0\n";
|
760 |
print $mailfh "MIME-Version: 1.0\n";
|
| Line 769... |
Line 767... |
| 769 |
while ( my $line = <$logfh> ) {
|
767 |
while ( my $line = <$logfh> ) {
|
| 770 |
print $mailfh $line;
|
768 |
print $mailfh $line;
|
| 771 |
}
|
769 |
}
|
| 772 |
close $logfh;
|
770 |
close $logfh;
|
| 773 |
} else {
|
771 |
} else {
|
| 774 |
logMsg( "Could not open log file [$logFile] for reading: $!" );
|
772 |
logMsg( "Could not open log file [$logFile] for reading: $!" ) if $verboseLoggingLevel >= 0;
|
| 775 |
};
|
773 |
};
|
| 776 |
|
774 |
|
| 777 |
close $mailfh;
|
775 |
close $mailfh;
|
| 778 |
}
|
776 |
}
|
| 779 |
|
777 |
|
| Line 802... |
Line 800... |
| 802 |
## - Logs the cleanup operation via logMsg.
|
800 |
## - Logs the cleanup operation via logMsg.
|
| 803 |
##
|
801 |
##
|
| 804 |
## Returns: 1 on completion. Note: individual unlink failures are currently reported via warn.
|
802 |
## Returns: 1 on completion. Note: individual unlink failures are currently reported via warn.
|
| 805 |
sub cleanDirectory {
|
803 |
sub cleanDirectory {
|
| 806 |
my $dirname = shift;
|
804 |
my $dirname = shift;
|
| 807 |
logMsg( "Cleaning up $dirname of all files" );
|
805 |
logMsg( "Cleaning up $dirname of all files" ) if $verboseLoggingLevel >= 2;
|
| 808 |
my $files = getDirectoryList( $dirname );
|
806 |
my $files = getDirectoryList( $dirname );
|
| 809 |
# clean up a directory
|
807 |
# clean up a directory
|
| 810 |
foreach my $file (@$files) {
|
808 |
foreach my $file (@$files) {
|
| 811 |
unlink $file or warn "Could not unlink $file: #!\n";
|
809 |
unlink $file or warn "Could not unlink $file: #!\n";
|
| 812 |
}
|
810 |
}
|
| Line 824... |
Line 822... |
| 824 |
## Behavior:
|
822 |
## Behavior:
|
| 825 |
## - Logs the fatal message via logMsg, runs the cleanup code if provided (errors in the cleanup
|
823 |
## - Logs the fatal message via logMsg, runs the cleanup code if provided (errors in the cleanup
|
| 826 |
## are logged), then terminates the process via die.
|
824 |
## are logged), then terminates the process via die.
|
| 827 |
sub fatalError {
|
825 |
sub fatalError {
|
| 828 |
my ( $message, $config, $cleanupRoutine ) = @_;
|
826 |
my ( $message, $config, $cleanupRoutine ) = @_;
|
| 829 |
logMsg( "FATAL ERROR: $message" );
|
827 |
logMsg( "FATAL ERROR: $message" ) if $verboseLoggingLevel >= 0;
|
| 830 |
if ( defined $cleanupRoutine && ref $cleanupRoutine eq 'CODE' ) {
|
828 |
if ( defined $cleanupRoutine && ref $cleanupRoutine eq 'CODE' ) {
|
| 831 |
logMsg( "Running cleanup routine before fatal error" );
|
829 |
logMsg( "Running cleanup routine before fatal error" ) if $verboseLoggingLevel >= 2;
|
| 832 |
eval {
|
830 |
eval {
|
| 833 |
$cleanupRoutine->( $config, $message );
|
831 |
$cleanupRoutine->( $config, $message );
|
| 834 |
1;
|
832 |
1;
|
| 835 |
} or do {
|
833 |
} or do {
|
| 836 |
logMsg( "Cleanup routine failed: $@" );
|
834 |
logMsg( "Cleanup routine failed: $@" ) if $verboseLoggingLevel >= 0;
|
| 837 |
};
|
835 |
};
|
| 838 |
}
|
836 |
}
|
| 839 |
die;
|
837 |
die;
|
| 840 |
}
|
838 |
}
|
| 841 |
|
839 |
|