]> git.cworth.org Git - glaze/blob - glaze.c
Add specs/README
[glaze] / glaze.c
1 /* Copyright © 2013, Intel Corporation
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining a copy
4  * of this software and associated documentation files (the "Software"), to deal
5  * in the Software without restriction, including without limitation the rights
6  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7  * copies of the Software, and to permit persons to whom the Software is
8  * furnished to do so, subject to the following conditions:
9  *
10  * The above copyright notice and this permission notice shall be included in
11  * all copies or substantial portions of the Software.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19  * THE SOFTWARE.
20  */
21
22 #include "config.h"
23
24 #define _GNU_SOURCE
25 #include <dlfcn.h>
26
27 #include "glaze.h"
28
29 #include <talloc.h>
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <errno.h>
38
39 /* For PATH_MAX */
40 #include <linux/limits.h>
41
42 void *
43 glaze_lookup (char *function)
44 {
45         static void *libgl_handle = NULL;
46         void *ret;
47
48         if (libgl_handle == NULL) {
49                 const char *path;
50
51                 path = getenv ("GLAZE_LIBGL");
52                 if (path == NULL) {
53                         fprintf (stderr, "GLAZE_LIBGL unset. "
54                                  "Please set to path of libGL.so under glaze.\n"
55                                 );
56                         exit (1);
57                 }
58
59                 libgl_handle = dlopen (path, RTLD_LAZY | RTLD_GLOBAL);
60                 if (libgl_handle == NULL) {
61                         fprintf (stderr, "glaze_lookup: Error: Failed to dlopen %s\n", path);
62                         exit (1);
63                 }
64         }
65
66         ret = dlsym (libgl_handle, function);
67
68         if (ret == NULL) {
69                 fprintf (stderr, "Error: glaze_lookup failed to dlsym %s\n",
70                          function);
71                 exit (1);
72         }
73
74         return ret;
75 }
76
77 /* Terminate a string representing a filename at the final '/' to
78  * eliminate the final filename component, (leaving only the directory
79  * portions of the original path).
80  *
81  * Notes: A path containing no '/' character will not be modified.
82  *        A path consisting only of "/" will not be modified.
83  */
84 static void
85 chop_trailing_path_component (char *path)
86 {
87         char *slash;
88
89         slash = strrchr (path, '/');
90
91         if (slash == NULL)
92                 return;
93
94         if (slash == path)
95                 return;
96
97         *slash = '\0';
98 }
99
100 /* Find the absolute path of the currently executing binary.
101  *
102  * Returns: a string talloc'ed to 'ctx'
103  */
104 static char *
105 get_bin_name (void *ctx)
106 {
107         const char *link = "/proc/self/exe";
108         char *name;
109
110         /* Yes, PATH_MAX is cheesy. I would have preferred to have
111          * used lstat and read the resulting st_size, but everytime I
112          * did that with /proc/self/exe I got a value of 0, (whereas
113          * with a "real" symbolic link I make myself I get the length
114          * of the filename being linked to). Go figure. */
115         int name_len = PATH_MAX + 1;
116
117         name = talloc_size (ctx, name_len);
118         if (name == NULL) {
119                 fprintf (stderr, "Out of memory.\n");
120                 exit (1);
121         }
122
123         name_len = readlink (link, name, name_len - 1);
124         if (name_len < 0) {
125                 fprintf (stderr, "Failed to readlink %s: %s\n", link,
126                          strerror (errno));
127                 exit (1);
128         }
129
130         name[name_len] = '\0';
131
132         return name;
133 }
134
135 /* Does path exist? */
136 static int
137 exists (const char *path)
138 {
139         struct stat st;
140         int err;
141
142         err = stat (path, &st);
143
144         /* Failed to stat. It either doesn't exist, or might as well not. */
145         if (err == -1)
146                 return 0;
147
148         return 1;
149 }
150
151 /* Execute "program" in a pipe, reads its first line of output on
152  * stdout, and returns that as a string (discarding any further
153  * output).
154  *
155  * Returns NULL if the program failed to execute for any reason.
156  *
157  * NOTE: The caller should free() the returned string when done with
158  * it.
159  */
160 static char *
161 read_process_output_one_line (const char *program)
162 {
163         FILE *process;
164         int status;
165         char *line = NULL;
166         size_t len = 0;
167         ssize_t bytes_read;
168
169         process = popen (program, "r");
170         if (process == NULL)
171                 return NULL;
172
173         bytes_read = getline (&line, &len, process);
174
175         status = pclose (process);
176         if (! WIFEXITED (status))
177                 return NULL;
178
179         if (WEXITSTATUS (status))
180                 return NULL;
181
182         if (bytes_read == -1)
183                 return NULL;
184
185         if (bytes_read) {
186                 if (line[strlen(line)-1] == '\n')
187                         line[strlen(line)-1] = '\0';
188                 return line;
189         } else {
190                 return NULL;
191         }
192 }
193
194
195 /* Look for "wrapper" library next to currently executing binary.
196  *
197  * If "wrapper" is an absolute path, return it directly.
198  *
199  * Otherwise, ("wrapper" is relative), look for an existing file named
200  * "wrapper" in the same directory as the currently executing binary,
201  * (as determined by /proc/self/exe). If that file exists, return its
202  * path.
203  *
204  * Otherwise, return the original, relative "wrapper".
205  */
206 static const char *
207 resolve_wrapper_path (void *ctx, const char *wrapper)
208 {
209         char *bin_path, *lib_path;
210
211         if (*wrapper == '/')
212                 return wrapper;
213
214         bin_path = get_bin_name (ctx);
215
216         chop_trailing_path_component (bin_path);
217
218         lib_path = talloc_asprintf (ctx, "%s/%s", bin_path, wrapper);
219
220         talloc_free (bin_path);
221
222         if (exists (lib_path))
223                 return lib_path;
224
225         talloc_free (lib_path);
226
227         return wrapper;
228 }
229
230 /* Return path to directory containing Glaze wrapper's libGL.so.1
231  * suitable for use in LD_PRELOAD or LD_LIBRARY_PATH. Note that the
232  * path returned may not be a full path to the directory but may end
233  * with "$LIB" which will be expanded by the Linux dynamic linker to
234  * an architecture specific string (such as "lib/i386-linux-gnu" or
235  * "lib/x86_64-linux-gnu"). */
236 static const char *
237 find_glaze_libgl_dir (void)
238 {
239         return CONFIG_LIBDIR "/glaze/$LIB";
240 }
241
242 void
243 glaze_execute (int argc, char *argv[], const char *wrapper)
244 {
245         void *ctx = talloc_new (NULL);
246         int i;
247
248         /* Set GLAZE_WRAPPER to absolute path of wrapper library */
249         if (wrapper == NULL || *wrapper == '\0') {
250                 fprintf (stderr, "Error: glaze_execute called with empty wrapper library.\n");
251                 return;
252         }
253
254         wrapper = resolve_wrapper_path (ctx, wrapper);
255
256         setenv ("GLAZE_WRAPPER", wrapper, 1);
257
258         /* Ensure GLAZE_LIBGL is set. If not, set GLAZE_LIBGL_32_AUTO
259          * and GLAZE_LIBGL_64_AUTO
260          *
261          * Note that we must do this before setting LD_LIBRARY_PATH,
262          * since after that, of course we would find Glaze's wrapper
263          * libGL.so.1. */
264         if (getenv ("GLAZE_LIBGL") == NULL) {
265                 char *libgl_path;
266
267                 libgl_path = read_process_output_one_line ("glaze-find-libgl-32");
268                 if (libgl_path) {
269                         setenv ("GLAZE_LIBGL_32_AUTO", libgl_path, 1);
270                         free (libgl_path);
271                 }
272
273                 libgl_path = read_process_output_one_line ("glaze-find-libgl-64");
274                 if (libgl_path) {
275                         setenv ("GLAZE_LIBGL_64_AUTO", libgl_path, 1);
276                         free (libgl_path);
277                 }
278         }
279
280         /* Set LD_LIBRARY_PATH to include glaze's own libGL.so */
281         const char *glaze_libgl_dir, *ld_library_path;
282
283         glaze_libgl_dir = find_glaze_libgl_dir ();
284
285         ld_library_path = getenv ("LD_LIBRARY_PATH");
286
287         if (ld_library_path == NULL)
288                 ld_library_path = glaze_libgl_dir;
289         else
290                 ld_library_path = talloc_asprintf (ctx, "%s:%s",
291                                                    glaze_libgl_dir,
292                                                    ld_library_path);
293
294         setenv ("LD_LIBRARY_PATH", ld_library_path, 1);
295
296         talloc_free (ctx);
297
298         /* Execute program */
299         execvp (argv[0], argv);
300
301         /* If execvp returns, something went wrong. */
302         fprintf (stderr, "Error: Failed to exec:");
303         for (i = 0; i < argc; i++)
304                 fprintf (stderr, " %s", argv[i]);
305         fprintf (stderr, "\n");
306
307         return;
308 }
309
310 void
311 glaze_set_first_gl_call_callback (const char *function_name)
312 {
313         setenv ("GLAZE_FIRST_GL_CALL_CALLBACK", function_name, 1);
314 }