Skip to content

Commit 363e8f9

Browse files
committed
Fix pg_basebackup with in-place tablespaces some more.
Commit c6f2f01 purported to make this work, but problems remained. In a plain-format backup, the files from an in-place tablespace got included in the tar file for the main tablespace, which is wrong but it's not clear that it has any user-visible consequences. In a tar-format backup, the TABLESPACE_MAP option is used, and so we never iterated over pg_tblspc and thus never backed up the in-place tablespaces anywhere at all. To fix this, reverse the changes in that commit, so that when we scan pg_tblspc during a backup, we create tablespaceinfo objects even for in-place tablespaces. We set the field that would normally contain the absolute pathname to the relative path pg_tblspc/${TSOID}, and that's good enough to make basebackup.c happy without any further changes. However, pg_basebackup needs a couple of adjustments to make it work. First, it needs to understand that a relative path for a tablespace means it's an in-place tablespace. Second, it needs to tolerate the situation where restoring the main tablespace tries to create pg_tblspc or a subdirectory and finds that it already exists, because we restore user-defined tablespaces before the main tablespace. Since in-place tablespaces are only intended for use in development and testing, no back-patch. Patch by me, reviewed by Thomas Munro and Michael Paquier. Discussion: http://postgr.es/m/CA+TgmobwvbEp+fLq2PykMYzizcvuNv0a7gPMJtxOTMOuuRLMHg@mail.gmail.com
1 parent f180290 commit 363e8f9

File tree

4 files changed

+159
-66
lines changed

4 files changed

+159
-66
lines changed

src/backend/access/transam/xlog.c

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8454,76 +8454,92 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
84548454
char fullpath[MAXPGPATH + 10];
84558455
char linkpath[MAXPGPATH];
84568456
char *relpath = NULL;
8457-
int rllen;
8458-
StringInfoData escapedpath;
84598457
char *s;
8458+
PGFileType de_type;
84608459

84618460
/* Skip anything that doesn't look like a tablespace */
84628461
if (strspn(de->d_name, "0123456789") != strlen(de->d_name))
84638462
continue;
84648463

84658464
snprintf(fullpath, sizeof(fullpath), "pg_tblspc/%s", de->d_name);
84668465

8467-
/*
8468-
* Skip anything that isn't a symlink/junction. For testing only,
8469-
* we sometimes use allow_in_place_tablespaces to create
8470-
* directories directly under pg_tblspc, which would fail below.
8471-
*/
8472-
if (get_dirent_type(fullpath, de, false, ERROR) != PGFILETYPE_LNK)
8473-
continue;
8466+
de_type = get_dirent_type(fullpath, de, false, ERROR);
84748467

8475-
rllen = readlink(fullpath, linkpath, sizeof(linkpath));
8476-
if (rllen < 0)
8468+
if (de_type == PGFILETYPE_LNK)
84778469
{
8478-
ereport(WARNING,
8479-
(errmsg("could not read symbolic link \"%s\": %m",
8480-
fullpath)));
8481-
continue;
8470+
StringInfoData escapedpath;
8471+
int rllen;
8472+
8473+
rllen = readlink(fullpath, linkpath, sizeof(linkpath));
8474+
if (rllen < 0)
8475+
{
8476+
ereport(WARNING,
8477+
(errmsg("could not read symbolic link \"%s\": %m",
8478+
fullpath)));
8479+
continue;
8480+
}
8481+
else if (rllen >= sizeof(linkpath))
8482+
{
8483+
ereport(WARNING,
8484+
(errmsg("symbolic link \"%s\" target is too long",
8485+
fullpath)));
8486+
continue;
8487+
}
8488+
linkpath[rllen] = '\0';
8489+
8490+
/*
8491+
* Relpath holds the relative path of the tablespace directory
8492+
* when it's located within PGDATA, or NULL if it's located
8493+
* elsewhere.
8494+
*/
8495+
if (rllen > datadirpathlen &&
8496+
strncmp(linkpath, DataDir, datadirpathlen) == 0 &&
8497+
IS_DIR_SEP(linkpath[datadirpathlen]))
8498+
relpath = pstrdup(linkpath + datadirpathlen + 1);
8499+
8500+
/*
8501+
* Add a backslash-escaped version of the link path to the
8502+
* tablespace map file.
8503+
*/
8504+
initStringInfo(&escapedpath);
8505+
for (s = linkpath; *s; s++)
8506+
{
8507+
if (*s == '\n' || *s == '\r' || *s == '\\')
8508+
appendStringInfoChar(&escapedpath, '\\');
8509+
appendStringInfoChar(&escapedpath, *s);
8510+
}
8511+
appendStringInfo(tblspcmapfile, "%s %s\n",
8512+
de->d_name, escapedpath.data);
8513+
pfree(escapedpath.data);
84828514
}
8483-
else if (rllen >= sizeof(linkpath))
8515+
else if (de_type == PGFILETYPE_DIR)
84848516
{
8485-
ereport(WARNING,
8486-
(errmsg("symbolic link \"%s\" target is too long",
8487-
fullpath)));
8488-
continue;
8517+
/*
8518+
* It's possible to use allow_in_place_tablespaces to create
8519+
* directories directly under pg_tblspc, for testing purposes
8520+
* only.
8521+
*
8522+
* In this case, we store a relative path rather than an
8523+
* absolute path into the tablespaceinfo.
8524+
*/
8525+
snprintf(linkpath, sizeof(linkpath), "pg_tblspc/%s",
8526+
de->d_name);
8527+
relpath = pstrdup(linkpath);
84898528
}
8490-
linkpath[rllen] = '\0';
8491-
8492-
/*
8493-
* Build a backslash-escaped version of the link path to include
8494-
* in the tablespace map file.
8495-
*/
8496-
initStringInfo(&escapedpath);
8497-
for (s = linkpath; *s; s++)
8529+
else
84988530
{
8499-
if (*s == '\n' || *s == '\r' || *s == '\\')
8500-
appendStringInfoChar(&escapedpath, '\\');
8501-
appendStringInfoChar(&escapedpath, *s);
8531+
/* Skip any other file type that appears here. */
8532+
continue;
85028533
}
85038534

