Move setting of LD_LIBRARY_PATH closer to invocation of cppunittester
[LibreOffice.git] / vcl / source / helper / driverblocklist.cxx
blob03e3bc52e2af072859025eb858490d22aae3fa06
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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 */
10 #include <driverblocklist.hxx>
12 #include <algorithm>
13 #include <string_view>
15 #include <sal/log.hxx>
16 #include <utility>
18 #ifdef _WIN32
19 #if !defined WIN32_LEAN_AND_MEAN
20 #define WIN32_LEAN_AND_MEAN
21 #endif
22 #include <windows.h>
23 #endif
25 namespace DriverBlocklist
27 static OperatingSystem getOperatingSystem(std::string_view rString)
29 if (rString == "all")
30 return DRIVER_OS_ALL;
31 else if (rString == "10")
32 return DRIVER_OS_WINDOWS_10;
33 else if (rString == "windows")
34 return DRIVER_OS_WINDOWS_ALL;
35 else if (rString == "linux")
36 return DRIVER_OS_LINUX;
37 else if (rString == "osx_10_5")
38 return DRIVER_OS_OSX_10_5;
39 else if (rString == "osx_10_6")
40 return DRIVER_OS_OSX_10_6;
41 else if (rString == "osx_10_7")
42 return DRIVER_OS_OSX_10_7;
43 else if (rString == "osx_10_8")
44 return DRIVER_OS_OSX_10_8;
45 else if (rString == "osx")
46 return DRIVER_OS_OSX_ALL;
47 else if (rString == "android")
48 return DRIVER_OS_ANDROID;
49 return DRIVER_OS_UNKNOWN;
52 static VersionComparisonOp getComparison(std::string_view rString)
54 if (rString == "less")
56 return DRIVER_LESS_THAN;
58 else if (rString == "less_equal")
60 return DRIVER_LESS_THAN_OR_EQUAL;
62 else if (rString == "greater")
64 return DRIVER_GREATER_THAN;
66 else if (rString == "greater_equal")
68 return DRIVER_GREATER_THAN_OR_EQUAL;
70 else if (rString == "equal")
72 return DRIVER_EQUAL;
74 else if (rString == "not_equal")
76 return DRIVER_NOT_EQUAL;
78 else if (rString == "between_exclusive")
80 return DRIVER_BETWEEN_EXCLUSIVE;
82 else if (rString == "between_inclusive")
84 return DRIVER_BETWEEN_INCLUSIVE;
86 else if (rString == "between_inclusive_start")
88 return DRIVER_BETWEEN_INCLUSIVE_START;
91 throw InvalidFileException();
94 static OUString GetVendorId(std::string_view rString)
96 if (rString == "all")
98 return u""_ustr;
100 else if (rString == "intel")
102 return u"0x8086"_ustr;
104 else if (rString == "nvidia")
106 return u"0x10de"_ustr;
108 else if (rString == "amd")
110 return u"0x1002"_ustr;
112 else if (rString == "microsoft")
114 return u"0x1414"_ustr;
116 else
118 // Allow having simply the hex number as such there, too.
119 return OStringToOUString(rString, RTL_TEXTENCODING_UTF8);
123 OUString GetVendorId(DeviceVendor id)
125 assert(id >= 0 && id < DeviceVendorMax);
127 switch (id)
129 case VendorAll:
130 return u""_ustr;
131 case VendorIntel:
132 return u"0x8086"_ustr;
133 case VendorNVIDIA:
134 return u"0x10de"_ustr;
135 case VendorAMD:
136 return u"0x1002"_ustr;
137 case VendorMicrosoft:
138 return u"0x1414"_ustr;
140 abort();
143 DeviceVendor GetVendorFromId(uint32_t id)
145 switch (id)
147 case 0x8086:
148 return VendorIntel;
149 case 0x10de:
150 return VendorNVIDIA;
151 case 0x1002:
152 return VendorAMD;
153 case 0x1414:
154 return VendorMicrosoft;
155 default:
156 return VendorAll;
160 std::string_view GetVendorNameFromId(uint32_t id)
162 switch (id)
164 case 0x8086:
165 return "Intel";
166 case 0x10de:
167 return "Nvidia";
168 case 0x1002:
169 return "AMD";
170 case 0x1414:
171 return "Microsoft";
172 default:
173 return "?";
177 Parser::Parser(OUString aURL, std::vector<DriverInfo>& rDriverList, VersionType versionType)
178 : meBlockType(BlockType::UNKNOWN)
179 , mrDriverList(rDriverList)
180 , maURL(std::move(aURL))
181 , mVersionType(versionType)
185 bool Parser::parse()
189 xmlreader::XmlReader aReader(maURL);
190 handleContent(aReader);
192 catch (...)
194 mrDriverList.clear();
195 return false;
197 return true;
200 // This allows us to pad driver version 'substrings' with 0s, this
201 // effectively allows us to treat the version numbers as 'decimals'. This is
202 // a little strange but this method seems to do the right thing for all
203 // different vendor's driver strings. i.e. .98 will become 9800, which is
204 // larger than .978 which would become 9780.
205 static void PadDriverDecimal(char* aString)
207 for (int i = 0; i < 4; i++)
209 if (!aString[i])
211 for (int c = i; c < 4; c++)
213 aString[c] = '0';
215 break;
218 aString[4] = 0;
221 // All destination string storage needs to have at least 5 bytes available.
222 static bool SplitDriverVersion(const char* aSource, char* aAStr, char* aBStr, char* aCStr,
223 char* aDStr, VersionType versionType)
225 // sscanf doesn't do what we want here to we parse this manually.
226 int len = strlen(aSource);
227 char* dest[4] = { aAStr, aBStr, aCStr, aDStr };
228 unsigned destIdx = 0;
229 unsigned destPos = 0;
231 for (int i = 0; i < len; i++)
233 if (destIdx >= SAL_N_ELEMENTS(dest))
235 // Invalid format found. Ensure we don't access dest beyond bounds.
236 return false;
239 if (aSource[i] == '.')
241 dest[destIdx++][destPos] = 0;
242 destPos = 0;
243 continue;
246 if (destPos > 3)
248 // Ignore more than 4 chars. Ensure we never access dest[destIdx]
249 // beyond its bounds.
250 continue;
253 dest[destIdx][destPos++] = aSource[i];
256 // Add last terminator.
257 dest[destIdx][destPos] = 0;
259 // Vulkan version numbers have only 3 fields.
260 if (versionType == VersionType::Vulkan && destIdx == SAL_N_ELEMENTS(dest) - 2)
261 dest[++destIdx][0] = '\0';
262 if (destIdx != SAL_N_ELEMENTS(dest) - 1)
264 return false;
266 return true;
269 static bool ParseDriverVersion(std::u16string_view aVersion, uint64_t& rNumericVersion,
270 VersionType versionType)
272 rNumericVersion = 0;
274 int a, b, c, d;
275 char aStr[8], bStr[8], cStr[8], dStr[8];
276 /* honestly, why do I even bother */
277 OString aOVersion = OUStringToOString(aVersion, RTL_TEXTENCODING_UTF8);
278 if (!SplitDriverVersion(aOVersion.getStr(), aStr, bStr, cStr, dStr, versionType))
279 return false;
281 if (versionType == VersionType::OpenGL)
283 PadDriverDecimal(bStr);
284 PadDriverDecimal(cStr);
285 PadDriverDecimal(dStr);
288 a = atoi(aStr);
289 b = atoi(bStr);
290 c = atoi(cStr);
291 d = atoi(dStr);
293 if (versionType == VersionType::Vulkan)
294 assert(d == 0);
296 if (a < 0 || a > 0xffff)
297 return false;
298 if (b < 0 || b > 0xffff)
299 return false;
300 if (c < 0 || c > 0xffff)
301 return false;
302 if (d < 0 || d > 0xffff)
303 return false;
305 rNumericVersion = GFX_DRIVER_VERSION(a, b, c, d);
306 return true;
309 uint64_t Parser::getVersion(std::string_view rString)
311 OUString aString = OStringToOUString(rString, RTL_TEXTENCODING_UTF8);
312 uint64_t nVersion;
313 bool bResult = ParseDriverVersion(aString, nVersion, mVersionType);
315 if (!bResult)
317 throw InvalidFileException();
320 return nVersion;
323 void Parser::handleDevices(DriverInfo& rDriver, xmlreader::XmlReader& rReader)
325 int nLevel = 1;
326 bool bInMsg = false;
328 while (true)
330 xmlreader::Span name;
331 int nsId;
333 xmlreader::XmlReader::Result res
334 = rReader.nextItem(xmlreader::XmlReader::Text::Normalized, &name, &nsId);
336 if (res == xmlreader::XmlReader::Result::Begin)
338 ++nLevel;
339 if (nLevel > 2)
340 throw InvalidFileException();
342 if (name == "msg")
344 bInMsg = true;
346 else if (name == "device")
348 int nsIdDeveice;
349 while (rReader.nextAttribute(&nsIdDeveice, &name))
351 if (name == "id")
353 name = rReader.getAttributeValue(false);
354 OString aDeviceId(name.begin, name.length);
355 rDriver.maDevices.push_back(
356 OStringToOUString(aDeviceId, RTL_TEXTENCODING_UTF8));
360 else
361 throw InvalidFileException();
363 else if (res == xmlreader::XmlReader::Result::End)
365 --nLevel;
366 bInMsg = false;
367 if (!nLevel)
368 break;
370 else if (res == xmlreader::XmlReader::Result::Text)
372 if (bInMsg)
374 OString sMsg(name.begin, name.length);
375 rDriver.maMsg = OStringToOUString(sMsg, RTL_TEXTENCODING_UTF8);
381 void Parser::handleEntry(DriverInfo& rDriver, xmlreader::XmlReader& rReader)
383 if (meBlockType == BlockType::ALLOWLIST)
385 rDriver.mbAllowlisted = true;
387 else if (meBlockType == BlockType::DENYLIST)
389 rDriver.mbAllowlisted = false;
391 else if (meBlockType == BlockType::UNKNOWN)
393 throw InvalidFileException();
396 xmlreader::Span name;
397 int nsId;
399 while (rReader.nextAttribute(&nsId, &name))
401 if (name == "os")
403 name = rReader.getAttributeValue(false);
404 OString sOS(name.begin, name.length);
405 rDriver.meOperatingSystem = getOperatingSystem(sOS);
407 else if (name == "vendor")
409 name = rReader.getAttributeValue(false);
410 OString sVendor(name.begin, name.length);
411 rDriver.maAdapterVendor = GetVendorId(sVendor);
413 else if (name == "compare")
415 name = rReader.getAttributeValue(false);
416 OString sCompare(name.begin, name.length);
417 rDriver.meComparisonOp = getComparison(sCompare);
419 else if (name == "version")
421 name = rReader.getAttributeValue(false);
422 OString sVersion(name.begin, name.length);
423 rDriver.mnDriverVersion = getVersion(sVersion);
425 else if (name == "minVersion")
427 name = rReader.getAttributeValue(false);
428 OString sMinVersion(name.begin, name.length);
429 rDriver.mnDriverVersion = getVersion(sMinVersion);
431 else if (name == "maxVersion")
433 name = rReader.getAttributeValue(false);
434 OString sMaxVersion(name.begin, name.length);
435 rDriver.mnDriverVersionMax = getVersion(sMaxVersion);
437 else
439 OString aAttrName(name.begin, name.length);
440 SAL_WARN("vcl.driver", "unsupported attribute: " << aAttrName);
444 handleDevices(rDriver, rReader);
447 void Parser::handleList(xmlreader::XmlReader& rReader)
449 xmlreader::Span name;
450 int nsId;
452 while (true)
454 xmlreader::XmlReader::Result res
455 = rReader.nextItem(xmlreader::XmlReader::Text::NONE, &name, &nsId);
457 if (res == xmlreader::XmlReader::Result::Begin)
459 if (name == "entry")
461 DriverInfo aDriver;
462 handleEntry(aDriver, rReader);
463 mrDriverList.push_back(aDriver);
465 else if (name == "entryRange")
467 DriverInfo aDriver;
468 handleEntry(aDriver, rReader);
469 mrDriverList.push_back(aDriver);
471 else
473 throw InvalidFileException();
476 else if (res == xmlreader::XmlReader::Result::End)
478 break;
483 void Parser::handleContent(xmlreader::XmlReader& rReader)
485 while (true)
487 xmlreader::Span name;
488 int nsId;
490 xmlreader::XmlReader::Result res
491 = rReader.nextItem(xmlreader::XmlReader::Text::NONE, &name, &nsId);
493 if (res == xmlreader::XmlReader::Result::Begin)
495 if (name == "allowlist")
497 meBlockType = BlockType::ALLOWLIST;
498 handleList(rReader);
500 else if (name == "denylist")
502 meBlockType = BlockType::DENYLIST;
503 handleList(rReader);
505 else if (name == "root")
508 else
510 throw InvalidFileException();
513 else if (res == xmlreader::XmlReader::Result::End)
515 if (name == "allowlist" || name == "denylist")
517 meBlockType = BlockType::UNKNOWN;
520 else if (res == xmlreader::XmlReader::Result::Done)
522 break;
527 static OperatingSystem getOperatingSystem()
529 #ifdef _WIN32
530 // OS version in 16.16 major/minor form
531 // based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
532 switch (DriverBlocklist::GetWindowsVersion())
534 case 0x000A0000: // Major 10 Minor 0
535 return DRIVER_OS_WINDOWS_10;
536 default:
537 return DRIVER_OS_UNKNOWN;
539 #elif defined LINUX
540 return DRIVER_OS_LINUX;
541 #else
542 return DRIVER_OS_UNKNOWN;
543 #endif
546 namespace
548 struct compareIgnoreAsciiCase
550 explicit compareIgnoreAsciiCase(OUString aString)
551 : maString(std::move(aString))
555 bool operator()(std::u16string_view rCompare)
557 return maString.equalsIgnoreAsciiCase(rCompare);
560 private:
561 OUString maString;
565 const uint64_t allDriverVersions = ~(uint64_t(0));
567 DriverInfo::DriverInfo()
568 : meOperatingSystem(DRIVER_OS_UNKNOWN)
569 , maAdapterVendor(GetVendorId(VendorAll))
570 , mbAllowlisted(false)
571 , meComparisonOp(DRIVER_COMPARISON_IGNORED)
572 , mnDriverVersion(0)
573 , mnDriverVersionMax(0)
577 DriverInfo::DriverInfo(OperatingSystem os, OUString vendor, VersionComparisonOp op,
578 uint64_t driverVersion, bool bAllowlisted,
579 const char* suggestedVersion /* = nullptr */)
580 : meOperatingSystem(os)
581 , maAdapterVendor(std::move(vendor))
582 , mbAllowlisted(bAllowlisted)
583 , meComparisonOp(op)
584 , mnDriverVersion(driverVersion)
585 , mnDriverVersionMax(0)
587 if (suggestedVersion)
588 maSuggestedVersion
589 = OStringToOUString(std::string_view(suggestedVersion), RTL_TEXTENCODING_UTF8);
592 bool FindBlocklistedDeviceInList(std::vector<DriverInfo>& aDeviceInfos, VersionType versionType,
593 std::u16string_view sDriverVersion,
594 std::u16string_view sAdapterVendorID,
595 OUString const& sAdapterDeviceID, OperatingSystem system,
596 const OUString& blocklistURL)
598 uint64_t driverVersion;
599 ParseDriverVersion(sDriverVersion, driverVersion, versionType);
601 bool match = false;
602 for (const auto& rDeviceInfo : aDeviceInfos)
604 bool osMatch = false;
605 if (rDeviceInfo.meOperatingSystem == DRIVER_OS_ALL)
606 osMatch = true;
607 else if (rDeviceInfo.meOperatingSystem == system)
608 osMatch = true;
609 else if (rDeviceInfo.meOperatingSystem == DRIVER_OS_WINDOWS_ALL
610 && system >= DRIVER_OS_WINDOWS_FIRST && system <= DRIVER_OS_WINDOWS_LAST)
611 osMatch = true;
612 else if (rDeviceInfo.meOperatingSystem == DRIVER_OS_OSX_ALL && system >= DRIVER_OS_OSX_FIRST
613 && system <= DRIVER_OS_OSX_LAST)
614 osMatch = true;
615 if (!osMatch)
617 continue;
620 if (!rDeviceInfo.maAdapterVendor.equalsIgnoreAsciiCase(GetVendorId(VendorAll))
621 && !rDeviceInfo.maAdapterVendor.equalsIgnoreAsciiCase(sAdapterVendorID))
623 continue;
626 if (std::none_of(rDeviceInfo.maDevices.begin(), rDeviceInfo.maDevices.end(),
627 compareIgnoreAsciiCase(u"all"_ustr))
628 && std::none_of(rDeviceInfo.maDevices.begin(), rDeviceInfo.maDevices.end(),
629 compareIgnoreAsciiCase(sAdapterDeviceID)))
631 continue;
634 switch (rDeviceInfo.meComparisonOp)
636 case DRIVER_LESS_THAN:
637 match = driverVersion < rDeviceInfo.mnDriverVersion;
638 break;
639 case DRIVER_LESS_THAN_OR_EQUAL:
640 match = driverVersion <= rDeviceInfo.mnDriverVersion;
641 break;
642 case DRIVER_GREATER_THAN:
643 match = driverVersion > rDeviceInfo.mnDriverVersion;
644 break;
645 case DRIVER_GREATER_THAN_OR_EQUAL:
646 match = driverVersion >= rDeviceInfo.mnDriverVersion;
647 break;
648 case DRIVER_EQUAL:
649 match = driverVersion == rDeviceInfo.mnDriverVersion;
650 break;
651 case DRIVER_NOT_EQUAL:
652 match = driverVersion != rDeviceInfo.mnDriverVersion;
653 break;
654 case DRIVER_BETWEEN_EXCLUSIVE:
655 match = driverVersion > rDeviceInfo.mnDriverVersion
656 && driverVersion < rDeviceInfo.mnDriverVersionMax;
657 break;
658 case DRIVER_BETWEEN_INCLUSIVE:
659 match = driverVersion >= rDeviceInfo.mnDriverVersion
660 && driverVersion <= rDeviceInfo.mnDriverVersionMax;
661 break;
662 case DRIVER_BETWEEN_INCLUSIVE_START:
663 match = driverVersion >= rDeviceInfo.mnDriverVersion
664 && driverVersion < rDeviceInfo.mnDriverVersionMax;
665 break;
666 case DRIVER_COMPARISON_IGNORED:
667 // We don't have a comparison op, so we match everything.
668 match = true;
669 break;
670 default:
671 SAL_WARN("vcl.driver", "Bogus op in " << blocklistURL);
672 break;
675 if (match || rDeviceInfo.mnDriverVersion == allDriverVersions)
677 // white listed drivers
678 if (rDeviceInfo.mbAllowlisted)
680 SAL_INFO("vcl.driver", "allowlisted driver");
681 return false;
684 match = true;
685 if (!rDeviceInfo.maSuggestedVersion.isEmpty())
687 SAL_WARN("vcl.driver", "use : " << rDeviceInfo.maSuggestedVersion);
689 break;
693 SAL_INFO("vcl.driver", (match ? "denylisted" : "not denylisted") << " in " << blocklistURL);
694 return match;
697 bool IsDeviceBlocked(const OUString& blocklistURL, VersionType versionType,
698 std::u16string_view driverVersion, std::u16string_view vendorId,
699 const OUString& deviceId)
701 std::vector<DriverInfo> driverList;
702 Parser parser(blocklistURL, driverList, versionType);
703 if (!parser.parse())
705 SAL_WARN("vcl.driver", "error parsing denylist " << blocklistURL);
706 return false;
708 return FindBlocklistedDeviceInList(driverList, versionType, driverVersion, vendorId, deviceId,
709 getOperatingSystem(), blocklistURL);
712 #ifdef _WIN32
713 int32_t GetWindowsVersion()
715 static int32_t winVersion = []() {
716 // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
717 // subject to manifest-based behavior since Windows 8.1, so give wrong results.
718 // Another approach would be to use NetWkstaGetInfo, but that has some small
719 // reported delays (some milliseconds), and might get slower in domains with
720 // poor network connections.
721 // So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429
722 HINSTANCE hLibrary = LoadLibraryW(L"kernel32.dll");
723 if (hLibrary != nullptr)
725 wchar_t szPath[MAX_PATH];
726 DWORD dwCount = GetModuleFileNameW(hLibrary, szPath, SAL_N_ELEMENTS(szPath));
727 FreeLibrary(hLibrary);
728 if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath))
730 dwCount = GetFileVersionInfoSizeW(szPath, nullptr);
731 if (dwCount != 0)
733 std::unique_ptr<char[]> ver(new char[dwCount]);
734 if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE)
736 void* pBlock = nullptr;
737 UINT dwBlockSz = 0;
738 if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE
739 && dwBlockSz >= sizeof(VS_FIXEDFILEINFO))
741 VS_FIXEDFILEINFO* vinfo = static_cast<VS_FIXEDFILEINFO*>(pBlock);
742 return int32_t(vinfo->dwProductVersionMS);
748 return 0;
749 }();
751 return winVersion;
753 #endif
755 } // namespace
757 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */