1 # Copyright (c) 2008, 2009, 2010 Zmanda, Inc. All Rights Reserved.
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.
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
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
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
19 package Amanda::Application::Zfs;
25 use Amanda::Debug qw( :logging );
29 Amanda::Application::Zfs -- collection of function to use with zfs
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);
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);
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);
52 if ($self->{pfexec} =~ /^YES$/i) {
53 $self->{pfexec_cmd} = $self->{pfexec_path};
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);
59 if (!defined $self->{pfexec_cmd}) {
60 $self->{pfexec_cmd} = "";
63 if (!defined $self->{device}) {
64 if ($self->{action} eq "check") {
67 $self->print_to_server_and_die("'--device' is not provided",
68 $Amanda::Script_App::ERROR);
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);
81 my $zmountpoint = <$rdr>;
89 # zfs dataset supplied
90 $self->{filesystem} = $device;
93 if ($zmountpoint ne '-') {
94 $self->{mountpoint} = $zmountpoint;
96 $self->{mountpoint} = undef;
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);
117 # invalid filesystem of ZFS dataset name
118 if (defined $errmsg) {
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);
128 $self->print_to_server_and_die(
129 "Failed to find mount points: $device",
130 $Amanda::Script_App::ERROR);
137 @ret = split /:/, $ret[0];
138 if ($ret[0] =~ /(\S*)(\s*)(\()(\S*)(\s*)(\))$/) {
139 $self->{mountpoint} = $1;
140 $self->{filesystem} = $4;
142 $self->print_to_server_and_die(
143 "Failed to find mount points: $device",
144 $Amanda::Script_App::ERROR);
147 # FreeBSD type df with header
148 if ($ret[1] =~ /^(\S+)(\s+)((\S+)(\s+))+(\S+)(\s*)$/) {
149 $self->{mountpoint} = $6;
150 $self->{filesystem} = $1;
152 $self->print_to_server_and_die(
153 "Failed to find mount points: $device",
154 $Amanda::Script_App::ERROR);
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);
163 $zmountpoint = <$rdr>;
171 if (defined $errmsg) {
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);
181 $self->print_to_server_and_die(
182 "Failed to find mount points: $self->{filesystem}",
183 $Amanda::Script_App::ERROR);
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);
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);
200 if ($self->{action} eq 'check') {
201 $self->{snapshot} = $self->zfs_build_snapshotname($device, -1);
203 $self->{snapshot} = $self->zfs_build_snapshotname($device);
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/" .
218 sub zfs_create_snapshot {
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);
228 my ($errmsg) = <$err>;
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);
240 $self->print_to_server_and_die("cannot create snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
245 sub zfs_destroy_snapshot {
248 my $cmd = "$self->{pfexec_cmd} $self->{zfs_path} destroy $self->{filesystem}\@$self->{snapshot}";
249 debug "running: $cmd|";
250 my($wtr, $rdr, $err, $pid);
252 $err = Symbol::gensym;
253 $pid = open3($wtr, $rdr, $err, $cmd);
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);
268 $self->print_to_server_and_die("cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
273 sub zfs_destroy_snapshot_level {
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);
284 $err = Symbol::gensym;
285 $pid = open3($wtr, $rdr, $err, $cmd);
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);
300 $self->print_to_server_and_die("cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
306 sub zfs_rename_snapshot {
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);
317 $err = Symbol::gensym;
318 $pid = open3($wtr, $rdr, $err, $cmd);
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);
333 $self->print_to_server_and_die("cannot rename snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
338 sub zfs_purge_snapshot {
340 my $minlevel = shift;
341 my $maxlevel = shift;
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);
350 sub zfs_find_snapshot_level {
354 my $device = $self->{device};
355 $device = $self->{directory} if defined $self->{directory};
356 my $snapshotname = $self->zfs_build_snapshotname($device, $level);
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);
362 $err = Symbol::gensym;
363 $pid = open3($wtr, $rdr, $err, $cmd);
373 return $snapshotname;
376 sub zfs_build_snapshotname {
381 my $snapshotname = "";
383 if (!defined $level) {
384 $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-current";
387 $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-check";
389 $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($self->{disk}) . "-" . $level;
393 return $snapshotname;