1// This file is part of SmallBASIC
2//
3// SmallBASIC - External library support (plugins)
4//
5// This program is distributed under the terms of the GPL v2.0 or later
6// Download the GNU Public License (GPL) from www.gnu.org
7//
8// Copyright(C) 2001 Nicholas Christopoulos
9
10#ifdef HAVE_CONFIG_H
11#include <config.h>
12#endif
13
14#include "common/smbas.h"
15
16#if defined(__MINGW32__)
17 #include <windows.h>
18 #include <error.h>
19 #define WIN_EXTLIB
20 #define LIB_EXT ".dll"
21#elif defined(_UnixOS)
22 #include <dlfcn.h>
23 #define LNX_EXTLIB
24 #define LIB_EXT ".so"
25#endif
26
27#if defined(LNX_EXTLIB) || defined(WIN_EXTLIB)
28#include "common/extlib.h"
29#include "common/pproc.h"
30#include <dirent.h>
31
32#define MAX_SLIBS 64
33#define MAX_PARAM 16
34#define TABLE_GROW_SIZE 16
35#define NAME_SIZE 256
36#define PATH_SIZE 1024
37
38typedef int (*sblib_exec_fn)(int, int, slib_par_t *, var_t *);
39typedef int (*sblib_getname_fn) (int, char *);
40typedef int (*sblib_count_fn) (void);
41typedef int (*sblib_init_fn) (const char *);
42typedef void (*sblib_close_fn) (void);
43typedef const char *(*sblib_get_module_name_fn) (void);
44
45typedef struct {
46 char name[NAME_SIZE];
47 char fullname[PATH_SIZE];
48 void *handle;
49 sblib_exec_fn sblib_proc_exec;
50 sblib_exec_fn sblib_func_exec;
51 uint32_t id;
52 uint32_t flags;
53 uint8_t imported;
54 uint32_t proc_count;
55 uint32_t func_count;
56 uint32_t proc_list_size;
57 uint32_t func_list_size;
58 ext_func_node_t *func_list;
59 ext_proc_node_t *proc_list;
60} slib_t;
61
62static slib_t slib_list[MAX_SLIBS];
63static uint32_t slib_count;
64
65#if defined(LNX_EXTLIB)
66int slib_llopen(slib_t *lib) {
67 lib->handle = dlopen(lib->fullname, RTLD_NOW);
68 if (lib->handle == NULL) {
69 sc_raise("LIB: error on loading %s\n%s", lib->name, dlerror());
70 }
71 return (lib->handle != NULL);
72}
73
74void *slib_getoptptr(slib_t *lib, const char *name) {
75 return dlsym(lib->handle, name);
76}
77
78int slib_llclose(slib_t *lib) {
79 if (!lib->handle) {
80 return 0;
81 }
82 dlclose(lib->handle);
83 lib->handle = NULL;
84 return 1;
85}
86
87#elif defined(WIN_EXTLIB)
88int slib_llopen(slib_t *lib) {
89 lib->handle = LoadLibraryA(lib->fullname);
90 if (lib->handle == NULL) {
91 int error = GetLastError();
92 switch (error) {
93 case ERROR_MOD_NOT_FOUND:
94 sc_raise("LIB: DLL dependency error [%d] loading %s [%s]\n", error, lib->fullname, lib->name);
95 break;
96 case ERROR_DYNLINK_FROM_INVALID_RING:
97 sc_raise("LIB: DLL build error [%d] loading %s [%s]\n", error, lib->fullname, lib->name);
98 break;
99 default:
100 sc_raise("LIB: error [%d] loading %s [%s]\n", error, lib->fullname, lib->name);
101 break;
102 }
103 }
104 return (lib->handle != NULL);
105}
106
107void *slib_getoptptr(slib_t *lib, const char *name) {
108 return GetProcAddress((HMODULE) lib->handle, name);
109}
110
111int slib_llclose(slib_t *lib) {
112 if (!lib->handle) {
113 return 0;
114 }
115 FreeLibrary(lib->handle);
116 lib->handle = NULL;
117 return 1;
118}
119#endif
120
121/**
122 * returns slib_t* for the given id
123 */
124slib_t *get_lib(int lib_id) {
125 if (lib_id < 0 || lib_id >= slib_count) {
126 return NULL;
127 }
128 return &slib_list[lib_id];
129}
130
131/**
132 * add an external procedure to the list
133 */
134int slib_add_external_proc(const char *proc_name, int lib_id) {
135 slib_t *lib = get_lib(lib_id);
136
137 if (lib->proc_list == NULL) {
138 lib->proc_list_size = TABLE_GROW_SIZE;
139 lib->proc_list = (ext_proc_node_t *)malloc(sizeof(ext_proc_node_t) * lib->proc_list_size);
140 } else if (lib->proc_list_size <= (lib->proc_count + 1)) {
141 lib->proc_list_size += TABLE_GROW_SIZE;
142 lib->proc_list = (ext_proc_node_t *)realloc(lib->proc_list, sizeof(ext_proc_node_t) * lib->proc_list_size);
143 }
144
145 lib->proc_list[lib->proc_count].lib_id = lib_id;
146 lib->proc_list[lib->proc_count].symbol_index = 0;
147 strlcpy(lib->proc_list[lib->proc_count].name, proc_name, sizeof(lib->proc_list[lib->proc_count].name));
148 strupper(lib->proc_list[lib->proc_count].name);
149
150 if (opt_verbose) {
151 log_printf("LIB: %d, Idx: %d, PROC '%s'\n", lib_id, lib->proc_count,
152 lib->proc_list[lib->proc_count].name);
153 }
154 lib->proc_count++;
155 return lib->proc_count - 1;
156}
157
158/**
159 * Add an external function to the list
160 */
161int slib_add_external_func(const char *func_name, uint32_t lib_id) {
162 slib_t *lib = get_lib(lib_id);
163
164 if (lib->func_list == NULL) {
165 lib->func_list_size = TABLE_GROW_SIZE;
166 lib->func_list = (ext_func_node_t *)malloc(sizeof(ext_func_node_t) * lib->func_list_size);
167 } else if (lib->func_list_size <= (lib->func_count + 1)) {
168 lib->func_list_size += TABLE_GROW_SIZE;
169 lib->func_list = (ext_func_node_t *)
170 realloc(lib->func_list, sizeof(ext_func_node_t) * lib->func_list_size);
171 }
172
173 lib->func_list[lib->func_count].lib_id = lib_id;
174 lib->func_list[lib->func_count].symbol_index = 0;
175 strlcpy(lib->func_list[lib->func_count].name, func_name, sizeof(lib->func_list[lib->func_count].name));
176 strupper(lib->func_list[lib->func_count].name);
177
178 if (opt_verbose) {
179 log_printf("LIB: %d, Idx: %d, FUNC '%s'\n", lib_id, lib->func_count,
180 lib->func_list[lib->func_count].name);
181 }
182 lib->func_count++;
183 return lib->func_count - 1;
184}
185
186/**
187 * returns the ID of the keyword
188 */
189int slib_get_kid(int lib_id, const char *name) {
190 slib_t *lib = get_lib(lib_id);
191 if (lib != NULL) {
192 const char *dot = strchr(name, '.');
193 const char *field = (dot != NULL ? dot + 1 : name);
194 for (int i = 0; i < lib->proc_count; i++) {
195 if (lib->proc_list[i].lib_id == lib_id &&
196 strcmp(lib->proc_list[i].name, field) == 0) {
197 return i;
198 }
199 }
200 for (int i = 0; i < lib->func_count; i++) {
201 if (lib->func_list[i].lib_id == lib_id &&
202 strcmp(lib->func_list[i].name, field) == 0) {
203 return i;
204 }
205 }
206 }
207 return -1;
208}
209
210/**
211 * returns the library-id (index of library of the current process)
212 */
213int slib_get_module_id(const char *name, const char *alias) {
214 for (int i = 0; i < slib_count; i++) {
215 slib_t *lib = &slib_list[i];
216 if (strcasecmp(lib->name, name) == 0) {
217 strcpy(lib->name, alias);
218 return i;
219 }
220 }
221 // not found
222 return -1;
223}
224
225void slib_import_routines(slib_t *lib, int comp) {
226 int total = 0;
227 char buf[SB_KEYWORD_SIZE];
228
229 lib->sblib_func_exec = slib_getoptptr(lib, "sblib_func_exec");
230 lib->sblib_proc_exec = slib_getoptptr(lib, "sblib_proc_exec");
231 sblib_count_fn fcount = slib_getoptptr(lib, "sblib_proc_count");
232 sblib_getname_fn fgetname = slib_getoptptr(lib, "sblib_proc_getname");
233
234 if (fcount && fgetname) {
235 int count = fcount();
236 total += count;
237 for (int i = 0; i < count; i++) {
238 if (fgetname(i, buf)) {
239 strupper(buf);
240 if (!lib->imported && slib_add_external_proc(buf, lib->id) == -1) {
241 break;
242 } else if (comp) {
243 char name[NAME_SIZE];
244 strlcpy(name, lib->name, sizeof(name));
245 strlcat(name, ".", sizeof(name));
246 strlcat(name, buf, sizeof(name));
247 strupper(name);
248 comp_add_external_proc(name, lib->id);
249 }
250 }
251 }
252 }
253
254 fcount = slib_getoptptr(lib, "sblib_func_count");
255 fgetname = slib_getoptptr(lib, "sblib_func_getname");
256
257 if (fcount && fgetname) {
258 int count = fcount();
259 total += count;
260 for (int i = 0; i < count; i++) {
261 if (fgetname(i, buf)) {
262 strupper(buf);
263 if (!lib->imported && slib_add_external_func(buf, lib->id) == -1) {
264 break;
265 } else if (comp) {
266 char name[NAME_SIZE];
267 strlcpy(name, lib->name, sizeof(name));
268 strlcat(name, ".", sizeof(name));
269 strlcat(name, buf, sizeof(name));
270 strupper(name);
271 comp_add_external_func(name, lib->id);
272 }
273 }
274 }
275 }
276
277 if (!total) {
278 log_printf("LIB: module '%s' has no exports\n", lib->name);
279 }
280}
281
282/**
283 * updates compiler with the module's keywords
284 */
285void slib_import(int lib_id, int comp) {
286 slib_t *lib = get_lib(lib_id);
287 if (lib && (comp || !lib->imported)) {
288 slib_import_routines(lib, comp);
289 lib->imported = 1;
290 }
291 if (lib && !comp) {
292 sblib_init_fn minit = slib_getoptptr(lib, "sblib_init");
293 if (minit && !minit(gsb_last_file)) {
294 rt_raise("LIB: %s->sblib_init(), failed", lib->name);
295 }
296 }
297}
298
299/**
300 * opens the library
301 */
302void slib_open(const char *fullname, const char *name) {
303 int name_index = 0;
304
305 if (strncmp(name, "lib", 3) == 0) {
306 // libmysql -> store mysql
307 name_index = 3;
308 }
309
310 slib_t *lib = &slib_list[slib_count];
311 memset(lib, 0, sizeof(slib_t));
312 strlcpy(lib->name, name + name_index, NAME_SIZE);
313 strlcpy(lib->fullname, fullname, PATH_SIZE);
314 lib->id = slib_count;
315 lib->imported = 0;
316
317 if (!opt_quiet) {
318 log_printf("LIB: registering '%s'", fullname);
319 }
320 if (slib_llopen(lib)) {
321 slib_count++;
322 // override default name
323 sblib_get_module_name_fn get_module_name = slib_getoptptr(lib, "sblib_get_module_name");
324 if (get_module_name) {
325 strlcpy(lib->name, get_module_name(), NAME_SIZE);
326 }
327 } else {
328 sc_raise("LIB: can't open %s", fullname);
329 }
330}
331
332/**
333 * whether name ends with LIB_EXT and does not contain '-', eg libstdc++-6.dll
334 */
335int slib_is_module(const char *name) {
336 int result = 0;
337 if (name && name[0] != '\0') {
338 int offs = strlen(name) - (sizeof(LIB_EXT) - 1);
339 result = offs > 0 && strchr(name, '-') == NULL && strcasecmp(name + offs, LIB_EXT) == 0;
340 }
341 return result;
342}
343
344void slib_open_path(const char *path, const char *name) {
345 if (slib_is_module(name)) {
346 // ends with LIB_EXT
347 char full[PATH_SIZE];
348 char libname[NAME_SIZE];
349
350 // copy name without extension
351 strlcpy(libname, name, sizeof(libname));
352 char *p = strchr(libname, '.');
353 *p = '\0';
354
355 // copy full path to name
356 strlcpy(full, path, sizeof(full));
357 if (path[strlen(path) - 1] != '/') {
358 // add trailing separator
359 strlcat(full, "/", sizeof(full));
360 }
361 strlcat(full, name, sizeof(full));
362 slib_open(full, libname);
363 }
364}
365
366void slib_scan_path(const char *path) {
367 struct stat stbuf;
368 if (stat(path, &stbuf) != -1) {
369 if (S_ISREG(stbuf.st_mode)) {
370 char *name = strrchr(path, '/');
371 slib_open_path(path, (name ? name + 1 : path));
372 } else {
373 DIR *dp = opendir(path);
374 if (dp != NULL) {
375 struct dirent *e;
376 while ((e = readdir(dp)) != NULL) {
377 char *name = e->d_name;
378 if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) {
379 slib_open_path(path, name);
380 }
381 }
382 closedir(dp);
383 }
384 }
385 } else if (!opt_quiet) {
386 log_printf("LIB: module path '%s' not found.\n", path);
387 }
388}
389
390void slib_init_path() {
391 char *path = opt_modpath;
392 while (path && path[0] != '\0') {
393 char *sep = strchr(path, ':');
394 if (sep) {
395 // null terminate the current path
396 *sep = '\0';
397 slib_scan_path(path);
398 *sep = ':';
399 path = sep + 1;
400 } else {
401 slib_scan_path(path);
402 path = NULL;
403 }
404 }
405}
406
407/**
408 * slib-manager: initialize manager
409 */
410void slib_init() {
411 slib_count = 0;
412
413 if (!prog_error && opt_loadmod) {
414 if (opt_modpath[0] == '\0') {
415 const char *modpath = getenv("SBASICPATH");
416 if (modpath != NULL) {
417 strlcpy(opt_modpath, modpath, OPT_MOD_SZ);
418 }
419 }
420 if (!opt_quiet) {
421 log_printf("LIB: scanning for modules in '%s'\n", opt_modpath);
422 }
423 slib_init_path();
424 }
425}
426
427/**
428 * slib-manager: close everything
429 */
430void slib_close() {
431 for (int i = 0; i < slib_count; i++) {
432 slib_t *lib = &slib_list[i];
433 if (lib->handle) {
434 sblib_close_fn mclose = slib_getoptptr(lib, "sblib_close");
435 if (mclose) {
436 mclose();
437 }
438 slib_llclose(lib);
439 }
440 free(lib->proc_list);
441 free(lib->func_list);
442 lib->proc_count = 0;
443 lib->func_count = 0;
444 lib->proc_list_size = 0;
445 lib->func_list_size = 0;
446 lib->func_list = NULL;
447 lib->proc_list = NULL;
448 }
449}
450
451/**
452 * build parameter table
453 */
454int slib_build_ptable(slib_par_t *ptable) {
455 int pcount = 0;
456 var_t *arg;
457 bcip_t ofs;
458
459 if (code_peek() == kwTYPE_LEVEL_BEGIN) {
460 code_skipnext();
461 byte ready = 0;
462 do {
463 byte code = code_peek();
464 switch (code) {
465 case kwTYPE_EOC:
466 code_skipnext();
467 break;
468 case kwTYPE_SEP:
469 code_skipsep();
470 break;
471 case kwTYPE_LEVEL_END:
472 ready = 1;
473 break;
474 case kwTYPE_VAR:
475 // variable
476 ofs = prog_ip;
477 if (code_isvar()) {
478 // push parameter
479 ptable[pcount].var_p = code_getvarptr();
480 ptable[pcount].byref = 1;
481 pcount++;
482 break;
483 }
484
485 // restore IP
486 prog_ip = ofs;
487 // no 'break' here
488 default:
489 // default --- expression (BYVAL ONLY)
490 arg = v_new();
491 eval(arg);
492 if (!prog_error) {
493 // push parameter
494 ptable[pcount].var_p = arg;
495 ptable[pcount].byref = 0;
496 pcount++;
497 } else {
498 v_free(arg);
499 v_detach(arg);
500 return pcount;
501 }
502 }
503 if (pcount == MAX_PARAM) {
504 err_parm_limit(MAX_PARAM);
505 }
506 } while (!ready && !prog_error);
507 // kwTYPE_LEVEL_END
508 code_skipnext();
509 }
510 return pcount;
511}
512
513/**
514 * free parameter table
515 */
516void slib_free_ptable(slib_par_t *ptable, int pcount) {
517 for (int i = 0; i < pcount; i++) {
518 if (ptable[i].byref == 0) {
519 v_free(ptable[i].var_p);
520 v_detach(ptable[i].var_p);
521 }
522 }
523}
524
525/**
526 * execute a function or procedure
527 */
528int slib_exec(slib_t *lib, var_t *ret, int index, int proc) {
529 slib_par_t *ptable;
530 int pcount;
531 if (code_peek() == kwTYPE_LEVEL_BEGIN) {
532 ptable = (slib_par_t *)malloc(sizeof(slib_par_t) * MAX_PARAM);
533 pcount = slib_build_ptable(ptable);
534 } else {
535 ptable = NULL;
536 pcount = 0;
537 }
538 if (prog_error) {
539 slib_free_ptable(ptable, pcount);
540 free(ptable);
541 return 0;
542 }
543
544 int success;
545 v_init(ret);
546 if (proc) {
547 success = lib->sblib_proc_exec(index, pcount, ptable, ret);
548 } else {
549 success = lib->sblib_func_exec(index, pcount, ptable, ret);
550 }
551
552 // error
553 if (!success) {
554 if (ret->type == V_STR) {
555 err_throw("LIB:%s: %s\n", lib->name, ret->v.p.ptr);
556 } else {
557 err_throw("LIB:%s: Unspecified error calling %s\n", lib->name, (proc ? "SUB" : "FUNC"));
558 }
559 }
560
561 // clean-up
562 if (ptable) {
563 slib_free_ptable(ptable, pcount);
564 free(ptable);
565 }
566
567 return success;
568}
569
570/**
571 * execute a procedure
572 */
573int slib_procexec(int lib_id, int index) {
574 int result;
575 slib_t *lib = get_lib(lib_id);
576 if (lib && lib->sblib_proc_exec) {
577 var_t ret;
578 v_init(&ret);
579 result = slib_exec(lib, &ret, index, 1);
580 v_free(&ret);
581 } else {
582 result = 0;
583 }
584 return result;
585}
586
587/**
588 * execute a function
589 */
590int slib_funcexec(int lib_id, int index, var_t *ret) {
591 int result;
592 slib_t *lib = get_lib(lib_id);
593 if (lib && lib->sblib_func_exec) {
594 result = slib_exec(lib, ret, index, 0);
595 } else {
596 result = 0;
597 }
598 return result;
599}
600
601void *slib_get_func(const char *name) {
602 void *result = NULL;
603 for (int i = 0; i < slib_count && result == NULL; i++) {
604 slib_t *lib = &slib_list[i];
605 if (lib->imported) {
606 result = slib_getoptptr(lib, name);
607 }
608 }
609 return result;
610}
611
612#else
613// dummy implementations
614int slib_funcexec(int lib_id, int index, var_t *ret) { return 0; }
615int slib_procexec(int lib_id, int index) { return 0; }
616int slib_get_kid(int lib_id, const char *name) { return -1; }
617int slib_get_module_id(const char *name, const char *alias) { return -1; }
618void slib_close() {}
619void slib_init(int mcount, const char *mlist) {}
620void *slib_get_func(const char *name) { return 0; }
621void slib_import(int lib_id, int comp) {}
622#endif
623