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