Imported Upstream version 3.2.0
[debian/amanda] / server-src / amrestore.pl
1 #! @PERL@
2 # Copyright (c) 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
24 use Getopt::Long;
25
26 use Amanda::Device qw( :constants );
27 use Amanda::Debug qw( :logging );
28 use Amanda::Config qw( :init :getconf config_dir_relative );
29 use Amanda::Util qw( :constants );
30 use Amanda::Changer;
31 use Amanda::Constants;
32 use Amanda::MainLoop;
33 use Amanda::Header;
34 use Amanda::Holding;
35 use Amanda::Cmdline;
36 use Amanda::Xfer qw( :constants );
37
38 sub usage {
39     my ($msg) = @_;
40     print STDERR "$msg\n" if $msg;
41     print STDERR <<EOF;
42 Usage: amrestore [--config config] [-b blocksize] [-r|-c|-C] [-p] [-h]
43     [-f filenum] [-l label] [-o configoption]*
44     {device | [--holding] holdingfile}
45     [hostname [diskname [datestamp [hostname [diskname [datestamp ... ]]]]]]"));
46 EOF
47     exit(1);
48 }
49
50 ##
51 # main
52
53 Amanda::Util::setup_application("amrestore", "server", $CONTEXT_CMDLINE);
54
55 my $config_overrides = new_config_overrides($#ARGV+1);
56
57 my ($opt_config, $opt_blocksize, $opt_raw, $opt_compress, $opt_compress_best,
58     $opt_pipe, $opt_header, $opt_filenum, $opt_label, $opt_holding, $opt_restore_src);
59 Getopt::Long::Configure(qw(bundling));
60 GetOptions(
61     'version' => \&Amanda::Util::version_opt,
62     'help|usage|?' => \&usage,
63     'config=s' => \$opt_config,
64     'holding' => \$opt_holding,
65     'b=i' => \$opt_blocksize,
66     'r' => \$opt_raw,
67     'c' => \$opt_compress,
68     'C' => \$opt_compress_best,
69     'p' => \$opt_pipe,
70     'h' => \$opt_header,
71     'f=i' => \$opt_filenum,
72     'l=s' => \$opt_label,
73     'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
74 ) or usage();
75
76 $opt_compress = 1 if $opt_compress_best;
77
78 # see if we have a holding file or a device
79 usage("Must specify a device or holding-disk file") unless (@ARGV);
80 $opt_restore_src = shift @ARGV;
81 if (!$opt_holding) {
82     $opt_holding = 1
83         if (Amanda::Holding::get_header($opt_restore_src));
84 }
85
86 my @opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV],
87     $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP);
88
89 usage("Cannot check a label on a holding-disk file")
90     if ($opt_holding and $opt_label);
91 usage("Cannot use both -r (raw) and -c/-C (compression) -- use -h instead")
92     if ($opt_raw and $opt_compress);
93
94 # -r implies -h, plus appending ".RAW" to filenames
95 $opt_header = 1 if $opt_raw;
96
97 set_config_overrides($config_overrides);
98 if ($opt_config) {
99     config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
100 } else {
101     config_init(0, undef);
102 }
103 my ($cfgerr_level, @cfgerr_errors) = config_errors();
104 if ($cfgerr_level >= $CFGERR_WARNINGS) {
105     config_print_errors();
106     if ($cfgerr_level >= $CFGERR_ERRORS) {
107         die("errors processing config file");
108     }
109 }
110
111 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
112
113 my $exit_status = 0;
114 my $res;
115
116 sub failure {
117     my ($msg, $finished_cb) = @_;
118     print STDERR "ERROR: $msg\n";
119     $exit_status = 1;
120     if ($res) {
121         $res->release(finished_cb => sub {
122             # ignore error
123             $finished_cb->();
124         });
125     } else {
126         $finished_cb->();
127     }
128 }
129
130 sub main {
131     my ($finished_cb) = @_;
132
133     my $dev;
134     my $hdr;
135     my $filenum = $opt_filenum;
136     $filenum = 1 if (!$filenum);
137     $filenum = 0 + "$filenum"; # convert to integer
138
139     my $steps = define_steps
140         cb_ref => \$finished_cb;
141
142     step start => sub {
143         # first, return to the original working directory we were started in
144         if (!chdir Amanda::Util::get_original_cwd()) {
145             return failure("Cannot chdir to original working directory", $finished_cb);
146         }
147
148         if ($opt_holding) {
149             $steps->{'read_header'}->();
150         } else {
151             my $chg = Amanda::Changer->new($opt_restore_src);
152             if ($chg->isa("Amanda::Changer::Error")) {
153                 return failure($chg, $finished_cb);
154             }
155
156             $chg->load(relative_slot => "current", mode => "read",
157                 res_cb => $steps->{'slot_loaded'});
158         }
159     };
160
161     step slot_loaded => sub {
162         (my $err, $res) = @_;
163         return failure($err, $finished_cb) if $err;
164
165         $dev = $res->{'device'};
166         if ($dev->status != $DEVICE_STATUS_SUCCESS) {
167             return failure($dev->error_or_status, $finished_cb);
168         }
169
170         $steps->{'check_label'}->();
171     };
172
173     step check_label => sub {
174         if ($dev->status != $DEVICE_STATUS_SUCCESS) {
175             return failure($dev->error_or_status, $finished_cb);
176         }
177
178         $res->set_label(label => $dev->volume_label,
179                         finished_cb => $steps->{'set_labeled'});
180     };
181
182     step set_labeled => sub {
183         if ($opt_label) {
184             if ($dev->volume_label ne $opt_label) {
185                 my $got = $dev->volume_label;
186                 return failure("Found unexpected label '$got'", $finished_cb);
187             }
188         }
189
190         my $lbl = $dev->volume_label;
191         print STDERR "Restoring from tape $lbl starting with file $filenum.\n";
192
193         $steps->{'start_device'}->();
194     };
195
196     step start_device => sub {
197         if (!$dev->start($ACCESS_READ, undef, undef)) {
198             return failure($dev->error_or_status(), $finished_cb);
199         }
200
201         $steps->{'read_header'}->();
202     };
203
204     step read_header => sub {
205         if ($opt_holding) {
206             print STDERR "Reading from '$opt_restore_src'\n";
207             $hdr = Amanda::Holding::get_header($opt_restore_src);
208         } else {
209             $hdr = $dev->seek_file($filenum);
210             if (!$hdr) {
211                 return failure("while reading next header: " . $dev->error_or_status(),
212                             $finished_cb);
213             } elsif ($hdr->{'type'} == $Amanda::Header::F_TAPEEND) {
214                 return $steps->{'finished'}->();
215             }
216
217             # seek_file may have skipped ahead; plan accordingly
218             $filenum = $dev->file + 1;
219         }
220
221         $steps->{'filter_dumpspecs'}->();
222     };
223
224     step filter_dumpspecs => sub {
225         if (@opt_dumpspecs and not $hdr->matches_dumpspecs([@opt_dumpspecs])) {
226             if (!$opt_holding) {
227                 my $dev_filenum = $dev->file;
228                 print STDERR "amrestore: $dev_filenum: skipping ",
229                         $hdr->summary(), "\n";
230             }
231
232             # skip to the next file without restoring this one
233             return $steps->{'next_file'}->();
234         }
235
236         if (!$opt_holding) {
237             my $dev_filenum = $dev->file;
238             print STDERR "amrestore: $dev_filenum: restoring ";
239         }
240         print STDERR $hdr->summary(), "\n";
241
242         $steps->{'xfer_dumpfile'}->();
243     };
244
245     step xfer_dumpfile => sub {
246         my ($src, $dest);
247
248         # set up the source..
249         if ($opt_holding) {
250             $src = Amanda::Xfer::Source::Holding->new($opt_restore_src);
251         } else {
252             $src = Amanda::Xfer::Source::Device->new($dev);
253         }
254
255         # and set up the destination..
256         my $dest_fh;
257         if ($opt_pipe) {
258             $dest_fh = \*STDOUT;
259         } else {
260             my $filename = sprintf("%s.%s.%s.%d",
261                     $hdr->{'name'},
262                     Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
263                     $hdr->{'datestamp'},
264                     $hdr->{'dumplevel'});
265             if ($hdr->{'partnum'} > 0) {
266                 $filename .= sprintf(".%07d", $hdr->{'partnum'});
267             }
268
269             # add an appropriate suffix
270             if ($opt_raw) {
271                 $filename .= ".RAW";
272             } elsif ($opt_compress) {
273                 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
274                     $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
275             }
276
277             if (!open($dest_fh, ">", $filename)) {
278                 return failure("Could not open '$filename' for writing: $!", $finished_cb);
279             }
280         }
281         $dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
282
283         # set up any filters that need to be applied, decryption first
284         my @filters;
285         if ($hdr->{'encrypted'} and not $opt_raw) {
286             if ($hdr->{'srv_encrypt'}) {
287                 push @filters,
288                     Amanda::Xfer::Filter::Process->new(
289                         [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0, 0);
290             } elsif ($hdr->{'clnt_encrypt'}) {
291                 push @filters,
292                     Amanda::Xfer::Filter::Process->new(
293                         [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0, 0);
294             } else {
295                 return failure("could not decrypt encrypted dump: no program specified",
296                             $finished_cb);
297             }
298
299             $hdr->{'encrypted'} = 0;
300             $hdr->{'srv_encrypt'} = '';
301             $hdr->{'srv_decrypt_opt'} = '';
302             $hdr->{'clnt_encrypt'} = '';
303             $hdr->{'clnt_decrypt_opt'} = '';
304             $hdr->{'encrypt_suffix'} = 'N';
305         }
306         if (!$opt_raw and $hdr->{'compressed'} and not $opt_compress) {
307             # need to uncompress this file
308
309             if ($hdr->{'srvcompprog'}) {
310                 # TODO: this assumes that srvcompprog takes "-d" to decompress
311                 push @filters,
312                     Amanda::Xfer::Filter::Process->new(
313                         [ $hdr->{'srvcompprog'}, "-d" ], 0, 0);
314             } elsif ($hdr->{'clntcompprog'}) {
315                 # TODO: this assumes that clntcompprog takes "-d" to decompress
316                 push @filters,
317                     Amanda::Xfer::Filter::Process->new(
318                         [ $hdr->{'clntcompprog'}, "-d" ], 0, 0);
319             } else {
320                 push @filters,
321                     Amanda::Xfer::Filter::Process->new(
322                         [ $Amanda::Constants::UNCOMPRESS_PATH,
323                           $Amanda::Constants::UNCOMPRESS_OPT ], 0, 0);
324             }
325             
326             # adjust the header
327             $hdr->{'compressed'} = 0;
328             $hdr->{'uncompress_cmd'} = '';
329         } elsif (!$opt_raw and !$hdr->{'compressed'} and $opt_compress) {
330             # need to compress this file
331
332             my $compress_opt = $opt_compress_best?
333                 $Amanda::Constants::COMPRESS_BEST_OPT :
334                 $Amanda::Constants::COMPRESS_FAST_OPT;
335             @filters = (
336                 Amanda::Xfer::Filter::Process->new(
337                     [ $Amanda::Constants::COMPRESS_PATH,
338                       $compress_opt ], 0, 0),
339             );
340             
341             # adjust the header
342             $hdr->{'compressed'} = 1;
343             $hdr->{'uncompress_cmd'} = " $Amanda::Constants::UNCOMPRESS_PATH " .
344                 "$Amanda::Constants::UNCOMPRESS_OPT |";
345             $hdr->{'comp_suffix'} = $Amanda::Constants::COMPRESS_SUFFIX;
346         }
347
348         # write the header to the destination if requested
349         if ($opt_header) {
350             $hdr->{'blocksize'} = Amanda::Holding::DISK_BLOCK_BYTES;
351             $dest_fh->syswrite($hdr->to_string(32768, 32768));
352         }
353         
354         my $xfer = Amanda::Xfer->new([ $src, @filters, $dest ]);
355         my $got_err = undef;
356         $xfer->get_source()->set_callback(sub {
357             my ($src, $msg, $xfer) = @_;
358
359             if ($msg->{'type'} == $XMSG_INFO) {
360                 Amanda::Debug::info($msg->{'message'});
361             } elsif ($msg->{'type'} == $XMSG_ERROR) {
362                 $got_err = $msg->{'message'};
363             } elsif ($msg->{'type'} == $XMSG_DONE) {
364                 $src->remove();
365                 $steps->{'xfer_done'}->($got_err);
366             }
367         });
368         $xfer->start();
369     };
370
371     step xfer_done => sub {
372         my ($err) = @_;
373         return failure($err, $finished_cb) if $err;
374
375         $steps->{'next_file'}->();
376     };
377
378     step next_file => sub {
379         # amrestore does not loop over multiple files when reading from
380         # holding or when outputting to a pipe
381         if ($opt_holding or $opt_pipe) {
382             return $steps->{'finished'}->();
383         }
384
385         # otherwise, try to read the next header from the device
386         $steps->{'read_header'}->();
387     };
388
389     step finished => sub {
390         if ($res) {
391             $res->release(finished_cb => $steps->{'quit'});
392         } else {
393             $steps->{'quit'}->();
394         }
395     };
396
397     step quit => sub {
398         my ($err) = @_;
399         $res = undef;
400
401         return failure($err, $finished_cb) if $err;
402
403         $finished_cb->();
404     };
405 }
406 main(\&Amanda::MainLoop::quit);
407 Amanda::MainLoop::run();
408 Amanda::Util::finish_application();
409 exit $exit_status;