| 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 $logFileName $displayLogsOnConsole);
|
55 |
our @EXPORT_OK = qw(loadConfig shredFile mountDriveByLabel mountGeli logMsg runCmd makeReplicateCommands sendReport $logFileName $displayLogsOnConsole);
|
| 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
|
| Line 113... |
Line 113... |
| 113 |
close $logfh;
|
113 |
close $logfh;
|
| 114 |
}
|
114 |
}
|
| 115 |
print "$timestamp\t$msg\n" if ($displayLogsOnConsole);
|
115 |
print "$timestamp\t$msg\n" if ($displayLogsOnConsole);
|
| 116 |
}
|
116 |
}
|
| 117 |
|
117 |
|
| 118 |
# find a drive by it's label by scanning /dev/gpt/ for $timeout seconds.
|
118 |
# find a drive by it's label by scanning /dev/gpt/
|
| - |
|
119 |
# driveInfo is a hashref with the following keys:
|
| - |
|
120 |
# label - the GPT label of the drive (required)
|
| - |
|
121 |
# filesystem - the filesystem type (default: ufs)
|
| - |
|
122 |
# mountPath - where to mount the drive (default: /mnt/label)
|
| - |
|
123 |
# timeout - how long to wait for the drive (default: 600 seconds)
|
| - |
|
124 |
# check_interval - how often to check for the drive (default: 15 seconds)
|
| 119 |
# If the drive is found, mount it on mountPath and return the mountPath.
|
125 |
# If the drive is found, mount it on mountPath and return the mountPath.
|
| 120 |
# If not found, return empty string.
|
126 |
# If not found, return empty string.
|
| 121 |
sub mountDriveByLabel {
|
127 |
sub mountDriveByLabel {
|
| 122 |
my ($label, $mountPath, $timeout, $checkEvery, $filesystem ) = @_;
|
128 |
my ( $driveInfo ) = @_;
|
| 123 |
unless ($label) {
|
129 |
unless ($driveInfo->{label}) {
|
| 124 |
logMsg("mountDriveByLabel: No label provided");
|
130 |
logMsg("mountDriveByLabel: No drive label provided");
|
| 125 |
return '';
|
131 |
return '';
|
| 126 |
}
|
132 |
}
|
| 127 |
unless ( $label =~ /^[a-zA-Z0-9_\-]+$/ ) {
|
133 |
unless ( $driveInfo->{label} =~ /^[a-zA-Z0-9_\-]+$/ ) {
|
| 128 |
logMsg("mountDriveByLabel: Invalid label '$label'");
|
134 |
logMsg("mountDriveByLabel: Invalid label '$driveInfo->{label}'");
|
| 129 |
return '';
|
135 |
return '';
|
| 130 |
}
|
136 |
}
|
| 131 |
|
137 |
|
| 132 |
logMsg("mountDriveByLabel: Looking for drive with label '$label'");
|
138 |
logMsg("mountDriveByLabel: Looking for drive with label '$driveInfo->{label}'");
|
| 133 |
# default to /mnt/label if not provided
|
139 |
# default to /mnt/label if not provided
|
| 134 |
$mountPath //= "/mnt/$label"; # this is where we'll mount it if we find it
|
140 |
$driveInfo->{mountPath} //= "/mnt/$driveInfo->{label}"; # this is where we'll mount it if we find it
|
| 135 |
$filesystem //= 'ufs'; # default to mounting ufs
|
141 |
$driveInfo->{filesystem} //= 'ufs'; # default to mounting ufs
|
| 136 |
# The location for the label depends on filesystem. Only providing access to ufs and msdos here for safety.
|
142 |
# The location for the label depends on filesystem. Only providing access to ufs and msdos here for safety.
|
| 137 |
# gpt labeled drives for ufs are in /dev/gpt/, for msdosfs in /dev/msdosfs/
|
143 |
# gpt labeled drives for ufs are in /dev/gpt/, for msdosfs in /dev/msdosfs/
|
| 138 |
$label = $filesystem eq 'msdos' ? "/dev/msdosfs/$label" : "/dev/gpt/$label";
|
144 |
$driveInfo->{mountPath} = $driveInfo->{filesystem} eq 'msdos' ? "/dev/msdosfs/$driveInfo->{label}" : "/dev/gpt/$driveInfo->{label}";
|
| 139 |
# drive already mounted, just return the path
|
145 |
# drive already mounted, just return the path
|
| 140 |
return $mountPath if ( runCmd( "mount | grep '$mountPath'" ) );
|
146 |
return $driveInfo->{mountPath} if ( runCmd( "mount | grep '$driveInfo->{mountPath}'" ) );
|
| 141 |
# default to 10 minutes (600 seconds) if not provided
|
147 |
# default to 10 minutes (600 seconds) if not provided
|
| 142 |
$timeout //= 600;
|
148 |
$driveInfo->{timeout} //= 600;
|
| 143 |
# default to checking every minute if not provided
|
149 |
# default to checking every minute if not provided
|
| 144 |
$checkEvery //= 15;
|
150 |
$driveInfo->{check_interval} //= 15;
|
| 145 |
# wait up to $timeout seconds for device to appear, checking every 10 seconds
|
151 |
# wait up to $timeout seconds for device to appear, checking every 10 seconds
|
| 146 |
while ( $timeout > 0 ) {
|
152 |
while ( $driveInfo->{timeout} > 0 ) {
|
| 147 |
if ( -e "$label" ) {
|
153 |
if ( -e "$driveInfo->{label}" ) {
|
| 148 |
last;
|
154 |
last;
|
| 149 |
} else {
|
155 |
} else {
|
| 150 |
sleep $checkEvery;
|
156 |
sleep $driveInfo->{check_interval};
|
| 151 |
$timeout -= $checkEvery;
|
157 |
$driveInfo->{timeout} -= $driveInfo->{check_interval};
|
| 152 |
print "Waiting for drive labeled $label\n";
|
158 |
print "Waiting for drive labeled $driveInfo->{label}\n";
|
| 153 |
}
|
159 |
}
|
| 154 |
}
|
160 |
}
|
| 155 |
# if we found it, mount and return mount path
|
161 |
# if we found it, mount and return mount path
|
| 156 |
if ( -e "$label" ) {
|
162 |
if ( -e "$driveInfo->{label}" ) {
|
| 157 |
# ensure mount point
|
163 |
# ensure mount point
|
| 158 |
unless ( -d $mountPath || make_path($mountPath) ) {
|
164 |
unless ( -d $driveInfo->{mountPath} || make_path($driveInfo->{mountPath}) ) {
|
| 159 |
logMsg("Failed to create $mountPath: $!");
|
165 |
logMsg("Failed to create $driveInfo->{mountPath}: $!");
|
| 160 |
return '';
|
166 |
return '';
|
| 161 |
}
|
167 |
}
|
| 162 |
# mount device (let mount detect filesystem)
|
168 |
# mount device (let mount detect filesystem)
|
| 163 |
unless ( runCmd( "mount -t $filesystem $label $mountPath" ) ) {
|
169 |
unless ( runCmd( "mount -t $driveInfo->{filesystem} $driveInfo->{label} $driveInfo->{mountPath}" ) ) {
|
| 164 |
logMsg("Failed to mount $label on $mountPath: $!");
|
170 |
logMsg("Failed to mount $driveInfo->{label} on $driveInfo->{mountPath}: $!");
|
| 165 |
return '';
|
171 |
return '';
|
| 166 |
}
|
172 |
}
|
| 167 |
return $mountPath;
|
173 |
return $driveInfo->{mountPath};
|
| 168 |
} else {
|
174 |
} else {
|
| 169 |
return '';
|
175 |
return '';
|
| 170 |
}
|
176 |
}
|
| 171 |
}
|
177 |
}
|
| 172 |
|
178 |
|
| Line 534... |
Line 540... |
| 534 |
|
540 |
|
| 535 |
# return arrayref of commands (caller can iterate or join with pipes)
|
541 |
# return arrayref of commands (caller can iterate or join with pipes)
|
| 536 |
return \%commands;
|
542 |
return \%commands;
|
| 537 |
}
|
543 |
}
|
| 538 |
|
544 |
|
| - |
|
545 |
# Send report via email and/or copy to target drive.
|
| - |
|
546 |
# $reportConfig is a hashref with optional keys:
|
| - |
|
547 |
# email - email address to send report to
|
| - |
|
548 |
# targetDrive - hashref with keys:
|
| - |
|
549 |
# label - GPT or msdosfs label of the target drive
|
| - |
|
550 |
# mount_point - optional mount point to use (if not provided, /mnt/label is used)
|
| - |
|
551 |
# $subject is the email subject
|
| - |
|
552 |
# $logFile is the path to the log file to send/copy
|
| - |
|
553 |
sub sendReport {
|
| - |
|
554 |
my ( $reportConfig, $subject, $logFile ) = @_;
|
| - |
|
555 |
return unless defined $reportConfig;
|
| - |
|
556 |
if ( defined $reportConfig->{email} && $reportConfig->{email} ne '' ) {
|
| - |
|
557 |
sendEmailReport( $reportConfig->{email}, $subject, $logFile );
|
| - |
|
558 |
}
|
| - |
|
559 |
if ( defined $reportConfig->{targetDrive} ) {
|
| - |
|
560 |
my $mountPoint = mountDriveByLabel( $reportConfig->{targetDrive}->{label}, $reportConfig->{targetDrive}->{mount_point}, 300 );
|
| - |
|
561 |
if ( defined $mountPoint ) {
|
| - |
|
562 |
copyReportToDrive( $logFile, $mountPoint );
|
| - |
|
563 |
`umount $mountPoint`;
|
| - |
|
564 |
rmdir $mountPoint;
|
| - |
|
565 |
} else {
|
| - |
|
566 |
logMsg( "Warning: could not mount report target drive with label '$reportConfig->{targetDrive}->{label}'" );
|
| - |
|
567 |
}
|
| - |
|
568 |
}
|
| - |
|
569 |
}
|
| - |
|
570 |
|
| - |
|
571 |
# Copy the report log file to the specified mount point.
|
| - |
|
572 |
# $logFile is the path to the log file to copy.
|
| - |
|
573 |
# $mountPoint is the mount point of the target drive.
|
| - |
|
574 |
# Does nothing if log file or mount point are invalid.
|
| - |
|
575 |
sub copyReportToDrive {
|
| - |
|
576 |
my ( $logFile, $mountPoint ) = @_;
|
| - |
|
577 |
return unless defined $logFile && -e $logFile;
|
| - |
|
578 |
return unless defined $mountPoint && -d $mountPoint;
|
| - |
|
579 |
|
| - |
|
580 |
my $targetFile = "$mountPoint/" . ( split( /\//, $logFile ) )[-1];
|
| - |
|
581 |
logMsg( "Copying report log file $logFile to drive at $mountPoint" );
|
| - |
|
582 |
unless ( copy( $logFile, $targetFile ) ) {
|
| - |
|
583 |
logMsg( "Could not copy report log file to target drive: $!" );
|
| - |
|
584 |
}
|
| - |
|
585 |
}
|
| - |
|
586 |
|
| - |
|
587 |
# Send an email report with the contents of the log file.
|
| - |
|
588 |
# $to is the recipient email address.
|
| - |
|
589 |
# $subject is the email subject.
|
| - |
|
590 |
# $logFile is the path to the log file to send.
|
| - |
|
591 |
# Does nothing if any parameter is invalid.
|
| - |
|
592 |
sub sendEmailReport {
|
| - |
|
593 |
my ( $to, $subject, $logFile ) = @_;
|
| - |
|
594 |
return unless defined $to && $to ne '';
|
| - |
|
595 |
return unless defined $subject && $subject ne '';
|
| - |
|
596 |
return unless defined $logFile && -e $logFile;
|
| - |
|
597 |
|
| - |
|
598 |
logMsg( "Sending email report to $to with subject '$subject'" );
|
| - |
|
599 |
open my $mailfh, '|-', '/usr/sbin/sendmail -t' or do {
|
| - |
|
600 |
logMsg( "Could not open sendmail: $!" );
|
| - |
|
601 |
return;
|
| - |
|
602 |
};
|
| - |
|
603 |
print $mailfh "To: $to\n";
|
| - |
|
604 |
print $mailfh "Subject: $subject\n";
|
| - |
|
605 |
print $mailfh "MIME-Version: 1.0\n";
|
| - |
|
606 |
print $mailfh "Content-Type: text/plain; charset=\"utf-8\"\n";
|
| - |
|
607 |
print $mailfh "\n"; # end of headers
|
| - |
|
608 |
|
| - |
|
609 |
open my $logfh, '<', $logFile or do {
|
| - |
|
610 |
logMsg( "Could not open log file $logFile for reading: $!" );
|
| - |
|
611 |
close $mailfh;
|
| - |
|
612 |
return;
|
| - |
|
613 |
};
|
| - |
|
614 |
while ( my $line = <$logfh> ) {
|
| - |
|
615 |
print $mailfh $line;
|
| - |
|
616 |
}
|
| - |
|
617 |
close $logfh;
|
| - |
|
618 |
close $mailfh;
|
| - |
|
619 |
}
|
| 539 |
|
620 |
|
| 540 |
1;
|
621 |
1;
|