On Sun, Jan 09, 2022 at 11:58:18AM -0800, Maciek Sakrejda wrote: > On Sat, Jan 8, 2022 at 2:07 PM Justin Pryzby <pry...@telsasoft.com> wrote: > > Unfortunately, the output for dlopen() is not portable, which (I think) > > means > > most of what I wrote can't be made to work.. Since it doesn't work to call > > dlopen() when using SET, I tried using just stat(). But that also fails on > > windows, since one of the regression tests has an invalid filename involving > > unbalanced quotes, which cause it to return EINVAL rather than ENOENT. So > > SET > > cannot warn portably, unless it includes no details at all (or we specially > > handle the windows case), or change the pre-existing regression test. But > > there's a 2nd instability, too, apparently having to do with timing. So I'm > > planning to drop the 0001 patch. > > Hmm. I think 001 is a big part of the usability improvement here.
I agree - it helps people avoid causing a disruption, rather than just helping them to fix it faster. > Could we not at least warn generically, without relaying the > underlying error? The notable thing in this situation is that the > specified library could not be loaded (and that it will almost > certainly cause problems on restart). The specific error would be nice > to have, but it's less important. What is the timing instability? I saw regression diffs like this, showing that the warning could be displayed before or after the SELECT was echoed. https://cirrus-ci.com/task/6301672321318912 -SELECT * FROM schema4.counted; WARNING: could not load library: $libdir/plugins/worker_spi: cannot open shared object file: No such file or directory +SELECT * FROM schema4.counted; It's certainly possible to show a static message without additional text from errno. On Tue, Dec 28, 2021 at 9:45 AM Justin Pryzby <pry...@telsasoft.com> wrote: > > > For whatever reason, I get slightly different (and somewhat redundant) > > > output on failing to start: > > > > > > 2022-01-08 12:59:36.784 PST [324482] WARNING: could not load library: > > > $libdir/plugins/totally bogus: cannot open shared object file: No such > > > file or directory > > > 2022-01-08 12:59:36.787 PST [324482] FATAL: could not load library: > > > totally bogus: cannot open shared object file: No such file or directory > > > 2022-01-08 12:59:36.787 PST [324482] LOG: database system is shut down > > > > I think the first WARNING is from the GUC mechanism "setting" the library. > > And then the FATAL is from trying to apply the GUC. > > It looks like you didn't apply the 0002 patch for that test so got no > > CONTEXT ? > > I still had the terminal open where I tested this, and the scrollback > did show me applying the patch (and building after). I tried a make > clean and applying the patch again, and I do see the CONTEXT line now. > I'm not sure what the problem was but seems like PEBKAC--sorry about > that. Maybe you missed "make install" or similar. I took the liberty of adding you as a reviewer here: https://commitfest.postgresql.org/36/3482/ -- Justin
>From 53bf0c82ea76a00fbf1c95c4833f1bd302228d25 Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Sat, 18 Dec 2021 22:51:01 -0600 Subject: [PATCH v3 1/2] 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 050da780804..40eed5b5530 100644 --- a/src/backend/utils/fmgr/dfmgr.c +++ b/src/backend/utils/fmgr/dfmgr.c @@ -76,7 +76,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 void internal_unload_library(const char *libname); @@ -115,7 +115,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) @@ -144,6 +144,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; @@ -158,7 +166,7 @@ load_file(const char *filename, bool restricted) internal_unload_library(fullname); /* Load the shared library */ - (void) internal_load_library(fullname); + (void) internal_load_library(fullname, gucname); pfree(fullname); } @@ -179,9 +187,10 @@ lookup_external_function(void *filehandle, const char *funcname) * loaded. Return the pg_dl* handle for the file. * * Note: libname is expected to be an exact name for the library file. + * gucname may be passed for error reports. */ 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 GUC \"%s\"", gucname) : 0, errmsg("could not access file \"%s\": %m", libname))); @@ -764,7 +774,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 0f2570d6264..5c295e44ff8 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -1658,7 +1658,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 6560e462d66..9dccd0fc047 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -727,6 +727,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 76f2caade9c0e3554ecb645c99ad148e593a9fe6 Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Mon, 13 Dec 2021 08:42:38 -0600 Subject: [PATCH v3 2/2] warn when setting GUC to a nonextant library XXX: the SPI test is unstable: -SELECT * FROM schema4.counted; WARNING: could not load library: $libdir/plugins/worker_spi: cannot open shared object file: No such file or directory +SELECT * FROM schema4.counted; --- src/backend/utils/misc/guc.c | 85 ++++++++++++++++++- .../unsafe_tests/expected/rolenames.out | 1 + .../worker_spi/expected/worker_spi.out | 2 + .../modules/worker_spi/sql/worker_spi.sql | 2 + src/test/regress/expected/rules.out | 5 ++ src/test/regress/sql/rules.sql | 1 + 6 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 4c94f09c645..5a228a37ee2 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -170,6 +170,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, @@ -4188,7 +4194,7 @@ static struct config_string ConfigureNamesString[] = }, &session_preload_libraries_string, "", - NULL, NULL, NULL + check_session_preload_libraries, NULL, NULL }, { @@ -4199,7 +4205,7 @@ static struct config_string ConfigureNamesString[] = }, &shared_preload_libraries_string, "", - NULL, NULL, NULL + check_shared_preload_libraries, NULL, NULL }, { @@ -4210,7 +4216,7 @@ static struct config_string ConfigureNamesString[] = }, &local_preload_libraries_string, "", - NULL, NULL, NULL + check_local_preload_libraries, NULL, NULL }, { @@ -12100,6 +12106,79 @@ 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; + + /* 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) == -1) // F_OK + ereport(WARNING, + errcode_for_file_access(), + errmsg("could not access file \"%s\"", filename)); + } + + 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 eb608fdc2ea..368b10558d7 100644 --- a/src/test/modules/unsafe_tests/expected/rolenames.out +++ b/src/test/modules/unsafe_tests/expected/rolenames.out @@ -1066,6 +1066,7 @@ GRANT pg_read_all_settings TO regress_role_haspriv; BEGIN; -- A GUC using GUC_SUPERUSER_ONLY is useful for negative tests. SET LOCAL session_preload_libraries TO 'path-to-preload-libraries'; +WARNING: could not access file "$libdir/plugins/path-to-preload-libraries" SET SESSION AUTHORIZATION regress_role_haspriv; -- passes with role member of pg_read_all_settings SHOW session_preload_libraries; 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 d652f7b5fb4..ce396814332 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -3574,6 +3574,7 @@ drop table hats; drop table hat_data; -- test for pg_get_functiondef properly regurgitating SET parameters -- Note that the function is kept around to stress pg_dump. +SET check_function_bodies=no; CREATE FUNCTION func_with_set_params() RETURNS integer AS 'select 1;' LANGUAGE SQL @@ -3583,6 +3584,10 @@ 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" +WARNING: could not access file "c:/'a"/path" +WARNING: could not access file "" +WARNING: could not access file "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" SELECT pg_get_functiondef('func_with_set_params()'::regprocedure); pg_get_functiondef -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql index b732833e63c..1e42d092862 100644 --- a/src/test/regress/sql/rules.sql +++ b/src/test/regress/sql/rules.sql @@ -1198,6 +1198,7 @@ drop table hat_data; -- test for pg_get_functiondef properly regurgitating SET parameters -- Note that the function is kept around to stress pg_dump. +SET check_function_bodies=no; CREATE FUNCTION func_with_set_params() RETURNS integer AS 'select 1;' LANGUAGE SQL -- 2.17.1