1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-18 13:44:19 +03:00

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
This commit is contained in:
Tom Lane 2025-03-26 10:59:42 -04:00
parent e92c0632c1
commit 9324c8c580
10 changed files with 248 additions and 28 deletions

View File

@ -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 */

View 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();

View 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>

View 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>

View 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
*

View 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

View File

@ -57,6 +57,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202503261
#define CATALOG_VERSION_NO 202503262
#endif

View File

@ -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' },

View 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);

View File

@ -2229,6 +2229,7 @@ PgStat_WalCounters
PgStat_WalStats
PgXmlErrorContext
PgXmlStrictness
Pg_abi_values
Pg_finfo_record
Pg_magic_struct
PipeProtoChunk