File Coverage

blib/lib/Filesys/ZFS.pm
Criterion Covered Total %
statement 144 236 61.0
branch 32 102 31.3
condition 12 28 42.8
subroutine 17 37 45.9
pod 22 24 91.6
total 227 427 53.1


line stmt bran cond sub pod time code
1             package Filesys::ZFS;
2              
3             #
4             # Copyright (C) 2014 Colin Faber
5             #
6             # This program is free software: you can redistribute it and/or modify
7             # it under the terms of the GNU General Public License as published by
8             # the Free Software Foundation version 2 of the License.
9             #
10             # This program is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13             # GNU General Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License
16             # along with this program. If not, see .
17             #
18             #
19             # Original author: Colin Faber
20             # Original creation date: 08/28/2014
21             # Version: $Id: ZFS.pm,v 1.5 2014/09/06 22:17:55 cfaber Exp $
22             #
23              
24              
25             # Required libraries
26 2     2   14496 use strict;
  2         6  
  2         77  
27 2     2   7318 use Sys::Hostname;
  2         2592  
  2         110  
28 2     2   12 use Fcntl qw(:flock :seek);
  2         10  
  2         299  
29 2     2   2186 use IPC::Open3;
  2         9180  
  2         125  
30 2     2   16 use Symbol qw(gensym);
  2         3  
  2         10312  
