Line data Source code
1 : /*************************************************************
2 : * gnc-module.c -- loadable plugin/module system for gnucash
3 : * Copyright 2001 Linux Developers Group, Inc.
4 : *************************************************************/
5 : /********************************************************************\
6 : * This program is free software; you can redistribute it and/or *
7 : * modify it under the terms of the GNU General Public License as *
8 : * published by the Free Software Foundation; either version 2 of *
9 : * the License, or (at your option) any later version. *
10 : * *
11 : * This program is distributed in the hope that it will be useful, *
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 : * GNU General Public License for more details. *
15 : * *
16 : * You should have received a copy of the GNU General Public License*
17 : * along with this program; if not, contact: *
18 : * *
19 : * Free Software Foundation Voice: +1-617-542-5942 *
20 : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 : * Boston, MA 02110-1301, USA gnu@gnu.org *
22 : * *
23 : \********************************************************************/
24 :
25 :
26 : #include <config.h>
27 :
28 : #include <stdio.h>
29 : #include <stdlib.h>
30 : #include <string.h>
31 : #include <gmodule.h>
32 : #include <sys/types.h>
33 : #ifdef HAVE_DIRENT_H
34 : # include <dirent.h>
35 : #endif
36 :
37 : #include "gnc-module.h"
38 :
39 : static GHashTable * loaded_modules = NULL;
40 : static GList * module_info = NULL;
41 :
42 : typedef struct
43 : {
44 : char * module_path;
45 : char * module_description;
46 : char * module_filepath;
47 : int module_interface;
48 : int module_age;
49 : int module_revision;
50 : } GNCModuleInfo;
51 :
52 : typedef struct
53 : {
54 : GModule * gmodule;
55 : gchar * filename;
56 : int load_count;
57 : GNCModuleInfo * info;
58 : int (* init_func)(int refcount);
59 : } GNCLoadedModule;
60 :
61 : static GNCModuleInfo * gnc_module_get_info(const char * lib_path);
62 :
63 : /*************************************************************
64 : * gnc_module_system_search_dirs
65 : * return a list of dirs to look in for gnc_module libraries
66 : *************************************************************/
67 :
68 : static GList *
69 6 : gnc_module_system_search_dirs(void)
70 : {
71 6 : const char *spath = g_getenv("GNC_MODULE_PATH");
72 6 : GList * list = NULL;
73 6 : GString * token = g_string_new(NULL);
74 6 : int escchar = 0;
75 : const char *cpos;
76 :
77 6 : if (!spath)
78 : {
79 2 : spath = DEFAULT_MODULE_PATH;
80 : }
81 :
82 297 : for (cpos = spath; *cpos; cpos++)
83 : {
84 291 : switch (*cpos)
85 : {
86 : #ifndef G_OS_WIN32
87 : /* On windows, with '\' as the directory separator character,
88 : this additional de-quoting will make every path processing
89 : fail miserably. Anyway this should probably be thrown out
90 : altogether, because this additional level of de-quoting
91 : (after shell quoting) is completely unexpected and
92 : uncommon. */
93 0 : case '\\':
94 0 : if (!escchar)
95 : {
96 0 : escchar = TRUE;
97 : }
98 : else
99 : {
100 0 : g_string_append_c(token, *cpos);
101 0 : escchar = FALSE;
102 : }
103 0 : break;
104 : #endif
105 :
106 : /* This is ':' on UNIX machines and ';' under Windows. */
107 0 : case G_SEARCHPATH_SEPARATOR:
108 0 : if (!escchar)
109 : {
110 0 : char *token_str = g_string_free (token, FALSE);
111 0 : list = g_list_append (list, token_str);
112 0 : token = g_string_new(NULL);
113 : }
114 : else
115 : {
116 0 : g_string_append_c(token, *cpos);
117 0 : escchar = FALSE;
118 : }
119 0 : break;
120 :
121 291 : default:
122 291 : g_string_append_c(token, *cpos);
123 291 : escchar = FALSE;
124 291 : break;
125 : }
126 : }
127 6 : if (token->len)
128 : {
129 6 : char *token_str = g_string_free (token, FALSE);
130 6 : list = g_list_append(list, token_str);
131 : }
132 : else
133 : {
134 0 : g_string_free(token, TRUE);
135 : }
136 6 : return list;
137 : }
138 :
139 : /*************************************************************
140 : * gnc_module_system_init
141 : * initialize the module system
142 : *************************************************************/
143 : void
144 6 : gnc_module_system_init(void)
145 : {
146 6 : if (loaded_modules)
147 0 : return;
148 :
149 6 : loaded_modules = g_hash_table_new(g_direct_hash, g_direct_equal);
150 :
151 : /* now crawl the GNC_MODULE_PATH to find likely libraries */
152 6 : gnc_module_system_refresh();
153 : }
154 :
155 : static inline gboolean
156 13 : exclude_module (const char* module)
157 : {
158 : /* These modules were converted to shared libraries and removed
159 : * from GnuCash 4. Simply dlopening them will introduce duplicate
160 : * symbols and cause unwanted initializations that may lead to a
161 : * crash. See https://bugs.gnucash.org/show_bug.cgi?id=798229.
162 : */
163 : static const char* excluded_modules[] =
164 : {
165 : "libgncmod-app-utils",
166 : "libgncmod-bi-import",
167 : "libgncmod-csv-export",
168 : "libgncmod-csv-import",
169 : "libgncmod-customer-import",
170 : "libgncmod-engine",
171 : "libgncmod-generic-import",
172 : "libgncmod-gnome-search",
173 : "libgncmod-gnome-utils",
174 : "libgncmod-html",
175 : "libgncmod-ledger-core",
176 : "libgncmod-locale-reports-us",
177 : "libgncmod-log-replay",
178 : "libgncmod-qif-import",
179 : "libgncmod-register-core",
180 : "libgncmod-register-gnome",
181 : "libgncmod-report-gnome",
182 : "libgncmod-report-system",
183 : "libgncmod-stylesheets",
184 : "libgncmod-tax-us"
185 : };
186 : static const unsigned len = G_N_ELEMENTS (excluded_modules);
187 13 : unsigned namelen = strchr(module, '.') ?
188 0 : strchr(module, '.') - module : strlen (module);
189 273 : for (unsigned i = 0; i < len; ++i)
190 260 : if (strncmp(excluded_modules[i], module, MIN(namelen, strlen(excluded_modules[i]))) == 0)
191 0 : return TRUE;
192 13 : return FALSE;
193 : }
194 :
195 : /*************************************************************
196 : * gnc_module_system_refresh
197 : * build the database of modules by looking through the
198 : * GNC_MODULE_PATH
199 : *************************************************************/
200 :
201 : void
202 6 : gnc_module_system_refresh(void)
203 : {
204 : GList * search_dirs;
205 : GList * current;
206 :
207 6 : if (!loaded_modules)
208 : {
209 0 : gnc_module_system_init();
210 : }
211 :
212 : /* get the GNC_MODULE_PATH and split it into directories */
213 6 : search_dirs = gnc_module_system_search_dirs();
214 :
215 : /* look in each search directory */
216 12 : for (current = search_dirs; current; current = current->next)
217 : {
218 6 : GDir *d = g_dir_open(current->data, 0, NULL);
219 6 : const gchar *dent = NULL;
220 6 : char * fullpath = NULL;
221 : GNCModuleInfo * info;
222 :
223 6 : if (!d) continue;
224 :
225 26 : while ((dent = g_dir_read_name(d)) != NULL)
226 : {
227 : /* is the file a loadable module? */
228 :
229 : /* Gotcha: On MacOS, G_MODULE_SUFFIX is defined as "so",
230 : * but if we do not build clean libtool modules with
231 : * "-module", we get dynamic libraries ending in .dylib
232 : * On Windows, all modules will move to bin/, so they will
233 : * be mixed with other libraries, such as gtk+. Adding a
234 : * prefix "libgncmod" filter will prevent the module loader
235 : * from loading other libraries. The filter should work on
236 : * other platforms.
237 : */
238 22 : if ((g_str_has_suffix(dent, "." G_MODULE_SUFFIX)
239 3 : || g_str_has_suffix(dent, ".dylib"))
240 19 : && g_str_has_prefix(dent, GNC_MODULE_PREFIX)
241 13 : && !exclude_module(dent))
242 : {
243 : /* get the full path name, then dlopen the library and see
244 : * if it has the appropriate symbols to be a gnc_module */
245 13 : fullpath = g_build_filename((const gchar *)(current->data),
246 : dent, (char*)NULL);
247 13 : info = gnc_module_get_info(fullpath);
248 :
249 13 : if (info)
250 : {
251 12 : module_info = g_list_prepend(module_info, info);
252 : }
253 13 : g_free(fullpath);
254 : }
255 : }
256 4 : g_dir_close(d);
257 :
258 : }
259 : /* free the search dir strings */
260 6 : g_list_free_full (search_dirs, g_free);
261 6 : }
262 :
263 :
264 : /*************************************************************
265 : * gnc_module_system_modinfo
266 : * return the list of module information
267 : *************************************************************/
268 :
269 : GList *
270 0 : gnc_module_system_modinfo(void)
271 : {
272 0 : if (!loaded_modules)
273 : {
274 0 : gnc_module_system_init();
275 : }
276 :
277 0 : return module_info;
278 : }
279 :
280 :
281 : /*
282 : * gnc_module_get_symbol
283 : * gets the munged symbol from the file
284 : */
285 : static gboolean
286 92 : gnc_module_get_symbol(GModule* gmodule, const char* symbol, gpointer res)
287 : {
288 : gchar** strs;
289 : gchar* munged_symbol;
290 : gchar *basename;
291 : gboolean ret;
292 :
293 92 : g_return_val_if_fail(gmodule, FALSE);
294 92 : g_return_val_if_fail(symbol, FALSE);
295 :
296 : /* Separate the file from its extension */
297 : /* Note: This currently does not work with versioned libtool dlls,
298 : * as they are named like libgncmodbaz-0.dll */
299 92 : basename = g_path_get_basename(g_module_name(gmodule));
300 92 : strs = g_strsplit(basename, ".", 2);
301 92 : g_free(basename);
302 :
303 : /* Translate any dashes to underscores */
304 92 : g_strdelimit(strs[0], "-", '_');
305 :
306 : /* Create the symbol <filename>_<symbol> and retrieve that symbol */
307 92 : munged_symbol = g_strdup_printf("%s_%s", strs[0], symbol);
308 92 : ret = g_module_symbol(gmodule, munged_symbol, res);
309 :
310 : /* printf("(%d) Looking for symbol %s\n", ret, munged_symbol); */
311 :
312 : /* Free everything */
313 92 : g_strfreev(strs);
314 92 : g_free(munged_symbol);
315 92 : return ret;
316 : }
317 :
318 : /*************************************************************
319 : * gnc_module_get_info
320 : * check a proposed gnc_module by looking for specific symbols in it;
321 : * if it's a gnc_module, return a struct describing it.
322 : *************************************************************/
323 :
324 : static GNCModuleInfo *
325 13 : gnc_module_get_info(const char * fullpath)
326 : {
327 : GModule *gmodule;
328 : gpointer modsysver;
329 13 : GNCModuleInfo *info = NULL;
330 : gpointer initfunc, pathfunc, descripfunc, iface, revision, age;
331 : gchar * (* f_path)(void);
332 : gchar * (* f_descrip)(void);
333 :
334 : /* g_debug("(init) dlopening '%s'\n", fullpath); */
335 13 : gmodule = g_module_open(fullpath, G_MODULE_BIND_LAZY);
336 13 : if (gmodule == NULL)
337 : {
338 0 : g_debug("Failed to dlopen() '%s': %s\n", fullpath, g_module_error());
339 0 : return NULL;
340 : }
341 :
342 : /* the modsysver tells us what the expected symbols and their
343 : * types are */
344 13 : if (!gnc_module_get_symbol(gmodule, "gnc_module_system_interface", &modsysver))
345 : {
346 : /* g_debug("Module '%s' does not contain 'gnc_module_system_interface'\n", */
347 : /* fullpath); */
348 0 : goto get_info_close;
349 : }
350 :
351 13 : if (*(int *)modsysver != 0)
352 : {
353 1 : g_warning("Module '%s' requires newer module system\n", fullpath);
354 1 : goto get_info_close;
355 : }
356 :
357 24 : if (!gnc_module_get_symbol(gmodule, "gnc_module_init", &initfunc) ||
358 24 : !gnc_module_get_symbol(gmodule, "gnc_module_path", &pathfunc) ||
359 24 : !gnc_module_get_symbol(gmodule, "gnc_module_description", &descripfunc) ||
360 24 : !gnc_module_get_symbol(gmodule, "gnc_module_current", &iface) ||
361 24 : !gnc_module_get_symbol(gmodule, "gnc_module_revision", &revision) ||
362 12 : !gnc_module_get_symbol(gmodule, "gnc_module_age", &age))
363 : {
364 0 : g_warning("Module '%s' does not match module signature\n", fullpath);
365 0 : goto get_info_close;
366 : }
367 :
368 : /* we have found a gnc_module. */
369 12 : info = g_new0(GNCModuleInfo, 1);
370 12 : f_path = pathfunc;
371 12 : f_descrip = descripfunc;
372 12 : info->module_path = f_path();
373 12 : info->module_description = f_descrip();
374 12 : info->module_filepath = g_strdup(fullpath);
375 12 : info->module_interface = *(int *)iface;
376 12 : info->module_age = *(int *)age;
377 12 : info->module_revision = *(int *)revision;
378 :
379 12 : g_module_make_resident(gmodule);
380 13 : get_info_close:
381 : /* g_debug("(init) closing '%s'\n", fullpath); */
382 13 : g_module_close(gmodule);
383 :
384 13 : return info;
385 : }
386 :
387 :
388 : /*************************************************************
389 : * gnc_module_locate
390 : * find the best matching module for the name, interface pair
391 : *************************************************************/
392 :
393 : static GNCModuleInfo *
394 14 : gnc_module_locate(const gchar * module_name, int iface)
395 : {
396 14 : GNCModuleInfo * best = NULL;
397 14 : GNCModuleInfo * current = NULL;
398 : GList * lptr;
399 :
400 14 : if (!loaded_modules)
401 : {
402 0 : gnc_module_system_init();
403 : }
404 :
405 62 : for (lptr = module_info; lptr; lptr = lptr->next)
406 : {
407 48 : current = lptr->data;
408 48 : if (!strcmp(module_name, current->module_path) &&
409 12 : (iface >= (current->module_interface - current->module_age)) &&
410 12 : (iface <= current->module_interface))
411 : {
412 10 : if (best)
413 : {
414 0 : if ((current->module_interface > best->module_interface) ||
415 0 : ((current->module_interface == best->module_interface) &&
416 0 : (current->module_age > best->module_age)) ||
417 0 : ((current->module_interface == best->module_interface) &&
418 0 : (current->module_age == best->module_age) &&
419 0 : (current->module_revision > best->module_revision)))
420 : {
421 0 : best = current;
422 : }
423 : }
424 : else
425 : {
426 10 : best = current;
427 : }
428 : }
429 : }
430 14 : return best;
431 : }
432 :
433 : static void
434 1 : list_loaded (gpointer k, gpointer v, gpointer data)
435 : {
436 1 : GList ** l = data;
437 1 : *l = g_list_prepend(*l, v);
438 1 : }
439 :
440 : static GNCLoadedModule *
441 7 : gnc_module_check_loaded(const char * module_name, gint iface)
442 : {
443 7 : GNCModuleInfo * modinfo = gnc_module_locate(module_name, iface);
444 7 : GList * modules = NULL;
445 7 : GList * p = NULL;
446 7 : GNCLoadedModule * rv = NULL;
447 :
448 7 : if (modinfo == NULL)
449 : {
450 2 : return NULL;
451 : }
452 :
453 5 : if (!loaded_modules)
454 : {
455 0 : gnc_module_system_init();
456 : }
457 :
458 : /* turn the loaded-modules table into a list */
459 5 : g_hash_table_foreach(loaded_modules, list_loaded, &modules);
460 :
461 : /* walk the list to see if the file we want is already open */
462 6 : for (p = modules; p; p = p->next)
463 : {
464 1 : GNCLoadedModule * lm = p->data;
465 1 : if (!strcmp(lm->filename, modinfo->module_filepath))
466 : {
467 0 : rv = lm;
468 0 : break;
469 : }
470 : }
471 5 : g_list_free(modules);
472 5 : return rv;
473 : }
474 :
475 :
476 : /*************************************************************
477 : * gnc_module_load
478 : * Ensure that the module named by "module_name" is loaded.
479 : *************************************************************/
480 :
481 : static GNCModule
482 7 : gnc_module_load_common(const char * module_name, gint iface, gboolean optional)
483 : {
484 :
485 : GNCLoadedModule * info;
486 : GModule * gmodule;
487 : GNCModuleInfo * modinfo;
488 :
489 7 : g_debug ("module_name: %s", module_name);
490 :
491 7 : if (!loaded_modules)
492 : {
493 0 : gnc_module_system_init();
494 : }
495 :
496 7 : info = gnc_module_check_loaded(module_name, iface);
497 :
498 : /* if the module's already loaded, just increment its use count.
499 : * otherwise, load it and check for the initializer
500 : * "gnc_module_init". if we find that, assume it's a gnucash module
501 : * and run the function. */
502 :
503 7 : if (info)
504 : {
505 : /* module already loaded ... call the init thunk */
506 0 : if (info->init_func)
507 : {
508 0 : if (info->init_func(info->load_count))
509 : {
510 0 : info->load_count++;
511 0 : g_debug ("module %s already loaded", module_name);
512 0 : return info;
513 : }
514 : else
515 : {
516 0 : g_warning ("module init failed: %s", module_name);
517 0 : return NULL;
518 : }
519 : }
520 : else
521 : {
522 0 : g_warning ("module has no init func: %s", module_name);
523 0 : return NULL;
524 : }
525 : /* NOTREACHED */
526 : g_error("internal error");
527 : return NULL;
528 : }
529 :
530 7 : modinfo = gnc_module_locate(module_name, iface);
531 7 : if (!modinfo)
532 : {
533 2 : if (optional)
534 : {
535 0 : g_message ("Could not locate optional module %s interface v.%d",
536 : module_name, iface);
537 : }
538 : else
539 : {
540 2 : g_warning ("Could not locate module %s interface v.%d",
541 : module_name, iface);
542 : }
543 2 : return NULL;
544 : }
545 :
546 : /* if (modinfo) */
547 : /* g_debug("(init) loading '%s' from '%s'\n", module_name, */
548 : /* modinfo->module_filepath); */
549 :
550 5 : if ((gmodule = g_module_open(modinfo->module_filepath, 0)) != NULL)
551 : {
552 : gpointer initfunc;
553 :
554 5 : if (gnc_module_get_symbol(gmodule, "gnc_module_init", &initfunc))
555 : {
556 : /* stick it in the hash table */
557 5 : info = g_new0(GNCLoadedModule, 1);
558 5 : info->gmodule = gmodule;
559 5 : info->filename = g_strdup(modinfo->module_filepath);
560 5 : info->load_count = 1;
561 5 : info->init_func = initfunc;
562 5 : g_hash_table_insert(loaded_modules, info, info);
563 :
564 : /* now call its init function. this should load any dependent
565 : * modules, too. If it doesn't return TRUE unload the module. */
566 5 : if (!info->init_func(0))
567 : {
568 : /* init failed. unload the module. */
569 1 : g_warning ("Initialization failed for module %s", module_name);
570 1 : g_hash_table_remove(loaded_modules, info);
571 1 : g_free(info->filename);
572 1 : g_free(info);
573 : /* g_module_close(module); */
574 1 : return NULL;
575 : }
576 :
577 4 : return info;
578 : }
579 : else
580 : {
581 0 : g_warning ("Module %s (%s) is not a gnc-module.\n", module_name,
582 : modinfo->module_filepath);
583 : //lt_dlclose(handle);
584 : }
585 0 : return info;
586 : }
587 :
588 0 : g_warning ("Failed to open module %s: %s\n", module_name, g_module_error());
589 :
590 0 : return NULL;
591 : }
592 :
593 :
594 : GNCModule
595 7 : gnc_module_load(const char * module_name, gint iface)
596 : {
597 7 : return gnc_module_load_common(module_name, iface, FALSE);
598 : }
599 :
600 : GNCModule
601 0 : gnc_module_load_optional(const char * module_name, gint iface)
602 : {
603 0 : return gnc_module_load_common(module_name, iface, TRUE);
604 : }
605 :
606 : /*************************************************************
607 : * gnc_module_unload
608 : * unload a module (only actually unload it if the use count goes to 0)
609 : *************************************************************/
610 :
611 : int
612 2 : gnc_module_unload(GNCModule module)
613 : {
614 : GNCLoadedModule * info;
615 :
616 2 : if (!loaded_modules)
617 : {
618 0 : gnc_module_system_init();
619 : }
620 :
621 2 : if ((info = g_hash_table_lookup(loaded_modules, module)) != NULL)
622 : {
623 : gpointer unload_thunk;
624 2 : int unload_val = TRUE;
625 :
626 2 : info->load_count--;
627 2 : if (gnc_module_get_symbol(info->gmodule, "gnc_module_end", &unload_thunk))
628 : {
629 0 : int (* thunk)(int) = unload_thunk;
630 0 : unload_val = thunk(info->load_count);
631 : }
632 :
633 : /* actually unload the module if necessary */
634 2 : if (info->load_count == 0)
635 : {
636 : /* now close the module and free the struct */
637 : /* g_debug("(unload) closing %s\n", info->filename); */
638 : /* g_module_close(info->gmodule); */
639 2 : g_hash_table_remove(loaded_modules, module);
640 2 : g_free(info);
641 : }
642 2 : return unload_val;
643 : }
644 : else
645 : {
646 0 : g_warning ("Failed to unload module %p (it is not loaded)\n", module);
647 0 : return 0;
648 : }
649 : }
650 :
|