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