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.
static char *_ShowOption(struct config_generic * record, bool use_units);
static bool validate_option_array_item(const char *name, const char *value,
bool skipIfNoPermissions);
+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,
+ char *config_file, char *name, char *value);
/*
return retstr.data;
}
+/*
+ * Validates configuration parameter and value, by calling check hook functions
+ * depending on record's vartype. It validates if the parameter
+ * value given is in range of expected predefined value for that parameter.
+ *
+ * freemem - true indicates memory for newval and newextra will be
+ * freed in this function, false indicates it will be freed
+ * by caller.
+ * Return value:
+ * 1: the value is valid
+ * 0: the name or value is invalid
+ */
+bool
+validate_conf_option(struct config_generic * record, const char *name,
+ const char *value, GucSource source, int elevel,
+ bool freemem, void *newval, void **newextra)
+{
+ /*
+ * Validate the value for the passed record, to ensure it is in expected
+ * range.
+ */
+ switch (record->vartype)
+ {
+
+ case PGC_BOOL:
+ {
+ struct config_bool *conf = (struct config_bool *) record;
+ bool tmpnewval;
+
+ if (newval == NULL)
+ newval = &tmpnewval;
+
+ if (value != NULL)
+ {
+ if (!parse_bool(value, newval))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" requires a Boolean value",
+ name)));
+ return 0;
+ }
+
+ if (!call_bool_check_hook(conf, newval, newextra,
+ source, elevel))
+ return 0;
+
+ if (*newextra && freemem)
+ free(*newextra);
+ }
+ }
+ break;
+ case PGC_INT:
+ {
+ struct config_int *conf = (struct config_int *) record;
+ int tmpnewval;
+
+ if (newval == NULL)
+ newval = &tmpnewval;
+
+ if (value != NULL)
+ {
+ const char *hintmsg;
+
+ if (!parse_int(value, newval, conf->gen.flags, &hintmsg))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ return 0;
+ }
+
+ if (*((int *) newval) < conf->min || *((int *) newval) > conf->max)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%d is outside the valid range for parameter \"%s\" (%d .. %d)",
+ *((int *) newval), name, conf->min, conf->max)));
+ return 0;
+ }
+
+ if (!call_int_check_hook(conf, newval, newextra,
+ source, elevel))
+ return 0;
+
+ if (*newextra && freemem)
+ free(*newextra);
+ }
+ }
+ break;
+ case PGC_REAL:
+ {
+ struct config_real *conf = (struct config_real *) record;
+ double tmpnewval;
+
+ if (newval == NULL)
+ newval = &tmpnewval;
+
+ if (value != NULL)
+ {
+ if (!parse_real(value, newval))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" requires a numeric value",
+ name)));
+ return 0;
+ }
+
+ if (*((double *) newval) < conf->min || *((double *) newval) > conf->max)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%g is outside the valid range for parameter \"%s\" (%g .. %g)",
+ *((double *) newval), name, conf->min, conf->max)));
+ return 0;
+ }
+
+ if (!call_real_check_hook(conf, newval, newextra,
+ source, elevel))
+ return 0;
+
+ if (*newextra && freemem)
+ free(*newextra);
+ }
+ }
+ break;
+ case PGC_STRING:
+ {
+ 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
+ * we always strdup it.
+ */
+ *tmpnewval = guc_strdup(elevel, value);
+ if (*tmpnewval == NULL)
+ return 0;
+
+ /*
+ * The only built-in "parsing" check we have is to apply
+ * truncation if GUC_IS_NAME.
+ */
+ if (conf->gen.flags & GUC_IS_NAME)
+ truncate_identifier(*tmpnewval, strlen(*tmpnewval), true);
+
+ if (!call_string_check_hook(conf, tmpnewval, newextra,
+ source, elevel))
+ {
+ free(*tmpnewval);
+ return 0;
+ }
+
+ /* Free the malloc'd data if any */
+ if (freemem)
+ {
+ if (*tmpnewval != NULL)
+ free(*tmpnewval);
+ if (*newextra != NULL)
+ free(*newextra);
+ }
+ }
+ }
+ break;
+ case PGC_ENUM:
+ {
+ 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))
+ {
+ char *hintmsg;
+
+ hintmsg = config_enum_get_options(conf,
+ "Available values: ",
+ ".", ", ");
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+
+ if (hintmsg != NULL)
+ pfree(hintmsg);
+ return 0;
+ }
+ if (!call_enum_check_hook(conf, newval, newextra,
+ source, LOG))
+ return 0;
+
+ if (*newextra && freemem)
+ free(*newextra);
+ }
+ }
+ break;
+ }
+ return 1;
+}
+
/*
* Sets option `name' to given value.
if (value)
{
- if (!parse_bool(value, &newval))
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a Boolean value",
- name)));
- return 0;
- }
- if (!call_bool_check_hook(conf, &newval, &newextra,
- source, elevel))
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
}
else if (source == PGC_S_DEFAULT)
if (value)
{
- const char *hintmsg;
-
- if (!parse_int(value, &newval, conf->gen.flags, &hintmsg))
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid value for parameter \"%s\": \"%s\"",
- name, value),
- hintmsg ? errhint("%s", _(hintmsg)) : 0));
- return 0;
- }
- if (newval < conf->min || newval > conf->max)
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("%d is outside the valid range for parameter \"%s\" (%d .. %d)",
- newval, name, conf->min, conf->max)));
- return 0;
- }
- if (!call_int_check_hook(conf, &newval, &newextra,
- source, elevel))
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
}
else if (source == PGC_S_DEFAULT)
if (value)
{
- if (!parse_real(value, &newval))
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a numeric value",
- name)));
- return 0;
- }
- if (newval < conf->min || newval > conf->max)
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("%g is outside the valid range for parameter \"%s\" (%g .. %g)",
- newval, name, conf->min, conf->max)));
- return 0;
- }
- if (!call_real_check_hook(conf, &newval, &newextra,
- source, elevel))
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
}
else if (source == PGC_S_DEFAULT)
if (value)
{
- /*
- * The value passed by the caller could be transient, so
- * we always strdup it.
- */
- newval = guc_strdup(elevel, value);
- if (newval == NULL)
- return 0;
-
- /*
- * The only built-in "parsing" check we have is to apply
- * truncation if GUC_IS_NAME.
- */
- if (conf->gen.flags & GUC_IS_NAME)
- truncate_identifier(newval, strlen(newval), true);
-
- if (!call_string_check_hook(conf, &newval, &newextra,
- source, elevel))
- {
- free(newval);
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
- }
}
else if (source == PGC_S_DEFAULT)
{
if (value)
{
- if (!config_enum_lookup_by_name(conf, value, &newval))
- {
- char *hintmsg;
-
- hintmsg = config_enum_get_options(conf,
- "Available values: ",
- ".", ", ");
-
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid value for parameter \"%s\": \"%s\"",
- name, value),
- hintmsg ? errhint("%s", _(hintmsg)) : 0));
-
- if (hintmsg)
- pfree(hintmsg);
- return 0;
- }
- if (!call_enum_check_hook(conf, &newval, &newextra,
- source, elevel))
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
}
else if (source == PGC_S_DEFAULT)
return buf.data;
}
+/*
+ * Writes updated configuration parameter values into
+ * postgresql.auto.conf.temp file. It traverses the list of parameters
+ * and quote the string values before writing them to temporaray file.
+ */
+static void
+write_auto_conf_file(int fd, const char *filename, ConfigVariable **head_p)
+{
+ ConfigVariable *item;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ appendStringInfoString(&buf, "# Do not edit this file manually! \n");
+ appendStringInfoString(&buf, "# It will be overwritten by ALTER SYSTEM command. \n");
+
+ /*
+ * write the file header message before contents, so that if there is no
+ * item it can contain message
+ */
+ if (write(fd, buf.data, buf.len) < 0)
+ ereport(ERROR,
+ (errmsg("failed to write to \"%s\" file", filename)));
+ resetStringInfo(&buf);
+
+ /*
+ * 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)
+ {
+ char *escaped;
+
+ appendStringInfoString(&buf, item->name);
+ appendStringInfoString(&buf, " = ");
+
+ appendStringInfoString(&buf, "\'");
+ escaped = escape_single_quotes_ascii(item->value);
+ appendStringInfoString(&buf, escaped);
+ free(escaped);
+ appendStringInfoString(&buf, "\'");
+
+ appendStringInfoString(&buf, "\n");
+
+ if (write(fd, buf.data, buf.len) < 0)
+ ereport(ERROR,
+ (errmsg("failed to write to \"%s\" file", filename)));
+ resetStringInfo(&buf);
+ }
+
+ if (pg_fsync(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", filename)));
+
+ pfree(buf.data);
+}
+
+
+/*
+ * This function takes list of all configuration parameters in
+ * postgresql.auto.conf and parameter to be updated as input arguments and
+ * 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
+replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
+ char *config_file,
+ char *name, char *value)
+{
+ ConfigVariable *item,
+ *prev = NULL;
+
+ if (*head_p != NULL)
+ {
+ for (item = *head_p; item != NULL; item = item->next)
+ {
+ if (strcmp(item->name, name) == 0)
+ {
+ pfree(item->value);
+ if (value != NULL)
+ /* update the parameter value */
+ item->value = pstrdup(value);
+ else
+ {
+ /* delete the configuration parameter from list */
+ if (*head_p == item)
+ *head_p = item->next;
+ else
+ prev->next = item->next;
+
+ if (*tail_p == item)
+ *tail_p = prev;
+
+ pfree(item->name);
+ pfree(item->filename);
+ pfree(item);
+ }
+ return;
+ }
+ prev = item;
+ }
+ }
+
+ if (value == NULL)
+ return;
+
+ item = palloc(sizeof *item);
+ item->name = pstrdup(name);
+ item->value = pstrdup(value);
+ item->filename = pstrdup(config_file);
+ item->next = NULL;
+
+ if (*head_p == NULL)
+ {
+ item->sourceline = 1;
+ *head_p = item;
+ }
+ else
+ {
+ item->sourceline = (*tail_p)->sourceline + 1;
+ (*tail_p)->next = item;
+ }
+
+ *tail_p = item;
+
+ return;
+}
+
+
+/*
+ * Persist the configuration parameter value.
+ *
+ * This function takes all previous configuration parameters
+ * set by ALTER SYSTEM command and the currently set ones
+ * and write them all to the automatic configuration file.
+ *
+ * The configuration parameters are written to a temporary
+ * file then renamed to the final name. The template for the
+ * temporary file is postgresql.auto.conf.temp.
+ *
+ * An LWLock is used to serialize writing to the same file.
+ *
+ * In case of an error, we leave the original automatic
+ * configuration file (postgresql.auto.conf) intact.
+ */
+void
+AlterSystemSetConfigFile(AlterSystemStmt * altersysstmt)
+{
+ char *name;
+ char *value;
+ int Tmpfd = -1;
+ FILE *infile;
+ struct config_generic *record;
+ ConfigVariable *head = NULL;
+ ConfigVariable *tail = NULL;
+ char AutoConfFileName[MAXPGPATH];
+ char AutoConfTmpFileName[MAXPGPATH];
+ struct stat st;
+ void *newextra = NULL;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to execute ALTER SYSTEM command"))));
+
+ /*
+ * Validate the name and arguments [value1, value2 ... ].
+ */
+ name = altersysstmt->setstmt->name;
+
+ switch (altersysstmt->setstmt->kind)
+ {
+ case VAR_SET_VALUE:
+ value = ExtractSetVariableArgs(altersysstmt->setstmt);
+ break;
+
+ case VAR_SET_DEFAULT:
+ value = NULL;
+ break;
+ default:
+ elog(ERROR, "unrecognized alter system stmt type: %d",
+ altersysstmt->setstmt->kind);
+ break;
+ }
+
+ record = find_option(name, false, LOG);
+ if (record == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("unrecognized configuration parameter \"%s\"", name)));
+
+ if ((record->context == PGC_INTERNAL) ||
+ (record->flags & GUC_DISALLOW_IN_FILE))
+ ereport(ERROR,
+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+ errmsg("parameter \"%s\" cannot be changed",
+ name)));
+
+ if (!validate_conf_option(record, name, value, PGC_S_FILE,
+ ERROR, true, NULL,
+ &newextra))
+ ereport(ERROR,
+ (errmsg("invalid value for parameter \"%s\": \"%s\"", name, value)));
+
+
+ /*
+ * Use data directory as reference path for postgresql.auto.conf and it's
+ * corresponding temp file
+ */
+ join_path_components(AutoConfFileName, data_directory, PG_AUTOCONF_FILENAME);
+ canonicalize_path(AutoConfFileName);
+ snprintf(AutoConfTmpFileName, sizeof(AutoConfTmpFileName), "%s.%s",
+ AutoConfFileName,
+ "temp");
+
+ /*
+ * one backend is allowed to operate on postgresql.auto.conf file, to
+ * ensure that we need to update the contents of the file with
+ * AutoFileLock. To ensure crash safety, first the contents are written to
+ * temporary file and then rename it to postgresql.auto.conf. In case
+ * there exists a temp file from previous crash, that can be reused.
+ */
+
+ 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("failed to open auto conf temp file \"%s\": %m ",
+ AutoConfTmpFileName)));
+
+ PG_TRY();
+ {
+ if (stat(AutoConfFileName, &st) == 0)
+ {
+ /* open postgresql.auto.conf file */
+ infile = AllocateFile(AutoConfFileName, "r");
+ if (infile == NULL)
+ ereport(ERROR,
+ (errmsg("failed to open auto conf file \"%s\": %m ",
+ AutoConfFileName)));
+
+ /* Parse the postgresql.auto.conf file */
+ ParseConfigFp(infile, AutoConfFileName, 0, LOG, &head, &tail);
+
+ FreeFile(infile);
+ }
+
+ /*
+ * replace with new value if the configuration parameter already
+ * exists OR add it as a new cofiguration parameter in the file.
+ */
+ replace_auto_config_value(&head, &tail, AutoConfFileName, name, value);
+
+ /* Write and sync the New contents to postgresql.auto.conf.temp file */
+ write_auto_conf_file(Tmpfd, AutoConfTmpFileName, &head);
+
+ close(Tmpfd);
+ Tmpfd = -1;
+
+ /*
+ * As the rename is atomic operation, if any problem occurs after this
+ * at max it can loose the parameters set by last ALTER SYSTEM
+ * command.
+ */
+ if (rename(AutoConfTmpFileName, AutoConfFileName) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not rename file \"%s\" to \"%s\" : %m",
+ AutoConfTmpFileName, AutoConfFileName)));
+ }
+ PG_CATCH();
+ {
+ if (Tmpfd >= 0)
+ close(Tmpfd);
+
+ unlink(AutoConfTmpFileName);
+ FreeConfigVariables(head);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ FreeConfigVariables(head);
+ LWLockRelease(AutoFileLock);
+ return;
+}
/*
* SET command