GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-module.c
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
39static GHashTable * loaded_modules = NULL;
40static GList * module_info = NULL;
41
42typedef 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;
51
52typedef struct
53{
54 GModule * gmodule;
55 gchar * filename;
56 int load_count;
57 GNCModuleInfo * info;
58 int (* init_func)(int refcount);
60
61static 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
68static GList *
69gnc_module_system_search_dirs(void)
70{
71 const char *spath = g_getenv("GNC_MODULE_PATH");
72 GList * list = NULL;
73 GString * token = g_string_new(NULL);
74 int escchar = 0;
75 const char *cpos;
76
77 if (!spath)
78 {
79 spath = DEFAULT_MODULE_PATH;
80 }
81
82 for (cpos = spath; *cpos; cpos++)
83 {
84 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 case '\\':
94 if (!escchar)
95 {
96 escchar = TRUE;
97 }
98 else
99 {
100 g_string_append_c(token, *cpos);
101 escchar = FALSE;
102 }
103 break;
104#endif
105
106 /* This is ':' on UNIX machines and ';' under Windows. */
107 case G_SEARCHPATH_SEPARATOR:
108 if (!escchar)
109 {
110 char *token_str = g_string_free (token, FALSE);
111 list = g_list_append (list, token_str);
112 token = g_string_new(NULL);
113 }
114 else
115 {
116 g_string_append_c(token, *cpos);
117 escchar = FALSE;
118 }
119 break;
120
121 default:
122 g_string_append_c(token, *cpos);
123 escchar = FALSE;
124 break;
125 }
126 }
127 if (token->len)
128 {
129 char *token_str = g_string_free (token, FALSE);
130 list = g_list_append(list, token_str);
131 }
132 else
133 {
134 g_string_free(token, TRUE);
135 }
136 return list;
137}
138
139/*************************************************************
140 * gnc_module_system_init
141 * initialize the module system
142 *************************************************************/
143void
144gnc_module_system_init(void)
145{
146 if (loaded_modules)
147 return;
148
149 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 gnc_module_system_refresh();
153}
154
155static inline gboolean
156exclude_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 unsigned namelen = strchr(module, '.') ?
188 strchr(module, '.') - module : strlen (module);
189 for (unsigned i = 0; i < len; ++i)
190 if (strncmp(excluded_modules[i], module, MIN(namelen, strlen(excluded_modules[i]))) == 0)
191 return TRUE;
192 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
201void
202gnc_module_system_refresh(void)
203{
204 GList * search_dirs;
205 GList * current;
206
207 if (!loaded_modules)
208 {
209 gnc_module_system_init();
210 }
211
212 /* get the GNC_MODULE_PATH and split it into directories */
213 search_dirs = gnc_module_system_search_dirs();
214
215 /* look in each search directory */
216 for (current = search_dirs; current; current = current->next)
217 {
218 GDir *d = g_dir_open(current->data, 0, NULL);
219 const gchar *dent = NULL;
220 char * fullpath = NULL;
221 GNCModuleInfo * info;
222
223 if (!d) continue;
224
225 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 if ((g_str_has_suffix(dent, "." G_MODULE_SUFFIX)
239 || g_str_has_suffix(dent, ".dylib"))
240 && g_str_has_prefix(dent, GNC_MODULE_PREFIX)
241 && !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 fullpath = g_build_filename((const gchar *)(current->data),
246 dent, (char*)NULL);
247 info = gnc_module_get_info(fullpath);
248
249 if (info)
250 {
251 module_info = g_list_prepend(module_info, info);
252 }
253 g_free(fullpath);
254 }
255 }
256 g_dir_close(d);
257
258 }
259 /* free the search dir strings */
260 g_list_free_full (search_dirs, g_free);
261}
262
263
264/*************************************************************
265 * gnc_module_system_modinfo
266 * return the list of module information
267 *************************************************************/
268
269GList *
270gnc_module_system_modinfo(void)
271{
272 if (!loaded_modules)
273 {
274 gnc_module_system_init();
275 }
276
277 return module_info;
278}
279
280
281/*
282 * gnc_module_get_symbol
283 * gets the munged symbol from the file
284 */
285static gboolean
286gnc_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 g_return_val_if_fail(gmodule, FALSE);
294 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 basename = g_path_get_basename(g_module_name(gmodule));
300 strs = g_strsplit(basename, ".", 2);
301 g_free(basename);
302
303 /* Translate any dashes to underscores */
304 g_strdelimit(strs[0], "-", '_');
305
306 /* Create the symbol <filename>_<symbol> and retrieve that symbol */
307 munged_symbol = g_strdup_printf("%s_%s", strs[0], symbol);
308 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 g_strfreev(strs);
314 g_free(munged_symbol);
315 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
324static GNCModuleInfo *
325gnc_module_get_info(const char * fullpath)
326{
327 GModule *gmodule;
328 gpointer modsysver;
329 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 gmodule = g_module_open(fullpath, G_MODULE_BIND_LAZY);
336 if (gmodule == NULL)
337 {
338 g_debug("Failed to dlopen() '%s': %s\n", fullpath, g_module_error());
339 return NULL;
340 }
341
342 /* the modsysver tells us what the expected symbols and their
343 * types are */
344 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 goto get_info_close;
349 }
350
351 if (*(int *)modsysver != 0)
352 {
353 g_warning("Module '%s' requires newer module system\n", fullpath);
354 goto get_info_close;
355 }
356
357 if (!gnc_module_get_symbol(gmodule, "gnc_module_init", &initfunc) ||
358 !gnc_module_get_symbol(gmodule, "gnc_module_path", &pathfunc) ||
359 !gnc_module_get_symbol(gmodule, "gnc_module_description", &descripfunc) ||
360 !gnc_module_get_symbol(gmodule, "gnc_module_current", &iface) ||
361 !gnc_module_get_symbol(gmodule, "gnc_module_revision", &revision) ||
362 !gnc_module_get_symbol(gmodule, "gnc_module_age", &age))
363 {
364 g_warning("Module '%s' does not match module signature\n", fullpath);
365 goto get_info_close;
366 }
367
368 /* we have found a gnc_module. */
369 info = g_new0(GNCModuleInfo, 1);
370 f_path = pathfunc;
371 f_descrip = descripfunc;
372 info->module_path = f_path();
373 info->module_description = f_descrip();
374 info->module_filepath = g_strdup(fullpath);
375 info->module_interface = *(int *)iface;
376 info->module_age = *(int *)age;
377 info->module_revision = *(int *)revision;
378
379 g_module_make_resident(gmodule);
380get_info_close:
381 /* g_debug("(init) closing '%s'\n", fullpath); */
382 g_module_close(gmodule);
383
384 return info;
385}
386
387
388/*************************************************************
389 * gnc_module_locate
390 * find the best matching module for the name, interface pair
391 *************************************************************/
392
393static GNCModuleInfo *
394gnc_module_locate(const gchar * module_name, int iface)
395{
396 GNCModuleInfo * best = NULL;
397 GNCModuleInfo * current = NULL;
398 GList * lptr;
399
400 if (!loaded_modules)
401 {
402 gnc_module_system_init();
403 }
404
405 for (lptr = module_info; lptr; lptr = lptr->next)
406 {
407 current = lptr->data;
408 if (!strcmp(module_name, current->module_path) &&
409 (iface >= (current->module_interface - current->module_age)) &&
410 (iface <= current->module_interface))
411 {
412 if (best)
413 {
414 if ((current->module_interface > best->module_interface) ||
415 ((current->module_interface == best->module_interface) &&
416 (current->module_age > best->module_age)) ||
417 ((current->module_interface == best->module_interface) &&
418 (current->module_age == best->module_age) &&
419 (current->module_revision > best->module_revision)))
420 {
421 best = current;
422 }
423 }
424 else
425 {
426 best = current;
427 }
428 }
429 }
430 return best;
431}
432
433static void
434list_loaded (gpointer k, gpointer v, gpointer data)
435{
436 GList ** l = data;
437 *l = g_list_prepend(*l, v);
438}
439
440static GNCLoadedModule *
441gnc_module_check_loaded(const char * module_name, gint iface)
442{
443 GNCModuleInfo * modinfo = gnc_module_locate(module_name, iface);
444 GList * modules = NULL;
445 GList * p = NULL;
446 GNCLoadedModule * rv = NULL;
447
448 if (modinfo == NULL)
449 {
450 return NULL;
451 }
452
453 if (!loaded_modules)
454 {
455 gnc_module_system_init();
456 }
457
458 /* turn the loaded-modules table into a list */
459 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 for (p = modules; p; p = p->next)
463 {
464 GNCLoadedModule * lm = p->data;
465 if (!strcmp(lm->filename, modinfo->module_filepath))
466 {
467 rv = lm;
468 break;
469 }
470 }
471 g_list_free(modules);
472 return rv;
473}
474
475
476/*************************************************************
477 * gnc_module_load
478 * Ensure that the module named by "module_name" is loaded.
479 *************************************************************/
480
481static GNCModule
482gnc_module_load_common(const char * module_name, gint iface, gboolean optional)
483{
484
485 GNCLoadedModule * info;
486 GModule * gmodule;
487 GNCModuleInfo * modinfo;
488
489 g_debug ("module_name: %s", module_name);
490
491 if (!loaded_modules)
492 {
493 gnc_module_system_init();
494 }
495
496 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 if (info)
504 {
505 /* module already loaded ... call the init thunk */
506 if (info->init_func)
507 {
508 if (info->init_func(info->load_count))
509 {
510 info->load_count++;
511 g_debug ("module %s already loaded", module_name);
512 return info;
513 }
514 else
515 {
516 g_warning ("module init failed: %s", module_name);
517 return NULL;
518 }
519 }
520 else
521 {
522 g_warning ("module has no init func: %s", module_name);
523 return NULL;
524 }
525 /* NOTREACHED */
526 g_error("internal error");
527 return NULL;
528 }
529
530 modinfo = gnc_module_locate(module_name, iface);
531 if (!modinfo)
532 {
533 if (optional)
534 {
535 g_message ("Could not locate optional module %s interface v.%d",
536 module_name, iface);
537 }
538 else
539 {
540 g_warning ("Could not locate module %s interface v.%d",
541 module_name, iface);
542 }
543 return NULL;
544 }
545
546 /* if (modinfo) */
547 /* g_debug("(init) loading '%s' from '%s'\n", module_name, */
548 /* modinfo->module_filepath); */
549
550 if ((gmodule = g_module_open(modinfo->module_filepath, 0)) != NULL)
551 {
552 gpointer initfunc;
553
554 if (gnc_module_get_symbol(gmodule, "gnc_module_init", &initfunc))
555 {
556 /* stick it in the hash table */
557 info = g_new0(GNCLoadedModule, 1);
558 info->gmodule = gmodule;
559 info->filename = g_strdup(modinfo->module_filepath);
560 info->load_count = 1;
561 info->init_func = initfunc;
562 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 if (!info->init_func(0))
567 {
568 /* init failed. unload the module. */
569 g_warning ("Initialization failed for module %s", module_name);
570 g_hash_table_remove(loaded_modules, info);
571 g_free(info->filename);
572 g_free(info);
573 /* g_module_close(module); */
574 return NULL;
575 }
576
577 return info;
578 }
579 else
580 {
581 g_warning ("Module %s (%s) is not a gnc-module.\n", module_name,
582 modinfo->module_filepath);
583 //lt_dlclose(handle);
584 }
585 return info;
586 }
587
588 g_warning ("Failed to open module %s: %s\n", module_name, g_module_error());
589
590 return NULL;
591}
592
593
594GNCModule
595gnc_module_load(const char * module_name, gint iface)
596{
597 return gnc_module_load_common(module_name, iface, FALSE);
598}
599
600GNCModule
601gnc_module_load_optional(const char * module_name, gint iface)
602{
603 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
611int
612gnc_module_unload(GNCModule module)
613{
614 GNCLoadedModule * info;
615
616 if (!loaded_modules)
617 {
618 gnc_module_system_init();
619 }
620
621 if ((info = g_hash_table_lookup(loaded_modules, module)) != NULL)
622 {
623 gpointer unload_thunk;
624 int unload_val = TRUE;
625
626 info->load_count--;
627 if (gnc_module_get_symbol(info->gmodule, "gnc_module_end", &unload_thunk))
628 {
629 int (* thunk)(int) = unload_thunk;
630 unload_val = thunk(info->load_count);
631 }
632
633 /* actually unload the module if necessary */
634 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 g_hash_table_remove(loaded_modules, module);
640 g_free(info);
641 }
642 return unload_val;
643 }
644 else
645 {
646 g_warning ("Failed to unload module %p (it is not loaded)\n", module);
647 return 0;
648 }
649}
650