#!@PERL@
-# Copyright (c) 2008,2009 Zmanda, Inc. All Rights Reserved.
+# Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 2 as published
-# by the Free Software Foundation.
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
use lib '@amperldir@';
use strict;
+use warnings;
use Getopt::Long;
package Amanda::Application::Amsamba;
use Sys::Hostname;
use Symbol;
use IO::Handle;
+use MIME::Base64 ();
use Amanda::Constants;
use Amanda::Config qw( :init :getconf config_dir_relative );
use Amanda::Debug qw( :logging );
use Amanda::Paths;
use Amanda::Util qw( :constants :quoting);
+use Amanda::MainLoop qw( :GIOCondition );
sub new {
my $class = shift;
} else {
$self->{device} = $disk;
}
- if ($self->{disk} =~ /^\\\\/) {
- $self->{unc} = 1;
- } else {
- $self->{unc} = 0;
- }
$self->{level} = [ @{$level} ];
$self->{index} = $index;
$self->{message} = $message;
#on exit:
# $self->{exclude}
# $self->{include}
+# $self->{include_filename}
sub validate_inexclude {
my $self = shift;
}
close(FF);
}
- if ($#{$self->{include_file}} >= 0) {
- $self->{include} = [ @{$self->{include_file}} ];
- }
- foreach my $file (@{$self->{include_list}}) {
- if (!open(FF, $file)) {
- if ($self->{action} eq 'check' && !$self->{include_optional}) {
- $self->print_to_server("Open of '$file' failed: $!",
- $Amanda::Script_App::ERROR);
+
+ if ($self->{action} eq "restore" and defined $self->{'include_list'}) {
+ # put all include in a single file $self->{'include_filename'}
+ $self->{'include_filename'} = "$AMANDA_TMPDIR/amsamba.$$.include";
+ open INC_FILE, ">$self->{'include_filename'}";
+ if ($#{$self->{include_file}} >= 0) {
+ print INC_FILE "$self->{include_file}\n";
+ }
+ foreach my $file (@{$self->{include_list}}) {
+ if (!open(FF, $file)) {
+ if ($self->{action} eq 'check' && !$self->{include_optional}) {
+ $self->print_to_server("Open of '$file' failed: $!",
+ $Amanda::Script_App::ERROR);
+ }
+ next;
}
- next;
+ while (<FF>) {
+ if (defined $self->{'subdir'}) {
+ $_ =~ s/^\./$self->{'subdir'}/;
+ }
+ print INC_FILE;
+ }
+ close(FF);
}
- while (<FF>) {
- chomp;
- push @{$self->{include}}, $_;
+
+ # add command line include for amrestore
+ for(my $i=1;defined $ARGV[$i]; $i++) {
+ my $param = $ARGV[$i];
+ $param =~ /^(.*)$/;
+ $_ = $1;
+ if (defined $self->{'subdir'}) {
+ $_ =~ s/^\./$self->{'subdir'}/;
+ }
+ print INC_FILE "$_\n";
+ }
+
+ close INC_FILE;
+ } else {
+ # put all include in $self->{'include'} they will be added on
+ # command line.
+ if ($#{$self->{include_file}} >= 0) {
+ $self->{include} = [ @{$self->{include_file}} ];
+ }
+
+ foreach my $file (@{$self->{include_list}}) {
+ if (!open(FF, $file)) {
+ if ($self->{action} eq 'check' && !$self->{include_optional}) {
+ $self->print_to_server("Open of '$file' failed: $!",
+ $Amanda::Script_App::ERROR);
+ }
+ next;
+ }
+ while (<FF>) {
+ chomp;
+ if ($self->{action} eq "restore" and
+ defined $self->{'subdir'}) {
+ $_ =~ s/^\./$self->{'subdir'}/;
+ }
+ push @{$self->{include}}, $_;
+ }
+ close(FF);
+ }
+
+ # add command line include for amrestore
+ if ($self->{action} eq "restore") {
+ for(my $i=1;defined $ARGV[$i]; $i++) {
+ my $param = $ARGV[$i];
+ $param =~ /^(.*)$/;
+ $_ = $1;
+ if (defined $self->{'subdir'}) {
+ $_ =~ s/^\./$self->{'subdir'}/;
+ }
+ push @{$self->{include}}, $1;
+ }
}
- close(FF);
}
}
$to_parse = $self->{device} if !defined $to_parse;;
return if !defined $to_parse;
+ if ($to_parse =~ /^\\\\/) {
+ $self->{unc} = 1;
+ } else {
+ $self->{unc} = 0;
+ }
if ($self->{unc}) {
if ($to_parse =~ m,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
while ($line = <$amandapass>) {
chomp $line;
next if $line =~ /^#/;
- my ($diskname, $userpasswd, $domain, $extra);
- ($diskname, $userpasswd) = Amanda::Util::skip_quoted_string($line);
- if ($userpasswd) {
- ($userpasswd, $domain) =
- Amanda::Util::skip_quoted_string($userpasswd);
- }
- if ($domain) {
- ($domain, $extra) =
- Amanda::Util::skip_quoted_string($domain);
- }
+ my ($diskname, $userpasswd, $domain, $extra) = Amanda::Util::split_quoted_string_friendly($line);
if ($extra) {
debug("Trailling characters ignored in amandapass line");
}
- $diskname = Amanda::Util::unquote_string($diskname);
- $userpasswd = Amanda::Util::unquote_string($userpasswd);
- $domain = Amanda::Util::unquote_string($domain);
if (defined $diskname &&
($diskname eq '*' ||
($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
$diskname eq $self->{share} ||
$diskname eq $self->{sambashare})) {
if (defined $userpasswd && $userpasswd ne "") {
- $self->{domain} = $domain if ($domain ne "");
+ $self->{domain} = $domain if defined $domain && $domain ne "";
my ($username, $password) = split('%', $userpasswd, 2);
$self->{username} = $username;
+ if ($password =~ /^6G\!dr(.*)/) {
+ my $base64 = $1;
+ $password = MIME::Base64::decode($base64);
+ }
$self->{password} = $password;
$self->{password} = undef if (defined $password && $password eq "");
} else {
sub command_selfcheck {
my $self = shift;
+ $self->print_to_server("disk " . quote_string($self->{disk}),
+ $Amanda::Script_App::GOOD);
+
+ $self->print_to_server("amsamba version " . $Amanda::Constants::VERSION,
+ $Amanda::Script_App::GOOD);
#check binary
if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
$self->print_to_server(
elsif (! -x $self->{smbclient}) {
$self->print_to_server("$self->{smbclient} is not executable",
$Amanda::Script_App::ERROR);
+ } else {
+ my @sv = `$self->{smbclient} --version`;
+ if ($? >> 8 == 0) {
+ $sv[0] =~ /^[^0-9]*(.*)$/;
+ my $sv = $1;
+ $self->print_to_server("amsamba smbclient-version $sv",
+ $Amanda::Script_App::GOOD);
+ } else {
+ $self->print_to_server(
+ "[Can't get " . $self->{smbclient} . " version]\n",
+ $Amanda::Script_App::ERROR);
+ }
}
+
$self->print_to_server("$self->{smbclient}",
$Amanda::Script_App::GOOD);
if (!defined $self->{disk} || !defined $self->{device}) {
$self->validate_inexclude();
print "OK " . $self->{share} . "\n";
- print "OK " . $self->{disk} . "\n";
print "OK " . $self->{device} . "\n";
print "OK " . $self->{directory} . "\n" if defined $self->{directory};
- my ($child_rdr, $parent_wtr);
+ my ($password_rdr, $password_wtr);
if (defined $self->{password}) {
# Don't set close-on-exec
$^F=10;
- pipe($child_rdr, $parent_wtr);
+ pipe($password_rdr, $password_wtr);
$^F=2;
- $parent_wtr->autoflush(1);
+ $password_wtr->autoflush(1);
}
my($wtr, $rdr, $err);
$err = Symbol::gensym;
if ($pid == 0) {
#child
if (defined $self->{password}) {
- my $ff = $child_rdr->fileno;
- debug("child_rdr $ff");
- $parent_wtr->close();
- $ENV{PASSWD_FD} = $child_rdr->fileno;
+ my $ff = $password_rdr->fileno;
+ debug("password_rdr $ff");
+ $password_wtr->close();
+ $ENV{PASSWD_FD} = $password_rdr->fileno;
}
close(1);
close(2);
}
#parent
if (defined $self->{password}) {
- my $ff = $parent_wtr->fileno;
- debug("parent_wtr $ff");
- $parent_wtr->print($self->{password});
- $parent_wtr->close();
- $child_rdr->close();
+ my $ff = $password_wtr->fileno;
+ debug("password_wtr $ff");
+ $password_wtr->print($self->{password});
+ $password_wtr->close();
+ $password_rdr->close();
} else {
debug("No password");
}
$self->validate_inexclude();
my $level = $self->{level}[0];
- my ($child_rdr, $parent_wtr);
+ my ($password_rdr, $password_wtr);
if (defined $self->{password}) {
# Don't set close-on-exec
$^F=10;
- pipe($child_rdr, $parent_wtr);
+ pipe($password_rdr, $password_wtr);
$^F=2;
- $parent_wtr->autoflush(1);
+ $password_wtr->autoflush(1);
}
my($wtr, $rdr, $err);
$err = Symbol::gensym;
if ($pid == 0) {
#child
if (defined $self->{password}) {
- my $ff = $child_rdr->fileno;
- debug("child_rdr $ff");
- $parent_wtr->close();
- $ENV{PASSWD_FD} = $child_rdr->fileno;
+ my $ff = $password_rdr->fileno;
+ debug("password_rdr $ff");
+ $password_wtr->close();
+ $ENV{PASSWD_FD} = $password_rdr->fileno;
}
close(0);
close(1);
}
#parent
if (defined $self->{password}) {
- my $ff = $parent_wtr->fileno;
- debug("parent_wtr $ff");
+ my $ff = $password_wtr->fileno;
+ debug("password_wtr $ff");
debug("password $self->{password}");
- $parent_wtr->print($self->{password});
- $parent_wtr->close();
- $child_rdr->close();
+ $password_wtr->print($self->{password});
+ $password_wtr->close();
+ $password_rdr->close();
}
close($wtr);
close($rdr);
}
}
+sub send_empty_tar_file {
+ my $self = shift;
+ my ($out1, $out2) = @_;
+ my $out;
+ my $buf;
+ my $size;
+
+ Amanda::Debug::debug("Create empty archive with: tar --create --file=- --files-from=/dev/null");
+ open2($out, undef, "tar", "--create", "--file=-", "--files-from=/dev/null");
+
+ while(($size = sysread($out, $buf, 32768))) {
+ syswrite($out1, $buf, $size);
+ syswrite($out2, $buf, $size);
+ }
+}
+
sub command_backup {
my $self = shift;
my $level = $self->{level}[0];
- my $mesgout_fd;
- open($mesgout_fd, '>&=3') ||
- $self->print_to_server_and_die("Can't open mesgout_fd: $!",
- $Amanda::Script_App::ERROR);
- $self->{mesgout} = $mesgout_fd;
$self->parsesharename();
$self->findpass();
$self->validate_inexclude();
- my $pid_tee = open3(\*INDEX_IN, '>&STDOUT', \*INDEX_TEE, "-");
- if ($pid_tee == 0) {
- close(INDEX_IN);
- close(INDEX_TEE);
- my $buf;
- my $size = -1;
- while (($size = POSIX::read(0, $buf, 32768)) > 0) {
- POSIX::write(1, $buf, $size);
- POSIX::write(2, $buf, $size);
- }
- exit 0;
- }
- my ($child_rdr, $parent_wtr);
+ my ($password_rdr, $password_wtr);
if (defined $self->{password}) {
# Don't set close-on-exec
$^F=10;
- pipe($child_rdr, $parent_wtr);
+ pipe($password_rdr, $password_wtr);
$^F=2;
- $parent_wtr->autoflush(1);
+ $password_wtr->autoflush(1);
}
- my($wtr, $err);
- $err = Symbol::gensym;
- my $pid = open3($wtr, ">&INDEX_IN", $err, "-");
+ my($smbclient_wtr, $smbclient_rdr, $smbclient_err);
+ $smbclient_err = Symbol::gensym;
+ my $pid = open3($smbclient_wtr, $smbclient_rdr, $smbclient_err, "-");
if ($pid == 0) {
#child
if (defined $self->{password}) {
- my $ff = $child_rdr->fileno;
- debug("child_rdr $ff");
- $parent_wtr->close();
- $ENV{PASSWD_FD} = $child_rdr->fileno;
+ my $ff = $password_rdr->fileno;
+ debug("password_rdr $ff");
+ $password_wtr->close();
+ $ENV{PASSWD_FD} = $password_rdr->fileno;
}
- close(0);
my @ARGV = ();
push @ARGV, $self->{smbclient}, $self->{share};
push @ARGV, "" if (!defined($self->{password}));
}
if (defined $self->{password}) {
- my $ff = $parent_wtr->fileno;
- debug("parent_wtr $ff");
- debug("password $self->{password}");
- $parent_wtr->print($self->{password});
- $parent_wtr->close();
- $child_rdr->close();
+ my $ff = $password_wtr->fileno;
+ debug("password_wtr $ff");
+ $password_wtr->print($self->{password});
+ $password_wtr->close();
+ $password_rdr->close();
} else {
debug("No password");
}
- close($wtr);
+ close($smbclient_wtr);
#index process
- my $index;
+ my $index_rdr;
+ my $index_wtr;
debug("$self->{gnutar} -tf -");
- my $pid_index1 = open2($index, '<&INDEX_TEE', $self->{gnutar}, "-tf", "-");
- close(INDEX_IN);
+ my $pid_index1 = open2($index_rdr, $index_wtr, $self->{gnutar}, "-tf", "-");
my $size = -1;
- my $index_fd = $index->fileno;
+ my $index_fd = $index_rdr->fileno;
debug("index $index_fd");
+ my $indexout_fd;
if (defined($self->{index})) {
- my $indexout_fd;
open($indexout_fd, '>&=4') ||
$self->print_to_server_and_die("Can't open indexout_fd: $!",
$Amanda::Script_App::ERROR);
- $self->parse_backup($index, $mesgout_fd, $indexout_fd);
- close($indexout_fd);
}
- else {
- $self->parse_backup($index_fd, $mesgout_fd, undef);
- }
- close($index);
- while (<$err>) {
- chomp;
- debug("stderr: " . $_);
- next if /^Domain=/;
- next if /^tarmode is now /;
- next if /dumped (\d+) files and directories/;
+ my $file_to_close = 3;
+ my $smbclient_stdout_src = Amanda::MainLoop::fd_source($smbclient_rdr,
+ $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
+ my $smbclient_stderr_src = Amanda::MainLoop::fd_source($smbclient_err,
+ $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
+ my $index_tar_stdout_src = Amanda::MainLoop::fd_source($index_rdr,
+ $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
+
+ my $smbclient_stdout_done = 0;
+ my $smbclient_stderr_done = 0;
+ my $data_size = 0;
+ my $nb_files = 0;
+ $smbclient_stdout_src->set_callback(sub {
+ my $buf;
+ my $blocksize = -1;
+ $blocksize = sysread($smbclient_rdr, $buf, 32768);
+ if (!$blocksize) {
+ $file_to_close--;
+ $smbclient_stdout_src->remove();
+ $smbclient_stdout_done = 1;
+ if ($smbclient_stderr_done) {
+ if ($data_size == 0 and $nb_files == 0 and $size == 0) {
+ $self->send_empty_tar_file(*STDOUT, $index_wtr);
+ }
+ close($index_wtr);
+ close(STDOUT);
+ }
+ close($smbclient_rdr);
+ Amanda::MainLoop::quit() if $file_to_close == 0;
+ return;
+ }
+ $data_size += $blocksize;
+ syswrite(STDOUT, $buf, $blocksize);
+ syswrite($index_wtr, $buf, $blocksize);
+ });
+
+ $smbclient_stderr_src->set_callback(sub {
+ my $line = <$smbclient_err>;
+ if (!defined $line) {
+ $file_to_close--;
+ $smbclient_stderr_src->remove();
+ $smbclient_stderr_done = 1;
+ if ($smbclient_stdout_done) {
+ if ($data_size == 0 and $nb_files == 0 and $size == 0) {
+ $self->send_empty_tar_file(*STDOUT, $index_wtr);
+ }
+ close($index_wtr);
+ close(STDOUT);
+ }
+ close ($smbclient_err);
+ Amanda::MainLoop::quit() if $file_to_close == 0;
+ return;
+ }
+ chomp $line;
+ debug("stderr: " . $line);
+ return if $line =~ /^Domain=/;
+ return if $line =~ /^tarmode is now /;
+ if ($line =~ /dumped (\d+) files and directories/) {
+ $nb_files = $1;
+ return;
+ }
# message if samba server is configured with 'security = share'
- next if /Server not using user level security and no password supplied./;
- if (/^Total bytes written: (\d*)/) {
+ return if $line =~$line =~ /Server not using user level security and no password supplied./;
+ if ($line =~ /^Total bytes written: (\d*)/) {
$size = $1;
+ return;
+ }
+ $self->print_to_server("smbclient: $line", $Amanda::Script_App::ERROR);
+ });
+
+ $index_tar_stdout_src->set_callback(sub {
+ my $line = <$index_rdr>;
+ if (!defined $line) {
+ $file_to_close--;
+ $index_tar_stdout_src->remove();
+ close($index_rdr);
+ close($indexout_fd);
+ Amanda::MainLoop::quit() if $file_to_close == 0;
+ return;
+ }
+ if ($line =~ /^\.\//) {
+ if(defined($indexout_fd)) {
+ if(defined($self->{index})) {
+ $line =~ s/^\.//;
+ print $indexout_fd $line;
+ }
+ }
} else {
- $self->print_to_server("smbclient: $_",
- $Amanda::Script_App::ERROR);
+ chomp $line;
+ $self->print_to_server($line, $Amanda::Script_App::ERROR);
}
- }
+ });
+
+ Amanda::MainLoop::run();
+
if ($size >= 0) {
my $ksize = $size / 1024;
if ($ksize < 32) {
$ksize = 32;
}
- print $mesgout_fd "sendbackup: size $ksize\n";
- print $mesgout_fd "sendbackup: end\n";
+ print {$self->{mesgout}} "sendbackup: size $ksize\n";
+ print {$self->{mesgout}} "sendbackup: end\n";
}
waitpid $pid, 0;
$self->validate_inexclude();
$self->findpass();
push @cmd, $self->{smbclient}, $self->{share};
+ push @cmd, "-D", $self->{'subdir'} if defined $self->{'subdir'};
push @cmd, "" if (!defined $self->{password});
push @cmd, "-d", "0",
"-U", $self->{username};
if (defined $self->{domain}) {
push @cmd, "-W", $self->{domain};
}
- push @cmd, "-Tx", "-";
- if ($#{$self->{include}} >= 0) {
- push @cmd, @{$self->{include}};
- }
- for(my $i=1;defined $ARGV[$i]; $i++) {
- my $param = $ARGV[$i];
- $param =~ /^(.*)$/;
- push @cmd, $1;
+ if (defined $self->{'include_filename'}) {
+ push @cmd, "-TFx", "-", "$self->{'include_filename'}";
+ } else {
+ push @cmd, "-Tx", "-";
+ if ($#{$self->{include}} >= 0) {
+ push @cmd, @{$self->{include}};
+ }
+ for(my $i=1;defined $ARGV[$i]; $i++) {
+ my $param = $ARGV[$i];
+ $param =~ /^(.*)$/;
+ push @cmd, $1;
+ }
}
my ($parent_rdr, $child_wtr);
if (defined $self->{password}) {