#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);
}
}
* 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)
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);
}
}
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;
+}
(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 */