Provide a HINT listing the allowed unit names when a GUC variable seems to

contain a wrong unit specification, per discussion.
In passing, fix the code to avoid unnecessary integer overflows when
converting units, and to detect overflows when they do occur.
This commit is contained in:
Tom Lane 2007-06-21 18:14:21 +00:00
parent 9f6aacd9e6
commit aa55d05571

View File

@ -10,7 +10,7 @@
* Written by Peter Eisentraut <peter_e@gmx.net>.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.400 2007/06/20 18:31:39 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.401 2007/06/21 18:14:21 tgl Exp $
*
*--------------------------------------------------------------------
*/
@ -94,8 +94,13 @@
#define KB_PER_GB (1024*1024)
#define MS_PER_S 1000
#define S_PER_MIN 60
#define MS_PER_MIN (1000 * 60)
#define MIN_PER_H 60
#define S_PER_H (60 * 60)
#define MS_PER_H (1000 * 60 * 60)
#define MIN_PER_D (60 * 24)
#define S_PER_D (60 * 60 * 24)
#define MS_PER_D (1000 * 60 * 60 * 24)
/* XXX these should appear in other modules' header files */
@ -3783,124 +3788,209 @@ parse_bool(const char *value, bool *result)
/*
* Try to parse value as an integer. The accepted formats are the
* usual decimal, octal, or hexadecimal formats. If the string parses
* okay, return true, else false. If result is not NULL, return the
* value there.
* usual decimal, octal, or hexadecimal formats, optionally followed by
* a unit name if "flags" indicates a unit is allowed.
*
* If the string parses okay, return true, else false.
* If okay and result is not NULL, return the value in *result.
* If not okay and hintmsg is not NULL, *hintmsg is set to a suitable
* HINT message, or NULL if no hint provided.
*/
static bool
parse_int(const char *value, int *result, int flags)
parse_int(const char *value, int *result, int flags, const char **hintmsg)
{
long val;
int64 val;
char *endptr;
/* To suppress compiler warnings, always set output params */
if (result)
*result = 0;
if (hintmsg)
*hintmsg = NULL;
/* We assume here that int64 is at least as wide as long */
errno = 0;
val = strtol(value, &endptr, 0);
if ((flags & GUC_UNIT_MEMORY) && endptr != value)
if (endptr == value)
return false; /* no HINT for integer syntax error */
if (errno == ERANGE || val != (int64) ((int32) val))
{
bool used = false;
while (*endptr == ' ')
endptr++;
if (strcmp(endptr, "kB") == 0)
{
used = true;
endptr += 2;
}
else if (strcmp(endptr, "MB") == 0)
{
val *= KB_PER_MB;
used = true;
endptr += 2;
}
else if (strcmp(endptr, "GB") == 0)
{
val *= KB_PER_GB;
used = true;
endptr += 2;
}
#if BLCKSZ < 1024
#error BLCKSZ must be >= 1024
#endif
if (used)
{
switch (flags & GUC_UNIT_MEMORY)
{
case GUC_UNIT_BLOCKS:
val /= (BLCKSZ / 1024);
break;
case GUC_UNIT_XBLOCKS:
val /= (XLOG_BLCKSZ / 1024);
break;
}
}
}
if ((flags & GUC_UNIT_TIME) && endptr != value)
{
bool used = false;
while (*endptr == ' ')
endptr++;
if (strcmp(endptr, "ms") == 0)
{
used = true;
endptr += 2;
}
else if (strcmp(endptr, "s") == 0)
{
val *= MS_PER_S;
used = true;
endptr += 1;
}
else if (strcmp(endptr, "min") == 0)
{
val *= MS_PER_MIN;
used = true;
endptr += 3;
}
else if (strcmp(endptr, "h") == 0)
{
val *= MS_PER_H;
used = true;
endptr += 1;
}
else if (strcmp(endptr, "d") == 0)
{
val *= MS_PER_D;
used = true;
endptr += 1;
}
if (used)
{
switch (flags & GUC_UNIT_TIME)
{
case GUC_UNIT_S:
val /= MS_PER_S;
break;
case GUC_UNIT_MIN:
val /= MS_PER_MIN;
break;
}
}
}
if (endptr == value || *endptr != '\0' || errno == ERANGE
#ifdef HAVE_LONG_INT_64
/* if long > 32 bits, check for overflow of int4 */
|| val != (long) ((int32) val)
#endif
)
{
if (result)
*result = 0; /* suppress compiler warning */
if (hintmsg)
*hintmsg = gettext_noop("Value exceeds integer range.");
return false;
}
/* allow whitespace between integer and unit */
while (isspace((unsigned char) *endptr))
endptr++;
/* Handle possible unit */
if (*endptr != '\0')
{
/*
* Note: the multiple-switch coding technique here is a bit tedious,
* but seems necessary to avoid intermediate-value overflows.
*
* If INT64_IS_BUSTED (ie, it's really int32) we will fail to detect
* overflow due to units conversion, but there are few enough such
* machines that it does not seem worth trying to be smarter.
*/
if (flags & GUC_UNIT_MEMORY)
{
/* Set hint for use if no match or trailing garbage */
if (hintmsg)
*hintmsg = gettext_noop("Valid units for this parameter are \"kB\", \"MB\", and \"GB\".");
#if BLCKSZ < 1024 || BLCKSZ > (1024*1024)
#error BLCKSZ must be between 1KB and 1MB
#endif
#if XLOG_BLCKSZ < 1024 || XLOG_BLCKSZ > (1024*1024)
#error XLOG_BLCKSZ must be between 1KB and 1MB
#endif
if (strncmp(endptr, "kB", 2) == 0)
{
endptr += 2;
switch (flags & GUC_UNIT_MEMORY)
{
case GUC_UNIT_BLOCKS:
val /= (BLCKSZ / 1024);
break;
case GUC_UNIT_XBLOCKS:
val /= (XLOG_BLCKSZ / 1024);
break;
}
}
else if (strncmp(endptr, "MB", 2) == 0)
{
endptr += 2;
switch (flags & GUC_UNIT_MEMORY)
{
case GUC_UNIT_KB:
val *= KB_PER_MB;
break;
case GUC_UNIT_BLOCKS:
val *= KB_PER_MB / (BLCKSZ / 1024);
break;
case GUC_UNIT_XBLOCKS:
val *= KB_PER_MB / (XLOG_BLCKSZ / 1024);
break;
}
}
else if (strncmp(endptr, "GB", 2) == 0)
{
endptr += 2;
switch (flags & GUC_UNIT_MEMORY)
{
case GUC_UNIT_KB:
val *= KB_PER_GB;
break;
case GUC_UNIT_BLOCKS:
val *= KB_PER_GB / (BLCKSZ / 1024);
break;
case GUC_UNIT_XBLOCKS:
val *= KB_PER_GB / (XLOG_BLCKSZ / 1024);
break;
}
}
}
else if (flags & GUC_UNIT_TIME)
{
/* Set hint for use if no match or trailing garbage */
if (hintmsg)
*hintmsg = gettext_noop("Valid units for this parameter are \"ms\", \"s\", \"min\", \"h\", and \"d\".");
if (strncmp(endptr, "ms", 2) == 0)
{
endptr += 2;
switch (flags & GUC_UNIT_TIME)
{
case GUC_UNIT_S:
val /= MS_PER_S;
break;
case GUC_UNIT_MIN:
val /= MS_PER_MIN;
break;
}
}
else if (strncmp(endptr, "s", 1) == 0)
{
endptr += 1;
switch (flags & GUC_UNIT_TIME)
{
case GUC_UNIT_MS:
val *= MS_PER_S;
break;
case GUC_UNIT_MIN:
val /= S_PER_MIN;
break;
}
}
else if (strncmp(endptr, "min", 3) == 0)
{
endptr += 3;
switch (flags & GUC_UNIT_TIME)
{
case GUC_UNIT_MS:
val *= MS_PER_MIN;
break;
case GUC_UNIT_S:
val *= S_PER_MIN;
break;
}
}
else if (strncmp(endptr, "h", 1) == 0)
{
endptr += 1;
switch (flags & GUC_UNIT_TIME)
{
case GUC_UNIT_MS:
val *= MS_PER_H;
break;
case GUC_UNIT_S:
val *= S_PER_H;
break;
case GUC_UNIT_MIN:
val *= MIN_PER_H;
break;
}
}
else if (strncmp(endptr, "d", 1) == 0)
{
endptr += 1;
switch (flags & GUC_UNIT_TIME)
{
case GUC_UNIT_MS:
val *= MS_PER_D;
break;
case GUC_UNIT_S:
val *= S_PER_D;
break;
case GUC_UNIT_MIN:
val *= MIN_PER_D;
break;
}
}
}
/* allow whitespace after unit */
while (isspace((unsigned char) *endptr))
endptr++;
if (*endptr != '\0')
return false; /* appropriate hint, if any, already set */
/* Check for overflow due to units conversion */
if (val != (int64) ((int32) val))
{
if (hintmsg)
*hintmsg = gettext_noop("Value exceeds integer range.");
return false;
}
}
if (result)
*result = (int) val;
return true;
@ -4243,12 +4333,15 @@ set_config_option(const char *name, const char *value,
if (value)
{
if (!parse_int(value, &newval, conf->gen.flags))
const char *hintmsg;
if (!parse_int(value, &newval, conf->gen.flags, &hintmsg))
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" requires an integer value",
name)));
errmsg("invalid value for parameter \"%s\": \"%s\"",
name, value),
hintmsg ? errhint(hintmsg) : 0));
return false;
}
if (newval < conf->min || newval > conf->max)
@ -5674,21 +5767,24 @@ is_newvalue_equal(struct config_generic * record, const char *newvalue)
struct config_bool *conf = (struct config_bool *) record;
bool newval;
return parse_bool(newvalue, &newval) && *conf->variable == newval;
return parse_bool(newvalue, &newval)
&& *conf->variable == newval;
}
case PGC_INT:
{
struct config_int *conf = (struct config_int *) record;
int newval;
return parse_int(newvalue, &newval, record->flags) && *conf->variable == newval;
return parse_int(newvalue, &newval, record->flags, NULL)
&& *conf->variable == newval;
}
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) record;
double newval;
return parse_real(newvalue, &newval) && *conf->variable == newval;
return parse_real(newvalue, &newval)
&& *conf->variable == newval;
}
case PGC_STRING:
{