Subversion Repositories zfs_utils

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
34 rodolico 1
# ZFS_Utils Module Documentation
2
 
3
Perl module providing utilities for ZFS management, GELI encryption, and sneakernet-based replication on FreeBSD systems.
4
 
5
**Version:** 0.2  
6
**Copyright:** 2024–2025 Daily Data Inc.  
7
**License:** Simplified BSD License (FreeBSD License)
8
 
9
---
10
 
11
## Exported Functions & Variables
12
 
13
Functions and variables listed in `@EXPORT_OK` and available via `use ZFS_Utils qw(...)`
14
 
15
### Functions
16
 
17
#### `runCmd(@args)`
18
 
19
Execute a shell command and return output.
20
 
21
**Parameters:**
22
 
23
- `@args` - Command and arguments (joined with spaces)
24
 
25
**Returns:**
26
 
27
- In scalar context: full command output as a string
28
- In list context: output split into lines  
29
- Empty string on failure
30
 
31
**Behavior:**
32
 
33
- Logs the command execution via `logMsg()`
34
- If `$merge_stderr` is true, stderr is merged with stdout
35
- Returns exit status and logs on command failure
36
- Calls `die` on fatal signal
37
 
38
**Example:**
39
 
40
```perl
41
my @files = runCmd('ls', '/tmp');
42
my $output = runCmd('grep pattern /var/log/file');
43
```
44
 
45
---
46
 
47
#### `shredFile($filename)`
48
 
49
Securely overwrite and delete a file using `gshred`.
50
 
51
**Parameters:**
52
 
53
- `$filename` - Path to file to securely delete
54
 
55
**Returns:** nothing
56
 
57
**Notes:**
58
 
59
- Uses `/usr/local/bin/gshred -u -f -s 32` (3-pass overwrite)
60
- Silently does nothing if file does not exist
61
- **Ineffective on COW filesystems** (e.g., ZFS)
62
- Best used with UFS or ramdisk storage
63
 
64
---
65
 
66
#### `logMsg($message, $filename?, $timeStampFormat?)`
67
 
68
Log a message to file and/or console with timestamp.
69
 
70
**Parameters:**
71
 
72
- `$message` - Message to log (required)
73
- `$filename` - Path to log file (optional; defaults to `$logFileName`)
74
- `$timeStampFormat` - strftime format string (optional; defaults to `'%Y-%m-%d %H:%M:%S'`)
75
 
76
**Returns:** nothing
77
 
78
**Behavior:**
79
 
80
- Appends timestamped message to log file if `$filename` is set and non-empty
81
- Prints to console if `$displayLogsOnConsole` is true
82
- Format: `TIMESTAMP\tMESSAGE`
83
 
84
**Example:**
85
 
86
```perl
87
use ZFS_Utils qw(logMsg);
88
logMsg("Operation started");
89
logMsg("Debug info", '/var/log/custom.log', '%H:%M:%S');
90
```
91
 
92
---
93
 
94
#### `mountDriveByLabel($label, $mountPath?, $timeout?, $checkEvery?, $filesystem?, $devPath?)`
95
 
96
Wait for a labeled device to appear and mount it. Works with gpt and msdos labels
97
 
98
**Parameters:**
99
 
100
- `$label` - GPT label name (required; alphanumeric, hyphen, underscore only)
101
- `$mountPath` - Mount destination (optional; defaults to `/mnt/$label`)
102
- `$timeout` - Seconds to wait (optional; defaults to 600)
103
- `$checkEvery` - Polling interval in seconds (optional; defaults to 15)
104
- `$filesystem` - Filesystem type for mount (optional; defaults to `'ufs'`)
105
- `$devPath` - Path to GPT devices (optional; defaults to `/dev/gpt/`)
106
 
107
**Returns:**
108
 
109
- Mount path on success
110
- Empty string on timeout or error
111
 
112
**Behavior:**
113
 
