PostgreSQL Source Code git master
dfmgr.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * dfmgr.c
4 * Dynamic function manager code.
5 *
6 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 *
10 * IDENTIFICATION
11 * src/backend/utils/fmgr/dfmgr.c
12 *
13 *-------------------------------------------------------------------------
14 */
15#include "postgres.h"
16
17#include <sys/stat.h>
18
19#ifndef WIN32
20#include <dlfcn.h>
21#endif /* !WIN32 */
22
23#include "fmgr.h"
24#include "lib/stringinfo.h"
25#include "miscadmin.h"
26#include "storage/fd.h"
27#include "storage/shmem.h"
28#include "utils/hsearch.h"
29
30
31/* signature for PostgreSQL-specific library init function */
32typedef void (*PG_init_t) (void);
33
34/* hashtable entry for rendezvous variables */
35typedef struct
36{
37 char varName[NAMEDATALEN]; /* hash key (must be first) */
38 void *varValue;
40
41/*
42 * List of dynamically loaded files (kept in malloc'd memory).
43 */
44
45typedef struct df_files
46{
47 struct df_files *next; /* List link */
48 dev_t device; /* Device file is on */
49#ifndef WIN32 /* ensures we never again depend on this under
50 * win32 */
51 ino_t inode; /* Inode number of file */
52#endif
53 void *handle; /* a handle for pg_dl* functions */
54 char filename[FLEXIBLE_ARRAY_MEMBER]; /* Full pathname of file */
56
59
60/* stat() call under Win32 returns an st_ino field, but it has no meaning */
61#ifndef WIN32
62#define SAME_INODE(A,B) ((A).st_ino == (B).inode && (A).st_dev == (B).device)
63#else
64#define SAME_INODE(A,B) false
65#endif
66
68
69static void *internal_load_library(const char *libname);
70static void incompatible_module_error(const char *libname,
71 const Pg_magic_struct *module_magic_data) pg_attribute_noreturn();
72static char *expand_dynamic_library_name(const char *name);
73static void check_restricted_library_name(const char *name);
74static char *substitute_libpath_macro(const char *name);
75static char *find_in_dynamic_libpath(const char *basename);
76
77/* Magic structure that module needs to match to be accepted */
79
80
81/*
82 * Load the specified dynamic-link library file, and look for a function
83 * named funcname in it.
84 *
85 * If the function is not found, we raise an error if signalNotFound is true,
86 * else return NULL. Note that errors in loading the library
87 * will provoke ereport() regardless of signalNotFound.
88 *
89 * If filehandle is not NULL, then *filehandle will be set to a handle
90 * identifying the library file. The filehandle can be used with
91 * lookup_external_function to lookup additional functions in the same file
92 * at less cost than repeating load_external_function.
93 */
94void *
95load_external_function(const char *filename, const char *funcname,
96 bool signalNotFound, void **filehandle)
97{
98 char *fullname;
99 void *lib_handle;
100 void *retval;
101
102 /* Expand the possibly-abbreviated filename to an exact path name */
104
105 /* Load the shared library, unless we already did */
106 lib_handle = internal_load_library(fullname);
107
108 /* Return handle if caller wants it */
109 if (filehandle)
110 *filehandle = lib_handle;
111
112 /* Look up the function within the library. */
113 retval = dlsym(lib_handle, funcname);
114
115 if (retval == NULL && signalNotFound)
117 (errcode(ERRCODE_UNDEFINED_FUNCTION),
118 errmsg("could not find function \"%s\" in file \"%s\"",
119 funcname, fullname)));
120
121 pfree(fullname);
122 return retval;
123}
124
125/*
126 * This function loads a shlib file without looking up any particular
127 * function in it. If the same shlib has previously been loaded,
128 * we do not load it again.
129 *
130 * When 'restricted' is true, only libraries in the presumed-secure
131 * directory $libdir/plugins may be referenced.
132 */
133void
134load_file(const char *filename, bool restricted)
135{
136 char *fullname;
137
138 /* Apply security restriction if requested */
139 if (restricted)
141
142 /* Expand the possibly-abbreviated filename to an exact path name */
144
145 /* Load the shared library, unless we already did */
146 (void) internal_load_library(fullname);
147
148 pfree(fullname);
149}
150
151/*
152 * Lookup a function whose library file is already loaded.
153 * Return NULL if not found.
154 */
155void *
156lookup_external_function(void *filehandle, const char *funcname)
157{
158 return dlsym(filehandle, funcname);
159}
160
161
162/*
163 * Load the specified dynamic-link library file, unless it already is
164 * loaded. Return the pg_dl* handle for the file.
165 *
166 * Note: libname is expected to be an exact name for the library file.
167 *
168 * NB: There is presently no way to unload a dynamically loaded file. We might
169 * add one someday if we can convince ourselves we have safe protocols for un-
170 * hooking from hook function pointers, releasing custom GUC variables, and
171 * perhaps other things that are definitely unsafe currently.
172 */
173static void *
174internal_load_library(const char *libname)
175{
176 DynamicFileList *file_scanner;
177 PGModuleMagicFunction magic_func;
178 char *load_error;
179 struct stat stat_buf;
180 PG_init_t PG_init;
181
182 /*
183 * Scan the list of loaded FILES to see if the file has been loaded.
184 */
185 for (file_scanner = file_list;
186 file_scanner != NULL &&
187 strcmp(libname, file_scanner->filename) != 0;
188 file_scanner = file_scanner->next)
189 ;
190
191 if (file_scanner == NULL)
192 {
193 /*
194 * Check for same files - different paths (ie, symlink or link)
195 */
196 if (stat(libname, &stat_buf) == -1)
199 errmsg("could not access file \"%s\": %m",
200 libname)));
201
202 for (file_scanner = file_list;
203 file_scanner != NULL &&
204 !SAME_INODE(stat_buf, *file_scanner);
205 file_scanner = file_scanner->next)
206 ;
207 }
208
209 if (file_scanner == NULL)
210 {
211 /*
212 * File not loaded yet.
213 */
214 file_scanner = (DynamicFileList *)
215 malloc(offsetof(DynamicFileList, filename) + strlen(libname) + 1);
216 if (file_scanner == NULL)
218 (errcode(ERRCODE_OUT_OF_MEMORY),
219 errmsg("out of memory")));
220
221 MemSet(file_scanner, 0, offsetof(DynamicFileList, filename));
222 strcpy(file_scanner->filename, libname);
223 file_scanner->device = stat_buf.st_dev;
224#ifndef WIN32
225 file_scanner->inode = stat_buf.st_ino;
226#endif
227 file_scanner->next = NULL;
228
229 file_scanner->handle = dlopen(file_scanner->filename, RTLD_NOW | RTLD_GLOBAL);
230 if (file_scanner->handle == NULL)
231 {
232 load_error = dlerror();
233 free(file_scanner);
234 /* errcode_for_file_access might not be appropriate here? */
237 errmsg("could not load library \"%s\": %s",
238 libname, load_error)));
239 }
240
241 /* Check the magic function to determine compatibility */
242 magic_func = (PGModuleMagicFunction)
244 if (magic_func)
245 {
246 const Pg_magic_struct *magic_data_ptr = (*magic_func) ();
247
248 if (magic_data_ptr->len != magic_data.len ||
249 memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0)
250 {
251 /* copy data block before unlinking library */
252 Pg_magic_struct module_magic_data = *magic_data_ptr;
253
254 /* try to close library */
255 dlclose(file_scanner->handle);
256 free(file_scanner);
257
258 /* issue suitable complaint */
259 incompatible_module_error(libname, &module_magic_data);
260 }
261 }
262 else
263 {
264 /* try to close library */
265 dlclose(file_scanner->handle);
266 free(file_scanner);
267 /* complain */
269 (errmsg("incompatible library \"%s\": missing magic block",
270 libname),
271 errhint("Extension libraries are required to use the PG_MODULE_MAGIC macro.")));
272 }
273
274 /*
275 * If the library has a _PG_init() function, call it.
276 */
277 PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init");
278 if (PG_init)
279 (*PG_init) ();
280
281 /* OK to link it into list */
282 if (file_list == NULL)
283 file_list = file_scanner;
284 else
285 file_tail->next = file_scanner;
286 file_tail = file_scanner;
287 }
288
289 return file_scanner->handle;
290}
291
292/*
293 * Report a suitable error for an incompatible magic block.
294 */
295static void
296incompatible_module_error(const char *libname,
297 const Pg_magic_struct *module_magic_data)
298{
299 StringInfoData details;
300
301 /*
302 * If the version doesn't match, just report that, because the rest of the
303 * block might not even have the fields we expect.
304 */
305 if (magic_data.version != module_magic_data->version)
306 {
307 char library_version[32];
308
309 if (module_magic_data->version >= 1000)
310 snprintf(library_version, sizeof(library_version), "%d",
311 module_magic_data->version / 100);
312 else
313 snprintf(library_version, sizeof(library_version), "%d.%d",
314 module_magic_data->version / 100,
315 module_magic_data->version % 100);
317 (errmsg("incompatible library \"%s\": version mismatch",
318 libname),
319 errdetail("Server is version %d, library is version %s.",
320 magic_data.version / 100, library_version)));
321 }
322
323 /*
324 * Similarly, if the ABI extra field doesn't match, error out. Other
325 * fields below might also mismatch, but that isn't useful information if
326 * you're using the wrong product altogether.
327 */
328 if (strcmp(module_magic_data->abi_extra, magic_data.abi_extra) != 0)
329 {
331 (errmsg("incompatible library \"%s\": ABI mismatch",
332 libname),
333 errdetail("Server has ABI \"%s\", library has \"%s\".",
335 module_magic_data->abi_extra)));
336 }
337
338 /*
339 * Otherwise, spell out which fields don't agree.
340 *
341 * XXX this code has to be adjusted any time the set of fields in a magic
342 * block change!
343 */
344 initStringInfo(&details);
345
346 if (module_magic_data->funcmaxargs != magic_data.funcmaxargs)
347 {
348 if (details.len)
349 appendStringInfoChar(&details, '\n');
350 appendStringInfo(&details,
351 /* translator: %s is a variable name and %d its values */
352 _("Server has %s = %d, library has %d."),
353 "FUNC_MAX_ARGS", magic_data.funcmaxargs,
354 module_magic_data->funcmaxargs);
355 }
356 if (module_magic_data->indexmaxkeys != magic_data.indexmaxkeys)
357 {
358 if (details.len)
359 appendStringInfoChar(&details, '\n');
360 appendStringInfo(&details,
361 /* translator: %s is a variable name and %d its values */
362 _("Server has %s = %d, library has %d."),
363 "INDEX_MAX_KEYS", magic_data.indexmaxkeys,
364 module_magic_data->indexmaxkeys);
365 }
366 if (module_magic_data->namedatalen != magic_data.namedatalen)
367 {
368 if (details.len)
369 appendStringInfoChar(&details, '\n');
370 appendStringInfo(&details,
371 /* translator: %s is a variable name and %d its values */
372 _("Server has %s = %d, library has %d."),
373 "NAMEDATALEN", magic_data.namedatalen,
374 module_magic_data->namedatalen);
375 }
376 if (module_magic_data->float8byval != magic_data.float8byval)
377 {
378 if (details.len)
379 appendStringInfoChar(&details, '\n');
380 appendStringInfo(&details,
381 /* translator: %s is a variable name and %d its values */
382 _("Server has %s = %s, library has %s."),
383 "FLOAT8PASSBYVAL", magic_data.float8byval ? "true" : "false",
384 module_magic_data->float8byval ? "true" : "false");
385 }
386
387 if (details.len == 0)
388 appendStringInfoString(&details,
389 _("Magic block has unexpected length or padding difference."));
390
392 (errmsg("incompatible library \"%s\": magic block mismatch",
393 libname),
394 errdetail_internal("%s", details.data)));
395}
396
397
398/*
399 * If name contains a slash, check if the file exists, if so return
400 * the name. Else (no slash) try to expand using search path (see
401 * find_in_dynamic_libpath below); if that works, return the fully
402 * expanded file name. If the previous failed, append DLSUFFIX and
403 * try again. If all fails, just return the original name.
404 *
405 * The result will always be freshly palloc'd.
406 */
407static char *
409{
410 bool have_slash;
411 char *new;
412 char *full;
413
414 Assert(name);
415
416 have_slash = (first_dir_separator(name) != NULL);
417
418 if (!have_slash)
419 {
421 if (full)
422 return full;
423 }
424 else
425 {
427 if (pg_file_exists(full))
428 return full;
429 pfree(full);
430 }
431
432 new = psprintf("%s%s", name, DLSUFFIX);
433
434 if (!have_slash)
435 {
436 full = find_in_dynamic_libpath(new);
437 pfree(new);
438 if (full)
439 return full;
440 }
441 else
442 {
443 full = substitute_libpath_macro(new);
444 pfree(new);
445 if (pg_file_exists(full))
446 return full;
447 pfree(full);
448 }
449
450 /*
451 * If we can't find the file, just return the string as-is. The ensuing
452 * load attempt will fail and report a suitable message.
453 */
454 return pstrdup(name);
455}
456
457/*
458 * Check a restricted library name. It must begin with "$libdir/plugins/"
459 * and there must not be any directory separators after that (this is
460 * sufficient to prevent ".." style attacks).
461 */
462static void
464{
465 if (strncmp(name, "$libdir/plugins/", 16) != 0 ||
466 first_dir_separator(name + 16) != NULL)
468 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
469 errmsg("access to library \"%s\" is not allowed",
470 name)));
471}
472
473/*
474 * Substitute for any macros appearing in the given string.
475 * Result is always freshly palloc'd.
476 */
477static char *
479{
480 const char *sep_ptr;
481
482 Assert(name != NULL);
483
484 /* Currently, we only recognize $libdir at the start of the string */
485 if (name[0] != '$')
486 return pstrdup(name);
487
488 if ((sep_ptr = first_dir_separator(name)) == NULL)
489 sep_ptr = name + strlen(name);
490
491 if (strlen("$libdir") != sep_ptr - name ||
492 strncmp(name, "$libdir", strlen("$libdir")) != 0)
494 (errcode(ERRCODE_INVALID_NAME),
495 errmsg("invalid macro name in dynamic library path: %s",
496 name)));
497
498 return psprintf("%s%s", pkglib_path, sep_ptr);
499}
500
501
502/*
503 * Search for a file called 'basename' in the colon-separated search
504 * path Dynamic_library_path. If the file is found, the full file name
505 * is returned in freshly palloc'd memory. If the file is not found,
506 * return NULL.
507 */
508static char *
509find_in_dynamic_libpath(const char *basename)
510{
511 const char *p;
512 size_t baselen;
513
514 Assert(basename != NULL);
515 Assert(first_dir_separator(basename) == NULL);
517
519 if (strlen(p) == 0)
520 return NULL;
521
522 baselen = strlen(basename);
523
524 for (;;)
525 {
526 size_t len;
527 char *piece;
528 char *mangled;
529 char *full;
530
531 piece = first_path_var_separator(p);
532 if (piece == p)
534 (errcode(ERRCODE_INVALID_NAME),
535 errmsg("zero-length component in parameter \"dynamic_library_path\"")));
536
537 if (piece == NULL)
538 len = strlen(p);
539 else
540 len = piece - p;
541
542 piece = palloc(len + 1);
543 strlcpy(piece, p, len + 1);
544
545 mangled = substitute_libpath_macro(piece);
546 pfree(piece);
547
548 canonicalize_path(mangled);
549
550 /* only absolute paths */
551 if (!is_absolute_path(mangled))
553 (errcode(ERRCODE_INVALID_NAME),
554 errmsg("component in parameter \"dynamic_library_path\" is not an absolute path")));
555
556 full = palloc(strlen(mangled) + 1 + baselen + 1);
557 sprintf(full, "%s/%s", mangled, basename);
558 pfree(mangled);
559
560 elog(DEBUG3, "find_in_dynamic_libpath: trying \"%s\"", full);
561
562 if (pg_file_exists(full))
563 return full;
564
565 pfree(full);
566
567 if (p[len] == '\0')
568 break;
569 else
570 p += len + 1;
571 }
572
573 return NULL;
574}
575
576
577/*
578 * Find (or create) a rendezvous variable that one dynamically
579 * loaded library can use to meet up with another.
580 *
581 * On the first call of this function for a particular varName,
582 * a "rendezvous variable" is created with the given name.
583 * The value of the variable is a void pointer (initially set to NULL).
584 * Subsequent calls with the same varName just return the address of
585 * the existing variable. Once created, a rendezvous variable lasts
586 * for the life of the process.
587 *
588 * Dynamically loaded libraries can use rendezvous variables
589 * to find each other and share information: they just need to agree
590 * on the variable name and the data it will point to.
591 */
592void **
593find_rendezvous_variable(const char *varName)
594{
595 static HTAB *rendezvousHash = NULL;
596
597 rendezvousHashEntry *hentry;
598 bool found;
599
600 /* Create a hashtable if we haven't already done so in this process */
601 if (rendezvousHash == NULL)
602 {
603 HASHCTL ctl;
604
605 ctl.keysize = NAMEDATALEN;
606 ctl.entrysize = sizeof(rendezvousHashEntry);
607 rendezvousHash = hash_create("Rendezvous variable hash",
608 16,
609 &ctl,
611 }
612
613 /* Find or create the hashtable entry for this varName */
614 hentry = (rendezvousHashEntry *) hash_search(rendezvousHash,
615 varName,
617 &found);
618
619 /* Initialize to NULL if first time */
620 if (!found)
621 hentry->varValue = NULL;
622
623 return &hentry->varValue;
624}
625
626/*
627 * Estimate the amount of space needed to serialize the list of libraries
628 * we have loaded.
629 */
630Size
632{
633 DynamicFileList *file_scanner;
634 Size size = 1;
635
636 for (file_scanner = file_list;
637 file_scanner != NULL;
638 file_scanner = file_scanner->next)
639 size = add_size(size, strlen(file_scanner->filename) + 1);
640
641 return size;
642}
643
644/*
645 * Serialize the list of libraries we have loaded to a chunk of memory.
646 */
647void
648SerializeLibraryState(Size maxsize, char *start_address)
649{
650 DynamicFileList *file_scanner;
651
652 for (file_scanner = file_list;
653 file_scanner != NULL;
654 file_scanner = file_scanner->next)
655 {
656 Size len;
657
658 len = strlcpy(start_address, file_scanner->filename, maxsize) + 1;
659 Assert(len < maxsize);
660 maxsize -= len;
661 start_address += len;
662 }
663 start_address[0] = '\0';
664}
665
666/*
667 * Load every library the serializing backend had loaded.
668 */
669void
670RestoreLibraryState(char *start_address)
671{
672 while (*start_address != '\0')
673 {
674 internal_load_library(start_address);
675 start_address += strlen(start_address) + 1;
676 }
677}
#define Assert(condition)
Definition: c.h:815
#define pg_attribute_noreturn()
Definition: c.h:239
#define FLEXIBLE_ARRAY_MEMBER
Definition: c.h:420
#define MemSet(start, val, len)
Definition: c.h:977
size_t Size
Definition: c.h:562
static char * expand_dynamic_library_name(const char *name)
Definition: dfmgr.c:408
static DynamicFileList * file_tail
Definition: dfmgr.c:58
static char * find_in_dynamic_libpath(const char *basename)
Definition: dfmgr.c:509
void RestoreLibraryState(char *start_address)
Definition: dfmgr.c:670
char * Dynamic_library_path
Definition: dfmgr.c:67
static char * substitute_libpath_macro(const char *name)
Definition: dfmgr.c:478
static void incompatible_module_error(const char *libname, const Pg_magic_struct *module_magic_data) pg_attribute_noreturn()
Definition: dfmgr.c:296
static void * internal_load_library(const char *libname)
Definition: dfmgr.c:174
void SerializeLibraryState(Size maxsize, char *start_address)
Definition: dfmgr.c:648
void load_file(const char *filename, bool restricted)
Definition: dfmgr.c:134
void ** find_rendezvous_variable(const char *varName)
Definition: dfmgr.c:593
static const Pg_magic_struct magic_data
Definition: dfmgr.c:78
Size EstimateLibraryStateSpace(void)
Definition: dfmgr.c:631
void * lookup_external_function(void *filehandle, const char *funcname)
Definition: dfmgr.c:156
static DynamicFileList * file_list
Definition: dfmgr.c:57
void(* PG_init_t)(void)
Definition: dfmgr.c:32
void * load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle)
Definition: dfmgr.c:95
struct df_files DynamicFileList
static void check_restricted_library_name(const char *name)
Definition: dfmgr.c:463
#define SAME_INODE(A, B)
Definition: dfmgr.c:62
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
Definition: dynahash.c:955
HTAB * hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags)
Definition: dynahash.c:352
int errdetail_internal(const char *fmt,...)
Definition: elog.c:1230
int errcode_for_file_access(void)
Definition: elog.c:876
int errdetail(const char *fmt,...)
Definition: elog.c:1203
int errhint(const char *fmt,...)
Definition: elog.c:1317
int errcode(int sqlerrcode)
Definition: elog.c:853
int errmsg(const char *fmt,...)
Definition: elog.c:1070
#define _(x)
Definition: elog.c:90
#define DEBUG3
Definition: elog.h:28
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
#define ereport(elevel,...)
Definition: elog.h:149
bool pg_file_exists(const char *name)
Definition: fd.c:502
#define PG_MAGIC_FUNCTION_NAME_STRING
Definition: fmgr.h:496
const Pg_magic_struct *(* PGModuleMagicFunction)(void)
Definition: fmgr.h:493
#define PG_MODULE_MAGIC_DATA
Definition: fmgr.h:475
char pkglib_path[MAXPGPATH]
Definition: globals.c:81
#define free(a)
Definition: header.h:65
#define malloc(a)
Definition: header.h:50
#define HASH_STRINGS
Definition: hsearch.h:96
@ HASH_ENTER
Definition: hsearch.h:114
#define HASH_ELEM
Definition: hsearch.h:95
#define funcname
Definition: indent_codes.h:69
char * pstrdup(const char *in)
Definition: mcxt.c:1696
void pfree(void *pointer)
Definition: mcxt.c:1521
void * palloc(Size size)
Definition: mcxt.c:1317
#define NAMEDATALEN
const void size_t len
static char * filename
Definition: pg_dumpall.c:119
#define is_absolute_path(filename)
Definition: port.h:104
#define sprintf
Definition: port.h:241
char * first_path_var_separator(const char *pathlist)
Definition: path.c:127
void canonicalize_path(char *path)
Definition: path.c:337
#define snprintf
Definition: port.h:239
char * first_dir_separator(const char *filename)
Definition: path.c:110
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:45
char * psprintf(const char *fmt,...)
Definition: psprintf.c:43
tree ctl
Definition: radixtree.h:1838
Size add_size(Size s1, Size s2)
Definition: shmem.c:488
static pg_noinline void Size size
Definition: slab.c:607
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition: stringinfo.c:145
void appendStringInfoString(StringInfo str, const char *s)
Definition: stringinfo.c:230
void appendStringInfoChar(StringInfo str, char ch)
Definition: stringinfo.c:242
void initStringInfo(StringInfo str)
Definition: stringinfo.c:97
Definition: dynahash.c:220
char abi_extra[32]
Definition: fmgr.h:471
int float8byval
Definition: fmgr.h:470
int funcmaxargs
Definition: fmgr.h:467
int indexmaxkeys
Definition: fmgr.h:468
int namedatalen
Definition: fmgr.h:469
int version
Definition: fmgr.h:466
Definition: dfmgr.c:46
dev_t device
Definition: dfmgr.c:48
ino_t inode
Definition: dfmgr.c:51
void * handle
Definition: dfmgr.c:53
struct df_files * next
Definition: dfmgr.c:47
char filename[FLEXIBLE_ARRAY_MEMBER]
Definition: dfmgr.c:54
void * varValue
Definition: dfmgr.c:38
_dev_t st_dev
Definition: win32_port.h:256
_ino_t st_ino
Definition: win32_port.h:257
const char * name
void * dlopen(const char *file, int mode)
Definition: win32dlopen.c:76
#define stat
Definition: win32_port.h:274
char * dlerror(void)
Definition: win32dlopen.c:40
void * dlsym(void *handle, const char *symbol)
Definition: win32dlopen.c:61
#define RTLD_NOW
Definition: win32_port.h:533
#define RTLD_GLOBAL
Definition: win32_port.h:534
int dlclose(void *handle)
Definition: win32dlopen.c:49