]> git.cworth.org Git - glaze/commitdiff
Add support for multiple Glaze wrappers to be used at the same time.
authorCarl Worth <cworth@cworth.org>
Mon, 23 Sep 2013 19:57:49 +0000 (12:57 -0700)
committerCarl Worth <cworth@cworth.org>
Mon, 23 Sep 2013 20:07:52 +0000 (13:07 -0700)
We simply use a colon-separated specification of the wrappers in
GLAZE_WRAPPER and hold on to an array of dlopened handles.

It's a simple matter to use the array of handles in several places.

The one place that involves some delicacy is glaze_lookup. Here we
must ensure that one wrapper's wrapped implementation of a function
chains to the subsequent wrapper and not to itself (which would lead
to infinite recursion). To get this right, we inspect the backtrace
and refuse to let glaze_lookup return a function that already appears
in the backtrace.

glaze-gl.c
glaze.c
libglaze.c

index 04f288ee97473791fdf40d3a1adfca230d75c9fc..8c3a82d883e4c2055e960fc5ba9d3eaada3913f5 100644 (file)
@@ -29,7 +29,8 @@
 #include <fcntl.h>
 
 void *libgl_handle;
-void *wrapper_handle;
+void **wrapper_handles;
+int num_wrapper_handles;
 
 #define STRNCMP_LITERAL(str, literal) strncmp (str, literal, sizeof (literal) - 1)
 
@@ -70,12 +71,34 @@ open_libgl_handle (void)
        }
 }
 
+/* Count the number of types 'chr' appears in 'str' */
+static int
+count_chars (const char *str, char chr)
+{
+       int count = 0;
+       const char *s = str;
+
+       while (1) {
+               s = strchr (s, chr);
+               if (s == NULL)
+                       break;
+               count++;
+               s++;
+               if (*s == '\0')
+                       break;
+       }
+
+       return count;
+}
+
 static void
-open_wrapper_handle (void)
+open_wrapper_handles (void)
 {
        const char *path;
+       char *path_copy, *wrapper, *save;
+       int i;
 
-       if (wrapper_handle)
+       if (wrapper_handles)
                return;
 
        path = getenv ("GLAZE_WRAPPER");
@@ -84,13 +107,37 @@ open_wrapper_handle (void)
                exit (1);
        }
 
+       num_wrapper_handles = count_chars (path, ':') + 1;
+
+       wrapper_handles = malloc (num_wrapper_handles * sizeof (void*));
+       if (wrapper_handles == NULL) {
+               fprintf (stderr, "Out of memory\n");
+               exit (1);
+       }
+
+       /* Clear dlerror state. */
        dlerror ();
-       wrapper_handle = dlopen (path, RTLD_LAZY);
-       if (wrapper_handle == NULL) {
-               const char *error = dlerror();
-               fprintf (stderr, "Error: Failed to dlopen %s: %s\n", path, error);
+
+       path_copy = strdup (path);
+       if (path_copy == NULL) {
+               fprintf (stderr, "Out of memory\n");
                exit (1);
        }
+
+       for (i = 0, wrapper = strtok_r (path_copy, ":", &save);
+            i < num_wrapper_handles;
+            i++, wrapper = strtok_r (NULL, ":", &save))
+       {
+               wrapper_handles[i] = dlopen (wrapper, RTLD_LAZY);
+               if (wrapper_handles[i] == NULL) {
+                       const char *error = dlerror();
+                       fprintf (stderr, "Error: Failed to dlopen %s: %s\n",
+                                wrapper, error);
+                       exit (1);
+               }
+       }
+
+       free (path_copy);
 }
 
 static void
@@ -100,7 +147,59 @@ static void
 glaze_init (void)
 {
        open_libgl_handle ();
-       open_wrapper_handle ();
+       open_wrapper_handles ();
+}
+
+static void
+call_first_gl_call_callbacks (void)
+{
+       char *callbacks = getenv ("GLAZE_FIRST_GL_CALL_CALLBACK");
+       char *name, *names, *save;
+       void (*callback) (void);
+       int i;
+
+       if (callbacks == NULL)
+               return;
+
+       names = strdup (callbacks);
+       if (names == NULL) {
+               fprintf (stderr, "Out of memory\n");
+               exit (1);
+       }
+
+       for (name = strtok_r (names, ":", &save);
+            name;
+            name = strtok_r (NULL, ":", &save))
+       {
+               for (i = 0; i < num_wrapper_handles; i++) {
+                       callback = dlsym (wrapper_handles[i], name);
+                       if (callback) {
+                               (callback) ();
+                               goto NEXT_NAME;
+                       }
+               }
+               fprintf (stderr, "Error: Failed to find function %s "
+                        "in any GLAZE_WRAPPER library.\n", name);
+       NEXT_NAME:
+               ;
+       }
+
+       free (names);
+}
+
+static void *
+find_symbol_in_glaze_wrappers (const char *name)
+{
+       void *symbol;
+       int i;
+
+       for (i = 0; i < num_wrapper_handles; i++) {
+               symbol = dlsym (wrapper_handles[i], name);
+               if (symbol)
+                       return symbol;
+       }
+
+       return NULL;
 }
 
 static void *
