63bb0bcc073f727fecd0664687ccb382e3e97de5
[debian/amanda] / application-src / amzfs-sendrecv.pl
1 #!@PERL@
2 # Copyright (c) 2008,2009 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 Getopt::Long;
23
24 package Amanda::Application::Amzfs_sendrecv;
25 use base qw(Amanda::Application Amanda::Application::Zfs);
26 use File::Copy;
27 use File::Path;
28 use IPC::Open3;
29 use Sys::Hostname;
30 use Symbol;
31 use Amanda::Constants;
32 use Amanda::Config qw( :init :getconf  config_dir_relative );
33 use Amanda::Debug qw( :logging );
34 use Amanda::Paths;
35 use Amanda::Util qw( :constants );
36
37 sub new {
38     my $class = shift;
39     my ($config, $host, $disk, $device, $level, $index, $message, $collection, $record, $df_path, $zfs_path, $pfexec_path, $pfexec, $keep_snapshot, $exclude_list, $include_list, $directory) = @_;
40     my $self = $class->SUPER::new($config);
41
42     $self->{config}     = $config;
43     $self->{host}       = $host;
44     if (defined $disk) {
45         $self->{disk}      = $disk;
46     } else {
47         $self->{disk}      = $device;
48     }
49     if (defined $device) {
50         $self->{device}    = $device;
51     } else {
52         $self->{device}    = $disk;
53     }
54     $self->{level}      = [ @{$level} ];
55     $self->{index}      = $index;
56     $self->{message}    = $message;
57     $self->{collection} = $collection;
58     $self->{record}     = $record;
59     $self->{df_path}       = $df_path;
60     $self->{zfs_path}      = $zfs_path;
61     $self->{pfexec_path}   = $pfexec_path;
62     $self->{pfexec}        = $pfexec;
63     $self->{keep_snapshot} = $keep_snapshot;
64     $self->{pfexec_cmd}    = undef;
65     $self->{exclude_list}  = [ @{$exclude_list} ];
66     $self->{include_list}  = [ @{$include_list} ];
67     $self->{directory}     = $directory;
68
69     if ($self->{keep_snapshot} =~ /^YES$/i) {
70         $self->{keep_snapshot} = "YES";
71         if (!defined $self->{record}) {
72             $self->{keep_snapshot} = "NO";
73         }
74     }
75
76     return $self;
77 }
78
79 sub check_for_backup_failure {
80    my $self = shift;
81
82    $self->zfs_destroy_snapshot();
83 }
84
85 sub command_support {
86    my $self = shift;
87
88    print "CONFIG YES\n";
89    print "HOST YES\n";
90    print "DISK YES\n";
91    print "MAX-LEVEL 9\n";
92    print "INDEX-LINE NO\n";
93    print "INDEX-XML NO\n";
94    print "MESSAGE-LINE YES\n";
95    print "MESSAGE-XML NO\n";
96    print "RECORD YES\n";
97    print "COLLECTION NO\n";
98    print "CLIENT-ESTIMATE YES\n";
99 }
100
101 sub command_selfcheck {
102     my $self = shift;
103
104     $self->zfs_set_value();
105
106     if (!defined $self->{device}) {
107         return;
108     }
109
110     if ($self->{error_status} == $Amanda::Script_App::GOOD) {
111         $self->zfs_create_snapshot();
112         $self->zfs_destroy_snapshot();
113         print "OK " . $self->{disk} . "\n";
114         print "OK " . $self->{device} . "\n";
115     }
116
117     if ($#{$self->{include_list}} >= 0) {
118         $self->print_to_server("include-list not supported for backup",
119                                $Amanda::Script_App::ERROR);
120     }
121     if ($#{$self->{exclude_list}} >= 0) {
122         $self->print_to_server("exclude-list not supported for backup",
123                                $Amanda::Script_App::ERROR);
124     }
125 }
126
127 sub command_estimate() {
128     my $self = shift;
129     my $level = 0;
130
131     if ($#{$self->{include_list}} >= 0) {
132         $self->print_to_server("include-list not supported for backup",
133                                $Amanda::Script_App::ERROR);
134     }
135     if ($#{$self->{exclude_list}} >= 0) {
136         $self->print_to_server("exclude-list not supported for backup",
137                                $Amanda::Script_App::ERROR);
138     }
139
140     $self->zfs_set_value();
141     $self->zfs_create_snapshot();
142
143     while (defined ($level = shift @{$self->{level}})) {
144       debug "Estimate of level $level";
145       my $size = $self->estimate_snapshot($level);
146       output_size($level, $size);
147     }
148
149     $self->zfs_destroy_snapshot();
150
151     exit 0;
152 }
153
154 sub output_size {
155    my($level) = shift;
156    my($size) = shift;
157    if($size == -1) {
158       print "$level -1 -1\n";
159       #exit 2;
160    }
161    else {
162       my($ksize) = int $size / (1024);
163       $ksize=32 if ($ksize<32);
164       print "$level $ksize 1\n";
165    }
166 }
167
168 sub command_backup {
169     my $self = shift;
170
171     my $mesgout_fd;
172     open($mesgout_fd, '>&=3') ||
173         $self->print_to_server_and_die("Can't open mesgout_fd: $!",
174                                        $Amanda::Script_App::ERROR);
175     $self->{mesgout} = $mesgout_fd;
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 $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     $errmsg = <$err>;
212     waitpid $pid, 0;
213     close $err;
214     if ($? !=  0) {
215         if (defined $errmsg) {
216             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
217         } else {
218             $self->print_to_server_and_die("cannot backup snapshot '$self->{filesystem}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
219         }
220     }
221     $size = $errmsg;
222     debug "Dump done";
223
224     my($ksize) = int ($size/1024);
225     $ksize=32 if ($ksize<32);
226
227     print $mesgout_fd "sendbackup: size $ksize\n";
228     print $mesgout_fd "sendbackup: end\n";
229
230     # destroy all snapshot of this level and higher
231     $self->zfs_purge_snapshot($level, 9);
232
233     if ($self->{keep_snapshot} eq 'YES') {
234         $self->zfs_rename_snapshot($level);
235     } else {
236         $self->zfs_destroy_snapshot();
237     }
238
239     exit 0;
240 }
241
242 sub estimate_snapshot
243 {
244     my $self = shift;
245     my $level = shift;
246
247     debug "\$filesystem = $self->{filesystem}";
248     debug "\$snapshot = $self->{snapshot}";
249     debug "\$level = $level";
250
251     my $cmd;
252     if ($level == 0) {
253       $cmd = "$self->{pfexec_cmd} $self->{zfs_path} get -Hp -o value referenced $self->{filesystem}\@$self->{snapshot}";
254     } else {
255       my $refsnapshotname = $self->zfs_find_snapshot_level($level-1);
256       debug "Referenced snapshot name: $refsnapshotname|";
257       if ($refsnapshotname ne "") {
258         $cmd = "$self->{pfexec_cmd} $self->{zfs_path} send -i $refsnapshotname $self->{filesystem}\@$self->{snapshot} | /usr/bin/wc -c";
259       } else {
260         return "-1";
261       }
262     }
263     debug "running (estimate): $cmd";
264     my($wtr, $rdr, $err, $pid);
265     $err = Symbol::gensym;
266     $pid = open3($wtr, $rdr, $err, $cmd);
267     close $wtr;
268     my ($msg) = <$rdr>;
269     my ($errmsg) = <$err>;
270     waitpid $pid, 0;
271     close $rdr;
272     close $err;
273     if ($? !=  0) {
274         if (defined $msg && defined $errmsg) {
275             $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
276         } elsif (defined $msg) {
277             $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
278         } elsif (defined $errmsg) {
279             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
280         } else {
281             $self->print_to_server_and_die("cannot estimate snapshot '$self->{snapshot}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
282         }
283     }
284     if ($level == 0) {
285         my $compratio = $self->get_compratio();
286         chop($compratio);
287         $msg *= $compratio;
288     }
289
290     return $msg;
291 }
292
293 sub get_compratio
294 {
295     my $self = shift;
296
297     my $cmd;
298     $cmd =  "$self->{pfexec_cmd} $self->{zfs_path} get -Hp -o value compressratio $self->{filesystem}\@$self->{snapshot}";
299     debug "running (get-compression): $cmd";
300     my($wtr, $rdr, $err, $pid);
301     $err = Symbol::gensym;
302     $pid = open3($wtr, $rdr, $err, $cmd);
303     close $wtr;
304     my ($msg) = <$rdr>;
305     my ($errmsg) = <$err>;
306     waitpid $pid, 0;
307     close $rdr;
308     close $err;
309     if ($? !=  0) {
310         if (defined $msg && defined $errmsg) {
311             $self->print_to_server_and_die("$msg, $errmsg", $Amanda::Script_App::ERROR);
312         } elsif (defined $msg) {
313             $self->print_to_server_and_die($msg, $Amanda::Script_App::ERROR);
314         } elsif (defined $errmsg) {
315             $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
316         } else {
317             $self->print_to_server_and_die("cannot read compression ratio '$self->{snapshot}\@$self->{snapshot}': unknown reason", $Amanda::Script_App::ERROR);
318         }
319     }
320     return $msg
321 }
322
323 sub command_index_from_output {
324 }
325
326 sub command_index_from_image {
327 }
328
329 #sub command_restore {
330 #    my $self = shift;
331 #
332 #TODO
333 #}
334
335 sub command_print_command {
336 }
337
338 package main;
339
340 sub usage {
341     print <<EOF;
342 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>.
343 EOF
344     exit(1);
345 }
346
347 my $opt_config;
348 my $opt_host;
349 my $opt_disk;
350 my $opt_device;
351 my @opt_level;
352 my $opt_index;
353 my $opt_message;
354 my $opt_collection;
355 my $opt_record;
356 my $df_path  = 'df';
357 my $zfs_path = 'zfs';
358 my $pfexec_path = 'pfexec';
359 my $pfexec = "NO";
360 my $opt_keep_snapshot = "YES";
361 my @opt_exclude_list;
362 my @opt_include_list;
363 my $opt_directory;
364
365 Getopt::Long::Configure(qw{bundling});
366 GetOptions(
367     'config=s'        => \$opt_config,
368     'host=s'          => \$opt_host,
369     'disk=s'          => \$opt_disk,
370     'device=s'        => \$opt_device,
371     'level=s'         => \@opt_level,
372     'index=s'         => \$opt_index,
373     'message=s'       => \$opt_message,
374     'collection=s'    => \$opt_collection,
375     'record'          => \$opt_record,
376     'df-path=s'       => \$df_path,
377     'zfs-path=s'      => \$zfs_path,
378     'pfexec-path=s'   => \$pfexec_path,
379     'pfexec=s'        => \$pfexec,
380     'keep-snapshot=s' => \$opt_keep_snapshot,
381     'exclude-list=s'  => \@opt_exclude_list,
382     'include-list=s'  => \@opt_include_list,
383     'directory=s'     => \$opt_directory,
384 ) or usage();
385
386 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);
387
388 $application->do($ARGV[0]);
389