2 # Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
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 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
25 package Amanda::Application::Amzfs_sendrecv;
26 use base qw(Amanda::Application Amanda::Application::Zfs);
32 use Amanda::Constants;
33 use Amanda::Config qw( :init :getconf config_dir_relative );
34 use Amanda::Debug qw( :logging );
36 use Amanda::Util qw( :constants quote_string );
40 my ($config, $host, $disk, $device, $level, $index, $message, $collection, $record, $df_path, $zfs_path, $pfexec_path, $pfexec, $keep_snapshot, $exclude_list, $include_list, $directory) = @_;
41 my $self = $class->SUPER::new($config);
43 $self->{config} = $config;
44 $self->{host} = $host;
46 $self->{disk} = $disk;
48 $self->{disk} = $device;
50 if (defined $device) {
51 $self->{device} = $device;
53 $self->{device} = $disk;
55 $self->{level} = [ @{$level} ];
56 $self->{index} = $index;
57 $self->{message} = $message;
58 $self->{collection} = $collection;
59 $self->{record} = $record;
60 $self->{df_path} = $df_path;
61 $self->{zfs_path} = $zfs_path;
62 $self->{pfexec_path} = $pfexec_path;
63 $self->{pfexec} = $pfexec;
64 $self->{keep_snapshot} = $keep_snapshot;
65 $self->{pfexec_cmd} = undef;
66 $self->{exclude_list} = [ @{$exclude_list} ];
67 $self->{include_list} = [ @{$include_list} ];
68 $self->{directory} = $directory;
70 if ($self->{keep_snapshot} =~ /^YES$/i) {
71 $self->{keep_snapshot} = "YES";
72 if (!defined $self->{record}) {
73 $self->{keep_snapshot} = "NO";
80 sub check_for_backup_failure {
83 $self->zfs_destroy_snapshot();
92 print "MAX-LEVEL 9\n";
93 print "INDEX-LINE YES\n";
94 print "INDEX-XML NO\n";
95 print "MESSAGE-LINE YES\n";
96 print "MESSAGE-XML NO\n";
98 print "COLLECTION NO\n";
99 print "CLIENT-ESTIMATE YES\n";
102 sub command_selfcheck {
105 $self->print_to_server("disk " . quote_string($self->{disk}),
106 $Amanda::Script_App::GOOD);
108 $self->print_to_server("amzfs-sendrecv version " . $Amanda::Constants::VERSION,
109 $Amanda::Script_App::GOOD);
110 $self->zfs_set_value();
112 if (!defined $self->{device}) {
116 if ($self->{error_status} == $Amanda::Script_App::GOOD) {
117 $self->zfs_create_snapshot();
118 $self->zfs_destroy_snapshot();
119 print "OK " . $self->{device} . "\n";
122 if ($#{$self->{include_list}} >= 0) {
123 $self->print_to_server("include-list not supported for backup",
124 $Amanda::Script_App::ERROR);
126 if ($#{$self->{exclude_list}} >= 0) {
127 $self->print_to_server("exclude-list not supported for backup",
128 $Amanda::Script_App::ERROR);
132 sub command_estimate() {
136 if ($#{$self->{include_list}} >= 0) {
137 $self->print_to_server("include-list not supported for backup",
138 $Amanda::Script_App::ERROR);
140 if ($#{$self->{exclude_list}} >= 0) {
141 $self->print_to_server("exclude-list not supported for backup",
142 $Amanda::Script_App::ERROR);
145 $self->zfs_set_value();
146 $self->zfs_create_snapshot();
148 while (defined ($level = shift @{$self->{level}})) {
149 debug "Estimate of level $level";
150 my $size = $self->estimate_snapshot($level);
151 output_size($level, $size);
154 $self->zfs_destroy_snapshot();
163 print "$level -1 -1\n";
167 my($ksize) = int $size / (1024);
168 $ksize=32 if ($ksize<32);
169 print "$level $ksize 1\n";
176 if ($#{$self->{include_list}} >= 0) {
177 $self->print_to_server("include-list not supported for backup",
178 $Amanda::Script_App::ERROR);
180 if ($#{$self->{exclude_list}} >= 0) {
181 $self->print_to_server("exclude-list not supported for backup",
182 $Amanda::Script_App::ERROR);
185 $self->zfs_set_value();
186 $self->zfs_create_snapshot();
189 my $level = $self->{level}[0];
191 debug "Backup of level $level";
193 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} send $self->{filesystem}\@$self->{snapshot} | $Amanda::Paths::amlibexecdir/teecount";
195 my $refsnapshotname = $self->zfs_find_snapshot_level($level-1);
196 debug "Referenced snapshot name: $refsnapshotname|";
197 if ($refsnapshotname ne "") {
198 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} send -i $self->{filesystem}\@$refsnapshotname $self->{filesystem}\@$self->{snapshot} | $Amanda::Paths::amlibexecdir/teecount";
200 $self->print_to_server_and_die("cannot backup snapshot '$self->{filesystem}\@$self->{snapshot}': reference snapshot doesn't exists for level $level", $Amanda::Script_App::ERROR);
204 debug "running (backup): $cmd";
205 my($wtr, $err, $pid);
207 $err = Symbol::gensym;
208 $pid = open3($wtr, '>&STDOUT', $err, $cmd);
211 if (defined($self->{index})) {
213 open($indexout, '>&=4') ||
214 $self->print_to_server_and_die("Can't open indexout: $!",
215 $Amanda::Script_App::ERROR);
216 print $indexout "/\n";
224 if (defined $errmsg) {
225 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
227 $self->print_to_server_and_die("cannot backup snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
233 my($ksize) = int ($size/1024);
234 $ksize=32 if ($ksize<32);
236 print {$self->{mesgout}} "sendbackup: size $ksize\n";
237 print {$self->{mesgout}} "sendbackup: end\n";
239 # destroy all snapshot of this level and higher
240 $self->zfs_purge_snapshot($level, 9);
242 if ($self->{keep_snapshot} eq 'YES') {
243 $self->zfs_rename_snapshot($level);
245 $self->zfs_destroy_snapshot();
251 sub estimate_snapshot
256 debug "\$filesystem = $self->{filesystem}";
257 debug "\$snapshot = $self->{snapshot}";
258 debug "\$level = $level";
262 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -Hp -o value referenced $self->{filesystem}\@$self->{snapshot}";
264 my $refsnapshotname = $self->zfs_find_snapshot_level($level-1);
265 debug "Referenced snapshot name: $refsnapshotname|";
266 if ($refsnapshotname ne "") {
267 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} send -i $refsnapshotname $self->{filesystem}\@$self->{snapshot} | /usr/bin/wc -c";
272 debug "running (estimate): $cmd";
273 my($wtr, $rdr, $err, $pid);
274 $err = Symbol::gensym;
275 $pid = open3($wtr, $rdr, $err, $cmd);
278 my ($errmsg) = <$err>;
283 if (defined $msg && defined $errmsg) {
284 $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
285 } elsif (defined $msg) {
286 $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
287 } elsif (defined $errmsg) {
288 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
290 $self->print_to_server_and_die("cannot estimate snapshot '$self->{snapshot}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
294 my $compratio = $self->get_compratio();
307 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -Hp -o value compressratio $self->{filesystem}\@$self->{snapshot}";
308 debug "running (get-compression): $cmd";
309 my($wtr, $rdr, $err, $pid);
310 $err = Symbol::gensym;
311 $pid = open3($wtr, $rdr, $err, $cmd);
314 my ($errmsg) = <$err>;
319 if (defined $msg && defined $errmsg) {
320 $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
321 } elsif (defined $msg) {
322 $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
323 } elsif (defined $errmsg) {
324 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
326 $self->print_to_server_and_die("cannot read compression ratio '$self->{snapshot}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
332 sub command_index_from_output {
335 sub command_index_from_image {
338 sub command_restore {
341 my $current_snapshot;
342 my $level = $self->{level}[0];
343 my $device = $self->{device};
344 if (defined $device) {
346 $current_snapshot = $self->zfs_build_snapshotname($device);
347 $self->{'snapshot'} = $self->zfs_build_snapshotname($device, $level);
350 my $directory = $device;
351 $directory = $self->{directory} if defined $self->{directory};
352 $directory =~ s,^/,,;
356 if ($self->{pfexec_cmd}) {
357 push @cmd, $self->{pfexec_cmd};
359 push @cmd, $self->{zfs_path};
361 push @cmd, $directory;
363 debug("cmd:" . join(" ", @cmd));
368 if (defined $device) {
369 $snapshotname = "$directory\@$current_snapshot";
370 $newsnapname = "$directory\@$self->{'snapshot'}";
374 if ($self->{pfexec_cmd}) {
375 push @cmd, $self->{pfexec_cmd};
377 push @cmd, $self->{zfs_path};
381 push @cmd, "snapshot";
382 push @cmd, $directory;
383 debug("cmd:" . join(" ", @cmd));
385 my($wtr, $rdr, $err, $pid);
387 $err = Symbol::gensym;
388 $pid = open3($wtr, $rdr, $err, @cmd);
390 while ($msg = <$rdr>) {
391 next if $msg =~ /^NAME/;
392 my ($name, $used, $avail) = split(/ +/, $msg);
393 if ($name =~ /-current$/) {
394 $snapshotname = $name;
403 if (defined $snapshotname and defined($level)) {
404 $newsnapname = $snapshotname;
405 $newsnapname =~ s/current$/$level/;
407 # destroy the snapshot
408 # restoring next level will fail.
410 if ($self->{pfexec_cmd}) {
411 push @cmd, $self->{pfexec_cmd};
413 push @cmd, $self->{zfs_path};
414 push @cmd, "destroy";
415 push @cmd, $snapshotname;
417 debug("cmd:" . join(" ", @cmd));
422 if (defined $newsnapname) {
423 # rename -current snapshot to -level
425 if ($self->{pfexec_cmd}) {
426 push @cmd, $self->{pfexec_cmd};
428 push @cmd, $self->{zfs_path};
430 push @cmd, $snapshotname;
431 push @cmd, $newsnapname;
433 debug("cmd:" . join(" ", @cmd));
438 sub command_print_command {
445 Usage: amzfs-sendrecv <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --df-path=<path/to/df> --zfs-path=<path/to/zfs> --pfexec-path=<path/to/pfexec> --pfexec=<yes|no> --keep-snapshot=<yes|no>.
460 my $zfs_path = 'zfs';
461 my $pfexec_path = 'pfexec';
463 my $opt_keep_snapshot = "YES";
464 my @opt_exclude_list;
465 my @opt_include_list;
468 Getopt::Long::Configure(qw{bundling});
470 'config=s' => \$opt_config,
471 'host=s' => \$opt_host,
472 'disk=s' => \$opt_disk,
473 'device=s' => \$opt_device,
474 'level=s' => \@opt_level,
475 'index=s' => \$opt_index,
476 'message=s' => \$opt_message,
477 'collection=s' => \$opt_collection,
478 'record' => \$opt_record,
479 'df-path=s' => \$df_path,
480 'zfs-path=s' => \$zfs_path,
481 'pfexec-path=s' => \$pfexec_path,
482 'pfexec=s' => \$pfexec,
483 'keep-snapshot=s' => \$opt_keep_snapshot,
484 'exclude-list=s' => \@opt_exclude_list,
485 'include-list=s' => \@opt_include_list,
486 'directory=s' => \$opt_directory,
489 my $application = Amanda::Application::Amzfs_sendrecv->new($opt_config, $opt_host, $opt_disk, $opt_device, \@opt_level, $opt_index, $opt_message, $opt_collection, $opt_record, $df_path, $zfs_path, $pfexec_path, $pfexec, $opt_keep_snapshot, \@opt_exclude_list, \@opt_include_list, $opt_directory);
491 $application->do($ARGV[0]);