summaryrefslogtreecommitdiff
path: root/src/bin
diff options
context:
space:
mode:
authorMagnus Hagander2018-04-05 19:57:26 +0000
committerMagnus Hagander2018-04-05 20:04:48 +0000
commit1fde38beaa0c3e66c340efc7cc0dc272d6254bb0 (patch)
tree1e8291cd8523789d919e239e92aa3ecd6aa749de /src/bin
parentc39e903d510064e4415bbadb43e34f6998351cca (diff)
Allow on-line enabling and disabling of data checksums
This makes it possible to turn checksums on in a live cluster, without the previous need for dump/reload or logical replication (and to turn it off). Enabling checkusm starts a background process in the form of a launcher/worker combination that goes through the entire database and recalculates checksums on each and every page. Only when all pages have been checksummed are they fully enabled in the cluster. Any failure of the process will revert to checksums off and the process has to be started. This adds a new WAL record that indicates the state of checksums, so the process works across replicated clusters. Authors: Magnus Hagander and Daniel Gustafsson Review: Tomas Vondra, Michael Banck, Heikki Linnakangas, Andrey Borodin
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/Makefile1
-rw-r--r--src/bin/pg_upgrade/controldata.c9
-rw-r--r--src/bin/pg_upgrade/pg_upgrade.h2
-rw-r--r--src/bin/pg_verify_checksums/.gitignore1
-rw-r--r--src/bin/pg_verify_checksums/Makefile36
-rw-r--r--src/bin/pg_verify_checksums/pg_verify_checksums.c315
6 files changed, 363 insertions, 1 deletions
diff --git a/src/bin/Makefile b/src/bin/Makefile
index 3b35835abe3..8c11060a2f5 100644
--- a/src/bin/Makefile
+++ b/src/bin/Makefile
@@ -26,6 +26,7 @@ SUBDIRS = \
pg_test_fsync \
pg_test_timing \
pg_upgrade \
+ pg_verify_checksums \
pg_waldump \
pgbench \
psql \
diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c
index 0fe98a550e1..4bb2b7e6ec3 100644
--- a/src/bin/pg_upgrade/controldata.c
+++ b/src/bin/pg_upgrade/controldata.c
@@ -591,6 +591,15 @@ check_control_data(ControlData *oldctrl,
*/
/*
+ * If checksums have been turned on in the old cluster, but the
+ * checksumhelper have yet to finish, then disallow upgrading. The user
+ * should either let the process finish, or turn off checksums, before
+ * retrying.
+ */
+ if (oldctrl->data_checksum_version == 2)
+ pg_fatal("transition to data checksums not completed in old cluster\n");
+
+ /*
* We might eventually allow upgrades from checksum to no-checksum
* clusters.
*/
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7e5e9712947..449a703c475 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -226,7 +226,7 @@ typedef struct
uint32 large_object;
bool date_is_int;
bool float8_pass_by_value;
- bool data_checksum_version;
+ uint32 data_checksum_version;
} ControlData;
/*
diff --git a/src/bin/pg_verify_checksums/.gitignore b/src/bin/pg_verify_checksums/.gitignore
new file mode 100644
index 00000000000..d1dcdaf0dd4
--- /dev/null
+++ b/src/bin/pg_verify_checksums/.gitignore
@@ -0,0 +1 @@
+/pg_verify_checksums
diff --git a/src/bin/pg_verify_checksums/Makefile b/src/bin/pg_verify_checksums/Makefile
new file mode 100644
index 00000000000..d16261571f8
--- /dev/null
+++ b/src/bin/pg_verify_checksums/Makefile
@@ -0,0 +1,36 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/bin/pg_verify_checksums
+#
+# Copyright (c) 1998-2018, PostgreSQL Global Development Group
+#
+# src/bin/pg_verify_checksums/Makefile
+#
+#-------------------------------------------------------------------------
+
+PGFILEDESC = "pg_verify_checksums - verify data checksums in an offline cluster"
+PGAPPICON=win32
+
+subdir = src/bin/pg_verify_checksums
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS= pg_verify_checksums.o $(WIN32RES)
+
+all: pg_verify_checksums
+
+pg_verify_checksums: $(OBJS) | submake-libpgport
+ $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+install: all installdirs
+ $(INSTALL_PROGRAM) pg_verify_checksums$(X) '$(DESTDIR)$(bindir)/pg_verify_checksums$(X)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(bindir)/pg_verify_checksums$(X)'
+
+clean distclean maintainer-clean:
+ rm -f pg_verify_checksums$(X) $(OBJS)
+ rm -rf tmp_check
diff --git a/src/bin/pg_verify_checksums/pg_verify_checksums.c b/src/bin/pg_verify_checksums/pg_verify_checksums.c
new file mode 100644
index 00000000000..e37f39bd2a0
--- /dev/null
+++ b/src/bin/pg_verify_checksums/pg_verify_checksums.c
@@ -0,0 +1,315 @@
+/*
+ * pg_verify_checksums
+ *
+ * Verifies page level checksums in an offline cluster
+ *
+ * Copyright (c) 2010-2018, PostgreSQL Global Development Group
+ *
+ * src/bin/pg_verify_checksums/pg_verify_checksums.c
+ */
+
+#define FRONTEND 1
+
+#include "postgres.h"
+#include "catalog/pg_control.h"
+#include "common/controldata_utils.h"
+#include "storage/bufpage.h"
+#include "storage/checksum.h"
+#include "storage/checksum_impl.h"
+
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include "pg_getopt.h"
+
+
+static int64 files = 0;
+static int64 blocks = 0;
+static int64 badblocks = 0;
+static ControlFileData *ControlFile;
+
+static char *only_relfilenode = NULL;
+static bool debug = false;
+
+static const char *progname;
+
+static void
+usage()
+{
+ printf(_("%s verifies page level checksums in offline PostgreSQL database cluster.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION] [DATADIR]\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" [-D] DATADIR data directory\n"));
+ printf(_(" -f, force check even if checksums are disabled\n"));
+ printf(_(" -r relfilenode check only relation with specified relfilenode\n"));
+ printf(_(" -d debug output, listing all checked blocks\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@postgresql.org>.\n"));
+}
+
+static const char *skip[] = {
+ "pg_control",
+ "pg_filenode.map",
+ "pg_internal.init",
+ "PG_VERSION",
+ NULL,
+};
+
+static bool
+skipfile(char *fn)
+{
+ const char **f;
+
+ if (strcmp(fn, ".") == 0 ||
+ strcmp(fn, "..") == 0)
+ return true;
+
+ for (f = skip; *f; f++)
+ if (strcmp(*f, fn) == 0)
+ return true;
+ return false;
+}
+
+static void
+scan_file(char *fn, int segmentno)
+{
+ char buf[BLCKSZ];
+ PageHeader header = (PageHeader) buf;
+ int f;
+ int blockno;
+
+ f = open(fn, 0);
+ if (f < 0)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\": %m\n"), progname, fn);
+ exit(1);
+ }
+
+ files++;
+
+ for (blockno = 0;; blockno++)
+ {
+ uint16 csum;
+ int r = read(f, buf, BLCKSZ);
+
+ if (r == 0)
+ break;
+ if (r != BLCKSZ)
+ {
+ fprintf(stderr, _("%s: short read of block %d in file \"%s\", got only %d bytes\n"),
+ progname, blockno, fn, r);
+ exit(1);
+ }
+ blocks++;
+
+ csum = pg_checksum_page(buf, 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 %d: calculated checksum %X but expected %X\n"),
+ progname, fn, blockno, csum, header->pd_checksum);
+ badblocks++;
+ }
+ else if (debug)
+ fprintf(stderr, _("%s: checksum verified in file \"%s\", block %d: %X\n"),
+ progname, fn, blockno, csum);
+ }
+
+ close(f);
+}
+
+static void
+scan_directory(char *basedir, char *subdir)
+{
+ char path[MAXPGPATH];
+ DIR *dir;
+ struct dirent *de;
+
+ snprintf(path, MAXPGPATH, "%s/%s", basedir, subdir);
+ dir = opendir(path);
+ if (!dir)
+ {
+ fprintf(stderr, _("%s: could not open directory \"%s\": %m\n"),
+ progname, path);
+ exit(1);
+ }
+ while ((de = readdir(dir)) != NULL)
+ {
+ char fn[MAXPGPATH];
+ struct stat st;
+
+ if (skipfile(de->d_name))
+ continue;
+
+ snprintf(fn, MAXPGPATH, "%s/%s", path, de->d_name);
+ if (lstat(fn, &st) < 0)
+ {
+ fprintf(stderr, _("%s: could not stat file \"%s\": %m\n"),
+ progname, fn);
+ exit(1);
+ }
+ if (S_ISREG(st.st_mode))
+ {
+ char *forkpath,
+ *segmentpath;
+ int segmentno = 0;
+
+ /*
+ * 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.
+ */
+ segmentpath = strchr(de->d_name, '.');
+ if (segmentpath != NULL)
+ {
+ *segmentpath++ = '\0';
+ segmentno = atoi(segmentpath);
+ if (segmentno == 0)
+ {
+ fprintf(stderr, _("%s: invalid segment number %d in filename \"%s\"\n"),
+ progname, segmentno, fn);
+ exit(1);
+ }
+ }
+
+ forkpath = strchr(de->d_name, '_');
+ if (forkpath != NULL)
+ *forkpath++ = '\0';
+
+ if (only_relfilenode && strcmp(only_relfilenode, de->d_name) != 0)
+ /* Relfilenode not to be included */
+ continue;
+
+ scan_file(fn, segmentno);
+ }
+ else if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
+ scan_directory(path, de->d_name);
+ }
+ closedir(dir);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *DataDir = NULL;
+ bool force = false;
+ int c;
+ bool crc_ok;
+
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_verify_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_verify_checksums (PostgreSQL) " PG_VERSION);
+ exit(0);
+ }
+ }
+
+ while ((c = getopt(argc, argv, "D:fr:d")) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ debug = true;
+ break;
+ case 'D':
+ DataDir = optarg;
+ break;
+ case 'f':
+ force = true;
+ break;
+ case 'r':
+ if (atoi(optarg) <= 0)
+ {
+ fprintf(stderr, _("%s: invalid relfilenode: %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->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 && !force)
+ {
+ 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: %" INT64_MODIFIER "d\n"), files);
+ printf(_("Blocks scanned: %" INT64_MODIFIER "d\n"), blocks);
+ if (ControlFile->data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_VERSION)
+ printf(_("Blocks left in progress: %" INT64_MODIFIER "d\n"), badblocks);
+ else
+ printf(_("Bad checksums: %" INT64_MODIFIER "d\n"), badblocks);
+
+ if (badblocks > 0)
+ return 1;
+
+ return 0;
+}