diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index d77b3ee34b..18ae318cd3 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -2980,7 +2980,7 @@ applyRemoteGucs(PGconn *conn) /* Apply the option (this will throw error on failure) */ (void) set_config_option(gucName, remoteVal, PGC_USERSET, PGC_S_SESSION, - GUC_ACTION_SAVE, true, 0); + GUC_ACTION_SAVE, true, 0, false); } return nestlevel; diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 905a821ad0..76bda73935 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -2104,15 +2104,15 @@ set_transmission_modes(void) if (DateStyle != USE_ISO_DATES) (void) set_config_option("datestyle", "ISO", PGC_USERSET, PGC_S_SESSION, - GUC_ACTION_SAVE, true, 0); + GUC_ACTION_SAVE, true, 0, false); if (IntervalStyle != INTSTYLE_POSTGRES) (void) set_config_option("intervalstyle", "postgres", PGC_USERSET, PGC_S_SESSION, - GUC_ACTION_SAVE, true, 0); + GUC_ACTION_SAVE, true, 0, false); if (extra_float_digits < 3) (void) set_config_option("extra_float_digits", "3", PGC_USERSET, PGC_S_SESSION, - GUC_ACTION_SAVE, true, 0); + GUC_ACTION_SAVE, true, 0, false); return nestlevel; } diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 9a0afa4b5d..6692bb545c 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -814,11 +814,11 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, if (client_min_messages < WARNING) (void) set_config_option("client_min_messages", "warning", PGC_USERSET, PGC_S_SESSION, - GUC_ACTION_SAVE, true, 0); + GUC_ACTION_SAVE, true, 0, false); if (log_min_messages < WARNING) (void) set_config_option("log_min_messages", "warning", PGC_SUSET, PGC_S_SESSION, - GUC_ACTION_SAVE, true, 0); + GUC_ACTION_SAVE, true, 0, false); /* * Set up the search path to contain the target schema, then the schemas @@ -843,7 +843,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, (void) set_config_option("search_path", pathbuf.data, PGC_USERSET, PGC_S_SESSION, - GUC_ACTION_SAVE, true, 0); + GUC_ACTION_SAVE, true, 0, false); /* * Set creating_extension and related variables so that diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index c0156fab1f..2f0230384f 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -2422,7 +2422,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem); (void) set_config_option("work_mem", workmembuf, PGC_USERSET, PGC_S_SESSION, - GUC_ACTION_SAVE, true, 0); + GUC_ACTION_SAVE, true, 0, false); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l index bb9207a777..6dca5dfa74 100644 --- a/src/backend/utils/misc/guc-file.l +++ b/src/backend/utils/misc/guc-file.l @@ -318,7 +318,7 @@ ProcessConfigFile(GucContext context) /* Now we can re-apply the wired-in default (i.e., the boot_val) */ if (set_config_option(gconf->name, NULL, context, PGC_S_DEFAULT, - GUC_ACTION_SET, true, 0) > 0) + GUC_ACTION_SET, true, 0, false) > 0) { /* Log the change if appropriate */ if (context == PGC_SIGHUP) @@ -373,7 +373,7 @@ ProcessConfigFile(GucContext context) scres = set_config_option(item->name, item->value, context, PGC_S_FILE, - GUC_ACTION_SET, true, 0); + GUC_ACTION_SET, true, 0, false); if (scres > 0) { /* variable was updated, so log the change if appropriate */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 23cbe906a7..f04757c582 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -110,6 +110,12 @@ #define S_PER_D (60 * 60 * 24) #define MS_PER_D (1000 * 60 * 60 * 24) +/* + * Precision with which REAL type guc values are to be printed for GUC + * serialization. + */ +#define REALTYPE_PRECISION 17 + /* XXX these should appear in other modules' header files */ extern bool Log_disconnections; extern int CommitDelay; @@ -136,6 +142,10 @@ char *GUC_check_errmsg_string; char *GUC_check_errdetail_string; char *GUC_check_errhint_string; +static void +do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) +/* This lets gcc check the format string for consistency. */ +__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); static void set_config_sourcefile(const char *name, char *sourcefile, int sourceline); @@ -5612,7 +5622,8 @@ validate_conf_option(struct config_generic * record, const char *name, int set_config_option(const char *name, const char *value, GucContext context, GucSource source, - GucAction action, bool changeVal, int elevel) + GucAction action, bool changeVal, int elevel, + bool is_reload) { struct config_generic *record; bool prohibitValueChange = false; @@ -5726,18 +5737,13 @@ set_config_option(const char *name, const char *value, * nondefault settings from the CONFIG_EXEC_PARAMS file during * backend start. In that case we must accept PGC_SIGHUP * settings, so as to have the same value as if we'd forked - * from the postmaster. We detect this situation by checking - * IsInitProcessingMode, which is a bit ugly, but it doesn't - * seem worth passing down an explicit flag saying we're doing - * read_nondefault_variables(). + * from the postmaster. This can also happen when using + * RestoreGUCState() within a background worker that needs to + * have the same settings as the user backend that started it. + * is_reload will be true when either situation applies. */ -#ifdef EXEC_BACKEND - if (IsUnderPostmaster && !IsInitProcessingMode()) + if (IsUnderPostmaster && !is_reload) return -1; -#else - if (IsUnderPostmaster) - return -1; -#endif } else if (context != PGC_POSTMASTER && context != PGC_BACKEND && @@ -6343,7 +6349,7 @@ SetConfigOption(const char *name, const char *value, GucContext context, GucSource source) { (void) set_config_option(name, value, context, source, - GUC_ACTION_SET, true, 0); + GUC_ACTION_SET, true, 0, false); } @@ -6923,9 +6929,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel) ExtractSetVariableArgs(stmt), (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, - action, - true, - 0); + action, true, 0, false); break; case VAR_SET_MULTI: @@ -7012,9 +7016,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel) NULL, (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, - action, - true, - 0); + action, true, 0, false); break; case VAR_RESET_ALL: ResetAllOptions(); @@ -7059,8 +7061,7 @@ SetPGVariable(const char *name, List *args, bool is_local) (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET, - true, - 0); + true, 0, false); } /* @@ -7103,8 +7104,7 @@ set_config_by_name(PG_FUNCTION_ARGS) (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET, - true, - 0); + true, 0, false); /* get the new current value */ new_value = GetConfigOptionByName(name, NULL); @@ -7225,7 +7225,7 @@ define_custom_variable(struct config_generic * variable) (void) set_config_option(name, pHolder->reset_val, pHolder->gen.reset_scontext, pHolder->gen.reset_source, - GUC_ACTION_SET, true, WARNING); + GUC_ACTION_SET, true, WARNING, false); /* That should not have resulted in stacking anything */ Assert(variable->stack == NULL); @@ -7281,30 +7281,35 @@ reapply_stacked_values(struct config_generic * variable, case GUC_SAVE: (void) set_config_option(name, curvalue, curscontext, cursource, - GUC_ACTION_SAVE, true, WARNING); + GUC_ACTION_SAVE, true, + WARNING, false); break; case GUC_SET: (void) set_config_option(name, curvalue, curscontext, cursource, - GUC_ACTION_SET, true, WARNING); + GUC_ACTION_SET, true, + WARNING, false); break; case GUC_LOCAL: (void) set_config_option(name, curvalue, curscontext, cursource, - GUC_ACTION_LOCAL, true, WARNING); + GUC_ACTION_LOCAL, true, + WARNING, false); break; case GUC_SET_LOCAL: /* first, apply the masked value as SET */ (void) set_config_option(name, stack->masked.val.stringval, stack->masked_scontext, PGC_S_SESSION, - GUC_ACTION_SET, true, WARNING); + GUC_ACTION_SET, true, + WARNING, false); /* then apply the current value as LOCAL */ (void) set_config_option(name, curvalue, curscontext, cursource, - GUC_ACTION_LOCAL, true, WARNING); + GUC_ACTION_LOCAL, true, + WARNING, false); break; } @@ -7328,7 +7333,7 @@ reapply_stacked_values(struct config_generic * variable, { (void) set_config_option(name, curvalue, curscontext, cursource, - GUC_ACTION_SET, true, WARNING); + GUC_ACTION_SET, true, WARNING, false); variable->stack = NULL; } } @@ -8452,7 +8457,7 @@ read_nondefault_variables(void) (void) set_config_option(varname, varvalue, varscontext, varsource, - GUC_ACTION_SET, true, 0); + GUC_ACTION_SET, true, 0, true); if (varsourcefile[0]) set_config_sourcefile(varname, varsourcefile, varsourceline); @@ -8465,6 +8470,398 @@ read_nondefault_variables(void) } #endif /* EXEC_BACKEND */ +/* + * can_skip_gucvar: + * When serializing, determine whether to skip this GUC. When restoring, the + * negation of this test determines whether to restore the compiled-in default + * value before processing serialized values. + * + * A PGC_S_DEFAULT setting on the serialize side will typically match new + * postmaster children, but that can be false when got_SIGHUP == true and the + * pending configuration change modifies this setting. Nonetheless, we omit + * PGC_S_DEFAULT settings from serialization and make up for that by restoring + * defaults before applying serialized values. + * + * PGC_POSTMASTER variables always have the same value in every child of a + * particular postmaster. Most PGC_INTERNAL variables are compile-time + * constants; a few, like server_encoding and lc_ctype, are handled specially + * outside the serialize/restore procedure. Therefore, SerializeGUCState() + * never sends these, and and RestoreGUCState() never changes them. + */ +static bool +can_skip_gucvar(struct config_generic * gconf) +{ + return gconf->context == PGC_POSTMASTER || + gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT; +} + +/* + * estimate_variable_size: + * Estimate max size for dumping the given GUC variable. + */ +static Size +estimate_variable_size(struct config_generic * gconf) +{ + Size size; + Size valsize; + + if (can_skip_gucvar(gconf)) + return 0; + + size = 0; + + size = add_size(size, strlen(gconf->name) + 1); + + /* Get the maximum display length of the GUC value. */ + switch (gconf->vartype) + { + case PGC_BOOL: + { + valsize = 5; /* max(strlen('true'), strlen('false')) */ + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + /* + * Instead of getting the exact display length, use max + * length. Also reduce the max length for typical ranges of + * small values. Maximum value is 2147483647, i.e. 10 chars. + * Include one byte for sign. + */ + if (abs(*conf->variable) < 1000) + valsize = 3 + 1; + else + valsize = 10 + 1; + } + break; + + case PGC_REAL: + { + /* + * We are going to print it with %.17g. Account for sign, + * decimal point, and e+nnn notation. E.g. + * -3.9932904234000002e+110 + */ + valsize = REALTYPE_PRECISION + 1 + 1 + 5; + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + valsize = strlen(*conf->variable); + } + break; + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + valsize = strlen(config_enum_lookup_by_value(conf, *conf->variable)); + } + break; + } + + /* Allow space for terminating zero-byte */ + size = add_size(size, valsize + 1); + + if (gconf->sourcefile) + size = add_size(size, strlen(gconf->sourcefile)); + + /* Allow space for terminating zero-byte */ + size = add_size(size, 1); + + /* Include line whenever we include file. */ + if (gconf->sourcefile) + size = add_size(size, sizeof(gconf->sourceline)); + + size = add_size(size, sizeof(gconf->source)); + size = add_size(size, sizeof(gconf->scontext)); + + return size; +} + +/* + * EstimateGUCStateSpace: + * Returns the size needed to store the GUC state for the current process + */ +Size +EstimateGUCStateSpace(void) +{ + Size size; + int i; + + /* Add space reqd for saving the data size of the guc state */ + size = sizeof(Size); + + /* Add up the space needed for each GUC variable */ + for (i = 0; i < num_guc_variables; i++) + size = add_size(size, + estimate_variable_size(guc_variables[i])); + + return size; +} + +/* + * do_serialize: + * Copies the formatted string into the destination. Moves ahead the + * destination pointer, and decrements the maxbytes by that many bytes. If + * maxbytes is not sufficient to copy the string, error out. + */ +static void +do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) +{ + va_list vargs; + int n; + + if (*maxbytes <= 0) + elog(ERROR, "not enough space to serialize GUC state"); + + va_start(vargs, fmt); + n = vsnprintf(*destptr, *maxbytes, fmt, vargs); + va_end(vargs); + + /* + * Cater to portability hazards in the vsnprintf() return value just like + * appendPQExpBufferVA() does. Note that this requires an extra byte of + * slack at the end of the buffer. Since serialize_variable() ends with a + * do_serialize_binary() rather than a do_serialize(), we'll always have + * that slack; estimate_variable_size() need not add a byte for it. + */ + if (n < 0 || n >= *maxbytes - 1) + { + if (n < 0 && errno != 0 && errno != ENOMEM) + /* Shouldn't happen. Better show errno description. */ + elog(ERROR, "vsnprintf failed: %m"); + else + elog(ERROR, "not enough space to serialize GUC state"); + } + + /* Shift the destptr ahead of the null terminator */ + *destptr += n + 1; + *maxbytes -= n + 1; +} + +/* Binary copy version of do_serialize() */ +static void +do_serialize_binary(char **destptr, Size *maxbytes, void *val, Size valsize) +{ + if (valsize > *maxbytes) + elog(ERROR, "not enough space to serialize GUC state"); + + memcpy(*destptr, val, valsize); + *destptr += valsize; + *maxbytes -= valsize; +} + +/* + * serialize_variable: + * Dumps name, value and other information of a GUC variable into destptr. + */ +static void +serialize_variable(char **destptr, Size *maxbytes, + struct config_generic * gconf) +{ + if (can_skip_gucvar(gconf)) + return; + + do_serialize(destptr, maxbytes, "%s", gconf->name); + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + do_serialize(destptr, maxbytes, + (*conf->variable ? "true" : "false")); + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + do_serialize(destptr, maxbytes, "%d", *conf->variable); + } + break; + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + do_serialize(destptr, maxbytes, "%.*g", + REALTYPE_PRECISION, *conf->variable); + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + do_serialize(destptr, maxbytes, "%s", *conf->variable); + } + break; + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + do_serialize(destptr, maxbytes, "%s", + config_enum_lookup_by_value(conf, *conf->variable)); + } + break; + } + + do_serialize(destptr, maxbytes, "%s", + (gconf->sourcefile ? gconf->sourcefile : "")); + + if (gconf->sourcefile) + do_serialize_binary(destptr, maxbytes, &gconf->sourceline, + sizeof(gconf->sourceline)); + + do_serialize_binary(destptr, maxbytes, &gconf->source, + sizeof(gconf->source)); + do_serialize_binary(destptr, maxbytes, &gconf->scontext, + sizeof(gconf->scontext)); +} + +/* + * SerializeGUCState: + * Dumps the complete GUC state onto the memory location at start_address. + */ +void +SerializeGUCState(Size maxsize, char *start_address) +{ + char *curptr; + Size actual_size; + Size bytes_left; + int i; + int i_role; + + /* Reserve space for saving the actual size of the guc state */ + curptr = start_address + sizeof(actual_size); + bytes_left = maxsize - sizeof(actual_size); + + for (i = 0; i < num_guc_variables; i++) + { + /* + * It's pretty ugly, but we've got to force "role" to be initialized + * after "session_authorization"; otherwise, the latter will override + * the former. + */ + if (strcmp(guc_variables[i]->name, "role") == 0) + i_role = i; + else + serialize_variable(&curptr, &bytes_left, guc_variables[i]); + } + serialize_variable(&curptr, &bytes_left, guc_variables[i_role]); + + /* Store actual size without assuming alignment of start_address. */ + actual_size = maxsize - bytes_left - sizeof(actual_size); + memcpy(start_address, &actual_size, sizeof(actual_size)); +} + +/* + * read_gucstate: + * Actually it does not read anything, just returns the srcptr. But it does + * move the srcptr past the terminating zero byte, so that the caller is ready + * to read the next string. + */ +static char * +read_gucstate(char **srcptr, char *srcend) +{ + char *retptr = *srcptr; + char *ptr; + + if (*srcptr >= srcend) + elog(ERROR, "incomplete GUC state"); + + /* The string variables are all null terminated */ + for (ptr = *srcptr; ptr < srcend && *ptr != '\0'; ptr++) + ; + + if (ptr > srcend) + elog(ERROR, "could not find null terminator in GUC state"); + + /* Set the new position to the byte following the terminating NUL */ + *srcptr = ptr + 1; + + return retptr; +} + +/* Binary read version of read_gucstate(). Copies into dest */ +static void +read_gucstate_binary(char **srcptr, char *srcend, void *dest, Size size) +{ + if (*srcptr + size > srcend) + elog(ERROR, "incomplete GUC state"); + + memcpy(dest, *srcptr, size); + *srcptr += size; +} + +/* + * RestoreGUCState: + * Reads the GUC state at the specified address and updates the GUCs with the + * values read from the GUC state. + */ +void +RestoreGUCState(void *gucstate) +{ + char *varname, + *varvalue, + *varsourcefile; + int varsourceline; + GucSource varsource; + GucContext varscontext; + char *srcptr = (char *) gucstate; + char *srcend; + Size len; + int i; + + /* See comment at can_skip_gucvar(). */ + for (i = 0; i < num_guc_variables; i++) + if (!can_skip_gucvar(guc_variables[i])) + InitializeOneGUCOption(guc_variables[i]); + + /* First item is the length of the subsequent data */ + memcpy(&len, gucstate, sizeof(len)); + + srcptr += sizeof(len); + srcend = srcptr + len; + + while (srcptr < srcend) + { + int result; + + if ((varname = read_gucstate(&srcptr, srcend)) == NULL) + break; + + varvalue = read_gucstate(&srcptr, srcend); + varsourcefile = read_gucstate(&srcptr, srcend); + if (varsourcefile[0]) + read_gucstate_binary(&srcptr, srcend, + &varsourceline, sizeof(varsourceline)); + read_gucstate_binary(&srcptr, srcend, + &varsource, sizeof(varsource)); + read_gucstate_binary(&srcptr, srcend, + &varscontext, sizeof(varscontext)); + + result = set_config_option(varname, varvalue, varscontext, varsource, + GUC_ACTION_SET, true, ERROR, true); + if (result <= 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("parameter \"%s\" could not be set", varname))); + if (varsourcefile[0]) + set_config_sourcefile(varname, varsourcefile, varsourceline); + } +} /* * A little "long argument" simulation, although not quite GNU @@ -8555,7 +8952,7 @@ ProcessGUCArray(ArrayType *array, (void) set_config_option(name, value, context, source, - action, true, 0); + action, true, 0, false); free(name); if (value) @@ -8858,7 +9255,7 @@ validate_option_array_item(const char *name, const char *value, /* test for permissions and valid option value */ (void) set_config_option(name, value, superuser() ? PGC_SUSET : PGC_USERSET, - PGC_S_TEST, GUC_ACTION_SET, false, 0); + PGC_S_TEST, GUC_ACTION_SET, false, 0, false); return true; } diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 2b2aaf4ac2..395858b102 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -339,7 +339,8 @@ extern bool parse_int(const char *value, int *result, int flags, 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); + GucAction action, bool changeVal, int elevel, + bool is_reload); extern void AlterSystemSetConfigFile(AlterSystemStmt *setstmt); extern char *GetConfigOptionByName(const char *name, const char **varname); extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow); @@ -363,6 +364,11 @@ extern void write_nondefault_variables(GucContext context); extern void read_nondefault_variables(void); #endif +/* GUC serialization */ +extern Size EstimateGUCStateSpace(void); +extern void SerializeGUCState(Size maxsize, char *start_address); +extern void RestoreGUCState(void *gucstate); + /* Support for messages reported from GUC check hooks */ extern PGDLLIMPORT char *GUC_check_errmsg_string;