v6-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patch
text/x-diff
Filename: v6-0001-Introduce-PG_MODULE_MAGIC_EXT-macro.patch
Type: text/x-diff
Part: 0
Message:
Re: Add Postgres module info
Patch
Format: format-patch
Series: patch v6-0001
Subject: Introduce PG_MODULE_MAGIC_EXT macro.
| File | + | − |
|---|---|---|
| contrib/auto_explain/auto_explain.c | 4 | 1 |
| contrib/auto_explain/t/001_auto_explain.pl | 11 | 0 |
| doc/src/sgml/func.sgml | 24 | 0 |
| doc/src/sgml/xfunc.sgml | 24 | 0 |
| src/backend/utils/fmgr/dfmgr.c | 74 | 7 |
| src/include/catalog/pg_proc.dat | 7 | 0 |
| src/include/fmgr.h | 51 | 11 |
| src/tools/pgindent/typedefs.list | 1 | 0 |
From 2f8e182dbd26e03a9568393307b64cb26b3842fd Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 22 Mar 2025 18:39:15 -0400
Subject: [PATCH v6] 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 <lepihov@gmail.com>
Reviewed-by: Yurii Rashkovskii <yrashk@omnigres.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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 | 11 +++
doc/src/sgml/func.sgml | 24 +++++++
doc/src/sgml/xfunc.sgml | 24 +++++++
src/backend/utils/fmgr/dfmgr.c | 81 ++++++++++++++++++++--
src/include/catalog/pg_proc.dat | 7 ++
src/include/fmgr.h | 62 ++++++++++++++---
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 196 insertions(+), 19 deletions(-)
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 3b73bd19107..72615a3c116 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 = "1.0.0"
+);
/* 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..07692b20e71 100644
--- a/contrib/auto_explain/t/001_auto_explain.pl
+++ b/contrib/auto_explain/t/001_auto_explain.pl
@@ -212,4 +212,15 @@ 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, library_path != '' as library_path_provided
+FROM pg_get_loaded_modules()
+WHERE module_name = 'auto_explain';
+});
+like($res, qr/^auto_explain\|1\.0\.0\|t$/, "pg_get_loaded_modules() ok");
+
done_testing();
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6fa1d6586b8..306f1be2715 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25002,6 +25002,30 @@ 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>library_path</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>library_path</parameter> field gives the full path
+ name of the loaded module, but it is NULL if the user does not
+ have the privileges of the <literal>pg_read_server_files</literal>
+ role.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 9f22dacac7d..0f8f6040d9b 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1968,6 +1968,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> macro 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>
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index dd4c83d1bba..e430410d080 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -20,11 +20,15 @@
#include <dlfcn.h>
#endif /* !WIN32 */
+#include "catalog/pg_authid_d.h"
#include "fmgr.h"
+#include "funcapi.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "storage/shmem.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
#include "utils/hsearch.h"
@@ -51,6 +55,7 @@ typedef struct df_files
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;
@@ -68,12 +73,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 +248,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 +261,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 +302,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;
@@ -694,3 +704,60 @@ RestoreLibraryState(char *start_address)
start_address += strlen(start_address) + 1;
}
}
+
+/*
+ * SQL-callable function to get per-loaded-module information.
+ */
+Datum
+pg_get_loaded_modules(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ MemoryContext oldcontext;
+ DynamicFileList *file_scanner;
+ Datum result;
+ Datum values[3];
+ bool isnull[3] = {0};
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupdesc;
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+ funcctx->user_fctx = (void *) file_list;
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+ file_scanner = (DynamicFileList *) funcctx->user_fctx;
+
+ if (file_scanner != NULL)
+ {
+ if (file_scanner->magic->name == NULL)
+ isnull[0] = true;
+ else
+ values[0] = CStringGetTextDatum(file_scanner->magic->name);
+ if (file_scanner->magic->version == NULL)
+ isnull[1] = true;
+ else
+ values[1] = CStringGetTextDatum(file_scanner->magic->version);
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ isnull[2] = true;
+ else
+ values[2] = CStringGetTextDatum(file_scanner->filename);
+
+ result = HeapTupleGetDatum(heap_form_tuple(funcctx->tuple_desc,
+ values, isnull));
+
+ funcctx->user_fctx = file_scanner->next;
+ SRF_RETURN_NEXT(funcctx, result);
+ }
+ else
+ SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 890822eaf79..86389afa3f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6749,6 +6749,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,library_path}',
+ 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..c6dbb76d68e 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -440,11 +440,14 @@ 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
* are custom-configurable and especially likely to break dynamically loaded
@@ -459,22 +462,30 @@ extern PGDLLEXPORT void _PG_init(void);
*-------------------------------------------------------------------------
*/
-/* 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,11 +522,29 @@ 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
/*-------------------------------------------------------------------------
* Support routines and macros for callers of fmgr-compatible functions
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bfa276d2d35..854b16d0dcc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2228,6 +2228,7 @@ PgStat_WalCounters
PgStat_WalStats
PgXmlErrorContext
PgXmlStrictness
+Pg_abi_values
Pg_finfo_record
Pg_magic_struct
PipeProtoChunk
--
2.43.5