Finally returning to this .. rebased and updated per feedback. I'm not sure of a good place to put test cases for this..
>From 13e8c8b96d1a5313fd3edde515a5278cf8c6b23e Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Mon, 13 Dec 2021 08:42:38 -0600 Subject: [PATCH v5 1/3] warn when setting GUC to a nonextant library
--- src/backend/utils/misc/guc.c | 106 +++++++++++++++++- .../unsafe_tests/expected/rolenames.out | 19 ++++ .../modules/unsafe_tests/sql/rolenames.sql | 13 +++ .../worker_spi/expected/worker_spi.out | 2 + .../modules/worker_spi/sql/worker_spi.sql | 2 + src/test/regress/expected/rules.out | 8 ++ 6 files changed, 147 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index af4a1c30689..4e38e73a277 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -176,6 +176,12 @@ 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 bool check_local_preload_libraries(char **newval, void **extra, + GucSource source); +static bool check_session_preload_libraries(char **newval, void **extra, + GucSource source); +static bool check_shared_preload_libraries(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, @@ -4272,7 +4278,7 @@ static struct config_string ConfigureNamesString[] = }, &session_preload_libraries_string, "", - NULL, NULL, NULL + check_session_preload_libraries, NULL, NULL }, { @@ -4283,7 +4289,7 @@ static struct config_string ConfigureNamesString[] = }, &shared_preload_libraries_string, "", - NULL, NULL, NULL + check_shared_preload_libraries, NULL, NULL }, { @@ -4294,7 +4300,7 @@ static struct config_string ConfigureNamesString[] = }, &local_preload_libraries_string, "", - NULL, NULL, NULL + check_local_preload_libraries, NULL, NULL }, { @@ -12506,6 +12512,100 @@ check_max_worker_processes(int *newval, void **extra, GucSource source) return true; } +/* + * See also load_libraries() and internal_load_library(). + */ +static bool +check_preload_libraries(char **newval, void **extra, GucSource source, + bool restricted) +{ + char *rawstring; + List *elemlist; + ListCell *l; + + /* nothing to do if empty */ + if (newval == NULL || *newval[0] == '\0') + return true; + + /* + * issue warnings only during an interactive SET, from ALTER + * ROLE/DATABASE/SYSTEM. + */ + if (source != PGC_S_TEST && source != PGC_S_FILE) + return true; + + /* Need a modifiable copy of string */ + rawstring = pstrdup(*newval); + + /* Parse string into list of filename paths */ + if (!SplitDirectoriesString(rawstring, ',', &elemlist)) + { + /* Should not happen ? */ + return false; + } + + foreach(l, elemlist) + { + /* Note that filename was already canonicalized */ + char *filename = (char *) lfirst(l); + char *expanded = NULL; + + /* If restricting, insert $libdir/plugins if not mentioned already */ + if (restricted && first_dir_separator(filename) == NULL) + { + expanded = psprintf("$libdir/plugins/%s", filename); + filename = expanded; + } + + /* + * stat()/access() only check that the library exists, not that it has + * the correct magic number or even a library. But error messages from + * dlopen() are not portable, so it'd be hard to report any problem + * other than "does not exist". + */ + if (access(filename, R_OK) == 0) + continue; + + + if (source == PGC_S_FILE) + /* ALTER SYSTEM */ + ereport(WARNING, + errcode_for_file_access(), + errmsg("could not access file \"%s\"", filename), + errdetail("The server will currently fail to start with this setting."), + errhint("If the server is shut down, it will be necessary to manually edit the %s file to allow it to start again.", + "postgresql.auto.conf")); + else + /* ALTER ROLE/DATABASE */ + ereport(WARNING, + errcode_for_file_access(), + errmsg("could not access file \"%s\"", filename), + errdetail("New sessions will currently fail to connect with the new setting.")); + } + + list_free_deep(elemlist); + pfree(rawstring); + return true; +} + +static bool +check_shared_preload_libraries(char **newval, void **extra, GucSource source) +{ + return check_preload_libraries(newval, extra, source, true); +} + +static bool +check_local_preload_libraries(char **newval, void **extra, GucSource source) +{ + return check_preload_libraries(newval, extra, source, false); +} + +static bool +check_session_preload_libraries(char **newval, void **extra, GucSource source) +{ + return check_preload_libraries(newval, extra, source, true); +} + static bool check_effective_io_concurrency(int *newval, void **extra, GucSource source) { diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out index 88b1ff843be..ac161ce7c33 100644 --- a/src/test/modules/unsafe_tests/expected/rolenames.out +++ b/src/test/modules/unsafe_tests/expected/rolenames.out @@ -1082,6 +1082,25 @@ RESET SESSION AUTHORIZATION; ERROR: current transaction is aborted, commands ignored until end of transaction block ROLLBACK; REVOKE pg_read_all_settings FROM regress_role_haspriv; +-- test some warnings +ALTER ROLE regress_testrol0 SET session_preload_libraries="DoesNotExist"; +WARNING: could not access file "$libdir/plugins/DoesNotExist" +DETAIL: New sessions will currently fail to connect with the new setting. +ALTER ROLE regress_testrol0 SET local_preload_libraries="DoesNotExist"; +WARNING: could not access file "DoesNotExist" +DETAIL: New sessions will currently fail to connect with the new setting. +CREATE DATABASE regress_nosuch_db; +ALTER DATABASE regress_nosuch_db SET session_preload_libraries="DoesNotExist"; +WARNING: could not access file "$libdir/plugins/DoesNotExist" +DETAIL: New sessions will currently fail to connect with the new setting. +ALTER DATABASE regress_nosuch_db SET local_preload_libraries="DoesNotExist"; +WARNING: could not access file "DoesNotExist" +DETAIL: New sessions will currently fail to connect with the new setting. +DROP DATABASE regress_nosuch_db; +-- SET doesn't do anything, but should not warn, either +SET session_preload_libraries="DoesNotExist"; +SET SESSION session_preload_libraries="DoesNotExist"; +begin; SET LOCAL session_preload_libraries="DoesNotExist"; rollback; -- clean up \c DROP SCHEMA test_roles_schema; diff --git a/src/test/modules/unsafe_tests/sql/rolenames.sql b/src/test/modules/unsafe_tests/sql/rolenames.sql index adac36536db..cf605b08d30 100644 --- a/src/test/modules/unsafe_tests/sql/rolenames.sql +++ b/src/test/modules/unsafe_tests/sql/rolenames.sql @@ -494,6 +494,19 @@ RESET SESSION AUTHORIZATION; ROLLBACK; REVOKE pg_read_all_settings FROM regress_role_haspriv; +-- test some warnings +ALTER ROLE regress_testrol0 SET session_preload_libraries="DoesNotExist"; +ALTER ROLE regress_testrol0 SET local_preload_libraries="DoesNotExist"; +CREATE DATABASE regress_nosuch_db; +ALTER DATABASE regress_nosuch_db SET session_preload_libraries="DoesNotExist"; +ALTER DATABASE regress_nosuch_db SET local_preload_libraries="DoesNotExist"; +DROP DATABASE regress_nosuch_db; + +-- SET doesn't do anything, but should not warn, either +SET session_preload_libraries="DoesNotExist"; +SET SESSION session_preload_libraries="DoesNotExist"; +begin; SET LOCAL session_preload_libraries="DoesNotExist"; rollback; + -- clean up \c diff --git a/src/test/modules/worker_spi/expected/worker_spi.out b/src/test/modules/worker_spi/expected/worker_spi.out index dc0a79bf759..5b275669bcf 100644 --- a/src/test/modules/worker_spi/expected/worker_spi.out +++ b/src/test/modules/worker_spi/expected/worker_spi.out @@ -28,6 +28,8 @@ SELECT pg_reload_conf(); t (1 row) +-- reconnect to avoid unstable test result due to asynchronous signal +\c -- wait until the worker has processed the tuple we just inserted DO $$ DECLARE diff --git a/src/test/modules/worker_spi/sql/worker_spi.sql b/src/test/modules/worker_spi/sql/worker_spi.sql index 4683523b29d..4ed5370d456 100644 --- a/src/test/modules/worker_spi/sql/worker_spi.sql +++ b/src/test/modules/worker_spi/sql/worker_spi.sql @@ -18,6 +18,8 @@ END $$; INSERT INTO schema4.counted VALUES ('total', 0), ('delta', 1); SELECT pg_reload_conf(); +-- reconnect to avoid unstable test result due to asynchronous signal +\c -- wait until the worker has processed the tuple we just inserted DO $$ DECLARE diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 7ec3d2688f0..ea4f9eb39e2 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -3392,6 +3392,14 @@ CREATE FUNCTION func_with_set_params() RETURNS integer SET datestyle to iso, mdy SET local_preload_libraries TO "Mixed/Case", 'c:/''a"/path', '', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' IMMUTABLE STRICT; +WARNING: could not access file "Mixed/Case" +DETAIL: New sessions will currently fail to connect with the new setting. +WARNING: could not access file "c:/'a"/path" +DETAIL: New sessions will currently fail to connect with the new setting. +WARNING: could not access file "" +DETAIL: New sessions will currently fail to connect with the new setting. +WARNING: could not access file "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +DETAIL: New sessions will currently fail to connect with the new setting. SELECT pg_get_functiondef('func_with_set_params()'::regprocedure); pg_get_functiondef -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- 2.17.1
>From c08b511a3f76dc423b71a2c2584696b2d41afc63 Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Sat, 18 Dec 2021 22:51:01 -0600 Subject: [PATCH v5 2/3] errcontext if server fails to start due to library GUCs --- src/backend/utils/fmgr/dfmgr.c | 20 +++++++++++++++----- src/backend/utils/init/miscinit.c | 2 +- src/include/fmgr.h | 1 + 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c index 7f9ea972804..081d75fd724 100644 --- a/src/backend/utils/fmgr/dfmgr.c +++ b/src/backend/utils/fmgr/dfmgr.c @@ -75,7 +75,7 @@ static DynamicFileList *file_tail = NULL; char *Dynamic_library_path; -static void *internal_load_library(const char *libname); +static void *internal_load_library(const char *libname, const char *gucname); static void incompatible_module_error(const char *libname, const Pg_magic_struct *module_magic_data) pg_attribute_noreturn(); static bool file_exists(const char *name); @@ -113,7 +113,7 @@ load_external_function(const char *filename, const char *funcname, fullname = expand_dynamic_library_name(filename); /* Load the shared library, unless we already did */ - lib_handle = internal_load_library(fullname); + lib_handle = internal_load_library(fullname, NULL); /* Return handle if caller wants it */ if (filehandle) @@ -142,6 +142,14 @@ load_external_function(const char *filename, const char *funcname, */ void load_file(const char *filename, bool restricted) +{ + load_file_guc(filename, restricted, NULL); +} + +/* + * Also accepts a GUC arg, for error reports + */ +void load_file_guc(const char *filename, bool restricted, const char *gucname) { char *fullname; @@ -153,7 +161,7 @@ load_file(const char *filename, bool restricted) fullname = expand_dynamic_library_name(filename); /* Load the shared library */ - (void) internal_load_library(fullname); + (void) internal_load_library(fullname, gucname); pfree(fullname); } @@ -172,6 +180,7 @@ lookup_external_function(void *filehandle, const char *funcname) /* * Load the specified dynamic-link library file, unless it already is * loaded. Return the pg_dl* handle for the file. + * gucname may be passed for error reports. * * Note: libname is expected to be an exact name for the library file. * @@ -181,7 +190,7 @@ lookup_external_function(void *filehandle, const char *funcname) * perhaps other things that are definitely unsafe currently. */ static void * -internal_load_library(const char *libname) +internal_load_library(const char *libname, const char *gucname) { DynamicFileList *file_scanner; PGModuleMagicFunction magic_func; @@ -206,6 +215,7 @@ internal_load_library(const char *libname) if (stat(libname, &stat_buf) == -1) ereport(ERROR, (errcode_for_file_access(), + gucname ? errcontext("while loading shared libraries for setting \"%s\"", gucname) : 0, errmsg("could not access file \"%s\": %m", libname))); @@ -694,7 +704,7 @@ RestoreLibraryState(char *start_address) { while (*start_address != '\0') { - internal_load_library(start_address); + internal_load_library(start_address, NULL); start_address += strlen(start_address) + 1; } } diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb43b2c5e57..4237677c222 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -1664,7 +1664,7 @@ load_libraries(const char *libraries, const char *gucname, bool restricted) expanded = psprintf("$libdir/plugins/%s", filename); filename = expanded; } - load_file(filename, restricted); + load_file_guc(filename, restricted, gucname); ereport(DEBUG1, (errmsg_internal("loaded library \"%s\"", filename))); if (expanded) diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 380a82b9de3..3af652cdf99 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -737,6 +737,7 @@ extern void *load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle); extern void *lookup_external_function(void *filehandle, const char *funcname); extern void load_file(const char *filename, bool restricted); +extern void load_file_guc(const char *filename, bool restricted, const char *gucname); extern void **find_rendezvous_variable(const char *varName); extern Size EstimateLibraryStateSpace(void); extern void SerializeLibraryState(Size maxsize, char *start_address); -- 2.17.1
>From 62f43d2731e4aa782ebd6503266f596103538a48 Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Wed, 2 Feb 2022 20:54:49 -0600 Subject: [PATCH v5 3/3] show the GUC source in errcontext --- src/backend/utils/fmgr/dfmgr.c | 13 ++++++++++--- src/backend/utils/misc/guc.c | 24 ++++++++++++++++++++++++ src/include/utils/guc.h | 1 + 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c index 081d75fd724..9ccb12637a0 100644 --- a/src/backend/utils/fmgr/dfmgr.c +++ b/src/backend/utils/fmgr/dfmgr.c @@ -34,6 +34,7 @@ #include "lib/stringinfo.h" #include "miscadmin.h" #include "storage/shmem.h" +#include "utils/guc_tables.h" #include "utils/hsearch.h" @@ -213,11 +214,17 @@ internal_load_library(const char *libname, const char *gucname) * Check for same files - different paths (ie, symlink or link) */ if (stat(libname, &stat_buf) == -1) + { + char *errstr = strerror(errno); + int linenum; + char *sourcefile = gucname ? GetConfigSourceFile(gucname, &linenum) : NULL; ereport(ERROR, (errcode_for_file_access(), gucname ? errcontext("while loading shared libraries for setting \"%s\"", gucname) : 0, - errmsg("could not access file \"%s\": %m", - libname))); + sourcefile ? errcontext("from %s:%d", sourcefile, linenum) : 0, + errmsg("could not access file \"%s\": %s", + libname, errstr))); + } for (file_scanner = file_list; file_scanner != NULL && @@ -281,7 +288,7 @@ internal_load_library(const char *libname, const char *gucname) } else { - /* try to close library */ + /* try to close library */ // Not needed due to ERROR ? // dlclose(file_scanner->handle); free((char *) file_scanner); /* complain */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 4e38e73a277..4e6fae2bd8b 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -8577,6 +8577,30 @@ GetConfigOptionFlags(const char *name, bool missing_ok) return record->flags; } +/* + * Get the source file and line associated with the given option, if it was set + * by a file. + * + * If the option doesn't exist, throw an ereport and don't return. + */ +char * +GetConfigSourceFile(const char *name, int *linenum) +{ + struct config_generic *record; + + record = find_option(name, false, false, ERROR); + + /* Should not happen */ + if (record == NULL) + return NULL; + + if (record->source != PGC_S_FILE) + return NULL; + + *linenum = record->sourceline; + return record->sourcefile; +} + /* * flatten_set_variable_args diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index e734493a484..cdb5e3488c5 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -367,6 +367,7 @@ extern const char *GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged); extern const char *GetConfigOptionResetString(const char *name); extern int GetConfigOptionFlags(const char *name, bool missing_ok); +extern char *GetConfigSourceFile(const char *name, int *linenum); extern void ProcessConfigFile(GucContext context); extern char *convert_GUC_name_for_parameter_acl(const char *name); extern bool check_GUC_name_for_parameter_acl(const char *name); -- 2.17.1