]> git.cworth.org Git - glaze/commitdiff
Add a glaze_execute function to libglaze
authorCarl Worth <cworth@cworth.org>
Fri, 23 Aug 2013 23:32:29 +0000 (16:32 -0700)
committerCarl Worth <cworth@cworth.org>
Fri, 23 Aug 2013 23:32:29 +0000 (16:32 -0700)
This function takes care of all of the magic needed in order to use Glaze.

With this function, the caller need not set the environment variables
such as LD_LIBRARY_PATH, GLAZE_WRAPPER, and GLAZE_LIBGL that would
otherwise need to be set in order to use Glaze.

In some cases, (such as when the "program" passed to glaze_execute is
a script that forces an alternate libGL.so.1 to be loaded), the user
may still need to set GLAZE_LIBGL to the absolute path of the correct,
underlying libGL.so.1 providing the implementation of OpenGL.

Makefile
configure
glaze-gl.c
glaze.c
glaze.h

index 7630a1869a026b398784e5f6bd6c4c7e36ad643c..422d37fbdbddcf07ba68ad3d5935ee3a6d89873a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -41,15 +41,15 @@ all: $(TARGETS)
 GLAZE_CFLAGS = $(CFLAGS) $(WARN_CFLAGS)
 
 $(LIBGLAZE_LIBNAME): glaze.c
-       $(CC) $(GLAZE_CFLAGS) -fPIC -shared -Wl,-Bsymbolic,-soname=$(LIBGLAZE_SONAME) -ldl -o $@ $<
+       $(CC) $(GLAZE_CFLAGS) -fPIC -shared -Wl,-Bsymbolic,-soname=$(LIBGLAZE_SONAME) -ldl -ltalloc -o $@ $<
 
 $(LIB64_DIR)/libGL.so.1: glaze-gl.c glapi.def
        mkdir -p $(LIB64_DIR)
-       $(CC) $(GLAZE_CFLAGS) -m64 -fPIC -shared -Wl,-Bsymbolic -o $@ $<
+       $(CC) $(GLAZE_CFLAGS) -m64 -fPIC -shared -Wl,-Bsymbolic -lelf -o $@ $<
 
 $(LIB32_DIR)/libGL.so.1: glaze-gl.c specs/gl.def
        mkdir -p $(LIB32_DIR)
-       $(CC) $(GLAZE_CFLAGS) -m32 -fPIC -shared -Wl,-Bsymbolic -o $@ $<
+       $(CC) $(GLAZE_CFLAGS) -m32 -fPIC -shared -Wl,-Bsymbolic -lelf -o $@ $<
 
 glaze-find-libgl-32: glaze-find-libgl.c
        $(CC) $(GLAZE_CFLAGS) -m32 -fPIC -ldl -o $@ $<
index d25e22a4b3820951164753053d173fb93e92860e..4df846b278b113b63d0d8cf0d4e021f565c3bd0a 100755 (executable)
--- a/configure
+++ b/configure
@@ -285,6 +285,22 @@ VERSION = ${VERSION}
 
 EOF
 
+# construct config.h
+QQ='"'
+cat > config.h <<EOF
+/* Generated by configure */
+
+/* The prefix to which ${PROJECT} should be installed */
+#define CONFIG_PREFIX "${PREFIX}"
+
+/* The directory to which libraries should be installed */
+#define CONFIG_LIBDIR "${LIBDIR:-${QQ} CONFIG_PREFIX ${QQ}/lib}"
+
+/* The directory to which executables should be installed */
+#define CONFIG_BINDIR "${BINDIR:-${QQ} CONFIG_PREFIX ${QQ}/bin}"
+
+EOF
+
 # construct the glaze.pc file
 cat > glaze.pc <<EOF
 prefix=${PREFIX}
index b6e7d72d3f8f9317a4ae03136f73b1ff9f444fa7..d4ad33f77023415466f76febc3ce5c62c8437f44 100644 (file)
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+
+#include <fcntl.h>
+#include <gelf.h>
 
 void *libgl_handle;
 void *wrapper_handle;
 
