#!@PERL@
# Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
# Copyright (c) 2013-2016 Carbonite, 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
# 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
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Contact information: Carbonite Inc., 756 N Pastoria Ave
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
use lib '@amperldir@';
use strict;
use warnings;
use Getopt::Long;
package Amanda::Application::Amsamba;
use base qw(Amanda::Application);
use File::Copy;
use File::Path;
use IPC::Open2;
use IPC::Open3;
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;
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, $target, $regex_match) = @_;
my $self = $class->SUPER::new($config);
if (defined $gnutar_path) {
$self->{gnutar} = $gnutar_path;
} else {
$self->{gnutar} = $Amanda::Constants::GNUTAR;
}
if (defined $smbclient_path) {
$self->{smbclient} = $smbclient_path;
} else {
$self->{smbclient} = $Amanda::Constants::SAMBA_CLIENT;
}
if (defined $amandapass) {
$self->{amandapass} = config_dir_relative($amandapass);
} else {
$self->{amandapass} = "$Amanda::Paths::CONFIG_DIR/amandapass";
}
$self->{config} = $config;
$self->{host} = $host;
if (defined $disk) {
$self->{disk} = $disk;
} else {
$self->{disk} = $device;
}
if (defined $device) {
$self->{device} = $device;
} else {
$self->{device} = $disk;
}
$self->{level} = [ @{$level} ];
$self->{index} = $index;
$self->{message} = $message;
$self->{collection} = $collection;
$self->{record} = $record;
$self->{calcsize} = $calcsize;
$self->{exclude_file} = [ @{$exclude_file} ];
$self->{exclude_list} = [ @{$exclude_list} ];
$self->{exclude_optional} = $exclude_optional;
$self->{include_file} = [ @{$include_file} ];
$self->{include_list} = [ @{$include_list} ];
$self->{include_optional} = $include_optional;
$self->{recover_mode} = $recover_mode;
$self->{allow_anonymous} = $allow_anonymous;
$self->{target} = $target;
if ($regex_match =~ /^yes$/i) {
$self->{'regex_match'} = 1;
} else {
$self->{'regex_match'} = 0;
}
return $self;
}
# on entry:
# $self->{exclude_file}
# $self->{exclude_list}
# $self->{include_file}
# $self->{include_list}
#on exit:
# $self->{exclude}
# $self->{include}
# $self->{include_filename}
sub validate_inexclude {
my $self = shift;
if ($#{$self->{exclude_file}} + $#{$self->{exclude_list}} >= -1 &&
$#{$self->{include_file}} + $#{$self->{include_list}} >= -1) {
$self->print_to_server_and_die("Can't have both include and exclude",
$Amanda::Script_App::ERROR);
}
if ($#{$self->{exclude_file}} >= 0) {
$self->{exclude} = [ @{$self->{exclude_file}} ];
}
foreach my $file (@{$self->{exclude_list}}) {
if (!open(FF, $file)) {
if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
$self->print_to_server("Open of '$file' failed: $!",
$Amanda::Script_App::ERROR);
}
next;
}
while (<FF>) {
chomp;
push @{$self->{exclude}}, $_;
}
close(FF);
}
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;
}
while (<FF>) {
if (defined $self->{'subdir'}) {
$_ =~ s/^\./$self->{'subdir'}/;
}
print INC_FILE;
}
close(FF);
}
# 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'}/;
}
$_ =~ s/\\([0-7]{3})/chr oct $1/eg;
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;
}
}
}
}
# on entry:
# $self->{target} == //host/share/subdir \\host\share\subdir
# or
# $self->{device} == //host/share/subdir \\host\share\subdir
# on exit:
# $self->{cifshost} = //host \\host
# $self->{share} = //host/share \\host\share
# $self->{sambashare} = \\host\share \\host\share
# $self->{subdir} = subdir subdir
sub parsesharename {
my $self = shift;
my $to_parse = $self->{target};
$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,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
$self->{share} = $1;
$self->{subdir} = $2
} else {
$self->{share} = $to_parse
}
$self->{sambashare} = $self->{share};
$to_parse =~ m,^(\\\\[^\\]+)\\[^\\]+,;
$self->{cifshost} = $1;
} else {
if ($to_parse =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
$self->{share} = $1;
$self->{subdir} = $2
} else {
$self->{share} = $to_parse
}
$self->{sambashare} = $self->{share};
$self->{sambashare} =~ s,/,\\,g;
$to_parse =~ m,^(//[^/]+)/[^/]+,;
$self->{cifshost} = $1;
}
}
# Read $self->{amandapass} file.
# on entry:
# $self->{cifshost} == //host/share
# $self->{share} == //host/share
# on exit:
# $self->{domain} = domain to connect to.
# $self->{username} = username (-U)
# $self->{password} = password
sub findpass {
my $self = shift;
my $amandapass;
my $line;
$self->{domain} = undef;
$self->{username} = undef;
$self->{password} = undef;
debug("amandapass: $self->{amandapass}");
if (!open($amandapass, $self->{amandapass})) {
if ($self->{allow_anonymous}) {
$self->{username} = $self->{allow_anonymous};
debug("cannot open password file '$self->{amandapass}': $!\n");
debug("Using anonymous user: $self->{username}");
return;
} else {
$self->print_to_server_and_die(
"cannot open password file '$self->{amandapass}': $!",
$Amanda::Script_App::ERROR);
}
}
while ($line = <$amandapass>) {
chomp $line;
next if $line =~ /^#/;
my ($diskname, $userpasswd, $domain, $extra) = Amanda::Util::split_quoted_string_friendly($line);
if ($extra) {
debug("Trailling characters ignored in amandapass line");
}
if (defined $diskname &&
($diskname eq '*' ||
($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
($self->{unc}==1 && $diskname =~ m,^(\\\\[^\\]+)\\\*$, && $1 eq $self->{cifshost}) ||
$diskname eq $self->{share} ||
$diskname eq $self->{sambashare})) {
if (defined $userpasswd && $userpasswd 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);
chomp($password);
}
$self->{password} = $password;
$self->{password} = undef if (defined $password && $password eq "");
} else {
$self->{username} = "guest";
}
close($amandapass);
return;
}
}
close($amandapass);
if ($self->{allow_anonymous}) {
$self->{username} = $self->{allow_anonymous};
debug("Cannot find password for share $self->{share} in $self->{amandapass}");
debug("Using anonymous user: $self->{username}");
return;
}
$self->print_to_server_and_die(
"Cannot find password for share $self->{share} in $self->{amandapass}",
$Amanda::Script_App::ERROR);
}
sub command_support {
my $self = shift;
print "CONFIG YES\n";
print "HOST YES\n";
print "DISK YES\n";
print "MAX-LEVEL 1\n";
print "INDEX-LINE YES\n";
print "INDEX-XML NO\n";
print "MESSAGE-LINE YES\n";
print "MESSAGE-XML NO\n";
print "RECORD YES\n";
print "COLLECTION NO\n";
print "MULTI-ESTIMATE NO\n";
print "CALCSIZE NO\n";
print "CLIENT-ESTIMATE YES\n";
print "EXCLUDE-FILE YES\n";
print "EXCLUDE-LIST YES\n";
print "EXCLUDE-OPTIONAL YES\n";
print "INCLUDE-FILE YES\n";
print "INCLUDE-LIST YES\n";
print "INCLUDE-OPTIONAL YES\n";
print "RECOVER-MODE SMB\n";
}
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(
"smbclient not set; you must define the SMBCLIENT-PATH property",
$Amanda::Script_App::ERROR);
}
elsif (! -e $self->{smbclient}) {
$self->print_to_server("$self->{smbclient} doesn't exist",
$Amanda::Script_App::ERROR);
}
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}) {
return;
}
$self->parsesharename();
$self->findpass();
$self->validate_inexclude();
print "OK " . $self->{share} . "\n";
print "OK " . $self->{device} . "\n";
print "OK " . $self->{target} . "\n" if defined $self->{target};
my ($password_rdr, $password_wtr);
if (defined $self->{password}) {
# Don't set close-on-exec
$^F=10;
pipe($password_rdr, $password_wtr);
$^F=2;
$password_wtr->autoflush(1);
}
my($wtr, $rdr, $err);
$err = Symbol::gensym;
my $pid = open3($wtr, $rdr, $err, "-");
if ($pid == 0) {
#child
if (defined $self->{password}) {
my $ff = $password_rdr->fileno;
debug("password_rdr $ff");
$password_wtr->close();
$ENV{PASSWD_FD} = $password_rdr->fileno;
}
close(1);
close(2);
my @ARGV = ();
push @ARGV, $self->{smbclient}, $self->{share};
push @ARGV, "" if (!defined $self->{password});
push @ARGV, "-U", $self->{username},
"-E";
if (defined $self->{domain}) {
push @ARGV, "-W", $self->{domain},
}
if (defined $self->{subdir}) {
push @ARGV, "-D", $self->{subdir},
}
push @ARGV, "-c", "quit";
debug("execute: " . $self->{smbclient} . " " .
join(" ", @ARGV));
$ENV{'LC_CTYPE'} = 'en_US.UTF-8';
exec {$self->{smbclient}} @ARGV;
}
#parent
if (defined $self->{password}) {
my $ff = $password_wtr->fileno;
debug("password_wtr $ff");
$password_wtr->print("$self->{password}\n");
$password_wtr->print("$self->{password}\n");
$password_wtr->close();
$password_rdr->close();
} else {
debug("No password");
}
close($wtr);
close($rdr);
while (<$err>) {
chomp;
debug("stderr: " . $_);
next if /^Domain=/;
next if /^WARNING/g;
# message if samba server is configured with 'security = share'
next if /Server not using user level security and no password supplied./;
$self->print_to_server("smbclient: $_",
$Amanda::Script_App::ERROR);
}
close($err);
waitpid($pid, 0);
#check statefile
#check amdevice
}
sub command_estimate {
my $self = shift;
$self->parsesharename();
$self->findpass();
$self->validate_inexclude();
my $level = $self->{level}[0];
my ($password_rdr, $password_wtr);
if (defined $self->{password}) {
# Don't set close-on-exec
$^F=10;
pipe($password_rdr, $password_wtr);
$^F=2;
$password_wtr->autoflush(1);
}
my($wtr, $rdr, $err);
$err = Symbol::gensym;
my $pid = open3($wtr, $rdr, $err, "-");
if ($pid == 0) {
#child
if (defined $self->{password}) {
my $ff = $password_rdr->fileno;
debug("password_rdr $ff");
$password_wtr->close();
$ENV{PASSWD_FD} = $password_rdr->fileno;
}
close(0);
close(1);
my @ARGV = ();
push @ARGV, $self->{smbclient}, $self->{share};
push @ARGV, "" if (!defined($self->{password}));
push @ARGV, "-d", "0",
"-U", $self->{username},
"-E";
if (defined $self->{domain}) {
push @ARGV, "-W", $self->{domain},
}
if (defined $self->{subdir}) {
push @ARGV, "-D", $self->{subdir},
}
if ($level == 0) {
push @ARGV, "-c", "archive 0;recurse;du";
} else {
push @ARGV, "-c", "archive 1;recurse;du";
}
debug("execute: " . $self->{smbclient} . " " .
join(" ", @ARGV));
$ENV{'LC_CTYPE'} = 'en_US.UTF-8';
exec {$self->{smbclient}} @ARGV;
}
#parent
if (defined $self->{password}) {
my $ff = $password_wtr->fileno;
debug("password_wtr $ff");
$password_wtr->print("$self->{password}\n");
$password_wtr->print("$self->{password}\n");
$password_wtr->close();
$password_rdr->close();
}
close($wtr);
close($rdr);
my $size = $self->parse_estimate($err);
close($err);
output_size($level, $size);
waitpid($pid, 0);
}
sub parse_estimate {
my $self = shift;
my($fh) = shift;
my($size) = -1;
while(<$fh>) {
chomp;
next if /^\s*$/;
next if /blocks of size/;
next if /blocks available/;
next if /^\s*$/;
next if /^Domain=/;
next if /dumped \d+ files and directories/;
next if /^WARNING/g;
# message if samba server is configured with 'security = share'
next if /Server not using user level security and no password supplied./;
debug("stderr: $_");
if ($_ =~ /^Total number of bytes: (\d*)/) {
$size = $1;
last;
} else {
$self->print_to_server("smbclient: $_",
$Amanda::Script_App::ERROR);
}
}
return $size;
}
sub output_size {
my($level) = shift;
my($size) = shift;
if($size == -1) {
print "$level -1 -1\n";
#exit 2;
}
else {
my($ksize) = int $size / (1024);
$ksize=32 if ($ksize<32);
print "$level $ksize 1\n";
}
}
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];
$self->parsesharename();
$self->findpass();
$self->validate_inexclude();
my ($password_rdr, $password_wtr);
if (defined $self->{password}) {
# Don't set close-on-exec
$^F=10;
pipe($password_rdr, $password_wtr);
$^F=2;
$password_wtr->autoflush(1);
}
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 = $password_rdr->fileno;
debug("password_rdr $ff");
$password_wtr->close();
$ENV{PASSWD_FD} = $password_rdr->fileno;
}
my @ARGV = ();
push @ARGV, $self->{smbclient}, $self->{share};
push @ARGV, "" if (!defined($self->{password}));
push @ARGV, "-d", "0",
"-U", $self->{username},
"-E";
if (defined $self->{domain}) {
push @ARGV, "-W", $self->{domain},
}
if (defined $self->{subdir}) {
push @ARGV, "-D", $self->{subdir},
}
my $comm ;
if ($level == 0) {
$comm = "tarmode full reset hidden system quiet;";
} else {
$comm = "tarmode inc noreset hidden system quiet;";
}
$comm .= " tar c";
if ($self->{'regex_match'}) {
$comm .= "r";
}
if ($#{$self->{exclude}} >= 0) {
$comm .= "X";
}
if ($#{$self->{include}} >= 0) {
$comm .= "I";
}
$comm .= " -";
if ($#{$self->{exclude}} >= 0) {
$comm .= " " . join(" ", @{$self->{exclude}});
}
if ($#{$self->{include}} >= 0) {
$comm .= " " . join(" ", @{$self->{include}});
}
push @ARGV, "-c", $comm;
debug("execute: " . $self->{smbclient} . " " .
join(" ", @ARGV));
$ENV{'LC_CTYPE'} = 'en_US.UTF-8';
exec {$self->{smbclient}} @ARGV;
}
if (defined $self->{password}) {
my $ff = $password_wtr->fileno;
debug("password_wtr $ff");
$password_wtr->print("$self->{password}\n");
$password_wtr->print("$self->{password}\n");
$password_wtr->close();
$password_rdr->close();
} else {
debug("No password");
}
close($smbclient_wtr);
#index process
my $index_wtr;
my $index_rdr;
my $index_err = Symbol::gensym;;
debug("$self->{gnutar} -tf -");
my $pid_index1 = open3($index_wtr, $index_rdr, $index_err, $self->{gnutar}, "-tf", "-");
my $size = -1;
my $index_fd = $index_rdr->fileno;
debug("index $index_fd");
my $indexout_fd;
if (defined($self->{index})) {
open($indexout_fd, '>&=4') ||
$self->print_to_server_and_die("Can't open indexout_fd: $!",
$Amanda::Script_App::ERROR);
}
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 $index_tar_stderr_src = Amanda::MainLoop::fd_source($index_err,
$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 /;
return if $line =~ /tar_re_search set/;
return if $line =~ /WARNING/g;
if ($line =~ /dumped (\d+) files and directories/) {
$nb_files = $1;
return;
}
# message if samba server is configured with 'security = share'
return if $line =~ /Server not using user level security and no password supplied./;
if ($line =~ /Total bytes written: (\d*)/) {
$size = $1;
return;
} elsif ($line =~ /Total bytes received: (\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 {
chomp $line;
if ($line =~ /Ignoring unknown extended header keyword/) {
debug("tar stderr: $line");
} else {
$self->print_to_server($line, $Amanda::Script_App::ERROR);
}
}
});
$index_tar_stderr_src->set_callback(sub {
my $line = <$index_err>;
if (!defined $line) {
$file_to_close--;
$index_tar_stderr_src->remove();
close($index_err);
Amanda::MainLoop::quit() if $file_to_close == 0;
return;
}
chomp $line;
if ($line =~ /Ignoring unknown extended header keyword/) {
debug("tar stderr: $line");
} else {
$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 {$self->{mesgout}} "sendbackup: size $ksize\n";
}
waitpid $pid, 0;
if ($? != 0) {
$self->print_to_server_and_die("smbclient returned error",
$Amanda::Script_App::ERROR);
}
}
sub parse_backup {
my $self = shift;
my($fhin, $fhout, $indexout) = @_;
my $size = -1;
while(<$fhin>) {
if ( /^\.\//) {
if(defined($indexout)) {
if(defined($self->{index})) {
s/^\.//;
print $indexout $_;
}
}
}
else {
print $fhout "? $_";
}
}
}
sub command_index_from_output {
index_from_output(0, 1);
}
sub index_from_output {
my($fhin, $fhout) = @_;
my($size) = -1;
while(<$fhin>) {
next if /^Total bytes written:/;
next if !/^\.\//;
s/^\.//;
print $fhout $_;
}
}
sub command_index {
my $self = shift;
my $index_fd;
open2($index_fd, ">&0", $self->{gnutar}, "--list", "--file", "-") ||
$self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
$Amanda::Script_App::ERROR);
index_from_output($index_fd, \*STDOUT);
}
sub command_restore {
my $self = shift;
my @cmd = ();
$self->parsesharename();
chdir(Amanda::Util::get_original_cwd());
if ($self->{recover_mode} eq "smb") {
$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};
}
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}) {
# Don't set close-on-exec
$^F=10;
pipe($parent_rdr, $child_wtr);
$^F=2;
$child_wtr->autoflush(1);
}
my($wtr, $rdr, $err);
$err = Symbol::gensym;
my $pid = open3($wtr, $rdr, $err, "-");
if ($pid == 0) {
$child_wtr->print($self->{password});
$child_wtr->close();
exit 0;
}
if (defined $self->{password}) {
$child_wtr->close();
$ENV{PASSWD_FD} = $parent_rdr->fileno;
}
debug("cmd:" . join(" ", @cmd));
exec { $cmd[0] } @cmd;
die("Can't exec '", $cmd[0], "'");
} else {
push @cmd, $self->{gnutar}, "-xpvf", "-";
if (defined $self->{target}) {
if (!-d $self->{target}) {
$self->print_to_server_and_die(
"Directory $self->{target}: $!",
$Amanda::Script_App::ERROR);
}
if (!-w $self->{target}) {
$self->print_to_server_and_die(
"Directory $self->{target}: $!",
$Amanda::Script_App::ERROR);
}
push @cmd, "--directory", $self->{directory};
}
if ($#{$self->{include_list}} == 0) {
push @cmd, "--files-from", $self->{include_list}[0];
}
if ($#{$self->{exclude_list}} == 0) {
push @cmd, "--exclude-from", $self->{exclude_list}[0];
}
for(my $i=1;defined $ARGV[$i]; $i++) {
my $param = $ARGV[$i];
$param =~ /^(.*)$/;
push @cmd, $1;
}
debug("cmd:" . join(" ", @cmd));
exec { $cmd[0] } @cmd;
die("Can't exec '", $cmd[0], "'");
}
}
sub command_validate {
my $self = shift;
if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
return $self->default_validate();
}
my(@cmd) = ($self->{gnutar}, "-tf", "-");
debug("cmd:" . join(" ", @cmd));
my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
$self->print_to_server_and_die("Unable to run @cmd: $!",
$Amanda::Script_App::ERROR);
waitpid $pid, 0;
if( $? != 0 ){
$self->print_to_server_and_die("$self->{gnutar} returned error",
$Amanda::Script_App::ERROR);
}
}
sub command_print_command {
}
package main;
sub usage {
print <<EOF;
Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
EOF
exit(1);
}
my $opt_version;
my $opt_config;
my $opt_host;
my $opt_disk;
my $opt_device;
my @opt_level;
my $opt_index;
my $opt_message;
my $opt_collection;
my $opt_record;
my $opt_calcsize;
my $opt_gnutar_path;
my $opt_smbclient_path;
my $opt_amandapass;
my @opt_exclude_file;
my @opt_exclude_list;
my $opt_exclude_optional;
my @opt_include_file;
my @opt_include_list;
my $opt_include_optional;
my $opt_recover_mode;
my $opt_allow_anonymous;
my $opt_target;
my $opt_regex_match;
my @orig_argv = @ARGV;
Getopt::Long::Configure(qw{bundling});
GetOptions(
'version' => \$opt_version,
'config=s' => \$opt_config,
'host=s' => \$opt_host,
'disk=s' => \$opt_disk,
'device=s' => \$opt_device,
'level=s' => \@opt_level,
'index=s' => \$opt_index,
'message=s' => \$opt_message,
'collection=s' => \$opt_collection,
'record' => \$opt_record,
'calcsize' => \$opt_calcsize,
'gnutar-path=s' => \$opt_gnutar_path,
'smbclient-path=s' => \$opt_smbclient_path,
'amandapass=s' => \$opt_amandapass,
'exclude-file=s' => \@opt_exclude_file,
'exclude-list=s' => \@opt_exclude_list,
'exclude-optional=s' => \$opt_exclude_optional,
'include-file=s' => \@opt_include_file,
'include-list=s' => \@opt_include_list,
'include-optional=s' => \$opt_include_optional,
'recover-mode=s' => \$opt_recover_mode,
'allow-anonymous=s' => \$opt_allow_anonymous,
'target|directory=s' => \$opt_target,
'regex-match=s' => \$opt_regex_match,
) or usage();
if (defined $opt_version) {
print "amsamba-" . $Amanda::Constants::VERSION , "\n";
exit(0);
}
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_target, $opt_regex_match);
Amanda::Debug::debug("Arguments: " . join(' ', @orig_argv));
$application->do($ARGV[0]);
# NOTREACHED