Add prefix checks in exclude lists for pg_rewind, pg_checksums and base backups
authorMichael Paquier <michael@paquier.xyz>
Mon, 24 Feb 2020 09:13:25 +0000 (18:13 +0900)
committerMichael Paquier <michael@paquier.xyz>
Mon, 24 Feb 2020 09:13:25 +0000 (18:13 +0900)
An instance of PostgreSQL crashing with a bad timing could leave behind
temporary pg_internal.init files, potentially causing failures when
verifying checksums.  As the same exclusion lists are used between
pg_rewind, pg_checksums and basebackup.c, all those tools are extended
with prefix checks to keep everything in sync, with dedicated checks
added for pg_internal.init.

Backpatch down to 11, where pg_checksums (pg_verify_checksums in 11) and
checksum verification for base backups have been introduced.

Reported-by: Michael Banck
Author: Michael Paquier
Reviewed-by: Kyotaro Horiguchi, David Steele
Discussion: https://postgr.es/m/62031974fd8e941dd8351fbc8c7eff60d59c5338.camel@credativ.de
Backpatch-through: 11

src/backend/replication/basebackup.c
src/bin/pg_basebackup/t/010_pg_basebackup.pl
src/bin/pg_checksums/pg_checksums.c
src/bin/pg_checksums/t/002_actions.pl
src/bin/pg_rewind/filemap.c

index dea8aab45e0ba109f614ee24ceab63326bd1a741..ca8bebf432b2c511090f92474f258556bc50e8e0 100644 (file)
@@ -121,6 +121,18 @@ static long long int total_checksum_failures;
 /* Do not verify checksums. */
 static bool noverify_checksums = false;
 
+/*
+ * Definition of one element part of an exclusion list, used for paths part
+ * of checksum validation or base backups.  "name" is the name of the file
+ * or path to check for exclusion.  If "match_prefix" is true, any items
+ * matching the name as prefix are excluded.
+ */
+struct exclude_list_item
+{
+       const char *name;
+       bool            match_prefix;
+};
+
 /*
  * The contents of these directories are removed or recreated during server
  * start so they are not included in backups.  The directories themselves are
@@ -170,16 +182,19 @@ static const char *const excludeDirContents[] =
 /*
  * List of files excluded from backups.
  */
-static const char *const excludeFiles[] =
+static const struct exclude_list_item excludeFiles[] =
 {
        /* Skip auto conf temporary file. */
-       PG_AUTOCONF_FILENAME ".tmp",
+       {PG_AUTOCONF_FILENAME ".tmp", false},
 
        /* Skip current log file temporary file */
-       LOG_METAINFO_DATAFILE_TMP,
+       {LOG_METAINFO_DATAFILE_TMP, false},
 
-       /* Skip relation cache because it is rebuilt on startup */
-       RELCACHE_INIT_FILENAME,
+       /*
+        * Skip relation cache because it is rebuilt on startup.  This includes
+        * temporary files.
+        */
+       {RELCACHE_INIT_FILENAME, true},
 
        /*
         * If there's a backup_label or tablespace_map file, it belongs to a
@@ -187,14 +202,14 @@ static const char *const excludeFiles[] =
         * for this backup.  Our backup_label/tablespace_map is injected into the
         * tar separately.
         */
-       BACKUP_LABEL_FILE,
-       TABLESPACE_MAP,
+       {BACKUP_LABEL_FILE, false},
+       {TABLESPACE_MAP, false},
 
-       "postmaster.pid",
-       "postmaster.opts",
+       {"postmaster.pid", false},
+       {"postmaster.opts", false},
 
        /* end of list */
-       NULL
+       {NULL, false}
 };
 
 /*
@@ -203,16 +218,15 @@ static const char *const excludeFiles[] =
  * Note: this list should be kept in sync with what pg_checksums.c
  * includes.
  */
