aboutsummaryrefslogtreecommitdiff
path: root/src/bin/pg_checksums/pg_checksums.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/pg_checksums/pg_checksums.c')
-rw-r--r--src/bin/pg_checksums/pg_checksums.c358
1 files changed, 358 insertions, 0 deletions
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
new file mode 100644
index 00000000000..6571c342111
--- /dev/null
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -0,0 +1,358 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_checksums.c
+ * Verifies page level checksums in an offline cluster.
+ *
+ * Copyright (c) 2010-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/bin/pg_checksums/pg_checksums.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "catalog/pg_control.h"
+#include "common/controldata_utils.h"
+#include "getopt_long.h"
+#include "pg_getopt.h"
+#include "storage/bufpage.h"
+#include "storage/checksum.h"
+#include "storage/checksum_impl.h"
+#include "storage/fd.h"
+
+
+static int64 files = 0;
+static int64 blocks = 0;
+static int64 badblocks = 0;
+static ControlFileData *ControlFile;
+
+static char *only_relfilenode = NULL;
+static bool verbose = false;
+
+static const char *progname;
+
+static void
+usage(void)
+{
+ printf(_("%s verifies data checksums in a PostgreSQL database cluster.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... [DATADIR]\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" [-D, --pgdata=]DATADIR data directory\n"));
+ printf(_(" -v, --verbose output verbose messages\n"));
+ printf(_(" -r RELFILENODE check only relation with specified relfilenode\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+ printf(_("\nIf no data directory (DATADIR) is specified, "
+ "the environment variable PGDATA\nis used.\n\n"));
+ printf(_("Report bugs to <pgsql-bugs@lists.postgresql.org>.\n"));
+}
+
+/*
+ * 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",
+#ifdef EXEC_BACKEND
+ "config_exec_params",
+ "config_exec_params.new",
+#endif
+ NULL,
+};
+
+static bool
+skipfile(const char *fn)
+{
+ const char *const *f;
+
+ for (f = skip; *f; f++)
+ if (strcmp(*f, fn) == 0)
+ return true;
+
+ return false;
+}
+
+static void
+scan_file(const char *fn, BlockNumber segmentno)
+{
+ PGAlignedBlock buf;
+ PageHeader header = (PageHeader) buf.data;
+ int f;
+ BlockNumber blockno;
+
+ f = open(fn, O_RDONLY | PG_BINARY, 0);
+ if (f < 0)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
+ progname, fn, strerror(errno));
+ exit(1);
+ }
+
+ files++;
+
+ for (blockno = 0;; blockno++)
+ {
+ uint16 csum;
+ int r = read(f, buf.data, BLCKSZ);
+
+ if (r == 0)
+ break;
+ if (r != BLCKSZ)
+ {
+ fprintf(stderr, _("%s: could not read block %u in file \"%s\": read %d of %d\n"),
+ progname, blockno, fn, r, BLCKSZ);
+ exit(1);
+ }
+ blocks++;
+
+ /* New pages have no checksum yet */
+ if (PageIsNew(header))
+ continue;
+
+ csum = pg_checksum_page(buf.data, blockno + segmentno * RELSEG_SIZE);
+ if (csum != header->pd_checksum)
+ {
+ if (ControlFile->data_checksum_version == PG_DATA_CHECKSUM_VERSION)
+ fprintf(stderr, _("%s: checksum verification failed in file \"%s\", block %u: calculated checksum %X but block contains %X\n"),
+ progname, fn, blockno, csum, header->pd_checksum);
+ badblocks++;
+ }
+ }
+
+ if (verbose)
+ fprintf(stderr,
+ _("%s: checksums verified in file \"%s\"\n"), progname, fn);
+
+ close(f);
+}
+
+static void
+scan_directory(const char *basedir, const char *subdir)
+{
+ char path[MAXPGPATH];
+ DIR *dir;
+ struct dirent *de;
+
+ snprintf(path, sizeof(path), "%s/%s", basedir, subdir);
+ dir = opendir(path);
+ if (!dir)
+ {
+ fprintf(stderr, _("%s: could not open directory \"%s\": %s\n"),
+ progname, path, strerror(errno));
+ exit(1);
+ }
+ while ((de = readdir(dir)) != NULL)
+ {
+ char fn[MAXPGPATH];
+ struct stat st;
+
+ if (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0)
+ continue;
+
+ /* Skip temporary files */
+ if (strncmp(de->d_name,
+ PG_TEMP_FILE_PREFIX,
+ strlen(PG_TEMP_FILE_PREFIX)) == 0)
+ continue;
+
+ /* Skip temporary folders */
+ if (strncmp(de->d_name,
+ PG_TEMP_FILES_DIR,
+ strlen(PG_TEMP_FILES_DIR)) == 0)
+ return;
+
+ snprintf(fn, sizeof(fn), "%s/%s", path, de->d_name);
+ if (lstat(fn, &st) < 0)
+ {
+ fprintf(stderr, _("%s: could not stat file \"%s\": %s\n"),
+ progname, fn, strerror(errno));
+ exit(1);
+ }
+ if (S_ISREG(st.st_mode))
+ {
+ char fnonly[MAXPGPATH];
+ char *forkpath,
+ *segmentpath;
+ BlockNumber segmentno = 0;
+
+ if (skipfile(de->d_name))
+ continue;
+
+ /*
+ * Cut off at the segment boundary (".") to get the segment number
+ * in order to mix it into the checksum. Then also cut off at the
+ * fork boundary, to get the relfilenode the file belongs to for
+ * filtering.
+ */
+ strlcpy(fnonly, de->d_name, sizeof(fnonly));
+ segmentpath = strchr(fnonly, '.');
+ if (segmentpath != NULL)
+ {
+ *segmentpath++ = '\0';
+ segmentno = atoi(segmentpath);
+ if (segmentno == 0)
+ {
+ fprintf(stderr, _("%s: invalid segment number %d in file name \"%s\"\n"),
+ progname, segmentno, fn);
+ exit(1);
+ }
+ }
+
+ forkpath = strchr(fnonly, '_');
+ if (forkpath != NULL)
+ *forkpath++ = '\0';
+
+ if (only_relfilenode && strcmp(only_relfilenode, fnonly) != 0)
+ /* Relfilenode not to be included */
+ continue;
+
+ scan_file(fn, segmentno);
+ }
+#ifndef WIN32
+ else if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
+#else
+ else if (S_ISDIR(st.st_mode) || pgwin32_is_junction(fn))
+#endif
+ scan_directory(path, de->d_name);
+ }
+ closedir(dir);
+}
+
+int
+main(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"pgdata", required_argument, NULL, 'D'},
+ {"verbose", no_argument, NULL, 'v'},
+ {NULL, 0, NULL, 0}
+ };
+
+ char *DataDir = NULL;
+ int c;
+ int option_index;
+ bool crc_ok;
+
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_checksums"));
+
+ progname = get_progname(argv[0]);
+
+ if (argc > 1)
+ {
+ if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+ {
+ usage();
+ exit(0);
+ }
+ if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+ {
+ puts("pg_checksums (PostgreSQL) " PG_VERSION);
+ exit(0);
+ }
+ }
+
+ while ((c = getopt_long(argc, argv, "D:r:v", long_options, &option_index)) != -1)
+ {
+ switch (c)
+ {
+ case 'v':
+ verbose = true;
+ break;
+ case 'D':
+ DataDir = optarg;
+ break;
+ case 'r':
+ if (atoi(optarg) == 0)
+ {
+ fprintf(stderr, _("%s: invalid relfilenode specification, must be numeric: %s\n"), progname, optarg);
+ exit(1);
+ }
+ only_relfilenode = pstrdup(optarg);
+ break;
+ default:
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+ exit(1);
+ }
+ }
+
+ if (DataDir == NULL)
+ {
+ if (optind < argc)
+ DataDir = argv[optind++];
+ else
+ DataDir = getenv("PGDATA");
+
+ /* If no DataDir was specified, and none could be found, error out */
+ if (DataDir == NULL)
+ {
+ fprintf(stderr, _("%s: no data directory specified\n"), progname);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+ exit(1);
+ }
+ }
+
+ /* Complain if any arguments remain */
+ if (optind < argc)
+ {
+ fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"),
+ progname, argv[optind]);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+
+ /* Check if cluster is running */
+ ControlFile = get_controlfile(DataDir, progname, &crc_ok);
+ if (!crc_ok)
+ {
+ fprintf(stderr, _("%s: pg_control CRC value is incorrect\n"), progname);
+ exit(1);
+ }
+
+ if (ControlFile->pg_control_version != PG_CONTROL_VERSION)
+ {
+ fprintf(stderr, _("%s: cluster is not compatible with this version of pg_checksums\n"),
+ progname);
+ exit(1);
+ }
+
+ if (ControlFile->state != DB_SHUTDOWNED &&
+ ControlFile->state != DB_SHUTDOWNED_IN_RECOVERY)
+ {
+ fprintf(stderr, _("%s: cluster must be shut down to verify checksums\n"), progname);
+ exit(1);
+ }
+
+ if (ControlFile->data_checksum_version == 0)
+ {
+ fprintf(stderr, _("%s: data checksums are not enabled in cluster\n"), progname);
+ exit(1);
+ }
+
+ /* Scan all files */
+ scan_directory(DataDir, "global");
+ scan_directory(DataDir, "base");
+ scan_directory(DataDir, "pg_tblspc");
+
+ printf(_("Checksum scan completed\n"));
+ printf(_("Data checksum version: %d\n"), ControlFile->data_checksum_version);
+ printf(_("Files scanned: %s\n"), psprintf(INT64_FORMAT, files));
+ printf(_("Blocks scanned: %s\n"), psprintf(INT64_FORMAT, blocks));
+ printf(_("Bad checksums: %s\n"), psprintf(INT64_FORMAT, badblocks));
+
+ if (badblocks > 0)
+ return 1;
+
+ return 0;
+}