Skip to content

Commit d55241a

Browse files
committed
Use whitelist to choose files scanned with pg_verify_checksums
The original implementation of pg_verify_checksums used a blacklist to decide which files should be skipped for scanning as they do not include data checksums, like pg_internal.init or pg_control. However, this missed two things: - Some files are created within builds of EXEC_BACKEND and these were not listed, causing failures on Windows. - Extensions may create custom files in data folders, causing the tool to equally fail. This commit switches to a whitelist-like method instead by checking if the files to scan are authorized relation files. This is close to a reverse-engineering of what is defined in relpath.c in charge of building the relation paths, and we could consider refactoring what this patch does so as all routines are in a single place. This is left for later. This is based on a suggestion from Andres Freund. TAP tests are updated so as multiple file patterns are tested. The bug has been spotted by various buildfarm members as a result of b34e84f which has introduced the TAP tests of pg_verify_checksums. Author: Michael Paquier Reviewed-by: Andrew Dunstan, Michael Banck Discussion: https://postgr.es/m/20181012005614.GC26424@paquier.xyz Backpatch-through: 11
1 parent 350410b commit d55241a

File tree

2 files changed

+116
-19
lines changed

2 files changed

+116
-19
lines changed

src/bin/pg_verify_checksums/pg_verify_checksums.c

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "catalog/pg_control.h"
1717
#include "common/controldata_utils.h"
18+
#include "common/relpath.h"
1819
#include "getopt_long.h"
1920
#include "pg_getopt.h"
2021
#include "storage/bufpage.h"
@@ -49,27 +50,69 @@ usage(void)
4950
printf(_("Report bugs to <pgsql-bugs@postgresql.org>.\n"));
5051
}
5152

52-
static const char *const skip[] = {
53-
"pg_control",
54-
"pg_filenode.map",
55-
"pg_internal.init",
56-
"PG_VERSION",
57-
NULL,
58-
};
59-
53+
/*
54+
* isRelFileName
55+
*
56+
* Check if the given file name is authorized for checksum verification.
57+
*/
6058
static bool
61-
skipfile(const char *fn)
59+
isRelFileName(const char *fn)
6260
{
63-
const char *const *f;
64-
65-
if (strcmp(fn, ".") == 0 ||
66-
strcmp(fn, "..") == 0)
61+
int pos;
62+
63+
/*----------
64+
* Only files including data checksums are authorized for verification.
65+
* This is guessed based on the file name by reverse-engineering
66+
* GetRelationPath() so make sure to update both code paths if any
67+
* updates are done. The following file name formats are allowed:
68+
* <digits>
69+
* <digits>.<segment>
70+
* <digits>_<forkname>
71+
* <digits>_<forkname>.<segment>
72+
*
73+
* Note that temporary files, beginning with 't', are also skipped.
74+
*
75+
*----------
76+
*/
77+
78+
/* A non-empty string of digits should follow */
79+
for (pos = 0; isdigit((unsigned char) fn[pos]); ++pos)
80+
;
81+
/* leave if no digits */
82+
if (pos == 0)
83+
return false;
84+
/* good to go if only digits */
85+
if (fn[pos] == '\0')
6786
return true;
6887

69-
for (f = skip; *f; f++)
70-
if (strcmp(*f, fn) == 0)
71-
return true;
72-
return false;
88+
/* Authorized fork files can be scanned */
89+
if (fn[pos] == '_')
90+
{
91+
int forkchar = forkname_chars(&fn[pos + 1], NULL);
92+
93+
if (forkchar <= 0)
94+
return false;
95+
96+
pos += forkchar + 1;
97+
}
98+
99+
/* Check for an optional segment number */
100+
if (fn[pos] == '.')
101+
{
102+
int segchar;
103+
104+
for (segchar = 1; isdigit((unsigned char) fn[pos + segchar]); ++segchar)
105+
;
106+
107+
if (segchar <= 1)
108+
return false;
109+
pos += segchar;
110+
}
111+
112+
/* Now this should be the end */
113+
if (fn[pos] != '\0')
114+
return false;
115+
return true;
73116
}
74117

