Imported Upstream version 3.2.0
[debian/amanda] / application-src / amsamba.pl
1 #!@PERL@ 
2 # Copyright (c) 2008, 2009, 2010 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
40 sub new {
41     my $class = shift;
42     my ($config, $host, $disk, $device, $level, $index, $message, $collection, $record, $calcsize, $gnutar_path, $smbclient_path, $amandapass, $exclude_file, $exclude_list, $exclude_optional, $include_file, $include_list, $include_optional, $recover_mode, $allow_anonymous, $directory) = @_;
43     my $self = $class->SUPER::new($config);
44
45     if (defined $gnutar_path) {
46         $self->{gnutar}     = $gnutar_path;
47     } else {
48         $self->{gnutar}     = $Amanda::Constants::GNUTAR;
49     }
50     if (defined $smbclient_path) {
51         $self->{smbclient}  = $smbclient_path;
52     } else {
53         $self->{smbclient}  = $Amanda::Constants::SAMBA_CLIENT;
54     }
55     if (defined $amandapass) {
56         $self->{amandapass}  = config_dir_relative($amandapass);
57     } else {
58         $self->{amandapass}  = "$Amanda::Paths::CONFIG_DIR/amandapass";
59     }
60
61     $self->{config}           = $config;
62     $self->{host}             = $host;
63     if (defined $disk) {
64         $self->{disk}         = $disk;
65     } else {
66         $self->{disk}         = $device;
67     }
68     if (defined $device) {
69         $self->{device}       = $device;
70     } else {
71         $self->{device}       = $disk;
72     }
73     if ($self->{disk} =~ /^\\\\/) {
74         $self->{unc}          = 1;
75     } else {
76         $self->{unc}          = 0;
77     }
78     $self->{level}            = [ @{$level} ];
79     $self->{index}            = $index;
80     $self->{message}          = $message;
81     $self->{collection}       = $collection;
82     $self->{record}           = $record;
83     $self->{calcsize}         = $calcsize;
84     $self->{exclude_file}     = [ @{$exclude_file} ];
85     $self->{exclude_list}     = [ @{$exclude_list} ];
86     $self->{exclude_optional} = $exclude_optional;
87     $self->{include_file}     = [ @{$include_file} ];
88     $self->{include_list}     = [ @{$include_list} ];
89     $self->{include_optional} = $include_optional;
90     $self->{recover_mode}     = $recover_mode;
91     $self->{allow_anonymous}  = $allow_anonymous;
92     $self->{directory}        = $directory;
93
94     return $self;
95 }
96
97 # on entry:
98 #   $self->{exclude_file}
99 #   $self->{exclude_list}
100 #   $self->{include_file}
101 #   $self->{include_list}
102 #on exit:
103 #  $self->{exclude}
104 #  $self->{include}
105 sub validate_inexclude {
106     my $self = shift;
107
108     if ($#{$self->{exclude_file}} + $#{$self->{exclude_list}} >= -1 &&
109         $#{$self->{include_file}} + $#{$self->{include_list}} >= -1) {
110         $self->print_to_server_and_die("Can't have both include and exclude",
111                                        $Amanda::Script_App::ERROR);
112     }
113
114     if ($#{$self->{exclude_file}} >= 0) {
115         $self->{exclude} = [ @{$self->{exclude_file}} ];
116     }
117     foreach my $file (@{$self->{exclude_list}}) {
118         if (!open(FF, $file)) {
119             if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
120                 $self->print_to_server("Open of '$file' failed: $!",
121                                        $Amanda::Script_App::ERROR);
122             }
123             next;
124         }
125         while (<FF>) {
126             chomp;
127             push @{$self->{exclude}}, $_;
128         }
129         close(FF);
130     }
131     if ($#{$self->{include_file}} >= 0) {
132         $self->{include} = [ @{$self->{include_file}} ];
133     }
134     foreach my $file (@{$self->{include_list}}) {
135         if (!open(FF, $file)) {
136             if ($self->{action} eq 'check' && !$self->{include_optional}) {
137                 $self->print_to_server("Open of '$file' failed: $!",
138                                        $Amanda::Script_App::ERROR);
139             }
140             next;
141         }
142         while (<FF>) {
143             chomp;
144             push @{$self->{include}}, $_;
145         }
146         close(FF);
147     }
148 }
149
150 # on entry:
151 #   $self->{directory} == //host/share/subdir           \\host\share\subdir
152 #   or
153 #   $self->{device}    == //host/share/subdir           \\host\share\subdir
154 # on exit:
155 #   $self->{cifshost}   = //host                        \\host
156 #   $self->{share}      = //host/share                  \\host\share
157 #   $self->{sambashare} = \\host\share                  \\host\share
158 #   $self->{subdir}     = subdir                        subdir
159 sub parsesharename {
160     my $self = shift;
161     my $to_parse = $self->{directory};
162     $to_parse = $self->{device} if !defined $to_parse;;
163
164     return if !defined $to_parse;
165
166     if ($self->{unc}) {
167         if ($to_parse =~ m,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
168             $self->{share} = $1;
169             $self->{subdir} = $2
170         } else {
171             $self->{share} = $to_parse
172         }
173         $self->{sambashare} = $self->{share};
174         $to_parse =~ m,^(\\\\[^\\]+)\\[^\\]+,;
175         $self->{cifshost} = $1;
176     } else {
177         if ($to_parse =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
178             $self->{share} = $1;
179             $self->{subdir} = $2
180         } else {
181             $self->{share} = $to_parse
182         }
183         $self->{sambashare} = $self->{share};
184         $self->{sambashare} =~ s,/,\\,g;
185         $to_parse =~ m,^(//[^/]+)/[^/]+,;
186         $self->{cifshost} = $1;
187     }
188 }
189
190
191 # Read $self->{amandapass} file.
192 # on entry:
193 #   $self->{cifshost} == //host/share
194 #   $self->{share} == //host/share
195 # on exit:
196 #   $self->{domain}   = domain to connect to.
197 #   $self->{username} = username (-U)
198 #   $self->{password} = password
199 sub findpass {
200     my $self = shift;
201
202     my $amandapass;
203     my $line;
204
205     $self->{domain} = undef;
206     $self->{username} = undef;
207     $self->{password} = undef;
208
209     debug("amandapass: $self->{amandapass}");
210     if (!open($amandapass, $self->{amandapass})) {
211         if ($self->{allow_anonymous}) {
212             $self->{username} = $self->{allow_anonymous};
213             debug("cannot open password file '$self->{amandapass}': $!\n");
214             debug("Using anonymous user: $self->{username}");
215             return;
216         } else {
217             $self->print_to_server_and_die(
218                         "cannot open password file '$self->{amandapass}': $!",
219                         $Amanda::Script_App::ERROR);
220         }
221     }
222
223     while ($line = <$amandapass>) {
224         chomp $line;
225         next if $line =~ /^#/;
226         my ($diskname, $userpasswd, $domain, $extra) = Amanda::Util::split_quoted_string_friendly($line);
227         if ($extra) {
228             debug("Trailling characters ignored in amandapass line");
229         }
230         if (defined $diskname &&
231             ($diskname eq '*' ||
232              ($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
233              ($self->{unc}==1 && $diskname =~ m,^(\\\\[^\\]+)\\\*$, && $1 eq $self->{cifshost}) ||
234              $diskname eq $self->{share} ||
235              $diskname eq $self->{sambashare})) {
236             if (defined $userpasswd && $userpasswd ne "") {
237                 $self->{domain} = $domain if ($domain ne "");
238                 my ($username, $password) = split('%', $userpasswd, 2);
239                 $self->{username} = $username;
240                 $self->{password} = $password;
241                 $self->{password} = undef if (defined $password && $password eq "");
242             } else {
243                 $self->{username} = "guest";
244             }
245             close($amandapass);
246             return;
247         }
248     }
249     close($amandapass);
250     if ($self->{allow_anonymous}) {
251         $self->{username} = $self->{allow_anonymous};
252         debug("Cannot find password for share $self->{share} in $self->{amandapass}");
253         debug("Using anonymous user: $self->{username}");
254         return;
255     }
256     $self->print_to_server_and_die(
257         "Cannot find password for share $self->{share} in $self->{amandapass}",
258         $Amanda::Script_App::ERROR);
259 }
260
261 sub command_support {
262     my $self = shift;
263
264     print "CONFIG YES\n";
265     print "HOST YES\n";
266     print "DISK YES\n";
267     print "MAX-LEVEL 1\n";
268     print "INDEX-LINE YES\n";
269     print "INDEX-XML NO\n";
270     print "MESSAGE-LINE YES\n";
271     print "MESSAGE-XML NO\n";
272     print "RECORD YES\n";
273     print "COLLECTION NO\n";
274     print "MULTI-ESTIMATE NO\n";
275     print "CALCSIZE NO\n";
276     print "CLIENT-ESTIMATE YES\n";
277     print "EXCLUDE-FILE YES\n";
278     print "EXCLUDE-LIST YES\n";
279     print "EXCLUDE-OPTIONAL YES\n";
280     print "INCLUDE-FILE YES\n";
281     print "INCLUDE-LIST YES\n";
282     print "INCLUDE-OPTIONAL YES\n";
283     print "RECOVER-MODE SMB\n";
284 }
285
286 sub command_selfcheck {
287     my $self = shift;
288
289     #check binary
290     if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
291         $self->print_to_server(
292             "smbclient not set; you must define the SMBCLIENT-PATH property",
293             $Amanda::Script_App::ERROR);
294     }
295     elsif (! -e $self->{smbclient}) {
296         $self->print_to_server("$self->{smbclient} doesn't exist",
297                                $Amanda::Script_App::ERROR);
298     }
299     elsif (! -x $self->{smbclient}) {
300         $self->print_to_server("$self->{smbclient} is not executable",
301                                $Amanda::Script_App::ERROR);
302     }
303     $self->print_to_server("$self->{smbclient}",
304                            $Amanda::Script_App::GOOD);
305     if (!defined $self->{disk} || !defined $self->{device}) {
306         return;
307     }
308     $self->parsesharename();
309     $self->findpass();
310     $self->validate_inexclude();
311
312     print "OK " . $self->{share} . "\n";
313     print "OK " . $self->{disk} . "\n";
314     print "OK " . $self->{device} . "\n";
315     print "OK " . $self->{directory} . "\n" if defined $self->{directory};
316
317     my ($child_rdr, $parent_wtr);
318     if (defined $self->{password}) {
319         # Don't set close-on-exec
320         $^F=10;
321         pipe($child_rdr, $parent_wtr);
322         $^F=2;
323         $parent_wtr->autoflush(1);
324     }
325     my($wtr, $rdr, $err);
326     $err = Symbol::gensym;
327     my $pid = open3($wtr, $rdr, $err, "-");
328     if ($pid == 0) {
329         #child
330         if (defined $self->{password}) {
331             my $ff = $child_rdr->fileno;
332             debug("child_rdr $ff");
333             $parent_wtr->close();
334             $ENV{PASSWD_FD} = $child_rdr->fileno;
335         }
336         close(1);
337         close(2);
338         my @ARGV = ();
339         push @ARGV, $self->{smbclient}, $self->{share};
340         push @ARGV, "" if (!defined $self->{password});
341         push @ARGV, "-U", $self->{username},
342                     "-E";
343         if (defined $self->{domain}) {
344             push @ARGV, "-W", $self->{domain},
345         }
346         if (defined $self->{subdir}) {
347             push @ARGV, "-D", $self->{subdir},
348         }
349         push @ARGV, "-c", "quit";
350         debug("execute: " . $self->{smbclient} . " " .
351               join(" ", @ARGV));
352         exec {$self->{smbclient}} @ARGV;
353     }
354     #parent
355     if (defined $self->{password}) {
356         my $ff = $parent_wtr->fileno;
357         debug("parent_wtr $ff");
358         $parent_wtr->print($self->{password});
359         $parent_wtr->close();
360         $child_rdr->close();
361     } else {
362         debug("No password");
363     }
364     close($wtr);
365     close($rdr);
366     while (<$err>) {
367         chomp;
368         debug("stderr: " . $_);
369         next if /^Domain=/;
370         # message if samba server is configured with 'security = share'
371         next if /Server not using user level security and no password supplied./;
372         $self->print_to_server("smbclient: $_",
373                                $Amanda::Script_App::ERROR);
374     }
375     close($err);
376     waitpid($pid, 0);
377     #check statefile
378     #check amdevice
379 }
380
381 sub command_estimate {
382     my $self = shift;
383
384     $self->parsesharename();
385     $self->findpass();
386     $self->validate_inexclude();
387
388     my $level = $self->{level}[0];
389     my ($child_rdr, $parent_wtr);
390     if (defined $self->{password}) {
391         # Don't set close-on-exec
392         $^F=10;
393         pipe($child_rdr,  $parent_wtr);
394         $^F=2;
395         $parent_wtr->autoflush(1);
396     }
397     my($wtr, $rdr, $err);
398     $err = Symbol::gensym;
399     my $pid = open3($wtr, $rdr, $err, "-");
400     if ($pid == 0) {
401         #child
402         if (defined $self->{password}) {
403             my $ff = $child_rdr->fileno;
404             debug("child_rdr $ff");
405             $parent_wtr->close();
406             $ENV{PASSWD_FD} = $child_rdr->fileno;
407         }
408         close(0);
409         close(1);
410         my @ARGV = ();
411         push @ARGV, $self->{smbclient}, $self->{share};
412         push @ARGV, "" if (!defined($self->{password}));
413         push @ARGV, "-d", "0",
414                     "-U", $self->{username},
415                     "-E";
416         if (defined $self->{domain}) {
417             push @ARGV, "-W", $self->{domain},
418         }
419         if (defined $self->{subdir}) {
420             push @ARGV, "-D", $self->{subdir},
421         }
422         if ($level == 0) {
423             push @ARGV, "-c", "archive 0;recurse;du";
424         } else {
425             push @ARGV, "-c", "archive 1;recurse;du";
426         }
427         debug("execute: " . $self->{smbclient} . " " .
428               join(" ", @ARGV));
429         exec {$self->{smbclient}} @ARGV;
430     }
431     #parent
432     if (defined $self->{password}) {
433         my $ff = $parent_wtr->fileno;
434         debug("parent_wtr $ff");
435         debug("password $self->{password}");
436         $parent_wtr->print($self->{password});
437         $parent_wtr->close();
438         $child_rdr->close();
439     }
440     close($wtr);
441     close($rdr);
442     my $size = $self->parse_estimate($err);
443     close($err);
444     output_size($level, $size);
445     waitpid($pid, 0);
446 }
447
448 sub parse_estimate {
449     my $self = shift;
450     my($fh)  = shift;
451     my($size) = -1;
452     while(<$fh>) {
453         chomp;
454         next if /^\s*$/;
455         next if /blocks of size/;
456         next if /blocks available/;
457         next if /^\s*$/;
458         next if /^Domain=/;
459         next if /dumped \d+ files and directories/;
460         # message if samba server is configured with 'security = share'
461         next if /Server not using user level security and no password supplied./;
462         debug("stderr: $_");
463         if ($_ =~ /^Total number of bytes: (\d*)/) {
464             $size = $1;
465             last;
466         } else {
467             $self->print_to_server("smbclient: $_",
468                                    $Amanda::Script_App::ERROR);
469         }
470     }
471     return $size;
472 }
473
474 sub output_size {
475    my($level) = shift;
476    my($size) = shift;
477    if($size == -1) {
478       print "$level -1 -1\n";
479       #exit 2;
480    }
481    else {
482       my($ksize) = int $size / (1024);
483       $ksize=32 if ($ksize<32);
484       print "$level $ksize 1\n";
485    }
486 }
487
488 sub command_backup {
489     my $self = shift;
490
491     my $level = $self->{level}[0];
492
493     $self->parsesharename();
494     $self->findpass();
495     $self->validate_inexclude();
496
497     my $pid_tee = open3(\*INDEX_IN, '>&STDOUT', \*INDEX_TEE, "-");
498     if ($pid_tee == 0) {
499         close(INDEX_IN);
500         close(INDEX_TEE);
501         my $buf;
502         my $size = -1;
503         while (($size = POSIX::read(0, $buf, 32768)) > 0) {
504             POSIX::write(1, $buf, $size);
505             POSIX::write(2, $buf, $size);
506         }
507         exit 0;
508     }
509     my ($child_rdr, $parent_wtr);
510     if (defined $self->{password}) {
511         # Don't set close-on-exec
512         $^F=10;
513         pipe($child_rdr,  $parent_wtr);
514         $^F=2;
515         $parent_wtr->autoflush(1);
516     }
517     my($wtr, $err);
518     $err = Symbol::gensym;
519     my $pid = open3($wtr, ">&INDEX_IN", $err, "-");
520     if ($pid == 0) {
521         #child
522         if (defined $self->{password}) {
523             my $ff = $child_rdr->fileno;
524             debug("child_rdr $ff");
525             $parent_wtr->close();
526             $ENV{PASSWD_FD} = $child_rdr->fileno;
527         }
528         close(0);
529         my @ARGV = ();
530         push @ARGV, $self->{smbclient}, $self->{share};
531         push @ARGV, "" if (!defined($self->{password}));
532         push @ARGV, "-d", "0",
533                     "-U", $self->{username},
534                     "-E";
535         if (defined $self->{domain}) {
536             push @ARGV, "-W", $self->{domain},
537         }
538         if (defined $self->{subdir}) {
539             push @ARGV, "-D", $self->{subdir},
540         }
541
542         my $comm ;
543         if ($level == 0) {
544             $comm = "tarmode full reset hidden system quiet;";
545         } else {
546             $comm = "tarmode inc noreset hidden system quiet;";
547         }
548         $comm .= " tar c";
549         if ($#{$self->{exclude}} >= 0) {
550             $comm .= "X";
551         }
552         if ($#{$self->{include}} >= 0) {
553             $comm .= "I";
554         }
555         $comm .= " -";
556         if ($#{$self->{exclude}} >= 0) {
557             $comm .= " " . join(" ", @{$self->{exclude}});
558         }
559         if ($#{$self->{include}} >= 0) {
560             $comm .= " " . join(" ", @{$self->{include}});
561         }
562         push @ARGV, "-c", $comm;
563         debug("execute: " . $self->{smbclient} . " " .
564               join(" ", @ARGV));
565         exec {$self->{smbclient}} @ARGV;
566     }
567
568     if (defined $self->{password}) {
569         my $ff = $parent_wtr->fileno;
570         debug("parent_wtr $ff");
571         debug("password $self->{password}");
572         $parent_wtr->print($self->{password});
573         $parent_wtr->close();
574         $child_rdr->close();
575     } else {
576         debug("No password");
577     }
578     close($wtr);
579
580     #index process 
581     my $index;
582     debug("$self->{gnutar} -tf -");
583     my $pid_index1 = open2($index, '<&INDEX_TEE', $self->{gnutar}, "-tf", "-");
584     close(INDEX_IN);
585     my $size = -1;
586     my $index_fd = $index->fileno;
587     debug("index $index_fd");
588     if (defined($self->{index})) {
589         my $indexout_fd;
590         open($indexout_fd, '>&=4') ||
591             $self->print_to_server_and_die("Can't open indexout_fd: $!",
592                                            $Amanda::Script_App::ERROR);
593         $self->parse_backup($index, $self->{mesgout}, $indexout_fd);
594         close($indexout_fd);
595     }
596     else {
597         $self->parse_backup($index_fd, $self->{mesgout}, undef);
598     }
599     close($index);
600
601     while (<$err>) {
602         chomp;
603         debug("stderr: " . $_);
604         next if /^Domain=/;
605         next if /^tarmode is now /;
606         next if /dumped (\d+) files and directories/;
607         # message if samba server is configured with 'security = share'
608         next if /Server not using user level security and no password supplied./;
609         if (/^Total bytes written: (\d*)/) {
610             $size = $1;
611         } else {
612             $self->print_to_server("smbclient: $_",
613                                    $Amanda::Script_App::ERROR);
614         }
615     }
616     if ($size >= 0) {
617         my $ksize = $size / 1024;
618         if ($ksize < 32) {
619             $ksize = 32;
620         }
621         print {$self->{mesgout}} "sendbackup: size $ksize\n";
622         print {$self->{mesgout}} "sendbackup: end\n";
623     }
624
625     waitpid $pid, 0;
626     if ($? != 0) {
627         $self->print_to_server_and_die("smbclient returned error",
628                                        $Amanda::Script_App::ERROR);
629     }
630     exit 0;
631 }
632
633 sub parse_backup {
634     my $self = shift;
635     my($fhin, $fhout, $indexout) = @_;
636     my $size  = -1;
637     while(<$fhin>) {
638         if ( /^\.\//) {
639             if(defined($indexout)) {
640                 if(defined($self->{index})) {
641                     s/^\.//;
642                     print $indexout $_;
643                 }
644             }
645         }
646         else {
647             print $fhout "? $_";
648         }
649     }
650 }
651
652 sub command_index_from_output {
653    index_from_output(0, 1);
654    exit 0;
655 }
656
657 sub index_from_output {
658    my($fhin, $fhout) = @_;
659    my($size) = -1;
660    while(<$fhin>) {
661       next if /^Total bytes written:/;
662       next if !/^\.\//;
663       s/^\.//;
664       print $fhout $_;
665    }
666 }
667
668 sub command_index_from_image {
669    my $self = shift;
670    my $index_fd;
671    open($index_fd, "$self->{gnutar} --list --file - |") ||
672       $self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
673                                      $Amanda::Script_App::ERROR);
674    index_from_output($index_fd, 1);
675 }
676
677 sub command_restore {
678     my $self = shift;
679     my @cmd = ();
680
681     $self->parsesharename();
682     chdir(Amanda::Util::get_original_cwd());
683
684     if ($self->{recover_mode} eq "smb") {
685         $self->validate_inexclude();
686         $self->findpass();
687         push @cmd, $self->{smbclient}, $self->{share};
688         push @cmd, "" if (!defined $self->{password});
689         push @cmd, "-d", "0",
690                    "-U", $self->{username};
691         
692         if (defined $self->{domain}) {
693             push @cmd, "-W", $self->{domain};
694         }
695         push @cmd, "-Tx", "-";
696         if ($#{$self->{include}} >= 0) {
697             push @cmd, @{$self->{include}};
698         }
699         for(my $i=1;defined $ARGV[$i]; $i++) {
700             my $param = $ARGV[$i];
701             $param =~ /^(.*)$/;
702             push @cmd, $1;
703         }
704         my ($parent_rdr, $child_wtr);
705         if (defined $self->{password}) {
706             # Don't set close-on-exec
707             $^F=10;
708             pipe($parent_rdr,  $child_wtr);
709             $^F=2;
710             $child_wtr->autoflush(1);
711         }
712         my($wtr, $rdr, $err);
713         $err = Symbol::gensym;
714         my $pid = open3($wtr, $rdr, $err, "-");
715         if ($pid == 0) {
716             $child_wtr->print($self->{password});
717             $child_wtr->close();
718             exit 0;
719         }
720         if (defined $self->{password}) {
721             $child_wtr->close();
722             $ENV{PASSWD_FD} = $parent_rdr->fileno;
723         }
724         debug("cmd:" . join(" ", @cmd));
725         exec { $cmd[0] } @cmd;
726         die("Can't exec '", $cmd[0], "'");
727     } else {
728         push @cmd, $self->{gnutar}, "-xpvf", "-";
729         if (defined $self->{directory}) {
730             if (!-d $self->{directory}) {
731                 $self->print_to_server_and_die(
732                                        "Directory $self->{directory}: $!",
733                                        $Amanda::Script_App::ERROR);
734             }
735             if (!-w $self->{directory}) {
736                 $self->print_to_server_and_die(
737                                        "Directory $self->{directory}: $!",
738                                        $Amanda::Script_App::ERROR);
739             }
740             push @cmd, "--directory", $self->{directory};
741         }
742         if ($#{$self->{include_list}} == 0) {
743             push @cmd, "--files-from", $self->{include_list}[0];
744         }
745         if ($#{$self->{exclude_list}} == 0) {
746             push @cmd, "--exclude-from", $self->{exclude_list}[0];
747         }
748         for(my $i=1;defined $ARGV[$i]; $i++) {
749             my $param = $ARGV[$i];
750             $param =~ /^(.*)$/;
751             push @cmd, $1;
752         }
753         debug("cmd:" . join(" ", @cmd));
754         exec { $cmd[0] } @cmd;
755         die("Can't exec '", $cmd[0], "'");
756     }
757 }
758
759 sub command_validate {
760    my $self = shift;
761
762    if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
763       return $self->default_validate();
764    }
765
766    my(@cmd) = ($self->{gnutar}, "-tf", "-");
767    debug("cmd:" . join(" ", @cmd));
768    my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
769         $self->print_to_server_and_die("Unable to run @cmd: $!",
770                                        $Amanda::Script_App::ERROR);
771    waitpid $pid, 0;
772    if( $? != 0 ){
773         $self->print_to_server_and_die("$self->{gnutar} returned error",
774                                        $Amanda::Script_App::ERROR);
775    }
776    exit(0);
777 }
778
779 sub command_print_command {
780 }
781
782 package main;
783
784 sub usage {
785     print <<EOF;
786 Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
787 EOF
788     exit(1);
789 }
790
791 my $opt_version;
792 my $opt_config;
793 my $opt_host;
794 my $opt_disk;
795 my $opt_device;
796 my @opt_level;
797 my $opt_index;
798 my $opt_message;
799 my $opt_collection;
800 my $opt_record;
801 my $opt_calcsize;
802 my $opt_gnutar_path;
803 my $opt_smbclient_path;
804 my $opt_amandapass;
805 my @opt_exclude_file;
806 my @opt_exclude_list;
807 my $opt_exclude_optional;
808 my @opt_include_file;
809 my @opt_include_list;
810 my $opt_include_optional;
811 my $opt_recover_mode;
812 my $opt_allow_anonymous;
813 my $opt_directory;
814
815 Getopt::Long::Configure(qw{bundling});
816 GetOptions(
817     'version'            => \$opt_version,
818     'config=s'           => \$opt_config,
819     'host=s'             => \$opt_host,
820     'disk=s'             => \$opt_disk,
821     'device=s'           => \$opt_device,
822     'level=s'            => \@opt_level,
823     'index=s'            => \$opt_index,
824     'message=s'          => \$opt_message,
825     'collection=s'       => \$opt_collection,
826     'record'             => \$opt_record,
827     'calcsize'           => \$opt_calcsize,
828     'gnutar-path=s'      => \$opt_gnutar_path,
829     'smbclient-path=s'   => \$opt_smbclient_path,
830     'amandapass=s'       => \$opt_amandapass,
831     'exclude-file=s'     => \@opt_exclude_file,
832     'exclude-list=s'     => \@opt_exclude_list,
833     'exclude-optional=s' => \$opt_exclude_optional,
834     'include-file=s'     => \@opt_include_file,
835     'include-list=s'     => \@opt_include_list,
836     'include-optional=s' => \$opt_include_optional,
837     'recover-mode=s'     => \$opt_recover_mode,
838     'allow-anonymous=s'  => \$opt_allow_anonymous,
839     'directory=s'        => \$opt_directory,
840 ) or usage();
841
842 if (defined $opt_version) {
843     print "amsamba-" . $Amanda::Constants::VERSION , "\n";
844     exit(0);
845 }
846
847 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);
848
849 $application->do($ARGV[0]);