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 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 671 : fileexists(const char *path)
68 : {
69 671 : 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 27 : for (idx = 0; idx < lastfile; idx++)
96 24 : if (idx != prev && /* skip already searched module */
97 24 : filesLoaded[idx].handle &&
98 18 : 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 2647 : loadLibrary(const char *filename, int flag)
145 : {
146 2647 : int mode = RTLD_NOW | RTLD_GLOBAL;
147 2647 : char nme[FILENAME_MAX];
148 2647 : void *handle = NULL;
149 2647 : const char *s;
150 2647 : int idx;
151 2647 : const char *mod_path = GDKgetenv("monet_mod_path");
152 2647 : bool is_mod;
153 2647 : bool is_monetdb5 = strcmp(filename, "monetdb5") == 0;
154 :
155 2647 : is_mod = (!is_monetdb5 && strcmp(filename, "embedded") != 0);
156 :
157 2647 : if (lastfile == 0 && is_mod) { /* first load reference to local functions */
158 336 : str msg = loadLibrary("monetdb5", flag);
159 336 : 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 11961 : for (idx = 0; idx < lastfile; idx++)
169 9314 : if (filesLoaded[idx].modname &&
170 9314 : strcmp(filesLoaded[idx].modname, filename) == 0)
171 : /* already loaded */
172 : return MAL_SUCCEED;
173 :
174 : /* ignore any path given */
175 2647 : if ((s = strrchr(filename, DIR_SEP)) == NULL)
176 2647 : s = filename;
177 :
178 2647 : if (mod_path != NULL) {
179 2641 : while (*mod_path == PATH_SEP)
180 0 : mod_path++;
181 2641 : if (*mod_path == 0)
182 : mod_path = NULL;
183 : }
184 : if (mod_path == NULL) {
185 6 : int len;
186 :
187 6 : if (is_mod)
188 4 : len = snprintf(nme, FILENAME_MAX, "%s_%s%s", SO_PREFIX, s, SO_EXT);
189 : else
190 2 : len = snprintf(nme, FILENAME_MAX, "%s%s%s", SO_PREFIX, s, SO_EXT);
191 6 : if (len == -1 || len >= FILENAME_MAX)
192 0 : throw(LOADER, "loadLibrary",
193 : RUNTIME_LOAD_ERROR "Library filename path is too large");
194 :
195 : #ifdef __APPLE__
196 : handle = mdlopen(is_monetdb5 ? NULL : nme, RTLD_NOW | RTLD_GLOBAL);
197 : #else
198 11 : handle = dlopen(is_monetdb5 ? NULL : nme, RTLD_NOW | RTLD_GLOBAL);
199 : #endif
200 6 : if (!handle) {
201 5 : if (flag)
202 0 : throw(LOADER, "loadLibrary", RUNTIME_FILE_NOT_FOUND ":%s", s);
203 : return MAL_SUCCEED;
204 : }
205 : }
206 :
207 2642 : while (!handle && *mod_path) {
208 : int len;
209 : const char *p;
210 :
211 205998 : for (p = mod_path; *p && *p != PATH_SEP; p++)
212 : ;
213 :
214 2641 : if (is_mod)
215 2306 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s%s",
216 2306 : (int) (p - mod_path), mod_path, DIR_SEP, SO_PREFIX,
217 : s, SO_EXT);
218 : else
219 335 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s%s%s",
220 335 : (int) (p - mod_path), mod_path, DIR_SEP, SO_PREFIX,
221 : s, SO_EXT);
222 2641 : if (len == -1 || len >= FILENAME_MAX)
223 0 : throw(LOADER, "loadLibrary",
224 : RUNTIME_LOAD_ERROR "Library filename path is too large");
225 2641 : handle = dlopen(nme, mode);
226 3312 : if (handle == NULL && fileexists(nme))
227 0 : throw(LOADER, "loadLibrary",
228 : RUNTIME_LOAD_ERROR
229 : " failed to open library %s (from within file '%s'): %s", s,
230 : nme, dlerror());
231 2641 : if (handle == NULL && strcmp(SO_EXT, ".so") != /* DISABLES CODE */ (0)) {
232 : /* try .so */
233 : if (is_mod)
234 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s.so",
235 : (int) (p - mod_path), mod_path, DIR_SEP,
236 : SO_PREFIX, s);
237 : else
238 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s%s.so",
239 : (int) (p - mod_path), mod_path, DIR_SEP,
240 : SO_PREFIX, s);
241 : if (len == -1 || len >= FILENAME_MAX)
242 : throw(LOADER, "loadLibrary",
243 : RUNTIME_LOAD_ERROR "Library filename path is too large");
244 : handle = dlopen(nme, mode);
245 : if (handle == NULL && fileexists(nme))
246 : throw(LOADER, "loadLibrary",
247 : RUNTIME_LOAD_ERROR
248 : " failed to open library %s (from within file '%s'): %s",
249 : s, nme, dlerror());
250 : }
251 : #ifdef __APPLE__
252 : if (handle == NULL && strcmp(SO_EXT, ".bundle") != 0) {
253 : /* try .bundle */
254 : if (is_mod)
255 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s.bundle",
256 : (int) (p - mod_path), mod_path, DIR_SEP,
257 : SO_PREFIX, s);
258 : else
259 : len = snprintf(nme, FILENAME_MAX, "%.*s%c%s%s.bundle",
260 : (int) (p - mod_path), mod_path, DIR_SEP,
261 : SO_PREFIX, s);
262 : if (len == -1 || len >= FILENAME_MAX)
263 : throw(LOADER, "loadLibrary",
264 : RUNTIME_LOAD_ERROR "Library filename path is too large");
265 : handle = dlopen(nme, mode);
266 : if (handle == NULL && fileexists(nme))
267 : throw(LOADER, "loadLibrary",
268 : RUNTIME_LOAD_ERROR
269 : " failed to open library %s (from within file '%s'): %s",
270 : s, nme, dlerror());
271 : }
272 : #endif
273 :
274 2641 : if (*p == 0 || handle != NULL)
275 : break;
276 0 : mod_path = p + 1;
277 : }
278 :
279 2642 : if (handle == NULL) {
280 671 : if (!is_monetdb5
281 336 : && strcmp(filename, "sql") != 0
282 1 : && strcmp(filename, "generator") != 0
283 : #ifdef HAVE_GEOM
284 1 : && strcmp(filename, "geom") != 0
285 : #endif
286 : #ifdef HAVE_LIBR
287 1 : && strcmp(filename, "rapi") != 0
288 : #endif
289 : #ifdef HAVE_LIBPY3
290 1 : && strcmp(filename, "pyapi3") != 0
291 : #endif
292 : #ifdef HAVE_CUDF
293 1 : && strcmp(filename, "capi") != 0
294 : #endif
295 : #ifdef HAVE_FITS
296 1 : && strcmp(filename, "fits") != 0
297 : #endif
298 : #ifdef HAVE_NETCDF
299 1 : && strcmp(filename, "netcdf") != 0
300 : #endif
301 : #ifdef HAVE_SHP
302 1 : && strcmp(filename, "shp") != 0
303 : #endif
304 : )
305 1 : throw(LOADER, "loadLibrary",
306 : RUNTIME_LOAD_ERROR
307 : " could not locate library %s (from within file '%s'): %s", s,
308 : filename, dlerror());
309 : }
310 :
311 2641 : MT_lock_set(&mal_contextLock);
312 2641 : if (lastfile == maxfiles) {
313 0 : MT_lock_unset(&mal_contextLock);
314 0 : if (handle)
315 0 : dlclose(handle);
316 0 : throw(MAL, "mal.linker",
317 : "loadModule internal error, too many modules loaded");
318 : } else {
319 2641 : filesLoaded[lastfile].modname = GDKstrdup(filename);
320 2641 : if (filesLoaded[lastfile].modname == NULL) {
321 0 : MT_lock_unset(&mal_contextLock);
322 0 : if (handle)
323 0 : dlclose(handle);
324 0 : throw(LOADER, "loadLibrary",
325 : RUNTIME_LOAD_ERROR " could not allocate space");
326 : }
327 3311 : filesLoaded[lastfile].fullname = GDKstrdup(handle ? nme : "");
328 2641 : if (filesLoaded[lastfile].fullname == NULL) {
329 0 : GDKfree(filesLoaded[lastfile].modname);
330 0 : MT_lock_unset(&mal_contextLock);
331 0 : if (handle)
332 0 : dlclose(handle);
333 0 : throw(LOADER, "loadLibrary",
334 : RUNTIME_LOAD_ERROR " could not allocate space");
335 : }
336 2641 : filesLoaded[lastfile].handle = handle ? handle : filesLoaded[0].handle;
337 2641 : lastfile++;
338 : }
339 2641 : MT_lock_unset(&mal_contextLock);
340 :
341 2641 : return MAL_SUCCEED;
342 : }
343 :
344 : /*
345 : * For analysis of memory leaks we should cleanup the libraries before
346 : * we exit the server. This does not involve the libraries themselves,
347 : * because they may still be in use.
348 : */
349 : void
350 334 : mal_linker_reset(void)
351 : {
352 334 : int i;
353 :
354 334 : MT_lock_set(&mal_contextLock);
355 3299 : for (i = 0; i < lastfile; i++) {
356 2631 : if (filesLoaded[i].fullname) {
357 : /* dlclose(filesLoaded[i].handle); */
358 2631 : GDKfree(filesLoaded[i].modname);
359 2631 : GDKfree(filesLoaded[i].fullname);
360 : }
361 2631 : filesLoaded[i].modname = NULL;
362 2631 : filesLoaded[i].fullname = NULL;
363 : }
364 334 : lastfile = 0;
365 334 : MT_lock_unset(&mal_contextLock);
366 334 : }
367 :
368 : /*
369 : * Handling of Module Library Search Path
370 : * The plausible locations of the modules can be designated by
371 : * an environment variable.
372 : */
373 : static int
374 0 : cmpstr(const void *_p1, const void *_p2)
375 : {
376 0 : const char *p1 = *(char *const *) _p1;
377 0 : const char *p2 = *(char *const *) _p2;
378 0 : const char *f1 = strrchr(p1, (int) DIR_SEP);
379 0 : const char *f2 = strrchr(p2, (int) DIR_SEP);
380 0 : return strcmp(f1 ? f1 : p1, f2 ? f2 : p2);
381 : }
382 :
383 :
384 : #define MAXMULTISCRIPT 48
385 : char *
386 7 : locate_file(const char *basename, const char *ext, bit recurse)
387 : {
388 7 : const char *mod_path = GDKgetenv("monet_mod_path");
389 7 : char *fullname;
390 7 : size_t fullnamelen;
391 7 : size_t filelen = strlen(basename) + strlen(ext);
392 7 : str strs[MAXMULTISCRIPT]; /* hardwired limit */
393 7 : int lasts = 0;
394 :
395 7 : if (mod_path == NULL)
396 : return NULL;
397 :
398 7 : while (*mod_path == PATH_SEP)
399 0 : mod_path++;
400 7 : if (*mod_path == 0)
401 : return NULL;
402 7 : fullnamelen = 512;
403 7 : fullname = GDKmalloc(fullnamelen);
404 7 : if (fullname == NULL)
405 : return NULL;
406 7 : while (*mod_path) {
407 7 : size_t i;
408 7 : const char *p;
409 7 : int fd;
410 7 : DIR *rdir;
411 :
412 7 : if ((p = strchr(mod_path, PATH_SEP)) != NULL) {
413 0 : i = p - mod_path;
414 : } else {
415 7 : i = strlen(mod_path);
416 : }
417 7 : while (i + filelen + 2 > fullnamelen) {
418 0 : char *tmp;
419 0 : fullnamelen += 512;
420 0 : tmp = GDKrealloc(fullname, fullnamelen);
421 0 : if (tmp == NULL) {
422 0 : GDKfree(fullname);
423 0 : return NULL;
424 : }
425 : fullname = tmp;
426 : }
427 : /* we are now sure the directory name, file
428 : base name, extension, and separator fit
429 : into fullname, so we don't need to do any
430 : extra checks */
431 7 : strncpy(fullname, mod_path, i);
432 7 : fullname[i] = DIR_SEP;
433 7 : strcpy(fullname + i + 1, basename);
434 : /* see if this is a directory, if so, recurse */
435 7 : if (recurse == 1 && (rdir = opendir(fullname)) != NULL) {
436 : struct dirent *e;
437 : /* list *ext, sort, return */
438 0 : while ((e = readdir(rdir)) != NULL) {
439 0 : if (strcmp(e->d_name, "..") == 0 || strcmp(e->d_name, ".") == 0)
440 0 : continue;
441 0 : if (strcmp(e->d_name + strlen(e->d_name) - strlen(ext), ext) == 0) {
442 0 : int len;
443 0 : strs[lasts] = GDKmalloc(strlen(fullname) + sizeof(DIR_SEP)
444 : + strlen(e->d_name) +
445 : sizeof(PATH_SEP) + 1);
446 0 : if (strs[lasts] == NULL) {
447 0 : while (lasts >= 0)
448 0 : GDKfree(strs[lasts--]);
449 0 : GDKfree(fullname);
450 0 : (void) closedir(rdir);
451 0 : return NULL;
452 : }
453 0 : len = sprintf(strs[lasts], "%s%c%s%c", fullname, DIR_SEP,
454 : e->d_name, PATH_SEP);
455 0 : if (len == -1 || len >= FILENAME_MAX) {
456 0 : while (lasts >= 0)
457 0 : GDKfree(strs[lasts--]);
458 0 : GDKfree(fullname);
459 0 : (void) closedir(rdir);
460 0 : return NULL;
461 : }
462 0 : lasts++;
463 : }
464 0 : if (lasts >= MAXMULTISCRIPT)
465 : break;
466 : }
467 0 : (void) closedir(rdir);
468 : } else {
469 7 : strcat(fullname + i + 1, ext);
470 7 : if ((fd = MT_open(fullname, O_RDONLY | O_CLOEXEC)) >= 0) {
471 6 : char *tmp;
472 6 : close(fd);
473 6 : tmp = GDKrealloc(fullname, strlen(fullname) + 1);
474 6 : if (tmp == NULL)
475 : return fullname;
476 6 : return tmp;
477 : }
478 : }
479 1 : if ((mod_path = p) == NULL)
480 : break;
481 0 : while (*mod_path == PATH_SEP)
482 0 : mod_path++;
483 : }
484 1 : if (lasts > 0) {
485 0 : size_t i = 0;
486 0 : int c;
487 0 : char *tmp;
488 : /* assure that an ordering such as 10_first, 20_second works */
489 0 : qsort(strs, lasts, sizeof(char *), cmpstr);
490 0 : for (c = 0; c < lasts; c++)
491 0 : i += strlen(strs[c]) + 1; /* PATH_SEP or \0 */
492 0 : tmp = GDKrealloc(fullname, i);
493 0 : if (tmp == NULL) {
494 0 : GDKfree(fullname);
495 0 : return NULL;
496 : }
497 0 : fullname = tmp;
498 : i = 0;
499 0 : for (c = 0; c < lasts; c++) {
500 0 : if (strstr(fullname, strs[c]) == NULL) {
501 0 : strcpy(fullname + i, strs[c]);
502 0 : i += strlen(strs[c]);
503 : }
504 0 : GDKfree(strs[c]);
505 : }
506 0 : fullname[i - 1] = '\0';
507 0 : return fullname;
508 : }
509 : /* not found */
510 1 : GDKfree(fullname);
511 1 : return NULL;
512 : }
513 :
514 : char *
515 1 : MSP_locate_script(const char *filename)
516 : {
517 1 : return locate_file(filename, MAL_EXT, 1);
518 : }
519 :
520 : char *
521 0 : MSP_locate_sqlscript(const char *filename, bit recurse)
522 : {
523 : /* no directory semantics (yet) */
524 0 : return locate_file(filename, SQL_EXT, recurse);
525 : }
526 :
527 : int
528 22056 : malLibraryEnabled(const char *name)
529 : {
530 22056 : if (strcmp(name, "pyapi3") == 0) {
531 334 : const char *val = GDKgetenv("embedded_py");
532 334 : return val && (strcmp(val, "3") == 0 ||
533 0 : strcasecmp(val, "true") == 0 ||
534 0 : strcasecmp(val, "yes") == 0);
535 21722 : } else if (strcmp(name, "rapi") == 0) {
536 330 : const char *val = GDKgetenv("embedded_r");
537 330 : return val && (strcasecmp(val, "true") == 0 ||
538 4 : strcasecmp(val, "yes") == 0);
539 21392 : } else if (strcmp(name, "capi") == 0) {
540 329 : const char *val = GDKgetenv("embedded_c");
541 329 : return val && (strcasecmp(val, "true") == 0 ||
542 2 : strcasecmp(val, "yes") == 0);
543 : }
544 : return true;
545 : }
546 :
547 : #define HOW_TO_ENABLE_ERROR(LANGUAGE, OPTION) \
548 : do { \
549 : if (malLibraryEnabled(name)) \
550 : return "Embedded " LANGUAGE " has not been installed. " \
551 : "Please install it first, then start server with " \
552 : "--set " OPTION; \
553 : return "Embedded " LANGUAGE " has not been enabled. " \
554 : "Start server with --set " OPTION; \
555 : } while (0)
556 :
557 : char *
558 23 : malLibraryHowToEnable(const char *name)
559 : {
560 23 : if (strcmp(name, "pyapi3") == 0) {
561 0 : HOW_TO_ENABLE_ERROR("Python 3", "embedded_py=3");
562 23 : } else if (strcmp(name, "rapi") == 0) {
563 0 : HOW_TO_ENABLE_ERROR("R", "embedded_r=true");
564 23 : } else if (strcmp(name, "capi") == 0) {
565 0 : HOW_TO_ENABLE_ERROR("C/C++", "embedded_c=true");
566 : }
567 : return "";
568 : }
|