+/* Is the given elf program 32 or 64 bit?
+ *
+ * Note: This function returns -1 if 'filename' cannot
+ * be opened as a valid ELF file.
+ */
+static int
+elf_bits (const char *filename)
+{
+       Elf *elf;
+       GElf_Ehdr ehdr;
+       int fd, class;
+
+       fd = open (filename, O_RDONLY, 0);
+       if (fd < 0)
+               return -1;
+
+       if (elf_version (EV_CURRENT ) == EV_NONE)
+               return -1;
+
+       elf = elf_begin (fd, ELF_C_READ, NULL);
+       if (elf == NULL)
+               return -1;
+
+       if (elf_kind (elf) != ELF_K_ELF)
+               return -1;
+
+       if (gelf_getehdr (elf, &ehdr) == NULL)
+               return -1;
+
+       class = gelf_getclass (elf);
+
+       switch (class) {
+       case ELFCLASS32:
+               return 32;
+       case ELFCLASS64:
+               return 64;
+       default:
+               return -1;
+       }
+}
+
 static void
 open_libgl_handle (void)
 {
-       const char *path;
+       const char *libgl_path;
 
        if (libgl_handle)
                return;
 
-       path = getenv ("GLAZE_LIBGL");
-       if (path == NULL) {
-               fprintf (stderr, "GLAZE_LIBGL unset. Please set to path of real libGL.so under glaze.\n");
-               exit (1);
+       libgl_path = getenv ("GLAZE_LIBGL");
+
+       if (libgl_path == NULL) {
+               Dl_info info;
+               int bits;
+
+               if (dladdr (open_libgl_handle, &info) == 0) {
+                       fprintf (stderr, "Internal error: Failed to lookup filename of glaze library with dladdr.\n");
+                       exit (1);
+               }
+
+               bits = elf_bits (info.dli_fname);
+
+               if (bits == 32)
+                       libgl_path = getenv ("GLAZE_LIBGL_32_AUTO");
+               if (bits == 64)
+                       libgl_path = getenv ("GLAZE_LIBGL_64_AUTO");
+
+               if (libgl_path == NULL) {
+                       fprintf (stderr,
+                                "Error: Failed to detect OpenGL library.\n"
+                                "Please set GLAZE_LIBGL to path of real libGL.so\n");
+                       exit (1);
+               }
+
+               setenv ("GLAZE_LIBGL", libgl_path, 1);
        }
 
-       libgl_handle = dlopen (path, RTLD_LAZY | RTLD_GLOBAL);
+       dlerror();
+       libgl_handle = dlopen (libgl_path, RTLD_LAZY | RTLD_GLOBAL);
        if (libgl_handle == NULL) {
-               fprintf (stderr, "Error: Failed to dlopen %s\n", path);
+               fprintf (stderr, "glaze_init: Error: Failed to dlopen %s: %s\n",
+                        libgl_path, dlerror());
                exit (1);
        }
 }
diff --git a/glaze.c b/glaze.c
index 72070256fb4365d6874ba58a3d28f94495c5f9e9..a9acf746f80941c9cc3aa6475802b6ea42649c04 100644 (file)
--- a/glaze.c
+++ b/glaze.c
  * THE SOFTWARE.
  */
 
+#include "config.h"
+
 #define _GNU_SOURCE
 #include <dlfcn.h>
 
 #include "glaze.h"
 
+#include <talloc.h>
+
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* For PATH_MAX */
+#include <linux/limits.h>
 
 void *
 glaze_lookup (char *function)
@@ -46,7 +58,7 @@ glaze_lookup (char *function)
 
                libgl_handle = dlopen (path, RTLD_LAZY | RTLD_GLOBAL);
                if (libgl_handle == NULL) {
-                       fprintf (stderr, "Error: Failed to dlopen %s\n", path);
+                       fprintf (stderr, "glaze_lookup: Error: Failed to dlopen %s\n", path);
                        exit (1);
                }
        }
@@ -61,3 +73,236 @@ glaze_lookup (char *function)
 
        return ret;
 }
