2 # Copyright (c) 2008, 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::Amsamba;
26 use base qw(Amanda::Application);
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 :quoting);
42 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, $allow_anonymous, $directory) = @_;
43 my $self = $class->SUPER::new($config);
45 if (defined $gnutar_path) {
46 $self->{gnutar} = $gnutar_path;
48 $self->{gnutar} = $Amanda::Constants::GNUTAR;
50 if (defined $smbclient_path) {
51 $self->{smbclient} = $smbclient_path;
53 $self->{smbclient} = $Amanda::Constants::SAMBA_CLIENT;
55 if (defined $amandapass) {
56 $self->{amandapass} = config_dir_relative($amandapass);
58 $self->{amandapass} = "$Amanda::Paths::CONFIG_DIR/amandapass";
61 $self->{config} = $config;
62 $self->{host} = $host;
64 $self->{disk} = $disk;
66 $self->{disk} = $device;
68 if (defined $device) {
69 $self->{device} = $device;
71 $self->{device} = $disk;
73 if ($self->{disk} =~ /^\\\\/) {
78 $self->{level} = [ @{$level} ];
79 $self->{index} = $index;
80 $self->{message} = $message;
81 $self->{collection} = $collection;
82 $self->{record} = $record;
83 $self->{calcsize} = $calcsize;
84 $self->{exclude_file} = [ @{$exclude_file} ];
85 $self->{exclude_list} = [ @{$exclude_list} ];
86 $self->{exclude_optional} = $exclude_optional;
87 $self->{include_file} = [ @{$include_file} ];
88 $self->{include_list} = [ @{$include_list} ];
89 $self->{include_optional} = $include_optional;
90 $self->{recover_mode} = $recover_mode;
91 $self->{allow_anonymous} = $allow_anonymous;
92 $self->{directory} = $directory;
98 # $self->{exclude_file}
99 # $self->{exclude_list}
100 # $self->{include_file}
101 # $self->{include_list}
105 # $self->{include_filename}
106 sub validate_inexclude {
109 if ($#{$self->{exclude_file}} + $#{$self->{exclude_list}} >= -1 &&
110 $#{$self->{include_file}} + $#{$self->{include_list}} >= -1) {
111 $self->print_to_server_and_die("Can't have both include and exclude",
112 $Amanda::Script_App::ERROR);
115 if ($#{$self->{exclude_file}} >= 0) {
116 $self->{exclude} = [ @{$self->{exclude_file}} ];
118 foreach my $file (@{$self->{exclude_list}}) {
119 if (!open(FF, $file)) {
120 if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
121 $self->print_to_server("Open of '$file' failed: $!",
122 $Amanda::Script_App::ERROR);
128 push @{$self->{exclude}}, $_;
133 if ($self->{action} eq "restore" and defined $self->{'include_list'}) {
134 # put all include in a single file $self->{'include_filename'}
135 $self->{'include_filename'} = "$AMANDA_TMPDIR/amsamba.$$.include";
136 open INC_FILE, ">$self->{'include_filename'}";
137 if ($#{$self->{include_file}} >= 0) {
138 print INC_FILE "$self->{include_file}\n";
140 foreach my $file (@{$self->{include_list}}) {
141 if (!open(FF, $file)) {
142 if ($self->{action} eq 'check' && !$self->{include_optional}) {
143 $self->print_to_server("Open of '$file' failed: $!",
144 $Amanda::Script_App::ERROR);
154 # add command line include for amrestore
155 for(my $i=1;defined $ARGV[$i]; $i++) {
156 my $param = $ARGV[$i];
158 print INC_FILE "$1\n";
163 # put all include in $self->{'include'} they will be added on
165 if ($#{$self->{include_file}} >= 0) {
166 $self->{include} = [ @{$self->{include_file}} ];
169 foreach my $file (@{$self->{include_list}}) {
170 if (!open(FF, $file)) {
171 if ($self->{action} eq 'check' && !$self->{include_optional}) {
172 $self->print_to_server("Open of '$file' failed: $!",
173 $Amanda::Script_App::ERROR);
179 push @{$self->{include}}, $_;
184 # add command line include for amrestore
185 if ($self->{action} eq "restore") {
186 for(my $i=1;defined $ARGV[$i]; $i++) {
187 my $param = $ARGV[$i];
189 push @{$self->{include}}, $1;
196 # $self->{directory} == //host/share/subdir \\host\share\subdir
198 # $self->{device} == //host/share/subdir \\host\share\subdir
200 # $self->{cifshost} = //host \\host
201 # $self->{share} = //host/share \\host\share
202 # $self->{sambashare} = \\host\share \\host\share
203 # $self->{subdir} = subdir subdir
206 my $to_parse = $self->{directory};
207 $to_parse = $self->{device} if !defined $to_parse;;
209 return if !defined $to_parse;
212 if ($to_parse =~ m,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
216 $self->{share} = $to_parse
218 $self->{sambashare} = $self->{share};
219 $to_parse =~ m,^(\\\\[^\\]+)\\[^\\]+,;
220 $self->{cifshost} = $1;
222 if ($to_parse =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
226 $self->{share} = $to_parse
228 $self->{sambashare} = $self->{share};
229 $self->{sambashare} =~ s,/,\\,g;
230 $to_parse =~ m,^(//[^/]+)/[^/]+,;
231 $self->{cifshost} = $1;
236 # Read $self->{amandapass} file.
238 # $self->{cifshost} == //host/share
239 # $self->{share} == //host/share
241 # $self->{domain} = domain to connect to.
242 # $self->{username} = username (-U)
243 # $self->{password} = password
250 $self->{domain} = undef;
251 $self->{username} = undef;
252 $self->{password} = undef;
254 debug("amandapass: $self->{amandapass}");
255 if (!open($amandapass, $self->{amandapass})) {
256 if ($self->{allow_anonymous}) {
257 $self->{username} = $self->{allow_anonymous};
258 debug("cannot open password file '$self->{amandapass}': $!\n");
259 debug("Using anonymous user: $self->{username}");
262 $self->print_to_server_and_die(
263 "cannot open password file '$self->{amandapass}': $!",
264 $Amanda::Script_App::ERROR);
268 while ($line = <$amandapass>) {
270 next if $line =~ /^#/;
271 my ($diskname, $userpasswd, $domain, $extra) = Amanda::Util::split_quoted_string_friendly($line);
273 debug("Trailling characters ignored in amandapass line");
275 if (defined $diskname &&
277 ($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
278 ($self->{unc}==1 && $diskname =~ m,^(\\\\[^\\]+)\\\*$, && $1 eq $self->{cifshost}) ||
279 $diskname eq $self->{share} ||
280 $diskname eq $self->{sambashare})) {
281 if (defined $userpasswd && $userpasswd ne "") {
282 $self->{domain} = $domain if defined $domain && $domain ne "";
283 my ($username, $password) = split('%', $userpasswd, 2);
284 $self->{username} = $username;
285 $self->{password} = $password;
286 $self->{password} = undef if (defined $password && $password eq "");
288 $self->{username} = "guest";
295 if ($self->{allow_anonymous}) {
296 $self->{username} = $self->{allow_anonymous};
297 debug("Cannot find password for share $self->{share} in $self->{amandapass}");
298 debug("Using anonymous user: $self->{username}");
301 $self->print_to_server_and_die(
302 "Cannot find password for share $self->{share} in $self->{amandapass}",
303 $Amanda::Script_App::ERROR);
306 sub command_support {
309 print "CONFIG YES\n";
312 print "MAX-LEVEL 1\n";
313 print "INDEX-LINE YES\n";
314 print "INDEX-XML NO\n";
315 print "MESSAGE-LINE YES\n";
316 print "MESSAGE-XML NO\n";
317 print "RECORD YES\n";
318 print "COLLECTION NO\n";
319 print "MULTI-ESTIMATE NO\n";
320 print "CALCSIZE NO\n";
321 print "CLIENT-ESTIMATE YES\n";
322 print "EXCLUDE-FILE YES\n";
323 print "EXCLUDE-LIST YES\n";
324 print "EXCLUDE-OPTIONAL YES\n";
325 print "INCLUDE-FILE YES\n";
326 print "INCLUDE-LIST YES\n";
327 print "INCLUDE-OPTIONAL YES\n";
328 print "RECOVER-MODE SMB\n";
331 sub command_selfcheck {
335 if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
336 $self->print_to_server(
337 "smbclient not set; you must define the SMBCLIENT-PATH property",
338 $Amanda::Script_App::ERROR);
340 elsif (! -e $self->{smbclient}) {
341 $self->print_to_server("$self->{smbclient} doesn't exist",
342 $Amanda::Script_App::ERROR);
344 elsif (! -x $self->{smbclient}) {
345 $self->print_to_server("$self->{smbclient} is not executable",
346 $Amanda::Script_App::ERROR);
348 $self->print_to_server("$self->{smbclient}",
349 $Amanda::Script_App::GOOD);
350 if (!defined $self->{disk} || !defined $self->{device}) {
353 $self->parsesharename();
355 $self->validate_inexclude();
357 print "OK " . $self->{share} . "\n";
358 print "OK " . $self->{disk} . "\n";
359 print "OK " . $self->{device} . "\n";
360 print "OK " . $self->{directory} . "\n" if defined $self->{directory};
362 my ($child_rdr, $parent_wtr);
363 if (defined $self->{password}) {
364 # Don't set close-on-exec
366 pipe($child_rdr, $parent_wtr);
368 $parent_wtr->autoflush(1);
370 my($wtr, $rdr, $err);
371 $err = Symbol::gensym;
372 my $pid = open3($wtr, $rdr, $err, "-");
375 if (defined $self->{password}) {
376 my $ff = $child_rdr->fileno;
377 debug("child_rdr $ff");
378 $parent_wtr->close();
379 $ENV{PASSWD_FD} = $child_rdr->fileno;
384 push @ARGV, $self->{smbclient}, $self->{share};
385 push @ARGV, "" if (!defined $self->{password});
386 push @ARGV, "-U", $self->{username},
388 if (defined $self->{domain}) {
389 push @ARGV, "-W", $self->{domain},
391 if (defined $self->{subdir}) {
392 push @ARGV, "-D", $self->{subdir},
394 push @ARGV, "-c", "quit";
395 debug("execute: " . $self->{smbclient} . " " .
397 exec {$self->{smbclient}} @ARGV;
400 if (defined $self->{password}) {
401 my $ff = $parent_wtr->fileno;
402 debug("parent_wtr $ff");
403 $parent_wtr->print($self->{password});
404 $parent_wtr->close();
407 debug("No password");
413 debug("stderr: " . $_);
415 # message if samba server is configured with 'security = share'
416 next if /Server not using user level security and no password supplied./;
417 $self->print_to_server("smbclient: $_",
418 $Amanda::Script_App::ERROR);
426 sub command_estimate {
429 $self->parsesharename();
431 $self->validate_inexclude();
433 my $level = $self->{level}[0];
434 my ($child_rdr, $parent_wtr);
435 if (defined $self->{password}) {
436 # Don't set close-on-exec
438 pipe($child_rdr, $parent_wtr);
440 $parent_wtr->autoflush(1);
442 my($wtr, $rdr, $err);
443 $err = Symbol::gensym;
444 my $pid = open3($wtr, $rdr, $err, "-");
447 if (defined $self->{password}) {
448 my $ff = $child_rdr->fileno;
449 debug("child_rdr $ff");
450 $parent_wtr->close();
451 $ENV{PASSWD_FD} = $child_rdr->fileno;
456 push @ARGV, $self->{smbclient}, $self->{share};
457 push @ARGV, "" if (!defined($self->{password}));
458 push @ARGV, "-d", "0",
459 "-U", $self->{username},
461 if (defined $self->{domain}) {
462 push @ARGV, "-W", $self->{domain},
464 if (defined $self->{subdir}) {
465 push @ARGV, "-D", $self->{subdir},
468 push @ARGV, "-c", "archive 0;recurse;du";
470 push @ARGV, "-c", "archive 1;recurse;du";
472 debug("execute: " . $self->{smbclient} . " " .
474 exec {$self->{smbclient}} @ARGV;
477 if (defined $self->{password}) {
478 my $ff = $parent_wtr->fileno;
479 debug("parent_wtr $ff");
480 debug("password $self->{password}");
481 $parent_wtr->print($self->{password});
482 $parent_wtr->close();
487 my $size = $self->parse_estimate($err);
489 output_size($level, $size);
500 next if /blocks of size/;
501 next if /blocks available/;
504 next if /dumped \d+ files and directories/;
505 # message if samba server is configured with 'security = share'
506 next if /Server not using user level security and no password supplied./;
508 if ($_ =~ /^Total number of bytes: (\d*)/) {
512 $self->print_to_server("smbclient: $_",
513 $Amanda::Script_App::ERROR);
523 print "$level -1 -1\n";
527 my($ksize) = int $size / (1024);
528 $ksize=32 if ($ksize<32);
529 print "$level $ksize 1\n";
536 my $level = $self->{level}[0];
538 $self->parsesharename();
540 $self->validate_inexclude();
542 my $pid_tee = open3(\*INDEX_IN, '>&STDOUT', \*INDEX_TEE, "-");
548 while (($size = POSIX::read(0, $buf, 32768)) > 0) {
549 POSIX::write(1, $buf, $size);
550 POSIX::write(2, $buf, $size);
554 my ($child_rdr, $parent_wtr);
555 if (defined $self->{password}) {
556 # Don't set close-on-exec
558 pipe($child_rdr, $parent_wtr);
560 $parent_wtr->autoflush(1);
563 $err = Symbol::gensym;
564 my $pid = open3($wtr, ">&INDEX_IN", $err, "-");
567 if (defined $self->{password}) {
568 my $ff = $child_rdr->fileno;
569 debug("child_rdr $ff");
570 $parent_wtr->close();
571 $ENV{PASSWD_FD} = $child_rdr->fileno;
575 push @ARGV, $self->{smbclient}, $self->{share};
576 push @ARGV, "" if (!defined($self->{password}));
577 push @ARGV, "-d", "0",
578 "-U", $self->{username},
580 if (defined $self->{domain}) {
581 push @ARGV, "-W", $self->{domain},
583 if (defined $self->{subdir}) {
584 push @ARGV, "-D", $self->{subdir},
589 $comm = "tarmode full reset hidden system quiet;";
591 $comm = "tarmode inc noreset hidden system quiet;";
594 if ($#{$self->{exclude}} >= 0) {
597 if ($#{$self->{include}} >= 0) {
601 if ($#{$self->{exclude}} >= 0) {
602 $comm .= " " . join(" ", @{$self->{exclude}});
604 if ($#{$self->{include}} >= 0) {
605 $comm .= " " . join(" ", @{$self->{include}});
607 push @ARGV, "-c", $comm;
608 debug("execute: " . $self->{smbclient} . " " .
610 exec {$self->{smbclient}} @ARGV;
613 if (defined $self->{password}) {
614 my $ff = $parent_wtr->fileno;
615 debug("parent_wtr $ff");
616 debug("password $self->{password}");
617 $parent_wtr->print($self->{password});
618 $parent_wtr->close();
621 debug("No password");
627 debug("$self->{gnutar} -tf -");
628 my $pid_index1 = open2($index, '<&INDEX_TEE', $self->{gnutar}, "-tf", "-");
631 my $index_fd = $index->fileno;
632 debug("index $index_fd");
633 if (defined($self->{index})) {
635 open($indexout_fd, '>&=4') ||
636 $self->print_to_server_and_die("Can't open indexout_fd: $!",
637 $Amanda::Script_App::ERROR);
638 $self->parse_backup($index, $self->{mesgout}, $indexout_fd);
642 $self->parse_backup($index_fd, $self->{mesgout}, undef);
648 debug("stderr: " . $_);
650 next if /^tarmode is now /;
651 next if /dumped (\d+) files and directories/;
652 # message if samba server is configured with 'security = share'
653 next if /Server not using user level security and no password supplied./;
654 if (/^Total bytes written: (\d*)/) {
657 $self->print_to_server("smbclient: $_",
658 $Amanda::Script_App::ERROR);
662 my $ksize = $size / 1024;
666 print {$self->{mesgout}} "sendbackup: size $ksize\n";
667 print {$self->{mesgout}} "sendbackup: end\n";
672 $self->print_to_server_and_die("smbclient returned error",
673 $Amanda::Script_App::ERROR);
680 my($fhin, $fhout, $indexout) = @_;
684 if(defined($indexout)) {
685 if(defined($self->{index})) {
697 sub command_index_from_output {
698 index_from_output(0, 1);
702 sub index_from_output {
703 my($fhin, $fhout) = @_;
706 next if /^Total bytes written:/;
713 sub command_index_from_image {
716 open($index_fd, "$self->{gnutar} --list --file - |") ||
717 $self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
718 $Amanda::Script_App::ERROR);
719 index_from_output($index_fd, 1);
722 sub command_restore {
726 $self->parsesharename();
727 chdir(Amanda::Util::get_original_cwd());
729 if ($self->{recover_mode} eq "smb") {
730 $self->validate_inexclude();
732 push @cmd, $self->{smbclient}, $self->{share};
733 push @cmd, "" if (!defined $self->{password});
734 push @cmd, "-d", "0",
735 "-U", $self->{username};
737 if (defined $self->{domain}) {
738 push @cmd, "-W", $self->{domain};
740 if (defined $self->{'include_filename'}) {
741 push @cmd, "-TFx", "-", "$self->{'include_filename'}";
743 push @cmd, "-Tx", "-";
744 if ($#{$self->{include}} >= 0) {
745 push @cmd, @{$self->{include}};
747 for(my $i=1;defined $ARGV[$i]; $i++) {
748 my $param = $ARGV[$i];
753 my ($parent_rdr, $child_wtr);
754 if (defined $self->{password}) {
755 # Don't set close-on-exec
757 pipe($parent_rdr, $child_wtr);
759 $child_wtr->autoflush(1);
761 my($wtr, $rdr, $err);
762 $err = Symbol::gensym;
763 my $pid = open3($wtr, $rdr, $err, "-");
765 $child_wtr->print($self->{password});
769 if (defined $self->{password}) {
771 $ENV{PASSWD_FD} = $parent_rdr->fileno;
773 debug("cmd:" . join(" ", @cmd));
774 exec { $cmd[0] } @cmd;
775 die("Can't exec '", $cmd[0], "'");
777 push @cmd, $self->{gnutar}, "-xpvf", "-";
778 if (defined $self->{directory}) {
779 if (!-d $self->{directory}) {
780 $self->print_to_server_and_die(
781 "Directory $self->{directory}: $!",
782 $Amanda::Script_App::ERROR);
784 if (!-w $self->{directory}) {
785 $self->print_to_server_and_die(
786 "Directory $self->{directory}: $!",
787 $Amanda::Script_App::ERROR);
789 push @cmd, "--directory", $self->{directory};
791 if ($#{$self->{include_list}} == 0) {
792 push @cmd, "--files-from", $self->{include_list}[0];
794 if ($#{$self->{exclude_list}} == 0) {
795 push @cmd, "--exclude-from", $self->{exclude_list}[0];
797 for(my $i=1;defined $ARGV[$i]; $i++) {
798 my $param = $ARGV[$i];
802 debug("cmd:" . join(" ", @cmd));
803 exec { $cmd[0] } @cmd;
804 die("Can't exec '", $cmd[0], "'");
808 sub command_validate {
811 if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
812 return $self->default_validate();
815 my(@cmd) = ($self->{gnutar}, "-tf", "-");
816 debug("cmd:" . join(" ", @cmd));
817 my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
818 $self->print_to_server_and_die("Unable to run @cmd: $!",
819 $Amanda::Script_App::ERROR);
822 $self->print_to_server_and_die("$self->{gnutar} returned error",
823 $Amanda::Script_App::ERROR);
828 sub command_print_command {
835 Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
852 my $opt_smbclient_path;
854 my @opt_exclude_file;
855 my @opt_exclude_list;
856 my $opt_exclude_optional;
857 my @opt_include_file;
858 my @opt_include_list;
859 my $opt_include_optional;
860 my $opt_recover_mode;
861 my $opt_allow_anonymous;
864 Getopt::Long::Configure(qw{bundling});
866 'version' => \$opt_version,
867 'config=s' => \$opt_config,
868 'host=s' => \$opt_host,
869 'disk=s' => \$opt_disk,
870 'device=s' => \$opt_device,
871 'level=s' => \@opt_level,
872 'index=s' => \$opt_index,
873 'message=s' => \$opt_message,
874 'collection=s' => \$opt_collection,
875 'record' => \$opt_record,
876 'calcsize' => \$opt_calcsize,
877 'gnutar-path=s' => \$opt_gnutar_path,
878 'smbclient-path=s' => \$opt_smbclient_path,
879 'amandapass=s' => \$opt_amandapass,
880 'exclude-file=s' => \@opt_exclude_file,
881 'exclude-list=s' => \@opt_exclude_list,
882 'exclude-optional=s' => \$opt_exclude_optional,
883 'include-file=s' => \@opt_include_file,
884 'include-list=s' => \@opt_include_list,
885 'include-optional=s' => \$opt_include_optional,
886 'recover-mode=s' => \$opt_recover_mode,
887 'allow-anonymous=s' => \$opt_allow_anonymous,
888 'directory=s' => \$opt_directory,
891 if (defined $opt_version) {
892 print "amsamba-" . $Amanda::Constants::VERSION , "\n";
896 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, $opt_allow_anonymous, $opt_directory);
898 $application->do($ARGV[0]);