Commit bf007a27 authored by Tom Lane's avatar Tom Lane

Clean up assorted issues in ALTER SYSTEM coding.

Fix unsafe use of a non-volatile variable in PG_TRY/PG_CATCH in
AlterSystemSetConfigFile().  While at it, clean up a bundle of other
infelicities and outright bugs, including corner-case-incorrect linked list
manipulation, a poorly designed and worse documented parse-and-validate
function (which even included some randomly chosen hard-wired substitutes
for the specified elevel in one code path ... wtf?), direct use of open()
instead of fd.c's facilities, inadequate checking of write()'s return
value, and generally poorly written commentary.
parent fd496129
...@@ -118,11 +118,11 @@ ProcessConfigFile(GucContext context) ...@@ -118,11 +118,11 @@ ProcessConfigFile(GucContext context)
bool error = false; bool error = false;
bool apply = false; bool apply = false;
int elevel; int elevel;
const char *ConfFileWithError;
ConfigVariable *item, ConfigVariable *item,
*head, *head,
*tail; *tail;
int i; int i;
char *ErrorConfFile = ConfigFileName;
/* /*
* Config files are processed on startup (by the postmaster only) * Config files are processed on startup (by the postmaster only)
...@@ -138,6 +138,7 @@ ProcessConfigFile(GucContext context) ...@@ -138,6 +138,7 @@ ProcessConfigFile(GucContext context)
elevel = IsUnderPostmaster ? DEBUG2 : LOG; elevel = IsUnderPostmaster ? DEBUG2 : LOG;
/* Parse the main config file into a list of option names and values */ /* Parse the main config file into a list of option names and values */
ConfFileWithError = ConfigFileName;
head = tail = NULL; head = tail = NULL;
if (!ParseConfigFile(ConfigFileName, NULL, true, 0, elevel, &head, &tail)) if (!ParseConfigFile(ConfigFileName, NULL, true, 0, elevel, &head, &tail))
...@@ -160,25 +161,23 @@ ProcessConfigFile(GucContext context) ...@@ -160,25 +161,23 @@ ProcessConfigFile(GucContext context)
{ {
/* Syntax error(s) detected in the file, so bail out */ /* Syntax error(s) detected in the file, so bail out */
error = true; error = true;
ErrorConfFile = PG_AUTOCONF_FILENAME; ConfFileWithError = PG_AUTOCONF_FILENAME;
goto cleanup_list; goto cleanup_list;
} }
} }
else else
{ {
ConfigVariable *prev = NULL;
/* /*
* Pick up only the data_directory if DataDir is not set, which * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
* means that the configuration file is read for the first time and * read. In this case, we don't want to accept any settings but
* PG_AUTOCONF_FILENAME file cannot be read yet. In this case, * data_directory from postgresql.conf, because they might be
* we shouldn't pick any settings except the data_directory * overwritten with settings in the PG_AUTOCONF_FILENAME file which
* from postgresql.conf because they might be overwritten * will be read later. OTOH, since data_directory isn't allowed in the
* with the settings in PG_AUTOCONF_FILENAME file which will be * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
* read later. OTOH, since it's ensured that data_directory doesn't
* exist in PG_AUTOCONF_FILENAME file, it will never be overwritten
* later.
*/ */
ConfigVariable *prev = NULL;
/* Prune all items except "data_directory" from the list */
for (item = head; item;) for (item = head; item;)
{ {
ConfigVariable *ptr = item; ConfigVariable *ptr = item;
...@@ -189,15 +188,9 @@ ProcessConfigFile(GucContext context) ...@@ -189,15 +188,9 @@ ProcessConfigFile(GucContext context)
if (prev == NULL) if (prev == NULL)
head = ptr->next; head = ptr->next;
else else
{
prev->next = ptr->next; prev->next = ptr->next;
/* if (ptr->next == NULL)
* On removing last item in list, we need to update tail
* to ensure that list will be maintianed.
*/
if (prev->next == NULL)
tail = prev; tail = prev;
}
FreeConfigVariable(ptr); FreeConfigVariable(ptr);
} }
else else
...@@ -205,10 +198,10 @@ ProcessConfigFile(GucContext context) ...@@ -205,10 +198,10 @@ ProcessConfigFile(GucContext context)
} }
/* /*
* Quick exit if data_directory is not present in list. * Quick exit if data_directory is not present in file.
* *
* Don't remember when we last successfully loaded the config file in * We need not do any further processing, in particular we don't set
* this case because that time will be set soon by subsequent load of * PgReloadTime; that will be set soon by subsequent full loading of
* the config file. * the config file.
*/ */
if (head == NULL) if (head == NULL)
...@@ -263,7 +256,7 @@ ProcessConfigFile(GucContext context) ...@@ -263,7 +256,7 @@ ProcessConfigFile(GucContext context)
item->name, item->name,
item->filename, item->sourceline))); item->filename, item->sourceline)));
error = true; error = true;
ErrorConfFile = item->filename; ConfFileWithError = item->filename;
} }
} }
...@@ -392,7 +385,7 @@ ProcessConfigFile(GucContext context) ...@@ -392,7 +385,7 @@ ProcessConfigFile(GucContext context)
else if (scres == 0) else if (scres == 0)
{ {
error = true; error = true;
ErrorConfFile = item->filename; ConfFileWithError = item->filename;
} }
/* else no error but variable's active value was not changed */ /* else no error but variable's active value was not changed */
...@@ -421,23 +414,23 @@ ProcessConfigFile(GucContext context) ...@@ -421,23 +414,23 @@ ProcessConfigFile(GucContext context)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_CONFIG_FILE_ERROR), (errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("configuration file \"%s\" contains errors", errmsg("configuration file \"%s\" contains errors",
ErrorConfFile))); ConfFileWithError)));
else if (apply) else if (apply)
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR), (errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("configuration file \"%s\" contains errors; unaffected changes were applied", errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
ErrorConfFile))); ConfFileWithError)));
else else
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR), (errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("configuration file \"%s\" contains errors; no changes were applied", errmsg("configuration file \"%s\" contains errors; no changes were applied",
ErrorConfFile))); ConfFileWithError)));
} }
/* /*
* Calling FreeConfigVariables() any earlier than this can cause problems, * Calling FreeConfigVariables() any earlier than this can cause problems,
* because ErrorConfFile could be pointing to a string that will be freed * because ConfFileWithError could be pointing to a string that will be
* here. * freed here.
*/ */
FreeConfigVariables(head); FreeConfigVariables(head);
} }
...@@ -477,8 +470,11 @@ AbsoluteConfigLocation(const char *location, const char *calling_file) ...@@ -477,8 +470,11 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
* Read and parse a single configuration file. This function recurses * Read and parse a single configuration file. This function recurses
* to handle "include" directives. * to handle "include" directives.
* *
* See ParseConfigFp for details. This one merely adds opening the * If "strict" is true, treat failure to open the config file as an error,
* file rather than working from a caller-supplied file descriptor, * otherwise just skip the file.
*
* See ParseConfigFp for further details. This one merely adds opening the
* config file rather than working from a caller-supplied file descriptor,
* and absolute-ifying the path name if necessary. * and absolute-ifying the path name if necessary.
*/ */
bool bool
...@@ -516,12 +512,13 @@ ParseConfigFile(const char *config_file, const char *calling_file, bool strict, ...@@ -516,12 +512,13 @@ ParseConfigFile(const char *config_file, const char *calling_file, bool strict,
errmsg("could not open configuration file \"%s\": %m", errmsg("could not open configuration file \"%s\": %m",
abs_path))); abs_path)));
OK = false; OK = false;
goto cleanup;
} }
else
{
ereport(LOG, ereport(LOG,
(errmsg("skipping missing configuration file \"%s\"", (errmsg("skipping missing configuration file \"%s\"",
abs_path))); abs_path)));
}
goto cleanup; goto cleanup;
} }
...@@ -616,9 +613,7 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel, ...@@ -616,9 +613,7 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
{ {
char *opt_name = NULL; char *opt_name = NULL;
char *opt_value = NULL; char *opt_value = NULL;
ConfigVariable *item, ConfigVariable *item;
*cur_item = NULL,
*prev_item = NULL;
if (token == GUC_EOL) /* empty or comment line */ if (token == GUC_EOL) /* empty or comment line */
continue; continue;
...@@ -701,41 +696,13 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel, ...@@ -701,41 +696,13 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
} }
else else
{ {
/* /* ordinary variable, append to list */
* ordinary variable, append to list. For multiple items of
* same parameter, retain only which comes later.
*/
item = palloc(sizeof *item); item = palloc(sizeof *item);
item->name = opt_name; item->name = opt_name;
item->value = opt_value; item->value = opt_value;
item->filename = pstrdup(config_file); item->filename = pstrdup(config_file);
item->sourceline = ConfigFileLineno-1; item->sourceline = ConfigFileLineno-1;
item->next = NULL; item->next = NULL;
/* Remove the existing item of same parameter from the list */
for (cur_item = *head_p; cur_item; prev_item = cur_item,
cur_item = cur_item->next)
{
if (strcmp(item->name, cur_item->name) == 0)
{
if (prev_item == NULL)
*head_p = cur_item->next;
else
{
prev_item->next = cur_item->next;
/*
* On removing last item in list, we need to update tail
* to ensure that list will be maintianed.
*/
if (prev_item->next == NULL)
*tail_p = prev_item;
}
FreeConfigVariable(cur_item);
break;
}
}
if (*head_p == NULL) if (*head_p == NULL)
*head_p = item; *head_p = item;
else else
...@@ -911,21 +878,6 @@ cleanup: ...@@ -911,21 +878,6 @@ cleanup:
return status; return status;
} }
/*
* Free a ConfigVariable
*/
static void
FreeConfigVariable(ConfigVariable *item)
{
if (item != NULL)
{
pfree(item->name);
pfree(item->value);
pfree(item->filename);
pfree(item);
}
}
/* /*
* Free a list of ConfigVariables, including the names and the values * Free a list of ConfigVariables, including the names and the values
*/ */
...@@ -944,6 +896,18 @@ FreeConfigVariables(ConfigVariable *list) ...@@ -944,6 +896,18 @@ FreeConfigVariables(ConfigVariable *list)
} }
} }
/*
* Free a single ConfigVariable
*/
static void
FreeConfigVariable(ConfigVariable *item)
{
pfree(item->name);
pfree(item->value);
pfree(item->filename);
pfree(item);
}
/* /*
* scanstr * scanstr
......
...@@ -202,14 +202,6 @@ static bool check_cluster_name(char **newval, void **extra, GucSource source); ...@@ -202,14 +202,6 @@ static bool check_cluster_name(char **newval, void **extra, GucSource source);
static const char *show_unix_socket_permissions(void); static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void); static const char *show_log_file_mode(void);
static char *config_enum_get_options(struct config_enum * record,
const char *prefix, const char *suffix,
const char *separator);
static bool validate_conf_option(struct config_generic * record,
const char *name, const char *value, GucSource source,
int elevel, bool freemem, void *newval, void **newextra);
/* /*
* Options for enum values defined in this module. * Options for enum values defined in this module.
...@@ -476,7 +468,7 @@ int tcp_keepalives_idle; ...@@ -476,7 +468,7 @@ int tcp_keepalives_idle;
int tcp_keepalives_interval; int tcp_keepalives_interval;
int tcp_keepalives_count; int tcp_keepalives_count;
int row_security = true; int row_security;
/* /*
* This really belongs in pg_shmem.c, but is defined here so that it doesn't * This really belongs in pg_shmem.c, but is defined here so that it doesn't
...@@ -3599,9 +3591,9 @@ static void ShowAllGUCConfig(DestReceiver *dest); ...@@ -3599,9 +3591,9 @@ static void ShowAllGUCConfig(DestReceiver *dest);
static char *_ShowOption(struct config_generic * record, bool use_units); static char *_ShowOption(struct config_generic * record, bool use_units);
static bool validate_option_array_item(const char *name, const char *value, static bool validate_option_array_item(const char *name, const char *value,
bool skipIfNoPermissions); bool skipIfNoPermissions);
static void write_auto_conf_file(int fd, const char *filename, ConfigVariable **head_p); static void write_auto_conf_file(int fd, const char *filename, ConfigVariable *head_p);
static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p, static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
char *config_file, char *name, char *value); const char *name, const char *value);
/* /*
...@@ -4375,11 +4367,9 @@ SelectConfigFiles(const char *userDoption, const char *progname) ...@@ -4375,11 +4367,9 @@ SelectConfigFiles(const char *userDoption, const char *progname)
} }
/* /*
* Read the configuration file for the first time. This time only * Read the configuration file for the first time. This time only the
* data_directory parameter is picked up to determine the data directory * data_directory parameter is picked up to determine the data directory,
* so that we can read PG_AUTOCONF_FILENAME file next time. Then don't * so that we can read the PG_AUTOCONF_FILENAME file next time.
* forget to read the configuration file again later to pick up all the
* parameters.
*/ */
ProcessConfigFile(PGC_POSTMASTER); ProcessConfigFile(PGC_POSTMASTER);
...@@ -5380,189 +5370,143 @@ config_enum_get_options(struct config_enum * record, const char *prefix, ...@@ -5380,189 +5370,143 @@ config_enum_get_options(struct config_enum * record, const char *prefix,
} }
/* /*
* Validates configuration parameter and value, by calling check hook functions * Parse and validate a proposed value for the specified configuration
* depending on record's vartype. It validates if the parameter * parameter.
* value given is in range of expected predefined value for that parameter.
* *
* freemem - true indicates memory for newval and newextra will be * This does built-in checks (such as range limits for an integer parameter)
* freed in this function, false indicates it will be freed * and also calls any check hook the parameter may have.
* by caller. *
* Return value: * record: GUC variable's info record
* 1: the value is valid * name: variable name (should match the record of course)
* 0: the name or value is invalid * value: proposed value, as a string
* source: identifies source of value (check hooks may need this)
* elevel: level to log any error reports at
* newval: on success, converted parameter value is returned here
* newextra: on success, receives any "extra" data returned by check hook
* (caller must initialize *newextra to NULL)
*
* Returns true if OK, false if not (or throws error, if elevel >= ERROR)
*/ */
static bool static bool
validate_conf_option(struct config_generic * record, const char *name, parse_and_validate_value(struct config_generic * record,
const char *value, GucSource source, int elevel, const char *name, const char *value,
bool freemem, void *newval, void **newextra) GucSource source, int elevel,
union config_var_val * newval, void **newextra)
{ {
/*
* Validate the value for the passed record, to ensure it is in expected
* range.
*/
switch (record->vartype) switch (record->vartype)
{ {
case PGC_BOOL: case PGC_BOOL:
{ {
struct config_bool *conf = (struct config_bool *) record; struct config_bool *conf = (struct config_bool *) record;
bool tmpnewval;
if (newval == NULL) if (!parse_bool(value, &newval->boolval))
newval = &tmpnewval;
if (value != NULL)
{
if (!parse_bool(value, newval))
{ {
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" requires a Boolean value", errmsg("parameter \"%s\" requires a Boolean value",
name))); name)));
return 0; return false;
} }
if (!call_bool_check_hook(conf, newval, newextra, if (!call_bool_check_hook(conf, &newval->boolval, newextra,
source, elevel)) source, elevel))
return 0; return false;
if (*newextra && freemem)
free(*newextra);
}
} }
break; break;
case PGC_INT: case PGC_INT:
{ {
struct config_int *conf = (struct config_int *) record; struct config_int *conf = (struct config_int *) record;
int tmpnewval;
if (newval == NULL)
newval = &tmpnewval;
if (value != NULL)
{
const char *hintmsg; const char *hintmsg;
if (!parse_int(value, newval, conf->gen.flags, &hintmsg)) if (!parse_int(value, &newval->intval,
conf->gen.flags, &hintmsg))
{ {
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for parameter \"%s\": \"%s\"", errmsg("invalid value for parameter \"%s\": \"%s\"",
name, value), name, value),
hintmsg ? errhint("%s", _(hintmsg)) : 0)); hintmsg ? errhint("%s", _(hintmsg)) : 0));
return 0; return false;
} }
if (*((int *) newval) < conf->min || *((int *) newval) > conf->max) if (newval->intval < conf->min || newval->intval > conf->max)
{ {
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("%d is outside the valid range for parameter \"%s\" (%d .. %d)", errmsg("%d is outside the valid range for parameter \"%s\" (%d .. %d)",
*((int *) newval), name, conf->min, conf->max))); newval->intval, name,
return 0; conf->min, conf->max)));
return false;
} }
if (!call_int_check_hook(conf, newval, newextra, if (!call_int_check_hook(conf, &newval->intval, newextra,
source, elevel)) source, elevel))
return 0; return false;
if (*newextra && freemem)
free(*newextra);
}
} }
break; break;
case PGC_REAL: case PGC_REAL:
{ {
struct config_real *conf = (struct config_real *) record; struct config_real *conf = (struct config_real *) record;
double tmpnewval;
if (newval == NULL)
newval = &tmpnewval;
if (value != NULL) if (!parse_real(value, &newval->realval))
{
if (!parse_real(value, newval))
{ {
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" requires a numeric value", errmsg("parameter \"%s\" requires a numeric value",
name))); name)));
return 0; return false;
} }
if (*((double *) newval) < conf->min || *((double *) newval) > conf->max) if (newval->realval < conf->min || newval->realval > conf->max)
{ {
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("%g is outside the valid range for parameter \"%s\" (%g .. %g)", errmsg("%g is outside the valid range for parameter \"%s\" (%g .. %g)",
*((double *) newval), name, conf->min, conf->max))); newval->realval, name,
return 0; conf->min, conf->max)));
return false;
} }
if (!call_real_check_hook(conf, newval, newextra, if (!call_real_check_hook(conf, &newval->realval, newextra,
source, elevel)) source, elevel))
return 0; return false;
if (*newextra && freemem)
free(*newextra);
}
} }
break; break;
case PGC_STRING: case PGC_STRING:
{ {
struct config_string *conf = (struct config_string *) record; struct config_string *conf = (struct config_string *) record;
char *tempPtr;
char **tmpnewval = newval;
if (newval == NULL)
tmpnewval = &tempPtr;
if (value != NULL)
{
/* /*
* The value passed by the caller could be transient, so * The value passed by the caller could be transient, so we
* we always strdup it. * always strdup it.
*/ */
*tmpnewval = guc_strdup(elevel, value); newval->stringval = guc_strdup(elevel, value);
if (*tmpnewval == NULL) if (newval->stringval == NULL)
return 0; return false;
/* /*
* The only built-in "parsing" check we have is to apply * The only built-in "parsing" check we have is to apply
* truncation if GUC_IS_NAME. * truncation if GUC_IS_NAME.
*/ */
if (conf->gen.flags & GUC_IS_NAME) if (conf->gen.flags & GUC_IS_NAME)
truncate_identifier(*tmpnewval, strlen(*tmpnewval), true); truncate_identifier(newval->stringval,
strlen(newval->stringval),
true);
if (!call_string_check_hook(conf, tmpnewval, newextra, if (!call_string_check_hook(conf, &newval->stringval, newextra,
source, elevel)) source, elevel))
{ {
free(*tmpnewval); free(newval->stringval);
return 0; newval->stringval = NULL;
} return false;
/* Free the malloc'd data if any */
if (freemem)
{
if (*tmpnewval != NULL)
free(*tmpnewval);
if (*newextra != NULL)
free(*newextra);
}
} }
} }
break; break;
case PGC_ENUM: case PGC_ENUM:
{ {
struct config_enum *conf = (struct config_enum *) record; struct config_enum *conf = (struct config_enum *) record;
int tmpnewval;
if (newval == NULL)
newval = &tmpnewval;
if (value != NULL) if (!config_enum_lookup_by_name(conf, value, &newval->enumval))
{
if (!config_enum_lookup_by_name(conf, value, newval))
{ {
char *hintmsg; char *hintmsg;
...@@ -5570,27 +5514,25 @@ validate_conf_option(struct config_generic * record, const char *name, ...@@ -5570,27 +5514,25 @@ validate_conf_option(struct config_generic * record, const char *name,
"Available values: ", "Available values: ",
".", ", "); ".", ", ");
ereport(ERROR, ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for parameter \"%s\": \"%s\"", errmsg("invalid value for parameter \"%s\": \"%s\"",
name, value), name, value),
hintmsg ? errhint("%s", _(hintmsg)) : 0)); hintmsg ? errhint("%s", _(hintmsg)) : 0));
if (hintmsg != NULL) if (hintmsg)
pfree(hintmsg); pfree(hintmsg);
return 0; return false;
} }
if (!call_enum_check_hook(conf, newval, newextra,
source, LOG))
return 0;
if (*newextra && freemem) if (!call_enum_check_hook(conf, &newval->enumval, newextra,
free(*newextra); source, elevel))
} return false;
} }
break; break;
} }
return 1;
return true;
} }
...@@ -5636,6 +5578,8 @@ set_config_option(const char *name, const char *value, ...@@ -5636,6 +5578,8 @@ set_config_option(const char *name, const char *value,
bool is_reload) bool is_reload)
{ {
struct config_generic *record; struct config_generic *record;
union config_var_val newval_union;
void *newextra = NULL;
bool prohibitValueChange = false; bool prohibitValueChange = false;
bool makeDefault; bool makeDefault;
...@@ -5649,7 +5593,9 @@ set_config_option(const char *name, const char *value, ...@@ -5649,7 +5593,9 @@ set_config_option(const char *name, const char *value,
*/ */
elevel = IsUnderPostmaster ? DEBUG3 : LOG; elevel = IsUnderPostmaster ? DEBUG3 : LOG;
} }
else if (source == PGC_S_GLOBAL || source == PGC_S_DATABASE || source == PGC_S_USER || else if (source == PGC_S_GLOBAL ||
source == PGC_S_DATABASE ||
source == PGC_S_USER ||
source == PGC_S_DATABASE_USER) source == PGC_S_DATABASE_USER)
elevel = WARNING; elevel = WARNING;
else else
...@@ -5859,14 +5805,14 @@ set_config_option(const char *name, const char *value, ...@@ -5859,14 +5805,14 @@ set_config_option(const char *name, const char *value,
case PGC_BOOL: case PGC_BOOL:
{ {
struct config_bool *conf = (struct config_bool *) record; struct config_bool *conf = (struct config_bool *) record;
bool newval;
void *newextra = NULL; #define newval (newval_union.boolval)
if (value) if (value)
{ {
if (!validate_conf_option(record, name, value, source, if (!parse_and_validate_value(record, name, value,
elevel, false, &newval, source, elevel,
&newextra)) &newval_union, &newextra))
return 0; return 0;
} }
else if (source == PGC_S_DEFAULT) else if (source == PGC_S_DEFAULT)
...@@ -5940,19 +5886,21 @@ set_config_option(const char *name, const char *value, ...@@ -5940,19 +5886,21 @@ set_config_option(const char *name, const char *value,
if (newextra && !extra_field_used(&conf->gen, newextra)) if (newextra && !extra_field_used(&conf->gen, newextra))
free(newextra); free(newextra);
break; break;
#undef newval
} }
case PGC_INT: case PGC_INT:
{ {
struct config_int *conf = (struct config_int *) record; struct config_int *conf = (struct config_int *) record;
int newval;
void *newextra = NULL; #define newval (newval_union.intval)
if (value) if (value)
{ {
if (!validate_conf_option(record, name, value, source, if (!parse_and_validate_value(record, name, value,
elevel, false, &newval, source, elevel,
&newextra)) &newval_union, &newextra))
return 0; return 0;
} }
else if (source == PGC_S_DEFAULT) else if (source == PGC_S_DEFAULT)
...@@ -6026,19 +5974,21 @@ set_config_option(const char *name, const char *value, ...@@ -6026,19 +5974,21 @@ set_config_option(const char *name, const char *value,
if (newextra && !extra_field_used(&conf->gen, newextra)) if (newextra && !extra_field_used(&conf->gen, newextra))
free(newextra); free(newextra);
break; break;
#undef newval
} }
case PGC_REAL: case PGC_REAL:
{ {
struct config_real *conf = (struct config_real *) record; struct config_real *conf = (struct config_real *) record;
double newval;
void *newextra = NULL; #define newval (newval_union.realval)
if (value) if (value)
{ {
if (!validate_conf_option(record, name, value, source, if (!parse_and_validate_value(record, name, value,
elevel, false, &newval, source, elevel,
&newextra)) &newval_union, &newextra))
return 0; return 0;
} }
else if (source == PGC_S_DEFAULT) else if (source == PGC_S_DEFAULT)
...@@ -6112,19 +6062,21 @@ set_config_option(const char *name, const char *value, ...@@ -6112,19 +6062,21 @@ set_config_option(const char *name, const char *value,
if (newextra && !extra_field_used(&conf->gen, newextra)) if (newextra && !extra_field_used(&conf->gen, newextra))
free(newextra); free(newextra);
break; break;
#undef newval
} }
case PGC_STRING: case PGC_STRING:
{ {
struct config_string *conf = (struct config_string *) record; struct config_string *conf = (struct config_string *) record;
char *newval;
void *newextra = NULL; #define newval (newval_union.stringval)
if (value) if (value)
{ {
if (!validate_conf_option(record, name, value, source, if (!parse_and_validate_value(record, name, value,
elevel, false, &newval, source, elevel,
&newextra)) &newval_union, &newextra))
return 0; return 0;
} }
else if (source == PGC_S_DEFAULT) else if (source == PGC_S_DEFAULT)
...@@ -6221,19 +6173,21 @@ set_config_option(const char *name, const char *value, ...@@ -6221,19 +6173,21 @@ set_config_option(const char *name, const char *value,
if (newextra && !extra_field_used(&conf->gen, newextra)) if (newextra && !extra_field_used(&conf->gen, newextra))
free(newextra); free(newextra);
break; break;
#undef newval
} }
case PGC_ENUM: case PGC_ENUM:
{ {
struct config_enum *conf = (struct config_enum *) record; struct config_enum *conf = (struct config_enum *) record;
int newval;
void *newextra = NULL; #define newval (newval_union.enumval)
if (value) if (value)
{ {
if (!validate_conf_option(record, name, value, source, if (!parse_and_validate_value(record, name, value,
elevel, false, &newval, source, elevel,
&newextra)) &newval_union, &newextra))
return 0; return 0;
} }
else if (source == PGC_S_DEFAULT) else if (source == PGC_S_DEFAULT)
...@@ -6307,6 +6261,8 @@ set_config_option(const char *name, const char *value, ...@@ -6307,6 +6261,8 @@ set_config_option(const char *name, const char *value,
if (newextra && !extra_field_used(&conf->gen, newextra)) if (newextra && !extra_field_used(&conf->gen, newextra))
free(newextra); free(newextra);
break; break;
#undef newval
} }
} }
...@@ -6609,50 +6565,61 @@ flatten_set_variable_args(const char *name, List *args) ...@@ -6609,50 +6565,61 @@ flatten_set_variable_args(const char *name, List *args)
* values before writing them. * values before writing them.
*/ */
static void static void
write_auto_conf_file(int fd, const char *filename, ConfigVariable **head_p) write_auto_conf_file(int fd, const char *filename, ConfigVariable *head)
{ {
ConfigVariable *item;
StringInfoData buf; StringInfoData buf;
ConfigVariable *item;
initStringInfo(&buf); initStringInfo(&buf);
/* Emit file header containing warning comment */
appendStringInfoString(&buf, "# Do not edit this file manually!\n"); appendStringInfoString(&buf, "# Do not edit this file manually!\n");
appendStringInfoString(&buf, "# It will be overwritten by ALTER SYSTEM command.\n"); appendStringInfoString(&buf, "# It will be overwritten by ALTER SYSTEM command.\n");
/* errno = 0;
* write the file header message before contents, so that if there is no if (write(fd, buf.data, buf.len) != buf.len)
* item it can contain message {
*/ /* if write didn't set errno, assume problem is no disk space */
if (write(fd, buf.data, buf.len) < 0) if (errno == 0)
errno = ENOSPC;
ereport(ERROR, ereport(ERROR,
(errmsg("could not write to file \"%s\": %m", filename))); (errcode_for_file_access(),
resetStringInfo(&buf); errmsg("could not write to file \"%s\": %m", filename)));
}
/*
* traverse the list of parameters, quote the string parameter and write
* it to file. Once all parameters are written fsync the file.
*/
for (item = *head_p; item != NULL; item = item->next) /* Emit each parameter, properly quoting the value */
for (item = head; item != NULL; item = item->next)
{ {
char *escaped; char *escaped;
resetStringInfo(&buf);
appendStringInfoString(&buf, item->name); appendStringInfoString(&buf, item->name);
appendStringInfoString(&buf, " = "); appendStringInfoString(&buf, " = '");
appendStringInfoString(&buf, "\'");
escaped = escape_single_quotes_ascii(item->value); escaped = escape_single_quotes_ascii(item->value);
if (!escaped)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
appendStringInfoString(&buf, escaped); appendStringInfoString(&buf, escaped);
free(escaped); free(escaped);
appendStringInfoString(&buf, "\'");
appendStringInfoString(&buf, "\n"); appendStringInfoString(&buf, "'\n");
if (write(fd, buf.data, buf.len) < 0) errno = 0;
if (write(fd, buf.data, buf.len) != buf.len)
{
/* if write didn't set errno, assume problem is no disk space */
if (errno == 0)
errno = ENOSPC;
ereport(ERROR, ereport(ERROR,
(errmsg("could not write to file \"%s\": %m", filename))); (errcode_for_file_access(),
resetStringInfo(&buf); errmsg("could not write to file \"%s\": %m", filename)));
}
} }
/* fsync before considering the write to be successful */
if (pg_fsync(fd) != 0) if (pg_fsync(fd) != 0)
ereport(ERROR, ereport(ERROR,
(errcode_for_file_access(), (errcode_for_file_access(),
...@@ -6661,32 +6628,29 @@ write_auto_conf_file(int fd, const char *filename, ConfigVariable **head_p) ...@@ -6661,32 +6628,29 @@ write_auto_conf_file(int fd, const char *filename, ConfigVariable **head_p)
pfree(buf.data); pfree(buf.data);
} }
/* /*
* This function takes list of all configuration parameters in * Update the given list of configuration parameters, adding, replacing
* PG_AUTOCONF_FILENAME and parameter to be updated as input arguments and * or deleting the entry for item "name" (delete if "value" == NULL).
* replace the updated configuration parameter value in a list. If the
* parameter to be updated is new then it is appended to the list of
* parameters.
*/ */
static void static void
replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p, replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
char *config_file, const char *name, const char *value)
char *name, char *value)
{ {
ConfigVariable *item, ConfigVariable *item,
*prev = NULL; *prev = NULL;
if (*head_p != NULL) /* Search the list for an existing match (we assume there's only one) */
{
for (item = *head_p; item != NULL; item = item->next) for (item = *head_p; item != NULL; item = item->next)
{ {
if (strcmp(item->name, name) == 0) if (strcmp(item->name, name) == 0)
{ {
/* found a match, replace it */
pfree(item->value); pfree(item->value);
if (value != NULL) if (value != NULL)
{
/* update the parameter value */ /* update the parameter value */
item->value = pstrdup(value); item->value = pstrdup(value);
}
else else
{ {
/* delete the configuration parameter from list */ /* delete the configuration parameter from list */
...@@ -6694,7 +6658,6 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p, ...@@ -6694,7 +6658,6 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
*head_p = item->next; *head_p = item->next;
else else
prev->next = item->next; prev->next = item->next;
if (*tail_p == item) if (*tail_p == item)
*tail_p = prev; *tail_p = prev;
...@@ -6706,47 +6669,35 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p, ...@@ -6706,47 +6669,35 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
} }
prev = item; prev = item;
} }
}
/* Not there; no work if we're trying to delete it */
if (value == NULL) if (value == NULL)
return; return;
/* OK, append a new entry */
item = palloc(sizeof *item); item = palloc(sizeof *item);
item->name = pstrdup(name); item->name = pstrdup(name);
item->value = pstrdup(value); item->value = pstrdup(value);
item->filename = pstrdup(config_file); item->filename = pstrdup(""); /* new item has no location */
item->sourceline = 0;
item->next = NULL; item->next = NULL;
if (*head_p == NULL) if (*head_p == NULL)
{
item->sourceline = 1;
*head_p = item; *head_p = item;
}
else else
{
item->sourceline = (*tail_p)->sourceline + 1;
(*tail_p)->next = item; (*tail_p)->next = item;
}
*tail_p = item; *tail_p = item;
return;
} }
/* /*
* Persist the configuration parameter value. * Execute ALTER SYSTEM statement.
* *
* This function takes all previous configuration parameters * Read the old PG_AUTOCONF_FILENAME file, merge in the new variable value,
* set by ALTER SYSTEM command and the currently set ones * and write out an updated file. If the command is ALTER SYSTEM RESET ALL,
* and write them all to the automatic configuration file. * we can skip reading the old file and just write an empty file.
* It just writes an empty file incase user wants to reset
* all the parameters.
* *
* The configuration parameters are written to a temporary * An LWLock is used to serialize updates of the configuration file.
* file then renamed to the final name.
*
* An LWLock is used to serialize writing to the same file.
* *
* In case of an error, we leave the original automatic * In case of an error, we leave the original automatic
* configuration file (PG_AUTOCONF_FILENAME) intact. * configuration file (PG_AUTOCONF_FILENAME) intact.
...@@ -6757,15 +6708,11 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) ...@@ -6757,15 +6708,11 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
char *name; char *name;
char *value; char *value;
bool resetall = false; bool resetall = false;
int Tmpfd = -1;
FILE *infile;
struct config_generic *record;
ConfigVariable *head = NULL; ConfigVariable *head = NULL;
ConfigVariable *tail = NULL; ConfigVariable *tail = NULL;
volatile int Tmpfd;
char AutoConfFileName[MAXPGPATH]; char AutoConfFileName[MAXPGPATH];
char AutoConfTmpFileName[MAXPGPATH]; char AutoConfTmpFileName[MAXPGPATH];
struct stat st;
void *newextra = NULL;
if (!superuser()) if (!superuser())
ereport(ERROR, ereport(ERROR,
...@@ -6773,7 +6720,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) ...@@ -6773,7 +6720,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
(errmsg("must be superuser to execute ALTER SYSTEM command")))); (errmsg("must be superuser to execute ALTER SYSTEM command"))));
/* /*
* Validate the name and arguments [value1, value2 ... ]. * Extract statement arguments
*/ */
name = altersysstmt->setstmt->name; name = altersysstmt->setstmt->name;
...@@ -6799,10 +6746,14 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) ...@@ -6799,10 +6746,14 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
break; break;
} }
/* If we're resetting everything, there's no need to validate anything */ /*
* Unless it's RESET_ALL, validate the target variable and value
*/
if (!resetall) if (!resetall)
{ {
record = find_option(name, false, LOG); struct config_generic *record;
record = find_option(name, false, ERROR);
if (record == NULL) if (record == NULL)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT), (errcode(ERRCODE_UNDEFINED_OBJECT),
...@@ -6810,8 +6761,8 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) ...@@ -6810,8 +6761,8 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
name))); name)));
/* /*
* Don't allow the parameters which can't be set in configuration * Don't allow parameters that can't be set in configuration files to
* files to be set in PG_AUTOCONF_FILENAME file. * be set in PG_AUTOCONF_FILENAME file.
*/ */
if ((record->context == PGC_INTERNAL) || if ((record->context == PGC_INTERNAL) ||
(record->flags & GUC_DISALLOW_IN_FILE) || (record->flags & GUC_DISALLOW_IN_FILE) ||
...@@ -6821,14 +6772,24 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) ...@@ -6821,14 +6772,24 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
errmsg("parameter \"%s\" cannot be changed", errmsg("parameter \"%s\" cannot be changed",
name))); name)));
if (!validate_conf_option(record, name, value, PGC_S_FILE, if (value)
ERROR, true, NULL, {
&newextra)) union config_var_val newval;
void *newextra = NULL;
if (!parse_and_validate_value(record, name, value,
PGC_S_FILE, ERROR,
&newval, &newextra))
ereport(ERROR, ereport(ERROR,
(errmsg("invalid value for parameter \"%s\": \"%s\"", (errmsg("invalid value for parameter \"%s\": \"%s\"",
name, value))); name, value)));
}
if (record->vartype == PGC_STRING && newval.stringval != NULL)
free(newval.stringval);
if (newextra)
free(newextra);
}
}
/* /*
* Use data directory as reference path for PG_AUTOCONF_FILENAME and its * Use data directory as reference path for PG_AUTOCONF_FILENAME and its
...@@ -6841,34 +6802,25 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) ...@@ -6841,34 +6802,25 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
"tmp"); "tmp");
/* /*
* One backend is allowed to operate on file PG_AUTOCONF_FILENAME, to * Only one backend is allowed to operate on PG_AUTOCONF_FILENAME at a
* ensure that we need to update the contents of the file with * time. Use AutoFileLock to ensure that. We must hold the lock while
* AutoFileLock. To ensure crash safety, first the contents are written to * reading the old file contents.
* a temporary file which is then renameed to PG_AUTOCONF_FILENAME. In
* case there exists a temp file from previous crash, that can be reused.
*/ */
LWLockAcquire(AutoFileLock, LW_EXCLUSIVE); LWLockAcquire(AutoFileLock, LW_EXCLUSIVE);
Tmpfd = open(AutoConfTmpFileName, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
if (Tmpfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m",
AutoConfTmpFileName)));
PG_TRY();
{
/* /*
* If we're going to reset everything, then don't open the file, don't * If we're going to reset everything, then no need to open or parse the
* parse it, and don't do anything with the configuration list. Just * old file. We'll just write out an empty list.
* write out an empty file.
*/ */
if (!resetall) if (!resetall)
{ {
struct stat st;
if (stat(AutoConfFileName, &st) == 0) if (stat(AutoConfFileName, &st) == 0)
{ {
/* open file PG_AUTOCONF_FILENAME */ /* open old file PG_AUTOCONF_FILENAME */
FILE *infile;
infile = AllocateFile(AutoConfFileName, "r"); infile = AllocateFile(AutoConfFileName, "r");
if (infile == NULL) if (infile == NULL)
ereport(ERROR, ereport(ERROR,
...@@ -6882,21 +6834,44 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) ...@@ -6882,21 +6834,44 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
} }
/* /*
* replace with new value if the configuration parameter already * Now, replace any existing entry with the new value, or add it if
* exists OR add it as a new cofiguration parameter in the file. * not present.
*/ */
replace_auto_config_value(&head, &tail, AutoConfFileName, name, value); replace_auto_config_value(&head, &tail, name, value);
} }
/*
* To ensure crash safety, first write the new file data to a temp file,
* then atomically rename it into place.
*
* If there is a temp file left over due to a previous crash, it's okay to
* truncate and reuse it.
*/
Tmpfd = BasicOpenFile(AutoConfTmpFileName,
O_CREAT | O_RDWR | O_TRUNC,
S_IRUSR | S_IWUSR);
if (Tmpfd < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m",
AutoConfTmpFileName)));
/*
* Use a TRY block to clean up the file if we fail. Since we need a TRY
* block anyway, OK to use BasicOpenFile rather than OpenTransientFile.
*/
PG_TRY();
{
/* Write and sync the new contents to the temporary file */ /* Write and sync the new contents to the temporary file */
write_auto_conf_file(Tmpfd, AutoConfTmpFileName, &head); write_auto_conf_file(Tmpfd, AutoConfTmpFileName, head);
/* Close before renaming; may be required on some platforms */
close(Tmpfd); close(Tmpfd);
Tmpfd = -1; Tmpfd = -1;
/* /*
* As the rename is atomic operation, if any problem occurs after this * As the rename is atomic operation, if any problem occurs after this
* at max it can loose the parameters set by last ALTER SYSTEM * at worst it can lose the parameters set by last ALTER SYSTEM
* command. * command.
*/ */
if (rename(AutoConfTmpFileName, AutoConfFileName) < 0) if (rename(AutoConfTmpFileName, AutoConfFileName) < 0)
...@@ -6907,18 +6882,20 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) ...@@ -6907,18 +6882,20 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
} }
PG_CATCH(); PG_CATCH();
{ {
/* Close file first, else unlink might fail on some platforms */
if (Tmpfd >= 0) if (Tmpfd >= 0)
close(Tmpfd); close(Tmpfd);
unlink(AutoConfTmpFileName); /* Unlink, but ignore any error */
FreeConfigVariables(head); (void) unlink(AutoConfTmpFileName);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
FreeConfigVariables(head); FreeConfigVariables(head);
LWLockRelease(AutoFileLock); LWLockRelease(AutoFileLock);
return;
} }
/* /*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment