Prevent running pg_resetwal/pg_resetxlog against wrong-version data dirs.

pg_resetwal (formerly pg_resetxlog) doesn't insist on finding a matching
version number in pg_control, and that seems like an important thing to
preserve since recovering from corrupt pg_control is a prime reason to
need to run it.  However, that means you can try to run it against a
data directory of a different major version, which is at best useless
and at worst disastrous.  So as to provide some protection against that
type of pilot error, inspect PG_VERSION at startup and refuse to do
anything if it doesn't match.  PG_VERSION is read-only after initdb,
so it's unlikely to get corrupted, and even if it were corrupted it would
be easy to fix by hand.

This hazard has been there all along, so back-patch to all supported
branches.

Michael Paquier, with some kibitzing by me

Discussion: https://postgr.es/m/f4b8eb91-b934-8a0d-b3cc-68f06e2279d1@enterprisedb.com
This commit is contained in:
Tom Lane 2017-05-29 17:08:16 -04:00
parent ce50945295
commit f3db7f164a
2 changed files with 74 additions and 1 deletions

View File

@ -281,6 +281,11 @@ PostgreSQL documentation
<command>pg_resetwal</command> to run. But before you do
so, make doubly certain that there is no server process still alive.
</para>
<para>
<command>pg_resetwal</command> works only with servers of the same
major version.
</para>
</refsect1>
<refsect1>

View File

@ -71,6 +71,7 @@ static MultiXactOffset set_mxoff = (MultiXactOffset) -1;
static uint32 minXlogTli = 0;
static XLogSegNo minXlogSegNo = 0;
static void CheckDataVersion(void);
static bool ReadControlFile(void);
static void GuessControlValues(void);
static void PrintControlValues(bool guessed);
@ -319,6 +320,9 @@ main(int argc, char *argv[])
exit(1);
}
/* Check that data directory matches our server version */
CheckDataVersion();
/*
* Check for a postmaster lock file --- if there is one, refuse to
* proceed, on grounds we might be interfering with a live installation.
@ -452,6 +456,70 @@ main(int argc, char *argv[])
}
/*
* Look at the version string stored in PG_VERSION and decide if this utility
* can be run safely or not.
*
* We don't want to inject pg_control and WAL files that are for a different
* major version; that can't do anything good. Note that we don't treat
* mismatching version info in pg_control as a reason to bail out, because
* recovering from a corrupted pg_control is one of the main reasons for this
* program to exist at all. However, PG_VERSION is unlikely to get corrupted,
* and if it were it would be easy to fix by hand. So let's make this check
* to prevent simple user errors.
*/
static void
CheckDataVersion(void)
{
const char *ver_file = "PG_VERSION";
FILE *ver_fd;
char rawline[64];
int len;
if ((ver_fd = fopen(ver_file, "r")) == NULL)
{
fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
progname, ver_file, strerror(errno));
exit(1);
}
/* version number has to be the first line read */
if (!fgets(rawline, sizeof(rawline), ver_fd))
{
if (!ferror(ver_fd))
{
fprintf(stderr, _("%s: unexpected empty file \"%s\"\n"),
progname, ver_file);
}
else
{
fprintf(stderr, _("%s: could not read file \"%s\": %s\n"),
progname, ver_file, strerror(errno));
}
exit(1);
}
/* remove trailing newline, handling Windows newlines as well */
len = strlen(rawline);
if (len > 0 && rawline[len - 1] == '\n')
{
rawline[--len] = '\0';
if (len > 0 && rawline[len - 1] == '\r')
rawline[--len] = '\0';
}
if (strcmp(rawline, PG_MAJORVERSION) != 0)
{
fprintf(stderr, _("%s: data directory is of wrong version\n"
"File \"%s\" contains \"%s\", which is not compatible with this program's version \"%s\".\n"),
progname, ver_file, rawline, PG_MAJORVERSION);
exit(1);
}
fclose(ver_fd);
}
/*
* Try to read the existing pg_control file.
*
@ -521,7 +589,7 @@ ReadControlFile(void)
}
/* Looks like it's a mess. */
fprintf(stderr, _("%s: pg_control exists but is broken or unknown version; ignoring it\n"),
fprintf(stderr, _("%s: pg_control exists but is broken or wrong version; ignoring it\n"),
progname);
return false;
}