31              
32             =head1 NAME
33              
34             Filesys::ZFS
35              
36             =head1 SYNOPSIS
37              
38             use Filesys::ZFS;
39              
40             =head1 DESCRIPTION
41              
42             Filesys::ZFS is a simple interface to zfs and zpool commands for managing ZFS file systems
43              
44             =head1 METHODS
45              
46             =head2 new(\%options)
47              
48             Create a new Filesys::ZFS object and return it.
49              
50             =head3 OPTIONS
51              
52             Various options can be passed to the new() method
53              
54             =over
55              
56             =item zpool
57              
58             Path to the zpool command, defaults to B
59              
60             =item zfs
61              
62             Path to the zfs command, defaults to B
63              
64             =item no_root_check
65              
66             Don't warn if the calling program isn't root.
67              
68             =back
69              
70             =cut
71              
72             sub new {
73 1     1 1 15 my ($pkg, $opt) = @_;
74              
75 1 50 33     8 if($opt && ref($opt) ne 'HASH'){
76 0         0 die __PACKAGE__ . ' only accepts a hash reference containing the options defined in the documentation.';
77             } else {
78 1         4 $opt = { opt => $opt };
79 1   50     7 $opt->{opt}->{zpool} ||= '/sbin/zpool';
80 1   50     5 $opt->{opt}->{zfs} ||= '/sbin/zfs';
81              
82              
83 1 50 33     8 if($< != 0 && !$opt->{opt}->{no_root_check}){
84 0         0 warn "Warning: " . __PACKAGE__ . " is not running as a super user!\n";
85             }
86              
87 1         4 return bless $opt, __PACKAGE__;
88             }
89             }
90              
91              
92             # Local variables
93             $__PACKAGE__::VERSION = $1 if('$Revision: 1.5 $' =~ /: ([\d\.]+) /);
94              
95             =head2 init()
96              
97             Initialize. Read the various file system details. This method must be run prior to just about anything else working. If file system details change B should be run again.
98              
99             =cut
100              
101             sub init {
102 1     1 1 84 my ($self) = @_;
103              
104 1         4 $self->_flush;
105 1 50       5 $self->_populate_zfs_list || return;
106              
107 1         8 $self->{init} = 1;
108              
109 1         13 for my $type (qw(pools snapshots volumes bookmarks)){
110 4         33 my (@list) = $self->list($type, 1);
111              
112 4 50 66     26 return if (!@list && $self->errstr);
113 4         12 for my $pool (@list){
114 4         52 $self->_populate_zpool_status($pool);
115 4         42 $self->_populate_zfs_properties($pool);
116             }
117             }
118              
119 1         116 return 1;
120             }
121              
122              
123             =head2 list(TYPE)
124              
125             Returns an object list
126              
127             =head3 TYPE
128              
129             =over
130              
131             =item pools
132              
133             Returns file system / Pools data (if any)
134              
135             =item snapshots
136              
137             Returns snapshots (if any)
138              
139             =item volumes
140              
141             Retruns volumes (if any)
142              
143             =item bookmarks
144              
145             Returns bookmarks (if any)
146              
147             =back
148              
149             =cut
150              
151             sub list {
152 4     4 1 11 my ($self, $key, $curse) = @_;
153              
154             # Flush error
155 4         20 $self->_err;
156              
157 4 50       16 return $self->_err('list() cannot be called prior to initializing with init() methods') if !$self->{init};
158              
159 4         11 my %vkeys = map { $_ => 1 } qw(pools snapshots volumes bookmarks);
  16         70  
160              
161 4 50       38 return $self->_err('Unknown key passed to list(): ' . $key) if !$vkeys{ $key };
162              
163 4         6 my @ret;
164              
165             # for(sort { $a cmp $b || $a <=> $b } keys %{ $self->{buf}->{ $key } }){a
166 4         9 for(@{ $self->{buf}->{order}->{ $key } }){
  4         17  
167 4         6 my $t;
168 4 50       15 if(!$curse){
169 0         0 $t = $self->{buf}->{ $key }->{ $_ };
170 0         0 $t->{opt} = $self->{opt};
171 0         0 $t = bless $t; #, 'Filesys::ZFS::list';
172             } else {
173 4         7 $t = $_;
174             }
175              
176 4         12 push @ret, $t;
177             }
178              
179 4         17 return (@ret);
180             }
181              
182             # package Filesys::ZFS::list;
183             =head2 name()
184              
185             Return the pool / snapshot / volume / bookmark name provided by the B method
186              
187             =over
188              
189             for my $pool ($ZFS->list('pools')){
190             print "Pool: " . $pool->name . "\n";
191             }
192              
193             =back
194              
195             =cut
196              
197 0     0 0 0 sub name { return $_[0]->{name}; }
198              
199             =head2 state()
200              
201             Return the pool / snapshot / volume / bookmark state provided by the B method
202              
203             =over
204              
205             for my $pool ($ZFS->list('pools')){
206             print "Current state: " . $pool->state . "\n";
207             }
208              
209             =back
210              
211             =cut
212              
213             sub state {
214 0     0 1 0 my ($self) = @_;
215              
216 0 0       0 if($self->{nlevel}){
    0          
217 0         0 return $self->{state};
218             } elsif(exists $self->{level}) {
219 0         0 return $self->{device}->{0}->{state};
220             } else {
221 0 0       0 return join("\n", @{ $self->{state} }) if $self->{state};
  0         0  
222             }
223             }
224              
225             =head2 errors()
226              
227             Return the pool / snapshot / volume / bookmark errors provided by the B method
228              
229             =over
230              
231             for my $pool ($ZFS->list('pools')){
232             print "Current errors: " . $pool->errors . "\n";
233             }
234              
235             =back
236              
237             =cut
238              
239 0 0   0 1 0 sub errors { return join("\n", @{ $_[0]->{errors} }) if $_[0]->{errors}; }
  0         0  
240              
241             =head2 mount()
242              
243             Return the pool / volume mount point (if available) provided by the B method
244              
245             =over
246              
247             for my $pool ($ZFS->list('pools')){
248             print "Mount point: " . $pool->mount . "\n";
249             }
250              
251             =back
252              
253             =cut
254              
255 0     0 1 0 sub mount { return $_[0]->{mount}; }
256              
257             =head2 scan()
258              
259             Return the pool / volume scan message (if available) provided by the B method
260              
261             =over
262              
263             for my $pool ($ZFS->list('pools')){
264             print "Last scan: " . $pool->scan . "\n";
265             }
266              
267             =back
268              
269             =cut
270              
271 0 0   0 1 0 sub scan { return join("\n", @{ $_[0]->{scan} }) if $_[0]->{scan}; }
  0         0  
