2 # Copyright (c) 2009, 2010 Zmanda, Inc. All Rights Reserved.
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.
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
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
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
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 );
31 use Amanda::Constants;
36 use Amanda::Xfer qw( :constants );
40 print STDERR "$msg\n" if $msg;
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 ... ]]]]]]"));
53 Amanda::Util::setup_application("amrestore", "server", $CONTEXT_CMDLINE);
55 my $config_overrides = new_config_overrides($#ARGV+1);
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));
61 'version' => \&Amanda::Util::version_opt,
62 'help|usage|?' => \&usage,
63 'config=s' => \$opt_config,
64 'holding' => \$opt_holding,
65 'b=i' => \$opt_blocksize,
67 'c' => \$opt_compress,
68 'C' => \$opt_compress_best,
71 'f=i' => \$opt_filenum,
73 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
76 $opt_compress = 1 if $opt_compress_best;
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;
83 if (Amanda::Holding::get_header($opt_restore_src));
86 my @opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV],
87 $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP);
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);
94 # -r implies -h, plus appending ".RAW" to filenames
95 $opt_header = 1 if $opt_raw;
97 set_config_overrides($config_overrides);
99 config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
101 config_init(0, undef);
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");
111 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
117 my ($msg, $finished_cb) = @_;
118 print STDERR "ERROR: $msg\n";
121 $res->release(finished_cb => sub {
131 my ($finished_cb) = @_;
135 my $filenum = $opt_filenum;
136 $filenum = 1 if (!$filenum);
137 $filenum = 0 + "$filenum"; # convert to integer
139 my $steps = define_steps
140 cb_ref => \$finished_cb;
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);
149 $steps->{'read_header'}->();
151 my $chg = Amanda::Changer->new($opt_restore_src);
152 if ($chg->isa("Amanda::Changer::Error")) {
153 return failure($chg, $finished_cb);
156 $chg->load(relative_slot => "current", mode => "read",
157 res_cb => $steps->{'slot_loaded'});
161 step slot_loaded => sub {
162 (my $err, $res) = @_;
163 return failure($err, $finished_cb) if $err;
165 $dev = $res->{'device'};
166 if ($dev->status != $DEVICE_STATUS_SUCCESS) {
167 return failure($dev->error_or_status, $finished_cb);
170 $steps->{'check_label'}->();
173 step check_label => sub {
174 if ($dev->status != $DEVICE_STATUS_SUCCESS) {
175 return failure($dev->error_or_status, $finished_cb);
178 $res->set_label(label => $dev->volume_label,
179 finished_cb => $steps->{'set_labeled'});
182 step set_labeled => sub {
184 if ($dev->volume_label ne $opt_label) {
185 my $got = $dev->volume_label;
186 return failure("Found unexpected label '$got'", $finished_cb);
190 my $lbl = $dev->volume_label;
191 print STDERR "Restoring from tape $lbl starting with file $filenum.\n";
193 $steps->{'start_device'}->();
196 step start_device => sub {
197 if (!$dev->start($ACCESS_READ, undef, undef)) {
198 return failure($dev->error_or_status(), $finished_cb);
201 $steps->{'read_header'}->();
204 step read_header => sub {
206 print STDERR "Reading from '$opt_restore_src'\n";
207 $hdr = Amanda::Holding::get_header($opt_restore_src);
209 $hdr = $dev->seek_file($filenum);
211 return failure("while reading next header: " . $dev->error_or_status(),
213 } elsif ($hdr->{'type'} == $Amanda::Header::F_TAPEEND) {
214 return $steps->{'finished'}->();
217 # seek_file may have skipped ahead; plan accordingly
218 $filenum = $dev->file + 1;
221 $steps->{'filter_dumpspecs'}->();
224 step filter_dumpspecs => sub {
225 if (@opt_dumpspecs and not $hdr->matches_dumpspecs([@opt_dumpspecs])) {
227 my $dev_filenum = $dev->file;
228 print STDERR "amrestore: $dev_filenum: skipping ",
229 $hdr->summary(), "\n";
232 # skip to the next file without restoring this one
233 return $steps->{'next_file'}->();
237 my $dev_filenum = $dev->file;
238 print STDERR "amrestore: $dev_filenum: restoring ";
240 print STDERR $hdr->summary(), "\n";
242 $steps->{'xfer_dumpfile'}->();
245 step xfer_dumpfile => sub {
248 # set up the source..
250 $src = Amanda::Xfer::Source::Holding->new($opt_restore_src);
252 $src = Amanda::Xfer::Source::Device->new($dev);
255 # and set up the destination..
260 my $filename = sprintf("%s.%s.%s.%d",
262 Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
264 $hdr->{'dumplevel'});
265 if ($hdr->{'partnum'} > 0) {
266 $filename .= sprintf(".%07d", $hdr->{'partnum'});
269 # add an appropriate suffix
272 } elsif ($opt_compress) {
273 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
274 $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
277 if (!open($dest_fh, ">", $filename)) {
278 return failure("Could not open '$filename' for writing: $!", $finished_cb);
281 $dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
283 # set up any filters that need to be applied, decryption first
285 if ($hdr->{'encrypted'} and not $opt_raw) {
286 if ($hdr->{'srv_encrypt'}) {
288 Amanda::Xfer::Filter::Process->new(
289 [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
290 } elsif ($hdr->{'clnt_encrypt'}) {
292 Amanda::Xfer::Filter::Process->new(
293 [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
295 return failure("could not decrypt encrypted dump: no program specified",
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';
306 if (!$opt_raw and $hdr->{'compressed'} and not $opt_compress) {
307 # need to uncompress this file
309 if ($hdr->{'srvcompprog'}) {
310 # TODO: this assumes that srvcompprog takes "-d" to decompress
312 Amanda::Xfer::Filter::Process->new(
313 [ $hdr->{'srvcompprog'}, "-d" ], 0);
314 } elsif ($hdr->{'clntcompprog'}) {
315 # TODO: this assumes that clntcompprog takes "-d" to decompress
317 Amanda::Xfer::Filter::Process->new(
318 [ $hdr->{'clntcompprog'}, "-d" ], 0);
321 Amanda::Xfer::Filter::Process->new(
322 [ $Amanda::Constants::UNCOMPRESS_PATH,
323 $Amanda::Constants::UNCOMPRESS_OPT ], 0);
327 $hdr->{'compressed'} = 0;
328 $hdr->{'uncompress_cmd'} = '';
329 } elsif (!$opt_raw and !$hdr->{'compressed'} and $opt_compress) {
330 # need to compress this file
332 my $compress_opt = $opt_compress_best?
333 $Amanda::Constants::COMPRESS_BEST_OPT :
334 $Amanda::Constants::COMPRESS_FAST_OPT;
336 Amanda::Xfer::Filter::Process->new(
337 [ $Amanda::Constants::COMPRESS_PATH,
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;
348 # write the header to the destination if requested
350 $hdr->{'blocksize'} = Amanda::Holding::DISK_BLOCK_BYTES;
351 print $dest_fh $hdr->to_string(32768, 32768);
354 my $xfer = Amanda::Xfer->new([ $src, @filters, $dest ]);
356 $xfer->get_source()->set_callback(sub {
357 my ($src, $msg, $xfer) = @_;
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) {
365 $steps->{'xfer_done'}->($got_err);
371 step xfer_done => sub {
373 return failure($err, $finished_cb) if $err;
375 $steps->{'next_file'}->();
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'}->();
385 # otherwise, try to read the next header from the device
386 $steps->{'read_header'}->();
389 step finished => sub {
391 $res->release(finished_cb => $steps->{'quit'});
393 $steps->{'quit'}->();
401 return failure($err, $finished_cb) if $err;
406 main(\&Amanda::MainLoop::quit);
407 Amanda::MainLoop::run();
408 Amanda::Util::finish_application();