Subversion Repositories zfs_utils

Rev

Rev 48 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 48 Rev 51
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