272              
273             =head2 free()
274              
275             Return the pool / volume free space (in KB) provided by the B method
276              
277             =over
278              
279             for my $pool ($ZFS->list('pools')){
280             print "Free Space: " . $pool->free . "\n";
281             }
282              
283             =back
284              
285             =cut
286              
287 0     0 1 0 sub free { return $_[0]->{free}; }
288              
289             =head2 used()
290              
291             Return the pool / volume used space (in KB) provided by the B method
292              
293             =over
294              
295             for my $pool ($ZFS->list('pools')){
296             print "Used Space: " . $pool->used . "\n";
297             }
298              
299             =back
300              
301             =cut
302              
303 0     0 1 0 sub used { return $_[0]->{used}; }
304              
305             =head2 referenced()
306              
307             Return the pool / volume referenced space (in KB) provided by the B method
308              
309             =over
310              
311             for my $pool ($ZFS->list('pools')){
312             print "Referencing: " . $pool->referenced . "\n";
313             }
314              
315             =back
316              
317             =cut
318              
319 0     0 1 0 sub referenced { return $_[0]->{refer}; }
320              
321             =head2 status()
322              
323             Return the pool / volume current status message provided by the B method
324              
325             =over
326              
327             for my $pool ($ZFS->list('pools')){
328             print "Current status: " . $pool->status . "\n";
329             }
330              
331             =back
332              
333             =cut
334              
335             sub status {
336 0     0 1 0 my ($self) = @_;
337 0 0       0 return join("\n", @{ $self->{status} }) if $self->{status};
  0         0  
338             }
339              
340             =head2 action()
341              
342             Return the pool / volume current action message provided by the B method
343              
344             =over
345              
346             for my $pool ($ZFS->list('pools')){
347             print "Requested action: " . $pool->action . "\n";
348             }
349              
350             =back
351              
352             =cut
353              
354 0 0   0 1 0 sub action { join("\n", @{ $_[0]->{action} }) if $_[0]->{action}; }
  0         0  
355              
356             =head2 read()
357              
358             Return the pool / volume read errors (if any) provided by the B method
359              
360             =cut
361              
362             sub read {
363 0     0 1 0 my ($self) = @_;
364              
365 0 0       0 if($self->{nlevel}){
366 0         0 return $self->{read};
367             } else {
368 0         0 return $self->{device}->{0}->{read};
369             }
370             }
371              
372             =head2 write()
373              
374             Return the pool / volume write errors (if any) provided by the B method
375              
376             =cut
377              
378             sub write {
379 0     0 1 0 my ($self) = @_;
380              
381 0 0       0 if($self->{nlevel}){
382 0         0 return $self->{write};
383             } else {
384 0         0 return $self->{device}->{0}->{write};
385             }
386             }
387              
388             =head2 cksum()
389              
390             Return the pool / volume checksum errors (if any) provided by the B method
391              
392             =cut
393              
394             sub cksum {
395 0     0 1 0 my ($self) = @_;
396              
397 0 0       0 if($self->{nlevel}){
398 0         0 return $self->{cksum};
399             } else {
400 0         0 return $self->{device}->{0}->{cksum};
401             }
402             }
403              
404             =head2 note()
405              
406             Return the pool / volume error note (if any) provided by the B method
407              
408             =cut
409              
410             sub note {
411 0     0 1 0 my ($self) = @_;
412              
413 0 0       0 if($self->{nlevel}){
414 0         0 return $self->{note};
415             } else {
416 0         0 return $self->{device}->{0}->{note};
417             }
418             }
419              
420             =head2 config()
421              
422             Return the pool / volume / snapshot / bookmark raw configuration message provided by the B method
423              
424             =over
425              
426             for my $pool ($ZFS->list('pools')){
427             print "Configuration: " . $pool->config . "\n";
428             }
429              
430             =back
431              
432             =cut
433              
434 0 0   0 1 0 sub config { join("\n", @{ $_[0]->{config} }) if $_[0]->{config}; }
  0         0  
