Imported Upstream version 3.3.2
[debian/amanda] / perl / Amanda / Application / Zfs.pm
1 # Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10 # for more details.
11 #
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
15 #
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
18
19 package Amanda::Application::Zfs;
20
21 use strict;
22 use warnings;
23 use Symbol;
24 use IPC::Open3;
25 use Amanda::Debug qw( :logging );
26
27 =head1 NAME
28
29 Amanda::Application::Zfs -- collection of function to use with zfs
30
31 =head1 SYNOPSIS
32
33 =head1 INTERFACE
34
35 =cut
36
37 sub zfs_set_value {
38     my $self = shift;
39
40     if (defined $self->{execute_where} && $self->{execute_where} ne "client") {
41         $self->print_to_server_and_die(" Script must be run on the client 'execute-where client'", $Amanda::Script_App::ERROR);
42     }
43     if ($self->{df_path} ne "df" && !-e $self->{df_path}) {
44         $self->print_to_server_and_die("Can't execute DF-PATH '$self->{df_path}' command",
45                         $Amanda::Script_App::ERROR);
46     }
47     if ($self->{zfs_path} ne "zfs" && !-e $self->{zfs_path}) {
48         $self->print_to_server_and_die("Can't execute ZFS-PATH '$self->{zfs_path}' command",
49                         $Amanda::Script_App::ERROR);
50     }
51
52     if ($self->{pfexec} =~ /^YES$/i) {
53         $self->{pfexec_cmd} = $self->{pfexec_path};
54     }
55     if (defined $self->{pfexec_cmd} && $self->{pfexec_cmd} ne "pfexec" && !-e $self->{pfexec_cmd}) {
56         $self->print_to_server_and_die("Can't execute PFEXEC-PATH '$self->{pfexec_cmd}' command",
57                         $Amanda::Script_App::ERROR);
58     }
59     if (!defined $self->{pfexec_cmd}) {
60         $self->{pfexec_cmd} = "";
61     }
62
63     if (!defined $self->{device}) {
64         if ($self->{action} eq "check") {
65             return;
66         } else {
67             $self->print_to_server_and_die("'--device' is not provided",
68                                            $Amanda::Script_App::ERROR);
69         }
70     }
71
72     my $device = $self->{device};
73     $device = $self->{directory} if defined $self->{directory};
74     # determine if $device is a mountpoint or ZFS dataset
75     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -H -o value mountpoint $device";
76     debug "running: $cmd";
77     my($wtr, $rdr, $err, $pid);
78     $err = Symbol::gensym;
79     $pid = open3($wtr, $rdr, $err, $cmd);
80     close $wtr;
81     my $zmountpoint = <$rdr>;
82     waitpid $pid, 0;
83     close $rdr;
84     close $err;
85
86     if ($? == 0) {
87         chomp $zmountpoint;
88
89         # zfs dataset supplied
90         $self->{filesystem} = $device;
91
92         # check if zfs volume
93         if ($zmountpoint ne '-') {
94             $self->{mountpoint} = $zmountpoint;
95         } else {
96             $self->{mountpoint} = undef;
97         }
98     } else {
99         # filesystem, directory or invalid ZFS dataset name
100         $cmd = "$self->{df_path} $device";
101         debug "running: $self->{df_path} $device";
102         $err = Symbol::gensym;
103         $pid = open3($wtr, $rdr, $err, $cmd);
104         close $wtr;
105         my @ret;
106         while (<$rdr>) {
107             chomp;
108             push @ret,$_;
109         }
110         my $errmsg = <$err>;
111         waitpid $pid, 0;
112         close $rdr;
113         close $err;
114
115         if ($? != 0) {
116             my $ret = $ret[0];
117             # invalid filesystem of ZFS dataset name
118             if (defined $errmsg) {
119                 chomp $errmsg;
120             }
121             if (defined $ret && defined $errmsg) {
122                 $self->print_to_server_and_die("$ret, $errmsg", $Amanda::Script_App::ERROR);
123             } elsif (defined $ret) {
124                 $self->print_to_server_and_die($ret, $Amanda::Script_App::ERROR);
125             } elsif (defined $errmsg) {
126                 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
127             } else {
128                 $self->print_to_server_and_die(
129                             "Failed to find mount points: $device",
130                             $Amanda::Script_App::ERROR);
131             }
132         }
133
134         my $size = @ret;
135         if ($size eq 1) {
136             # Solaris type df
137             @ret = split /:/, $ret[0];
138             if ($ret[0] =~ /(\S*)(\s*)(\()(\S*)(\s*)(\))$/) {
139                 $self->{mountpoint} = $1;
140                 $self->{filesystem} = $4;
141             } else {
142                 $self->print_to_server_and_die(
143                             "Failed to find mount points: $device",
144                             $Amanda::Script_App::ERROR);
145             }
146         } else {
147             # FreeBSD type df with header
148             if ($ret[1] =~ /^(\S+)(\s+)((\S+)(\s+))+(\S+)(\s*)$/) {
149                 $self->{mountpoint} = $6;
150                 $self->{filesystem} = $1;
151             } else {
152                 $self->print_to_server_and_die(
153                             "Failed to find mount points: $device",
154                             $Amanda::Script_App::ERROR);
155             }
156         }
157
158         $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -H -o value mountpoint $self->{filesystem}";
159         debug "running: $cmd|";
160         $err = Symbol::gensym;
161         $pid = open3($wtr, $rdr, $err, $cmd);
162         close $wtr;
163         $zmountpoint = <$rdr>;
164         chomp $zmountpoint;
165         $errmsg = <$err>;
166         waitpid $pid, 0;
167         close $rdr;
168         close $err;
169
170         if ($? != 0) {
171             if (defined $errmsg) {
172                 chomp $errmsg;
173             }
174             if (defined $zmountpoint && defined $errmsg) {
175                 $self->print_to_server_and_die($zmountpoint, $errmsg, $Amanda::Script_App::ERROR);
176             } elsif (defined $zmountpoint) {
177                 $self->print_to_server_and_die($zmountpoint, $Amanda::Script_App::ERROR);
178             } elsif (defined $errmsg) {
179                 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
180             } else {
181                 $self->print_to_server_and_die(
182                         "Failed to find mount points: $self->{filesystem}",
183                         $Amanda::Script_App::ERROR);
184             }
185         }
186         if ($zmountpoint ne 'legacy' && $zmountpoint ne $self->{mountpoint}) {
187             $self->print_to_server_and_die(
188                 "mountpoint from 'df' ($self->{mountpoint}) and 'zfs list' ($zmountpoint) differ",
189                 $Amanda::Script_App::ERROR);
190         }
191
192         if (!($device =~ /^$self->{mountpoint}/)) {
193             $self->print_to_server_and_die(
194                 "mountpoint '$self->{mountpoint}' is not a prefix of the device '$device'",
195                 $Amanda::Script_App::ERROR);
196         }
197
198     }
199
200     $self->{snapshot} = $self->zfs_build_snapshotname($device);
201     if (defined $self->{mountpoint}) {
202         if ($device =~ /^$self->{mountpoint}/) {
203             $self->{dir} = $device;
204             $self->{dir} =~ s,^$self->{mountpoint},,;
205             $self->{directory} = $self->{mountpoint} . "/.zfs/snapshot/" .
206                                  $self->{snapshot} . $self->{dir};
207         } else { # device is not the mountpoint
208             $self->{directory} = $self->{mountpoint} . "/.zfs/snapshot/" .
209                                  $self->{snapshot};
210         }
211     }
212 }
213
214 sub zfs_create_snapshot {
215     my $self = shift;
216
217     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} snapshot $self->{filesystem}\@$self->{snapshot}";
218     debug "running: $cmd";
219     my($wtr, $rdr, $err, $pid);
220     $err = Symbol::gensym;
221     $pid = open3($wtr, $rdr, $err, $cmd);
222     close $wtr;
223     my ($msg) = <$rdr>;
224     my ($errmsg) = <$err>;
225     waitpid $pid, 0;
226     close $rdr;
227     close $err;
228     if( $? != 0 ) {
229         if(defined $msg && defined $errmsg) {
230             $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
231         } elsif (defined $msg) {
232             $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
233         } elsif (defined $errmsg) {
234             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
235         } else {
236             $self->print_to_server_and_die("cannot create snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
237         }
238     }
239 }
240
241 sub zfs_destroy_snapshot {
242     my $self = shift;
243
244     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} destroy $self->{filesystem}\@$self->{snapshot}";
245     debug "running: $cmd|";
246     my($wtr, $rdr, $err, $pid);
247     my($msg, $errmsg);
248     $err = Symbol::gensym;
249     $pid = open3($wtr, $rdr, $err, $cmd);
250     close $wtr;
251     $msg = <$rdr>;
252     $errmsg = <$err>;
253     waitpid $pid, 0;
254     close $rdr;
255     close $err;
256     if( $? != 0 ) {
257         if(defined $msg && defined $errmsg) {
258             $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
259         } elsif (defined $msg) {
260             $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
261         } elsif (defined $errmsg) {
262             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
263         } else {
264             $self->print_to_server_and_die("cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
265         }
266     }
267 }
268
269 sub zfs_destroy_snapshot_level {
270     my $self = shift;
271     my $level = shift;
272
273     my $snapshotname = $self->zfs_find_snapshot_level($level);
274     debug "zfs_destroy_snapshot_level: Current $snapshotname";
275     if ($snapshotname ne "") {
276       my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} destroy  $self->{filesystem}\@$snapshotname";
277       debug "running: $cmd|";
278       my($wtr, $rdr, $err, $pid);
279       my($msg, $errmsg);
280       $err = Symbol::gensym;
281       $pid = open3($wtr, $rdr, $err, $cmd);
282       close $wtr;
283       $msg = <$rdr>;
284       $errmsg = <$err>;
285       waitpid $pid, 0;
286       close $rdr;
287       close $err;
288       if( $? != 0 ) {
289           if(defined $msg && defined $errmsg) {
290               $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
291           } elsif (defined $msg) {
292               $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
293           } elsif (defined $errmsg) {
294               $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
295           } else {
296               $self->print_to_server_and_die("cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
297           }
298       }
299     }
300 }
301
302 sub zfs_rename_snapshot {
303     my $self = shift;
304     my $level = shift;
305
306     my $device = $self->{device};
307     $device = $self->{directory} if defined $self->{directory};
308     my $newsnapshotname = $self->zfs_build_snapshotname($device, $level);
309     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} rename $self->{filesystem}\@$self->{snapshot} $self->{filesystem}\@$newsnapshotname";
310     debug "running: $cmd|";
311     my($wtr, $rdr, $err, $pid);
312     my($msg, $errmsg);
313     $err = Symbol::gensym;
314     $pid = open3($wtr, $rdr, $err, $cmd);
315     close $wtr;
316     $msg = <$rdr>;
317     $errmsg = <$err>;
318     waitpid $pid, 0;
319     close $rdr;
320     close $err;
321     if( $? != 0 ) {
322         if(defined $msg && defined $errmsg) {
323             $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
324         } elsif (defined $msg) {
325             $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
326         } elsif (defined $errmsg) {
327             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
328         } else {
329             $self->print_to_server_and_die("cannot rename snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
330         }
331     }
332 }
333
334 sub zfs_purge_snapshot {
335     my $self = shift;
336     my $minlevel = shift;
337     my $maxlevel = shift;
338
339     my $level;
340     for ($level = $maxlevel; $level >= $minlevel; $level--) {
341         debug "zfs_purge_snapshot: Check for existing snapshot at level $level";
342         $self->zfs_destroy_snapshot_level($level);
343     }
344 }
345
346 sub zfs_find_snapshot_level {
347     my $self = shift;
348     my $level = shift;
349
350     my $device = $self->{device};
351     $device = $self->{directory} if defined $self->{directory};
352     my $snapshotname = $self->zfs_build_snapshotname($device, $level);
353
354     my $cmd =  "$self->{pfexec_cmd} $self->{zfs_path} list -t snapshot $self->{filesystem}\@$snapshotname";
355     debug "running: $cmd|";
356     my($wtr, $rdr, $err, $pid);
357     my($msg, $errmsg);
358     $err = Symbol::gensym;
359     $pid = open3($wtr, $rdr, $err, $cmd);
360     close $wtr;
361     $msg = <$rdr>;
362     $errmsg = <$err>;
363     waitpid $pid, 0;
364     close $rdr;
365     close $err;
366     if( $? != 0 ) {
367         return "";
368     }
369     return $snapshotname;
370 }
371
372 sub zfs_build_snapshotname {
373     my $self = shift;
374     my $device = shift;
375     my $level = shift;
376
377     my $snapshotname = "";
378
379     if ($self->{action} eq 'check') {
380         $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-check";
381     } elsif (!defined $level) {
382         $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-current";
383     } else {
384         $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-" . $level;
385     }
386
387     return $snapshotname;
388 }
389
390 1;