2 # Copyright (c) 2005-2008 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 Mathlida Ave, Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
24 package Amanda::Application::Amsamba;
25 use base qw(Amanda::Application);
33 use Amanda::Constants;
34 use Amanda::Config qw( :init :getconf config_dir_relative );
35 use Amanda::Debug qw( :logging );
37 use Amanda::Util qw( :constants :quoting);
41 my ($config, $host, $disk, $device, $level, $index, $message, $collection, $record, $calcsize, $gnutar_path, $smbclient_path, $amandapass, $exclude_file, $exclude_list, $exclude_optional, $include_file, $include_list, $include_optional, $recover_mode) = @_;
42 my $self = $class->SUPER::new();
44 if (defined $gnutar_path) {
45 $self->{gnutar} = $gnutar_path;
47 $self->{gnutar} = $Amanda::Constants::GNUTAR;
49 if (defined $smbclient_path) {
50 $self->{smbclient} = $smbclient_path;
52 $self->{smbclient} = $Amanda::Constants::SAMBA_CLIENT;
54 if (defined $amandapass) {
55 $self->{amandapass} = $amandapass;
57 $self->{amandapass} = "$Amanda::Paths::sysconfdir/amandapass";
60 $self->{config} = $config;
61 $self->{host} = $host;
62 $self->{disk} = $disk;
63 $self->{device} = $device;
64 $self->{level} = [ @{$level} ];
65 $self->{index} = $index;
66 $self->{message} = $message;
67 $self->{collection} = $collection;
68 $self->{record} = $record;
69 $self->{calcsize} = $calcsize;
70 $self->{exclude_file} = [ @{$exclude_file} ];
71 $self->{exclude_list} = [ @{$exclude_list} ];
72 $self->{exclude_optional} = $exclude_optional;
73 $self->{include_file} = [ @{$include_file} ];
74 $self->{include_list} = [ @{$include_list} ];
75 $self->{include_optional} = $include_optional;
76 $self->{recover_mode} = $recover_mode;
82 # $self->{exclude_file}
83 # $self->{exclude_list}
84 # $self->{include_file}
85 # $self->{include_list}
89 sub validate_inexclude {
92 if ($#{$self->{exclude_file}} + $#{$self->{exclude_list}} >= -1 &&
93 $#{$self->{include_file}} + $#{$self->{include_list}} >= -1) {
94 $self->print_to_server_and_die($self->{action},
95 "Can't have both include and exclude",
96 $Amanda::Script_App::ERROR);
99 if ($#{$self->{exclude_file}} >= 0) {
100 $self->{exclude} = [ @{$self->{exclude_file}} ];
102 foreach my $file (@{$self->{exclude_list}}) {
103 if (!open(FF, $file)) {
104 if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
105 $self->print_to_server($self->{action},
106 "Open of '$file' failed: $!",
107 $Amanda::Script_App::ERROR);
113 push @{$self->{exclude}}, $_;
117 if ($#{$self->{include_file}} >= 0) {
118 $self->{include} = [ @{$self->{include_file}} ];
120 foreach my $file (@{$self->{include_list}}) {
121 if (open(FF, $file)) {
122 if ($self->{action} eq 'check') {
123 $self->print_to_server($self->{action},
124 "Open of '$file' failed: $!",
125 $Amanda::Script_App::ERROR);
131 push @{$self->{include}}, $_;
138 # $self->disk == //host/share/subdir
140 # self->{cifshost} = //host
141 # $self->{share} = //host/share
142 # $self->{sambashare} = \\host\share
143 # $self->{subdir} = subdir
147 return if !defined $self->{disk};
149 if ($self->{disk} =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
153 $self->{share} = $self->{disk};
155 $self->{sambashare} = $self->{share};
156 $self->{sambashare} =~ s,/,\\,g;
157 $self->{disk} =~ m,^(//[^/]+)/[^/]+,;
158 $self->{cifshost} = $1;
162 # Read $self->{amandapass} file.
164 # $self->{share} == //host/share
166 # $self->{domain} = domain to connect to.
167 # $self->{username} = username (-U)
168 # $self->{password} = password
175 open($amandapass, $self->{amandapass});
176 while ($line = <$amandapass>) {
178 next if $line =~ /^#/;
179 my ($diskname, $userdomain) = Amanda::Util::skip_quoted_string($line);
180 if (defined $diskname &&
182 ($diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
183 $diskname eq $self->{share} ||
184 $diskname eq $self->{sambashare})) {
185 my ($userpasswd, $domain) = split ' ', $userdomain;
186 $self->{domain} = $domain;
187 my ($username, $password) = split('%', $userpasswd);
188 $self->{username} = $username;
189 $self->{password} = $password;
195 $self->print_to_server_and_die($self->{action},"Cannot find password for share $self->{share} in $self->{amandapass}", $Amanda::Script_App::ERROR);
198 sub command_support {
201 print "CONFIG YES\n";
204 print "MAX-LEVEL 1\n";
205 print "INDEX-LINE YES\n";
206 print "INDEX-XML NO\n";
207 print "MESSAGE-LINE YES\n";
208 print "MESSAGE-XML NO\n";
209 print "RECORD YES\n";
210 print "COLLECTION NO\n";
211 print "MULTI-ESTIMATE NO\n";
212 print "CALCSIZE NO\n";
213 print "EXCLUDE-FILE YES\n";
214 print "EXCLUDE-LIST YES\n";
215 print "EXCLUDE-OPTIONAL YES\n";
216 print "INCLUDE-FILE YES\n";
217 print "INCLUDE-LIST YES\n";
218 print "INCLUDE-OPTIONAL YES\n";
219 print "RECOVER-MODE SMB\n";
222 sub command_selfcheck {
225 $self->{action} = 'check';
226 $self->parsesharename();
228 $self->validate_inexclude();
230 print "OK " . $self->{share} . "\n";
231 print "OK " . $self->{disk} . "\n";
232 print "OK " . $self->{device} . "\n";
234 my ($child_rdr, $parent_wtr);
236 pipe($child_rdr, $parent_wtr);
237 $parent_wtr->autoflush(1);
238 my($wtr, $rdr, $err);
239 $err = Symbol::gensym;
240 my $pid = open3($wtr, $rdr, $err, "-");
243 my $ff = $child_rdr->fileno;
244 debug("child_rdr $ff");
245 $parent_wtr->close();
246 $ENV{PASSWD_FD} = $child_rdr->fileno;
250 push @ARGV, $self->{smbclient}, $self->{share},
251 "-U", $self->{username},
253 if (defined $self->{domain}) {
254 push @ARGV, "-W", $self->{domain},
256 if (defined $self->{subdir}) {
257 push @ARGV, "-D", $self->{subdir},
259 push @ARGV, "-c", "quit";
260 debug("execute: " . $self->{smbclient} . " " .
262 exec {$self->{smbclient}} @ARGV;
265 my $ff = $parent_wtr->fileno;
266 debug("parent_wtr $ff");
267 debug("password $self->{password}");
268 $parent_wtr->print($self->{password});
269 $parent_wtr->close();
275 debug("stderr: " . $_);
277 $self->print_to_server($self->{action}, "smbclient: $_",
278 $Amanda::Script_App::ERROR);
287 sub command_estimate {
290 $self->{action} = 'estimate';
291 $self->parsesharename();
293 $self->validate_inexclude();
295 my $level = $self->{level}[0];
296 my ($child_rdr, $parent_wtr);
298 pipe($child_rdr, $parent_wtr);
299 $parent_wtr->autoflush(1);
300 my($wtr, $rdr, $err);
301 $err = Symbol::gensym;
302 my $pid = open3($wtr, $rdr, $err, "-");
305 my $ff = $child_rdr->fileno;
306 debug("child_rdr $ff");
307 $parent_wtr->close();
308 $ENV{PASSWD_FD} = $child_rdr->fileno;
312 push @ARGV, $self->{smbclient}, $self->{share},
314 "-U", $self->{username},
316 if (defined $self->{domain}) {
317 push @ARGV, "-W", $self->{domain},
319 if (defined $self->{subdir}) {
320 push @ARGV, "-D", $self->{subdir},
323 push @ARGV, "-c", "archive 0;recurse;du";
325 push @ARGV, "-c", "archive 1;recurse;du";
327 debug("execute: " . $self->{smbclient} . " " .
329 exec {$self->{smbclient}} @ARGV;
332 my $ff = $parent_wtr->fileno;
333 debug("parent_wtr $ff");
334 debug("password $self->{password}");
335 $parent_wtr->print($self->{password});
336 $parent_wtr->close();
340 my $size = $self->parse_estimate($err);
342 output_size($level, $size);
353 next if /blocks of size/;
354 next if /blocks available/;
357 next if /dumped \d+ files and directories/;
359 if ($_ =~ /^Total number of bytes: (\d*)/) {
363 $self->print_to_server($self->{action}, "smbclient: $_",
364 $Amanda::Script_App::ERROR);
374 print "$level -1 -1\n";
378 my($ksize) = int $size / (1024);
379 $ksize=32 if ($ksize<32);
380 print "$level $ksize 1\n";
387 $self->{action} = 'backup';
388 $self->parsesharename();
390 $self->validate_inexclude();
392 my $level = $self->{level}[0];
394 open($mesgout_fd, '>&=3') || die();
395 $self->{mesgout} = $mesgout_fd;
397 my $pid_tee = open3(\*INDEX_IN, '>&STDOUT', \*INDEX_TEE, "-");
403 while (($size = POSIX::read(0, $buf, 32768)) > 0) {
404 POSIX::write(1, $buf, $size);
405 POSIX::write(2, $buf, $size);
409 my ($child_rdr, $parent_wtr);
411 pipe($child_rdr, $parent_wtr);
413 $parent_wtr->autoflush(1);
415 $err = Symbol::gensym;
416 my $pid = open3($wtr, ">&INDEX_IN", $err, "-");
419 my $ff = $child_rdr->fileno;
420 debug("child_rdr $ff");
421 $parent_wtr->close();
422 $ENV{PASSWD_FD} = $child_rdr->fileno;
425 push @ARGV, $self->{smbclient}, $self->{share},
427 "-U", $self->{username},
429 if (defined $self->{domain}) {
430 push @ARGV, "-W", $self->{domain},
432 if (defined $self->{subdir}) {
433 push @ARGV, "-D", $self->{subdir},
441 if ($#{$self->{exclude}} >= 0) {
444 if ($#{$self->{include}} >= 0) {
447 push @ARGV, $comm, "-";
448 if ($#{$self->{exclude}} >= 0) {
449 push @ARGV, @{$self->{exclude}};
451 if ($#{$self->{include}} >= 0) {
452 push @ARGV, @{$self->{include}};
454 debug("execute: " . $self->{smbclient} . " " .
456 exec {$self->{smbclient}} @ARGV;
459 my $ff = $parent_wtr->fileno;
460 debug("parent_wtr $ff");
461 debug("password $self->{password}");
462 $parent_wtr->print($self->{password});
463 $parent_wtr->close();
469 debug("$self->{gnutar} -tf -");
470 my $pid_index1 = open2($index, '<&INDEX_TEE', $self->{gnutar}, "-tf", "-");
473 my $index_fd = $index->fileno;
474 debug("index $index_fd");
475 if (defined($self->{index})) {
477 open($indexout_fd, '>&=4') || die();
478 $self->parse_backup($index, $mesgout_fd, $indexout_fd);
482 $self->parse_backup($index_fd, $mesgout_fd, undef);
488 debug("stderr: " . $_);
490 next if /dumped (\d+) files and directories/;
491 if (/^Total bytes written: (\d*)/) {
494 $self->print_to_server($self->{action}, "smbclient: $_",
495 $Amanda::Script_App::ERROR);
499 my $ksize = $size / 1024;
503 print $mesgout_fd "sendbackup: size $ksize\n";
504 print $mesgout_fd "sendbackup: end\n";
509 $self->print_to_server_and_die($self->{action},
510 "smbclient returned error",
511 $Amanda::Script_App::ERROR);
518 my($fhin, $fhout, $indexout) = @_;
522 if(defined($indexout)) {
523 if(defined($self->{index})) {
535 sub command_index_from_output {
536 index_from_output(0, 1);
540 sub index_from_output {
541 my($fhin, $fhout) = @_;
544 next if /^Total bytes written:/;
551 sub command_index_from_image {
554 open($index_fd, "$self->{gnutar} --list --file - |") || die();
555 index_from_output($index_fd, 1);
558 sub command_restore {
562 $self->{restore} = 'backup';
563 $self->parsesharename();
564 chdir(Amanda::Util::get_original_cwd());
566 if ($self->{recover_mode} eq "smb") {
568 push @cmd, $self->{smbclient}, $self->{share},
570 "-U", $self->{username};
572 if (defined $self->{domain}) {
573 push @cmd, "-W", $self->{domain};
575 push @cmd, "-Tx", "-";
576 for(my $i=1;defined $ARGV[$i]; $i++) {
577 my $param = $ARGV[$i];
581 my ($parent_rdr, $child_wtr);
583 pipe($parent_rdr, $child_wtr);
584 $child_wtr->autoflush(1);
585 my($wtr, $rdr, $err);
586 $err = Symbol::gensym;
587 my $pid = open3($wtr, $rdr, $err, "-");
589 $child_wtr->print($self->{password});
594 $ENV{PASSWD_FD} = $parent_rdr->fileno;
595 debug("cmd:" . join(" ", @cmd));
596 exec { $cmd[0] } @cmd;
597 die("Can't exec '", $cmd[0], "'");
599 push @cmd, $self->{gnutar}, "-xpvf", "-";
600 for(my $i=1;defined $ARGV[$i]; $i++) {
601 my $param = $ARGV[$i];
605 debug("cmd:" . join(" ", @cmd));
606 exec { $cmd[0] } @cmd;
607 die("Can't exec '", $cmd[0], "'");
611 sub command_validate {
614 $self->{validate} = 'backup';
615 my(@cmd) = ($self->{gnutar}, "-tf", "-");
616 debug("cmd:" . join(" ", @cmd));
617 my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) || die("validate", "Unable to run @cmd");
620 die("validate", "$self->{gnutar} returned error");
625 sub command_print_command {
632 Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
649 my $opt_smbclient_path;
651 my @opt_exclude_file;
652 my @opt_exclude_list;
653 my $opt_exclude_optional;
654 my @opt_include_file;
655 my @opt_include_list;
656 my $opt_include_optional;
657 my $opt_recover_mode;
659 Getopt::Long::Configure(qw{bundling});
661 'version' => \$opt_version,
662 'config=s' => \$opt_config,
663 'host=s' => \$opt_host,
664 'disk=s' => \$opt_disk,
665 'device=s' => \$opt_device,
666 'level=s' => \@opt_level,
667 'index=s' => \$opt_index,
668 'message=s' => \$opt_message,
669 'collection=s' => \$opt_collection,
670 'record' => \$opt_record,
671 'calcsize' => \$opt_calcsize,
672 'gnutar_path' => \$opt_gnutar_path,
673 'smbclient_path' => \$opt_smbclient_path,
674 'amandapass' => \$opt_amandapass,
675 'exclude-file=s' => \@opt_exclude_file,
676 'exclude-list=s' => \@opt_exclude_list,
677 'exclude-optional=s' => \$opt_exclude_optional,
678 'include-file=s' => \@opt_include_file,
679 'include-list=s' => \@opt_include_list,
680 'include-optional=s' => \$opt_include_optional,
681 'recover-mode=s' => \$opt_recover_mode,
684 if (defined $opt_version) {
685 print "amsamba-" . $Amanda::Constants::VERSION , "\n";
689 my $application = Amanda::Application::Amsamba->new($opt_config, $opt_host, $opt_disk, $opt_device, \@opt_level, $opt_index, $opt_message, $opt_collection, $opt_record, $opt_calcsize, $opt_gnutar_path, $opt_smbclient_path, $opt_amandapass, \@opt_exclude_file, \@opt_exclude_list, $opt_exclude_optional, \@opt_include_file, \@opt_include_list, $opt_include_optional, $opt_recover_mode);
691 $application->do($ARGV[0]);