[mojosetup] [3/3] Prevent GUI plugins from intefering with each other.

Francois Gouget fgouget at codeweavers.com
Mon Aug 10 13:38:35 EDT 2020


The GTK 2 and GTK 3 plugins cannot cannot coexist in the same process
so the corresponding plugins must be unloaded as soon as we have their
priority. But even unloading + reloading GTK 2 or GTK 3 can lead to
random crashes.
So hold the GUI plugin tryouts in a subprocess.
Since this is a platform-specific operation add the necessary
infrastructure in platform_{unix,windows}.c.
Reuse MojoSetup_loadGuiPlugin() to simplify initGuiPluginsByPriority().

Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
---

Unlike the previous patch this could be considered to be a bug in GTK 2 
/ GTK 3. In practice expecting it to be possible to dlopen() + dlclose() 
libgtk2 / libgtk3 at will is unrealistic and in the current situation 
just leads to random (but frequent) crashes on startup.

 gui.c              | 82 ++++++++++++++++++++--------------------------
 platform.h         | 10 ++++++
 platform_unix.c    | 37 ++++++++++++++++++++-
 platform_windows.c | 11 +++++++
 4 files changed, 92 insertions(+), 48 deletions(-)

diff --git a/gui.c b/gui.c
index 8feda49..e81e993 100644
--- a/gui.c
+++ b/gui.c
@@ -76,6 +76,21 @@ static MojoGuiPluginPriority calcGuiPriority(const MojoGui *gui)
 } // calcGuiPriority
 
 
+const MojoGui *MojoSetup_loadGuiPlugin(const uint8 *img, uint32 len, void **lib)
+{
+    *lib = MojoPlatform_dlopen(img, len);
+    if (*lib != NULL)
+    {
+        void *addr = MojoPlatform_dlsym(*lib, MOJOGUI_ENTRY_POINT_STR);
+        MojoGuiEntryPoint entry = (MojoGuiEntryPoint) addr;
+        if (entry != NULL)
+            return entry(MOJOGUI_INTERFACE_REVISION, &GEntryPoints);
+    } // if
+
+    return NULL;
+} // MojoSetup_loadGuiPlugin
+
+
 static PluginList *initGuiPluginsByPriority(PluginList *plugins)
 {
     MojoGuiPluginPriority p;
@@ -88,16 +103,7 @@ static PluginList *initGuiPluginsByPriority(PluginList *plugins)
                 continue;
 
             if (i->img != NULL)
-            {
-                i->lib = MojoPlatform_dlopen(i->img, i->imglen);
-                if (i->lib != NULL)
-                {
-                    void *addr = MojoPlatform_dlsym(i->lib, MOJOGUI_ENTRY_POINT_STR);
-                    MojoGuiEntryPoint entry = (MojoGuiEntryPoint) addr;
-                    if (entry != NULL)
-                        i->gui = entry(MOJOGUI_INTERFACE_REVISION, &GEntryPoints);
-                } // if
-            } // if
+                i->gui = MojoSetup_loadGuiPlugin(i->img, i->imglen, &i->lib);
 
             if (i->gui && i->gui->init())
             {
@@ -132,22 +138,17 @@ static void deleteGuiPlugin(PluginList *plugin)
 } // deleteGuiPlugin
 
 
-static PluginList *tryGuiPlugin(PluginList *plugins, MojoGuiEntryPoint entry)
+static PluginList *addGuiPlugin(PluginList *plugins, const MojoGui *gui, MojoGuiPluginPriority priority)
 {
-    PluginList *plug = NULL;
-    const MojoGui *gui = entry(MOJOGUI_INTERFACE_REVISION, &GEntryPoints);
-    if (gui != NULL)
-    {
-        plug = xmalloc(sizeof (PluginList));
-        plug->lib = NULL;
-        plug->gui = gui;
-        plug->priority = calcGuiPriority(gui);
-        plug->next = plugins->next;
-        plugins->next = plug;
-    } // if
+    PluginList *plug = xmalloc(sizeof (PluginList));
+    plug->lib = NULL;
+    plug->gui = gui;
+    plug->priority = priority;
+    plug->next = plugins->next;
+    plugins->next = plug;
 
     return plug;
-} // tryGuiPlugin
+} // addGuiPlugin
 
 
 static void loadStaticGuiPlugins(PluginList *plugins)
@@ -155,8 +156,11 @@ static void loadStaticGuiPlugins(PluginList *plugins)
     int i;
     for (i = 0; staticGui[i].entry != NULL; i++)
     {
+        const MojoGui *gui;
         logInfo("Testing static GUI plugin %0", staticGui[i].name);
-        tryGuiPlugin(plugins, staticGui[i].entry);
+         gui = staticGui[i].entry(MOJOGUI_INTERFACE_REVISION, &GEntryPoints);
+        if (gui != NULL)
+            addGuiPlugin(plugins, gui, calcGuiPriority(gui));
     }
 } // loadStaticGuiPlugins
 
