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