Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024, 2025 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : /*
14 : * (author) M. Kersten
15 : * An include file name is also used as library name
16 : */
17 : #include "monetdb_config.h"
18 : #include "mal_module.h"
19 : #include "mal_linker.h"
20 : #include "mal_function.h" /* for throw() */
21 : #include "mal_import.h" /* for slash_2_dir_sep() */
22 : #include "mal_private.h"
23 : #include "mal_internal.h"
24 :
25 : #include "mutils.h"
26 : #include <sys/types.h> /* opendir */
27 : #ifdef HAVE_DIRENT_H
28 : #include <dirent.h>
29 : #endif
30 : #ifdef HAVE_FCNTL_H
31 : #include <fcntl.h>
32 : #endif
33 : #include <unistd.h>
34 :
35 : #if defined(_MSC_VER) && _MSC_VER >= 1400
36 : #define open _open
37 : #define close _close
38 : #endif
39 :
40 : #define MAXMODULES 128
41 :
42 : typedef struct {
43 : str modname;
44 : str fullname;
45 : void *handle;
46 : } FileRecord;
47 :
48 : static FileRecord filesLoaded[MAXMODULES];
49 : static int maxfiles = MAXMODULES;
50 : static int lastfile = 0;
51 :
52 : #ifndef O_CLOEXEC
53 : #ifdef _O_NOINHERIT
54 : #define O_CLOEXEC _O_NOINHERIT /* Windows */
55 : #else
56 : #define O_CLOEXEC 0
57 : #endif
58 : #endif
59 :
60 : /*
61 : * returns 1 if the file exists
62 : */
63 : #ifndef F_OK
64 : #define F_OK 0
65 : #endif
66 : static inline int
67 693 : fileexists(const char *path)
68 : {
69 693 : return MT_access(path, F_OK) == 0;
70 : }
71 :
72 : /* Search for occurrence of the function in the library identified by the filename. */
73 : MALfcn
74 14 : getAddress(const char *modname, const char *fcnname)
75 : {
76 14 : MALfcn adr;
77 14 : int idx = 0;
78 14 : static int prev = -1;
79 :
80 14 : if ((adr = findFunctionImplementation(fcnname)) != NULL)
81 : return adr;
82 :
83 : /* First try the last module loaded */
84 3 : if (prev >= 0 && strcmp(filesLoaded[prev].modname, modname) == 0) { /* test if just pointer compare could work */
85 0 : adr = (MALfcn) dlsym(filesLoaded[prev].handle, fcnname);
86 0 : if (adr != NULL)
87 : return adr; /* found it */
88 : }
89 : /*
90 : * Search for occurrence of the function in any library already loaded.
91 : * This deals with the case that files are linked together to reduce
92 : * the loading time, while the signatures of the functions are still
93 : * obtained from the source-file MAL script.
94 : */
95 33 : for (idx = 0; idx < lastfile; idx++)
96 30 : if (idx != prev && /* skip already searched module */
97 30 : filesLoaded[idx].handle &&
98 24 : strcmp(filesLoaded[idx].modname, modname) == 0 &&
99 0 : (idx == 0 || filesLoaded[idx].handle != filesLoaded[0].handle)) {
100 0 : adr = (MALfcn) dlsym(filesLoaded[idx].handle, fcnname);
101 0 : if (adr != NULL) {
102 0 : prev = idx;
103 0 : return adr; /* found it */
104 : }
105 : }
106 :
107 3 : if (lastfile == 0) {
108 0 : char *msg = loadLibrary("monetdb5", 1);
109 0 : if (msg) {
110 0 : freeException(msg);
111 0 : return NULL;
112 : }
113 : }
114 :
115 : /* first should be monetdb5 */
116 3 : assert(strcmp(filesLoaded[0].modname, "monetdb5") == 0
117 : || strcmp(filesLoaded[0].modname, "embedded") == 0);
118 3 : adr = (MALfcn) dlsym(filesLoaded[0].handle, fcnname);
119 3 : if (adr != NULL) {
120 0 : prev = 0;
121 0 : return adr; /* found it */
122 : }
123 : return NULL;
124 : }
125 :
126 : /*
127 : * Module file loading
128 : * The default location to search for the module is in monet_mod_path
129 : * unless an absolute path is given.
130 : * Loading further relies on the Linux policy to search for the module
131 : * location in the following order: 1) the colon-separated list of
132 : * directories in the user's LD_LIBRARY_PATH, 2) the libraries specified
133 : * in /etc/ld.so.cache and 3) /usr/lib followed by /lib.
134 : * If the module contains a routine _init, then that code is executed
135 : * before the loader returns. Likewise the routine _fini is called just
136 : * before the module is unloaded.
137 : *
138 : * A module loading conflict emerges if a function is redefined.
139 : * A duplicate load is simply ignored by keeping track of modules
140 : * already loaded.
141 : */
142 :
143 : str
144 3519 : loadLibrary(const char *filename, int flag)
145 : {
146 3519 : int mode = RTLD_NOW | RTLD_GLOBAL;
147 3519 : char nme[FILENAME_MAX];
148 3519 : void *handle = NULL;
149 3519 : const char *s;
150 3519 : int idx;
151 3519 : const char *mod_path = GDKgetenv("monet_mod_path");
152 3519 : bool is_mod;
153 3519 : bool is_monetdb5 = strcmp(filename, "monetdb5") == 0;
154 :
155 3519 : is_mod = (!is_monetdb5 && strcmp(filename, "embedded") != 0);
156 :
157 3519 : if (lastfile == 0 && is_mod) { /* first load reference to local functions */
158 358 : str msg = loadLibrary("monetdb5", flag>=0?flag:0);
159 358 : if (msg != MAL_SUCCEED)
160 : return msg;
161 : }
162 : /* AIX requires RTLD_MEMBER to load a module that is a member of an
163 : * archive. */
164 : #ifdef RTLD_MEMBER
165 : mode |= RTLD_MEMBER;
166 : #endif
167 :
168 19407 : for (idx = 0; idx < lastfile; idx++)
169 15888 : if (filesLoaded[idx].modname &&
170 15888 : strcmp(filesLoaded[idx].modname, filename) == 0)
171 : /* already loaded */
172 : return MAL_SUCCEED;
173 :
174 : /* ignore any path given */
175 3519 : if ((s = strrchr(filename, DIR_SEP)) == NULL)
176 3519 : s = filename;
177 :
178 3519 : if (mod_path != NULL) {
179 3489 : while (*mod_path == PATH_SEP)
180 0 : mod_path++;
181 3489 : if (*mod_path == 0)
182 : mod_path = NULL;
183 : }
184 : if (mod_path == NULL) {
185 30 : int len;
186 :
187 30 : if (is_mod && flag < 0)
188 0 : len = snprintf(nme, FILENAME_MAX, ".%c%s_%s%s", DIR_SEP, SO_PREFIX, s, SO_EXT);
189 30 : else if (is_mod)
190 17 : len = snprintf(nme, FILENAME_MAX, "%s_%s%s", SO_PREFIX, s, SO_EXT);
191 : else
192 13 : len = snprintf(nme, FILENAME_MAX, "%s%s%s", SO_PREFIX, s, SO_EXT);
193 30 : if (len == -1 || len >= FILENAME_MAX)
194 0 : throw(LOADER, "loadLibrary",
195 : RUNTIME_LOAD_ERROR "Library filename path is too large");
196 :
197 : #ifdef __APPLE__
198 : handle = mdlopen(is_monetdb5 ? NULL : nme, RTLD_NOW | RTLD_GLOBAL);
199 : #else
200 48 : handle = dlopen(is_monetdb5 ? NULL : nme, RTLD_NOW | RTLD_GLOBAL);
201 : #endif
202 30 : if (!handle) {
203 18 : if (flag>0)
204 0 : throw(LOADER, "loadLibrary", RUNTIME_FILE_NOT_FOUND ":%s", s);
205 18 : TRC_INFO(MAL_LOADER, "Module %s not loaded\n", filename);
206 18 : return MAL_SUCCEED;
207 : }
208 12 : TRC_INFO_IF(MAL_LOADER) {
209 0 : if (is_monetdb5)
210 0 : TRC_INFO_ENDIF(MAL_LOADER, "Module %s loaded\n", filename);
211 : else
212 0 : TRC_INFO_ENDIF(MAL_LOADER, "Module %s loaded from %s\n",
213 : filename, nme);
214 : }
215 : }
216 :
217 3501 : while (!handle && *mod_path) {
218 : int len;
219 : const char *p;
220 :
221 300054 : for (p = mod_path; *p && *p != PATH_SEP; p++)
222 : ;
223 :
224 3489 : if (is_mod)
225 3143 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s%s",
226 3143 : (int) (p - mod_path), mod_path, DIR_SEP, SO_PREFIX,
227 : s, SO_EXT);
228 : else
229 346 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s%s%s",
230 346 : (int) (p - mod_path), mod_path, DIR_SEP, SO_PREFIX,
231 : s, SO_EXT);
232 3489 : if (len == -1 || len >= FILENAME_MAX)
233 0 : throw(LOADER, "loadLibrary",
234 : RUNTIME_LOAD_ERROR "Library filename path is too large");
235 3489 : handle = dlopen(nme, mode);
236 4182 : if (handle == NULL && fileexists(nme))
237 0 : throw(LOADER, "loadLibrary",
238 : RUNTIME_LOAD_ERROR
239 : " failed to open library %s (from within file '%s'): %s", s,
240 : nme, dlerror());
241 2796 : if (handle == NULL && strcmp(SO_EXT, ".so") != /* DISABLES CODE */ (0)) {
242 : /* try .so */
243 : if (is_mod)
244 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s.so",
245 : (int) (p - mod_path), mod_path, DIR_SEP,
246 : SO_PREFIX, s);
247 : else
248 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s%s.so",
249 : (int) (p - mod_path), mod_path, DIR_SEP,
250 : SO_PREFIX, s);
251 : if (len == -1 || len >= FILENAME_MAX)
252 : throw(LOADER, "loadLibrary",
253 : RUNTIME_LOAD_ERROR "Library filename path is too large");
254 : handle = dlopen(nme, mode);
255 : if (handle == NULL && fileexists(nme))
256 : throw(LOADER, "loadLibrary",
257 : RUNTIME_LOAD_ERROR
258 : " failed to open library %s (from within file '%s'): %s",
259 : s, nme, dlerror());
260 : }
261 : #ifdef __APPLE__
262 : if (handle == NULL && strcmp(SO_EXT, ".bundle") != 0) {
263 : /* try .bundle */
264 : if (is_mod)
265 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s.bundle",
266 : (int) (p - mod_path), mod_path, DIR_SEP,
267 : SO_PREFIX, s);
268 : else
269 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s%s.bundle",
270 : (int) (p - mod_path), mod_path, DIR_SEP,
271 : SO_PREFIX, s);
272 : if (len == -1 || len >= FILENAME_MAX)
273 : throw(LOADER, "loadLibrary",
274 : RUNTIME_LOAD_ERROR "Library filename path is too large");
275 : handle = dlopen(nme, mode);
276 : if (handle == NULL && fileexists(nme))
277 : throw(LOADER, "loadLibrary",
278 : RUNTIME_LOAD_ERROR
279 : " failed to open library %s (from within file '%s'): %s",
280 : s, nme, dlerror());
281 : }
282 : #endif
283 2796 : if (handle)
284 2796 : TRC_INFO(MAL_LOADER, "Module %s loaded from %s\n", filename, nme);
285 :
286 3489 : if (*p == 0 || handle != NULL)
287 : break;
288 0 : mod_path = p + 1;
289 : }
290 :
291 3501 : if (handle == NULL) {
292 : static const char *const optional[] = {
293 : #ifdef HAVE_CUDF
294 : "capi",
295 : #endif
296 : #ifdef HAVE_FITS
297 : "fits",
298 : #endif
299 : #ifdef HAVE_GEOM
300 : "geom",
301 : #endif
302 : #ifdef HAVE_NETCDF
303 : "netcdf",
304 : #endif
305 : #ifdef HAVE_LIBPY3
306 : "pyapi3",
307 : #endif
308 : #ifdef HAVE_LIBR
309 : "rapi",
310 : #endif
311 : #ifdef HAVE_SHP
312 : "shp",
313 : #endif
314 : NULL
315 : };
316 5544 : for (const char * const *p = optional; *p; p++) {
317 4851 : if (strcmp(filename, *p) == 0) {
318 0 : TRC_INFO(MAL_LOADER, "Optional module %s not loaded\n",
319 : filename);
320 0 : return MAL_SUCCEED;
321 : }
322 : }
323 693 : if (!is_monetdb5 && strcmp(filename, "sql") != 0)
324 1 : throw(LOADER, "loadLibrary",
325 : RUNTIME_LOAD_ERROR
326 : " could not locate library %s (from within file '%s'): %s", s,
327 : filename, dlerror());
328 : }
329 :
330 3500 : MT_lock_set(&mal_contextLock);
331 3500 : if (lastfile == maxfiles) {
332 0 : MT_lock_unset(&mal_contextLock);
333 0 : if (handle)
334 0 : dlclose(handle);
335 0 : throw(MAL, "mal.linker",
336 : "loadModule internal error, too many modules loaded");
337 : } else {
338 3500 : filesLoaded[lastfile].modname = GDKstrdup(filename);
339 3500 : if (filesLoaded[lastfile].modname == NULL) {
340 0 : MT_lock_unset(&mal_contextLock);
341 0 : if (handle)
342 0 : dlclose(handle);
343 0 : throw(LOADER, "loadLibrary",
344 : RUNTIME_LOAD_ERROR " could not allocate space");
345 : }
346 4192 : filesLoaded[lastfile].fullname = GDKstrdup(handle ? nme : "");
347 3500 : if (filesLoaded[lastfile].fullname == NULL) {
348 0 : GDKfree(filesLoaded[lastfile].modname);
349 0 : MT_lock_unset(&mal_contextLock);
350 0 : if (handle)
351 0 : dlclose(handle);
352 0 : throw(LOADER, "loadLibrary",
353 : RUNTIME_LOAD_ERROR " could not allocate space");
354 : }
355 3500 : filesLoaded[lastfile].handle = handle ? handle : filesLoaded[0].handle;
356 3500 : lastfile++;
357 : }
358 3500 : MT_lock_unset(&mal_contextLock);
359 :
360 3500 : return MAL_SUCCEED;
361 : }
362 :
363 : /*
364 : * For analysis of memory leaks we should cleanup the libraries before
365 : * we exit the server. This does not involve the libraries themselves,
366 : * because they may still be in use.
367 : */
368 : void
369 356 : mal_linker_reset(void)
370 : {
371 356 : int i;
372 :
373 356 : MT_lock_set(&mal_contextLock);
374 4201 : for (i = 0; i < lastfile; i++) {
375 3489 : if (filesLoaded[i].fullname) {
376 : /* dlclose(filesLoaded[i].handle); */
377 3489 : GDKfree(filesLoaded[i].modname);
378 3489 : GDKfree(filesLoaded[i].fullname);
379 : }
380 3489 : filesLoaded[i].modname = NULL;
381 3489 : filesLoaded[i].fullname = NULL;
382 : }
383 356 : lastfile = 0;
384 356 : MT_lock_unset(&mal_contextLock);
385 356 : }
386 :
387 : /*
388 : * Handling of Module Library Search Path
389 : * The plausible locations of the modules can be designated by
390 : * an environment variable.
391 : */
392 : static int
393 0 : cmpstr(const void *_p1, const void *_p2)
394 : {
395 0 : const char *p1 = *(char *const *) _p1;
396 0 : const char *p2 = *(char *const *) _p2;
397 0 : const char *f1 = strrchr(p1, (int) DIR_SEP);
398 0 : const char *f2 = strrchr(p2, (int) DIR_SEP);
399 0 : return strcmp(f1 ? f1 : p1, f2 ? f2 : p2);
400 : }
401 :
402 :
403 : #define MAXMULTISCRIPT 48
404 : char *
405 7 : locate_file(const char *basename, const char *ext, bit recurse)
406 : {
407 7 : const char *mod_path = GDKgetenv("monet_mod_path");
408 7 : char *fullname;
409 7 : size_t fullnamelen;
410 7 : size_t filelen = strlen(basename) + strlen(ext);
411 7 : str strs[MAXMULTISCRIPT]; /* hardwired limit */
412 7 : int lasts = 0;
413 :
414 7 : if (mod_path == NULL)
415 : return NULL;
416 :
417 7 : while (*mod_path == PATH_SEP)
418 0 : mod_path++;
419 7 : if (*mod_path == 0)
420 : return NULL;
421 7 : fullnamelen = 512;
422 7 : fullname = GDKmalloc(fullnamelen);
423 7 : if (fullname == NULL)
424 : return NULL;
425 7 : while (*mod_path) {
426 7 : size_t i;
427 7 : const char *p;
428 7 : int fd;
429 7 : DIR *rdir;
430 :
431 7 : if ((p = strchr(mod_path, PATH_SEP)) != NULL) {
432 0 : i = p - mod_path;
433 : } else {
434 7 : i = strlen(mod_path);
435 : }
436 7 : while (i + filelen + 2 > fullnamelen) {
437 0 : char *tmp;
438 0 : fullnamelen += 512;
439 0 : tmp = GDKrealloc(fullname, fullnamelen);
440 0 : if (tmp == NULL) {
441 0 : GDKfree(fullname);
442 0 : return NULL;
443 : }
444 : fullname = tmp;
445 : }
446 : /* we are now sure the directory name, file
447 : base name, extension, and separator fit
448 : into fullname, so we don't need to do any
449 : extra checks */
450 7 : strncpy(fullname, mod_path, i);
451 7 : fullname[i] = DIR_SEP;
452 7 : char *nameend = stpcpy(fullname + i + 1, basename);
453 : /* see if this is a directory, if so, recurse */
454 7 : if (recurse == 1 && (rdir = opendir(fullname)) != NULL) {
455 : struct dirent *e;
456 : /* list *ext, sort, return */
457 0 : while ((e = readdir(rdir)) != NULL) {
458 0 : if (strcmp(e->d_name, "..") == 0 || strcmp(e->d_name, ".") == 0)
459 0 : continue;
460 0 : if (strcmp(e->d_name + strlen(e->d_name) - strlen(ext), ext) == 0) {
461 0 : int len;
462 0 : strs[lasts] = GDKmalloc(strlen(fullname) + sizeof(DIR_SEP)
463 : + strlen(e->d_name) +
464 : sizeof(PATH_SEP) + 1);
465 0 : if (strs[lasts] == NULL) {
466 0 : while (lasts >= 0)
467 0 : GDKfree(strs[lasts--]);
468 0 : GDKfree(fullname);
469 0 : (void) closedir(rdir);
470 0 : return NULL;
471 : }
472 0 : len = sprintf(strs[lasts], "%s%c%s%c", fullname, DIR_SEP,
473 : e->d_name, PATH_SEP);
474 0 : if (len == -1 || len >= FILENAME_MAX) {
475 0 : while (lasts >= 0)
476 0 : GDKfree(strs[lasts--]);
477 0 : GDKfree(fullname);
478 0 : (void) closedir(rdir);
479 0 : return NULL;
480 : }
481 0 : lasts++;
482 : }
483 0 : if (lasts >= MAXMULTISCRIPT)
484 : break;
485 : }
486 0 : (void) closedir(rdir);
487 : } else {
488 7 : strcpy(nameend, ext);
489 7 : if ((fd = MT_open(fullname, O_RDONLY | O_CLOEXEC)) >= 0) {
490 6 : char *tmp;
491 6 : close(fd);
492 6 : tmp = GDKrealloc(fullname, strlen(fullname) + 1);
493 6 : if (tmp == NULL)
494 : return fullname;
495 6 : return tmp;
496 : }
497 : }
498 1 : if ((mod_path = p) == NULL)
499 : break;
500 0 : while (*mod_path == PATH_SEP)
501 0 : mod_path++;
502 : }
503 1 : if (lasts > 0) {
504 0 : size_t i = 0;
505 0 : int c;
506 0 : char *tmp;
507 : /* assure that an ordering such as 10_first, 20_second works */
508 0 : qsort(strs, lasts, sizeof(char *), cmpstr);
509 0 : for (c = 0; c < lasts; c++)
510 0 : i += strlen(strs[c]) + 1; /* PATH_SEP or \0 */
511 0 : tmp = GDKrealloc(fullname, i);
512 0 : if (tmp == NULL) {
513 0 : GDKfree(fullname);
514 0 : return NULL;
515 : }
516 0 : fullname = tmp;
517 : i = 0;
518 0 : for (c = 0; c < lasts; c++) {
519 0 : if (strstr(fullname, strs[c]) == NULL) {
520 0 : strcpy(fullname + i, strs[c]);
521 0 : i += strlen(strs[c]);
522 : }
523 0 : GDKfree(strs[c]);
524 : }
525 0 : fullname[i - 1] = '\0';
526 0 : return fullname;
527 : }
528 : /* not found */
529 1 : GDKfree(fullname);
530 1 : return NULL;
531 : }
532 :
533 : char *
534 1 : MSP_locate_script(const char *filename)
535 : {
536 1 : return locate_file(filename, MAL_EXT, 1);
537 : }
538 :
539 : char *
540 0 : MSP_locate_sqlscript(const char *filename, bit recurse)
541 : {
542 : /* no directory semantics (yet) */
543 0 : return locate_file(filename, SQL_EXT, recurse);
544 : }
545 :
546 : int
547 23822 : malLibraryEnabled(const char *name)
548 : {
549 23822 : if (strcmp(name, "pyapi3") == 0) {
550 356 : const char *val = GDKgetenv("embedded_py");
551 356 : return val && (strcmp(val, "3") == 0 ||
552 0 : strcasecmp(val, "true") == 0 ||
553 0 : strcasecmp(val, "yes") == 0);
554 23466 : } else if (strcmp(name, "rapi") == 0) {
555 352 : const char *val = GDKgetenv("embedded_r");
556 352 : return val && (strcasecmp(val, "true") == 0 ||
557 4 : strcasecmp(val, "yes") == 0);
558 23114 : } else if (strcmp(name, "capi") == 0) {
559 352 : const char *val = GDKgetenv("embedded_c");
560 352 : return val && (strcasecmp(val, "true") == 0 ||
561 2 : strcasecmp(val, "yes") == 0);
562 : }
563 : return true;
564 : }
565 :
566 : #define HOW_TO_ENABLE_ERROR(LANGUAGE, OPTION) \
567 : do { \
568 : if (malLibraryEnabled(name)) \
569 : return "Embedded " LANGUAGE " has not been installed. " \
570 : "Please install it first, then start server with " \
571 : "--set " OPTION; \
572 : return "Embedded " LANGUAGE " has not been enabled. " \
573 : "Start server with --set " OPTION; \
574 : } while (0)
575 :
576 : char *
577 22 : malLibraryHowToEnable(const char *name)
578 : {
579 22 : if (strcmp(name, "pyapi3") == 0) {
580 0 : HOW_TO_ENABLE_ERROR("Python 3", "embedded_py=3");
581 22 : } else if (strcmp(name, "rapi") == 0) {
582 0 : HOW_TO_ENABLE_ERROR("R", "embedded_r=true");
583 22 : } else if (strcmp(name, "capi") == 0) {
584 0 : HOW_TO_ENABLE_ERROR("C/C++", "embedded_c=true");
585 : }
586 : return "";
587 : }
|