75118
static void
@@ -146,7 +189,7 @@ scan_directory(const char *basedir, const char *subdir)
146189
char fn[MAXPGPATH];
147190
struct stat st;
148191

149-
if (skipfile(de->d_name))
192+
if (!isRelFileName(de->d_name))
150193
continue;
151194

152195
snprintf(fn, sizeof(fn), "%s/%s", path, de->d_name);

src/bin/pg_verify_checksums/t/002_actions.pl

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use warnings;
66
use PostgresNode;
77
use TestLib;
8-
use Test::More tests => 12;
8+
use Test::More tests => 36;
99

1010
# Initialize node with checksums enabled.
1111
my $node = get_new_node('node_checksum');
@@ -17,6 +17,31 @@
1717
qr/Data page checksum version:.*1/,
1818
'checksums enabled in control file');
1919

20+
# Add set of dummy files with some contents. These should not be scanned
21+
# by the tool.
22+
append_to_file "$pgdata/global/123.", "foo";
23+
append_to_file "$pgdata/global/123_", "foo";
24+
append_to_file "$pgdata/global/123_.", "foo";
25+
append_to_file "$pgdata/global/123.12t", "foo";
26+
append_to_file "$pgdata/global/foo", "foo2";
27+
append_to_file "$pgdata/global/t123", "bar";
28+
append_to_file "$pgdata/global/123a", "bar2";
29+
append_to_file "$pgdata/global/.123", "foobar";
30+
append_to_file "$pgdata/global/_fsm", "foobar2";
31+
append_to_file "$pgdata/global/_init", "foobar3";
32+
append_to_file "$pgdata/global/_vm.123", "foohoge";
33+
append_to_file "$pgdata/global/123_vm.123t", "foohoge2";
34+
35+
# Those are correct but empty files, so they should pass through.
36+
append_to_file "$pgdata/global/99999", "";
37+
append_to_file "$pgdata/global/99999.123", "";
38+
append_to_file "$pgdata/global/99999_fsm", "";
39+
append_to_file "$pgdata/global/99999_init", "";
40+
append_to_file "$pgdata/global/99999_vm", "";
41+
append_to_file "$pgdata/global/99999_init.123", "";
42+
append_to_file "$pgdata/global/99999_fsm.123", "";
43+
append_to_file "$pgdata/global/99999_vm.123", "";
44+
2045
# Checksums pass on a newly-created cluster
2146
command_ok(['pg_verify_checksums', '-D', $pgdata],
2247
"succeeds with offline cluster");
@@ -67,3 +92,32 @@
6792
[qr/Bad checksums:.*1/],
6893
[qr/checksum verification failed/],
6994
'fails for corrupted data on single relfilenode');
95+
96+
# Utility routine to check that pg_verify_checksums is able to detect
97+
# correctly-named relation files filled with some corrupted data.
98+
sub fail_corrupt
99+
{
100+
my $node = shift;
101+
my $file = shift;
102+
my $pgdata = $node->data_dir;
103+
104+
# Create the file with some dummy data in it.
105+
append_to_file "$pgdata/global/$file", "foo";
106+
107+
$node->command_checks_all([ 'pg_verify_checksums', '-D', $pgdata],
108+
1,
109+
[qr/^$/],
110+
[qr/could not read block/],
111+
"fails for corrupted data in $file");
112+
}
113+
114+
# Authorized relation files filled with corrupted data cause the
115+
# checksum checks to fail.
116+
fail_corrupt($node, "99999");
117+
fail_corrupt($node, "99999.123");
118+
fail_corrupt($node, "99999_fsm");
119+
fail_corrupt($node, "99999_init");
120+
fail_corrupt($node, "99999_vm");
121+
fail_corrupt($node, "99999_init.123");
122+
fail_corrupt($node, "99999_fsm.123");
123+
fail_corrupt($node, "99999_vm.123");

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy