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