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