@@ -113,24 +212,13 @@ resolve (const char *name)
            STRNCMP_LITERAL (name, "gl") == 0 &&
            STRNCMP_LITERAL (name, "glX") != 0)
        {
-               char *callback_name = getenv ("GLAZE_FIRST_GL_CALL_CALLBACK");
-               if (callback_name) {
-                       void (*callback) (void) = dlsym (wrapper_handle,
-                                                        callback_name);
-                       if (callback) {
-                               (callback) ();
-                       } else {
-                               fprintf (stderr,
-                                        "Error: Failed to find function %s "
-                                        "in GLAZE_WRAPPER library.\n",
-                                        callback_name);
-                       }
-               }
+               call_first_gl_call_callbacks ();
+
                before_first_gl_call = 0;
        }
 
-       /* The wrapper gets first choice on all symbols. */
-       symbol = dlsym (wrapper_handle, name);
+       /* The wrappers get first choice on all symbols. */
+       symbol = find_symbol_in_glaze_wrappers (name);
        if (symbol)
                return symbol;
 
@@ -151,8 +239,8 @@ void
        /* On the first call, check if the wrapper provides an
         * implementation of this function. */
        if (first_call) {
-               wrapper_glXGetProcAddress = dlsym (wrapper_handle,
-                                                  "glXGetProcAddress");
+               wrapper_glXGetProcAddress =
+                       find_symbol_in_glaze_wrappers ("glXGetProcAddress");
                libgl_glXGetProcAddress = dlsym (libgl_handle,
                                                 "glXGetProcAddress");
                first_call = 0;
@@ -165,8 +253,8 @@ void
 
        /* Otherwise, we need to resolve the name.
         *
-        * The wrapper gets first choice on all symbols. */
-       symbol = dlsym (wrapper_handle, (char *) name);
+        * The wrappers get first choice on all symbols. */
+       symbol = find_symbol_in_glaze_wrappers ((char *) name);
        if (symbol)
                return symbol;
 
@@ -190,8 +278,8 @@ void
        /* On the first call, check if the wrapper provides an
         * implementation of this function. */
        if (first_call) {
-               wrapper_glXGetProcAddressARB = dlsym (wrapper_handle,
-                                                     "glXGetProcAddressARB");
+               wrapper_glXGetProcAddressARB =
+                       find_symbol_in_glaze_wrappers ("glXGetProcAddressARB");
                libgl_glXGetProcAddressARB = dlsym (libgl_handle,
                                                    "glXGetProcAddressARB");
                first_call = 0;
@@ -204,8 +292,8 @@ void
 
        /* Otherwise, we need to resolve the name.
         *
-        * The wrapper gets first choice on all symbols. */
-       symbol = dlsym (wrapper_handle, (char *) name);
+        * The wrappers get first choice on all symbols. */
+       symbol = find_symbol_in_glaze_wrappers ((char *) name);
        if (symbol)
                return symbol;
 
diff --git a/glaze.c b/glaze.c
index 9a3e0553474783e2f95b63dded7ff3d4726354a6..02a39275d0b3ba50101988e2386b7e499ecf8c88 100644 (file)
--- a/glaze.c
+++ b/glaze.c
@@ -44,7 +44,17 @@ main (int argc, char *argv[])
        const char *wrapper = NULL;
        int opt;
 
-       const char *short_options="h";
+       /* The initial '+' means that getopt will stop looking for
+        * options after the first non-option argument. This means
+        * that a command such as:
+        *
+        *      glaze glenv --renderer=Foo glxgears
+        *
+        * Will do what is intended, (namely, have glaze invoke "glenv
+        * --renderer=Foo glxgears" rather than trying to interpret
+        * --renderer=Foo as an option to glaze itself.
+        */
+       const char *short_options="+h";
        const struct option long_options[] = {
                {"help", no_argument, 0, 'h'},
                {"wrapper", required_argument, 0, WRAPPER_OPT},
index 17c473657f132b61bade34319ec268b9ce8f3c15..bf83669e16e3f2420f9572f633916e1840405f13 100644 (file)
@@ -23,6 +23,8 @@
 
 #define _GNU_SOURCE
 #include <dlfcn.h>
+#include <link.h>
+#include <execinfo.h>
 
 #include "glaze.h"
 
 /* For PATH_MAX */
 #include <linux/limits.h>
 
+#ifdef __GNUC__
+#define unused __attribute__ ((unused))
+#else
+#define unused
+#endif
+
+void **wrapper_handles;
+int num_wrapper_handles;
+
+/* FIXME: Both count_chars and open_wrapper_handles are copied code
+ * from glaze-gl.c. It would be better to avoid this code
+ * duplication. */
+
+/* Count the number of types 'chr' appears in 'str' */
+static int
+count_chars (const char *str, char chr)
+{
+       int count = 0;
+       const char *s = str;
+
+       while (1) {
+               s = strchr (s, chr);
+               if (s == NULL)
+                       break;
+               count++;
+               s++;
+               if (*s == '\0')
+                       break;
+       }
+
+       return count;
+}
+
+static void
+open_wrapper_handles (void)
+{
+       const char *path;
+       char *path_copy, *wrapper, *save;
+       int i;
+
+       if (wrapper_handles)
+               return;
+
+       path = getenv ("GLAZE_WRAPPER");
+       if (path == NULL) {
+               fprintf (stderr, "GLAZE_WRAPPER unset. Please set to path of Glaze-using wrapper library.\n");
+               exit (1);
+       }
+
+       num_wrapper_handles = count_chars (path, ':') + 1;
+
+       wrapper_handles = malloc (num_wrapper_handles * sizeof (void*));
+       if (wrapper_handles == NULL) {
+               fprintf (stderr, "Out of memory\n");
+               exit (1);
+       }
+
+       /* Clear dlerror state. */
+       dlerror ();
+
+       path_copy = strdup (path);
+       if (path_copy == NULL) {
+               fprintf (stderr, "Out of memory\n");
+               exit (1);
+       }
+
+       for (i = 0, wrapper = strtok_r (path_copy, ":", &save);
+            i < num_wrapper_handles;
+            i++, wrapper = strtok_r (NULL, ":", &save))
+       {
+               wrapper_handles[i] = dlopen (wrapper, RTLD_LAZY);
+               if (wrapper_handles[i] == NULL) {
+                       const char *error = dlerror();
+                       fprintf (stderr, "Error: Failed to dlopen %s: %s\n",
+                                wrapper, error);
+                       exit (1);
+               }
+       }
+
+       free (path_copy);
+}
+
+static int
+symbol_is_in_backtrace (void *symbol, void **bt, int num_bt)
+{
+       Dl_info info;
+       int status;
+       int i;
+
+       for (i = 0; i < num_bt; i++) {
+               status = dladdr (bt[i], &info);
+               if (status == 0)
+                       continue;
+               if (info.dli_saddr == symbol)
+                       return 1;
+       }
+
+       return 0;
+}
+
+/* FIXME: The hardcoded MAX_BT value here is uncool.
+ *
+ * If the number is too small, we could miss one of the wrapper
+ * functions in the backtrace which could lead to an infinite
+ * loop. For this bound, something on the order of the number of
+ * wrappers is likely sufficiently large.
+ *
+ * Meanwhile, if the number here is too large, we just waste a bunch
+ * of time grubbing through the backtrace of the application itself.
+ *
+ * The Right Thing to do here is to look at the back trace and ensure
+ * we have seen back to Glaze's libGL.so. If we see that, then we've
+ * looked far enough and should not have to look any further.
+ *
+ * Until then, 15 levels of backtrace out to be enough for anybody...
+ */
+#define MAX_BT 15
+
 void *
 glaze_lookup (char *function)
 {
        static void *libgl_handle = NULL;
        void *ret;
+       void *bt_buffer[MAX_BT];
+       int i, num_bt;
+
+       /* Before looking up the symbol in the underlying libGL, we
+        * first check whether any wrapper library has that symbol. Of
+        * course, we're already within at least one wrapper, and it
+        * wants to call into the next. So, for each wrapper, skip the
+        * looked up symbol if it already appears in the current
+        * backtrace. */
+
+       if (wrapper_handles == NULL)
+               open_wrapper_handles ();
+
+       num_bt = backtrace (bt_buffer, MAX_BT);
 
+       for (i = 0; i < num_wrapper_handles; i++) {
+               ret = dlsym (wrapper_handles[i], function);
+               if (ret == NULL)
+                       continue;
+
+               /* Check if this function appears in the backtrace. */
+               if (symbol_is_in_backtrace (ret, bt_buffer, num_bt))
+                       continue;
+
+               /* Otherwise, we've found the right thing to chain to. */
+               return ret;
+       }
+
+       /* Did not find the symbol in any wrapper library, so defer to
+        * the real, underlying function in libGL.so */
        if (libgl_handle == NULL) {
                const char *path;
 
@@ -273,7 +422,19 @@ glaze_execute (int argc, char *argv[], const char *wrapper)
 
        wrapper = resolve_wrapper_path (ctx, wrapper);
 
-       setenv ("GLAZE_WRAPPER", wrapper, 1);
+       /* If GLAZE_WRAPPER is already set, append so that both
+        * wrappers get involved correctly. */
+       if (getenv ("GLAZE_WRAPPER")) {
+               char *aggregate;
+
+               aggregate = talloc_asprintf (ctx, "%s:%s",
+                                            getenv ("GLAZE_WRAPPER"),
+                                            wrapper);
+               setenv ("GLAZE_WRAPPER", aggregate, 1);
+               talloc_free (aggregate);
+       } else {
+               setenv ("GLAZE_WRAPPER", wrapper, 1);
+       }
 
        /* Ensure GLAZE_LIBGL is set. If not, set GLAZE_LIBGL_32_AUTO
         * and GLAZE_LIBGL_64_AUTO