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