1 /*
2 * pg_verify_checksums
3 *
4 * Verifies page level checksums in an offline cluster
5 *
6 * Copyright (c) 2010-2018, PostgreSQL Global Development Group
7 *
8 * src/bin/pg_verify_checksums/pg_verify_checksums.c
9 */
10 #include "postgres_fe.h"
11
12 #include <dirent.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15
16 #include "catalog/pg_control.h"
17 #include "common/controldata_utils.h"
18 #include "common/relpath.h"
19 #include "getopt_long.h"
20 #include "pg_getopt.h"
21 #include "storage/bufpage.h"
22 #include "storage/checksum.h"
23 #include "storage/checksum_impl.h"
24 #include "storage/fd.h"
25
26
27 static int64 files = 0;
28 static int64 blocks = 0;
29 static int64 badblocks = 0;
30 static ControlFileData *ControlFile;
31
32 static char *only_relfilenode = NULL;
33 static bool verbose = false;
34
35 static const char *progname;
36
37 static void
38 usage(void)
39 {
40 printf(_("%s verifies data checksums in a PostgreSQL database cluster.\n\n"), progname);
41 printf(_("Usage:\n"));
42 printf(_(" %s [OPTION]... [DATADIR]\n"), progname);
43 printf(_("\nOptions:\n"));
44 printf(_(" [-D, --pgdata=]DATADIR data directory\n"));
45 printf(_(" -v, --verbose output verbose messages\n"));
46 printf(_(" -r RELFILENODE check only relation with specified relfilenode\n"));
47 printf(_(" -V, --version output version information, then exit\n"));
48 printf(_(" -?, --help show this help, then exit\n"));
49 printf(_("\nIf no data directory (DATADIR) is specified, "
50 "the environment variable PGDATA\nis used.\n\n"));
51 printf(_("Report bugs to <pgsql-bugs@postgresql.org>.\n"));
52 }
53
54 /*
55 * Definition of one element part of an exclusion list, used for files
56 * to exclude from checksum validation. "name" is the name of the file
57 * or path to check for exclusion. If "match_prefix" is true, any items
58 * matching the name as prefix are excluded.
59 */
60 struct exclude_list_item
61 {
62 const char *name;
63 bool match_prefix;
64 };
65
66 /*
67 * List of files excluded from checksum validation.
68 *
69 * Note: this list should be kept in sync with what basebackup.c includes.
70 */
xml_spec_editor_class_init(XmlSpecEditorClass * klass)71 static const struct exclude_list_item skip[] = {
72 {"pg_control", false},
73 {"pg_filenode.map", false},
74 {"pg_internal.init", true},
75 {"PG_VERSION", false},
76 #ifdef EXEC_BACKEND
77 {"config_exec_params", true},
78 #endif
79 {NULL, false}
80 };
81
xml_spec_editor_grab_focus(GtkWidget * widget)82 static bool
83 skipfile(const char *fn)
84 {
85 int excludeIdx;
86
87 for (excludeIdx = 0; skip[excludeIdx].name != NULL; excludeIdx++)
88 {
89 int cmplen = strlen(skip[excludeIdx].name);
90
91 if (!skip[excludeIdx].match_prefix)
92 cmplen++;
93 if (strncmp(skip[excludeIdx].name, fn, cmplen) == 0)
94 return true;
95 }
96
97 return false;
98 }
99
xml_spec_editor_get_type(void)100 static void
101 scan_file(const char *fn, BlockNumber segmentno)
102 {
103 PGAlignedBlock buf;
104 PageHeader header = (PageHeader) buf.data;
105 int f;
106 BlockNumber blockno;
107
108 f = open(fn, O_RDONLY | PG_BINARY);
109 if (f < 0)
110 {
111 fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
112 progname, fn, strerror(errno));
113 exit(1);
114 }
115
116 files++;
117
118 for (blockno = 0;; blockno++)
119 {
120 uint16 csum;
121 int r = read(f, buf.data, BLCKSZ);
122
123 if (r == 0)
124 break;
125 if (r != BLCKSZ)
126 {
127 fprintf(stderr, _("%s: could not read block %u in file \"%s\": read %d of %d\n"),
128 progname, blockno, fn, r, BLCKSZ);
129 exit(1);
130 }
131 blocks++;
132
133 /* New pages have no checksum yet */
134 if (PageIsNew(header))
135 continue;
136
137 csum = pg_checksum_page(buf.data, blockno + segmentno * RELSEG_SIZE);
138 if (csum != header->pd_checksum)
139 {
140 if (ControlFile->data_checksum_version == PG_DATA_CHECKSUM_VERSION)
141 fprintf(stderr, _("%s: checksum verification failed in file \"%s\", block %u: calculated checksum %X but block contains %X\n"),
142 progname, fn, blockno, csum, header->pd_checksum);
143 badblocks++;
144 }
145 }
146
147 if (verbose)
148 fprintf(stderr,
149 _("%s: checksums verified in file \"%s\"\n"), progname, fn);
150
151 close(f);
152 }
153
154 static void
155 scan_directory(const char *basedir, const char *subdir)
156 {
157 char path[MAXPGPATH];
158 DIR *dir;
159 struct dirent *de;
160
161 snprintf(path, sizeof(path), "%s/%s", basedir, subdir);
162 dir = opendir(path);
163 if (!dir)
164 {
165 fprintf(stderr, _("%s: could not open directory \"%s\": %s\n"),
166 progname, path, strerror(errno));
167 exit(1);
168 }
169 while ((de = readdir(dir)) != NULL)
170 {
171 char fn[MAXPGPATH];
172 struct stat st;
173
174 if (strcmp(de->d_name, ".") == 0 ||
175 strcmp(de->d_name, "..") == 0)
176 continue;
177
178 /* Skip temporary files */
179 if (strncmp(de->d_name,
180 PG_TEMP_FILE_PREFIX,
181 strlen(PG_TEMP_FILE_PREFIX)) == 0)
182 continue;
183
184 /* Skip temporary folders */
185 if (strncmp(de->d_name,
186 PG_TEMP_FILES_DIR,
187 strlen(PG_TEMP_FILES_DIR)) == 0)
188 continue;
189
190 snprintf(fn, sizeof(fn), "%s/%s", path, de->d_name);
191 if (lstat(fn, &st) < 0)
192 {
193 fprintf(stderr, _("%s: could not stat file \"%s\": %s\n"),
194 progname, fn, strerror(errno));
195 exit(1);
196 }
197 if (S_ISREG(st.st_mode))
198 {
199 char fnonly[MAXPGPATH];
200 char *forkpath,
201 *segmentpath;
202 BlockNumber segmentno = 0;
203
204 if (skipfile(de->d_name))
205 continue;
206
207 /*
208 * Cut off at the segment boundary (".") to get the segment number
209 * in order to mix it into the checksum. Then also cut off at the
210 * fork boundary, to get the relfilenode the file belongs to for
211 * filtering.
212 */
213 strlcpy(fnonly, de->d_name, sizeof(fnonly));
214 segmentpath = strchr(fnonly, '.');
215 if (segmentpath != NULL)
216 {
217 *segmentpath++ = '\0';
218 segmentno = atoi(segmentpath);
219 if (segmentno == 0)
220 {
221 fprintf(stderr, _("%s: invalid segment number %d in file name \"%s\"\n"),
222 progname, segmentno, fn);
223 exit(1);
224 }
225 }
226
227 forkpath = strchr(fnonly, '_');
228 if (forkpath != NULL)
229 *forkpath++ = '\0';
230
231 if (only_relfilenode && strcmp(only_relfilenode, fnonly) != 0)
232 /* Relfilenode not to be included */
233 continue;
234
235 scan_file(fn, segmentno);
236 }
237 #ifndef WIN32
238 else if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
239 #else
240 else if (S_ISDIR(st.st_mode) || pgwin32_is_junction(fn))
241 #endif
242 {
243 /*
244 * If going through the entries of pg_tblspc, we assume to operate
245 * on tablespace locations where only TABLESPACE_VERSION_DIRECTORY
246 * is valid, resolving the linked locations and dive into them
247 * directly.
248 */
249 if (strncmp("pg_tblspc", subdir, strlen("pg_tblspc")) == 0)
250 {
251 char tblspc_path[MAXPGPATH];
252 struct stat tblspc_st;
editor_changed_cb(G_GNUC_UNUSED GtkTextBuffer * buffer,XmlSpecEditor * sped)253
254 /*
255 * Resolve tablespace location path and check whether
256 * TABLESPACE_VERSION_DIRECTORY exists. Not finding a valid
257 * location is unexpected, since there should be no orphaned
258 * links and no links pointing to something else than a
259 * directory.
260 */
261 snprintf(tblspc_path, sizeof(tblspc_path), "%s/%s/%s",
262 path, de->d_name, TABLESPACE_VERSION_DIRECTORY);
263
264 if (lstat(tblspc_path, &tblspc_st) < 0)
265 {
266 fprintf(stderr, _("%s: could not stat file \"%s\": %s\n"),
267 progname, tblspc_path, strerror(errno));
268 exit(1);
269 }
270
271 /*
272 * Move backwards once as the scan needs to happen for the
273 * contents of TABLESPACE_VERSION_DIRECTORY.
274 */
275 snprintf(tblspc_path, sizeof(tblspc_path), "%s/%s",
276 path, de->d_name);
277
278 /* Looks like a valid tablespace location */
279 scan_directory(tblspc_path,
280 TABLESPACE_VERSION_DIRECTORY);
281 }
282 else
283 {
284 scan_directory(path, de->d_name);
285 }
286 }
287 }
288 closedir(dir);
289 }
data_source_changed_cb(DataSourceManager * mgr,G_GNUC_UNUSED DataSource * source,XmlSpecEditor * sped)290
291 int
292 main(int argc, char *argv[])
293 {
294 static struct option long_options[] = {
295 {"pgdata", required_argument, NULL, 'D'},
296 {"verbose", no_argument, NULL, 'v'},
297 {NULL, 0, NULL, 0}
298 };
299
300 char *DataDir = NULL;
301 int c;
302 int option_index;
303 bool crc_ok;
304
305 set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_verify_checksums"));
306
307 progname = get_progname(argv[0]);
308
309 if (argc > 1)
310 {
311 if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
312 {
313 usage();
314 exit(0);
315 }
316 if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
317 {
318 puts("pg_verify_checksums (PostgreSQL) " PG_VERSION);
319 exit(0);
320 }
321 }
322
323 while ((c = getopt_long(argc, argv, "D:r:v", long_options, &option_index)) != -1)
324 {
325 switch (c)
326 {
327 case 'v':
328 verbose = true;
329 break;
330 case 'D':
331 DataDir = optarg;
332 break;
333 case 'r':
334 if (atoi(optarg) == 0)
335 {
336 fprintf(stderr, _("%s: invalid relfilenode specification, must be numeric: %s\n"), progname, optarg);
337 exit(1);
338 }
339 only_relfilenode = pstrdup(optarg);
340 break;
341 default:
342 fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
343 exit(1);
344 }
345 }
346
347 if (DataDir == NULL)
348 {
349 if (optind < argc)
350 DataDir = argv[optind++];
351 else
352 DataDir = getenv("PGDATA");
353
354 /* If no DataDir was specified, and none could be found, error out */
355 if (DataDir == NULL)
356 {
357 fprintf(stderr, _("%s: no data directory specified\n"), progname);
358 fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
359 exit(1);
360 }
361 }
362
363 /* Complain if any arguments remain */
364 if (optind < argc)
365 {
366 fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"),
367 progname, argv[optind]);
368 fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
369 progname);
370 exit(1);
371 }
372
373 /* Check if cluster is running */
374 ControlFile = get_controlfile(DataDir, progname, &crc_ok);
375 if (!crc_ok)
376 {
377 fprintf(stderr, _("%s: pg_control CRC value is incorrect\n"), progname);
378 exit(1);
379 }
380
381 if (ControlFile->pg_control_version != PG_CONTROL_VERSION)
382 {
383 fprintf(stderr, _("%s: cluster is not compatible with this version of pg_verify_checksums\n"),
384 progname);
385 exit(1);
386 }
387
388 if (ControlFile->blcksz != BLCKSZ)
389 {
390 fprintf(stderr, _("%s: database cluster is not compatible\n"),
391 progname);
392 fprintf(stderr, _("The database cluster was initialized with block size %u, but pg_verify_checksums was compiled with block size %u.\n"),
393 ControlFile->blcksz, BLCKSZ);
394 exit(1);
395 }
396
397 if (ControlFile->state != DB_SHUTDOWNED &&
398 ControlFile->state != DB_SHUTDOWNED_IN_RECOVERY)
399 {
400 fprintf(stderr, _("%s: cluster must be shut down to verify checksums\n"), progname);
401 exit(1);
402 }
403
404 if (ControlFile->data_checksum_version == 0)
405 {
406 fprintf(stderr, _("%s: data checksums are not enabled in cluster\n"), progname);
407 exit(1);
408 }
409
410 /* Scan all files */
411 scan_directory(DataDir, "global");
412 scan_directory(DataDir, "base");
413 scan_directory(DataDir, "pg_tblspc");
414
415 printf(_("Checksum scan completed\n"));
416 printf(_("Data checksum version: %d\n"), ControlFile->data_checksum_version);
417 printf(_("Files scanned: %s\n"), psprintf(INT64_FORMAT, files));
418 printf(_("Blocks scanned: %s\n"), psprintf(INT64_FORMAT, blocks));
419 printf(_("Bad checksums: %s\n"), psprintf(INT64_FORMAT, badblocks));
420
421 if (badblocks > 0)
422 return 1;
423
424 return 0;
425 }
426