Imported Upstream version 3.3.3
[debian/amanda] / application-src / amzfs-sendrecv.pl
1 #!@PERL@
2 # Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
3 #
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.
8 #
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
12 # for more details.
13 #
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
17 #
18 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20
21 use lib '@amperldir@';
22 use strict;
23 use warnings;
24 use Getopt::Long;
25
26 package Amanda::Application::Amzfs_sendrecv;
27 use base qw(Amanda::Application Amanda::Application::Zfs);
28 use File::Copy;
29 use File::Path;
30 use IPC::Open3;
31 use Sys::Hostname;
32 use Symbol;
33 use Amanda::Constants;
34 use Amanda::Config qw( :init :getconf  config_dir_relative );
35 use Amanda::Debug qw( :logging );
36 use Amanda::Paths;
37 use Amanda::Util qw( :constants quote_string );
38
39 sub new {
40     my $class = shift;
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);
43
44     $self->{config}     = $config;
45     $self->{host}       = $host;
46     if (defined $disk) {
47         $self->{disk}      = $disk;
48     } else {
49         $self->{disk}      = $device;
50     }
51     if (defined $device) {
52         $self->{device}    = $device;
53     } else {
54         $self->{device}    = $disk;
55     }
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;
70
71     if ($self->{keep_snapshot} =~ /^YES$/i) {
72         $self->{keep_snapshot} = "YES";
73         if (!defined $self->{record}) {
74             $self->{keep_snapshot} = "NO";
75         }
76     }
77
78     return $self;
79 }
80
81 sub check_for_backup_failure {
82    my $self = shift;
83
84    $self->zfs_destroy_snapshot();
85 }
86
87 sub command_support {
88    my $self = shift;
89
90    print "CONFIG YES\n";
91    print "HOST YES\n";
92    print "DISK YES\n";
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";
98    print "RECORD YES\n";
99    print "COLLECTION NO\n";
100    print "CLIENT-ESTIMATE YES\n";
101 }
102
103 sub command_selfcheck {
104     my $self = shift;
105
106     $self->print_to_server("disk " . quote_string($self->{disk}),
107                            $Amanda::Script_App::GOOD);
108
109     $self->print_to_server("amzfs-sendrecv version " . $Amanda::Constants::VERSION,
110                            $Amanda::Script_App::GOOD);
111     $self->zfs_set_value();
112
113     if (!defined $self->{device}) {
114         return;
115     }
116
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";
121     }
122
123     if ($#{$self->{include_list}} >= 0) {
124         $self->print_to_server("include-list not supported for backup",
125                                $Amanda::Script_App::ERROR);
126     }
127     if ($#{$self->{exclude_list}} >= 0) {
128         $self->print_to_server("exclude-list not supported for backup",
129                                $Amanda::Script_App::ERROR);
130     }
131 }
132
133 sub command_estimate() {
134     my $self = shift;
135     my $level = 0;
136
137     if ($#{$self->{include_list}} >= 0) {
138         $self->print_to_server("include-list not supported for backup",
139                                $Amanda::Script_App::ERROR);
140     }
141     if ($#{$self->{exclude_list}} >= 0) {
142         $self->print_to_server("exclude-list not supported for backup",
143                                $Amanda::Script_App::ERROR);
144     }
145
146     $self->zfs_set_value();
147     $self->zfs_create_snapshot();
148
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);
153     }
154
155     $self->zfs_destroy_snapshot();
156
157     exit 0;
158 }
159
160 sub output_size {
161    my($level) = shift;
162    my($size) = shift;
163    if($size == -1) {
164       print "$level -1 -1\n";
165       #exit 2;
166    }
167    else {
168       my($ksize) = int $size / (1024);
169       $ksize=32 if ($ksize<32);
170       print "$level $ksize 1\n";
171    }
172 }
173
174 sub command_backup {
175     my $self = shift;
176
177     if ($#{$self->{include_list}} >= 0) {
178         $self->print_to_server("include-list not supported for backup",
179                                $Amanda::Script_App::ERROR);
180     }
181     if ($#{$self->{exclude_list}} >= 0) {
182         $self->print_to_server("exclude-list not supported for backup",
183                                $Amanda::Script_App::ERROR);
184     }
185
186     $self->zfs_set_value();
187     $self->zfs_create_snapshot();
188
189     my $size = -1;
190     my $level = $self->{level}[0];
191     my $cmd;
192     debug "Backup of level $level";
193     if ($level == 0) {
194        $cmd = "$self->{pfexec_cmd} $self->{zfs_path} send $self->{filesystem}\@$self->{snapshot} | $Amanda::Paths::amlibexecdir/teecount";
195     } else {
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";
200       } else {
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);
202       }
203     }
204
205     debug "running (backup): $cmd";
206     my($wtr, $err, $pid);
207     my($errmsg);
208     $err = Symbol::gensym;
209     $pid = open3($wtr, '>&STDOUT', $err, $cmd);
210     close $wtr;
211
212     if (defined($self->{index})) {
213         my $indexout;
214         open($indexout, '>&=4') ||
215         $self->print_to_server_and_die("Can't open indexout: $!",
216                                        $Amanda::Script_App::ERROR);
217         print $indexout "/\n";
218         close($indexout);
219     }
220
221     $errmsg = <$err>;
222     waitpid $pid, 0;
223     close $err;
224     if ($? !=  0) {
225         if (defined $errmsg) {
226             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
227         } else {
228             $self->print_to_server_and_die("cannot backup snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
229         }
230     }
231     $size = $errmsg;
232     debug "Dump done";
233
234     my($ksize) = int ($size/1024);
235     $ksize=32 if ($ksize<32);
236
237     print {$self->{mesgout}} "sendbackup: size $ksize\n";
238     print {$self->{mesgout}} "sendbackup: end\n";
239
240     # destroy all snapshot of this level and higher
241     $self->zfs_purge_snapshot($level, 9);
242
243     if ($self->{keep_snapshot} eq 'YES') {
244         $self->zfs_rename_snapshot($level);
245     } else {
246         $self->zfs_destroy_snapshot();
247     }
248
249     exit 0;
250 }
251
252 sub estimate_snapshot
253 {
254     my $self = shift;
255     my $level = shift;
256
257     debug "\$filesystem = $self->{filesystem}";
258     debug "\$snapshot = $self->{snapshot}";
259     debug "\$level = $level";
260
261     my $cmd;
262     if ($level == 0) {
263       $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -Hp -o value referenced $self->{filesystem}\@$self->{snapshot}";
264     } else {
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";
269       } else {
270         return "-1";
271       }
272     }
273     debug "running (estimate): $cmd";
274     my($wtr, $rdr, $err, $pid);
275     $err = Symbol::gensym;
276     $pid = open3($wtr, $rdr, $err, $cmd);
277     close $wtr;
278     my ($msg) = <$rdr>;
279     my ($errmsg) = <$err>;
280     waitpid $pid, 0;
281     close $rdr;
282     close $err;
283     if ($? !=  0) {
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);
290         } else {
291             $self->print_to_server_and_die("cannot estimate snapshot '$self->{snapshot}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
292         }
293     }
294     if ($level == 0) {
295         my $compratio = $self->get_compratio();
296         chop($compratio);
297         $msg *= $compratio;
298     }
299
300     return $msg;
301 }
302
303 sub get_compratio
304 {
305     my $self = shift;
306
307     my $cmd;
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);
313     close $wtr;
314     my ($msg) = <$rdr>;
315     my ($errmsg) = <$err>;
316     waitpid $pid, 0;
317     close $rdr;
318     close $err;
319     if ($? !=  0) {
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);
326         } else {
327             $self->print_to_server_and_die("cannot read compression ratio '$self->{snapshot}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
328         }
329     }
330     return $msg
331 }
332
333 sub command_index_from_output {
334 }
335
336 sub command_index_from_image {
337 }
338
339 sub command_restore {
340     my $self = shift;
341
342     my $current_snapshot;
343     my $level = $self->{level}[0];
344     my $device = $self->{device};
345     if (defined $device) {
346         $device =~ s,^/,,;
347         $current_snapshot = $self->zfs_build_snapshotname($device);
348         $self->{'snapshot'} = $self->zfs_build_snapshotname($device, $level);
349     }
350
351     my $directory = $device;
352     $directory = $self->{directory} if defined $self->{directory};
353     $directory =~ s,^/,,;
354
355     my @cmd = ();
356
357     if ($self->{pfexec_cmd}) {
358         push @cmd, $self->{pfexec_cmd};
359     }
360     push @cmd, $self->{zfs_path};
361     push @cmd, "recv";
362     push @cmd, $directory;
363
364     debug("cmd:" . join(" ", @cmd));
365     system @cmd;
366
367     my $snapshotname;
368     my $newsnapname;
369     if (defined $device) {
370         $snapshotname = "$directory\@$current_snapshot";
371         $newsnapname = "$directory\@$self->{'snapshot'}";
372     } else {
373         # find snapshot name
374         @cmd = ();
375         if ($self->{pfexec_cmd}) {
376             push @cmd, $self->{pfexec_cmd};
377         }
378         push @cmd, $self->{zfs_path};
379         push @cmd, "list";
380         push @cmd, "-r";
381         push @cmd, "-t";
382         push @cmd, "snapshot";
383         push @cmd, $directory;
384         debug("cmd:" . join(" ", @cmd));
385
386         my($wtr, $rdr, $err, $pid);
387         my($msg, $errmsg);
388         $err = Symbol::gensym;
389         $pid = open3($wtr, $rdr, $err, @cmd);
390         close $wtr;
391         while ($msg = <$rdr>) {
392             next if $msg =~ /^NAME/;
393             my ($name, $used, $avail) = split(/ +/, $msg);
394             if ($name =~ /-current$/) {
395                 $snapshotname = $name;
396                 last;
397             }
398         }
399         $errmsg = <$err>;
400         waitpid $pid, 0;
401         close $rdr;
402         close $err;
403
404         if (defined $snapshotname and defined($level)) {
405             $newsnapname = $snapshotname;
406             $newsnapname =~ s/current$/$level/;
407         } else {
408             # destroy the snapshot
409             # restoring next level will fail.
410             @cmd = ();
411             if ($self->{pfexec_cmd}) {
412                 push @cmd, $self->{pfexec_cmd};
413             }
414             push @cmd, $self->{zfs_path};
415             push @cmd, "destroy";
416             push @cmd, $snapshotname;
417
418             debug("cmd:" . join(" ", @cmd));
419             system @cmd;
420         }
421     }
422
423     if (defined $newsnapname) {
424         # rename -current snapshot to -level
425         @cmd = ();
426         if ($self->{pfexec_cmd}) {
427             push @cmd, $self->{pfexec_cmd};
428         }
429         push @cmd, $self->{zfs_path};
430         push @cmd, "rename";
431         push @cmd, $snapshotname;
432         push @cmd, $newsnapname;
433
434         debug("cmd:" . join(" ", @cmd));
435         system @cmd;
436     }
437 }
438
439 sub command_print_command {
440 }
441
442 package main;
443
444 sub usage {
445     print <<EOF;
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>.
447 EOF
448     exit(1);
449 }
450
451 my $opt_config;
452 my $opt_host;
453 my $opt_disk;
454 my $opt_device;
455 my @opt_level;
456 my $opt_index;
457 my $opt_message;
458 my $opt_collection;
459 my $opt_record;
460 my $df_path  = 'df';
461 my $zfs_path = 'zfs';
462 my $pfexec_path = 'pfexec';
463 my $pfexec = "NO";
464 my $opt_keep_snapshot = "YES";
465 my @opt_exclude_list;
466 my @opt_include_list;
467 my $opt_directory;
468
469 Getopt::Long::Configure(qw{bundling});
470 GetOptions(
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,
488 ) or usage();
489
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);
491
492 $application->do($ARGV[0]);
493