1 # Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved.
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.
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 Mathlida Ave, Suite 300
17 # Sunnyvale, CA 94086, 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
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);
45 if (!defined $self->{device}) {
46 $self->print_to_server_and_die($action, "'--device' is not provided",
47 $Amanda::Script_App::ERROR);
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);
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);
58 if ($self->{pfexec} =~ /^YES$/i) {
59 $self->{pfexec_cmd} = $self->{pfexec_path};
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);
65 if (!defined $self->{pfexec_cmd}) {
66 $self->{pfexec_cmd} = "";
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);
76 my $zmountpoint = <$rdr>;
84 # zfs dataset supplied
85 $self->{filesystem} = $self->{device};
88 if ($zmountpoint ne '-') {
89 $self->{mountpoint} = $zmountpoint;
91 $self->{mountpoint} = undef;
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);
112 # invalid filesystem of ZFS dataset name
113 if (defined $errmsg) {
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);
123 $self->print_to_server_and_die($action,
124 "Failed to find mount points: $self->{device}",
125 $Amanda::Script_App::ERROR);
132 @ret = split /:/, $ret[0];
133 if ($ret[0] =~ /(\S*)(\s*)(\()(\S*)(\s*)(\))$/) {
134 $self->{mountpoint} = $1;
135 $self->{filesystem} = $4;
137 $self->print_to_server_and_die($action,
138 "Failed to find mount points: $self->{device}",
139 $Amanda::Script_App::ERROR);
142 # FreeBSD type df with header
143 if ($ret[1] =~ /^(\S+)(\s+)((\S+)(\s+))+(\S+)(\s*)$/) {
144 $self->{mountpoint} = $6;
145 $self->{filesystem} = $1;
147 $self->print_to_server_and_die($action,
148 "Failed to find mount points: $self->{device}",
149 $Amanda::Script_App::ERROR);
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);
158 $zmountpoint = <$rdr>;
166 if (defined $errmsg) {
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);
176 $self->print_to_server_and_die($action,
177 "Failed to find mount points: $self->{filesystem}",
178 $Amanda::Script_App::ERROR);
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);
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);
195 if ($action eq 'check') {
196 $self->{snapshot} = $self->zfs_build_snapshotname($self->{device}, -1);
198 $self->{snapshot} = $self->zfs_build_snapshotname($self->{device});
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/" .
213 sub zfs_create_snapshot {
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);
224 my ($errmsg) = <$err>;
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);
236 $self->print_to_server_and_die($action, "cannot create snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
241 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($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);
265 $self->print_to_server_and_die($action, "cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
270 sub zfs_destroy_snapshot_level {
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);
282 $err = Symbol::gensym;
283 $pid = open3($wtr, $rdr, $err, $cmd);
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);
298 $self->print_to_server_and_die($action, "cannot destroy snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
304 sub zfs_rename_snapshot {
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);
314 $err = Symbol::gensym;
315 $pid = open3($wtr, $rdr, $err, $cmd);
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);
330 $self->print_to_server_and_die($action, "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;
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);
348 sub zfs_find_snapshot_level {
353 my $snapshotname = $self->zfs_build_snapshotname($self->{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 {
379 my $snapshotname = "";
381 if (!defined $level) {
382 $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($device) . "-current";
385 $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($device) . "-check";
387 $snapshotname = "amanda-" . Amanda::Util::sanitise_filename($device) . "-" . $level;
391 return $snapshotname;