435              
436             =head2 providers()
437              
438             The B method is intended to be called from a B object initially, it returns a list of pool / volume providers, such as virtual devices or block devices.
439              
440             The resulting list returned is a list of provider objects, which can be used to call the standard B, B, B, B, B and B methods defined above.
441              
442             Additionally B can be called with a B object, allowing you to successully recruse through the providers stack. An example of this is as follows:
443              
444             =over
445              
446             prov($list_obj)
447              
448             sub prov {
449             my ($p) = @_;
450             for my $prov ($p->providers){
451             if($prov->is_vdev){
452             print "\tVirtual Device: " . $prov->name . "\n";
453             prov($prov);
454             } else {
455             print "\tBlock Device: " . $prov->name . "\n";
456             }
457             }
458             }
459              
460             =back
461              
462             =cut
463              
464              
465             sub providers {
466 0     0 1 0 my ($self) = @_;
467              
468 0 0       0 if($self->{nlevel}){
    0          
469 0         0 $self->{level} = $self->{nlevel};
470             } elsif(!$self->{level}){
471 0         0 $self->{level} = 2;
472             }
473              
474 0         0 my @ret;
475 0         0 for(@{ $self->{device}->{ $self->{level} . '.order' } }){
  0         0  
476 0         0 my $dev = $self->{device}->{ $self->{level} }->{ $_ };
477 0         0 push @ret, bless {
478             'device' => $self->{device},
479             'nlevel' => $self->{level} + 2,
480             'name' => $_,
481             'level' => $self->{level},
482             'read' => $dev->{read},
483             'write' => $dev->{write},
484             'cksum' => $dev->{cksum},
485             'state' => $dev->{state},
486             'note' => $dev->{note}
487             };
488             }
489              
490 0         0 return @ret;
491             }
492              
493             =head2 is_vdev(NAME)
494              
495             Return true if the B device is a virtual device (vdev) of some kind or a regular block device or file
496              
497             If B is omitted then the object name is used. This is useful when calling is_dev() from a providers() return:
498              
499             =over
500             for my $prov ($list_obj->providers){
501             if($prov->is_vdev){
502             do something neat;
503             }
504             }
505              
506             =back
507              
508             =cut
509              
510              
511             sub is_vdev {
512 0     0 1 0 my ($self, $name) = @_;
513 0 0       0 if(!$name){
514 0         0 $name = $self->{name};
515             }
516              
517 0 0       0 if($name =~ /^(raidz[123]\-\d+|mirror\-\d+|logs|cache|spares|replacing\-\d+)$/){
518 0         0 return 1;
519             } else {
520 0         0 return;
521             }
522             }
523              
524              
525             # Returns true if all ZFS pools appear healthy
526             =head2 is_health()
527              
528             Return true if all zpools are in a healthy state. This can be called without initializing with init()
529              
530             =cut
531              
532             sub is_healthy {
533 1     1 0 3 my ($self) = @_;
534 1         10 my (@res) = $self->_run($self->{opt}->{zpool}, 'status', '-x');
535 1 50       53 if((split(/\s+/, $res[0]))[3] eq 'healthy'){
536 1         35 return 1;
537             } else {
538 0         0 return;
539             }
540             }
541              
542             =head2 properties(POOL)
543              
544             Return a list (in order) of all property keys for B, which can be a pool / file system / volume, etc.
545              
546             =cut
547              
548             sub properties {
549 0     0 1 0 my ($self, $pool) = @_;
550 0 0       0 return $self->_err('properties() cannot be called prior to initializing with init() methods') if !$self->{init};
551              
552 0 0       0 if(ref($self->{buf}->{properties}->{ $pool }->{order}) eq 'ARRAY'){
553 0         0 return @{ $self->{buf}->{properties}->{ $pool }->{order} };
  0         0  
554             }
555             }
556              
557             =head2 propval(POOL, PROPERTY_KEY)
558              
559             Return a two element list of the property value, and default for the property key B in B pool / file system / volume.
560              
561             =cut
562              
563             sub propval {
564 0     0 1 0 my ($self, $pool, $key) = @_;
565              
566 0 0       0 return $self->_err('propval() cannot be called prior to initializing with init() methods') if !$self->{init};
567 0 0       0 if(ref($self->{buf}->{properties}->{ $pool }->{set}->{ $key }) eq 'ARRAY'){
568 0         0 return @{ $self->{buf}->{properties}->{ $pool }->{set}->{ $key } };
  0         0  
569             }
570             }
571              
572              
573             =head2 errstr()
574              
575             Return the last error string captured if any.
576              
577             =cut
578              
579 3     3 1 18 sub errstr { return $_[0]->{err}; }
580              
581              
582 1     1   3 sub _flush { $_[0]->{buf} = {}; $_[0]->{init} = 0; }
  1         3  