+
+/* Terminate a string representing a filename at the final '/' to
+ * eliminate the final filename component, (leaving only the directory
+ * portions of the original path).
+ *
+ * Notes: A path containing no '/' character will not be modified.
+ *        A path consisting only of "/" will not be modified.
+ */
+static void
+chop_trailing_path_component (char *path)
+{
+       char *slash;
+
+       slash = strrchr (path, '/');
+
+       if (slash == NULL)
+               return;
+
+       if (slash == path)
+               return;
+
+       *slash = '\0';
+}
+
+/* Find the absolute path of the currently executing binary.
+ *
+ * Returns: a string talloc'ed to 'ctx'
+ */
+static char *
+get_bin_name (void *ctx)
+{
+       const char *link = "/proc/self/exe";
+       char *name;
+
+       /* Yes, PATH_MAX is cheesy. I would have preferred to have
+        * used lstat and read the resulting st_size, but everytime I
+        * did that with /proc/self/exe I got a value of 0, (whereas
+        * with a "real" symbolic link I make myself I get the length
+        * of the filename being linked to). Go figure. */
+       int name_len = PATH_MAX + 1;
+
+       name = talloc_size (ctx, name_len);
+       if (name == NULL) {
+               fprintf (stderr, "Out of memory.\n");
+               exit (1);
+       }
+
+       name_len = readlink (link, name, name_len - 1);
+       if (name_len < 0) {
+               fprintf (stderr, "Failed to readlink %s: %s\n", link,
+                        strerror (errno));
+               exit (1);
+       }
+
+       name[name_len] = '\0';
+
+       return name;
+}
+
+/* Does path exist? */
+static int
+exists (const char *path)
+{
+       struct stat st;
+       int err;
+
+       err = stat (path, &st);
+
+       /* Failed to stat. It either doesn't exist, or might as well not. */
+       if (err == -1)
+               return 0;
+
+       return 1;
+}
+
+/* Execute "program" in a pipe, reads its first line of output on
+ * stdout, and returns that as a string (discarding any further
+ * output).
+ *
+ * Returns NULL if the program failed to execute for any reason.
+ *
+ * NOTE: The caller should free() the returned string when done with
+ * it.
+ */
+static char *
+read_process_output_one_line (const char *program)
+{
+       FILE *process;
+       int status;
+       char *line = NULL;
+       size_t len = 0;
+       ssize_t bytes_read;
+
+       process = popen (program, "r");
+       if (process == NULL)
+               return NULL;
+
+       bytes_read = getline (&line, &len, process);
+
+       status = pclose (process);
+       if (! WIFEXITED (status))
+               return NULL;
+
+       if (WEXITSTATUS (status))
+               return NULL;
+
+       if (bytes_read == -1)
+               return NULL;
+
+       if (bytes_read) {
+               if (line[strlen(line)-1] == '\n')
+                       line[strlen(line)-1] = '\0';
+               return line;
+       } else {
+               return NULL;
+       }
+}
+
+
+/* Look for "wrapper" library next to currently executing binary.
+ *
+ * If "wrapper" is an absolute path, return it directly.
+ *
+ * Otherwise, ("wrapper" is relative), look for an existing file named
+ * "wrapper" in the same directory as the currently executing binary,
+ * (as determined by /proc/self/exe). If that file exists, return its
+ * path.
+ *
+ * Otherwise, return the original, relative "wrapper".
+ */
+static const char *
+resolve_wrapper_path (void *ctx, const char *wrapper)
+{
+       char *bin_path, *lib_path;
+
+       if (*wrapper == '/')
+               return wrapper;
+
+       bin_path = get_bin_name (ctx);
+
+       chop_trailing_path_component (bin_path);
+
+       lib_path = talloc_asprintf (ctx, "%s/%s", bin_path, wrapper);
+
+       talloc_free (bin_path);
+
+       if (exists (lib_path))
+               return lib_path;
+
+       talloc_free (lib_path);
+
+       return wrapper;
+}
+
+/* Return path to directory containing Glaze wrapper's libGL.so.1
+ * suitable for use in LD_PRELOAD or LD_LIBRARY_PATH. Note that the
+ * path returned may not be a full path to the directory but may end
+ * with "$LIB" which will be expanded by the Linux dynamic linker to
+ * an architecture specific string (such as "lib/i386-linux-gnu" or
+ * "lib/x86_64-linux-gnu"). */
+static const char *
+find_glaze_libgl_dir (void)
+{
+       return CONFIG_LIBDIR "/glaze/$LIB";
+}
+
+void
+glaze_execute (int argc, char *argv[], const char *wrapper)
+{
+       void *ctx = talloc_new (NULL);
+       int i;
+
+       /* Set GLAZE_WRAPPER to absolute path of wrapper library */
+       if (wrapper == NULL || *wrapper == '\0') {
+               fprintf (stderr, "Error: glaze_execute called with empty wrapper library.\n");
+               return;
+       }
+
+       wrapper = resolve_wrapper_path (ctx, wrapper);
+
+       setenv ("GLAZE_WRAPPER", wrapper, 1);
+
+       /* Ensure GLAZE_LIBGL is set. If not, set GLAZE_LIBGL_32_AUTO
+        * and GLAZE_LIBGL_64_AUTO
+        *
+        * Note that we must do this before setting LD_LIBRARY_PATH,
+        * since after that, of course we would find Glaze's wrapper
+        * libGL.so.1. */
+       if (getenv ("GLAZE_LIBGL") == NULL) {
+               char *libgl_path;
+
+               libgl_path = read_process_output_one_line ("glaze-find-libgl-32");
+               if (libgl_path) {
+                       setenv ("GLAZE_LIBGL_32_AUTO", libgl_path, 1);
+                       free (libgl_path);
+               }
+
+               libgl_path = read_process_output_one_line ("glaze-find-libgl-64");
+               if (libgl_path) {
+                       setenv ("GLAZE_LIBGL_64_AUTO", libgl_path, 1);
+                       free (libgl_path);
+               }
+       }
+
+       /* Set LD_LIBRARY_PATH to include glaze's own libGL.so */
+       const char *glaze_libgl_dir, *ld_library_path;
+
+       glaze_libgl_dir = find_glaze_libgl_dir ();
+
+       ld_library_path = getenv ("LD_LIBRARY_PATH");
+
+       if (ld_library_path == NULL)
+               ld_library_path = glaze_libgl_dir;
+       else
+               ld_library_path = talloc_asprintf (ctx, "%s:%s",
+                                                  glaze_libgl_dir,
+                                                  ld_library_path);
+
+       setenv ("LD_LIBRARY_PATH", ld_library_path, 1);
+
+       talloc_free (ctx);
+
+       /* Execute program */
+       execvp (argv[0], argv);
+
+       /* If execvp returns, something went wrong. */
+       fprintf (stderr, "Error: Failed to exec:");
+       for (i = 0; i < argc; i++)
+               fprintf (stderr, " %s", argv[i]);
+       fprintf (stderr, "\n");
+
+       return;
+}
diff --git a/glaze.h b/glaze.h
index 9a9115a3a516e33189f737f82f5f2fe1998cd62f..325483a86f1e480fa8f93480cc1f090d06fe12e8 100644 (file)
--- a/glaze.h
+++ b/glaze.h
@@ -55,4 +55,49 @@ glaze_lookup (char *function);
        (ret) = real_ ## function(__VA_ARGS__);                 \
 } while (0);
 
