From 9324c8c580655800331b0582b770e88c01b7a5c4 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 26 Mar 2025 10:59:42 -0400 Subject: [PATCH] Introduce PG_MODULE_MAGIC_EXT macro. This macro allows dynamically loaded shared libraries (modules) to provide a wired-in module name and version, and possibly other compile-time-constant fields in future. This information can be retrieved with the new pg_get_loaded_modules() function. This feature is expected to be particularly useful for modules that do not have any exposed SQL functionality and thus are not associated with a SQL-level extension object. But even for modules that do belong to extensions, being able to verify the actual code version can be useful. Author: Andrei Lepikhov Reviewed-by: Yurii Rashkovskii Reviewed-by: Tom Lane Discussion: https://postgr.es/m/dd4d1b59-d0fe-49d5-b28f-1e463b68fa32@gmail.com --- contrib/auto_explain/auto_explain.c | 5 +- contrib/auto_explain/t/001_auto_explain.pl | 13 ++++ doc/src/sgml/func.sgml | 22 ++++++ doc/src/sgml/xfunc.sgml | 27 ++++++++ src/backend/commands/extension.c | 53 +++++++++++++++ src/backend/utils/fmgr/dfmgr.c | 67 +++++++++++++++--- src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_proc.dat | 7 ++ src/include/fmgr.h | 79 ++++++++++++++++++---- src/tools/pgindent/typedefs.list | 1 + 10 files changed, 248 insertions(+), 28 deletions(-) diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index 3b73bd19107..cd6625020a7 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -22,7 +22,10 @@ #include "executor/instrument.h" #include "utils/guc.h" -PG_MODULE_MAGIC; +PG_MODULE_MAGIC_EXT( + .name = "auto_explain", + .version = PG_VERSION +); /* GUC variables */ static int auto_explain_log_min_duration = -1; /* msec or -1 */ diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl index 25252604b7d..6af5ac1da18 100644 --- a/contrib/auto_explain/t/001_auto_explain.pl +++ b/contrib/auto_explain/t/001_auto_explain.pl @@ -212,4 +212,17 @@ REVOKE SET ON PARAMETER auto_explain.log_format FROM regress_user1; DROP USER regress_user1; }); +# Test pg_get_loaded_modules() function. This function is particularly +# useful for modules with no SQL presence, such as auto_explain. + +my $res = $node->safe_psql( + "postgres", q{ +SELECT module_name, + version = current_setting('server_version') as version_ok, + regexp_replace(file_name, '\..*', '') as file_name_stripped +FROM pg_get_loaded_modules() +WHERE module_name = 'auto_explain'; +}); +like($res, qr/^auto_explain\|t\|auto_explain$/, "pg_get_loaded_modules() ok"); + done_testing(); diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 907c9ef7efa..5bf6656deca 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -25040,6 +25040,28 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n); + + + + pg_get_loaded_modules + + pg_get_loaded_modules () + setof record + ( module_name text, + version text, + file_name text ) + + + Returns a list of the loadable modules that are loaded into the + current server session. The module_name + and version fields are NULL unless the + module author supplied values for them using + the PG_MODULE_MAGIC_EXT macro. + The file_name field gives the file + name of the module (shared library). + + + diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 9f22dacac7d..35d34f224ef 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -1954,6 +1954,9 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision magic block + + PG_MODULE_MAGIC + To ensure that a dynamically loaded object file is not loaded into an @@ -1968,6 +1971,30 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision PG_MODULE_MAGIC; +or + +PG_MODULE_MAGIC_EXT(parameters); + + + + + The PG_MODULE_MAGIC_EXT variant allows the specification + of additional information about the module; currently, a name and/or a + version string can be added. (More fields might be allowed in future.) + Write something like this: + + +PG_MODULE_MAGIC_EXT( + .name = "my_module_name", + .version = "1.2.3" +); + + + Subsequently the name and version can be examined via + the pg_get_loaded_modules() function. + The meaning of the version string is not restricted + by PostgreSQL, but use of semantic versioning + rules is recommended. diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index dc38c325770..180f4af9be3 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -2811,6 +2811,59 @@ pg_extension_config_dump(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +/* + * pg_get_loaded_modules + * + * SQL-callable function to get per-loaded-module information. Modules + * (shared libraries) aren't necessarily one-to-one with extensions, but + * they're sufficiently closely related to make this file a good home. + */ +Datum +pg_get_loaded_modules(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + DynamicFileList *file_scanner; + + /* Build tuplestore to hold the result rows */ + InitMaterializedSRF(fcinfo, 0); + + for (file_scanner = get_first_loaded_module(); file_scanner != NULL; + file_scanner = get_next_loaded_module(file_scanner)) + { + const char *library_path, + *module_name, + *module_version; + const char *sep; + Datum values[3] = {0}; + bool nulls[3] = {0}; + + get_loaded_module_details(file_scanner, + &library_path, + &module_name, + &module_version); + + if (module_name == NULL) + nulls[0] = true; + else + values[0] = CStringGetTextDatum(module_name); + if (module_version == NULL) + nulls[1] = true; + else + values[1] = CStringGetTextDatum(module_version); + + /* For security reasons, we don't show the directory path */ + sep = last_dir_separator(library_path); + if (sep) + library_path = sep + 1; + values[2] = CStringGetTextDatum(library_path); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); + } + + return (Datum) 0; +} + /* * extension_config_remove * diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c index dd4c83d1bba..603632581d0 100644 --- a/src/backend/utils/fmgr/dfmgr.c +++ b/src/backend/utils/fmgr/dfmgr.c @@ -40,19 +40,21 @@ typedef struct /* * List of dynamically loaded files (kept in malloc'd memory). + * + * Note: "typedef struct DynamicFileList DynamicFileList" appears in fmgr.h. */ - -typedef struct df_files +struct DynamicFileList { - struct df_files *next; /* List link */ + DynamicFileList *next; /* List link */ dev_t device; /* Device file is on */ #ifndef WIN32 /* ensures we never again depend on this under * win32 */ ino_t inode; /* Inode number of file */ #endif void *handle; /* a handle for pg_dl* functions */ + const Pg_magic_struct *magic; /* Location of module's magic block */ char filename[FLEXIBLE_ARRAY_MEMBER]; /* Full pathname of file */ -} DynamicFileList; +}; static DynamicFileList *file_list = NULL; static DynamicFileList *file_tail = NULL; @@ -68,12 +70,12 @@ char *Dynamic_library_path; static void *internal_load_library(const char *libname); pg_noreturn static void incompatible_module_error(const char *libname, - const Pg_magic_struct *module_magic_data); + const Pg_abi_values *module_magic_data); static char *expand_dynamic_library_name(const char *name); static void check_restricted_library_name(const char *name); -/* Magic structure that module needs to match to be accepted */ -static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA; +/* ABI values that module needs to match to be accepted */ +static const Pg_abi_values magic_data = PG_MODULE_ABI_DATA; /* @@ -243,8 +245,10 @@ internal_load_library(const char *libname) { const Pg_magic_struct *magic_data_ptr = (*magic_func) (); - if (magic_data_ptr->len != magic_data.len || - memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0) + /* Check ABI compatibility fields */ + if (magic_data_ptr->len != sizeof(Pg_magic_struct) || + memcmp(&magic_data_ptr->abi_fields, &magic_data, + sizeof(Pg_abi_values)) != 0) { /* copy data block before unlinking library */ Pg_magic_struct module_magic_data = *magic_data_ptr; @@ -254,8 +258,11 @@ internal_load_library(const char *libname) free(file_scanner); /* issue suitable complaint */ - incompatible_module_error(libname, &module_magic_data); + incompatible_module_error(libname, &module_magic_data.abi_fields); } + + /* Remember the magic block's location for future use */ + file_scanner->magic = magic_data_ptr; } else { @@ -292,7 +299,7 @@ internal_load_library(const char *libname) */ static void incompatible_module_error(const char *libname, - const Pg_magic_struct *module_magic_data) + const Pg_abi_values *module_magic_data) { StringInfoData details; @@ -393,6 +400,44 @@ incompatible_module_error(const char *libname, } +/* + * Iterator functions to allow callers to scan the list of loaded modules. + * + * Note: currently, there is no special provision for dealing with changes + * in the list while a scan is happening. Current callers don't need it. + */ +DynamicFileList * +get_first_loaded_module(void) +{ + return file_list; +} + +DynamicFileList * +get_next_loaded_module(DynamicFileList *dfptr) +{ + return dfptr->next; +} + +/* + * Return some details about the specified module. + * + * Note that module_name and module_version could be returned as NULL. + * + * We could dispense with this function by exposing struct DynamicFileList + * globally, but this way seems preferable. + */ +void +get_loaded_module_details(DynamicFileList *dfptr, + const char **library_path, + const char **module_name, + const char **module_version) +{ + *library_path = dfptr->filename; + *module_name = dfptr->magic->name; + *module_version = dfptr->magic->version; +} + + /* * If name contains a slash, check if the file exists, if so return * the name. Else (no slash) try to expand using search path (see diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index d32758981e1..798a186e893 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202503261 +#define CATALOG_VERSION_NO 202503262 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index df0370256dc..8b68b16d79d 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6756,6 +6756,13 @@ proargnames => '{rm_id, rm_name, rm_builtin}', prosrc => 'pg_get_wal_resource_managers' }, +{ oid => '8303', descr => 'get info about loaded modules', + proname => 'pg_get_loaded_modules', prorows => '10', proretset => 't', + provolatile => 'v', proparallel => 'r', prorettype => 'record', + proargtypes => '', proallargtypes => '{text,text,text}', + proargmodes => '{o,o,o}', proargnames => '{module_name,version,file_name}', + prosrc => 'pg_get_loaded_modules' }, + { oid => '2621', descr => 'reload configuration files', proname => 'pg_reload_conf', provolatile => 'v', prorettype => 'bool', proargtypes => '', prosrc => 'pg_reload_conf' }, diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 82ee38b31e5..853870d3abf 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -440,41 +440,52 @@ extern PGDLLEXPORT void _PG_init(void); * We require dynamically-loaded modules to include the macro call * PG_MODULE_MAGIC; * so that we can check for obvious incompatibility, such as being compiled - * for a different major PostgreSQL version. + * for a different major PostgreSQL version. Alternatively, write + * PG_MODULE_MAGIC_EXT(...); + * where the optional arguments can specify module name and version, and + * perhaps other values in future. Note that in a multiple-source-file + * module, there should be exactly one such macro call. * - * To compile with versions of PostgreSQL that do not support this, - * you may put an #ifdef/#endif test around it. Note that in a multiple- - * source-file module, the macro call should only appear once. + * You may need an #ifdef test to verify that the version of PostgreSQL + * you are compiling against supports PG_MODULE_MAGIC_EXT(). * - * The specific items included in the magic block are intended to be ones that + * The specific items included in the ABI fields are intended to be ones that * are custom-configurable and especially likely to break dynamically loaded * modules if they were compiled with other values. Also, the length field * can be used to detect definition changes. * - * Note: we compare magic blocks with memcmp(), so there had better not be - * any alignment pad bytes in them. + * Note: we compare Pg_abi_values structs with memcmp(), so there had better + * not be any alignment pad bytes in them. * - * Note: when changing the contents of magic blocks, be sure to adjust the + * Note: when changing the contents of Pg_abi_values, be sure to adjust the * incompatible_module_error() function in dfmgr.c. *------------------------------------------------------------------------- */ -/* Definition of the magic block structure */ +/* Definition of the values we check to verify ABI compatibility */ typedef struct { - int len; /* sizeof(this struct) */ int version; /* PostgreSQL major version */ int funcmaxargs; /* FUNC_MAX_ARGS */ int indexmaxkeys; /* INDEX_MAX_KEYS */ int namedatalen; /* NAMEDATALEN */ int float8byval; /* FLOAT8PASSBYVAL */ char abi_extra[32]; /* see pg_config_manual.h */ +} Pg_abi_values; + +/* Definition of the magic block structure */ +typedef struct +{ + int len; /* sizeof(this struct) */ + Pg_abi_values abi_fields; /* see above */ + /* Remaining fields are zero unless filled via PG_MODULE_MAGIC_EXT */ + const char *name; /* optional module name */ + const char *version; /* optional module version */ } Pg_magic_struct; -/* The actual data block contents */ -#define PG_MODULE_MAGIC_DATA \ +/* Macro to fill the ABI fields */ +#define PG_MODULE_ABI_DATA \ { \ - sizeof(Pg_magic_struct), \ PG_VERSION_NUM / 100, \ FUNC_MAX_ARGS, \ INDEX_MAX_KEYS, \ @@ -483,7 +494,18 @@ typedef struct FMGR_ABI_EXTRA, \ } -StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_magic_struct *) 0)->abi_extra), +/* + * Macro to fill a magic block. If any arguments are given, they should + * be field initializers. + */ +#define PG_MODULE_MAGIC_DATA(...) \ +{ \ + .len = sizeof(Pg_magic_struct), \ + .abi_fields = PG_MODULE_ABI_DATA, \ + __VA_ARGS__ \ +} + +StaticAssertDecl(sizeof(FMGR_ABI_EXTRA) <= sizeof(((Pg_abi_values *) 0)->abi_extra), "FMGR_ABI_EXTRA too long"); /* @@ -500,7 +522,26 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \ const Pg_magic_struct * \ PG_MAGIC_FUNCTION_NAME(void) \ { \ - static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \ + static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(0); \ + return &Pg_magic_data; \ +} \ +extern int no_such_variable + +/* + * Alternate declaration that allows specification of additional fields. + * The additional values should be written as field initializers, for example + * PG_MODULE_MAGIC_EXT( + * .name = "some string", + * .version = "some string" + * ); + */ +#define PG_MODULE_MAGIC_EXT(...) \ +extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \ +const Pg_magic_struct * \ +PG_MAGIC_FUNCTION_NAME(void) \ +{ \ + static const Pg_magic_struct Pg_magic_data = \ + PG_MODULE_MAGIC_DATA(__VA_ARGS__); \ return &Pg_magic_data; \ } \ extern int no_such_variable @@ -738,6 +779,8 @@ extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid); /* * Routines in dfmgr.c */ +typedef struct DynamicFileList DynamicFileList; /* opaque outside dfmgr.c */ + extern PGDLLIMPORT char *Dynamic_library_path; extern char *substitute_path_macro(const char *str, const char *macro, const char *value); @@ -747,6 +790,12 @@ 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 DynamicFileList *get_first_loaded_module(void); +extern DynamicFileList *get_next_loaded_module(DynamicFileList *dfptr); +extern void get_loaded_module_details(DynamicFileList *dfptr, + const char **library_path, + const char **module_name, + const char **module_version); extern void **find_rendezvous_variable(const char *varName); extern Size EstimateLibraryStateSpace(void); extern void SerializeLibraryState(Size maxsize, char *start_address); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3fbf5a4c212..ff8d9ff0777 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2229,6 +2229,7 @@ PgStat_WalCounters PgStat_WalStats PgXmlErrorContext PgXmlStrictness +Pg_abi_values Pg_finfo_record Pg_magic_struct PipeProtoChunk