Subversion Repositories zfs_utils

Rev

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

Rev 33 Rev 34
Line -... Line 1...
-
 
1
# Simplified BSD License (FreeBSD License)
-
 
2
#
-
 
3
# Copyright (c) 2025, Daily Data Inc.
-
 
4
# All rights reserved.
-
 
5
#
-
 
6
# Redistribution and use in source and binary forms, with or without
-
 
7
# modification, are permitted provided that the following conditions are met:
-
 
8
#
-
 
9
# 1. Redistributions of source code must retain the above copyright notice, this
-
 
10
#    list of conditions and the following disclaimer.
-
 
11
#
-
 
12
# 2. Redistributions in binary form must reproduce the above copyright notice,
-
 
13
#    this list of conditions and the following disclaimer in the documentation
-
 
14
#    and/or other materials provided with the distribution.
-
 
15
#
-
 
16
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-
 
17
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-
 
18
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-
 
19
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-
 
20
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-
 
21
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-
 
22
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-
 
23
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-
 
24
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-
 
25
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 
26
 
1
package ZFS_Utils;
27
package ZFS_Utils;
2
 
28
 
3
use strict;
29
use strict;
4
use warnings;
30
use warnings;
5
use Exporter 'import';
31
use Exporter 'import';
6
use Data::Dumper;
32
use Data::Dumper;
7
use POSIX qw(strftime);
33
use POSIX qw(strftime);
8
use File::Path qw(make_path);
34
use File::Path qw(make_path);
9
 
35
 
-
 
36
# library of ZFS related utility functions
-
 
37
# Copyright 2024 Daily Data Inc. <rodo@dailydata.net>
-
 
38
 
-
 
39
# currently used for sneakernet scripts, but plans to expand to other ZFS related tasks
-
 
40
# functions include:
-
 
41
#   runCmd: execute a command and return its output
-
 
42
#   shredFile: securely delete a file using gshred
-
 
43
#   logMsg: log messages to a log file and optionally to console
-
 
44
#   mountDriveByLabel: find and mount a drive by its GPT label
-
 
45
#   loadConfig: load a YAML configuration file into a hashref
-
 
46
#   mountGeli: decrypt and mount a GELI encrypted ZFS pool
-
 
47
#   makeGeliKey: create a GELI key by XOR'ing a remote binary keyfile and a local hex key
-
 
48
#   decryptAndMountGeli: decrypt GELI disks and mount the ZFS pool
-
 
49
#   findGeliDisks: find available disks for GELI/ZFS use
-
 
50
#   makeReplicateCommands: create zfs send commands for replication based on snapshot lists
-
 
51
 
-
 
52
 
-
 
53
# Exported functions and variables
-
 
54
 
10
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 $logFileName $displayLogsOnConsole);
11
 
56
 
12
 
57
 
13
our $VERSION = '0.1';
58
our $VERSION = '0.2';
14
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
15
our $displayLogsOnConsole = 1;
60
our $displayLogsOnConsole = 1; # if non-zero, log messages are also printed to console
16
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
17
 
62
 