+/* Execute a program (as specified with 'argc' and 'argv') with a
+ * wrapper library providing some substitute OpenGL functions.
+ *
+ * Here, 'wrapper' should be a loadable library providing one or more
+ * symbols in the OpenGL API. Glaze will arrange for those substitute
+ * functions to be called instead of the functions in the underlying
+ * OpenGL library. The functions may want to defer to the underlying
+ * OpenGL functions, which they can do by using the GLAZE_DEFER macro
+ * or by using glaze_lookup to obtain a function pointer to the
+ * underlying function.
+ *
+ * If 'wrapper' is not an absolute path, glaze_execute will attempt to
+ * find the library by searching in the following locations in order:
+ *
+ *   1. The same directory of the current executable, (as determined
+ *      by /proc/self/exe).
+ *
+ *   2. All directories as searched by dlopen, (such as
+ *      LD_LIBRARY_PATH, /etc/ld.so.cache, etc.)
+ *
+ * The behavior of glaze_execute can be influenced by the following
+ * environement variable:
+ *
+ *   GLAZE_LIBGL    Specifies the complete path to the library
+ *                  providing the underlying OpenGL implementation
+ *                  (libGL.so.1).
+ *
+ *                 In most cases, setting this variable is not
+ *                  necessary. If thisvariable is not set,
+ *                  glaze_execute will attempt to automatically find
+ *                  the correct libGL.so.1. It does this by executing
+ *                  a test program which loads libGL.so.1 and prints
+ *                  which file gets loaded. This guess may be
+ *                  incorrect if the program being executed by
+ *                  glaze_execute is actually a script which alters
+ *                  the LD_LIBRARY_PATH and causes a different
+ *                  libGL.so.1 to be loaded.
+ *
+ *                 If you are executing such a program, you can
+ *                 manually determine the correct libGL.so.1 and
+ *                 specifies its absolute path in this variable.
+ */
+void
+glaze_execute (int argc, char *argv[], const char *wrapper);
+
 #endif /* GLAZE_H */