Am Dienstag, den 18.02.2020, 15:15 +0900 schrieb Michael Paquier: > Fair point. Now, while the proposed patch is right to use > TABLESPACE_VERSION_DIRECTORY, shouldn't we use strncmp based on the > length of TABLESPACE_VERSION_DIRECTORY instead of de->d_name? It > seems also to me that the code as proposed is rather fragile, and > that > we had better be sure that the check only happens when we are > scanning > entries within pg_tblspc. >
Yes, after thinking and playing around with it a little i share your position. You can still easily cause pg_checksums to error out by just having arbitrary files around in the reference tablespace locations. Though i don't think this is something of a big issue, it looks strange and misleading if pg_checksums just complains about files not belonging to the scanned PostgreSQL data directory (even we explicitly note in the docs that even tablespace locations are somehow taboo for DBAs to put other files and/or directories in there). So i propose a different approach like the attached patch tries to implement: instead of just blindly iterating over directory contents and filter them out, reference the tablespace location and TABLESPACE_VERSION_DIRECTORY directly. This is done by a new function scan_tablespaces() which is specialized in just follow the symlinks/junctions in pg_tblspc and call scan_directory() with just what it has found there. It will also honour directories, just in case an experienced DBA has copied over the tablespace into pg_tblspc directly. > The issue with pg_internal.init.XX is quite different, so I think > that > it would be better to commit that separately first. Agreed. Thanks, Bernd
From 4b6d369f731f111216f9ed6613ad1b41872fb0ea Mon Sep 17 00:00:00 2001 From: Bernd Helmle <maili...@oopsware.de> Date: Thu, 20 Feb 2020 17:18:10 +0100 Subject: [PATCH] Make scanning pg_tblspc more robust. Currently we dive blindly into the subdirectories referenced by the symlinks (or under Windows via file junctions) located in pg_tblspc, which has various problems. We need to filter out any other possible tablespace location found in the referenced subdirectory, which could likely happen if you have e.g. an upgraded instance via pg_upgrade or other installations using the same tablespace location. Instead of filtering out any of these possible foreign directories, try to resolve the locations in pg_tblspc directly. This is done with the new scan_tablespaces() helper function, which indirectly calls scan_directory() after resolving the links in pg_tblspc. It explicitely follows directories found there too, since it might happen that users copy over tablespace locations directly into pg_tblspc. --- src/bin/pg_checksums/pg_checksums.c | 99 ++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c index 46ee1f1dc3..4e2e9cb3e2 100644 --- a/src/bin/pg_checksums/pg_checksums.c +++ b/src/bin/pg_checksums/pg_checksums.c @@ -108,6 +108,15 @@ static const char *const skip[] = { NULL, }; +/* + * Forwarded declarations. + */ +static int64 +scan_directory(const char *basedir, const char *subdir, bool sizeonly); + +static int64 +scan_tablespaces(const char *basedir, bool sizeonly); + /* * Report current progress status. Parts borrowed from * src/bin/pg_basebackup/pg_basebackup.c. @@ -267,6 +276,91 @@ scan_file(const char *fn, BlockNumber segmentno) close(f); } +/* + * Scans pg_tblspc of the given base directory for either links + * or directories and examines wether an existing tablespace version + * directory exists there. If true, scan_directory() will do the checksum + * legwork. sizeonly specifies wether we want to compute the directory size + * only, required for progress reporting. + */ +static int64 +scan_tablespaces(const char *basedir, bool sizeonly) +{ + int64 dirsize = 0; + char path[MAXPGPATH]; + DIR *dir; + struct dirent *de; + + snprintf(path, sizeof(path), "%s/%s", basedir, "pg_tblspc"); + dir = opendir(path); + + if (!dir) + { + pg_log_error("could not open directory \"%s\": %m", path); + exit(1); + } + + /* + * Scan through the pg_tblspc contents. We expect either a symlink + * (or Windows junction) or a directory. The latter might exist due + * systems where administrators copy over the tablespace directory + * directly into PGDATA, e.g. on recovery systems. + */ + while((de = readdir(dir)) != NULL) + { + char tblspc_path[MAXPGPATH]; + char fn[MAXPGPATH]; + struct stat st; + + if (strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0) + continue; + + snprintf(fn, sizeof(fn), "%s/%s", path, de->d_name); + if (lstat(fn, &st) < 0) + { + pg_log_error("could not stat file \"%s\": %m", fn); + exit(1); + } + + if (S_ISREG(st.st_mode)) + { + pg_log_debug("ignoring file %s in pg_tblspc", de->d_name); + continue; + } + +#ifndef WIN32 + if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) +#else + if (S_ISDIR(st.st_mode) || pgwin32_is_junction(fn)) +#endif + { + /* + * Every link or tablespace directory found here + * must have a TABLESPACE_VERSION_DIRECTORY. We build + * the direct path to it and check via lstat wether it exists. + */ + snprintf(tblspc_path, sizeof(tblspc_path), "%s/%s", + fn, TABLESPACE_VERSION_DIRECTORY); + + if (lstat(tblspc_path, &st) < 0) + { + pg_log_warning("directory or link \"%s\" doesn't contain a valid tablespace" , fn); + continue; + } + + /* Seems okay, dive into and do the ordinary checksum checks. */ + dirsize += scan_directory(fn, TABLESPACE_VERSION_DIRECTORY, sizeonly); + } + else + { + pg_log_debug("ignoring file %s in pg_tblspc", de->d_name); + } + } + + return dirsize; +} + /* * Scan the given directory for items which can be checksummed and * operate on each one of them. If "sizeonly" is true, the size of @@ -438,6 +532,7 @@ main(int argc, char *argv[]) break; case 'v': verbose = true; + pg_logging_set_level(PG_LOG_DEBUG); break; case 'D': DataDir = optarg; @@ -553,12 +648,12 @@ main(int argc, char *argv[]) { total_size = scan_directory(DataDir, "global", true); total_size += scan_directory(DataDir, "base", true); - total_size += scan_directory(DataDir, "pg_tblspc", true); + total_size += scan_tablespaces(DataDir, true); } (void) scan_directory(DataDir, "global", false); (void) scan_directory(DataDir, "base", false); - (void) scan_directory(DataDir, "pg_tblspc", false); + (void) scan_tablespaces(DataDir, false); if (showprogress) { -- 2.24.1