Merge branch 'master' of ssh://gnunet.org/gnunet
[oweals/gnunet.git] / src / util / plugin.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2002-2013 GNUnet e.V.
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file util/plugin.c
23  * @brief Methods to access plugins
24  * @author Christian Grothoff
25  */
26
27 #include "platform.h"
28 #include <ltdl.h>
29 #include "gnunet_util_lib.h"
30
31 #define LOG(kind,...) GNUNET_log_from (kind, "util-plugin", __VA_ARGS__)
32
33 /**
34  * Linked list of active plugins.
35  */
36 struct PluginList
37 {
38   /**
39    * This is a linked list.
40    */
41   struct PluginList *next;
42
43   /**
44    * Name of the library.
45    */
46   char *name;
47
48   /**
49    * System handle.
50    */
51   void *handle;
52 };
53
54
55 /**
56  * Have we been initialized?
57  */
58 static int initialized;
59
60 /**
61  * Libtool search path before we started.
62  */
63 static char *old_dlsearchpath;
64
65 /**
66  * List of plugins we have loaded.
67  */
68 static struct PluginList *plugins;
69
70
71 /**
72  * Setup libtool paths.
73  */
74 static void
75 plugin_init ()
76 {
77   int err;
78   const char *opath;
79   char *path;
80   char *cpath;
81
82   err = lt_dlinit ();
83   if (err > 0)
84   {
85     FPRINTF (stderr,
86              _("Initialization of plugin mechanism failed: %s!\n"),
87              lt_dlerror ());
88     return;
89   }
90   opath = lt_dlgetsearchpath ();
91   if (NULL != opath)
92     old_dlsearchpath = GNUNET_strdup (opath);
93   path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
94   if (NULL != path)
95   {
96     if (NULL != opath)
97     {
98       GNUNET_asprintf (&cpath, "%s:%s", opath, path);
99       lt_dlsetsearchpath (cpath);
100       GNUNET_free (path);
101       GNUNET_free (cpath);
102     }
103     else
104     {
105       lt_dlsetsearchpath (path);
106       GNUNET_free (path);
107     }
108   }
109 }
110
111
112 /**
113  * Shutdown libtool.
114  */
115 static void
116 plugin_fini ()
117 {
118   lt_dlsetsearchpath (old_dlsearchpath);
119   if (NULL != old_dlsearchpath)
120   {
121     GNUNET_free (old_dlsearchpath);
122     old_dlsearchpath = NULL;
123   }
124   lt_dlexit ();
125 }
126
127
128 /**
129  * Lookup a function in the plugin.
130  *
131  * @param plug the plugin to check
132  * @param name name of the symbol to look for
133  * @return NULL if the symbol was not found
134  */
135 static GNUNET_PLUGIN_Callback
136 resolve_function (struct PluginList *plug,
137                   const char *name)
138 {
139   char *initName;
140   void *mptr;
141
142   GNUNET_asprintf (&initName,
143                    "_%s_%s",
144                    plug->name,
145                    name);
146   mptr = lt_dlsym (plug->handle, &initName[1]);
147   if (NULL == mptr)
148     mptr = lt_dlsym (plug->handle, initName);
149   if (NULL == mptr)
150     LOG (GNUNET_ERROR_TYPE_ERROR,
151          _("`%s' failed to resolve method '%s' with error: %s\n"),
152          "lt_dlsym",
153          &initName[1], lt_dlerror ());
154   GNUNET_free (initName);
155   return mptr;
156 }
157
158
159 /**
160  * Test if a plugin exists.
161  *
162  * Note that the library must export a symbol called
163  * `library_name_init` for the test to succeed.
164  *
165  * @param library_name name of the plugin to test if it is installed
166  * @return #GNUNET_YES if the plugin exists, #GNUNET_NO if not
167  */
168 int
169 GNUNET_PLUGIN_test (const char *library_name)
170 {
171   void *libhandle;
172   GNUNET_PLUGIN_Callback init;
173   struct PluginList plug;
174
175   if (! initialized)
176   {
177     initialized = GNUNET_YES;
178     plugin_init ();
179   }
180   libhandle = lt_dlopenext (library_name);
181   if (NULL == libhandle)
182     return GNUNET_NO;
183   plug.handle = libhandle;
184   plug.name = (char *) library_name;
185   init = resolve_function (&plug, "init");
186   if (NULL == init)
187   {
188     GNUNET_break (0);
189     lt_dlclose (libhandle);
190     return GNUNET_NO;
191   }
192   lt_dlclose (libhandle);
193   return GNUNET_YES;
194 }
195
196
197 /**
198  * Setup plugin (runs the `init` callback and returns whatever `init`
199  * returned).  If `init` returns NULL, the plugin is unloaded.
200  *
201  * Note that the library must export symbols called
202  * `library_name_init` and `library_name_done`.  These will be called
203  * when the library is loaded and unloaded respectively.
204  *
205  * @param library_name name of the plugin to load
206  * @param arg argument to the plugin initialization function
207  * @return whatever the initialization function returned
208  */
209 void *
210 GNUNET_PLUGIN_load (const char *library_name, void *arg)
211 {
212   void *libhandle;
213   struct PluginList *plug;
214   GNUNET_PLUGIN_Callback init;
215   void *ret;
216
217   if (!initialized)
218   {
219     initialized = GNUNET_YES;
220     plugin_init ();
221   }
222   libhandle = lt_dlopenext (library_name);
223   if (libhandle == NULL)
224   {
225     LOG (GNUNET_ERROR_TYPE_ERROR,
226          _("`%s' failed for library `%s' with error: %s\n"),
227          "lt_dlopenext",
228          library_name, lt_dlerror ());
229     return NULL;
230   }
231   plug = GNUNET_new (struct PluginList);
232   plug->handle = libhandle;
233   plug->name = GNUNET_strdup (library_name);
234   plug->next = plugins;
235   plugins = plug;
236   init = resolve_function (plug, "init");
237   if ((init == NULL) || (NULL == (ret = init (arg))))
238   {
239     lt_dlclose (libhandle);
240     GNUNET_free (plug->name);
241     plugins = plug->next;
242     GNUNET_free (plug);
243     return NULL;
244   }
245   return ret;
246 }
247
248
249 /**
250  * Unload plugin (runs the `done` callback and returns whatever `done`
251  * returned).  The plugin is then unloaded.
252  *
253  * @param library_name name of the plugin to unload
254  * @param arg argument to the plugin shutdown function
255  * @return whatever the shutdown function returned
256  */
257 void *
258 GNUNET_PLUGIN_unload (const char *library_name,
259                       void *arg)
260 {
261   struct PluginList *pos;
262   struct PluginList *prev;
263   GNUNET_PLUGIN_Callback done;
264   void *ret;
265
266   prev = NULL;
267   pos = plugins;
268   while ((NULL != pos) && (0 != strcmp (pos->name, library_name)))
269   {
270     prev = pos;
271     pos = pos->next;
272   }
273   if (NULL == pos)
274     return NULL;
275
276   done = resolve_function (pos, "done");
277   ret = NULL;
278   if (NULL != done)
279     ret = done (arg);
280   if (NULL == prev)
281     plugins = pos->next;
282   else
283     prev->next = pos->next;
284   lt_dlclose (pos->handle);
285   GNUNET_free (pos->name);
286   GNUNET_free (pos);
287   if (NULL == plugins)
288   {
289     plugin_fini ();
290     initialized = GNUNET_NO;
291   }
292   return ret;
293 }
294
295
296 /**
297  * Closure for #find_libraries().
298  */
299 struct LoadAllContext
300 {
301   /**
302    * Prefix the plugin names we find have to match.
303    */
304   const char *basename;
305
306   /**
307    * Argument to give to 'init' when loading the plugin.
308    */
309   void *arg;
310
311   /**
312    * Function to call for each plugin.
313    */
314   GNUNET_PLUGIN_LoaderCallback cb;
315
316   /**
317    * Closure for @e cb
318    */
319   void *cb_cls;
320 };
321
322
323 /**
324  * Function called on each plugin in the directory.  Loads
325  * the plugins that match the given basename.
326  *
327  * @param cls the `struct LoadAllContext` describing which
328  *            plugins to load and what to do with them
329  * @param filename name of a plugin library to check
330  * @return #GNUNET_OK (continue loading)
331  */
332 static int
333 find_libraries (void *cls, const char *filename)
334 {
335   struct LoadAllContext *lac = cls;
336   const char *slashpos;
337   const char *libname;
338   char *basename;
339   char *dot;
340   void *lib_ret;
341   size_t n;
342
343   libname = filename;
344   while (NULL != (slashpos = strstr (libname, DIR_SEPARATOR_STR)))
345     libname = slashpos + 1;
346   n = strlen (libname);
347   if (0 != strncmp (lac->basename, libname, strlen (lac->basename)))
348     return GNUNET_OK;           /* wrong name */
349   if ((n > 3) && (0 == strcmp (&libname[n - 3], ".la")))
350     return GNUNET_OK;           /* .la file */
351   basename = GNUNET_strdup (libname);
352   if (NULL != (dot = strstr (basename, ".")))
353     *dot = '\0';
354   lib_ret = GNUNET_PLUGIN_load (basename, lac->arg);
355   if (NULL != lib_ret)
356     lac->cb (lac->cb_cls, basename, lib_ret);
357   GNUNET_free (basename);
358   return GNUNET_OK;
359 }
360
361
362 /**
363  * Load all compatible plugins with the given base name.
364  *
365  * Note that the library must export symbols called
366  * `basename_ANYTHING_init` and `basename_ANYTHING__done`.  These will
367  * be called when the library is loaded and unloaded respectively.
368  *
369  * @param basename basename of the plugins to load
370  * @param arg argument to the plugin initialization function
371  * @param cb function to call for each plugin found
372  * @param cb_cls closure for @a cb
373  */
374 void
375 GNUNET_PLUGIN_load_all (const char *basename, void *arg,
376                         GNUNET_PLUGIN_LoaderCallback cb, void *cb_cls)
377 {
378   struct LoadAllContext lac;
379   char *path;
380
381   path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
382   if (NULL == path)
383   {
384     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
385                 _("Could not determine plugin installation path.\n"));
386     return;
387   }
388   lac.basename = basename;
389   lac.arg = arg;
390   lac.cb = cb;
391   lac.cb_cls = cb_cls;
392   GNUNET_DISK_directory_scan (path, &find_libraries, &lac);
393   GNUNET_free (path);
394 }
395
396
397 /* end of plugin.c */