diff options
Diffstat (limited to 'src/backend/utils/misc')
-rw-r--r-- | src/backend/utils/misc/.gitignore | 1 | ||||
-rw-r--r-- | src/backend/utils/misc/Makefile | 46 | ||||
-rw-r--r-- | src/backend/utils/misc/README | 295 | ||||
-rw-r--r-- | src/backend/utils/misc/guc-file.c | 3231 | ||||
-rw-r--r-- | src/backend/utils/misc/guc-file.l | 1227 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 13003 | ||||
-rw-r--r-- | src/backend/utils/misc/help_config.c | 137 | ||||
-rw-r--r-- | src/backend/utils/misc/pg_config.c | 51 | ||||
-rw-r--r-- | src/backend/utils/misc/pg_controldata.c | 353 | ||||
-rw-r--r-- | src/backend/utils/misc/pg_rusage.c | 73 | ||||
-rw-r--r-- | src/backend/utils/misc/postgresql.conf.sample | 813 | ||||
-rw-r--r-- | src/backend/utils/misc/ps_status.c | 449 | ||||
-rw-r--r-- | src/backend/utils/misc/queryenvironment.c | 144 | ||||
-rw-r--r-- | src/backend/utils/misc/queryjumble.c | 869 | ||||
-rw-r--r-- | src/backend/utils/misc/rls.c | 167 | ||||
-rw-r--r-- | src/backend/utils/misc/sampling.c | 304 | ||||
-rw-r--r-- | src/backend/utils/misc/superuser.c | 107 | ||||
-rw-r--r-- | src/backend/utils/misc/timeout.c | 834 | ||||
-rw-r--r-- | src/backend/utils/misc/tzparser.c | 484 |
19 files changed, 22588 insertions, 0 deletions
diff --git a/src/backend/utils/misc/.gitignore b/src/backend/utils/misc/.gitignore new file mode 100644 index 0000000..495b1ae --- /dev/null +++ b/src/backend/utils/misc/.gitignore @@ -0,0 +1 @@ +/guc-file.c diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile new file mode 100644 index 0000000..1d5327c --- /dev/null +++ b/src/backend/utils/misc/Makefile @@ -0,0 +1,46 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for utils/misc +# +# IDENTIFICATION +# src/backend/utils/misc/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/utils/misc +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) + +OBJS = \ + guc.o \ + help_config.o \ + pg_config.o \ + pg_controldata.o \ + pg_rusage.o \ + ps_status.o \ + queryenvironment.o \ + queryjumble.o \ + rls.o \ + sampling.o \ + superuser.o \ + timeout.o \ + tzparser.o + +# This location might depend on the installation directories. Therefore +# we can't substitute it into pg_config.h. +ifdef krb_srvtab +override CPPFLAGS += -DPG_KRB_SRVTAB='"$(krb_srvtab)"' +endif + +include $(top_srcdir)/src/backend/common.mk + +# guc-file is compiled as part of guc +guc.o: guc-file.c + +# Note: guc-file.c is not deleted by 'make clean', +# since we want to ship it in distribution tarballs. +clean: + @rm -f lex.yy.c diff --git a/src/backend/utils/misc/README b/src/backend/utils/misc/README new file mode 100644 index 0000000..6e29438 --- /dev/null +++ b/src/backend/utils/misc/README @@ -0,0 +1,295 @@ +src/backend/utils/misc/README + +GUC Implementation Notes +======================== + +The GUC (Grand Unified Configuration) module implements configuration +variables of multiple types (currently boolean, enum, int, real, and string). +Variable settings can come from various places, with a priority ordering +determining which setting is used. + + +Per-Variable Hooks +------------------ + +Each variable known to GUC can optionally have a check_hook, an +assign_hook, and/or a show_hook to provide customized behavior. +Check hooks are used to perform validity checking on variable values +(above and beyond what GUC can do), to compute derived settings when +nontrivial work is needed to do that, and optionally to "canonicalize" +user-supplied values. Assign hooks are used to update any derived state +that needs to change when a GUC variable is set. Show hooks are used to +modify the default SHOW display for a variable. + + +If a check_hook is provided, it points to a function of the signature + bool check_hook(datatype *newvalue, void **extra, GucSource source) +The "newvalue" argument is of type bool *, int *, double *, or char ** +for bool, int/enum, real, or string variables respectively. The check +function should validate the proposed new value, and return true if it is +OK or false if not. The function can optionally do a few other things: + +* When rejecting a bad proposed value, it may be useful to append some +additional information to the generic "invalid value for parameter FOO" +complaint that guc.c will emit. To do that, call + void GUC_check_errdetail(const char *format, ...) +where the format string and additional arguments follow the rules for +errdetail() arguments. The resulting string will be emitted as the +DETAIL line of guc.c's error report, so it should follow the message style +guidelines for DETAIL messages. There is also + void GUC_check_errhint(const char *format, ...) +which can be used in the same way to append a HINT message. +Occasionally it may even be appropriate to override guc.c's generic primary +message or error code, which can be done with + void GUC_check_errcode(int sqlerrcode) + void GUC_check_errmsg(const char *format, ...) +In general, check_hooks should avoid throwing errors directly if possible, +though this may be impractical to avoid for some corner cases such as +out-of-memory. + +* Since the newvalue is pass-by-reference, the function can modify it. +This might be used for example to canonicalize the spelling of a string +value, round off a buffer size to the nearest supported value, or replace +a special value such as "-1" with a computed default value. If the +function wishes to replace a string value, it must malloc (not palloc) +the replacement value, and be sure to free() the previous value. + +* Derived information, such as the role OID represented by a user name, +can be stored for use by the assign hook. To do this, malloc (not palloc) +storage space for the information, and return its address at *extra. +guc.c will automatically free() this space when the associated GUC setting +is no longer of interest. *extra is initialized to NULL before call, so +it can be ignored if not needed. + +The "source" argument indicates the source of the proposed new value, +If it is >= PGC_S_INTERACTIVE, then we are performing an interactive +assignment (e.g., a SET command). But when source < PGC_S_INTERACTIVE, +we are reading a non-interactive option source, such as postgresql.conf. +This is sometimes needed to determine whether a setting should be +allowed. The check_hook might also look at the current actual value of +the variable to determine what is allowed. + +Note that check hooks are sometimes called just to validate a value, +without any intention of actually changing the setting. Therefore the +check hook must *not* take any action based on the assumption that an +assignment will occur. + + +If an assign_hook is provided, it points to a function of the signature + void assign_hook(datatype newvalue, void *extra) +where the type of "newvalue" matches the kind of variable, and "extra" +is the derived-information pointer returned by the check_hook (always +NULL if there is no check_hook). This function is called immediately +before actually setting the variable's value (so it can look at the actual +variable to determine the old value, for example to avoid doing work when +the value isn't really changing). + +Note that there is no provision for a failure result code. assign_hooks +should never fail except under the most dire circumstances, since a failure +may for example result in GUC settings not being rolled back properly during +transaction abort. In general, try to do anything that could conceivably +fail in a check_hook instead, and pass along the results in an "extra" +struct, so that the assign hook has little to do beyond copying the data to +someplace. This applies particularly to catalog lookups: any required +lookups must be done in the check_hook, since the assign_hook may be +executed during transaction rollback when lookups will be unsafe. + +Note that check_hooks are sometimes called outside any transaction, too. +This happens when processing the wired-in "bootstrap" value, values coming +from the postmaster command line or environment, or values coming from +postgresql.conf. Therefore, any catalog lookups done in a check_hook +should be guarded with an IsTransactionState() test, and there must be a +fallback path to allow derived values to be computed during the first +subsequent use of the GUC setting within a transaction. A typical +arrangement is for the catalog values computed by the check_hook and +installed by the assign_hook to be used only for the remainder of the +transaction in which the new setting is made. Each subsequent transaction +looks up the values afresh on first use. This arrangement is useful to +prevent use of stale catalog values, independently of the problem of +needing to check GUC values outside a transaction. + + +If a show_hook is provided, it points to a function of the signature + const char *show_hook(void) +This hook allows variable-specific computation of the value displayed +by SHOW (and other SQL features for showing GUC variable values). +The return value can point to a static buffer, since show functions are +not used reentrantly. + + +Saving/Restoring GUC Variable Values +------------------------------------ + +Prior values of configuration variables must be remembered in order to deal +with several special cases: RESET (a/k/a SET TO DEFAULT), rollback of SET +on transaction abort, rollback of SET LOCAL at transaction end (either +commit or abort), and save/restore around a function that has a SET option. +RESET is defined as selecting the value that would be effective had there +never been any SET commands in the current session. + +To handle these cases we must keep track of many distinct values for each +variable. The primary values are: + +* actual variable contents always the current effective value + +* reset_val the value to use for RESET + +(Each GUC entry also has a boot_val which is the wired-in default value. +This is assigned to the reset_val and the actual variable during +InitializeGUCOptions(). The boot_val is also consulted to restore the +correct reset_val if SIGHUP processing discovers that a variable formerly +specified in postgresql.conf is no longer set there.) + +In addition to the primary values, there is a stack of former effective +values that might need to be restored in future. Stacking and unstacking +is controlled by the GUC "nest level", which is zero when outside any +transaction, one at top transaction level, and incremented for each +open subtransaction or function call with a SET option. A stack entry +is made whenever a GUC variable is first modified at a given nesting level. +(Note: the reset_val need not be stacked because it is only changed by +non-transactional operations.) + +A stack entry has a state, a prior value of the GUC variable, a remembered +source of that prior value, and depending on the state may also have a +"masked" value. The masked value is needed when SET followed by SET LOCAL +occur at the same nest level: the SET's value is masked but must be +remembered to restore after transaction commit. + +During initialization we set the actual value and reset_val based on +whichever non-interactive source has the highest priority. They will +have the same value. + +The possible transactional operations on a GUC value are: + +Entry to a function with a SET option: + + Push a stack entry with the prior variable value and state SAVE, + then set the variable. + +Plain SET command: + + If no stack entry of current level: + Push new stack entry w/prior value and state SET + else if stack entry's state is SAVE, SET, or LOCAL: + change stack state to SET, don't change saved value + (here we are forgetting effects of prior set action) + else (entry must have state SET+LOCAL): + discard its masked value, change state to SET + (here we are forgetting effects of prior SET and SET LOCAL) + Now set new value. + +SET LOCAL command: + + If no stack entry of current level: + Push new stack entry w/prior value and state LOCAL + else if stack entry's state is SAVE or LOCAL or SET+LOCAL: + no change to stack entry + (in SAVE case, SET LOCAL will be forgotten at func exit) + else (entry must have state SET): + put current active into its masked slot, set state SET+LOCAL + Now set new value. + +Transaction or subtransaction abort: + + Pop stack entries, restoring prior value, until top < subxact depth + +Transaction or subtransaction commit (incl. successful function exit): + + While stack entry level >= subxact depth + + if entry's state is SAVE: + pop, restoring prior value + else if level is 1 and entry's state is SET+LOCAL: + pop, restoring *masked* value + else if level is 1 and entry's state is SET: + pop, discarding old value + else if level is 1 and entry's state is LOCAL: + pop, restoring prior value + else if there is no entry of exactly level N-1: + decrement entry's level, no other state change + else + merge entries of level N-1 and N as specified below + +The merged entry will have level N-1 and prior = older prior, so easiest +to keep older entry and free newer. There are 12 possibilities since +we already handled level N state = SAVE: + +N-1 N + +SAVE SET discard top prior, set state SET +SAVE LOCAL discard top prior, no change to stack entry +SAVE SET+LOCAL discard top prior, copy masked, state S+L + +SET SET discard top prior, no change to stack entry +SET LOCAL copy top prior to masked, state S+L +SET SET+LOCAL discard top prior, copy masked, state S+L + +LOCAL SET discard top prior, set state SET +LOCAL LOCAL discard top prior, no change to stack entry +LOCAL SET+LOCAL discard top prior, copy masked, state S+L + +SET+LOCAL SET discard top prior and second masked, state SET +SET+LOCAL LOCAL discard top prior, no change to stack entry +SET+LOCAL SET+LOCAL discard top prior, copy masked, state S+L + + +RESET is executed like a SET, but using the reset_val as the desired new +value. (We do not provide a RESET LOCAL command, but SET LOCAL TO DEFAULT +has the same behavior that RESET LOCAL would.) The source associated with +the reset_val also becomes associated with the actual value. + +If SIGHUP is received, the GUC code rereads the postgresql.conf +configuration file (this does not happen in the signal handler, but at +next return to main loop; note that it can be executed while within a +transaction). New values from postgresql.conf are assigned to actual +variable, reset_val, and stacked actual values, but only if each of +these has a current source priority <= PGC_S_FILE. (It is thus possible +for reset_val to track the config-file setting even if there is +currently a different interactive value of the actual variable.) + +The check_hook, assign_hook and show_hook routines work only with the +actual variable, and are not directly aware of the additional values +maintained by GUC. + + +GUC Memory Handling +------------------- + +String variable values are allocated with malloc/strdup, not with the +palloc/pstrdup mechanisms. We would need to keep them in a permanent +context anyway, and malloc gives us more control over handling +out-of-memory failures. + +We allow a string variable's actual value, reset_val, boot_val, and stacked +values to point at the same storage. This makes it slightly harder to free +space (we must test whether a value to be freed isn't equal to any of the +other pointers in the GUC entry or associated stack items). The main +advantage is that we never need to malloc during transaction commit/abort, +so cannot cause an out-of-memory failure there. + +"Extra" structs returned by check_hook routines are managed in the same +way as string values. Note that we support "extra" structs for all types +of GUC variables, although they are mainly useful with strings. + + +GUC and Null String Variables +----------------------------- + +A GUC string variable can have a boot_val of NULL. guc.c handles this +unsurprisingly, assigning the NULL to the underlying C variable. Any code +using such a variable, as well as any hook functions for it, must then be +prepared to deal with a NULL value. + +However, it is not possible to assign a NULL value to a GUC string +variable in any other way: values coming from SET, postgresql.conf, etc, +might be empty strings, but they'll never be NULL. And SHOW displays +a NULL the same as an empty string. It is therefore not appropriate to +treat a NULL value as a distinct user-visible setting. A typical use +for a NULL boot_val is to denote that a value hasn't yet been set for +a variable that will receive a real value later in startup. + +If it's undesirable for code using the underlying C variable to have to +worry about NULL values ever, the variable can be given a non-null static +initializer as well as a non-null boot_val. guc.c will overwrite the +static initializer pointer with a copy of the boot_val during +InitializeGUCOptions, but the variable will never contain a NULL. diff --git a/src/backend/utils/misc/guc-file.c b/src/backend/utils/misc/guc-file.c new file mode 100644 index 0000000..5425174 --- /dev/null +++ b/src/backend/utils/misc/guc-file.c @@ -0,0 +1,3231 @@ +#line 2 "guc-file.c" + +#line 4 "guc-file.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define yy_create_buffer GUC_yy_create_buffer +#define yy_delete_buffer GUC_yy_delete_buffer +#define yy_scan_buffer GUC_yy_scan_buffer +#define yy_scan_string GUC_yy_scan_string +#define yy_scan_bytes GUC_yy_scan_bytes +#define yy_init_buffer GUC_yy_init_buffer +#define yy_flush_buffer GUC_yy_flush_buffer +#define yy_load_buffer_state GUC_yy_load_buffer_state +#define yy_switch_to_buffer GUC_yy_switch_to_buffer +#define yypush_buffer_state GUC_yypush_buffer_state +#define yypop_buffer_state GUC_yypop_buffer_state +#define yyensure_buffer_stack GUC_yyensure_buffer_stack +#define yy_flex_debug GUC_yy_flex_debug +#define yyin GUC_yyin +#define yyleng GUC_yyleng +#define yylex GUC_yylex +#define yylineno GUC_yylineno +#define yyout GUC_yyout +#define yyrestart GUC_yyrestart +#define yytext GUC_yytext +#define yywrap GUC_yywrap +#define yyalloc GUC_yyalloc +#define yyrealloc GUC_yyrealloc +#define yyfree GUC_yyfree + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define GUC_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer GUC_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define GUC_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer GUC_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define GUC_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer GUC_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define GUC_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string GUC_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define GUC_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes GUC_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define GUC_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer GUC_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define GUC_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer GUC_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define GUC_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state GUC_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define GUC_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer GUC_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define GUC_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state GUC_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define GUC_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state GUC_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define GUC_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack GUC_yyensure_buffer_stack +#endif + +#ifdef yylex +#define GUC_yylex_ALREADY_DEFINED +#else +#define yylex GUC_yylex +#endif + +#ifdef yyrestart +#define GUC_yyrestart_ALREADY_DEFINED +#else +#define yyrestart GUC_yyrestart +#endif + +#ifdef yylex_init +#define GUC_yylex_init_ALREADY_DEFINED +#else +#define yylex_init GUC_yylex_init +#endif + +#ifdef yylex_init_extra +#define GUC_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra GUC_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define GUC_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy GUC_yylex_destroy +#endif + +#ifdef yyget_debug +#define GUC_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug GUC_yyget_debug +#endif + +#ifdef yyset_debug +#define GUC_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug GUC_yyset_debug +#endif + +#ifdef yyget_extra +#define GUC_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra GUC_yyget_extra +#endif + +#ifdef yyset_extra +#define GUC_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra GUC_yyset_extra +#endif + +#ifdef yyget_in +#define GUC_yyget_in_ALREADY_DEFINED +#else +#define yyget_in GUC_yyget_in +#endif + +#ifdef yyset_in +#define GUC_yyset_in_ALREADY_DEFINED +#else +#define yyset_in GUC_yyset_in +#endif + +#ifdef yyget_out +#define GUC_yyget_out_ALREADY_DEFINED +#else +#define yyget_out GUC_yyget_out +#endif + +#ifdef yyset_out +#define GUC_yyset_out_ALREADY_DEFINED +#else +#define yyset_out GUC_yyset_out +#endif + +#ifdef yyget_leng +#define GUC_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng GUC_yyget_leng +#endif + +#ifdef yyget_text +#define GUC_yyget_text_ALREADY_DEFINED +#else +#define yyget_text GUC_yyget_text +#endif + +#ifdef yyget_lineno +#define GUC_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno GUC_yyget_lineno +#endif + +#ifdef yyset_lineno +#define GUC_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno GUC_yyset_lineno +#endif + +#ifdef yywrap +#define GUC_yywrap_ALREADY_DEFINED +#else +#define yywrap GUC_yywrap +#endif + +#ifdef yyalloc +#define GUC_yyalloc_ALREADY_DEFINED +#else +#define yyalloc GUC_yyalloc +#endif + +#ifdef yyrealloc +#define GUC_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc GUC_yyrealloc +#endif + +#ifdef yyfree +#define GUC_yyfree_ALREADY_DEFINED +#else +#define yyfree GUC_yyfree +#endif + +#ifdef yytext +#define GUC_yytext_ALREADY_DEFINED +#else +#define yytext GUC_yytext +#endif + +#ifdef yyleng +#define GUC_yyleng_ALREADY_DEFINED +#else +#define yyleng GUC_yyleng +#endif + +#ifdef yyin +#define GUC_yyin_ALREADY_DEFINED +#else +#define yyin GUC_yyin +#endif + +#ifdef yyout +#define GUC_yyout_ALREADY_DEFINED +#else +#define yyout GUC_yyout +#endif + +#ifdef yy_flex_debug +#define GUC_yy_flex_debug_ALREADY_DEFINED +#else +#define yy_flex_debug GUC_yy_flex_debug +#endif + +#ifdef yylineno +#define GUC_yylineno_ALREADY_DEFINED +#else +#define yylineno GUC_yylineno +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +extern int yyleng; + +extern FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + #define YY_LINENO_REWIND_TO(ptr) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, (yytext_ptr) ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = NULL; +static int yy_init = 0; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void yyrestart ( FILE *input_file ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size ); +void yy_delete_buffer ( YY_BUFFER_STATE b ); +void yy_flush_buffer ( YY_BUFFER_STATE b ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer ); +void yypop_buffer_state ( void ); + +static void yyensure_buffer_stack ( void ); +static void yy_load_buffer_state ( void ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len ); + +void *yyalloc ( yy_size_t ); +void *yyrealloc ( void *, yy_size_t ); +void yyfree ( void * ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define GUC_yywrap() (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +FILE *yyin = NULL, *yyout = NULL; + +typedef int yy_state_type; + +extern int yylineno; +int yylineno = 1; + +extern char *yytext; +#ifdef yytext_ptr +#undef yytext_ptr +#endif +#define yytext_ptr yytext + +static yy_state_type yy_get_previous_state ( void ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state ); +static int yy_get_next_buffer ( void ); +static void yynoreturn yy_fatal_error ( const char* msg ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; +#define YY_NUM_RULES 12 +#define YY_END_OF_BUFFER 13 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[41] = + { 0, + 0, 0, 13, 11, 2, 1, 3, 11, 11, 9, + 8, 8, 10, 4, 2, 3, 0, 6, 0, 9, + 8, 8, 9, 0, 8, 8, 7, 7, 4, 4, + 0, 9, 8, 8, 7, 5, 5, 5, 5, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 4, 1, 1, 1, 5, 1, + 1, 1, 6, 1, 7, 8, 9, 10, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 9, 1, 1, + 12, 1, 1, 1, 13, 13, 13, 13, 14, 13, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 1, 16, 1, 1, 17, 1, 13, 13, 13, 13, + + 14, 13, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 18, + 15, 15, 1, 1, 1, 1, 1, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19 + } ; + +static const YY_CHAR yy_meta[20] = + { 0, + 1, 1, 2, 1, 1, 1, 3, 3, 3, 4, + 4, 1, 5, 6, 5, 1, 3, 5, 3 + } ; + +static const flex_int16_t yy_base[48] = + { 0, + 0, 0, 50, 148, 43, 148, 0, 15, 24, 30, + 28, 22, 148, 40, 35, 0, 17, 25, 0, 15, + 0, 10, 0, 52, 0, 54, 10, 66, 79, 0, + 13, 15, 0, 0, 4, 90, 101, 0, 0, 148, + 118, 124, 127, 131, 133, 137, 141 + } ; + +static const flex_int16_t yy_def[48] = + { 0, + 40, 1, 40, 40, 40, 40, 41, 42, 40, 43, + 40, 11, 40, 44, 40, 41, 42, 40, 42, 43, + 11, 11, 20, 40, 45, 40, 46, 40, 44, 29, + 40, 40, 26, 26, 46, 47, 47, 37, 37, 0, + 40, 40, 40, 40, 40, 40, 40 + } ; + +static const flex_int16_t yy_nxt[168] = + { 0, + 4, 5, 6, 7, 8, 9, 9, 10, 4, 11, + 12, 13, 14, 14, 14, 4, 14, 14, 14, 18, + 35, 18, 32, 32, 32, 32, 35, 25, 24, 17, + 19, 20, 19, 21, 22, 20, 15, 22, 22, 25, + 25, 25, 25, 24, 15, 26, 27, 28, 27, 40, + 40, 40, 40, 40, 40, 40, 30, 31, 31, 40, + 40, 32, 32, 33, 33, 40, 34, 34, 25, 40, + 40, 25, 27, 27, 27, 27, 27, 40, 36, 36, + 36, 40, 37, 36, 36, 27, 28, 27, 40, 40, + 40, 40, 40, 40, 40, 30, 27, 27, 27, 40, + + 40, 40, 40, 40, 40, 40, 39, 27, 27, 27, + 40, 40, 40, 40, 40, 40, 40, 39, 16, 40, + 16, 16, 16, 16, 17, 40, 17, 17, 17, 17, + 23, 40, 23, 29, 29, 29, 29, 25, 25, 27, + 27, 27, 27, 38, 38, 38, 38, 3, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40 + } ; + +static const flex_int16_t yy_chk[168] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, + 35, 17, 31, 31, 32, 32, 27, 22, 20, 18, + 8, 9, 17, 9, 9, 11, 15, 11, 11, 12, + 11, 11, 11, 10, 5, 11, 14, 14, 14, 3, + 0, 0, 0, 0, 0, 0, 14, 24, 24, 0, + 0, 24, 24, 26, 26, 0, 26, 26, 26, 0, + 0, 26, 28, 28, 28, 28, 28, 0, 28, 28, + 28, 0, 28, 28, 28, 29, 29, 29, 0, 0, + 0, 0, 0, 0, 0, 29, 36, 36, 36, 0, + + 0, 0, 0, 0, 0, 0, 36, 37, 37, 37, + 0, 0, 0, 0, 0, 0, 0, 37, 41, 0, + 41, 41, 41, 41, 42, 0, 42, 42, 42, 42, + 43, 0, 43, 44, 44, 44, 44, 45, 45, 46, + 46, 46, 46, 47, 47, 47, 47, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40 + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +extern int yy_flex_debug; +int yy_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *yytext; +#line 1 "guc-file.l" +/* -*-pgsql-c-*- */ +/* + * Scanner for the configuration file + * + * Copyright (c) 2000-2022, PostgreSQL Global Development Group + * + * src/backend/utils/misc/guc-file.l + */ +#line 11 "guc-file.l" + +#include "postgres.h" + +#include <ctype.h> +#include <unistd.h> + +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/guc.h" + + +/* + * flex emits a yy_fatal_error() function that it calls in response to + * critical errors like malloc failure, file I/O errors, and detection of + * internal inconsistency. That function prints a message and calls exit(). + * Mutate it to instead call our handler, which jumps out of the parser. + */ +#undef fprintf +#define fprintf(file, fmt, msg) GUC_flex_fatal(msg) + +enum +{ + GUC_ID = 1, + GUC_STRING = 2, + GUC_INTEGER = 3, + GUC_REAL = 4, + GUC_EQUALS = 5, + GUC_UNQUOTED_STRING = 6, + GUC_QUALIFIED_ID = 7, + GUC_EOL = 99, + GUC_ERROR = 100 +}; + +static unsigned int ConfigFileLineno; +static const char *GUC_flex_fatal_errmsg; +static sigjmp_buf *GUC_flex_fatal_jmp; + +static void FreeConfigVariable(ConfigVariable *item); + +static void record_config_file_error(const char *errmsg, + const char *config_file, + int lineno, + ConfigVariable **head_p, + ConfigVariable **tail_p); + +static int GUC_flex_fatal(const char *msg); + +/* LCOV_EXCL_START */ + +#line 809 "guc-file.c" +#define YY_NO_INPUT 1 +#line 811 "guc-file.c" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals ( void ); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( void ); + +int yyget_debug ( void ); + +void yyset_debug ( int debug_flag ); + +YY_EXTRA_TYPE yyget_extra ( void ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined ); + +FILE *yyget_in ( void ); + +void yyset_in ( FILE * _in_str ); + +FILE *yyget_out ( void ); + +void yyset_out ( FILE * _out_str ); + + int yyget_leng ( void ); + +char *yyget_text ( void ); + +int yyget_lineno ( void ); + +void yyset_lineno ( int _line_number ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( void ); +#else +extern int yywrap ( void ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * ); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( void ); +#else +static int input ( void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex (void); + +#define YY_DECL int yylex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + { +#line 93 "guc-file.l" + + +#line 1029 "guc-file.c" + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 41 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_current_state != 40 ); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +#line 95 "guc-file.l" +ConfigFileLineno++; return GUC_EOL; + YY_BREAK +case 2: +YY_RULE_SETUP +#line 96 "guc-file.l" +/* eat whitespace */ + YY_BREAK +case 3: +YY_RULE_SETUP +#line 97 "guc-file.l" +/* eat comment (.* matches anything until newline) */ + YY_BREAK +case 4: +YY_RULE_SETUP +#line 99 "guc-file.l" +return GUC_ID; + YY_BREAK +case 5: +YY_RULE_SETUP +#line 100 "guc-file.l" +return GUC_QUALIFIED_ID; + YY_BREAK +case 6: +YY_RULE_SETUP +#line 101 "guc-file.l" +return GUC_STRING; + YY_BREAK +case 7: +YY_RULE_SETUP +#line 102 "guc-file.l" +return GUC_UNQUOTED_STRING; + YY_BREAK +case 8: +YY_RULE_SETUP +#line 103 "guc-file.l" +return GUC_INTEGER; + YY_BREAK +case 9: +YY_RULE_SETUP +#line 104 "guc-file.l" +return GUC_REAL; + YY_BREAK +case 10: +YY_RULE_SETUP +#line 105 "guc-file.l" +return GUC_EQUALS; + YY_BREAK +case 11: +YY_RULE_SETUP +#line 107 "guc-file.l" +return GUC_ERROR; + YY_BREAK +case 12: +YY_RULE_SETUP +#line 109 "guc-file.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 1143 "guc-file.c" +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = (yytext_ptr); + int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + yy_state_type yy_current_state; + char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 41 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + int yy_is_jam; + char *yy_cp = (yy_c_buf_p); + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 41 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 40); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (int) ((yy_c_buf_p) - (yytext_ptr)); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return 0; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file ); + yy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void yy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ + void yy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf ); + + yyfree( (void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + yy_flush_buffer( b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void yy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void yypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (void) +{ + yy_size_t num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr ) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg ) +{ + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int yyget_lineno (void) +{ + + return yylineno; +} + +/** Get the input stream. + * + */ +FILE *yyget_in (void) +{ + return yyin; +} + +/** Get the output stream. + * + */ +FILE *yyget_out (void) +{ + return yyout; +} + +/** Get the length of the current token. + * + */ +int yyget_leng (void) +{ + return yyleng; +} + +/** Get the current token. + * + */ + +char *yyget_text (void) +{ + return yytext; +} + +/** Set the current line number. + * @param _line_number line number + * + */ +void yyset_lineno (int _line_number ) +{ + + yylineno = _line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str ) +{ + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str ) +{ + yyout = _out_str ; +} + +int yyget_debug (void) +{ + return yy_flex_debug; +} + +void yyset_debug (int _bdebug ) +{ + yy_flex_debug = _bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = NULL; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = NULL; + (yy_init) = 0; + (yy_start) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(); + } + + /* Destroy the stack itself. */ + yyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n ) +{ + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s ) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size ) +{ + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size ) +{ + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr ) +{ + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 109 "guc-file.l" + + +/* LCOV_EXCL_STOP */ + +/* + * Exported function to read and process the configuration file. The + * parameter indicates in what context the file is being read --- either + * postmaster startup (including standalone-backend startup) or SIGHUP. + * All options mentioned in the configuration file are set to new values. + * If a hard error occurs, no values will be changed. (There can also be + * errors that prevent just one value from being changed.) + */ +void +ProcessConfigFile(GucContext context) +{ + int elevel; + MemoryContext config_cxt; + MemoryContext caller_cxt; + + /* + * Config files are processed on startup (by the postmaster only) and on + * SIGHUP (by the postmaster and its children) + */ + Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) || + context == PGC_SIGHUP); + + /* + * To avoid cluttering the log, only the postmaster bleats loudly about + * problems with the config file. + */ + elevel = IsUnderPostmaster ? DEBUG2 : LOG; + + /* + * This function is usually called within a process-lifespan memory + * context. To ensure that any memory leaked during GUC processing does + * not accumulate across repeated SIGHUP cycles, do the work in a private + * context that we can free at exit. + */ + config_cxt = AllocSetContextCreate(CurrentMemoryContext, + "config file processing", + ALLOCSET_DEFAULT_SIZES); + caller_cxt = MemoryContextSwitchTo(config_cxt); + + /* + * Read and apply the config file. We don't need to examine the result. + */ + (void) ProcessConfigFileInternal(context, true, elevel); + + /* Clean up */ + MemoryContextSwitchTo(caller_cxt); + MemoryContextDelete(config_cxt); +} + +/* + * This function handles both actual config file (re)loads and execution of + * show_all_file_settings() (i.e., the pg_file_settings view). In the latter + * case we don't apply any of the settings, but we make all the usual validity + * checks, and we return the ConfigVariable list so that it can be printed out + * by show_all_file_settings(). + */ +static ConfigVariable * +ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel) +{ + bool error = false; + bool applying = false; + const char *ConfFileWithError; + ConfigVariable *item, + *head, + *tail; + int i; + + /* Parse the main config file into a list of option names and values */ + ConfFileWithError = ConfigFileName; + head = tail = NULL; + + if (!ParseConfigFile(ConfigFileName, true, + NULL, 0, 0, elevel, + &head, &tail)) + { + /* Syntax error(s) detected in the file, so bail out */ + error = true; + goto bail_out; + } + + /* + * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to + * replace any parameters set by ALTER SYSTEM command. Because this file + * is in the data directory, we can't read it until the DataDir has been + * set. + */ + if (DataDir) + { + if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false, + NULL, 0, 0, elevel, + &head, &tail)) + { + /* Syntax error(s) detected in the file, so bail out */ + error = true; + ConfFileWithError = PG_AUTOCONF_FILENAME; + goto bail_out; + } + } + else + { + /* + * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be + * read. In this case, we don't want to accept any settings but + * data_directory from postgresql.conf, because they might be + * overwritten with settings in the PG_AUTOCONF_FILENAME file which + * will be read later. OTOH, since data_directory isn't allowed in the + * PG_AUTOCONF_FILENAME file, it will never be overwritten later. + */ + ConfigVariable *newlist = NULL; + + /* + * Prune all items except the last "data_directory" from the list. + */ + for (item = head; item; item = item->next) + { + if (!item->ignore && + strcmp(item->name, "data_directory") == 0) + newlist = item; + } + + if (newlist) + newlist->next = NULL; + head = tail = newlist; + + /* + * Quick exit if data_directory is not present in file. + * + * We need not do any further processing, in particular we don't set + * PgReloadTime; that will be set soon by subsequent full loading of + * the config file. + */ + if (head == NULL) + goto bail_out; + } + + /* + * 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. + */ + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *gconf = guc_variables[i]; + + gconf->status &= ~GUC_IS_IN_FILE; + } + + /* + * Check if all the supplied option names are valid, as an additional + * quasi-syntactic check on the validity of the config file. It is + * important that the postmaster and all backends agree on the results of + * this phase, else we will have strange inconsistencies about which + * processes accept a config file update and which don't. Hence, unknown + * custom variable names have to be accepted without complaint. For the + * same reason, we don't attempt to validate the options' values here. + * + * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC + * variable mentioned in the file; and we detect duplicate entries in the + * file and mark the earlier occurrences as ignorable. + */ + for (item = head; item; item = item->next) + { + struct config_generic *record; + + /* Ignore anything already marked as ignorable */ + if (item->ignore) + continue; + + /* + * Try to find the variable; but do not create a custom placeholder if + * it's not there already. + */ + record = find_option(item->name, false, true, elevel); + + if (record) + { + /* If it's already marked, then this is a duplicate entry */ + if (record->status & GUC_IS_IN_FILE) + { + /* + * Mark the earlier occurrence(s) as dead/ignorable. We could + * avoid the O(N^2) behavior here with some additional state, + * but it seems unlikely to be worth the trouble. + */ + ConfigVariable *pitem; + + for (pitem = head; pitem != item; pitem = pitem->next) + { + if (!pitem->ignore && + strcmp(pitem->name, item->name) == 0) + pitem->ignore = true; + } + } + /* Now mark it as present in file */ + record->status |= GUC_IS_IN_FILE; + } + else if (!valid_custom_variable_name(item->name)) + { + /* Invalid non-custom variable, so complain */ + ereport(elevel, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %d", + item->name, + item->filename, item->sourceline))); + item->errmsg = pstrdup("unrecognized configuration parameter"); + error = true; + ConfFileWithError = item->filename; + } + } + + /* + * If we've detected any errors so far, we don't want to risk applying any + * changes. + */ + if (error) + goto bail_out; + + /* Otherwise, set flag that we're beginning to apply changes */ + applying = true; + + /* + * Check for variables having been removed from the config file, and + * revert their reset values (and perhaps also effective values) to the + * boot-time defaults. If such a variable can't be changed after startup, + * report that and continue. + */ + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *gconf = guc_variables[i]; + GucStack *stack; + + if (gconf->reset_source != PGC_S_FILE || + (gconf->status & GUC_IS_IN_FILE)) + continue; + if (gconf->context < PGC_SIGHUP) + { + /* The removal can't be effective without a restart */ + gconf->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + gconf->name))); + record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server", + gconf->name), + NULL, 0, + &head, &tail); + error = true; + continue; + } + + /* No more to do if we're just doing show_all_file_settings() */ + if (!applySettings) + continue; + + /* + * Reset any "file" sources to "default", else set_config_option will + * not override those settings. + */ + if (gconf->reset_source == PGC_S_FILE) + gconf->reset_source = PGC_S_DEFAULT; + if (gconf->source == PGC_S_FILE) + gconf->source = PGC_S_DEFAULT; + for (stack = gconf->stack; stack; stack = stack->prev) + { + if (stack->source == PGC_S_FILE) + stack->source = PGC_S_DEFAULT; + } + + /* 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, false) > 0) + { + /* Log the change if appropriate */ + if (context == PGC_SIGHUP) + ereport(elevel, + (errmsg("parameter \"%s\" removed from configuration file, reset to default", + gconf->name))); + } + } + + /* + * Restore any variables determined by environment variables or + * dynamically-computed defaults. This is a no-op except in the case + * where one of these had been in the config file and is now removed. + * + * In particular, we *must not* do this during the postmaster's initial + * loading of the file, since the timezone functions in particular should + * be run only after initialization is complete. + * + * XXX this is an unmaintainable crock, because we have to know how to set + * (or at least what to call to set) every non-PGC_INTERNAL variable that + * could potentially have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. + */ + if (context == PGC_SIGHUP && applySettings) + { + InitializeGUCOptionsFromEnvironment(); + pg_timezone_abbrev_initialize(); + /* this selects SQL_ASCII in processes not connected to a database */ + SetConfigOption("client_encoding", GetDatabaseEncodingName(), + PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT); + } + + /* + * Now apply the values from the config file. + */ + for (item = head; item; item = item->next) + { + char *pre_value = NULL; + int scres; + + /* Ignore anything marked as ignorable */ + if (item->ignore) + continue; + + /* In SIGHUP cases in the postmaster, we want to report changes */ + if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster) + { + const char *preval = GetConfigOption(item->name, true, false); + + /* If option doesn't exist yet or is NULL, treat as empty string */ + if (!preval) + preval = ""; + /* must dup, else might have dangling pointer below */ + pre_value = pstrdup(preval); + } + + scres = set_config_option(item->name, item->value, + context, PGC_S_FILE, + GUC_ACTION_SET, applySettings, 0, false); + if (scres > 0) + { + /* variable was updated, so log the change if appropriate */ + if (pre_value) + { + const char *post_value = GetConfigOption(item->name, true, false); + + if (!post_value) + post_value = ""; + if (strcmp(pre_value, post_value) != 0) + ereport(elevel, + (errmsg("parameter \"%s\" changed to \"%s\"", + item->name, item->value))); + } + item->applied = true; + } + else if (scres == 0) + { + error = true; + item->errmsg = pstrdup("setting could not be applied"); + ConfFileWithError = item->filename; + } + else + { + /* no error, but variable's active value was not changed */ + item->applied = true; + } + + /* + * We should update source location unless there was an error, since + * even if the active value didn't change, the reset value might have. + * (In the postmaster, there won't be a difference, but it does matter + * in backends.) + */ + if (scres != 0 && applySettings) + set_config_sourcefile(item->name, item->filename, + item->sourceline); + + if (pre_value) + pfree(pre_value); + } + + /* Remember when we last successfully loaded the config file. */ + if (applySettings) + PgReloadTime = GetCurrentTimestamp(); + +bail_out: + if (error && applySettings) + { + /* During postmaster startup, any error is fatal */ + if (context == PGC_POSTMASTER) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors", + ConfFileWithError))); + else if (applying) + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors; unaffected changes were applied", + ConfFileWithError))); + else + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors; no changes were applied", + ConfFileWithError))); + } + + /* Successful or otherwise, return the collected data list */ + return head; +} + +/* + * Given a configuration file or directory location that may be a relative + * path, return an absolute one. We consider the location to be relative to + * the directory holding the calling file, or to DataDir if no calling file. + */ +static char * +AbsoluteConfigLocation(const char *location, const char *calling_file) +{ + char abs_path[MAXPGPATH]; + + if (is_absolute_path(location)) + return pstrdup(location); + else + { + if (calling_file != NULL) + { + strlcpy(abs_path, calling_file, sizeof(abs_path)); + get_parent_directory(abs_path); + join_path_components(abs_path, abs_path, location); + canonicalize_path(abs_path); + } + else + { + AssertState(DataDir); + join_path_components(abs_path, DataDir, location); + canonicalize_path(abs_path); + } + return pstrdup(abs_path); + } +} + +/* + * Read and parse a single configuration file. This function recurses + * to handle "include" directives. + * + * If "strict" is true, treat failure to open the config file as an error, + * otherwise just skip the file. + * + * calling_file/calling_lineno identify the source of the request. + * Pass NULL/0 if not recursing from an inclusion request. + * + * 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. + */ +bool +ParseConfigFile(const char *config_file, bool strict, + const char *calling_file, int calling_lineno, + int depth, int elevel, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + char *abs_path; + bool OK = true; + FILE *fp; + + /* + * Reject file name that is all-blank (including empty), as that leads to + * confusion --- we'd try to read the containing directory as a file. + */ + if (strspn(config_file, " \t\r\n") == strlen(config_file)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty configuration file name: \"%s\"", + config_file))); + record_config_file_error("empty configuration file name", + calling_file, calling_lineno, + head_p, tail_p); + return false; + } + + /* + * Reject too-deep include nesting depth. This is just a safety check to + * avoid dumping core due to stack overflow if an include file loops back + * to itself. The maximum nesting depth is pretty arbitrary. + */ + if (depth > 10) + { + ereport(elevel, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded", + config_file))); + record_config_file_error("nesting depth exceeded", + calling_file, calling_lineno, + head_p, tail_p); + return false; + } + + abs_path = AbsoluteConfigLocation(config_file, calling_file); + + /* + * Reject direct recursion. Indirect recursion is also possible, but it's + * harder to detect and so doesn't seem worth the trouble. (We test at + * this step because the canonicalization done by AbsoluteConfigLocation + * makes it more likely that a simple strcmp comparison will match.) + */ + if (calling_file && strcmp(abs_path, calling_file) == 0) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("configuration file recursion in \"%s\"", + calling_file))); + record_config_file_error("configuration file recursion", + calling_file, calling_lineno, + head_p, tail_p); + pfree(abs_path); + return false; + } + + fp = AllocateFile(abs_path, "r"); + if (!fp) + { + if (strict) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not open configuration file \"%s\": %m", + abs_path))); + record_config_file_error(psprintf("could not open file \"%s\"", + abs_path), + calling_file, calling_lineno, + head_p, tail_p); + OK = false; + } + else + { + ereport(LOG, + (errmsg("skipping missing configuration file \"%s\"", + abs_path))); + } + goto cleanup; + } + + OK = ParseConfigFp(fp, abs_path, depth, elevel, head_p, tail_p); + +cleanup: + if (fp) + FreeFile(fp); + pfree(abs_path); + + return OK; +} + +/* + * Capture an error message in the ConfigVariable list returned by + * config file parsing. + */ +static void +record_config_file_error(const char *errmsg, + const char *config_file, + int lineno, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + ConfigVariable *item; + + item = palloc(sizeof *item); + item->name = NULL; + item->value = NULL; + item->errmsg = pstrdup(errmsg); + item->filename = config_file ? pstrdup(config_file) : NULL; + item->sourceline = lineno; + item->ignore = true; + item->applied = false; + item->next = NULL; + if (*head_p == NULL) + *head_p = item; + else + (*tail_p)->next = item; + *tail_p = item; +} + +/* + * Flex fatal errors bring us here. Stash the error message and jump back to + * ParseConfigFp(). Assume all msg arguments point to string constants; this + * holds for flex 2.5.31 (earliest we support) and flex 2.5.35 (latest as of + * this writing). Otherwise, we would need to copy the message. + * + * We return "int" since this takes the place of calls to fprintf(). +*/ +static int +GUC_flex_fatal(const char *msg) +{ + GUC_flex_fatal_errmsg = msg; + siglongjmp(*GUC_flex_fatal_jmp, 1); + return 0; /* keep compiler quiet */ +} + +/* + * Read and parse a single configuration file. This function recurses + * to handle "include" directives. + * + * Input parameters: + * fp: file pointer from AllocateFile for the configuration file to parse + * config_file: absolute or relative path name of the configuration file + * depth: recursion depth (should be 0 in the outermost call) + * elevel: error logging level to use + * Input/Output parameters: + * head_p, tail_p: head and tail of linked list of name/value pairs + * + * *head_p and *tail_p must be initialized, either to NULL or valid pointers + * to a ConfigVariable list, before calling the outer recursion level. Any + * name-value pairs read from the input file(s) will be appended to the list. + * Error reports will also be appended to the list, if elevel < ERROR. + * + * Returns TRUE if successful, FALSE if an error occurred. The error has + * already been ereport'd, it is only necessary for the caller to clean up + * its own state and release the ConfigVariable list. + * + * Note: if elevel >= ERROR then an error will not return control to the + * caller, so there is no need to check the return value in that case. + * + * Note: this function is used to parse not only postgresql.conf, but + * various other configuration files that use the same "name = value" + * syntax. Hence, do not do anything here or in the subsidiary routines + * ParseConfigFile/ParseConfigDirectory that assumes we are processing + * GUCs specifically. + */ +bool +ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel, + ConfigVariable **head_p, ConfigVariable **tail_p) +{ + volatile bool OK = true; + unsigned int save_ConfigFileLineno = ConfigFileLineno; + sigjmp_buf *save_GUC_flex_fatal_jmp = GUC_flex_fatal_jmp; + sigjmp_buf flex_fatal_jmp; + volatile YY_BUFFER_STATE lex_buffer = NULL; + int errorcount; + int token; + + if (sigsetjmp(flex_fatal_jmp, 1) == 0) + GUC_flex_fatal_jmp = &flex_fatal_jmp; + else + { + /* + * Regain control after a fatal, internal flex error. It may have + * corrupted parser state. Consequently, abandon the file, but trust + * that the state remains sane enough for yy_delete_buffer(). + */ + elog(elevel, "%s at file \"%s\" line %u", + GUC_flex_fatal_errmsg, config_file, ConfigFileLineno); + record_config_file_error(GUC_flex_fatal_errmsg, + config_file, ConfigFileLineno, + head_p, tail_p); + OK = false; + goto cleanup; + } + + /* + * Parse + */ + ConfigFileLineno = 1; + errorcount = 0; + + lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE); + yy_switch_to_buffer(lex_buffer); + + /* This loop iterates once per logical line */ + while ((token = yylex())) + { + char *opt_name = NULL; + char *opt_value = NULL; + ConfigVariable *item; + + if (token == GUC_EOL) /* empty or comment line */ + continue; + + /* first token on line is option name */ + if (token != GUC_ID && token != GUC_QUALIFIED_ID) + goto parse_error; + opt_name = pstrdup(yytext); + + /* next we have an optional equal sign; discard if present */ + token = yylex(); + if (token == GUC_EQUALS) + token = yylex(); + + /* now we must have the option value */ + if (token != GUC_ID && + token != GUC_STRING && + token != GUC_INTEGER && + token != GUC_REAL && + token != GUC_UNQUOTED_STRING) + goto parse_error; + if (token == GUC_STRING) /* strip quotes and escapes */ + opt_value = DeescapeQuotedString(yytext); + else + opt_value = pstrdup(yytext); + + /* now we'd like an end of line, or possibly EOF */ + token = yylex(); + if (token != GUC_EOL) + { + if (token != 0) + goto parse_error; + /* treat EOF like \n for line numbering purposes, cf bug 4752 */ + ConfigFileLineno++; + } + + /* OK, process the option name and value */ + if (guc_name_compare(opt_name, "include_dir") == 0) + { + /* + * An include_dir directive isn't a variable and should be + * processed immediately. + */ + if (!ParseConfigDirectory(opt_value, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else if (guc_name_compare(opt_name, "include_if_exists") == 0) + { + /* + * An include_if_exists directive isn't a variable and should be + * processed immediately. + */ + if (!ParseConfigFile(opt_value, false, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else if (guc_name_compare(opt_name, "include") == 0) + { + /* + * An include directive isn't a variable and should be processed + * immediately. + */ + if (!ParseConfigFile(opt_value, true, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else + { + /* ordinary variable, append to list */ + item = palloc(sizeof *item); + item->name = opt_name; + item->value = opt_value; + item->errmsg = NULL; + item->filename = pstrdup(config_file); + item->sourceline = ConfigFileLineno - 1; + item->ignore = false; + item->applied = false; + item->next = NULL; + if (*head_p == NULL) + *head_p = item; + else + (*tail_p)->next = item; + *tail_p = item; + } + + /* break out of loop if read EOF, else loop for next line */ + if (token == 0) + break; + continue; + +parse_error: + /* release storage if we allocated any on this line */ + if (opt_name) + pfree(opt_name); + if (opt_value) + pfree(opt_value); + + /* report the error */ + if (token == GUC_EOL || token == 0) + { + ereport(elevel, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in file \"%s\" line %u, near end of line", + config_file, ConfigFileLineno - 1))); + record_config_file_error("syntax error", + config_file, ConfigFileLineno - 1, + head_p, tail_p); + } + else + { + ereport(elevel, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in file \"%s\" line %u, near token \"%s\"", + config_file, ConfigFileLineno, yytext))); + record_config_file_error("syntax error", + config_file, ConfigFileLineno, + head_p, tail_p); + } + OK = false; + errorcount++; + + /* + * To avoid producing too much noise when fed a totally bogus file, + * give up after 100 syntax errors per file (an arbitrary number). + * Also, if we're only logging the errors at DEBUG level anyway, might + * as well give up immediately. (This prevents postmaster children + * from bloating the logs with duplicate complaints.) + */ + if (errorcount >= 100 || elevel <= DEBUG1) + { + ereport(elevel, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many syntax errors found, abandoning file \"%s\"", + config_file))); + break; + } + + /* resync to next end-of-line or EOF */ + while (token != GUC_EOL && token != 0) + token = yylex(); + /* break out of loop on EOF */ + if (token == 0) + break; + } + +cleanup: + yy_delete_buffer(lex_buffer); + /* Each recursion level must save and restore these static variables. */ + ConfigFileLineno = save_ConfigFileLineno; + GUC_flex_fatal_jmp = save_GUC_flex_fatal_jmp; + return OK; +} + +/* + * Read and parse all config files in a subdirectory in alphabetical order + * + * includedir is the absolute or relative path to the subdirectory to scan. + * + * calling_file/calling_lineno identify the source of the request. + * Pass NULL/0 if not recursing from an inclusion request. + * + * See ParseConfigFp for further details. + */ +bool +ParseConfigDirectory(const char *includedir, + const char *calling_file, int calling_lineno, + int depth, int elevel, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + char *directory; + DIR *d; + struct dirent *de; + char **filenames; + int num_filenames; + int size_filenames; + bool status; + + /* + * Reject directory name that is all-blank (including empty), as that + * leads to confusion --- we'd read the containing directory, typically + * resulting in recursive inclusion of the same file(s). + */ + if (strspn(includedir, " \t\r\n") == strlen(includedir)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty configuration directory name: \"%s\"", + includedir))); + record_config_file_error("empty configuration directory name", + calling_file, calling_lineno, + head_p, tail_p); + return false; + } + + /* + * We don't check for recursion or too-deep nesting depth here; the + * subsequent calls to ParseConfigFile will take care of that. + */ + + directory = AbsoluteConfigLocation(includedir, calling_file); + d = AllocateDir(directory); + if (d == NULL) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not open configuration directory \"%s\": %m", + directory))); + record_config_file_error(psprintf("could not open directory \"%s\"", + directory), + calling_file, calling_lineno, + head_p, tail_p); + status = false; + goto cleanup; + } + + /* + * Read the directory and put the filenames in an array, so we can sort + * them prior to processing the contents. + */ + size_filenames = 32; + filenames = (char **) palloc(size_filenames * sizeof(char *)); + num_filenames = 0; + + while ((de = ReadDir(d, directory)) != NULL) + { + struct stat st; + char filename[MAXPGPATH]; + + /* + * Only parse files with names ending in ".conf". Explicitly reject + * files starting with ".". This excludes things like "." and "..", + * as well as typical hidden files, backup files, and editor debris. + */ + if (strlen(de->d_name) < 6) + continue; + if (de->d_name[0] == '.') + continue; + if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0) + continue; + + join_path_components(filename, directory, de->d_name); + canonicalize_path(filename); + if (stat(filename, &st) == 0) + { + if (!S_ISDIR(st.st_mode)) + { + /* Add file to array, increasing its size in blocks of 32 */ + if (num_filenames >= size_filenames) + { + size_filenames += 32; + filenames = (char **) repalloc(filenames, + size_filenames * sizeof(char *)); + } + filenames[num_filenames] = pstrdup(filename); + num_filenames++; + } + } + else + { + /* + * stat does not care about permissions, so the most likely reason + * a file can't be accessed now is if it was removed between the + * directory listing and now. + */ + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", + filename))); + record_config_file_error(psprintf("could not stat file \"%s\"", + filename), + calling_file, calling_lineno, + head_p, tail_p); + status = false; + goto cleanup; + } + } + + if (num_filenames > 0) + { + int i; + + qsort(filenames, num_filenames, sizeof(char *), pg_qsort_strcmp); + for (i = 0; i < num_filenames; i++) + { + if (!ParseConfigFile(filenames[i], true, + calling_file, calling_lineno, + depth, elevel, + head_p, tail_p)) + { + status = false; + goto cleanup; + } + } + } + status = true; + +cleanup: + if (d) + FreeDir(d); + pfree(directory); + return status; +} + +/* + * Free a list of ConfigVariables, including the names and the values + */ +void +FreeConfigVariables(ConfigVariable *list) +{ + ConfigVariable *item; + + item = list; + while (item) + { + ConfigVariable *next = item->next; + + FreeConfigVariable(item); + item = next; + } +} + +/* + * Free a single ConfigVariable + */ +static void +FreeConfigVariable(ConfigVariable *item) +{ + if (item->name) + pfree(item->name); + if (item->value) + pfree(item->value); + if (item->errmsg) + pfree(item->errmsg); + if (item->filename) + pfree(item->filename); + pfree(item); +} + + +/* + * DeescapeQuotedString + * + * Strip the quotes surrounding the given string, and collapse any embedded + * '' sequences and backslash escapes. + * + * The string returned is palloc'd and should eventually be pfree'd by the + * caller. + * + * This is exported because it is also used by the bootstrap scanner. + */ +char * +DeescapeQuotedString(const char *s) +{ + char *newStr; + int len, + i, + j; + + /* We just Assert that there are leading and trailing quotes */ + Assert(s != NULL && s[0] == '\''); + len = strlen(s); + Assert(len >= 2); + Assert(s[len - 1] == '\''); + + /* Skip the leading quote; we'll handle the trailing quote below */ + s++, len--; + + /* Since len still includes trailing quote, this is enough space */ + newStr = palloc(len); + + for (i = 0, j = 0; i < len; i++) + { + if (s[i] == '\\') + { + i++; + switch (s[i]) + { + case 'b': + newStr[j] = '\b'; + break; + case 'f': + newStr[j] = '\f'; + break; + case 'n': + newStr[j] = '\n'; + break; + case 'r': + newStr[j] = '\r'; + break; + case 't': + newStr[j] = '\t'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int k; + long octVal = 0; + + for (k = 0; + s[i + k] >= '0' && s[i + k] <= '7' && k < 3; + k++) + octVal = (octVal << 3) + (s[i + k] - '0'); + i += k - 1; + newStr[j] = ((char) octVal); + } + break; + default: + newStr[j] = s[i]; + break; + } /* switch */ + } + else if (s[i] == '\'' && s[i + 1] == '\'') + { + /* doubled quote becomes just one quote */ + newStr[j] = s[++i]; + } + else + newStr[j] = s[i]; + j++; + } + + /* We copied the ending quote to newStr, so replace with \0 */ + Assert(j > 0 && j <= len); + newStr[--j] = '\0'; + + return newStr; +} + diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l new file mode 100644 index 0000000..ce56338 --- /dev/null +++ b/src/backend/utils/misc/guc-file.l @@ -0,0 +1,1227 @@ +/* -*-pgsql-c-*- */ +/* + * Scanner for the configuration file + * + * Copyright (c) 2000-2022, PostgreSQL Global Development Group + * + * src/backend/utils/misc/guc-file.l + */ + +%{ + +#include "postgres.h" + +#include <ctype.h> +#include <unistd.h> + +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/guc.h" + + +/* + * flex emits a yy_fatal_error() function that it calls in response to + * critical errors like malloc failure, file I/O errors, and detection of + * internal inconsistency. That function prints a message and calls exit(). + * Mutate it to instead call our handler, which jumps out of the parser. + */ +#undef fprintf +#define fprintf(file, fmt, msg) GUC_flex_fatal(msg) + +enum +{ + GUC_ID = 1, + GUC_STRING = 2, + GUC_INTEGER = 3, + GUC_REAL = 4, + GUC_EQUALS = 5, + GUC_UNQUOTED_STRING = 6, + GUC_QUALIFIED_ID = 7, + GUC_EOL = 99, + GUC_ERROR = 100 +}; + +static unsigned int ConfigFileLineno; +static const char *GUC_flex_fatal_errmsg; +static sigjmp_buf *GUC_flex_fatal_jmp; + +static void FreeConfigVariable(ConfigVariable *item); + +static void record_config_file_error(const char *errmsg, + const char *config_file, + int lineno, + ConfigVariable **head_p, + ConfigVariable **tail_p); + +static int GUC_flex_fatal(const char *msg); + +/* LCOV_EXCL_START */ + +%} + +%option 8bit +%option never-interactive +%option nodefault +%option noinput +%option nounput +%option noyywrap +%option warn +%option prefix="GUC_yy" + + +SIGN ("-"|"+") +DIGIT [0-9] +HEXDIGIT [0-9a-fA-F] + +UNIT_LETTER [a-zA-Z] + +INTEGER {SIGN}?({DIGIT}+|0x{HEXDIGIT}+){UNIT_LETTER}* + +EXPONENT [Ee]{SIGN}?{DIGIT}+ +REAL {SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}? + +LETTER [A-Za-z_\200-\377] +LETTER_OR_DIGIT [A-Za-z_0-9\200-\377] + +ID {LETTER}{LETTER_OR_DIGIT}* +QUALIFIED_ID {ID}"."{ID} + +UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])* +STRING \'([^'\\\n]|\\.|\'\')*\' + +%% + +\n ConfigFileLineno++; return GUC_EOL; +[ \t\r]+ /* eat whitespace */ +#.* /* eat comment (.* matches anything until newline) */ + +{ID} return GUC_ID; +{QUALIFIED_ID} return GUC_QUALIFIED_ID; +{STRING} return GUC_STRING; +{UNQUOTED_STRING} return GUC_UNQUOTED_STRING; +{INTEGER} return GUC_INTEGER; +{REAL} return GUC_REAL; += return GUC_EQUALS; + +. return GUC_ERROR; + +%% + +/* LCOV_EXCL_STOP */ + +/* + * Exported function to read and process the configuration file. The + * parameter indicates in what context the file is being read --- either + * postmaster startup (including standalone-backend startup) or SIGHUP. + * All options mentioned in the configuration file are set to new values. + * If a hard error occurs, no values will be changed. (There can also be + * errors that prevent just one value from being changed.) + */ +void +ProcessConfigFile(GucContext context) +{ + int elevel; + MemoryContext config_cxt; + MemoryContext caller_cxt; + + /* + * Config files are processed on startup (by the postmaster only) and on + * SIGHUP (by the postmaster and its children) + */ + Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) || + context == PGC_SIGHUP); + + /* + * To avoid cluttering the log, only the postmaster bleats loudly about + * problems with the config file. + */ + elevel = IsUnderPostmaster ? DEBUG2 : LOG; + + /* + * This function is usually called within a process-lifespan memory + * context. To ensure that any memory leaked during GUC processing does + * not accumulate across repeated SIGHUP cycles, do the work in a private + * context that we can free at exit. + */ + config_cxt = AllocSetContextCreate(CurrentMemoryContext, + "config file processing", + ALLOCSET_DEFAULT_SIZES); + caller_cxt = MemoryContextSwitchTo(config_cxt); + + /* + * Read and apply the config file. We don't need to examine the result. + */ + (void) ProcessConfigFileInternal(context, true, elevel); + + /* Clean up */ + MemoryContextSwitchTo(caller_cxt); + MemoryContextDelete(config_cxt); +} + +/* + * This function handles both actual config file (re)loads and execution of + * show_all_file_settings() (i.e., the pg_file_settings view). In the latter + * case we don't apply any of the settings, but we make all the usual validity + * checks, and we return the ConfigVariable list so that it can be printed out + * by show_all_file_settings(). + */ +static ConfigVariable * +ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel) +{ + bool error = false; + bool applying = false; + const char *ConfFileWithError; + ConfigVariable *item, + *head, + *tail; + int i; + + /* Parse the main config file into a list of option names and values */ + ConfFileWithError = ConfigFileName; + head = tail = NULL; + + if (!ParseConfigFile(ConfigFileName, true, + NULL, 0, 0, elevel, + &head, &tail)) + { + /* Syntax error(s) detected in the file, so bail out */ + error = true; + goto bail_out; + } + + /* + * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to + * replace any parameters set by ALTER SYSTEM command. Because this file + * is in the data directory, we can't read it until the DataDir has been + * set. + */ + if (DataDir) + { + if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false, + NULL, 0, 0, elevel, + &head, &tail)) + { + /* Syntax error(s) detected in the file, so bail out */ + error = true; + ConfFileWithError = PG_AUTOCONF_FILENAME; + goto bail_out; + } + } + else + { + /* + * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be + * read. In this case, we don't want to accept any settings but + * data_directory from postgresql.conf, because they might be + * overwritten with settings in the PG_AUTOCONF_FILENAME file which + * will be read later. OTOH, since data_directory isn't allowed in the + * PG_AUTOCONF_FILENAME file, it will never be overwritten later. + */ + ConfigVariable *newlist = NULL; + + /* + * Prune all items except the last "data_directory" from the list. + */ + for (item = head; item; item = item->next) + { + if (!item->ignore && + strcmp(item->name, "data_directory") == 0) + newlist = item; + } + + if (newlist) + newlist->next = NULL; + head = tail = newlist; + + /* + * Quick exit if data_directory is not present in file. + * + * We need not do any further processing, in particular we don't set + * PgReloadTime; that will be set soon by subsequent full loading of + * the config file. + */ + if (head == NULL) + goto bail_out; + } + + /* + * 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. + */ + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *gconf = guc_variables[i]; + + gconf->status &= ~GUC_IS_IN_FILE; + } + + /* + * Check if all the supplied option names are valid, as an additional + * quasi-syntactic check on the validity of the config file. It is + * important that the postmaster and all backends agree on the results of + * this phase, else we will have strange inconsistencies about which + * processes accept a config file update and which don't. Hence, unknown + * custom variable names have to be accepted without complaint. For the + * same reason, we don't attempt to validate the options' values here. + * + * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC + * variable mentioned in the file; and we detect duplicate entries in the + * file and mark the earlier occurrences as ignorable. + */ + for (item = head; item; item = item->next) + { + struct config_generic *record; + + /* Ignore anything already marked as ignorable */ + if (item->ignore) + continue; + + /* + * Try to find the variable; but do not create a custom placeholder if + * it's not there already. + */ + record = find_option(item->name, false, true, elevel); + + if (record) + { + /* If it's already marked, then this is a duplicate entry */ + if (record->status & GUC_IS_IN_FILE) + { + /* + * Mark the earlier occurrence(s) as dead/ignorable. We could + * avoid the O(N^2) behavior here with some additional state, + * but it seems unlikely to be worth the trouble. + */ + ConfigVariable *pitem; + + for (pitem = head; pitem != item; pitem = pitem->next) + { + if (!pitem->ignore && + strcmp(pitem->name, item->name) == 0) + pitem->ignore = true; + } + } + /* Now mark it as present in file */ + record->status |= GUC_IS_IN_FILE; + } + else if (!valid_custom_variable_name(item->name)) + { + /* Invalid non-custom variable, so complain */ + ereport(elevel, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %d", + item->name, + item->filename, item->sourceline))); + item->errmsg = pstrdup("unrecognized configuration parameter"); + error = true; + ConfFileWithError = item->filename; + } + } + + /* + * If we've detected any errors so far, we don't want to risk applying any + * changes. + */ + if (error) + goto bail_out; + + /* Otherwise, set flag that we're beginning to apply changes */ + applying = true; + + /* + * Check for variables having been removed from the config file, and + * revert their reset values (and perhaps also effective values) to the + * boot-time defaults. If such a variable can't be changed after startup, + * report that and continue. + */ + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *gconf = guc_variables[i]; + GucStack *stack; + + if (gconf->reset_source != PGC_S_FILE || + (gconf->status & GUC_IS_IN_FILE)) + continue; + if (gconf->context < PGC_SIGHUP) + { + /* The removal can't be effective without a restart */ + gconf->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + gconf->name))); + record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server", + gconf->name), + NULL, 0, + &head, &tail); + error = true; + continue; + } + + /* No more to do if we're just doing show_all_file_settings() */ + if (!applySettings) + continue; + + /* + * Reset any "file" sources to "default", else set_config_option will + * not override those settings. + */ + if (gconf->reset_source == PGC_S_FILE) + gconf->reset_source = PGC_S_DEFAULT; + if (gconf->source == PGC_S_FILE) + gconf->source = PGC_S_DEFAULT; + for (stack = gconf->stack; stack; stack = stack->prev) + { + if (stack->source == PGC_S_FILE) + stack->source = PGC_S_DEFAULT; + } + + /* 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, false) > 0) + { + /* Log the change if appropriate */ + if (context == PGC_SIGHUP) + ereport(elevel, + (errmsg("parameter \"%s\" removed from configuration file, reset to default", + gconf->name))); + } + } + + /* + * Restore any variables determined by environment variables or + * dynamically-computed defaults. This is a no-op except in the case + * where one of these had been in the config file and is now removed. + * + * In particular, we *must not* do this during the postmaster's initial + * loading of the file, since the timezone functions in particular should + * be run only after initialization is complete. + * + * XXX this is an unmaintainable crock, because we have to know how to set + * (or at least what to call to set) every non-PGC_INTERNAL variable that + * could potentially have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. + */ + if (context == PGC_SIGHUP && applySettings) + { + InitializeGUCOptionsFromEnvironment(); + pg_timezone_abbrev_initialize(); + /* this selects SQL_ASCII in processes not connected to a database */ + SetConfigOption("client_encoding", GetDatabaseEncodingName(), + PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT); + } + + /* + * Now apply the values from the config file. + */ + for (item = head; item; item = item->next) + { + char *pre_value = NULL; + int scres; + + /* Ignore anything marked as ignorable */ + if (item->ignore) + continue; + + /* In SIGHUP cases in the postmaster, we want to report changes */ + if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster) + { + const char *preval = GetConfigOption(item->name, true, false); + + /* If option doesn't exist yet or is NULL, treat as empty string */ + if (!preval) + preval = ""; + /* must dup, else might have dangling pointer below */ + pre_value = pstrdup(preval); + } + + scres = set_config_option(item->name, item->value, + context, PGC_S_FILE, + GUC_ACTION_SET, applySettings, 0, false); + if (scres > 0) + { + /* variable was updated, so log the change if appropriate */ + if (pre_value) + { + const char *post_value = GetConfigOption(item->name, true, false); + + if (!post_value) + post_value = ""; + if (strcmp(pre_value, post_value) != 0) + ereport(elevel, + (errmsg("parameter \"%s\" changed to \"%s\"", + item->name, item->value))); + } + item->applied = true; + } + else if (scres == 0) + { + error = true; + item->errmsg = pstrdup("setting could not be applied"); + ConfFileWithError = item->filename; + } + else + { + /* no error, but variable's active value was not changed */ + item->applied = true; + } + + /* + * We should update source location unless there was an error, since + * even if the active value didn't change, the reset value might have. + * (In the postmaster, there won't be a difference, but it does matter + * in backends.) + */ + if (scres != 0 && applySettings) + set_config_sourcefile(item->name, item->filename, + item->sourceline); + + if (pre_value) + pfree(pre_value); + } + + /* Remember when we last successfully loaded the config file. */ + if (applySettings) + PgReloadTime = GetCurrentTimestamp(); + +bail_out: + if (error && applySettings) + { + /* During postmaster startup, any error is fatal */ + if (context == PGC_POSTMASTER) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors", + ConfFileWithError))); + else if (applying) + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors; unaffected changes were applied", + ConfFileWithError))); + else + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors; no changes were applied", + ConfFileWithError))); + } + + /* Successful or otherwise, return the collected data list */ + return head; +} + +/* + * Given a configuration file or directory location that may be a relative + * path, return an absolute one. We consider the location to be relative to + * the directory holding the calling file, or to DataDir if no calling file. + */ +static char * +AbsoluteConfigLocation(const char *location, const char *calling_file) +{ + char abs_path[MAXPGPATH]; + + if (is_absolute_path(location)) + return pstrdup(location); + else + { + if (calling_file != NULL) + { + strlcpy(abs_path, calling_file, sizeof(abs_path)); + get_parent_directory(abs_path); + join_path_components(abs_path, abs_path, location); + canonicalize_path(abs_path); + } + else + { + AssertState(DataDir); + join_path_components(abs_path, DataDir, location); + canonicalize_path(abs_path); + } + return pstrdup(abs_path); + } +} + +/* + * Read and parse a single configuration file. This function recurses + * to handle "include" directives. + * + * If "strict" is true, treat failure to open the config file as an error, + * otherwise just skip the file. + * + * calling_file/calling_lineno identify the source of the request. + * Pass NULL/0 if not recursing from an inclusion request. + * + * 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. + */ +bool +ParseConfigFile(const char *config_file, bool strict, + const char *calling_file, int calling_lineno, + int depth, int elevel, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + char *abs_path; + bool OK = true; + FILE *fp; + + /* + * Reject file name that is all-blank (including empty), as that leads to + * confusion --- we'd try to read the containing directory as a file. + */ + if (strspn(config_file, " \t\r\n") == strlen(config_file)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty configuration file name: \"%s\"", + config_file))); + record_config_file_error("empty configuration file name", + calling_file, calling_lineno, + head_p, tail_p); + return false; + } + + /* + * Reject too-deep include nesting depth. This is just a safety check to + * avoid dumping core due to stack overflow if an include file loops back + * to itself. The maximum nesting depth is pretty arbitrary. + */ + if (depth > 10) + { + ereport(elevel, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded", + config_file))); + record_config_file_error("nesting depth exceeded", + calling_file, calling_lineno, + head_p, tail_p); + return false; + } + + abs_path = AbsoluteConfigLocation(config_file, calling_file); + + /* + * Reject direct recursion. Indirect recursion is also possible, but it's + * harder to detect and so doesn't seem worth the trouble. (We test at + * this step because the canonicalization done by AbsoluteConfigLocation + * makes it more likely that a simple strcmp comparison will match.) + */ + if (calling_file && strcmp(abs_path, calling_file) == 0) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("configuration file recursion in \"%s\"", + calling_file))); + record_config_file_error("configuration file recursion", + calling_file, calling_lineno, + head_p, tail_p); + pfree(abs_path); + return false; + } + + fp = AllocateFile(abs_path, "r"); + if (!fp) + { + if (strict) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not open configuration file \"%s\": %m", + abs_path))); + record_config_file_error(psprintf("could not open file \"%s\"", + abs_path), + calling_file, calling_lineno, + head_p, tail_p); + OK = false; + } + else + { + ereport(LOG, + (errmsg("skipping missing configuration file \"%s\"", + abs_path))); + } + goto cleanup; + } + + OK = ParseConfigFp(fp, abs_path, depth, elevel, head_p, tail_p); + +cleanup: + if (fp) + FreeFile(fp); + pfree(abs_path); + + return OK; +} + +/* + * Capture an error message in the ConfigVariable list returned by + * config file parsing. + */ +static void +record_config_file_error(const char *errmsg, + const char *config_file, + int lineno, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + ConfigVariable *item; + + item = palloc(sizeof *item); + item->name = NULL; + item->value = NULL; + item->errmsg = pstrdup(errmsg); + item->filename = config_file ? pstrdup(config_file) : NULL; + item->sourceline = lineno; + item->ignore = true; + item->applied = false; + item->next = NULL; + if (*head_p == NULL) + *head_p = item; + else + (*tail_p)->next = item; + *tail_p = item; +} + +/* + * Flex fatal errors bring us here. Stash the error message and jump back to + * ParseConfigFp(). Assume all msg arguments point to string constants; this + * holds for flex 2.5.31 (earliest we support) and flex 2.5.35 (latest as of + * this writing). Otherwise, we would need to copy the message. + * + * We return "int" since this takes the place of calls to fprintf(). +*/ +static int +GUC_flex_fatal(const char *msg) +{ + GUC_flex_fatal_errmsg = msg; + siglongjmp(*GUC_flex_fatal_jmp, 1); + return 0; /* keep compiler quiet */ +} + +/* + * Read and parse a single configuration file. This function recurses + * to handle "include" directives. + * + * Input parameters: + * fp: file pointer from AllocateFile for the configuration file to parse + * config_file: absolute or relative path name of the configuration file + * depth: recursion depth (should be 0 in the outermost call) + * elevel: error logging level to use + * Input/Output parameters: + * head_p, tail_p: head and tail of linked list of name/value pairs + * + * *head_p and *tail_p must be initialized, either to NULL or valid pointers + * to a ConfigVariable list, before calling the outer recursion level. Any + * name-value pairs read from the input file(s) will be appended to the list. + * Error reports will also be appended to the list, if elevel < ERROR. + * + * Returns TRUE if successful, FALSE if an error occurred. The error has + * already been ereport'd, it is only necessary for the caller to clean up + * its own state and release the ConfigVariable list. + * + * Note: if elevel >= ERROR then an error will not return control to the + * caller, so there is no need to check the return value in that case. + * + * Note: this function is used to parse not only postgresql.conf, but + * various other configuration files that use the same "name = value" + * syntax. Hence, do not do anything here or in the subsidiary routines + * ParseConfigFile/ParseConfigDirectory that assumes we are processing + * GUCs specifically. + */ +bool +ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel, + ConfigVariable **head_p, ConfigVariable **tail_p) +{ + volatile bool OK = true; + unsigned int save_ConfigFileLineno = ConfigFileLineno; + sigjmp_buf *save_GUC_flex_fatal_jmp = GUC_flex_fatal_jmp; + sigjmp_buf flex_fatal_jmp; + volatile YY_BUFFER_STATE lex_buffer = NULL; + int errorcount; + int token; + + if (sigsetjmp(flex_fatal_jmp, 1) == 0) + GUC_flex_fatal_jmp = &flex_fatal_jmp; + else + { + /* + * Regain control after a fatal, internal flex error. It may have + * corrupted parser state. Consequently, abandon the file, but trust + * that the state remains sane enough for yy_delete_buffer(). + */ + elog(elevel, "%s at file \"%s\" line %u", + GUC_flex_fatal_errmsg, config_file, ConfigFileLineno); + record_config_file_error(GUC_flex_fatal_errmsg, + config_file, ConfigFileLineno, + head_p, tail_p); + OK = false; + goto cleanup; + } + + /* + * Parse + */ + ConfigFileLineno = 1; + errorcount = 0; + + lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE); + yy_switch_to_buffer(lex_buffer); + + /* This loop iterates once per logical line */ + while ((token = yylex())) + { + char *opt_name = NULL; + char *opt_value = NULL; + ConfigVariable *item; + + if (token == GUC_EOL) /* empty or comment line */ + continue; + + /* first token on line is option name */ + if (token != GUC_ID && token != GUC_QUALIFIED_ID) + goto parse_error; + opt_name = pstrdup(yytext); + + /* next we have an optional equal sign; discard if present */ + token = yylex(); + if (token == GUC_EQUALS) + token = yylex(); + + /* now we must have the option value */ + if (token != GUC_ID && + token != GUC_STRING && + token != GUC_INTEGER && + token != GUC_REAL && + token != GUC_UNQUOTED_STRING) + goto parse_error; + if (token == GUC_STRING) /* strip quotes and escapes */ + opt_value = DeescapeQuotedString(yytext); + else + opt_value = pstrdup(yytext); + + /* now we'd like an end of line, or possibly EOF */ + token = yylex(); + if (token != GUC_EOL) + { + if (token != 0) + goto parse_error; + /* treat EOF like \n for line numbering purposes, cf bug 4752 */ + ConfigFileLineno++; + } + + /* OK, process the option name and value */ + if (guc_name_compare(opt_name, "include_dir") == 0) + { + /* + * An include_dir directive isn't a variable and should be + * processed immediately. + */ + if (!ParseConfigDirectory(opt_value, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else if (guc_name_compare(opt_name, "include_if_exists") == 0) + { + /* + * An include_if_exists directive isn't a variable and should be + * processed immediately. + */ + if (!ParseConfigFile(opt_value, false, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else if (guc_name_compare(opt_name, "include") == 0) + { + /* + * An include directive isn't a variable and should be processed + * immediately. + */ + if (!ParseConfigFile(opt_value, true, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else + { + /* ordinary variable, append to list */ + item = palloc(sizeof *item); + item->name = opt_name; + item->value = opt_value; + item->errmsg = NULL; + item->filename = pstrdup(config_file); + item->sourceline = ConfigFileLineno - 1; + item->ignore = false; + item->applied = false; + item->next = NULL; + if (*head_p == NULL) + *head_p = item; + else + (*tail_p)->next = item; + *tail_p = item; + } + + /* break out of loop if read EOF, else loop for next line */ + if (token == 0) + break; + continue; + +parse_error: + /* release storage if we allocated any on this line */ + if (opt_name) + pfree(opt_name); + if (opt_value) + pfree(opt_value); + + /* report the error */ + if (token == GUC_EOL || token == 0) + { + ereport(elevel, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in file \"%s\" line %u, near end of line", + config_file, ConfigFileLineno - 1))); + record_config_file_error("syntax error", + config_file, ConfigFileLineno - 1, + head_p, tail_p); + } + else + { + ereport(elevel, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in file \"%s\" line %u, near token \"%s\"", + config_file, ConfigFileLineno, yytext))); + record_config_file_error("syntax error", + config_file, ConfigFileLineno, + head_p, tail_p); + } + OK = false; + errorcount++; + + /* + * To avoid producing too much noise when fed a totally bogus file, + * give up after 100 syntax errors per file (an arbitrary number). + * Also, if we're only logging the errors at DEBUG level anyway, might + * as well give up immediately. (This prevents postmaster children + * from bloating the logs with duplicate complaints.) + */ + if (errorcount >= 100 || elevel <= DEBUG1) + { + ereport(elevel, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many syntax errors found, abandoning file \"%s\"", + config_file))); + break; + } + + /* resync to next end-of-line or EOF */ + while (token != GUC_EOL && token != 0) + token = yylex(); + /* break out of loop on EOF */ + if (token == 0) + break; + } + +cleanup: + yy_delete_buffer(lex_buffer); + /* Each recursion level must save and restore these static variables. */ + ConfigFileLineno = save_ConfigFileLineno; + GUC_flex_fatal_jmp = save_GUC_flex_fatal_jmp; + return OK; +} + +/* + * Read and parse all config files in a subdirectory in alphabetical order + * + * includedir is the absolute or relative path to the subdirectory to scan. + * + * calling_file/calling_lineno identify the source of the request. + * Pass NULL/0 if not recursing from an inclusion request. + * + * See ParseConfigFp for further details. + */ +bool +ParseConfigDirectory(const char *includedir, + const char *calling_file, int calling_lineno, + int depth, int elevel, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + char *directory; + DIR *d; + struct dirent *de; + char **filenames; + int num_filenames; + int size_filenames; + bool status; + + /* + * Reject directory name that is all-blank (including empty), as that + * leads to confusion --- we'd read the containing directory, typically + * resulting in recursive inclusion of the same file(s). + */ + if (strspn(includedir, " \t\r\n") == strlen(includedir)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty configuration directory name: \"%s\"", + includedir))); + record_config_file_error("empty configuration directory name", + calling_file, calling_lineno, + head_p, tail_p); + return false; + } + + /* + * We don't check for recursion or too-deep nesting depth here; the + * subsequent calls to ParseConfigFile will take care of that. + */ + + directory = AbsoluteConfigLocation(includedir, calling_file); + d = AllocateDir(directory); + if (d == NULL) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not open configuration directory \"%s\": %m", + directory))); + record_config_file_error(psprintf("could not open directory \"%s\"", + directory), + calling_file, calling_lineno, + head_p, tail_p); + status = false; + goto cleanup; + } + + /* + * Read the directory and put the filenames in an array, so we can sort + * them prior to processing the contents. + */ + size_filenames = 32; + filenames = (char **) palloc(size_filenames * sizeof(char *)); + num_filenames = 0; + + while ((de = ReadDir(d, directory)) != NULL) + { + struct stat st; + char filename[MAXPGPATH]; + + /* + * Only parse files with names ending in ".conf". Explicitly reject + * files starting with ".". This excludes things like "." and "..", + * as well as typical hidden files, backup files, and editor debris. + */ + if (strlen(de->d_name) < 6) + continue; + if (de->d_name[0] == '.') + continue; + if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0) + continue; + + join_path_components(filename, directory, de->d_name); + canonicalize_path(filename); + if (stat(filename, &st) == 0) + { + if (!S_ISDIR(st.st_mode)) + { + /* Add file to array, increasing its size in blocks of 32 */ + if (num_filenames >= size_filenames) + { + size_filenames += 32; + filenames = (char **) repalloc(filenames, + size_filenames * sizeof(char *)); + } + filenames[num_filenames] = pstrdup(filename); + num_filenames++; + } + } + else + { + /* + * stat does not care about permissions, so the most likely reason + * a file can't be accessed now is if it was removed between the + * directory listing and now. + */ + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", + filename))); + record_config_file_error(psprintf("could not stat file \"%s\"", + filename), + calling_file, calling_lineno, + head_p, tail_p); + status = false; + goto cleanup; + } + } + + if (num_filenames > 0) + { + int i; + + qsort(filenames, num_filenames, sizeof(char *), pg_qsort_strcmp); + for (i = 0; i < num_filenames; i++) + { + if (!ParseConfigFile(filenames[i], true, + calling_file, calling_lineno, + depth, elevel, + head_p, tail_p)) + { + status = false; + goto cleanup; + } + } + } + status = true; + +cleanup: + if (d) + FreeDir(d); + pfree(directory); + return status; +} + +/* + * Free a list of ConfigVariables, including the names and the values + */ +void +FreeConfigVariables(ConfigVariable *list) +{ + ConfigVariable *item; + + item = list; + while (item) + { + ConfigVariable *next = item->next; + + FreeConfigVariable(item); + item = next; + } +} + +/* + * Free a single ConfigVariable + */ +static void +FreeConfigVariable(ConfigVariable *item) +{ + if (item->name) + pfree(item->name); + if (item->value) + pfree(item->value); + if (item->errmsg) + pfree(item->errmsg); + if (item->filename) + pfree(item->filename); + pfree(item); +} + + +/* + * DeescapeQuotedString + * + * Strip the quotes surrounding the given string, and collapse any embedded + * '' sequences and backslash escapes. + * + * The string returned is palloc'd and should eventually be pfree'd by the + * caller. + * + * This is exported because it is also used by the bootstrap scanner. + */ +char * +DeescapeQuotedString(const char *s) +{ + char *newStr; + int len, + i, + j; + + /* We just Assert that there are leading and trailing quotes */ + Assert(s != NULL && s[0] == '\''); + len = strlen(s); + Assert(len >= 2); + Assert(s[len - 1] == '\''); + + /* Skip the leading quote; we'll handle the trailing quote below */ + s++, len--; + + /* Since len still includes trailing quote, this is enough space */ + newStr = palloc(len); + + for (i = 0, j = 0; i < len; i++) + { + if (s[i] == '\\') + { + i++; + switch (s[i]) + { + case 'b': + newStr[j] = '\b'; + break; + case 'f': + newStr[j] = '\f'; + break; + case 'n': + newStr[j] = '\n'; + break; + case 'r': + newStr[j] = '\r'; + break; + case 't': + newStr[j] = '\t'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int k; + long octVal = 0; + + for (k = 0; + s[i + k] >= '0' && s[i + k] <= '7' && k < 3; + k++) + octVal = (octVal << 3) + (s[i + k] - '0'); + i += k - 1; + newStr[j] = ((char) octVal); + } + break; + default: + newStr[j] = s[i]; + break; + } /* switch */ + } + else if (s[i] == '\'' && s[i + 1] == '\'') + { + /* doubled quote becomes just one quote */ + newStr[j] = s[++i]; + } + else + newStr[j] = s[i]; + j++; + } + + /* We copied the ending quote to newStr, so replace with \0 */ + Assert(j > 0 && j <= len); + newStr[--j] = '\0'; + + return newStr; +} diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c new file mode 100644 index 0000000..0b5b77b --- /dev/null +++ b/src/backend/utils/misc/guc.c @@ -0,0 +1,13003 @@ +/*-------------------------------------------------------------------- + * guc.c + * + * Support for grand unified configuration scheme, including SET + * command, configuration file, and command line options. + * See src/backend/utils/misc/README for more information. + * + * + * Copyright (c) 2000-2022, PostgreSQL Global Development Group + * Written by Peter Eisentraut <peter_e@gmx.net>. + * + * IDENTIFICATION + * src/backend/utils/misc/guc.c + * + *-------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <float.h> +#include <math.h> +#include <limits.h> +#ifdef HAVE_POLL_H +#include <poll.h> +#endif +#ifndef WIN32 +#include <sys/mman.h> +#endif +#include <sys/stat.h> +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif +#include <unistd.h> + +#include "access/commit_ts.h" +#include "access/gin.h" +#include "access/rmgr.h" +#include "access/tableam.h" +#include "access/toast_compression.h" +#include "access/transam.h" +#include "access/twophase.h" +#include "access/xact.h" +#include "access/xlog_internal.h" +#include "access/xlogprefetcher.h" +#include "access/xlogrecovery.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_parameter_acl.h" +#include "catalog/storage.h" +#include "commands/async.h" +#include "commands/prepare.h" +#include "commands/tablespace.h" +#include "commands/trigger.h" +#include "commands/user.h" +#include "commands/vacuum.h" +#include "commands/variable.h" +#include "common/string.h" +#include "funcapi.h" +#include "jit/jit.h" +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "optimizer/cost.h" +#include "optimizer/geqo.h" +#include "optimizer/optimizer.h" +#include "optimizer/paths.h" +#include "optimizer/planmain.h" +#include "parser/parse_expr.h" +#include "parser/parse_type.h" +#include "parser/parser.h" +#include "parser/scansup.h" +#include "pgstat.h" +#include "postmaster/autovacuum.h" +#include "postmaster/bgworker_internals.h" +#include "postmaster/bgwriter.h" +#include "postmaster/postmaster.h" +#include "postmaster/startup.h" +#include "postmaster/syslogger.h" +#include "postmaster/walwriter.h" +#include "replication/logicallauncher.h" +#include "replication/reorderbuffer.h" +#include "replication/slot.h" +#include "replication/syncrep.h" +#include "replication/walreceiver.h" +#include "replication/walsender.h" +#include "storage/bufmgr.h" +#include "storage/dsm_impl.h" +#include "storage/fd.h" +#include "storage/large_object.h" +#include "storage/pg_shmem.h" +#include "storage/predicate.h" +#include "storage/proc.h" +#include "storage/standby.h" +#include "tcop/tcopprot.h" +#include "tsearch/ts_cache.h" +#include "utils/acl.h" +#include "utils/backend_status.h" +#include "utils/builtins.h" +#include "utils/bytea.h" +#include "utils/float.h" +#include "utils/guc_tables.h" +#include "utils/memutils.h" +#include "utils/pg_locale.h" +#include "utils/pg_lsn.h" +#include "utils/plancache.h" +#include "utils/portal.h" +#include "utils/ps_status.h" +#include "utils/queryjumble.h" +#include "utils/rls.h" +#include "utils/snapmgr.h" +#include "utils/tzparser.h" +#include "utils/inval.h" +#include "utils/varlena.h" +#include "utils/xml.h" + +#ifndef PG_KRB_SRVTAB +#define PG_KRB_SRVTAB "" +#endif + +#define CONFIG_FILENAME "postgresql.conf" +#define HBA_FILENAME "pg_hba.conf" +#define IDENT_FILENAME "pg_ident.conf" + +#ifdef EXEC_BACKEND +#define CONFIG_EXEC_PARAMS "global/config_exec_params" +#define CONFIG_EXEC_PARAMS_NEW "global/config_exec_params.new" +#endif + +/* + * 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; +extern int CommitSiblings; +extern char *default_tablespace; +extern char *temp_tablespaces; +extern bool ignore_checksum_failure; +extern bool ignore_invalid_pages; +extern bool synchronize_seqscans; + +#ifdef TRACE_SYNCSCAN +extern bool trace_syncscan; +#endif +#ifdef DEBUG_BOUNDED_SORT +extern bool optimize_bounded_sort; +#endif + +static int GUC_check_errcode_value; + +static List *reserved_class_prefix = NIL; + +/* global variables for check hook support */ +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,...) pg_attribute_printf(3, 4); + +static void set_config_sourcefile(const char *name, char *sourcefile, + int sourceline); +static bool call_bool_check_hook(struct config_bool *conf, bool *newval, + void **extra, GucSource source, int elevel); +static bool call_int_check_hook(struct config_int *conf, int *newval, + void **extra, GucSource source, int elevel); +static bool call_real_check_hook(struct config_real *conf, double *newval, + void **extra, GucSource source, int elevel); +static bool call_string_check_hook(struct config_string *conf, char **newval, + void **extra, GucSource source, int elevel); +static bool call_enum_check_hook(struct config_enum *conf, int *newval, + void **extra, GucSource source, int elevel); + +static bool check_log_destination(char **newval, void **extra, GucSource source); +static void assign_log_destination(const char *newval, void *extra); + +static bool check_wal_consistency_checking(char **newval, void **extra, + GucSource source); +static void assign_wal_consistency_checking(const char *newval, void *extra); + +#ifdef HAVE_SYSLOG +static int syslog_facility = LOG_LOCAL0; +#else +static int syslog_facility = 0; +#endif + +static void assign_syslog_facility(int newval, void *extra); +static void assign_syslog_ident(const char *newval, void *extra); +static void assign_session_replication_role(int newval, void *extra); +static bool check_temp_buffers(int *newval, void **extra, GucSource source); +static bool check_bonjour(bool *newval, void **extra, GucSource source); +static bool check_ssl(bool *newval, void **extra, GucSource source); +static bool check_stage_log_stats(bool *newval, void **extra, GucSource source); +static bool check_log_stats(bool *newval, void **extra, GucSource source); +static bool check_canonical_path(char **newval, void **extra, GucSource source); +static bool check_timezone_abbreviations(char **newval, void **extra, GucSource source); +static void assign_timezone_abbreviations(const char *newval, void *extra); +static void pg_timezone_abbrev_initialize(void); +static const char *show_archive_command(void); +static void assign_tcp_keepalives_idle(int newval, void *extra); +static void assign_tcp_keepalives_interval(int newval, void *extra); +static void assign_tcp_keepalives_count(int newval, void *extra); +static void assign_tcp_user_timeout(int newval, void *extra); +static const char *show_tcp_keepalives_idle(void); +static const char *show_tcp_keepalives_interval(void); +static const char *show_tcp_keepalives_count(void); +static const char *show_tcp_user_timeout(void); +static bool check_maxconnections(int *newval, void **extra, GucSource source); +static bool check_max_worker_processes(int *newval, void **extra, GucSource source); +static bool check_autovacuum_max_workers(int *newval, void **extra, GucSource source); +static bool check_max_wal_senders(int *newval, void **extra, GucSource source); +static bool check_autovacuum_work_mem(int *newval, void **extra, GucSource source); +static bool check_effective_io_concurrency(int *newval, void **extra, GucSource source); +static bool check_maintenance_io_concurrency(int *newval, void **extra, GucSource source); +static bool check_huge_page_size(int *newval, void **extra, GucSource source); +static bool check_client_connection_check_interval(int *newval, void **extra, GucSource source); +static void assign_maintenance_io_concurrency(int newval, void *extra); +static bool check_application_name(char **newval, void **extra, GucSource source); +static void assign_application_name(const char *newval, void *extra); +static bool check_cluster_name(char **newval, void **extra, GucSource source); +static const char *show_unix_socket_permissions(void); +static const char *show_log_file_mode(void); +static const char *show_data_directory_mode(void); +static const char *show_in_hot_standby(void); +static bool check_backtrace_functions(char **newval, void **extra, GucSource source); +static void assign_backtrace_functions(const char *newval, void *extra); +static bool check_recovery_target_timeline(char **newval, void **extra, GucSource source); +static void assign_recovery_target_timeline(const char *newval, void *extra); +static bool check_recovery_target(char **newval, void **extra, GucSource source); +static void assign_recovery_target(const char *newval, void *extra); +static bool check_recovery_target_xid(char **newval, void **extra, GucSource source); +static void assign_recovery_target_xid(const char *newval, void *extra); +static bool check_recovery_target_time(char **newval, void **extra, GucSource source); +static void assign_recovery_target_time(const char *newval, void *extra); +static bool check_recovery_target_name(char **newval, void **extra, GucSource source); +static void assign_recovery_target_name(const char *newval, void *extra); +static bool check_recovery_target_lsn(char **newval, void **extra, GucSource source); +static void assign_recovery_target_lsn(const char *newval, void *extra); +static bool check_primary_slot_name(char **newval, void **extra, GucSource source); +static bool check_default_with_oids(bool *newval, void **extra, GucSource source); + +/* Private functions in guc-file.l that need to be called from guc.c */ +static ConfigVariable *ProcessConfigFileInternal(GucContext context, + bool applySettings, int elevel); + +/* + * Track whether there were any deferred checks for custom resource managers + * specified in wal_consistency_checking. + */ +static bool check_wal_consistency_checking_deferred = false; + +/* + * Options for enum values defined in this module. + * + * NOTE! Option values may not contain double quotes! + */ + +static const struct config_enum_entry bytea_output_options[] = { + {"escape", BYTEA_OUTPUT_ESCAPE, false}, + {"hex", BYTEA_OUTPUT_HEX, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(bytea_output_options) == (BYTEA_OUTPUT_HEX + 2), + "array length mismatch"); + +/* + * We have different sets for client and server message level options because + * they sort slightly different (see "log" level), and because "fatal"/"panic" + * aren't sensible for client_min_messages. + */ +static const struct config_enum_entry client_message_level_options[] = { + {"debug5", DEBUG5, false}, + {"debug4", DEBUG4, false}, + {"debug3", DEBUG3, false}, + {"debug2", DEBUG2, false}, + {"debug1", DEBUG1, false}, + {"debug", DEBUG2, true}, + {"log", LOG, false}, + {"info", INFO, true}, + {"notice", NOTICE, false}, + {"warning", WARNING, false}, + {"error", ERROR, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry server_message_level_options[] = { + {"debug5", DEBUG5, false}, + {"debug4", DEBUG4, false}, + {"debug3", DEBUG3, false}, + {"debug2", DEBUG2, false}, + {"debug1", DEBUG1, false}, + {"debug", DEBUG2, true}, + {"info", INFO, false}, + {"notice", NOTICE, false}, + {"warning", WARNING, false}, + {"error", ERROR, false}, + {"log", LOG, false}, + {"fatal", FATAL, false}, + {"panic", PANIC, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry intervalstyle_options[] = { + {"postgres", INTSTYLE_POSTGRES, false}, + {"postgres_verbose", INTSTYLE_POSTGRES_VERBOSE, false}, + {"sql_standard", INTSTYLE_SQL_STANDARD, false}, + {"iso_8601", INTSTYLE_ISO_8601, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(intervalstyle_options) == (INTSTYLE_ISO_8601 + 2), + "array length mismatch"); + +static const struct config_enum_entry log_error_verbosity_options[] = { + {"terse", PGERROR_TERSE, false}, + {"default", PGERROR_DEFAULT, false}, + {"verbose", PGERROR_VERBOSE, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(log_error_verbosity_options) == (PGERROR_VERBOSE + 2), + "array length mismatch"); + +static const struct config_enum_entry log_statement_options[] = { + {"none", LOGSTMT_NONE, false}, + {"ddl", LOGSTMT_DDL, false}, + {"mod", LOGSTMT_MOD, false}, + {"all", LOGSTMT_ALL, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(log_statement_options) == (LOGSTMT_ALL + 2), + "array length mismatch"); + +static const struct config_enum_entry isolation_level_options[] = { + {"serializable", XACT_SERIALIZABLE, false}, + {"repeatable read", XACT_REPEATABLE_READ, false}, + {"read committed", XACT_READ_COMMITTED, false}, + {"read uncommitted", XACT_READ_UNCOMMITTED, false}, + {NULL, 0} +}; + +static const struct config_enum_entry session_replication_role_options[] = { + {"origin", SESSION_REPLICATION_ROLE_ORIGIN, false}, + {"replica", SESSION_REPLICATION_ROLE_REPLICA, false}, + {"local", SESSION_REPLICATION_ROLE_LOCAL, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(session_replication_role_options) == (SESSION_REPLICATION_ROLE_LOCAL + 2), + "array length mismatch"); + +static const struct config_enum_entry syslog_facility_options[] = { +#ifdef HAVE_SYSLOG + {"local0", LOG_LOCAL0, false}, + {"local1", LOG_LOCAL1, false}, + {"local2", LOG_LOCAL2, false}, + {"local3", LOG_LOCAL3, false}, + {"local4", LOG_LOCAL4, false}, + {"local5", LOG_LOCAL5, false}, + {"local6", LOG_LOCAL6, false}, + {"local7", LOG_LOCAL7, false}, +#else + {"none", 0, false}, +#endif + {NULL, 0} +}; + +static const struct config_enum_entry track_function_options[] = { + {"none", TRACK_FUNC_OFF, false}, + {"pl", TRACK_FUNC_PL, false}, + {"all", TRACK_FUNC_ALL, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(track_function_options) == (TRACK_FUNC_ALL + 2), + "array length mismatch"); + +static const struct config_enum_entry stats_fetch_consistency[] = { + {"none", PGSTAT_FETCH_CONSISTENCY_NONE, false}, + {"cache", PGSTAT_FETCH_CONSISTENCY_CACHE, false}, + {"snapshot", PGSTAT_FETCH_CONSISTENCY_SNAPSHOT, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(stats_fetch_consistency) == (PGSTAT_FETCH_CONSISTENCY_SNAPSHOT + 2), + "array length mismatch"); + +static const struct config_enum_entry xmlbinary_options[] = { + {"base64", XMLBINARY_BASE64, false}, + {"hex", XMLBINARY_HEX, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(xmlbinary_options) == (XMLBINARY_HEX + 2), + "array length mismatch"); + +static const struct config_enum_entry xmloption_options[] = { + {"content", XMLOPTION_CONTENT, false}, + {"document", XMLOPTION_DOCUMENT, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(xmloption_options) == (XMLOPTION_CONTENT + 2), + "array length mismatch"); + +/* + * Although only "on", "off", and "safe_encoding" are documented, we + * accept all the likely variants of "on" and "off". + */ +static const struct config_enum_entry backslash_quote_options[] = { + {"safe_encoding", BACKSLASH_QUOTE_SAFE_ENCODING, false}, + {"on", BACKSLASH_QUOTE_ON, false}, + {"off", BACKSLASH_QUOTE_OFF, false}, + {"true", BACKSLASH_QUOTE_ON, true}, + {"false", BACKSLASH_QUOTE_OFF, true}, + {"yes", BACKSLASH_QUOTE_ON, true}, + {"no", BACKSLASH_QUOTE_OFF, true}, + {"1", BACKSLASH_QUOTE_ON, true}, + {"0", BACKSLASH_QUOTE_OFF, true}, + {NULL, 0, false} +}; + +/* + * Although only "on", "off", and "auto" are documented, we accept + * all the likely variants of "on" and "off". + */ +static const struct config_enum_entry compute_query_id_options[] = { + {"auto", COMPUTE_QUERY_ID_AUTO, false}, + {"regress", COMPUTE_QUERY_ID_REGRESS, false}, + {"on", COMPUTE_QUERY_ID_ON, false}, + {"off", COMPUTE_QUERY_ID_OFF, false}, + {"true", COMPUTE_QUERY_ID_ON, true}, + {"false", COMPUTE_QUERY_ID_OFF, true}, + {"yes", COMPUTE_QUERY_ID_ON, true}, + {"no", COMPUTE_QUERY_ID_OFF, true}, + {"1", COMPUTE_QUERY_ID_ON, true}, + {"0", COMPUTE_QUERY_ID_OFF, true}, + {NULL, 0, false} +}; + +/* + * Although only "on", "off", and "partition" are documented, we + * accept all the likely variants of "on" and "off". + */ +static const struct config_enum_entry constraint_exclusion_options[] = { + {"partition", CONSTRAINT_EXCLUSION_PARTITION, false}, + {"on", CONSTRAINT_EXCLUSION_ON, false}, + {"off", CONSTRAINT_EXCLUSION_OFF, false}, + {"true", CONSTRAINT_EXCLUSION_ON, true}, + {"false", CONSTRAINT_EXCLUSION_OFF, true}, + {"yes", CONSTRAINT_EXCLUSION_ON, true}, + {"no", CONSTRAINT_EXCLUSION_OFF, true}, + {"1", CONSTRAINT_EXCLUSION_ON, true}, + {"0", CONSTRAINT_EXCLUSION_OFF, true}, + {NULL, 0, false} +}; + +/* + * Although only "on", "off", "remote_apply", "remote_write", and "local" are + * documented, we accept all the likely variants of "on" and "off". + */ +static const struct config_enum_entry synchronous_commit_options[] = { + {"local", SYNCHRONOUS_COMMIT_LOCAL_FLUSH, false}, + {"remote_write", SYNCHRONOUS_COMMIT_REMOTE_WRITE, false}, + {"remote_apply", SYNCHRONOUS_COMMIT_REMOTE_APPLY, false}, + {"on", SYNCHRONOUS_COMMIT_ON, false}, + {"off", SYNCHRONOUS_COMMIT_OFF, false}, + {"true", SYNCHRONOUS_COMMIT_ON, true}, + {"false", SYNCHRONOUS_COMMIT_OFF, true}, + {"yes", SYNCHRONOUS_COMMIT_ON, true}, + {"no", SYNCHRONOUS_COMMIT_OFF, true}, + {"1", SYNCHRONOUS_COMMIT_ON, true}, + {"0", SYNCHRONOUS_COMMIT_OFF, true}, + {NULL, 0, false} +}; + +/* + * Although only "on", "off", "try" are documented, we accept all the likely + * variants of "on" and "off". + */ +static const struct config_enum_entry huge_pages_options[] = { + {"off", HUGE_PAGES_OFF, false}, + {"on", HUGE_PAGES_ON, false}, + {"try", HUGE_PAGES_TRY, false}, + {"true", HUGE_PAGES_ON, true}, + {"false", HUGE_PAGES_OFF, true}, + {"yes", HUGE_PAGES_ON, true}, + {"no", HUGE_PAGES_OFF, true}, + {"1", HUGE_PAGES_ON, true}, + {"0", HUGE_PAGES_OFF, true}, + {NULL, 0, false} +}; + +static const struct config_enum_entry recovery_prefetch_options[] = { + {"off", RECOVERY_PREFETCH_OFF, false}, + {"on", RECOVERY_PREFETCH_ON, false}, + {"try", RECOVERY_PREFETCH_TRY, false}, + {"true", RECOVERY_PREFETCH_ON, true}, + {"false", RECOVERY_PREFETCH_OFF, true}, + {"yes", RECOVERY_PREFETCH_ON, true}, + {"no", RECOVERY_PREFETCH_OFF, true}, + {"1", RECOVERY_PREFETCH_ON, true}, + {"0", RECOVERY_PREFETCH_OFF, true}, + {NULL, 0, false} +}; + +static const struct config_enum_entry force_parallel_mode_options[] = { + {"off", FORCE_PARALLEL_OFF, false}, + {"on", FORCE_PARALLEL_ON, false}, + {"regress", FORCE_PARALLEL_REGRESS, false}, + {"true", FORCE_PARALLEL_ON, true}, + {"false", FORCE_PARALLEL_OFF, true}, + {"yes", FORCE_PARALLEL_ON, true}, + {"no", FORCE_PARALLEL_OFF, true}, + {"1", FORCE_PARALLEL_ON, true}, + {"0", FORCE_PARALLEL_OFF, true}, + {NULL, 0, false} +}; + +static const struct config_enum_entry plan_cache_mode_options[] = { + {"auto", PLAN_CACHE_MODE_AUTO, false}, + {"force_generic_plan", PLAN_CACHE_MODE_FORCE_GENERIC_PLAN, false}, + {"force_custom_plan", PLAN_CACHE_MODE_FORCE_CUSTOM_PLAN, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry password_encryption_options[] = { + {"md5", PASSWORD_TYPE_MD5, false}, + {"scram-sha-256", PASSWORD_TYPE_SCRAM_SHA_256, false}, + {NULL, 0, false} +}; + +const struct config_enum_entry ssl_protocol_versions_info[] = { + {"", PG_TLS_ANY, false}, + {"TLSv1", PG_TLS1_VERSION, false}, + {"TLSv1.1", PG_TLS1_1_VERSION, false}, + {"TLSv1.2", PG_TLS1_2_VERSION, false}, + {"TLSv1.3", PG_TLS1_3_VERSION, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2), + "array length mismatch"); + +static struct config_enum_entry recovery_init_sync_method_options[] = { + {"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false}, +#ifdef HAVE_SYNCFS + {"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false}, +#endif + {NULL, 0, false} +}; + +static struct config_enum_entry shared_memory_options[] = { +#ifndef WIN32 + {"sysv", SHMEM_TYPE_SYSV, false}, +#endif +#ifndef EXEC_BACKEND + {"mmap", SHMEM_TYPE_MMAP, false}, +#endif +#ifdef WIN32 + {"windows", SHMEM_TYPE_WINDOWS, false}, +#endif + {NULL, 0, false} +}; + +static struct config_enum_entry default_toast_compression_options[] = { + {"pglz", TOAST_PGLZ_COMPRESSION, false}, +#ifdef USE_LZ4 + {"lz4", TOAST_LZ4_COMPRESSION, false}, +#endif + {NULL, 0, false} +}; + +static const struct config_enum_entry wal_compression_options[] = { + {"pglz", WAL_COMPRESSION_PGLZ, false}, +#ifdef USE_LZ4 + {"lz4", WAL_COMPRESSION_LZ4, false}, +#endif +#ifdef USE_ZSTD + {"zstd", WAL_COMPRESSION_ZSTD, false}, +#endif + {"on", WAL_COMPRESSION_PGLZ, false}, + {"off", WAL_COMPRESSION_NONE, false}, + {"true", WAL_COMPRESSION_PGLZ, true}, + {"false", WAL_COMPRESSION_NONE, true}, + {"yes", WAL_COMPRESSION_PGLZ, true}, + {"no", WAL_COMPRESSION_NONE, true}, + {"1", WAL_COMPRESSION_PGLZ, true}, + {"0", WAL_COMPRESSION_NONE, true}, + {NULL, 0, false} +}; + +/* + * Options for enum values stored in other modules + */ +extern const struct config_enum_entry wal_level_options[]; +extern const struct config_enum_entry archive_mode_options[]; +extern const struct config_enum_entry recovery_target_action_options[]; +extern const struct config_enum_entry sync_method_options[]; +extern const struct config_enum_entry dynamic_shared_memory_options[]; + +/* + * GUC option variables that are exported from this module + */ +bool log_duration = false; +bool Debug_print_plan = false; +bool Debug_print_parse = false; +bool Debug_print_rewritten = false; +bool Debug_pretty_print = true; + +bool log_parser_stats = false; +bool log_planner_stats = false; +bool log_executor_stats = false; +bool log_statement_stats = false; /* this is sort of all three above + * together */ +bool log_btree_build_stats = false; +char *event_source; + +bool row_security; +bool check_function_bodies = true; + +/* + * This GUC exists solely for backward compatibility, check its definition for + * details. + */ +bool default_with_oids = false; +bool session_auth_is_superuser; + +int log_min_error_statement = ERROR; +int log_min_messages = WARNING; +int client_min_messages = NOTICE; +int log_min_duration_sample = -1; +int log_min_duration_statement = -1; +int log_parameter_max_length = -1; +int log_parameter_max_length_on_error = 0; +int log_temp_files = -1; +double log_statement_sample_rate = 1.0; +double log_xact_sample_rate = 0; +int trace_recovery_messages = LOG; +char *backtrace_functions; +char *backtrace_symbol_list; + +int temp_file_limit = -1; + +int num_temp_buffers = 1024; + +char *cluster_name = ""; +char *ConfigFileName; +char *HbaFileName; +char *IdentFileName; +char *external_pid_file; + +char *pgstat_temp_directory; + +char *application_name; + +int tcp_keepalives_idle; +int tcp_keepalives_interval; +int tcp_keepalives_count; +int tcp_user_timeout; + +/* + * SSL renegotiation was been removed in PostgreSQL 9.5, but we tolerate it + * being set to zero (meaning never renegotiate) for backward compatibility. + * This avoids breaking compatibility with clients that have never supported + * renegotiation and therefore always try to zero it. + */ +int ssl_renegotiation_limit; + +/* + * This really belongs in pg_shmem.c, but is defined here so that it doesn't + * need to be duplicated in all the different implementations of pg_shmem.c. + */ +int huge_pages; +int huge_page_size; + +/* + * These variables are all dummies that don't do anything, except in some + * cases provide the value for SHOW to display. The real state is elsewhere + * and is kept in sync by assign_hooks. + */ +static char *syslog_ident_str; +static double phony_random_seed; +static char *client_encoding_string; +static char *datestyle_string; +static char *locale_collate; +static char *locale_ctype; +static char *server_encoding_string; +static char *server_version_string; +static int server_version_num; +static char *timezone_string; +static char *log_timezone_string; +static char *timezone_abbreviations_string; +static char *data_directory; +static char *session_authorization_string; +static int max_function_args; +static int max_index_keys; +static int max_identifier_length; +static int block_size; +static int segment_size; +static int shared_memory_size_mb; +static int shared_memory_size_in_huge_pages; +static int wal_block_size; +static bool data_checksums; +static bool integer_datetimes; +static bool assert_enabled; +static bool in_hot_standby; +static char *recovery_target_timeline_string; +static char *recovery_target_string; +static char *recovery_target_xid_string; +static char *recovery_target_name_string; +static char *recovery_target_lsn_string; + + +/* should be static, but commands/variable.c needs to get at this */ +char *role_string; + + +/* + * Displayable names for context types (enum GucContext) + * + * Note: these strings are deliberately not localized. + */ +const char *const GucContext_Names[] = +{ + /* PGC_INTERNAL */ "internal", + /* PGC_POSTMASTER */ "postmaster", + /* PGC_SIGHUP */ "sighup", + /* PGC_SU_BACKEND */ "superuser-backend", + /* PGC_BACKEND */ "backend", + /* PGC_SUSET */ "superuser", + /* PGC_USERSET */ "user" +}; + +StaticAssertDecl(lengthof(GucContext_Names) == (PGC_USERSET + 1), + "array length mismatch"); + +/* + * Displayable names for source types (enum GucSource) + * + * Note: these strings are deliberately not localized. + */ +const char *const GucSource_Names[] = +{ + /* PGC_S_DEFAULT */ "default", + /* PGC_S_DYNAMIC_DEFAULT */ "default", + /* PGC_S_ENV_VAR */ "environment variable", + /* PGC_S_FILE */ "configuration file", + /* PGC_S_ARGV */ "command line", + /* PGC_S_GLOBAL */ "global", + /* PGC_S_DATABASE */ "database", + /* PGC_S_USER */ "user", + /* PGC_S_DATABASE_USER */ "database user", + /* PGC_S_CLIENT */ "client", + /* PGC_S_OVERRIDE */ "override", + /* PGC_S_INTERACTIVE */ "interactive", + /* PGC_S_TEST */ "test", + /* PGC_S_SESSION */ "session" +}; + +StaticAssertDecl(lengthof(GucSource_Names) == (PGC_S_SESSION + 1), + "array length mismatch"); + +/* + * Displayable names for the groupings defined in enum config_group + */ +const char *const config_group_names[] = +{ + /* UNGROUPED */ + gettext_noop("Ungrouped"), + /* FILE_LOCATIONS */ + gettext_noop("File Locations"), + /* CONN_AUTH_SETTINGS */ + gettext_noop("Connections and Authentication / Connection Settings"), + /* CONN_AUTH_AUTH */ + gettext_noop("Connections and Authentication / Authentication"), + /* CONN_AUTH_SSL */ + gettext_noop("Connections and Authentication / SSL"), + /* RESOURCES_MEM */ + gettext_noop("Resource Usage / Memory"), + /* RESOURCES_DISK */ + gettext_noop("Resource Usage / Disk"), + /* RESOURCES_KERNEL */ + gettext_noop("Resource Usage / Kernel Resources"), + /* RESOURCES_VACUUM_DELAY */ + gettext_noop("Resource Usage / Cost-Based Vacuum Delay"), + /* RESOURCES_BGWRITER */ + gettext_noop("Resource Usage / Background Writer"), + /* RESOURCES_ASYNCHRONOUS */ + gettext_noop("Resource Usage / Asynchronous Behavior"), + /* WAL_SETTINGS */ + gettext_noop("Write-Ahead Log / Settings"), + /* WAL_CHECKPOINTS */ + gettext_noop("Write-Ahead Log / Checkpoints"), + /* WAL_ARCHIVING */ + gettext_noop("Write-Ahead Log / Archiving"), + /* WAL_RECOVERY */ + gettext_noop("Write-Ahead Log / Recovery"), + /* WAL_ARCHIVE_RECOVERY */ + gettext_noop("Write-Ahead Log / Archive Recovery"), + /* WAL_RECOVERY_TARGET */ + gettext_noop("Write-Ahead Log / Recovery Target"), + /* REPLICATION_SENDING */ + gettext_noop("Replication / Sending Servers"), + /* REPLICATION_PRIMARY */ + gettext_noop("Replication / Primary Server"), + /* REPLICATION_STANDBY */ + gettext_noop("Replication / Standby Servers"), + /* REPLICATION_SUBSCRIBERS */ + gettext_noop("Replication / Subscribers"), + /* QUERY_TUNING_METHOD */ + gettext_noop("Query Tuning / Planner Method Configuration"), + /* QUERY_TUNING_COST */ + gettext_noop("Query Tuning / Planner Cost Constants"), + /* QUERY_TUNING_GEQO */ + gettext_noop("Query Tuning / Genetic Query Optimizer"), + /* QUERY_TUNING_OTHER */ + gettext_noop("Query Tuning / Other Planner Options"), + /* LOGGING_WHERE */ + gettext_noop("Reporting and Logging / Where to Log"), + /* LOGGING_WHEN */ + gettext_noop("Reporting and Logging / When to Log"), + /* LOGGING_WHAT */ + gettext_noop("Reporting and Logging / What to Log"), + /* PROCESS_TITLE */ + gettext_noop("Reporting and Logging / Process Title"), + /* STATS_MONITORING */ + gettext_noop("Statistics / Monitoring"), + /* STATS_CUMULATIVE */ + gettext_noop("Statistics / Cumulative Query and Index Statistics"), + /* AUTOVACUUM */ + gettext_noop("Autovacuum"), + /* CLIENT_CONN_STATEMENT */ + gettext_noop("Client Connection Defaults / Statement Behavior"), + /* CLIENT_CONN_LOCALE */ + gettext_noop("Client Connection Defaults / Locale and Formatting"), + /* CLIENT_CONN_PRELOAD */ + gettext_noop("Client Connection Defaults / Shared Library Preloading"), + /* CLIENT_CONN_OTHER */ + gettext_noop("Client Connection Defaults / Other Defaults"), + /* LOCK_MANAGEMENT */ + gettext_noop("Lock Management"), + /* COMPAT_OPTIONS_PREVIOUS */ + gettext_noop("Version and Platform Compatibility / Previous PostgreSQL Versions"), + /* COMPAT_OPTIONS_CLIENT */ + gettext_noop("Version and Platform Compatibility / Other Platforms and Clients"), + /* ERROR_HANDLING */ + gettext_noop("Error Handling"), + /* PRESET_OPTIONS */ + gettext_noop("Preset Options"), + /* CUSTOM_OPTIONS */ + gettext_noop("Customized Options"), + /* DEVELOPER_OPTIONS */ + gettext_noop("Developer Options"), + /* help_config wants this array to be null-terminated */ + NULL +}; + +StaticAssertDecl(lengthof(config_group_names) == (DEVELOPER_OPTIONS + 2), + "array length mismatch"); + +/* + * Displayable names for GUC variable types (enum config_type) + * + * Note: these strings are deliberately not localized. + */ +const char *const config_type_names[] = +{ + /* PGC_BOOL */ "bool", + /* PGC_INT */ "integer", + /* PGC_REAL */ "real", + /* PGC_STRING */ "string", + /* PGC_ENUM */ "enum" +}; + +StaticAssertDecl(lengthof(config_type_names) == (PGC_ENUM + 1), + "array length mismatch"); + +/* + * Unit conversion tables. + * + * There are two tables, one for memory units, and another for time units. + * For each supported conversion from one unit to another, we have an entry + * in the table. + * + * To keep things simple, and to avoid possible roundoff error, + * conversions are never chained. There needs to be a direct conversion + * between all units (of the same type). + * + * The conversions for each base unit must be kept in order from greatest to + * smallest human-friendly unit; convert_xxx_from_base_unit() rely on that. + * (The order of the base-unit groups does not matter.) + */ +#define MAX_UNIT_LEN 3 /* length of longest recognized unit string */ + +typedef struct +{ + char unit[MAX_UNIT_LEN + 1]; /* unit, as a string, like "kB" or + * "min" */ + int base_unit; /* GUC_UNIT_XXX */ + double multiplier; /* Factor for converting unit -> base_unit */ +} unit_conversion; + +/* Ensure that the constants in the tables don't overflow or underflow */ +#if BLCKSZ < 1024 || BLCKSZ > (1024*1024) +#error BLCKSZ must be between 1KB and 1MB +#endif +#if XLOG_BLCKSZ < 1024 || XLOG_BLCKSZ > (1024*1024) +#error XLOG_BLCKSZ must be between 1KB and 1MB +#endif + +static const char *memory_units_hint = gettext_noop("Valid units for this parameter are \"B\", \"kB\", \"MB\", \"GB\", and \"TB\"."); + +static const unit_conversion memory_unit_conversion_table[] = +{ + {"TB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0 * 1024.0}, + {"GB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0}, + {"MB", GUC_UNIT_BYTE, 1024.0 * 1024.0}, + {"kB", GUC_UNIT_BYTE, 1024.0}, + {"B", GUC_UNIT_BYTE, 1.0}, + + {"TB", GUC_UNIT_KB, 1024.0 * 1024.0 * 1024.0}, + {"GB", GUC_UNIT_KB, 1024.0 * 1024.0}, + {"MB", GUC_UNIT_KB, 1024.0}, + {"kB", GUC_UNIT_KB, 1.0}, + {"B", GUC_UNIT_KB, 1.0 / 1024.0}, + + {"TB", GUC_UNIT_MB, 1024.0 * 1024.0}, + {"GB", GUC_UNIT_MB, 1024.0}, + {"MB", GUC_UNIT_MB, 1.0}, + {"kB", GUC_UNIT_MB, 1.0 / 1024.0}, + {"B", GUC_UNIT_MB, 1.0 / (1024.0 * 1024.0)}, + + {"TB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0 * 1024.0) / (BLCKSZ / 1024)}, + {"GB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0) / (BLCKSZ / 1024)}, + {"MB", GUC_UNIT_BLOCKS, 1024.0 / (BLCKSZ / 1024)}, + {"kB", GUC_UNIT_BLOCKS, 1.0 / (BLCKSZ / 1024)}, + {"B", GUC_UNIT_BLOCKS, 1.0 / BLCKSZ}, + + {"TB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)}, + {"GB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)}, + {"MB", GUC_UNIT_XBLOCKS, 1024.0 / (XLOG_BLCKSZ / 1024)}, + {"kB", GUC_UNIT_XBLOCKS, 1.0 / (XLOG_BLCKSZ / 1024)}, + {"B", GUC_UNIT_XBLOCKS, 1.0 / XLOG_BLCKSZ}, + + {""} /* end of table marker */ +}; + +static const char *time_units_hint = gettext_noop("Valid units for this parameter are \"us\", \"ms\", \"s\", \"min\", \"h\", and \"d\"."); + +static const unit_conversion time_unit_conversion_table[] = +{ + {"d", GUC_UNIT_MS, 1000 * 60 * 60 * 24}, + {"h", GUC_UNIT_MS, 1000 * 60 * 60}, + {"min", GUC_UNIT_MS, 1000 * 60}, + {"s", GUC_UNIT_MS, 1000}, + {"ms", GUC_UNIT_MS, 1}, + {"us", GUC_UNIT_MS, 1.0 / 1000}, + + {"d", GUC_UNIT_S, 60 * 60 * 24}, + {"h", GUC_UNIT_S, 60 * 60}, + {"min", GUC_UNIT_S, 60}, + {"s", GUC_UNIT_S, 1}, + {"ms", GUC_UNIT_S, 1.0 / 1000}, + {"us", GUC_UNIT_S, 1.0 / (1000 * 1000)}, + + {"d", GUC_UNIT_MIN, 60 * 24}, + {"h", GUC_UNIT_MIN, 60}, + {"min", GUC_UNIT_MIN, 1}, + {"s", GUC_UNIT_MIN, 1.0 / 60}, + {"ms", GUC_UNIT_MIN, 1.0 / (1000 * 60)}, + {"us", GUC_UNIT_MIN, 1.0 / (1000 * 1000 * 60)}, + + {""} /* end of table marker */ +}; + +/* + * Contents of GUC tables + * + * See src/backend/utils/misc/README for design notes. + * + * TO ADD AN OPTION: + * + * 1. Declare a global variable of type bool, int, double, or char* + * and make use of it. + * + * 2. Decide at what times it's safe to set the option. See guc.h for + * details. + * + * 3. Decide on a name, a default value, upper and lower bounds (if + * applicable), etc. + * + * 4. Add a record below. + * + * 5. Add it to src/backend/utils/misc/postgresql.conf.sample, if + * appropriate. + * + * 6. Don't forget to document the option (at least in config.sgml). + * + * 7. If it's a new GUC_LIST_QUOTE option, you must add it to + * variable_is_guc_list_quote() in src/bin/pg_dump/dumputils.c. + */ + + +/******** option records follow ********/ + +static struct config_bool ConfigureNamesBool[] = +{ + { + {"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of sequential-scan plans."), + NULL, + GUC_EXPLAIN + }, + &enable_seqscan, + true, + NULL, NULL, NULL + }, + { + {"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of index-scan plans."), + NULL, + GUC_EXPLAIN + }, + &enable_indexscan, + true, + NULL, NULL, NULL + }, + { + {"enable_indexonlyscan", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of index-only-scan plans."), + NULL, + GUC_EXPLAIN + }, + &enable_indexonlyscan, + true, + NULL, NULL, NULL + }, + { + {"enable_bitmapscan", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of bitmap-scan plans."), + NULL, + GUC_EXPLAIN + }, + &enable_bitmapscan, + true, + NULL, NULL, NULL + }, + { + {"enable_tidscan", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of TID scan plans."), + NULL, + GUC_EXPLAIN + }, + &enable_tidscan, + true, + NULL, NULL, NULL + }, + { + {"enable_sort", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of explicit sort steps."), + NULL, + GUC_EXPLAIN + }, + &enable_sort, + true, + NULL, NULL, NULL + }, + { + {"enable_incremental_sort", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of incremental sort steps."), + NULL, + GUC_EXPLAIN + }, + &enable_incremental_sort, + true, + NULL, NULL, NULL + }, + { + {"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of hashed aggregation plans."), + NULL, + GUC_EXPLAIN + }, + &enable_hashagg, + true, + NULL, NULL, NULL + }, + { + {"enable_material", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of materialization."), + NULL, + GUC_EXPLAIN + }, + &enable_material, + true, + NULL, NULL, NULL + }, + { + {"enable_memoize", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of memoization."), + NULL, + GUC_EXPLAIN + }, + &enable_memoize, + true, + NULL, NULL, NULL + }, + { + {"enable_nestloop", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of nested-loop join plans."), + NULL, + GUC_EXPLAIN + }, + &enable_nestloop, + true, + NULL, NULL, NULL + }, + { + {"enable_mergejoin", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of merge join plans."), + NULL, + GUC_EXPLAIN + }, + &enable_mergejoin, + true, + NULL, NULL, NULL + }, + { + {"enable_hashjoin", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of hash join plans."), + NULL, + GUC_EXPLAIN + }, + &enable_hashjoin, + true, + NULL, NULL, NULL + }, + { + {"enable_gathermerge", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of gather merge plans."), + NULL, + GUC_EXPLAIN + }, + &enable_gathermerge, + true, + NULL, NULL, NULL + }, + { + {"enable_partitionwise_join", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables partitionwise join."), + NULL, + GUC_EXPLAIN + }, + &enable_partitionwise_join, + false, + NULL, NULL, NULL + }, + { + {"enable_partitionwise_aggregate", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables partitionwise aggregation and grouping."), + NULL, + GUC_EXPLAIN + }, + &enable_partitionwise_aggregate, + false, + NULL, NULL, NULL + }, + { + {"enable_parallel_append", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of parallel append plans."), + NULL, + GUC_EXPLAIN + }, + &enable_parallel_append, + true, + NULL, NULL, NULL + }, + { + {"enable_parallel_hash", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of parallel hash plans."), + NULL, + GUC_EXPLAIN + }, + &enable_parallel_hash, + true, + NULL, NULL, NULL + }, + { + {"enable_partition_pruning", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables plan-time and execution-time partition pruning."), + gettext_noop("Allows the query planner and executor to compare partition " + "bounds to conditions in the query to determine which " + "partitions must be scanned."), + GUC_EXPLAIN + }, + &enable_partition_pruning, + true, + NULL, NULL, NULL + }, + { + {"enable_async_append", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner's use of async append plans."), + NULL, + GUC_EXPLAIN + }, + &enable_async_append, + true, + NULL, NULL, NULL + }, + { + {"geqo", PGC_USERSET, QUERY_TUNING_GEQO, + gettext_noop("Enables genetic query optimization."), + gettext_noop("This algorithm attempts to do planning without " + "exhaustive searching."), + GUC_EXPLAIN + }, + &enable_geqo, + true, + NULL, NULL, NULL + }, + { + /* Not for general use --- used by SET SESSION AUTHORIZATION */ + {"is_superuser", PGC_INTERNAL, UNGROUPED, + gettext_noop("Shows whether the current user is a superuser."), + NULL, + GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &session_auth_is_superuser, + false, + NULL, NULL, NULL + }, + { + {"bonjour", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Enables advertising the server via Bonjour."), + NULL + }, + &enable_bonjour, + false, + check_bonjour, NULL, NULL + }, + { + {"track_commit_timestamp", PGC_POSTMASTER, REPLICATION_SENDING, + gettext_noop("Collects transaction commit time."), + NULL + }, + &track_commit_timestamp, + false, + NULL, NULL, NULL + }, + { + {"ssl", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Enables SSL connections."), + NULL + }, + &EnableSSL, + false, + check_ssl, NULL, NULL + }, + { + {"ssl_passphrase_command_supports_reload", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Controls whether ssl_passphrase_command is called during server reload."), + NULL + }, + &ssl_passphrase_command_supports_reload, + false, + NULL, NULL, NULL + }, + { + {"ssl_prefer_server_ciphers", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Give priority to server ciphersuite order."), + NULL + }, + &SSLPreferServerCiphers, + true, + NULL, NULL, NULL + }, + { + {"fsync", PGC_SIGHUP, WAL_SETTINGS, + gettext_noop("Forces synchronization of updates to disk."), + gettext_noop("The server will use the fsync() system call in several places to make " + "sure that updates are physically written to disk. This insures " + "that a database cluster will recover to a consistent state after " + "an operating system or hardware crash.") + }, + &enableFsync, + true, + NULL, NULL, NULL + }, + { + {"ignore_checksum_failure", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Continues processing after a checksum failure."), + gettext_noop("Detection of a checksum failure normally causes PostgreSQL to " + "report an error, aborting the current transaction. Setting " + "ignore_checksum_failure to true causes the system to ignore the failure " + "(but still report a warning), and continue processing. This " + "behavior could cause crashes or other serious problems. Only " + "has an effect if checksums are enabled."), + GUC_NOT_IN_SAMPLE + }, + &ignore_checksum_failure, + false, + NULL, NULL, NULL + }, + { + {"zero_damaged_pages", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Continues processing past damaged page headers."), + gettext_noop("Detection of a damaged page header normally causes PostgreSQL to " + "report an error, aborting the current transaction. Setting " + "zero_damaged_pages to true causes the system to instead report a " + "warning, zero out the damaged page, and continue processing. This " + "behavior will destroy data, namely all the rows on the damaged page."), + GUC_NOT_IN_SAMPLE + }, + &zero_damaged_pages, + false, + NULL, NULL, NULL + }, + { + {"ignore_invalid_pages", PGC_POSTMASTER, DEVELOPER_OPTIONS, + gettext_noop("Continues recovery after an invalid pages failure."), + gettext_noop("Detection of WAL records having references to " + "invalid pages during recovery causes PostgreSQL to " + "raise a PANIC-level error, aborting the recovery. " + "Setting ignore_invalid_pages to true causes " + "the system to ignore invalid page references " + "in WAL records (but still report a warning), " + "and continue recovery. This behavior may cause " + "crashes, data loss, propagate or hide corruption, " + "or other serious problems. Only has an effect " + "during recovery or in standby mode."), + GUC_NOT_IN_SAMPLE + }, + &ignore_invalid_pages, + false, + NULL, NULL, NULL + }, + { + {"full_page_writes", PGC_SIGHUP, WAL_SETTINGS, + gettext_noop("Writes full pages to WAL when first modified after a checkpoint."), + gettext_noop("A page write in process during an operating system crash might be " + "only partially written to disk. During recovery, the row changes " + "stored in WAL are not enough to recover. This option writes " + "pages when first modified after a checkpoint to WAL so full recovery " + "is possible.") + }, + &fullPageWrites, + true, + NULL, NULL, NULL + }, + + { + {"wal_log_hints", PGC_POSTMASTER, WAL_SETTINGS, + gettext_noop("Writes full pages to WAL when first modified after a checkpoint, even for a non-critical modification."), + NULL + }, + &wal_log_hints, + false, + NULL, NULL, NULL + }, + + { + {"wal_init_zero", PGC_SUSET, WAL_SETTINGS, + gettext_noop("Writes zeroes to new WAL files before first use."), + NULL + }, + &wal_init_zero, + true, + NULL, NULL, NULL + }, + + { + {"wal_recycle", PGC_SUSET, WAL_SETTINGS, + gettext_noop("Recycles WAL files by renaming them."), + NULL + }, + &wal_recycle, + true, + NULL, NULL, NULL + }, + + { + {"log_checkpoints", PGC_SIGHUP, LOGGING_WHAT, + gettext_noop("Logs each checkpoint."), + NULL + }, + &log_checkpoints, + true, + NULL, NULL, NULL + }, + { + {"log_connections", PGC_SU_BACKEND, LOGGING_WHAT, + gettext_noop("Logs each successful connection."), + NULL + }, + &Log_connections, + false, + NULL, NULL, NULL + }, + { + {"log_disconnections", PGC_SU_BACKEND, LOGGING_WHAT, + gettext_noop("Logs end of a session, including duration."), + NULL + }, + &Log_disconnections, + false, + NULL, NULL, NULL + }, + { + {"log_replication_commands", PGC_SUSET, LOGGING_WHAT, + gettext_noop("Logs each replication command."), + NULL + }, + &log_replication_commands, + false, + NULL, NULL, NULL + }, + { + {"debug_assertions", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows whether the running server has assertion checks enabled."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &assert_enabled, +#ifdef USE_ASSERT_CHECKING + true, +#else + false, +#endif + NULL, NULL, NULL + }, + + { + {"exit_on_error", PGC_USERSET, ERROR_HANDLING_OPTIONS, + gettext_noop("Terminate session on any error."), + NULL + }, + &ExitOnAnyError, + false, + NULL, NULL, NULL + }, + { + {"restart_after_crash", PGC_SIGHUP, ERROR_HANDLING_OPTIONS, + gettext_noop("Reinitialize server after backend crash."), + NULL + }, + &restart_after_crash, + true, + NULL, NULL, NULL + }, + { + {"remove_temp_files_after_crash", PGC_SIGHUP, DEVELOPER_OPTIONS, + gettext_noop("Remove temporary files after backend crash."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &remove_temp_files_after_crash, + true, + NULL, NULL, NULL + }, + + { + {"log_duration", PGC_SUSET, LOGGING_WHAT, + gettext_noop("Logs the duration of each completed SQL statement."), + NULL + }, + &log_duration, + false, + NULL, NULL, NULL + }, + { + {"debug_print_parse", PGC_USERSET, LOGGING_WHAT, + gettext_noop("Logs each query's parse tree."), + NULL + }, + &Debug_print_parse, + false, + NULL, NULL, NULL + }, + { + {"debug_print_rewritten", PGC_USERSET, LOGGING_WHAT, + gettext_noop("Logs each query's rewritten parse tree."), + NULL + }, + &Debug_print_rewritten, + false, + NULL, NULL, NULL + }, + { + {"debug_print_plan", PGC_USERSET, LOGGING_WHAT, + gettext_noop("Logs each query's execution plan."), + NULL + }, + &Debug_print_plan, + false, + NULL, NULL, NULL + }, + { + {"debug_pretty_print", PGC_USERSET, LOGGING_WHAT, + gettext_noop("Indents parse and plan tree displays."), + NULL + }, + &Debug_pretty_print, + true, + NULL, NULL, NULL + }, + { + {"log_parser_stats", PGC_SUSET, STATS_MONITORING, + gettext_noop("Writes parser performance statistics to the server log."), + NULL + }, + &log_parser_stats, + false, + check_stage_log_stats, NULL, NULL + }, + { + {"log_planner_stats", PGC_SUSET, STATS_MONITORING, + gettext_noop("Writes planner performance statistics to the server log."), + NULL + }, + &log_planner_stats, + false, + check_stage_log_stats, NULL, NULL + }, + { + {"log_executor_stats", PGC_SUSET, STATS_MONITORING, + gettext_noop("Writes executor performance statistics to the server log."), + NULL + }, + &log_executor_stats, + false, + check_stage_log_stats, NULL, NULL + }, + { + {"log_statement_stats", PGC_SUSET, STATS_MONITORING, + gettext_noop("Writes cumulative performance statistics to the server log."), + NULL + }, + &log_statement_stats, + false, + check_log_stats, NULL, NULL + }, +#ifdef BTREE_BUILD_STATS + { + {"log_btree_build_stats", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Logs system resource usage statistics (memory and CPU) on various B-tree operations."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &log_btree_build_stats, + false, + NULL, NULL, NULL + }, +#endif + + { + {"track_activities", PGC_SUSET, STATS_CUMULATIVE, + gettext_noop("Collects information about executing commands."), + gettext_noop("Enables the collection of information on the currently " + "executing command of each session, along with " + "the time at which that command began execution.") + }, + &pgstat_track_activities, + true, + NULL, NULL, NULL + }, + { + {"track_counts", PGC_SUSET, STATS_CUMULATIVE, + gettext_noop("Collects statistics on database activity."), + NULL + }, + &pgstat_track_counts, + true, + NULL, NULL, NULL + }, + { + {"track_io_timing", PGC_SUSET, STATS_CUMULATIVE, + gettext_noop("Collects timing statistics for database I/O activity."), + NULL + }, + &track_io_timing, + false, + NULL, NULL, NULL + }, + { + {"track_wal_io_timing", PGC_SUSET, STATS_CUMULATIVE, + gettext_noop("Collects timing statistics for WAL I/O activity."), + NULL + }, + &track_wal_io_timing, + false, + NULL, NULL, NULL + }, + + { + {"update_process_title", PGC_SUSET, PROCESS_TITLE, + gettext_noop("Updates the process title to show the active SQL command."), + gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.") + }, + &update_process_title, +#ifdef WIN32 + false, +#else + true, +#endif + NULL, NULL, NULL + }, + + { + {"autovacuum", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Starts the autovacuum subprocess."), + NULL + }, + &autovacuum_start_daemon, + true, + NULL, NULL, NULL + }, + + { + {"trace_notify", PGC_USERSET, DEVELOPER_OPTIONS, + gettext_noop("Generates debugging output for LISTEN and NOTIFY."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &Trace_notify, + false, + NULL, NULL, NULL + }, + +#ifdef LOCK_DEBUG + { + {"trace_locks", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Emits information about lock usage."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &Trace_locks, + false, + NULL, NULL, NULL + }, + { + {"trace_userlocks", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Emits information about user lock usage."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &Trace_userlocks, + false, + NULL, NULL, NULL + }, + { + {"trace_lwlocks", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Emits information about lightweight lock usage."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &Trace_lwlocks, + false, + NULL, NULL, NULL + }, + { + {"debug_deadlocks", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Dumps information about all current locks when a deadlock timeout occurs."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &Debug_deadlocks, + false, + NULL, NULL, NULL + }, +#endif + + { + {"log_lock_waits", PGC_SUSET, LOGGING_WHAT, + gettext_noop("Logs long lock waits."), + NULL + }, + &log_lock_waits, + false, + NULL, NULL, NULL + }, + { + {"log_recovery_conflict_waits", PGC_SIGHUP, LOGGING_WHAT, + gettext_noop("Logs standby recovery conflict waits."), + NULL + }, + &log_recovery_conflict_waits, + false, + NULL, NULL, NULL + }, + { + {"log_hostname", PGC_SIGHUP, LOGGING_WHAT, + gettext_noop("Logs the host name in the connection logs."), + gettext_noop("By default, connection logs only show the IP address " + "of the connecting host. If you want them to show the host name you " + "can turn this on, but depending on your host name resolution " + "setup it might impose a non-negligible performance penalty.") + }, + &log_hostname, + false, + NULL, NULL, NULL + }, + { + {"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT, + gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."), + gettext_noop("When turned on, expressions of the form expr = NULL " + "(or NULL = expr) are treated as expr IS NULL, that is, they " + "return true if expr evaluates to the null value, and false " + "otherwise. The correct behavior of expr = NULL is to always " + "return null (unknown).") + }, + &Transform_null_equals, + false, + NULL, NULL, NULL + }, + { + {"db_user_namespace", PGC_SIGHUP, CONN_AUTH_AUTH, + gettext_noop("Enables per-database user names."), + NULL + }, + &Db_user_namespace, + false, + NULL, NULL, NULL + }, + { + {"default_transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default read-only status of new transactions."), + NULL, + GUC_REPORT + }, + &DefaultXactReadOnly, + false, + NULL, NULL, NULL + }, + { + {"transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the current transaction's read-only status."), + NULL, + GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &XactReadOnly, + false, + check_transaction_read_only, NULL, NULL + }, + { + {"default_transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default deferrable status of new transactions."), + NULL + }, + &DefaultXactDeferrable, + false, + NULL, NULL, NULL + }, + { + {"transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Whether to defer a read-only serializable transaction until it can be executed with no possible serialization failures."), + NULL, + GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &XactDeferrable, + false, + check_transaction_deferrable, NULL, NULL + }, + { + {"row_security", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Enable row security."), + gettext_noop("When enabled, row security will be applied to all users.") + }, + &row_security, + true, + NULL, NULL, NULL + }, + { + {"check_function_bodies", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Check routine bodies during CREATE FUNCTION and CREATE PROCEDURE."), + NULL + }, + &check_function_bodies, + true, + NULL, NULL, NULL + }, + { + {"array_nulls", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("Enable input of NULL elements in arrays."), + gettext_noop("When turned on, unquoted NULL in an array input " + "value means a null value; " + "otherwise it is taken literally.") + }, + &Array_nulls, + true, + NULL, NULL, NULL + }, + + /* + * WITH OIDS support, and consequently default_with_oids, was removed in + * PostgreSQL 12, but we tolerate the parameter being set to false to + * avoid unnecessarily breaking older dump files. + */ + { + {"default_with_oids", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("WITH OIDS is no longer supported; this can only be false."), + NULL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE + }, + &default_with_oids, + false, + check_default_with_oids, NULL, NULL + }, + { + {"logging_collector", PGC_POSTMASTER, LOGGING_WHERE, + gettext_noop("Start a subprocess to capture stderr output and/or csvlogs into log files."), + NULL + }, + &Logging_collector, + false, + NULL, NULL, NULL + }, + { + {"log_truncate_on_rotation", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Truncate existing log files of same name during log rotation."), + NULL + }, + &Log_truncate_on_rotation, + false, + NULL, NULL, NULL + }, + +#ifdef TRACE_SORT + { + {"trace_sort", PGC_USERSET, DEVELOPER_OPTIONS, + gettext_noop("Emit information about resource usage in sorting."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &trace_sort, + false, + NULL, NULL, NULL + }, +#endif + +#ifdef TRACE_SYNCSCAN + /* this is undocumented because not exposed in a standard build */ + { + {"trace_syncscan", PGC_USERSET, DEVELOPER_OPTIONS, + gettext_noop("Generate debugging output for synchronized scanning."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &trace_syncscan, + false, + NULL, NULL, NULL + }, +#endif + +#ifdef DEBUG_BOUNDED_SORT + /* this is undocumented because not exposed in a standard build */ + { + { + "optimize_bounded_sort", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enable bounded sorting using heap sort."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_EXPLAIN + }, + &optimize_bounded_sort, + true, + NULL, NULL, NULL + }, +#endif + +#ifdef WAL_DEBUG + { + {"wal_debug", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Emit WAL-related debugging output."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &XLOG_DEBUG, + false, + NULL, NULL, NULL + }, +#endif + + { + {"integer_datetimes", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows whether datetimes are integer based."), + NULL, + GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &integer_datetimes, + true, + NULL, NULL, NULL + }, + + { + {"krb_caseins_users", PGC_SIGHUP, CONN_AUTH_AUTH, + gettext_noop("Sets whether Kerberos and GSSAPI user names should be treated as case-insensitive."), + NULL + }, + &pg_krb_caseins_users, + false, + NULL, NULL, NULL + }, + + { + {"escape_string_warning", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("Warn about backslash escapes in ordinary string literals."), + NULL + }, + &escape_string_warning, + true, + NULL, NULL, NULL + }, + + { + {"standard_conforming_strings", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("Causes '...' strings to treat backslashes literally."), + NULL, + GUC_REPORT + }, + &standard_conforming_strings, + true, + NULL, NULL, NULL + }, + + { + {"synchronize_seqscans", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("Enable synchronized sequential scans."), + NULL + }, + &synchronize_seqscans, + true, + NULL, NULL, NULL + }, + + { + {"recovery_target_inclusive", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets whether to include or exclude transaction with recovery target."), + NULL + }, + &recoveryTargetInclusive, + true, + NULL, NULL, NULL + }, + + { + {"hot_standby", PGC_POSTMASTER, REPLICATION_STANDBY, + gettext_noop("Allows connections and queries during recovery."), + NULL + }, + &EnableHotStandby, + true, + NULL, NULL, NULL + }, + + { + {"hot_standby_feedback", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Allows feedback from a hot standby to the primary that will avoid query conflicts."), + NULL + }, + &hot_standby_feedback, + false, + NULL, NULL, NULL + }, + + { + {"in_hot_standby", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows whether hot standby is currently active."), + NULL, + GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &in_hot_standby, + false, + NULL, NULL, show_in_hot_standby + }, + + { + {"allow_system_table_mods", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Allows modifications of the structure of system tables."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &allowSystemTableMods, + false, + NULL, NULL, NULL + }, + + { + {"ignore_system_indexes", PGC_BACKEND, DEVELOPER_OPTIONS, + gettext_noop("Disables reading from system indexes."), + gettext_noop("It does not prevent updating the indexes, so it is safe " + "to use. The worst consequence is slowness."), + GUC_NOT_IN_SAMPLE + }, + &IgnoreSystemIndexes, + false, + NULL, NULL, NULL + }, + + { + {"allow_in_place_tablespaces", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Allows tablespaces directly inside pg_tblspc, for testing."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &allow_in_place_tablespaces, + false, + NULL, NULL, NULL + }, + + { + {"lo_compat_privileges", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("Enables backward compatibility mode for privilege checks on large objects."), + gettext_noop("Skips privilege checks when reading or modifying large objects, " + "for compatibility with PostgreSQL releases prior to 9.0.") + }, + &lo_compat_privileges, + false, + NULL, NULL, NULL + }, + + { + {"quote_all_identifiers", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("When generating SQL fragments, quote all identifiers."), + NULL, + }, + "e_all_identifiers, + false, + NULL, NULL, NULL + }, + + { + {"data_checksums", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows whether data checksums are turned on for this cluster."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_RUNTIME_COMPUTED + }, + &data_checksums, + false, + NULL, NULL, NULL + }, + + { + {"syslog_sequence_numbers", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Add sequence number to syslog messages to avoid duplicate suppression."), + NULL + }, + &syslog_sequence_numbers, + true, + NULL, NULL, NULL + }, + + { + {"syslog_split_messages", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Split messages sent to syslog by lines and to fit into 1024 bytes."), + NULL + }, + &syslog_split_messages, + true, + NULL, NULL, NULL + }, + + { + {"parallel_leader_participation", PGC_USERSET, RESOURCES_ASYNCHRONOUS, + gettext_noop("Controls whether Gather and Gather Merge also run subplans."), + gettext_noop("Should gather nodes also run subplans or just gather tuples?"), + GUC_EXPLAIN + }, + ¶llel_leader_participation, + true, + NULL, NULL, NULL + }, + + { + {"jit", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Allow JIT compilation."), + NULL, + GUC_EXPLAIN + }, + &jit_enabled, + true, + NULL, NULL, NULL + }, + + { + {"jit_debugging_support", PGC_SU_BACKEND, DEVELOPER_OPTIONS, + gettext_noop("Register JIT-compiled functions with debugger."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &jit_debugging_support, + false, + + /* + * This is not guaranteed to be available, but given it's a developer + * oriented option, it doesn't seem worth adding code checking + * availability. + */ + NULL, NULL, NULL + }, + + { + {"jit_dump_bitcode", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Write out LLVM bitcode to facilitate JIT debugging."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &jit_dump_bitcode, + false, + NULL, NULL, NULL + }, + + { + {"jit_expressions", PGC_USERSET, DEVELOPER_OPTIONS, + gettext_noop("Allow JIT compilation of expressions."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &jit_expressions, + true, + NULL, NULL, NULL + }, + + { + {"jit_profiling_support", PGC_SU_BACKEND, DEVELOPER_OPTIONS, + gettext_noop("Register JIT-compiled functions with perf profiler."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &jit_profiling_support, + false, + + /* + * This is not guaranteed to be available, but given it's a developer + * oriented option, it doesn't seem worth adding code checking + * availability. + */ + NULL, NULL, NULL + }, + + { + {"jit_tuple_deforming", PGC_USERSET, DEVELOPER_OPTIONS, + gettext_noop("Allow JIT compilation of tuple deforming."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &jit_tuple_deforming, + true, + NULL, NULL, NULL + }, + + { + {"data_sync_retry", PGC_POSTMASTER, ERROR_HANDLING_OPTIONS, + gettext_noop("Whether to continue running after a failure to sync data files."), + }, + &data_sync_retry, + false, + NULL, NULL, NULL + }, + + { + {"wal_receiver_create_temp_slot", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets whether a WAL receiver should create a temporary replication slot if no permanent slot is configured."), + }, + &wal_receiver_create_temp_slot, + false, + NULL, NULL, NULL + }, + + /* End-of-list marker */ + { + {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL + } +}; + + +static struct config_int ConfigureNamesInt[] = +{ + { + {"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING, + gettext_noop("Sets the amount of time to wait before forcing a " + "switch to the next WAL file."), + NULL, + GUC_UNIT_S + }, + &XLogArchiveTimeout, + 0, 0, INT_MAX / 2, + NULL, NULL, NULL + }, + { + {"post_auth_delay", PGC_BACKEND, DEVELOPER_OPTIONS, + gettext_noop("Sets the amount of time to wait after " + "authentication on connection startup."), + gettext_noop("This allows attaching a debugger to the process."), + GUC_NOT_IN_SAMPLE | GUC_UNIT_S + }, + &PostAuthDelay, + 0, 0, INT_MAX / 1000000, + NULL, NULL, NULL + }, + { + {"default_statistics_target", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Sets the default statistics target."), + gettext_noop("This applies to table columns that have not had a " + "column-specific target set via ALTER TABLE SET STATISTICS.") + }, + &default_statistics_target, + 100, 1, 10000, + NULL, NULL, NULL + }, + { + {"from_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Sets the FROM-list size beyond which subqueries " + "are not collapsed."), + gettext_noop("The planner will merge subqueries into upper " + "queries if the resulting FROM list would have no more than " + "this many items."), + GUC_EXPLAIN + }, + &from_collapse_limit, + 8, 1, INT_MAX, + NULL, NULL, NULL + }, + { + {"join_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Sets the FROM-list size beyond which JOIN " + "constructs are not flattened."), + gettext_noop("The planner will flatten explicit JOIN " + "constructs into lists of FROM items whenever a " + "list of no more than this many items would result."), + GUC_EXPLAIN + }, + &join_collapse_limit, + 8, 1, INT_MAX, + NULL, NULL, NULL + }, + { + {"geqo_threshold", PGC_USERSET, QUERY_TUNING_GEQO, + gettext_noop("Sets the threshold of FROM items beyond which GEQO is used."), + NULL, + GUC_EXPLAIN + }, + &geqo_threshold, + 12, 2, INT_MAX, + NULL, NULL, NULL + }, + { + {"geqo_effort", PGC_USERSET, QUERY_TUNING_GEQO, + gettext_noop("GEQO: effort is used to set the default for other GEQO parameters."), + NULL, + GUC_EXPLAIN + }, + &Geqo_effort, + DEFAULT_GEQO_EFFORT, MIN_GEQO_EFFORT, MAX_GEQO_EFFORT, + NULL, NULL, NULL + }, + { + {"geqo_pool_size", PGC_USERSET, QUERY_TUNING_GEQO, + gettext_noop("GEQO: number of individuals in the population."), + gettext_noop("Zero selects a suitable default value."), + GUC_EXPLAIN + }, + &Geqo_pool_size, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + { + {"geqo_generations", PGC_USERSET, QUERY_TUNING_GEQO, + gettext_noop("GEQO: number of iterations of the algorithm."), + gettext_noop("Zero selects a suitable default value."), + GUC_EXPLAIN + }, + &Geqo_generations, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + /* This is PGC_SUSET to prevent hiding from log_lock_waits. */ + {"deadlock_timeout", PGC_SUSET, LOCK_MANAGEMENT, + gettext_noop("Sets the time to wait on a lock before checking for deadlock."), + NULL, + GUC_UNIT_MS + }, + &DeadlockTimeout, + 1000, 1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"max_standby_archive_delay", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the maximum delay before canceling queries when a hot standby server is processing archived WAL data."), + NULL, + GUC_UNIT_MS + }, + &max_standby_archive_delay, + 30 * 1000, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"max_standby_streaming_delay", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the maximum delay before canceling queries when a hot standby server is processing streamed WAL data."), + NULL, + GUC_UNIT_MS + }, + &max_standby_streaming_delay, + 30 * 1000, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"recovery_min_apply_delay", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the minimum delay for applying changes during recovery."), + NULL, + GUC_UNIT_MS + }, + &recovery_min_apply_delay, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"wal_receiver_status_interval", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the maximum interval between WAL receiver status reports to the sending server."), + NULL, + GUC_UNIT_S + }, + &wal_receiver_status_interval, + 10, 0, INT_MAX / 1000, + NULL, NULL, NULL + }, + + { + {"wal_receiver_timeout", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the maximum wait time to receive data from the sending server."), + NULL, + GUC_UNIT_MS + }, + &wal_receiver_timeout, + 60 * 1000, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"max_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the maximum number of concurrent connections."), + NULL + }, + &MaxConnections, + 100, 1, MAX_BACKENDS, + check_maxconnections, NULL, NULL + }, + + { + /* see max_connections */ + {"superuser_reserved_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the number of connection slots reserved for superusers."), + NULL + }, + &ReservedBackends, + 3, 0, MAX_BACKENDS, + NULL, NULL, NULL + }, + + { + {"min_dynamic_shared_memory", PGC_POSTMASTER, RESOURCES_MEM, + gettext_noop("Amount of dynamic shared memory reserved at startup."), + NULL, + GUC_UNIT_MB + }, + &min_dynamic_shared_memory, + 0, 0, (int) Min((size_t) INT_MAX, SIZE_MAX / (1024 * 1024)), + NULL, NULL, NULL + }, + + /* + * We sometimes multiply the number of shared buffers by two without + * checking for overflow, so we mustn't allow more than INT_MAX / 2. + */ + { + {"shared_buffers", PGC_POSTMASTER, RESOURCES_MEM, + gettext_noop("Sets the number of shared memory buffers used by the server."), + NULL, + GUC_UNIT_BLOCKS + }, + &NBuffers, + 16384, 16, INT_MAX / 2, + NULL, NULL, NULL + }, + + { + {"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_UNIT_MB | GUC_RUNTIME_COMPUTED + }, + &shared_memory_size_mb, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"shared_memory_size_in_huge_pages", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the number of huge pages needed for the main shared memory area."), + gettext_noop("-1 indicates that the value could not be determined."), + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_RUNTIME_COMPUTED + }, + &shared_memory_size_in_huge_pages, + -1, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"temp_buffers", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum number of temporary buffers used by each session."), + NULL, + GUC_UNIT_BLOCKS | GUC_EXPLAIN + }, + &num_temp_buffers, + 1024, 100, INT_MAX / 2, + check_temp_buffers, NULL, NULL + }, + + { + {"port", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the TCP port the server listens on."), + NULL + }, + &PostPortNumber, + DEF_PGPORT, 1, 65535, + NULL, NULL, NULL + }, + + { + {"unix_socket_permissions", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the access permissions of the Unix-domain socket."), + gettext_noop("Unix-domain sockets use the usual Unix file system " + "permission set. The parameter value is expected " + "to be a numeric mode specification in the form " + "accepted by the chmod and umask system calls. " + "(To use the customary octal format the number must " + "start with a 0 (zero).)") + }, + &Unix_socket_permissions, + 0777, 0000, 0777, + NULL, NULL, show_unix_socket_permissions + }, + + { + {"log_file_mode", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the file permissions for log files."), + gettext_noop("The parameter value is expected " + "to be a numeric mode specification in the form " + "accepted by the chmod and umask system calls. " + "(To use the customary octal format the number must " + "start with a 0 (zero).)") + }, + &Log_file_mode, + 0600, 0000, 0777, + NULL, NULL, show_log_file_mode + }, + + + { + {"data_directory_mode", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the mode of the data directory."), + gettext_noop("The parameter value is a numeric mode specification " + "in the form accepted by the chmod and umask system " + "calls. (To use the customary octal format the number " + "must start with a 0 (zero).)"), + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_RUNTIME_COMPUTED + }, + &data_directory_mode, + 0700, 0000, 0777, + NULL, NULL, show_data_directory_mode + }, + + { + {"work_mem", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum memory to be used for query workspaces."), + gettext_noop("This much memory can be used by each internal " + "sort operation and hash table before switching to " + "temporary disk files."), + GUC_UNIT_KB | GUC_EXPLAIN + }, + &work_mem, + 4096, 64, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + { + {"maintenance_work_mem", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum memory to be used for maintenance operations."), + gettext_noop("This includes operations such as VACUUM and CREATE INDEX."), + GUC_UNIT_KB + }, + &maintenance_work_mem, + 65536, 1024, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + { + {"logical_decoding_work_mem", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum memory to be used for logical decoding."), + gettext_noop("This much memory can be used by each internal " + "reorder buffer before spilling to disk."), + GUC_UNIT_KB + }, + &logical_decoding_work_mem, + 65536, 64, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + /* + * We use the hopefully-safely-small value of 100kB as the compiled-in + * default for max_stack_depth. InitializeGUCOptions will increase it if + * possible, depending on the actual platform-specific stack limit. + */ + { + {"max_stack_depth", PGC_SUSET, RESOURCES_MEM, + gettext_noop("Sets the maximum stack depth, in kilobytes."), + NULL, + GUC_UNIT_KB + }, + &max_stack_depth, + 100, 100, MAX_KILOBYTES, + check_max_stack_depth, assign_max_stack_depth, NULL + }, + + { + {"temp_file_limit", PGC_SUSET, RESOURCES_DISK, + gettext_noop("Limits the total size of all temporary files used by each process."), + gettext_noop("-1 means no limit."), + GUC_UNIT_KB + }, + &temp_file_limit, + -1, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"vacuum_cost_page_hit", PGC_USERSET, RESOURCES_VACUUM_DELAY, + gettext_noop("Vacuum cost for a page found in the buffer cache."), + NULL + }, + &VacuumCostPageHit, + 1, 0, 10000, + NULL, NULL, NULL + }, + + { + {"vacuum_cost_page_miss", PGC_USERSET, RESOURCES_VACUUM_DELAY, + gettext_noop("Vacuum cost for a page not found in the buffer cache."), + NULL + }, + &VacuumCostPageMiss, + 2, 0, 10000, + NULL, NULL, NULL + }, + + { + {"vacuum_cost_page_dirty", PGC_USERSET, RESOURCES_VACUUM_DELAY, + gettext_noop("Vacuum cost for a page dirtied by vacuum."), + NULL + }, + &VacuumCostPageDirty, + 20, 0, 10000, + NULL, NULL, NULL + }, + + { + {"vacuum_cost_limit", PGC_USERSET, RESOURCES_VACUUM_DELAY, + gettext_noop("Vacuum cost amount available before napping."), + NULL + }, + &VacuumCostLimit, + 200, 1, 10000, + NULL, NULL, NULL + }, + + { + {"autovacuum_vacuum_cost_limit", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Vacuum cost amount available before napping, for autovacuum."), + NULL + }, + &autovacuum_vac_cost_limit, + -1, -1, 10000, + NULL, NULL, NULL + }, + + { + {"max_files_per_process", PGC_POSTMASTER, RESOURCES_KERNEL, + gettext_noop("Sets the maximum number of simultaneously open files for each server process."), + NULL + }, + &max_files_per_process, + 1000, 64, INT_MAX, + NULL, NULL, NULL + }, + + /* + * See also CheckRequiredParameterValues() if this parameter changes + */ + { + {"max_prepared_transactions", PGC_POSTMASTER, RESOURCES_MEM, + gettext_noop("Sets the maximum number of simultaneously prepared transactions."), + NULL + }, + &max_prepared_xacts, + 0, 0, MAX_BACKENDS, + NULL, NULL, NULL + }, + +#ifdef LOCK_DEBUG + { + {"trace_lock_oidmin", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Sets the minimum OID of tables for tracking locks."), + gettext_noop("Is used to avoid output on system tables."), + GUC_NOT_IN_SAMPLE + }, + &Trace_lock_oidmin, + FirstNormalObjectId, 0, INT_MAX, + NULL, NULL, NULL + }, + { + {"trace_lock_table", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Sets the OID of the table with unconditionally lock tracing."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &Trace_lock_table, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, +#endif + + { + {"statement_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the maximum allowed duration of any statement."), + gettext_noop("A value of 0 turns off the timeout."), + GUC_UNIT_MS + }, + &StatementTimeout, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"lock_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the maximum allowed duration of any wait for a lock."), + gettext_noop("A value of 0 turns off the timeout."), + GUC_UNIT_MS + }, + &LockTimeout, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"idle_in_transaction_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the maximum allowed idle time between queries, when in a transaction."), + gettext_noop("A value of 0 turns off the timeout."), + GUC_UNIT_MS + }, + &IdleInTransactionSessionTimeout, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"idle_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the maximum allowed idle time between queries, when not in a transaction."), + gettext_noop("A value of 0 turns off the timeout."), + GUC_UNIT_MS + }, + &IdleSessionTimeout, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Minimum age at which VACUUM should freeze a table row."), + NULL + }, + &vacuum_freeze_min_age, + 50000000, 0, 1000000000, + NULL, NULL, NULL + }, + + { + {"vacuum_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Age at which VACUUM should scan whole table to freeze tuples."), + NULL + }, + &vacuum_freeze_table_age, + 150000000, 0, 2000000000, + NULL, NULL, NULL + }, + + { + {"vacuum_multixact_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Minimum age at which VACUUM should freeze a MultiXactId in a table row."), + NULL + }, + &vacuum_multixact_freeze_min_age, + 5000000, 0, 1000000000, + NULL, NULL, NULL + }, + + { + {"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."), + NULL + }, + &vacuum_multixact_freeze_table_age, + 150000000, 0, 2000000000, + NULL, NULL, NULL + }, + + { + {"vacuum_defer_cleanup_age", PGC_SIGHUP, REPLICATION_PRIMARY, + gettext_noop("Number of transactions by which VACUUM and HOT cleanup should be deferred, if any."), + NULL + }, + &vacuum_defer_cleanup_age, + 0, 0, 1000000, /* see ComputeXidHorizons */ + NULL, NULL, NULL + }, + { + {"vacuum_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Age at which VACUUM should trigger failsafe to avoid a wraparound outage."), + NULL + }, + &vacuum_failsafe_age, + 1600000000, 0, 2100000000, + NULL, NULL, NULL + }, + { + {"vacuum_multixact_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Multixact age at which VACUUM should trigger failsafe to avoid a wraparound outage."), + NULL + }, + &vacuum_multixact_failsafe_age, + 1600000000, 0, 2100000000, + NULL, NULL, NULL + }, + + /* + * See also CheckRequiredParameterValues() if this parameter changes + */ + { + {"max_locks_per_transaction", PGC_POSTMASTER, LOCK_MANAGEMENT, + gettext_noop("Sets the maximum number of locks per transaction."), + gettext_noop("The shared lock table is sized on the assumption that " + "at most max_locks_per_transaction * max_connections distinct " + "objects will need to be locked at any one time.") + }, + &max_locks_per_xact, + 64, 10, INT_MAX, + NULL, NULL, NULL + }, + + { + {"max_pred_locks_per_transaction", PGC_POSTMASTER, LOCK_MANAGEMENT, + gettext_noop("Sets the maximum number of predicate locks per transaction."), + gettext_noop("The shared predicate lock table is sized on the assumption that " + "at most max_pred_locks_per_transaction * max_connections distinct " + "objects will need to be locked at any one time.") + }, + &max_predicate_locks_per_xact, + 64, 10, INT_MAX, + NULL, NULL, NULL + }, + + { + {"max_pred_locks_per_relation", PGC_SIGHUP, LOCK_MANAGEMENT, + gettext_noop("Sets the maximum number of predicate-locked pages and tuples per relation."), + gettext_noop("If more than this total of pages and tuples in the same relation are locked " + "by a connection, those locks are replaced by a relation-level lock.") + }, + &max_predicate_locks_per_relation, + -2, INT_MIN, INT_MAX, + NULL, NULL, NULL + }, + + { + {"max_pred_locks_per_page", PGC_SIGHUP, LOCK_MANAGEMENT, + gettext_noop("Sets the maximum number of predicate-locked tuples per page."), + gettext_noop("If more than this number of tuples on the same page are locked " + "by a connection, those locks are replaced by a page-level lock.") + }, + &max_predicate_locks_per_page, + 2, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"authentication_timeout", PGC_SIGHUP, CONN_AUTH_AUTH, + gettext_noop("Sets the maximum allowed time to complete client authentication."), + NULL, + GUC_UNIT_S + }, + &AuthenticationTimeout, + 60, 1, 600, + NULL, NULL, NULL + }, + + { + /* Not for general use */ + {"pre_auth_delay", PGC_SIGHUP, DEVELOPER_OPTIONS, + gettext_noop("Sets the amount of time to wait before " + "authentication on connection startup."), + gettext_noop("This allows attaching a debugger to the process."), + GUC_NOT_IN_SAMPLE | GUC_UNIT_S + }, + &PreAuthDelay, + 0, 0, 60, + NULL, NULL, NULL + }, + + { + {"wal_decode_buffer_size", PGC_POSTMASTER, WAL_RECOVERY, + gettext_noop("Buffer size for reading ahead in the WAL during recovery."), + gettext_noop("Maximum distance to read ahead in the WAL to prefetch referenced data blocks."), + GUC_UNIT_BYTE + }, + &wal_decode_buffer_size, + 512 * 1024, 64 * 1024, MaxAllocSize, + NULL, NULL, NULL + }, + + { + {"wal_keep_size", PGC_SIGHUP, REPLICATION_SENDING, + gettext_noop("Sets the size of WAL files held for standby servers."), + NULL, + GUC_UNIT_MB + }, + &wal_keep_size_mb, + 0, 0, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + { + {"min_wal_size", PGC_SIGHUP, WAL_CHECKPOINTS, + gettext_noop("Sets the minimum size to shrink the WAL to."), + NULL, + GUC_UNIT_MB + }, + &min_wal_size_mb, + DEFAULT_MIN_WAL_SEGS * (DEFAULT_XLOG_SEG_SIZE / (1024 * 1024)), + 2, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + { + {"max_wal_size", PGC_SIGHUP, WAL_CHECKPOINTS, + gettext_noop("Sets the WAL size that triggers a checkpoint."), + NULL, + GUC_UNIT_MB + }, + &max_wal_size_mb, + DEFAULT_MAX_WAL_SEGS * (DEFAULT_XLOG_SEG_SIZE / (1024 * 1024)), + 2, MAX_KILOBYTES, + NULL, assign_max_wal_size, NULL + }, + + { + {"checkpoint_timeout", PGC_SIGHUP, WAL_CHECKPOINTS, + gettext_noop("Sets the maximum time between automatic WAL checkpoints."), + NULL, + GUC_UNIT_S + }, + &CheckPointTimeout, + 300, 30, 86400, + NULL, NULL, NULL + }, + + { + {"checkpoint_warning", PGC_SIGHUP, WAL_CHECKPOINTS, + gettext_noop("Sets the maximum time before warning if checkpoints " + "triggered by WAL volume happen too frequently."), + gettext_noop("Write a message to the server log if checkpoints " + "caused by the filling of WAL segment files happen more " + "frequently than this amount of time. " + "Zero turns off the warning."), + GUC_UNIT_S + }, + &CheckPointWarning, + 30, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"checkpoint_flush_after", PGC_SIGHUP, WAL_CHECKPOINTS, + gettext_noop("Number of pages after which previously performed writes are flushed to disk."), + NULL, + GUC_UNIT_BLOCKS + }, + &checkpoint_flush_after, + DEFAULT_CHECKPOINT_FLUSH_AFTER, 0, WRITEBACK_MAX_PENDING_FLUSHES, + NULL, NULL, NULL + }, + + { + {"wal_buffers", PGC_POSTMASTER, WAL_SETTINGS, + gettext_noop("Sets the number of disk-page buffers in shared memory for WAL."), + NULL, + GUC_UNIT_XBLOCKS + }, + &XLOGbuffers, + -1, -1, (INT_MAX / XLOG_BLCKSZ), + check_wal_buffers, NULL, NULL + }, + + { + {"wal_writer_delay", PGC_SIGHUP, WAL_SETTINGS, + gettext_noop("Time between WAL flushes performed in the WAL writer."), + NULL, + GUC_UNIT_MS + }, + &WalWriterDelay, + 200, 1, 10000, + NULL, NULL, NULL + }, + + { + {"wal_writer_flush_after", PGC_SIGHUP, WAL_SETTINGS, + gettext_noop("Amount of WAL written out by WAL writer that triggers a flush."), + NULL, + GUC_UNIT_XBLOCKS + }, + &WalWriterFlushAfter, + (1024 * 1024) / XLOG_BLCKSZ, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"wal_skip_threshold", PGC_USERSET, WAL_SETTINGS, + gettext_noop("Minimum size of new file to fsync instead of writing WAL."), + NULL, + GUC_UNIT_KB + }, + &wal_skip_threshold, + 2048, 0, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + { + {"max_wal_senders", PGC_POSTMASTER, REPLICATION_SENDING, + gettext_noop("Sets the maximum number of simultaneously running WAL sender processes."), + NULL + }, + &max_wal_senders, + 10, 0, MAX_BACKENDS, + check_max_wal_senders, NULL, NULL + }, + + { + /* see max_wal_senders */ + {"max_replication_slots", PGC_POSTMASTER, REPLICATION_SENDING, + gettext_noop("Sets the maximum number of simultaneously defined replication slots."), + NULL + }, + &max_replication_slots, + 10, 0, MAX_BACKENDS /* XXX? */ , + NULL, NULL, NULL + }, + + { + {"max_slot_wal_keep_size", PGC_SIGHUP, REPLICATION_SENDING, + gettext_noop("Sets the maximum WAL size that can be reserved by replication slots."), + gettext_noop("Replication slots will be marked as failed, and segments released " + "for deletion or recycling, if this much space is occupied by WAL " + "on disk."), + GUC_UNIT_MB + }, + &max_slot_wal_keep_size_mb, + -1, -1, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + { + {"wal_sender_timeout", PGC_USERSET, REPLICATION_SENDING, + gettext_noop("Sets the maximum time to wait for WAL replication."), + NULL, + GUC_UNIT_MS + }, + &wal_sender_timeout, + 60 * 1000, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"commit_delay", PGC_SUSET, WAL_SETTINGS, + gettext_noop("Sets the delay in microseconds between transaction commit and " + "flushing WAL to disk."), + NULL + /* we have no microseconds designation, so can't supply units here */ + }, + &CommitDelay, + 0, 0, 100000, + NULL, NULL, NULL + }, + + { + {"commit_siblings", PGC_USERSET, WAL_SETTINGS, + gettext_noop("Sets the minimum number of concurrent open transactions " + "required before performing commit_delay."), + NULL + }, + &CommitSiblings, + 5, 0, 1000, + NULL, NULL, NULL + }, + + { + {"extra_float_digits", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the number of digits displayed for floating-point values."), + gettext_noop("This affects real, double precision, and geometric data types. " + "A zero or negative parameter value is added to the standard " + "number of digits (FLT_DIG or DBL_DIG as appropriate). " + "Any value greater than zero selects precise output mode.") + }, + &extra_float_digits, + 1, -15, 3, + NULL, NULL, NULL + }, + + { + {"log_min_duration_sample", PGC_SUSET, LOGGING_WHEN, + gettext_noop("Sets the minimum execution time above which " + "a sample of statements will be logged." + " Sampling is determined by log_statement_sample_rate."), + gettext_noop("Zero logs a sample of all queries. -1 turns this feature off."), + GUC_UNIT_MS + }, + &log_min_duration_sample, + -1, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"log_min_duration_statement", PGC_SUSET, LOGGING_WHEN, + gettext_noop("Sets the minimum execution time above which " + "all statements will be logged."), + gettext_noop("Zero prints all queries. -1 turns this feature off."), + GUC_UNIT_MS + }, + &log_min_duration_statement, + -1, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"log_autovacuum_min_duration", PGC_SIGHUP, LOGGING_WHAT, + gettext_noop("Sets the minimum execution time above which " + "autovacuum actions will be logged."), + gettext_noop("Zero prints all actions. -1 turns autovacuum logging off."), + GUC_UNIT_MS + }, + &Log_autovacuum_min_duration, + 600000, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"log_parameter_max_length", PGC_SUSET, LOGGING_WHAT, + gettext_noop("Sets the maximum length in bytes of data logged for bind " + "parameter values when logging statements."), + gettext_noop("-1 to print values in full."), + GUC_UNIT_BYTE + }, + &log_parameter_max_length, + -1, -1, INT_MAX / 2, + NULL, NULL, NULL + }, + + { + {"log_parameter_max_length_on_error", PGC_USERSET, LOGGING_WHAT, + gettext_noop("Sets the maximum length in bytes of data logged for bind " + "parameter values when logging statements, on error."), + gettext_noop("-1 to print values in full."), + GUC_UNIT_BYTE + }, + &log_parameter_max_length_on_error, + 0, -1, INT_MAX / 2, + NULL, NULL, NULL + }, + + { + {"bgwriter_delay", PGC_SIGHUP, RESOURCES_BGWRITER, + gettext_noop("Background writer sleep time between rounds."), + NULL, + GUC_UNIT_MS + }, + &BgWriterDelay, + 200, 10, 10000, + NULL, NULL, NULL + }, + + { + {"bgwriter_lru_maxpages", PGC_SIGHUP, RESOURCES_BGWRITER, + gettext_noop("Background writer maximum number of LRU pages to flush per round."), + NULL + }, + &bgwriter_lru_maxpages, + 100, 0, INT_MAX / 2, /* Same upper limit as shared_buffers */ + NULL, NULL, NULL + }, + + { + {"bgwriter_flush_after", PGC_SIGHUP, RESOURCES_BGWRITER, + gettext_noop("Number of pages after which previously performed writes are flushed to disk."), + NULL, + GUC_UNIT_BLOCKS + }, + &bgwriter_flush_after, + DEFAULT_BGWRITER_FLUSH_AFTER, 0, WRITEBACK_MAX_PENDING_FLUSHES, + NULL, NULL, NULL + }, + + { + {"effective_io_concurrency", + PGC_USERSET, + RESOURCES_ASYNCHRONOUS, + gettext_noop("Number of simultaneous requests that can be handled efficiently by the disk subsystem."), + NULL, + GUC_EXPLAIN + }, + &effective_io_concurrency, +#ifdef USE_PREFETCH + 1, +#else + 0, +#endif + 0, MAX_IO_CONCURRENCY, + check_effective_io_concurrency, NULL, NULL + }, + + { + {"maintenance_io_concurrency", + PGC_USERSET, + RESOURCES_ASYNCHRONOUS, + gettext_noop("A variant of effective_io_concurrency that is used for maintenance work."), + NULL, + GUC_EXPLAIN + }, + &maintenance_io_concurrency, +#ifdef USE_PREFETCH + 10, +#else + 0, +#endif + 0, MAX_IO_CONCURRENCY, + check_maintenance_io_concurrency, assign_maintenance_io_concurrency, + NULL + }, + + { + {"backend_flush_after", PGC_USERSET, RESOURCES_ASYNCHRONOUS, + gettext_noop("Number of pages after which previously performed writes are flushed to disk."), + NULL, + GUC_UNIT_BLOCKS + }, + &backend_flush_after, + DEFAULT_BACKEND_FLUSH_AFTER, 0, WRITEBACK_MAX_PENDING_FLUSHES, + NULL, NULL, NULL + }, + + { + {"max_worker_processes", + PGC_POSTMASTER, + RESOURCES_ASYNCHRONOUS, + gettext_noop("Maximum number of concurrent worker processes."), + NULL, + }, + &max_worker_processes, + 8, 0, MAX_BACKENDS, + check_max_worker_processes, NULL, NULL + }, + + { + {"max_logical_replication_workers", + PGC_POSTMASTER, + REPLICATION_SUBSCRIBERS, + gettext_noop("Maximum number of logical replication worker processes."), + NULL, + }, + &max_logical_replication_workers, + 4, 0, MAX_BACKENDS, + NULL, NULL, NULL + }, + + { + {"max_sync_workers_per_subscription", + PGC_SIGHUP, + REPLICATION_SUBSCRIBERS, + gettext_noop("Maximum number of table synchronization workers per subscription."), + NULL, + }, + &max_sync_workers_per_subscription, + 2, 0, MAX_BACKENDS, + NULL, NULL, NULL + }, + + { + {"log_rotation_age", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the amount of time to wait before forcing " + "log file rotation."), + NULL, + GUC_UNIT_MIN + }, + &Log_RotationAge, + HOURS_PER_DAY * MINS_PER_HOUR, 0, INT_MAX / SECS_PER_MINUTE, + NULL, NULL, NULL + }, + + { + {"log_rotation_size", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the maximum size a log file can reach before " + "being rotated."), + NULL, + GUC_UNIT_KB + }, + &Log_RotationSize, + 10 * 1024, 0, INT_MAX / 1024, + NULL, NULL, NULL + }, + + { + {"max_function_args", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the maximum number of function arguments."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &max_function_args, + FUNC_MAX_ARGS, FUNC_MAX_ARGS, FUNC_MAX_ARGS, + NULL, NULL, NULL + }, + + { + {"max_index_keys", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the maximum number of index keys."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &max_index_keys, + INDEX_MAX_KEYS, INDEX_MAX_KEYS, INDEX_MAX_KEYS, + NULL, NULL, NULL + }, + + { + {"max_identifier_length", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the maximum identifier length."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &max_identifier_length, + NAMEDATALEN - 1, NAMEDATALEN - 1, NAMEDATALEN - 1, + NULL, NULL, NULL + }, + + { + {"block_size", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the size of a disk block."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &block_size, + BLCKSZ, BLCKSZ, BLCKSZ, + NULL, NULL, NULL + }, + + { + {"segment_size", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the number of pages per disk file."), + NULL, + GUC_UNIT_BLOCKS | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &segment_size, + RELSEG_SIZE, RELSEG_SIZE, RELSEG_SIZE, + NULL, NULL, NULL + }, + + { + {"wal_block_size", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the block size in the write ahead log."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &wal_block_size, + XLOG_BLCKSZ, XLOG_BLCKSZ, XLOG_BLCKSZ, + NULL, NULL, NULL + }, + + { + {"wal_retrieve_retry_interval", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the time to wait before retrying to retrieve WAL " + "after a failed attempt."), + NULL, + GUC_UNIT_MS + }, + &wal_retrieve_retry_interval, + 5000, 1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"wal_segment_size", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the size of write ahead log segments."), + NULL, + GUC_UNIT_BYTE | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_RUNTIME_COMPUTED + }, + &wal_segment_size, + DEFAULT_XLOG_SEG_SIZE, + WalSegMinSize, + WalSegMaxSize, + NULL, NULL, NULL + }, + + { + {"autovacuum_naptime", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Time to sleep between autovacuum runs."), + NULL, + GUC_UNIT_S + }, + &autovacuum_naptime, + 60, 1, INT_MAX / 1000, + NULL, NULL, NULL + }, + { + {"autovacuum_vacuum_threshold", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Minimum number of tuple updates or deletes prior to vacuum."), + NULL + }, + &autovacuum_vac_thresh, + 50, 0, INT_MAX, + NULL, NULL, NULL + }, + { + {"autovacuum_vacuum_insert_threshold", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums."), + NULL + }, + &autovacuum_vac_ins_thresh, + 1000, -1, INT_MAX, + NULL, NULL, NULL + }, + { + {"autovacuum_analyze_threshold", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Minimum number of tuple inserts, updates, or deletes prior to analyze."), + NULL + }, + &autovacuum_anl_thresh, + 50, 0, INT_MAX, + NULL, NULL, NULL + }, + { + /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */ + {"autovacuum_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM, + gettext_noop("Age at which to autovacuum a table to prevent transaction ID wraparound."), + NULL + }, + &autovacuum_freeze_max_age, + + /* see vacuum_failsafe_age if you change the upper-limit value. */ + 200000000, 100000, 2000000000, + NULL, NULL, NULL + }, + { + /* see multixact.c for why this is PGC_POSTMASTER not PGC_SIGHUP */ + {"autovacuum_multixact_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM, + gettext_noop("Multixact age at which to autovacuum a table to prevent multixact wraparound."), + NULL + }, + &autovacuum_multixact_freeze_max_age, + 400000000, 10000, 2000000000, + NULL, NULL, NULL + }, + { + /* see max_connections */ + {"autovacuum_max_workers", PGC_POSTMASTER, AUTOVACUUM, + gettext_noop("Sets the maximum number of simultaneously running autovacuum worker processes."), + NULL + }, + &autovacuum_max_workers, + 3, 1, MAX_BACKENDS, + check_autovacuum_max_workers, NULL, NULL + }, + + { + {"max_parallel_maintenance_workers", PGC_USERSET, RESOURCES_ASYNCHRONOUS, + gettext_noop("Sets the maximum number of parallel processes per maintenance operation."), + NULL + }, + &max_parallel_maintenance_workers, + 2, 0, 1024, + NULL, NULL, NULL + }, + + { + {"max_parallel_workers_per_gather", PGC_USERSET, RESOURCES_ASYNCHRONOUS, + gettext_noop("Sets the maximum number of parallel processes per executor node."), + NULL, + GUC_EXPLAIN + }, + &max_parallel_workers_per_gather, + 2, 0, MAX_PARALLEL_WORKER_LIMIT, + NULL, NULL, NULL + }, + + { + {"max_parallel_workers", PGC_USERSET, RESOURCES_ASYNCHRONOUS, + gettext_noop("Sets the maximum number of parallel workers that can be active at one time."), + NULL, + GUC_EXPLAIN + }, + &max_parallel_workers, + 8, 0, MAX_PARALLEL_WORKER_LIMIT, + NULL, NULL, NULL + }, + + { + {"autovacuum_work_mem", PGC_SIGHUP, RESOURCES_MEM, + gettext_noop("Sets the maximum memory to be used by each autovacuum worker process."), + NULL, + GUC_UNIT_KB + }, + &autovacuum_work_mem, + -1, -1, MAX_KILOBYTES, + check_autovacuum_work_mem, NULL, NULL + }, + + { + {"old_snapshot_threshold", PGC_POSTMASTER, RESOURCES_ASYNCHRONOUS, + gettext_noop("Time before a snapshot is too old to read pages changed after the snapshot was taken."), + gettext_noop("A value of -1 disables this feature."), + GUC_UNIT_MIN + }, + &old_snapshot_threshold, + -1, -1, MINS_PER_HOUR * HOURS_PER_DAY * 60, + NULL, NULL, NULL + }, + + { + {"tcp_keepalives_idle", PGC_USERSET, CONN_AUTH_SETTINGS, + gettext_noop("Time between issuing TCP keepalives."), + gettext_noop("A value of 0 uses the system default."), + GUC_UNIT_S + }, + &tcp_keepalives_idle, + 0, 0, INT_MAX, + NULL, assign_tcp_keepalives_idle, show_tcp_keepalives_idle + }, + + { + {"tcp_keepalives_interval", PGC_USERSET, CONN_AUTH_SETTINGS, + gettext_noop("Time between TCP keepalive retransmits."), + gettext_noop("A value of 0 uses the system default."), + GUC_UNIT_S + }, + &tcp_keepalives_interval, + 0, 0, INT_MAX, + NULL, assign_tcp_keepalives_interval, show_tcp_keepalives_interval + }, + + { + {"ssl_renegotiation_limit", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("SSL renegotiation is no longer supported; this can only be 0."), + NULL, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE, + }, + &ssl_renegotiation_limit, + 0, 0, 0, + NULL, NULL, NULL + }, + + { + {"tcp_keepalives_count", PGC_USERSET, CONN_AUTH_SETTINGS, + gettext_noop("Maximum number of TCP keepalive retransmits."), + gettext_noop("Number of consecutive keepalive retransmits that can be " + "lost before a connection is considered dead. A value of 0 uses the " + "system default."), + }, + &tcp_keepalives_count, + 0, 0, INT_MAX, + NULL, assign_tcp_keepalives_count, show_tcp_keepalives_count + }, + + { + {"gin_fuzzy_search_limit", PGC_USERSET, CLIENT_CONN_OTHER, + gettext_noop("Sets the maximum allowed result for exact search by GIN."), + NULL, + 0 + }, + &GinFuzzySearchLimit, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"effective_cache_size", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the planner's assumption about the total size of the data caches."), + gettext_noop("That is, the total size of the caches (kernel cache and shared buffers) used for PostgreSQL data files. " + "This is measured in disk pages, which are normally 8 kB each."), + GUC_UNIT_BLOCKS | GUC_EXPLAIN, + }, + &effective_cache_size, + DEFAULT_EFFECTIVE_CACHE_SIZE, 1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"min_parallel_table_scan_size", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the minimum amount of table data for a parallel scan."), + gettext_noop("If the planner estimates that it will read a number of table pages too small to reach this limit, a parallel scan will not be considered."), + GUC_UNIT_BLOCKS | GUC_EXPLAIN, + }, + &min_parallel_table_scan_size, + (8 * 1024 * 1024) / BLCKSZ, 0, INT_MAX / 3, + NULL, NULL, NULL + }, + + { + {"min_parallel_index_scan_size", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the minimum amount of index data for a parallel scan."), + gettext_noop("If the planner estimates that it will read a number of index pages too small to reach this limit, a parallel scan will not be considered."), + GUC_UNIT_BLOCKS | GUC_EXPLAIN, + }, + &min_parallel_index_scan_size, + (512 * 1024) / BLCKSZ, 0, INT_MAX / 3, + NULL, NULL, NULL + }, + + { + /* Can't be set in postgresql.conf */ + {"server_version_num", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the server version as an integer."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &server_version_num, + PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, + NULL, NULL, NULL + }, + + { + {"log_temp_files", PGC_SUSET, LOGGING_WHAT, + gettext_noop("Log the use of temporary files larger than this number of kilobytes."), + gettext_noop("Zero logs all files. The default is -1 (turning this feature off)."), + GUC_UNIT_KB + }, + &log_temp_files, + -1, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"track_activity_query_size", PGC_POSTMASTER, STATS_CUMULATIVE, + gettext_noop("Sets the size reserved for pg_stat_activity.query, in bytes."), + NULL, + GUC_UNIT_BYTE + }, + &pgstat_track_activity_query_size, + 1024, 100, 1048576, + NULL, NULL, NULL + }, + + { + {"gin_pending_list_limit", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the maximum size of the pending list for GIN index."), + NULL, + GUC_UNIT_KB + }, + &gin_pending_list_limit, + 4096, 64, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + { + {"tcp_user_timeout", PGC_USERSET, CONN_AUTH_SETTINGS, + gettext_noop("TCP user timeout."), + gettext_noop("A value of 0 uses the system default."), + GUC_UNIT_MS + }, + &tcp_user_timeout, + 0, 0, INT_MAX, + NULL, assign_tcp_user_timeout, show_tcp_user_timeout + }, + + { + {"huge_page_size", PGC_POSTMASTER, RESOURCES_MEM, + gettext_noop("The size of huge page that should be requested."), + NULL, + GUC_UNIT_KB + }, + &huge_page_size, + 0, 0, INT_MAX, + check_huge_page_size, NULL, NULL + }, + + { + {"debug_discard_caches", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Aggressively flush system caches for debugging purposes."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &debug_discard_caches, +#ifdef DISCARD_CACHES_ENABLED + /* Set default based on older compile-time-only cache clobber macros */ +#if defined(CLOBBER_CACHE_RECURSIVELY) + 3, +#elif defined(CLOBBER_CACHE_ALWAYS) + 1, +#else + 0, +#endif + 0, 5, +#else /* not DISCARD_CACHES_ENABLED */ + 0, 0, 0, +#endif /* not DISCARD_CACHES_ENABLED */ + NULL, NULL, NULL + }, + + { + {"client_connection_check_interval", PGC_USERSET, CONN_AUTH_SETTINGS, + gettext_noop("Sets the time interval between checks for disconnection while running queries."), + NULL, + GUC_UNIT_MS + }, + &client_connection_check_interval, + 0, 0, INT_MAX, + check_client_connection_check_interval, NULL, NULL + }, + + { + {"log_startup_progress_interval", PGC_SIGHUP, LOGGING_WHEN, + gettext_noop("Time between progress updates for " + "long-running startup operations."), + gettext_noop("0 turns this feature off."), + GUC_UNIT_MS, + }, + &log_startup_progress_interval, + 10000, 0, INT_MAX, + NULL, NULL, NULL + }, + + /* End-of-list marker */ + { + {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL + } +}; + + +static struct config_real ConfigureNamesReal[] = +{ + { + {"seq_page_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the planner's estimate of the cost of a " + "sequentially fetched disk page."), + NULL, + GUC_EXPLAIN + }, + &seq_page_cost, + DEFAULT_SEQ_PAGE_COST, 0, DBL_MAX, + NULL, NULL, NULL + }, + { + {"random_page_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the planner's estimate of the cost of a " + "nonsequentially fetched disk page."), + NULL, + GUC_EXPLAIN + }, + &random_page_cost, + DEFAULT_RANDOM_PAGE_COST, 0, DBL_MAX, + NULL, NULL, NULL + }, + { + {"cpu_tuple_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the planner's estimate of the cost of " + "processing each tuple (row)."), + NULL, + GUC_EXPLAIN + }, + &cpu_tuple_cost, + DEFAULT_CPU_TUPLE_COST, 0, DBL_MAX, + NULL, NULL, NULL + }, + { + {"cpu_index_tuple_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the planner's estimate of the cost of " + "processing each index entry during an index scan."), + NULL, + GUC_EXPLAIN + }, + &cpu_index_tuple_cost, + DEFAULT_CPU_INDEX_TUPLE_COST, 0, DBL_MAX, + NULL, NULL, NULL + }, + { + {"cpu_operator_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the planner's estimate of the cost of " + "processing each operator or function call."), + NULL, + GUC_EXPLAIN + }, + &cpu_operator_cost, + DEFAULT_CPU_OPERATOR_COST, 0, DBL_MAX, + NULL, NULL, NULL + }, + { + {"parallel_tuple_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the planner's estimate of the cost of " + "passing each tuple (row) from worker to leader backend."), + NULL, + GUC_EXPLAIN + }, + ¶llel_tuple_cost, + DEFAULT_PARALLEL_TUPLE_COST, 0, DBL_MAX, + NULL, NULL, NULL + }, + { + {"parallel_setup_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Sets the planner's estimate of the cost of " + "starting up worker processes for parallel query."), + NULL, + GUC_EXPLAIN + }, + ¶llel_setup_cost, + DEFAULT_PARALLEL_SETUP_COST, 0, DBL_MAX, + NULL, NULL, NULL + }, + + { + {"jit_above_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Perform JIT compilation if query is more expensive."), + gettext_noop("-1 disables JIT compilation."), + GUC_EXPLAIN + }, + &jit_above_cost, + 100000, -1, DBL_MAX, + NULL, NULL, NULL + }, + + { + {"jit_optimize_above_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Optimize JIT-compiled functions if query is more expensive."), + gettext_noop("-1 disables optimization."), + GUC_EXPLAIN + }, + &jit_optimize_above_cost, + 500000, -1, DBL_MAX, + NULL, NULL, NULL + }, + + { + {"jit_inline_above_cost", PGC_USERSET, QUERY_TUNING_COST, + gettext_noop("Perform JIT inlining if query is more expensive."), + gettext_noop("-1 disables inlining."), + GUC_EXPLAIN + }, + &jit_inline_above_cost, + 500000, -1, DBL_MAX, + NULL, NULL, NULL + }, + + { + {"cursor_tuple_fraction", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Sets the planner's estimate of the fraction of " + "a cursor's rows that will be retrieved."), + NULL, + GUC_EXPLAIN + }, + &cursor_tuple_fraction, + DEFAULT_CURSOR_TUPLE_FRACTION, 0.0, 1.0, + NULL, NULL, NULL + }, + + { + {"recursive_worktable_factor", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Sets the planner's estimate of the average size " + "of a recursive query's working table."), + NULL, + GUC_EXPLAIN + }, + &recursive_worktable_factor, + DEFAULT_RECURSIVE_WORKTABLE_FACTOR, 0.001, 1000000.0, + NULL, NULL, NULL + }, + + { + {"geqo_selection_bias", PGC_USERSET, QUERY_TUNING_GEQO, + gettext_noop("GEQO: selective pressure within the population."), + NULL, + GUC_EXPLAIN + }, + &Geqo_selection_bias, + DEFAULT_GEQO_SELECTION_BIAS, + MIN_GEQO_SELECTION_BIAS, MAX_GEQO_SELECTION_BIAS, + NULL, NULL, NULL + }, + { + {"geqo_seed", PGC_USERSET, QUERY_TUNING_GEQO, + gettext_noop("GEQO: seed for random path selection."), + NULL, + GUC_EXPLAIN + }, + &Geqo_seed, + 0.0, 0.0, 1.0, + NULL, NULL, NULL + }, + + { + {"hash_mem_multiplier", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Multiple of work_mem to use for hash tables."), + NULL, + GUC_EXPLAIN + }, + &hash_mem_multiplier, + 2.0, 1.0, 1000.0, + NULL, NULL, NULL + }, + + { + {"bgwriter_lru_multiplier", PGC_SIGHUP, RESOURCES_BGWRITER, + gettext_noop("Multiple of the average buffer usage to free per round."), + NULL + }, + &bgwriter_lru_multiplier, + 2.0, 0.0, 10.0, + NULL, NULL, NULL + }, + + { + {"seed", PGC_USERSET, UNGROUPED, + gettext_noop("Sets the seed for random-number generation."), + NULL, + GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &phony_random_seed, + 0.0, -1.0, 1.0, + check_random_seed, assign_random_seed, show_random_seed + }, + + { + {"vacuum_cost_delay", PGC_USERSET, RESOURCES_VACUUM_DELAY, + gettext_noop("Vacuum cost delay in milliseconds."), + NULL, + GUC_UNIT_MS + }, + &VacuumCostDelay, + 0, 0, 100, + NULL, NULL, NULL + }, + + { + {"autovacuum_vacuum_cost_delay", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Vacuum cost delay in milliseconds, for autovacuum."), + NULL, + GUC_UNIT_MS + }, + &autovacuum_vac_cost_delay, + 2, -1, 100, + NULL, NULL, NULL + }, + + { + {"autovacuum_vacuum_scale_factor", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Number of tuple updates or deletes prior to vacuum as a fraction of reltuples."), + NULL + }, + &autovacuum_vac_scale, + 0.2, 0.0, 100.0, + NULL, NULL, NULL + }, + + { + {"autovacuum_vacuum_insert_scale_factor", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Number of tuple inserts prior to vacuum as a fraction of reltuples."), + NULL + }, + &autovacuum_vac_ins_scale, + 0.2, 0.0, 100.0, + NULL, NULL, NULL + }, + + { + {"autovacuum_analyze_scale_factor", PGC_SIGHUP, AUTOVACUUM, + gettext_noop("Number of tuple inserts, updates, or deletes prior to analyze as a fraction of reltuples."), + NULL + }, + &autovacuum_anl_scale, + 0.1, 0.0, 100.0, + NULL, NULL, NULL + }, + + { + {"checkpoint_completion_target", PGC_SIGHUP, WAL_CHECKPOINTS, + gettext_noop("Time spent flushing dirty buffers during checkpoint, as fraction of checkpoint interval."), + NULL + }, + &CheckPointCompletionTarget, + 0.9, 0.0, 1.0, + NULL, assign_checkpoint_completion_target, NULL + }, + + { + {"log_statement_sample_rate", PGC_SUSET, LOGGING_WHEN, + gettext_noop("Fraction of statements exceeding log_min_duration_sample to be logged."), + gettext_noop("Use a value between 0.0 (never log) and 1.0 (always log).") + }, + &log_statement_sample_rate, + 1.0, 0.0, 1.0, + NULL, NULL, NULL + }, + + { + {"log_transaction_sample_rate", PGC_SUSET, LOGGING_WHEN, + gettext_noop("Sets the fraction of transactions from which to log all statements."), + gettext_noop("Use a value between 0.0 (never log) and 1.0 (log all " + "statements for all transactions).") + }, + &log_xact_sample_rate, + 0.0, 0.0, 1.0, + NULL, NULL, NULL + }, + + /* End-of-list marker */ + { + {NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL, NULL + } +}; + + +static struct config_string ConfigureNamesString[] = +{ + { + {"archive_command", PGC_SIGHUP, WAL_ARCHIVING, + gettext_noop("Sets the shell command that will be called to archive a WAL file."), + gettext_noop("This is used only if \"archive_library\" is not set.") + }, + &XLogArchiveCommand, + "", + NULL, NULL, show_archive_command + }, + + { + {"archive_library", PGC_SIGHUP, WAL_ARCHIVING, + gettext_noop("Sets the library that will be called to archive a WAL file."), + gettext_noop("An empty string indicates that \"archive_command\" should be used.") + }, + &XLogArchiveLibrary, + "", + NULL, NULL, NULL + }, + + { + {"restore_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the shell command that will be called to retrieve an archived WAL file."), + NULL + }, + &recoveryRestoreCommand, + "", + NULL, NULL, NULL + }, + + { + {"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the shell command that will be executed at every restart point."), + NULL + }, + &archiveCleanupCommand, + "", + NULL, NULL, NULL + }, + + { + {"recovery_end_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the shell command that will be executed once at the end of recovery."), + NULL + }, + &recoveryEndCommand, + "", + NULL, NULL, NULL + }, + + { + {"recovery_target_timeline", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Specifies the timeline to recover into."), + NULL + }, + &recovery_target_timeline_string, + "latest", + check_recovery_target_timeline, assign_recovery_target_timeline, NULL + }, + + { + {"recovery_target", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Set to \"immediate\" to end recovery as soon as a consistent state is reached."), + NULL + }, + &recovery_target_string, + "", + check_recovery_target, assign_recovery_target, NULL + }, + { + {"recovery_target_xid", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the transaction ID up to which recovery will proceed."), + NULL + }, + &recovery_target_xid_string, + "", + check_recovery_target_xid, assign_recovery_target_xid, NULL + }, + { + {"recovery_target_time", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the time stamp up to which recovery will proceed."), + NULL + }, + &recovery_target_time_string, + "", + check_recovery_target_time, assign_recovery_target_time, NULL + }, + { + {"recovery_target_name", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the named restore point up to which recovery will proceed."), + NULL + }, + &recovery_target_name_string, + "", + check_recovery_target_name, assign_recovery_target_name, NULL + }, + { + {"recovery_target_lsn", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the LSN of the write-ahead log location up to which recovery will proceed."), + NULL + }, + &recovery_target_lsn_string, + "", + check_recovery_target_lsn, assign_recovery_target_lsn, NULL + }, + + { + {"promote_trigger_file", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Specifies a file name whose presence ends recovery in the standby."), + NULL + }, + &PromoteTriggerFile, + "", + NULL, NULL, NULL + }, + + { + {"primary_conninfo", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the connection string to be used to connect to the sending server."), + NULL, + GUC_SUPERUSER_ONLY + }, + &PrimaryConnInfo, + "", + NULL, NULL, NULL + }, + + { + {"primary_slot_name", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the name of the replication slot to use on the sending server."), + NULL + }, + &PrimarySlotName, + "", + check_primary_slot_name, NULL, NULL + }, + + { + {"client_encoding", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the client's character set encoding."), + NULL, + GUC_IS_NAME | GUC_REPORT + }, + &client_encoding_string, + "SQL_ASCII", + check_client_encoding, assign_client_encoding, NULL + }, + + { + {"log_line_prefix", PGC_SIGHUP, LOGGING_WHAT, + gettext_noop("Controls information prefixed to each log line."), + gettext_noop("If blank, no prefix is used.") + }, + &Log_line_prefix, + "%m [%p] ", + NULL, NULL, NULL + }, + + { + {"log_timezone", PGC_SIGHUP, LOGGING_WHAT, + gettext_noop("Sets the time zone to use in log messages."), + NULL + }, + &log_timezone_string, + "GMT", + check_log_timezone, assign_log_timezone, show_log_timezone + }, + + { + {"DateStyle", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the display format for date and time values."), + gettext_noop("Also controls interpretation of ambiguous " + "date inputs."), + GUC_LIST_INPUT | GUC_REPORT + }, + &datestyle_string, + "ISO, MDY", + check_datestyle, assign_datestyle, NULL + }, + + { + {"default_table_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default table access method for new tables."), + NULL, + GUC_IS_NAME + }, + &default_table_access_method, + DEFAULT_TABLE_ACCESS_METHOD, + check_default_table_access_method, NULL, NULL + }, + + { + {"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default tablespace to create tables and indexes in."), + gettext_noop("An empty string selects the database's default tablespace."), + GUC_IS_NAME + }, + &default_tablespace, + "", + check_default_tablespace, NULL, NULL + }, + + { + {"temp_tablespaces", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the tablespace(s) to use for temporary tables and sort files."), + NULL, + GUC_LIST_INPUT | GUC_LIST_QUOTE + }, + &temp_tablespaces, + "", + check_temp_tablespaces, assign_temp_tablespaces, NULL + }, + + { + {"dynamic_library_path", PGC_SUSET, CLIENT_CONN_OTHER, + gettext_noop("Sets the path for dynamically loadable modules."), + gettext_noop("If a dynamically loadable module needs to be opened and " + "the specified name does not have a directory component (i.e., the " + "name does not contain a slash), the system will search this path for " + "the specified file."), + GUC_SUPERUSER_ONLY + }, + &Dynamic_library_path, + "$libdir", + NULL, NULL, NULL + }, + + { + {"krb_server_keyfile", PGC_SIGHUP, CONN_AUTH_AUTH, + gettext_noop("Sets the location of the Kerberos server key file."), + NULL, + GUC_SUPERUSER_ONLY + }, + &pg_krb_server_keyfile, + PG_KRB_SRVTAB, + NULL, NULL, NULL + }, + + { + {"bonjour_name", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the Bonjour service name."), + NULL + }, + &bonjour_name, + "", + NULL, NULL, NULL + }, + + /* See main.c about why defaults for LC_foo are not all alike */ + + { + {"lc_collate", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the collation order locale."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &locale_collate, + "C", + NULL, NULL, NULL + }, + + { + {"lc_ctype", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the character classification and case conversion locale."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &locale_ctype, + "C", + NULL, NULL, NULL + }, + + { + {"lc_messages", PGC_SUSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the language in which messages are displayed."), + NULL + }, + &locale_messages, + "", + check_locale_messages, assign_locale_messages, NULL + }, + + { + {"lc_monetary", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the locale for formatting monetary amounts."), + NULL + }, + &locale_monetary, + "C", + check_locale_monetary, assign_locale_monetary, NULL + }, + + { + {"lc_numeric", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the locale for formatting numbers."), + NULL + }, + &locale_numeric, + "C", + check_locale_numeric, assign_locale_numeric, NULL + }, + + { + {"lc_time", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the locale for formatting date and time values."), + NULL + }, + &locale_time, + "C", + check_locale_time, assign_locale_time, NULL + }, + + { + {"session_preload_libraries", PGC_SUSET, CLIENT_CONN_PRELOAD, + gettext_noop("Lists shared libraries to preload into each backend."), + NULL, + GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_SUPERUSER_ONLY + }, + &session_preload_libraries_string, + "", + NULL, NULL, NULL + }, + + { + {"shared_preload_libraries", PGC_POSTMASTER, CLIENT_CONN_PRELOAD, + gettext_noop("Lists shared libraries to preload into server."), + NULL, + GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_SUPERUSER_ONLY + }, + &shared_preload_libraries_string, + "", + NULL, NULL, NULL + }, + + { + {"local_preload_libraries", PGC_USERSET, CLIENT_CONN_PRELOAD, + gettext_noop("Lists unprivileged shared libraries to preload into each backend."), + NULL, + GUC_LIST_INPUT | GUC_LIST_QUOTE + }, + &local_preload_libraries_string, + "", + NULL, NULL, NULL + }, + + { + {"search_path", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the schema search order for names that are not schema-qualified."), + NULL, + GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_EXPLAIN + }, + &namespace_search_path, + "\"$user\", public", + check_search_path, assign_search_path, NULL + }, + + { + /* Can't be set in postgresql.conf */ + {"server_encoding", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the server (database) character set encoding."), + NULL, + GUC_IS_NAME | GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &server_encoding_string, + "SQL_ASCII", + NULL, NULL, NULL + }, + + { + /* Can't be set in postgresql.conf */ + {"server_version", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the server version."), + NULL, + GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &server_version_string, + PG_VERSION, + NULL, NULL, NULL + }, + + { + /* Not for general use --- used by SET ROLE */ + {"role", PGC_USERSET, UNGROUPED, + gettext_noop("Sets the current role."), + NULL, + GUC_IS_NAME | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_REST + }, + &role_string, + "none", + check_role, assign_role, show_role + }, + + { + /* Not for general use --- used by SET SESSION AUTHORIZATION */ + {"session_authorization", PGC_USERSET, UNGROUPED, + gettext_noop("Sets the session user name."), + NULL, + GUC_IS_NAME | GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_REST + }, + &session_authorization_string, + NULL, + check_session_authorization, assign_session_authorization, NULL + }, + + { + {"log_destination", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the destination for server log output."), + gettext_noop("Valid values are combinations of \"stderr\", " + "\"syslog\", \"csvlog\", \"jsonlog\", and \"eventlog\", " + "depending on the platform."), + GUC_LIST_INPUT + }, + &Log_destination_string, + "stderr", + check_log_destination, assign_log_destination, NULL + }, + { + {"log_directory", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the destination directory for log files."), + gettext_noop("Can be specified as relative to the data directory " + "or as absolute path."), + GUC_SUPERUSER_ONLY + }, + &Log_directory, + "log", + check_canonical_path, NULL, NULL + }, + { + {"log_filename", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the file name pattern for log files."), + NULL, + GUC_SUPERUSER_ONLY + }, + &Log_filename, + "postgresql-%Y-%m-%d_%H%M%S.log", + NULL, NULL, NULL + }, + + { + {"syslog_ident", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the program name used to identify PostgreSQL " + "messages in syslog."), + NULL + }, + &syslog_ident_str, + "postgres", + NULL, assign_syslog_ident, NULL + }, + + { + {"event_source", PGC_POSTMASTER, LOGGING_WHERE, + gettext_noop("Sets the application name used to identify " + "PostgreSQL messages in the event log."), + NULL + }, + &event_source, + DEFAULT_EVENT_SOURCE, + NULL, NULL, NULL + }, + + { + {"TimeZone", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the time zone for displaying and interpreting time stamps."), + NULL, + GUC_REPORT + }, + &timezone_string, + "GMT", + check_timezone, assign_timezone, show_timezone + }, + { + {"timezone_abbreviations", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Selects a file of time zone abbreviations."), + NULL + }, + &timezone_abbreviations_string, + NULL, + check_timezone_abbreviations, assign_timezone_abbreviations, NULL + }, + + { + {"unix_socket_group", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the owning group of the Unix-domain socket."), + gettext_noop("The owning user of the socket is always the user " + "that starts the server.") + }, + &Unix_socket_group, + "", + NULL, NULL, NULL + }, + + { + {"unix_socket_directories", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the directories where Unix-domain sockets will be created."), + NULL, + GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_SUPERUSER_ONLY + }, + &Unix_socket_directories, +#ifdef HAVE_UNIX_SOCKETS + DEFAULT_PGSOCKET_DIR, +#else + "", +#endif + NULL, NULL, NULL + }, + + { + {"listen_addresses", PGC_POSTMASTER, CONN_AUTH_SETTINGS, + gettext_noop("Sets the host name or IP address(es) to listen to."), + NULL, + GUC_LIST_INPUT + }, + &ListenAddresses, + "localhost", + NULL, NULL, NULL + }, + + { + /* + * Can't be set by ALTER SYSTEM as it can lead to recursive definition + * of data_directory. + */ + {"data_directory", PGC_POSTMASTER, FILE_LOCATIONS, + gettext_noop("Sets the server's data directory."), + NULL, + GUC_SUPERUSER_ONLY | GUC_DISALLOW_IN_AUTO_FILE + }, + &data_directory, + NULL, + NULL, NULL, NULL + }, + + { + {"config_file", PGC_POSTMASTER, FILE_LOCATIONS, + gettext_noop("Sets the server's main configuration file."), + NULL, + GUC_DISALLOW_IN_FILE | GUC_SUPERUSER_ONLY + }, + &ConfigFileName, + NULL, + NULL, NULL, NULL + }, + + { + {"hba_file", PGC_POSTMASTER, FILE_LOCATIONS, + gettext_noop("Sets the server's \"hba\" configuration file."), + NULL, + GUC_SUPERUSER_ONLY + }, + &HbaFileName, + NULL, + NULL, NULL, NULL + }, + + { + {"ident_file", PGC_POSTMASTER, FILE_LOCATIONS, + gettext_noop("Sets the server's \"ident\" configuration file."), + NULL, + GUC_SUPERUSER_ONLY + }, + &IdentFileName, + NULL, + NULL, NULL, NULL + }, + + { + {"external_pid_file", PGC_POSTMASTER, FILE_LOCATIONS, + gettext_noop("Writes the postmaster PID to the specified file."), + NULL, + GUC_SUPERUSER_ONLY + }, + &external_pid_file, + NULL, + check_canonical_path, NULL, NULL + }, + + { + {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the name of the SSL library."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &ssl_library, +#ifdef USE_SSL + "OpenSSL", +#else + "", +#endif + NULL, NULL, NULL + }, + + { + {"ssl_cert_file", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the SSL server certificate file."), + NULL + }, + &ssl_cert_file, + "server.crt", + NULL, NULL, NULL + }, + + { + {"ssl_key_file", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the SSL server private key file."), + NULL + }, + &ssl_key_file, + "server.key", + NULL, NULL, NULL + }, + + { + {"ssl_ca_file", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the SSL certificate authority file."), + NULL + }, + &ssl_ca_file, + "", + NULL, NULL, NULL + }, + + { + {"ssl_crl_file", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the SSL certificate revocation list file."), + NULL + }, + &ssl_crl_file, + "", + NULL, NULL, NULL + }, + + { + {"ssl_crl_dir", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the SSL certificate revocation list directory."), + NULL + }, + &ssl_crl_dir, + "", + NULL, NULL, NULL + }, + + { + {"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY, + gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."), + NULL, + GUC_LIST_INPUT + }, + &SyncRepStandbyNames, + "", + check_synchronous_standby_names, assign_synchronous_standby_names, NULL + }, + + { + {"default_text_search_config", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets default text search configuration."), + NULL + }, + &TSCurrentConfig, + "pg_catalog.simple", + check_TSCurrentConfig, assign_TSCurrentConfig, NULL + }, + + { + {"ssl_ciphers", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Sets the list of allowed SSL ciphers."), + NULL, + GUC_SUPERUSER_ONLY + }, + &SSLCipherSuites, +#ifdef USE_OPENSSL + "HIGH:MEDIUM:+3DES:!aNULL", +#else + "none", +#endif + NULL, NULL, NULL + }, + + { + {"ssl_ecdh_curve", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Sets the curve to use for ECDH."), + NULL, + GUC_SUPERUSER_ONLY + }, + &SSLECDHCurve, +#ifdef USE_SSL + "prime256v1", +#else + "none", +#endif + NULL, NULL, NULL + }, + + { + {"ssl_dh_params_file", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the SSL DH parameters file."), + NULL, + GUC_SUPERUSER_ONLY + }, + &ssl_dh_params_file, + "", + NULL, NULL, NULL + }, + + { + {"ssl_passphrase_command", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Command to obtain passphrases for SSL."), + NULL, + GUC_SUPERUSER_ONLY + }, + &ssl_passphrase_command, + "", + NULL, NULL, NULL + }, + + { + {"application_name", PGC_USERSET, LOGGING_WHAT, + gettext_noop("Sets the application name to be reported in statistics and logs."), + NULL, + GUC_IS_NAME | GUC_REPORT | GUC_NOT_IN_SAMPLE + }, + &application_name, + "", + check_application_name, assign_application_name, NULL + }, + + { + {"cluster_name", PGC_POSTMASTER, PROCESS_TITLE, + gettext_noop("Sets the name of the cluster, which is included in the process title."), + NULL, + GUC_IS_NAME + }, + &cluster_name, + "", + check_cluster_name, NULL, NULL + }, + + { + {"wal_consistency_checking", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Sets the WAL resource managers for which WAL consistency checks are done."), + gettext_noop("Full-page images will be logged for all data blocks and cross-checked against the results of WAL replay."), + GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE + }, + &wal_consistency_checking_string, + "", + check_wal_consistency_checking, assign_wal_consistency_checking, NULL + }, + + { + {"jit_provider", PGC_POSTMASTER, CLIENT_CONN_PRELOAD, + gettext_noop("JIT provider to use."), + NULL, + GUC_SUPERUSER_ONLY + }, + &jit_provider, + "llvmjit", + NULL, NULL, NULL + }, + + { + {"backtrace_functions", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Log backtrace for errors in these functions."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &backtrace_functions, + "", + check_backtrace_functions, assign_backtrace_functions, NULL + }, + + /* End-of-list marker */ + { + {NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL + } +}; + + +static struct config_enum ConfigureNamesEnum[] = +{ + { + {"backslash_quote", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, + gettext_noop("Sets whether \"\\'\" is allowed in string literals."), + NULL + }, + &backslash_quote, + BACKSLASH_QUOTE_SAFE_ENCODING, backslash_quote_options, + NULL, NULL, NULL + }, + + { + {"bytea_output", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the output format for bytea."), + NULL + }, + &bytea_output, + BYTEA_OUTPUT_HEX, bytea_output_options, + NULL, NULL, NULL + }, + + { + {"client_min_messages", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the message levels that are sent to the client."), + gettext_noop("Each level includes all the levels that follow it. The later" + " the level, the fewer messages are sent.") + }, + &client_min_messages, + NOTICE, client_message_level_options, + NULL, NULL, NULL + }, + + { + {"compute_query_id", PGC_SUSET, STATS_MONITORING, + gettext_noop("Enables in-core computation of query identifiers."), + NULL + }, + &compute_query_id, + COMPUTE_QUERY_ID_AUTO, compute_query_id_options, + NULL, NULL, NULL + }, + + { + {"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Enables the planner to use constraints to optimize queries."), + gettext_noop("Table scans will be skipped if their constraints" + " guarantee that no rows match the query."), + GUC_EXPLAIN + }, + &constraint_exclusion, + CONSTRAINT_EXCLUSION_PARTITION, constraint_exclusion_options, + NULL, NULL, NULL + }, + + { + {"default_toast_compression", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default compression method for compressible values."), + NULL + }, + &default_toast_compression, + TOAST_PGLZ_COMPRESSION, + default_toast_compression_options, + NULL, NULL, NULL + }, + + { + {"default_transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the transaction isolation level of each new transaction."), + NULL + }, + &DefaultXactIsoLevel, + XACT_READ_COMMITTED, isolation_level_options, + NULL, NULL, NULL + }, + + { + {"transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the current transaction's isolation level."), + NULL, + GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &XactIsoLevel, + XACT_READ_COMMITTED, isolation_level_options, + check_XactIsoLevel, NULL, NULL + }, + + { + {"IntervalStyle", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the display format for interval values."), + NULL, + GUC_REPORT + }, + &IntervalStyle, + INTSTYLE_POSTGRES, intervalstyle_options, + NULL, NULL, NULL + }, + + { + {"log_error_verbosity", PGC_SUSET, LOGGING_WHAT, + gettext_noop("Sets the verbosity of logged messages."), + NULL + }, + &Log_error_verbosity, + PGERROR_DEFAULT, log_error_verbosity_options, + NULL, NULL, NULL + }, + + { + {"log_min_messages", PGC_SUSET, LOGGING_WHEN, + gettext_noop("Sets the message levels that are logged."), + gettext_noop("Each level includes all the levels that follow it. The later" + " the level, the fewer messages are sent.") + }, + &log_min_messages, + WARNING, server_message_level_options, + NULL, NULL, NULL + }, + + { + {"log_min_error_statement", PGC_SUSET, LOGGING_WHEN, + gettext_noop("Causes all statements generating error at or above this level to be logged."), + gettext_noop("Each level includes all the levels that follow it. The later" + " the level, the fewer messages are sent.") + }, + &log_min_error_statement, + ERROR, server_message_level_options, + NULL, NULL, NULL + }, + + { + {"log_statement", PGC_SUSET, LOGGING_WHAT, + gettext_noop("Sets the type of statements logged."), + NULL + }, + &log_statement, + LOGSTMT_NONE, log_statement_options, + NULL, NULL, NULL + }, + + { + {"syslog_facility", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the syslog \"facility\" to be used when syslog enabled."), + NULL + }, + &syslog_facility, +#ifdef HAVE_SYSLOG + LOG_LOCAL0, +#else + 0, +#endif + syslog_facility_options, + NULL, assign_syslog_facility, NULL + }, + + { + {"session_replication_role", PGC_SUSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the session's behavior for triggers and rewrite rules."), + NULL + }, + &SessionReplicationRole, + SESSION_REPLICATION_ROLE_ORIGIN, session_replication_role_options, + NULL, assign_session_replication_role, NULL + }, + + { + {"synchronous_commit", PGC_USERSET, WAL_SETTINGS, + gettext_noop("Sets the current transaction's synchronization level."), + NULL + }, + &synchronous_commit, + SYNCHRONOUS_COMMIT_ON, synchronous_commit_options, + NULL, assign_synchronous_commit, NULL + }, + + { + {"archive_mode", PGC_POSTMASTER, WAL_ARCHIVING, + gettext_noop("Allows archiving of WAL files using archive_command."), + NULL + }, + &XLogArchiveMode, + ARCHIVE_MODE_OFF, archive_mode_options, + NULL, NULL, NULL + }, + + { + {"recovery_target_action", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the action to perform upon reaching the recovery target."), + NULL + }, + &recoveryTargetAction, + RECOVERY_TARGET_ACTION_PAUSE, recovery_target_action_options, + NULL, NULL, NULL + }, + + { + {"trace_recovery_messages", PGC_SIGHUP, DEVELOPER_OPTIONS, + gettext_noop("Enables logging of recovery-related debugging information."), + gettext_noop("Each level includes all the levels that follow it. The later" + " the level, the fewer messages are sent."), + GUC_NOT_IN_SAMPLE, + }, + &trace_recovery_messages, + + /* + * client_message_level_options allows too many values, really, but + * it's not worth having a separate options array for this. + */ + LOG, client_message_level_options, + NULL, NULL, NULL + }, + + { + {"track_functions", PGC_SUSET, STATS_CUMULATIVE, + gettext_noop("Collects function-level statistics on database activity."), + NULL + }, + &pgstat_track_functions, + TRACK_FUNC_OFF, track_function_options, + NULL, NULL, NULL + }, + + + { + {"stats_fetch_consistency", PGC_USERSET, STATS_CUMULATIVE, + gettext_noop("Sets the consistency of accesses to statistics data."), + NULL + }, + &pgstat_fetch_consistency, + PGSTAT_FETCH_CONSISTENCY_CACHE, stats_fetch_consistency, + NULL, assign_stats_fetch_consistency, NULL + }, + + { + {"wal_compression", PGC_SUSET, WAL_SETTINGS, + gettext_noop("Compresses full-page writes written in WAL file with specified method."), + NULL + }, + &wal_compression, + WAL_COMPRESSION_NONE, wal_compression_options, + NULL, NULL, NULL + }, + + { + {"wal_level", PGC_POSTMASTER, WAL_SETTINGS, + gettext_noop("Sets the level of information written to the WAL."), + NULL + }, + &wal_level, + WAL_LEVEL_REPLICA, wal_level_options, + NULL, NULL, NULL + }, + + { + {"dynamic_shared_memory_type", PGC_POSTMASTER, RESOURCES_MEM, + gettext_noop("Selects the dynamic shared memory implementation used."), + NULL + }, + &dynamic_shared_memory_type, + DEFAULT_DYNAMIC_SHARED_MEMORY_TYPE, dynamic_shared_memory_options, + NULL, NULL, NULL + }, + + { + {"shared_memory_type", PGC_POSTMASTER, RESOURCES_MEM, + gettext_noop("Selects the shared memory implementation used for the main shared memory region."), + NULL + }, + &shared_memory_type, + DEFAULT_SHARED_MEMORY_TYPE, shared_memory_options, + NULL, NULL, NULL + }, + + { + {"wal_sync_method", PGC_SIGHUP, WAL_SETTINGS, + gettext_noop("Selects the method used for forcing WAL updates to disk."), + NULL + }, + &sync_method, + DEFAULT_SYNC_METHOD, sync_method_options, + NULL, assign_xlog_sync_method, NULL + }, + + { + {"xmlbinary", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets how binary values are to be encoded in XML."), + NULL + }, + &xmlbinary, + XMLBINARY_BASE64, xmlbinary_options, + NULL, NULL, NULL + }, + + { + {"xmloption", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets whether XML data in implicit parsing and serialization " + "operations is to be considered as documents or content fragments."), + NULL + }, + &xmloption, + XMLOPTION_CONTENT, xmloption_options, + NULL, NULL, NULL + }, + + { + {"huge_pages", PGC_POSTMASTER, RESOURCES_MEM, + gettext_noop("Use of huge pages on Linux or Windows."), + NULL + }, + &huge_pages, + HUGE_PAGES_TRY, huge_pages_options, + NULL, NULL, NULL + }, + + { + {"recovery_prefetch", PGC_SIGHUP, WAL_RECOVERY, + gettext_noop("Prefetch referenced blocks during recovery."), + gettext_noop("Look ahead in the WAL to find references to uncached data.") + }, + &recovery_prefetch, + RECOVERY_PREFETCH_TRY, recovery_prefetch_options, + check_recovery_prefetch, assign_recovery_prefetch, NULL + }, + + { + {"force_parallel_mode", PGC_USERSET, DEVELOPER_OPTIONS, + gettext_noop("Forces use of parallel query facilities."), + gettext_noop("If possible, run query using a parallel worker and with parallel restrictions."), + GUC_NOT_IN_SAMPLE | GUC_EXPLAIN + }, + &force_parallel_mode, + FORCE_PARALLEL_OFF, force_parallel_mode_options, + NULL, NULL, NULL + }, + + { + {"password_encryption", PGC_USERSET, CONN_AUTH_AUTH, + gettext_noop("Chooses the algorithm for encrypting passwords."), + NULL + }, + &Password_encryption, + PASSWORD_TYPE_SCRAM_SHA_256, password_encryption_options, + NULL, NULL, NULL + }, + + { + {"plan_cache_mode", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Controls the planner's selection of custom or generic plan."), + gettext_noop("Prepared statements can have custom and generic plans, and the planner " + "will attempt to choose which is better. This can be set to override " + "the default behavior."), + GUC_EXPLAIN + }, + &plan_cache_mode, + PLAN_CACHE_MODE_AUTO, plan_cache_mode_options, + NULL, NULL, NULL + }, + + { + {"ssl_min_protocol_version", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Sets the minimum SSL/TLS protocol version to use."), + NULL, + GUC_SUPERUSER_ONLY + }, + &ssl_min_protocol_version, + PG_TLS1_2_VERSION, + ssl_protocol_versions_info + 1, /* don't allow PG_TLS_ANY */ + NULL, NULL, NULL + }, + + { + {"ssl_max_protocol_version", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Sets the maximum SSL/TLS protocol version to use."), + NULL, + GUC_SUPERUSER_ONLY + }, + &ssl_max_protocol_version, + PG_TLS_ANY, + ssl_protocol_versions_info, + NULL, NULL, NULL + }, + + { + {"recovery_init_sync_method", PGC_SIGHUP, ERROR_HANDLING_OPTIONS, + gettext_noop("Sets the method for synchronizing the data directory before crash recovery."), + }, + &recovery_init_sync_method, + RECOVERY_INIT_SYNC_METHOD_FSYNC, recovery_init_sync_method_options, + NULL, NULL, NULL + }, + + /* End-of-list marker */ + { + {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL + } +}; + +/******** end of options list ********/ + + +/* + * To allow continued support of obsolete names for GUC variables, we apply + * the following mappings to any unrecognized name. Note that an old name + * should be mapped to a new one only if the new variable has very similar + * semantics to the old. + */ +static const char *const map_old_guc_names[] = { + "sort_mem", "work_mem", + "vacuum_mem", "maintenance_work_mem", + NULL +}; + + +/* + * Actual lookup of variables is done through this single, sorted array. + */ +static struct config_generic **guc_variables; + +/* Current number of variables contained in the vector */ +static int num_guc_variables; + +/* Vector capacity */ +static int size_guc_variables; + + +static bool guc_dirty; /* true if need to do commit/abort work */ + +static bool reporting_enabled; /* true to enable GUC_REPORT */ + +static bool report_needed; /* true if any GUC_REPORT reports are needed */ + +static int GUCNestLevel = 0; /* 1 when in main transaction */ + + +static int guc_var_compare(const void *a, const void *b); +static int guc_name_compare(const char *namea, const char *nameb); +static void InitializeGUCOptionsFromEnvironment(void); +static void InitializeOneGUCOption(struct config_generic *gconf); +static void push_old_value(struct config_generic *gconf, GucAction action); +static void ReportGUCOption(struct config_generic *record); +static void reapply_stacked_values(struct config_generic *variable, + struct config_string *pHolder, + GucStack *stack, + const char *curvalue, + GucContext curscontext, GucSource cursource, + Oid cursrole); +static void ShowGUCConfigOption(const char *name, DestReceiver *dest); +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, + const char *name, const char *value); + + +/* + * Some infrastructure for checking malloc/strdup/realloc calls + */ +static void * +guc_malloc(int elevel, size_t size) +{ + void *data; + + /* Avoid unportable behavior of malloc(0) */ + if (size == 0) + size = 1; + data = malloc(size); + if (data == NULL) + ereport(elevel, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return data; +} + +static void * +guc_realloc(int elevel, void *old, size_t size) +{ + void *data; + + /* Avoid unportable behavior of realloc(NULL, 0) */ + if (old == NULL && size == 0) + size = 1; + data = realloc(old, size); + if (data == NULL) + ereport(elevel, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return data; +} + +static char * +guc_strdup(int elevel, const char *src) +{ + char *data; + + data = strdup(src); + if (data == NULL) + ereport(elevel, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return data; +} + + +/* + * Detect whether strval is referenced anywhere in a GUC string item + */ +static bool +string_field_used(struct config_string *conf, char *strval) +{ + GucStack *stack; + + if (strval == *(conf->variable) || + strval == conf->reset_val || + strval == conf->boot_val) + return true; + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (strval == stack->prior.val.stringval || + strval == stack->masked.val.stringval) + return true; + } + return false; +} + +/* + * Support for assigning to a field of a string GUC item. Free the prior + * value if it's not referenced anywhere else in the item (including stacked + * states). + */ +static void +set_string_field(struct config_string *conf, char **field, char *newval) +{ + char *oldval = *field; + + /* Do the assignment */ + *field = newval; + + /* Free old value if it's not NULL and isn't referenced anymore */ + if (oldval && !string_field_used(conf, oldval)) + free(oldval); +} + +/* + * Detect whether an "extra" struct is referenced anywhere in a GUC item + */ +static bool +extra_field_used(struct config_generic *gconf, void *extra) +{ + GucStack *stack; + + if (extra == gconf->extra) + return true; + switch (gconf->vartype) + { + case PGC_BOOL: + if (extra == ((struct config_bool *) gconf)->reset_extra) + return true; + break; + case PGC_INT: + if (extra == ((struct config_int *) gconf)->reset_extra) + return true; + break; + case PGC_REAL: + if (extra == ((struct config_real *) gconf)->reset_extra) + return true; + break; + case PGC_STRING: + if (extra == ((struct config_string *) gconf)->reset_extra) + return true; + break; + case PGC_ENUM: + if (extra == ((struct config_enum *) gconf)->reset_extra) + return true; + break; + } + for (stack = gconf->stack; stack; stack = stack->prev) + { + if (extra == stack->prior.extra || + extra == stack->masked.extra) + return true; + } + + return false; +} + +/* + * Support for assigning to an "extra" field of a GUC item. Free the prior + * value if it's not referenced anywhere else in the item (including stacked + * states). + */ +static void +set_extra_field(struct config_generic *gconf, void **field, void *newval) +{ + void *oldval = *field; + + /* Do the assignment */ + *field = newval; + + /* Free old value if it's not NULL and isn't referenced anymore */ + if (oldval && !extra_field_used(gconf, oldval)) + free(oldval); +} + +/* + * Support for copying a variable's active value into a stack entry. + * The "extra" field associated with the active value is copied, too. + * + * NB: be sure stringval and extra fields of a new stack entry are + * initialized to NULL before this is used, else we'll try to free() them. + */ +static void +set_stack_value(struct config_generic *gconf, config_var_value *val) +{ + switch (gconf->vartype) + { + case PGC_BOOL: + val->val.boolval = + *((struct config_bool *) gconf)->variable; + break; + case PGC_INT: + val->val.intval = + *((struct config_int *) gconf)->variable; + break; + case PGC_REAL: + val->val.realval = + *((struct config_real *) gconf)->variable; + break; + case PGC_STRING: + set_string_field((struct config_string *) gconf, + &(val->val.stringval), + *((struct config_string *) gconf)->variable); + break; + case PGC_ENUM: + val->val.enumval = + *((struct config_enum *) gconf)->variable; + break; + } + set_extra_field(gconf, &(val->extra), gconf->extra); +} + +/* + * Support for discarding a no-longer-needed value in a stack entry. + * The "extra" field associated with the stack entry is cleared, too. + */ +static void +discard_stack_value(struct config_generic *gconf, config_var_value *val) +{ + switch (gconf->vartype) + { + case PGC_BOOL: + case PGC_INT: + case PGC_REAL: + case PGC_ENUM: + /* no need to do anything */ + break; + case PGC_STRING: + set_string_field((struct config_string *) gconf, + &(val->val.stringval), + NULL); + break; + } + set_extra_field(gconf, &(val->extra), NULL); +} + + +/* + * Fetch the sorted array pointer (exported for help_config.c's use ONLY) + */ +struct config_generic ** +get_guc_variables(void) +{ + return guc_variables; +} + + +/* + * Build the sorted array. This is split out so that it could be + * re-executed after startup (e.g., we could allow loadable modules to + * add vars, and then we'd need to re-sort). + */ +void +build_guc_variables(void) +{ + int size_vars; + int num_vars = 0; + struct config_generic **guc_vars; + int i; + + for (i = 0; ConfigureNamesBool[i].gen.name; i++) + { + struct config_bool *conf = &ConfigureNamesBool[i]; + + /* Rather than requiring vartype to be filled in by hand, do this: */ + conf->gen.vartype = PGC_BOOL; + num_vars++; + } + + for (i = 0; ConfigureNamesInt[i].gen.name; i++) + { + struct config_int *conf = &ConfigureNamesInt[i]; + + conf->gen.vartype = PGC_INT; + num_vars++; + } + + for (i = 0; ConfigureNamesReal[i].gen.name; i++) + { + struct config_real *conf = &ConfigureNamesReal[i]; + + conf->gen.vartype = PGC_REAL; + num_vars++; + } + + for (i = 0; ConfigureNamesString[i].gen.name; i++) + { + struct config_string *conf = &ConfigureNamesString[i]; + + conf->gen.vartype = PGC_STRING; + num_vars++; + } + + for (i = 0; ConfigureNamesEnum[i].gen.name; i++) + { + struct config_enum *conf = &ConfigureNamesEnum[i]; + + conf->gen.vartype = PGC_ENUM; + num_vars++; + } + + /* + * Create table with 20% slack + */ + size_vars = num_vars + num_vars / 4; + + guc_vars = (struct config_generic **) + guc_malloc(FATAL, size_vars * sizeof(struct config_generic *)); + + num_vars = 0; + + for (i = 0; ConfigureNamesBool[i].gen.name; i++) + guc_vars[num_vars++] = &ConfigureNamesBool[i].gen; + + for (i = 0; ConfigureNamesInt[i].gen.name; i++) + guc_vars[num_vars++] = &ConfigureNamesInt[i].gen; + + for (i = 0; ConfigureNamesReal[i].gen.name; i++) + guc_vars[num_vars++] = &ConfigureNamesReal[i].gen; + + for (i = 0; ConfigureNamesString[i].gen.name; i++) + guc_vars[num_vars++] = &ConfigureNamesString[i].gen; + + for (i = 0; ConfigureNamesEnum[i].gen.name; i++) + guc_vars[num_vars++] = &ConfigureNamesEnum[i].gen; + + if (guc_variables) + free(guc_variables); + guc_variables = guc_vars; + num_guc_variables = num_vars; + size_guc_variables = size_vars; + qsort((void *) guc_variables, num_guc_variables, + sizeof(struct config_generic *), guc_var_compare); +} + +/* + * Add a new GUC variable to the list of known variables. The + * list is expanded if needed. + */ +static bool +add_guc_variable(struct config_generic *var, int elevel) +{ + if (num_guc_variables + 1 >= size_guc_variables) + { + /* + * Increase the vector by 25% + */ + int size_vars = size_guc_variables + size_guc_variables / 4; + struct config_generic **guc_vars; + + if (size_vars == 0) + { + size_vars = 100; + guc_vars = (struct config_generic **) + guc_malloc(elevel, size_vars * sizeof(struct config_generic *)); + } + else + { + guc_vars = (struct config_generic **) + guc_realloc(elevel, guc_variables, size_vars * sizeof(struct config_generic *)); + } + + if (guc_vars == NULL) + return false; /* out of memory */ + + guc_variables = guc_vars; + size_guc_variables = size_vars; + } + guc_variables[num_guc_variables++] = var; + qsort((void *) guc_variables, num_guc_variables, + sizeof(struct config_generic *), guc_var_compare); + return true; +} + +/* + * Decide whether a proposed custom variable name is allowed. + * + * It must be two or more identifiers separated by dots, where the rules + * for what is an identifier agree with scan.l. (If you change this rule, + * adjust the errdetail in find_option().) + */ +static bool +valid_custom_variable_name(const char *name) +{ + bool saw_sep = false; + bool name_start = true; + + for (const char *p = name; *p; p++) + { + if (*p == GUC_QUALIFIER_SEPARATOR) + { + if (name_start) + return false; /* empty name component */ + saw_sep = true; + name_start = true; + } + else if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz_", *p) != NULL || + IS_HIGHBIT_SET(*p)) + { + /* okay as first or non-first character */ + name_start = false; + } + else if (!name_start && strchr("0123456789$", *p) != NULL) + /* okay as non-first character */ ; + else + return false; + } + if (name_start) + return false; /* empty name component */ + /* OK if we found at least one separator */ + return saw_sep; +} + +/* + * Create and add a placeholder variable for a custom variable name. + */ +static struct config_generic * +add_placeholder_variable(const char *name, int elevel) +{ + size_t sz = sizeof(struct config_string) + sizeof(char *); + struct config_string *var; + struct config_generic *gen; + + var = (struct config_string *) guc_malloc(elevel, sz); + if (var == NULL) + return NULL; + memset(var, 0, sz); + gen = &var->gen; + + gen->name = guc_strdup(elevel, name); + if (gen->name == NULL) + { + free(var); + return NULL; + } + + gen->context = PGC_USERSET; + gen->group = CUSTOM_OPTIONS; + gen->short_desc = "GUC placeholder variable"; + gen->flags = GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_CUSTOM_PLACEHOLDER; + gen->vartype = PGC_STRING; + + /* + * The char* is allocated at the end of the struct since we have no + * 'static' place to point to. Note that the current value, as well as + * the boot and reset values, start out NULL. + */ + var->variable = (char **) (var + 1); + + if (!add_guc_variable((struct config_generic *) var, elevel)) + { + free(unconstify(char *, gen->name)); + free(var); + return NULL; + } + + return gen; +} + +/* + * Look up option "name". If it exists, return a pointer to its record. + * Otherwise, if create_placeholders is true and name is a valid-looking + * custom variable name, we'll create and return a placeholder record. + * Otherwise, if skip_errors is true, then we silently return NULL for + * an unrecognized or invalid name. Otherwise, the error is reported at + * error level elevel (and we return NULL if that's less than ERROR). + * + * Note: internal errors, primarily out-of-memory, draw an elevel-level + * report and NULL return regardless of skip_errors. Hence, callers must + * handle a NULL return whenever elevel < ERROR, but they should not need + * to emit any additional error message. (In practice, internal errors + * can only happen when create_placeholders is true, so callers passing + * false need not think terribly hard about this.) + */ +static struct config_generic * +find_option(const char *name, bool create_placeholders, bool skip_errors, + int elevel) +{ + const char **key = &name; + struct config_generic **res; + int i; + + Assert(name); + + /* + * By equating const char ** with struct config_generic *, we are assuming + * the name field is first in config_generic. + */ + res = (struct config_generic **) bsearch((void *) &key, + (void *) guc_variables, + num_guc_variables, + sizeof(struct config_generic *), + guc_var_compare); + if (res) + return *res; + + /* + * See if the name is an obsolete name for a variable. We assume that the + * set of supported old names is short enough that a brute-force search is + * the best way. + */ + for (i = 0; map_old_guc_names[i] != NULL; i += 2) + { + if (guc_name_compare(name, map_old_guc_names[i]) == 0) + return find_option(map_old_guc_names[i + 1], false, + skip_errors, elevel); + } + + if (create_placeholders) + { + /* + * Check if the name is valid, and if so, add a placeholder. If it + * doesn't contain a separator, don't assume that it was meant to be a + * placeholder. + */ + const char *sep = strchr(name, GUC_QUALIFIER_SEPARATOR); + + if (sep != NULL) + { + size_t classLen = sep - name; + ListCell *lc; + + /* The name must be syntactically acceptable ... */ + if (!valid_custom_variable_name(name)) + { + if (!skip_errors) + ereport(elevel, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid configuration parameter name \"%s\"", + name), + errdetail("Custom parameter names must be two or more simple identifiers separated by dots."))); + return NULL; + } + /* ... and it must not match any previously-reserved prefix */ + foreach(lc, reserved_class_prefix) + { + const char *rcprefix = lfirst(lc); + + if (strlen(rcprefix) == classLen && + strncmp(name, rcprefix, classLen) == 0) + { + if (!skip_errors) + ereport(elevel, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid configuration parameter name \"%s\"", + name), + errdetail("\"%s\" is a reserved prefix.", + rcprefix))); + return NULL; + } + } + /* OK, create it */ + return add_placeholder_variable(name, elevel); + } + } + + /* Unknown name */ + if (!skip_errors) + ereport(elevel, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("unrecognized configuration parameter \"%s\"", + name))); + return NULL; +} + + +/* + * comparator for qsorting and bsearching guc_variables array + */ +static int +guc_var_compare(const void *a, const void *b) +{ + const struct config_generic *confa = *(struct config_generic *const *) a; + const struct config_generic *confb = *(struct config_generic *const *) b; + + return guc_name_compare(confa->name, confb->name); +} + +/* + * the bare comparison function for GUC names + */ +static int +guc_name_compare(const char *namea, const char *nameb) +{ + /* + * The temptation to use strcasecmp() here must be resisted, because the + * array ordering has to remain stable across setlocale() calls. So, build + * our own with a simple ASCII-only downcasing. + */ + while (*namea && *nameb) + { + char cha = *namea++; + char chb = *nameb++; + + if (cha >= 'A' && cha <= 'Z') + cha += 'a' - 'A'; + if (chb >= 'A' && chb <= 'Z') + chb += 'a' - 'A'; + if (cha != chb) + return cha - chb; + } + if (*namea) + return 1; /* a is longer */ + if (*nameb) + return -1; /* b is longer */ + return 0; +} + + +/* + * Convert a GUC name to the form that should be used in pg_parameter_acl. + * + * We need to canonicalize entries since, for example, case should not be + * significant. In addition, we apply the map_old_guc_names[] mapping so that + * any obsolete names will be converted when stored in a new PG version. + * Note however that this function does not verify legality of the name. + * + * The result is a palloc'd string. + */ +char * +convert_GUC_name_for_parameter_acl(const char *name) +{ + char *result; + + /* Apply old-GUC-name mapping. */ + for (int i = 0; map_old_guc_names[i] != NULL; i += 2) + { + if (guc_name_compare(name, map_old_guc_names[i]) == 0) + { + name = map_old_guc_names[i + 1]; + break; + } + } + + /* Apply case-folding that matches guc_name_compare(). */ + result = pstrdup(name); + for (char *ptr = result; *ptr != '\0'; ptr++) + { + char ch = *ptr; + + if (ch >= 'A' && ch <= 'Z') + { + ch += 'a' - 'A'; + *ptr = ch; + } + } + + return result; +} + +/* + * Check whether we should allow creation of a pg_parameter_acl entry + * for the given name. (This can be applied either before or after + * canonicalizing it.) + */ +bool +check_GUC_name_for_parameter_acl(const char *name) +{ + /* OK if the GUC exists. */ + if (find_option(name, false, true, DEBUG1) != NULL) + return true; + /* Otherwise, it'd better be a valid custom GUC name. */ + if (valid_custom_variable_name(name)) + return true; + return false; +} + + +/* + * Initialize GUC options during program startup. + * + * Note that we cannot read the config file yet, since we have not yet + * processed command-line switches. + */ +void +InitializeGUCOptions(void) +{ + int i; + + /* + * Before log_line_prefix could possibly receive a nonempty setting, make + * sure that timezone processing is minimally alive (see elog.c). + */ + pg_timezone_initialize(); + + /* + * Build sorted array of all GUC variables. + */ + build_guc_variables(); + + /* + * Load all variables with their compiled-in defaults, and initialize + * status fields as needed. + */ + for (i = 0; i < num_guc_variables; i++) + { + InitializeOneGUCOption(guc_variables[i]); + } + + guc_dirty = false; + + reporting_enabled = false; + + /* + * Prevent any attempt to override the transaction modes from + * non-interactive sources. + */ + SetConfigOption("transaction_isolation", "read committed", + PGC_POSTMASTER, PGC_S_OVERRIDE); + SetConfigOption("transaction_read_only", "no", + PGC_POSTMASTER, PGC_S_OVERRIDE); + SetConfigOption("transaction_deferrable", "no", + PGC_POSTMASTER, PGC_S_OVERRIDE); + + /* + * For historical reasons, some GUC parameters can receive defaults from + * environment variables. Process those settings. + */ + InitializeGUCOptionsFromEnvironment(); +} + +/* + * If any custom resource managers were specified in the + * wal_consistency_checking GUC, processing was deferred. Now that + * shared_preload_libraries have been loaded, process wal_consistency_checking + * again. + */ +void +InitializeWalConsistencyChecking(void) +{ + Assert(process_shared_preload_libraries_done); + + if (check_wal_consistency_checking_deferred) + { + struct config_generic *guc; + + guc = find_option("wal_consistency_checking", false, false, ERROR); + + check_wal_consistency_checking_deferred = false; + + set_config_option_ext("wal_consistency_checking", + wal_consistency_checking_string, + guc->scontext, guc->source, guc->srole, + GUC_ACTION_SET, true, ERROR, false); + + /* checking should not be deferred again */ + Assert(!check_wal_consistency_checking_deferred); + } +} + +/* + * Assign any GUC values that can come from the server's environment. + * + * This is called from InitializeGUCOptions, and also from ProcessConfigFile + * to deal with the possibility that a setting has been removed from + * postgresql.conf and should now get a value from the environment. + * (The latter is a kludge that should probably go away someday; if so, + * fold this back into InitializeGUCOptions.) + */ +static void +InitializeGUCOptionsFromEnvironment(void) +{ + char *env; + long stack_rlimit; + + env = getenv("PGPORT"); + if (env != NULL) + SetConfigOption("port", env, PGC_POSTMASTER, PGC_S_ENV_VAR); + + env = getenv("PGDATESTYLE"); + if (env != NULL) + SetConfigOption("datestyle", env, PGC_POSTMASTER, PGC_S_ENV_VAR); + + env = getenv("PGCLIENTENCODING"); + if (env != NULL) + SetConfigOption("client_encoding", env, PGC_POSTMASTER, PGC_S_ENV_VAR); + + /* + * rlimit isn't exactly an "environment variable", but it behaves about + * the same. If we can identify the platform stack depth rlimit, increase + * default stack depth setting up to whatever is safe (but at most 2MB). + * Report the value's source as PGC_S_DYNAMIC_DEFAULT if it's 2MB, or as + * PGC_S_ENV_VAR if it's reflecting the rlimit limit. + */ + stack_rlimit = get_stack_depth_rlimit(); + if (stack_rlimit > 0) + { + long new_limit = (stack_rlimit - STACK_DEPTH_SLOP) / 1024L; + + if (new_limit > 100) + { + GucSource source; + char limbuf[16]; + + if (new_limit < 2048) + source = PGC_S_ENV_VAR; + else + { + new_limit = 2048; + source = PGC_S_DYNAMIC_DEFAULT; + } + snprintf(limbuf, sizeof(limbuf), "%ld", new_limit); + SetConfigOption("max_stack_depth", limbuf, + PGC_POSTMASTER, source); + } + } +} + +/* + * 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->srole = BOOTSTRAP_SUPERUSERID; + gconf->reset_srole = BOOTSTRAP_SUPERUSERID; + gconf->stack = NULL; + gconf->extra = NULL; + gconf->last_reported = 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, (int) 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_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; + } + } +} + + +/* + * Select the configuration files and data directory to be used, and + * do the initial read of postgresql.conf. + * + * This is called after processing command-line switches. + * userDoption is the -D switch value if any (NULL if unspecified). + * progname is just for use in error messages. + * + * Returns true on success; on failure, prints a suitable error message + * to stderr and returns false. + */ +bool +SelectConfigFiles(const char *userDoption, const char *progname) +{ + char *configdir; + char *fname; + struct stat stat_buf; + + /* configdir is -D option, or $PGDATA if no -D */ + if (userDoption) + configdir = make_absolute_path(userDoption); + else + configdir = make_absolute_path(getenv("PGDATA")); + + if (configdir && stat(configdir, &stat_buf) != 0) + { + write_stderr("%s: could not access directory \"%s\": %s\n", + progname, + configdir, + strerror(errno)); + if (errno == ENOENT) + write_stderr("Run initdb or pg_basebackup to initialize a PostgreSQL data directory.\n"); + return false; + } + + /* + * Find the configuration file: if config_file was specified on the + * command line, use it, else use configdir/postgresql.conf. In any case + * ensure the result is an absolute path, so that it will be interpreted + * the same way by future backends. + */ + if (ConfigFileName) + fname = make_absolute_path(ConfigFileName); + else if (configdir) + { + fname = guc_malloc(FATAL, + strlen(configdir) + strlen(CONFIG_FILENAME) + 2); + sprintf(fname, "%s/%s", configdir, CONFIG_FILENAME); + } + else + { + write_stderr("%s does not know where to find the server configuration file.\n" + "You must specify the --config-file or -D invocation " + "option or set the PGDATA environment variable.\n", + progname); + return false; + } + + /* + * Set the ConfigFileName GUC variable to its final value, ensuring that + * it can't be overridden later. + */ + SetConfigOption("config_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + free(fname); + + /* + * Now read the config file for the first time. + */ + if (stat(ConfigFileName, &stat_buf) != 0) + { + write_stderr("%s: could not access the server configuration file \"%s\": %s\n", + progname, ConfigFileName, strerror(errno)); + free(configdir); + return false; + } + + /* + * Read the configuration file for the first time. This time only the + * data_directory parameter is picked up to determine the data directory, + * so that we can read the PG_AUTOCONF_FILENAME file next time. + */ + ProcessConfigFile(PGC_POSTMASTER); + + /* + * If the data_directory GUC variable has been set, use that as DataDir; + * otherwise use configdir if set; else punt. + * + * Note: SetDataDir will copy and absolute-ize its argument, so we don't + * have to. + */ + if (data_directory) + SetDataDir(data_directory); + else if (configdir) + SetDataDir(configdir); + else + { + write_stderr("%s does not know where to find the database system data.\n" + "This can be specified as \"data_directory\" in \"%s\", " + "or by the -D invocation option, or by the " + "PGDATA environment variable.\n", + progname, ConfigFileName); + return false; + } + + /* + * Reflect the final DataDir value back into the data_directory GUC var. + * (If you are wondering why we don't just make them a single variable, + * it's because the EXEC_BACKEND case needs DataDir to be transmitted to + * child backends specially. XXX is that still true? Given that we now + * chdir to DataDir, EXEC_BACKEND can read the config file without knowing + * DataDir in advance.) + */ + SetConfigOption("data_directory", DataDir, PGC_POSTMASTER, PGC_S_OVERRIDE); + + /* + * Now read the config file a second time, allowing any settings in the + * PG_AUTOCONF_FILENAME file to take effect. (This is pretty ugly, but + * since we have to determine the DataDir before we can find the autoconf + * file, the alternatives seem worse.) + */ + ProcessConfigFile(PGC_POSTMASTER); + + /* + * If timezone_abbreviations wasn't set in the configuration file, install + * the default value. We do it this way because we can't safely install a + * "real" value until my_exec_path is set, which may not have happened + * when InitializeGUCOptions runs, so the bootstrap default value cannot + * be the real desired default. + */ + pg_timezone_abbrev_initialize(); + + /* + * Figure out where pg_hba.conf is, and make sure the path is absolute. + */ + if (HbaFileName) + fname = make_absolute_path(HbaFileName); + else if (configdir) + { + fname = guc_malloc(FATAL, + strlen(configdir) + strlen(HBA_FILENAME) + 2); + sprintf(fname, "%s/%s", configdir, HBA_FILENAME); + } + else + { + write_stderr("%s does not know where to find the \"hba\" configuration file.\n" + "This can be specified as \"hba_file\" in \"%s\", " + "or by the -D invocation option, or by the " + "PGDATA environment variable.\n", + progname, ConfigFileName); + return false; + } + SetConfigOption("hba_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + free(fname); + + /* + * Likewise for pg_ident.conf. + */ + if (IdentFileName) + fname = make_absolute_path(IdentFileName); + else if (configdir) + { + fname = guc_malloc(FATAL, + strlen(configdir) + strlen(IDENT_FILENAME) + 2); + sprintf(fname, "%s/%s", configdir, IDENT_FILENAME); + } + else + { + write_stderr("%s does not know where to find the \"ident\" configuration file.\n" + "This can be specified as \"ident_file\" in \"%s\", " + "or by the -D invocation option, or by the " + "PGDATA environment variable.\n", + progname, ConfigFileName); + return false; + } + SetConfigOption("ident_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + free(fname); + + free(configdir); + + return true; +} + + +/* + * Reset all options to their saved default values (implements RESET ALL) + */ +void +ResetAllOptions(void) +{ + int i; + + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *gconf = guc_variables[i]; + + /* Don't reset non-SET-able values */ + if (gconf->context != PGC_SUSET && + gconf->context != PGC_USERSET) + continue; + /* Don't reset if special exclusion from RESET ALL */ + if (gconf->flags & GUC_NO_RESET_ALL) + continue; + /* No need to reset if wasn't SET */ + if (gconf->source <= PGC_S_OVERRIDE) + continue; + + /* Save old value to support transaction abort */ + push_old_value(gconf, GUC_ACTION_SET); + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + set_string_field(conf, conf->variable, conf->reset_val); + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + } + + gconf->source = gconf->reset_source; + gconf->scontext = gconf->reset_scontext; + gconf->srole = gconf->reset_srole; + + if (gconf->flags & GUC_REPORT) + { + gconf->status |= GUC_NEEDS_REPORT; + report_needed = true; + } + } +} + + +/* + * push_old_value + * Push previous state during transactional assignment to a GUC variable. + */ +static void +push_old_value(struct config_generic *gconf, GucAction action) +{ + GucStack *stack; + + /* If we're not inside a nest level, do nothing */ + if (GUCNestLevel == 0) + return; + + /* Do we already have a stack entry of the current nest level? */ + stack = gconf->stack; + if (stack && stack->nest_level >= GUCNestLevel) + { + /* Yes, so adjust its state if necessary */ + Assert(stack->nest_level == GUCNestLevel); + switch (action) + { + case GUC_ACTION_SET: + /* SET overrides any prior action at same nest level */ + if (stack->state == GUC_SET_LOCAL) + { + /* must discard old masked value */ + discard_stack_value(gconf, &stack->masked); + } + stack->state = GUC_SET; + break; + case GUC_ACTION_LOCAL: + if (stack->state == GUC_SET) + { + /* SET followed by SET LOCAL, remember SET's value */ + stack->masked_scontext = gconf->scontext; + stack->masked_srole = gconf->srole; + set_stack_value(gconf, &stack->masked); + stack->state = GUC_SET_LOCAL; + } + /* in all other cases, no change to stack entry */ + break; + case GUC_ACTION_SAVE: + /* Could only have a prior SAVE of same variable */ + Assert(stack->state == GUC_SAVE); + break; + } + Assert(guc_dirty); /* must be set already */ + return; + } + + /* + * Push a new stack entry + * + * We keep all the stack entries in TopTransactionContext for simplicity. + */ + stack = (GucStack *) MemoryContextAllocZero(TopTransactionContext, + sizeof(GucStack)); + + stack->prev = gconf->stack; + stack->nest_level = GUCNestLevel; + switch (action) + { + case GUC_ACTION_SET: + stack->state = GUC_SET; + break; + case GUC_ACTION_LOCAL: + stack->state = GUC_LOCAL; + break; + case GUC_ACTION_SAVE: + stack->state = GUC_SAVE; + break; + } + stack->source = gconf->source; + stack->scontext = gconf->scontext; + stack->srole = gconf->srole; + set_stack_value(gconf, &stack->prior); + + gconf->stack = stack; + + /* Ensure we remember to pop at end of xact */ + guc_dirty = true; +} + + +/* + * Do GUC processing at main transaction start. + */ +void +AtStart_GUC(void) +{ + /* + * The nest level should be 0 between transactions; if it isn't, somebody + * didn't call AtEOXact_GUC, or called it with the wrong nestLevel. We + * throw a warning but make no other effort to clean up. + */ + if (GUCNestLevel != 0) + elog(WARNING, "GUC nest level = %d at transaction start", + GUCNestLevel); + GUCNestLevel = 1; +} + +/* + * Enter a new nesting level for GUC values. This is called at subtransaction + * start, and when entering a function that has proconfig settings, and in + * some other places where we want to set GUC variables transiently. + * NOTE we must not risk error here, else subtransaction start will be unhappy. + */ +int +NewGUCNestLevel(void) +{ + return ++GUCNestLevel; +} + +/* + * Do GUC processing at transaction or subtransaction commit or abort, or + * when exiting a function that has proconfig settings, or when undoing a + * transient assignment to some GUC variables. (The name is thus a bit of + * a misnomer; perhaps it should be ExitGUCNestLevel or some such.) + * During abort, we discard all GUC settings that were applied at nesting + * levels >= nestLevel. nestLevel == 1 corresponds to the main transaction. + */ +void +AtEOXact_GUC(bool isCommit, int nestLevel) +{ + bool still_dirty; + int i; + + /* + * Note: it's possible to get here with GUCNestLevel == nestLevel-1 during + * abort, if there is a failure during transaction start before + * AtStart_GUC is called. + */ + Assert(nestLevel > 0 && + (nestLevel <= GUCNestLevel || + (nestLevel == GUCNestLevel + 1 && !isCommit))); + + /* Quick exit if nothing's changed in this transaction */ + if (!guc_dirty) + { + GUCNestLevel = nestLevel - 1; + return; + } + + still_dirty = false; + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *gconf = guc_variables[i]; + GucStack *stack; + + /* + * Process and pop each stack entry within the nest level. To simplify + * fmgr_security_definer() and other places that use GUC_ACTION_SAVE, + * we allow failure exit from code that uses a local nest level to be + * recovered at the surrounding transaction or subtransaction abort; + * so there could be more than one stack entry to pop. + */ + while ((stack = gconf->stack) != NULL && + stack->nest_level >= nestLevel) + { + GucStack *prev = stack->prev; + bool restorePrior = false; + bool restoreMasked = false; + bool changed; + + /* + * In this next bit, if we don't set either restorePrior or + * restoreMasked, we must "discard" any unwanted fields of the + * stack entries to avoid leaking memory. If we do set one of + * those flags, unused fields will be cleaned up after restoring. + */ + if (!isCommit) /* if abort, always restore prior value */ + restorePrior = true; + else if (stack->state == GUC_SAVE) + restorePrior = true; + else if (stack->nest_level == 1) + { + /* transaction commit */ + if (stack->state == GUC_SET_LOCAL) + restoreMasked = true; + else if (stack->state == GUC_SET) + { + /* we keep the current active value */ + discard_stack_value(gconf, &stack->prior); + } + else /* must be GUC_LOCAL */ + restorePrior = true; + } + else if (prev == NULL || + prev->nest_level < stack->nest_level - 1) + { + /* decrement entry's level and do not pop it */ + stack->nest_level--; + continue; + } + else + { + /* + * We have to merge this stack entry into prev. See README for + * discussion of this bit. + */ + switch (stack->state) + { + case GUC_SAVE: + Assert(false); /* can't get here */ + break; + + case GUC_SET: + /* next level always becomes SET */ + discard_stack_value(gconf, &stack->prior); + if (prev->state == GUC_SET_LOCAL) + discard_stack_value(gconf, &prev->masked); + prev->state = GUC_SET; + break; + + case GUC_LOCAL: + if (prev->state == GUC_SET) + { + /* LOCAL migrates down */ + prev->masked_scontext = stack->scontext; + prev->masked_srole = stack->srole; + prev->masked = stack->prior; + prev->state = GUC_SET_LOCAL; + } + else + { + /* else just forget this stack level */ + discard_stack_value(gconf, &stack->prior); + } + break; + + case GUC_SET_LOCAL: + /* prior state at this level no longer wanted */ + discard_stack_value(gconf, &stack->prior); + /* copy down the masked state */ + prev->masked_scontext = stack->masked_scontext; + prev->masked_srole = stack->masked_srole; + if (prev->state == GUC_SET_LOCAL) + discard_stack_value(gconf, &prev->masked); + prev->masked = stack->masked; + prev->state = GUC_SET_LOCAL; + break; + } + } + + changed = false; + + if (restorePrior || restoreMasked) + { + /* Perform appropriate restoration of the stacked value */ + config_var_value newvalue; + GucSource newsource; + GucContext newscontext; + Oid newsrole; + + if (restoreMasked) + { + newvalue = stack->masked; + newsource = PGC_S_SESSION; + newscontext = stack->masked_scontext; + newsrole = stack->masked_srole; + } + else + { + newvalue = stack->prior; + newsource = stack->source; + newscontext = stack->scontext; + newsrole = stack->srole; + } + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + bool newval = newvalue.val.boolval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + int newval = newvalue.val.intval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + double newval = newvalue.val.realval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + char *newval = newvalue.val.stringval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + set_string_field(conf, conf->variable, newval); + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + + /* + * Release stacked values if not used anymore. We + * could use discard_stack_value() here, but since + * we have type-specific code anyway, might as + * well inline it. + */ + set_string_field(conf, &stack->prior.val.stringval, NULL); + set_string_field(conf, &stack->masked.val.stringval, NULL); + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + int newval = newvalue.val.enumval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + break; + } + } + + /* + * Release stacked extra values if not used anymore. + */ + set_extra_field(gconf, &(stack->prior.extra), NULL); + set_extra_field(gconf, &(stack->masked.extra), NULL); + + /* And restore source information */ + gconf->source = newsource; + gconf->scontext = newscontext; + gconf->srole = newsrole; + } + + /* Finish popping the state stack */ + gconf->stack = prev; + pfree(stack); + + /* Report new value if we changed it */ + if (changed && (gconf->flags & GUC_REPORT)) + { + gconf->status |= GUC_NEEDS_REPORT; + report_needed = true; + } + } /* end of stack-popping loop */ + + if (stack != NULL) + still_dirty = true; + } + + /* If there are no remaining stack entries, we can reset guc_dirty */ + guc_dirty = still_dirty; + + /* Update nesting level */ + GUCNestLevel = nestLevel - 1; +} + + +/* + * Start up automatic reporting of changes to variables marked GUC_REPORT. + * This is executed at completion of backend startup. + */ +void +BeginReportingGUCOptions(void) +{ + int i; + + /* + * Don't do anything unless talking to an interactive frontend. + */ + if (whereToSendOutput != DestRemote) + return; + + reporting_enabled = true; + + /* + * Hack for in_hot_standby: set the GUC value true if appropriate. This + * is kind of an ugly place to do it, but there's few better options. + * + * (This could be out of date by the time we actually send it, in which + * case the next ReportChangedGUCOptions call will send a duplicate + * report.) + */ + if (RecoveryInProgress()) + SetConfigOption("in_hot_standby", "true", + PGC_INTERNAL, PGC_S_OVERRIDE); + + /* Transmit initial values of interesting variables */ + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *conf = guc_variables[i]; + + if (conf->flags & GUC_REPORT) + ReportGUCOption(conf); + } + + report_needed = false; +} + +/* + * ReportChangedGUCOptions: report recently-changed GUC_REPORT variables + * + * This is called just before we wait for a new client query. + * + * By handling things this way, we ensure that a ParameterStatus message + * is sent at most once per variable per query, even if the variable + * changed multiple times within the query. That's quite possible when + * using features such as function SET clauses. Function SET clauses + * also tend to cause values to change intraquery but eventually revert + * to their prevailing values; ReportGUCOption is responsible for avoiding + * redundant reports in such cases. + */ +void +ReportChangedGUCOptions(void) +{ + /* Quick exit if not (yet) enabled */ + if (!reporting_enabled) + return; + + /* + * Since in_hot_standby isn't actually changed by normal GUC actions, we + * need a hack to check whether a new value needs to be reported to the + * client. For speed, we rely on the assumption that it can never + * transition from false to true. + */ + if (in_hot_standby && !RecoveryInProgress()) + SetConfigOption("in_hot_standby", "false", + PGC_INTERNAL, PGC_S_OVERRIDE); + + /* Quick exit if no values have been changed */ + if (!report_needed) + return; + + /* Transmit new values of interesting variables */ + for (int i = 0; i < num_guc_variables; i++) + { + struct config_generic *conf = guc_variables[i]; + + if ((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT)) + ReportGUCOption(conf); + } + + report_needed = false; +} + +/* + * ReportGUCOption: if appropriate, transmit option value to frontend + * + * We need not transmit the value if it's the same as what we last + * transmitted. However, clear the NEEDS_REPORT flag in any case. + */ +static void +ReportGUCOption(struct config_generic *record) +{ + char *val = _ShowOption(record, false); + + if (record->last_reported == NULL || + strcmp(val, record->last_reported) != 0) + { + StringInfoData msgbuf; + + pq_beginmessage(&msgbuf, 'S'); + pq_sendstring(&msgbuf, record->name); + pq_sendstring(&msgbuf, val); + pq_endmessage(&msgbuf); + + /* + * We need a long-lifespan copy. If strdup() fails due to OOM, we'll + * set last_reported to NULL and thereby possibly make a duplicate + * report later. + */ + if (record->last_reported) + free(record->last_reported); + record->last_reported = strdup(val); + } + + pfree(val); + + record->status &= ~GUC_NEEDS_REPORT; +} + +/* + * Convert a value from one of the human-friendly units ("kB", "min" etc.) + * to the given base unit. 'value' and 'unit' are the input value and unit + * to convert from (there can be trailing spaces in the unit string). + * The converted value is stored in *base_value. + * It's caller's responsibility to round off the converted value as necessary + * and check for out-of-range. + * + * Returns true on success, false if the input unit is not recognized. + */ +static bool +convert_to_base_unit(double value, const char *unit, + int base_unit, double *base_value) +{ + char unitstr[MAX_UNIT_LEN + 1]; + int unitlen; + const unit_conversion *table; + int i; + + /* extract unit string to compare to table entries */ + unitlen = 0; + while (*unit != '\0' && !isspace((unsigned char) *unit) && + unitlen < MAX_UNIT_LEN) + unitstr[unitlen++] = *(unit++); + unitstr[unitlen] = '\0'; + /* allow whitespace after unit */ + while (isspace((unsigned char) *unit)) + unit++; + if (*unit != '\0') + return false; /* unit too long, or garbage after it */ + + /* now search the appropriate table */ + if (base_unit & GUC_UNIT_MEMORY) + table = memory_unit_conversion_table; + else + table = time_unit_conversion_table; + + for (i = 0; *table[i].unit; i++) + { + if (base_unit == table[i].base_unit && + strcmp(unitstr, table[i].unit) == 0) + { + double cvalue = value * table[i].multiplier; + + /* + * If the user gave a fractional value such as "30.1GB", round it + * off to the nearest multiple of the next smaller unit, if there + * is one. + */ + if (*table[i + 1].unit && + base_unit == table[i + 1].base_unit) + cvalue = rint(cvalue / table[i + 1].multiplier) * + table[i + 1].multiplier; + + *base_value = cvalue; + return true; + } + } + return false; +} + +/* + * Convert an integer value in some base unit to a human-friendly unit. + * + * The output unit is chosen so that it's the greatest unit that can represent + * the value without loss. For example, if the base unit is GUC_UNIT_KB, 1024 + * is converted to 1 MB, but 1025 is represented as 1025 kB. + */ +static void +convert_int_from_base_unit(int64 base_value, int base_unit, + int64 *value, const char **unit) +{ + const unit_conversion *table; + int i; + + *unit = NULL; + + if (base_unit & GUC_UNIT_MEMORY) + table = memory_unit_conversion_table; + else + table = time_unit_conversion_table; + + for (i = 0; *table[i].unit; i++) + { + if (base_unit == table[i].base_unit) + { + /* + * Accept the first conversion that divides the value evenly. We + * assume that the conversions for each base unit are ordered from + * greatest unit to the smallest! + */ + if (table[i].multiplier <= 1.0 || + base_value % (int64) table[i].multiplier == 0) + { + *value = (int64) rint(base_value / table[i].multiplier); + *unit = table[i].unit; + break; + } + } + } + + Assert(*unit != NULL); +} + +/* + * Convert a floating-point value in some base unit to a human-friendly unit. + * + * Same as above, except we have to do the math a bit differently, and + * there's a possibility that we don't find any exact divisor. + */ +static void +convert_real_from_base_unit(double base_value, int base_unit, + double *value, const char **unit) +{ + const unit_conversion *table; + int i; + + *unit = NULL; + + if (base_unit & GUC_UNIT_MEMORY) + table = memory_unit_conversion_table; + else + table = time_unit_conversion_table; + + for (i = 0; *table[i].unit; i++) + { + if (base_unit == table[i].base_unit) + { + /* + * Accept the first conversion that divides the value evenly; or + * if there is none, use the smallest (last) target unit. + * + * What we actually care about here is whether snprintf with "%g" + * will print the value as an integer, so the obvious test of + * "*value == rint(*value)" is too strict; roundoff error might + * make us choose an unreasonably small unit. As a compromise, + * accept a divisor that is within 1e-8 of producing an integer. + */ + *value = base_value / table[i].multiplier; + *unit = table[i].unit; + if (*value > 0 && + fabs((rint(*value) / *value) - 1.0) <= 1e-8) + break; + } + } + + Assert(*unit != NULL); +} + +/* + * Return the name of a GUC's base unit (e.g. "ms") given its flags. + * Return NULL if the GUC is unitless. + */ +static const char * +get_config_unit_name(int flags) +{ + switch (flags & (GUC_UNIT_MEMORY | GUC_UNIT_TIME)) + { + case 0: + return NULL; /* GUC has no units */ + case GUC_UNIT_BYTE: + return "B"; + case GUC_UNIT_KB: + return "kB"; + case GUC_UNIT_MB: + return "MB"; + case GUC_UNIT_BLOCKS: + { + static char bbuf[8]; + + /* initialize if first time through */ + if (bbuf[0] == '\0') + snprintf(bbuf, sizeof(bbuf), "%dkB", BLCKSZ / 1024); + return bbuf; + } + case GUC_UNIT_XBLOCKS: + { + static char xbuf[8]; + + /* initialize if first time through */ + if (xbuf[0] == '\0') + snprintf(xbuf, sizeof(xbuf), "%dkB", XLOG_BLCKSZ / 1024); + return xbuf; + } + case GUC_UNIT_MS: + return "ms"; + case GUC_UNIT_S: + return "s"; + case GUC_UNIT_MIN: + return "min"; + default: + elog(ERROR, "unrecognized GUC units value: %d", + flags & (GUC_UNIT_MEMORY | GUC_UNIT_TIME)); + return NULL; + } +} + + +/* + * Try to parse value as an integer. The accepted formats are the + * usual decimal, octal, or hexadecimal formats, as well as floating-point + * formats (which will be rounded to integer after any units conversion). + * Optionally, the value can be followed by a unit name if "flags" indicates + * a unit is allowed. + * + * If the string parses okay, return true, else false. + * If okay and result is not NULL, return the value in *result. + * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable + * HINT message, or NULL if no hint provided. + */ +bool +parse_int(const char *value, int *result, int flags, const char **hintmsg) +{ + /* + * We assume here that double is wide enough to represent any integer + * value with adequate precision. + */ + double val; + char *endptr; + + /* To suppress compiler warnings, always set output params */ + if (result) + *result = 0; + if (hintmsg) + *hintmsg = NULL; + + /* + * Try to parse as an integer (allowing octal or hex input). If the + * conversion stops at a decimal point or 'e', or overflows, re-parse as + * float. This should work fine as long as we have no unit names starting + * with 'e'. If we ever do, the test could be extended to check for a + * sign or digit after 'e', but for now that's unnecessary. + */ + errno = 0; + val = strtol(value, &endptr, 0); + if (*endptr == '.' || *endptr == 'e' || *endptr == 'E' || + errno == ERANGE) + { + errno = 0; + val = strtod(value, &endptr); + } + + if (endptr == value || errno == ERANGE) + return false; /* no HINT for these cases */ + + /* reject NaN (infinities will fail range check below) */ + if (isnan(val)) + return false; /* treat same as syntax error; no HINT */ + + /* allow whitespace between number and unit */ + while (isspace((unsigned char) *endptr)) + endptr++; + + /* Handle possible unit */ + if (*endptr != '\0') + { + if ((flags & GUC_UNIT) == 0) + return false; /* this setting does not accept a unit */ + + if (!convert_to_base_unit(val, + endptr, (flags & GUC_UNIT), + &val)) + { + /* invalid unit, or garbage after the unit; set hint and fail. */ + if (hintmsg) + { + if (flags & GUC_UNIT_MEMORY) + *hintmsg = memory_units_hint; + else + *hintmsg = time_units_hint; + } + return false; + } + } + + /* Round to int, then check for overflow */ + val = rint(val); + + if (val > INT_MAX || val < INT_MIN) + { + if (hintmsg) + *hintmsg = gettext_noop("Value exceeds integer range."); + return false; + } + + if (result) + *result = (int) val; + return true; +} + +/* + * Try to parse value as a floating point number in the usual format. + * Optionally, the value can be followed by a unit name if "flags" indicates + * a unit is allowed. + * + * If the string parses okay, return true, else false. + * If okay and result is not NULL, return the value in *result. + * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable + * HINT message, or NULL if no hint provided. + */ +bool +parse_real(const char *value, double *result, int flags, const char **hintmsg) +{ + double val; + char *endptr; + + /* To suppress compiler warnings, always set output params */ + if (result) + *result = 0; + if (hintmsg) + *hintmsg = NULL; + + errno = 0; + val = strtod(value, &endptr); + + if (endptr == value || errno == ERANGE) + return false; /* no HINT for these cases */ + + /* reject NaN (infinities will fail range checks later) */ + if (isnan(val)) + return false; /* treat same as syntax error; no HINT */ + + /* allow whitespace between number and unit */ + while (isspace((unsigned char) *endptr)) + endptr++; + + /* Handle possible unit */ + if (*endptr != '\0') + { + if ((flags & GUC_UNIT) == 0) + return false; /* this setting does not accept a unit */ + + if (!convert_to_base_unit(val, + endptr, (flags & GUC_UNIT), + &val)) + { + /* invalid unit, or garbage after the unit; set hint and fail. */ + if (hintmsg) + { + if (flags & GUC_UNIT_MEMORY) + *hintmsg = memory_units_hint; + else + *hintmsg = time_units_hint; + } + return false; + } + } + + if (result) + *result = val; + return true; +} + + +/* + * Lookup the name for an enum option with the selected value. + * Should only ever be called with known-valid values, so throws + * an elog(ERROR) if the enum option is not found. + * + * The returned string is a pointer to static data and not + * allocated for modification. + */ +const char * +config_enum_lookup_by_value(struct config_enum *record, int val) +{ + const struct config_enum_entry *entry; + + for (entry = record->options; entry && entry->name; entry++) + { + if (entry->val == val) + return entry->name; + } + + elog(ERROR, "could not find enum option %d for %s", + val, record->gen.name); + return NULL; /* silence compiler */ +} + + +/* + * Lookup the value for an enum option with the selected name + * (case-insensitive). + * If the enum option is found, sets the retval value and returns + * true. If it's not found, return false and retval is set to 0. + */ +bool +config_enum_lookup_by_name(struct config_enum *record, const char *value, + int *retval) +{ + const struct config_enum_entry *entry; + + for (entry = record->options; entry && entry->name; entry++) + { + if (pg_strcasecmp(value, entry->name) == 0) + { + *retval = entry->val; + return true; + } + } + + *retval = 0; + return false; +} + + +/* + * Return a list of all available options for an enum, excluding + * hidden ones, separated by the given separator. + * If prefix is non-NULL, it is added before the first enum value. + * If suffix is non-NULL, it is added to the end of the string. + */ +static char * +config_enum_get_options(struct config_enum *record, const char *prefix, + const char *suffix, const char *separator) +{ + const struct config_enum_entry *entry; + StringInfoData retstr; + int seplen; + + initStringInfo(&retstr); + appendStringInfoString(&retstr, prefix); + + seplen = strlen(separator); + for (entry = record->options; entry && entry->name; entry++) + { + if (!entry->hidden) + { + appendStringInfoString(&retstr, entry->name); + appendBinaryStringInfo(&retstr, separator, seplen); + } + } + + /* + * All the entries may have been hidden, leaving the string empty if no + * prefix was given. This indicates a broken GUC setup, since there is no + * use for an enum without any values, so we just check to make sure we + * don't write to invalid memory instead of actually trying to do + * something smart with it. + */ + if (retstr.len >= seplen) + { + /* Replace final separator */ + retstr.data[retstr.len - seplen] = '\0'; + retstr.len -= seplen; + } + + appendStringInfoString(&retstr, suffix); + + return retstr.data; +} + +/* + * Parse and validate a proposed value for the specified configuration + * parameter. + * + * This does built-in checks (such as range limits for an integer parameter) + * and also calls any check hook the parameter may have. + * + * record: GUC variable's info record + * name: variable name (should match the record of course) + * 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 +parse_and_validate_value(struct config_generic *record, + const char *name, const char *value, + GucSource source, int elevel, + union config_var_val *newval, void **newextra) +{ + switch (record->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) record; + + if (!parse_bool(value, &newval->boolval)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" requires a Boolean value", + name))); + return false; + } + + if (!call_bool_check_hook(conf, &newval->boolval, newextra, + source, elevel)) + return false; + } + break; + case PGC_INT: + { + struct config_int *conf = (struct config_int *) record; + const char *hintmsg; + + if (!parse_int(value, &newval->intval, + 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 false; + } + + if (newval->intval < conf->min || newval->intval > conf->max) + { + const char *unit = get_config_unit_name(conf->gen.flags); + + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%d%s%s is outside the valid range for parameter \"%s\" (%d .. %d)", + newval->intval, + unit ? " " : "", + unit ? unit : "", + name, + conf->min, conf->max))); + return false; + } + + if (!call_int_check_hook(conf, &newval->intval, newextra, + source, elevel)) + return false; + } + break; + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) record; + const char *hintmsg; + + if (!parse_real(value, &newval->realval, + 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 false; + } + + if (newval->realval < conf->min || newval->realval > conf->max) + { + const char *unit = get_config_unit_name(conf->gen.flags); + + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%g%s%s is outside the valid range for parameter \"%s\" (%g .. %g)", + newval->realval, + unit ? " " : "", + unit ? unit : "", + name, + conf->min, conf->max))); + return false; + } + + if (!call_real_check_hook(conf, &newval->realval, newextra, + source, elevel)) + return false; + } + break; + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) record; + + /* + * The value passed by the caller could be transient, so we + * always strdup it. + */ + newval->stringval = guc_strdup(elevel, value); + if (newval->stringval == NULL) + return false; + + /* + * 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->stringval, + strlen(newval->stringval), + true); + + if (!call_string_check_hook(conf, &newval->stringval, newextra, + source, elevel)) + { + free(newval->stringval); + newval->stringval = NULL; + return false; + } + } + break; + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) record; + + if (!config_enum_lookup_by_name(conf, value, &newval->enumval)) + { + 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 false; + } + + if (!call_enum_check_hook(conf, &newval->enumval, newextra, + source, elevel)) + return false; + } + break; + } + + return true; +} + + +/* + * set_config_option: sets option `name' to given value. + * + * The value should be a string, which will be parsed and converted to + * the appropriate data type. The context and source parameters indicate + * in which context this function is being called, so that it can apply the + * access restrictions properly. + * + * If value is NULL, set the option to its default value (normally the + * reset_val, but if source == PGC_S_DEFAULT we instead use the boot_val). + * + * action indicates whether to set the value globally in the session, locally + * to the current top transaction, or just for the duration of a function call. + * + * If changeVal is false then don't really set the option but do all + * the checks to see if it would work. + * + * elevel should normally be passed as zero, allowing this function to make + * its standard choice of ereport level. However some callers need to be + * able to override that choice; they should pass the ereport level to use. + * + * is_reload should be true only when called from read_nondefault_variables() + * or RestoreGUCState(), where we are trying to load some other process's + * GUC settings into a new process. + * + * Return value: + * +1: the value is valid and was successfully applied. + * 0: the name or value is invalid (but see below). + * -1: the value was not applied because of context, priority, or changeVal. + * + * If there is an error (non-existing option, invalid value) then an + * ereport(ERROR) is thrown *unless* this is called for a source for which + * we don't want an ERROR (currently, those are defaults, the config file, + * and per-database or per-user settings, as well as callers who specify + * a less-than-ERROR elevel). In those cases we write a suitable error + * message via ereport() and return 0. + * + * See also SetConfigOption for an external interface. + */ +int +set_config_option(const char *name, const char *value, + GucContext context, GucSource source, + GucAction action, bool changeVal, int elevel, + bool is_reload) +{ + Oid srole; + + /* + * Non-interactive sources should be treated as having all privileges, + * except for PGC_S_CLIENT. Note in particular that this is true for + * pg_db_role_setting sources (PGC_S_GLOBAL etc): we assume a suitable + * privilege check was done when the pg_db_role_setting entry was made. + */ + if (source >= PGC_S_INTERACTIVE || source == PGC_S_CLIENT) + srole = GetUserId(); + else + srole = BOOTSTRAP_SUPERUSERID; + + return set_config_option_ext(name, value, + context, source, srole, + action, changeVal, elevel, + is_reload); +} + +/* + * set_config_option_ext: sets option `name' to given value. + * + * This API adds the ability to explicitly specify which role OID + * is considered to be setting the value. Most external callers can use + * set_config_option() and let it determine that based on the GucSource, + * but there are a few that are supplying a value that was determined + * in some special way and need to override the decision. Also, when + * restoring a previously-assigned value, it's important to supply the + * same role OID that set the value originally; so all guc.c callers + * that are doing that type of thing need to call this directly. + * + * Generally, srole should be GetUserId() when the source is a SQL operation, + * or BOOTSTRAP_SUPERUSERID if the source is a config file or similar. + */ +int +set_config_option_ext(const char *name, const char *value, + GucContext context, GucSource source, Oid srole, + GucAction action, bool changeVal, int elevel, + bool is_reload) +{ + struct config_generic *record; + union config_var_val newval_union; + void *newextra = NULL; + bool prohibitValueChange = false; + bool makeDefault; + + if (elevel == 0) + { + if (source == PGC_S_DEFAULT || source == PGC_S_FILE) + { + /* + * To avoid cluttering the log, only the postmaster bleats loudly + * about problems with the config file. + */ + elevel = IsUnderPostmaster ? DEBUG3 : LOG; + } + else if (source == PGC_S_GLOBAL || + source == PGC_S_DATABASE || + source == PGC_S_USER || + source == PGC_S_DATABASE_USER) + elevel = WARNING; + else + elevel = ERROR; + } + + /* + * GUC_ACTION_SAVE changes are acceptable during a parallel operation, + * because the current worker will also pop the change. We're probably + * dealing with a function having a proconfig entry. Only the function's + * body should observe the change, and peer workers do not share in the + * execution of a function call started by this worker. + * + * Other changes might need to affect other workers, so forbid them. + */ + if (IsInParallelMode() && changeVal && action != GUC_ACTION_SAVE) + ereport(elevel, + (errcode(ERRCODE_INVALID_TRANSACTION_STATE), + errmsg("cannot set parameters during a parallel operation"))); + + record = find_option(name, true, false, elevel); + if (record == NULL) + return 0; + + /* + * Check if the option can be set at this time. See guc.h for the precise + * rules. + */ + switch (record->context) + { + case PGC_INTERNAL: + if (context != PGC_INTERNAL) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed", + name))); + return 0; + } + break; + case PGC_POSTMASTER: + if (context == PGC_SIGHUP) + { + /* + * We are re-reading a PGC_POSTMASTER variable from + * postgresql.conf. We can't change the setting, so we should + * give a warning if the DBA tries to change it. However, + * because of variant formats, canonicalization by check + * hooks, etc, we can't just compare the given string directly + * to what's stored. Set a flag to check below after we have + * the final storable value. + */ + prohibitValueChange = true; + } + else if (context != PGC_POSTMASTER) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + break; + case PGC_SIGHUP: + if (context != PGC_SIGHUP && context != PGC_POSTMASTER) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed now", + name))); + return 0; + } + + /* + * Hmm, the idea of the SIGHUP context is "ought to be global, but + * can be changed after postmaster start". But there's nothing + * that prevents a crafty administrator from sending SIGHUP + * signals to individual backends only. + */ + break; + case PGC_SU_BACKEND: + if (context == PGC_BACKEND) + { + /* + * Check whether the requesting user has been granted + * privilege to set this GUC. + */ + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, srole, ACL_SET); + if (aclresult != ACLCHECK_OK) + { + /* No granted privilege */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + return 0; + } + } + /* fall through to process the same as PGC_BACKEND */ + /* FALLTHROUGH */ + case PGC_BACKEND: + if (context == PGC_SIGHUP) + { + /* + * If a PGC_BACKEND or PGC_SU_BACKEND parameter is changed in + * the config file, we want to accept the new value in the + * postmaster (whence it will propagate to + * subsequently-started backends), but ignore it in existing + * backends. This is a tad klugy, but necessary because we + * don't re-read the config file during backend start. + * + * In EXEC_BACKEND builds, this works differently: we load all + * non-default 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. 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. + */ + if (IsUnderPostmaster && !is_reload) + return -1; + } + else if (context != PGC_POSTMASTER && + context != PGC_BACKEND && + context != PGC_SU_BACKEND && + source != PGC_S_CLIENT) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be set after connection start", + name))); + return 0; + } + break; + case PGC_SUSET: + if (context == PGC_USERSET || context == PGC_BACKEND) + { + /* + * Check whether the requesting user has been granted + * privilege to set this GUC. + */ + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, srole, ACL_SET); + if (aclresult != ACLCHECK_OK) + { + /* No granted privilege */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + return 0; + } + } + break; + case PGC_USERSET: + /* always okay */ + break; + } + + /* + * Disallow changing GUC_NOT_WHILE_SEC_REST values if we are inside a + * security restriction context. We can reject this regardless of the GUC + * context or source, mainly because sources that it might be reasonable + * to override for won't be seen while inside a function. + * + * Note: variables marked GUC_NOT_WHILE_SEC_REST should usually be marked + * GUC_NO_RESET_ALL as well, because ResetAllOptions() doesn't check this. + * An exception might be made if the reset value is assumed to be "safe". + * + * Note: this flag is currently used for "session_authorization" and + * "role". We need to prohibit changing these inside a local userid + * context because when we exit it, GUC won't be notified, leaving things + * out of sync. (This could be fixed by forcing a new GUC nesting level, + * but that would change behavior in possibly-undesirable ways.) Also, we + * prohibit changing these in a security-restricted operation because + * otherwise RESET could be used to regain the session user's privileges. + */ + if (record->flags & GUC_NOT_WHILE_SEC_REST) + { + if (InLocalUserIdChange()) + { + /* + * Phrasing of this error message is historical, but it's the most + * common case. + */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-definer function", + name))); + return 0; + } + if (InSecurityRestrictedOperation()) + { + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-restricted operation", + name))); + return 0; + } + } + + /* + * Should we set reset/stacked values? (If so, the behavior is not + * transactional.) This is done either when we get a default value from + * the database's/user's/client's default settings or when we reset a + * value to its default. + */ + makeDefault = changeVal && (source <= PGC_S_OVERRIDE) && + ((value != NULL) || source == PGC_S_DEFAULT); + + /* + * Ignore attempted set if overridden by previously processed setting. + * However, if changeVal is false then plow ahead anyway since we are + * trying to find out if the value is potentially good, not actually use + * it. Also keep going if makeDefault is true, since we may want to set + * the reset/stacked values even if we can't set the variable itself. + */ + if (record->source > source) + { + if (changeVal && !makeDefault) + { + elog(DEBUG3, "\"%s\": setting ignored because previous source is higher priority", + name); + return -1; + } + changeVal = false; + } + + /* + * Evaluate value and set variable. + */ + switch (record->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) record; + +#define newval (newval_union.boolval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + newval = conf->boot_val; + if (!call_bool_check_hook(conf, &newval, &newextra, + source, elevel)) + return 0; + } + else + { + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + + if (*conf->variable != newval) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + conf->gen.source = source; + conf->gen.scontext = context; + conf->gen.srole = srole; + } + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + stack->prior.val.boolval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + break; + +#undef newval + } + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) record; + +#define newval (newval_union.intval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + newval = conf->boot_val; + if (!call_int_check_hook(conf, &newval, &newextra, + source, elevel)) + return 0; + } + else + { + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + + if (*conf->variable != newval) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + conf->gen.source = source; + conf->gen.scontext = context; + conf->gen.srole = srole; + } + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + stack->prior.val.intval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + break; + +#undef newval + } + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) record; + +#define newval (newval_union.realval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + newval = conf->boot_val; + if (!call_real_check_hook(conf, &newval, &newextra, + source, elevel)) + return 0; + } + else + { + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + + if (*conf->variable != newval) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + conf->gen.source = source; + conf->gen.scontext = context; + conf->gen.srole = srole; + } + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + stack->prior.val.realval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + break; + +#undef newval + } + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) record; + +#define newval (newval_union.stringval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + /* non-NULL boot_val must always get strdup'd */ + if (conf->boot_val != NULL) + { + newval = guc_strdup(elevel, conf->boot_val); + if (newval == NULL) + return 0; + } + else + newval = NULL; + + if (!call_string_check_hook(conf, &newval, &newextra, + source, elevel)) + { + free(newval); + return 0; + } + } + else + { + /* + * strdup not needed, since reset_val is already under + * guc.c's control + */ + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + bool newval_different; + + /* newval shouldn't be NULL, so we're a bit sloppy here */ + newval_different = (*conf->variable == NULL || + newval == NULL || + strcmp(*conf->variable, newval) != 0); + + /* Release newval, unless it's reset_val */ + if (newval && !string_field_used(conf, newval)) + free(newval); + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + + if (newval_different) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + set_string_field(conf, conf->variable, newval); + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + conf->gen.source = source; + conf->gen.scontext = context; + conf->gen.srole = srole; + } + + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + set_string_field(conf, &conf->reset_val, newval); + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + set_string_field(conf, &stack->prior.val.stringval, + newval); + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newval anywhere */ + if (newval && !string_field_used(conf, newval)) + free(newval); + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + break; + +#undef newval + } + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) record; + +#define newval (newval_union.enumval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + newval = conf->boot_val; + if (!call_enum_check_hook(conf, &newval, &newextra, + source, elevel)) + return 0; + } + else + { + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + + if (*conf->variable != newval) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + conf->gen.source = source; + conf->gen.scontext = context; + conf->gen.srole = srole; + } + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + stack->prior.val.enumval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + break; + +#undef newval + } + } + + if (changeVal && (record->flags & GUC_REPORT)) + { + record->status |= GUC_NEEDS_REPORT; + report_needed = true; + } + + return changeVal ? 1 : -1; +} + + +/* + * Set the fields for source file and line number the setting came from. + */ +static void +set_config_sourcefile(const char *name, char *sourcefile, int sourceline) +{ + struct config_generic *record; + int elevel; + + /* + * To avoid cluttering the log, only the postmaster bleats loudly about + * problems with the config file. + */ + elevel = IsUnderPostmaster ? DEBUG3 : LOG; + + record = find_option(name, true, false, elevel); + /* should not happen */ + if (record == NULL) + return; + + sourcefile = guc_strdup(elevel, sourcefile); + if (record->sourcefile) + free(record->sourcefile); + record->sourcefile = sourcefile; + record->sourceline = sourceline; +} + +/* + * Set a config option to the given value. + * + * See also set_config_option; this is just the wrapper to be called from + * outside GUC. (This function should be used when possible, because its API + * is more stable than set_config_option's.) + * + * Note: there is no support here for setting source file/line, as it + * is currently not needed. + */ +void +SetConfigOption(const char *name, const char *value, + GucContext context, GucSource source) +{ + (void) set_config_option(name, value, context, source, + GUC_ACTION_SET, true, 0, false); +} + + + +/* + * Fetch the current value of the option `name', as a string. + * + * If the option doesn't exist, return NULL if missing_ok is true (NOTE that + * this cannot be distinguished from a string variable with a NULL value!), + * otherwise throw an ereport and don't return. + * + * If restrict_privileged is true, we also enforce that only superusers and + * members of the pg_read_all_settings role can see GUC_SUPERUSER_ONLY + * variables. This should only be passed as true in user-driven calls. + * + * The string is *not* allocated for modification and is really only + * valid until the next call to configuration related functions. + */ +const char * +GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged) +{ + struct config_generic *record; + static char buffer[256]; + + record = find_option(name, false, missing_ok, ERROR); + if (record == NULL) + return NULL; + if (restrict_privileged && + (record->flags & GUC_SUPERUSER_ONLY) && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"", + name))); + + switch (record->vartype) + { + case PGC_BOOL: + return *((struct config_bool *) record)->variable ? "on" : "off"; + + case PGC_INT: + snprintf(buffer, sizeof(buffer), "%d", + *((struct config_int *) record)->variable); + return buffer; + + case PGC_REAL: + snprintf(buffer, sizeof(buffer), "%g", + *((struct config_real *) record)->variable); + return buffer; + + case PGC_STRING: + return *((struct config_string *) record)->variable; + + case PGC_ENUM: + return config_enum_lookup_by_value((struct config_enum *) record, + *((struct config_enum *) record)->variable); + } + return NULL; +} + +/* + * Get the RESET value associated with the given option. + * + * Note: this is not re-entrant, due to use of static result buffer; + * not to mention that a string variable could have its reset_val changed. + * Beware of assuming the result value is good for very long. + */ +const char * +GetConfigOptionResetString(const char *name) +{ + struct config_generic *record; + static char buffer[256]; + + record = find_option(name, false, false, ERROR); + Assert(record != NULL); + if ((record->flags & GUC_SUPERUSER_ONLY) && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"", + name))); + + switch (record->vartype) + { + case PGC_BOOL: + return ((struct config_bool *) record)->reset_val ? "on" : "off"; + + case PGC_INT: + snprintf(buffer, sizeof(buffer), "%d", + ((struct config_int *) record)->reset_val); + return buffer; + + case PGC_REAL: + snprintf(buffer, sizeof(buffer), "%g", + ((struct config_real *) record)->reset_val); + return buffer; + + case PGC_STRING: + return ((struct config_string *) record)->reset_val; + + case PGC_ENUM: + return config_enum_lookup_by_value((struct config_enum *) record, + ((struct config_enum *) record)->reset_val); + } + return NULL; +} + +/* + * Get the GUC flags associated with the given option. + * + * If the option doesn't exist, return 0 if missing_ok is true, + * otherwise throw an ereport and don't return. + */ +int +GetConfigOptionFlags(const char *name, bool missing_ok) +{ + struct config_generic *record; + + record = find_option(name, false, missing_ok, ERROR); + if (record == NULL) + return 0; + return record->flags; +} + + +/* + * flatten_set_variable_args + * Given a parsenode List as emitted by the grammar for SET, + * convert to the flat string representation used by GUC. + * + * We need to be told the name of the variable the args are for, because + * the flattening rules vary (ugh). + * + * The result is NULL if args is NIL (i.e., SET ... TO DEFAULT), otherwise + * a palloc'd string. + */ +static char * +flatten_set_variable_args(const char *name, List *args) +{ + struct config_generic *record; + int flags; + StringInfoData buf; + ListCell *l; + + /* Fast path if just DEFAULT */ + if (args == NIL) + return NULL; + + /* + * Get flags for the variable; if it's not known, use default flags. + * (Caller might throw error later, but not our business to do so here.) + */ + record = find_option(name, false, true, WARNING); + if (record) + flags = record->flags; + else + flags = 0; + + /* Complain if list input and non-list variable */ + if ((flags & GUC_LIST_INPUT) == 0 && + list_length(args) != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("SET %s takes only one argument", name))); + + initStringInfo(&buf); + + /* + * Each list member may be a plain A_Const node, or an A_Const within a + * TypeCast; the latter case is supported only for ConstInterval arguments + * (for SET TIME ZONE). + */ + foreach(l, args) + { + Node *arg = (Node *) lfirst(l); + char *val; + TypeName *typeName = NULL; + A_Const *con; + + if (l != list_head(args)) + appendStringInfoString(&buf, ", "); + + if (IsA(arg, TypeCast)) + { + TypeCast *tc = (TypeCast *) arg; + + arg = tc->arg; + typeName = tc->typeName; + } + + if (!IsA(arg, A_Const)) + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg)); + con = (A_Const *) arg; + + switch (nodeTag(&con->val)) + { + case T_Integer: + appendStringInfo(&buf, "%d", intVal(&con->val)); + break; + case T_Float: + /* represented as a string, so just copy it */ + appendStringInfoString(&buf, castNode(Float, &con->val)->fval); + break; + case T_String: + val = strVal(&con->val); + if (typeName != NULL) + { + /* + * Must be a ConstInterval argument for TIME ZONE. Coerce + * to interval and back to normalize the value and account + * for any typmod. + */ + Oid typoid; + int32 typmod; + Datum interval; + char *intervalout; + + typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod); + Assert(typoid == INTERVALOID); + + interval = + DirectFunctionCall3(interval_in, + CStringGetDatum(val), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(typmod)); + + intervalout = + DatumGetCString(DirectFunctionCall1(interval_out, + interval)); + appendStringInfo(&buf, "INTERVAL '%s'", intervalout); + } + else + { + /* + * Plain string literal or identifier. For quote mode, + * quote it if it's not a vanilla identifier. + */ + if (flags & GUC_LIST_QUOTE) + appendStringInfoString(&buf, quote_identifier(val)); + else + appendStringInfoString(&buf, val); + } + break; + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(&con->val)); + break; + } + } + + return buf.data; +} + +/* + * Write updated configuration parameter values into a temporary file. + * This function traverses the list of parameters and quotes the string + * values before writing them. + */ +static void +write_auto_conf_file(int fd, const char *filename, ConfigVariable *head) +{ + StringInfoData buf; + ConfigVariable *item; + + initStringInfo(&buf); + + /* Emit file header containing warning comment */ + appendStringInfoString(&buf, "# Do not edit this file manually!\n"); + appendStringInfoString(&buf, "# It will be overwritten by the ALTER SYSTEM command.\n"); + + 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, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", filename))); + } + + /* Emit each parameter, properly quoting the value */ + for (item = head; item != NULL; item = item->next) + { + char *escaped; + + resetStringInfo(&buf); + + appendStringInfoString(&buf, item->name); + appendStringInfoString(&buf, " = '"); + + escaped = escape_single_quotes_ascii(item->value); + if (!escaped) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + appendStringInfoString(&buf, escaped); + free(escaped); + + appendStringInfoString(&buf, "'\n"); + + 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, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", filename))); + } + } + + /* fsync before considering the write to be successful */ + if (pg_fsync(fd) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", filename))); + + pfree(buf.data); +} + +/* + * Update the given list of configuration parameters, adding, replacing + * or deleting the entry for item "name" (delete if "value" == NULL). + */ +static void +replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p, + const char *name, const char *value) +{ + ConfigVariable *item, + *next, + *prev = NULL; + + /* + * Remove any existing match(es) for "name". Normally there'd be at most + * one, but if external tools have modified the config file, there could + * be more. + */ + for (item = *head_p; item != NULL; item = next) + { + next = item->next; + if (guc_name_compare(item->name, name) == 0) + { + /* found a match, delete it */ + if (prev) + prev->next = next; + else + *head_p = next; + if (next == NULL) + *tail_p = prev; + + pfree(item->name); + pfree(item->value); + pfree(item->filename); + pfree(item); + } + else + prev = item; + } + + /* Done if we're trying to delete it */ + if (value == NULL) + return; + + /* OK, append a new entry */ + item = palloc(sizeof *item); + item->name = pstrdup(name); + item->value = pstrdup(value); + item->errmsg = NULL; + item->filename = pstrdup(""); /* new item has no location */ + item->sourceline = 0; + item->ignore = false; + item->applied = false; + item->next = NULL; + + if (*head_p == NULL) + *head_p = item; + else + (*tail_p)->next = item; + *tail_p = item; +} + + +/* + * Execute ALTER SYSTEM statement. + * + * Read the old PG_AUTOCONF_FILENAME file, merge in the new variable value, + * and write out an updated file. If the command is ALTER SYSTEM RESET ALL, + * we can skip reading the old file and just write an empty file. + * + * An LWLock is used to serialize updates of the configuration file. + * + * In case of an error, we leave the original automatic + * configuration file (PG_AUTOCONF_FILENAME) intact. + */ +void +AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) +{ + char *name; + char *value; + bool resetall = false; + ConfigVariable *head = NULL; + ConfigVariable *tail = NULL; + volatile int Tmpfd; + char AutoConfFileName[MAXPGPATH]; + char AutoConfTmpFileName[MAXPGPATH]; + + /* + * Extract statement arguments + */ + name = altersysstmt->setstmt->name; + + switch (altersysstmt->setstmt->kind) + { + case VAR_SET_VALUE: + value = ExtractSetVariableArgs(altersysstmt->setstmt); + break; + + case VAR_SET_DEFAULT: + case VAR_RESET: + value = NULL; + break; + + case VAR_RESET_ALL: + value = NULL; + resetall = true; + break; + + default: + elog(ERROR, "unrecognized alter system stmt type: %d", + altersysstmt->setstmt->kind); + break; + } + + /* + * Check permission to run ALTER SYSTEM on the target variable + */ + if (!superuser()) + { + if (resetall) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to perform ALTER SYSTEM RESET ALL"))); + else + { + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, GetUserId(), + ACL_ALTER_SYSTEM); + if (aclresult != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + } + } + + /* + * Unless it's RESET_ALL, validate the target variable and value + */ + if (!resetall) + { + struct config_generic *record; + + record = find_option(name, false, false, ERROR); + Assert(record != NULL); + + /* + * Don't allow parameters that can't be set in configuration files to + * be set in PG_AUTOCONF_FILENAME file. + */ + if ((record->context == PGC_INTERNAL) || + (record->flags & GUC_DISALLOW_IN_FILE) || + (record->flags & GUC_DISALLOW_IN_AUTO_FILE)) + ereport(ERROR, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed", + name))); + + /* + * If a value is specified, verify that it's sane. + */ + if (value) + { + union config_var_val newval; + void *newextra = NULL; + + /* Check that it's acceptable for the indicated parameter */ + if (!parse_and_validate_value(record, name, value, + PGC_S_FILE, ERROR, + &newval, &newextra)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": \"%s\"", + name, value))); + + if (record->vartype == PGC_STRING && newval.stringval != NULL) + free(newval.stringval); + if (newextra) + free(newextra); + + /* + * We must also reject values containing newlines, because the + * grammar for config files doesn't support embedded newlines in + * string literals. + */ + if (strchr(value, '\n')) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter value for ALTER SYSTEM must not contain a newline"))); + } + } + + /* + * PG_AUTOCONF_FILENAME and its corresponding temporary file are always in + * the data directory, so we can reference them by simple relative paths. + */ + snprintf(AutoConfFileName, sizeof(AutoConfFileName), "%s", + PG_AUTOCONF_FILENAME); + snprintf(AutoConfTmpFileName, sizeof(AutoConfTmpFileName), "%s.%s", + AutoConfFileName, + "tmp"); + + /* + * Only one backend is allowed to operate on PG_AUTOCONF_FILENAME at a + * time. Use AutoFileLock to ensure that. We must hold the lock while + * reading the old file contents. + */ + LWLockAcquire(AutoFileLock, LW_EXCLUSIVE); + + /* + * If we're going to reset everything, then no need to open or parse the + * old file. We'll just write out an empty list. + */ + if (!resetall) + { + struct stat st; + + if (stat(AutoConfFileName, &st) == 0) + { + /* open old file PG_AUTOCONF_FILENAME */ + FILE *infile; + + infile = AllocateFile(AutoConfFileName, "r"); + if (infile == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + AutoConfFileName))); + + /* parse it */ + if (!ParseConfigFp(infile, AutoConfFileName, 0, LOG, &head, &tail)) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not parse contents of file \"%s\"", + AutoConfFileName))); + + FreeFile(infile); + } + + /* + * Now, replace any existing entry with the new value, or add it if + * not present. + */ + replace_auto_config_value(&head, &tail, name, value); + } + + /* + * Invoke the post-alter hook for setting this GUC variable. GUCs + * typically do not have corresponding entries in pg_parameter_acl, so we + * call the hook using the name rather than a potentially-non-existent + * OID. Nonetheless, we pass ParameterAclRelationId so that this call + * context can be distinguished from others. (Note that "name" will be + * NULL in the RESET ALL case.) + * + * We do this here rather than at the end, because ALTER SYSTEM is not + * transactional. If the hook aborts our transaction, it will be cleaner + * to do so before we touch any files. + */ + InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, name, + ACL_ALTER_SYSTEM, + altersysstmt->setstmt->kind, + false); + + /* + * 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); + 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_auto_conf_file(Tmpfd, AutoConfTmpFileName, head); + + /* Close before renaming; may be required on some platforms */ + close(Tmpfd); + Tmpfd = -1; + + /* + * As the rename is atomic operation, if any problem occurs after this + * at worst it can lose the parameters set by last ALTER SYSTEM + * command. + */ + durable_rename(AutoConfTmpFileName, AutoConfFileName, ERROR); + } + PG_CATCH(); + { + /* Close file first, else unlink might fail on some platforms */ + if (Tmpfd >= 0) + close(Tmpfd); + + /* Unlink, but ignore any error */ + (void) unlink(AutoConfTmpFileName); + + PG_RE_THROW(); + } + PG_END_TRY(); + + FreeConfigVariables(head); + + LWLockRelease(AutoFileLock); +} + +/* + * SET command + */ +void +ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel) +{ + GucAction action = stmt->is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET; + + /* + * Workers synchronize these parameters at the start of the parallel + * operation; then, we block SET during the operation. + */ + if (IsInParallelMode()) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TRANSACTION_STATE), + errmsg("cannot set parameters during a parallel operation"))); + + switch (stmt->kind) + { + case VAR_SET_VALUE: + case VAR_SET_CURRENT: + if (stmt->is_local) + WarnNoTransactionBlock(isTopLevel, "SET LOCAL"); + (void) set_config_option(stmt->name, + ExtractSetVariableArgs(stmt), + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + action, true, 0, false); + break; + case VAR_SET_MULTI: + + /* + * Special-case SQL syntaxes. The TRANSACTION and SESSION + * CHARACTERISTICS cases effectively set more than one variable + * per statement. TRANSACTION SNAPSHOT only takes one argument, + * but we put it here anyway since it's a special case and not + * related to any GUC variable. + */ + if (strcmp(stmt->name, "TRANSACTION") == 0) + { + ListCell *head; + + WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION"); + + foreach(head, stmt->args) + { + DefElem *item = (DefElem *) lfirst(head); + + if (strcmp(item->defname, "transaction_isolation") == 0) + SetPGVariable("transaction_isolation", + list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_read_only") == 0) + SetPGVariable("transaction_read_only", + list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_deferrable") == 0) + SetPGVariable("transaction_deferrable", + list_make1(item->arg), stmt->is_local); + else + elog(ERROR, "unexpected SET TRANSACTION element: %s", + item->defname); + } + } + else if (strcmp(stmt->name, "SESSION CHARACTERISTICS") == 0) + { + ListCell *head; + + foreach(head, stmt->args) + { + DefElem *item = (DefElem *) lfirst(head); + + if (strcmp(item->defname, "transaction_isolation") == 0) + SetPGVariable("default_transaction_isolation", + list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_read_only") == 0) + SetPGVariable("default_transaction_read_only", + list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_deferrable") == 0) + SetPGVariable("default_transaction_deferrable", + list_make1(item->arg), stmt->is_local); + else + elog(ERROR, "unexpected SET SESSION element: %s", + item->defname); + } + } + else if (strcmp(stmt->name, "TRANSACTION SNAPSHOT") == 0) + { + A_Const *con = linitial_node(A_Const, stmt->args); + + if (stmt->is_local) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SET LOCAL TRANSACTION SNAPSHOT is not implemented"))); + + WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION"); + ImportSnapshot(strVal(&con->val)); + } + else + elog(ERROR, "unexpected SET MULTI element: %s", + stmt->name); + break; + case VAR_SET_DEFAULT: + if (stmt->is_local) + WarnNoTransactionBlock(isTopLevel, "SET LOCAL"); + /* fall through */ + case VAR_RESET: + if (strcmp(stmt->name, "transaction_isolation") == 0) + WarnNoTransactionBlock(isTopLevel, "RESET TRANSACTION"); + + (void) set_config_option(stmt->name, + NULL, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + action, true, 0, false); + break; + case VAR_RESET_ALL: + ResetAllOptions(); + break; + } + + /* Invoke the post-alter hook for setting this GUC variable, by name. */ + InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, stmt->name, + ACL_SET, stmt->kind, false); +} + +/* + * Get the value to assign for a VariableSetStmt, or NULL if it's RESET. + * The result is palloc'd. + * + * This is exported for use by actions such as ALTER ROLE SET. + */ +char * +ExtractSetVariableArgs(VariableSetStmt *stmt) +{ + switch (stmt->kind) + { + case VAR_SET_VALUE: + return flatten_set_variable_args(stmt->name, stmt->args); + case VAR_SET_CURRENT: + return GetConfigOptionByName(stmt->name, NULL, false); + default: + return NULL; + } +} + +/* + * SetPGVariable - SET command exported as an easily-C-callable function. + * + * This provides access to SET TO value, as well as SET TO DEFAULT (expressed + * by passing args == NIL), but not SET FROM CURRENT functionality. + */ +void +SetPGVariable(const char *name, List *args, bool is_local) +{ + char *argstring = flatten_set_variable_args(name, args); + + /* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */ + (void) set_config_option(name, + argstring, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET, + true, 0, false); +} + +/* + * SET command wrapped as a SQL callable function. + */ +Datum +set_config_by_name(PG_FUNCTION_ARGS) +{ + char *name; + char *value; + char *new_value; + bool is_local; + + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("SET requires parameter name"))); + + /* Get the GUC variable name */ + name = TextDatumGetCString(PG_GETARG_DATUM(0)); + + /* Get the desired value or set to NULL for a reset request */ + if (PG_ARGISNULL(1)) + value = NULL; + else + value = TextDatumGetCString(PG_GETARG_DATUM(1)); + + /* + * Get the desired state of is_local. Default to false if provided value + * is NULL + */ + if (PG_ARGISNULL(2)) + is_local = false; + else + is_local = PG_GETARG_BOOL(2); + + /* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */ + (void) set_config_option(name, + value, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET, + true, 0, false); + + /* get the new current value */ + new_value = GetConfigOptionByName(name, NULL, false); + + /* Convert return string to text */ + PG_RETURN_TEXT_P(cstring_to_text(new_value)); +} + + +/* + * Common code for DefineCustomXXXVariable subroutines: allocate the + * new variable's config struct and fill in generic fields. + */ +static struct config_generic * +init_custom_variable(const char *name, + const char *short_desc, + const char *long_desc, + GucContext context, + int flags, + enum config_type type, + size_t sz) +{ + struct config_generic *gen; + + /* + * Only allow custom PGC_POSTMASTER variables to be created during shared + * library preload; any later than that, we can't ensure that the value + * doesn't change after startup. This is a fatal elog if it happens; just + * erroring out isn't safe because we don't know what the calling loadable + * module might already have hooked into. + */ + if (context == PGC_POSTMASTER && + !process_shared_preload_libraries_in_progress) + elog(FATAL, "cannot create PGC_POSTMASTER variables after startup"); + + /* + * We can't support custom GUC_LIST_QUOTE variables, because the wrong + * things would happen if such a variable were set or pg_dump'd when the + * defining extension isn't loaded. Again, treat this as fatal because + * the loadable module may be partly initialized already. + */ + if (flags & GUC_LIST_QUOTE) + elog(FATAL, "extensions cannot define GUC_LIST_QUOTE variables"); + + /* + * Before pljava commit 398f3b876ed402bdaec8bc804f29e2be95c75139 + * (2015-12-15), two of that module's PGC_USERSET variables facilitated + * trivial escalation to superuser privileges. Restrict the variables to + * protect sites that have yet to upgrade pljava. + */ + if (context == PGC_USERSET && + (strcmp(name, "pljava.classpath") == 0 || + strcmp(name, "pljava.vmoptions") == 0)) + context = PGC_SUSET; + + gen = (struct config_generic *) guc_malloc(ERROR, sz); + memset(gen, 0, sz); + + gen->name = guc_strdup(ERROR, name); + gen->context = context; + gen->group = CUSTOM_OPTIONS; + gen->short_desc = short_desc; + gen->long_desc = long_desc; + gen->flags = flags; + gen->vartype = type; + + return gen; +} + +/* + * Common code for DefineCustomXXXVariable subroutines: insert the new + * variable into the GUC variable array, replacing any placeholder. + */ +static void +define_custom_variable(struct config_generic *variable) +{ + const char *name = variable->name; + const char **nameAddr = &name; + struct config_string *pHolder; + struct config_generic **res; + + /* + * See if there's a placeholder by the same name. + */ + res = (struct config_generic **) bsearch((void *) &nameAddr, + (void *) guc_variables, + num_guc_variables, + sizeof(struct config_generic *), + guc_var_compare); + if (res == NULL) + { + /* + * No placeholder to replace, so we can just add it ... but first, + * make sure it's initialized to its default value. + */ + InitializeOneGUCOption(variable); + add_guc_variable(variable, ERROR); + return; + } + + /* + * This better be a placeholder + */ + if (((*res)->flags & GUC_CUSTOM_PLACEHOLDER) == 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("attempt to redefine parameter \"%s\"", name))); + + Assert((*res)->vartype == PGC_STRING); + pHolder = (struct config_string *) (*res); + + /* + * First, set the variable to its default value. We must do this even + * though we intend to immediately apply a new value, since it's possible + * that the new value is invalid. + */ + InitializeOneGUCOption(variable); + + /* + * Replace the placeholder. We aren't changing the name, so no re-sorting + * is necessary + */ + *res = variable; + + /* + * Assign the string value(s) stored in the placeholder to the real + * variable. Essentially, we need to duplicate all the active and stacked + * values, but with appropriate validation and datatype adjustment. + * + * If an assignment fails, we report a WARNING and keep going. We don't + * want to throw ERROR for bad values, because it'd bollix the add-on + * module that's presumably halfway through getting loaded. In such cases + * the default or previous state will become active instead. + */ + + /* First, apply the reset value if any */ + if (pHolder->reset_val) + (void) set_config_option_ext(name, pHolder->reset_val, + pHolder->gen.reset_scontext, + pHolder->gen.reset_source, + pHolder->gen.reset_srole, + GUC_ACTION_SET, true, WARNING, false); + /* That should not have resulted in stacking anything */ + Assert(variable->stack == NULL); + + /* Now, apply current and stacked values, in the order they were stacked */ + reapply_stacked_values(variable, pHolder, pHolder->gen.stack, + *(pHolder->variable), + pHolder->gen.scontext, pHolder->gen.source, + pHolder->gen.srole); + + /* Also copy over any saved source-location information */ + if (pHolder->gen.sourcefile) + set_config_sourcefile(name, pHolder->gen.sourcefile, + pHolder->gen.sourceline); + + /* + * Free up as much as we conveniently can of the placeholder structure. + * (This neglects any stack items, so it's possible for some memory to be + * leaked. Since this can only happen once per session per variable, it + * doesn't seem worth spending much code on.) + */ + set_string_field(pHolder, pHolder->variable, NULL); + set_string_field(pHolder, &pHolder->reset_val, NULL); + + free(pHolder); +} + +/* + * Recursive subroutine for define_custom_variable: reapply non-reset values + * + * We recurse so that the values are applied in the same order as originally. + * At each recursion level, apply the upper-level value (passed in) in the + * fashion implied by the stack entry. + */ +static void +reapply_stacked_values(struct config_generic *variable, + struct config_string *pHolder, + GucStack *stack, + const char *curvalue, + GucContext curscontext, GucSource cursource, + Oid cursrole) +{ + const char *name = variable->name; + GucStack *oldvarstack = variable->stack; + + if (stack != NULL) + { + /* First, recurse, so that stack items are processed bottom to top */ + reapply_stacked_values(variable, pHolder, stack->prev, + stack->prior.val.stringval, + stack->scontext, stack->source, stack->srole); + + /* See how to apply the passed-in value */ + switch (stack->state) + { + case GUC_SAVE: + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_SAVE, true, + WARNING, false); + break; + + case GUC_SET: + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_SET, true, + WARNING, false); + break; + + case GUC_LOCAL: + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_LOCAL, true, + WARNING, false); + break; + + case GUC_SET_LOCAL: + /* first, apply the masked value as SET */ + (void) set_config_option_ext(name, stack->masked.val.stringval, + stack->masked_scontext, + PGC_S_SESSION, + stack->masked_srole, + GUC_ACTION_SET, true, + WARNING, false); + /* then apply the current value as LOCAL */ + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_LOCAL, true, + WARNING, false); + break; + } + + /* If we successfully made a stack entry, adjust its nest level */ + if (variable->stack != oldvarstack) + variable->stack->nest_level = stack->nest_level; + } + else + { + /* + * We are at the end of the stack. If the active/previous value is + * different from the reset value, it must represent a previously + * committed session value. Apply it, and then drop the stack entry + * that set_config_option will have created under the impression that + * this is to be just a transactional assignment. (We leak the stack + * entry.) + */ + if (curvalue != pHolder->reset_val || + curscontext != pHolder->gen.reset_scontext || + cursource != pHolder->gen.reset_source || + cursrole != pHolder->gen.reset_srole) + { + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_SET, true, WARNING, false); + variable->stack = NULL; + } + } +} + +/* + * Functions for extensions to call to define their custom GUC variables. + */ +void +DefineCustomBoolVariable(const char *name, + const char *short_desc, + const char *long_desc, + bool *valueAddr, + bool bootValue, + GucContext context, + int flags, + GucBoolCheckHook check_hook, + GucBoolAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_bool *var; + + var = (struct config_bool *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_BOOL, sizeof(struct config_bool)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->reset_val = bootValue; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +void +DefineCustomIntVariable(const char *name, + const char *short_desc, + const char *long_desc, + int *valueAddr, + int bootValue, + int minValue, + int maxValue, + GucContext context, + int flags, + GucIntCheckHook check_hook, + GucIntAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_int *var; + + var = (struct config_int *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_INT, sizeof(struct config_int)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->reset_val = bootValue; + var->min = minValue; + var->max = maxValue; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +void +DefineCustomRealVariable(const char *name, + const char *short_desc, + const char *long_desc, + double *valueAddr, + double bootValue, + double minValue, + double maxValue, + GucContext context, + int flags, + GucRealCheckHook check_hook, + GucRealAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_real *var; + + var = (struct config_real *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_REAL, sizeof(struct config_real)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->reset_val = bootValue; + var->min = minValue; + var->max = maxValue; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +void +DefineCustomStringVariable(const char *name, + const char *short_desc, + const char *long_desc, + char **valueAddr, + const char *bootValue, + GucContext context, + int flags, + GucStringCheckHook check_hook, + GucStringAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_string *var; + + var = (struct config_string *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_STRING, sizeof(struct config_string)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +void +DefineCustomEnumVariable(const char *name, + const char *short_desc, + const char *long_desc, + int *valueAddr, + int bootValue, + const struct config_enum_entry *options, + GucContext context, + int flags, + GucEnumCheckHook check_hook, + GucEnumAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_enum *var; + + var = (struct config_enum *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_ENUM, sizeof(struct config_enum)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->reset_val = bootValue; + var->options = options; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +/* + * Mark the given GUC prefix as "reserved". + * + * This deletes any existing placeholders matching the prefix, + * and then prevents new ones from being created. + * Extensions should call this after they've defined all of their custom + * GUCs, to help catch misspelled config-file entries. + */ +void +MarkGUCPrefixReserved(const char *className) +{ + int classLen = strlen(className); + int i; + MemoryContext oldcontext; + + /* + * Check for existing placeholders. We must actually remove invalid + * placeholders, else future parallel worker startups will fail. (We + * don't bother trying to free associated memory, since this shouldn't + * happen often.) + */ + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *var = guc_variables[i]; + + if ((var->flags & GUC_CUSTOM_PLACEHOLDER) != 0 && + strncmp(className, var->name, classLen) == 0 && + var->name[classLen] == GUC_QUALIFIER_SEPARATOR) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid configuration parameter name \"%s\", removing it", + var->name), + errdetail("\"%s\" is now a reserved prefix.", + className))); + num_guc_variables--; + memmove(&guc_variables[i], &guc_variables[i + 1], + (num_guc_variables - i) * sizeof(struct config_generic *)); + i--; + } + } + + /* And remember the name so we can prevent future mistakes. */ + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + reserved_class_prefix = lappend(reserved_class_prefix, pstrdup(className)); + MemoryContextSwitchTo(oldcontext); +} + + +/* + * SHOW command + */ +void +GetPGVariable(const char *name, DestReceiver *dest) +{ + if (guc_name_compare(name, "all") == 0) + ShowAllGUCConfig(dest); + else + ShowGUCConfigOption(name, dest); +} + +TupleDesc +GetPGVariableResultDesc(const char *name) +{ + TupleDesc tupdesc; + + if (guc_name_compare(name, "all") == 0) + { + /* need a tuple descriptor representing three TEXT columns */ + tupdesc = CreateTemplateTupleDesc(3); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "description", + TEXTOID, -1, 0); + } + else + { + const char *varname; + + /* Get the canonical spelling of name */ + (void) GetConfigOptionByName(name, &varname, false); + + /* need a tuple descriptor representing a single TEXT column */ + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, varname, + TEXTOID, -1, 0); + } + return tupdesc; +} + + +/* + * SHOW command + */ +static void +ShowGUCConfigOption(const char *name, DestReceiver *dest) +{ + TupOutputState *tstate; + TupleDesc tupdesc; + const char *varname; + char *value; + + /* Get the value and canonical spelling of name */ + value = GetConfigOptionByName(name, &varname, false); + + /* need a tuple descriptor representing a single TEXT column */ + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, varname, + TEXTOID, -1, 0); + + /* prepare for projection of tuples */ + tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); + + /* Send it */ + do_text_output_oneline(tstate, value); + + end_tup_output(tstate); +} + +/* + * SHOW ALL command + */ +static void +ShowAllGUCConfig(DestReceiver *dest) +{ + int i; + TupOutputState *tstate; + TupleDesc tupdesc; + Datum values[3]; + bool isnull[3] = {false, false, false}; + + /* need a tuple descriptor representing three TEXT columns */ + tupdesc = CreateTemplateTupleDesc(3); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "setting", + TEXTOID, -1, 0); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "description", + TEXTOID, -1, 0); + + /* prepare for projection of tuples */ + tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); + + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *conf = guc_variables[i]; + char *setting; + + if ((conf->flags & GUC_NO_SHOW_ALL) || + ((conf->flags & GUC_SUPERUSER_ONLY) && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))) + continue; + + /* assign to the values array */ + values[0] = PointerGetDatum(cstring_to_text(conf->name)); + + setting = _ShowOption(conf, true); + if (setting) + { + values[1] = PointerGetDatum(cstring_to_text(setting)); + isnull[1] = false; + } + else + { + values[1] = PointerGetDatum(NULL); + isnull[1] = true; + } + + if (conf->short_desc) + { + values[2] = PointerGetDatum(cstring_to_text(conf->short_desc)); + isnull[2] = false; + } + else + { + values[2] = PointerGetDatum(NULL); + isnull[2] = true; + } + + /* send it to dest */ + do_tup_output(tstate, values, isnull); + + /* clean up */ + pfree(DatumGetPointer(values[0])); + if (setting) + { + pfree(setting); + pfree(DatumGetPointer(values[1])); + } + if (conf->short_desc) + pfree(DatumGetPointer(values[2])); + } + + end_tup_output(tstate); +} + +/* + * Return an array of modified GUC options to show in EXPLAIN. + * + * We only report options related to query planning (marked with GUC_EXPLAIN), + * with values different from their built-in defaults. + */ +struct config_generic ** +get_explain_guc_options(int *num) +{ + struct config_generic **result; + + *num = 0; + + /* + * While only a fraction of all the GUC variables are marked GUC_EXPLAIN, + * it doesn't seem worth dynamically resizing this array. + */ + result = palloc(sizeof(struct config_generic *) * num_guc_variables); + + for (int i = 0; i < num_guc_variables; i++) + { + bool modified; + struct config_generic *conf = guc_variables[i]; + + /* return only parameters marked for inclusion in explain */ + if (!(conf->flags & GUC_EXPLAIN)) + continue; + + /* return only options visible to the current user */ + if ((conf->flags & GUC_NO_SHOW_ALL) || + ((conf->flags & GUC_SUPERUSER_ONLY) && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))) + continue; + + /* return only options that are different from their boot values */ + modified = false; + + switch (conf->vartype) + { + case PGC_BOOL: + { + struct config_bool *lconf = (struct config_bool *) conf; + + modified = (lconf->boot_val != *(lconf->variable)); + } + break; + + case PGC_INT: + { + struct config_int *lconf = (struct config_int *) conf; + + modified = (lconf->boot_val != *(lconf->variable)); + } + break; + + case PGC_REAL: + { + struct config_real *lconf = (struct config_real *) conf; + + modified = (lconf->boot_val != *(lconf->variable)); + } + break; + + case PGC_STRING: + { + struct config_string *lconf = (struct config_string *) conf; + + if (lconf->boot_val == NULL && + *lconf->variable == NULL) + modified = false; + else if (lconf->boot_val == NULL || + *lconf->variable == NULL) + modified = true; + else + modified = (strcmp(lconf->boot_val, *(lconf->variable)) != 0); + } + break; + + case PGC_ENUM: + { + struct config_enum *lconf = (struct config_enum *) conf; + + modified = (lconf->boot_val != *(lconf->variable)); + } + break; + + default: + elog(ERROR, "unexpected GUC type: %d", conf->vartype); + } + + if (!modified) + continue; + + /* OK, report it */ + result[*num] = conf; + *num = *num + 1; + } + + return result; +} + +/* + * Return GUC variable value by name; optionally return canonical form of + * name. If the GUC is unset, then throw an error unless missing_ok is true, + * in which case return NULL. Return value is palloc'd (but *varname isn't). + */ +char * +GetConfigOptionByName(const char *name, const char **varname, bool missing_ok) +{ + struct config_generic *record; + + record = find_option(name, false, missing_ok, ERROR); + if (record == NULL) + { + if (varname) + *varname = NULL; + return NULL; + } + + if ((record->flags & GUC_SUPERUSER_ONLY) && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"", + name))); + + if (varname) + *varname = record->name; + + return _ShowOption(record, true); +} + +/* + * Return some of the flags associated to the specified GUC in the shape of + * a text array, and NULL if it does not exist. An empty array is returned + * if the GUC exists without any meaningful flags to show. + */ +Datum +pg_settings_get_flags(PG_FUNCTION_ARGS) +{ +#define MAX_GUC_FLAGS 5 + char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); + struct config_generic *record; + int cnt = 0; + Datum flags[MAX_GUC_FLAGS]; + ArrayType *a; + + record = find_option(varname, false, true, ERROR); + + /* return NULL if no such variable */ + if (record == NULL) + PG_RETURN_NULL(); + + if (record->flags & GUC_EXPLAIN) + flags[cnt++] = CStringGetTextDatum("EXPLAIN"); + if (record->flags & GUC_NO_RESET_ALL) + flags[cnt++] = CStringGetTextDatum("NO_RESET_ALL"); + if (record->flags & GUC_NO_SHOW_ALL) + flags[cnt++] = CStringGetTextDatum("NO_SHOW_ALL"); + if (record->flags & GUC_NOT_IN_SAMPLE) + flags[cnt++] = CStringGetTextDatum("NOT_IN_SAMPLE"); + if (record->flags & GUC_RUNTIME_COMPUTED) + flags[cnt++] = CStringGetTextDatum("RUNTIME_COMPUTED"); + + Assert(cnt <= MAX_GUC_FLAGS); + + /* Returns the record as Datum */ + a = construct_array(flags, cnt, TEXTOID, -1, false, TYPALIGN_INT); + PG_RETURN_ARRAYTYPE_P(a); +} + +/* + * Return GUC variable value by variable number; optionally return canonical + * form of name. Return value is palloc'd. + */ +void +GetConfigOptionByNum(int varnum, const char **values, bool *noshow) +{ + char buffer[256]; + struct config_generic *conf; + + /* check requested variable number valid */ + Assert((varnum >= 0) && (varnum < num_guc_variables)); + + conf = guc_variables[varnum]; + + if (noshow) + { + if ((conf->flags & GUC_NO_SHOW_ALL) || + ((conf->flags & GUC_SUPERUSER_ONLY) && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))) + *noshow = true; + else + *noshow = false; + } + + /* first get the generic attributes */ + + /* name */ + values[0] = conf->name; + + /* setting: use _ShowOption in order to avoid duplicating the logic */ + values[1] = _ShowOption(conf, false); + + /* unit, if any (NULL is fine) */ + values[2] = get_config_unit_name(conf->flags); + + /* group */ + values[3] = _(config_group_names[conf->group]); + + /* short_desc */ + values[4] = conf->short_desc != NULL ? _(conf->short_desc) : NULL; + + /* extra_desc */ + values[5] = conf->long_desc != NULL ? _(conf->long_desc) : NULL; + + /* context */ + values[6] = GucContext_Names[conf->context]; + + /* vartype */ + values[7] = config_type_names[conf->vartype]; + + /* source */ + values[8] = GucSource_Names[conf->source]; + + /* now get the type specific attributes */ + switch (conf->vartype) + { + case PGC_BOOL: + { + struct config_bool *lconf = (struct config_bool *) conf; + + /* min_val */ + values[9] = NULL; + + /* max_val */ + values[10] = NULL; + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + values[12] = pstrdup(lconf->boot_val ? "on" : "off"); + + /* reset_val */ + values[13] = pstrdup(lconf->reset_val ? "on" : "off"); + } + break; + + case PGC_INT: + { + struct config_int *lconf = (struct config_int *) conf; + + /* min_val */ + snprintf(buffer, sizeof(buffer), "%d", lconf->min); + values[9] = pstrdup(buffer); + + /* max_val */ + snprintf(buffer, sizeof(buffer), "%d", lconf->max); + values[10] = pstrdup(buffer); + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + snprintf(buffer, sizeof(buffer), "%d", lconf->boot_val); + values[12] = pstrdup(buffer); + + /* reset_val */ + snprintf(buffer, sizeof(buffer), "%d", lconf->reset_val); + values[13] = pstrdup(buffer); + } + break; + + case PGC_REAL: + { + struct config_real *lconf = (struct config_real *) conf; + + /* min_val */ + snprintf(buffer, sizeof(buffer), "%g", lconf->min); + values[9] = pstrdup(buffer); + + /* max_val */ + snprintf(buffer, sizeof(buffer), "%g", lconf->max); + values[10] = pstrdup(buffer); + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + snprintf(buffer, sizeof(buffer), "%g", lconf->boot_val); + values[12] = pstrdup(buffer); + + /* reset_val */ + snprintf(buffer, sizeof(buffer), "%g", lconf->reset_val); + values[13] = pstrdup(buffer); + } + break; + + case PGC_STRING: + { + struct config_string *lconf = (struct config_string *) conf; + + /* min_val */ + values[9] = NULL; + + /* max_val */ + values[10] = NULL; + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + if (lconf->boot_val == NULL) + values[12] = NULL; + else + values[12] = pstrdup(lconf->boot_val); + + /* reset_val */ + if (lconf->reset_val == NULL) + values[13] = NULL; + else + values[13] = pstrdup(lconf->reset_val); + } + break; + + case PGC_ENUM: + { + struct config_enum *lconf = (struct config_enum *) conf; + + /* min_val */ + values[9] = NULL; + + /* max_val */ + values[10] = NULL; + + /* enumvals */ + + /* + * NOTE! enumvals with double quotes in them are not + * supported! + */ + values[11] = config_enum_get_options((struct config_enum *) conf, + "{\"", "\"}", "\",\""); + + /* boot_val */ + values[12] = pstrdup(config_enum_lookup_by_value(lconf, + lconf->boot_val)); + + /* reset_val */ + values[13] = pstrdup(config_enum_lookup_by_value(lconf, + lconf->reset_val)); + } + break; + + default: + { + /* + * should never get here, but in case we do, set 'em to NULL + */ + + /* min_val */ + values[9] = NULL; + + /* max_val */ + values[10] = NULL; + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + values[12] = NULL; + + /* reset_val */ + values[13] = NULL; + } + break; + } + + /* + * If the setting came from a config file, set the source location. For + * security reasons, we don't show source file/line number for + * insufficiently-privileged users. + */ + if (conf->source == PGC_S_FILE && + has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)) + { + values[14] = conf->sourcefile; + snprintf(buffer, sizeof(buffer), "%d", conf->sourceline); + values[15] = pstrdup(buffer); + } + else + { + values[14] = NULL; + values[15] = NULL; + } + + values[16] = (conf->status & GUC_PENDING_RESTART) ? "t" : "f"; +} + +/* + * Return the total number of GUC variables + */ +int +GetNumConfigOptions(void) +{ + return num_guc_variables; +} + +/* + * show_config_by_name - equiv to SHOW X command but implemented as + * a function. + */ +Datum +show_config_by_name(PG_FUNCTION_ARGS) +{ + char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); + char *varval; + + /* Get the value */ + varval = GetConfigOptionByName(varname, NULL, false); + + /* Convert to text */ + PG_RETURN_TEXT_P(cstring_to_text(varval)); +} + +/* + * show_config_by_name_missing_ok - equiv to SHOW X command but implemented as + * a function. If X does not exist, suppress the error and just return NULL + * if missing_ok is true. + */ +Datum +show_config_by_name_missing_ok(PG_FUNCTION_ARGS) +{ + char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); + bool missing_ok = PG_GETARG_BOOL(1); + char *varval; + + /* Get the value */ + varval = GetConfigOptionByName(varname, NULL, missing_ok); + + /* return NULL if no such variable */ + if (varval == NULL) + PG_RETURN_NULL(); + + /* Convert to text */ + PG_RETURN_TEXT_P(cstring_to_text(varval)); +} + +/* + * show_all_settings - equiv to SHOW ALL command but implemented as + * a Table Function. + */ +#define NUM_PG_SETTINGS_ATTS 17 + +Datum +show_all_settings(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tupdesc; + int call_cntr; + int max_calls; + AttInMetadata *attinmeta; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* + * need a tuple descriptor representing NUM_PG_SETTINGS_ATTS columns + * of the appropriate types + */ + tupdesc = CreateTemplateTupleDesc(NUM_PG_SETTINGS_ATTS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "unit", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "category", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "short_desc", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "extra_desc", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "context", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "vartype", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "source", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "min_val", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "max_val", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "enumvals", + TEXTARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "boot_val", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "reset_val", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sourcefile", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 16, "sourceline", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 17, "pending_restart", + BOOLOID, -1, 0); + + /* + * Generate attribute metadata needed later to produce tuples from raw + * C strings + */ + attinmeta = TupleDescGetAttInMetadata(tupdesc); + funcctx->attinmeta = attinmeta; + + /* total number of tuples to be returned */ + funcctx->max_calls = GetNumConfigOptions(); + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + attinmeta = funcctx->attinmeta; + + if (call_cntr < max_calls) /* do when there is more left to send */ + { + char *values[NUM_PG_SETTINGS_ATTS]; + bool noshow; + HeapTuple tuple; + Datum result; + + /* + * Get the next visible GUC variable name and value + */ + do + { + GetConfigOptionByNum(call_cntr, (const char **) values, &noshow); + if (noshow) + { + /* bump the counter and get the next config setting */ + call_cntr = ++funcctx->call_cntr; + + /* make sure we haven't gone too far now */ + if (call_cntr >= max_calls) + SRF_RETURN_DONE(funcctx); + } + } while (noshow); + + /* build a tuple */ + tuple = BuildTupleFromCStrings(attinmeta, values); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + else + { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +} + +/* + * show_all_file_settings + * + * Returns a table of all parameter settings in all configuration files + * which includes the config file pathname, the line number, a sequence number + * indicating the order in which the settings were encountered, the parameter + * name and value, a bool showing if the value could be applied, and possibly + * an associated error message. (For problems such as syntax errors, the + * parameter name/value might be NULL.) + * + * Note: no filtering is done here, instead we depend on the GRANT system + * to prevent unprivileged users from accessing this function or the view + * built on top of it. + */ +Datum +show_all_file_settings(PG_FUNCTION_ARGS) +{ +#define NUM_PG_FILE_SETTINGS_ATTS 7 + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + ConfigVariable *conf; + int seqno; + + /* Scan the config files using current context as workspace */ + conf = ProcessConfigFileInternal(PGC_SIGHUP, false, DEBUG3); + + /* Build a tuplestore to return our results in */ + InitMaterializedSRF(fcinfo, 0); + + /* Process the results and create a tuplestore */ + for (seqno = 1; conf != NULL; conf = conf->next, seqno++) + { + Datum values[NUM_PG_FILE_SETTINGS_ATTS]; + bool nulls[NUM_PG_FILE_SETTINGS_ATTS]; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + /* sourcefile */ + if (conf->filename) + values[0] = PointerGetDatum(cstring_to_text(conf->filename)); + else + nulls[0] = true; + + /* sourceline (not meaningful if no sourcefile) */ + if (conf->filename) + values[1] = Int32GetDatum(conf->sourceline); + else + nulls[1] = true; + + /* seqno */ + values[2] = Int32GetDatum(seqno); + + /* name */ + if (conf->name) + values[3] = PointerGetDatum(cstring_to_text(conf->name)); + else + nulls[3] = true; + + /* setting */ + if (conf->value) + values[4] = PointerGetDatum(cstring_to_text(conf->value)); + else + nulls[4] = true; + + /* applied */ + values[5] = BoolGetDatum(conf->applied); + + /* error */ + if (conf->errmsg) + values[6] = PointerGetDatum(cstring_to_text(conf->errmsg)); + else + nulls[6] = true; + + /* shove row into tuplestore */ + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + +static char * +_ShowOption(struct config_generic *record, bool use_units) +{ + char buffer[256]; + const char *val; + + switch (record->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else + val = *conf->variable ? "on" : "off"; + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else + { + /* + * Use int64 arithmetic to avoid overflows in units + * conversion. + */ + int64 result = *conf->variable; + const char *unit; + + if (use_units && result > 0 && (record->flags & GUC_UNIT)) + convert_int_from_base_unit(result, + record->flags & GUC_UNIT, + &result, &unit); + else + unit = ""; + + snprintf(buffer, sizeof(buffer), INT64_FORMAT "%s", + result, unit); + val = buffer; + } + } + break; + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else + { + double result = *conf->variable; + const char *unit; + + if (use_units && result > 0 && (record->flags & GUC_UNIT)) + convert_real_from_base_unit(result, + record->flags & GUC_UNIT, + &result, &unit); + else + unit = ""; + + snprintf(buffer, sizeof(buffer), "%g%s", + result, unit); + val = buffer; + } + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else if (*conf->variable && **conf->variable) + val = *conf->variable; + else + val = ""; + } + break; + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else + val = config_enum_lookup_by_value(conf, *conf->variable); + } + break; + + default: + /* just to keep compiler quiet */ + val = "???"; + break; + } + + return pstrdup(val); +} + + +#ifdef EXEC_BACKEND + +/* + * These routines dump out all non-default GUC options into a binary + * file that is read by all exec'ed backends. The format is: + * + * variable name, string, null terminated + * variable value, string, null terminated + * variable sourcefile, string, null terminated (empty if none) + * variable sourceline, integer + * variable source, integer + * variable scontext, integer +* variable srole, OID + */ +static void +write_one_nondefault_variable(FILE *fp, struct config_generic *gconf) +{ + if (gconf->source == PGC_S_DEFAULT) + return; + + fprintf(fp, "%s", gconf->name); + fputc(0, fp); + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + if (*conf->variable) + fprintf(fp, "true"); + else + fprintf(fp, "false"); + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + fprintf(fp, "%d", *conf->variable); + } + break; + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + fprintf(fp, "%.17g", *conf->variable); + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + if (*conf->variable) + fprintf(fp, "%s", *conf->variable); + } + break; + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + fprintf(fp, "%s", + config_enum_lookup_by_value(conf, *conf->variable)); + } + break; + } + + fputc(0, fp); + + if (gconf->sourcefile) + fprintf(fp, "%s", gconf->sourcefile); + fputc(0, fp); + + fwrite(&gconf->sourceline, 1, sizeof(gconf->sourceline), fp); + fwrite(&gconf->source, 1, sizeof(gconf->source), fp); + fwrite(&gconf->scontext, 1, sizeof(gconf->scontext), fp); + fwrite(&gconf->srole, 1, sizeof(gconf->srole), fp); +} + +void +write_nondefault_variables(GucContext context) +{ + int elevel; + FILE *fp; + int i; + + Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP); + + elevel = (context == PGC_SIGHUP) ? LOG : ERROR; + + /* + * Open file + */ + fp = AllocateFile(CONFIG_EXEC_PARAMS_NEW, "w"); + if (!fp) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + CONFIG_EXEC_PARAMS_NEW))); + return; + } + + for (i = 0; i < num_guc_variables; i++) + { + write_one_nondefault_variable(fp, guc_variables[i]); + } + + if (FreeFile(fp)) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + CONFIG_EXEC_PARAMS_NEW))); + return; + } + + /* + * Put new file in place. This could delay on Win32, but we don't hold + * any exclusive locks. + */ + rename(CONFIG_EXEC_PARAMS_NEW, CONFIG_EXEC_PARAMS); +} + + +/* + * Read string, including null byte from file + * + * Return NULL on EOF and nothing read + */ +static char * +read_string_with_null(FILE *fp) +{ + int i = 0, + ch, + maxlen = 256; + char *str = NULL; + + do + { + if ((ch = fgetc(fp)) == EOF) + { + if (i == 0) + return NULL; + else + elog(FATAL, "invalid format of exec config params file"); + } + if (i == 0) + str = guc_malloc(FATAL, maxlen); + else if (i == maxlen) + str = guc_realloc(FATAL, str, maxlen *= 2); + str[i++] = ch; + } while (ch != 0); + + return str; +} + + +/* + * This routine loads a previous postmaster dump of its non-default + * settings. + */ +void +read_nondefault_variables(void) +{ + FILE *fp; + char *varname, + *varvalue, + *varsourcefile; + int varsourceline; + GucSource varsource; + GucContext varscontext; + Oid varsrole; + + /* + * Open file + */ + fp = AllocateFile(CONFIG_EXEC_PARAMS, "r"); + if (!fp) + { + /* File not found is fine */ + if (errno != ENOENT) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not read from file \"%s\": %m", + CONFIG_EXEC_PARAMS))); + return; + } + + for (;;) + { + struct config_generic *record; + + if ((varname = read_string_with_null(fp)) == NULL) + break; + + if ((record = find_option(varname, true, false, FATAL)) == NULL) + elog(FATAL, "failed to locate variable \"%s\" in exec config params file", varname); + + if ((varvalue = read_string_with_null(fp)) == NULL) + elog(FATAL, "invalid format of exec config params file"); + if ((varsourcefile = read_string_with_null(fp)) == NULL) + elog(FATAL, "invalid format of exec config params file"); + if (fread(&varsourceline, 1, sizeof(varsourceline), fp) != sizeof(varsourceline)) + elog(FATAL, "invalid format of exec config params file"); + if (fread(&varsource, 1, sizeof(varsource), fp) != sizeof(varsource)) + elog(FATAL, "invalid format of exec config params file"); + if (fread(&varscontext, 1, sizeof(varscontext), fp) != sizeof(varscontext)) + elog(FATAL, "invalid format of exec config params file"); + if (fread(&varsrole, 1, sizeof(varsrole), fp) != sizeof(varsrole)) + elog(FATAL, "invalid format of exec config params file"); + + (void) set_config_option_ext(varname, varvalue, + varscontext, varsource, varsrole, + GUC_ACTION_SET, true, 0, true); + if (varsourcefile[0]) + set_config_sourcefile(varname, varsourcefile, varsourceline); + + free(varname); + free(varvalue); + free(varsourcefile); + } + + FreeFile(fp); +} +#endif /* EXEC_BACKEND */ + +/* + * can_skip_gucvar: + * Decide whether SerializeGUCState can skip sending this GUC variable, + * or whether RestoreGUCState can skip resetting this GUC to default. + * + * It is somewhat magical and fragile that the same test works for both cases. + * Realize in particular that we are very likely selecting different sets of + * GUCs on the leader and worker sides! Be sure you've understood the + * comments here and in RestoreGUCState thoroughly before changing this. + */ +static bool +can_skip_gucvar(struct config_generic *gconf) +{ + /* + * We can skip GUCs that are guaranteed to have the same values in leaders + * and workers. (Note it is critical that the leader and worker have the + * same idea of which GUCs fall into this category. It's okay to consider + * context and name for this purpose, since those are unchanging + * properties of a GUC.) + * + * PGC_POSTMASTER variables always have the same value in every child of a + * particular postmaster, so the worker will certainly have the right + * value already. Likewise, PGC_INTERNAL variables are set by special + * mechanisms (if indeed they aren't compile-time constants). So we may + * always skip these. + * + * Role must be handled specially because its current value can be an + * invalid value (for instance, if someone dropped the role since we set + * it). So if we tried to serialize it normally, we might get a failure. + * We skip it here, and use another mechanism to ensure the worker has the + * right value. + * + * For all other GUCs, we skip if the GUC has its compiled-in default + * value (i.e., source == PGC_S_DEFAULT). On the leader side, this means + * we don't send GUCs that have their default values, which typically + * saves lots of work. On the worker side, this means we don't need to + * reset the GUC to default because it already has that value. See + * comments in RestoreGUCState for more info. + */ + return gconf->context == PGC_POSTMASTER || + gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT || + strcmp(gconf->name, "role") == 0; +} + +/* + * estimate_variable_size: + * Compute space needed for dumping the given GUC variable. + * + * It's OK to overestimate, but not to underestimate. + */ +static Size +estimate_variable_size(struct config_generic *gconf) +{ + Size size; + Size valsize = 0; + + /* Skippable GUCs consume zero space. */ + if (can_skip_gucvar(gconf)) + return 0; + + /* Name, plus trailing zero byte. */ + 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 %e with REALTYPE_PRECISION + * fractional digits. Account for sign, leading digit, + * decimal point, and exponent with up to 3 digits. E.g. + * -3.99329042340000021e+110 + */ + valsize = 1 + 1 + 1 + REALTYPE_PRECISION + 5; + } + break; + + case PGC_STRING: + { + struct config_string *conf = (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; + } + + /* Allow space for terminating zero-byte for value */ + size = add_size(size, valsize + 1); + + if (gconf->sourcefile) + size = add_size(size, strlen(gconf->sourcefile)); + + /* Allow space for terminating zero-byte for sourcefile */ + size = add_size(size, 1); + + /* Include line whenever file is nonempty. */ + 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)); + size = add_size(size, sizeof(gconf->srole)); + + 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); + + if (n < 0) + { + /* Shouldn't happen. Better show errno description. */ + elog(ERROR, "vsnprintf failed: %m with format string \"%s\"", fmt); + } + if (n >= *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 += 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) +{ + /* Ignore skippable GUCs. */ + 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, "%.*e", + REALTYPE_PRECISION, *conf->variable); + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + /* NULL becomes empty string, see estimate_variable_size() */ + do_serialize(destptr, maxbytes, "%s", + *conf->variable ? *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 && gconf->sourcefile[0]) + 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)); + do_serialize_binary(destptr, maxbytes, &gconf->srole, + sizeof(gconf->srole)); +} + +/* + * 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; + + /* Reserve space for saving the actual size of the guc state */ + Assert(maxsize > sizeof(actual_size)); + curptr = start_address + sizeof(actual_size); + bytes_left = maxsize - sizeof(actual_size); + + for (i = 0; i < num_guc_variables; i++) + serialize_variable(&curptr, &bytes_left, guc_variables[i]); + + /* 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; +} + +/* + * Callback used to add a context message when reporting errors that occur + * while trying to restore GUCs in parallel workers. + */ +static void +guc_restore_error_context_callback(void *arg) +{ + char **error_context_name_and_value = (char **) arg; + + if (error_context_name_and_value) + errcontext("while setting parameter \"%s\" to \"%s\"", + error_context_name_and_value[0], + error_context_name_and_value[1]); +} + +/* + * RestoreGUCState: + * Reads the GUC state at the specified address and sets this process's + * GUCs to match. + * + * Note that this provides the worker with only a very shallow view of the + * leader's GUC state: we'll know about the currently active values, but not + * about stacked or reset values. That's fine since the worker is just + * executing one part of a query, within which the active values won't change + * and the stacked values are invisible. + */ +void +RestoreGUCState(void *gucstate) +{ + char *varname, + *varvalue, + *varsourcefile; + int varsourceline; + GucSource varsource; + GucContext varscontext; + Oid varsrole; + char *srcptr = (char *) gucstate; + char *srcend; + Size len; + int i; + ErrorContextCallback error_context_callback; + + /* + * First, ensure that all potentially-shippable GUCs are reset to their + * default values. We must not touch those GUCs that the leader will + * never ship, while there is no need to touch those that are shippable + * but already have their default values. Thus, this ends up being the + * same test that SerializeGUCState uses, even though the sets of + * variables involved may well be different since the leader's set of + * variables-not-at-default-values can differ from the set that are + * not-default in this freshly started worker. + * + * Once we have set all the potentially-shippable GUCs to default values, + * restoring the GUCs that the leader sent (because they had non-default + * values over there) leads us to exactly the set of GUC values that the + * leader has. This is true even though the worker may have initially + * absorbed postgresql.conf settings that the leader hasn't yet seen, or + * ALTER USER/DATABASE SET settings that were established after the leader + * started. + * + * Note that ensuring all the potential target GUCs are at PGC_S_DEFAULT + * also ensures that set_config_option won't refuse to set them because of + * source-priority comparisons. + */ + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *gconf = guc_variables[i]; + + /* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */ + if (can_skip_gucvar(gconf)) + continue; + + /* + * We can use InitializeOneGUCOption to reset the GUC to default, but + * first we must free any existing subsidiary data to avoid leaking + * memory. The stack must be empty, but we have to clean up all other + * fields. Beware that there might be duplicate value or "extra" + * pointers. + */ + Assert(gconf->stack == NULL); + if (gconf->extra) + free(gconf->extra); + if (gconf->last_reported) /* probably can't happen */ + free(gconf->last_reported); + if (gconf->sourcefile) + free(gconf->sourcefile); + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + if (conf->reset_extra && conf->reset_extra != gconf->extra) + free(conf->reset_extra); + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + if (conf->reset_extra && conf->reset_extra != gconf->extra) + free(conf->reset_extra); + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + if (conf->reset_extra && conf->reset_extra != gconf->extra) + free(conf->reset_extra); + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + if (*conf->variable) + free(*conf->variable); + if (conf->reset_val && conf->reset_val != *conf->variable) + free(conf->reset_val); + if (conf->reset_extra && conf->reset_extra != gconf->extra) + free(conf->reset_extra); + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + if (conf->reset_extra && conf->reset_extra != gconf->extra) + free(conf->reset_extra); + break; + } + } + /* Now we can reset the struct to PGS_S_DEFAULT state. */ + InitializeOneGUCOption(gconf); + } + + /* First item is the length of the subsequent data */ + memcpy(&len, gucstate, sizeof(len)); + + srcptr += sizeof(len); + srcend = srcptr + len; + + /* If the GUC value check fails, we want errors to show useful context. */ + error_context_callback.callback = guc_restore_error_context_callback; + error_context_callback.previous = error_context_stack; + error_context_callback.arg = NULL; + error_context_stack = &error_context_callback; + + /* Restore all the listed GUCs. */ + while (srcptr < srcend) + { + int result; + char *error_context_name_and_value[2]; + + varname = read_gucstate(&srcptr, srcend); + varvalue = read_gucstate(&srcptr, srcend); + varsourcefile = read_gucstate(&srcptr, srcend); + if (varsourcefile[0]) + read_gucstate_binary(&srcptr, srcend, + &varsourceline, sizeof(varsourceline)); + else + varsourceline = 0; + read_gucstate_binary(&srcptr, srcend, + &varsource, sizeof(varsource)); + read_gucstate_binary(&srcptr, srcend, + &varscontext, sizeof(varscontext)); + read_gucstate_binary(&srcptr, srcend, + &varsrole, sizeof(varsrole)); + + error_context_name_and_value[0] = varname; + error_context_name_and_value[1] = varvalue; + error_context_callback.arg = &error_context_name_and_value[0]; + result = set_config_option_ext(varname, varvalue, + varscontext, varsource, varsrole, + 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); + error_context_callback.arg = NULL; + } + + error_context_stack = error_context_callback.previous; +} + +/* + * A little "long argument" simulation, although not quite GNU + * compliant. Takes a string of the form "some-option=some value" and + * returns name = "some_option" and value = "some value" in malloc'ed + * storage. Note that '-' is converted to '_' in the option name. If + * there is no '=' in the input string then value will be NULL. + */ +void +ParseLongOption(const char *string, char **name, char **value) +{ + size_t equal_pos; + char *cp; + + AssertArg(string); + AssertArg(name); + AssertArg(value); + + equal_pos = strcspn(string, "="); + + if (string[equal_pos] == '=') + { + *name = guc_malloc(FATAL, equal_pos + 1); + strlcpy(*name, string, equal_pos + 1); + + *value = guc_strdup(FATAL, &string[equal_pos + 1]); + } + else + { + /* no equal sign in string */ + *name = guc_strdup(FATAL, string); + *value = NULL; + } + + for (cp = *name; *cp; cp++) + if (*cp == '-') + *cp = '_'; +} + + +/* + * Handle options fetched from pg_db_role_setting.setconfig, + * pg_proc.proconfig, etc. Caller must specify proper context/source/action. + * + * The array parameter must be an array of TEXT (it must not be NULL). + */ +void +ProcessGUCArray(ArrayType *array, + GucContext context, GucSource source, GucAction action) +{ + int i; + + Assert(array != NULL); + Assert(ARR_ELEMTYPE(array) == TEXTOID); + Assert(ARR_NDIM(array) == 1); + Assert(ARR_LBOUND(array)[0] == 1); + + for (i = 1; i <= ARR_DIMS(array)[0]; i++) + { + Datum d; + bool isnull; + char *s; + char *name; + char *value; + char *namecopy; + char *valuecopy; + + d = array_ref(array, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + + if (isnull) + continue; + + s = TextDatumGetCString(d); + + ParseLongOption(s, &name, &value); + if (!value) + { + ereport(WARNING, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("could not parse setting for parameter \"%s\"", + name))); + free(name); + continue; + } + + /* free malloc'd strings immediately to avoid leak upon error */ + namecopy = pstrdup(name); + free(name); + valuecopy = pstrdup(value); + free(value); + + (void) set_config_option(namecopy, valuecopy, + context, source, + action, true, 0, false); + + pfree(namecopy); + pfree(valuecopy); + pfree(s); + } +} + + +/* + * Add an entry to an option array. The array parameter may be NULL + * to indicate the current table entry is NULL. + */ +ArrayType * +GUCArrayAdd(ArrayType *array, const char *name, const char *value) +{ + struct config_generic *record; + Datum datum; + char *newval; + ArrayType *a; + + Assert(name); + Assert(value); + + /* test if the option is valid and we're allowed to set it */ + (void) validate_option_array_item(name, value, false); + + /* normalize name (converts obsolete GUC names to modern spellings) */ + record = find_option(name, false, true, WARNING); + if (record) + name = record->name; + + /* build new item for array */ + newval = psprintf("%s=%s", name, value); + datum = CStringGetTextDatum(newval); + + if (array) + { + int index; + bool isnull; + int i; + + Assert(ARR_ELEMTYPE(array) == TEXTOID); + Assert(ARR_NDIM(array) == 1); + Assert(ARR_LBOUND(array)[0] == 1); + + index = ARR_DIMS(array)[0] + 1; /* add after end */ + + for (i = 1; i <= ARR_DIMS(array)[0]; i++) + { + Datum d; + char *current; + + d = array_ref(array, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + if (isnull) + continue; + current = TextDatumGetCString(d); + + /* check for match up through and including '=' */ + if (strncmp(current, newval, strlen(name) + 1) == 0) + { + index = i; + break; + } + } + + a = array_set(array, 1, &index, + datum, + false, + -1 /* varlena array */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ ); + } + else + a = construct_array(&datum, 1, + TEXTOID, + -1, false, TYPALIGN_INT); + + return a; +} + + +/* + * Delete an entry from an option array. The array parameter may be NULL + * to indicate the current table entry is NULL. Also, if the return value + * is NULL then a null should be stored. + */ +ArrayType * +GUCArrayDelete(ArrayType *array, const char *name) +{ + struct config_generic *record; + ArrayType *newarray; + int i; + int index; + + Assert(name); + + /* test if the option is valid and we're allowed to set it */ + (void) validate_option_array_item(name, NULL, false); + + /* normalize name (converts obsolete GUC names to modern spellings) */ + record = find_option(name, false, true, WARNING); + if (record) + name = record->name; + + /* if array is currently null, then surely nothing to delete */ + if (!array) + return NULL; + + newarray = NULL; + index = 1; + + for (i = 1; i <= ARR_DIMS(array)[0]; i++) + { + Datum d; + char *val; + bool isnull; + + d = array_ref(array, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + if (isnull) + continue; + val = TextDatumGetCString(d); + + /* ignore entry if it's what we want to delete */ + if (strncmp(val, name, strlen(name)) == 0 + && val[strlen(name)] == '=') + continue; + + /* else add it to the output array */ + if (newarray) + newarray = array_set(newarray, 1, &index, + d, + false, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ ); + else + newarray = construct_array(&d, 1, + TEXTOID, + -1, false, TYPALIGN_INT); + + index++; + } + + return newarray; +} + + +/* + * Given a GUC array, delete all settings from it that our permission + * level allows: if superuser, delete them all; if regular user, only + * those that are PGC_USERSET or we have permission to set + */ +ArrayType * +GUCArrayReset(ArrayType *array) +{ + ArrayType *newarray; + int i; + int index; + + /* if array is currently null, nothing to do */ + if (!array) + return NULL; + + /* if we're superuser, we can delete everything, so just do it */ + if (superuser()) + return NULL; + + newarray = NULL; + index = 1; + + for (i = 1; i <= ARR_DIMS(array)[0]; i++) + { + Datum d; + char *val; + char *eqsgn; + bool isnull; + + d = array_ref(array, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + if (isnull) + continue; + val = TextDatumGetCString(d); + + eqsgn = strchr(val, '='); + *eqsgn = '\0'; + + /* skip if we have permission to delete it */ + if (validate_option_array_item(val, NULL, true)) + continue; + + /* else add it to the output array */ + if (newarray) + newarray = array_set(newarray, 1, &index, + d, + false, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ ); + else + newarray = construct_array(&d, 1, + TEXTOID, + -1, false, TYPALIGN_INT); + + index++; + pfree(val); + } + + return newarray; +} + +/* + * Validate a proposed option setting for GUCArrayAdd/Delete/Reset. + * + * name is the option name. value is the proposed value for the Add case, + * or NULL for the Delete/Reset cases. If skipIfNoPermissions is true, it's + * not an error to have no permissions to set the option. + * + * Returns true if OK, false if skipIfNoPermissions is true and user does not + * have permission to change this option (all other error cases result in an + * error being thrown). + */ +static bool +validate_option_array_item(const char *name, const char *value, + bool skipIfNoPermissions) + +{ + struct config_generic *gconf; + + /* + * There are three cases to consider: + * + * name is a known GUC variable. Check the value normally, check + * permissions normally (i.e., allow if variable is USERSET, or if it's + * SUSET and user is superuser or holds ACL_SET permissions). + * + * name is not known, but exists or can be created as a placeholder (i.e., + * it has a valid custom name). We allow this case if you're a superuser, + * otherwise not. Superusers are assumed to know what they're doing. We + * can't allow it for other users, because when the placeholder is + * resolved it might turn out to be a SUSET variable. (With currently + * available infrastructure, we can actually handle such cases within the + * current session --- but once an entry is made in pg_db_role_setting, + * it's assumed to be fully validated.) + * + * name is not known and can't be created as a placeholder. Throw error, + * unless skipIfNoPermissions is true, in which case return false. + */ + gconf = find_option(name, true, skipIfNoPermissions, ERROR); + if (!gconf) + { + /* not known, failed to make a placeholder */ + return false; + } + + if (gconf->flags & GUC_CUSTOM_PLACEHOLDER) + { + /* + * We cannot do any meaningful check on the value, so only permissions + * are useful to check. + */ + if (superuser() || + pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK) + return true; + if (skipIfNoPermissions) + return false; + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", name))); + } + + /* manual permissions check so we can avoid an error being thrown */ + if (gconf->context == PGC_USERSET) + /* ok */ ; + else if (gconf->context == PGC_SUSET && + (superuser() || + pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK)) + /* ok */ ; + else if (skipIfNoPermissions) + return false; + /* if a permissions error should be thrown, let set_config_option do it */ + + /* 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, false); + + return true; +} + + +/* + * Called by check_hooks that want to override the normal + * ERRCODE_INVALID_PARAMETER_VALUE SQLSTATE for check hook failures. + * + * Note that GUC_check_errmsg() etc are just macros that result in a direct + * assignment to the associated variables. That is ugly, but forced by the + * limitations of C's macro mechanisms. + */ +void +GUC_check_errcode(int sqlerrcode) +{ + GUC_check_errcode_value = sqlerrcode; +} + + +/* + * Convenience functions to manage calling a variable's check_hook. + * These mostly take care of the protocol for letting check hooks supply + * portions of the error report on failure. + */ + +static bool +call_bool_check_hook(struct config_bool *conf, bool *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %d", + conf->gen.name, (int) *newval), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_int_check_hook(struct config_int *conf, int *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %d", + conf->gen.name, *newval), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_real_check_hook(struct config_real *conf, double *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %g", + conf->gen.name, *newval), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_string_check_hook(struct config_string *conf, char **newval, void **extra, + GucSource source, int elevel) +{ + volatile bool result = true; + + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* + * If elevel is ERROR, or if the check_hook itself throws an elog + * (undesirable, but not always avoidable), make sure we don't leak the + * already-malloc'd newval string. + */ + PG_TRY(); + { + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": \"%s\"", + conf->gen.name, *newval ? *newval : ""), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + result = false; + } + } + PG_CATCH(); + { + free(*newval); + PG_RE_THROW(); + } + PG_END_TRY(); + + return result; +} + +static bool +call_enum_check_hook(struct config_enum *conf, int *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": \"%s\"", + conf->gen.name, + config_enum_lookup_by_value(conf, *newval)), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + + +/* + * check_hook, assign_hook and show_hook subroutines + */ + +static bool +check_wal_consistency_checking(char **newval, void **extra, GucSource source) +{ + char *rawstring; + List *elemlist; + ListCell *l; + bool newwalconsistency[RM_MAX_ID + 1]; + + /* Initialize the array */ + MemSet(newwalconsistency, 0, (RM_MAX_ID + 1) * sizeof(bool)); + + /* Need a modifiable copy of string */ + rawstring = pstrdup(*newval); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + foreach(l, elemlist) + { + char *tok = (char *) lfirst(l); + bool found = false; + int rmid; + + /* Check for 'all'. */ + if (pg_strcasecmp(tok, "all") == 0) + { + for (rmid = 0; rmid <= RM_MAX_ID; rmid++) + if (RmgrIdExists(rmid) && GetRmgr(rmid).rm_mask != NULL) + newwalconsistency[rmid] = true; + found = true; + } + else + { + /* + * Check if the token matches with any individual resource + * manager. + */ + for (rmid = 0; rmid <= RM_MAX_ID; rmid++) + { + if (RmgrIdExists(rmid) && GetRmgr(rmid).rm_mask != NULL && + pg_strcasecmp(tok, GetRmgr(rmid).rm_name) == 0) + { + newwalconsistency[rmid] = true; + found = true; + } + } + } + + /* If a valid resource manager is found, check for the next one. */ + if (!found) + { + /* + * Perhaps it's a custom resource manager. If so, defer checking + * until InitializeWalConsistencyChecking(). + */ + if (!process_shared_preload_libraries_done) + { + check_wal_consistency_checking_deferred = true; + } + else + { + GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); + pfree(rawstring); + list_free(elemlist); + return false; + } + } + } + + pfree(rawstring); + list_free(elemlist); + + /* assign new value */ + *extra = guc_malloc(ERROR, (RM_MAX_ID + 1) * sizeof(bool)); + memcpy(*extra, newwalconsistency, (RM_MAX_ID + 1) * sizeof(bool)); + return true; +} + +static void +assign_wal_consistency_checking(const char *newval, void *extra) +{ + /* + * If some checks were deferred, it's possible that the checks will fail + * later during InitializeWalConsistencyChecking(). But in that case, the + * postmaster will exit anyway, so it's safe to proceed with the + * assignment. + * + * Any built-in resource managers specified are assigned immediately, + * which affects WAL created before shared_preload_libraries are + * processed. Any custom resource managers specified won't be assigned + * until after shared_preload_libraries are processed, but that's OK + * because WAL for a custom resource manager can't be written before the + * module is loaded anyway. + */ + wal_consistency_checking = extra; +} + +static bool +check_log_destination(char **newval, void **extra, GucSource source) +{ + char *rawstring; + List *elemlist; + ListCell *l; + int newlogdest = 0; + int *myextra; + + /* Need a modifiable copy of string */ + rawstring = pstrdup(*newval); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + foreach(l, elemlist) + { + char *tok = (char *) lfirst(l); + + if (pg_strcasecmp(tok, "stderr") == 0) + newlogdest |= LOG_DESTINATION_STDERR; + else if (pg_strcasecmp(tok, "csvlog") == 0) + newlogdest |= LOG_DESTINATION_CSVLOG; + else if (pg_strcasecmp(tok, "jsonlog") == 0) + newlogdest |= LOG_DESTINATION_JSONLOG; +#ifdef HAVE_SYSLOG + else if (pg_strcasecmp(tok, "syslog") == 0) + newlogdest |= LOG_DESTINATION_SYSLOG; +#endif +#ifdef WIN32 + else if (pg_strcasecmp(tok, "eventlog") == 0) + newlogdest |= LOG_DESTINATION_EVENTLOG; +#endif + else + { + GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); + pfree(rawstring); + list_free(elemlist); + return false; + } + } + + pfree(rawstring); + list_free(elemlist); + + myextra = (int *) guc_malloc(ERROR, sizeof(int)); + *myextra = newlogdest; + *extra = (void *) myextra; + + return true; +} + +static void +assign_log_destination(const char *newval, void *extra) +{ + Log_destination = *((int *) extra); +} + +static void +assign_syslog_facility(int newval, void *extra) +{ +#ifdef HAVE_SYSLOG + set_syslog_parameters(syslog_ident_str ? syslog_ident_str : "postgres", + newval); +#endif + /* Without syslog support, just ignore it */ +} + +static void +assign_syslog_ident(const char *newval, void *extra) +{ +#ifdef HAVE_SYSLOG + set_syslog_parameters(newval, syslog_facility); +#endif + /* Without syslog support, it will always be set to "none", so ignore */ +} + + +static void +assign_session_replication_role(int newval, void *extra) +{ + /* + * Must flush the plan cache when changing replication role; but don't + * flush unnecessarily. + */ + if (SessionReplicationRole != newval) + ResetPlanCache(); +} + +static bool +check_temp_buffers(int *newval, void **extra, GucSource source) +{ + /* + * Once local buffers have been initialized, it's too late to change this. + * However, if this is only a test call, allow it. + */ + if (source != PGC_S_TEST && NLocBuffer && NLocBuffer != *newval) + { + GUC_check_errdetail("\"temp_buffers\" cannot be changed after any temporary tables have been accessed in the session."); + return false; + } + return true; +} + +static bool +check_bonjour(bool *newval, void **extra, GucSource source) +{ +#ifndef USE_BONJOUR + if (*newval) + { + GUC_check_errmsg("Bonjour is not supported by this build"); + return false; + } +#endif + return true; +} + +static bool +check_ssl(bool *newval, void **extra, GucSource source) +{ +#ifndef USE_SSL + if (*newval) + { + GUC_check_errmsg("SSL is not supported by this build"); + return false; + } +#endif + return true; +} + +static bool +check_stage_log_stats(bool *newval, void **extra, GucSource source) +{ + if (*newval && log_statement_stats) + { + GUC_check_errdetail("Cannot enable parameter when \"log_statement_stats\" is true."); + return false; + } + return true; +} + +static bool +check_log_stats(bool *newval, void **extra, GucSource source) +{ + if (*newval && + (log_parser_stats || log_planner_stats || log_executor_stats)) + { + GUC_check_errdetail("Cannot enable \"log_statement_stats\" when " + "\"log_parser_stats\", \"log_planner_stats\", " + "or \"log_executor_stats\" is true."); + return false; + } + return true; +} + +static bool +check_canonical_path(char **newval, void **extra, GucSource source) +{ + /* + * Since canonicalize_path never enlarges the string, we can just modify + * newval in-place. But watch out for NULL, which is the default value + * for external_pid_file. + */ + if (*newval) + canonicalize_path(*newval); + return true; +} + +static bool +check_timezone_abbreviations(char **newval, void **extra, GucSource source) +{ + /* + * The boot_val given above for timezone_abbreviations is NULL. When we + * see this we just do nothing. If this value isn't overridden from the + * config file then pg_timezone_abbrev_initialize() will eventually + * replace it with "Default". This hack has two purposes: to avoid + * wasting cycles loading values that might soon be overridden from the + * config file, and to avoid trying to read the timezone abbrev files + * during InitializeGUCOptions(). The latter doesn't work in an + * EXEC_BACKEND subprocess because my_exec_path hasn't been set yet and so + * we can't locate PGSHAREDIR. + */ + if (*newval == NULL) + { + Assert(source == PGC_S_DEFAULT); + return true; + } + + /* OK, load the file and produce a malloc'd TimeZoneAbbrevTable */ + *extra = load_tzoffsets(*newval); + + /* tzparser.c returns NULL on failure, reporting via GUC_check_errmsg */ + if (!*extra) + return false; + + return true; +} + +static void +assign_timezone_abbreviations(const char *newval, void *extra) +{ + /* Do nothing for the boot_val default of NULL */ + if (!extra) + return; + + InstallTimeZoneAbbrevs((TimeZoneAbbrevTable *) extra); +} + +/* + * pg_timezone_abbrev_initialize --- set default value if not done already + * + * This is called after initial loading of postgresql.conf. If no + * timezone_abbreviations setting was found therein, select default. + * If a non-default value is already installed, nothing will happen. + * + * This can also be called from ProcessConfigFile to establish the default + * value after a postgresql.conf entry for it is removed. + */ +static void +pg_timezone_abbrev_initialize(void) +{ + SetConfigOption("timezone_abbreviations", "Default", + PGC_POSTMASTER, PGC_S_DYNAMIC_DEFAULT); +} + +static const char * +show_archive_command(void) +{ + if (XLogArchivingActive()) + return XLogArchiveCommand; + else + return "(disabled)"; +} + +static void +assign_tcp_keepalives_idle(int newval, void *extra) +{ + /* + * The kernel API provides no way to test a value without setting it; and + * once we set it we might fail to unset it. So there seems little point + * in fully implementing the check-then-assign GUC API for these + * variables. Instead we just do the assignment on demand. pqcomm.c + * reports any problems via ereport(LOG). + * + * This approach means that the GUC value might have little to do with the + * actual kernel value, so we use a show_hook that retrieves the kernel + * value rather than trusting GUC's copy. + */ + (void) pq_setkeepalivesidle(newval, MyProcPort); +} + +static const char * +show_tcp_keepalives_idle(void) +{ + /* See comments in assign_tcp_keepalives_idle */ + static char nbuf[16]; + + snprintf(nbuf, sizeof(nbuf), "%d", pq_getkeepalivesidle(MyProcPort)); + return nbuf; +} + +static void +assign_tcp_keepalives_interval(int newval, void *extra) +{ + /* See comments in assign_tcp_keepalives_idle */ + (void) pq_setkeepalivesinterval(newval, MyProcPort); +} + +static const char * +show_tcp_keepalives_interval(void) +{ + /* See comments in assign_tcp_keepalives_idle */ + static char nbuf[16]; + + snprintf(nbuf, sizeof(nbuf), "%d", pq_getkeepalivesinterval(MyProcPort)); + return nbuf; +} + +static void +assign_tcp_keepalives_count(int newval, void *extra) +{ + /* See comments in assign_tcp_keepalives_idle */ + (void) pq_setkeepalivescount(newval, MyProcPort); +} + +static const char * +show_tcp_keepalives_count(void) +{ + /* See comments in assign_tcp_keepalives_idle */ + static char nbuf[16]; + + snprintf(nbuf, sizeof(nbuf), "%d", pq_getkeepalivescount(MyProcPort)); + return nbuf; +} + +static void +assign_tcp_user_timeout(int newval, void *extra) +{ + /* See comments in assign_tcp_keepalives_idle */ + (void) pq_settcpusertimeout(newval, MyProcPort); +} + +static const char * +show_tcp_user_timeout(void) +{ + /* See comments in assign_tcp_keepalives_idle */ + static char nbuf[16]; + + snprintf(nbuf, sizeof(nbuf), "%d", pq_gettcpusertimeout(MyProcPort)); + return nbuf; +} + +static bool +check_maxconnections(int *newval, void **extra, GucSource source) +{ + if (*newval + autovacuum_max_workers + 1 + + max_worker_processes + max_wal_senders > MAX_BACKENDS) + return false; + return true; +} + +static bool +check_autovacuum_max_workers(int *newval, void **extra, GucSource source) +{ + if (MaxConnections + *newval + 1 + + max_worker_processes + max_wal_senders > MAX_BACKENDS) + return false; + return true; +} + +static bool +check_max_wal_senders(int *newval, void **extra, GucSource source) +{ + if (MaxConnections + autovacuum_max_workers + 1 + + max_worker_processes + *newval > MAX_BACKENDS) + return false; + return true; +} + +static bool +check_autovacuum_work_mem(int *newval, void **extra, GucSource source) +{ + /* + * -1 indicates fallback. + * + * If we haven't yet changed the boot_val default of -1, just let it be. + * Autovacuum will look to maintenance_work_mem instead. + */ + if (*newval == -1) + return true; + + /* + * We clamp manually-set values to at least 1MB. Since + * maintenance_work_mem is always set to at least this value, do the same + * here. + */ + if (*newval < 1024) + *newval = 1024; + + return true; +} + +static bool +check_max_worker_processes(int *newval, void **extra, GucSource source) +{ + if (MaxConnections + autovacuum_max_workers + 1 + + *newval + max_wal_senders > MAX_BACKENDS) + return false; + return true; +} + +static bool +check_effective_io_concurrency(int *newval, void **extra, GucSource source) +{ +#ifndef USE_PREFETCH + if (*newval != 0) + { + GUC_check_errdetail("effective_io_concurrency must be set to 0 on platforms that lack posix_fadvise()."); + return false; + } +#endif /* USE_PREFETCH */ + return true; +} + +static bool +check_maintenance_io_concurrency(int *newval, void **extra, GucSource source) +{ +#ifndef USE_PREFETCH + if (*newval != 0) + { + GUC_check_errdetail("maintenance_io_concurrency must be set to 0 on platforms that lack posix_fadvise()."); + return false; + } +#endif /* USE_PREFETCH */ + return true; +} + +static bool +check_huge_page_size(int *newval, void **extra, GucSource source) +{ +#if !(defined(MAP_HUGE_MASK) && defined(MAP_HUGE_SHIFT)) + /* Recent enough Linux only, for now. See GetHugePageSize(). */ + if (*newval != 0) + { + GUC_check_errdetail("huge_page_size must be 0 on this platform."); + return false; + } +#endif + return true; +} + +static bool +check_client_connection_check_interval(int *newval, void **extra, GucSource source) +{ + if (!WaitEventSetCanReportClosed() && *newval != 0) + { + GUC_check_errdetail("client_connection_check_interval must be set to 0 on this platform."); + return false; + } + return true; +} + +static void +assign_maintenance_io_concurrency(int newval, void *extra) +{ +#ifdef USE_PREFETCH + /* + * Reconfigure recovery prefetching, because a setting it depends on + * changed. + */ + maintenance_io_concurrency = newval; + if (AmStartupProcess()) + XLogPrefetchReconfigure(); +#endif +} + +static bool +check_application_name(char **newval, void **extra, GucSource source) +{ + /* Only allow clean ASCII chars in the application name */ + pg_clean_ascii(*newval); + + return true; +} + +static void +assign_application_name(const char *newval, void *extra) +{ + /* Update the pg_stat_activity view */ + pgstat_report_appname(newval); +} + +static bool +check_cluster_name(char **newval, void **extra, GucSource source) +{ + /* Only allow clean ASCII chars in the cluster name */ + pg_clean_ascii(*newval); + + return true; +} + +static const char * +show_unix_socket_permissions(void) +{ + static char buf[12]; + + snprintf(buf, sizeof(buf), "%04o", Unix_socket_permissions); + return buf; +} + +static const char * +show_log_file_mode(void) +{ + static char buf[12]; + + snprintf(buf, sizeof(buf), "%04o", Log_file_mode); + return buf; +} + +static const char * +show_data_directory_mode(void) +{ + static char buf[12]; + + snprintf(buf, sizeof(buf), "%04o", data_directory_mode); + return buf; +} + +static const char * +show_in_hot_standby(void) +{ + /* + * We display the actual state based on shared memory, so that this GUC + * reports up-to-date state if examined intra-query. The underlying + * variable in_hot_standby changes only when we transmit a new value to + * the client. + */ + return RecoveryInProgress() ? "on" : "off"; +} + +/* + * We split the input string, where commas separate function names + * and certain whitespace chars are ignored, into a \0-separated (and + * \0\0-terminated) list of function names. This formulation allows + * easy scanning when an error is thrown while avoiding the use of + * non-reentrant strtok(), as well as keeping the output data in a + * single palloc() chunk. + */ +static bool +check_backtrace_functions(char **newval, void **extra, GucSource source) +{ + int newvallen = strlen(*newval); + char *someval; + int validlen; + int i; + int j; + + /* + * Allow characters that can be C identifiers and commas as separators, as + * well as some whitespace for readability. + */ + validlen = strspn(*newval, + "0123456789_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + ", \n\t"); + if (validlen != newvallen) + { + GUC_check_errdetail("invalid character"); + return false; + } + + if (*newval[0] == '\0') + { + *extra = NULL; + return true; + } + + /* + * Allocate space for the output and create the copy. We could discount + * whitespace chars to save some memory, but it doesn't seem worth the + * trouble. + */ + someval = guc_malloc(ERROR, newvallen + 1 + 1); + for (i = 0, j = 0; i < newvallen; i++) + { + if ((*newval)[i] == ',') + someval[j++] = '\0'; /* next item */ + else if ((*newval)[i] == ' ' || + (*newval)[i] == '\n' || + (*newval)[i] == '\t') + ; /* ignore these */ + else + someval[j++] = (*newval)[i]; /* copy anything else */ + } + + /* two \0s end the setting */ + someval[j] = '\0'; + someval[j + 1] = '\0'; + + *extra = someval; + return true; +} + +static void +assign_backtrace_functions(const char *newval, void *extra) +{ + backtrace_symbol_list = (char *) extra; +} + +static bool +check_recovery_target_timeline(char **newval, void **extra, GucSource source) +{ + RecoveryTargetTimeLineGoal rttg; + RecoveryTargetTimeLineGoal *myextra; + + if (strcmp(*newval, "current") == 0) + rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE; + else if (strcmp(*newval, "latest") == 0) + rttg = RECOVERY_TARGET_TIMELINE_LATEST; + else + { + rttg = RECOVERY_TARGET_TIMELINE_NUMERIC; + + errno = 0; + strtoul(*newval, NULL, 0); + if (errno == EINVAL || errno == ERANGE) + { + GUC_check_errdetail("recovery_target_timeline is not a valid number."); + return false; + } + } + + myextra = (RecoveryTargetTimeLineGoal *) guc_malloc(ERROR, sizeof(RecoveryTargetTimeLineGoal)); + *myextra = rttg; + *extra = (void *) myextra; + + return true; +} + +static void +assign_recovery_target_timeline(const char *newval, void *extra) +{ + recoveryTargetTimeLineGoal = *((RecoveryTargetTimeLineGoal *) extra); + if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC) + recoveryTargetTLIRequested = (TimeLineID) strtoul(newval, NULL, 0); + else + recoveryTargetTLIRequested = 0; +} + +/* + * Recovery target settings: Only one of the several recovery_target* settings + * may be set. Setting a second one results in an error. The global variable + * recoveryTarget tracks which kind of recovery target was chosen. Other + * variables store the actual target value (for example a string or a xid). + * The assign functions of the parameters check whether a competing parameter + * was already set. But we want to allow setting the same parameter multiple + * times. We also want to allow unsetting a parameter and setting a different + * one, so we unset recoveryTarget when the parameter is set to an empty + * string. + */ + +static void +pg_attribute_noreturn() +error_multiple_recovery_targets(void) +{ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("multiple recovery targets specified"), + errdetail("At most one of recovery_target, recovery_target_lsn, recovery_target_name, recovery_target_time, recovery_target_xid may be set."))); +} + +static bool +check_recovery_target(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval, "immediate") != 0 && strcmp(*newval, "") != 0) + { + GUC_check_errdetail("The only allowed value is \"immediate\"."); + return false; + } + return true; +} + +static void +assign_recovery_target(const char *newval, void *extra) +{ + if (recoveryTarget != RECOVERY_TARGET_UNSET && + recoveryTarget != RECOVERY_TARGET_IMMEDIATE) + error_multiple_recovery_targets(); + + if (newval && strcmp(newval, "") != 0) + recoveryTarget = RECOVERY_TARGET_IMMEDIATE; + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_recovery_target_xid(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval, "") != 0) + { + TransactionId xid; + TransactionId *myextra; + + errno = 0; + xid = (TransactionId) strtou64(*newval, NULL, 0); + if (errno == EINVAL || errno == ERANGE) + return false; + + myextra = (TransactionId *) guc_malloc(ERROR, sizeof(TransactionId)); + *myextra = xid; + *extra = (void *) myextra; + } + return true; +} + +static void +assign_recovery_target_xid(const char *newval, void *extra) +{ + if (recoveryTarget != RECOVERY_TARGET_UNSET && + recoveryTarget != RECOVERY_TARGET_XID) + error_multiple_recovery_targets(); + + if (newval && strcmp(newval, "") != 0) + { + recoveryTarget = RECOVERY_TARGET_XID; + recoveryTargetXid = *((TransactionId *) extra); + } + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +/* + * The interpretation of the recovery_target_time string can depend on the + * time zone setting, so we need to wait until after all GUC processing is + * done before we can do the final parsing of the string. This check function + * only does a parsing pass to catch syntax errors, but we store the string + * and parse it again when we need to use it. + */ +static bool +check_recovery_target_time(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval, "") != 0) + { + /* reject some special values */ + if (strcmp(*newval, "now") == 0 || + strcmp(*newval, "today") == 0 || + strcmp(*newval, "tomorrow") == 0 || + strcmp(*newval, "yesterday") == 0) + { + return false; + } + + /* + * parse timestamp value (see also timestamptz_in()) + */ + { + char *str = *newval; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + int tz; + int dtype; + int nf; + int dterr; + char *field[MAXDATEFIELDS]; + int ftype[MAXDATEFIELDS]; + char workbuf[MAXDATELEN + MAXDATEFIELDS]; + TimestampTz timestamp; + + dterr = ParseDateTime(str, workbuf, sizeof(workbuf), + field, ftype, MAXDATEFIELDS, &nf); + if (dterr == 0) + dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz); + if (dterr != 0) + return false; + if (dtype != DTK_DATE) + return false; + + if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) + { + GUC_check_errdetail("timestamp out of range: \"%s\"", str); + return false; + } + } + } + return true; +} + +static void +assign_recovery_target_time(const char *newval, void *extra) +{ + if (recoveryTarget != RECOVERY_TARGET_UNSET && + recoveryTarget != RECOVERY_TARGET_TIME) + error_multiple_recovery_targets(); + + if (newval && strcmp(newval, "") != 0) + recoveryTarget = RECOVERY_TARGET_TIME; + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_recovery_target_name(char **newval, void **extra, GucSource source) +{ + /* Use the value of newval directly */ + if (strlen(*newval) >= MAXFNAMELEN) + { + GUC_check_errdetail("%s is too long (maximum %d characters).", + "recovery_target_name", MAXFNAMELEN - 1); + return false; + } + return true; +} + +static void +assign_recovery_target_name(const char *newval, void *extra) +{ + if (recoveryTarget != RECOVERY_TARGET_UNSET && + recoveryTarget != RECOVERY_TARGET_NAME) + error_multiple_recovery_targets(); + + if (newval && strcmp(newval, "") != 0) + { + recoveryTarget = RECOVERY_TARGET_NAME; + recoveryTargetName = newval; + } + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_recovery_target_lsn(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval, "") != 0) + { + XLogRecPtr lsn; + XLogRecPtr *myextra; + bool have_error = false; + + lsn = pg_lsn_in_internal(*newval, &have_error); + if (have_error) + return false; + + myextra = (XLogRecPtr *) guc_malloc(ERROR, sizeof(XLogRecPtr)); + *myextra = lsn; + *extra = (void *) myextra; + } + return true; +} + +static void +assign_recovery_target_lsn(const char *newval, void *extra) +{ + if (recoveryTarget != RECOVERY_TARGET_UNSET && + recoveryTarget != RECOVERY_TARGET_LSN) + error_multiple_recovery_targets(); + + if (newval && strcmp(newval, "") != 0) + { + recoveryTarget = RECOVERY_TARGET_LSN; + recoveryTargetLSN = *((XLogRecPtr *) extra); + } + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_primary_slot_name(char **newval, void **extra, GucSource source) +{ + if (*newval && strcmp(*newval, "") != 0 && + !ReplicationSlotValidateName(*newval, WARNING)) + return false; + + return true; +} + +static bool +check_default_with_oids(bool *newval, void **extra, GucSource source) +{ + if (*newval) + { + /* check the GUC's definition for an explanation */ + GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED); + GUC_check_errmsg("tables declared WITH OIDS are not supported"); + + return false; + } + + return true; +} + +#include "guc-file.c" diff --git a/src/backend/utils/misc/help_config.c b/src/backend/utils/misc/help_config.c new file mode 100644 index 0000000..61c83f3 --- /dev/null +++ b/src/backend/utils/misc/help_config.c @@ -0,0 +1,137 @@ +/*------------------------------------------------------------------------- + * help_config.c + * + * Displays available options under grand unified configuration scheme + * + * Options whose flag bits are set to GUC_NO_SHOW_ALL, GUC_NOT_IN_SAMPLE, + * or GUC_DISALLOW_IN_FILE are not displayed, unless the user specifically + * requests that variable by name + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/misc/help_config.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> +#include <unistd.h> + +#include "utils/guc_tables.h" +#include "utils/help_config.h" + + +/* + * This union allows us to mix the numerous different types of structs + * that we are organizing. + */ +typedef union +{ + struct config_generic generic; + struct config_bool _bool; + struct config_real real; + struct config_int integer; + struct config_string string; + struct config_enum _enum; +} mixedStruct; + + +static void printMixedStruct(mixedStruct *structToPrint); +static bool displayStruct(mixedStruct *structToDisplay); + + +void +GucInfoMain(void) +{ + struct config_generic **guc_vars; + int numOpts, + i; + + /* Initialize the guc_variables[] array */ + build_guc_variables(); + + guc_vars = get_guc_variables(); + numOpts = GetNumConfigOptions(); + + for (i = 0; i < numOpts; i++) + { + mixedStruct *var = (mixedStruct *) guc_vars[i]; + + if (displayStruct(var)) + printMixedStruct(var); + } + + exit(0); +} + + +/* + * This function will return true if the struct passed to it + * should be displayed to the user. + */ +static bool +displayStruct(mixedStruct *structToDisplay) +{ + return !(structToDisplay->generic.flags & (GUC_NO_SHOW_ALL | + GUC_NOT_IN_SAMPLE | + GUC_DISALLOW_IN_FILE)); +} + + +/* + * This function prints out the generic struct passed to it. It will print out + * a different format, depending on what the user wants to see. + */ +static void +printMixedStruct(mixedStruct *structToPrint) +{ + printf("%s\t%s\t%s\t", + structToPrint->generic.name, + GucContext_Names[structToPrint->generic.context], + _(config_group_names[structToPrint->generic.group])); + + switch (structToPrint->generic.vartype) + { + + case PGC_BOOL: + printf("BOOLEAN\t%s\t\t\t", + (structToPrint->_bool.reset_val == 0) ? + "FALSE" : "TRUE"); + break; + + case PGC_INT: + printf("INTEGER\t%d\t%d\t%d\t", + structToPrint->integer.reset_val, + structToPrint->integer.min, + structToPrint->integer.max); + break; + + case PGC_REAL: + printf("REAL\t%g\t%g\t%g\t", + structToPrint->real.reset_val, + structToPrint->real.min, + structToPrint->real.max); + break; + + case PGC_STRING: + printf("STRING\t%s\t\t\t", + structToPrint->string.boot_val ? structToPrint->string.boot_val : ""); + break; + + case PGC_ENUM: + printf("ENUM\t%s\t\t\t", + config_enum_lookup_by_value(&structToPrint->_enum, + structToPrint->_enum.boot_val)); + break; + + default: + write_stderr("internal error: unrecognized run-time parameter type\n"); + break; + } + + printf("%s\t%s\n", + (structToPrint->generic.short_desc == NULL) ? "" : _(structToPrint->generic.short_desc), + (structToPrint->generic.long_desc == NULL) ? "" : _(structToPrint->generic.long_desc)); +} diff --git a/src/backend/utils/misc/pg_config.c b/src/backend/utils/misc/pg_config.c new file mode 100644 index 0000000..5819653 --- /dev/null +++ b/src/backend/utils/misc/pg_config.c @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------- + * + * pg_config.c + * Expose same output as pg_config except as an SRF + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/misc/pg_config.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "common/config_info.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "port.h" +#include "utils/builtins.h" + +Datum +pg_config(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + ConfigData *configdata; + size_t configdata_len; + int i = 0; + + /* initialize our tuplestore */ + InitMaterializedSRF(fcinfo, 0); + + configdata = get_configdata(my_exec_path, &configdata_len); + for (i = 0; i < configdata_len; i++) + { + Datum values[2]; + bool nulls[2]; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[0] = CStringGetTextDatum(configdata[i].name); + values[1] = CStringGetTextDatum(configdata[i].setting); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c new file mode 100644 index 0000000..fe52cff --- /dev/null +++ b/src/backend/utils/misc/pg_controldata.c @@ -0,0 +1,353 @@ +/*------------------------------------------------------------------------- + * + * pg_controldata.c + * + * Routines to expose the contents of the control data file via + * a set of SQL functions. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/misc/pg_controldata.c + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/transam.h" +#include "access/xlog.h" +#include "access/xlog_internal.h" +#include "catalog/pg_control.h" +#include "catalog/pg_type.h" +#include "common/controldata_utils.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/lwlock.h" +#include "utils/builtins.h" +#include "utils/pg_lsn.h" +#include "utils/timestamp.h" + +Datum +pg_control_system(PG_FUNCTION_ARGS) +{ + Datum values[4]; + bool nulls[4]; + TupleDesc tupdesc; + HeapTuple htup; + ControlFileData *ControlFile; + bool crc_ok; + + /* + * Construct a tuple descriptor for the result row. This must match this + * function's pg_proc entry! + */ + tupdesc = CreateTemplateTupleDesc(4); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_control_version", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catalog_version_no", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "system_identifier", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "pg_control_last_modified", + TIMESTAMPTZOID, -1, 0); + tupdesc = BlessTupleDesc(tupdesc); + + /* read the control file */ + LWLockAcquire(ControlFileLock, LW_SHARED); + ControlFile = get_controlfile(DataDir, &crc_ok); + LWLockRelease(ControlFileLock); + if (!crc_ok) + ereport(ERROR, + (errmsg("calculated CRC checksum does not match value stored in file"))); + + values[0] = Int32GetDatum(ControlFile->pg_control_version); + nulls[0] = false; + + values[1] = Int32GetDatum(ControlFile->catalog_version_no); + nulls[1] = false; + + values[2] = Int64GetDatum(ControlFile->system_identifier); + nulls[2] = false; + + values[3] = TimestampTzGetDatum(time_t_to_timestamptz(ControlFile->time)); + nulls[3] = false; + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} + +Datum +pg_control_checkpoint(PG_FUNCTION_ARGS) +{ + Datum values[18]; + bool nulls[18]; + TupleDesc tupdesc; + HeapTuple htup; + ControlFileData *ControlFile; + XLogSegNo segno; + char xlogfilename[MAXFNAMELEN]; + bool crc_ok; + + /* + * Construct a tuple descriptor for the result row. This must match this + * function's pg_proc entry! + */ + tupdesc = CreateTemplateTupleDesc(18); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "checkpoint_lsn", + PG_LSNOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "redo_lsn", + PG_LSNOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "redo_wal_file", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "timeline_id", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "prev_timeline_id", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "full_page_writes", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "next_xid", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "next_oid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "next_multixact_id", + XIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "next_multi_offset", + XIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "oldest_xid", + XIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "oldest_xid_dbid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "oldest_active_xid", + XIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "oldest_multi_xid", + XIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 15, "oldest_multi_dbid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 16, "oldest_commit_ts_xid", + XIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 17, "newest_commit_ts_xid", + XIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 18, "checkpoint_time", + TIMESTAMPTZOID, -1, 0); + tupdesc = BlessTupleDesc(tupdesc); + + /* Read the control file. */ + LWLockAcquire(ControlFileLock, LW_SHARED); + ControlFile = get_controlfile(DataDir, &crc_ok); + LWLockRelease(ControlFileLock); + if (!crc_ok) + ereport(ERROR, + (errmsg("calculated CRC checksum does not match value stored in file"))); + + /* + * Calculate name of the WAL file containing the latest checkpoint's REDO + * start point. + */ + XLByteToSeg(ControlFile->checkPointCopy.redo, segno, wal_segment_size); + XLogFileName(xlogfilename, ControlFile->checkPointCopy.ThisTimeLineID, + segno, wal_segment_size); + + /* Populate the values and null arrays */ + values[0] = LSNGetDatum(ControlFile->checkPoint); + nulls[0] = false; + + values[1] = LSNGetDatum(ControlFile->checkPointCopy.redo); + nulls[1] = false; + + values[2] = CStringGetTextDatum(xlogfilename); + nulls[2] = false; + + values[3] = Int32GetDatum(ControlFile->checkPointCopy.ThisTimeLineID); + nulls[3] = false; + + values[4] = Int32GetDatum(ControlFile->checkPointCopy.PrevTimeLineID); + nulls[4] = false; + + values[5] = BoolGetDatum(ControlFile->checkPointCopy.fullPageWrites); + nulls[5] = false; + + values[6] = CStringGetTextDatum(psprintf("%u:%u", + EpochFromFullTransactionId(ControlFile->checkPointCopy.nextXid), + XidFromFullTransactionId(ControlFile->checkPointCopy.nextXid))); + nulls[6] = false; + + values[7] = ObjectIdGetDatum(ControlFile->checkPointCopy.nextOid); + nulls[7] = false; + + values[8] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMulti); + nulls[8] = false; + + values[9] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMultiOffset); + nulls[9] = false; + + values[10] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestXid); + nulls[10] = false; + + values[11] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestXidDB); + nulls[11] = false; + + values[12] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestActiveXid); + nulls[12] = false; + + values[13] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestMulti); + nulls[13] = false; + + values[14] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestMultiDB); + nulls[14] = false; + + values[15] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestCommitTsXid); + nulls[15] = false; + + values[16] = TransactionIdGetDatum(ControlFile->checkPointCopy.newestCommitTsXid); + nulls[16] = false; + + values[17] = TimestampTzGetDatum(time_t_to_timestamptz(ControlFile->checkPointCopy.time)); + nulls[17] = false; + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} + +Datum +pg_control_recovery(PG_FUNCTION_ARGS) +{ + Datum values[5]; + bool nulls[5]; + TupleDesc tupdesc; + HeapTuple htup; + ControlFileData *ControlFile; + bool crc_ok; + + /* + * Construct a tuple descriptor for the result row. This must match this + * function's pg_proc entry! + */ + tupdesc = CreateTemplateTupleDesc(5); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "min_recovery_end_lsn", + PG_LSNOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "min_recovery_end_timeline", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "backup_start_lsn", + PG_LSNOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "backup_end_lsn", + PG_LSNOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "end_of_backup_record_required", + BOOLOID, -1, 0); + tupdesc = BlessTupleDesc(tupdesc); + + /* read the control file */ + LWLockAcquire(ControlFileLock, LW_SHARED); + ControlFile = get_controlfile(DataDir, &crc_ok); + LWLockRelease(ControlFileLock); + if (!crc_ok) + ereport(ERROR, + (errmsg("calculated CRC checksum does not match value stored in file"))); + + values[0] = LSNGetDatum(ControlFile->minRecoveryPoint); + nulls[0] = false; + + values[1] = Int32GetDatum(ControlFile->minRecoveryPointTLI); + nulls[1] = false; + + values[2] = LSNGetDatum(ControlFile->backupStartPoint); + nulls[2] = false; + + values[3] = LSNGetDatum(ControlFile->backupEndPoint); + nulls[3] = false; + + values[4] = BoolGetDatum(ControlFile->backupEndRequired); + nulls[4] = false; + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} + +Datum +pg_control_init(PG_FUNCTION_ARGS) +{ + Datum values[11]; + bool nulls[11]; + TupleDesc tupdesc; + HeapTuple htup; + ControlFileData *ControlFile; + bool crc_ok; + + /* + * Construct a tuple descriptor for the result row. This must match this + * function's pg_proc entry! + */ + tupdesc = CreateTemplateTupleDesc(11); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "blocks_per_segment", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "wal_block_size", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "bytes_per_wal_segment", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "max_identifier_length", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "max_index_columns", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "max_toast_chunk_size", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "large_object_chunk_size", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "float8_pass_by_value", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "data_page_checksum_version", + INT4OID, -1, 0); + tupdesc = BlessTupleDesc(tupdesc); + + /* read the control file */ + LWLockAcquire(ControlFileLock, LW_SHARED); + ControlFile = get_controlfile(DataDir, &crc_ok); + LWLockRelease(ControlFileLock); + if (!crc_ok) + ereport(ERROR, + (errmsg("calculated CRC checksum does not match value stored in file"))); + + values[0] = Int32GetDatum(ControlFile->maxAlign); + nulls[0] = false; + + values[1] = Int32GetDatum(ControlFile->blcksz); + nulls[1] = false; + + values[2] = Int32GetDatum(ControlFile->relseg_size); + nulls[2] = false; + + values[3] = Int32GetDatum(ControlFile->xlog_blcksz); + nulls[3] = false; + + values[4] = Int32GetDatum(ControlFile->xlog_seg_size); + nulls[4] = false; + + values[5] = Int32GetDatum(ControlFile->nameDataLen); + nulls[5] = false; + + values[6] = Int32GetDatum(ControlFile->indexMaxKeys); + nulls[6] = false; + + values[7] = Int32GetDatum(ControlFile->toast_max_chunk_size); + nulls[7] = false; + + values[8] = Int32GetDatum(ControlFile->loblksize); + nulls[8] = false; + + values[9] = BoolGetDatum(ControlFile->float8ByVal); + nulls[9] = false; + + values[10] = Int32GetDatum(ControlFile->data_checksum_version); + nulls[10] = false; + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} diff --git a/src/backend/utils/misc/pg_rusage.c b/src/backend/utils/misc/pg_rusage.c new file mode 100644 index 0000000..e60703c --- /dev/null +++ b/src/backend/utils/misc/pg_rusage.c @@ -0,0 +1,73 @@ +/*------------------------------------------------------------------------- + * + * pg_rusage.c + * Resource usage measurement support routines. + * + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/pg_rusage.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <unistd.h> + +#include "utils/pg_rusage.h" + + +/* + * Initialize usage snapshot. + */ +void +pg_rusage_init(PGRUsage *ru0) +{ + getrusage(RUSAGE_SELF, &ru0->ru); + gettimeofday(&ru0->tv, NULL); +} + +/* + * Compute elapsed time since ru0 usage snapshot, and format into + * a displayable string. Result is in a static string, which is + * tacky, but no one ever claimed that the Postgres backend is + * threadable... + */ +const char * +pg_rusage_show(const PGRUsage *ru0) +{ + static char result[100]; + PGRUsage ru1; + + pg_rusage_init(&ru1); + + if (ru1.tv.tv_usec < ru0->tv.tv_usec) + { + ru1.tv.tv_sec--; + ru1.tv.tv_usec += 1000000; + } + if (ru1.ru.ru_stime.tv_usec < ru0->ru.ru_stime.tv_usec) + { + ru1.ru.ru_stime.tv_sec--; + ru1.ru.ru_stime.tv_usec += 1000000; + } + if (ru1.ru.ru_utime.tv_usec < ru0->ru.ru_utime.tv_usec) + { + ru1.ru.ru_utime.tv_sec--; + ru1.ru.ru_utime.tv_usec += 1000000; + } + + snprintf(result, sizeof(result), + _("CPU: user: %d.%02d s, system: %d.%02d s, elapsed: %d.%02d s"), + (int) (ru1.ru.ru_utime.tv_sec - ru0->ru.ru_utime.tv_sec), + (int) (ru1.ru.ru_utime.tv_usec - ru0->ru.ru_utime.tv_usec) / 10000, + (int) (ru1.ru.ru_stime.tv_sec - ru0->ru.ru_stime.tv_sec), + (int) (ru1.ru.ru_stime.tv_usec - ru0->ru.ru_stime.tv_usec) / 10000, + (int) (ru1.tv.tv_sec - ru0->tv.tv_sec), + (int) (ru1.tv.tv_usec - ru0->tv.tv_usec) / 10000); + + return result; +} diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample new file mode 100644 index 0000000..f92ff4c --- /dev/null +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -0,0 +1,813 @@ +# ----------------------------- +# PostgreSQL configuration file +# ----------------------------- +# +# This file consists of lines of the form: +# +# name = value +# +# (The "=" is optional.) Whitespace may be used. Comments are introduced with +# "#" anywhere on a line. The complete list of parameter names and allowed +# values can be found in the PostgreSQL documentation. +# +# The commented-out settings shown in this file represent the default values. +# Re-commenting a setting is NOT sufficient to revert it to the default value; +# you need to reload the server. +# +# This file is read on server startup and when the server receives a SIGHUP +# signal. If you edit the file on a running system, you have to SIGHUP the +# server for the changes to take effect, run "pg_ctl reload", or execute +# "SELECT pg_reload_conf()". Some parameters, which are marked below, +# require a server shutdown and restart to take effect. +# +# Any parameter can also be given as a command-line option to the server, e.g., +# "postgres -c log_connections=on". Some parameters can be changed at run time +# with the "SET" SQL command. +# +# Memory units: B = bytes Time units: us = microseconds +# kB = kilobytes ms = milliseconds +# MB = megabytes s = seconds +# GB = gigabytes min = minutes +# TB = terabytes h = hours +# d = days + + +#------------------------------------------------------------------------------ +# FILE LOCATIONS +#------------------------------------------------------------------------------ + +# The default values of these variables are driven from the -D command-line +# option or PGDATA environment variable, represented here as ConfigDir. + +#data_directory = 'ConfigDir' # use data in another directory + # (change requires restart) +#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file + # (change requires restart) +#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file + # (change requires restart) + +# If external_pid_file is not explicitly set, no extra PID file is written. +#external_pid_file = '' # write an extra PID file + # (change requires restart) + + +#------------------------------------------------------------------------------ +# CONNECTIONS AND AUTHENTICATION +#------------------------------------------------------------------------------ + +# - Connection Settings - + +#listen_addresses = 'localhost' # what IP address(es) to listen on; + # comma-separated list of addresses; + # defaults to 'localhost'; use '*' for all + # (change requires restart) +#port = 5432 # (change requires restart) +#max_connections = 100 # (change requires restart) +#superuser_reserved_connections = 3 # (change requires restart) +#unix_socket_directories = '/tmp' # comma-separated list of directories + # (change requires restart) +#unix_socket_group = '' # (change requires restart) +#unix_socket_permissions = 0777 # begin with 0 to use octal notation + # (change requires restart) +#bonjour = off # advertise server via Bonjour + # (change requires restart) +#bonjour_name = '' # defaults to the computer name + # (change requires restart) + +# - TCP settings - +# see "man tcp" for details + +#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; + # 0 selects the system default +#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; + # 0 selects the system default +#tcp_keepalives_count = 0 # TCP_KEEPCNT; + # 0 selects the system default +#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; + # 0 selects the system default + +#client_connection_check_interval = 0 # time between checks for client + # disconnection while running queries; + # 0 for never + +# - Authentication - + +#authentication_timeout = 1min # 1s-600s +#password_encryption = scram-sha-256 # scram-sha-256 or md5 +#db_user_namespace = off + +# GSSAPI using Kerberos +#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' +#krb_caseins_users = off + +# - SSL - + +#ssl = off +#ssl_ca_file = '' +#ssl_cert_file = 'server.crt' +#ssl_crl_file = '' +#ssl_crl_dir = '' +#ssl_key_file = 'server.key' +#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers +#ssl_prefer_server_ciphers = on +#ssl_ecdh_curve = 'prime256v1' +#ssl_min_protocol_version = 'TLSv1.2' +#ssl_max_protocol_version = '' +#ssl_dh_params_file = '' +#ssl_passphrase_command = '' +#ssl_passphrase_command_supports_reload = off + + +#------------------------------------------------------------------------------ +# RESOURCE USAGE (except WAL) +#------------------------------------------------------------------------------ + +# - Memory - + +#shared_buffers = 128MB # min 128kB + # (change requires restart) +#huge_pages = try # on, off, or try + # (change requires restart) +#huge_page_size = 0 # zero for system default + # (change requires restart) +#temp_buffers = 8MB # min 800kB +#max_prepared_transactions = 0 # zero disables the feature + # (change requires restart) +# Caution: it is not advisable to set max_prepared_transactions nonzero unless +# you actively intend to use prepared transactions. +#work_mem = 4MB # min 64kB +#hash_mem_multiplier = 2.0 # 1-1000.0 multiplier on hash table work_mem +#maintenance_work_mem = 64MB # min 1MB +#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem +#logical_decoding_work_mem = 64MB # min 64kB +#max_stack_depth = 2MB # min 100kB +#shared_memory_type = mmap # the default is the first option + # supported by the operating system: + # mmap + # sysv + # windows + # (change requires restart) +#dynamic_shared_memory_type = posix # the default is usually the first option + # supported by the operating system: + # posix + # sysv + # windows + # mmap + # (change requires restart) +#min_dynamic_shared_memory = 0MB # (change requires restart) + +# - Disk - + +#temp_file_limit = -1 # limits per-process temp file space + # in kilobytes, or -1 for no limit + +# - Kernel Resources - + +#max_files_per_process = 1000 # min 64 + # (change requires restart) + +# - Cost-Based Vacuum Delay - + +#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) +#vacuum_cost_page_hit = 1 # 0-10000 credits +#vacuum_cost_page_miss = 2 # 0-10000 credits +#vacuum_cost_page_dirty = 20 # 0-10000 credits +#vacuum_cost_limit = 200 # 1-10000 credits + +# - Background Writer - + +#bgwriter_delay = 200ms # 10-10000ms between rounds +#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables +#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round +#bgwriter_flush_after = 0 # measured in pages, 0 disables + +# - Asynchronous Behavior - + +#backend_flush_after = 0 # measured in pages, 0 disables +#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching +#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching +#max_worker_processes = 8 # (change requires restart) +#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers +#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers +#max_parallel_workers = 8 # maximum number of max_worker_processes that + # can be used in parallel operations +#parallel_leader_participation = on +#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate + # (change requires restart) + + +#------------------------------------------------------------------------------ +# WRITE-AHEAD LOG +#------------------------------------------------------------------------------ + +# - Settings - + +#wal_level = replica # minimal, replica, or logical + # (change requires restart) +#fsync = on # flush data to disk for crash safety + # (turning this off can cause + # unrecoverable data corruption) +#synchronous_commit = on # synchronization level; + # off, local, remote_write, remote_apply, or on +#wal_sync_method = fsync # the default is the first option + # supported by the operating system: + # open_datasync + # fdatasync (default on Linux and FreeBSD) + # fsync + # fsync_writethrough + # open_sync +#full_page_writes = on # recover from partial page writes +#wal_log_hints = off # also do full page writes of non-critical updates + # (change requires restart) +#wal_compression = off # enables compression of full-page writes; + # off, pglz, lz4, zstd, or on +#wal_init_zero = on # zero-fill new WAL files +#wal_recycle = on # recycle WAL files +#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers + # (change requires restart) +#wal_writer_delay = 200ms # 1-10000 milliseconds +#wal_writer_flush_after = 1MB # measured in pages, 0 disables +#wal_skip_threshold = 2MB + +#commit_delay = 0 # range 0-100000, in microseconds +#commit_siblings = 5 # range 1-1000 + +# - Checkpoints - + +#checkpoint_timeout = 5min # range 30s-1d +#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0 +#checkpoint_flush_after = 0 # measured in pages, 0 disables +#checkpoint_warning = 30s # 0 disables +#max_wal_size = 1GB +#min_wal_size = 80MB + +# - Prefetching during recovery - + +#recovery_prefetch = try # prefetch pages referenced in the WAL? +#wal_decode_buffer_size = 512kB # lookahead window used for prefetching + # (change requires restart) + +# - Archiving - + +#archive_mode = off # enables archiving; off, on, or always + # (change requires restart) +#archive_library = '' # library to use to archive a logfile segment + # (empty string indicates archive_command should + # be used) +#archive_command = '' # command to use to archive a logfile segment + # placeholders: %p = path of file to archive + # %f = file name only + # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' +#archive_timeout = 0 # force a logfile segment switch after this + # number of seconds; 0 disables + +# - Archive Recovery - + +# These are only used in recovery mode. + +#restore_command = '' # command to use to restore an archived logfile segment + # placeholders: %p = path of file to restore + # %f = file name only + # e.g. 'cp /mnt/server/archivedir/%f %p' +#archive_cleanup_command = '' # command to execute at every restartpoint +#recovery_end_command = '' # command to execute at completion of recovery + +# - Recovery Target - + +# Set these only when performing a targeted recovery. + +#recovery_target = '' # 'immediate' to end recovery as soon as a + # consistent state is reached + # (change requires restart) +#recovery_target_name = '' # the named restore point to which recovery will proceed + # (change requires restart) +#recovery_target_time = '' # the time stamp up to which recovery will proceed + # (change requires restart) +#recovery_target_xid = '' # the transaction ID up to which recovery will proceed + # (change requires restart) +#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed + # (change requires restart) +#recovery_target_inclusive = on # Specifies whether to stop: + # just after the specified recovery target (on) + # just before the recovery target (off) + # (change requires restart) +#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID + # (change requires restart) +#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' + # (change requires restart) + + +#------------------------------------------------------------------------------ +# REPLICATION +#------------------------------------------------------------------------------ + +# - Sending Servers - + +# Set these on the primary and on any standby that will send replication data. + +#max_wal_senders = 10 # max number of walsender processes + # (change requires restart) +#max_replication_slots = 10 # max number of replication slots + # (change requires restart) +#wal_keep_size = 0 # in megabytes; 0 disables +#max_slot_wal_keep_size = -1 # in megabytes; -1 disables +#wal_sender_timeout = 60s # in milliseconds; 0 disables +#track_commit_timestamp = off # collect timestamp of transaction commit + # (change requires restart) + +# - Primary Server - + +# These settings are ignored on a standby server. + +#synchronous_standby_names = '' # standby servers that provide sync rep + # method to choose sync standbys, number of sync standbys, + # and comma-separated list of application_name + # from standby(s); '*' = all +#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed + +# - Standby Servers - + +# These settings are ignored on a primary server. + +#primary_conninfo = '' # connection string to sending server +#primary_slot_name = '' # replication slot on sending server +#promote_trigger_file = '' # file name whose presence ends recovery +#hot_standby = on # "off" disallows queries during recovery + # (change requires restart) +#max_standby_archive_delay = 30s # max delay before canceling queries + # when reading WAL from archive; + # -1 allows indefinite delay +#max_standby_streaming_delay = 30s # max delay before canceling queries + # when reading streaming WAL; + # -1 allows indefinite delay +#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name + # is not set +#wal_receiver_status_interval = 10s # send replies at least this often + # 0 disables +#hot_standby_feedback = off # send info from standby to prevent + # query conflicts +#wal_receiver_timeout = 60s # time that receiver waits for + # communication from primary + # in milliseconds; 0 disables +#wal_retrieve_retry_interval = 5s # time to wait before retrying to + # retrieve WAL after a failed attempt +#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery + +# - Subscribers - + +# These settings are ignored on a publisher. + +#max_logical_replication_workers = 4 # taken from max_worker_processes + # (change requires restart) +#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers + + +#------------------------------------------------------------------------------ +# QUERY TUNING +#------------------------------------------------------------------------------ + +# - Planner Method Configuration - + +#enable_async_append = on +#enable_bitmapscan = on +#enable_gathermerge = on +#enable_hashagg = on +#enable_hashjoin = on +#enable_incremental_sort = on +#enable_indexscan = on +#enable_indexonlyscan = on +#enable_material = on +#enable_memoize = on +#enable_mergejoin = on +#enable_nestloop = on +#enable_parallel_append = on +#enable_parallel_hash = on +#enable_partition_pruning = on +#enable_partitionwise_join = off +#enable_partitionwise_aggregate = off +#enable_seqscan = on +#enable_sort = on +#enable_tidscan = on + +# - Planner Cost Constants - + +#seq_page_cost = 1.0 # measured on an arbitrary scale +#random_page_cost = 4.0 # same scale as above +#cpu_tuple_cost = 0.01 # same scale as above +#cpu_index_tuple_cost = 0.005 # same scale as above +#cpu_operator_cost = 0.0025 # same scale as above +#parallel_setup_cost = 1000.0 # same scale as above +#parallel_tuple_cost = 0.1 # same scale as above +#min_parallel_table_scan_size = 8MB +#min_parallel_index_scan_size = 512kB +#effective_cache_size = 4GB + +#jit_above_cost = 100000 # perform JIT compilation if available + # and query more expensive than this; + # -1 disables +#jit_inline_above_cost = 500000 # inline small functions if query is + # more expensive than this; -1 disables +#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if + # query is more expensive than this; + # -1 disables + +# - Genetic Query Optimizer - + +#geqo = on +#geqo_threshold = 12 +#geqo_effort = 5 # range 1-10 +#geqo_pool_size = 0 # selects default based on effort +#geqo_generations = 0 # selects default based on effort +#geqo_selection_bias = 2.0 # range 1.5-2.0 +#geqo_seed = 0.0 # range 0.0-1.0 + +# - Other Planner Options - + +#default_statistics_target = 100 # range 1-10000 +#constraint_exclusion = partition # on, off, or partition +#cursor_tuple_fraction = 0.1 # range 0.0-1.0 +#from_collapse_limit = 8 +#jit = on # allow JIT compilation +#join_collapse_limit = 8 # 1 disables collapsing of explicit + # JOIN clauses +#plan_cache_mode = auto # auto, force_generic_plan or + # force_custom_plan +#recursive_worktable_factor = 10.0 # range 0.001-1000000 + + +#------------------------------------------------------------------------------ +# REPORTING AND LOGGING +#------------------------------------------------------------------------------ + +# - Where to Log - + +#log_destination = 'stderr' # Valid values are combinations of + # stderr, csvlog, jsonlog, syslog, and + # eventlog, depending on platform. + # csvlog and jsonlog require + # logging_collector to be on. + +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr, jsonlog, + # and csvlog into log files. Required + # to be on for csvlogs and jsonlogs. + # (change requires restart) + +# These are only used if logging_collector is on: +#log_directory = 'log' # directory where log files are written, + # can be absolute or relative to PGDATA +#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, + # can include strftime() escapes +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that time. 0 disables. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much log output. + # 0 disables. +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +# These are relevant when logging to syslog: +#syslog_facility = 'LOCAL0' +#syslog_ident = 'postgres' +#syslog_sequence_numbers = on +#syslog_split_messages = on + +# This is only relevant when logging to eventlog (Windows): +# (change requires restart) +#event_source = 'PostgreSQL' + +# - When to Log - + +#log_min_messages = warning # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic + +#log_min_error_statement = error # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic (effectively off) + +#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements + # and their durations, > 0 logs only + # statements running at least this number + # of milliseconds + +#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements + # and their durations, > 0 logs only a sample of + # statements running at least this number + # of milliseconds; + # sample fraction is determined by log_statement_sample_rate + +#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding + # log_min_duration_sample to be logged; + # 1.0 logs all such statements, 0.0 never logs + + +#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements + # are logged regardless of their duration; 1.0 logs all + # statements from all transactions, 0.0 never logs + +#log_startup_progress_interval = 10s # Time between progress updates for + # long-running startup operations. + # 0 disables the feature, > 0 indicates + # the interval in milliseconds. + +# - What to Log - + +#debug_print_parse = off +#debug_print_rewritten = off +#debug_print_plan = off +#debug_pretty_print = on +#log_autovacuum_min_duration = 10min # log autovacuum activity; + # -1 disables, 0 logs all actions and + # their durations, > 0 logs only + # actions running at least this number + # of milliseconds. +#log_checkpoints = on +#log_connections = off +#log_disconnections = off +#log_duration = off +#log_error_verbosity = default # terse, default, or verbose messages +#log_hostname = off +#log_line_prefix = '%m [%p] ' # special values: + # %a = application name + # %u = user name + # %d = database name + # %r = remote host and port + # %h = remote host + # %b = backend type + # %p = process ID + # %P = process ID of parallel group leader + # %t = timestamp without milliseconds + # %m = timestamp with milliseconds + # %n = timestamp with milliseconds (as a Unix epoch) + # %Q = query ID (0 if none or not computed) + # %i = command tag + # %e = SQL state + # %c = session ID + # %l = session line number + # %s = session start timestamp + # %v = virtual transaction ID + # %x = transaction ID (0 if none) + # %q = stop here in non-session + # processes + # %% = '%' + # e.g. '<%u%%%d> ' +#log_lock_waits = off # log lock waits >= deadlock_timeout +#log_recovery_conflict_waits = off # log standby recovery conflict waits + # >= deadlock_timeout +#log_parameter_max_length = -1 # when logging statements, limit logged + # bind-parameter values to N bytes; + # -1 means print in full, 0 disables +#log_parameter_max_length_on_error = 0 # when logging an error, limit logged + # bind-parameter values to N bytes; + # -1 means print in full, 0 disables +#log_statement = 'none' # none, ddl, mod, all +#log_replication_commands = off +#log_temp_files = -1 # log temporary files equal or larger + # than the specified size in kilobytes; + # -1 disables, 0 logs all temp files +#log_timezone = 'GMT' + + +#------------------------------------------------------------------------------ +# PROCESS TITLE +#------------------------------------------------------------------------------ + +#cluster_name = '' # added to process titles if nonempty + # (change requires restart) +#update_process_title = on + + +#------------------------------------------------------------------------------ +# STATISTICS +#------------------------------------------------------------------------------ + +# - Cumulative Query and Index Statistics - + +#track_activities = on +#track_activity_query_size = 1024 # (change requires restart) +#track_counts = on +#track_io_timing = off +#track_wal_io_timing = off +#track_functions = none # none, pl, all +#stats_fetch_consistency = cache + + +# - Monitoring - + +#compute_query_id = auto +#log_statement_stats = off +#log_parser_stats = off +#log_planner_stats = off +#log_executor_stats = off + + +#------------------------------------------------------------------------------ +# AUTOVACUUM +#------------------------------------------------------------------------------ + +#autovacuum = on # Enable autovacuum subprocess? 'on' + # requires track_counts to also be on. +#autovacuum_max_workers = 3 # max number of autovacuum subprocesses + # (change requires restart) +#autovacuum_naptime = 1min # time between autovacuum runs +#autovacuum_vacuum_threshold = 50 # min number of row updates before + # vacuum +#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts + # before vacuum; -1 disables insert + # vacuums +#autovacuum_analyze_threshold = 50 # min number of row updates before + # analyze +#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum +#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table + # size before insert vacuum +#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze +#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum + # (change requires restart) +#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age + # before forced vacuum + # (change requires restart) +#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for + # autovacuum, in milliseconds; + # -1 means use vacuum_cost_delay +#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for + # autovacuum, -1 means use + # vacuum_cost_limit + + +#------------------------------------------------------------------------------ +# CLIENT CONNECTION DEFAULTS +#------------------------------------------------------------------------------ + +# - Statement Behavior - + +#client_min_messages = notice # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # log + # notice + # warning + # error +#search_path = '"$user", public' # schema names +#row_security = on +#default_table_access_method = 'heap' +#default_tablespace = '' # a tablespace name, '' uses the default +#default_toast_compression = 'pglz' # 'pglz' or 'lz4' +#temp_tablespaces = '' # a list of tablespace names, '' uses + # only default tablespace +#check_function_bodies = on +#default_transaction_isolation = 'read committed' +#default_transaction_read_only = off +#default_transaction_deferrable = off +#session_replication_role = 'origin' +#statement_timeout = 0 # in milliseconds, 0 is disabled +#lock_timeout = 0 # in milliseconds, 0 is disabled +#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled +#idle_session_timeout = 0 # in milliseconds, 0 is disabled +#vacuum_freeze_table_age = 150000000 +#vacuum_freeze_min_age = 50000000 +#vacuum_failsafe_age = 1600000000 +#vacuum_multixact_freeze_table_age = 150000000 +#vacuum_multixact_freeze_min_age = 5000000 +#vacuum_multixact_failsafe_age = 1600000000 +#bytea_output = 'hex' # hex, escape +#xmlbinary = 'base64' +#xmloption = 'content' +#gin_pending_list_limit = 4MB + +# - Locale and Formatting - + +#datestyle = 'iso, mdy' +#intervalstyle = 'postgres' +#timezone = 'GMT' +#timezone_abbreviations = 'Default' # Select the set of available time zone + # abbreviations. Currently, there are + # Default + # Australia (historical usage) + # India + # You can create your own file in + # share/timezonesets/. +#extra_float_digits = 1 # min -15, max 3; any value >0 actually + # selects precise output mode +#client_encoding = sql_ascii # actually, defaults to database + # encoding + +# These settings are initialized by initdb, but they can be changed. +#lc_messages = 'C' # locale for system error message + # strings +#lc_monetary = 'C' # locale for monetary formatting +#lc_numeric = 'C' # locale for number formatting +#lc_time = 'C' # locale for time formatting + +# default configuration for text search +#default_text_search_config = 'pg_catalog.simple' + +# - Shared Library Preloading - + +#local_preload_libraries = '' +#session_preload_libraries = '' +#shared_preload_libraries = '' # (change requires restart) +#jit_provider = 'llvmjit' # JIT library to use + +# - Other Defaults - + +#dynamic_library_path = '$libdir' +#gin_fuzzy_search_limit = 0 + + +#------------------------------------------------------------------------------ +# LOCK MANAGEMENT +#------------------------------------------------------------------------------ + +#deadlock_timeout = 1s +#max_locks_per_transaction = 64 # min 10 + # (change requires restart) +#max_pred_locks_per_transaction = 64 # min 10 + # (change requires restart) +#max_pred_locks_per_relation = -2 # negative values mean + # (max_pred_locks_per_transaction + # / -max_pred_locks_per_relation) - 1 +#max_pred_locks_per_page = 2 # min 0 + + +#------------------------------------------------------------------------------ +# VERSION AND PLATFORM COMPATIBILITY +#------------------------------------------------------------------------------ + +# - Previous PostgreSQL Versions - + +#array_nulls = on +#backslash_quote = safe_encoding # on, off, or safe_encoding +#escape_string_warning = on +#lo_compat_privileges = off +#quote_all_identifiers = off +#standard_conforming_strings = on +#synchronize_seqscans = on + +# - Other Platforms and Clients - + +#transform_null_equals = off + + +#------------------------------------------------------------------------------ +# ERROR HANDLING +#------------------------------------------------------------------------------ + +#exit_on_error = off # terminate session on any error? +#restart_after_crash = on # reinitialize after backend crash? +#data_sync_retry = off # retry or panic on failure to fsync + # data? + # (change requires restart) +#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+) + + +#------------------------------------------------------------------------------ +# CONFIG FILE INCLUDES +#------------------------------------------------------------------------------ + +# These options allow settings to be loaded from files other than the +# default postgresql.conf. Note that these are directives, not variable +# assignments, so they can usefully be given more than once. + +#include_dir = '...' # include files ending in '.conf' from + # a directory, e.g., 'conf.d' +#include_if_exists = '...' # include file only if it exists +#include = '...' # include file + + +#------------------------------------------------------------------------------ +# CUSTOMIZED OPTIONS +#------------------------------------------------------------------------------ + +# Add settings for extensions here diff --git a/src/backend/utils/misc/ps_status.c b/src/backend/utils/misc/ps_status.c new file mode 100644 index 0000000..ec314c0 --- /dev/null +++ b/src/backend/utils/misc/ps_status.c @@ -0,0 +1,449 @@ +/*-------------------------------------------------------------------- + * ps_status.c + * + * Routines to support changing the ps display of PostgreSQL backends + * to contain some useful information. Mechanism differs wildly across + * platforms. + * + * src/backend/utils/misc/ps_status.c + * + * Copyright (c) 2000-2022, PostgreSQL Global Development Group + * various details abducted from various places + *-------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <unistd.h> +#ifdef HAVE_SYS_PSTAT_H +#include <sys/pstat.h> /* for HP-UX */ +#endif +#ifdef HAVE_PS_STRINGS +#include <machine/vmparam.h> /* for old BSD */ +#include <sys/exec.h> +#endif +#if defined(__darwin__) +#include <crt_externs.h> +#endif + +#include "libpq/libpq.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "utils/guc.h" +#include "utils/ps_status.h" + +extern char **environ; +bool update_process_title = true; + + +/* + * Alternative ways of updating ps display: + * + * PS_USE_SETPROCTITLE_FAST + * use the function setproctitle_fast(const char *, ...) + * (newer FreeBSD systems) + * PS_USE_SETPROCTITLE + * use the function setproctitle(const char *, ...) + * (newer BSD systems) + * PS_USE_PSTAT + * use the pstat(PSTAT_SETCMD, ) + * (HPUX) + * PS_USE_PS_STRINGS + * assign PS_STRINGS->ps_argvstr = "string" + * (some BSD systems) + * PS_USE_CHANGE_ARGV + * assign argv[0] = "string" + * (some other BSD systems) + * PS_USE_CLOBBER_ARGV + * write over the argv and environment area + * (Linux and most SysV-like systems) + * PS_USE_WIN32 + * push the string out as the name of a Windows event + * PS_USE_NONE + * don't update ps display + * (This is the default, as it is safest.) + */ +#if defined(HAVE_SETPROCTITLE_FAST) +#define PS_USE_SETPROCTITLE_FAST +#elif defined(HAVE_SETPROCTITLE) +#define PS_USE_SETPROCTITLE +#elif defined(HAVE_PSTAT) && defined(PSTAT_SETCMD) +#define PS_USE_PSTAT +#elif defined(HAVE_PS_STRINGS) +#define PS_USE_PS_STRINGS +#elif (defined(BSD) || defined(__hurd__)) && !defined(__darwin__) +#define PS_USE_CHANGE_ARGV +#elif defined(__linux__) || defined(_AIX) || defined(__sgi) || (defined(sun) && !defined(BSD)) || defined(__svr5__) || defined(__darwin__) +#define PS_USE_CLOBBER_ARGV +#elif defined(WIN32) +#define PS_USE_WIN32 +#else +#define PS_USE_NONE +#endif + + +/* Different systems want the buffer padded differently */ +#if defined(_AIX) || defined(__linux__) || defined(__darwin__) +#define PS_PADDING '\0' +#else +#define PS_PADDING ' ' +#endif + + +#ifndef PS_USE_NONE + +#ifndef PS_USE_CLOBBER_ARGV +/* all but one option need a buffer to write their ps line in */ +#define PS_BUFFER_SIZE 256 +static char ps_buffer[PS_BUFFER_SIZE]; +static const size_t ps_buffer_size = PS_BUFFER_SIZE; +#else /* PS_USE_CLOBBER_ARGV */ +static char *ps_buffer; /* will point to argv area */ +static size_t ps_buffer_size; /* space determined at run time */ +static size_t last_status_len; /* use to minimize length of clobber */ +#endif /* PS_USE_CLOBBER_ARGV */ + +static size_t ps_buffer_cur_len; /* nominal strlen(ps_buffer) */ + +static size_t ps_buffer_fixed_size; /* size of the constant prefix */ + +#endif /* not PS_USE_NONE */ + +/* save the original argv[] location here */ +static int save_argc; +static char **save_argv; + + +/* + * Call this early in startup to save the original argc/argv values. + * If needed, we make a copy of the original argv[] array to preserve it + * from being clobbered by subsequent ps_display actions. + * + * (The original argv[] will not be overwritten by this routine, but may be + * overwritten during init_ps_display. Also, the physical location of the + * environment strings may be moved, so this should be called before any code + * that might try to hang onto a getenv() result.) + * + * Note that in case of failure this cannot call elog() as that is not + * initialized yet. We rely on write_stderr() instead. + */ +char ** +save_ps_display_args(int argc, char **argv) +{ + save_argc = argc; + save_argv = argv; + +#if defined(PS_USE_CLOBBER_ARGV) + + /* + * If we're going to overwrite the argv area, count the available space. + * Also move the environment to make additional room. + */ + { + char *end_of_area = NULL; + char **new_environ; + int i; + + /* + * check for contiguous argv strings + */ + for (i = 0; i < argc; i++) + { + if (i == 0 || end_of_area + 1 == argv[i]) + end_of_area = argv[i] + strlen(argv[i]); + } + + if (end_of_area == NULL) /* probably can't happen? */ + { + ps_buffer = NULL; + ps_buffer_size = 0; + return argv; + } + + /* + * check for contiguous environ strings following argv + */ + for (i = 0; environ[i] != NULL; i++) + { + if (end_of_area + 1 == environ[i]) + end_of_area = environ[i] + strlen(environ[i]); + } + + ps_buffer = argv[0]; + last_status_len = ps_buffer_size = end_of_area - argv[0]; + + /* + * move the environment out of the way + */ + new_environ = (char **) malloc((i + 1) * sizeof(char *)); + if (!new_environ) + { + write_stderr("out of memory\n"); + exit(1); + } + for (i = 0; environ[i] != NULL; i++) + { + new_environ[i] = strdup(environ[i]); + if (!new_environ[i]) + { + write_stderr("out of memory\n"); + exit(1); + } + } + new_environ[i] = NULL; + environ = new_environ; + } +#endif /* PS_USE_CLOBBER_ARGV */ + +#if defined(PS_USE_CHANGE_ARGV) || defined(PS_USE_CLOBBER_ARGV) + + /* + * If we're going to change the original argv[] then make a copy for + * argument parsing purposes. + * + * (NB: do NOT think to remove the copying of argv[], even though + * postmaster.c finishes looking at argv[] long before we ever consider + * changing the ps display. On some platforms, getopt() keeps pointers + * into the argv array, and will get horribly confused when it is + * re-called to analyze a subprocess' argument string if the argv storage + * has been clobbered meanwhile. Other platforms have other dependencies + * on argv[]. + */ + { + char **new_argv; + int i; + + new_argv = (char **) malloc((argc + 1) * sizeof(char *)); + if (!new_argv) + { + write_stderr("out of memory\n"); + exit(1); + } + for (i = 0; i < argc; i++) + { + new_argv[i] = strdup(argv[i]); + if (!new_argv[i]) + { + write_stderr("out of memory\n"); + exit(1); + } + } + new_argv[argc] = NULL; + +#if defined(__darwin__) + + /* + * macOS (and perhaps other NeXT-derived platforms?) has a static copy + * of the argv pointer, which we may fix like so: + */ + *_NSGetArgv() = new_argv; +#endif + + argv = new_argv; + } +#endif /* PS_USE_CHANGE_ARGV or PS_USE_CLOBBER_ARGV */ + + return argv; +} + +/* + * Call this once during subprocess startup to set the identification + * values. + * + * If fixed_part is NULL, a default will be obtained from MyBackendType. + * + * At this point, the original argv[] array may be overwritten. + */ +void +init_ps_display(const char *fixed_part) +{ +#ifndef PS_USE_NONE + bool save_update_process_title; +#endif + + Assert(fixed_part || MyBackendType); + if (!fixed_part) + fixed_part = GetBackendTypeDesc(MyBackendType); + +#ifndef PS_USE_NONE + /* no ps display for stand-alone backend */ + if (!IsUnderPostmaster) + return; + + /* no ps display if you didn't call save_ps_display_args() */ + if (!save_argv) + return; + +#ifdef PS_USE_CLOBBER_ARGV + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + return; +#endif + + /* + * Overwrite argv[] to point at appropriate space, if needed + */ + +#ifdef PS_USE_CHANGE_ARGV + save_argv[0] = ps_buffer; + save_argv[1] = NULL; +#endif /* PS_USE_CHANGE_ARGV */ + +#ifdef PS_USE_CLOBBER_ARGV + { + int i; + + /* make extra argv slots point at end_of_area (a NUL) */ + for (i = 1; i < save_argc; i++) + save_argv[i] = ps_buffer + ps_buffer_size; + } +#endif /* PS_USE_CLOBBER_ARGV */ + + /* + * Make fixed prefix of ps display. + */ + +#if defined(PS_USE_SETPROCTITLE) || defined(PS_USE_SETPROCTITLE_FAST) + + /* + * apparently setproctitle() already adds a `progname:' prefix to the ps + * line + */ +#define PROGRAM_NAME_PREFIX "" +#else +#define PROGRAM_NAME_PREFIX "postgres: " +#endif + + if (*cluster_name == '\0') + { + snprintf(ps_buffer, ps_buffer_size, + PROGRAM_NAME_PREFIX "%s ", + fixed_part); + } + else + { + snprintf(ps_buffer, ps_buffer_size, + PROGRAM_NAME_PREFIX "%s: %s ", + cluster_name, fixed_part); + } + + ps_buffer_cur_len = ps_buffer_fixed_size = strlen(ps_buffer); + + /* + * On the first run, force the update. + */ + save_update_process_title = update_process_title; + update_process_title = true; + set_ps_display(""); + update_process_title = save_update_process_title; +#endif /* not PS_USE_NONE */ +} + + + +/* + * Call this to update the ps status display to a fixed prefix plus an + * indication of what you're currently doing passed in the argument. + */ +void +set_ps_display(const char *activity) +{ +#ifndef PS_USE_NONE + /* update_process_title=off disables updates */ + if (!update_process_title) + return; + + /* no ps display for stand-alone backend */ + if (!IsUnderPostmaster) + return; + +#ifdef PS_USE_CLOBBER_ARGV + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + return; +#endif + + /* Update ps_buffer to contain both fixed part and activity */ + strlcpy(ps_buffer + ps_buffer_fixed_size, activity, + ps_buffer_size - ps_buffer_fixed_size); + ps_buffer_cur_len = strlen(ps_buffer); + + /* Transmit new setting to kernel, if necessary */ + +#ifdef PS_USE_SETPROCTITLE + setproctitle("%s", ps_buffer); +#elif defined(PS_USE_SETPROCTITLE_FAST) + setproctitle_fast("%s", ps_buffer); +#endif + +#ifdef PS_USE_PSTAT + { + union pstun pst; + + pst.pst_command = ps_buffer; + pstat(PSTAT_SETCMD, pst, ps_buffer_cur_len, 0, 0); + } +#endif /* PS_USE_PSTAT */ + +#ifdef PS_USE_PS_STRINGS + PS_STRINGS->ps_nargvstr = 1; + PS_STRINGS->ps_argvstr = ps_buffer; +#endif /* PS_USE_PS_STRINGS */ + +#ifdef PS_USE_CLOBBER_ARGV + /* pad unused memory; need only clobber remainder of old status string */ + if (last_status_len > ps_buffer_cur_len) + MemSet(ps_buffer + ps_buffer_cur_len, PS_PADDING, + last_status_len - ps_buffer_cur_len); + last_status_len = ps_buffer_cur_len; +#endif /* PS_USE_CLOBBER_ARGV */ + +#ifdef PS_USE_WIN32 + { + /* + * Win32 does not support showing any changed arguments. To make it at + * all possible to track which backend is doing what, we create a + * named object that can be viewed with for example Process Explorer. + */ + static HANDLE ident_handle = INVALID_HANDLE_VALUE; + char name[PS_BUFFER_SIZE + 32]; + + if (ident_handle != INVALID_HANDLE_VALUE) + CloseHandle(ident_handle); + + sprintf(name, "pgident(%d): %s", MyProcPid, ps_buffer); + + ident_handle = CreateEvent(NULL, TRUE, FALSE, name); + } +#endif /* PS_USE_WIN32 */ +#endif /* not PS_USE_NONE */ +} + + +/* + * Returns what's currently in the ps display, in case someone needs + * it. Note that only the activity part is returned. On some platforms + * the string will not be null-terminated, so return the effective + * length into *displen. + */ +const char * +get_ps_display(int *displen) +{ +#ifdef PS_USE_CLOBBER_ARGV + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + { + *displen = 0; + return ""; + } +#endif + +#ifndef PS_USE_NONE + *displen = (int) (ps_buffer_cur_len - ps_buffer_fixed_size); + + return ps_buffer + ps_buffer_fixed_size; +#else + *displen = 0; + return ""; +#endif +} diff --git a/src/backend/utils/misc/queryenvironment.c b/src/backend/utils/misc/queryenvironment.c new file mode 100644 index 0000000..3a065b2 --- /dev/null +++ b/src/backend/utils/misc/queryenvironment.c @@ -0,0 +1,144 @@ +/*------------------------------------------------------------------------- + * + * queryenvironment.c + * Query environment, to store context-specific values like ephemeral named + * relations. Initial use is for named tuplestores for delta information + * from "normal" relations. + * + * The initial implementation uses a list because the number of such relations + * in any one context is expected to be very small. If that becomes a + * performance problem, the implementation can be changed with no other impact + * on callers, since this is an opaque structure. This is the reason to + * require a create function. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/queryenvironment.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/table.h" +#include "utils/queryenvironment.h" +#include "utils/rel.h" + +/* + * Private state of a query environment. + */ +struct QueryEnvironment +{ + List *namedRelList; +}; + + +QueryEnvironment * +create_queryEnv(void) +{ + return (QueryEnvironment *) palloc0(sizeof(QueryEnvironment)); +} + +EphemeralNamedRelationMetadata +get_visible_ENR_metadata(QueryEnvironment *queryEnv, const char *refname) +{ + EphemeralNamedRelation enr; + + Assert(refname != NULL); + + if (queryEnv == NULL) + return NULL; + + enr = get_ENR(queryEnv, refname); + + if (enr) + return &(enr->md); + + return NULL; +} + +/* + * Register a named relation for use in the given environment. + * + * If this is intended exclusively for planning purposes, the tstate field can + * be left NULL; + */ +void +register_ENR(QueryEnvironment *queryEnv, EphemeralNamedRelation enr) +{ + Assert(enr != NULL); + Assert(get_ENR(queryEnv, enr->md.name) == NULL); + + queryEnv->namedRelList = lappend(queryEnv->namedRelList, enr); +} + +/* + * Unregister an ephemeral relation by name. This will probably be a rarely + * used function, but seems like it should be provided "just in case". + */ +void +unregister_ENR(QueryEnvironment *queryEnv, const char *name) +{ + EphemeralNamedRelation match; + + match = get_ENR(queryEnv, name); + if (match) + queryEnv->namedRelList = list_delete(queryEnv->namedRelList, match); +} + +/* + * This returns an ENR if there is a name match in the given collection. It + * must quietly return NULL if no match is found. + */ +EphemeralNamedRelation +get_ENR(QueryEnvironment *queryEnv, const char *name) +{ + ListCell *lc; + + Assert(name != NULL); + + if (queryEnv == NULL) + return NULL; + + foreach(lc, queryEnv->namedRelList) + { + EphemeralNamedRelation enr = (EphemeralNamedRelation) lfirst(lc); + + if (strcmp(enr->md.name, name) == 0) + return enr; + } + + return NULL; +} + +/* + * Gets the TupleDesc for a Ephemeral Named Relation, based on which field was + * filled. + * + * When the TupleDesc is based on a relation from the catalogs, we count on + * that relation being used at the same time, so that appropriate locks will + * already be held. Locking here would be too late anyway. + */ +TupleDesc +ENRMetadataGetTupDesc(EphemeralNamedRelationMetadata enrmd) +{ + TupleDesc tupdesc; + + /* One, and only one, of these fields must be filled. */ + Assert((enrmd->reliddesc == InvalidOid) != (enrmd->tupdesc == NULL)); + + if (enrmd->tupdesc != NULL) + tupdesc = enrmd->tupdesc; + else + { + Relation relation; + + relation = table_open(enrmd->reliddesc, NoLock); + tupdesc = relation->rd_att; + table_close(relation, NoLock); + } + + return tupdesc; +} diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c new file mode 100644 index 0000000..1224bb6 --- /dev/null +++ b/src/backend/utils/misc/queryjumble.c @@ -0,0 +1,869 @@ +/*------------------------------------------------------------------------- + * + * queryjumble.c + * Query normalization and fingerprinting. + * + * Normalization is a process whereby similar queries, typically differing only + * in their constants (though the exact rules are somewhat more subtle than + * that) are recognized as equivalent, and are tracked as a single entry. This + * is particularly useful for non-prepared queries. + * + * Normalization is implemented by fingerprinting queries, selectively + * serializing those fields of each query tree's nodes that are judged to be + * essential to the query. This is referred to as a query jumble. This is + * distinct from a regular serialization in that various extraneous + * information is ignored as irrelevant or not essential to the query, such + * as the collations of Vars and, most notably, the values of constants. + * + * This jumble is acquired at the end of parse analysis of each query, and + * a 64-bit hash of it is stored into the query's Query.queryId field. + * The server then copies this value around, making it available in plan + * tree(s) generated from the query. The executor can then use this value + * to blame query costs on the proper queryId. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/queryjumble.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/hashfn.h" +#include "miscadmin.h" +#include "parser/scansup.h" +#include "utils/queryjumble.h" + +#define JUMBLE_SIZE 1024 /* query serialization buffer size */ + +/* GUC parameters */ +int compute_query_id = COMPUTE_QUERY_ID_AUTO; + +/* True when compute_query_id is ON, or AUTO and a module requests them */ +bool query_id_enabled = false; + +static uint64 compute_utility_query_id(const char *str, int query_location, int query_len); +static void AppendJumble(JumbleState *jstate, + const unsigned char *item, Size size); +static void JumbleQueryInternal(JumbleState *jstate, Query *query); +static void JumbleRangeTable(JumbleState *jstate, List *rtable); +static void JumbleRowMarks(JumbleState *jstate, List *rowMarks); +static void JumbleExpr(JumbleState *jstate, Node *node); +static void RecordConstLocation(JumbleState *jstate, int location); + +/* + * Given a possibly multi-statement source string, confine our attention to the + * relevant part of the string. + */ +const char * +CleanQuerytext(const char *query, int *location, int *len) +{ + int query_location = *location; + int query_len = *len; + + /* First apply starting offset, unless it's -1 (unknown). */ + if (query_location >= 0) + { + Assert(query_location <= strlen(query)); + query += query_location; + /* Length of 0 (or -1) means "rest of string" */ + if (query_len <= 0) + query_len = strlen(query); + else + Assert(query_len <= strlen(query)); + } + else + { + /* If query location is unknown, distrust query_len as well */ + query_location = 0; + query_len = strlen(query); + } + + /* + * Discard leading and trailing whitespace, too. Use scanner_isspace() + * not libc's isspace(), because we want to match the lexer's behavior. + */ + while (query_len > 0 && scanner_isspace(query[0])) + query++, query_location++, query_len--; + while (query_len > 0 && scanner_isspace(query[query_len - 1])) + query_len--; + + *location = query_location; + *len = query_len; + + return query; +} + +JumbleState * +JumbleQuery(Query *query, const char *querytext) +{ + JumbleState *jstate = NULL; + + Assert(IsQueryIdEnabled()); + + if (query->utilityStmt) + { + query->queryId = compute_utility_query_id(querytext, + query->stmt_location, + query->stmt_len); + } + else + { + jstate = (JumbleState *) palloc(sizeof(JumbleState)); + + /* Set up workspace for query jumbling */ + jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE); + jstate->jumble_len = 0; + jstate->clocations_buf_size = 32; + jstate->clocations = (LocationLen *) + palloc(jstate->clocations_buf_size * sizeof(LocationLen)); + jstate->clocations_count = 0; + jstate->highest_extern_param_id = 0; + + /* Compute query ID and mark the Query node with it */ + JumbleQueryInternal(jstate, query); + query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble, + jstate->jumble_len, + 0)); + + /* + * If we are unlucky enough to get a hash of zero, use 1 instead, to + * prevent confusion with the utility-statement case. + */ + if (query->queryId == UINT64CONST(0)) + query->queryId = UINT64CONST(1); + } + + return jstate; +} + +/* + * Enables query identifier computation. + * + * Third-party plugins can use this function to inform core that they require + * a query identifier to be computed. + */ +void +EnableQueryId(void) +{ + if (compute_query_id != COMPUTE_QUERY_ID_OFF) + query_id_enabled = true; +} + +/* + * Compute a query identifier for the given utility query string. + */ +static uint64 +compute_utility_query_id(const char *query_text, int query_location, int query_len) +{ + uint64 queryId; + const char *sql; + + /* + * Confine our attention to the relevant part of the string, if the query + * is a portion of a multi-statement source string. + */ + sql = CleanQuerytext(query_text, &query_location, &query_len); + + queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql, + query_len, 0)); + + /* + * If we are unlucky enough to get a hash of zero(invalid), use queryID as + * 2 instead, queryID 1 is already in use for normal statements. + */ + if (queryId == UINT64CONST(0)) + queryId = UINT64CONST(2); + + return queryId; +} + +/* + * AppendJumble: Append a value that is substantive in a given query to + * the current jumble. + */ +static void +AppendJumble(JumbleState *jstate, const unsigned char *item, Size size) +{ + unsigned char *jumble = jstate->jumble; + Size jumble_len = jstate->jumble_len; + + /* + * Whenever the jumble buffer is full, we hash the current contents and + * reset the buffer to contain just that hash value, thus relying on the + * hash to summarize everything so far. + */ + while (size > 0) + { + Size part_size; + + if (jumble_len >= JUMBLE_SIZE) + { + uint64 start_hash; + + start_hash = DatumGetUInt64(hash_any_extended(jumble, + JUMBLE_SIZE, 0)); + memcpy(jumble, &start_hash, sizeof(start_hash)); + jumble_len = sizeof(start_hash); + } + part_size = Min(size, JUMBLE_SIZE - jumble_len); + memcpy(jumble + jumble_len, item, part_size); + jumble_len += part_size; + item += part_size; + size -= part_size; + } + jstate->jumble_len = jumble_len; +} + +/* + * Wrappers around AppendJumble to encapsulate details of serialization + * of individual local variable elements. + */ +#define APP_JUMB(item) \ + AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item)) +#define APP_JUMB_STRING(str) \ + AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1) + +/* + * JumbleQueryInternal: Selectively serialize the query tree, appending + * significant data to the "query jumble" while ignoring nonsignificant data. + * + * Rule of thumb for what to include is that we should ignore anything not + * semantically significant (such as alias names) as well as anything that can + * be deduced from child nodes (else we'd just be double-hashing that piece + * of information). + */ +static void +JumbleQueryInternal(JumbleState *jstate, Query *query) +{ + Assert(IsA(query, Query)); + Assert(query->utilityStmt == NULL); + + APP_JUMB(query->commandType); + /* resultRelation is usually predictable from commandType */ + JumbleExpr(jstate, (Node *) query->cteList); + JumbleRangeTable(jstate, query->rtable); + JumbleExpr(jstate, (Node *) query->jointree); + JumbleExpr(jstate, (Node *) query->mergeActionList); + JumbleExpr(jstate, (Node *) query->targetList); + JumbleExpr(jstate, (Node *) query->onConflict); + JumbleExpr(jstate, (Node *) query->returningList); + JumbleExpr(jstate, (Node *) query->groupClause); + APP_JUMB(query->groupDistinct); + JumbleExpr(jstate, (Node *) query->groupingSets); + JumbleExpr(jstate, query->havingQual); + JumbleExpr(jstate, (Node *) query->windowClause); + JumbleExpr(jstate, (Node *) query->distinctClause); + JumbleExpr(jstate, (Node *) query->sortClause); + JumbleExpr(jstate, query->limitOffset); + JumbleExpr(jstate, query->limitCount); + APP_JUMB(query->limitOption); + JumbleRowMarks(jstate, query->rowMarks); + JumbleExpr(jstate, query->setOperations); +} + +/* + * Jumble a range table + */ +static void +JumbleRangeTable(JumbleState *jstate, List *rtable) +{ + ListCell *lc; + + foreach(lc, rtable) + { + RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + + APP_JUMB(rte->rtekind); + switch (rte->rtekind) + { + case RTE_RELATION: + APP_JUMB(rte->relid); + JumbleExpr(jstate, (Node *) rte->tablesample); + APP_JUMB(rte->inh); + break; + case RTE_SUBQUERY: + JumbleQueryInternal(jstate, rte->subquery); + break; + case RTE_JOIN: + APP_JUMB(rte->jointype); + break; + case RTE_FUNCTION: + JumbleExpr(jstate, (Node *) rte->functions); + break; + case RTE_TABLEFUNC: + JumbleExpr(jstate, (Node *) rte->tablefunc); + break; + case RTE_VALUES: + JumbleExpr(jstate, (Node *) rte->values_lists); + break; + case RTE_CTE: + + /* + * Depending on the CTE name here isn't ideal, but it's the + * only info we have to identify the referenced WITH item. + */ + APP_JUMB_STRING(rte->ctename); + APP_JUMB(rte->ctelevelsup); + break; + case RTE_NAMEDTUPLESTORE: + APP_JUMB_STRING(rte->enrname); + break; + case RTE_RESULT: + break; + default: + elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); + break; + } + } +} + +/* + * Jumble a rowMarks list + */ +static void +JumbleRowMarks(JumbleState *jstate, List *rowMarks) +{ + ListCell *lc; + + foreach(lc, rowMarks) + { + RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc); + + if (!rowmark->pushedDown) + { + APP_JUMB(rowmark->rti); + APP_JUMB(rowmark->strength); + APP_JUMB(rowmark->waitPolicy); + } + } +} + +/* + * Jumble an expression tree + * + * In general this function should handle all the same node types that + * expression_tree_walker() does, and therefore it's coded to be as parallel + * to that function as possible. However, since we are only invoked on + * queries immediately post-parse-analysis, we need not handle node types + * that only appear in planning. + * + * Note: the reason we don't simply use expression_tree_walker() is that the + * point of that function is to support tree walkers that don't care about + * most tree node types, but here we care about all types. We should complain + * about any unrecognized node type. + */ +static void +JumbleExpr(JumbleState *jstate, Node *node) +{ + ListCell *temp; + + if (node == NULL) + return; + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + + /* + * We always emit the node's NodeTag, then any additional fields that are + * considered significant, and then we recurse to any child nodes. + */ + APP_JUMB(node->type); + + switch (nodeTag(node)) + { + case T_Var: + { + Var *var = (Var *) node; + + APP_JUMB(var->varno); + APP_JUMB(var->varattno); + APP_JUMB(var->varlevelsup); + } + break; + case T_Const: + { + Const *c = (Const *) node; + + /* We jumble only the constant's type, not its value */ + APP_JUMB(c->consttype); + /* Also, record its parse location for query normalization */ + RecordConstLocation(jstate, c->location); + } + break; + case T_Param: + { + Param *p = (Param *) node; + + APP_JUMB(p->paramkind); + APP_JUMB(p->paramid); + APP_JUMB(p->paramtype); + /* Also, track the highest external Param id */ + if (p->paramkind == PARAM_EXTERN && + p->paramid > jstate->highest_extern_param_id) + jstate->highest_extern_param_id = p->paramid; + } + break; + case T_Aggref: + { + Aggref *expr = (Aggref *) node; + + APP_JUMB(expr->aggfnoid); + JumbleExpr(jstate, (Node *) expr->aggdirectargs); + JumbleExpr(jstate, (Node *) expr->args); + JumbleExpr(jstate, (Node *) expr->aggorder); + JumbleExpr(jstate, (Node *) expr->aggdistinct); + JumbleExpr(jstate, (Node *) expr->aggfilter); + } + break; + case T_GroupingFunc: + { + GroupingFunc *grpnode = (GroupingFunc *) node; + + JumbleExpr(jstate, (Node *) grpnode->refs); + APP_JUMB(grpnode->agglevelsup); + } + break; + case T_WindowFunc: + { + WindowFunc *expr = (WindowFunc *) node; + + APP_JUMB(expr->winfnoid); + APP_JUMB(expr->winref); + JumbleExpr(jstate, (Node *) expr->args); + JumbleExpr(jstate, (Node *) expr->aggfilter); + } + break; + case T_SubscriptingRef: + { + SubscriptingRef *sbsref = (SubscriptingRef *) node; + + JumbleExpr(jstate, (Node *) sbsref->refupperindexpr); + JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr); + JumbleExpr(jstate, (Node *) sbsref->refexpr); + JumbleExpr(jstate, (Node *) sbsref->refassgnexpr); + } + break; + case T_FuncExpr: + { + FuncExpr *expr = (FuncExpr *) node; + + APP_JUMB(expr->funcid); + JumbleExpr(jstate, (Node *) expr->args); + } + break; + case T_NamedArgExpr: + { + NamedArgExpr *nae = (NamedArgExpr *) node; + + APP_JUMB(nae->argnumber); + JumbleExpr(jstate, (Node *) nae->arg); + } + break; + case T_OpExpr: + case T_DistinctExpr: /* struct-equivalent to OpExpr */ + case T_NullIfExpr: /* struct-equivalent to OpExpr */ + { + OpExpr *expr = (OpExpr *) node; + + APP_JUMB(expr->opno); + JumbleExpr(jstate, (Node *) expr->args); + } + break; + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; + + APP_JUMB(expr->opno); + APP_JUMB(expr->useOr); + JumbleExpr(jstate, (Node *) expr->args); + } + break; + case T_BoolExpr: + { + BoolExpr *expr = (BoolExpr *) node; + + APP_JUMB(expr->boolop); + JumbleExpr(jstate, (Node *) expr->args); + } + break; + case T_SubLink: + { + SubLink *sublink = (SubLink *) node; + + APP_JUMB(sublink->subLinkType); + APP_JUMB(sublink->subLinkId); + JumbleExpr(jstate, (Node *) sublink->testexpr); + JumbleQueryInternal(jstate, castNode(Query, sublink->subselect)); + } + break; + case T_FieldSelect: + { + FieldSelect *fs = (FieldSelect *) node; + + APP_JUMB(fs->fieldnum); + JumbleExpr(jstate, (Node *) fs->arg); + } + break; + case T_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + + JumbleExpr(jstate, (Node *) fstore->arg); + JumbleExpr(jstate, (Node *) fstore->newvals); + } + break; + case T_RelabelType: + { + RelabelType *rt = (RelabelType *) node; + + APP_JUMB(rt->resulttype); + JumbleExpr(jstate, (Node *) rt->arg); + } + break; + case T_CoerceViaIO: + { + CoerceViaIO *cio = (CoerceViaIO *) node; + + APP_JUMB(cio->resulttype); + JumbleExpr(jstate, (Node *) cio->arg); + } + break; + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node; + + APP_JUMB(acexpr->resulttype); + JumbleExpr(jstate, (Node *) acexpr->arg); + JumbleExpr(jstate, (Node *) acexpr->elemexpr); + } + break; + case T_ConvertRowtypeExpr: + { + ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node; + + APP_JUMB(crexpr->resulttype); + JumbleExpr(jstate, (Node *) crexpr->arg); + } + break; + case T_CollateExpr: + { + CollateExpr *ce = (CollateExpr *) node; + + APP_JUMB(ce->collOid); + JumbleExpr(jstate, (Node *) ce->arg); + } + break; + case T_CaseExpr: + { + CaseExpr *caseexpr = (CaseExpr *) node; + + JumbleExpr(jstate, (Node *) caseexpr->arg); + foreach(temp, caseexpr->args) + { + CaseWhen *when = lfirst_node(CaseWhen, temp); + + JumbleExpr(jstate, (Node *) when->expr); + JumbleExpr(jstate, (Node *) when->result); + } + JumbleExpr(jstate, (Node *) caseexpr->defresult); + } + break; + case T_CaseTestExpr: + { + CaseTestExpr *ct = (CaseTestExpr *) node; + + APP_JUMB(ct->typeId); + } + break; + case T_ArrayExpr: + JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements); + break; + case T_RowExpr: + JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args); + break; + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + + APP_JUMB(rcexpr->rctype); + JumbleExpr(jstate, (Node *) rcexpr->largs); + JumbleExpr(jstate, (Node *) rcexpr->rargs); + } + break; + case T_CoalesceExpr: + JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args); + break; + case T_MinMaxExpr: + { + MinMaxExpr *mmexpr = (MinMaxExpr *) node; + + APP_JUMB(mmexpr->op); + JumbleExpr(jstate, (Node *) mmexpr->args); + } + break; + case T_SQLValueFunction: + { + SQLValueFunction *svf = (SQLValueFunction *) node; + + APP_JUMB(svf->op); + /* type is fully determined by op */ + APP_JUMB(svf->typmod); + } + break; + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + + APP_JUMB(xexpr->op); + JumbleExpr(jstate, (Node *) xexpr->named_args); + JumbleExpr(jstate, (Node *) xexpr->args); + } + break; + case T_NullTest: + { + NullTest *nt = (NullTest *) node; + + APP_JUMB(nt->nulltesttype); + JumbleExpr(jstate, (Node *) nt->arg); + } + break; + case T_BooleanTest: + { + BooleanTest *bt = (BooleanTest *) node; + + APP_JUMB(bt->booltesttype); + JumbleExpr(jstate, (Node *) bt->arg); + } + break; + case T_CoerceToDomain: + { + CoerceToDomain *cd = (CoerceToDomain *) node; + + APP_JUMB(cd->resulttype); + JumbleExpr(jstate, (Node *) cd->arg); + } + break; + case T_CoerceToDomainValue: + { + CoerceToDomainValue *cdv = (CoerceToDomainValue *) node; + + APP_JUMB(cdv->typeId); + } + break; + case T_SetToDefault: + { + SetToDefault *sd = (SetToDefault *) node; + + APP_JUMB(sd->typeId); + } + break; + case T_CurrentOfExpr: + { + CurrentOfExpr *ce = (CurrentOfExpr *) node; + + APP_JUMB(ce->cvarno); + if (ce->cursor_name) + APP_JUMB_STRING(ce->cursor_name); + APP_JUMB(ce->cursor_param); + } + break; + case T_NextValueExpr: + { + NextValueExpr *nve = (NextValueExpr *) node; + + APP_JUMB(nve->seqid); + APP_JUMB(nve->typeId); + } + break; + case T_InferenceElem: + { + InferenceElem *ie = (InferenceElem *) node; + + APP_JUMB(ie->infercollid); + APP_JUMB(ie->inferopclass); + JumbleExpr(jstate, ie->expr); + } + break; + case T_TargetEntry: + { + TargetEntry *tle = (TargetEntry *) node; + + APP_JUMB(tle->resno); + APP_JUMB(tle->ressortgroupref); + JumbleExpr(jstate, (Node *) tle->expr); + } + break; + case T_RangeTblRef: + { + RangeTblRef *rtr = (RangeTblRef *) node; + + APP_JUMB(rtr->rtindex); + } + break; + case T_JoinExpr: + { + JoinExpr *join = (JoinExpr *) node; + + APP_JUMB(join->jointype); + APP_JUMB(join->isNatural); + APP_JUMB(join->rtindex); + JumbleExpr(jstate, join->larg); + JumbleExpr(jstate, join->rarg); + JumbleExpr(jstate, join->quals); + } + break; + case T_FromExpr: + { + FromExpr *from = (FromExpr *) node; + + JumbleExpr(jstate, (Node *) from->fromlist); + JumbleExpr(jstate, from->quals); + } + break; + case T_OnConflictExpr: + { + OnConflictExpr *conf = (OnConflictExpr *) node; + + APP_JUMB(conf->action); + JumbleExpr(jstate, (Node *) conf->arbiterElems); + JumbleExpr(jstate, conf->arbiterWhere); + JumbleExpr(jstate, (Node *) conf->onConflictSet); + JumbleExpr(jstate, conf->onConflictWhere); + APP_JUMB(conf->constraint); + APP_JUMB(conf->exclRelIndex); + JumbleExpr(jstate, (Node *) conf->exclRelTlist); + } + break; + case T_MergeAction: + { + MergeAction *mergeaction = (MergeAction *) node; + + APP_JUMB(mergeaction->matched); + APP_JUMB(mergeaction->commandType); + JumbleExpr(jstate, mergeaction->qual); + JumbleExpr(jstate, (Node *) mergeaction->targetList); + } + break; + case T_List: + foreach(temp, (List *) node) + { + JumbleExpr(jstate, (Node *) lfirst(temp)); + } + break; + case T_IntList: + foreach(temp, (List *) node) + { + APP_JUMB(lfirst_int(temp)); + } + break; + case T_SortGroupClause: + { + SortGroupClause *sgc = (SortGroupClause *) node; + + APP_JUMB(sgc->tleSortGroupRef); + APP_JUMB(sgc->eqop); + APP_JUMB(sgc->sortop); + APP_JUMB(sgc->nulls_first); + } + break; + case T_GroupingSet: + { + GroupingSet *gsnode = (GroupingSet *) node; + + JumbleExpr(jstate, (Node *) gsnode->content); + } + break; + case T_WindowClause: + { + WindowClause *wc = (WindowClause *) node; + + APP_JUMB(wc->winref); + APP_JUMB(wc->frameOptions); + JumbleExpr(jstate, (Node *) wc->partitionClause); + JumbleExpr(jstate, (Node *) wc->orderClause); + JumbleExpr(jstate, wc->startOffset); + JumbleExpr(jstate, wc->endOffset); + } + break; + case T_CommonTableExpr: + { + CommonTableExpr *cte = (CommonTableExpr *) node; + + /* we store the string name because RTE_CTE RTEs need it */ + APP_JUMB_STRING(cte->ctename); + APP_JUMB(cte->ctematerialized); + JumbleQueryInternal(jstate, castNode(Query, cte->ctequery)); + } + break; + case T_SetOperationStmt: + { + SetOperationStmt *setop = (SetOperationStmt *) node; + + APP_JUMB(setop->op); + APP_JUMB(setop->all); + JumbleExpr(jstate, setop->larg); + JumbleExpr(jstate, setop->rarg); + } + break; + case T_RangeTblFunction: + { + RangeTblFunction *rtfunc = (RangeTblFunction *) node; + + JumbleExpr(jstate, rtfunc->funcexpr); + } + break; + case T_TableFunc: + { + TableFunc *tablefunc = (TableFunc *) node; + + JumbleExpr(jstate, tablefunc->docexpr); + JumbleExpr(jstate, tablefunc->rowexpr); + JumbleExpr(jstate, (Node *) tablefunc->colexprs); + } + break; + case T_TableSampleClause: + { + TableSampleClause *tsc = (TableSampleClause *) node; + + APP_JUMB(tsc->tsmhandler); + JumbleExpr(jstate, (Node *) tsc->args); + JumbleExpr(jstate, (Node *) tsc->repeatable); + } + break; + default: + /* Only a warning, since we can stumble along anyway */ + elog(WARNING, "unrecognized node type: %d", + (int) nodeTag(node)); + break; + } +} + +/* + * Record location of constant within query string of query tree + * that is currently being walked. + */ +static void +RecordConstLocation(JumbleState *jstate, int location) +{ + /* -1 indicates unknown or undefined location */ + if (location >= 0) + { + /* enlarge array if needed */ + if (jstate->clocations_count >= jstate->clocations_buf_size) + { + jstate->clocations_buf_size *= 2; + jstate->clocations = (LocationLen *) + repalloc(jstate->clocations, + jstate->clocations_buf_size * + sizeof(LocationLen)); + } + jstate->clocations[jstate->clocations_count].location = location; + /* initialize lengths to -1 to simplify third-party module usage */ + jstate->clocations[jstate->clocations_count].length = -1; + jstate->clocations_count++; + } +} diff --git a/src/backend/utils/misc/rls.c b/src/backend/utils/misc/rls.c new file mode 100644 index 0000000..d158806 --- /dev/null +++ b/src/backend/utils/misc/rls.c @@ -0,0 +1,167 @@ +/*------------------------------------------------------------------------- + * + * rls.c + * RLS-related utility functions. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/rls.c + * + *------------------------------------------------------------------------- +*/ +#include "postgres.h" + +#include "access/htup.h" +#include "access/htup_details.h" +#include "access/transam.h" +#include "catalog/namespace.h" +#include "catalog/pg_class.h" +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rls.h" +#include "utils/syscache.h" +#include "utils/varlena.h" + + +/* + * check_enable_rls + * + * Determine, based on the relation, row_security setting, and current role, + * if RLS is applicable to this query. RLS_NONE_ENV indicates that, while + * RLS is not to be added for this query, a change in the environment may change + * that. RLS_NONE means that RLS is not on the relation at all and therefore + * we don't need to worry about it. RLS_ENABLED means RLS should be implemented + * for the table and the plan cache needs to be invalidated if the environment + * changes. + * + * Handle checking as another role via checkAsUser (for views, etc). Pass + * InvalidOid to check the current user. + * + * If noError is set to 'true' then we just return RLS_ENABLED instead of doing + * an ereport() if the user has attempted to bypass RLS and they are not + * allowed to. This allows users to check if RLS is enabled without having to + * deal with the actual error case (eg: error cases which are trying to decide + * if the user should get data from the relation back as part of the error). + */ +int +check_enable_rls(Oid relid, Oid checkAsUser, bool noError) +{ + Oid user_id = checkAsUser ? checkAsUser : GetUserId(); + HeapTuple tuple; + Form_pg_class classform; + bool relrowsecurity; + bool relforcerowsecurity; + bool amowner; + + /* Nothing to do for built-in relations */ + if (relid < (Oid) FirstNormalObjectId) + return RLS_NONE; + + /* Fetch relation's relrowsecurity and relforcerowsecurity flags */ + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + return RLS_NONE; + classform = (Form_pg_class) GETSTRUCT(tuple); + + relrowsecurity = classform->relrowsecurity; + relforcerowsecurity = classform->relforcerowsecurity; + + ReleaseSysCache(tuple); + + /* Nothing to do if the relation does not have RLS */ + if (!relrowsecurity) + return RLS_NONE; + + /* + * BYPASSRLS users always bypass RLS. Note that superusers are always + * considered to have BYPASSRLS. + * + * Return RLS_NONE_ENV to indicate that this decision depends on the + * environment (in this case, the user_id). + */ + if (has_bypassrls_privilege(user_id)) + return RLS_NONE_ENV; + + /* + * Table owners generally bypass RLS, except if the table has been set (by + * an owner) to FORCE ROW SECURITY, and this is not a referential + * integrity check. + * + * Return RLS_NONE_ENV to indicate that this decision depends on the + * environment (in this case, the user_id). + */ + amowner = pg_class_ownercheck(relid, user_id); + if (amowner) + { + /* + * If FORCE ROW LEVEL SECURITY has been set on the relation then we + * should return RLS_ENABLED to indicate that RLS should be applied. + * If not, or if we are in an InNoForceRLSOperation context, we return + * RLS_NONE_ENV. + * + * InNoForceRLSOperation indicates that we should not apply RLS even + * if the table has FORCE RLS set - IF the current user is the owner. + * This is specifically to ensure that referential integrity checks + * are able to still run correctly. + * + * This is intentionally only done after we have checked that the user + * is the table owner, which should always be the case for referential + * integrity checks. + */ + if (!relforcerowsecurity || InNoForceRLSOperation()) + return RLS_NONE_ENV; + } + + /* + * We should apply RLS. However, the user may turn off the row_security + * GUC to get a forced error instead. + */ + if (!row_security && !noError) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("query would be affected by row-level security policy for table \"%s\"", + get_rel_name(relid)), + amowner ? errhint("To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.") : 0)); + + /* RLS should be fully enabled for this relation. */ + return RLS_ENABLED; +} + +/* + * row_security_active + * + * check_enable_rls wrapped as a SQL callable function except + * RLS_NONE_ENV and RLS_NONE are the same for this purpose. + */ +Datum +row_security_active(PG_FUNCTION_ARGS) +{ + /* By OID */ + Oid tableoid = PG_GETARG_OID(0); + int rls_status; + + rls_status = check_enable_rls(tableoid, InvalidOid, true); + PG_RETURN_BOOL(rls_status == RLS_ENABLED); +} + +Datum +row_security_active_name(PG_FUNCTION_ARGS) +{ + /* By qualified name */ + text *tablename = PG_GETARG_TEXT_PP(0); + RangeVar *tablerel; + Oid tableoid; + int rls_status; + + /* Look up table name. Can't lock it - we might not have privileges. */ + tablerel = makeRangeVarFromNameList(textToQualifiedNameList(tablename)); + tableoid = RangeVarGetRelid(tablerel, NoLock, false); + + rls_status = check_enable_rls(tableoid, InvalidOid, true); + PG_RETURN_BOOL(rls_status == RLS_ENABLED); +} diff --git a/src/backend/utils/misc/sampling.c b/src/backend/utils/misc/sampling.c new file mode 100644 index 0000000..580e9d9 --- /dev/null +++ b/src/backend/utils/misc/sampling.c @@ -0,0 +1,304 @@ +/*------------------------------------------------------------------------- + * + * sampling.c + * Relation block sampling routines. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/sampling.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <math.h> + +#include "utils/sampling.h" + + +/* + * BlockSampler_Init -- prepare for random sampling of blocknumbers + * + * BlockSampler provides algorithm for block level sampling of a relation + * as discussed on pgsql-hackers 2004-04-02 (subject "Large DB") + * It selects a random sample of samplesize blocks out of + * the nblocks blocks in the table. If the table has less than + * samplesize blocks, all blocks are selected. + * + * Since we know the total number of blocks in advance, we can use the + * straightforward Algorithm S from Knuth 3.4.2, rather than Vitter's + * algorithm. + * + * Returns the number of blocks that BlockSampler_Next will return. + */ +BlockNumber +BlockSampler_Init(BlockSampler bs, BlockNumber nblocks, int samplesize, + uint32 randseed) +{ + bs->N = nblocks; /* measured table size */ + + /* + * If we decide to reduce samplesize for tables that have less or not much + * more than samplesize blocks, here is the place to do it. + */ + bs->n = samplesize; + bs->t = 0; /* blocks scanned so far */ + bs->m = 0; /* blocks selected so far */ + + sampler_random_init_state(randseed, &bs->randstate); + + return Min(bs->n, bs->N); +} + +bool +BlockSampler_HasMore(BlockSampler bs) +{ + return (bs->t < bs->N) && (bs->m < bs->n); +} + +BlockNumber +BlockSampler_Next(BlockSampler bs) +{ + BlockNumber K = bs->N - bs->t; /* remaining blocks */ + int k = bs->n - bs->m; /* blocks still to sample */ + double p; /* probability to skip block */ + double V; /* random */ + + Assert(BlockSampler_HasMore(bs)); /* hence K > 0 and k > 0 */ + + if ((BlockNumber) k >= K) + { + /* need all the rest */ + bs->m++; + return bs->t++; + } + + /*---------- + * It is not obvious that this code matches Knuth's Algorithm S. + * Knuth says to skip the current block with probability 1 - k/K. + * If we are to skip, we should advance t (hence decrease K), and + * repeat the same probabilistic test for the next block. The naive + * implementation thus requires a sampler_random_fract() call for each + * block number. But we can reduce this to one sampler_random_fract() + * call per selected block, by noting that each time the while-test + * succeeds, we can reinterpret V as a uniform random number in the range + * 0 to p. Therefore, instead of choosing a new V, we just adjust p to be + * the appropriate fraction of its former value, and our next loop + * makes the appropriate probabilistic test. + * + * We have initially K > k > 0. If the loop reduces K to equal k, + * the next while-test must fail since p will become exactly zero + * (we assume there will not be roundoff error in the division). + * (Note: Knuth suggests a "<=" loop condition, but we use "<" just + * to be doubly sure about roundoff error.) Therefore K cannot become + * less than k, which means that we cannot fail to select enough blocks. + *---------- + */ + V = sampler_random_fract(&bs->randstate); + p = 1.0 - (double) k / (double) K; + while (V < p) + { + /* skip */ + bs->t++; + K--; /* keep K == N - t */ + + /* adjust p to be new cutoff point in reduced range */ + p *= 1.0 - (double) k / (double) K; + } + + /* select */ + bs->m++; + return bs->t++; +} + +/* + * These two routines embody Algorithm Z from "Random sampling with a + * reservoir" by Jeffrey S. Vitter, in ACM Trans. Math. Softw. 11, 1 + * (Mar. 1985), Pages 37-57. Vitter describes his algorithm in terms + * of the count S of records to skip before processing another record. + * It is computed primarily based on t, the number of records already read. + * The only extra state needed between calls is W, a random state variable. + * + * reservoir_init_selection_state computes the initial W value. + * + * Given that we've already read t records (t >= n), reservoir_get_next_S + * determines the number of records to skip before the next record is + * processed. + */ +void +reservoir_init_selection_state(ReservoirState rs, int n) +{ + /* + * Reservoir sampling is not used anywhere where it would need to return + * repeatable results so we can initialize it randomly. + */ + sampler_random_init_state(pg_prng_uint32(&pg_global_prng_state), + &rs->randstate); + + /* Initial value of W (for use when Algorithm Z is first applied) */ + rs->W = exp(-log(sampler_random_fract(&rs->randstate)) / n); +} + +double +reservoir_get_next_S(ReservoirState rs, double t, int n) +{ + double S; + + /* The magic constant here is T from Vitter's paper */ + if (t <= (22.0 * n)) + { + /* Process records using Algorithm X until t is large enough */ + double V, + quot; + + V = sampler_random_fract(&rs->randstate); /* Generate V */ + S = 0; + t += 1; + /* Note: "num" in Vitter's code is always equal to t - n */ + quot = (t - (double) n) / t; + /* Find min S satisfying (4.1) */ + while (quot > V) + { + S += 1; + t += 1; + quot *= (t - (double) n) / t; + } + } + else + { + /* Now apply Algorithm Z */ + double W = rs->W; + double term = t - (double) n + 1; + + for (;;) + { + double numer, + numer_lim, + denom; + double U, + X, + lhs, + rhs, + y, + tmp; + + /* Generate U and X */ + U = sampler_random_fract(&rs->randstate); + X = t * (W - 1.0); + S = floor(X); /* S is tentatively set to floor(X) */ + /* Test if U <= h(S)/cg(X) in the manner of (6.3) */ + tmp = (t + 1) / term; + lhs = exp(log(((U * tmp * tmp) * (term + S)) / (t + X)) / n); + rhs = (((t + X) / (term + S)) * term) / t; + if (lhs <= rhs) + { + W = rhs / lhs; + break; + } + /* Test if U <= f(S)/cg(X) */ + y = (((U * (t + 1)) / term) * (t + S + 1)) / (t + X); + if ((double) n < S) + { + denom = t; + numer_lim = term + S; + } + else + { + denom = t - (double) n + S; + numer_lim = t + 1; + } + for (numer = t + S; numer >= numer_lim; numer -= 1) + { + y *= numer / denom; + denom -= 1; + } + W = exp(-log(sampler_random_fract(&rs->randstate)) / n); /* Generate W in advance */ + if (exp(log(y) / n) <= (t + X) / t) + break; + } + rs->W = W; + } + return S; +} + + +/*---------- + * Random number generator used by sampling + *---------- + */ +void +sampler_random_init_state(uint32 seed, pg_prng_state *randstate) +{ + pg_prng_seed(randstate, (uint64) seed); +} + +/* Select a random value R uniformly distributed in (0 - 1) */ +double +sampler_random_fract(pg_prng_state *randstate) +{ + double res; + + /* pg_prng_double returns a value in [0.0 - 1.0), so we must reject 0.0 */ + do + { + res = pg_prng_double(randstate); + } while (unlikely(res == 0.0)); + return res; +} + + +/* + * Backwards-compatible API for block sampling + * + * This code is now deprecated, but since it's still in use by many FDWs, + * we should keep it for awhile at least. The functionality is the same as + * sampler_random_fract/reservoir_init_selection_state/reservoir_get_next_S, + * except that a common random state is used across all callers. + */ +static ReservoirStateData oldrs; +static bool oldrs_initialized = false; + +double +anl_random_fract(void) +{ + /* initialize if first time through */ + if (unlikely(!oldrs_initialized)) + { + sampler_random_init_state(pg_prng_uint32(&pg_global_prng_state), + &oldrs.randstate); + oldrs_initialized = true; + } + + /* and compute a random fraction */ + return sampler_random_fract(&oldrs.randstate); +} + +double +anl_init_selection_state(int n) +{ + /* initialize if first time through */ + if (unlikely(!oldrs_initialized)) + { + sampler_random_init_state(pg_prng_uint32(&pg_global_prng_state), + &oldrs.randstate); + oldrs_initialized = true; + } + + /* Initial value of W (for use when Algorithm Z is first applied) */ + return exp(-log(sampler_random_fract(&oldrs.randstate)) / n); +} + +double +anl_get_next_S(double t, int n, double *stateptr) +{ + double result; + + oldrs.W = *stateptr; + result = reservoir_get_next_S(&oldrs, t, n); + *stateptr = oldrs.W; + return result; +} diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c new file mode 100644 index 0000000..d20e7af --- /dev/null +++ b/src/backend/utils/misc/superuser.c @@ -0,0 +1,107 @@ +/*------------------------------------------------------------------------- + * + * superuser.c + * The superuser() function. Determines if user has superuser privilege. + * + * All code should use either of these two functions to find out + * whether a given user is a superuser, rather than examining + * pg_authid.rolsuper directly, so that the escape hatch built in for + * the single-user case works. + * + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/superuser.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_authid.h" +#include "miscadmin.h" +#include "utils/inval.h" +#include "utils/syscache.h" + +/* + * In common cases the same roleid (ie, the session or current ID) will + * be queried repeatedly. So we maintain a simple one-entry cache for + * the status of the last requested roleid. The cache can be flushed + * at need by watching for cache update events on pg_authid. + */ +static Oid last_roleid = InvalidOid; /* InvalidOid == cache not valid */ +static bool last_roleid_is_super = false; +static bool roleid_callback_registered = false; + +static void RoleidCallback(Datum arg, int cacheid, uint32 hashvalue); + + +/* + * The Postgres user running this command has Postgres superuser privileges + */ +bool +superuser(void) +{ + return superuser_arg(GetUserId()); +} + + +/* + * The specified role has Postgres superuser privileges + */ +bool +superuser_arg(Oid roleid) +{ + bool result; + HeapTuple rtup; + + /* Quick out for cache hit */ + if (OidIsValid(last_roleid) && last_roleid == roleid) + return last_roleid_is_super; + + /* Special escape path in case you deleted all your users. */ + if (!IsUnderPostmaster && roleid == BOOTSTRAP_SUPERUSERID) + return true; + + /* OK, look up the information in pg_authid */ + rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(rtup)) + { + result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper; + ReleaseSysCache(rtup); + } + else + { + /* Report "not superuser" for invalid roleids */ + result = false; + } + + /* If first time through, set up callback for cache flushes */ + if (!roleid_callback_registered) + { + CacheRegisterSyscacheCallback(AUTHOID, + RoleidCallback, + (Datum) 0); + roleid_callback_registered = true; + } + + /* Cache the result for next time */ + last_roleid = roleid; + last_roleid_is_super = result; + + return result; +} + +/* + * RoleidCallback + * Syscache inval callback function + */ +static void +RoleidCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + /* Invalidate our local cache in case role's superuserness changed */ + last_roleid = InvalidOid; +} diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c new file mode 100644 index 0000000..6f5e08b --- /dev/null +++ b/src/backend/utils/misc/timeout.c @@ -0,0 +1,834 @@ +/*------------------------------------------------------------------------- + * + * timeout.c + * Routines to multiplex SIGALRM interrupts for multiple timeout reasons. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/timeout.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/time.h> + +#include "miscadmin.h" +#include "storage/proc.h" +#include "utils/timeout.h" +#include "utils/timestamp.h" + + +/* Data about any one timeout reason */ +typedef struct timeout_params +{ + TimeoutId index; /* identifier of timeout reason */ + + /* volatile because these may be changed from the signal handler */ + volatile bool active; /* true if timeout is in active_timeouts[] */ + volatile bool indicator; /* true if timeout has occurred */ + + /* callback function for timeout, or NULL if timeout not registered */ + timeout_handler_proc timeout_handler; + + TimestampTz start_time; /* time that timeout was last activated */ + TimestampTz fin_time; /* time it is, or was last, due to fire */ + int interval_in_ms; /* time between firings, or 0 if just once */ +} timeout_params; + +/* + * List of possible timeout reasons in the order of enum TimeoutId. + */ +static timeout_params all_timeouts[MAX_TIMEOUTS]; +static bool all_timeouts_initialized = false; + +/* + * List of active timeouts ordered by their fin_time and priority. + * This list is subject to change by the interrupt handler, so it's volatile. + */ +static volatile int num_active_timeouts = 0; +static timeout_params *volatile active_timeouts[MAX_TIMEOUTS]; + +/* + * Flag controlling whether the signal handler is allowed to do anything. + * This is useful to avoid race conditions with the handler. Note in + * particular that this lets us make changes in the data structures without + * tediously disabling and re-enabling the timer signal. Most of the time, + * no interrupt would happen anyway during such critical sections, but if + * one does, this rule ensures it's safe. Leaving the signal enabled across + * multiple operations can greatly reduce the number of kernel calls we make, + * too. See comments in schedule_alarm() about that. + * + * We leave this "false" when we're not expecting interrupts, just in case. + */ +static volatile sig_atomic_t alarm_enabled = false; + +#define disable_alarm() (alarm_enabled = false) +#define enable_alarm() (alarm_enabled = true) + +/* + * State recording if and when we next expect the interrupt to fire. + * (signal_due_at is valid only when signal_pending is true.) + * Note that the signal handler will unconditionally reset signal_pending to + * false, so that can change asynchronously even when alarm_enabled is false. + */ +static volatile sig_atomic_t signal_pending = false; +static volatile TimestampTz signal_due_at = 0; + + +/***************************************************************************** + * Internal helper functions + * + * For all of these, it is caller's responsibility to protect them from + * interruption by the signal handler. Generally, call disable_alarm() + * first to prevent interruption, then update state, and last call + * schedule_alarm(), which will re-enable the signal handler if needed. + *****************************************************************************/ + +/* + * Find the index of a given timeout reason in the active array. + * If it's not there, return -1. + */ +static int +find_active_timeout(TimeoutId id) +{ + int i; + + for (i = 0; i < num_active_timeouts; i++) + { + if (active_timeouts[i]->index == id) + return i; + } + + return -1; +} + +/* + * Insert specified timeout reason into the list of active timeouts + * at the given index. + */ +static void +insert_timeout(TimeoutId id, int index) +{ + int i; + + if (index < 0 || index > num_active_timeouts) + elog(FATAL, "timeout index %d out of range 0..%d", index, + num_active_timeouts); + + Assert(!all_timeouts[id].active); + all_timeouts[id].active = true; + + for (i = num_active_timeouts - 1; i >= index; i--) + active_timeouts[i + 1] = active_timeouts[i]; + + active_timeouts[index] = &all_timeouts[id]; + + num_active_timeouts++; +} + +/* + * Remove the index'th element from the timeout list. + */ +static void +remove_timeout_index(int index) +{ + int i; + + if (index < 0 || index >= num_active_timeouts) + elog(FATAL, "timeout index %d out of range 0..%d", index, + num_active_timeouts - 1); + + Assert(active_timeouts[index]->active); + active_timeouts[index]->active = false; + + for (i = index + 1; i < num_active_timeouts; i++) + active_timeouts[i - 1] = active_timeouts[i]; + + num_active_timeouts--; +} + +/* + * Enable the specified timeout reason + */ +static void +enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time, + int interval_in_ms) +{ + int i; + + /* Assert request is sane */ + Assert(all_timeouts_initialized); + Assert(all_timeouts[id].timeout_handler != NULL); + + /* + * If this timeout was already active, momentarily disable it. We + * interpret the call as a directive to reschedule the timeout. + */ + if (all_timeouts[id].active) + remove_timeout_index(find_active_timeout(id)); + + /* + * Find out the index where to insert the new timeout. We sort by + * fin_time, and for equal fin_time by priority. + */ + for (i = 0; i < num_active_timeouts; i++) + { + timeout_params *old_timeout = active_timeouts[i]; + + if (fin_time < old_timeout->fin_time) + break; + if (fin_time == old_timeout->fin_time && id < old_timeout->index) + break; + } + + /* + * Mark the timeout active, and insert it into the active list. + */ + all_timeouts[id].indicator = false; + all_timeouts[id].start_time = now; + all_timeouts[id].fin_time = fin_time; + all_timeouts[id].interval_in_ms = interval_in_ms; + + insert_timeout(id, i); +} + +/* + * Schedule alarm for the next active timeout, if any + * + * We assume the caller has obtained the current time, or a close-enough + * approximation. (It's okay if a tick or two has passed since "now", or + * if a little more time elapses before we reach the kernel call; that will + * cause us to ask for an interrupt a tick or two later than the nearest + * timeout, which is no big deal. Passing a "now" value that's in the future + * would be bad though.) + */ +static void +schedule_alarm(TimestampTz now) +{ + if (num_active_timeouts > 0) + { + struct itimerval timeval; + TimestampTz nearest_timeout; + long secs; + int usecs; + + MemSet(&timeval, 0, sizeof(struct itimerval)); + + /* + * If we think there's a signal pending, but current time is more than + * 10ms past when the signal was due, then assume that the timeout + * request got lost somehow; clear signal_pending so that we'll reset + * the interrupt request below. (10ms corresponds to the worst-case + * timeout granularity on modern systems.) It won't hurt us if the + * interrupt does manage to fire between now and when we reach the + * setitimer() call. + */ + if (signal_pending && now > signal_due_at + 10 * 1000) + signal_pending = false; + + /* + * Get the time remaining till the nearest pending timeout. If it is + * negative, assume that we somehow missed an interrupt, and clear + * signal_pending. This gives us another chance to recover if the + * kernel drops a timeout request for some reason. + */ + nearest_timeout = active_timeouts[0]->fin_time; + if (now > nearest_timeout) + { + signal_pending = false; + /* force an interrupt as soon as possible */ + secs = 0; + usecs = 1; + } + else + { + TimestampDifference(now, nearest_timeout, + &secs, &usecs); + + /* + * It's possible that the difference is less than a microsecond; + * ensure we don't cancel, rather than set, the interrupt. + */ + if (secs == 0 && usecs == 0) + usecs = 1; + } + + timeval.it_value.tv_sec = secs; + timeval.it_value.tv_usec = usecs; + + /* + * We must enable the signal handler before calling setitimer(); if we + * did it in the other order, we'd have a race condition wherein the + * interrupt could occur before we can set alarm_enabled, so that the + * signal handler would fail to do anything. + * + * Because we didn't bother to disable the timer in disable_alarm(), + * it's possible that a previously-set interrupt will fire between + * enable_alarm() and setitimer(). This is safe, however. There are + * two possible outcomes: + * + * 1. The signal handler finds nothing to do (because the nearest + * timeout event is still in the future). It will re-set the timer + * and return. Then we'll overwrite the timer value with a new one. + * This will mean that the timer fires a little later than we + * intended, but only by the amount of time it takes for the signal + * handler to do nothing useful, which shouldn't be much. + * + * 2. The signal handler executes and removes one or more timeout + * events. When it returns, either the queue is now empty or the + * frontmost event is later than the one we looked at above. So we'll + * overwrite the timer value with one that is too soon (plus or minus + * the signal handler's execution time), causing a useless interrupt + * to occur. But the handler will then re-set the timer and + * everything will still work as expected. + * + * Since these cases are of very low probability (the window here + * being quite narrow), it's not worth adding cycles to the mainline + * code to prevent occasional wasted interrupts. + */ + enable_alarm(); + + /* + * If there is already an interrupt pending that's at or before the + * needed time, we need not do anything more. The signal handler will + * do the right thing in the first case, and re-schedule the interrupt + * for later in the second case. It might seem that the extra + * interrupt is wasted work, but it's not terribly much work, and this + * method has very significant advantages in the common use-case where + * we repeatedly set a timeout that we don't expect to reach and then + * cancel it. Instead of invoking setitimer() every time the timeout + * is set or canceled, we perform one interrupt and a re-scheduling + * setitimer() call at intervals roughly equal to the timeout delay. + * For example, with statement_timeout = 1s and a throughput of + * thousands of queries per second, this method requires an interrupt + * and setitimer() call roughly once a second, rather than thousands + * of setitimer() calls per second. + * + * Because of the possible passage of time between when we obtained + * "now" and when we reach setitimer(), the kernel's opinion of when + * to trigger the interrupt is likely to be a bit later than + * signal_due_at. That's fine, for the same reasons described above. + */ + if (signal_pending && nearest_timeout >= signal_due_at) + return; + + /* + * As with calling enable_alarm(), we must set signal_pending *before* + * calling setitimer(); if we did it after, the signal handler could + * trigger before we set it, leaving us with a false opinion that a + * signal is still coming. + * + * Other race conditions involved with setting/checking signal_pending + * are okay, for the reasons described above. One additional point is + * that the signal handler could fire after we set signal_due_at, but + * still before the setitimer() call. Then the handler could + * overwrite signal_due_at with a value it computes, which will be the + * same as or perhaps later than what we just computed. After we + * perform setitimer(), the net effect would be that signal_due_at + * gives a time later than when the interrupt will really happen; + * which is a safe situation. + */ + signal_due_at = nearest_timeout; + signal_pending = true; + + /* Set the alarm timer */ + if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) + { + /* + * Clearing signal_pending here is a bit pro forma, but not + * entirely so, since something in the FATAL exit path could try + * to use timeout facilities. + */ + signal_pending = false; + elog(FATAL, "could not enable SIGALRM timer: %m"); + } + } +} + + +/***************************************************************************** + * Signal handler + *****************************************************************************/ + +/* + * Signal handler for SIGALRM + * + * Process any active timeout reasons and then reschedule the interrupt + * as needed. + */ +static void +handle_sig_alarm(SIGNAL_ARGS) +{ + int save_errno = errno; + + /* + * Bump the holdoff counter, to make sure nothing we call will process + * interrupts directly. No timeout handler should do that, but these + * failures are hard to debug, so better be sure. + */ + HOLD_INTERRUPTS(); + + /* + * SIGALRM is always cause for waking anything waiting on the process + * latch. + */ + SetLatch(MyLatch); + + /* + * Always reset signal_pending, even if !alarm_enabled, since indeed no + * signal is now pending. + */ + signal_pending = false; + + /* + * Fire any pending timeouts, but only if we're enabled to do so. + */ + if (alarm_enabled) + { + /* + * Disable alarms, just in case this platform allows signal handlers + * to interrupt themselves. schedule_alarm() will re-enable if + * appropriate. + */ + disable_alarm(); + + if (num_active_timeouts > 0) + { + TimestampTz now = GetCurrentTimestamp(); + + /* While the first pending timeout has been reached ... */ + while (num_active_timeouts > 0 && + now >= active_timeouts[0]->fin_time) + { + timeout_params *this_timeout = active_timeouts[0]; + + /* Remove it from the active list */ + remove_timeout_index(0); + + /* Mark it as fired */ + this_timeout->indicator = true; + + /* And call its handler function */ + this_timeout->timeout_handler(); + + /* If it should fire repeatedly, re-enable it. */ + if (this_timeout->interval_in_ms > 0) + { + TimestampTz new_fin_time; + + /* + * To guard against drift, schedule the next instance of + * the timeout based on the intended firing time rather + * than the actual firing time. But if the timeout was so + * late that we missed an entire cycle, fall back to + * scheduling based on the actual firing time. + */ + new_fin_time = + TimestampTzPlusMilliseconds(this_timeout->fin_time, + this_timeout->interval_in_ms); + if (new_fin_time < now) + new_fin_time = + TimestampTzPlusMilliseconds(now, + this_timeout->interval_in_ms); + enable_timeout(this_timeout->index, now, new_fin_time, + this_timeout->interval_in_ms); + } + + /* + * The handler might not take negligible time (CheckDeadLock + * for instance isn't too cheap), so let's update our idea of + * "now" after each one. + */ + now = GetCurrentTimestamp(); + } + + /* Done firing timeouts, so reschedule next interrupt if any */ + schedule_alarm(now); + } + } + + RESUME_INTERRUPTS(); + + errno = save_errno; +} + + +/***************************************************************************** + * Public API + *****************************************************************************/ + +/* + * Initialize timeout module. + * + * This must be called in every process that wants to use timeouts. + * + * If the process was forked from another one that was also using this + * module, be sure to call this before re-enabling signals; else handlers + * meant to run in the parent process might get invoked in this one. + */ +void +InitializeTimeouts(void) +{ + int i; + + /* Initialize, or re-initialize, all local state */ + disable_alarm(); + + num_active_timeouts = 0; + + for (i = 0; i < MAX_TIMEOUTS; i++) + { + all_timeouts[i].index = i; + all_timeouts[i].active = false; + all_timeouts[i].indicator = false; + all_timeouts[i].timeout_handler = NULL; + all_timeouts[i].start_time = 0; + all_timeouts[i].fin_time = 0; + all_timeouts[i].interval_in_ms = 0; + } + + all_timeouts_initialized = true; + + /* Now establish the signal handler */ + pqsignal(SIGALRM, handle_sig_alarm); +} + +/* + * Register a timeout reason + * + * For predefined timeouts, this just registers the callback function. + * + * For user-defined timeouts, pass id == USER_TIMEOUT; we then allocate and + * return a timeout ID. + */ +TimeoutId +RegisterTimeout(TimeoutId id, timeout_handler_proc handler) +{ + Assert(all_timeouts_initialized); + + /* There's no need to disable the signal handler here. */ + + if (id >= USER_TIMEOUT) + { + /* Allocate a user-defined timeout reason */ + for (id = USER_TIMEOUT; id < MAX_TIMEOUTS; id++) + if (all_timeouts[id].timeout_handler == NULL) + break; + if (id >= MAX_TIMEOUTS) + ereport(FATAL, + (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED), + errmsg("cannot add more timeout reasons"))); + } + + Assert(all_timeouts[id].timeout_handler == NULL); + + all_timeouts[id].timeout_handler = handler; + + return id; +} + +/* + * Reschedule any pending SIGALRM interrupt. + * + * This can be used during error recovery in case query cancel resulted in loss + * of a SIGALRM event (due to longjmp'ing out of handle_sig_alarm before it + * could do anything). But note it's not necessary if any of the public + * enable_ or disable_timeout functions are called in the same area, since + * those all do schedule_alarm() internally if needed. + */ +void +reschedule_timeouts(void) +{ + /* For flexibility, allow this to be called before we're initialized. */ + if (!all_timeouts_initialized) + return; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Reschedule the interrupt, if any timeouts remain active. */ + if (num_active_timeouts > 0) + schedule_alarm(GetCurrentTimestamp()); +} + +/* + * Enable the specified timeout to fire after the specified delay. + * + * Delay is given in milliseconds. + */ +void +enable_timeout_after(TimeoutId id, int delay_ms) +{ + TimestampTz now; + TimestampTz fin_time; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Queue the timeout at the appropriate time. */ + now = GetCurrentTimestamp(); + fin_time = TimestampTzPlusMilliseconds(now, delay_ms); + enable_timeout(id, now, fin_time, 0); + + /* Set the timer interrupt. */ + schedule_alarm(now); +} + +/* + * Enable the specified timeout to fire periodically, with the specified + * delay as the time between firings. + * + * Delay is given in milliseconds. + */ +void +enable_timeout_every(TimeoutId id, TimestampTz fin_time, int delay_ms) +{ + TimestampTz now; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Queue the timeout at the appropriate time. */ + now = GetCurrentTimestamp(); + enable_timeout(id, now, fin_time, delay_ms); + + /* Set the timer interrupt. */ + schedule_alarm(now); +} + +/* + * Enable the specified timeout to fire at the specified time. + * + * This is provided to support cases where there's a reason to calculate + * the timeout by reference to some point other than "now". If there isn't, + * use enable_timeout_after(), to avoid calling GetCurrentTimestamp() twice. + */ +void +enable_timeout_at(TimeoutId id, TimestampTz fin_time) +{ + TimestampTz now; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Queue the timeout at the appropriate time. */ + now = GetCurrentTimestamp(); + enable_timeout(id, now, fin_time, 0); + + /* Set the timer interrupt. */ + schedule_alarm(now); +} + +/* + * Enable multiple timeouts at once. + * + * This works like calling enable_timeout_after() and/or enable_timeout_at() + * multiple times. Use this to reduce the number of GetCurrentTimestamp() + * and setitimer() calls needed to establish multiple timeouts. + */ +void +enable_timeouts(const EnableTimeoutParams *timeouts, int count) +{ + TimestampTz now; + int i; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Queue the timeout(s) at the appropriate times. */ + now = GetCurrentTimestamp(); + + for (i = 0; i < count; i++) + { + TimeoutId id = timeouts[i].id; + TimestampTz fin_time; + + switch (timeouts[i].type) + { + case TMPARAM_AFTER: + fin_time = TimestampTzPlusMilliseconds(now, + timeouts[i].delay_ms); + enable_timeout(id, now, fin_time, 0); + break; + + case TMPARAM_AT: + enable_timeout(id, now, timeouts[i].fin_time, 0); + break; + + case TMPARAM_EVERY: + fin_time = TimestampTzPlusMilliseconds(now, + timeouts[i].delay_ms); + enable_timeout(id, now, fin_time, timeouts[i].delay_ms); + break; + + default: + elog(ERROR, "unrecognized timeout type %d", + (int) timeouts[i].type); + break; + } + } + + /* Set the timer interrupt. */ + schedule_alarm(now); +} + +/* + * Cancel the specified timeout. + * + * The timeout's I've-been-fired indicator is reset, + * unless keep_indicator is true. + * + * When a timeout is canceled, any other active timeout remains in force. + * It's not an error to disable a timeout that is not enabled. + */ +void +disable_timeout(TimeoutId id, bool keep_indicator) +{ + /* Assert request is sane */ + Assert(all_timeouts_initialized); + Assert(all_timeouts[id].timeout_handler != NULL); + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Find the timeout and remove it from the active list. */ + if (all_timeouts[id].active) + remove_timeout_index(find_active_timeout(id)); + + /* Mark it inactive, whether it was active or not. */ + if (!keep_indicator) + all_timeouts[id].indicator = false; + + /* Reschedule the interrupt, if any timeouts remain active. */ + if (num_active_timeouts > 0) + schedule_alarm(GetCurrentTimestamp()); +} + +/* + * Cancel multiple timeouts at once. + * + * The timeouts' I've-been-fired indicators are reset, + * unless timeouts[i].keep_indicator is true. + * + * This works like calling disable_timeout() multiple times. + * Use this to reduce the number of GetCurrentTimestamp() + * and setitimer() calls needed to cancel multiple timeouts. + */ +void +disable_timeouts(const DisableTimeoutParams *timeouts, int count) +{ + int i; + + Assert(all_timeouts_initialized); + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Cancel the timeout(s). */ + for (i = 0; i < count; i++) + { + TimeoutId id = timeouts[i].id; + + Assert(all_timeouts[id].timeout_handler != NULL); + + if (all_timeouts[id].active) + remove_timeout_index(find_active_timeout(id)); + + if (!timeouts[i].keep_indicator) + all_timeouts[id].indicator = false; + } + + /* Reschedule the interrupt, if any timeouts remain active. */ + if (num_active_timeouts > 0) + schedule_alarm(GetCurrentTimestamp()); +} + +/* + * Disable the signal handler, remove all timeouts from the active list, + * and optionally reset their timeout indicators. + */ +void +disable_all_timeouts(bool keep_indicators) +{ + int i; + + disable_alarm(); + + /* + * We used to disable the timer interrupt here, but in common usage + * patterns it's cheaper to leave it enabled; that may save us from having + * to enable it again shortly. See comments in schedule_alarm(). + */ + + num_active_timeouts = 0; + + for (i = 0; i < MAX_TIMEOUTS; i++) + { + all_timeouts[i].active = false; + if (!keep_indicators) + all_timeouts[i].indicator = false; + } +} + +/* + * Return true if the timeout is active (enabled and not yet fired) + * + * This is, of course, subject to race conditions, as the timeout could fire + * immediately after we look. + */ +bool +get_timeout_active(TimeoutId id) +{ + return all_timeouts[id].active; +} + +/* + * Return the timeout's I've-been-fired indicator + * + * If reset_indicator is true, reset the indicator when returning true. + * To avoid missing timeouts due to race conditions, we are careful not to + * reset the indicator when returning false. + */ +bool +get_timeout_indicator(TimeoutId id, bool reset_indicator) +{ + if (all_timeouts[id].indicator) + { + if (reset_indicator) + all_timeouts[id].indicator = false; + return true; + } + return false; +} + +/* + * Return the time when the timeout was most recently activated + * + * Note: will return 0 if timeout has never been activated in this process. + * However, we do *not* reset the start_time when a timeout occurs, so as + * not to create a race condition if SIGALRM fires just as some code is + * about to fetch the value. + */ +TimestampTz +get_timeout_start_time(TimeoutId id) +{ + return all_timeouts[id].start_time; +} + +/* + * Return the time when the timeout is, or most recently was, due to fire + * + * Note: will return 0 if timeout has never been activated in this process. + * However, we do *not* reset the fin_time when a timeout occurs, so as + * not to create a race condition if SIGALRM fires just as some code is + * about to fetch the value. + */ +TimestampTz +get_timeout_finish_time(TimeoutId id) +{ + return all_timeouts[id].fin_time; +} diff --git a/src/backend/utils/misc/tzparser.c b/src/backend/utils/misc/tzparser.c new file mode 100644 index 0000000..8f2c95f --- /dev/null +++ b/src/backend/utils/misc/tzparser.c @@ -0,0 +1,484 @@ +/*------------------------------------------------------------------------- + * + * tzparser.c + * Functions for parsing timezone offset files + * + * Note: this code is invoked from the check_hook for the GUC variable + * timezone_abbreviations. Therefore, it should report problems using + * GUC_check_errmsg() and related functions, and try to avoid throwing + * elog(ERROR). This is not completely bulletproof at present --- in + * particular out-of-memory will throw an error. Could probably fix with + * PG_TRY if necessary. + * + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/misc/tzparser.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> + +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/tzparser.h" + + +#define WHITESPACE " \t\n\r" + +static bool validateTzEntry(tzEntry *tzentry); +static bool splitTzLine(const char *filename, int lineno, + char *line, tzEntry *tzentry); +static int addToArray(tzEntry **base, int *arraysize, int n, + tzEntry *entry, bool override); +static int ParseTzFile(const char *filename, int depth, + tzEntry **base, int *arraysize, int n); + + +/* + * Apply additional validation checks to a tzEntry + * + * Returns true if OK, else false + */ +static bool +validateTzEntry(tzEntry *tzentry) +{ + unsigned char *p; + + /* + * Check restrictions imposed by datetktbl storage format (see datetime.c) + */ + if (strlen(tzentry->abbrev) > TOKMAXLEN) + { + GUC_check_errmsg("time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d", + tzentry->abbrev, TOKMAXLEN, + tzentry->filename, tzentry->lineno); + return false; + } + + /* + * Sanity-check the offset: shouldn't exceed 14 hours + */ + if (tzentry->offset > 14 * 60 * 60 || + tzentry->offset < -14 * 60 * 60) + { + GUC_check_errmsg("time zone offset %d is out of range in time zone file \"%s\", line %d", + tzentry->offset, + tzentry->filename, tzentry->lineno); + return false; + } + + /* + * Convert abbrev to lowercase (must match datetime.c's conversion) + */ + for (p = (unsigned char *) tzentry->abbrev; *p; p++) + *p = pg_tolower(*p); + + return true; +} + +/* + * Attempt to parse the line as a timezone abbrev spec + * + * Valid formats are: + * name zone + * name offset dst + * + * Returns true if OK, else false; data is stored in *tzentry + */ +static bool +splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry) +{ + char *abbrev; + char *offset; + char *offset_endptr; + char *remain; + char *is_dst; + + tzentry->lineno = lineno; + tzentry->filename = filename; + + abbrev = strtok(line, WHITESPACE); + if (!abbrev) + { + GUC_check_errmsg("missing time zone abbreviation in time zone file \"%s\", line %d", + filename, lineno); + return false; + } + tzentry->abbrev = pstrdup(abbrev); + + offset = strtok(NULL, WHITESPACE); + if (!offset) + { + GUC_check_errmsg("missing time zone offset in time zone file \"%s\", line %d", + filename, lineno); + return false; + } + + /* We assume zone names don't begin with a digit or sign */ + if (isdigit((unsigned char) *offset) || *offset == '+' || *offset == '-') + { + tzentry->zone = NULL; + tzentry->offset = strtol(offset, &offset_endptr, 10); + if (offset_endptr == offset || *offset_endptr != '\0') + { + GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d", + filename, lineno); + return false; + } + + is_dst = strtok(NULL, WHITESPACE); + if (is_dst && pg_strcasecmp(is_dst, "D") == 0) + { + tzentry->is_dst = true; + remain = strtok(NULL, WHITESPACE); + } + else + { + /* there was no 'D' dst specifier */ + tzentry->is_dst = false; + remain = is_dst; + } + } + else + { + /* + * Assume entry is a zone name. We do not try to validate it by + * looking up the zone, because that would force loading of a lot of + * zones that probably will never be used in the current session. + */ + tzentry->zone = pstrdup(offset); + tzentry->offset = 0; + tzentry->is_dst = false; + remain = strtok(NULL, WHITESPACE); + } + + if (!remain) /* no more non-whitespace chars */ + return true; + + if (remain[0] != '#') /* must be a comment */ + { + GUC_check_errmsg("invalid syntax in time zone file \"%s\", line %d", + filename, lineno); + return false; + } + return true; +} + +/* + * Insert entry into sorted array + * + * *base: base address of array (changeable if must enlarge array) + * *arraysize: allocated length of array (changeable if must enlarge array) + * n: current number of valid elements in array + * entry: new data to insert + * override: true if OK to override + * + * Returns the new array length (new value for n), or -1 if error + */ +static int +addToArray(tzEntry **base, int *arraysize, int n, + tzEntry *entry, bool override) +{ + tzEntry *arrayptr; + int low; + int high; + + /* + * Search the array for a duplicate; as a useful side effect, the array is + * maintained in sorted order. We use strcmp() to ensure we match the + * sort order datetime.c expects. + */ + arrayptr = *base; + low = 0; + high = n - 1; + while (low <= high) + { + int mid = (low + high) >> 1; + tzEntry *midptr = arrayptr + mid; + int cmp; + + cmp = strcmp(entry->abbrev, midptr->abbrev); + if (cmp < 0) + high = mid - 1; + else if (cmp > 0) + low = mid + 1; + else + { + /* + * Found a duplicate entry; complain unless it's the same. + */ + if ((midptr->zone == NULL && entry->zone == NULL && + midptr->offset == entry->offset && + midptr->is_dst == entry->is_dst) || + (midptr->zone != NULL && entry->zone != NULL && + strcmp(midptr->zone, entry->zone) == 0)) + { + /* return unchanged array */ + return n; + } + if (override) + { + /* same abbrev but something is different, override */ + midptr->zone = entry->zone; + midptr->offset = entry->offset; + midptr->is_dst = entry->is_dst; + return n; + } + /* same abbrev but something is different, complain */ + GUC_check_errmsg("time zone abbreviation \"%s\" is multiply defined", + entry->abbrev); + GUC_check_errdetail("Entry in time zone file \"%s\", line %d, conflicts with entry in file \"%s\", line %d.", + midptr->filename, midptr->lineno, + entry->filename, entry->lineno); + return -1; + } + } + + /* + * No match, insert at position "low". + */ + if (n >= *arraysize) + { + *arraysize *= 2; + *base = (tzEntry *) repalloc(*base, *arraysize * sizeof(tzEntry)); + } + + arrayptr = *base + low; + + memmove(arrayptr + 1, arrayptr, (n - low) * sizeof(tzEntry)); + + memcpy(arrayptr, entry, sizeof(tzEntry)); + + return n + 1; +} + +/* + * Parse a single timezone abbrev file --- can recurse to handle @INCLUDE + * + * filename: user-specified file name (does not include path) + * depth: current recursion depth + * *base: array for results (changeable if must enlarge array) + * *arraysize: allocated length of array (changeable if must enlarge array) + * n: current number of valid elements in array + * + * Returns the new array length (new value for n), or -1 if error + */ +static int +ParseTzFile(const char *filename, int depth, + tzEntry **base, int *arraysize, int n) +{ + char share_path[MAXPGPATH]; + char file_path[MAXPGPATH]; + FILE *tzFile; + char tzbuf[1024]; + char *line; + tzEntry tzentry; + int lineno = 0; + bool override = false; + const char *p; + + /* + * We enforce that the filename is all alpha characters. This may be + * overly restrictive, but we don't want to allow access to anything + * outside the timezonesets directory, so for instance '/' *must* be + * rejected. + */ + for (p = filename; *p; p++) + { + if (!isalpha((unsigned char) *p)) + { + /* at level 0, just use guc.c's regular "invalid value" message */ + if (depth > 0) + GUC_check_errmsg("invalid time zone file name \"%s\"", + filename); + return -1; + } + } + + /* + * The maximal recursion depth is a pretty arbitrary setting. It is hard + * to imagine that someone needs more than 3 levels so stick with this + * conservative setting until someone complains. + */ + if (depth > 3) + { + GUC_check_errmsg("time zone file recursion limit exceeded in file \"%s\"", + filename); + return -1; + } + + get_share_path(my_exec_path, share_path); + snprintf(file_path, sizeof(file_path), "%s/timezonesets/%s", + share_path, filename); + tzFile = AllocateFile(file_path, "r"); + if (!tzFile) + { + /* + * Check to see if the problem is not the filename but the directory. + * This is worth troubling over because if the installation share/ + * directory is missing or unreadable, this is likely to be the first + * place we notice a problem during postmaster startup. + */ + int save_errno = errno; + DIR *tzdir; + + snprintf(file_path, sizeof(file_path), "%s/timezonesets", + share_path); + tzdir = AllocateDir(file_path); + if (tzdir == NULL) + { + GUC_check_errmsg("could not open directory \"%s\": %m", + file_path); + GUC_check_errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.", + my_exec_path); + return -1; + } + FreeDir(tzdir); + errno = save_errno; + + /* + * otherwise, if file doesn't exist and it's level 0, guc.c's + * complaint is enough + */ + if (errno != ENOENT || depth > 0) + GUC_check_errmsg("could not read time zone file \"%s\": %m", + filename); + + return -1; + } + + while (!feof(tzFile)) + { + lineno++; + if (fgets(tzbuf, sizeof(tzbuf), tzFile) == NULL) + { + if (ferror(tzFile)) + { + GUC_check_errmsg("could not read time zone file \"%s\": %m", + filename); + n = -1; + break; + } + /* else we're at EOF after all */ + break; + } + if (strlen(tzbuf) == sizeof(tzbuf) - 1) + { + /* the line is too long for tzbuf */ + GUC_check_errmsg("line is too long in time zone file \"%s\", line %d", + filename, lineno); + n = -1; + break; + } + + /* skip over whitespace */ + line = tzbuf; + while (*line && isspace((unsigned char) *line)) + line++; + + if (*line == '\0') /* empty line */ + continue; + if (*line == '#') /* comment line */ + continue; + + if (pg_strncasecmp(line, "@INCLUDE", strlen("@INCLUDE")) == 0) + { + /* pstrdup so we can use filename in result data structure */ + char *includeFile = pstrdup(line + strlen("@INCLUDE")); + + includeFile = strtok(includeFile, WHITESPACE); + if (!includeFile || !*includeFile) + { + GUC_check_errmsg("@INCLUDE without file name in time zone file \"%s\", line %d", + filename, lineno); + n = -1; + break; + } + n = ParseTzFile(includeFile, depth + 1, + base, arraysize, n); + if (n < 0) + break; + continue; + } + + if (pg_strncasecmp(line, "@OVERRIDE", strlen("@OVERRIDE")) == 0) + { + override = true; + continue; + } + + if (!splitTzLine(filename, lineno, line, &tzentry)) + { + n = -1; + break; + } + if (!validateTzEntry(&tzentry)) + { + n = -1; + break; + } + n = addToArray(base, arraysize, n, &tzentry, override); + if (n < 0) + break; + } + + FreeFile(tzFile); + + return n; +} + +/* + * load_tzoffsets --- read and parse the specified timezone offset file + * + * On success, return a filled-in TimeZoneAbbrevTable, which must have been + * malloc'd not palloc'd. On failure, return NULL, using GUC_check_errmsg + * and friends to give details of the problem. + */ +TimeZoneAbbrevTable * +load_tzoffsets(const char *filename) +{ + TimeZoneAbbrevTable *result = NULL; + MemoryContext tmpContext; + MemoryContext oldContext; + tzEntry *array; + int arraysize; + int n; + + /* + * Create a temp memory context to work in. This makes it easy to clean + * up afterwards. + */ + tmpContext = AllocSetContextCreate(CurrentMemoryContext, + "TZParserMemory", + ALLOCSET_SMALL_SIZES); + oldContext = MemoryContextSwitchTo(tmpContext); + + /* Initialize array at a reasonable size */ + arraysize = 128; + array = (tzEntry *) palloc(arraysize * sizeof(tzEntry)); + + /* Parse the file(s) */ + n = ParseTzFile(filename, 0, &array, &arraysize, 0); + + /* If no errors so far, let datetime.c allocate memory & convert format */ + if (n >= 0) + { + result = ConvertTimeZoneAbbrevs(array, n); + if (!result) + GUC_check_errmsg("out of memory"); + } + + /* Clean up */ + MemoryContextSwitchTo(oldContext); + MemoryContextDelete(tmpContext); + + return result; +} |