Hi, This discussion started at https://postgr.es/m/20230627212423.p56zxuqeh5kya...@awork3.anarazel.de but isn't really related to the bug.
On 2023-06-27 17:44:57 -0400, Tom Lane wrote: > Andres Freund <and...@anarazel.de> writes: > > That's not going to help you / the reporter, but to make issues like this > > easier to debug in the future, I think we should > > a) install an error context in load_libraries() printing the GUC name and > > b) install an error context in internal_load_library() printing the name of > > the shared library name > > +1. I'm not sure this specific issue justifies it, but it seems like that > might make it easier to diagnose other shared-library-load-time issues > as well. Yea, this bug alone wouldn't have made me suggest it, but I've looked at enough issues where I regretted not knowing what library caused and error and/or what caused a library to be loaded that I think it's worth it. I first added an error context to both the places mentioned above, but that leads to annoyingly redundant messages. And I realized that it'd also be useful to print a message when loading a library due to load_external_function() - I've definitely wondered about errors triggered below that in the past. I ended up adding a reason enum and a detail const char* to internal_load_library(). I don't like that approach a whole lot, but couldn't really come up with something better. Example errors that now have a context: Error in _PG_init() of library called via shared_preload_libraries (error added by me): FATAL: not today CONTEXT: in "_PG_init()" callback of library "/tmp/meson-install/lib/x86_64-linux-gnu/postgresql/auto_explain.so" library load for "shared_preload_libraries" parameter or FATAL: could not access file "dont_exist": No such file or directory CONTEXT: library load for "shared_preload_libraries" parameter Creating a C function referencing a library that needs to be loaded with shared_preload_libraries: =# CREATE FUNCTION frak() RETURNS text IMMUTABLE STRICT AS '/srv/dev/build/m/src/test/modules/test_slru/test_slru.so' LANGUAGE C; ERROR: XX000: cannot load "test_slru" after startup DETAIL: "test_slru" must be loaded with shared_preload_libraries. CONTEXT: in "_PG_init()" callback of library "/srv/dev/build/m/src/test/modules/test_slru/test_slru.so" library load for C function "frak" LOAD of a non-postgres library: =# LOAD '/usr/lib/libarmadillo.so.11'; ERROR: XX000: incompatible library "/usr/lib/libarmadillo.so.11": missing magic block HINT: Extension libraries are required to use the PG_MODULE_MAGIC macro. CONTEXT: library load for LOAD statement Note that here the errcontext callback prints the reason for the library being loaded, but not the library name. I made it so that the library name is only printed during _PG_init(), otherwise it's always duplicating the primary error message. Which looks messy - but perhaps it's more important to be "predictable"? I don't love "library load for ..." and played around with a few other variants, but I didn't come up with anything particularly satisfying. I was tempted to invent a separate "library load reason" for fmgr_info_C_lang() and other uses of load_external_function(), but concluded that that reaches diminishing-returns territory. Is it worth adding tests for: 1) an error during shared_preload_libraries, local_preload_libraries, session_preload_libraries 2) loading a non-postgres library and hitting "missing magic block" 3) local_preload_libraries not being allowed to load libraries outside of plugins/? 4) session_preload_libraries being allowed to load libraries outside of plugins/? ? Greetings, Andres Freund
>From 2d8c6f0c14c34c373f54834a703e4e7b0539665b Mon Sep 17 00:00:00 2001 From: Andres Freund <and...@anarazel.de> Date: Tue, 27 Jun 2023 16:42:51 -0700 Subject: [PATCH v1] Add error context callback to internal_load_library() Author: Reviewed-by: Discussion: https://postgr.es/20230627212423.p56zxuqeh5kya...@awork3.anarazel.de Backpatch: --- src/include/fmgr.h | 11 +++ src/backend/tcop/utility.c | 2 +- src/backend/utils/fmgr/dfmgr.c | 91 +++++++++++++++++-- src/backend/utils/init/miscinit.c | 2 +- contrib/test_decoding/expected/slot.out | 1 + .../regress/expected/create_function_c.out | 1 + src/tools/pgindent/typedefs.list | 2 + 7 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/include/fmgr.h b/src/include/fmgr.h index b120f5e7fef..10c3404cff2 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -741,10 +741,21 @@ extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid); */ extern PGDLLIMPORT char *Dynamic_library_path; +typedef enum LoadLibraryReason +{ + LLR_UNSPECIFIED, + LLR_GUC, + LLR_LOAD, + LLR_C_FUNCTION, + LLR_RESTORE_LIBRARY_STATE, +} LoadLibraryReason; + 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_ext(const char *filename, bool restricted, + LoadLibraryReason reason, const char *detail); extern void **find_rendezvous_variable(const char *varName); extern Size EstimateLibraryStateSpace(void); extern void SerializeLibraryState(Size maxsize, char *start_address); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 30b51bf4d30..dbf903f29e7 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -850,7 +850,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, closeAllVfds(); /* probably not necessary... */ /* Allowed names are restricted if you're not superuser */ - load_file(stmt->filename, !superuser()); + load_file_ext(stmt->filename, !superuser(), LLR_LOAD, NULL); } break; diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c index b85d52c913c..d9e23bb3b27 100644 --- a/src/backend/utils/fmgr/dfmgr.c +++ b/src/backend/utils/fmgr/dfmgr.c @@ -63,6 +63,15 @@ typedef struct df_files char filename[FLEXIBLE_ARRAY_MEMBER]; /* Full pathname of file */ } DynamicFileList; +/* State for load_library_error_callback() */ +typedef struct LoadLibraryErrorCallbackState +{ + const char *libname; + LoadLibraryReason reason; + const char *detail; + bool in_pg_init; +} LoadLibraryErrorCallbackState; + static DynamicFileList *file_list = NULL; static DynamicFileList *file_tail = NULL; @@ -75,7 +84,9 @@ 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, + LoadLibraryReason reason, + const char *detail); 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); @@ -83,6 +94,7 @@ static char *expand_dynamic_library_name(const char *name); static void check_restricted_library_name(const char *name); static char *substitute_libpath_macro(const char *name); static char *find_in_dynamic_libpath(const char *basename); +static void load_library_error_callback(void *arg); /* Magic structure that module needs to match to be accepted */ static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA; @@ -113,7 +125,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, LLR_C_FUNCTION, funcname); /* Return handle if caller wants it */ if (filehandle) @@ -139,9 +151,13 @@ load_external_function(const char *filename, const char *funcname, * * When 'restricted' is true, only libraries in the presumed-secure * directory $libdir/plugins may be referenced. + * + * 'reason' and 'detail' are used to set up an error context handler during + * library loading. */ void -load_file(const char *filename, bool restricted) +load_file_ext(const char *filename, bool restricted, + LoadLibraryReason reason, const char *detail) { char *fullname; @@ -153,11 +169,20 @@ 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, reason, detail); pfree(fullname); } +/* + * Backward compatible wrapper around load_file_ext() + */ +void +load_file(const char *filename, bool restricted) +{ + load_file_ext(filename, restricted, LLR_UNSPECIFIED, NULL); +} + /* * Lookup a function whose library file is already loaded. * Return NULL if not found. @@ -181,13 +206,26 @@ 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, + LoadLibraryReason reason, const char *detail) { DynamicFileList *file_scanner; PGModuleMagicFunction magic_func; char *load_error; struct stat stat_buf; PG_init_t PG_init; + LoadLibraryErrorCallbackState errstate = { + .reason = reason,.detail = detail, + .libname = libname, + .in_pg_init = false, + }; + ErrorContextCallback errcallback = { + .callback = load_library_error_callback, + .arg = (void *) &errstate, + .previous = error_context_stack, + }; + + error_context_stack = &errcallback; /* * Scan the list of loaded FILES to see if the file has been loaded. @@ -286,7 +324,11 @@ internal_load_library(const char *libname) */ PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init"); if (PG_init) + { + errstate.in_pg_init = true; (*PG_init) (); + errstate.in_pg_init = false; + } /* OK to link it into list */ if (file_list == NULL) @@ -296,6 +338,8 @@ internal_load_library(const char *libname) file_tail = file_scanner; } + error_context_stack = errcallback.previous; + return file_scanner->handle; } @@ -400,6 +444,41 @@ incompatible_module_error(const char *libname, errdetail_internal("%s", details.data))); } +/* + * Error context callback for internal_load_library() + */ +static void +load_library_error_callback(void *arg) +{ + LoadLibraryErrorCallbackState *state = (LoadLibraryErrorCallbackState *) arg; + + /* + * The errors in internal_load_library() contain the filename, but that's + * not the case during _PG_init(). + */ + if (state->in_pg_init) + errcontext("in \"%s\" callback of library \"%s\"", "_PG_init()", state->libname); + + switch (state->reason) + { + case LLR_UNSPECIFIED: + errcontext("library load for unspecified reason"); + break; + case LLR_GUC: + errcontext("library load for \"%s\" parameter", state->detail); + break; + case LLR_LOAD: + errcontext("library load for LOAD statement"); + break; + case LLR_C_FUNCTION: + errcontext("library load for C function \"%s\"", state->detail); + break; + case LLR_RESTORE_LIBRARY_STATE: + errcontext("restoring library state in parallel worker"); + break; + } +} + static bool file_exists(const char *name) { @@ -694,7 +773,7 @@ RestoreLibraryState(char *start_address) { while (*start_address != '\0') { - internal_load_library(start_address); + internal_load_library(start_address, LLR_RESTORE_LIBRARY_STATE, NULL); start_address += strlen(start_address) + 1; } } diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index a604432126c..3476f03c8be 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -1829,7 +1829,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_ext(filename, restricted, LLR_GUC, gucname); ereport(DEBUG1, (errmsg_internal("loaded library \"%s\"", filename))); if (expanded) diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out index 63a9940f73a..7d63140f43b 100644 --- a/contrib/test_decoding/expected/slot.out +++ b/contrib/test_decoding/expected/slot.out @@ -32,6 +32,7 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t2', 'tes SELECT pg_create_logical_replication_slot('foo', 'nonexistent'); ERROR: could not access file "nonexistent": No such file or directory +CONTEXT: library load for C function "_PG_output_plugin_init" -- here we want to start a new session and wait till old one is gone select pg_backend_pid() as oldpid \gset \c - diff --git a/src/test/regress/expected/create_function_c.out b/src/test/regress/expected/create_function_c.out index 2dba9d70349..0fc31c8ef89 100644 --- a/src/test/regress/expected/create_function_c.out +++ b/src/test/regress/expected/create_function_c.out @@ -18,6 +18,7 @@ LOAD :'regresslib'; CREATE FUNCTION test1 (int) RETURNS int LANGUAGE C AS 'nosuchfile'; ERROR: could not access file "nosuchfile": No such file or directory +CONTEXT: library load for C function "test1" -- To produce stable regression test output, we have to filter the name -- of the regresslib file out of the error message in this test. \set VERBOSITY sqlstate diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 260854747b4..3292a6c9f3a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1431,6 +1431,8 @@ ListenAction ListenActionKind ListenStmt LoInfo +LoadLibraryReason +LoadLibraryErrorCallbackState LoadStmt LocalBufferLookupEnt LocalPgBackendStatus -- 2.38.0