d1f0bda72689c6c2e33da142ea208d0dc8e9ca1d
[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     $self->print_to_server("disk " . quote_string($self->{disk}));
335
336     $self->print_to_server("amsamba version " . $Amanda::Constants::VERSION,
337                            $Amanda::Script_App::GOOD);
338     #check binary
339     if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
340         $self->print_to_server(
341             "smbclient not set; you must define the SMBCLIENT-PATH property",
342             $Amanda::Script_App::ERROR);
343     }
344     elsif (! -e $self->{smbclient}) {
345         $self->print_to_server("$self->{smbclient} doesn't exist",
346                                $Amanda::Script_App::ERROR);
347     }
348     elsif (! -x $self->{smbclient}) {
349         $self->print_to_server("$self->{smbclient} is not executable",
350                                $Amanda::Script_App::ERROR);
351     } else {
352         my @sv = `$self->{smbclient} --version`;
353         if ($? >> 8 == 0) {
354             $sv[0] =~ /^[^0-9]*(.*)$/;
355             my $sv = $1;
356             $self->print_to_server("amsamba smbclient-version $sv",
357                                    $Amanda::Script_App::GOOD);
358         } else {
359             $self->print_to_server(
360                 "[Can't get " . $self->{smbclient} . " version]\n",
361                 $Amanda::Script_App::ERROR);
362         }
363     }
364
365     $self->print_to_server("$self->{smbclient}",
366                            $Amanda::Script_App::GOOD);
367     if (!defined $self->{disk} || !defined $self->{device}) {
368         return;
369     }
370     $self->parsesharename();
371     $self->findpass();
372     $self->validate_inexclude();
373
374     print "OK " . $self->{share} . "\n";
375     print "OK " . $self->{device} . "\n";
376     print "OK " . $self->{directory} . "\n" if defined $self->{directory};
377
378     my ($child_rdr, $parent_wtr);
379     if (defined $self->{password}) {
380         # Don't set close-on-exec
381         $^F=10;
382         pipe($child_rdr, $parent_wtr);
383         $^F=2;
384         $parent_wtr->autoflush(1);
385     }
386     my($wtr, $rdr, $err);
387     $err = Symbol::gensym;
388     my $pid = open3($wtr, $rdr, $err, "-");
389     if ($pid == 0) {
390         #child
391         if (defined $self->{password}) {
392             my $ff = $child_rdr->fileno;
393             debug("child_rdr $ff");
394             $parent_wtr->close();
395             $ENV{PASSWD_FD} = $child_rdr->fileno;
396         }
397         close(1);
398         close(2);
399         my @ARGV = ();
400         push @ARGV, $self->{smbclient}, $self->{share};
401         push @ARGV, "" if (!defined $self->{password});
402         push @ARGV, "-U", $self->{username},
403                     "-E";
404         if (defined $self->{domain}) {
405             push @ARGV, "-W", $self->{domain},
406         }
407         if (defined $self->{subdir}) {
408             push @ARGV, "-D", $self->{subdir},
409         }
410         push @ARGV, "-c", "quit";
411         debug("execute: " . $self->{smbclient} . " " .
412               join(" ", @ARGV));
413         exec {$self->{smbclient}} @ARGV;
414     }
415     #parent
416     if (defined $self->{password}) {
417         my $ff = $parent_wtr->fileno;
418         debug("parent_wtr $ff");
419         $parent_wtr->print($self->{password});
420         $parent_wtr->close();
421         $child_rdr->close();
422     } else {
423         debug("No password");
424     }
425     close($wtr);
426     close($rdr);
427     while (<$err>) {
428         chomp;
429         debug("stderr: " . $_);
430         next if /^Domain=/;
431         # message if samba server is configured with 'security = share'
432         next if /Server not using user level security and no password supplied./;
433         $self->print_to_server("smbclient: $_",
434                                $Amanda::Script_App::ERROR);
435     }
436     close($err);
437     waitpid($pid, 0);
438     #check statefile
439     #check amdevice
440 }
441
442 sub command_estimate {
443     my $self = shift;
444
445     $self->parsesharename();
446     $self->findpass();
447     $self->validate_inexclude();
448
449     my $level = $self->{level}[0];
450     my ($child_rdr, $parent_wtr);
451     if (defined $self->{password}) {
452         # Don't set close-on-exec
453         $^F=10;
454         pipe($child_rdr,  $parent_wtr);
455         $^F=2;
456         $parent_wtr->autoflush(1);
457     }
458     my($wtr, $rdr, $err);
459     $err = Symbol::gensym;
460     my $pid = open3($wtr, $rdr, $err, "-");
461     if ($pid == 0) {
462         #child
463         if (defined $self->{password}) {
464             my $ff = $child_rdr->fileno;
465             debug("child_rdr $ff");
466             $parent_wtr->close();
467             $ENV{PASSWD_FD} = $child_rdr->fileno;
468         }
469         close(0);
470         close(1);
471         my @ARGV = ();
472         push @ARGV, $self->{smbclient}, $self->{share};
473         push @ARGV, "" if (!defined($self->{password}));
474         push @ARGV, "-d", "0",
475                     "-U", $self->{username},
476                     "-E";
477         if (defined $self->{domain}) {
478             push @ARGV, "-W", $self->{domain},
479         }
480         if (defined $self->{subdir}) {
481             push @ARGV, "-D", $self->{subdir},
482         }
483         if ($level == 0) {
484             push @ARGV, "-c", "archive 0;recurse;du";
485         } else {
486             push @ARGV, "-c", "archive 1;recurse;du";
487         }
488         debug("execute: " . $self->{smbclient} . " " .
489               join(" ", @ARGV));
490         exec {$self->{smbclient}} @ARGV;
491     }
492     #parent
493     if (defined $self->{password}) {
494         my $ff = $parent_wtr->fileno;
495         debug("parent_wtr $ff");
496         debug("password $self->{password}");
497         $parent_wtr->print($self->{password});
498         $parent_wtr->close();
499         $child_rdr->close();
500     }
501     close($wtr);
502     close($rdr);
503     my $size = $self->parse_estimate($err);
504     close($err);
505     output_size($level, $size);
506     waitpid($pid, 0);
507 }
508
509 sub parse_estimate {
510     my $self = shift;
511     my($fh)  = shift;
512     my($size) = -1;
513     while(<$fh>) {
514         chomp;
515         next if /^\s*$/;
516         next if /blocks of size/;
517         next if /blocks available/;
518         next if /^\s*$/;
519         next if /^Domain=/;
520         next if /dumped \d+ files and directories/;
521         # message if samba server is configured with 'security = share'
522         next if /Server not using user level security and no password supplied./;
523         debug("stderr: $_");
524         if ($_ =~ /^Total number of bytes: (\d*)/) {
525             $size = $1;
526             last;
527         } else {
528             $self->print_to_server("smbclient: $_",
529                                    $Amanda::Script_App::ERROR);
530         }
531     }
532     return $size;
533 }
534
535 sub output_size {
536    my($level) = shift;
537    my($size) = shift;
538    if($size == -1) {
539       print "$level -1 -1\n";
540       #exit 2;
541    }
542    else {
543       my($ksize) = int $size / (1024);
544       $ksize=32 if ($ksize<32);
545       print "$level $ksize 1\n";
546    }
547 }
548
549 sub command_backup {
550     my $self = shift;
551
552     my $level = $self->{level}[0];
553
554     $self->parsesharename();
555     $self->findpass();
556     $self->validate_inexclude();
557
558     my $pid_tee = open3(\*INDEX_IN, '>&STDOUT', \*INDEX_TEE, "-");
559     if ($pid_tee == 0) {
560         close(INDEX_IN);
561         close(INDEX_TEE);
562         my $buf;
563         my $size = -1;
564         while (($size = POSIX::read(0, $buf, 32768)) > 0) {
565             POSIX::write(1, $buf, $size);
566             POSIX::write(2, $buf, $size);
567         }
568         exit 0;
569     }
570     my ($child_rdr, $parent_wtr);
571     if (defined $self->{password}) {
572         # Don't set close-on-exec
573         $^F=10;
574         pipe($child_rdr,  $parent_wtr);
575         $^F=2;
576         $parent_wtr->autoflush(1);
577     }
578     my($wtr, $err);
579     $err = Symbol::gensym;
580     my $pid = open3($wtr, ">&INDEX_IN", $err, "-");
581     if ($pid == 0) {
582         #child
583         if (defined $self->{password}) {
584             my $ff = $child_rdr->fileno;
585             debug("child_rdr $ff");
586             $parent_wtr->close();
587             $ENV{PASSWD_FD} = $child_rdr->fileno;
588         }
589         close(0);
590         my @ARGV = ();
591         push @ARGV, $self->{smbclient}, $self->{share};
592         push @ARGV, "" if (!defined($self->{password}));
593         push @ARGV, "-d", "0",
594                     "-U", $self->{username},
595                     "-E";
596         if (defined $self->{domain}) {
597             push @ARGV, "-W", $self->{domain},
598         }
599         if (defined $self->{subdir}) {
600             push @ARGV, "-D", $self->{subdir},
601         }
602
603         my $comm ;
604         if ($level == 0) {
605             $comm = "tarmode full reset hidden system quiet;";
606         } else {
607             $comm = "tarmode inc noreset hidden system quiet;";
608         }
609         $comm .= " tar c";
610         if ($#{$self->{exclude}} >= 0) {
611             $comm .= "X";
612         }
613         if ($#{$self->{include}} >= 0) {
614             $comm .= "I";
615         }
616         $comm .= " -";
617         if ($#{$self->{exclude}} >= 0) {
618             $comm .= " " . join(" ", @{$self->{exclude}});
619         }
620         if ($#{$self->{include}} >= 0) {
621             $comm .= " " . join(" ", @{$self->{include}});
622         }
623         push @ARGV, "-c", $comm;
624         debug("execute: " . $self->{smbclient} . " " .
625               join(" ", @ARGV));
626         exec {$self->{smbclient}} @ARGV;
627     }
628
629     if (defined $self->{password}) {
630         my $ff = $parent_wtr->fileno;
631         debug("parent_wtr $ff");
632         debug("password $self->{password}");
633         $parent_wtr->print($self->{password});
634         $parent_wtr->close();
635         $child_rdr->close();
636     } else {
637         debug("No password");
638     }
639     close($wtr);
640
641     #index process 
642     my $index;
643     debug("$self->{gnutar} -tf -");
644     my $pid_index1 = open2($index, '<&INDEX_TEE', $self->{gnutar}, "-tf", "-");
645     close(INDEX_IN);
646     my $size = -1;
647     my $index_fd = $index->fileno;
648     debug("index $index_fd");
649     if (defined($self->{index})) {
650         my $indexout_fd;
651         open($indexout_fd, '>&=4') ||
652             $self->print_to_server_and_die("Can't open indexout_fd: $!",
653                                            $Amanda::Script_App::ERROR);
654         $self->parse_backup($index, $self->{mesgout}, $indexout_fd);
655         close($indexout_fd);
656     }
657     else {
658         $self->parse_backup($index_fd, $self->{mesgout}, undef);
659     }
660     close($index);
661
662     while (<$err>) {
663         chomp;
664         debug("stderr: " . $_);
665         next if /^Domain=/;
666         next if /^tarmode is now /;
667         next if /dumped (\d+) files and directories/;
668         # message if samba server is configured with 'security = share'
669         next if /Server not using user level security and no password supplied./;
670         if (/^Total bytes written: (\d*)/) {
671             $size = $1;
672         } else {
673             $self->print_to_server("smbclient: $_",
674                                    $Amanda::Script_App::ERROR);
675         }
676     }
677     if ($size >= 0) {
678         my $ksize = $size / 1024;
679         if ($ksize < 32) {
680             $ksize = 32;
681         }
682         print {$self->{mesgout}} "sendbackup: size $ksize\n";
683         print {$self->{mesgout}} "sendbackup: end\n";
684     }
685
686     waitpid $pid, 0;
687     if ($? != 0) {
688         $self->print_to_server_and_die("smbclient returned error",
689                                        $Amanda::Script_App::ERROR);
690     }
691     exit 0;
692 }
693
694 sub parse_backup {
695     my $self = shift;
696     my($fhin, $fhout, $indexout) = @_;
697     my $size  = -1;
698     while(<$fhin>) {
699         if ( /^\.\//) {
700             if(defined($indexout)) {
701                 if(defined($self->{index})) {
702                     s/^\.//;
703                     print $indexout $_;
704                 }
705             }
706         }
707         else {
708             print $fhout "? $_";
709         }
710     }
711 }
712
713 sub command_index_from_output {
714    index_from_output(0, 1);
715    exit 0;
716 }
717
718 sub index_from_output {
719    my($fhin, $fhout) = @_;
720    my($size) = -1;
721    while(<$fhin>) {
722       next if /^Total bytes written:/;
723       next if !/^\.\//;
724       s/^\.//;
725       print $fhout $_;
726    }
727 }
728
729 sub command_index_from_image {
730    my $self = shift;
731    my $index_fd;
732    open($index_fd, "$self->{gnutar} --list --file - |") ||
733       $self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
734                                      $Amanda::Script_App::ERROR);
735    index_from_output($index_fd, 1);
736 }
737
738 sub command_restore {
739     my $self = shift;
740     my @cmd = ();
741
742     $self->parsesharename();
743     chdir(Amanda::Util::get_original_cwd());
744
745     if ($self->{recover_mode} eq "smb") {
746         $self->validate_inexclude();
747         $self->findpass();
748         push @cmd, $self->{smbclient}, $self->{share};
749         push @cmd, "" if (!defined $self->{password});
750         push @cmd, "-d", "0",
751                    "-U", $self->{username};
752         
753         if (defined $self->{domain}) {
754             push @cmd, "-W", $self->{domain};
755         }
756         if (defined $self->{'include_filename'}) {
757             push @cmd, "-TFx", "-", "$self->{'include_filename'}";
758         } else {
759             push @cmd, "-Tx", "-";
760             if ($#{$self->{include}} >= 0) {
761                 push @cmd, @{$self->{include}};
762             }
763             for(my $i=1;defined $ARGV[$i]; $i++) {
764                 my $param = $ARGV[$i];
765                 $param =~ /^(.*)$/;
766                 push @cmd, $1;
767             }
768         }
769         my ($parent_rdr, $child_wtr);
770         if (defined $self->{password}) {
771             # Don't set close-on-exec
772             $^F=10;
773             pipe($parent_rdr,  $child_wtr);
774             $^F=2;
775             $child_wtr->autoflush(1);
776         }
777         my($wtr, $rdr, $err);
778         $err = Symbol::gensym;
779         my $pid = open3($wtr, $rdr, $err, "-");
780         if ($pid == 0) {
781             $child_wtr->print($self->{password});
782             $child_wtr->close();
783             exit 0;
784         }
785         if (defined $self->{password}) {
786             $child_wtr->close();
787             $ENV{PASSWD_FD} = $parent_rdr->fileno;
788         }
789         debug("cmd:" . join(" ", @cmd));
790         exec { $cmd[0] } @cmd;
791         die("Can't exec '", $cmd[0], "'");
792     } else {
793         push @cmd, $self->{gnutar}, "-xpvf", "-";
794         if (defined $self->{directory}) {
795             if (!-d $self->{directory}) {
796                 $self->print_to_server_and_die(
797                                        "Directory $self->{directory}: $!",
798                                        $Amanda::Script_App::ERROR);
799             }
800             if (!-w $self->{directory}) {
801                 $self->print_to_server_and_die(
802                                        "Directory $self->{directory}: $!",
803                                        $Amanda::Script_App::ERROR);
804             }
805             push @cmd, "--directory", $self->{directory};
806         }
807         if ($#{$self->{include_list}} == 0) {
808             push @cmd, "--files-from", $self->{include_list}[0];
809         }
810         if ($#{$self->{exclude_list}} == 0) {
811             push @cmd, "--exclude-from", $self->{exclude_list}[0];
812         }
813         for(my $i=1;defined $ARGV[$i]; $i++) {
814             my $param = $ARGV[$i];
815             $param =~ /^(.*)$/;
816             push @cmd, $1;
817         }
818         debug("cmd:" . join(" ", @cmd));
819         exec { $cmd[0] } @cmd;
820         die("Can't exec '", $cmd[0], "'");
821     }
822 }
823
824 sub command_validate {
825    my $self = shift;
826
827    if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
828       return $self->default_validate();
829    }
830
831    my(@cmd) = ($self->{gnutar}, "-tf", "-");
832    debug("cmd:" . join(" ", @cmd));
833    my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
834         $self->print_to_server_and_die("Unable to run @cmd: $!",
835                                        $Amanda::Script_App::ERROR);
836    waitpid $pid, 0;
837    if( $? != 0 ){
838         $self->print_to_server_and_die("$self->{gnutar} returned error",
839                                        $Amanda::Script_App::ERROR);
840    }
841    exit(0);
842 }
843
844 sub command_print_command {
845 }
846
847 package main;
848
849 sub usage {
850     print <<EOF;
851 Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
852 EOF
853     exit(1);
854 }
855
856 my $opt_version;
857 my $opt_config;
858 my $opt_host;
859 my $opt_disk;
860 my $opt_device;
861 my @opt_level;
862 my $opt_index;
863 my $opt_message;
864 my $opt_collection;
865 my $opt_record;
866 my $opt_calcsize;
867 my $opt_gnutar_path;
868 my $opt_smbclient_path;
869 my $opt_amandapass;
870 my @opt_exclude_file;
871 my @opt_exclude_list;
872 my $opt_exclude_optional;
873 my @opt_include_file;
874 my @opt_include_list;
875 my $opt_include_optional;
876 my $opt_recover_mode;
877 my $opt_allow_anonymous;
878 my $opt_directory;
879
880 Getopt::Long::Configure(qw{bundling});
881 GetOptions(
882     'version'            => \$opt_version,
883     'config=s'           => \$opt_config,
884     'host=s'             => \$opt_host,
885     'disk=s'             => \$opt_disk,
886     'device=s'           => \$opt_device,
887     'level=s'            => \@opt_level,
888     'index=s'            => \$opt_index,
889     'message=s'          => \$opt_message,
890     'collection=s'       => \$opt_collection,
891     'record'             => \$opt_record,
892     'calcsize'           => \$opt_calcsize,
893     'gnutar-path=s'      => \$opt_gnutar_path,
894     'smbclient-path=s'   => \$opt_smbclient_path,
895     'amandapass=s'       => \$opt_amandapass,
896     'exclude-file=s'     => \@opt_exclude_file,
897     'exclude-list=s'     => \@opt_exclude_list,
898     'exclude-optional=s' => \$opt_exclude_optional,
899     'include-file=s'     => \@opt_include_file,
900     'include-list=s'     => \@opt_include_list,
901     'include-optional=s' => \$opt_include_optional,
902     'recover-mode=s'     => \$opt_recover_mode,
903     'allow-anonymous=s'  => \$opt_allow_anonymous,
904     'directory=s'        => \$opt_directory,
905 ) or usage();
906
907 if (defined $opt_version) {
908     print "amsamba-" . $Amanda::Constants::VERSION , "\n";
909     exit(0);
910 }
911
912 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);
913
914 $application->do($ARGV[0]);