Hello,

a few months ago, I had submitted a patch[0] to solve a problem
involving loading plugins and third-party applications, which was not
applied for a number of reasons.

I hoped to submit another patch sooner than now, but unfortunately I
ended up not being able to. Anyway, the attached patch should be more in
line with the solution suggested in the responses of [0].

It also contains a small change to the `datacache.c' file, as it's the
only part of the codebase using `GNUNET_PLUGIN_load' (as far as I
understood) which is exposed to third-party applications. To avoid the
same issues which the new function introduced by the patch tries to
solve, I added a few lines which are essentially the "expansion" of the
function, as appropriate for the surrounding code.

Unfortunately, I couldn't implement one of the proposed changes, that
is, making the context thread-local.  I know that there is a macro which
simply expands to the built-in `__thread' (where appliable) and that
there are a few uses in the code, but I'm still not too sure how it
works within GNUnet, so I prefer to submit this patch while I try to
understand the rest.

Thanks,
A.V.

[0] https://lists.gnu.org/archive/html/gnunet-developers/2020-12/msg00008.html

>From d46844972f2baa061ef7aa333eb11a8755f22898 Mon Sep 17 00:00:00 2001
From: Alessio Vanni <vanni...@firemail.cc>
Date: Sun, 4 Apr 2021 16:51:09 +0200
Subject: [PATCH] Implement function to load plugins within a specific context

When `GNUNET_OS_init' is called to change the application project data,
lazy-loading plugins will fail as it will not find the requested files.

The function will temporarily swap the project data with the argument value
and will search for plugins, within the installation directory tree inferred
from that structure.

Applications can still use `GNUNET_PLUGIN_load_all' to load their plugins from
within their own installation directory tree, though services are recommended
to use the `in_context' version to avoid falling in the same pit.
---
 src/block/block.c                |  9 +++++----
 src/datacache/datacache.c        | 24 +++++++++++++++++------
 src/gnsrecord/gnsrecord.c        | 13 +++++--------
 src/include/gnunet_plugin_lib.h  | 22 +++++++++++++++++++++
 src/reclaim/reclaim_attribute.c  |  9 +++++----
 src/reclaim/reclaim_credential.c |  9 +++++----
 src/rest/gnunet-rest-server.c    |  9 +++++----
 src/util/plugin.c                | 33 ++++++++++++++++++++++++++++++++
 8 files changed, 98 insertions(+), 30 deletions(-)

diff --git a/src/block/block.c b/src/block/block.c
index 5abe64e69..975c0f747 100644
--- a/src/block/block.c
+++ b/src/block/block.c
@@ -134,10 +134,11 @@ GNUNET_BLOCK_context_create (const struct GNUNET_CONFIGURATION_Handle *cfg)
 
   ctx = GNUNET_new (struct GNUNET_BLOCK_Context);
   ctx->cfg = cfg;
-  GNUNET_PLUGIN_load_all ("libgnunet_plugin_block_",
-                          (void *) cfg,
-                          &add_plugin,
-                          ctx);
+  GNUNET_PLUGIN_load_all_in_context (GNUNET_OS_project_data_default (),
+                                     "libgnunet_plugin_block_",
+                                     (void *) cfg,
+                                     &add_plugin,
+                                     ctx);
   return ctx;
 }
 
diff --git a/src/datacache/datacache.c b/src/datacache/datacache.c
index 1ae228b86..5fc5a7481 100644
--- a/src/datacache/datacache.c
+++ b/src/datacache/datacache.c
@@ -138,6 +138,7 @@ GNUNET_DATACACHE_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
   struct GNUNET_DATACACHE_Handle *ret;
   char *libname;
   char *name;
+  const struct GNUNET_OS_ProjectData *pd;
 
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_size (cfg, section, "QUOTA", &quota))
@@ -190,14 +191,25 @@ GNUNET_DATACACHE_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
   GNUNET_asprintf (&libname, "libgnunet_plugin_datacache_%s", name);
   ret->short_name = name;
   ret->lib_name = libname;