18
# Execute a command and return its output.
63
# Execute a command and return its output.
19
# If called in scalar context, returns the full output as a single string.
64
# If called in scalar context, returns the full output as a single string.
20
# If called in list context, returns the output split into lines.
65
# If called in list context, returns the output split into lines.
21
# If $merge_stderr is true (default), stderr is merged into stdout (only for scalar commands).
66
# If $merge_stderr is true (default), stderr is merged into stdout (only for scalar commands).
22
# returns empty string or empty list on failure and logs failure message.
67
# returns undef on failure and logs failure message.
23
sub runCmd {
68
sub runCmd {
24
   my $cmd = join( ' ', @_ );
69
   my $cmd = join( ' ', @_ );
25
   $merge_stderr = 1 unless defined $merge_stderr;
70
   $merge_stderr = 1 unless defined $merge_stderr;
26
   my $output = '';
71
   my $output = '';
27
 
72
 
28
#   if (ref $cmd eq 'ARRAY') {
-
 
29
#      # Execute without a shell (safer). Note: stderr is not merged in this path.
-
 
30
#      logMsg( 'Running command [' . join( ' ', @$cmd ) . ']');
-
 
31
#      open my $fh, '-|', @{$cmd} or do {
-
 
32
#         logMsg("runCmd: failed to exec '@{$cmd}': $!");
-
 
33
#         return wantarray ? () : '';
-
 
34
#      };
-
 
35
#      local $/ = undef;
-
 
36
#      $output = <$fh>;
-
 
37
#      close $fh;
-
 
38
#   } else {
-
 
39
      # Scalar command runs via the shell; optionally merge stderr into stdout.
-
 
40
      logMsg( "Running command [$cmd]" );
73
   logMsg( "Running command [$cmd]" );
41
      $cmd .= ' 2>&1' if $merge_stderr;
74
   $cmd .= ' 2>&1' if $merge_stderr;
42
      $output = `$cmd`;
75
   $output = `$cmd`;
43
if ($? == -1) {
76
   if ($? == -1) {
44
    logMsg( "failed to execute: $!");
77
      logMsg( "failed to execute: $!");
45
    return ''
78
      return ''
46
}
79
   }
47
elsif ($? & 127) { # fatal error, exit program
80
   elsif ($? & 127) { # fatal error, exit program
48
    logMsg( sprintf( "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without' ) );
81
      logMsg( sprintf( "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without' ) );
49
    die;
82
      die;
50
}
83
   }
51
else {
84
   else {
52
    logMsg( sprintf( "child exited with value %d\n", $? >> 8 ) );
85
      logMsg( sprintf( "child exited with value %d\n", $? >> 8 ) );
53
}
-
 
54
 
-
 
55
#   }
86
   }
56
   $output //= '';
87
   $output //= '';
57
 
88
 
58
   if (wantarray) {
89
   if (wantarray) {
59
      return $output eq '' ? () : split(/\n/, $output);
90
      return $output eq '' ? () : split(/\n/, $output);
60
   } else {
91
   } else {
Line 86... Line 117...
86
 
117
 
87
# 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/ for $timeout seconds.
88
# If the drive is found, mount it on mountPath and return the mountPath.
119
# If the drive is found, mount it on mountPath and return the mountPath.
89
# If not found, return empty string.
120
# If not found, return empty string.
90
sub mountDriveByLabel {
121
sub mountDriveByLabel {
91
   my ($label, $mountPath, $timeout, $checkEvery, $filesystem, $devPath ) = @_;
122
   my ($label, $mountPath, $timeout, $checkEvery, $filesystem ) = @_;
92
   unless ($label) {
123
   unless ($label) {
93
      logMsg("mountDriveByLabel: No label provided");
124
      logMsg("mountDriveByLabel: No label provided");
94
      return '';
125
      return '';
95
   }
126
   }
96
   unless ( $label =~ /^[a-zA-Z0-9_\-]+$/ ) {
127
   unless ( $label =~ /^[a-zA-Z0-9_\-]+$/ ) {
Line 99... Line 130...
99
   }
130
   }
100
 
131
 
101
   logMsg("mountDriveByLabel: Looking for drive with label '$label'");
132
   logMsg("mountDriveByLabel: Looking for drive with label '$label'");
102
   # default to /mnt/label if not provided
133
   # default to /mnt/label if not provided
103
   $mountPath //= "/mnt/$label"; # this is where we'll mount it if we find it
134
   $mountPath //= "/mnt/$label"; # this is where we'll mount it if we find it
104
   $devPath //= "/dev/gpt/";
-
 
105
   $label = "$devPath$label"; #  this is where FreeBSD puts gpt labeled drives
-
 
106
   $filesystem //= 'ufs'; # default to mounting ufs
135
   $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.
-
 
137
   # 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"; 
107
   # drive already mounted, just return the path
139
   # drive already mounted, just return the path
108
   return $mountPath if ( runCmd( "mount | grep '$mountPath'" ) );
140
   return $mountPath if ( runCmd( "mount | grep '$mountPath'" ) );
109
   # default to 10 minutes (600 seconds) if not provided
141
   # default to 10 minutes (600 seconds) if not provided
110
   $timeout //= 600;
142
   $timeout //= 600;
111
   # default to checking every minute if not provided
143
   # default to checking every minute if not provided