Subversion Repositories zfs_utils

Rev

Rev 35 | Rev 38 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 35 Rev 37
Line 50... Line 50...
50
#   makeReplicateCommands: create zfs send commands for replication based on snapshot lists
50
#   makeReplicateCommands: create zfs send commands for replication based on snapshot lists
51
 
51
 
52
 
52
 
53
# Exported functions and variables
53
# Exported functions and variables
54
 
54
 
55
our @EXPORT_OK = qw(loadConfig shredFile mountDriveByLabel mountGeli logMsg runCmd makeReplicateCommands sendReport $logFileName $displayLogsOnConsole);
55
our @EXPORT_OK = qw(loadConfig shredFile mountDriveByLabel mountGeli logMsg runCmd makeReplicateCommands sendReport $logFileName $displayLogsOnConsole $lastRunError);
56
 
56
 
57
 
57
 
58
our $VERSION = '0.2';
58
our $VERSION = '0.2';
59
our $logFileName = '/tmp/zfs_utils.log'; # this can be overridden by the caller, and turned off with empty string
59
our $logFileName = '/tmp/zfs_utils.log'; # this can be overridden by the caller, and turned off with empty string
60
our $displayLogsOnConsole = 1; # if non-zero, log messages are also printed to console
60
our $displayLogsOnConsole = 1; # if non-zero, log messages are also printed to console
61
our $merge_stderr = 0; # if set to 1, stderr is captured in runCmd
61
our $merge_stderr = 0; # if set to 1, stderr is captured in runCmd
-
 
62
our $lastRunError = 0; # tracks the last error code from runCmd
62
 
63
 
63
# Execute a command and return its output.
64
# Execute a command and return its output.
64
# If called in scalar context, returns the full output as a single string.
65
# If called in scalar context, returns the full output as a single string.
65
# If called in list context, returns the output split into lines.
66
# If called in list context, returns the output split into lines.
66
# If $merge_stderr is true (default), stderr is merged into stdout (only for scalar commands).
67
# If $merge_stderr is true (default), stderr is merged into stdout (only for scalar commands).
Line 71... Line 72...
71
   my $output = '';
72
   my $output = '';
72
 
73
 
73
   logMsg( "Running command [$cmd]" );
74
   logMsg( "Running command [$cmd]" );
74
   $cmd .= ' 2>&1' if $merge_stderr;
75
   $cmd .= ' 2>&1' if $merge_stderr;
75
   $output = `$cmd`;
76
   $output = `$cmd`;
-
 
77
   $lastRunError = $?;
-
 
