Imported Upstream version 3.3.2
[debian/amanda] / application-src / ampgsql.pl
index 1a20a242e5f2dc30a0d9f278dbb853e71162ea5f..652345f08202e0c3b546a96d9f0beeb7efe906c7 100644 (file)
@@ -1,5 +1,5 @@
 #!@PERL@
-# Copyright (c) 2009, 2010 Zmanda, Inc.  All Rights Reserved.
+# Copyright (c) 2009-2012 Zmanda, Inc.  All Rights Reserved.
 #
 # This program is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License version 2 as published
@@ -38,7 +38,8 @@ use Amanda::Constants;
 use Amanda::Config qw( :init :getconf  config_dir_relative string_to_boolean );
 use Amanda::Debug qw( :logging );
 use Amanda::Paths;
-use Amanda::Util qw( :constants :encoding );
+use Amanda::Util qw( :constants :encoding quote_string );
+use Amanda::MainLoop qw( :GIOCondition );
 
 my $_DATA_DIR_TAR = "data_dir.tar";
 my $_ARCHIVE_DIR_TAR = "archive_dir.tar";
@@ -89,10 +90,11 @@ sub new {
             $self->{'props'}->{$pname} = $conf_props->{$pname}->{'values'}->[0];
         }
     }
-    # check for properties like 'foo-pg-host' where the device is 'foo'
-    if ($self->{'args'}->{'device'}) {
+
+    # check for properties like 'foo-pg-host' where the diskname is 'foo'
+    if ($self->{'args'}->{'disk'}) {
         foreach my $pname (@PROP_NAMES) {
-            my $tmp = "$self->{'args'}->{'device'}-$pname";
+            my $tmp = "$self->{'args'}->{'disk'}-$pname";
             if ($conf_props->{$tmp}) {
                 debug("More than one value for $tmp. Using the first.")
                     if scalar(@{$conf_props->{$tmp}->{'values'}}) > 1;
@@ -101,6 +103,16 @@ sub new {
         }
     }
 
+    # overwrite with dumptype properties if they are set.
+    foreach my $pname (@PROP_NAMES) {
+       my $pdumpname = $pname;
+       $pdumpname =~ s/^pg-//g;
+       $self->{'props'}->{$pname} = $self->{'args'}->{$pdumpname}
+                                if defined $self->{'args'}->{$pdumpname};
+debug("prop $pname set from dumpname $pdumpname: $self->{'args'}->{$pdumpname}")
+if defined $self->{'args'}->{$pdumpname};
+    }
+
     unless ($self->{'props'}->{'psql-path'}) {
         foreach my $pre (split(/:/, $ENV{PATH})) {
             my $psql = "$pre/psql";
@@ -127,6 +139,10 @@ sub new {
         }
     }
 
+    if (!exists $self->{'props'}->{'pg-datadir'}) {
+       $self->{'props'}->{'pg-datadir'} =  $self->{'args'}->{'device'};
+    }
+
     return $self;
 }
 
@@ -161,6 +177,7 @@ sub _check {
 
 sub _check_parent_dirs {
     my ($dir) = @_;
+    my $ok = 1;
     my $is_abs = substr($dir, 0, 1) eq "/";
     _check("$dir is an absolute path?", "Yes", "No. It should start with '/'",
        sub {$is_abs});
@@ -170,11 +187,14 @@ sub _check_parent_dirs {
     my $partial_path = '';
     for my $path_part (@parts) {
         $partial_path .= $path_part . (($partial_path || $is_abs)? '/' : '');
-        _check("$partial_path is executable?", "Yes", "No",
+        $ok &&=
+           _check("$partial_path is executable?", "Yes", "No",
                sub {-x $_[0]}, $partial_path);
-        _check("$partial_path is a directory?", "Yes", "No",
+        $ok &&=
+           _check("$partial_path is a directory?", "Yes", "No",
                sub {-d $_[0]}, $partial_path);
     }
+    $ok;
 }
 
 sub _ok_passfile_perms {
@@ -202,14 +222,78 @@ sub _run_psql_command {
     push @cmd, "-p", $self->{'props'}->{'pg-port'} if ($self->{'props'}->{'pg-port'});
     push @cmd, "-U", $self->{'props'}->{'pg-user'} if ($self->{'props'}->{'pg-user'});
 
-    push @cmd, '--quiet', '--output', '/dev/null', '--command', $cmd, $self->{'props'}->{'pg-db'};
+    push @cmd, '--quiet', '--output', '/dev/null' if (!($cmd =~ /pg_xlogfile_name_offset/));
+    push @cmd, '--command', $cmd, $self->{'props'}->{'pg-db'};
     debug("running " . join(" ", @cmd));
-    my $status = system(@cmd);
+
+    my ($wtr, $rdr);
+    my $err = Symbol::gensym;
+    my $pid = open3($wtr, $rdr, $err, @cmd);
+    close($wtr);
+
+    my $file_to_close = 2;
+    my $psql_stdout_src = Amanda::MainLoop::fd_source($rdr,
+                                               $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
+    my $psql_stderr_src = Amanda::MainLoop::fd_source($err,
+                                               $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
+    $psql_stdout_src->set_callback(sub {
+       my $line = <$rdr>;
+       if (!defined $line) {
+           $file_to_close--;
+           $psql_stdout_src->remove();
+           Amanda::MainLoop::quit() if $file_to_close == 0;
+           return;
+       }
+       chomp $line;
+       return if $line =~ /^\s*$/;
+       debug("psql stdout: $line");
+       if ($cmd =~ /pg_xlogfile_name_offset/) {
+           return if $line =~ /file_name/;
+           return if $line =~ /------/;
+           return if $line =~ /\(1 row\)/;
+           if ($line =~ /^ ($_WAL_FILE_PAT)/) {
+               $self->{'switch_xlog_filename'} = $1;
+               return;
+           }
+       }
+       if ($line =~ /NOTICE: pg_stop_backup complete, all required WAL segments have been archived/) {
+       } else {
+           $self->print_to_server("psql stdout: $line",
+                                  $Amanda::Script_App::GOOD);
+       }
+    });
+    $psql_stderr_src->set_callback(sub {
+       my $line = <$err>;
+       if (!defined $line) {
+           $file_to_close--;
+           $psql_stderr_src->remove();
+           Amanda::MainLoop::quit() if $file_to_close == 0;
+           return;
+       }
+       chomp $line;
+       debug("psql stderr: $line");
+       if ($line =~ /NOTICE: pg_stop_backup complete, all required WAL segments have been archived/) {
+       } elsif ($line =~ /could not connect to server/) {
+           $self->print_to_server("psql stderr: $line",
+                                  $Amanda::Script_App::ERROR);
+       } else {
+           $self->print_to_server("psql stderr: $line",
+                                  $Amanda::Script_App::GOOD);
+       }
+    });
+
+    close($wtr);
+    Amanda::MainLoop::run();
+    close($rdr);
+    close($err);
+
+    waitpid $pid, 0;
+    my $status = $?;
 
     $ENV{'PGPASSWORD'} = $orig_pgpassword || '';
     $ENV{'PGPASSFILE'} = $orig_pgpassfile || '';
 
-    return 0 == ($status >>8)
+    return 0 == ($status >> 8)
 }
 
 sub command_selfcheck {
@@ -223,6 +307,11 @@ sub command_selfcheck {
        exit(1);
    };
 
+    $self->print_to_server("disk " . quote_string($self->{args}->{disk}));
+
+    $self->print_to_server("ampgsql version " . $Amanda::Constants::VERSION,
+                          $Amanda::Script_App::GOOD);
+
     for my $k (keys %{$self->{'args'}}) {
         print "OK application property: $k = $self->{'args'}->{$k}\n";
     }
@@ -243,62 +332,116 @@ sub command_selfcheck {
            sub {!(-d $_[0])}, $Amanda::Constants::GNUTAR);
     _check_parent_dirs($Amanda::Constants::GNUTAR);
 
-    _check("TMPDIR $self->{'args'}->{'tmpdir'}",
+    _check("TMPDIR '$self->{'args'}->{'tmpdir'}'",
            "is an acessible directory", "is NOT an acessible directory",
-           sub {-d $_[0] && -r $_[0] && -w $_[0] && -x $_[0]},
+           sub {$_[0] && -d $_[0] && -r $_[0] && -w $_[0] && -x $_[0]},
            $self->{'args'}->{'tmpdir'});
-    _check("STATEDIR $self->{'args'}->{'statedir'}",
+
+    if (exists $self->{'props'}->{'pg-datadir'}) {
+       _check("PG-DATADIR property is",
+              "same as diskdevice", "differrent than diskdevice",
+              sub { $_[0] eq $_[1] },
+              $self->{'props'}->{'pg-datadir'}, $self->{'args'}->{'device'});
+    } else {
+       $self->{'props'}->{'pg-datadir'} = $self->{'args'}->{'device'};
+    }
+
+    _check("PG-DATADIR property", "is set", "is NOT set",
+          sub { $_[0] }, $self->{'props'}->{'pg-datadir'});
+       # note that the backup user need not be able ot read this dir
+
+    _check("STATEDIR '$self->{'args'}->{'statedir'}'",
            "is an acessible directory", "is NOT an acessible directory",
-           sub {-d $_[0] && -r $_[0] && -w $_[0] && -x $_[0]},
+           sub {$_[0] && -d $_[0] && -r $_[0] && -w $_[0] && -x $_[0]},
            $self->{'args'}->{'statedir'});
     _check_parent_dirs($self->{'args'}->{'statedir'});
 
     if ($self->{'args'}->{'device'}) {
+       my $try_connect = 1;
+
         for my $k (keys %{$self->{'props'}}) {
             print "OK client property: $k = $self->{'props'}->{$k}\n";
         }
 
-        _check("PG-ARCHIVEDIR $self->{'props'}->{'pg-archivedir'}",
-               "is a directory", "is NOT a directory",
-               sub {-d $_[0]}, $self->{'props'}->{'pg-archivedir'});
-        _check("PG-ARCHIVEDIR $self->{'props'}->{'pg-archivedir'}",
-               "is readable", "is NOT readable",
-               sub {-r $_[0]}, $self->{'props'}->{'pg-archivedir'});
-        _check("PG-ARCHIVEDIR $self->{'props'}->{'pg-archivedir'}",
-               "is executable", "is NOT executable",
-               sub {-x $_[0]}, $self->{'props'}->{'pg-archivedir'});
-        _check_parent_dirs($self->{'props'}->{'pg-archivedir'});
-        _check("Are both PG-PASSFILE and PG-PASSWORD set?",
-               "No (okay)",
-               "Yes. Please set only one or the other",
-               sub {!($self->{'props'}->{'pg-passfile'} and
-                      $self->{'props'}->{'pg-password'})});
+        if (_check("PG-ARCHIVEDIR property", "is set", "is NOT set",
+               sub { $_[0] }, $self->{'props'}->{'pg-archivedir'})) {
+           _check("PG-ARCHIVEDIR $self->{'props'}->{'pg-archivedir'}",
+                  "is a directory", "is NOT a directory",
+                  sub {-d $_[0]}, $self->{'props'}->{'pg-archivedir'});
+           _check("PG-ARCHIVEDIR $self->{'props'}->{'pg-archivedir'}",
+                  "is readable", "is NOT readable",
+                  sub {-r $_[0]}, $self->{'props'}->{'pg-archivedir'});
+           _check("PG-ARCHIVEDIR $self->{'props'}->{'pg-archivedir'}",
+                  "is executable", "is NOT executable",
+                  sub {-x $_[0]}, $self->{'props'}->{'pg-archivedir'});
+           _check_parent_dirs($self->{'props'}->{'pg-archivedir'});
+       }
+
+       $try_connect &&=
+           _check("Are both PG-PASSFILE and PG-PASSWORD set?",
+                  "No (okay)",
+                  "Yes. Please set only one or the other",
+                  sub {!($self->{'props'}->{'pg-passfile'} and
+                         $self->{'props'}->{'pg-password'})});
+
         if ($self->{'props'}->{'pg-passfile'}) {
-            _check("PG-PASSFILE $self->{'props'}->{'pg-passfile'}",
+           $try_connect &&=
+               _check("PG-PASSFILE $self->{'props'}->{'pg-passfile'}",
                    "has correct permissions", "does not have correct permissions",
                    \&_ok_passfile_perms, $self->{'props'}->{'pg-passfile'});
-            _check_parent_dirs($self->{'props'}->{'pg-passfile'});
-        }
-        _check("PSQL-PATH $self->{'props'}->{'psql-path'}",
-               "is executable", "is NOT executable",
-               sub {-x $_[0]}, $self->{'props'}->{'psql-path'});
-        _check("PSQL-PATH $self->{'props'}->{'psql-path'}",
-               "is not a directory (okay)", "is a directory (it shouldn't be)",
-               sub {!(-d $_[0])}, $self->{'props'}->{'psql-path'});
-        _check_parent_dirs($self->{'props'}->{'psql-path'});
-        _check("Connecting to database server", "succeeded", "failed",
-               \&_run_psql_command, $self, '');
-        
-        my $label = "$self->{'label-prefix'}-selfcheck-" . time();
-        if (_check("Call pg_start_backup", "succeeded",
-                   "failed (is another backup running?)",
-                   \&_run_psql_command, $self, "SELECT pg_start_backup('$label')")
-            and _check("Call pg_stop_backup", "succeeded", "failed",
-                       \&_run_psql_command, $self, "SELECT pg_stop_backup()")) {
-
-            _check("Get info from .backup file", "succeeded", "failed",
-                   sub {my ($start, $end) = _get_backup_info($self, $label); $start and $end});
+           $try_connect &&=
+               _check_parent_dirs($self->{'props'}->{'pg-passfile'});
         }
+
+        if (_check("PSQL-PATH property", "is set", "is NOT set and psql is not in \$PATH",
+               sub { $_[0] }, $self->{'props'}->{'psql-path'})) {
+           $try_connect &&=
+               _check("PSQL-PATH $self->{'props'}->{'psql-path'}",
+                      "is executable", "is NOT executable",
+                      sub {-x $_[0]}, $self->{'props'}->{'psql-path'});
+           $try_connect &&=
+               _check("PSQL-PATH $self->{'props'}->{'psql-path'}",
+                      "is not a directory (okay)", "is a directory (it shouldn't be)",
+                      sub {!(-d $_[0])}, $self->{'props'}->{'psql-path'});
+           $try_connect &&=
+               _check_parent_dirs($self->{'props'}->{'psql-path'});
+       } else {
+           $try_connect = 0;
+       }
+
+       if ($try_connect) {
+           my @pv = `$self->{'props'}->{'psql-path'} --version`;
+           if ($? >> 8 == 0) {
+               $pv[0] =~ /^[^0-9]*([0-9.]*)[^0-9]*$/;
+               my $pv = $1;
+               $self->print_to_server("ampgsql psql-version $pv",
+                                      $Amanda::Script_App::GOOD);
+           } else {
+               $self->print_to_server(
+               "[Can't get " . $self->{'props'}->{'psql-path'} . " version]\n",
+               $Amanda::Script_App::ERROR);
+           }
+       }
+
+       if ($try_connect) {
+           $try_connect &&=
+               _check("Connecting to database server", "succeeded", "failed",
+                  \&_run_psql_command, $self, '');
+       }
+
+       {
+           my @gv = `$self->{'args'}->{'gnutar-path'} --version`;
+           if ($? >> 8 == 0) {
+               $gv[0] =~ /^[^0-9]*([0-9.]*)[^0-9]*$/;
+               my $gv = $1;
+               $self->print_to_server("ampgsql gtar-version $gv",
+                                      $Amanda::Script_App::GOOD);
+           } else {
+               $self->print_to_server(
+               "[Can't get " . $self->{'props'}->{'gnutar-path'} . " version]\n",
+               $Amanda::Script_App::ERROR);
+           }
+       }
     }
 }
 
@@ -306,7 +449,9 @@ sub _state_filename {
     my ($self, $level) = @_;
 
     my @parts = ("ampgsql", hexencode($self->{'args'}->{'host'}), hexencode($self->{'args'}->{'disk'}), $level);
-    $self->{'args'}->{'statedir'} . '/'  . join("-", @parts);
+    my $statefile = $self->{'args'}->{'statedir'} . '/'  . join("-", @parts);
+    debug("statefile: $statefile");
+    return $statefile;
 }
 
 sub _write_state_file {
@@ -324,9 +469,11 @@ sub _write_state_file {
 
 sub _get_prev_state {
     my $self = shift @_;
+    my $initial_level = shift;
+    $initial_level = $self->{'args'}->{'level'} - 1 if !defined $initial_level;
 
     my $end_wal;
-    for (my $level = $self->{'args'}->{'level'} - 1; $level >= 0; $level--) {
+    for (my $level = $initial_level; $level >= 0; $level--) {
         my $fn = _state_filename($self, $level);
         debug("reading state file: $fn");
         my $h = new IO::File($fn, "r");
@@ -349,6 +496,15 @@ sub _get_prev_state {
     $end_wal;
 }
 
+sub _make_dummy_dir_base {
+    my ($self) = @_;
+
+   my $dummydir = "$self->{'args'}->{'tmpdir'}/ampgsql-dummy-$$";
+   mkpath("$dummydir/$_ARCHIVE_DIR_RESTORE");
+
+   return $dummydir;
+}
+
 sub _make_dummy_dir {
     my ($self) = @_;
 
@@ -363,9 +519,10 @@ sub _make_dummy_dir {
 sub _run_tar_totals {
     my ($self, @other_args) = @_;
 
-    my @cmd = ($self->{'runtar'}, $self->{'args'}->{'config'},
+    my @cmd;
+    @cmd = ($self->{'runtar'}, $self->{'args'}->{'config'},
         $Amanda::Constants::GNUTAR, '--create', '--totals', @other_args);
-    debug("running " . join(" ", @cmd));
+    debug("running: " . join(" ", @cmd));
 
     local (*TAR_IN, *TAR_OUT, *TAR_ERR);
     open TAR_OUT, ">&", $self->{'out_h'};
@@ -381,7 +538,7 @@ sub _run_tar_totals {
             $size = $1;
         } else {
            chomp $l;
-           #$self->print_to_server($l, $Amanda::Script_App::ERROR);
+           $self->print_to_server($l, $Amanda::Script_App::ERROR);
            debug("TAR_ERR: $l");
        }
     }
@@ -448,7 +605,7 @@ sub _get_backup_info {
               # this works!)
                local *TAROUT;
                my $conf = $self->{'args'}->{'config'} || 'NOCONFIG';
-               my $cmd = "$self->{'runtar'} $conf $Amanda::Constants::GNUTAR --create --directory $self->{'props'}->{'pg-archivedir'} $fname | $Amanda::Constants::GNUTAR --extract --to-stdout";
+               my $cmd = "$self->{'runtar'} $conf $Amanda::Constants::GNUTAR --create --file - --directory $self->{'props'}->{'pg-archivedir'} $fname | $Amanda::Constants::GNUTAR --file - --extract --to-stdout";
                debug("running: $cmd");
                open(TAROUT, "$cmd |");
                my ($start, $end, $lab);
@@ -462,6 +619,7 @@ sub _get_backup_info {
                        $lab = $1;
                    }
                }
+              close TAROUT;
                if ($lab and $lab eq $label) {
                    $start_wal = $start;
                    $end_wal = $end;
@@ -477,7 +635,12 @@ sub _get_backup_info {
           debug("$bfile named WALs $start_wal .. $end_wal");
 
            # try to cleanup a bit, although this may fail and that's ok
-           unlink("$self->{'props'}->{'pg-archivedir'}/$bfile");
+          my $filename = "$self->{'props'}->{'pg-archivedir'}/$bfile";
+           if (unlink($filename) == 0) {
+               debug("Failed to unlink '$filename': $!");
+               $self->print_to_server("Failed to unlink '$filename': $!",
+                                     $Amanda::Script_App::ERROR);
+          }
            last;
        }
        sleep(1);
@@ -537,11 +700,14 @@ sub _wait_for_wal {
     my $count = 0; # try at least 4 cycles
     my $stoptime = time() + $maxwait;
     while ($maxwait == 0 || time < $stoptime || $count++ < 4) {
-       return if -f "$archive_dir/$wal";
-       
+       if (-f "$archive_dir/$wal") {
+           sleep(1);
+           return;
+       }
+
        # for versions 8.0 or 8.1, the only way to "force" a WAL archive is to write
        # garbage to the database.
-       if ($pg_version < 802000) {
+       if ($pg_version < 80200) {
            $self->_write_garbage_to_db();
        } else {
            sleep(1);
@@ -557,30 +723,14 @@ sub _base_backup {
    debug("running _base_backup");
 
    my $label = "$self->{'label-prefix'}-" . time();
-   my $tmp = "$self->{'args'}->{'tmpdir'}/$label";
 
    -d $self->{'props'}->{'pg-archivedir'} or
        die("WAL file archive directory does not exist (or is not a directory)");
 
-   # try to protect what we create
-   my $old_umask = umask();
-   umask(077);
-
-   my $cleanup = sub {
-       umask($old_umask);
-       eval {rmtree($tmp); 1}
-   };
-   my $old_die = $self->{'die_cb'};
-   $self->{'die_cb'} = sub {
-       my $msg = shift @_;
-       $cleanup->();
-       $old_die->($msg);
-   };
-   eval {rmtree($tmp,{'keep_root' => 1}); 1} or $self->{'die_cb'}->("Failed to clear tmp directory: $@");
-   eval {mkpath($tmp, 0, 0700); 1} or $self->{'die_cb'}->("Failed to create tmp directory: $@");
-
-   _run_psql_command($self, "SELECT pg_start_backup('$label')") or
-       $self->{'die_cb'}->("Failed to call pg_start_backup");
+   if ($self->{'action'} eq 'backup') {
+       _run_psql_command($self, "SELECT pg_start_backup('$label')") or
+           $self->{'die_cb'}->("Failed to call pg_start_backup");
+   }
 
    # tar data dir, using symlink to prefix
    # XXX: tablespaces and their symlinks?
@@ -588,40 +738,54 @@ sub _base_backup {
    my $old_die_cb = $self->{'die_cb'};
    $self->{'die_cb'} = sub {
        my $msg = shift @_;
-       unless(_run_psql_command($self, "SELECT pg_stop_backup()")) {
-           $msg .= " and failed to call pg_stop_backup";
+       if ($self->{'action'} eq 'backup') {
+           unless(_run_psql_command($self, "SELECT pg_stop_backup()")) {
+               $msg .= " and failed to call pg_stop_backup";
+          }
        }
        $old_die_cb->($msg);
    };
-   _run_tar_totals($self, '--file', "$tmp/$_DATA_DIR_TAR",
+   my $size = _run_tar_totals($self, '--file', "-",
        '--directory', $self->{'props'}->{'pg-datadir'},
        '--exclude', 'postmaster.pid',
        '--exclude', 'pg_xlog/*', # contains WAL files; will be handled below
+       '--transform', "s,^,$_DATA_DIR_RESTORE/,S",
        ".");
    $self->{'die_cb'} = $old_die_cb;
 
-   unless (_run_psql_command($self, "SELECT pg_stop_backup()")) {
-       $self->{'die_cb'}->("Failed to call pg_stop_backup");
+   if ($self->{'action'} eq 'backup') {
+       unless (_run_psql_command($self, "SELECT pg_stop_backup()")) {
+           $self->{'die_cb'}->("Failed to call pg_stop_backup");
+       }
    }
 
    # determine WAL files and append and create their tar file
-   my ($start_wal, $end_wal) = _get_backup_info($self, $label);
-
-   ($start_wal and $end_wal)
-       or $self->{'die_cb'}->("A .backup file was never found in the archive "
-                           . "dir $self->{'props'}->{'pg-archivedir'}");
-
-   $self->_wait_for_wal($end_wal);
+   my $start_wal;
+   my $end_wal;
+
+   if ($self->{'action'} eq 'backup') {
+       ($start_wal, $end_wal)  = _get_backup_info($self, $label);
+       ($start_wal and $end_wal)
+               or $self->{'die_cb'}->("A .backup file was never found in the archive "
+                                   . "dir $self->{'props'}->{'pg-archivedir'}");
+       $self->_wait_for_wal($end_wal);
+   } else {
+       $start_wal = undef;
+       $end_wal = _get_prev_state($self, 0);
+   }
 
    # now grab all of the WAL files, *inclusive* of $start_wal
    my @wal_files;
    my $adir = new IO::Dir($self->{'props'}->{'pg-archivedir'});
    while (defined(my $fname = $adir->read())) {
        if ($fname =~ /^$_WAL_FILE_PAT$/) {
-           if (($fname ge $start_wal) and ($fname le $end_wal)) {
+           if (!defined $end_wal ||
+              (!defined $start_wal and ($fname le $end_wal)) ||
+              (defined $start_wal and ($fname ge $start_wal) and
+               ($fname le $end_wal))) {
                push @wal_files, $fname;
                debug("will store: $fname");
-           } elsif ($fname lt $start_wal) {
+           } elsif (defined $start_wal and $fname lt $start_wal) {
                $self->{'unlink_cb'}->("$self->{'props'}->{'pg-archivedir'}/$fname");
            }
        }
@@ -629,22 +793,19 @@ sub _base_backup {
    $adir->close();
 
    if (@wal_files) {
-       _run_tar_totals($self, '--file', "$tmp/$_ARCHIVE_DIR_TAR",
-          '--directory', $self->{'props'}->{'pg-archivedir'}, @wal_files);
+       $size += _run_tar_totals($self, '--file', "-",
+          '--directory', $self->{'props'}->{'pg-archivedir'},
+          '--transform', "s,^,$_ARCHIVE_DIR_RESTORE/,S",
+          @wal_files);
    } else {
-       my $dummydir = $self->_make_dummy_dir();
-       $self->{'done_cb'}->(_run_tar_totals($self,
-           '--directory', $dummydir, "empty-incremental"));
+       my $dummydir = $self->_make_dummy_dir_base();
+       $self->{'done_cb'}->(_run_tar_totals($self, '--file', '-',
+           '--directory', $dummydir, "$_ARCHIVE_DIR_RESTORE"));
        rmtree($dummydir);
    }
 
-   # create the final tar file
-   my $size = _run_tar_totals($self, '--directory', $tmp,
-       $_ARCHIVE_DIR_TAR, $_DATA_DIR_TAR);
-
    $self->{'state_cb'}->($self, $end_wal);
 
-   $cleanup->();
    $self->{'done_cb'}->($size);
 }
 
@@ -653,6 +814,13 @@ sub _incr_backup {
 
    debug("running _incr_backup");
 
+   if ($self->{'action'} eq 'backup') {
+      _run_psql_command($self, "SELECT file_name from pg_xlogfile_name_offset(pg_switch_xlog())");
+      if (defined($self->{'switch_xlog_filename'})) {
+        $self->_wait_for_wal($self->{'switch_xlog_filename'});
+      }
+   }
+
    my $end_wal = _get_prev_state($self);
    if ($end_wal) {
        debug("previously ended at: $end_wal");
@@ -676,11 +844,11 @@ sub _incr_backup {
    $self->{'state_cb'}->($self, $max_wal ? $max_wal : $end_wal);
 
    if (@wal_files) {
-       $self->{'done_cb'}->(_run_tar_totals($self,
+       $self->{'done_cb'}->(_run_tar_totals($self, '--file', '-',
            '--directory', $self->{'props'}->{'pg-archivedir'}, @wal_files));
    } else {
        my $dummydir = $self->_make_dummy_dir();
-       $self->{'done_cb'}->(_run_tar_totals($self,
+       $self->{'done_cb'}->(_run_tar_totals($self, '--file', '-',
            '--directory', $dummydir, "empty-incremental"));
        rmtree($dummydir);
    }
@@ -726,7 +894,11 @@ sub command_backup {
        $self->{'unlink_cb'} = sub {
            my $filename = shift @_;
           debug("unlinking WAL file $filename");
-           unlink($filename);
+           if (unlink($filename) == 0) {
+               debug("Failed to unlink '$filename': $!");
+               $self->print_to_server("Failed to unlink '$filename': $!",
+                                      $Amanda::Script_App::ERROR);
+           }
        };
    } else {
        $self->{'unlink_cb'} = sub {
@@ -742,53 +914,82 @@ sub command_backup {
 }
 
 sub command_restore {
-   my $self = shift;
+    my $self = shift;
 
-   chdir(Amanda::Util::get_original_cwd());
-   if (defined $self->{'args'}->{directory}) {
-      if (!-d $self->{'args'}->{directory}) {
-        $self->print_to_server_and_die("Directory $self->{directory}: $!",
-                                       $Amanda::Script_App::ERROR);
-      }
-      if (!-w $self->{'args'}->{directory}) {
-        $self->print_to_server_and_die("Directory $self->{directory}: $!",
-                                       $Amanda::Script_App::ERROR);
-      }
-      chdir($self->{'args'}->{directory});
-   }
-   my $cur_dir = POSIX::getcwd();
+    chdir(Amanda::Util::get_original_cwd());
+    if (defined $self->{'args'}->{directory}) {
+       if (!-d $self->{'args'}->{directory}) {
+           $self->print_to_server_and_die("Directory $self->{directory}: $!",
+                                          $Amanda::Script_App::ERROR);
+       }
+       if (!-w $self->{'args'}->{directory}) {
+           $self->print_to_server_and_die("Directory $self->{directory}: $!",
+                                          $Amanda::Script_App::ERROR);
+       }
+       chdir($self->{'args'}->{directory});
+    }
+    my $cur_dir = POSIX::getcwd();
 
-   if (!-d $_ARCHIVE_DIR_RESTORE) {
-       mkdir($_ARCHIVE_DIR_RESTORE) or die("could not create archive WAL directory: $!");
-   }
-   my $status;
-   if ($self->{'args'}->{'level'} > 0) {
-       debug("extracting incremental backup to $cur_dir/$_ARCHIVE_DIR_RESTORE");
-       $status = system($self->{'args'}->{'gnutar-path'}, '--extract',
-          '--exclude', 'empty-incremental',
-           '--directory', $_ARCHIVE_DIR_RESTORE) >> 8;
-       (0 == $status) or die("Failed to extract level $self->{'args'}->{'level'} backup (exit status: $status)");
-   } else {
-       debug("extracting base of full backup");
-       if (!-d $_DATA_DIR_RESTORE) {
-           mkdir($_DATA_DIR_RESTORE) or die("could not create archive WAL directory: $!");
-       }
-       $status = system($self->{'args'}->{'gnutar-path'}, '--extract') >> 8;
-       (0 == $status) or die("Failed to extract base backup (exit status: $status)");
-
-       debug("extracting archive dir to $cur_dir/$_ARCHIVE_DIR_RESTORE");
-       $status = system($self->{'args'}->{'gnutar-path'}, '--extract',
-         '--exclude', 'empty-incremental',
-          '--file', $_ARCHIVE_DIR_TAR, '--directory', $_ARCHIVE_DIR_RESTORE) >> 8;
-       (0 == $status) or die("Failed to extract archived WAL files from base backup (exit status: $status)");
-       unlink($_ARCHIVE_DIR_TAR);
-
-       debug("extracting data dir to $cur_dir/$_DATA_DIR_RESTORE");
-       $status = system($self->{'args'}->{'gnutar-path'}, '--extract',
-          '--file', $_DATA_DIR_TAR, '--directory', $_DATA_DIR_RESTORE) >> 8;
-       (0 == $status) or die("Failed to extract data directory from base backup (exit status: $status)");
-       unlink($_DATA_DIR_TAR);
-   }
+    if (!-d $_ARCHIVE_DIR_RESTORE) {
+       mkdir($_ARCHIVE_DIR_RESTORE) or die("could not create archive WAL directory: $!");
+    }
+    my $status;
+    if ($self->{'args'}->{'level'} > 0) {
+       debug("extracting incremental backup to $cur_dir/$_ARCHIVE_DIR_RESTORE");
+       $status = system($self->{'args'}->{'gnutar-path'},
+               '--extract',
+               '--file', '-',
+               '--ignore-zeros',
+               '--exclude', 'empty-incremental',
+               '--directory', $_ARCHIVE_DIR_RESTORE) >> 8;
+       (0 == $status) or die("Failed to extract level $self->{'args'}->{'level'} backup (exit status: $status)");
+    } else {
+       debug("extracting base of full backup to $cur_dir/$_DATA_DIR_RESTORE");
+       debug("extracting archive dir to $cur_dir/$_ARCHIVE_DIR_RESTORE");
+       if (!-d $_DATA_DIR_RESTORE) {
+           mkdir($_DATA_DIR_RESTORE) or die("could not create archive WAL directory: $!");
+       }
+       my @cmd = ($self->{'args'}->{'gnutar-path'}, '--extract',
+               '--file', '-',
+               '--ignore-zero',
+               '--transform', "s,^DATA/,$_DATA_DIR_RESTORE/,S",
+               '--transform', "s,^WAL/,$_ARCHIVE_DIR_RESTORE/,S");
+       debug("run: " . join ' ',@cmd);
+       $status = system(@cmd) >> 8;
+       (0 == $status) or die("Failed to extract base backup (exit status: $status)");
+
+       if (-f $_ARCHIVE_DIR_TAR) {
+           debug("extracting archive dir to $cur_dir/$_ARCHIVE_DIR_RESTORE");
+           my @cmd = ($self->{'args'}->{'gnutar-path'}, '--extract',
+               '--exclude', 'empty-incremental',
+               '--file', $_ARCHIVE_DIR_TAR, '--directory',
+               $_ARCHIVE_DIR_RESTORE);
+           debug("run: " . join ' ',@cmd);
+           $status = system(@cmd) >> 8;
+           (0 == $status) or die("Failed to extract archived WAL files from base backup (exit status: $status)");
+           if (unlink($_ARCHIVE_DIR_TAR) == 0) {
+               debug("Failed to unlink '$_ARCHIVE_DIR_TAR': $!");
+               $self->print_to_server(
+                               "Failed to unlink '$_ARCHIVE_DIR_TAR': $!",
+                               $Amanda::Script_App::ERROR);
+           }
+       }
+
+       if (-f $_DATA_DIR_TAR) {
+           debug("extracting data dir to $cur_dir/$_DATA_DIR_RESTORE");
+           my @cmd = ($self->{'args'}->{'gnutar-path'}, '--extract',
+               '--file', $_DATA_DIR_TAR,
+               '--directory', $_DATA_DIR_RESTORE);
+           debug("run: " . join ' ',@cmd);
+           $status = system(@cmd) >> 8;
+           (0 == $status) or die("Failed to extract data directory from base backup (exit status: $status)");
+           if (unlink($_DATA_DIR_TAR) == 0) {
+               debug("Failed to unlink '$_DATA_DIR_TAR': $!");
+               $self->print_to_server("Failed to unlink '$_DATA_DIR_TAR': $!",
+                               $Amanda::Script_App::ERROR);
+           }
+       }
+    }
 }
 
 sub command_validate {
@@ -807,7 +1008,7 @@ sub command_validate {
       return $self->default_validate();
    }
 
-   my(@cmd) = ($self->{'args'}->{'gnutar-path'}, "-tf", "-");
+   my(@cmd) = ($self->{'args'}->{'gnutar-path'}, "--ignore-zeros", "-tf", "-");
    debug("cmd:" . join(" ", @cmd));
    my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
       $self->print_to_server_and_die("Unable to run @cmd",
@@ -830,9 +1031,11 @@ EOF
 }
 
 my $opts = {};
+my $opt_version;
 
 GetOptions(
     $opts,
+    'version' => \$opt_version,
     'config=s',
     'host=s',
     'disk=s',
@@ -850,8 +1053,22 @@ GetOptions(
     'statedir=s',
     'tmpdir=s',
     'gnutar-path=s',
+    'cleanupwal=s',
+    'archivedir=s',
+    'db=s',
+    'host=s',
+    'max-wal-wait=s',
+    'passfile=s',
+    'port=s',
+    'user=s',
+    'psql-path=s'
 ) or usage();
 
+if (defined $opt_version) {
+    print "ampgsql-" . $Amanda::Constants::VERSION , "\n";
+    exit(0);
+}
+
 my $application = Amanda::Application::ampgsql->new($opts);
 
 $application->do($ARGV[0]);