1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 *
6 * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V.
7 */
8
9/*
10 * (author) M. Kersten
11 * An include file name is also used as library name
12 */
13#include "monetdb_config.h"
14#include "mal_module.h"
15#include "mal_linker.h"
16#include "mal_function.h" /* for throw() */
17#include "mal_import.h" /* for slash_2_dir_sep() */
18#include "mal_private.h"
19
20#include "mutils.h"
21#include <sys/types.h> /* opendir */
22#ifdef HAVE_DIRENT_H
23#include <dirent.h>
24#endif
25#ifdef HAVE_FCNTL_H
26#include <fcntl.h>
27#endif
28#include <unistd.h>
29
30#if defined(_MSC_VER) && _MSC_VER >= 1400
31#define open _open
32#define close _close
33#endif
34
35#define MAXMODULES 128
36
37typedef struct{
38 str modname;
39 str fullname;
40 void **handle;
41} FileRecord;
42
43static FileRecord filesLoaded[MAXMODULES];
44static int maxfiles = MAXMODULES;
45static int lastfile = 0;
46
47#ifndef O_CLOEXEC
48#define O_CLOEXEC 0
49#endif
50
51/*
52 * returns 1 if the file exists
53 */
54#ifndef F_OK
55#define F_OK 0
56#endif
57#ifdef _MSC_VER
58#define access(f, m) _access(f, m)
59#endif
60static inline int
61fileexists(const char *path)
62{
63 return access(path, F_OK) == 0;
64}
65
66/* Search for occurrence of the function in the library identified by the filename. */
67MALfcn
68getAddress(str fcnname)
69{
70 void *dl;
71 MALfcn adr;
72 int idx=0;
73 static int prev= -1;
74
75 /* First try the last module loaded */
76 if( prev >= 0){
77 adr = (MALfcn) dlsym(filesLoaded[prev].handle, fcnname);
78 if( adr != NULL)
79 return adr; /* found it */
80 }
81 /*
82 * Search for occurrence of the function in any library already loaded.
83 * This deals with the case that files are linked together to reduce
84 * the loading time, while the signatures of the functions are still
85 * obtained from the source-file MAL script.
86 */
87 for (idx =0; idx < lastfile; idx++)
88 if (idx != prev && /* skip already searched module */
89 filesLoaded[idx].handle &&
90 (idx == 0 || filesLoaded[idx].handle != filesLoaded[0].handle)) {
91 adr = (MALfcn) dlsym(filesLoaded[idx].handle, fcnname);
92 if (adr != NULL) {
93 prev = idx;
94 return adr; /* found it */
95 }
96 }
97
98 if (lastfile)
99 return NULL;
100 /*
101 * Try the program libraries at large or run through all
102 * loaded files and try to resolve the functionname again.
103 *
104 * the first argument must be the same as the base name of the
105 * library that is created in src/tools */
106 dl = mdlopen("libmonetdb5", RTLD_NOW | RTLD_GLOBAL);
107 if (dl == NULL)
108 return NULL;
109
110 adr = (MALfcn) dlsym(dl, fcnname);
111 filesLoaded[lastfile].modname = GDKstrdup("libmonetdb5");
112 if(filesLoaded[lastfile].modname == NULL) {
113 dlclose(dl);
114 return NULL;
115 }
116 filesLoaded[lastfile].fullname = GDKstrdup("libmonetdb5");
117 if(filesLoaded[lastfile].fullname == NULL) {
118 dlclose(dl);
119 GDKfree(filesLoaded[lastfile].modname);
120 return NULL;
121 }
122 filesLoaded[lastfile].handle = dl;
123 lastfile ++;
124 return adr;
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
143str
144loadLibrary(str filename, int flag)
145{
146 int mode = RTLD_NOW | RTLD_GLOBAL;
147 char nme[FILENAME_MAX];
148 void *handle = NULL;
149 str s;
150 int idx;
151 const char *mod_path = GDKgetenv("monet_mod_path");
152
153 /* AIX requires RTLD_MEMBER to load a module that is a member of an
154 * archive. */
155#ifdef RTLD_MEMBER
156 mode |= RTLD_MEMBER;
157#endif
158
159 for (idx = 0; idx < lastfile; idx++)
160 if (filesLoaded[idx].modname &&
161 strcmp(filesLoaded[idx].modname, filename) == 0)
162 /* already loaded */
163 return MAL_SUCCEED;
164
165 /* ignore any path given */
166 if ((s = strrchr(filename, DIR_SEP)) == NULL)
167 s = filename;
168
169 if (mod_path != NULL) {
170 while (*mod_path == PATH_SEP)
171 mod_path++;
172 if (*mod_path == 0)
173 mod_path = NULL;
174 }
175 if (mod_path == NULL) {
176 if (flag)
177 throw(LOADER, "loadLibrary", RUNTIME_FILE_NOT_FOUND ":%s", s);
178 return MAL_SUCCEED;
179 }
180
181 while (*mod_path) {
182 int len;
183 const char *p;
184
185 for (p = mod_path; *p && *p != PATH_SEP; p++)
186 ;
187
188 /* try hardcoded SO_EXT if that is the same for modules */
189#ifdef _AIX
190 len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s%s(%s_%s.0)",
191 (int) (p - mod_path),
192 mod_path, DIR_SEP, SO_PREFIX, s, SO_EXT, SO_PREFIX, s);
193#else
194 len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s%s",
195 (int) (p - mod_path),
196 mod_path, DIR_SEP, SO_PREFIX, s, SO_EXT);
197#endif
198 if (len == -1 || len >= FILENAME_MAX)
199 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR "Library filename path is too large");
200 handle = dlopen(nme, mode);
201 if (handle == NULL && fileexists(nme))
202 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR " failed to open library %s (from within file '%s'): %s", s, nme, dlerror());
203 if (handle == NULL && strcmp(SO_EXT, ".so") != /* DISABLES CODE */ (0)) {
204 /* try .so */
205 len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s.so",
206 (int) (p - mod_path),
207 mod_path, DIR_SEP, SO_PREFIX, s);
208 if (len == -1 || len >= FILENAME_MAX)
209 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR "Library filename path is too large");
210 handle = dlopen(nme, mode);
211 if (handle == NULL && fileexists(nme))
212 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR " failed to open library %s (from within file '%s'): %s", s, nme, dlerror());
213 }
214#ifdef __APPLE__
215 if (handle == NULL && strcmp(SO_EXT, ".bundle") != 0) {
216 /* try .bundle */
217 len = snprintf(nme, FILENAME_MAX, "%.*s%c%s_%s.bundle",
218 (int) (p - mod_path),
219 mod_path, DIR_SEP, SO_PREFIX, s);
220 if (len == -1 || len >= FILENAME_MAX)
221 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR "Library filename path is too large");
222 handle = dlopen(nme, mode);
223 if (handle == NULL && fileexists(nme))
224 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR " failed to open library %s (from within file '%s'): %s", s, nme, dlerror());
225 }
226#endif
227
228 if (*p == 0 || handle != NULL)
229 break;
230 mod_path = p + 1;
231 }
232
233 if (handle == NULL) {
234 if (flag)
235 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR " could not locate library %s (from within file '%s'): %s", s, filename, dlerror());
236 }
237
238 MT_lock_set(&mal_contextLock);
239 if (lastfile == maxfiles) {
240 MT_lock_unset(&mal_contextLock);
241 if (handle)
242 dlclose(handle);
243 throw(MAL,"mal.linker", "loadModule internal error, too many modules loaded");
244 } else {
245 filesLoaded[lastfile].modname = GDKstrdup(filename);
246 if(filesLoaded[lastfile].modname == NULL) {
247 MT_lock_unset(&mal_contextLock);
248 if (handle)
249 dlclose(handle);
250 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR " could not allocate space");
251 }
252 filesLoaded[lastfile].fullname = GDKstrdup(handle ? nme : "");
253 if(filesLoaded[lastfile].fullname == NULL) {
254 GDKfree(filesLoaded[lastfile].modname);
255 MT_lock_unset(&mal_contextLock);
256 if (handle)
257 dlclose(handle);
258 throw(LOADER, "loadLibrary", RUNTIME_LOAD_ERROR " could not allocate space");
259 }
260 filesLoaded[lastfile].handle = handle ? handle : filesLoaded[0].handle;
261 lastfile ++;
262 }
263 MT_lock_unset(&mal_contextLock);
264
265 return MAL_SUCCEED;
266}
267
268/*
269 * For analysis of memory leaks we should cleanup the libraries before
270 * we exit the server. This does not involve the libraries themselves,
271 * because they may still be in use.
272 */
273void
274mal_linker_reset(void)
275{
276 int i;
277
278 MT_lock_set(&mal_contextLock);
279 for (i = 0; i < lastfile; i++){
280 if (filesLoaded[i].fullname) {
281 /* dlclose(filesLoaded[i].handle);*/
282 GDKfree(filesLoaded[i].modname);
283 GDKfree(filesLoaded[i].fullname);
284 }
285 filesLoaded[i].modname = NULL;
286 filesLoaded[i].fullname = NULL;
287 }
288 lastfile = 0;
289 MT_lock_unset(&mal_contextLock);
290}
291
292/*
293 * Handling of Module Library Search Path
294 * The plausible locations of the modules can be designated by
295 * an environment variable.
296 */
297static int
298cmpstr(const void *_p1, const void *_p2)
299{
300 const char *p1 = *(char* const*)_p1;
301 const char *p2 = *(char* const*)_p2;
302 const char *f1 = strrchr(p1, (int) DIR_SEP);
303 const char *f2 = strrchr(p2, (int) DIR_SEP);
304 return strcmp(f1?f1:p1, f2?f2:p2);
305}
306
307
308#define MAXMULTISCRIPT 48
309char *
310locate_file(const char *basename, const char *ext, bit recurse)
311{
312 const char *mod_path = GDKgetenv("monet_mod_path");
313 char *fullname;
314 size_t fullnamelen;
315 size_t filelen = strlen(basename) + strlen(ext);
316 str strs[MAXMULTISCRIPT]; /* hardwired limit */
317 int lasts = 0;
318
319 if (mod_path == NULL)
320 return NULL;
321
322 while (*mod_path == PATH_SEP)
323 mod_path++;
324 if (*mod_path == 0)
325 return NULL;
326 fullnamelen = 512;
327 fullname = GDKmalloc(fullnamelen);
328 if (fullname == NULL)
329 return NULL;
330 while (*mod_path) {
331 size_t i;
332 const char *p;
333 int fd;
334 DIR *rdir;
335
336 if ((p = strchr(mod_path, PATH_SEP)) != NULL) {
337 i = p - mod_path;
338 } else {
339 i = strlen(mod_path);
340 }
341 while (i + filelen + 2 > fullnamelen) {
342 char *tmp;
343 fullnamelen += 512;
344 tmp = GDKrealloc(fullname, fullnamelen);
345 if (tmp == NULL) {
346 GDKfree(fullname);
347 return NULL;
348 }
349 fullname = tmp;
350 }
351 /* we are now sure the directory name, file
352 base name, extension, and separator fit
353 into fullname, so we don't need to do any
354 extra checks */
355 strncpy(fullname, mod_path, i);
356 fullname[i] = DIR_SEP;
357 strcpy(fullname + i + 1, basename);
358 /* see if this is a directory, if so, recurse */
359 if (recurse == 1 && (rdir = opendir(fullname)) != NULL) {
360 struct dirent *e;
361 /* list *ext, sort, return */
362 while ((e = readdir(rdir)) != NULL) {
363 if (strcmp(e->d_name, "..") == 0 || strcmp(e->d_name, ".") == 0)
364 continue;
365 if (strcmp(e->d_name + strlen(e->d_name) - strlen(ext), ext) == 0) {
366 int len;
367 strs[lasts] = GDKmalloc(strlen(fullname) + sizeof(DIR_SEP)
368 + strlen(e->d_name) + sizeof(PATH_SEP) + 1);
369 if (strs[lasts] == NULL) {
370 while (lasts >= 0)
371 GDKfree(strs[lasts--]);
372 GDKfree(fullname);
373 (void)closedir(rdir);
374 return NULL;
375 }
376 len = sprintf(strs[lasts], "%s%c%s%c", fullname, DIR_SEP, e->d_name, PATH_SEP);
377 if (len == -1 || len >= FILENAME_MAX) {
378 while (lasts >= 0)
379 GDKfree(strs[lasts--]);
380 GDKfree(fullname);
381 (void)closedir(rdir);
382 return NULL;
383 }
384 lasts++;
385 }
386 if (lasts >= MAXMULTISCRIPT)
387 break;
388 }
389 (void)closedir(rdir);
390 } else {
391 strcat(fullname + i + 1, ext);
392 if ((fd = open(fullname, O_RDONLY | O_CLOEXEC)) >= 0) {
393 char *tmp;
394 close(fd);
395 tmp = GDKrealloc(fullname, strlen(fullname) + 1);
396 if (tmp == NULL)
397 GDKfree(fullname);
398 return tmp;
399 }
400 }
401 if ((mod_path = p) == NULL)
402 break;
403 while (*mod_path == PATH_SEP)
404 mod_path++;
405 }
406 if (lasts > 0) {
407 size_t i = 0;
408 int c;
409 char *tmp;
410 /* assure that an ordering such as 10_first, 20_second works */
411 qsort(strs, lasts, sizeof(char *), cmpstr);
412 for (c = 0; c < lasts; c++)
413 i += strlen(strs[c]) + 1; /* PATH_SEP or \0 */
414 tmp = GDKrealloc(fullname, i);
415 if( tmp == NULL){
416 GDKfree(fullname);
417 return NULL;
418 }
419 fullname = tmp;
420 i = 0;
421 for (c = 0; c < lasts; c++) {
422 if (strstr(fullname, strs[c]) == NULL) {
423 strcpy(fullname + i, strs[c]);
424 i += strlen(strs[c]);
425 }
426 GDKfree(strs[c]);
427 }
428 fullname[i - 1] = '\0';
429 return fullname;
430 }
431 /* not found */
432 GDKfree(fullname);
433 return NULL;
434}
435
436char *
437MSP_locate_script(const char *filename)
438{
439 return locate_file(filename, MAL_EXT, 1);
440}
441
442char *
443MSP_locate_sqlscript(const char *filename, bit recurse)
444{
445 /* no directory semantics (yet) */
446 return locate_file(filename, SQL_EXT, recurse);
447}
448
449
450int
451malLibraryEnabled(str name) {
452 if (strcmp(name, "pyapi") == 0) {
453 const char *val = GDKgetenv("embedded_py");
454 return val && (strcmp(val, "2") == 0 ||
455 strcasecmp(val, "true") == 0 ||
456 strcasecmp(val, "yes") == 0);
457 } else if (strcmp(name, "pyapi3") == 0) {
458 const char *val = GDKgetenv("embedded_py");
459 return val && strcasecmp(val, "3") == 0;
460 }
461 return true;
462}
463
464char*
465malLibraryHowToEnable(str name) {
466 if (strcmp(name, "pyapi") == 0) {
467 return "Embedded Python 2 has not been enabled. Start server with --set embedded_py=2";
468 }
469 if (strcmp(name, "pyapi3") == 0) {
470 return "Embedded Python 3 has not been enabled. Start server with --set embedded_py=3";
471 }
472 return "";
473}
474