move to modern source package format
[debian/amanda] / application-src / ampgsql.pl
index 1a20a242e5f2dc30a0d9f278dbb853e71162ea5f..5c930ed323eaf099a202112a8945125de7c64474 100644 (file)
@@ -39,6 +39,7 @@ 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::MainLoop qw( :GIOCondition );
 
 my $_DATA_DIR_TAR = "data_dir.tar";
 my $_ARCHIVE_DIR_TAR = "archive_dir.tar";
@@ -89,10 +90,10 @@ 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;
@@ -161,6 +162,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 +172,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 {
@@ -204,12 +209,62 @@ sub _run_psql_command {
 
     push @cmd, '--quiet', '--output', '/dev/null', '--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;
+       debug("psql stdout: $line");
+       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/) {
+       } 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 {
@@ -243,62 +298,101 @@ 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'});
+           $try_connect &&=
+               _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, '');
+
+        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) {
+           $try_connect &&=
+               _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});
-        }
+       if ($try_connect) {
+           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});
+           }
+       }
     }
 }
 
@@ -381,7 +475,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 +542,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);
@@ -633,13 +727,13 @@ sub _base_backup {
           '--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);
    }
 
    # create the final tar file
-   my $size = _run_tar_totals($self, '--directory', $tmp,
+   my $size = _run_tar_totals($self, '--directory', $tmp, '--file', '-',
        $_ARCHIVE_DIR_TAR, $_DATA_DIR_TAR);
 
    $self->{'state_cb'}->($self, $end_wal);
@@ -676,11 +770,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);
    }
@@ -765,6 +859,8 @@ sub command_restore {
    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)");
@@ -773,7 +869,7 @@ sub command_restore {
        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;
+       $status = system($self->{'args'}->{'gnutar-path'}, '--extract', '--file', '-',) >> 8;
        (0 == $status) or die("Failed to extract base backup (exit status: $status)");
 
        debug("extracting archive dir to $cur_dir/$_ARCHIVE_DIR_RESTORE");
@@ -807,7 +903,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",