114
- Validates label format (alphanumeric, hyphen, underscore)
115
- Checks if already mounted; returns path immediately if so
116
- Polls for device appearance with `$checkEvery` second intervals
117
- Creates mount point if needed (via `make_path`)
118
- Logs all operations and errors
119
- Prints "Waiting for drive labeled..." during polling
120
 
121
**Example:**
122
 
123
```perl
124
use ZFS_Utils qw(mountDriveByLabel);
125
my $mp = mountDriveByLabel('replica', '/mnt/backup', 300, 10);
126
die "Failed to mount" unless $mp;
127
```
128
 
129
---
130
 
131
#### `loadConfig($filename, $default?)`
132
 
133
Load a YAML configuration file into a hashref.
134
 
135
**Parameters:**
136
 
137
- `$filename` - Path to YAML config file (required)
138
- `$default` - Default hashref to use if file missing (optional)
139
 
140
**Returns:** hashref loaded from YAML file, or `$default` if provided and file is missing
141
 
142
**Behavior:**
143
 
144
- Dies if no filename provided
145
- If file missing and `$default` provided, writes `$default` to file in YAML format
146
- Tries `YAML::XS` first, falls back to `YAML::Tiny`
147
- Logs which YAML module was used
148
- Dies if file exists but does not contain a hashref
149
- Returns empty hashref if both file is missing and no default is provided
150
 
151
**Dependencies:** YAML::XS or YAML::Tiny (at least one required)
152
 
153
**Example:**
154
 
155
```perl
156
use ZFS_Utils qw(loadConfig);
157
my $cfg = loadConfig(
158
    '/etc/app.yaml',
159
    { debug => 0, port => 8080 }
160
);
161
```
162
 
163
---
164
 
165
#### `mountGeli($geliConfig)`
166
 
167
Prepare and decrypt GELI-encrypted disks, then mount the ZFS pool.
168
 
169
**Parameters:**
170
 
171
- `$geliConfig` - hashref with GELI configuration
172
 
173
**Expected keys in `$geliConfig`:**
174
 
175
- `localKey` - Path to local hex key (or hex string)
176
- `keydiskname` - GPT label of disk holding remote binary keyfile
177
- `keyfile` - Filename on key disk
178
- `target` - Path to write combined GELI key
179
- `diskList` - Arrayref of disk paths to decrypt (passed to `decryptAndMountGeli`)
180
- `poolname` - Name of ZFS pool to mount (passed to `decryptAndMountGeli`)
181
 
182
**Returns:**
183
 
184
- Pool name on success
185
- Empty string on error
186
 
187
**Workflow:**
188
 
189
1. Validates local key file exists
190
2. Mounts key disk via `mountDriveByLabel`
191
3. Creates combined GELI key via `makeGeliKey`
192
4. Decrypts disks and mounts pool via `decryptAndMountGeli`
193
 
194
---
195
 
196
#### `makeGeliKey($remote_keyfile, $localKeyHexOrPath, $target)`
197
 
198
Create a GELI key by XOR-ing a remote binary keyfile with a local hex key.
199
 
200
**Parameters:**
201
 
202
- `$remote_keyfile` - Path to binary keyfile (32 bytes; required)
203
- `$localKeyHexOrPath` - Hex string (64 hex chars) or path to file containing hex (required)
204
- `$target` - Path where to write resulting 32-byte binary key (required)
205
 
206
**Returns:** 1 on success; dies on error
207
 
208
**Behavior:**
209
 
210
- Reads exactly 32 bytes from `$remote_keyfile` in binary mode
211
- Accepts `$localKeyHexOrPath` as direct hex string or file path
212
- Cleans hex: removes `0x` prefix and whitespace
213
- Validates local key is exactly 64 hex characters (256-bit)
214
- XORs remote and local buffers byte-by-byte
215
- Creates target directory if needed
216
- Writes result with 0600 permissions
217
- Dies with descriptive error on any failure
218
 
219
**Example:**
220
 
221
```perl
222
use ZFS_Utils qw(makeGeliKey);
223
makeGeliKey(
224
    '/mnt/key_disk/geli.key',
225
    'a1b2c3d4...(64 hex chars)',
226
    '/root/combined.key'
227
);
228
```
229
 
