diff options
Diffstat (limited to '')
-rwxr-xr-x | pg_ctlcluster | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/pg_ctlcluster b/pg_ctlcluster new file mode 100755 index 0000000..d296cfe --- /dev/null +++ b/pg_ctlcluster @@ -0,0 +1,658 @@ +#!/usr/bin/perl -wT + +# multiversion/cluster aware pg_ctl wrapper; this also supplies the correct +# configuration parameters to 'start', and makes sure that postgres really +# stops on 'stop'. +# +# (C) 2005-2009 Martin Pitt <mpitt@debian.org> +# (C) 2009 Cyril Bouthors <cyril@bouthors.org> +# (C) 2013-2019 Christoph Berg <myon@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +use strict; +use warnings; +use Getopt::Long; +use POSIX qw/setsid dup2 :sys_wait_h/; +use PgCommon; +use Fcntl qw(SEEK_SET O_RDWR O_CREAT O_EXCL); +use POSIX qw(lchown); + +my ($version, $cluster, $pg_ctl, $force); +my (@postgres_auxoptions, @pg_ctl_opts_from_cli); +my (%postgresql_conf, %info); +my $mode = 'fast'; # default shutdown mode +my $foreground = 0; # don't daemonize, use postgres instead of pg_ctl +my $stdlog = 0; # when run in foreground, still log to the default logfile + +# untaint environment +$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin'; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; +chdir '/'; + +# If a pid file is already present, delete it if it is stale/invalid, or exit +# with a notice if it belongs to an already running postgres. +sub start_check_pid_file { + my $pidfile = $info{'pgdata'}.'/postmaster.pid'; + if (PgCommon::check_pidfile_running $pidfile) { + print "Cluster is already running.\n"; + exit 2; + } + + # Remove invalid or stale PID file + if (-e $pidfile) { + unlink $pidfile; + print "Removed stale pid file.\n"; + } +} + +# Check if a pid file is not present or it is invalid. If so, clean up/abort. +sub stop_check_pid_file { + my $pidfile = $info{'pgdata'}.'/postmaster.pid'; + my $pid = read_pidfile $pidfile; + return if (defined $pid && PgCommon::check_pidfile_running $pidfile); + if ($info{'running'}) { + error 'pid file is invalid, please manually kill the stale server process.'; + } + + # Remove invalid or stale PID file + if (-e $pidfile) { + unlink $pidfile; + print "Removed stale pid file.\n"; + } + print "Cluster is not running.\n"; + exit 2; +} + +# check if a cluster reliably connects or fails +# Arguments: <version> <cluster> <port> <socket dir> +sub cluster_port_ready { + my ($v, $c, $p, $sd) = @_; + my $psql = get_program_path 'psql', $v; + error 'cluster_port_ready: could not find psql binary' unless $psql; + my $n = 0; + my $result = 0; + + + # probe until we get three successful or failed connections in a row + $ENV{'PGPASSWORD'} = 'foo'; # prevent hangs if superuser cannot connect without password + my $out; + while ($n < ($result ? 10 : 3)) { + select undef, undef, undef, 0.5; + $out = `$psql -h '$sd' --port $p -Xl 2>&1 >/dev/null`; + + if ($? == $result) { + $n++; + } else { + $n = 0; + } + $result = $?; + } + + if ($out =~ 'FATAL:') { + print STDERR "Warning: connection to the database failed, disabling startup checks:\n$out\n"; + return cluster_port_running $v, $c, $p; + } + return !$result; +} + +sub start { + start_check_pid_file; + + # check conflicting port + if (cluster_port_running $version, $cluster, $info{'port'}) { + my $sockdir = get_cluster_socketdir $version, $cluster; + error("Port conflict: another instance is already running on $sockdir with port $info{'port'}"); + } + + # prepare environment (empty except for content of 'environment', and LANG) + %ENV = read_cluster_conf_file $version, $cluster, 'environment'; + # set LANG so non-ascii chars in the server log are not replaced by '?' (affected are non-session contexts) + unless (exists $ENV{LANG}) { + my $lc_messages = PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', 'lc_messages'; + $ENV{LANG} = $lc_messages if $lc_messages; + } + # 9.5 configures OOM killer using env vars + if ($version >= 9.5) { + $ENV{PG_OOM_ADJUST_FILE} = "/proc/self/oom_score_adj" unless (exists $ENV{PG_OOM_ADJUST_FILE}); + # PG_OOM_ADJUST_VALUE defaults to 0, but can be overridden here + } + + my $postgres_opts = ''; + my $usd = $version >= 9.3 ? 'unix_socket_directories' : 'unix_socket_directory'; + if (!(PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', $usd)) { + $postgres_opts .= "-c $usd=\"$info{'socketdir'}\""; + } + + my $cdir = $info{'configdir'}; + $postgres_opts .= " -c config_file=\"$cdir/postgresql.conf\""; + if (!(PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', 'hba_file')) { + $postgres_opts .= " -c hba_file=\"$cdir/pg_hba.conf\""; + } + if (!(PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', 'ident_file')) { + $postgres_opts .= " -c ident_file=\"$cdir/pg_ident.conf\""; + } + + if ((-d '/var/run/postgresql') && !defined (PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', 'external_pid_file')) { + # check whether /var/run/postgresql/ is writable as the cluster owner + my $vrp_writable; + if ($> == 0) { + change_ugid $info{'owneruid'}, $info{'ownergid'}; + $vrp_writable = -w '/var/run/postgresql'; + $< = $> = 0; + $( = $) = 0; + } else { + $vrp_writable = -w '/var/run/postgresql'; + } + if ($vrp_writable) { + $postgres_opts .= " -c external_pid_file=\"/var/run/postgresql/$version-$cluster.pid\""; + } + } + + $postgres_opts .= ' ' . (join ' ', @postgres_auxoptions); + ($postgres_opts) = $postgres_opts =~ /(.*)/; # untaint + + if ($foreground) { + if ($stdlog and $info{'logfile'}) { + my $fd = POSIX::open($info{logfile}, POSIX::O_WRONLY|POSIX::O_APPEND|POSIX::O_CREAT) or error "Could not open logfile $info{logfile}"; + dup2($fd, 1); + dup2($fd, 2); + } + my $postgres = get_program_path 'postgres', $version; + error "Could not find postgres executable for version $version" unless ($postgres); + exec '/bin/sh', '-c', "exec $postgres $postgres_opts" or + error "Executing $postgres failed: $!" + } + + # only supply or default logfile path when none is given explicitly in + # postgresql.conf + my @options = ($pg_ctl, 'start', '-D', $info{'pgdata'}); + my $logsize = 0; + if ($info{'logfile'}) { + push @options, ('-l', $info{'logfile'}); + # remember current size of the log + $logsize = (stat $info{'logfile'})[7] || 0; # ignore stat errors + } + + push @options, @pg_ctl_opts_from_cli if @pg_ctl_opts_from_cli; + + my %pg_ctl_opts_from_file = read_cluster_conf_file $version, $cluster, 'pg_ctl.conf'; + push @options, split(' ', $pg_ctl_opts_from_file{'pg_ctl_options'}) + if defined $pg_ctl_opts_from_file{'pg_ctl_options'} and $pg_ctl_opts_from_file{'pg_ctl_options'} ne ''; + + push @options, ('-s', '-o', $postgres_opts); + + if (fork) { + wait; + if ($?) { + my $exit = $? >> 8; + print STDERR "Error: $pg_ctl @options exited with status $exit: $!\n"; + my $currlogsize = (stat $info{'logfile'})[7] if $info{'logfile'} && -r $info{'logfile'}; + if ($currlogsize) { + open LOG, $info{'logfile'} or + error "Could not open log file " . $info{'logfile'}; + seek LOG, $logsize, SEEK_SET; + print STDERR $_ while <LOG>; + } + exit $exit; + } + } else { + setsid or error "could not start session: $!"; + if ($info{'logfile'}) { + my $fd = POSIX::open($info{'logfile'}, POSIX::O_WRONLY|POSIX::O_APPEND|POSIX::O_CREAT) or error "Could not open logfile $info{'logfile'}"; + dup2($fd, 1); + dup2($fd, 2); + } + exec $pg_ctl @options or error "could not exec $pg_ctl @options: $!"; + } + + # wait a bit until the socket exists + my $success = 0; + my $currlogsize = 0; + my $pidfile = $info{'pgdata'}.'/postmaster.pid'; + for (my $attempt = 0; $attempt < 60; $attempt++) { + select (undef, undef, undef, 0.5); + $currlogsize = (stat $info{'logfile'})[7] if $info{'logfile'} && -r $info{'logfile'}; + if (cluster_port_running $version, $cluster, $info{'port'}) { + $success = 1; + last; + } + + # if postgres wrote something, but the process does not exist any + # more, there must be a problem and we can stop immediately + last if ($currlogsize > $logsize && !PgCommon::check_pidfile_running $pidfile); + } + + # OK, the server runs, now wait until it stabilized + if ($success) { + $success = cluster_port_ready $version, $cluster, $info{'port'}, $info{'socketdir'}; + } + + if (!$success) { + if ($currlogsize) { + print STDERR "The PostgreSQL server failed to start. Please check the log output:\n"; + open LOG, $info{'logfile'} or + error "Could not open log file " . $info{'logfile'}; + seek LOG, $logsize, SEEK_SET; + print STDERR $_ while <LOG>; + } else { + print STDERR "The PostgreSQL server failed to start. Please check the log output.\n"; + } + exit 1; + } + + return 0; +} + +sub stop { + stop_check_pid_file; + my $result = 1; + + my @options = ('stop', '-s', '-w', '-D', $info{'pgdata'}); + + push @options, @pg_ctl_opts_from_cli if @pg_ctl_opts_from_cli; + + my %pg_ctl_opts_from_file = read_cluster_conf_file $version, $cluster, 'pg_ctl.conf'; + push @options, split(' ', $pg_ctl_opts_from_file{'pg_ctl_options'}) + if defined $pg_ctl_opts_from_file{'pg_ctl_options'} and $pg_ctl_opts_from_file{'pg_ctl_options'} ne ''; + + if (!fork()) { + close STDOUT; + exec $pg_ctl, @options, '-m', ($force ? 'fast' : $mode); + } else { + wait; + $result = $? >> 8; + } + + # try harder if forced and server hasn't stopped yet + if ($force and -f $info{'pgdata'}.'/postmaster.pid') { + print "(does not shutdown gracefully, now stopping immediately)\n"; + $result = system $pg_ctl, @options, '-m', 'immediate'; + } + + # external_pid_file files are currently not removed by postgres itself + if ($result == 0) { + unlink "/var/run/postgresql/$version-$cluster.pid"; + } + + return $result; +} + +sub restart { + my $result; + + if ($info{'running'}) { + $result = stop; + return $result if $result; + } + return start; +} + +sub reload { + exec $pg_ctl, '-D', $info{'pgdata'}, '-s', @pg_ctl_opts_from_cli, 'reload'; +} + +sub status { + exec $pg_ctl, '-D', $info{'pgdata'}, 'status'; +} + +sub promote { + exec $pg_ctl, '-D', $info{'pgdata'}, '-s', @pg_ctl_opts_from_cli, 'promote'; +} + +# +# main +# + +my ($skip_systemctl_redirect, $bindir); + +exit 1 unless GetOptions ('o|options=s' => \@postgres_auxoptions, + 'f|force' => \$force, + 'm|mode=s' => \$mode, + 'foreground' => \$foreground, + 'skip-systemctl-redirect' => \$skip_systemctl_redirect, + 'stdlog' => \$stdlog, + 'bindir=s' => \$bindir, +); + +if ($mode =~ /^(s(mart)?|f(ast)?|i(mmediate)?)$/) { + $mode = $1; # untaint +} else { + print "Invalid -m mode, valid are: smart fast immediate\n"; + exit 1; +} + +($bindir) = $bindir =~ /^(\/.*)$/ if $bindir; # untaint + +# accept both "version cluster action" and "version[-/]cluster action" +if (@ARGV < 2 or (@ARGV < 3 and $ARGV[0] !~ m![-/]!)) { + print "Usage: $0 <version> <cluster> <action>\n"; + exit 1; +} + +$version = shift @ARGV; +if ($version =~ m!^(\d+\.?\d)[-/](.+)!) { + ($version, $cluster) = ($1, $2); +} else { + $cluster = shift @ARGV; +} +my $action = shift @ARGV; + +@pg_ctl_opts_from_cli=(); +foreach my $argv (@ARGV) { + push @pg_ctl_opts_from_cli, $argv =~ /(.*)/; # untaint +} + +($version) = $version =~ /^(\d+\.?\d+)$/; # untaint +($cluster) = $cluster =~ /^([^'"\s]+)$/; # untaint +error 'specified cluster does not exist' unless $version && $cluster && cluster_exists $version, $cluster; +%info = cluster_info ($version, $cluster); + +unless ($action eq 'stop') { + # check if cluster is disabled in start.conf + error 'Cluster is disabled' if $info{'start'} eq 'disabled'; +} + +unless (-d $info{'pgdata'} && defined $info{'owneruid'}) { + error $info{'pgdata'} . ' is not accessible or does not exist'; +} + +# check that owner uid/gid is valid +unless (getpwuid $info{'owneruid'}) { + error 'The cluster is owned by user id '.$info{'owneruid'}.' which does not exist' +} +unless (getgrgid $info{'ownergid'}) { + error 'The cluster is owned by group id '.$info{'ownergid'}.' which does not exist' +} +# owneruid and configuid need to match, unless configuid is root +if (($< == 0 or $> == 0) and $info{'configuid'} != 0 and + $info{'configuid'} != $info{'owneruid'}) { + my $configowner = (getpwuid $info{'configuid'})[0] || "(unknown)"; + my $dataowner = (getpwuid $info{'owneruid'})[0]; + error "Config owner ($configowner:$info{configuid}) and data owner ($dataowner:$info{owneruid}) do not match, and config owner is not root"; +} + +# redirect the request through systemd +if (not $skip_systemctl_redirect and getppid() != 1 and # not run from init + -d '/run/systemd/system' and not exists $ENV{_SYSTEMCTL_SKIP_REDIRECT} and # systemd is running + not $foreground and # foreground operation not requested + not exists $ENV{'PG_CLUSTER_CONF_ROOT'} and # not handling user clusters + $action =~ /^(start|stop|restart)$/ # redirect only these actions +) { + $action = $1; # untaint + system 'systemctl', 'is-active', '-q', "postgresql\@$version-$cluster"; + my $unit_active = $? == 0; + + # if extra options are given, proceed with pg_ctlcluster with a warning + if (@postgres_auxoptions != 0 or @pg_ctl_opts_from_cli != 0 or $bindir) { # extra options given + print "Notice: extra pg_ctl/postgres options given, bypassing systemctl for $action operation\n" if (-t 1); + # if we are root, redirect to systemctl unless stopping a cluster running outside systemd + } elsif ($> == 0) { + if ($action eq 'stop' and not $unit_active) { + # proceed with pg_ctlcluster + } else { + #print "Redirecting $action request to systemctl\n" if (-t 1); + system 'systemctl', $action, "postgresql\@$version-$cluster"; + exit $? >> 8; + # program end + } + # as non-root, raise an error on restarting a cluster running from systemd + } elsif ($action eq 'restart' and $unit_active) { + error "cluster is running from systemd, can only restart it as root. Try instead:\n sudo systemctl $action postgresql\@$version-$cluster"; + # program end + # otherwise just raise a warning on start and restart as non-root + } elsif (-t 1) { + if ($action =~ /start/) { + print "Warning: the cluster will not be running as a systemd service. Consider using systemctl:\n"; + } elsif ($unit_active) { # on stop, warn when running from systemd + print "Warning: stopping the cluster using pg_ctlcluster will mark the systemd unit as failed. Consider using systemctl:\n"; + } + print " sudo systemctl $action postgresql\@$version-$cluster\n"; + } +} + +# recreate missing standard log dir +if ($> == 0 && ! -e '/var/log/postgresql' && + $info{'logfile'} =~ m!^/var/log/postgresql!) { + system 'install', '-d', '-m', '1775', '-o', 'root', '-g', 'postgres', '/var/log/postgresql'; +} + +# recreate missing log file +if ($action ne 'stop' && $info{'logfile'} && ! -e $info{'logfile'}) { + if ($> == 0) { # drop privileges; this is important if logfile + # was determined via an /etc/postgresql/.../log symlink + change_ugid $info{'owneruid'}, $info{'ownergid'}; + } + sysopen (L, $info{'logfile'}, O_RDWR|O_CREAT|O_EXCL) or + error 'Could not create log file ' . $info{'logfile'}; + close L; + chmod 0640, $info{'logfile'}; + $< = $> = 0; # will silently fail if we were not root before, that's intended + $( = $) = 0; + if ($info{'owneruid'} < 500) { + my $g = (getgrnam 'adm')[2]; + lchown $info{'owneruid'}, $g, $info{'logfile'} if (defined $g); + } +} + +if ($action ne 'stop') { + # recreate /var/run/postgresql while possibly still running as root + if (! -d '/var/run/postgresql') { + system 'install', '-d', '-m', 2775, '-o', 'postgres', '-g', 'postgres', '/var/run/postgresql'; + } + + # allow creating socket directories below /var/run/postgresql for any user + if ($info{socketdir} =~ m!^(/var)/run/postgresql/[\w_.-]+$! and ! -d $info{socketdir}) { + if (mkdir $info{socketdir}, 02775) { # don't use "install" here as it would allow stealing existing directories + chown $info{owneruid}, $info{ownergid}, $info{socketdir}; + } else { + error "Could not create $info{socketdir}: $!"; + } + } + + # allow creating stats_temp_directory below /var/run/postgresql for any user + if ($info{config}->{stats_temp_directory} and $info{config}->{stats_temp_directory}=~ m!^(/var)/run/postgresql/[\w_.-]+$! and ! -d $info{config}->{stats_temp_directory}) { + if (mkdir $info{config}->{stats_temp_directory}, 0750) { # don't use "install" here as it would allow stealing existing directories + chown $info{owneruid}, $info{ownergid}, $info{config}->{stats_temp_directory}; + } else { + error "Could not create $info{config}->{stats_temp_directory}: $!"; + } + } +} + +if ($> == 0) { + # have postgres start with increased OOM killer protection; 9.0 and + # later has builtin support for resetting the adjustment of child processes + if ($action eq 'start' and $version >= '9.0' and not $PgCommon::rpm) { + if (-w '/proc/self/oom_score_adj') { + open F, '>/proc/self/oom_score_adj'; + print F "-900\n"; + close F; + } + } + + change_ugid $info{'owneruid'}, $info{'ownergid'}; +} + +# we are running as the cluster owner now + +if( $> != $info{'owneruid'} ) { + error 'You must run this program as the cluster owner ('. + (getpwuid $info{'owneruid'})[0].') or root'; +} + +# create socket directory (if it wasn't already created in /var/run/postgresql by the code above) +if ($action ne 'stop' && ! -d $info{socketdir}) { + system 'install', '-d', '-m', 2775, $info{socketdir}; +} + +# create stats_temp_directory (if it wasn't already created in /var/run/postgresql by the code above) +if ($action ne 'stop' && $info{config}->{stats_temp_directory} && ! -d $info{config}->{stats_temp_directory}) { + system 'install', '-d', '-m', 750, $info{config}->{stats_temp_directory}; +} + +$pg_ctl = $bindir ? "$bindir/pg_ctl" : get_program_path ('pg_ctl', $version); +error "Could not find pg_ctl executable for version $version" unless ($pg_ctl); + +if ($action =~ /^(start|stop|restart|reload|status|promote)$/) { + no strict 'refs'; + exit &$action; +} else { + error 'Invalid action (must be one of: start stop restart reload status promote)'; +} + +__END__ + +=head1 NAME + +pg_ctlcluster - start/stop/restart/reload a PostgreSQL cluster + +=head1 SYNOPSIS + +B<pg_ctlcluster> [I<options>] I<cluster-version> I<cluster-name> I<action> [B<--> I<pg_ctl options>] + +where I<action> = B<start>|B<stop>|B<restart>|B<reload>|B<status>|B<promote> + +=head1 DESCRIPTION + +This program controls the B<postgres> server for a particular cluster. It +essentially wraps the L<pg_ctl(1)> command. It determines the cluster version +and data path and calls the right version of B<pg_ctl> with appropriate +configuration parameters and paths. + +You have to start this program as the user who owns the database cluster or as +root. + +To ease integration with B<systemd> operation, the alternative syntax +"B<pg_ctlcluster> I<version>B<->I<cluster> I<action>" is also supported. + +=head1 ACTIONS + +=over 4 + +=item B<start> + +A log file for this specific cluster is created if it does not exist yet (by +default, +C</var/log/postgresql/postgresql->I<cluster-version>C<->I<cluster-name>C<.log>), +and a PostgreSQL server process (L<postgres(1)>) is started on it. Exits with +0 on success, with 2 if the server is already running, and with 1 on other +failure conditions. + +=item B<stop> + +Stops the L<postgres(1)> server of the given cluster. By default, "fast" +shutdown mode is used. + +=item B<restart> + +Stops the server if it is running and starts it (again). + +=item B<reload> + +Causes the configuration files to be re-read without a full shutdown of the +server. + +=item B<status> + +Checks whether a server is running. If it is, the PID and the command line +options that were used to invoke it are displayed. + +=item B<promote> + +Commands a running standby server to exit recovery and begin read-write +operations. + +=back + +=head1 OPTIONS + +=over 4 + +=item B<-f>|B<--force> + +For B<stop> and B<restart>, the "fast" mode is used which rolls back all active +transactions, disconnects clients immediately and thus shuts down cleanly. If +that does not work, shutdown is attempted again in "immediate" mode, which can +leave the cluster in an inconsistent state and thus will lead to a recovery run +at the next start. If this still does not help, the B<postgres> process is +killed. Exits with 0 on success, with 2 if the server is not running, and with +1 on other failure conditions. This mode should only be used when the machine +is about to be shut down. + +=item B<-m>|B<--mode> [B<smart>|B<fast>|B<immediate>] + +Shutdown mode to use for B<stop> and B<restart> actions, default is B<fast>. +See pg_ctl(1) for documentation. + +=item B<--foreground> + +Start B<postgres> in foreground, without daemonizing via B<pg_ctl>. + +=item B<--stdlog> + +When B<--foreground> is in use, redirect stderr to the standard logfile in +C</var/log/postgresql/>. (Default when not run in foreground.) + +=item B<--skip-systemctl-redirect> + +When running as root, B<pg_ctlcluster> redirects actions to B<systemctl> so +running clusters are properly supervised by B<systemd>. This option skips the +redirect; it is used in the B<postgresql@.service> unit file. The redirect is +also skipped if additional B<postgres> or B<pg_ctl> options are provided. + +=item B<--bindir> I<directory> + +Path to B<pg_ctl>. (Default is C</usr/lib/postgresql/>I<version>C</bin>.) + +=item B<-o>|B<--options> I<option> + +Pass given I<option> as command line option to the C<postgres> process. It is +possible to specify B<-o> multiple times. See L<postgres(1)> for a +description of valid options. + +=item I<pg_ctl options> + +Pass given I<pg_ctl options> as command line options to B<pg_ctl>. See L<pg_ctl(1)> +for a description of valid options. + +=back + +=head1 FILES + +=over 4 + +=item C</etc/postgresql/>I<cluster-version>C</>I<cluster-name>C</pg_ctl.conf> + +This configuration file contains cluster specific options to be passed to +L<pg_ctl(1)>. + +=item C</etc/postgresql/>I<cluster-version>C</>I<cluster-name>C</start.conf> + +This configuration file controls the start/stop behavior of the cluster. See +section "STARTUP CONTROL" in L<pg_createcluster(8)> for details. + +=back + +=head1 BUGS + +Changing the port number on startup using B<-o -p> will not work as it breaks +the checks for running clusters. + +=head1 SEE ALSO + +L<pg_createcluster(8)>, L<pg_ctl(1)>, L<pg_wrapper(1)>, L<pg_lsclusters(1)>, +L<postgres(1)> + +=head1 AUTHOR + +Martin Pitt L<E<lt>mpitt@debian.orgE<gt>> + |