1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: sw=2 ts=8 et :
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 
9 //////////////////////////////////////////////////////////////////////////////
10 //
11 // Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, because the only way to do
12 // that is to create a GL context and call glGetString(), but with bad drivers,
13 // just creating a GL context may crash.
14 //
15 // This file implements the idea to do that in a separate process.
16 //
17 // The only non-static function here is fire_glxtest_process(). It creates a pipe, publishes its 'read' end as the
18 // mozilla::widget::glxtest_pipe global variable, forks, and runs that GLX probe in the child process,
19 // which runs the glxtest() static function. This creates a X connection, a GLX context, calls glGetString, and writes that
20 // to the 'write' end of the pipe.
21 
22 #include <cstdio>
23 #include <cstdlib>
24 #include <unistd.h>
25 #include <dlfcn.h>
26 #include "nscore.h"
27 #include <fcntl.h>
28 #include "stdint.h"
29 
30 #if MOZ_WIDGET_GTK == 2
31 #include <glib.h>
32 #endif
33 
34 #ifdef __SUNPRO_CC
35 #include <stdio.h>
36 #endif
37 
38 #include "X11/Xlib.h"
39 #include "X11/Xutil.h"
40 
41 #include "mozilla/Unused.h"
42 
43 // stuff from glx.h
44 typedef struct __GLXcontextRec *GLXContext;
45 typedef XID GLXPixmap;
46 typedef XID GLXDrawable;
47 /* GLX 1.3 and later */
48 typedef struct __GLXFBConfigRec *GLXFBConfig;
49 typedef XID GLXFBConfigID;
50 typedef XID GLXContextID;
51 typedef XID GLXWindow;
52 typedef XID GLXPbuffer;
53 #define GLX_RGBA        4
54 #define GLX_RED_SIZE    8
55 #define GLX_GREEN_SIZE  9
56 #define GLX_BLUE_SIZE   10
57 
58 // stuff from gl.h
59 typedef uint8_t GLubyte;
60 typedef uint32_t GLenum;
61 #define GL_VENDOR       0x1F00
62 #define GL_RENDERER     0x1F01
63 #define GL_VERSION      0x1F02
64 
65 namespace mozilla {
66 namespace widget {
67 // the read end of the pipe, which will be used by GfxInfo
68 extern int glxtest_pipe;
69 // the PID of the glxtest process, to pass to waitpid()
70 extern pid_t glxtest_pid;
71 }
72 }
73 
74 // the write end of the pipe, which we're going to write to
75 static int write_end_of_the_pipe = -1;
76 
77 #if MOZ_WIDGET_GTK == 2
78 static int gtk_write_end_of_the_pipe = -1;
79 int gtk_read_end_of_the_pipe = -1;
80 #endif
81 
82 // C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types.
83 // So the work-around is to convert first to size_t.
84 // http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
85 template<typename func_ptr_type>
cast(void * ptr)86 static func_ptr_type cast(void *ptr)
87 {
88   return reinterpret_cast<func_ptr_type>(
89            reinterpret_cast<size_t>(ptr)
90          );
91 }
92 
fatal_error(const char * str)93 static void fatal_error(const char *str)
94 {
95   mozilla::Unused << write(write_end_of_the_pipe, str, strlen(str));
96   mozilla::Unused << write(write_end_of_the_pipe, "\n", 1);
97   _exit(EXIT_FAILURE);
98 }
99 
100 static int
x_error_handler(Display *,XErrorEvent * ev)101 x_error_handler(Display *, XErrorEvent *ev)
102 {
103   enum { bufsize = 1024 };
104   char buf[bufsize];
105   int length = snprintf(buf, bufsize,
106                         "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n",
107                         ev->error_code,
108                         ev->request_code,
109                         ev->minor_code);
110   mozilla::Unused << write(write_end_of_the_pipe, buf, length);
111   _exit(EXIT_FAILURE);
112   return 0;
113 }
114 
115 
116 // glxtest is declared inside extern "C" so that the name is not mangled.
117 // The name is used in build/valgrind/x86_64-redhat-linux-gnu.sup to suppress
118 // memory leak errors because we run it inside a short lived fork and we don't
119 // care about leaking memory
120 extern "C" {
121 
glxtest()122 void glxtest()
123 {
124   // we want to redirect to /dev/null stdout, stderr, and while we're at it,
125   // any PR logging file descriptors. To that effect, we redirect all positive
126   // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr.
127   int fd = open("/dev/null", O_WRONLY);
128   for (int i = 1; i < fd; i++)
129     dup2(fd, i);
130   close(fd);
131 
132 #if MOZ_WIDGET_GTK == 2
133   // On Gtk+2 builds, try to get the Gtk+3 version if it's installed, and
134   // use that in nsSystemInfo for secondaryLibrary. Better safe than sorry,
135   // we want to load the Gtk+3 library in a subprocess, and since we already
136   // have such a subprocess for the GLX test, we piggy back on it.
137   void *gtk3 = dlopen("libgtk-3.so.0", RTLD_LOCAL | RTLD_LAZY);
138   if (gtk3) {
139     auto gtk_get_major_version = reinterpret_cast<guint (*)(void)>(
140       dlsym(gtk3, "gtk_get_major_version"));
141     auto gtk_get_minor_version = reinterpret_cast<guint (*)(void)>(
142       dlsym(gtk3, "gtk_get_minor_version"));
143     auto gtk_get_micro_version = reinterpret_cast<guint (*)(void)>(
144       dlsym(gtk3, "gtk_get_micro_version"));
145 
146     if (gtk_get_major_version && gtk_get_minor_version &&
147         gtk_get_micro_version) {
148       // 64 bytes is going to be well enough for "GTK " followed by 3 integers
149       // separated with dots.
150       char gtkver[64];
151       int len = snprintf(gtkver, sizeof(gtkver), "GTK %u.%u.%u",
152                          gtk_get_major_version(), gtk_get_minor_version(),
153                          gtk_get_micro_version());
154       if (len > 0 && size_t(len) < sizeof(gtkver)) {
155         mozilla::Unused << write(gtk_write_end_of_the_pipe, gtkver, len);
156       }
157     }
158   }
159 #endif
160 
161 
162   if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER"))
163     fatal_error("The MOZ_AVOID_OPENGL_ALTOGETHER environment variable is defined");
164 
165   ///// Open libGL and load needed symbols /////
166 #ifdef __OpenBSD__
167   #define LIBGL_FILENAME "libGL.so"
168 #else
169   #define LIBGL_FILENAME "libGL.so.1"
170 #endif
171   void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY);
172   if (!libgl)
173     fatal_error("Unable to load " LIBGL_FILENAME);
174 
175   typedef void* (* PFNGLXGETPROCADDRESS) (const char *);
176   PFNGLXGETPROCADDRESS glXGetProcAddress = cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress"));
177 
178   if (!glXGetProcAddress)
179     fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME);
180 
181   typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *);
182   PFNGLXQUERYEXTENSION glXQueryExtension = cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension"));
183 
184   typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *);
185   PFNGLXQUERYVERSION glXQueryVersion = cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion"));
186 
187   typedef XVisualInfo* (* PFNGLXCHOOSEVISUAL) (Display *, int, int *);
188   PFNGLXCHOOSEVISUAL glXChooseVisual = cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual"));
189 
190   typedef GLXContext (* PFNGLXCREATECONTEXT) (Display *, XVisualInfo *, GLXContext, Bool);
191   PFNGLXCREATECONTEXT glXCreateContext = cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext"));
192 
193   typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext);
194   PFNGLXMAKECURRENT glXMakeCurrent = cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent"));
195 
196   typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext);
197   PFNGLXDESTROYCONTEXT glXDestroyContext = cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext"));
198 
199   typedef GLubyte* (* PFNGLGETSTRING) (GLenum);
200   PFNGLGETSTRING glGetString = cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString"));
201 
202   if (!glXQueryExtension ||
203       !glXQueryVersion ||
204       !glXChooseVisual ||
205       !glXCreateContext ||
206       !glXMakeCurrent ||
207       !glXDestroyContext ||
208       !glGetString)
209   {
210     fatal_error("glXGetProcAddress couldn't find required functions");
211   }
212   ///// Open a connection to the X server /////
213   Display *dpy = XOpenDisplay(nullptr);
214   if (!dpy)
215     fatal_error("Unable to open a connection to the X server");
216 
217   ///// Check that the GLX extension is present /////
218   if (!glXQueryExtension(dpy, nullptr, nullptr))
219     fatal_error("GLX extension missing");
220 
221   XSetErrorHandler(x_error_handler);
222 
223   ///// Get a visual /////
224    int attribs[] = {
225       GLX_RGBA,
226       GLX_RED_SIZE, 1,
227       GLX_GREEN_SIZE, 1,
228       GLX_BLUE_SIZE, 1,
229       None };
230   XVisualInfo *vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs);
231   if (!vInfo)
232     fatal_error("No visuals found");
233 
234   // using a X11 Window instead of a GLXPixmap does not crash
235   // fglrx in indirect rendering. bug 680644
236   Window window;
237   XSetWindowAttributes swa;
238   swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen),
239                                  vInfo->visual, AllocNone);
240 
241   swa.border_pixel = 0;
242   window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen),
243                        0, 0, 16, 16,
244                        0, vInfo->depth, InputOutput, vInfo->visual,
245                        CWBorderPixel | CWColormap, &swa);
246 
247   ///// Get a GL context and make it current //////
248   GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True);
249   glXMakeCurrent(dpy, window, context);
250 
251   ///// Look for this symbol to determine texture_from_pixmap support /////
252   void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT");
253 
254   ///// Get GL vendor/renderer/versions strings /////
255   enum { bufsize = 1024 };
256   char buf[bufsize];
257   const GLubyte *vendorString = glGetString(GL_VENDOR);
258   const GLubyte *rendererString = glGetString(GL_RENDERER);
259   const GLubyte *versionString = glGetString(GL_VERSION);
260 
261   if (!vendorString || !rendererString || !versionString)
262     fatal_error("glGetString returned null");
263 
264   int length = snprintf(buf, bufsize,
265                         "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
266                         vendorString,
267                         rendererString,
268                         versionString,
269                         glXBindTexImageEXT ? "TRUE" : "FALSE");
270   if (length >= bufsize)
271     fatal_error("GL strings length too large for buffer size");
272 
273   ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info)
274   ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as
275   ///// possible. Also we want to check that we're able to do that too without generating X errors.
276   glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it
277   glXDestroyContext(dpy, context);
278   XDestroyWindow(dpy, window);
279   XFreeColormap(dpy, swa.colormap);
280 
281 #ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above
282   XCloseDisplay(dpy);
283 #else
284   // This XSync call wanted to be instead:
285   //   XCloseDisplay(dpy);
286   // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192
287   XSync(dpy, False);
288 #endif
289 
290   dlclose(libgl);
291 
292   ///// Finally write data to the pipe
293   mozilla::Unused << write(write_end_of_the_pipe, buf, length);
294 }
295 
296 }
297 
298 /** \returns true in the child glxtest process, false in the parent process */
fire_glxtest_process()299 bool fire_glxtest_process()
300 {
301   int pfd[2];
302   if (pipe(pfd) == -1) {
303       perror("pipe");
304       return false;
305   }
306 #if MOZ_WIDGET_GTK == 2
307   int gtkpfd[2];
308   if (pipe(gtkpfd) == -1) {
309       perror("pipe");
310       return false;
311   }
312 #endif
313   pid_t pid = fork();
314   if (pid < 0) {
315       perror("fork");
316       close(pfd[0]);
317       close(pfd[1]);
318 #if MOZ_WIDGET_GTK == 2
319       close(gtkpfd[0]);
320       close(gtkpfd[1]);
321 #endif
322       return false;
323   }
324   // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads
325   // we have already spawned (like the profiler).
326   if (pid == 0) {
327       close(pfd[0]);
328       write_end_of_the_pipe = pfd[1];
329 #if MOZ_WIDGET_GTK == 2
330       close(gtkpfd[0]);
331       gtk_write_end_of_the_pipe = gtkpfd[1];
332 #endif
333       glxtest();
334       close(pfd[1]);
335 #if MOZ_WIDGET_GTK == 2
336       close(gtkpfd[1]);
337 #endif
338       _exit(0);
339   }
340 
341   close(pfd[1]);
342   mozilla::widget::glxtest_pipe = pfd[0];
343   mozilla::widget::glxtest_pid = pid;
344 #if MOZ_WIDGET_GTK == 2
345   close(gtkpfd[1]);
346   gtk_read_end_of_the_pipe = gtkpfd[0];
347 #endif
348   return false;
349 }
350