GnuCash  5.6-150-g038405b370+
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 
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;
51 
52 typedef struct
53 {
54  GModule * gmodule;
55  gchar * filename;
56  int load_count;
57  GNCModuleInfo * info;
58  int (* init_func)(int refcount);
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 gnc_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  *************************************************************/
143 void
144 gnc_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 
155 static inline gboolean
156 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  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 
201 void
202 gnc_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 
269 GList *
270 gnc_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  */
285 static gboolean
286 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  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 
324 static GNCModuleInfo *
325 gnc_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);
380 get_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 
393 static GNCModuleInfo *
394 gnc_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 
433 static void
434 list_loaded (gpointer k, gpointer v, gpointer data)
435 {
436  GList ** l = data;
437  *l = g_list_prepend(*l, v);
438 }
439 
440 static GNCLoadedModule *
441 gnc_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 
481 static GNCModule
482 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  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 
594 GNCModule
595 gnc_module_load(const char * module_name, gint iface)
596 {
597  return gnc_module_load_common(module_name, iface, FALSE);
598 }
599 
600 GNCModule
601 gnc_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 
611 int
612 gnc_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