1 # Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
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.
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
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
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
20 package Amanda::Application::Zfs;
26 use Amanda::Debug qw( :logging );
30 Amanda::Application::Zfs -- collection of function to use with zfs
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);
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);
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);
53 if ($self->{pfexec} =~ /^YES$/i) {
54 $self->{pfexec_cmd} = $self->{pfexec_path};
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);
60 if (!defined $self->{pfexec_cmd}) {
61 $self->{pfexec_cmd} = "";
64 if (!defined $self->{device}) {
65 if ($self->{action} eq "check") {
68 $self->print_to_server_and_die("'--device' is not provided",
69 $Amanda::Script_App::ERROR);
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);
82 my $zmountpoint = <$rdr>;
90 # zfs dataset supplied
91 $self->{filesystem} = $device;
94 if ($zmountpoint ne '-') {
95 $self->{mountpoint} = $zmountpoint;
97 $self->{mountpoint} = undef;
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);
118 # invalid filesystem of ZFS dataset name
119 if (defined $errmsg) {
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);
129 $self->print_to_server_and_die(
130 "Failed to find mount points: $device",
131 $Amanda::Script_App::ERROR);
138 @ret = split /:/, $ret[0];
139 if ($ret[0] =~ /(\S*)(\s*)(\()(\S*)(\s*)(\))$/) {
140 $self->{mountpoint} = $1;
141 $self->{filesystem} = $4;
143 $self->print_to_server_and_die(
144 "Failed to find mount points: $device",
145 $Amanda::Script_App::ERROR);
148 # FreeBSD type df with header
149 if ($ret[1] =~ /^(\S+)(\s+)((\S+)(\s+))+(\S+)(\s*)$/) {
150 $self->{mountpoint} = $6;
151 $self->{filesystem} = $1;
153 $self->print_to_server_and_die(
154 "Failed to find mount points: $device",
155 $Amanda::Script_App::ERROR);
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);
164 $zmountpoint = <$rdr>;
172 if (defined $errmsg) {
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);
182 $self->print_to_server_and_die(
183 "Failed to find mount points: $self->{filesystem}",
184 $Amanda::Script_App::ERROR);
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);
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);
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/" .
215 sub zfs_create_snapshot {
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);
225 my ($errmsg) = <$err>;
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);
237 $self->print_to_server_and_die("cannot create snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
242 sub zfs_destroy_snapshot {
245 my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} destroy $self->{filesystem}\@$self->{snapshot}";
246 debug "running: $cmd|";
247 my($wtr, $rdr, $err, $pid);
249 $err = Symbol::gensym;
250 $pid = open3($wtr, $rdr, $err, $cmd);
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);
265 $self->print_to_server_and_die("cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
270 sub zfs_destroy_snapshot_level {
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);
281 $err = Symbol::gensym;
282 $pid = open3($wtr, $rdr, $err, $cmd);
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);
297 $self->print_to_server_and_die("cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
303 sub zfs_rename_snapshot {
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);
314 $err = Symbol::gensym;
315 $pid = open3($wtr, $rdr, $err, $cmd);
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);
330 $self->print_to_server_and_die("cannot rename snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
335 sub zfs_purge_snapshot {
337 my $minlevel = shift;
338 my $maxlevel = shift;
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);
347 sub zfs_find_snapshot_level {
351 my $device = $self->{device};
352 $device = $self->{directory} if defined $self->{directory};
353 my $snapshotname = $self->zfs_build_snapshotname($device, $level);
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);
359 $err = Symbol::gensym;
360 $pid = open3($wtr, $rdr, $err, $cmd);
370 return $snapshotname;
373 sub zfs_build_snapshotname {
378 my $snapshotname = "";
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";
385 $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-" . $level;
388 return $snapshotname;