583              
584             sub _err {
585 4     4   7 my ($self, $msg) = @_;
586 4         7 $self->{err} = $msg;
587 4         8 return;
588             }
589              
590              
591             # List active pools and details
592             sub _populate_zfs_list {
593 1     1   1 my ($self) = @_;
594              
595 1         4 my @types = qw(filesystem snapshot snap volume bookmark);
596 1         2 for my $type (@types){
597 5         58 my (@res) = $self->_run($self->{opt}->{zfs}, 'list', '-H', '-p', '-t', $type);
598 5 50 66     91 if(!@res && $self->_run_err){
599 0         0 return $self->_err("Unable to read $type list: " . $self->_run_err);
600             }
601              
602 5 100       27 if($type eq 'filesystem'){
603 1         11 $type = 'pools';
604             }
605              
606 5         34 for(@res){
607 4         13 chomp;
608 4 50       13 if($_ eq 'no datasets available'){
609 0         0 $self->{buf}->{ $type } = '';
610             } else {
611 4         23 my ($pool, $used, $free, $refer, $mount) = split(/\s+/, $_, 5);
612 4 50       18 $used = int($used / 1024) if $used;
613 4 100       11 $free = int($free / 1024) if $free;
614 4 50       73 $refer = int($refer / 1024) if $refer;
615              
616 4         49 $self->{buf}->{ $type }->{ $pool }->{name} = $pool;
617 4         15 $self->{buf}->{ $type }->{ $pool }->{used} = $used;
618 4         14 $self->{buf}->{ $type }->{ $pool }->{free} = $free;
619 4         12 $self->{buf}->{ $type }->{ $pool }->{refer} = $refer;
620 4         15 $self->{buf}->{ $type }->{ $pool }->{mount} = $mount;
621 4         6 push @{ $self->{buf}->{order}->{ $type } }, $pool;
  4         121  
622             }
623             }
624             }
625              
626 1         37 return 1;
627             }
628              
629             # Produce a text report of the zfs pool, include smart errors if any for each device.
630             sub _populate_zpool_status {
631 4     4   21 my ($self, $pool) = @_;
632              
633 4         142 my (@res) = $self->_run($self->{opt}->{zpool}, 'status', '-v', $pool);
634              
635 4 50       48 if(!@res){
636 0         0 return $self->_err("Unable to read pool data for: $pool\: " . $self->_run_err);
637             }
638              
639             # First pass
640 4         200 my $n;
641 4         21 for(my $i = 0; $i < @res; $i++){
642 76         220 chomp $res[$i];
643 76 100 66     788 if($res[$i] =~ /^\s*(pool|state|scrub|scan|errors|config|status|action):\s*(.+)?$/){
    100          
644 24         95 $n = $1;
645 24         78 my $v = $2;
646 24         115 chomp $v;
647 24 100       65 next if !$v;
648 20         38 push @{ $self->{buf}->{pools}->{ $pool }->{ $n } }, $v;
  20         4293  
649             } elsif($n && $res[$i]){
650 44         46 push @{ $self->{buf}->{pools}->{ $pool }->{ $n } }, $res[$i];
  44         242  
651             }
652             }
653              
654 4 50       27 if(ref($self->{buf}->{pools}->{ $pool }->{config}) eq 'ARRAY'){
655 4         16 my $cfg = $self->{buf}->{pools}->{ $pool }->{config};
656 4         6 my ($name, $bd, $dev);
657              
658 4         12 for(@$cfg){
659 38         59 chomp;
660              
661             # Headers
662 38 50 33     228 next if(/^\s{8}NAME\s+STATE/ || !$_);
663              
664              
665             # First stage entries (usually just pool name)
666 38 100       240 if(/^\t(\s*)([^\s]+)\s+([A-Z]+)\s+(\d+)\s+(\d+)\s+(\d+)(\s+(.+))?$/){
667 30         71 my $d = length($1);
668 30 100       49 if($d){
669 26         31 push @{ $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d . '.order' } }, $2;
  26         171  
670 26         185 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ $2 }->{ 'state' } = $3;
671 26         136 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ $2 }->{ 'read' } = $4;
672 26         99 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ $2 }->{ 'write' } = $5;
673 26         100 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ $2 }->{ 'cksum' } = $6;
674 26         114 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ $2 }->{ 'note' } = $7;
675             } else {
676 4         68 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ 'state' } = $3;
677 4         50 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ 'read' } = $4;
678 4         46 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ 'write' } = $5;
679 4         33 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ 'cksum' } = $6;
680 4         24 $self->{buf}->{pools}->{ $pool }->{ 'device' }->{ $d }->{ 'note' } = $7;
681             }
682             }
683             }
684              
685             }
686              
687 4         21 return 1;
688             }
689              
690             # Get the pool / file system properties
691             sub _populate_zfs_properties {
692 4     4   12 my ($self, $pool) = @_;
693              
694 4         48 my (@res) = $self->_run($self->{opt}->{zfs}, 'get', '-H', 'all', $pool);
695              
696 4 50 33     60 if(!@res && $self->_run_err){
697 0         0 return $self->_err("Unable to read $pool properties list: " . $self->_run_err);
698             }
699              
700 4         25 for(my $i = 0; $i < @res; $i++){
701 224         287 chomp $res[$i];
702 224         844 my ($p, $prop, $val, $src) = split(/\t/, $res[$i]);
703              
704 224         324 push @{ $self->{buf}->{properties}->{ $pool }->{ 'order '} }, $prop;
  224         589  
705 224         2056 $self->{buf}->{properties}->{ $pool }->{ 'set' }->{ $prop } = [ $val, $src ];
706             }
707              
708 4         62 return 1;
709             }
710              
711             # Map by-id to device
712             sub _map_by_id {
713 0     0   0 my ($self, $dev) = @_;
714 0 0       0 if(opendir(my $dh, '/dev/disk/by-id')){
715 0         0 my @loc;
716 0         0 for(readdir $dh){
717 0 0 0     0 if($_ eq '.' || $_ eq '..'){
718 0         0 next;
719             } else {
720 0         0 my $link;
721 0 0       0 $link = $1 if(readlink('/dev/disk/by-id/' . $_) =~ /\/([^\/]+)$/);
722              
723 0 0       0 if($link eq $dev){
    0          
724 0         0 push @loc, $_;
725             } elsif($dev eq $_){
726 0         0 push @loc, $link;
727             }
728             }
729             }
730              
731 0         0 closedir($dh);
732 0         0 return (@loc);
733             } else {
734 0         0 return $self->_err("Unable to read udev path /dev/disk/by-id: " . $self->_run_err);
735             }
736             }
737              
738              
739             # Execute and return the results
740             sub _run {
741 14     14   146 my ($self, @com) = @_;
742 14         23 my @res;
743             my @err;
744 14         51 delete $self->{run_err};
745              
746             # Untaint external command
747 14         57 for(my $i = 0; $i < @com; $i++){
748 69 50       691 $com[$i] = $1 if($com[$i] =~ /(.+)/);
749             }
750              
751 14 50       38481 if(open(__ZFSERR, '+>', undef)){
752 14         112 my $pid = open3(gensym, \*__ZFSOUT, ">&__ZFSERR", @com);
753 14         189823 while(<__ZFSOUT>){
754 305         5536 push @res, $_;
755             }
756              
757             # Probably should allow for command timeout...
758 14         5420 waitpid($pid, 0);
759              
760 14         313 seek(__ZFSERR, 0, &SEEK_SET);
761 14         676 while(<__ZFSERR>){
762 0         0 chomp;
763 0 0       0 push @err, $_ if $_;
764             }
765              
766 14         224 close(__ZFSOUT);
767 14         43163 close(__ZFSERR);
768              
769 14         188 $self->{run_err} = join("\n", @err);
770 14         492 return @res;
771             } else {
772 0         0 $self->{run_err} = "ERROR: can't generate anonymous file handle: $!";
773 0         0 return;
774             }
775             }
776              
777             sub _run_err {
778 4     4   15 my ($self) = @_;
779 4         37 return $self->{run_err};
780             }
781              
782              
783             =head1 LICENSE
784              
785             This library is licensed under the Perl Artistic license and may be used, modified, and copied under it's terms.
786              
787             =head1 AUTHOR
788              
789             This library was authorized by Colin Faber . Please contact the author with any questions.
790              
791             =head1 BUGS
792              
793             Please report all bugs to https://rt.cpan.org/Dist/Display.html?Status=Active&Queue=Filesys-ZFS
794              
795             =cut
796              
797             1;