+  /* Load the plugin within GNUnet's default context */
+  pd = GNUNET_OS_project_data_get ();
+  GNUNET_OS_init(GNUNET_OS_project_data_default ());
   ret->api = GNUNET_PLUGIN_load (libname, &ret->env);
-  if (ret->api == NULL)
+  GNUNET_OS_init(pd);
+  if (NULL == ret->api)
   {
-    LOG (GNUNET_ERROR_TYPE_ERROR,
-         _ ("Failed to load datacache plugin for `%s'\n"),
-         name);
-    GNUNET_DATACACHE_destroy (ret);
-    return NULL;
+    /* Try to load the plugin within the application's context
+       This normally happens when the application is not GNUnet itself but a
+       third party; inside GNUnet this is effectively a double failure. */
+    ret->api = GNUNET_PLUGIN_load (libname, &ret->env);
+    if (NULL == ret->api)
+    {
+      LOG (GNUNET_ERROR_TYPE_ERROR,
+           _ ("Failed to load datacache plugin for `%s'\n"),
+           name);
+      GNUNET_DATACACHE_destroy (ret);
+      return NULL;
+    }
   }
   return ret;
 }
diff --git a/src/gnsrecord/gnsrecord.c b/src/gnsrecord/gnsrecord.c
index 8d5a6d95b..31749a629 100644
--- a/src/gnsrecord/gnsrecord.c
+++ b/src/gnsrecord/gnsrecord.c
@@ -102,15 +102,12 @@ init ()
   if (1 == once)
     return;
   once = 1;
-  const struct GNUNET_OS_ProjectData *pd = GNUNET_OS_project_data_get ();
-  const struct GNUNET_OS_ProjectData *dpd = GNUNET_OS_project_data_default ();
 
-  if (pd != dpd)
-    GNUNET_OS_init (dpd);
-  GNUNET_PLUGIN_load_all ("libgnunet_plugin_gnsrecord_", NULL,
-                          &add_plugin, NULL);
-  if (pd != dpd)
-    GNUNET_OS_init (pd);
+  GNUNET_PLUGIN_load_all_in_context (GNUNET_OS_project_data_default (),
+                                     "libgnunet_plugin_gnsrecord_",
+                                     NULL,
+                                     &add_plugin,
+                                     NULL);
 }
 
 
diff --git a/src/include/gnunet_plugin_lib.h b/src/include/gnunet_plugin_lib.h
index ffffcab8b..d265eb343 100644
--- a/src/include/gnunet_plugin_lib.h
+++ b/src/include/gnunet_plugin_lib.h
@@ -121,6 +121,28 @@ GNUNET_PLUGIN_load_all (const char *basename,
                         void *cb_cls);
 
 