230
---
231
 
232
### Exported Variables
233
 
234
#### `$logFileName`
235
 
236
Path to the log file for `logMsg()` output.
237
 
238
**Default:** `/tmp/zfs_utils.log`  
239
**Type:** Scalar string  
240
**Usage:** Can be overridden by caller before calling any function
241
 
242
**Example:**
243
 
244
```perl
245
use ZFS_Utils qw(logMsg $logFileName);
246
$logFileName = '/var/log/myapp.log';
247
logMsg("This goes to /var/log/myapp.log");
248
```
249
 
250
---
251
 
252
#### `$displayLogsOnConsole`
253
 
254
Flag to enable/disable console output in `logMsg()`.
255
 
256
**Default:** 1 (enabled)  
257
**Type:** Integer (0 = disabled, non-zero = enabled)  
258
**Usage:** Can be toggled at runtime
259
 
260
**Example:**
261
 
262
```perl
263
use ZFS_Utils qw(logMsg $displayLogsOnConsole);
264
$displayLogsOnConsole = 0;  # suppress console output
265
logMsg("Silent operation");
266
```
267
 
268
---
269
 
270
## Non-Exported Functions
271
 
272
Functions not in `@EXPORT_OK`; use full package path or import explicitly.
273
 
274
### `decryptAndMountGeli($geliConfig)`
275
 
276
Decrypt each GELI disk and import/mount the ZFS pool.
277
 
278
**Parameters:**
279
 
280
- `$geliConfig` - hashref with GELI configuration
281
 
282
**Expected keys:**
283
 
284
- `poolname` - ZFS pool name (required; dies if missing)
285
- `diskList` - Arrayref of disk paths; auto-discovered if missing
286
- `target` - Path to GELI keyfile
287
 
288
**Returns:**
289
 
290
- Pool name on success
291
- Empty string on error (logs instead of dying)
292
 
293
**Behavior:**
294
 
295
- Dies if no pool name specified
296
- Auto-discovers available disks via `findGeliDisks()` if no `diskList` provided
297
- Decrypts each disk via `geli attach -k <keyfile> <disk>`
298
- Logs failures but continues with remaining disks
299
- Imports pool via `zpool import`
300
- Mounts all filesystems via `zfs mount -a`
301
- Logs all operations and errors
302
 
303
**Internal Use:** Called by `mountGeli()`
304
 
305
---
306
 
307
### `findGeliDisks()`
308
 
309
Find all disks available for GELI/ZFS use.
310
 
311
**Parameters:** none
312
 
313
**Returns:** List of disk names (e.g., `('da0', 'da1')`)
314
 
315
**Behavior:**
316
 
317
- Lists all disks from `geom disk list`
318
- Filters out disks with existing partitions
319
- Filters out disks already in zpools
320
- Returns only unpartitioned, unused disks
321
- Logs the search operation
322
 
323
**Internal Use:** Called by `decryptAndMountGeli()` if no disk list provided
324
 
325
---
326
 
327
### `makeReplicateCommands($sourceSnapsRef, $statusRef?, $newStatusRef?)`
328
 
329
Generate ZFS send commands for replication based on snapshot lists.
330
 
331
**Parameters:**
332
 
333
- `$sourceSnapsRef` - Arrayref of snapshot lines (required)
334
- `$statusRef` - Arrayref of previously replicated snapshots (optional)
335
- `$newStatusRef` - Arrayref to populate with newly replicated snapshots (optional)
336
 
337
**Expected format of snapshot lines:**
338
 
339
- First token (space-separated) is the full snapshot name: `pool/filesystem@snapshot [extra tokens...]`
340
 
341
**Returns:** Hashref of `{ filesystem => 'zfs send command' }`
342
 
343
**Behavior:**
344
 
