2 # Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
21 use lib '@amperldir@';
26 package Amanda::Application::Amzfs_sendrecv;
27 use base qw(Amanda::Application Amanda::Application::Zfs);
33 use Amanda::Constants;
34 use Amanda::Config qw( :init :getconf config_dir_relative );
35 use Amanda::Debug qw( :logging );
37 use Amanda::Util qw( :constants quote_string );
41 my ($config, $host, $disk, $device, $level, $index, $message, $collection, $record, $df_path, $zfs_path, $pfexec_path, $pfexec, $keep_snapshot, $exclude_list, $include_list, $directory) = @_;
42 my $self = $class->SUPER::new($config);
44 $self->{config} = $config;
45 $self->{host} = $host;
47 $self->{disk} = $disk;
49 $self->{disk} = $device;
51 if (defined $device) {
52 $self->{device} = $device;
54 $self->{device} = $disk;
56 $self->{level} = [ @{$level} ];
57 $self->{index} = $index;
58 $self->{message} = $message;
59 $self->{collection} = $collection;
60 $self->{record} = $record;
61 $self->{df_path} = $df_path;
62 $self->{zfs_path} = $zfs_path;
63 $self->{pfexec_path} = $pfexec_path;
64 $self->{pfexec} = $pfexec;
65 $self->{keep_snapshot} = $keep_snapshot;
66 $self->{pfexec_cmd} = undef;
67 $self->{exclude_list} = [ @{$exclude_list} ];
68 $self->{include_list} = [ @{$include_list} ];
69 $self->{directory} = $directory;
71 if ($self->{keep_snapshot} =~ /^YES$/i) {
72 $self->{keep_snapshot} = "YES";
73 if (!defined $self->{record}) {
74 $self->{keep_snapshot} = "NO";
81 sub check_for_backup_failure {
84 $self->zfs_destroy_snapshot();
93 print "MAX-LEVEL 9\n";
94 print "INDEX-LINE YES\n";
95 print "INDEX-XML NO\n";
96 print "MESSAGE-LINE YES\n";
97 print "MESSAGE-XML NO\n";
99 print "COLLECTION NO\n";
100 print "CLIENT-ESTIMATE YES\n";
103 sub command_selfcheck {
106 $self->print_to_server("disk " . quote_string($self->{disk}),
107 $Amanda::Script_App::GOOD);
109 $self->print_to_server("amzfs-sendrecv version " . $Amanda::Constants::VERSION,
110 $Amanda::Script_App::GOOD);
111 $self->zfs_set_value();
113 if (!defined $self->{device}) {
117 if ($self->{error_status} == $Amanda::Script_App::GOOD) {
118 $self->zfs_create_snapshot();
119 $self->zfs_destroy_snapshot();
120 print "OK " . $self->{device} . "\n";
123 if ($#{$self->{include_list}} >= 0) {
124 $self->print_to_server("include-list not supported for backup",
125 $Amanda::Script_App::ERROR);
127 if ($#{$self->{exclude_list}} >= 0) {
128 $self->print_to_server("exclude-list not supported for backup",
129 $Amanda::Script_App::ERROR);
133 sub command_estimate() {
137 if ($#{$self->{include_list}} >= 0) {
138 $self->print_to_server("include-list not supported for backup",
139 $Amanda::Script_App::ERROR);
141 if ($#{$self->{exclude_list}} >= 0) {
142 $self->print_to_server("exclude-list not supported for backup",
143 $Amanda::Script_App::ERROR);
146 $self->zfs_set_value();
147 $self->zfs_create_snapshot();
149 while (defined ($level = shift @{$self->{level}})) {
150 debug "Estimate of level $level";
151 my $size = $self->estimate_snapshot($level);
152 output_size($level, $size);
155 $self->zfs_destroy_snapshot();
164 print "$level -1 -1\n";
168 my($ksize) = int $size / (1024);
169 $ksize=32 if ($ksize<32);
170 print "$level $ksize 1\n";
177 if ($#{$self->{include_list}} >= 0) {
178 $self->print_to_server("include-list not supported for backup",
179 $Amanda::Script_App::ERROR);
181 if ($#{$self->{exclude_list}} >= 0) {
182 $self->print_to_server("exclude-list not supported for backup",
183 $Amanda::Script_App::ERROR);
186 $self->zfs_set_value();
187 $self->zfs_create_snapshot();
190 my $level = $self->{level}[0];
192 debug "Backup of level $level";
194 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} send $self->{filesystem}\@$self->{snapshot} | $Amanda::Paths::amlibexecdir/teecount";
196 my $refsnapshotname = $self->zfs_find_snapshot_level($level-1);
197 debug "Referenced snapshot name: $refsnapshotname|";
198 if ($refsnapshotname ne "") {
199 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} send -i $self->{filesystem}\@$refsnapshotname $self->{filesystem}\@$self->{snapshot} | $Amanda::Paths::amlibexecdir/teecount";
201 $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);
205 debug "running (backup): $cmd";
206 my($wtr, $err, $pid);
208 $err = Symbol::gensym;
209 $pid = open3($wtr, '>&STDOUT', $err, $cmd);
212 if (defined($self->{index})) {
214 open($indexout, '>&=4') ||
215 $self->print_to_server_and_die("Can't open indexout: $!",
216 $Amanda::Script_App::ERROR);
217 print $indexout "/\n";
225 if (defined $errmsg) {
226 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
228 $self->print_to_server_and_die("cannot backup snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
234 my($ksize) = int ($size/1024);
235 $ksize=32 if ($ksize<32);
237 print {$self->{mesgout}} "sendbackup: size $ksize\n";
238 print {$self->{mesgout}} "sendbackup: end\n";
240 # destroy all snapshot of this level and higher
241 $self->zfs_purge_snapshot($level, 9);
243 if ($self->{keep_snapshot} eq 'YES') {
244 $self->zfs_rename_snapshot($level);
246 $self->zfs_destroy_snapshot();
252 sub estimate_snapshot
257 debug "\$filesystem = $self->{filesystem}";
258 debug "\$snapshot = $self->{snapshot}";
259 debug "\$level = $level";
263 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -Hp -o value referenced $self->{filesystem}\@$self->{snapshot}";
265 my $refsnapshotname = $self->zfs_find_snapshot_level($level-1);
266 debug "Referenced snapshot name: $refsnapshotname|";
267 if ($refsnapshotname ne "") {
268 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} send -i $refsnapshotname $self->{filesystem}\@$self->{snapshot} | /usr/bin/wc -c";
273 debug "running (estimate): $cmd";
274 my($wtr, $rdr, $err, $pid);
275 $err = Symbol::gensym;
276 $pid = open3($wtr, $rdr, $err, $cmd);
279 my ($errmsg) = <$err>;
284 if (defined $msg && defined $errmsg) {
285 $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
286 } elsif (defined $msg) {
287 $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
288 } elsif (defined $errmsg) {
289 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
291 $self->print_to_server_and_die("cannot estimate snapshot '$self->{snapshot}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
295 my $compratio = $self->get_compratio();
308 $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -Hp -o value compressratio $self->{filesystem}\@$self->{snapshot}";
309 debug "running (get-compression): $cmd";
310 my($wtr, $rdr, $err, $pid);
311 $err = Symbol::gensym;
312 $pid = open3($wtr, $rdr, $err, $cmd);
315 my ($errmsg) = <$err>;
320 if (defined $msg && defined $errmsg) {
321 $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
322 } elsif (defined $msg) {
323 $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
324 } elsif (defined $errmsg) {
325 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
327 $self->print_to_server_and_die("cannot read compression ratio '$self->{snapshot}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
333 sub command_index_from_output {
336 sub command_index_from_image {
339 sub command_restore {
342 my $current_snapshot;
343 my $level = $self->{level}[0];
344 my $device = $self->{device};
345 if (defined $device) {
347 $current_snapshot = $self->zfs_build_snapshotname($device);
348 $self->{'snapshot'} = $self->zfs_build_snapshotname($device, $level);
351 my $directory = $device;
352 $directory = $self->{directory} if defined $self->{directory};
353 $directory =~ s,^/,,;
357 if ($self->{pfexec_cmd}) {
358 push @cmd, $self->{pfexec_cmd};
360 push @cmd, $self->{zfs_path};
362 push @cmd, $directory;
364 debug("cmd:" . join(" ", @cmd));
369 if (defined $device) {
370 $snapshotname = "$directory\@$current_snapshot";
371 $newsnapname = "$directory\@$self->{'snapshot'}";
375 if ($self->{pfexec_cmd}) {
376 push @cmd, $self->{pfexec_cmd};
378 push @cmd, $self->{zfs_path};
382 push @cmd, "snapshot";
383 push @cmd, $directory;
384 debug("cmd:" . join(" ", @cmd));
386 my($wtr, $rdr, $err, $pid);
388 $err = Symbol::gensym;
389 $pid = open3($wtr, $rdr, $err, @cmd);
391 while ($msg = <$rdr>) {
392 next if $msg =~ /^NAME/;
393 my ($name, $used, $avail) = split(/ +/, $msg);
394 if ($name =~ /-current$/) {
395 $snapshotname = $name;
404 if (defined $snapshotname and defined($level)) {
405 $newsnapname = $snapshotname;
406 $newsnapname =~ s/current$/$level/;
408 # destroy the snapshot
409 # restoring next level will fail.
411 if ($self->{pfexec_cmd}) {
412 push @cmd, $self->{pfexec_cmd};
414 push @cmd, $self->{zfs_path};
415 push @cmd, "destroy";
416 push @cmd, $snapshotname;
418 debug("cmd:" . join(" ", @cmd));
423 if (defined $newsnapname) {
424 # rename -current snapshot to -level
426 if ($self->{pfexec_cmd}) {
427 push @cmd, $self->{pfexec_cmd};
429 push @cmd, $self->{zfs_path};
431 push @cmd, $snapshotname;
432 push @cmd, $newsnapname;
434 debug("cmd:" . join(" ", @cmd));
439 sub command_print_command {
446 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>.
461 my $zfs_path = 'zfs';
462 my $pfexec_path = 'pfexec';
464 my $opt_keep_snapshot = "YES";
465 my @opt_exclude_list;
466 my @opt_include_list;
469 Getopt::Long::Configure(qw{bundling});
471 'config=s' => \$opt_config,
472 'host=s' => \$opt_host,
473 'disk=s' => \$opt_disk,
474 'device=s' => \$opt_device,
475 'level=s' => \@opt_level,
476 'index=s' => \$opt_index,
477 'message=s' => \$opt_message,
478 'collection=s' => \$opt_collection,
479 'record' => \$opt_record,
480 'df-path=s' => \$df_path,
481 'zfs-path=s' => \$zfs_path,
482 'pfexec-path=s' => \$pfexec_path,
483 'pfexec=s' => \$pfexec,
484 'keep-snapshot=s' => \$opt_keep_snapshot,
485 'exclude-list=s' => \@opt_exclude_list,
486 'include-list=s' => \@opt_include_list,
487 'directory=s' => \$opt_directory,
490 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);
492 $application->do($ARGV[0]);