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 <bossartn@amazon.com>, tests by me
This commit is contained in:
Peter Eisentraut 2018-03-21 12:14:53 -04:00
parent 56163004b8
commit 4731d848f2
2 changed files with 38 additions and 9 deletions

View File

@ -95,7 +95,6 @@ main(int argc, char *argv[])
char mock_auth_nonce_str[MOCK_AUTH_NONCE_LEN * 2 + 1]; char mock_auth_nonce_str[MOCK_AUTH_NONCE_LEN * 2 + 1];
const char *strftime_fmt = "%c"; const char *strftime_fmt = "%c";
const char *progname; const char *progname;
XLogSegNo segno;
char xlogfilename[MAXFNAMELEN]; char xlogfilename[MAXFNAMELEN];
int c; int c;
int i; int i;
@ -169,10 +168,11 @@ main(int argc, char *argv[])
WalSegSz = ControlFile->xlog_seg_size; WalSegSz = ControlFile->xlog_seg_size;
if (!IsValidWalSegSize(WalSegSz)) if (!IsValidWalSegSize(WalSegSz))
fprintf(stderr, printf(_("WARNING: invalid WAL segment size\n"
_("WARNING: WAL segment size specified, %d bytes, is not a power of two between 1MB and 1GB.\n" "The WAL segment size stored in the file, %d bytes, is not a power of two\n"
"The file is corrupt and the results below are untrustworthy.\n"), "between 1 MB and 1 GB. The file is corrupt and the results below are\n"
WalSegSz); "untrustworthy.\n\n"),
WalSegSz);
/* /*
* This slightly-chintzy coding will work as long as the control file * 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 * Calculate name of the WAL file containing the latest checkpoint's REDO
* start point. * 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); if (WalSegSz != 0)
XLogFileName(xlogfilename, ControlFile->checkPointCopy.ThisTimeLineID, {
segno, WalSegSz); 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 * Format system_identifier and mock_authentication_nonce separately to

View File

@ -2,7 +2,7 @@ use strict;
use warnings; use warnings;
use PostgresNode; use PostgresNode;
use TestLib; use TestLib;
use Test::More tests => 13; use Test::More tests => 17;
program_help_ok('pg_controldata'); program_help_ok('pg_controldata');
program_version_ok('pg_controldata'); program_version_ok('pg_controldata');
@ -16,3 +16,22 @@ $node->init;
command_like([ 'pg_controldata', $node->data_dir ], command_like([ 'pg_controldata', $node->data_dir ],
qr/checkpoint/, 'pg_controldata produces output'); 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');