+/**
+ * Load all compatible plugins with the given base name while inside the given
+ * context (i.e. a specific project data structure.)
+ *
+ * Note that the library must export symbols called `basename_ANYTHING_init`
+ * and `basename_ANYTHING__done`.  These will be called when the library is
+ * loaded and unloaded respectively.
+ *
+ * @param ctx the context used to find the plugins
+ * @param basename basename of the plugins to load
+ * @param arg argument to the plugin initialization function
+ * @param cb function to call for each plugin found
+ * @param cb_cls closure for @a cb
+ */
+void
+GNUNET_PLUGIN_load_all_in_context (const struct GNUNET_OS_ProjectData *ctx,
+                                   const char *basename,
+                                   void *arg,
+                                   GNUNET_PLUGIN_LoaderCallback cb,
+                                   void *cb_cls);
+
+
 /**
  * Unload plugin (runs the "done" callback and returns whatever "done"
  * returned).  The plugin is then unloaded.
diff --git a/src/reclaim/reclaim_attribute.c b/src/reclaim/reclaim_attribute.c
index 14690d7c9..21fdd83a2 100644
--- a/src/reclaim/reclaim_attribute.c
+++ b/src/reclaim/reclaim_attribute.c
@@ -96,10 +96,11 @@ init ()
   if (GNUNET_YES == initialized)
     return;
   initialized = GNUNET_YES;
-  GNUNET_PLUGIN_load_all ("libgnunet_plugin_reclaim_attribute_",
-                          NULL,
-                          &add_plugin,
-                          NULL);
+  GNUNET_PLUGIN_load_all_in_context (GNUNET_OS_project_data_default (),
+                                     "libgnunet_plugin_reclaim_attribute_",
+                                     NULL,
+                                     &add_plugin,
+                                     NULL);
 }
 
 /**
diff --git a/src/reclaim/reclaim_credential.c b/src/reclaim/reclaim_credential.c
index 05601c3d3..c8c0d397c 100644
--- a/src/reclaim/reclaim_credential.c
+++ b/src/reclaim/reclaim_credential.c
@@ -96,10 +96,11 @@ init ()
   if (GNUNET_YES == initialized)
     return;
   initialized = GNUNET_YES;
-  GNUNET_PLUGIN_load_all ("libgnunet_plugin_reclaim_credential_",
-                          NULL,
-                          &add_plugin,
-                          NULL);
+  GNUNET_PLUGIN_load_all_in_context (GNUNET_OS_project_data_default (),
+                                     "libgnunet_plugin_reclaim_credential_",
+                                     NULL,
+                                     &add_plugin,
+                                     NULL);
 }
 
 
diff --git a/src/rest/gnunet-rest-server.c b/src/rest/gnunet-rest-server.c
index e6e03b16d..ecb3e2ae5 100644
--- a/src/rest/gnunet-rest-server.c
+++ b/src/rest/gnunet-rest-server.c
@@ -1229,10 +1229,11 @@ run (void *cls,
     return;
   }
   /* Load plugins */
-  GNUNET_PLUGIN_load_all ("libgnunet_plugin_rest",
-                          (void *) cfg,
-                          &load_plugin,
-                          NULL);
+  GNUNET_PLUGIN_load_all_in_context (GNUNET_OS_project_data_default (),
+                                     "libgnunet_plugin_rest",
+                                     (void *) cfg,
+                                     &load_plugin,
+                                     NULL);
   GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL);
 }
 
diff --git a/src/util/plugin.c b/src/util/plugin.c
index d169bc911..feb661f24 100644
--- a/src/util/plugin.c
+++ b/src/util/plugin.c
@@ -388,6 +388,11 @@ find_libraries (void *cls,
  * `basename_ANYTHING_init` and `basename_ANYTHING__done`.  These will
  * be called when the library is loaded and unloaded respectively.
  *
+ * If you are writing a service to which third-party applications can connect,
+ * like GNUnet's own GNS service for example, you should use
+ * #GNUNET_PLUGIN_load_all_in_context instead of this function, passing your
+ * service's project data as context.
+ *
  * @param basename basename of the plugins to load
  * @param arg argument to the plugin initialization function
  * @param cb function to call for each plugin found
@@ -420,4 +425,32 @@ GNUNET_PLUGIN_load_all (const char *basename,
 }
 
 
+/**
+ * Load all compatible plugins with the given base name while inside the given
+ * context (i.e. a specific project data structure.)
+ *
+ * Note that the library must export symbols called `basename_ANYTHING_init`
+ * and `basename_ANYTHING__done`.  These will be called when the library is
+ * loaded and unloaded respectively.
+ *
+ * @param ctx the context used to find the plugins
+ * @param basename basename of the plugins to load
+ * @param arg argument to the plugin initialization function
+ * @param cb function to call for each plugin found
+ * @param cb_cls closure for @a cb
+ */
+void
+GNUNET_PLUGIN_load_all_in_context (const struct GNUNET_OS_ProjectData *ctx,
+                                   const char *basename,
+                                   void *arg,
+                                   GNUNET_PLUGIN_LoaderCallback cb,
+                                   void *cb_cls)
+{
+  const struct GNUNET_OS_ProjectData *cpd = GNUNET_OS_project_data_get ();
+  GNUNET_OS_init (ctx);
+  GNUNET_PLUGIN_load_all (basename, arg, cb, cb_cls);
+  GNUNET_OS_init (cpd);
+}
+
+
 /* end of plugin.c */
-- 
2.26.3

Reply via email to