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@';
25 package Amanda::Application::Amsuntar;
26 use base qw(Amanda::Application);
28 use File::Temp qw( tempfile );
34 use Amanda::Constants;
35 use Amanda::Config qw( :init :getconf config_dir_relative );
36 use Amanda::Debug qw( :logging );
38 use Amanda::Util qw( :constants quote_string );
42 my ($config, $host, $disk, $device, $level, $index, $message, $collection, $record, $exclude_list, $exclude_optional, $include_list, $include_optional, $bsize, $ext_attrib, $ext_header, $ignore, $normal, $strange, $error_exp, $directory, $suntar_path) = @_;
43 my $self = $class->SUPER::new($config);
45 $self->{suntar} = $Amanda::Constants::SUNTAR;
46 if (defined $suntar_path) {
47 $self->{suntar} = $suntar_path;
49 $self->{pfexec} = "/usr/bin/pfexec";
50 $self->{gnutar} = $Amanda::Constants::GNUTAR;
51 $self->{teecount} = $Amanda::Paths::amlibexecdir."/teecount";
53 $self->{config} = $config;
54 $self->{host} = $host;
56 $self->{disk} = $disk;
58 $self->{disk} = $device;
60 if (defined $device) {
61 $self->{device} = $device;
63 $self->{device} = $disk;
65 $self->{level} = $level;
66 $self->{index} = $index;
67 $self->{message} = $message;
68 $self->{collection} = $collection;
69 $self->{record} = $record;
70 $self->{exclude_list} = [ @{$exclude_list} ];
71 $self->{exclude_optional} = $exclude_optional;
72 $self->{include_list} = [ @{$include_list} ];
73 $self->{include_optional} = $include_optional;
74 $self->{block_size} = $bsize;
75 $self->{extended_header} = $ext_header;
76 $self->{extended_attrib} = $ext_attrib;
77 $self->{directory} = $directory;
81 for $regex (@{$ignore}) {
82 my $a = { regex => $regex, type => "IGNORE" };
83 push @{$self->{regex}}, $a;
86 for $regex (@{$normal}) {
87 my $a = { regex => $regex, type => "NORMAL" };
88 push @{$self->{regex}}, $a;
91 for $regex (@{$strange}) {
92 my $a = { regex => $regex, type => "STRANGE" };
93 push @{$self->{regex}}, $a;
96 for $regex (@{$error_exp}) {
97 my $a = { regex => $regex, type => "ERROR" };
98 push @{$self->{regex}}, $a;
101 #type can be IGNORE/NORMAL/STRANGE/ERROR
102 push @{$self->{regex}}, { regex => "is not a file. Not dumped\$",
104 push @{$self->{regex}}, { regex => "same as archive file\$",
106 push @{$self->{regex}}, { regex => ": invalid character in UTF-8 conversion of ",
108 push @{$self->{regex}}, { regex => ": UTF-8 conversion failed.\$",
110 push @{$self->{regex}}, { regex => ": Permission denied\$",
113 for $regex (@{$self->{regex}}) {
114 debug ($regex->{type} . ": " . $regex->{regex});
120 sub command_support {
123 print "CONFIG YES\n";
126 print "MAX-LEVEL 0\n";
127 print "INDEX-LINE YES\n";
128 print "INDEX-XML NO\n";
129 print "MESSAGE-LINE YES\n";
130 print "MESSAGE-XML NO\n";
131 print "RECORD YES\n";
132 print "EXCLUDE-FILE NO\n";
133 print "EXCLUDE-LIST YES\n";
134 print "EXCLUDE-OPTIONAL YES\n";
135 print "INCLUDE-FILE NO\n";
136 print "INCLUDE-LIST YES\n";
137 print "INCLUDE-OPTIONAL YES\n";
138 print "COLLECTION NO\n";
139 print "MULTI-ESTIMATE NO\n";
140 print "CALCSIZE NO\n";
141 print "CLIENT-ESTIMATE YES\n";
144 sub command_selfcheck {
147 $self->print_to_server("disk " . quote_string($self->{disk}));
149 $self->print_to_server("amsuntar version " . $Amanda::Constants::VERSION,
150 $Amanda::Script_App::GOOD);
152 if (!-e $self->{suntar}) {
153 $self->print_to_server_and_die(
154 "application binary $self->{suntar} doesn't exist",
155 $Amanda::Script_App::ERROR);
157 if (!-x $self->{suntar}) {
158 $self->print_to_server_and_die(
159 "application binary $self->{suntar} is not a executable",
160 $Amanda::Script_App::ERROR);
162 if (!defined $self->{disk} || !defined $self->{device}) {
165 print "OK " . $self->{device} . "\n";
166 print "OK " . $self->{directory} . "\n" if defined $self->{directory};
167 $self->validate_inexclude();
170 sub command_estimate() {
173 my $level = $self->{level};
175 $self->{index} = undef; #remove verbose flag to suntar.
176 my(@cmd) = $self->build_command();
177 my(@cmdwc) = ("/usr/bin/wc", "-c");
179 debug("cmd:" . join(" ", @cmd) . " | " . join(" ", @cmdwc));
180 my($wtr, $rdr, $err, $pid, $rdrwc, $pidwc);
181 $err = Symbol::gensym;
182 $pid = open3($wtr, \*DATA, $err, @cmd);
183 $pidwc = open2($rdrwc, '>&DATA', @cmdwc);
186 my ($msgsize) = <$rdrwc>;
191 for my $regex (@{$self->{regex}}) {
192 my $regex1 = $regex->{regex};
194 $result = 1 if ($regex->{type} eq "ERROR");
199 $result = 1 if ($matched == 0);
200 $errmsg = $_ if (!defined $errmsg);
206 if (defined $errmsg) {
207 $self->print_to_server_and_die($errmsg, $Amanda::Script_App::ERROR);
209 $self->print_to_server_and_die(
210 "cannot estimate archive size': unknown reason",
211 $Amanda::Script_App::ERROR);
214 output_size($level, $msgsize);
223 print "$level -1 -1\n";
227 my($ksize) = int $size / (1024);
228 $ksize=32 if ($ksize<32);
229 print "$level $ksize 1\n";
236 $self->validate_inexclude();
238 my(@cmd) = $self->build_command();
239 my(@cmdtc) = $self->{teecount};
241 debug("cmd:" . join(" ", @cmd) . " | " . join(" ", @cmdtc));
243 my($wtr, $pid, $rdrtc, $errtc, $pidtc);
244 my $index_fd = Symbol::gensym;
245 $errtc = Symbol::gensym;
247 $pid = open3($wtr, \*DATA, $index_fd, @cmd) ||
248 $self->print_to_server_and_die("Can't run $cmd[0]: $!",
249 $Amanda::Script_App::ERROR);
250 $pidtc = open3('<&DATA', '>&STDOUT', $errtc, @cmdtc) ||
251 $self->print_to_server_and_die("Can't run $cmdtc[0]: $!",
252 $Amanda::Script_App::ERROR);
255 unlink($self->{include_tmp}) if(-e $self->{include_tmp});
256 unlink($self->{exclude_tmp}) if(-e $self->{exclude_tmp});
259 if(defined($self->{index})) {
261 open($indexout_fd, '>&=4') ||
262 $self->print_to_server_and_die("Can't open indexout_fd: $!",
263 $Amanda::Script_App::ERROR);
264 $result = $self->parse_backup($index_fd, $self->{mesgout}, $indexout_fd);
268 $result = $self->parse_backup($index_fd, $self->{mesgout}, undef);
277 debug("exit status $status ?" );
281 debug("$self->{suntar} returned error" );
282 $self->print_to_server("$self->{suntar} returned error",
283 $Amanda::Script_App::ERROR);
286 my($ksize) = int ($size/1024);
287 print {$self->{mesgout}} "sendbackup: size $ksize\n";
288 print {$self->{mesgout}} "sendbackup: end\n";
289 debug("sendbackup: size $ksize ");
296 my($fhin, $fhout, $indexout) = @_;
300 if ( /^ ?a\s+(\.\/.*) \d*K/ ||
301 /^a\s+(\.\/.*) symbolic link to/ ||
302 /^a\s+(\.\/.*) link to/ ) {
304 if(defined($indexout)) {
305 if(defined($self->{index})) {
307 print $indexout $name, "\n";
313 for my $regex (@{$self->{regex}}) {
314 my $regex1 = $regex->{regex};
316 $result = 1 if ($regex->{type} eq "ERROR");
317 if (defined($fhout)) {
318 if ($regex->{type} eq "IGNORE") {
319 } elsif ($regex->{type} eq "NORMAL") {
321 } elsif ($regex->{type} eq "STRANGE") {
333 if (defined($fhout)) {
342 sub validate_inexclude {
347 if ($#{$self->{exclude_list}} >= 0 && $#{$self->{include_list}} >= 0 ) {
348 $self->print_to_server_and_die("Can't have both include and exclude",
349 $Amanda::Script_App::ERROR);
352 foreach my $file (@{$self->{exclude_list}}){
353 if (!open($fh, $file)) {
354 if ($self->{action} eq "check" && !$self->{exclude_optional}) {
355 $self->print_to_server("Open of '$file' failed: $!",
356 $Amanda::Script_App::ERROR);
366 #Merging list into a single file
367 if($self->{action} eq 'backup' && $#{$self->{exculde_list}} >= 0) {
368 ($fh, $self->{exclude_tmp}) = tempfile(DIR => $Amanda::paths::AMANDA_TMPDIR);
370 $self->print_to_server_and_die(
371 "Open of tmp file '$self->{exclude_tmp}' failed: $!",
372 $Amanda::Script_App::ERROR);
379 foreach my $file (@{$self->{include_list}}) {
380 if (!open($fh, $file)) {
381 if ($self->{action} eq "check" && !$self->{include_optional}) {
382 $self->print_to_server("Open of '$file' failed: $!",
383 $Amanda::Script_App::ERROR);
393 if($self->{action} eq 'backup' && $#{$self->{include_list}} >= 0) {
394 ($fh, $self->{include_tmp}) = tempfile(DIR => $Amanda::paths::AMANDA_TMPDIR);
396 $self->print_to_server_and_die(
397 "Open of tmp file '$self->{include_tmp}' failed: $!",
398 $Amanda::Script_App::ERROR);
406 sub command_index_from_output {
407 index_from_output(0, 1);
411 sub index_from_output {
412 my($fhin, $fhout) = @_;
415 next if /^Total bytes written:/;
422 sub command_index_from_image {
425 open($index_fd, "$self->{suntar} -tf - |") ||
426 $self->print_to_server_and_die("Can't run $self->{suntar}: $!",
427 $Amanda::Script_App::ERROR);
428 index_from_output($index_fd, 1);
431 sub command_restore {
434 chdir(Amanda::Util::get_original_cwd());
435 if (defined $self->{directory}) {
436 if (!-d $self->{directory}) {
437 $self->print_to_server_and_die("Directory $self->{directory}: $!",
438 $Amanda::Script_App::ERROR);
440 if (!-w $self->{directory}) {
441 $self->print_to_server_and_die("Directory $self->{directory}: $!",
442 $Amanda::Script_App::ERROR);
444 chdir($self->{directory});
449 if($self->{extended_header} eq "YES") {
452 if($self->{extended_attrib} eq "YES") {
458 if (defined($self->{exclude_list}) && (-e $self->{exclude_list}[0])) {
462 my(@cmd) = ($self->{pfexec},$self->{suntar}, $cmd);
464 push @cmd, "-"; # for f argument
465 if (defined($self->{exclude_list}) && (-e $self->{exclude_list}[0])) {
466 push @cmd, $self->{exclude_list}[0]; # for X argument
469 if(defined($self->{include_list}) && (-e $self->{include_list}[0])) {
470 push @cmd, "-I", $self->{include_list}[0];
473 for(my $i=1;defined $ARGV[$i]; $i++) {
474 my $param = $ARGV[$i];
478 debug("cmd:" . join(" ", @cmd));
479 exec { $cmd[0] } @cmd;
480 die("Can't exec '", $cmd[0], "'");
483 sub command_validate {
488 if (-e $self->{suntar}) {
489 $program = $self->{suntar};
490 } elsif (-e $self->{gnutar}) {
491 $program = $self->{gnutar};
493 return $self->default_validate();
495 @cmd = ($program, "-tf", "-");
496 debug("cmd:" . join(" ", @cmd));
497 my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
498 $self->print_to_server_and_die("Unable to run @cmd",
499 $Amanda::Script_App::ERROR);
502 $self->print_to_server_and_die("$program returned error",
503 $Amanda::Script_App::ERROR);
511 #Careful sun tar options and ordering is very very tricky
516 $self->validate_inexclude();
518 if($self->{extended_header} =~ /^YES$/i) {
521 if($self->{extended_attrib} =~ /^YES$/i) {
524 if(defined($self->{index})) {
528 if(defined($self->{block_size})) {
530 push @optparams, $self->{block_size};
533 if (defined($self->{exclude_tmp})) {
535 push @optparams,"-",$self->{exclude_tmp};
540 if ($self->{directory}) {
541 push @optparams, "-C", $self->{directory};
543 push @optparams, "-C", $self->{device};
546 if(defined($self->{include_tmp})) {
547 push @optparams,"-I", $self->{include_tmp};
552 my(@cmd) = ($self->{pfexec}, $self->{suntar}, $cmd, @optparams);
560 Usage: Amsuntar <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --exclude-list=<fileList> --include-list=<fileList> --block-size=<size> --extended_attributes=<yes|no> --extended_headers<yes|no> --ignore=<regex> --normal=<regex> --strange=<regex> --error=<regex> --lang=<lang>.
574 my @opt_exclude_list;
575 my $opt_exclude_optional;
576 my @opt_include_list;
577 my $opt_include_optional;
579 my $opt_ext_attrib = "YES";
580 my $opt_ext_head = "YES";
589 Getopt::Long::Configure(qw{bundling});
591 'config=s' => \$opt_config,
592 'host=s' => \$opt_host,
593 'disk=s' => \$opt_disk,
594 'device=s' => \$opt_device,
595 'level=s' => \$opt_level,
596 'index=s' => \$opt_index,
597 'message=s' => \$opt_message,
598 'collection=s' => \$opt_collection,
599 'exclude-list=s' => \@opt_exclude_list,
600 'exclude-optional=s' => \$opt_exclude_optional,
601 'include-list=s' => \@opt_include_list,
602 'include-optional=s' => \$opt_include_optional,
603 'record' => \$opt_record,
604 'block-size=s' => \$opt_bsize,
605 'extended-attributes=s' => \$opt_ext_attrib,
606 'extended-headers=s' => \$opt_ext_head,
607 'ignore=s' => \@opt_ignore,
608 'normal=s' => \@opt_normal,
609 'strange=s' => \@opt_strange,
610 'error=s' => \@opt_error,
611 'lang=s' => \$opt_lang,
612 'directory=s' => \$opt_directory,
613 'suntar-path=s' => \$opt_suntar_path,
616 if (defined $opt_lang) {
617 $ENV{LANG} = $opt_lang;
620 my $application = Amanda::Application::Amsuntar->new($opt_config, $opt_host, $opt_disk, $opt_device, $opt_level, $opt_index, $opt_message, $opt_collection, $opt_record, \@opt_exclude_list, $opt_exclude_optional, \@opt_include_list, $opt_include_optional,$opt_bsize,$opt_ext_attrib,$opt_ext_head, \@opt_ignore, \@opt_normal, \@opt_strange, \@opt_error, $opt_directory, $opt_suntar_path);
622 $application->do($ARGV[0]);