diff options
| author | Magnus Hagander | 2018-04-05 19:57:26 +0000 |
|---|---|---|
| committer | Magnus Hagander | 2018-04-05 20:04:48 +0000 |
| commit | 1fde38beaa0c3e66c340efc7cc0dc272d6254bb0 (patch) | |
| tree | 1e8291cd8523789d919e239e92aa3ecd6aa749de /src/bin | |
| parent | c39e903d510064e4415bbadb43e34f6998351cca (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/Makefile | 1 | ||||
| -rw-r--r-- | src/bin/pg_upgrade/controldata.c | 9 | ||||
| -rw-r--r-- | src/bin/pg_upgrade/pg_upgrade.h | 2 | ||||
| -rw-r--r-- | src/bin/pg_verify_checksums/.gitignore | 1 | ||||
| -rw-r--r-- | src/bin/pg_verify_checksums/Makefile | 36 | ||||
| -rw-r--r-- | src/bin/pg_verify_checksums/pg_verify_checksums.c | 315 |
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; +} |