8504-
/*
8505-
* Relpath holds the relative path of the tablespace directory
8506-
* when it's located within PGDATA, or NULL if it's located
8507-
* elsewhere.
8508-
*/
8509-
if (rllen > datadirpathlen &&
8510-
strncmp(linkpath, DataDir, datadirpathlen) == 0 &&
8511-
IS_DIR_SEP(linkpath[datadirpathlen]))
8512-
relpath = linkpath + datadirpathlen + 1;
8513-
85148535
ti = palloc(sizeof(tablespaceinfo));
85158536
ti->oid = pstrdup(de->d_name);
85168537
ti->path = pstrdup(linkpath);
8517-
ti->rpath = relpath ? pstrdup(relpath) : NULL;
8538+
ti->rpath = relpath;
85188539
ti->size = -1;
85198540

85208541
if (tablespaces)
85218542
*tablespaces = lappend(*tablespaces, ti);
8522-
8523-
appendStringInfo(tblspcmapfile, "%s %s\n",
8524-
ti->oid, escapedpath.data);
8525-
8526-
pfree(escapedpath.data);
85278543
}
85288544
FreeDir(tblspcdir);
85298545

src/bin/pg_basebackup/bbstreamer_file.c

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -276,28 +276,49 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member,
276276
}
277277
}
278278

