2 # Copyright (c) 2009-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@';
27 use Amanda::Device qw( :constants );
28 use Amanda::Debug qw( :logging );
29 use Amanda::Config qw( :init :getconf config_dir_relative );
30 use Amanda::Util qw( :constants );
32 use Amanda::Constants;
34 use Amanda::MainLoop qw( :GIOCondition );
39 use Amanda::Xfer qw( :constants );
43 print STDERR "$msg\n" if $msg;
45 Usage: amrestore [--config config] [-b blocksize] [-r|-c|-C] [-p] [-h]
46 [-f filenum] [-l label] [--exact-match] [-o configoption]*
47 {device | [--holding] holdingfile}
48 [hostname [diskname [datestamp [hostname [diskname [datestamp ... ]]]]]]"));
56 Amanda::Util::setup_application("amrestore", "server", $CONTEXT_CMDLINE);
58 my $config_overrides = new_config_overrides($#ARGV+1);
60 my ($opt_config, $opt_blocksize, $opt_raw, $opt_compress, $opt_compress_best,
61 $opt_pipe, $opt_header, $opt_filenum, $opt_label, $opt_holding, $opt_restore_src, $opt_exact_match);
63 debug("Arguments: " . join(' ', @ARGV));
64 Getopt::Long::Configure(qw(bundling));
66 'version' => \&Amanda::Util::version_opt,
67 'help|usage|?' => \&usage,
68 'config=s' => \$opt_config,
69 'holding' => \$opt_holding,
70 'exact-match' => \$opt_exact_match,
71 'b=i' => \$opt_blocksize,
73 'c' => \$opt_compress,
74 'C' => \$opt_compress_best,
77 'f=i' => \$opt_filenum,
79 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
82 $opt_compress = 1 if $opt_compress_best;
84 # see if we have a holding file or a device
85 usage("Must specify a device or holding-disk file") unless (@ARGV);
86 $opt_restore_src = shift @ARGV;
89 if (Amanda::Holding::get_header($opt_restore_src));
92 my $cmd_flags = $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP;
93 $cmd_flags |= $Amanda::Cmdline::CMDLINE_EXACT_MATCH if $opt_exact_match;
94 my @opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV], $cmd_flags);
96 usage("Cannot check a label on a holding-disk file")
97 if ($opt_holding and $opt_label);
98 usage("Cannot use both -r (raw) and -c/-C (compression) -- use -h instead")
99 if ($opt_raw and $opt_compress);
101 # -r implies -h, plus appending ".RAW" to filenames
102 $opt_header = 1 if $opt_raw;
104 set_config_overrides($config_overrides);
106 config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
108 config_init(0, undef);
110 my ($cfgerr_level, @cfgerr_errors) = config_errors();
111 if ($cfgerr_level >= $CFGERR_WARNINGS) {
112 config_print_errors();
113 if ($cfgerr_level >= $CFGERR_ERRORS) {
114 die("errors processing config file");
118 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
124 my ($msg, $finished_cb) = @_;
125 print STDERR "ERROR: $msg\n";
128 $res->release(finished_cb => sub {
138 my ($finished_cb) = @_;
143 my $filenum = $opt_filenum;
144 $filenum = 1 if (!$filenum);
145 $filenum = 0 + "$filenum"; # convert to integer
149 my $steps = define_steps
150 cb_ref => \$finished_cb,
151 finalize => sub { $chg->quit() if defined $chg };
154 # first, return to the original working directory we were started in
155 if (!chdir Amanda::Util::get_original_cwd()) {
156 return failure("Cannot chdir to original working directory", $finished_cb);
160 $steps->{'read_header'}->();
162 my $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST));
163 my $tl = Amanda::Tapelist->new($tlf);
164 $chg = Amanda::Changer->new($opt_restore_src, tapelist => $tl);
165 if ($chg->isa("Amanda::Changer::Error")) {
166 return failure($chg, $finished_cb);
169 $chg->load(relative_slot => "current", mode => "read",
170 res_cb => $steps->{'slot_loaded'});
174 step slot_loaded => sub {
175 (my $err, $res) = @_;
176 return failure($err, $finished_cb) if $err;
178 $dev = $res->{'device'};
180 if ($opt_blocksize) {
181 if ( !$dev->property_set("BLOCK_SIZE", $opt_blocksize)) {
182 return failure($dev->error_or_status, $finished_cb);
185 # re-read the label with the correct blocksize
189 if ($dev->status != $DEVICE_STATUS_SUCCESS) {
190 return failure($dev->error_or_status, $finished_cb);
193 $steps->{'check_label'}->();
196 step check_label => sub {
197 if ($dev->status != $DEVICE_STATUS_SUCCESS) {
198 return failure($dev->error_or_status, $finished_cb);
202 if ($dev->volume_label ne $opt_label) {
203 my $got = $dev->volume_label;
204 return failure("Found unexpected label '$got'", $finished_cb);
208 my $lbl = $dev->volume_label;
209 print STDERR "Restoring from tape $lbl starting with file $filenum.\n";
211 $steps->{'start_device'}->();
214 step start_device => sub {
215 if (!$dev->start($ACCESS_READ, undef, undef)) {
216 return failure($dev->error_or_status(), $finished_cb);
219 $steps->{'read_header'}->();
222 step read_header => sub {
224 print STDERR "Reading from '$opt_restore_src'\n";
225 $hdr = Amanda::Holding::get_header($opt_restore_src);
227 $hdr = $dev->seek_file($filenum);
229 return failure("while reading next header: " . $dev->error_or_status(),
231 } elsif ($hdr->{'type'} == $Amanda::Header::F_TAPEEND) {
232 return $steps->{'finished'}->();
235 # seek_file may have skipped ahead; plan accordingly
236 $filenum = $dev->file + 1;
239 $steps->{'filter_dumpspecs'}->();
242 step filter_dumpspecs => sub {
243 if (@opt_dumpspecs and not $hdr->matches_dumpspecs([@opt_dumpspecs])) {
245 my $dev_filenum = $dev->file;
246 print STDERR "amrestore: $dev_filenum: skipping ",
247 $hdr->summary(), "\n";
250 # skip to the next file without restoring this one
251 return $steps->{'next_file'}->();
255 my $dev_filenum = $dev->file;
256 print STDERR "amrestore: $dev_filenum: restoring ";
258 print STDERR $hdr->summary(), "\n";
260 $steps->{'xfer_dumpfile'}->();
263 step xfer_dumpfile => sub {
266 # set up the source..
268 $src = Amanda::Xfer::Source::Holding->new($opt_restore_src);
270 $src = Amanda::Xfer::Source::Device->new($dev);
273 # and set up the destination..
278 my $filename = sprintf("%s.%s.%s.%d",
280 Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
282 $hdr->{'dumplevel'});
283 if ($hdr->{'partnum'} > 0) {
284 $filename .= sprintf(".%07d", $hdr->{'partnum'});
287 # add an appropriate suffix
290 } elsif ($opt_compress) {
291 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
292 $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
295 if (!open($dest_fh, ">", $filename)) {
296 return failure("Could not open '$filename' for writing: $!", $finished_cb);
299 $dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
301 # set up any filters that need to be applied, decryption first
303 if ($hdr->{'encrypted'} and not $opt_raw) {
304 if ($hdr->{'srv_encrypt'}) {
306 Amanda::Xfer::Filter::Process->new(
307 [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
308 } elsif ($hdr->{'clnt_encrypt'}) {
310 Amanda::Xfer::Filter::Process->new(
311 [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
313 return failure("could not decrypt encrypted dump: no program specified",
317 $hdr->{'encrypted'} = 0;
318 $hdr->{'srv_encrypt'} = '';
319 $hdr->{'srv_decrypt_opt'} = '';
320 $hdr->{'clnt_encrypt'} = '';
321 $hdr->{'clnt_decrypt_opt'} = '';
322 $hdr->{'encrypt_suffix'} = 'N';
324 if (!$opt_raw and $hdr->{'compressed'} and not $opt_compress) {
325 # need to uncompress this file
327 if ($hdr->{'srvcompprog'}) {
328 # TODO: this assumes that srvcompprog takes "-d" to decompress
330 Amanda::Xfer::Filter::Process->new(
331 [ $hdr->{'srvcompprog'}, "-d" ], 0);
332 } elsif ($hdr->{'clntcompprog'}) {
333 # TODO: this assumes that clntcompprog takes "-d" to decompress
335 Amanda::Xfer::Filter::Process->new(
336 [ $hdr->{'clntcompprog'}, "-d" ], 0);
339 Amanda::Xfer::Filter::Process->new(
340 [ $Amanda::Constants::UNCOMPRESS_PATH,
341 $Amanda::Constants::UNCOMPRESS_OPT ], 0);
345 $hdr->{'compressed'} = 0;
346 $hdr->{'uncompress_cmd'} = '';
347 } elsif (!$opt_raw and !$hdr->{'compressed'} and $opt_compress) {
348 # need to compress this file
350 my $compress_opt = $opt_compress_best?
351 $Amanda::Constants::COMPRESS_BEST_OPT :
352 $Amanda::Constants::COMPRESS_FAST_OPT;
354 Amanda::Xfer::Filter::Process->new(
355 [ $Amanda::Constants::COMPRESS_PATH,
360 $hdr->{'compressed'} = 1;
361 $hdr->{'uncompress_cmd'} = " $Amanda::Constants::UNCOMPRESS_PATH " .
362 "$Amanda::Constants::UNCOMPRESS_OPT |";
363 $hdr->{'comp_suffix'} = $Amanda::Constants::COMPRESS_SUFFIX;
366 # write the header to the destination if requested
368 $hdr->{'blocksize'} = Amanda::Holding::DISK_BLOCK_BYTES;
369 $dest_fh->syswrite($hdr->to_string(32768, 32768));
372 # start reading all filter stderr
373 foreach my $filter (@filters) {
374 my $fd = $filter->get_stderr_fd();
377 my $src = Amanda::MainLoop::fd_source($fd,
378 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
380 $all_filter{$src} = 1;
381 $src->set_callback( sub {
383 my $n_read = POSIX::read($fd, $b, 1);
384 if (!defined $n_read) {
386 } elsif ($n_read == 0) {
387 delete $all_filter{$src};
390 if (!%all_filter and $restore_done) {
397 print STDERR "filter stderr: $line";
399 debug("filter stderr: $line");
406 my $xfer = Amanda::Xfer->new([ $src, @filters, $dest ]);
408 $xfer->get_source()->set_callback(sub {
409 my ($src, $msg, $xfer) = @_;
411 if ($msg->{'type'} == $XMSG_INFO) {
412 Amanda::Debug::info($msg->{'message'});
413 } elsif ($msg->{'type'} == $XMSG_ERROR) {
414 $got_err = $msg->{'message'};
415 } elsif ($msg->{'type'} == $XMSG_DONE) {
417 $steps->{'xfer_done'}->($got_err);
423 step xfer_done => sub {
425 return failure($err, $finished_cb) if $err;
427 $steps->{'next_file'}->('extracted');
430 step next_file => sub {
431 my ($extracted) = @_;
432 # amrestore does not loop over multiple files when reading from holding
433 # when outputting to a pipe amrestore extracts only the first file
434 if ($opt_holding or ($opt_pipe and $extracted)) {
435 return $steps->{'finished'}->();
438 # otherwise, try to read the next header from the device
439 $steps->{'read_header'}->();
442 step finished => sub {
444 $res->release(finished_cb => $steps->{'quit'});
446 $steps->{'quit'}->();
454 return failure($err, $finished_cb) if $err;
461 main(\&Amanda::MainLoop::quit);
462 Amanda::MainLoop::run();
463 Amanda::Util::finish_application();