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:
parent
e92c0632c1
commit
9324c8c580
@ -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 */
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -57,6 +57,6 @@
|
||||
*/
|
||||
|
||||
/* yyyymmddN */
|
||||
#define CATALOG_VERSION_NO 202503261
|
||||
#define CATALOG_VERSION_NO 202503262
|
||||
|
||||
#endif
|
||||
|
@ -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' },
|
||||
|
@ -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);
|
||||
|
@ -2229,6 +2229,7 @@ PgStat_WalCounters
|
||||
PgStat_WalStats
|
||||
PgXmlErrorContext
|
||||
PgXmlStrictness
|
||||
Pg_abi_values
|
||||
Pg_finfo_record
|
||||
Pg_magic_struct
|
||||
PipeProtoChunk
|
||||
|
Loading…
x
Reference in New Issue
Block a user