0cbd7a186eb1be6142e89ee99ae01a620b7a5bbe
[debian/amanda] / perl / Amanda / Application / Zfs.pm
1 # Copyright (c) 2005-2008 Zmanda Inc.  All Rights Reserved.
2 #
3 # This library is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU Lesser General Public License version 2.1 as
5 # published 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 Mathlida Ave, Suite 300
17 # Sunnyvale, CA 94086, 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     my $action = $_[0];
41
42     if (defined $self->{execute_where} && $self->{execute_where} ne "client") {
43         $self->print_to_server_and_die($action, " Script must be run on the client 'execute_where client'", $Amanda::Script_App::ERROR);
44     }
45     if (!defined $self->{device}) {
46         $self->print_to_server_and_die($action, "'--device' is not provided",
47                         $Amanda::Script_App::ERROR);
48     }
49     if ($self->{df_path} ne "df" && !-e $self->{df_path}) {
50         $self->print_to_server_and_die($action, "Can't execute DF-PATH '$self->{df_path}' command",
51                         $Amanda::Script_App::ERROR);
52     }
53     if ($self->{zfs_path} ne "zfs" && !-e $self->{zfs_path}) {
54         $self->print_to_server_and_die($action, "Can't execute ZFS-PATH '$self->{zfs_path}' command",
55                         $Amanda::Script_App::ERROR);
56     }
57
58     if ($self->{pfexec} =~ /^YES$/i) {
59         $self->{pfexec_cmd} = $self->{pfexec_path};
60     }
61     if (defined $self->{pfexec_cmd} && $self->{pfexec_cmd} ne "pfexec" && !-e $self->{pfexec_cmd}) {
62         $self->print_to_server_and_die($action, "Can't execute PFEXEC-PATH '$self->{pfexec_cmd}' command",
63                         $Amanda::Script_App::ERROR);
64     }
65     if (!defined $self->{pfexec_cmd}) {
66         $self->{pfexec_cmd} = "";
67     }
68
69     # determine if $self->{device} is a mountpoint or ZFS dataset
70     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -H -o value mountpoint $self->{device}";
71     debug "running: $cmd";
72     my($wtr, $rdr, $err, $pid);
73     $err = Symbol::gensym;
74     $pid = open3($wtr, $rdr, $err, $cmd);
75     close $wtr;
76     my $zmountpoint = <$rdr>;
77     waitpid $pid, 0;
78     close $rdr;
79     close $err;
80
81     if ($? == 0) {
82         chomp $zmountpoint;
83
84         # zfs dataset supplied
85         $self->{filesystem} = $self->{device};
86
87         # check if zfs volume
88         if ($zmountpoint ne '-') {
89             $self->{mountpoint} = $zmountpoint;
90         } else {
91             $self->{mountpoint} = undef;
92         }
93     } else {
94         # filesystem, directory or invalid ZFS dataset name
95         $cmd = "$self->{df_path} $self->{device}";
96         debug "running: $self->{df_path} $self->{device}";
97         $err = Symbol::gensym;
98         $pid = open3($wtr, $rdr, $err, $cmd);
99         close $wtr;
100         my @ret;
101         while (<$rdr>) {
102             chomp;
103             push @ret,$_;
104         }
105         my $errmsg = <$err>;
106         waitpid $pid, 0;
107         close $rdr;
108         close $err;
109
110         if ($? != 0) {
111             my $ret = $ret[0];
112             # invalid filesystem of ZFS dataset name
113             if (defined $errmsg) {
114                 chomp $errmsg;
115             }
116             if (defined $ret && defined $errmsg) {
117                 $self->print_to_server_and_die($action, "$ret, $errmsg", $Amanda::Script_App::ERROR);
118             } elsif (defined $ret) {
119                 $self->print_to_server_and_die($action, $ret, $Amanda::Script_App::ERROR);
120             } elsif (defined $errmsg) {
121                 $self->print_to_server_and_die($action, $errmsg, $Amanda::Script_App::ERROR);
122             } else {
123                 $self->print_to_server_and_die($action,
124                             "Failed to find mount points: $self->{device}",
125                             $Amanda::Script_App::ERROR);
126             }
127         }
128
129         my $size = @ret;
130         if ($size eq 1) {
131             # Solaris type df
132             @ret = split /:/, $ret[0];
133             if ($ret[0] =~ /(\S*)(\s*)(\()(\S*)(\s*)(\))$/) {
134                 $self->{mountpoint} = $1;
135                 $self->{filesystem} = $4;
136             } else {
137                 $self->print_to_server_and_die($action,
138                             "Failed to find mount points: $self->{device}",
139                             $Amanda::Script_App::ERROR);
140             }
141         } else {
142             # FreeBSD type df with header
143             if ($ret[1] =~ /^(\S+)(\s+)((\S+)(\s+))+(\S+)(\s*)$/) {
144                 $self->{mountpoint} = $6;
145                 $self->{filesystem} = $1;
146             } else {
147                 $self->print_to_server_and_die($action,
148                             "Failed to find mount points: $self->{device}",
149                             $Amanda::Script_App::ERROR);
150             }
151         }
152
153         $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -H -o value mountpoint $self->{filesystem}";
154         debug "running: $cmd|";
155         $err = Symbol::gensym;
156         $pid = open3($wtr, $rdr, $err, $cmd);
157         close $wtr;
158         $zmountpoint = <$rdr>;
159         chomp $zmountpoint;
160         $errmsg = <$err>;
161         waitpid $pid, 0;
162         close $rdr;
163         close $err;
164
165         if ($? != 0) {
166             if (defined $errmsg) {
167                 chomp $errmsg;
168             }
169             if (defined $zmountpoint && defined $errmsg) {
170                 $self->print_to_server_and_die($action, $zmountpoint, $errmsg, $Amanda::Script_App::ERROR);
171             } elsif (defined $zmountpoint) {
172                 $self->print_to_server_and_die($action, $zmountpoint, $Amanda::Script_App::ERROR);
173             } elsif (defined $errmsg) {
174                 $self->print_to_server_and_die($action, $errmsg, $Amanda::Script_App::ERROR);
175             } else {
176                 $self->print_to_server_and_die($action,
177                         "Failed to find mount points: $self->{filesystem}",
178                         $Amanda::Script_App::ERROR);
179             }
180         }
181         if ($zmountpoint ne 'legacy' && $zmountpoint ne $self->{mountpoint}) {
182             $self->print_to_server_and_die($action,
183                 "mountpoint from 'df' ($self->{mountpoint}) and 'zfs list' ($zmountpoint) differ",
184                 $Amanda::Script_App::ERROR);
185         }
186
187         if (!($self->{device} =~ /^$self->{mountpoint}/)) {
188             $self->print_to_server_and_die($action,
189                 "mountpoint '$self->{mountpoint}' is not a prefix of diskdevice '$self->{device}'",
190                 $Amanda::Script_App::ERROR);
191         }
192
193     }
194
195     if ($action eq 'check') {
196       $self->{snapshot} = $self->zfs_build_snapshotname($self->{device}, -1);
197     } else {
198       $self->{snapshot} = $self->zfs_build_snapshotname($self->{device});
199     }
200     if (defined $self->{mountpoint}) {
201         if ($self->{device} =~ /^$self->{mountpoint}/) {
202             $self->{dir} = $self->{device};
203             $self->{dir} =~ s,^$self->{mountpoint},,;
204             $self->{directory} = $self->{mountpoint} . "/.zfs/snapshot/" .
205                                  $self->{snapshot} . $self->{dir};
206         } else { # device is not the mountpoint
207             $self->{directory} = $self->{mountpoint} . "/.zfs/snapshot/" .
208                                  $self->{snapshot};
209         }
210     }
211 }
212
213 sub zfs_create_snapshot {
214     my $self = shift;
215     my $action = 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($action, "$msg, $errmsg", $Amanda::Script_App::ERROR);
231         } elsif (defined $msg) {
232             $self->print_to_server_and_die($action, $msg, $Amanda::Script_App::ERROR);
233         } elsif (defined $errmsg) {
234             $self->print_to_server_and_die($action, $errmsg, $Amanda::Script_App::ERROR);
235         } else {
236             $self->print_to_server_and_die($action, "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     my $action = 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($action, "$msg, $errmsg", $Amanda::Script_App::ERROR);
260         } elsif (defined $msg) {
261             $self->print_to_server_and_die($action, $msg, $Amanda::Script_App::ERROR);
262         } elsif (defined $errmsg) {
263             $self->print_to_server_and_die($action, $errmsg, $Amanda::Script_App::ERROR);
264         } else {
265             $self->print_to_server_and_die($action, "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     my $action = shift;
274
275     my $snapshotname = $self->zfs_find_snapshot_level($level);
276     debug "zfs_destroy_snapshot_level: Current $snapshotname";
277     if ($snapshotname ne "") {
278       my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} destroy  $self->{filesystem}\@$snapshotname";
279       debug "running: $cmd|";
280       my($wtr, $rdr, $err, $pid);
281       my($msg, $errmsg);
282       $err = Symbol::gensym;
283       $pid = open3($wtr, $rdr, $err, $cmd);
284       close $wtr;
285       $msg = <$rdr>;
286       $errmsg = <$err>;
287       waitpid $pid, 0;
288       close $rdr;
289       close $err;
290       if( $? != 0 ) {
291           if(defined $msg && defined $errmsg) {
292               $self->print_to_server_and_die($action, "$msg, $errmsg", $Amanda::Script_App::ERROR);
293           } elsif (defined $msg) {
294               $self->print_to_server_and_die($action, $msg, $Amanda::Script_App::ERROR);
295           } elsif (defined $errmsg) {
296               $self->print_to_server_and_die($action, $errmsg, $Amanda::Script_App::ERROR);
297           } else {
298               $self->print_to_server_and_die($action, "cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
299           }
300       }
301     }
302 }
303
304 sub zfs_rename_snapshot {
305     my $self = shift;
306     my $level = shift;
307     my $action = shift;
308
309     my $newsnapshotname = $self->zfs_build_snapshotname($self->{device}, $level);
310     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} rename $self->{filesystem}\@$self->{snapshot} $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($action, "$msg, $errmsg", $Amanda::Script_App::ERROR);
325         } elsif (defined $msg) {
326             $self->print_to_server_and_die($action, $msg, $Amanda::Script_App::ERROR);
327         } elsif (defined $errmsg) {
328             $self->print_to_server_and_die($action, $errmsg, $Amanda::Script_App::ERROR);
329         } else {
330             $self->print_to_server_and_die($action, "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     my $action = shift;
340
341     my $level;
342     for ($level = $maxlevel; $level >= $minlevel; $level--) {
343         debug "zfs_purge_snapshot: Check for existing snapshot at level $level";
344         $self->zfs_destroy_snapshot_level($level, $action);
345     }
346 }
347
348 sub zfs_find_snapshot_level {
349     my $self = shift;
350     my $level = shift;
351     my $action = shift;
352
353     my $snapshotname = $self->zfs_build_snapshotname($self->{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     my $action = shift;
378
379     my $snapshotname = "";
380
381     if (!defined $level) {
382       $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-current";
383     } else {
384       if ($level < 0) {
385         $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-check";
386       } else {
387         $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-" . $level;
388       }
389     } 
390
391     return $snapshotname;
392 }
393
394 1;