From fded9f20d5f5c92bd5af051117327816dda0238e Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Fri, 23 Aug 2013 16:32:29 -0700 Subject: [PATCH] Add a glaze_execute function to libglaze 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 | 6 +- configure | 16 ++++ glaze-gl.c | 83 ++++++++++++++++-- glaze.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++- glaze.h | 45 ++++++++++ 5 files changed, 386 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 7630a18..422d37f 100644 --- 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 $@ $< diff --git a/configure b/configure index d25e22a..4df846b 100755 --- a/configure +++ b/configure @@ -285,6 +285,22 @@ VERSION = ${VERSION} EOF +# construct config.h +QQ='"' +cat > config.h < glaze.pc < #include +#include + +#include +#include 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 7207025..a9acf74 100644 --- a/glaze.c +++ b/glaze.c @@ -19,13 +19,25 @@ * THE SOFTWARE. */ +#include "config.h" + #define _GNU_SOURCE #include #include "glaze.h" +#include + #include #include +#include +#include +#include +#include +#include + +/* For PATH_MAX */ +#include 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 9a9115a..325483a 100644 --- 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 */ -- 2.43.0