From 4731d848f23e08a9396b4831d13fbb6dd460faf2 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Wed, 21 Mar 2018 12:14:53 -0400 Subject: [PATCH] pg_controldata: Prevent division-by-zero errors If the control file is corrupted and specifies the WAL segment size to be 0 bytes, calculating the latest checkpoint's REDO WAL file will fail with a division-by-zero error. Show it as "???" instead. Also reword the warning message a bit and send it to stdout, like the other pre-existing warning messages. Add some tests for dealing with a corrupted pg_control file. Author: Nathan Bossart , tests by me --- src/bin/pg_controldata/pg_controldata.c | 26 +++++++++++++------ .../pg_controldata/t/001_pg_controldata.pl | 21 ++++++++++++++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c index cc73b7d6c2..f9dc854b4a 100644 --- a/src/bin/pg_controldata/pg_controldata.c +++ b/src/bin/pg_controldata/pg_controldata.c @@ -95,7 +95,6 @@ main(int argc, char *argv[]) char mock_auth_nonce_str[MOCK_AUTH_NONCE_LEN * 2 + 1]; const char *strftime_fmt = "%c"; const char *progname; - XLogSegNo segno; char xlogfilename[MAXFNAMELEN]; int c; int i; @@ -169,10 +168,11 @@ main(int argc, char *argv[]) WalSegSz = ControlFile->xlog_seg_size; if (!IsValidWalSegSize(WalSegSz)) - fprintf(stderr, - _("WARNING: WAL segment size specified, %d bytes, is not a power of two between 1MB and 1GB.\n" - "The file is corrupt and the results below are untrustworthy.\n"), - WalSegSz); + printf(_("WARNING: invalid WAL segment size\n" + "The WAL segment size stored in the file, %d bytes, is not a power of two\n" + "between 1 MB and 1 GB. The file is corrupt and the results below are\n" + "untrustworthy.\n\n"), + WalSegSz); /* * This slightly-chintzy coding will work as long as the control file @@ -193,10 +193,20 @@ main(int argc, char *argv[]) /* * Calculate name of the WAL file containing the latest checkpoint's REDO * start point. + * + * A corrupted control file could report a WAL segment size of 0, and to + * guard against division by zero, we need to treat that specially. */ - XLByteToSeg(ControlFile->checkPointCopy.redo, segno, WalSegSz); - XLogFileName(xlogfilename, ControlFile->checkPointCopy.ThisTimeLineID, - segno, WalSegSz); + if (WalSegSz != 0) + { + XLogSegNo segno; + + XLByteToSeg(ControlFile->checkPointCopy.redo, segno, WalSegSz); + XLogFileName(xlogfilename, ControlFile->checkPointCopy.ThisTimeLineID, + segno, WalSegSz); + } + else + strcpy(xlogfilename, _("???")); /* * Format system_identifier and mock_authentication_nonce separately to diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl index 40405516ca..af9fad7a38 100644 --- a/src/bin/pg_controldata/t/001_pg_controldata.pl +++ b/src/bin/pg_controldata/t/001_pg_controldata.pl @@ -2,7 +2,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests => 13; +use Test::More tests => 17; program_help_ok('pg_controldata'); program_version_ok('pg_controldata'); @@ -16,3 +16,22 @@ $node->init; command_like([ 'pg_controldata', $node->data_dir ], qr/checkpoint/, 'pg_controldata produces output'); + + +# check with a corrupted pg_control + +my $pg_control = $node->data_dir . '/global/pg_control'; +my $size = (stat($pg_control))[7]; + +open my $fh, '>', $pg_control or BAIL_OUT($!); +binmode $fh; +# fill file with zeros +print $fh pack("x[$size]"); +close $fh; + +command_checks_all([ 'pg_controldata', $node->data_dir ], + 0, + [ qr/WARNING: Calculated CRC checksum does not match value stored in file/, + qr/WARNING: invalid WAL segment size/ ], + [ qr/^$/ ], + 'pg_controldata with corrupted pg_control');