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