78
   if ( $lastRunError ) {
76
   if ($? == -1) {
79
      if ($? == -1) {
77
      logMsg( "failed to execute: $!");
80
         logMsg( "failed to execute: $!");
78
      return ''
81
         return '';
79
   }
-
 
80
   elsif ($? & 127) { # fatal error, exit program
82
      } elsif ($? & 127) { # fatal error, exit program
81
      logMsg( sprintf( "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without' ) );
83
         logMsg( sprintf( "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without' ) );
82
      die;
84
         die;
83
   }
-
 
84
   else {
85
      } elsif ($? >> 8) { # it had some return code other than 0
85
      logMsg( sprintf( "child exited with value %d\n", $? >> 8 ) );
86
         logMsg( sprintf( "child exited with value %d\n", $? >> 8 ) );
-
 
87
      }
86
   }
88
   }
87
   $output //= '';
89
   $output //= '';
88
 
90
 
89
   if (wantarray) {
91
   if (wantarray) {
90
      return $output eq '' ? () : split(/\n/, $output);
92
      return $output eq '' ? () : split(/\n/, $output);
Line 139... Line 141...
139
   # default to /mnt/label if not provided
141
   # default to /mnt/label if not provided
140
   $driveInfo->{mountPath} //= "/mnt/$driveInfo->{label}"; # this is where we'll mount it if we find it
142
   $driveInfo->{mountPath} //= "/mnt/$driveInfo->{label}"; # this is where we'll mount it if we find it
141
   $driveInfo->{filesystem} //= 'ufs'; # default to mounting ufs
143
   $driveInfo->{filesystem} //= 'ufs'; # default to mounting ufs
142
   # The location for the label depends on filesystem. Only providing access to ufs and msdos here for safety.
144
   # The location for the label depends on filesystem. Only providing access to ufs and msdos here for safety.
143
   # gpt labeled drives for ufs are in /dev/gpt/, for msdosfs in /dev/msdosfs/
145
   # gpt labeled drives for ufs are in /dev/gpt/, for msdosfs in /dev/msdosfs/
144
   $driveInfo->{mountPath} = $driveInfo->{filesystem} eq 'msdos' ? "/dev/msdosfs/$driveInfo->{label}" : "/dev/gpt/$driveInfo->{label}"; 
146
   $driveInfo->{label} = $driveInfo->{filesystem} eq 'msdos' ? "/dev/msdosfs/$driveInfo->{label}" : "/dev/gpt/$driveInfo->{label}"; 
145
   # drive already mounted, just return the path
147
   # drive already mounted, just return the path
146
   return $driveInfo->{mountPath} if ( runCmd( "mount | grep '$driveInfo->{mountPath}'" ) );
148
   my $output = runCmd( "mount | grep '$driveInfo->{mountPath}'" );
-
 
149
   return $driveInfo->{mountPath} if ( $lastRunError == 0 ); # grep found it for us
147
   # default to 10 minutes (600 seconds) if not provided
150
   # default to 10 minutes (600 seconds) if not provided
148
   $driveInfo->{timeout} //= 600;
151
   $driveInfo->{timeout} //= 600;
149
   # default to checking every minute if not provided
152
   # default to checking every minute if not provided
150
   $driveInfo->{check_interval} //= 15;
153
   $driveInfo->{check_interval} //= 15;
151
   # wait up to $timeout seconds for device to appear, checking every 10 seconds
154
   # wait up to $timeout seconds for device to appear, checking every 10 seconds
152
   while ( $driveInfo->{timeout} > 0 ) {
155
   while ( $driveInfo->{timeout} > 0 ) {
153
      if ( -e "$driveInfo->{label}" ) {
156
      if ( -e "$driveInfo->{label}" ) {
154
         last;
157
         last;
155
      } else {
158
      } else {
-
 
159
         print "Waiting for drive labeled $driveInfo->{label}\n";
156
         sleep $driveInfo->{check_interval};
160
         sleep $driveInfo->{check_interval};
157
         $driveInfo->{timeout} -= $driveInfo->{check_interval};
161
         $driveInfo->{timeout} -= $driveInfo->{check_interval};
158
         print "Waiting for drive labeled $driveInfo->{label}\n";
-
 
159
      }
162
      }
160
    }
163
    }
161
    # if we found it, mount and return mount path
164
    # if we found it, mount and return mount path
162
    if ( -e "$driveInfo->{label}" ) {
165
    if ( -e "$driveInfo->{label}" ) {
163
       # ensure mount point
166
       # ensure mount point
164
       unless ( -d $driveInfo->{mountPath} || make_path($driveInfo->{mountPath}) ) {
167
       unless ( -d $driveInfo->{mountPath} || make_path($driveInfo->{mountPath}) ) {
165
         logMsg("Failed to create $driveInfo->{mountPath}: $!");
168
         logMsg("Failed to create $driveInfo->{mountPath}: $!");
166
         return '';
169
         return '';
167
       }
170
       }
168
       # mount device (let mount detect filesystem)
171
       # mount device
169
       unless ( runCmd( "mount -t $driveInfo->{filesystem} $driveInfo->{label} $driveInfo->{mountPath}" ) ) {
172
       runCmd( "mount -t $driveInfo->{filesystem} $driveInfo->{label} $driveInfo->{mountPath}" );
-
 
173
       if ( $lastRunError ) {
170
         logMsg("Failed to mount $driveInfo->{label} on $driveInfo->{mountPath}: $!");
174
         logMsg("Failed to mount $driveInfo->{label} on $driveInfo->{mountPath}: $!");
171
         return '';
175
         return '';
172
       }
176
       }
173
       return $driveInfo->{mountPath};
177
       return $driveInfo->{mountPath};
174
    } else {
178
    } else {
Line 495... Line 499...
495
      my %from_names = map { $_ => 1 } grep { defined $_ } @from_values;
499
      my %from_names = map { $_ => 1 } grep { defined $_ } @from_values;
496
      my $single_from_name = (keys %from_names == 1) ? (keys %from_names)[0] : undef;
500
      my $single_from_name = (keys %from_names == 1) ? (keys %from_names)[0] : undef;
497
 
501
 
498
      if ($any_from_missing) {
502
      if ($any_from_missing) {
499
         # full recursive send from root
503
         # full recursive send from root
500
         $commands{'root_fs'} = sprintf('zfs send -R %s@%s', $root_fs, $single_to_name);
504
         $commands{$root_fs} = sprintf('zfs send -R %s@%s', $root_fs, $single_to_name);
501
      }
505
      }
502
      elsif ($single_from_name) {
506
      elsif ($single_from_name) {
503
         # incremental recursive send, but don't do it if they are the same
507
         # incremental recursive send, but don't do it if they are the same
504
         $commands{$root_fs} = sprintf('zfs send -R -I %s@%s %s@%s',
508
         $commands{$root_fs} = sprintf('zfs send -R -I %s@%s %s@%s',
505
                           $root_fs, $single_from_name, $root_fs, $single_to_name)
509
                           $root_fs, $single_from_name, $root_fs, $single_to_name)
Line 551... Line 555...
551
# $subject is the email subject
555
# $subject is the email subject
552
# $logFile is the path to the log file to send/copy
556
# $logFile is the path to the log file to send/copy
553
sub sendReport {
557
sub sendReport {
554
   my ( $reportConfig, $subject, $logFile ) = @_;
558
   my ( $reportConfig, $subject, $logFile ) = @_;
555
   return unless defined $reportConfig;
559
   return unless defined $reportConfig;
-
 
560
   logMsg( "Beginning sendReport" );
-
 
561
   # if they have set an e-mail address, try to e-mail the report
556
   if ( defined $reportConfig->{email} && $reportConfig->{email} ne '' ) {
562
   if ( defined $reportConfig->{email} && $reportConfig->{email} ne '' ) {
-
 
563
      logMsg( "Sending report via e-mail to $reportConfig->{email}");
557
      sendEmailReport( $reportConfig->{email}, $subject, $logFile );
564
      sendEmailReport( $reportConfig->{email}, $subject, $logFile );
558
   }
565
   }
-
 
566
   # if targetDrive defined and there is a valid label for it, try to mount it and write the report there
-
 
567
   if ( defined $reportConfig->{targetDrive} && defined $reportConfig->{targetDrive}->{label} && $reportConfig->{targetDrive}->{label} ) {
559
   if ( defined $reportConfig->{targetDrive} ) {
568
      logMsg( "Saving report to disk with label $reportConfig->{targetDrive}->{label}" );
560
      my $mountPoint = mountDriveByLabel( $reportConfig->{targetDrive}->{label}, $reportConfig->{targetDrive}->{mount_point}, 300 );
569
      my $mountPoint = mountDriveByLabel( $reportConfig->{targetDrive}->{label}, $reportConfig->{targetDrive}->{mount_point}, 300 );
561
      if ( defined $mountPoint ) {
570
      if ( defined $mountPoint && $mountPoint ) {
562
         copyReportToDrive( $logFile, $mountPoint );
571
         copyReportToDrive( $logFile, $mountPoint );
563
         `umount $mountPoint`;
572
         `umount $mountPoint`;
564
         rmdir $mountPoint;
573
         rmdir $mountPoint;
565
      } else {
574
      } else {
566
         logMsg( "Warning: could not mount report target drive with label '$reportConfig->{targetDrive}->{label}'" );
575
         logMsg( "Warning: could not mount report target drive with label '$reportConfig->{targetDrive}->{label}'" );
Line 590... Line 599...
590
# $logFile is the path to the log file to send.
599
# $logFile is the path to the log file to send.
591
# Does nothing if any parameter is invalid.
600
# Does nothing if any parameter is invalid.
592
sub sendEmailReport {
601
sub sendEmailReport {
593
   my ( $to, $subject, $logFile ) = @_;
602
   my ( $to, $subject, $logFile ) = @_;
594
   return unless defined $to && $to ne '';
603
   return unless defined $to && $to ne '';
595
   return unless defined $subject && $subject ne '';
604
   $subject //= 'Sneakernet Replication Report from ' . `hostname`;
596
   return unless defined $logFile && -e $logFile;
605
   $logFile //= '';
597
 
606
 
598
   logMsg( "Sending email report to $to with subject '$subject'" );
607
   logMsg( "Sending email report to $to with subject '$subject'" );
599
   open my $mailfh, '|-', '/usr/sbin/sendmail -t' or do {
608
   open my $mailfh, '|-', '/usr/sbin/sendmail -t' or do {
600
      logMsg( "Could not open sendmail: $!" );
609
      logMsg( "Could not open sendmail: $!" );
601
      return;
610
      return;
Line 603... Line 612...
603
   print $mailfh "To: $to\n";
612
   print $mailfh "To: $to\n";
604
   print $mailfh "Subject: $subject\n";
613
   print $mailfh "Subject: $subject\n";
605
   print $mailfh "MIME-Version: 1.0\n";
614
   print $mailfh "MIME-Version: 1.0\n";
606
   print $mailfh "Content-Type: text/plain; charset=\"utf-8\"\n";
615
   print $mailfh "Content-Type: text/plain; charset=\"utf-8\"\n";
607
   print $mailfh "\n"; # end of headers
616
   print $mailfh "\n"; # end of headers
-
 
617
   
-
 
618
   print $mailfh "Following is the report for replication\n\n";
608
 
619
 
609
   open my $logfh, '<', $logFile or do {
620
   if ( -e $logFile && open my $logfh, '<', $logFile ) {
610
      logMsg( "Could not open log file $logFile for reading: $!" );
621
      while ( my $line = <$logfh> ) {
-
 
622
         print $mailfh $line;
-
 
623
      }
611
      close $mailfh;
624
      close $logfh;
612
      return;
625
   } else {
-
 
626
      logMsg( "Could not open log file [$logFile] for reading: $!" );
613
   };
627
   };
614
   while ( my $line = <$logfh> ) {
-
 
615
      print $mailfh $line;
-
 
616
   }
628
 
617
   close $logfh;
-
 
618
   close $mailfh;
629
   close $mailfh;
619
}  
630
}  
620
 
631
 
621
1;
632
1;