Imported Upstream version 3.3.2
[debian/amanda] / application-src / amsamba.pl
index 368b424b190dd02725acdc58e7d398d7dfcff0c0..7a6ffd8bdb7554f1866e47eafd331ac8266e3cab 100644 (file)
@@ -1,5 +1,5 @@
 #!@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
@@ -19,6 +19,7 @@
 
 use lib '@amperldir@';
 use strict;
+use warnings;
 use Getopt::Long;
 
 package Amanda::Application::Amsamba;
@@ -35,6 +36,7 @@ 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;
@@ -69,11 +71,6 @@ sub new {
     } else {
        $self->{device}       = $disk;
     }
-    if ($self->{disk} =~ /^\\\\/) {
-       $self->{unc}          = 1;
-    } else {
-       $self->{unc}          = 0;
-    }
     $self->{level}            = [ @{$level} ];
     $self->{index}            = $index;
     $self->{message}          = $message;
@@ -101,6 +98,7 @@ sub new {
 #on exit:
 #  $self->{exclude}
 #  $self->{include}
+#  $self->{include_filename}
 sub validate_inexclude {
     my $self = shift;
 
@@ -127,22 +125,81 @@ sub validate_inexclude {
        }
        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);
     }
 }
 
@@ -161,6 +218,11 @@ sub parsesharename {
     $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,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
@@ -222,22 +284,10 @@ sub findpass {
     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}) ||
@@ -245,7 +295,7 @@ sub findpass {
             $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;
                $self->{password} = $password;
@@ -297,6 +347,10 @@ sub command_support {
 sub command_selfcheck {
     my $self = shift;
 
+    $self->print_to_server("disk " . quote_string($self->{disk}));
+
+    $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(
@@ -310,7 +364,20 @@ sub command_selfcheck {
     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}) {
@@ -321,17 +388,16 @@ sub command_selfcheck {
     $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;
@@ -339,10 +405,10 @@ sub command_selfcheck {
     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);
@@ -364,11 +430,11 @@ sub command_selfcheck {
     }
     #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");
     }
@@ -397,13 +463,13 @@ sub command_estimate {
     $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;
@@ -411,10 +477,10 @@ sub command_estimate {
     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);
@@ -441,12 +507,12 @@ sub command_estimate {
     }
     #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);
@@ -496,52 +562,50 @@ sub output_size {
    }
 }
 
+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}));
@@ -582,60 +646,133 @@ sub command_backup {
     }
 
     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;
@@ -701,6 +838,7 @@ sub command_restore {
        $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};
@@ -708,14 +846,18 @@ sub command_restore {
        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}) {