/*-------------------------------------------------------------------------
*
* dfmgr . c
* Dynamic function manager code .
*
* Portions Copyright ( c ) 1996 - 2025 , PostgreSQL Global Development Group
* Portions Copyright ( c ) 1994 , Regents of the University of California
*
*
* IDENTIFICATION
* src / backend / utils / fmgr / dfmgr . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include <sys/stat.h>
#ifndef WIN32
#include <dlfcn.h>
#endif /* !WIN32 */
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "storage/shmem.h"
#include "utils/hsearch.h"
/* signature for PostgreSQL-specific library init function */
typedef void (*PG_init_t) (void );
/* hashtable entry for rendezvous variables */
typedef struct
{
char varName[NAMEDATALEN]; /* hash key (must be first) */
void *varValue;
} rendezvousHashEntry;
/*
* List of dynamically loaded files ( kept in malloc ' d memory ) .
*
* Note : " typedef struct DynamicFileList DynamicFileList " appears in fmgr . h .
*/
struct DynamicFileList
{
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 */
};
static DynamicFileList *file_list = NULL;
static DynamicFileList *file_tail = NULL;
/* stat() call under Win32 returns an st_ino field, but it has no meaning */
#ifndef WIN32
#define SAME_INODE(A,B) ((A).st_ino == (B).inode && (A).st_dev == (B).device)
#else
#define SAME_INODE(A,B) false
#endif
char *Dynamic_library_path;
static void *internal_load_library(const char *libname);
pg_noreturn static void incompatible_module_error(const char *libname,
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);
/* ABI values that module needs to match to be accepted */
static const Pg_abi_values magic_data = PG_MODULE_ABI_DATA;
/*
* Load the specified dynamic - link library file , and look for a function
* named funcname in it .
*
* If the function is not found , we raise an error if signalNotFound is true ,
* else return NULL . Note that errors in loading the library
* will provoke ereport ( ) regardless of signalNotFound .
*
* If filehandle is not NULL , then * filehandle will be set to a handle
* identifying the library file . The filehandle can be used with
* lookup_external_function to lookup additional functions in the same file
* at less cost than repeating load_external_function .
*/
void *
load_external_function(const char *filename, const char *funcname,
bool signalNotFound, void **filehandle)
{
char *fullname;
void *lib_handle;
void *retval;
/*
* For extensions with hardcoded ' $ libdir / ' library names , we strip the
* prefix to allow the library search path to be used . This is done only
* for simple names ( e . g . , " $ libdir / foo " ) , not for nested paths ( e . g . ,
* " $ libdir / foo / bar " ) .
*
* For nested paths , ' expand_dynamic_library_name ' directly expands the
* ' $ libdir ' macro , so we leave them untouched .
*/
if (strncmp(filename, "$libdir/" , 8 ) == 0 )
{
if (first_dir_separator(filename + 8 ) == NULL)
filename += 8 ;
}
/* Expand the possibly-abbreviated filename to an exact path name */
fullname = expand_dynamic_library_name(filename);
/* Load the shared library, unless we already did */
lib_handle = internal_load_library(fullname);
/* Return handle if caller wants it */
if (filehandle)
*filehandle = lib_handle;
/* Look up the function within the library. */
retval = dlsym(lib_handle, funcname);
if (retval == NULL && signalNotFound)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("could not find function \" %s\" in file \" %s\"" ,
funcname, fullname)));
pfree(fullname);
return retval;
}
/*
* This function loads a shlib file without looking up any particular
* function in it . If the same shlib has previously been loaded ,
* we do not load it again .
*
* When ' restricted ' is true , only libraries in the presumed - secure
* directory $ libdir / plugins may be referenced .
*/
void
load_file(const char *filename, bool restricted)
{
char *fullname;
/* Apply security restriction if requested */
if (restricted)
check_restricted_library_name(filename);
/* Expand the possibly-abbreviated filename to an exact path name */
fullname = expand_dynamic_library_name(filename);
/* Load the shared library, unless we already did */
(void ) internal_load_library(fullname);
pfree(fullname);
}
/*
* Lookup a function whose library file is already loaded .
* Return NULL if not found .
*/
void *
lookup_external_function(void *filehandle, const char *funcname)
{
return dlsym(filehandle, funcname);
}
/*
* Load the specified dynamic - link library file , unless it already is
* loaded . Return the pg_dl * handle for the file .
*
* Note : libname is expected to be an exact name for the library file .
*
* NB : There is presently no way to unload a dynamically loaded file . We might
* add one someday if we can convince ourselves we have safe protocols for un -
* hooking from hook function pointers , releasing custom GUC variables , and
* perhaps other things that are definitely unsafe currently .
*/
static void *
internal_load_library(const char *libname)
{
DynamicFileList *file_scanner;
PGModuleMagicFunction magic_func;
char *load_error;
struct stat stat_buf;
PG_init_t PG_init;
/*
* Scan the list of loaded FILES to see if the file has been loaded .
*/
for (file_scanner = file_list;
file_scanner != NULL &&
strcmp(libname, file_scanner->filename) != 0 ;
file_scanner = file_scanner->next)
;
if (file_scanner == NULL)
{
/*
* Check for same files - different paths ( ie , symlink or link )
*/
if (stat(libname, &stat_buf) == -1 )
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not access file \" %s\": %m" ,
libname)));
for (file_scanner = file_list;
file_scanner != NULL &&
!SAME_INODE(stat_buf, *file_scanner);
file_scanner = file_scanner->next)
;
}
if (file_scanner == NULL)
{
/*
* File not loaded yet .
*/
file_scanner = (DynamicFileList *)
malloc(offsetof(DynamicFileList, filename) + strlen(libname) + 1 );
if (file_scanner == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory" )));
MemSet(file_scanner, 0 , offsetof(DynamicFileList, filename));
strcpy(file_scanner->filename, libname);
file_scanner->device = stat_buf.st_dev;
#ifndef WIN32
file_scanner->inode = stat_buf.st_ino;
#endif
file_scanner->next = NULL;
file_scanner->handle = dlopen(file_scanner->filename, RTLD_NOW | RTLD_GLOBAL);
if (file_scanner->handle == NULL)
{
load_error = dlerror();
free(file_scanner);
/* errcode_for_file_access might not be appropriate here? */
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not load library \" %s\": %s" ,
libname, load_error)));
}
/* Check the magic function to determine compatibility */
magic_func = (PGModuleMagicFunction)
dlsym(file_scanner->handle, PG_MAGIC_FUNCTION_NAME_STRING);
if (magic_func)
{
const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
/* 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;
/* try to close library */
dlclose(file_scanner->handle);
free(file_scanner);
/* issue suitable complaint */
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
{
/* try to close library */
dlclose(file_scanner->handle);
free(file_scanner);
/* complain */
ereport(ERROR,
(errmsg("incompatible library \" %s\": missing magic block" ,
libname),
errhint("Extension libraries are required to use the PG_MODULE_MAGIC macro." )));
}
/*
* If the library has a _ PG_init ( ) function , call it .
*/
PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init" );
if (PG_init)
(*PG_init) ();
/* OK to link it into list */
if (file_list == NULL)
file_list = file_scanner;
else
file_tail->next = file_scanner;
file_tail = file_scanner;
}
return file_scanner->handle;
}
/*
* Report a suitable error for an incompatible magic block .
*/
static void
incompatible_module_error(const char *libname,
const Pg_abi_values *module_magic_data)
{
StringInfoData details;
/*
* If the version doesn ' t match , just report that , because the rest of the
* block might not even have the fields we expect .
*/
if (magic_data.version != module_magic_data->version)
{
char library_version[32 ];
if (module_magic_data->version >= 1000 )
snprintf(library_version, sizeof (library_version), "%d" ,
module_magic_data->version / 100 );
else
snprintf(library_version, sizeof (library_version), "%d.%d" ,
module_magic_data->version / 100 ,
module_magic_data->version % 100 );
ereport(ERROR,
(errmsg("incompatible library \" %s\": version mismatch" ,
libname),
errdetail("Server is version %d, library is version %s." ,
magic_data.version / 100 , library_version)));
}
/*
* Similarly , if the ABI extra field doesn ' t match , error out . Other
* fields below might also mismatch , but that isn ' t useful information if
* you ' re using the wrong product altogether .
*/
if (strcmp(module_magic_data->abi_extra, magic_data.abi_extra) != 0 )
{
ereport(ERROR,
(errmsg("incompatible library \" %s\": ABI mismatch" ,
libname),
errdetail("Server has ABI \" %s\", library has \" %s\"." ,
magic_data.abi_extra,
module_magic_data->abi_extra)));
}
/*
* Otherwise , spell out which fields don ' t agree .
*
* XXX this code has to be adjusted any time the set of fields in a magic
* block change !
*/
initStringInfo(&details);
if (module_magic_data->funcmaxargs != magic_data.funcmaxargs)
{
if (details.len)
appendStringInfoChar(&details, '\n' );
appendStringInfo(&details,
/* translator: %s is a variable name and %d its values */
_("Server has %s = %d, library has %d." ),
"FUNC_MAX_ARGS" , magic_data.funcmaxargs,
module_magic_data->funcmaxargs);
}
if (module_magic_data->indexmaxkeys != magic_data.indexmaxkeys)
{
if (details.len)
appendStringInfoChar(&details, '\n' );
appendStringInfo(&details,
/* translator: %s is a variable name and %d its values */
_("Server has %s = %d, library has %d." ),
"INDEX_MAX_KEYS" , magic_data.indexmaxkeys,
module_magic_data->indexmaxkeys);
}
if (module_magic_data->namedatalen != magic_data.namedatalen)
{
if (details.len)
appendStringInfoChar(&details, '\n' );
appendStringInfo(&details,
/* translator: %s is a variable name and %d its values */
_("Server has %s = %d, library has %d." ),
"NAMEDATALEN" , magic_data.namedatalen,
module_magic_data->namedatalen);
}
if (module_magic_data->float8byval != magic_data.float8byval)
{
if (details.len)
appendStringInfoChar(&details, '\n' );
appendStringInfo(&details,
/* translator: %s is a variable name and %d its values */
_("Server has %s = %s, library has %s." ),
"FLOAT8PASSBYVAL" , magic_data.float8byval ? "true" : "false" ,
module_magic_data->float8byval ? "true" : "false" );
}
if (details.len == 0 )
appendStringInfoString(&details,
_("Magic block has unexpected length or padding difference." ));
ereport(ERROR,
(errmsg("incompatible library \" %s\": magic block mismatch" ,
libname),
errdetail_internal("%s" , details.data)));
}
/*
* 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
* find_in_path below ) ; if that works , return the fully
* expanded file name . If the previous failed , append DLSUFFIX and
* try again . If all fails , just return the original name .
*
* The result will always be freshly palloc ' d .
*/
static char *
expand_dynamic_library_name(const char *name)
{
bool have_slash;
char *new ;
char *full;
Assert(name);
have_slash = (first_dir_separator(name) != NULL);
if (!have_slash)
{
full = find_in_path(name, Dynamic_library_path, "dynamic_library_path" , "$libdir" , pkglib_path);
if (full)
return full;
}
else
{
full = substitute_path_macro(name, "$libdir" , pkglib_path);
if (pg_file_exists(full))
return full;
pfree(full);
}
new = psprintf("%s%s" , name, DLSUFFIX);
if (!have_slash)
{
full = find_in_path(new , Dynamic_library_path, "dynamic_library_path" , "$libdir" , pkglib_path);
pfree(new );
if (full)
return full;
}
else
{
full = substitute_path_macro(new , "$libdir" , pkglib_path);
pfree(new );
if (pg_file_exists(full))
return full;
pfree(full);
}
/*
* If we can ' t find the file , just return the string as - is . The ensuing
* load attempt will fail and report a suitable message .
*/
return pstrdup(name);
}
/*
* Check a restricted library name . It must begin with " $ libdir / plugins / "
* and there must not be any directory separators after that ( this is
* sufficient to prevent " . . " style attacks ) .
*/
static void
check_restricted_library_name(const char *name)
{
if (strncmp(name, "$libdir/plugins/" , 16 ) != 0 ||
first_dir_separator(name + 16 ) != NULL)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("access to library \" %s\" is not allowed" ,
name)));
}
/*
* Substitute for any macros appearing in the given string .
* Result is always freshly palloc ' d .
*/
char *
substitute_path_macro(const char *str, const char *macro, const char *value)
{
const char *sep_ptr;
Assert(str != NULL);
Assert(macro[0 ] == '$' );
/* Currently, we only recognize $macro at the start of the string */
if (str[0 ] != '$' )
return pstrdup(str);
if ((sep_ptr = first_dir_separator(str)) == NULL)
sep_ptr = str + strlen(str);
if (strlen(macro) != sep_ptr - str ||
strncmp(str, macro, strlen(macro)) != 0 )
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid macro name in path: %s" ,
str)));
return psprintf("%s%s" , value, sep_ptr);
}
/*
* Search for a file called ' basename ' in the colon - separated search
* path given . If the file is found , the full file name
* is returned in freshly palloc ' d memory . If the file is not found ,
* return NULL .
*
* path_param is the name of the parameter that path came from , for error
* messages .
*
* macro and macro_val allow substituting a macro ; see
* substitute_path_macro ( ) .
*/
char *
find_in_path(const char *basename, const char *path, const char *path_param,
const char *macro, const char *macro_val)
{
const char *p;
size_t baselen;
Assert(basename != NULL);
Assert(first_dir_separator(basename) == NULL);
Assert(path != NULL);
Assert(path_param != NULL);
p = path;
/*
* If the path variable is empty , don ' t do a path search .
*/
if (strlen(p) == 0 )
return NULL;
baselen = strlen(basename);
for (;;)
{
size_t len;
char *piece;
char *mangled;
char *full;
piece = first_path_var_separator(p);
if (piece == p)
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("zero-length component in parameter \" %s\"" , path_param)));
if (piece == NULL)
len = strlen(p);
else
len = piece - p;
piece = palloc(len + 1 );
strlcpy(piece, p, len + 1 );
mangled = substitute_path_macro(piece, macro, macro_val);
pfree(piece);
canonicalize_path(mangled);
/* only absolute paths */
if (!is_absolute_path(mangled))
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("component in parameter \" %s\" is not an absolute path" , path_param)));
full = palloc(strlen(mangled) + 1 + baselen + 1 );
sprintf(full, "%s/%s" , mangled, basename);
pfree(mangled);
elog(DEBUG3, "%s: trying \" %s\"" , __func__, full);
if (pg_file_exists(full))
return full;
pfree(full);
if (p[len] == '\0' )
break ;
else
p += len + 1 ;
}
return NULL;
}
/*
* Find ( or create ) a rendezvous variable that one dynamically
* loaded library can use to meet up with another .
*
* On the first call of this function for a particular varName ,
* a " rendezvous variable " is created with the given name .
* The value of the variable is a void pointer ( initially set to NULL ) .
* Subsequent calls with the same varName just return the address of
* the existing variable . Once created , a rendezvous variable lasts
* for the life of the process .
*
* Dynamically loaded libraries can use rendezvous variables
* to find each other and share information : they just need to agree
* on the variable name and the data it will point to .
*/
void **
find_rendezvous_variable(const char *varName)
{
static HTAB *rendezvousHash = NULL;
rendezvousHashEntry *hentry;
bool found;
/* Create a hashtable if we haven't already done so in this process */
if (rendezvousHash == NULL)
{
HASHCTL ctl;
ctl.keysize = NAMEDATALEN;
ctl.entrysize = sizeof (rendezvousHashEntry);
rendezvousHash = hash_create("Rendezvous variable hash" ,
16 ,
&ctl,
HASH_ELEM | HASH_STRINGS);
}
/* Find or create the hashtable entry for this varName */
hentry = (rendezvousHashEntry *) hash_search(rendezvousHash,
varName,
HASH_ENTER,
&found);
/* Initialize to NULL if first time */
if (!found)
hentry->varValue = NULL;
return &hentry->varValue;
}
/*
* Estimate the amount of space needed to serialize the list of libraries
* we have loaded .
*/
Size
EstimateLibraryStateSpace(void )
{
DynamicFileList *file_scanner;
Size size = 1 ;
for (file_scanner = file_list;
file_scanner != NULL;
file_scanner = file_scanner->next)
size = add_size(size, strlen(file_scanner->filename) + 1 );
return size;
}
/*
* Serialize the list of libraries we have loaded to a chunk of memory .
*/
void
SerializeLibraryState(Size maxsize, char *start_address)
{
DynamicFileList *file_scanner;
for (file_scanner = file_list;
file_scanner != NULL;
file_scanner = file_scanner->next)
{
Size len;
len = strlcpy(start_address, file_scanner->filename, maxsize) + 1 ;
Assert(len < maxsize);
maxsize -= len;
start_address += len;
}
start_address[0 ] = '\0' ;
}
/*
* Load every library the serializing backend had loaded .
*/
void
RestoreLibraryState(char *start_address)
{
while (*start_address != '\0' )
{
internal_load_library(start_address);
start_address += strlen(start_address) + 1 ;
}
}
Messung V0.5 in Prozent C=92 H=98 G=94
¤ Dauer der Verarbeitung: 0.22 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland