Introduce PG_MODULE_MAGIC_EXT macro.
authorTom Lane <[email protected]>
Wed, 26 Mar 2025 14:59:42 +0000 (10:59 -0400)
committerTom Lane <[email protected]>
Wed, 26 Mar 2025 15:06:12 +0000 (11:06 -0400)
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 <[email protected]>
Reviewed-by: Yurii Rashkovskii <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/dd4d1b59-d0fe-49d5-b28f-1e463b68fa32@gmail.com

contrib/auto_explain/auto_explain.c
contrib/auto_explain/t/001_auto_explain.pl
doc/src/sgml/func.sgml
doc/src/sgml/xfunc.sgml
src/backend/commands/extension.c
src/backend/utils/fmgr/dfmgr.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/fmgr.h
src/tools/pgindent/typedefs.list

index 3b73bd191079454b576c74bb719392a0e8d8b871..cd6625020a730f45bf2bd98ceeceab9db52297b9 100644 (file)
 #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 */
index 25252604b7d7eb1d1405ad59074f243ccb1f9d59..6af5ac1da1845446231c1e823de349fe0b7d266f 100644 (file)
@@ -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();
index 907c9ef7efaf13766b516f0e283c84eb9a63d6b2..5bf6656deca588ba20b0ce4a53dc678e74f34d9b 100644 (file)
@@ -25040,6 +25040,28 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_loaded_modules</primary>
+        </indexterm>
+        <function>pg_get_loaded_modules</function> ()
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>module_name</parameter> <type>text</type>,
+        <parameter>version</parameter> <type>text</type>,
+        <parameter>file_name</parameter> <type>text</type> )
+       </para>
+       <para>
+        Returns a list of the loadable modules that are loaded into the
+        current server session.  The <parameter>module_name</parameter>
+        and <parameter>version</parameter> fields are NULL unless the
+        module author supplied values for them using
+        the <literal>PG_MODULE_MAGIC_EXT</literal> macro.
+        The <parameter>file_name</parameter> field gives the file
+        name of the module (shared library).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
index 9f22dacac7d3c39f69e1d26d4cf223d59fb175a6..35d34f224ef9b31cb67c3a13782f34dbdf026892 100644 (file)
@@ -1954,6 +1954,9 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision
    <indexterm zone="xfunc-c-dynload">
     <primary>magic block</primary>
    </indexterm>
+   <indexterm zone="xfunc-c-dynload">
+    <primary><literal>PG_MODULE_MAGIC</literal></primary>
+   </indexterm>
 
    <para>
     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
 <programlisting>
 PG_MODULE_MAGIC;
 </programlisting>
+or
+<programlisting>
+PG_MODULE_MAGIC_EXT(<replaceable>parameters</replaceable>);
+</programlisting>
+   </para>
+
+   <para>
+    The <literal>PG_MODULE_MAGIC_EXT</literal> 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:
+
+<programlisting>
+PG_MODULE_MAGIC_EXT(
+    .name = "my_module_name",
+    .version = "1.2.3"
+);
+</programlisting>
+
+    Subsequently the name and version can be examined via
+    the <function>pg_get_loaded_modules()</function> function.
+    The meaning of the version string is not restricted
+    by <productname>PostgreSQL</productname>, but use of semantic versioning
+    rules is recommended.
    </para>
 
    <para>
index dc38c32577010bdf65e5dad76a1a2f76f5b346b6..180f4af9be36aedc35f1651f4a357d07b8d5334a 100644 (file)
@@ -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
  *
index dd4c83d1bba603897fa0bcae5d76b6464ec9fb6f..603632581d04a716263e5a70e41e64f486102179 100644 (file)
@@ -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
index d32758981e1867bf7783ae1d6117426bc9a1add8..798a186e8939237d6f02c16f56a9c253c941ce92 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202503261
+#define CATALOG_VERSION_NO 202503262
 
 #endif
index df0370256dc693ea70d6d240bb1ddc96aa2d5597..8b68b16d79daee57062ac6db35c47300363ede2d 100644 (file)
   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' },
index 82ee38b31e53fef108962cd8c7a7e6ebffb6d96a..853870d3abfbd165ea4e88c0046ffabfd1dd746f 100644 (file)
@@ -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);
index 3fbf5a4c2128e9aaa18973975d0b6e9dcb54829d..ff8d9ff0777ebd952bd56f8761e4edfc28a95484 100644 (file)
@@ -2229,6 +2229,7 @@ PgStat_WalCounters
 PgStat_WalStats
 PgXmlErrorContext
 PgXmlStrictness
+Pg_abi_values
 Pg_finfo_record
 Pg_magic_struct
 PipeProtoChunk