Imported Upstream version 3.2.0
[debian/amanda] / perl / Amanda / Application / Zfs.pm
1 # Copyright (c) 2008, 2009, 2010 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     if ($self->{action} eq 'check') {
201       $self->{snapshot} = $self->zfs_build_snapshotname($device, -1);
202     } else {
203       $self->{snapshot} = $self->zfs_build_snapshotname($device);
204     }
205     if (defined $self->{mountpoint}) {
206         if ($device =~ /^$self->{mountpoint}/) {
207             $self->{dir} = $device;
208             $self->{dir} =~ s,^$self->{mountpoint},,;
209             $self->{directory} = $self->{mountpoint} . "/.zfs/snapshot/" .
210                                  $self->{snapshot} . $self->{dir};
211         } else { # device is not the mountpoint
212             $self->{directory} = $self->{mountpoint} . "/.zfs/snapshot/" .
213                                  $self->{snapshot};
214         }
215     }
216 }
217
218 sub zfs_create_snapshot {
219     my $self = shift;
220
221     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} snapshot $self->{filesystem}\@$self->{snapshot}";
222     debug "running: $cmd";
223     my($wtr, $rdr, $err, $pid);
224     $err = Symbol::gensym;
225     $pid = open3($wtr, $rdr, $err, $cmd);
226     close $wtr;
227     my ($msg) = <$rdr>;
228     my ($errmsg) = <$err>;
229     waitpid $pid, 0;
230     close $rdr;
231     close $err;
232     if( $? != 0 ) {
233         if(defined $msg && defined $errmsg) {
234             $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
235         } elsif (defined $msg) {
236             $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
237         } elsif (defined $errmsg) {
238             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
239         } else {
240             $self->print_to_server_and_die("cannot create snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
241         }
242     }
243 }
244
245 sub zfs_destroy_snapshot {
246     my $self = shift;
247
248     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} destroy $self->{filesystem}\@$self->{snapshot}";
249     debug "running: $cmd|";
250     my($wtr, $rdr, $err, $pid);
251     my($msg, $errmsg);
252     $err = Symbol::gensym;
253     $pid = open3($wtr, $rdr, $err, $cmd);
254     close $wtr;
255     $msg = <$rdr>;
256     $errmsg = <$err>;
257     waitpid $pid, 0;
258     close $rdr;
259     close $err;
260     if( $? != 0 ) {
261         if(defined $msg && defined $errmsg) {
262             $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
263         } elsif (defined $msg) {
264             $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
265         } elsif (defined $errmsg) {
266             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
267         } else {
268             $self->print_to_server_and_die("cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
269         }
270     }
271 }
272
273 sub zfs_destroy_snapshot_level {
274     my $self = shift;
275     my $level = shift;
276
277     my $snapshotname = $self->zfs_find_snapshot_level($level);
278     debug "zfs_destroy_snapshot_level: Current $snapshotname";
279     if ($snapshotname ne "") {
280       my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} destroy  $self->{filesystem}\@$snapshotname";
281       debug "running: $cmd|";
282       my($wtr, $rdr, $err, $pid);
283       my($msg, $errmsg);
284       $err = Symbol::gensym;
285       $pid = open3($wtr, $rdr, $err, $cmd);
286       close $wtr;
287       $msg = <$rdr>;
288       $errmsg = <$err>;
289       waitpid $pid, 0;
290       close $rdr;
291       close $err;
292       if( $? != 0 ) {
293           if(defined $msg && defined $errmsg) {
294               $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
295           } elsif (defined $msg) {
296               $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
297           } elsif (defined $errmsg) {
298               $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
299           } else {
300               $self->print_to_server_and_die("cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
301           }
302       }
303     }
304 }
305
306 sub zfs_rename_snapshot {
307     my $self = shift;
308     my $level = shift;
309
310     my $device = $self->{device};
311     $device = $self->{directory} if defined $self->{directory};
312     my $newsnapshotname = $self->zfs_build_snapshotname($device, $level);
313     my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} rename $self->{filesystem}\@$self->{snapshot} $newsnapshotname";
314     debug "running: $cmd|";
315     my($wtr, $rdr, $err, $pid);
316     my($msg, $errmsg);
317     $err = Symbol::gensym;
318     $pid = open3($wtr, $rdr, $err, $cmd);
319     close $wtr;
320     $msg = <$rdr>;
321     $errmsg = <$err>;
322     waitpid $pid, 0;
323     close $rdr;
324     close $err;
325     if( $? != 0 ) {
326         if(defined $msg && defined $errmsg) {
327             $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
328         } elsif (defined $msg) {
329             $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
330         } elsif (defined $errmsg) {
331             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
332         } else {
333             $self->print_to_server_and_die("cannot rename snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
334         }
335     }
336 }
337
338 sub zfs_purge_snapshot {
339     my $self = shift;
340     my $minlevel = shift;
341     my $maxlevel = shift;
342
343     my $level;
344     for ($level = $maxlevel; $level >= $minlevel; $level--) {
345         debug "zfs_purge_snapshot: Check for existing snapshot at level $level";
346         $self->zfs_destroy_snapshot_level($level);
347     }
348 }
349
350 sub zfs_find_snapshot_level {
351     my $self = shift;
352     my $level = shift;
353
354     my $device = $self->{device};
355     $device = $self->{directory} if defined $self->{directory};
356     my $snapshotname = $self->zfs_build_snapshotname($device, $level);
357
358     my $cmd =  "$self->{pfexec_cmd} $self->{zfs_path} list -t snapshot $self->{filesystem}\@$snapshotname";
359     debug "running: $cmd|";
360     my($wtr, $rdr, $err, $pid);
361     my($msg, $errmsg);
362     $err = Symbol::gensym;
363     $pid = open3($wtr, $rdr, $err, $cmd);
364     close $wtr;
365     $msg = <$rdr>;
366     $errmsg = <$err>;
367     waitpid $pid, 0;
368     close $rdr;
369     close $err;
370     if( $? != 0 ) {
371         return "";
372     }
373     return $snapshotname;
374 }
375
376 sub zfs_build_snapshotname {
377     my $self = shift;
378     my $device = shift;
379     my $level = shift;
380
381     my $snapshotname = "";
382
383     if (!defined $level) {
384       $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-current";
385     } else {
386       if ($level < 0) {
387         $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-check";
388       } else {
389         $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-" . $level;
390       }
391     } 
392
393     return $snapshotname;
394 }
395
396 1;