-static const char *const noChecksumFiles[] = {
-       "pg_control",
-       "pg_filenode.map",
-       "pg_internal.init",
-       "PG_VERSION",
+static const struct exclude_list_item noChecksumFiles[] = {
+       {"pg_control", false},
+       {"pg_filenode.map", false},
+       {"pg_internal.init", true},
+       {"PG_VERSION", false},
 #ifdef EXEC_BACKEND
-       "config_exec_params",
-       "config_exec_params.new",
+       {"config_exec_params", true},
 #endif
-       NULL,
+       {NULL, false}
 };
 
 /*
@@ -1082,9 +1096,13 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
 
                /* Scan for files that should be excluded */
                excludeFound = false;
-               for (excludeIdx = 0; excludeFiles[excludeIdx] != NULL; excludeIdx++)
+               for (excludeIdx = 0; excludeFiles[excludeIdx].name != NULL; excludeIdx++)
                {
-                       if (strcmp(de->d_name, excludeFiles[excludeIdx]) == 0)
+                       int                     cmplen = strlen(excludeFiles[excludeIdx].name);
+
+                       if (!excludeFiles[excludeIdx].match_prefix)
+                               cmplen++;
+                       if (strncmp(de->d_name, excludeFiles[excludeIdx].name, cmplen) == 0)
                        {
                                elog(DEBUG1, "file \"%s\" excluded from backup", de->d_name);
                                excludeFound = true;
@@ -1317,17 +1335,24 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
 static bool
 is_checksummed_file(const char *fullpath, const char *filename)
 {
-       const char *const *f;
-
        /* Check that the file is in a tablespace */
        if (strncmp(fullpath, "./global/", 9) == 0 ||
                strncmp(fullpath, "./base/", 7) == 0 ||
                strncmp(fullpath, "/", 1) == 0)
        {
-               /* Compare file against noChecksumFiles skiplist */
-               for (f = noChecksumFiles; *f; f++)
-                       if (strcmp(*f, filename) == 0)
+               int                     excludeIdx;
+
+               /* Compare file against noChecksumFiles skip list */
+               for (excludeIdx = 0; noChecksumFiles[excludeIdx].name != NULL; excludeIdx++)
+               {
+                       int                     cmplen = strlen(noChecksumFiles[excludeIdx].name);
+
+                       if (!noChecksumFiles[excludeIdx].match_prefix)
+                               cmplen++;
+                       if (strncmp(filename, noChecksumFiles[excludeIdx].name,
+                                               cmplen) == 0)
                                return false;
+               }
 
                return true;
        }
index b7d36b65dd7dedb5f74a20e4aa24f75ac2782735..3c70499febfc460d1150408bf9f559344001961a 100644 (file)
@@ -6,7 +6,7 @@ use File::Basename qw(basename dirname);
 use File::Path qw(rmtree);
 use PostgresNode;
 use TestLib;
-use Test::More tests => 106;
+use Test::More tests => 107;
 
 program_help_ok('pg_basebackup');
 program_version_ok('pg_basebackup');
@@ -65,8 +65,8 @@ $node->restart;
 
 # Write some files to test that they are not copied.
 foreach my $filename (
-       qw(backup_label tablespace_map postgresql.auto.conf.tmp current_logfiles.tmp)
-  )
+       qw(backup_label tablespace_map postgresql.auto.conf.tmp
+       current_logfiles.tmp global/pg_internal.init.123))
 {
        open my $file, '>>', "$pgdata/$filename";
        print $file "DONOTCOPY";
@@ -135,7 +135,7 @@ foreach my $dirname (
 # These files should not be copied.
 foreach my $filename (
        qw(postgresql.auto.conf.tmp postmaster.opts postmaster.pid tablespace_map current_logfiles.tmp
-       global/pg_internal.init))
+       global/pg_internal.init global/pg_internal.init.123))
 {
        ok(!-f "$tempdir/backup/$filename", "$filename not copied");
 }
index 46ee1f1dc31970838594cec1f171940a16939b23..9bd0bf947f7fa77ebb7a585c4a459b2f1fb194ad 100644 (file)
@@ -91,21 +91,32 @@ usage(void)
        printf(_("Report bugs to <pgsql-bugs@lists.postgresql.org>.\n"));
 }
 
+/*
+ * Definition of one element part of an exclusion list, used for files
+ * to exclude from checksum validation.  "name" is the name of the file
+ * or path to check for exclusion.  If "match_prefix" is true, any items
+ * matching the name as prefix are excluded.
+ */
+struct exclude_list_item
+{
+       const char *name;
+       bool            match_prefix;
+};
+
 /*
  * List of files excluded from checksum validation.
  *
  * Note: this list should be kept in sync with what basebackup.c includes.
  */
-static const char *const skip[] = {
-       "pg_control",
-       "pg_filenode.map",
-       "pg_internal.init",
-       "PG_VERSION",
+static const struct exclude_list_item skip[] = {
+       {"pg_control", false},
+       {"pg_filenode.map", false},
+       {"pg_internal.init", true},
+       {"PG_VERSION", false},
 #ifdef EXEC_BACKEND
-       "config_exec_params",
-       "config_exec_params.new",
+       {"config_exec_params", true},
 #endif
-       NULL,
+       {NULL, false}
 };
 
 /*
@@ -157,11 +168,17 @@ progress_report(bool force)
 static bool
 skipfile(const char *fn)
 {
-       const char *const *f;
+       int                     excludeIdx;
+
+       for (excludeIdx = 0; skip[excludeIdx].name != NULL; excludeIdx++)
+       {
+               int                     cmplen = strlen(skip[excludeIdx].name);
 
-       for (f = skip; *f; f++)
-               if (strcmp(*f, fn) == 0)
+               if (!skip[excludeIdx].match_prefix)
+                       cmplen++;
+               if (strncmp(skip[excludeIdx].name, fn, cmplen) == 0)
                        return true;
+       }
 
        return false;
 }
index 59228b916cb64a9cf39d9f5615cd1d6e3d513b0d..83a730ea94784d85a5616ad61f6acee222f4382d 100644 (file)
@@ -111,7 +111,9 @@ append_to_file "$pgdata/global/99999_vm.123",   "";
 # should be ignored by the scan.
 append_to_file "$pgdata/global/pgsql_tmp_123", "foo";
 mkdir "$pgdata/global/pgsql_tmp";
-append_to_file "$pgdata/global/pgsql_tmp/1.1", "foo";
+append_to_file "$pgdata/global/pgsql_tmp/1.1",        "foo";
+append_to_file "$pgdata/global/pg_internal.init",     "foo";
+append_to_file "$pgdata/global/pg_internal.init.123", "foo";
 
 # Enable checksums.
 command_ok([ 'pg_checksums', '--enable', '--no-sync', '-D', $pgdata ],
index fd14844eecffc3370a773575d965732fe3348d16..9088f1f80fcb172527b0931aab66242498863b38 100644 (file)
@@ -30,6 +30,18 @@ static int   final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
 
+/*
+ * Definition of one element part of an exclusion list, used to exclude
+ * contents when rewinding.  "name" is the name of the file or path to
+ * check for exclusion.  If "match_prefix" is true, any items matching
+ * the name as prefix are excluded.
+ */
+struct exclude_list_item
+{
+       const char *name;
+       bool            match_prefix;
+};
+
 /*
  * The contents of these directories are removed or recreated during server
  * start so they are not included in data processed by pg_rewind.
@@ -78,32 +90,34 @@ static const char *excludeDirContents[] =
 };
 
 /*
- * List of files excluded from filemap processing.
+ * List of files excluded from filemap processing.   Files are excluded
+ * if their prefix match.
  */
-static const char *excludeFiles[] =
+static const struct exclude_list_item excludeFiles[] =
 {
        /* Skip auto conf temporary file. */
-       "postgresql.auto.conf.tmp", /* defined as PG_AUTOCONF_FILENAME */
+       {"postgresql.auto.conf.tmp", false},    /* defined as PG_AUTOCONF_FILENAME */
 
        /* Skip current log file temporary file */
-       "current_logfiles.tmp",         /* defined as LOG_METAINFO_DATAFILE_TMP */
+       {"current_logfiles.tmp", false},        /* defined as
+                                                                                * LOG_METAINFO_DATAFILE_TMP */
 
        /* Skip relation cache because it is rebuilt on startup */
-       "pg_internal.init",                     /* defined as RELCACHE_INIT_FILENAME */
+       {"pg_internal.init", true}, /* defined as RELCACHE_INIT_FILENAME */
 
        /*
         * If there's a backup_label or tablespace_map file, it belongs to a
         * backup started by the user with pg_start_backup().  It is *not* correct
         * for this backup.  Our backup_label is written later on separately.
         */
-       "backup_label",                         /* defined as BACKUP_LABEL_FILE */
-       "tablespace_map",                       /* defined as TABLESPACE_MAP */
+       {"backup_label", false},        /* defined as BACKUP_LABEL_FILE */
+       {"tablespace_map", false},      /* defined as TABLESPACE_MAP */
 
-       "postmaster.pid",
-       "postmaster.opts",
+       {"postmaster.pid", false},
+       {"postmaster.opts", false},
 
        /* end of list */
-       NULL
+       {NULL, false}
 };
 
 /*
@@ -496,14 +510,19 @@ check_file_excluded(const char *path, bool is_source)
        const char *filename;
 
        /* check individual files... */
-       for (excludeIdx = 0; excludeFiles[excludeIdx] != NULL; excludeIdx++)
+       for (excludeIdx = 0; excludeFiles[excludeIdx].name != NULL; excludeIdx++)
        {
+               int                     cmplen = strlen(excludeFiles[excludeIdx].name);
+
                filename = last_dir_separator(path);
                if (filename == NULL)
                        filename = path;
                else
                        filename++;
-               if (strcmp(filename, excludeFiles[excludeIdx]) == 0)
+
+               if (!excludeFiles[excludeIdx].match_prefix)
+                       cmplen++;
+               if (strncmp(filename, excludeFiles[excludeIdx].name, cmplen) == 0)
                {
                        if (is_source)
                                pg_log_debug("entry \"%s\" excluded from source file list",