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