LibreOffice Module vcl (master)  1
X11DeviceInfo.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
11 #include <opengl/x11/glxtest.hxx>
12 
13 #include <config_features.h>
14 
15 #include <rtl/ustring.hxx>
16 #include <sal/log.hxx>
17 
18 #include <unistd.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <errno.h>
22 #include <sys/utsname.h>
23 
24 #include <desktop/crashreport.hxx>
25 
26 namespace glx {
27 
28 static int glxtest_pipe = 0;
29 
30 static pid_t glxtest_pid = 0;
31 
32 }
33 
34 pid_t* getGlxPid()
35 {
36  return &glx::glxtest_pid;
37 }
38 
39 int* getGlxPipe()
40 {
41  return &glx::glxtest_pipe;
42 }
43 
44 namespace {
45 
46 const char*
47 strspnp_wrapper(const char* aDelims, const char* aStr)
48 {
49  const char* d;
50  do
51  {
52  for (d = aDelims; *d != '\0'; ++d)
53  {
54  if (*aStr == *d)
55  {
56  ++aStr;
57  break;
58  }
59  }
60  } while (*d);
61 
62  return aStr;
63 }
64 
65 char* strtok_wrapper(const char* aDelims, char** aStr)
66 {
67  if (!*aStr)
68  {
69  return nullptr;
70  }
71 
72  char* ret = const_cast<char*>(strspnp_wrapper(aDelims, *aStr));
73 
74  if (!*ret)
75  {
76  *aStr = ret;
77  return nullptr;
78  }
79 
80  char* i = ret;
81  do
82  {
83  for (const char* d = aDelims; *d != '\0'; ++d)
84  {
85  if (*i == *d) {
86  *i = '\0';
87  *aStr = ++i;
88  return ret;
89  }
90  }
91  ++i;
92  } while (*i);
93 
94  *aStr = nullptr;
95  return ret;
96 }
97 
98 uint64_t version(uint32_t major, uint32_t minor, uint32_t revision = 0)
99 {
100  return (uint64_t(major) << 32) + (uint64_t(minor) << 16) + uint64_t(revision);
101 }
102 
103 }
104 
106  mbIsMesa(false),
107  mbIsNVIDIA(false),
108  mbIsFGLRX(false),
109  mbIsNouveau(false),
110  mbIsIntel(false),
111  mbIsOldSwrast(false),
112  mbIsLlvmpipe(false),
113  mnGLMajorVersion(0),
114  mnMajorVersion(0),
115  mnMinorVersion(0),
116  mnRevisionVersion(0)
117 {
118  GetData();
119 }
120 
122 {
123  if (!glx::glxtest_pipe)
124  return;
125 
126  // to understand this function, see bug moz#639842. We retrieve the OpenGL driver information in a
127  // separate process to protect against bad drivers.
128  enum { buf_size = 1024 };
129  char buf[buf_size];
130  ssize_t bytesread = read(glx::glxtest_pipe,
131  &buf,
132  buf_size-1); // -1 because we'll append a zero
134  glx::glxtest_pipe = 0;
135 
136  // bytesread < 0 would mean that the above read() call failed.
137  // This should never happen. If it did, the outcome would be to denylist anyway.
138  if (bytesread < 0)
139  bytesread = 0;
140 
141  // let buf be a zero-terminated string
142  buf[bytesread] = 0;
143 
144  // Wait for the glxtest process to finish. This serves 2 purposes:
145  // * avoid having a zombie glxtest process laying around
146  // * get the glxtest process status info.
147  int glxtest_status = 0;
148  bool wait_for_glxtest_process = true;
149  bool waiting_for_glxtest_process_failed = false;
150  int waitpid_errno = 0;
151  while(wait_for_glxtest_process)
152  {
153  wait_for_glxtest_process = false;
154  if (waitpid(glx::glxtest_pid, &glxtest_status, 0) == -1)
155  {
156  waitpid_errno = errno;
157  if (waitpid_errno == EINTR)
158  {
159  wait_for_glxtest_process = true;
160  }
161  else
162  {
163  // Bug moz#718629
164  // ECHILD happens when the glxtest process got reaped got reaped after a PR_CreateProcess
165  // as per bug moz#227246. This shouldn't matter, as we still seem to get the data
166  // from the pipe, and if we didn't, the outcome would be to denylist anyway.
167  waiting_for_glxtest_process_failed = (waitpid_errno != ECHILD);
168  }
169  }
170  }
171 
172  bool exited_with_error_code = !waiting_for_glxtest_process_failed &&
173  WIFEXITED(glxtest_status) &&
174  WEXITSTATUS(glxtest_status) != EXIT_SUCCESS;
175  bool received_signal = !waiting_for_glxtest_process_failed &&
176  WIFSIGNALED(glxtest_status);
177 
178  bool error = waiting_for_glxtest_process_failed || exited_with_error_code || received_signal;
179 
180  OString textureFromPixmap;
181  OString *stringToFill = nullptr;
182  char *bufptr = buf;
183  if (!error)
184  {
185  while(true)
186  {
187  char *line = strtok_wrapper("\n", &bufptr);
188  if (!line)
189  break;
190  if (stringToFill) {
191  *stringToFill = OString(line);
192  stringToFill = nullptr;
193  }
194  else if(!strcmp(line, "VENDOR"))
195  stringToFill = &maVendor;
196  else if(!strcmp(line, "RENDERER"))
197  stringToFill = &maRenderer;
198  else if(!strcmp(line, "VERSION"))
199  stringToFill = &maVersion;
200  else if(!strcmp(line, "TFP"))
201  stringToFill = &textureFromPixmap;
202  }
203  }
204 
205  // only useful for Linux kernel version check for FGLRX driver.
206  // assumes X client == X server, which is sad.
207  struct utsname unameobj;
208  if (!uname(&unameobj))
209  {
210  maOS = OString(unameobj.sysname);
211  maOSRelease = OString(unameobj.release);
212  }
213 
214  // determine the major OpenGL version. That's the first integer in the version string.
215  mnGLMajorVersion = strtol(maVersion.getStr(), nullptr, 10);
216 
217  // determine driver type (vendor) and where in the version string
218  // the actual driver version numbers should be expected to be found (whereToReadVersionNumbers)
219  const char *whereToReadVersionNumbers = nullptr;
220  const char *Mesa_in_version_string = strstr(maVersion.getStr(), "Mesa");
221  if (Mesa_in_version_string)
222  {
223  mbIsMesa = true;
224  // with Mesa, the version string contains "Mesa major.minor" and that's all the version information we get:
225  // there is no actual driver version info.
226  whereToReadVersionNumbers = Mesa_in_version_string + strlen("Mesa");
227  if (strcasestr(maVendor.getStr(), "nouveau"))
228  mbIsNouveau = true;
229  if (strcasestr(maRenderer.getStr(), "intel")) // yes, intel is in the renderer string
230  mbIsIntel = true;
231  if (strcasestr(maRenderer.getStr(), "llvmpipe"))
232  mbIsLlvmpipe = true;
233  if (strcasestr(maRenderer.getStr(), "software rasterizer"))
234  mbIsOldSwrast = true;
235  }
236  else if (strstr(maVendor.getStr(), "NVIDIA Corporation"))
237  {
238  mbIsNVIDIA = true;
239  // with the NVIDIA driver, the version string contains "NVIDIA major.minor"
240  // note that here the vendor and version strings behave differently, that's why we don't put this above
241  // alongside Mesa_in_version_string.
242  const char *NVIDIA_in_version_string = strstr(maVersion.getStr(), "NVIDIA");
243  if (NVIDIA_in_version_string)
244  whereToReadVersionNumbers = NVIDIA_in_version_string + strlen("NVIDIA");
245  }
246  else if (strstr(maVendor.getStr(), "ATI Technologies Inc"))
247  {
248  mbIsFGLRX = true;
249  // with the FGLRX driver, the version string only gives an OpenGL version: so let's return that.
250  // that can at least give a rough idea of how old the driver is.
251  whereToReadVersionNumbers = maVersion.getStr();
252  }
253 
254  // read major.minor version numbers of the driver (not to be confused with the OpenGL version)
255  if (!whereToReadVersionNumbers)
256  return;
257 
258  // copy into writable buffer, for tokenization
259  strncpy(buf, whereToReadVersionNumbers, buf_size-1);
260  buf[buf_size-1] = 0;
261  bufptr = buf;
262 
263  // now try to read major.minor version numbers. In case of failure, gracefully exit: these numbers have
264  // been initialized as 0 anyways
265  char *token = strtok_wrapper(".", &bufptr);
266  if (token)
267  {
268  mnMajorVersion = strtol(token, nullptr, 10);
269  token = strtok_wrapper(".", &bufptr);
270  if (token)
271  {
272  mnMinorVersion = strtol(token, nullptr, 10);
273  token = strtok_wrapper(".", &bufptr);
274  if (token)
275  mnRevisionVersion = strtol(token, nullptr, 10);
276  }
277  }
278 }
279 
281 {
282  // don't even try to use OpenGL 1.x
283  if (mnGLMajorVersion == 1)
284  return true;
285 
286  CrashReporter::addKeyValue("AdapterVendorId", OStringToOUString(maVendor, RTL_TEXTENCODING_UTF8), CrashReporter::AddItem);
287  CrashReporter::addKeyValue("AdapterDeviceId", OStringToOUString(maRenderer, RTL_TEXTENCODING_UTF8), CrashReporter::Write);
288 
289  SAL_INFO("vcl.opengl", "Vendor: " << maVendor);
290  SAL_INFO("vcl.opengl", "Renderer: " << maRenderer);
291  SAL_INFO("vcl.opengl", "Version: " << maVersion);
292  SAL_INFO("vcl.opengl", "OS: " << maOS);
293  SAL_INFO("vcl.opengl", "OSRelease: " << maOSRelease);
294 
295  if (mbIsMesa)
296  {
297  if (mbIsNouveau && version(mnMajorVersion, mnMinorVersion) < version(8,0))
298  {
299  SAL_WARN("vcl.opengl", "blocked driver version: old nouveau driver (requires mesa 8.0+)");
300  return true;
301  }
302  else if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(7,10,3))
303  {
304  SAL_WARN("vcl.opengl", "blocked driver version: requires at least mesa 7.10.3");
305  return true;
306  }
307  else if (mbIsIntel && version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) == version(9,0,2))
308  {
309  SAL_WARN("vcl.opengl", "blocked driver version: my broken intel driver Mesa 9.0.2");
310  return true;
311  }
312  else if (mbIsOldSwrast)
313  {
314  SAL_WARN("vcl.opengl", "blocked driver version: software rasterizer");
315  return true;
316  }
317  else if (mbIsLlvmpipe && version(mnMajorVersion, mnMinorVersion) < version(9, 1))
318  {
319  // bug moz#791905, Mesa bug 57733, fixed in Mesa 9.1 according to
320  // https://bugs.freedesktop.org/show_bug.cgi?id=57733#c3
321  SAL_WARN("vcl.opengl", "blocked driver version: fdo#57733");
322  return true;
323  }
324  }
325  else if (mbIsNVIDIA)
326  {
327  if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(257,21))
328  {
329  SAL_WARN("vcl.opengl", "blocked driver version: nvidia requires at least 257.21");
330  return true;
331  }
332  }
333  else if (mbIsFGLRX)
334  {
335  // FGLRX does not report a driver version number, so we have the OpenGL version instead.
336  // by requiring OpenGL 3, we effectively require recent drivers.
337  if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(3, 0))
338  {
339  SAL_WARN("vcl.opengl", "blocked driver version: require at least OpenGL 3 for fglrx");
340  return true;
341  }
342  // Bug moz#724640: FGLRX + Linux 2.6.32 is a crashy combo
343  bool unknownOS = maOS.isEmpty() || maOSRelease.isEmpty();
344  bool badOS = maOS.indexOf("Linux") != -1 &&
345  maOSRelease.indexOf("2.6.32") != -1;
346  if (unknownOS || badOS)
347  {
348  SAL_WARN("vcl.opengl", "blocked OS version with fglrx");
349  return true;
350  }
351  }
352  else
353  {
354  // like on windows, let's block unknown vendors. Think of virtual machines.
355  // Also, this case is hit whenever the GLXtest probe failed to get driver info or crashed.
356  SAL_WARN("vcl.opengl", "unknown vendor => blocked");
357  return true;
358  }
359 
360  return false;
361 }
362 
363 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
tuple line
virtual bool isDeviceBlocked() override
double d
int * getGlxPipe()
static pid_t glxtest_pid
int i
static int glxtest_pipe
bool close
pid_t * getGlxPid()
#define SAL_INFO(area, stream)
#define SAL_WARN(area, stream)
static void addKeyValue(SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER tAddKeyHandling)