345
- Parses snapshot names from input lines
346
- Identifies root filesystem (first snapshot's fs)
347
- Looks up last replicated snapshot per filesystem from `$statusRef`
348
- Attempts recursive send if all child snapshots share same name
349
- Falls back to per-filesystem incremental or full sends
350
- Populates `$newStatusRef` with new snapshot names for status tracking
351
 
352
**Replaces missing `from` snapshots with full sends**
353
 
354
**Example:**
355
 
356
```perl
357
my @snaps = (
358
    'tank/home@snap1 some extra info',
359
    'tank/home/user@snap1 another field'
360
);
361
my @old_status = ('tank/home@snap0');
362
my @new_status = ();
363
 
364
my $cmds = makeReplicateCommands(\@snaps, \@old_status, \@new_status);
365
foreach my $fs (keys %$cmds) {
366
    print "For $fs: " . $cmds->{$fs} . "\n";
367
}
368
# @new_status now contains the new snapshot names
369
```
370
 
371
---
372
 
373
## Module Variables (Internal)
374
 
375
- `$VERSION` - Module version (0.2)
376
- `$merge_stderr` - Global flag for `runCmd()` stderr handling (default: 0, set to 1 in runCmd)
377
 
378
---
379
 
380
## Usage Examples
381
 
382
### Basic Logging
383
 
384
```perl
385
use ZFS_Utils qw(logMsg $logFileName $displayLogsOnConsole);
386
 
387
$logFileName = '/var/log/backup.log';
388
$displayLogsOnConsole = 1;
389
 
390
logMsg("Backup started");
391
logMsg("Backup completed");
392
```
393
 
394
### Loading Configuration
395
 
396
```perl
397
use ZFS_Utils qw(loadConfig);
398
 
399
my $config = loadConfig('/etc/backup.yaml', {
400
    pool => 'tank',
401
    retention_days => 30,
402
});
403
```
404
 
405
### Mounting an Encrypted Pool
406
 
407
```perl
408
use ZFS_Utils qw(mountGeli);
409
 
410
my $result = mountGeli({
411
    localKey => '/root/.key/local.hex',
412
    keydiskname => 'key_disk',
413
    keyfile => 'geli.key',
414
    target => '/tmp/combined.key',
415
    diskList => ['/dev/gpt/encrypted1', '/dev/gpt/encrypted2'],
416
    poolname => 'backup',
417
});
418
 
419
die "Failed to mount" unless $result;
420
print "Pool $result mounted\n";
421
```
422
 
423
### Creating a GELI Key
424
 
425
```perl
426
use ZFS_Utils qw(makeGeliKey);
427
 
428
makeGeliKey(
429
    '/mnt/usb/remote.bin',
430
    'deadbeefcafebabe' . ('0' x 48),  # 64 hex chars
431
    '/root/.key/geli.key'
432
);
433
```
434
 
435
### Running Commands with Output
436
 
437
```perl
438
use ZFS_Utils qw(runCmd);
439
 
440
my @lines = runCmd('zfs', 'list', '-H');
441
foreach my $line (@lines) {
442
    print "Pool: $line\n";
443
}
444
 
445
my $output = runCmd('df', '-h');
446
print $output;
447
```
448
 
449
---
450
 
451
## Dependencies
452
 
453
- **Core:** Perl 5.10+, strict, warnings, Exporter, Data::Dumper, POSIX, File::Path
454
- **External (optional):** YAML::XS or YAML::Tiny (at least one required for `loadConfig()`)
455
- **System (FreeBSD):** gshred, geli, zfs, zpool, geom, gpart, mount
456
 
457
---
458
 
459
## Notes
460
 
461
- All functions use `logMsg()` for diagnostics; configure logging before use
462
- Functions prefer returning empty strings or undef over dying (except `makeGeliKey`)
463
- `mountGeli()` and `decryptAndMountGeli()` require FreeBSD with GELI and ZFS
464
- Binary key operations use raw `:raw` mode for safe byte handling
465
- XOR operations assume 256-bit (32-byte) keys
466
 
467
---
468
 
469
## License
470
 
471
Simplified BSD License (FreeBSD License) – see module header for full text.