diff options
author | Tatsuo Ishii <ishii@postgresql.org> | 2013-12-18 23:42:44 +0900 |
---|---|---|
committer | Tatsuo Ishii <ishii@postgresql.org> | 2013-12-18 23:42:44 +0900 |
commit | 65d6e4cb5c62371dae6c236a7e709d503ae6ddf8 (patch) | |
tree | 07fff22fb42940bcf618885589909de0adaa9f9c /src | |
parent | dba5a9dda9adbda16a72c46e1c012ee6552c248a (diff) | |
download | postgresql-65d6e4cb5c62371dae6c236a7e709d503ae6ddf8.tar.gz postgresql-65d6e4cb5c62371dae6c236a7e709d503ae6ddf8.zip |
Add ALTER SYSTEM command to edit the server configuration file.
Patch contributed by Amit Kapila. Reviewed by Hari Babu, Masao Fujii,
Boszormenyi Zoltan, Andres Freund, Greg Smith and others.
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 13 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 12 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 27 | ||||
-rw-r--r-- | src/backend/replication/basebackup.c | 7 | ||||
-rw-r--r-- | src/backend/tcop/utility.c | 13 | ||||
-rw-r--r-- | src/backend/utils/misc/guc-file.l | 35 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 614 | ||||
-rw-r--r-- | src/bin/initdb/initdb.c | 16 | ||||
-rw-r--r-- | src/include/nodes/nodes.h | 1 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 10 | ||||
-rw-r--r-- | src/include/pg_config_manual.h | 7 | ||||
-rw-r--r-- | src/include/storage/lwlock.h | 1 | ||||
-rw-r--r-- | src/include/utils/guc.h | 1 |
13 files changed, 662 insertions, 95 deletions
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index cd8a11b8d5d..3e102310c59 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3292,6 +3292,16 @@ _copyReplicaIdentityStmt(const ReplicaIdentityStmt *from) return newnode; } +static AlterSystemStmt * +_copyAlterSystemStmt(const AlterSystemStmt * from) +{ + AlterSystemStmt *newnode = makeNode(AlterSystemStmt); + + COPY_NODE_FIELD(setstmt); + + return newnode; +} + static CreateSeqStmt * _copyCreateSeqStmt(const CreateSeqStmt *from) { @@ -4368,6 +4378,9 @@ copyObject(const void *from) case T_ReplicaIdentityStmt: retval = _copyReplicaIdentityStmt(from); break; + case T_AlterSystemStmt: + retval = _copyAlterSystemStmt(from); + break; case T_CreateSeqStmt: retval = _copyCreateSeqStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 6188114060f..329755c703f 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1547,6 +1547,15 @@ _equalReplicaIdentityStmt(const ReplicaIdentityStmt *a, const ReplicaIdentityStm } static bool +_equalAlterSystemStmt(const AlterSystemStmt * a, const AlterSystemStmt * b) +{ + COMPARE_NODE_FIELD(setstmt); + + return true; +} + + +static bool _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b) { COMPARE_NODE_FIELD(sequence); @@ -2838,6 +2847,9 @@ equal(const void *a, const void *b) case T_ReplicaIdentityStmt: retval = _equalReplicaIdentityStmt(a, b); break; + case T_AlterSystemStmt: + retval = _equalAlterSystemStmt(a, b); + break; case T_CreateSeqStmt: retval = _equalCreateSeqStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index f9d45777ca7..b4e5552636e 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -216,7 +216,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); AlterEventTrigStmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt - AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt + AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterSystemStmt AlterTableStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt @@ -397,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <istmt> insert_rest -%type <vsetstmt> set_rest set_rest_more SetResetClause FunctionSetResetClause +%type <vsetstmt> generic_set set_rest set_rest_more SetResetClause FunctionSetResetClause %type <node> TableElement TypedTableElement ConstraintElem TableFuncElement %type <node> columnDef columnOptions @@ -724,6 +724,7 @@ stmt : | AlterObjectSchemaStmt | AlterOwnerStmt | AlterSeqStmt + | AlterSystemStmt | AlterTableStmt | AlterCompositeTypeStmt | AlterRoleSetStmt @@ -1333,7 +1334,7 @@ set_rest: | set_rest_more ; -set_rest_more: /* Generic SET syntaxes: */ +generic_set: var_name TO var_list { VariableSetStmt *n = makeNode(VariableSetStmt); @@ -1364,6 +1365,9 @@ set_rest_more: /* Generic SET syntaxes: */ n->name = $1; $$ = n; } + +set_rest_more: /* Generic SET syntaxes: */ + generic_set {$$ = $1;} | var_name FROM CURRENT_P { VariableSetStmt *n = makeNode(VariableSetStmt); @@ -8312,6 +8316,23 @@ DropdbStmt: DROP DATABASE database_name /***************************************************************************** * + * ALTER SYSTEM SET + * + * This is used to change configuration parameters persistently. + *****************************************************************************/ + +AlterSystemStmt: + ALTER SYSTEM_P SET generic_set + { + AlterSystemStmt *n = makeNode(AlterSystemStmt); + n->setstmt = $4; + $$ = (Node *)n; + } + ; + + +/***************************************************************************** + * * Manipulate a domain * *****************************************************************************/ diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c index ba8d173357e..244e3b0ab3f 100644 --- a/src/backend/replication/basebackup.c +++ b/src/backend/replication/basebackup.c @@ -811,6 +811,13 @@ sendDir(char *path, int basepathlen, bool sizeonly) strlen(PG_TEMP_FILE_PREFIX)) == 0) continue; + /* skip auto conf temporary file */ + if (strncmp(de->d_name, + PG_AUTOCONF_FILENAME ".temp", + sizeof(PG_AUTOCONF_FILENAME) + 4) == 0) + continue; + + /* * If there's a backup_label file, it belongs to a backup started by * the user with pg_start_backup(). It is *not* correct for this diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 7d75b3383fa..dca4503471e 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -687,6 +687,11 @@ standard_ProcessUtility(Node *parsetree, ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest); break; + case T_AlterSystemStmt: + PreventTransactionChain(isTopLevel, "ALTER SYSTEM"); + AlterSystemSetConfigFile((AlterSystemStmt *) parsetree); + break; + case T_VariableSetStmt: ExecSetVariableStmt((VariableSetStmt *) parsetree, isTopLevel); break; @@ -2157,6 +2162,10 @@ CreateCommandTag(Node *parsetree) tag = "REFRESH MATERIALIZED VIEW"; break; + case T_AlterSystemStmt: + tag = "ALTER SYSTEM"; + break; + case T_VariableSetStmt: switch (((VariableSetStmt *) parsetree)->kind) { @@ -2726,6 +2735,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_AlterSystemStmt: + lev = LOGSTMT_ALL; + break; + case T_VariableSetStmt: lev = LOGSTMT_ALL; break; diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l index c5ca4a40742..640899bae58 100644 --- a/src/backend/utils/misc/guc-file.l +++ b/src/backend/utils/misc/guc-file.l @@ -120,6 +120,9 @@ ProcessConfigFile(GucContext context) *head, *tail; int i; + char ConfigAutoFileName[MAXPGPATH]; + char *ErrorConfFile; + char *CallingFileName; /* * Config files are processed on startup (by the postmaster only) @@ -134,6 +137,8 @@ ProcessConfigFile(GucContext context) */ elevel = IsUnderPostmaster ? DEBUG2 : LOG; + ErrorConfFile = ConfigFileName; + /* Parse the file into a list of option names and values */ head = tail = NULL; @@ -145,6 +150,26 @@ ProcessConfigFile(GucContext context) } /* + * Parse postgresql.auto.conf file after postgresql.conf to replace + * parameters set by ALTER SYSTEM command. This file is present in + * data directory, however when called during initdb data directory is not + * set till this point, so use ConfigFile path which will be same. + */ + snprintf(ConfigAutoFileName,sizeof(ConfigAutoFileName),"%s", PG_AUTOCONF_FILENAME); + if (data_directory) + CallingFileName = NULL; + else + CallingFileName = ConfigFileName; + + if (!ParseConfigFile(ConfigAutoFileName, CallingFileName, false, 0, elevel, &head, &tail)) + { + /* Syntax error(s) detected in the file, so bail out */ + error = true; + ErrorConfFile = ConfigAutoFileName; + goto cleanup_list; + } + + /* * Mark all extant GUC variables as not present in the config file. * We need this so that we can tell below which ones have been removed * from the file since we last processed it. @@ -192,6 +217,7 @@ ProcessConfigFile(GucContext context) item->name, item->filename, item->sourceline))); error = true; + ErrorConfFile = item->filename; } } @@ -318,7 +344,10 @@ ProcessConfigFile(GucContext context) } } else if (scres == 0) + { error = true; + ErrorConfFile = item->filename; + } /* else no error but variable's active value was not changed */ /* @@ -348,17 +377,17 @@ ProcessConfigFile(GucContext context) ereport(ERROR, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("configuration file \"%s\" contains errors", - ConfigFileName))); + ErrorConfFile))); else if (apply) ereport(elevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("configuration file \"%s\" contains errors; unaffected changes were applied", - ConfigFileName))); + ErrorConfFile))); else ereport(elevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("configuration file \"%s\" contains errors; no changes were applied", - ConfigFileName))); + ErrorConfFile))); } } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index b0c14a2dfce..51416f49cd9 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -207,6 +207,10 @@ 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. @@ -3484,6 +3488,9 @@ static void ShowAllGUCConfig(DestReceiver *dest); 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); /* @@ -5248,6 +5255,220 @@ config_enum_get_options(struct config_enum * record, const char *prefix, 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. @@ -5496,16 +5717,9 @@ set_config_option(const char *name, const char *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) @@ -5589,27 +5803,9 @@ set_config_option(const char *name, const char *value, 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) @@ -5693,24 +5889,9 @@ set_config_option(const char *name, const char *value, 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) @@ -5794,27 +5975,10 @@ set_config_option(const char *name, const char *value, 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) { @@ -5920,26 +6084,9 @@ set_config_option(const char *name, const char *value, 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) @@ -6309,6 +6456,295 @@ flatten_set_variable_args(const char *name, List *args) 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 diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 30e3701f92a..e6bb132beaf 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -1228,6 +1228,7 @@ setup_config(void) char repltok[MAXPGPATH]; char path[MAXPGPATH]; const char *default_timezone; + char *autoconflines[3]; fputs(_("creating configuration files ... "), stdout); fflush(stdout); @@ -1320,6 +1321,21 @@ setup_config(void) writefile(path, conflines); chmod(path, S_IRUSR | S_IWUSR); + /* + * create the automatic configuration file to store the configuration + * parameters set by ALTER SYSTEM command. The parameters present in this + * file will override the value of parameters that exists before parse of + * this file. + */ + autoconflines[0] = pg_strdup("# Do not edit this file manually! \n"); + autoconflines[1] = pg_strdup("# It will be overwritten by the ALTER SYSTEM command. \n"); + autoconflines[2] = NULL; + + sprintf(path, "%s/%s", pg_data, PG_AUTOCONF_FILENAME); + + writefile(path, autoconflines); + chmod(path, S_IRUSR | S_IWUSR); + free(conflines); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index ff9af7691c9..a68c8ad5cac 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -363,6 +363,7 @@ typedef enum NodeTag T_AlterEventTrigStmt, T_RefreshMatViewStmt, T_ReplicaIdentityStmt, + T_AlterSystemStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0ad7586853b..6a5a8c5f2d7 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2472,6 +2472,16 @@ typedef struct DropdbStmt } DropdbStmt; /* ---------------------- + * Alter System Statement + * ---------------------- + */ +typedef struct AlterSystemStmt +{ + NodeTag type; + VariableSetStmt *setstmt; /* SET subcommand */ +} AlterSystemStmt; + +/* ---------------------- * Cluster Statement (support pbrown's cluster index implementation) * ---------------------- */ diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h index 2e6aad1ca56..9d1166305d3 100644 --- a/src/include/pg_config_manual.h +++ b/src/include/pg_config_manual.h @@ -292,3 +292,10 @@ /* #define HEAPDEBUGALL */ /* #define ACLDEBUG */ /* #define RTDEBUG */ + +/* + * Automatic configuration file name for ALTER SYSTEM. + * This file will be used to store values of configuration parameters + * set by ALTER SYSTEM command + */ +#define PG_AUTOCONF_FILENAME "postgresql.auto.conf" diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index 730c47ba686..3e42f6a468f 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -81,6 +81,7 @@ typedef enum LWLockId SyncRepLock, BackgroundWorkerLock, DynamicSharedMemoryControlLock, + AutoFileLock, /* Individual lock IDs end here */ FirstBufMappingLock, FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS, diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 3e981b3e94b..0a02999e3f8 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -326,6 +326,7 @@ extern bool parse_real(const char *value, double *result); extern int set_config_option(const char *name, const char *value, GucContext context, GucSource source, GucAction action, bool changeVal, int elevel); +extern void AlterSystemSetConfigFile(AlterSystemStmt * setstmt); extern char *GetConfigOptionByName(const char *name, const char **varname); extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow); extern int GetNumConfigOptions(void); |