279+
/*
280+
* Should we tolerate an already-existing directory?
281+
*
282+
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6 clusters) will have been
283+
* created by the wal receiver process. Also, when the WAL directory location
284+
* was specified, pg_wal (or pg_xlog) has already been created as a symbolic
285+
* link before starting the actual backup. So just ignore creation failures
286+
* on related directories.
287+
*
288+
* If in-place tablespaces are used, pg_tblspc and subdirectories may already
289+
* exist when we get here. So tolerate that case, too.
290+
*/
291+
static bool
292+
should_allow_existing_directory(const char *pathname)
293+
{
294+
const char *filename = last_dir_separator(pathname) + 1;
295+
296+
if (strcmp(filename, "pg_wal") == 0 ||
297+
strcmp(filename, "pg_xlog") == 0 ||
298+
strcmp(filename, "archive_status") == 0 ||
299+
strcmp(filename, "pg_tblspc") == 0)
300+
return true;
301+
302+
if (strspn(filename, "0123456789") == strlen(filename))
303+
{
304+
const char *pg_tblspc = strstr(pathname, "/pg_tblspc/");
305+
306+
return pg_tblspc != NULL && pg_tblspc + 11 == filename;
307+
}
308+
309+
return false;
310+
}
311+
279312
/*
280313
* Create a directory.
281314
*/
282315
static void
283316
extract_directory(const char *filename, mode_t mode)
284317
{
285-
if (mkdir(filename, pg_dir_create_mode) != 0)
286-
{
287-
/*
288-
* When streaming WAL, pg_wal (or pg_xlog for pre-9.6 clusters) will
289-
* have been created by the wal receiver process. Also, when the WAL
290-
* directory location was specified, pg_wal (or pg_xlog) has already
291-
* been created as a symbolic link before starting the actual backup.
292-
* So just ignore creation failures on related directories.
293-
*/
294-
if (!((pg_str_endswith(filename, "/pg_wal") ||
295-
pg_str_endswith(filename, "/pg_xlog") ||
296-
pg_str_endswith(filename, "/archive_status")) &&
297-
errno == EEXIST))
298-
pg_fatal("could not create directory \"%s\": %m",
299-
filename);
300-
}
318+
if (mkdir(filename, pg_dir_create_mode) != 0 &&
319+
(errno != EEXIST || !should_allow_existing_directory(filename)))
320+
pg_fatal("could not create directory \"%s\": %m",
321+
filename);
301322

302323
#ifndef WIN32
303324
if (chmod(filename, mode))

src/bin/pg_basebackup/pg_basebackup.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,9 +1122,17 @@ CreateBackupStreamer(char *archive_name, char *spclocation,
11221122
* other tablespaces will be written to the directory where they're
11231123
* located on the server, after applying any user-specified tablespace
11241124
* mappings.
1125+
*
1126+
* In the case of an in-place tablespace, spclocation will be a
1127+
* relative path. We just convert it to an absolute path by prepending
1128+
* basedir.
11251129
*/
1126-
directory = spclocation == NULL ? basedir
1127-
: get_tablespace_mapping(spclocation);
1130+
if (spclocation == NULL)
1131+
directory = basedir;
1132+
else if (!is_absolute_path(spclocation))
1133+
directory = psprintf("%s/%s", basedir, spclocation);
1134+
else
1135+
directory = get_tablespace_mapping(spclocation);
11281136
streamer = bbstreamer_extractor_new(directory,
11291137
get_tablespace_mapping,
11301138
progress_update_filename);
@@ -1955,7 +1963,15 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
19551963
*/
19561964
if (backup_target == NULL && format == 'p' && !PQgetisnull(res, i, 1))
19571965
{
1958-
char *path = unconstify(char *, get_tablespace_mapping(PQgetvalue(res, i, 1)));
1966+
char *path = PQgetvalue(res, i, 1);
1967+
1968+
if (is_absolute_path(path))
1969+
path = unconstify(char *, get_tablespace_mapping(path));
1970+
else
1971+
{
1972+
/* This is an in-place tablespace, so prepend basedir. */
1973+
path = psprintf("%s/%s", basedir, path);
1974+
}
19591975

19601976
verify_dir_is_empty_or_create(path, &made_tablespace_dirs, &found_tablespace_dirs);
19611977
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright (c) 2021-2023, PostgreSQL Global Development Group
2+
3+
use strict;
4+
use warnings;
5+
use PostgreSQL::Test::Cluster;
6+
use PostgreSQL::Test::Utils;
7+
use Test::More;
8+
9+
my $tempdir = PostgreSQL::Test::Utils::tempdir;
10+
11+
# For nearly all pg_basebackup invocations some options should be specified,
12+
# to keep test times reasonable. Using @pg_basebackup_defs as the first
13+
# element of the array passed to IPC::Run interpolate the array (as it is
14+
# not a reference to an array)...
15+
my @pg_basebackup_defs = ('pg_basebackup', '--no-sync', '-cfast');
16+
17+
# Set up an instance.
18+
my $node = PostgreSQL::Test::Cluster->new('main');
19+
$node->init('allows_streaming' => 1);
20+
$node->start();
21+
22+
# Create an in-place tablespace.
23+
$node->safe_psql('postgres', <<EOM);
24+
SET allow_in_place_tablespaces = on;
25+
CREATE TABLESPACE inplace LOCATION '';
26+
EOM
27+
28+
# Back it up.
29+
my $backupdir = $tempdir . '/backup';
30+
$node->command_ok(
31+
[ @pg_basebackup_defs, '-D', $backupdir, '-Ft', '-X', 'none' ],
32+
'pg_basebackup runs');
33+
34+
# Make sure we got base.tar and one tablespace.
35+
ok(-f "$backupdir/base.tar", 'backup tar was created');
36+
my @tblspc_tars = glob "$backupdir/[0-9]*.tar";
37+
is(scalar(@tblspc_tars), 1, 'one tablespace tar was created');
38+
39+
# All good.
40+
done_testing();

0 commit comments

Comments
 (0)