diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 03615da480..9f1eae12d8 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -398,6 +398,24 @@ PostgreSQL documentation
+
+
+
+
+
+ By default, when pg_basebackup aborts with an
+ error, it removes any directories it might have created before
+ discovering that it cannot finish the job (for example, data directory
+ and transaction log directory). This option inhibits tidying-up and is
+ thus useful for debugging.
+
+
+
+ Note that tablespace directories are not cleaned up either way.
+
+
+
+
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 351a42068f..42f3b273a6 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -58,6 +58,7 @@ static TablespaceList tablespace_dirs = {NULL, NULL};
static char *xlog_dir = "";
static char format = 'p'; /* p(lain)/t(ar) */
static char *label = "pg_basebackup base backup";
+static bool noclean = false;
static bool showprogress = false;
static int verbose = 0;
static int compresslevel = 0;
@@ -69,6 +70,13 @@ static int standby_message_timeout = 10 * 1000; /* 10 sec = default */
static pg_time_t last_progress_report = 0;
static int32 maxrate = 0; /* no limit by default */
+static bool success = false;
+static bool made_new_pgdata = false;
+static bool found_existing_pgdata = false;
+static bool made_new_xlogdir = false;
+static bool found_existing_xlogdir = false;
+static bool made_tablespace_dirs = false;
+static bool found_tablespace_dirs = false;
/* Progress counters */
static uint64 totalsize;
@@ -82,6 +90,7 @@ static int bgpipe[2] = {-1, -1};
/* Handle to child process */
static pid_t bgchild = -1;
+static bool in_log_streamer = false;
/* End position for xlog streaming, empty string if unknown yet */
static XLogRecPtr xlogendptr;
@@ -98,7 +107,7 @@ static PQExpBuffer recoveryconfcontents = NULL;
/* Function headers */
static void usage(void);
static void disconnect_and_exit(int code);
-static void verify_dir_is_empty_or_create(char *dirname);
+static void verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found);
static void progress_report(int tablespacenum, const char *filename, bool force);
static void ReceiveTarFile(PGconn *conn, PGresult *res, int rownum);
@@ -114,6 +123,69 @@ static const char *get_tablespace_mapping(const char *dir);
static void tablespace_list_append(const char *arg);
+static void
+cleanup_directories_atexit(void)
+{
+ if (success || in_log_streamer)
+ return;
+
+ if (!noclean)
+ {
+ if (made_new_pgdata)
+ {
+ fprintf(stderr, _("%s: removing data directory \"%s\"\n"),
+ progname, basedir);
+ if (!rmtree(basedir, true))
+ fprintf(stderr, _("%s: failed to remove data directory\n"),
+ progname);
+ }
+ else if (found_existing_pgdata)
+ {
+ fprintf(stderr,
+ _("%s: removing contents of data directory \"%s\"\n"),
+ progname, basedir);
+ if (!rmtree(basedir, false))
+ fprintf(stderr, _("%s: failed to remove contents of data directory\n"),
+ progname);
+ }
+
+ if (made_new_xlogdir)
+ {
+ fprintf(stderr, _("%s: removing transaction log directory \"%s\"\n"),
+ progname, xlog_dir);
+ if (!rmtree(xlog_dir, true))
+ fprintf(stderr, _("%s: failed to remove transaction log directory\n"),
+ progname);
+ }
+ else if (found_existing_xlogdir)
+ {
+ fprintf(stderr,
+ _("%s: removing contents of transaction log directory \"%s\"\n"),
+ progname, xlog_dir);
+ if (!rmtree(xlog_dir, false))
+ fprintf(stderr, _("%s: failed to remove contents of transaction log directory\n"),
+ progname);
+ }
+ }
+ else
+ {
+ if (made_new_pgdata || found_existing_pgdata)
+ fprintf(stderr,
+ _("%s: data directory \"%s\" not removed at user's request\n"),
+ progname, basedir);
+
+ if (made_new_xlogdir || found_existing_xlogdir)
+ fprintf(stderr,
+ _("%s: transaction log directory \"%s\" not removed at user's request\n"),
+ progname, xlog_dir);
+ }
+
+ if (made_tablespace_dirs || found_tablespace_dirs)
+ fprintf(stderr,
+ _("%s: changes to tablespace directories will not be undone"),
+ progname);
+}
+
static void
disconnect_and_exit(int code)
{
@@ -253,6 +325,7 @@ usage(void)
printf(_(" -c, --checkpoint=fast|spread\n"
" set fast or spread checkpointing\n"));
printf(_(" -l, --label=LABEL set backup label\n"));
+ printf(_(" -n, --noclean do not clean up after errors\n"));
printf(_(" -P, --progress show progress information\n"));
printf(_(" -v, --verbose output verbose messages\n"));
printf(_(" -V, --version output version information, then exit\n"));
@@ -375,6 +448,8 @@ LogStreamerMain(logstreamer_param *param)
{
StreamCtl stream;
+ in_log_streamer = true;
+
MemSet(&stream, 0, sizeof(stream));
stream.startpos = param->startptr;
stream.timeline = param->timeline;
@@ -501,7 +576,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
* be give and the process ended.
*/
static void
-verify_dir_is_empty_or_create(char *dirname)
+verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found)
{
switch (pg_check_dir(dirname))
{
@@ -517,12 +592,16 @@ verify_dir_is_empty_or_create(char *dirname)
progname, dirname, strerror(errno));
disconnect_and_exit(1);
}
+ if (created)
+ *created = true;
return;
case 1:
/*
* Exists, empty
*/
+ if (found)
+ *found = true;
return;
case 2:
case 3:
@@ -1683,7 +1762,7 @@ BaseBackup(void)
{
char *path = (char *) get_tablespace_mapping(PQgetvalue(res, i, 1));
- verify_dir_is_empty_or_create(path);
+ verify_dir_is_empty_or_create(path, &made_tablespace_dirs, &found_tablespace_dirs);
}
}
@@ -1892,6 +1971,7 @@ main(int argc, char **argv)
{"gzip", no_argument, NULL, 'z'},
{"compress", required_argument, NULL, 'Z'},
{"label", required_argument, NULL, 'l'},
+ {"noclean", no_argument, NULL, 'n'},
{"dbname", required_argument, NULL, 'd'},
{"host", required_argument, NULL, 'h'},
{"port", required_argument, NULL, 'p'},
@@ -1926,7 +2006,9 @@ main(int argc, char **argv)
}
}
- while ((c = getopt_long(argc, argv, "D:F:r:RT:xX:l:zZ:d:c:h:p:U:s:S:wWvP",
+ atexit(cleanup_directories_atexit);
+
+ while ((c = getopt_long(argc, argv, "D:F:r:RT:xX:l:nzZ:d:c:h:p:U:s:S:wWvP",
long_options, &option_index)) != -1)
{
switch (c)
@@ -2001,6 +2083,9 @@ main(int argc, char **argv)
case 'l':
label = pg_strdup(optarg);
break;
+ case 'n':
+ noclean = true;
+ break;
case 'z':
#ifdef HAVE_LIBZ
compresslevel = Z_DEFAULT_COMPRESSION;
@@ -2170,14 +2255,14 @@ main(int argc, char **argv)
* unless we are writing to stdout.
*/
if (format == 'p' || strcmp(basedir, "-") != 0)
- verify_dir_is_empty_or_create(basedir);
+ verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
/* Create transaction log symlink, if required */
if (strcmp(xlog_dir, "") != 0)
{
char *linkloc;
- verify_dir_is_empty_or_create(xlog_dir);
+ verify_dir_is_empty_or_create(xlog_dir, &made_new_xlogdir, &found_existing_xlogdir);
/* form name of the place where the symlink must go */
linkloc = psprintf("%s/pg_xlog", basedir);
@@ -2198,5 +2283,6 @@ main(int argc, char **argv)
BaseBackup();
+ success = true;
return 0;
}
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 6c33936d25..fd9857d67b 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -4,7 +4,7 @@ use Cwd;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 51;
+use Test::More tests => 54;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -40,6 +40,14 @@ $node->command_fails(
[ 'pg_basebackup', '-D', "$tempdir/backup" ],
'pg_basebackup fails because of WAL configuration');
+ok(! -d "$tempdir/backup", 'backup directory was cleaned up');
+
+$node->command_fails(
+ [ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
+ 'failing run with noclean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
open CONF, ">>$pgdata/postgresql.conf";
print CONF "max_replication_slots = 10\n";
print CONF "max_wal_senders = 10\n";