Imported Upstream version 3.3.2
[debian/amanda] / application-src / amsamba.pl
1 #!@PERL@ 
2 # Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
3 #
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.
7 #
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
11 # for more details.
12 #
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
16 #
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19
20 use lib '@amperldir@';
21 use strict;
22 use warnings;
23 use Getopt::Long;
24
25 package Amanda::Application::Amsamba;
26 use base qw(Amanda::Application);
27 use File::Copy;
28 use File::Path;
29 use IPC::Open2;
30 use IPC::Open3;
31 use Sys::Hostname;
32 use Symbol;
33 use IO::Handle;
34 use Amanda::Constants;
35 use Amanda::Config qw( :init :getconf  config_dir_relative );
36 use Amanda::Debug qw( :logging );
37 use Amanda::Paths;
38 use Amanda::Util qw( :constants :quoting);
39 use Amanda::MainLoop qw( :GIOCondition );
40
41 sub new {
42     my $class = shift;
43     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) = @_;
44     my $self = $class->SUPER::new($config);
45
46     if (defined $gnutar_path) {
47         $self->{gnutar}     = $gnutar_path;
48     } else {
49         $self->{gnutar}     = $Amanda::Constants::GNUTAR;
50     }
51     if (defined $smbclient_path) {
52         $self->{smbclient}  = $smbclient_path;
53     } else {
54         $self->{smbclient}  = $Amanda::Constants::SAMBA_CLIENT;
55     }
56     if (defined $amandapass) {
57         $self->{amandapass}  = config_dir_relative($amandapass);
58     } else {
59         $self->{amandapass}  = "$Amanda::Paths::CONFIG_DIR/amandapass";
60     }
61
62     $self->{config}           = $config;
63     $self->{host}             = $host;
64     if (defined $disk) {
65         $self->{disk}         = $disk;
66     } else {
67         $self->{disk}         = $device;
68     }
69     if (defined $device) {
70         $self->{device}       = $device;
71     } else {
72         $self->{device}       = $disk;
73     }
74     $self->{level}            = [ @{$level} ];
75     $self->{index}            = $index;
76     $self->{message}          = $message;
77     $self->{collection}       = $collection;
78     $self->{record}           = $record;
79     $self->{calcsize}         = $calcsize;
80     $self->{exclude_file}     = [ @{$exclude_file} ];
81     $self->{exclude_list}     = [ @{$exclude_list} ];
82     $self->{exclude_optional} = $exclude_optional;
83     $self->{include_file}     = [ @{$include_file} ];
84     $self->{include_list}     = [ @{$include_list} ];
85     $self->{include_optional} = $include_optional;
86     $self->{recover_mode}     = $recover_mode;
87     $self->{allow_anonymous}  = $allow_anonymous;
88     $self->{directory}        = $directory;
89
90     return $self;
91 }
92
93 # on entry:
94 #   $self->{exclude_file}
95 #   $self->{exclude_list}
96 #   $self->{include_file}
97 #   $self->{include_list}
98 #on exit:
99 #  $self->{exclude}
100 #  $self->{include}
101 #  $self->{include_filename}
102 sub validate_inexclude {
103     my $self = shift;
104
105     if ($#{$self->{exclude_file}} + $#{$self->{exclude_list}} >= -1 &&
106         $#{$self->{include_file}} + $#{$self->{include_list}} >= -1) {
107         $self->print_to_server_and_die("Can't have both include and exclude",
108                                        $Amanda::Script_App::ERROR);
109     }
110
111     if ($#{$self->{exclude_file}} >= 0) {
112         $self->{exclude} = [ @{$self->{exclude_file}} ];
113     }
114     foreach my $file (@{$self->{exclude_list}}) {
115         if (!open(FF, $file)) {
116             if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
117                 $self->print_to_server("Open of '$file' failed: $!",
118                                        $Amanda::Script_App::ERROR);
119             }
120             next;
121         }
122         while (<FF>) {
123             chomp;
124             push @{$self->{exclude}}, $_;
125         }
126         close(FF);
127     }
128
129     if ($self->{action} eq "restore" and defined $self->{'include_list'}) {
130         # put all include in a single file $self->{'include_filename'}
131         $self->{'include_filename'} = "$AMANDA_TMPDIR/amsamba.$$.include";
132         open INC_FILE, ">$self->{'include_filename'}";
133         if ($#{$self->{include_file}} >= 0) {
134             print INC_FILE "$self->{include_file}\n";
135         }
136         foreach my $file (@{$self->{include_list}}) {
137             if (!open(FF, $file)) {
138                 if ($self->{action} eq 'check' && !$self->{include_optional}) {
139                     $self->print_to_server("Open of '$file' failed: $!",
140                                            $Amanda::Script_App::ERROR);
141                 }
142                 next;
143             }
144             while (<FF>) {
145                 if (defined $self->{'subdir'}) {
146                     $_ =~ s/^\./$self->{'subdir'}/;
147                 }
148                 print INC_FILE;
149             }
150             close(FF);
151         }
152
153         # add command line include for amrestore
154         for(my $i=1;defined $ARGV[$i]; $i++) {
155             my $param = $ARGV[$i];
156             $param =~ /^(.*)$/;
157             $_ = $1;
158             if (defined $self->{'subdir'}) {
159                 $_ =~ s/^\./$self->{'subdir'}/;
160             }
161             print INC_FILE "$_\n";
162         }
163
164         close INC_FILE;
165     } else {
166         # put all include in $self->{'include'} they will be added on
167         # command line.
168         if ($#{$self->{include_file}} >= 0) {
169             $self->{include} = [ @{$self->{include_file}} ];
170         }
171
172         foreach my $file (@{$self->{include_list}}) {
173             if (!open(FF, $file)) {
174                 if ($self->{action} eq 'check' && !$self->{include_optional}) {
175                     $self->print_to_server("Open of '$file' failed: $!",
176                                            $Amanda::Script_App::ERROR);
177                 }
178                 next;
179             }
180             while (<FF>) {
181                 chomp;
182                 if ($self->{action} eq "restore" and
183                     defined $self->{'subdir'}) {
184                     $_ =~ s/^\./$self->{'subdir'}/;
185                 }
186                 push @{$self->{include}}, $_;
187             }
188             close(FF);
189         }
190
191         # add command line include for amrestore
192         if ($self->{action} eq "restore") {
193                 for(my $i=1;defined $ARGV[$i]; $i++) {
194                 my $param = $ARGV[$i];
195                 $param =~ /^(.*)$/;
196                 $_ = $1;
197                 if (defined $self->{'subdir'}) {
198                     $_ =~ s/^\./$self->{'subdir'}/;
199                 }
200                 push @{$self->{include}}, $1;
201             }
202         }
203     }
204 }
205
206 # on entry:
207 #   $self->{directory} == //host/share/subdir           \\host\share\subdir
208 #   or
209 #   $self->{device}    == //host/share/subdir           \\host\share\subdir
210 # on exit:
211 #   $self->{cifshost}   = //host                        \\host
212 #   $self->{share}      = //host/share                  \\host\share
213 #   $self->{sambashare} = \\host\share                  \\host\share
214 #   $self->{subdir}     = subdir                        subdir
215 sub parsesharename {
216     my $self = shift;
217     my $to_parse = $self->{directory};
218     $to_parse = $self->{device} if !defined $to_parse;;
219
220     return if !defined $to_parse;
221     if ($to_parse =~ /^\\\\/) {
222         $self->{unc}          = 1;
223     } else {
224         $self->{unc}          = 0;
225     }
226
227     if ($self->{unc}) {
228         if ($to_parse =~ m,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
229             $self->{share} = $1;
230             $self->{subdir} = $2
231         } else {
232             $self->{share} = $to_parse
233         }
234         $self->{sambashare} = $self->{share};
235         $to_parse =~ m,^(\\\\[^\\]+)\\[^\\]+,;
236         $self->{cifshost} = $1;
237     } else {
238         if ($to_parse =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
239             $self->{share} = $1;
240             $self->{subdir} = $2
241         } else {
242             $self->{share} = $to_parse
243         }
244         $self->{sambashare} = $self->{share};
245         $self->{sambashare} =~ s,/,\\,g;
246         $to_parse =~ m,^(//[^/]+)/[^/]+,;
247         $self->{cifshost} = $1;
248     }
249 }
250
251
252 # Read $self->{amandapass} file.
253 # on entry:
254 #   $self->{cifshost} == //host/share
255 #   $self->{share} == //host/share
256 # on exit:
257 #   $self->{domain}   = domain to connect to.
258 #   $self->{username} = username (-U)
259 #   $self->{password} = password
260 sub findpass {
261     my $self = shift;
262
263     my $amandapass;
264     my $line;
265
266     $self->{domain} = undef;
267     $self->{username} = undef;
268     $self->{password} = undef;
269
270     debug("amandapass: $self->{amandapass}");
271     if (!open($amandapass, $self->{amandapass})) {
272         if ($self->{allow_anonymous}) {
273             $self->{username} = $self->{allow_anonymous};
274             debug("cannot open password file '$self->{amandapass}': $!\n");
275             debug("Using anonymous user: $self->{username}");
276             return;
277         } else {
278             $self->print_to_server_and_die(
279                         "cannot open password file '$self->{amandapass}': $!",
280                         $Amanda::Script_App::ERROR);
281         }
282     }
283
284     while ($line = <$amandapass>) {
285         chomp $line;
286         next if $line =~ /^#/;
287         my ($diskname, $userpasswd, $domain, $extra) = Amanda::Util::split_quoted_string_friendly($line);
288         if ($extra) {
289             debug("Trailling characters ignored in amandapass line");
290         }
291         if (defined $diskname &&
292             ($diskname eq '*' ||
293              ($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
294              ($self->{unc}==1 && $diskname =~ m,^(\\\\[^\\]+)\\\*$, && $1 eq $self->{cifshost}) ||
295              $diskname eq $self->{share} ||
296              $diskname eq $self->{sambashare})) {
297             if (defined $userpasswd && $userpasswd ne "") {
298                 $self->{domain} = $domain if defined $domain && $domain ne "";
299                 my ($username, $password) = split('%', $userpasswd, 2);
300                 $self->{username} = $username;
301                 $self->{password} = $password;
302                 $self->{password} = undef if (defined $password && $password eq "");
303             } else {
304                 $self->{username} = "guest";
305             }
306             close($amandapass);
307             return;
308         }
309     }
310     close($amandapass);
311     if ($self->{allow_anonymous}) {
312         $self->{username} = $self->{allow_anonymous};
313         debug("Cannot find password for share $self->{share} in $self->{amandapass}");
314         debug("Using anonymous user: $self->{username}");
315         return;
316     }
317     $self->print_to_server_and_die(
318         "Cannot find password for share $self->{share} in $self->{amandapass}",
319         $Amanda::Script_App::ERROR);
320 }
321
322 sub command_support {
323     my $self = shift;
324
325     print "CONFIG YES\n";
326     print "HOST YES\n";
327     print "DISK YES\n";
328     print "MAX-LEVEL 1\n";
329     print "INDEX-LINE YES\n";
330     print "INDEX-XML NO\n";
331     print "MESSAGE-LINE YES\n";
332     print "MESSAGE-XML NO\n";
333     print "RECORD YES\n";
334     print "COLLECTION NO\n";
335     print "MULTI-ESTIMATE NO\n";
336     print "CALCSIZE NO\n";
337     print "CLIENT-ESTIMATE YES\n";
338     print "EXCLUDE-FILE YES\n";
339     print "EXCLUDE-LIST YES\n";
340     print "EXCLUDE-OPTIONAL YES\n";
341     print "INCLUDE-FILE YES\n";
342     print "INCLUDE-LIST YES\n";
343     print "INCLUDE-OPTIONAL YES\n";
344     print "RECOVER-MODE SMB\n";
345 }
346
347 sub command_selfcheck {
348     my $self = shift;
349
350     $self->print_to_server("disk " . quote_string($self->{disk}));
351
352     $self->print_to_server("amsamba version " . $Amanda::Constants::VERSION,
353                            $Amanda::Script_App::GOOD);
354     #check binary
355     if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
356         $self->print_to_server(
357             "smbclient not set; you must define the SMBCLIENT-PATH property",
358             $Amanda::Script_App::ERROR);
359     }
360     elsif (! -e $self->{smbclient}) {
361         $self->print_to_server("$self->{smbclient} doesn't exist",
362                                $Amanda::Script_App::ERROR);
363     }
364     elsif (! -x $self->{smbclient}) {
365         $self->print_to_server("$self->{smbclient} is not executable",
366                                $Amanda::Script_App::ERROR);
367     } else {
368         my @sv = `$self->{smbclient} --version`;
369         if ($? >> 8 == 0) {
370             $sv[0] =~ /^[^0-9]*(.*)$/;
371             my $sv = $1;
372             $self->print_to_server("amsamba smbclient-version $sv",
373                                    $Amanda::Script_App::GOOD);
374         } else {
375             $self->print_to_server(
376                 "[Can't get " . $self->{smbclient} . " version]\n",
377                 $Amanda::Script_App::ERROR);
378         }
379     }
380
381     $self->print_to_server("$self->{smbclient}",
382                            $Amanda::Script_App::GOOD);
383     if (!defined $self->{disk} || !defined $self->{device}) {
384         return;
385     }
386     $self->parsesharename();
387     $self->findpass();
388     $self->validate_inexclude();
389
390     print "OK " . $self->{share} . "\n";
391     print "OK " . $self->{device} . "\n";
392     print "OK " . $self->{directory} . "\n" if defined $self->{directory};
393
394     my ($password_rdr, $password_wtr);
395     if (defined $self->{password}) {
396         # Don't set close-on-exec
397         $^F=10;
398         pipe($password_rdr, $password_wtr);
399         $^F=2;
400         $password_wtr->autoflush(1);
401     }
402     my($wtr, $rdr, $err);
403     $err = Symbol::gensym;
404     my $pid = open3($wtr, $rdr, $err, "-");
405     if ($pid == 0) {
406         #child
407         if (defined $self->{password}) {
408             my $ff = $password_rdr->fileno;
409             debug("password_rdr $ff");
410             $password_wtr->close();
411             $ENV{PASSWD_FD} = $password_rdr->fileno;
412         }
413         close(1);
414         close(2);
415         my @ARGV = ();
416         push @ARGV, $self->{smbclient}, $self->{share};
417         push @ARGV, "" if (!defined $self->{password});
418         push @ARGV, "-U", $self->{username},
419                     "-E";
420         if (defined $self->{domain}) {
421             push @ARGV, "-W", $self->{domain},
422         }
423         if (defined $self->{subdir}) {
424             push @ARGV, "-D", $self->{subdir},
425         }
426         push @ARGV, "-c", "quit";
427         debug("execute: " . $self->{smbclient} . " " .
428               join(" ", @ARGV));
429         exec {$self->{smbclient}} @ARGV;
430     }
431     #parent
432     if (defined $self->{password}) {
433         my $ff = $password_wtr->fileno;
434         debug("password_wtr $ff");
435         $password_wtr->print($self->{password});
436         $password_wtr->close();
437         $password_rdr->close();
438     } else {
439         debug("No password");
440     }
441     close($wtr);
442     close($rdr);
443     while (<$err>) {
444         chomp;
445         debug("stderr: " . $_);
446         next if /^Domain=/;
447         # message if samba server is configured with 'security = share'
448         next if /Server not using user level security and no password supplied./;
449         $self->print_to_server("smbclient: $_",
450                                $Amanda::Script_App::ERROR);
451     }
452     close($err);
453     waitpid($pid, 0);
454     #check statefile
455     #check amdevice
456 }
457
458 sub command_estimate {
459     my $self = shift;
460
461     $self->parsesharename();
462     $self->findpass();
463     $self->validate_inexclude();
464
465     my $level = $self->{level}[0];
466     my ($password_rdr, $password_wtr);
467     if (defined $self->{password}) {
468         # Don't set close-on-exec
469         $^F=10;
470         pipe($password_rdr,  $password_wtr);
471         $^F=2;
472         $password_wtr->autoflush(1);
473     }
474     my($wtr, $rdr, $err);
475     $err = Symbol::gensym;
476     my $pid = open3($wtr, $rdr, $err, "-");
477     if ($pid == 0) {
478         #child
479         if (defined $self->{password}) {
480             my $ff = $password_rdr->fileno;
481             debug("password_rdr $ff");
482             $password_wtr->close();
483             $ENV{PASSWD_FD} = $password_rdr->fileno;
484         }
485         close(0);
486         close(1);
487         my @ARGV = ();
488         push @ARGV, $self->{smbclient}, $self->{share};
489         push @ARGV, "" if (!defined($self->{password}));
490         push @ARGV, "-d", "0",
491                     "-U", $self->{username},
492                     "-E";
493         if (defined $self->{domain}) {
494             push @ARGV, "-W", $self->{domain},
495         }
496         if (defined $self->{subdir}) {
497             push @ARGV, "-D", $self->{subdir},
498         }
499         if ($level == 0) {
500             push @ARGV, "-c", "archive 0;recurse;du";
501         } else {
502             push @ARGV, "-c", "archive 1;recurse;du";
503         }
504         debug("execute: " . $self->{smbclient} . " " .
505               join(" ", @ARGV));
506         exec {$self->{smbclient}} @ARGV;
507     }
508     #parent
509     if (defined $self->{password}) {
510         my $ff = $password_wtr->fileno;
511         debug("password_wtr $ff");
512         debug("password $self->{password}");
513         $password_wtr->print($self->{password});
514         $password_wtr->close();
515         $password_rdr->close();
516     }
517     close($wtr);
518     close($rdr);
519     my $size = $self->parse_estimate($err);
520     close($err);
521     output_size($level, $size);
522     waitpid($pid, 0);
523 }
524
525 sub parse_estimate {
526     my $self = shift;
527     my($fh)  = shift;
528     my($size) = -1;
529     while(<$fh>) {
530         chomp;
531         next if /^\s*$/;
532         next if /blocks of size/;
533         next if /blocks available/;
534         next if /^\s*$/;
535         next if /^Domain=/;
536         next if /dumped \d+ files and directories/;
537         # message if samba server is configured with 'security = share'
538         next if /Server not using user level security and no password supplied./;
539         debug("stderr: $_");
540         if ($_ =~ /^Total number of bytes: (\d*)/) {
541             $size = $1;
542             last;
543         } else {
544             $self->print_to_server("smbclient: $_",
545                                    $Amanda::Script_App::ERROR);
546         }
547     }
548     return $size;
549 }
550
551 sub output_size {
552    my($level) = shift;
553    my($size) = shift;
554    if($size == -1) {
555       print "$level -1 -1\n";
556       #exit 2;
557    }
558    else {
559       my($ksize) = int $size / (1024);
560       $ksize=32 if ($ksize<32);
561       print "$level $ksize 1\n";
562    }
563 }
564
565 sub send_empty_tar_file {
566     my $self = shift;
567     my ($out1, $out2) = @_;
568     my $out;
569     my $buf;
570     my $size;
571
572     Amanda::Debug::debug("Create empty archive with: tar --create --file=- --files-from=/dev/null");
573     open2($out, undef, "tar", "--create", "--file=-", "--files-from=/dev/null");
574
575     while(($size = sysread($out, $buf, 32768))) {
576         syswrite($out1, $buf, $size);
577         syswrite($out2, $buf, $size);
578     }
579 }
580
581 sub command_backup {
582     my $self = shift;
583
584     my $level = $self->{level}[0];
585
586     $self->parsesharename();
587     $self->findpass();
588     $self->validate_inexclude();
589
590     my ($password_rdr, $password_wtr);
591     if (defined $self->{password}) {
592         # Don't set close-on-exec
593         $^F=10;
594         pipe($password_rdr,  $password_wtr);
595         $^F=2;
596         $password_wtr->autoflush(1);
597     }
598     my($smbclient_wtr, $smbclient_rdr, $smbclient_err);
599     $smbclient_err = Symbol::gensym;
600     my $pid = open3($smbclient_wtr, $smbclient_rdr, $smbclient_err, "-");
601     if ($pid == 0) {
602         #child
603         if (defined $self->{password}) {
604             my $ff = $password_rdr->fileno;
605             debug("password_rdr $ff");
606             $password_wtr->close();
607             $ENV{PASSWD_FD} = $password_rdr->fileno;
608         }
609         my @ARGV = ();
610         push @ARGV, $self->{smbclient}, $self->{share};
611         push @ARGV, "" if (!defined($self->{password}));
612         push @ARGV, "-d", "0",
613                     "-U", $self->{username},
614                     "-E";
615         if (defined $self->{domain}) {
616             push @ARGV, "-W", $self->{domain},
617         }
618         if (defined $self->{subdir}) {
619             push @ARGV, "-D", $self->{subdir},
620         }
621
622         my $comm ;
623         if ($level == 0) {
624             $comm = "tarmode full reset hidden system quiet;";
625         } else {
626             $comm = "tarmode inc noreset hidden system quiet;";
627         }
628         $comm .= " tar c";
629         if ($#{$self->{exclude}} >= 0) {
630             $comm .= "X";
631         }
632         if ($#{$self->{include}} >= 0) {
633             $comm .= "I";
634         }
635         $comm .= " -";
636         if ($#{$self->{exclude}} >= 0) {
637             $comm .= " " . join(" ", @{$self->{exclude}});
638         }
639         if ($#{$self->{include}} >= 0) {
640             $comm .= " " . join(" ", @{$self->{include}});
641         }
642         push @ARGV, "-c", $comm;
643         debug("execute: " . $self->{smbclient} . " " .
644               join(" ", @ARGV));
645         exec {$self->{smbclient}} @ARGV;
646     }
647
648     if (defined $self->{password}) {
649         my $ff = $password_wtr->fileno;
650         debug("password_wtr $ff");
651         $password_wtr->print($self->{password});
652         $password_wtr->close();
653         $password_rdr->close();
654     } else {
655         debug("No password");
656     }
657     close($smbclient_wtr);
658
659     #index process 
660     my $index_rdr;
661     my $index_wtr;
662     debug("$self->{gnutar} -tf -");
663     my $pid_index1 = open2($index_rdr, $index_wtr, $self->{gnutar}, "-tf", "-");
664     my $size = -1;
665     my $index_fd = $index_rdr->fileno;
666     debug("index $index_fd");
667     my $indexout_fd;
668     if (defined($self->{index})) {
669         open($indexout_fd, '>&=4') ||
670             $self->print_to_server_and_die("Can't open indexout_fd: $!",
671                                            $Amanda::Script_App::ERROR);
672     }
673
674     my $file_to_close = 3;
675     my $smbclient_stdout_src = Amanda::MainLoop::fd_source($smbclient_rdr,
676                                 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
677     my $smbclient_stderr_src = Amanda::MainLoop::fd_source($smbclient_err,
678                                 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
679     my $index_tar_stdout_src = Amanda::MainLoop::fd_source($index_rdr,
680                                 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
681
682     my $smbclient_stdout_done = 0;
683     my $smbclient_stderr_done = 0;
684     my $data_size = 0;
685     my $nb_files = 0;
686     $smbclient_stdout_src->set_callback(sub {
687         my $buf;
688         my $blocksize = -1;
689         $blocksize = sysread($smbclient_rdr, $buf, 32768);
690         if (!$blocksize) {
691             $file_to_close--;
692             $smbclient_stdout_src->remove();
693             $smbclient_stdout_done = 1;
694             if ($smbclient_stderr_done) {
695                 if ($data_size == 0 and $nb_files == 0 and $size == 0) {
696                     $self->send_empty_tar_file(*STDOUT, $index_wtr);
697                 }
698                 close($index_wtr);
699                 close(STDOUT);
700             }
701             close($smbclient_rdr);
702             Amanda::MainLoop::quit() if $file_to_close == 0;
703             return;
704         }
705         $data_size += $blocksize;
706         syswrite(STDOUT, $buf, $blocksize);
707         syswrite($index_wtr, $buf, $blocksize);
708     });
709
710     $smbclient_stderr_src->set_callback(sub {
711         my $line = <$smbclient_err>;
712         if (!defined $line) {
713             $file_to_close--;
714             $smbclient_stderr_src->remove();
715             $smbclient_stderr_done = 1;
716             if ($smbclient_stdout_done) {
717                 if ($data_size == 0 and $nb_files == 0 and $size == 0) {
718                     $self->send_empty_tar_file(*STDOUT, $index_wtr);
719                 }
720                 close($index_wtr);
721                 close(STDOUT);
722             }
723             close ($smbclient_err);
724             Amanda::MainLoop::quit() if $file_to_close == 0;
725             return;
726         }
727         chomp $line;
728         debug("stderr: " . $line);
729         return if $line =~ /^Domain=/;
730         return if $line =~ /^tarmode is now /;
731         if ($line =~ /dumped (\d+) files and directories/) {
732             $nb_files = $1;
733             return;
734         }
735         # message if samba server is configured with 'security = share'
736         return if $line =~$line =~  /Server not using user level security and no password supplied./;
737         if ($line =~ /^Total bytes written: (\d*)/) {
738             $size = $1;
739             return;
740         }
741         $self->print_to_server("smbclient: $line", $Amanda::Script_App::ERROR);
742     });
743
744     $index_tar_stdout_src->set_callback(sub {
745         my $line = <$index_rdr>;
746         if (!defined $line) {
747             $file_to_close--;
748             $index_tar_stdout_src->remove();
749             close($index_rdr);
750             close($indexout_fd);
751             Amanda::MainLoop::quit() if $file_to_close == 0;
752             return;
753         }
754         if ($line =~ /^\.\//) {
755             if(defined($indexout_fd)) {
756                 if(defined($self->{index})) {
757                     $line =~ s/^\.//;
758                     print $indexout_fd $line;
759                 }
760             }
761         } else {
762             chomp $line;
763             $self->print_to_server($line, $Amanda::Script_App::ERROR);
764         }
765     });
766
767     Amanda::MainLoop::run();
768
769     if ($size >= 0) {
770         my $ksize = $size / 1024;
771         if ($ksize < 32) {
772             $ksize = 32;
773         }
774         print {$self->{mesgout}} "sendbackup: size $ksize\n";
775         print {$self->{mesgout}} "sendbackup: end\n";
776     }
777
778     waitpid $pid, 0;
779     if ($? != 0) {
780         $self->print_to_server_and_die("smbclient returned error",
781                                        $Amanda::Script_App::ERROR);
782     }
783     exit 0;
784 }
785
786 sub parse_backup {
787     my $self = shift;
788     my($fhin, $fhout, $indexout) = @_;
789     my $size  = -1;
790     while(<$fhin>) {
791         if ( /^\.\//) {
792             if(defined($indexout)) {
793                 if(defined($self->{index})) {
794                     s/^\.//;
795                     print $indexout $_;
796                 }
797             }
798         }
799         else {
800             print $fhout "? $_";
801         }
802     }
803 }
804
805 sub command_index_from_output {
806    index_from_output(0, 1);
807    exit 0;
808 }
809
810 sub index_from_output {
811    my($fhin, $fhout) = @_;
812    my($size) = -1;
813    while(<$fhin>) {
814       next if /^Total bytes written:/;
815       next if !/^\.\//;
816       s/^\.//;
817       print $fhout $_;
818    }
819 }
820
821 sub command_index_from_image {
822    my $self = shift;
823    my $index_fd;
824    open($index_fd, "$self->{gnutar} --list --file - |") ||
825       $self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
826                                      $Amanda::Script_App::ERROR);
827    index_from_output($index_fd, 1);
828 }
829
830 sub command_restore {
831     my $self = shift;
832     my @cmd = ();
833
834     $self->parsesharename();
835     chdir(Amanda::Util::get_original_cwd());
836
837     if ($self->{recover_mode} eq "smb") {
838         $self->validate_inexclude();
839         $self->findpass();
840         push @cmd, $self->{smbclient}, $self->{share};
841         push @cmd, "-D", $self->{'subdir'} if defined $self->{'subdir'};
842         push @cmd, "" if (!defined $self->{password});
843         push @cmd, "-d", "0",
844                    "-U", $self->{username};
845         
846         if (defined $self->{domain}) {
847             push @cmd, "-W", $self->{domain};
848         }
849         if (defined $self->{'include_filename'}) {
850             push @cmd, "-TFx", "-", "$self->{'include_filename'}";
851         } else {
852             push @cmd, "-Tx", "-";
853             if ($#{$self->{include}} >= 0) {
854                 push @cmd, @{$self->{include}};
855             }
856             for(my $i=1;defined $ARGV[$i]; $i++) {
857                 my $param = $ARGV[$i];
858                 $param =~ /^(.*)$/;
859                 push @cmd, $1;
860             }
861         }
862         my ($parent_rdr, $child_wtr);
863         if (defined $self->{password}) {
864             # Don't set close-on-exec
865             $^F=10;
866             pipe($parent_rdr,  $child_wtr);
867             $^F=2;
868             $child_wtr->autoflush(1);
869         }
870         my($wtr, $rdr, $err);
871         $err = Symbol::gensym;
872         my $pid = open3($wtr, $rdr, $err, "-");
873         if ($pid == 0) {
874             $child_wtr->print($self->{password});
875             $child_wtr->close();
876             exit 0;
877         }
878         if (defined $self->{password}) {
879             $child_wtr->close();
880             $ENV{PASSWD_FD} = $parent_rdr->fileno;
881         }
882         debug("cmd:" . join(" ", @cmd));
883         exec { $cmd[0] } @cmd;
884         die("Can't exec '", $cmd[0], "'");
885     } else {
886         push @cmd, $self->{gnutar}, "-xpvf", "-";
887         if (defined $self->{directory}) {
888             if (!-d $self->{directory}) {
889                 $self->print_to_server_and_die(
890                                        "Directory $self->{directory}: $!",
891                                        $Amanda::Script_App::ERROR);
892             }
893             if (!-w $self->{directory}) {
894                 $self->print_to_server_and_die(
895                                        "Directory $self->{directory}: $!",
896                                        $Amanda::Script_App::ERROR);
897             }
898             push @cmd, "--directory", $self->{directory};
899         }
900         if ($#{$self->{include_list}} == 0) {
901             push @cmd, "--files-from", $self->{include_list}[0];
902         }
903         if ($#{$self->{exclude_list}} == 0) {
904             push @cmd, "--exclude-from", $self->{exclude_list}[0];
905         }
906         for(my $i=1;defined $ARGV[$i]; $i++) {
907             my $param = $ARGV[$i];
908             $param =~ /^(.*)$/;
909             push @cmd, $1;
910         }
911         debug("cmd:" . join(" ", @cmd));
912         exec { $cmd[0] } @cmd;
913         die("Can't exec '", $cmd[0], "'");
914     }
915 }
916
917 sub command_validate {
918    my $self = shift;
919
920    if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
921       return $self->default_validate();
922    }
923
924    my(@cmd) = ($self->{gnutar}, "-tf", "-");
925    debug("cmd:" . join(" ", @cmd));
926    my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
927         $self->print_to_server_and_die("Unable to run @cmd: $!",
928                                        $Amanda::Script_App::ERROR);
929    waitpid $pid, 0;
930    if( $? != 0 ){
931         $self->print_to_server_and_die("$self->{gnutar} returned error",
932                                        $Amanda::Script_App::ERROR);
933    }
934    exit(0);
935 }
936
937 sub command_print_command {
938 }
939
940 package main;
941
942 sub usage {
943     print <<EOF;
944 Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
945 EOF
946     exit(1);
947 }
948
949 my $opt_version;
950 my $opt_config;
951 my $opt_host;
952 my $opt_disk;
953 my $opt_device;
954 my @opt_level;
955 my $opt_index;
956 my $opt_message;
957 my $opt_collection;
958 my $opt_record;
959 my $opt_calcsize;
960 my $opt_gnutar_path;
961 my $opt_smbclient_path;
962 my $opt_amandapass;
963 my @opt_exclude_file;
964 my @opt_exclude_list;
965 my $opt_exclude_optional;
966 my @opt_include_file;
967 my @opt_include_list;
968 my $opt_include_optional;
969 my $opt_recover_mode;
970 my $opt_allow_anonymous;
971 my $opt_directory;
972
973 Getopt::Long::Configure(qw{bundling});
974 GetOptions(
975     'version'            => \$opt_version,
976     'config=s'           => \$opt_config,
977     'host=s'             => \$opt_host,
978     'disk=s'             => \$opt_disk,
979     'device=s'           => \$opt_device,
980     'level=s'            => \@opt_level,
981     'index=s'            => \$opt_index,
982     'message=s'          => \$opt_message,
983     'collection=s'       => \$opt_collection,
984     'record'             => \$opt_record,
985     'calcsize'           => \$opt_calcsize,
986     'gnutar-path=s'      => \$opt_gnutar_path,
987     'smbclient-path=s'   => \$opt_smbclient_path,
988     'amandapass=s'       => \$opt_amandapass,
989     'exclude-file=s'     => \@opt_exclude_file,
990     'exclude-list=s'     => \@opt_exclude_list,
991     'exclude-optional=s' => \$opt_exclude_optional,
992     'include-file=s'     => \@opt_include_file,
993     'include-list=s'     => \@opt_include_list,
994     'include-optional=s' => \$opt_include_optional,
995     'recover-mode=s'     => \$opt_recover_mode,
996     'allow-anonymous=s'  => \$opt_allow_anonymous,
997     'directory=s'        => \$opt_directory,
998 ) or usage();
999
1000 if (defined $opt_version) {
1001     print "amsamba-" . $Amanda::Constants::VERSION , "\n";
1002     exit(0);
1003 }
1004
1005 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);
1006
1007 $application->do($ARGV[0]);