diff --git a/src/common/backend/utils/misc/guc.cpp b/src/common/backend/utils/misc/guc.cpp index a7977f878..000be09fb 100644 --- a/src/common/backend/utils/misc/guc.cpp +++ b/src/common/backend/utils/misc/guc.cpp @@ -198,6 +198,18 @@ #define MAX_PASSWORD_ASSIGNED_CHARACTER 999 /* max length of password */ #define MAX_PASSWORD_LENGTH 999 +/* + * Precision with which REAL type guc values are to be printed for GUC + * serialization. + */ +static const int REALTYPE_PRECISION = 17; + +static const int TYPICAL_LEN_RANGE_OF_VALUE = 1000; +static const int MAX_DISPLAY_LEN_OF_BOOL = 5; +static const int TYPICAL_DISPLAY_LEN_OF_INT = 4; +static const int MAX_DISPLAY_LEN_OF_INT = 11; +static const int MAX_DISPLAY_LEN_OF_INT64 = 20; +static const int LEN_OF_REAL_EXCEPT_PRECISION = 8; extern volatile int synchronous_commit; extern volatile bool most_available_sync; @@ -17767,6 +17779,542 @@ ArrayType* GUCArrayReset(ArrayType* array) return newarray; } +/* GUC serialization */ +static bool CanSkipGucvar(const struct config_generic* gconf); +static Size EstimateVariableSize(const struct config_generic* gconf); +static void DoSerialize(char** destptr, Size& maxbytes, const char* fmt, ...); +static void DoSerializeBinary(char** destptr, Size& maxbytes, const char* val, Size valsize); +static void SerializeVariable(char** destptr, Size& maxbytes, const struct config_generic* gconf); +static void InitializeOneGUCOption(struct config_generic& gconf); +static char* ReadGucstate(char** srcptr, const char* srcend); +static void ReadGucstateBinary(char** srcptr, const char* srcend, char* dest, Size size); + +/* + * CanSkipGucvar: + * 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 RestoreGUCState() never changes them. + */ +static bool CanSkipGucvar(const struct config_generic* gconf) +{ + return gconf->context == PGC_POSTMASTER || + gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT || + strcmp(gconf->name, "role") == 0; +} + + +/* + * EstimateVariableSize: + * Estimate max size for dumping the given GUC variable. + */ +static Size EstimateVariableSize(const struct config_generic* gconf) +{ + Size size; + Size valsize = 0; + + if (CanSkipGucvar(gconf)) { + return 0; + } + + size = strlen(gconf->name) + 1; + + /* Get the maximum display length of the GUC value. */ + switch (gconf->vartype) { + case PGC_BOOL: { + valsize = MAX_DISPLAY_LEN_OF_BOOL; + break; + } + + case PGC_INT: { + const struct config_int* conf = (const 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) < TYPICAL_LEN_RANGE_OF_VALUE) { + valsize = TYPICAL_DISPLAY_LEN_OF_INT; + } else { + valsize = MAX_DISPLAY_LEN_OF_INT; + } + break; + } + + case PGC_INT64: { + const struct config_int* conf = (const struct config_int*)gconf; + + if (Abs(*conf->variable) < TYPICAL_LEN_RANGE_OF_VALUE) { + valsize = TYPICAL_DISPLAY_LEN_OF_INT; + } else { + valsize = MAX_DISPLAY_LEN_OF_INT64; /* Maximum value is 9,223,372,036,854,775,807, i.e. 19 chars. */ + } + 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 = LEN_OF_REAL_EXCEPT_PRECISION + REALTYPE_PRECISION; + break; + } + + case PGC_STRING: { + const struct config_string* conf = (const struct config_string*)gconf; + /* + * If the value is NULL, we transmit it as an empty string. + * Although this is not physically the same value, GUC + * generally treats a NULL the same as empty string. + */ + if (*conf->variable) { + valsize = strlen(*conf->variable); + } else { + valsize = 0; + } + break; + } + + case PGC_ENUM: { + struct config_enum* conf = (struct config_enum*) gconf; + valsize = strlen(config_enum_lookup_by_value(conf, *conf->variable)); + break; + } + default: + 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 && gconf->sourcefile[0]) { + 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 < u_sess->num_guc_variables; i++) { + size = add_size(size, EstimateVariableSize(u_sess->guc_variables[i])); + } + + return size; +} + +/* + * DoSerialize: + * 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 DoSerialize(char** destptr, Size& maxbytes, const char* fmt, ...) +{ + va_list vargs; + int nRet; + + if (maxbytes == 0) { + elog(ERROR, "not enough space to serialize GUC state"); + } + + va_start(vargs, fmt); + nRet = vsnprintf_s(*destptr, maxbytes, maxbytes - 1, fmt, vargs); + securec_check_ss(nRet, "\0", "\0"); + 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 (nRet < 0) { + /* Shouldn't happen. Better show errno description. */ + elog(ERROR, "vsnprintf failed: %s with format string \"%s\"", strerror(nRet), fmt); + } + if (nRet >= static_cast(maxbytes)) { + /* This shouldn't happen either, really. */ + elog(ERROR, "not enough space to serialize GUC state"); + } + + /* Shift the destptr ahead of the null terminator */ + *destptr += nRet + 1; + maxbytes -= static_cast(nRet) + 1; +} + +/* Binary copy version of DoSerialize() */ +static void DoSerializeBinary(char** destptr, Size& maxbytes, const char* val, Size valsize) +{ + if (valsize > maxbytes) { + elog(ERROR, "not enough space to serialize GUC state"); + } + + errno_t rc = memcpy_s(*destptr, maxbytes, val, valsize); + securec_check(rc, "\0", "\0"); + *destptr += valsize; + maxbytes -= valsize; +} + +/* + * SerializeVariable: + * Dumps name, value and other information of a GUC variable into destptr. + */ +static void SerializeVariable(char** destptr, Size& maxbytes, const struct config_generic* gconf) +{ + if (CanSkipGucvar(gconf)) { + return; + } + + DoSerialize(destptr, maxbytes, "%s", gconf->name); + + switch (gconf->vartype) { + case PGC_BOOL: { + const struct config_bool* conf = (const struct config_bool*)gconf; + DoSerialize(destptr, maxbytes, (*conf->variable ? "true" : "false")); + break; + } + + case PGC_INT: { + const struct config_int* conf = (const struct config_int*)gconf; + DoSerialize(destptr, maxbytes, "%d", *conf->variable); + break; + } + + case PGC_INT64: { + const struct config_int64* conf = (const struct config_int64*)gconf; + DoSerialize(destptr, maxbytes, "%ld", *conf->variable); + break; + } + + case PGC_REAL: { + const struct config_real* conf = (const struct config_real*)gconf; + DoSerialize(destptr, maxbytes, "%.*e", REALTYPE_PRECISION, *conf->variable); + break; + } + + case PGC_STRING:{ + const struct config_string* conf = (const struct config_string*)gconf; + DoSerialize(destptr, maxbytes, "%s", *conf->variable ? *conf->variable : ""); + break; + } + + case PGC_ENUM:{ + struct config_enum* conf = (struct config_enum*)gconf; + DoSerialize(destptr, maxbytes, "%s", config_enum_lookup_by_value(conf, *conf->variable)); + break; + } + default: + break; + } + + DoSerialize(destptr, maxbytes, "%s", (gconf->sourcefile ? gconf->sourcefile : "")); + + if (gconf->sourcefile) { + DoSerializeBinary(destptr, maxbytes, reinterpret_cast(&gconf->sourceline), + sizeof(gconf->sourceline)); + } + + DoSerializeBinary(destptr, maxbytes, reinterpret_cast(&gconf->source), sizeof(gconf->source)); + DoSerializeBinary(destptr, maxbytes, reinterpret_cast(&gconf->scontext), sizeof(gconf->scontext)); +} + +/* + * SerializeGUCState: + * Dumps the complete GUC state onto the memory location at startAddress. + */ +void SerializeGUCState(Size maxsize, char* startAddress) +{ + char *curptr; + Size actualSize; + Size bytesLeft; + int i; + + /* Reserve space for saving the actual size of the guc state */ + Assert(maxsize > sizeof(actualSize)); + curptr = startAddress + sizeof(actualSize); + bytesLeft = maxsize - sizeof(actualSize); + + for (i = 0; i < u_sess->num_guc_variables; i++) { + SerializeVariable(&curptr, bytesLeft, u_sess->guc_variables[i]); + } + + /* Store actual size without assuming alignment of startAddress. */ + actualSize = maxsize - bytesLeft - sizeof(actualSize); + errno_t rc = memcpy_s(startAddress, maxsize, &actualSize, sizeof(actualSize)); + securec_check(rc, "\0", "\0"); +} + +/* + * Initialize one GUC option variable to its compiled-in default. + * + * Note: the reason for calling check_hooks is not that we think the boot_val + * might fail, but that the hooks might wish to compute an "extra" struct. + */ +static void InitializeOneGUCOption(struct config_generic& gconf) +{ + gconf.status = 0; + gconf.source = PGC_S_DEFAULT; + gconf.reset_source = PGC_S_DEFAULT; + gconf.scontext = PGC_INTERNAL; + gconf.reset_scontext = PGC_INTERNAL; + gconf.stack = NULL; + gconf.extra = NULL; + gconf.sourcefile = NULL; + gconf.sourceline = 0; + + switch (gconf.vartype) { + case PGC_BOOL: { + struct config_bool *conf = (struct config_bool*)&gconf; + bool newval = conf->boot_val; + void* extra = NULL; + + if (!call_bool_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) { + elog(FATAL, "failed to initialize %s to %d", conf->gen.name, static_cast(newval)); + } + if (conf->assign_hook) { + (*conf->assign_hook) (newval, extra); + } + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + + case PGC_INT: { + struct config_int* conf = (struct config_int*)&gconf; + int newval = conf->boot_val; + void* extra = NULL; + + Assert(newval >= conf->min); + Assert(newval <= conf->max); + if (!call_int_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) { + elog(FATAL, "failed to initialize %s to %d", conf->gen.name, newval); + } + if (conf->assign_hook) { + (*conf->assign_hook) (newval, extra); + } + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + + case PGC_INT64: { + struct config_int64* conf = (struct config_int64*)&gconf; + int64 newval = conf->boot_val; + void* extra = NULL; + + Assert(newval >= conf->min); + Assert(newval <= conf->max); + if (!call_int64_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) { + elog(FATAL, "failed to initialize %s to %ld", conf->gen.name, newval); + } + if (conf->assign_hook) { + (*conf->assign_hook) (newval, extra); + } + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + + case PGC_REAL: { + struct config_real* conf = (struct config_real*)&gconf; + double newval = conf->boot_val; + void* extra = NULL; + + Assert(newval >= conf->min); + Assert(newval <= conf->max); + if (!call_real_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) { + elog(FATAL, "failed to initialize %s to %g", conf->gen.name, newval); + } + if (conf->assign_hook) { + (*conf->assign_hook) (newval, extra); + } + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + + case PGC_STRING: { + struct config_string* conf = (struct config_string*)&gconf; + char* newval; + void* extra = NULL; + + /* non-NULL boot_val must always get strdup'd */ + if (conf->boot_val != NULL) { + newval = guc_strdup(FATAL, conf->boot_val); + } else { + newval = NULL; + } + + if (!call_string_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) { + elog(FATAL, "failed to initialize %s to \"%s\"", conf->gen.name, newval ? newval : ""); + } + if (conf->assign_hook) { + (*conf->assign_hook) (newval, extra); + } + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + + case PGC_ENUM: { + struct config_enum *conf = (struct config_enum*)&gconf; + int newval = conf->boot_val; + void* extra = NULL; + + if (!call_enum_check_hook(conf, &newval, &extra, PGC_S_DEFAULT, LOG)) { + elog(FATAL, "failed to initialize %s to %d", conf->gen.name, newval); + } + if (conf->assign_hook) { + (*conf->assign_hook) (newval, extra); + } + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + default: + break; + } +} + +/* + * ReadGucstate: + * 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* ReadGucstate(char** srcptr, const 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 ReadGucstate(). Copies into dest */ +static void ReadGucstateBinary(char** srcptr, const char* srcend, char* dest, Size size) +{ + if (*srcptr + size > srcend) { + elog(ERROR, "incomplete GUC state"); + } + + errno_t rc = memcpy_s(dest, size, *srcptr, size); + securec_check(rc, "\0", "\0"); + *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(char* gucstate) +{ + char* varname; + char* varvalue; + char* varsourcefile; + int varsourceline; + GucSource varsource; + GucContext varscontext; + char* srcptr = gucstate; + char* srcend; + Size len; + int i; + + /* See comment at can_skip_gucvar(). */ + for (i = 0; i < u_sess->num_guc_variables; i++) { + if (!CanSkipGucvar(u_sess->guc_variables[i])) { + InitializeOneGUCOption(*u_sess->guc_variables[i]); + } + } + /* First item is the length of the subsequent data */ + errno_t rc = memcpy_s(&len, sizeof(len), gucstate, sizeof(len)); + securec_check(rc, "\0", "\0"); + srcptr += sizeof(len); + srcend = srcptr + len; + + while (srcptr < srcend) { + int result; + varname = ReadGucstate(&srcptr, srcend); + varvalue = ReadGucstate(&srcptr, srcend); + varsourcefile = ReadGucstate(&srcptr, srcend); + + if (varsourcefile[0]) { + ReadGucstateBinary(&srcptr, srcend, + reinterpret_cast(&varsourceline), sizeof(varsourceline)); + } else { + varsourceline = 0; + } + ReadGucstateBinary(&srcptr, srcend, + reinterpret_cast(&varsource), sizeof(varsource)); + ReadGucstateBinary(&srcptr, srcend, + reinterpret_cast(&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); + } + } +} + /* * Validate a proposed option setting for GUCArrayAdd/Delete/Reset. * diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 14640be87..d99e9e4f8 100755 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -266,6 +266,10 @@ extern ArrayType* GUCArrayAdd(ArrayType* array, const char* name, const char* va extern ArrayType* GUCArrayDelete(ArrayType* array, const char* name); extern ArrayType* GUCArrayReset(ArrayType* array); +extern Size EstimateGUCStateSpace(void); +extern void SerializeGUCState(Size maxsize, char *start_address); +extern void RestoreGUCState(char *gucstate); + #ifdef EXEC_BACKEND extern void write_nondefault_variables(GucContext context); extern void read_nondefault_variables(void);