@@ -167,37 +171,21 @@ static boolean loadDynamicGuiPlugin(PluginList *plugins, MojoArchive *ar)
     MojoInput *io = ar->openCurrentEntry(ar);
     if (io != NULL)
     {
-        void *lib = NULL;
         const uint32 imglen = (uint32) io->length(io);
         uint8 *img = (uint8 *) xmalloc(imglen);
         const uint32 br = (uint32) io->read(io, img, imglen);
         io->close(io);
         if (br == imglen)
         {
-            lib = MojoPlatform_dlopen(img, imglen);
-            if (lib != NULL)
+            MojoGuiPluginPriority priority;
+            priority = MojoPlatform_getGuiPriority(img, imglen);
+            if (priority != MOJOGUI_PRIORITY_NEVER_TRY)
             {
-                void *addr = MojoPlatform_dlsym(lib, MOJOGUI_ENTRY_POINT_STR);
-                MojoGuiEntryPoint entry = (MojoGuiEntryPoint) addr;
-                if (entry != NULL)
-                {
-                    plug = tryGuiPlugin(plugins, entry);
-                    if (plug)
-                    {
-                        plug->img = img;
-                        plug->imglen = imglen;
-                    } // if
-                } // if
-
-                // always close, because GTK+2 and GTK+3 can't coexist.
-                //  we'll reload them when trying them!
-                MojoPlatform_dlclose(lib);
-                plug->gui = NULL;
-            } // if
+                plug = addGuiPlugin(plugins, NULL, priority);
+                plug->img = img;
+                plug->imglen = imglen;
+            }
         } // if
-
-        if (!plug)
-            free(img);
     } // if
 
     return plug != NULL;
diff --git a/platform.h b/platform.h
index 8ebe6ad..65cccbc 100644
--- a/platform.h
+++ b/platform.h
@@ -10,6 +10,7 @@
 #define _INCL_PLATFORM_H_
 
 #include "universal.h"
+#include "gui.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -18,6 +19,10 @@ extern "C" {
 // this is called by your mainline.
 int MojoSetup_main(int argc, char **argv);
 
+// Loads the specified dynamic plugin and returns its library handle (see
+//  MojoPlatform_dlopen()) and GUI entry point table.
+const MojoGui *MojoSetup_loadGuiPlugin(const uint8 *img, uint32 imglen, void **lib);
+
 // Caller must free returned string!
 char *MojoPlatform_appBinaryPath(void);
 
@@ -225,6 +230,11 @@ void *MojoPlatform_dlopen(const uint8 *img, size_t len);
 void *MojoPlatform_dlsym(void *lib, const char *sym);
 void MojoPlatform_dlclose(void *lib);
 
+// Temporarily load a GUI plugin (see MojoPlatform_dlopen()) and returns its
+//  priority. This may be called many times and, if necessary, steps should
+//  be taken so the plugins don't interfere with each other.
+MojoGuiPluginPriority MojoPlatform_getGuiPriority(const uint8 *img, size_t len);
+
 // Launch the user's preferred browser to view the URL (url).
 //  Returns true if the browser launched, false otherwise. We can't know
 //  if the URL actually loaded, just if the browser launched. The hope is that
diff --git a/platform_unix.c b/platform_unix.c
index 230fb36..9046b7f 100644
--- a/platform_unix.c
+++ b/platform_unix.c
@@ -62,7 +62,6 @@ void beos_usleep(unsigned long ticks);
 #endif
 
 #include "platform.h"
-#include "gui.h"
 #include "fileio.h"
 
 static struct timeval startup_time;
@@ -1002,6 +1001,42 @@ void MojoPlatform_dlclose(void *lib)
     dlclose(lib);
 } // MojoPlatform_dlclose
 
+MojoGuiPluginPriority MojoPlatform_getGuiPriority(const uint8 *img, size_t len)
+{
+    MojoGuiPluginPriority priority = MOJOGUI_PRIORITY_NEVER_TRY;
+
+    // GTK+2 and GTK+3 cannot coexist in the same process so the corresponding
+    //  plugins must be unloaded as soon as we have their priority. But even
+    //  unloading + reloading GTK+2 or GTK+3 can lead to random crashes.
+    //  So hold the GUI plugin tryouts in a subprocess.
+    pid_t pid = fork();
+    if (pid == 0)
+    {
+        void *lib;
+        const MojoGui *gui = MojoSetup_loadGuiPlugin(img, len, &lib);
+        if (gui)
+            priority = gui->priority(MojoPlatform_istty());
+        // some libraries react badly to dlclose() so don't bother before
+        // exit()
+        exit(priority);
+    }
+    else if (pid == -1)  // -1 == fork() failed.
+        logError("Unable to fork the test child: %0", strerror(errno));
+    else
+    {
+        int status;
+        pid_t rc;
+        rc = waitpid(pid, &status, 0);
+        if (rc <= 0)
+            logError("Unable to wait for the test child: %0", strerror(errno));
+        else if (rc == pid && WIFEXITED(status))
+            priority = WEXITSTATUS(status);
+        else
+            logInfo("The test child failed");
+    }
+
+    return priority;
+} // MojoPlatform_getGuiPriority
 
 static int runScriptString(const char *str, boolean devnull, const char **_argv)
 {
diff --git a/platform_windows.c b/platform_windows.c
index 8fa5467..4f60836 100644
--- a/platform_windows.c
+++ b/platform_windows.c
@@ -1511,6 +1511,17 @@ void MojoPlatform_dlclose(void *_lib)
     } // if
 } // MojoPlatform_dlclose
 
+MojoGuiPluginPriority MojoPlatform_getGuiPriority(const uint8 *img, size_t len)
+{
+    void *lib;
+    MojoGui *gui = MojoSetup_loadGuiPlugin(img, len, &lib);
+    MojoGuiPluginPriority priority = MOJOGUI_PRIORITY_NEVER_TRY;
+    if (gui)
+        priority = gui->priority(MojoPlatform_istty());
+    if (lib)
+        MojoSetup_dlclose(lib);
+    return priority;
+} // MojoPlatform_getGuiPriority
 
 uint64 MojoPlatform_getuid(void)
 {
-- 
2.20.1


More information about the mojosetup mailing list