From 3ca48a69fda85e652b04f7e32ed97c80e25f2e3e Mon Sep 17 00:00:00 2001 From: henrika Date: Mon, 21 May 2018 13:34:51 +0200 Subject: [PATCH] Ports base::win:OSInfo from Chrome to rtc_win in WebRTC. Enables us to do stuff like: TEST(WindowsVersion, GetVersionGlobalScopeAccessor) { if (GetVersion() < VERSION_WIN10) { MethodNotSupportedOnWin10AndLater(); } else { MethodSupportedOnWin10AndLater(); } } which is useful when working with Windows. Note that, I also port a limited part of base::win::RegKey but only those parts that are needed to implement OSInfo. Hence, I don't expose any RegKey APIs. NOTRY=TRUE No-Presubmit: True Bug: webrtc:9265 Change-Id: Ia2fc0963f24044ffaad954aa21d28df9c32b3ee7 Reviewed-on: https://webrtc-review.googlesource.com/77723 Commit-Queue: Henrik Andreassson Reviewed-by: Karl Wiberg Cr-Commit-Position: refs/heads/master@{#23326} --- rtc_base/BUILD.gn | 9 +- rtc_base/win/windows_version.cc | 397 +++++++++++++++++++++++ rtc_base/win/windows_version.h | 149 +++++++++ rtc_base/win/windows_version_unittest.cc | 47 +++ 4 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 rtc_base/win/windows_version.cc create mode 100644 rtc_base/win/windows_version.h create mode 100644 rtc_base/win/windows_version_unittest.cc diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index 0ef33fdf77..aaf974ebee 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -456,7 +456,11 @@ rtc_source_set("rtc_base_approved_generic") { } if (is_win) { - sources += [ "file_win.cc" ] + sources += [ + "file_win.cc", + "win/windows_version.cc", + "win/windows_version.h", + ] data_deps += [ "//build/win:runtime_libs" ] } @@ -1159,6 +1163,9 @@ if (rtc_include_tests) { "virtualsocket_unittest.cc", "zero_memory_unittest.cc", ] + if (is_win) { + sources += [ "win/windows_version_unittest.cc" ] + } deps = [ ":checks", ":rate_limiter", diff --git a/rtc_base/win/windows_version.cc b/rtc_base/win/windows_version.cc new file mode 100644 index 0000000000..9ccde4c510 --- /dev/null +++ b/rtc_base/win/windows_version.cc @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "rtc_base/win/windows_version.h" + +#include +#include + +#include "rtc_base/checks.h" +#include "rtc_base/stringutils.h" + +#if !defined(__clang__) && _MSC_FULL_VER < 191125507 +#error VS 2017 Update 3.2 or higher is required +#endif + +#if !defined(NTDDI_WIN10_RS2) +// Windows 10 Creators Update SDK is required to build Chrome. It is important +// to install the 10.0.15063.468 version, released June 2017, because earlier +// versions had bugs and could not build Chrome. See this link for details: +// https://developercommunity.visualstudio.com/content/problem/42961/15063-sdk-is-broken-bitsh-indirectly-references-no.html +#error Creators Update SDK (10.0.15063.468) required. +#endif + +namespace { + +typedef BOOL(WINAPI* GetProductInfoPtr)(DWORD, DWORD, DWORD, DWORD, PDWORD); + +// Mask to pull WOW64 access flags out of REGSAM access. +const REGSAM kWow64AccessMask = KEY_WOW64_32KEY | KEY_WOW64_64KEY; + +// Utility class to read, write and manipulate the Windows Registry. +// Registry vocabulary primer: a "key" is like a folder, in which there +// are "values", which are pairs, with an associated data type. +// Based on base::win::RegKey but only implements a small fraction of it. +class RegKey { + public: + RegKey() : key_(nullptr), wow64access_(0) {} + + RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access) + : key_(nullptr), wow64access_(0) { + if (rootkey) { + if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) + Create(rootkey, subkey, access); + else + Open(rootkey, subkey, access); + } else { + RTC_DCHECK(!subkey); + wow64access_ = access & kWow64AccessMask; + } + } + + ~RegKey() { Close(); } + + LONG Create(HKEY rootkey, const wchar_t* subkey, REGSAM access) { + DWORD disposition_value; + return CreateWithDisposition(rootkey, subkey, &disposition_value, access); + } + + LONG CreateWithDisposition(HKEY rootkey, + const wchar_t* subkey, + DWORD* disposition, + REGSAM access) { + RTC_DCHECK(rootkey && subkey && access && disposition); + HKEY subhkey = NULL; + LONG result = + ::RegCreateKeyEx(rootkey, subkey, 0, NULL, REG_OPTION_NON_VOLATILE, + access, NULL, &subhkey, disposition); + if (result == ERROR_SUCCESS) { + Close(); + key_ = subhkey; + wow64access_ = access & kWow64AccessMask; + } + + return result; + } + + // Opens an existing reg key. + LONG Open(HKEY rootkey, const wchar_t* subkey, REGSAM access) { + RTC_DCHECK(rootkey && subkey && access); + HKEY subhkey = NULL; + + LONG result = ::RegOpenKeyEx(rootkey, subkey, 0, access, &subhkey); + if (result == ERROR_SUCCESS) { + Close(); + key_ = subhkey; + wow64access_ = access & kWow64AccessMask; + } + + return result; + } + + // Closes this reg key. + void Close() { + if (key_) { + ::RegCloseKey(key_); + key_ = nullptr; + } + } + + // Reads a REG_DWORD (uint32_t) into |out_value|. If |name| is null or empty, + // reads the key's default value, if any. + LONG ReadValueDW(const wchar_t* name, DWORD* out_value) const { + RTC_DCHECK(out_value); + DWORD type = REG_DWORD; + DWORD size = sizeof(DWORD); + DWORD local_value = 0; + LONG result = ReadValue(name, &local_value, &size, &type); + if (result == ERROR_SUCCESS) { + if ((type == REG_DWORD || type == REG_BINARY) && size == sizeof(DWORD)) + *out_value = local_value; + else + result = ERROR_CANTREAD; + } + + return result; + } + + // Reads a string into |out_value|. If |name| is null or empty, reads + // the key's default value, if any. + LONG ReadValue(const wchar_t* name, std::wstring* out_value) const { + RTC_DCHECK(out_value); + const size_t kMaxStringLength = 1024; // This is after expansion. + // Use the one of the other forms of ReadValue if 1024 is too small for you. + wchar_t raw_value[kMaxStringLength]; + DWORD type = REG_SZ, size = sizeof(raw_value); + LONG result = ReadValue(name, raw_value, &size, &type); + if (result == ERROR_SUCCESS) { + if (type == REG_SZ) { + *out_value = raw_value; + } else if (type == REG_EXPAND_SZ) { + wchar_t expanded[kMaxStringLength]; + size = + ::ExpandEnvironmentStrings(raw_value, expanded, kMaxStringLength); + // Success: returns the number of wchar_t's copied + // Fail: buffer too small, returns the size required + // Fail: other, returns 0 + if (size == 0 || size > kMaxStringLength) { + result = ERROR_MORE_DATA; + } else { + *out_value = expanded; + } + } else { + // Not a string. Oops. + result = ERROR_CANTREAD; + } + } + + return result; + } + + LONG ReadValue(const wchar_t* name, + void* data, + DWORD* dsize, + DWORD* dtype) const { + LONG result = RegQueryValueEx(key_, name, 0, dtype, + reinterpret_cast(data), dsize); + return result; + } + + private: + HKEY key_; + REGSAM wow64access_; +}; + +} // namespace + +namespace rtc { +namespace rtc_win { +namespace { + +// Helper to map a major.minor.x.build version (e.g. 6.1) to a Windows release. +Version MajorMinorBuildToVersion(int major, int minor, int build) { + if ((major == 5) && (minor > 0)) { + // Treat XP Pro x64, Home Server, and Server 2003 R2 as Server 2003. + return (minor == 1) ? VERSION_XP : VERSION_SERVER_2003; + } else if (major == 6) { + switch (minor) { + case 0: + // Treat Windows Server 2008 the same as Windows Vista. + return VERSION_VISTA; + case 1: + // Treat Windows Server 2008 R2 the same as Windows 7. + return VERSION_WIN7; + case 2: + // Treat Windows Server 2012 the same as Windows 8. + return VERSION_WIN8; + default: + RTC_DCHECK_EQ(minor, 3); + return VERSION_WIN8_1; + } + } else if (major == 10) { + if (build < 10586) { + return VERSION_WIN10; + } else if (build < 14393) { + return VERSION_WIN10_TH2; + } else if (build < 15063) { + return VERSION_WIN10_RS1; + } else if (build < 16299) { + return VERSION_WIN10_RS2; + } else if (build < 17134) { + return VERSION_WIN10_RS3; + } else { + return VERSION_WIN10_RS4; + } + } else if (major > 6) { + RTC_NOTREACHED(); + return VERSION_WIN_LAST; + } + + return VERSION_PRE_XP; +} + +// Returns the the "UBR" value from the registry. Introduced in Windows 10, +// this undocumented value appears to be similar to a patch number. +// Returns 0 if the value does not exist or it could not be read. +int GetUBR() { + // The values under the CurrentVersion registry hive are mirrored under + // the corresponding Wow6432 hive. + static constexpr wchar_t kRegKeyWindowsNTCurrentVersion[] = + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; + + RegKey key; + if (key.Open(HKEY_LOCAL_MACHINE, kRegKeyWindowsNTCurrentVersion, + KEY_QUERY_VALUE) != ERROR_SUCCESS) { + return 0; + } + + DWORD ubr = 0; + key.ReadValueDW(L"UBR", &ubr); + + return static_cast(ubr); +} + +} // namespace + +// static +OSInfo* OSInfo::GetInstance() { + // Note: we don't use the Singleton class because it depends on AtExitManager, + // and it's convenient for other modules to use this class without it. This + // pattern is copied from gurl.cc. + static OSInfo* info; + if (!info) { + OSInfo* new_info = new OSInfo(); + if (InterlockedCompareExchangePointer(reinterpret_cast(&info), + new_info, NULL)) { + delete new_info; + } + } + return info; +} + +OSInfo::OSInfo() + : version_(VERSION_PRE_XP), + architecture_(OTHER_ARCHITECTURE), + wow64_status_(GetWOW64StatusForProcess(GetCurrentProcess())) { + OSVERSIONINFOEX version_info = {sizeof version_info}; + ::GetVersionEx(reinterpret_cast(&version_info)); + version_number_.major = version_info.dwMajorVersion; + version_number_.minor = version_info.dwMinorVersion; + version_number_.build = version_info.dwBuildNumber; + version_number_.patch = GetUBR(); + version_ = MajorMinorBuildToVersion( + version_number_.major, version_number_.minor, version_number_.build); + service_pack_.major = version_info.wServicePackMajor; + service_pack_.minor = version_info.wServicePackMinor; + service_pack_str_ = rtc::ToUtf8(version_info.szCSDVersion); + + SYSTEM_INFO system_info = {}; + ::GetNativeSystemInfo(&system_info); + switch (system_info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_INTEL: + architecture_ = X86_ARCHITECTURE; + break; + case PROCESSOR_ARCHITECTURE_AMD64: + architecture_ = X64_ARCHITECTURE; + break; + case PROCESSOR_ARCHITECTURE_IA64: + architecture_ = IA64_ARCHITECTURE; + break; + } + processors_ = system_info.dwNumberOfProcessors; + allocation_granularity_ = system_info.dwAllocationGranularity; + + GetProductInfoPtr get_product_info; + DWORD os_type; + + if (version_info.dwMajorVersion == 6 || version_info.dwMajorVersion == 10) { + // Only present on Vista+. + get_product_info = reinterpret_cast( + ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "GetProductInfo")); + + get_product_info(version_info.dwMajorVersion, version_info.dwMinorVersion, + 0, 0, &os_type); + switch (os_type) { + case PRODUCT_CLUSTER_SERVER: + case PRODUCT_DATACENTER_SERVER: + case PRODUCT_DATACENTER_SERVER_CORE: + case PRODUCT_ENTERPRISE_SERVER: + case PRODUCT_ENTERPRISE_SERVER_CORE: + case PRODUCT_ENTERPRISE_SERVER_IA64: + case PRODUCT_SMALLBUSINESS_SERVER: + case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM: + case PRODUCT_STANDARD_SERVER: + case PRODUCT_STANDARD_SERVER_CORE: + case PRODUCT_WEB_SERVER: + version_type_ = SUITE_SERVER; + break; + case PRODUCT_PROFESSIONAL: + case PRODUCT_ULTIMATE: + version_type_ = SUITE_PROFESSIONAL; + break; + case PRODUCT_ENTERPRISE: + case PRODUCT_ENTERPRISE_E: + case PRODUCT_ENTERPRISE_EVALUATION: + case PRODUCT_ENTERPRISE_N: + case PRODUCT_ENTERPRISE_N_EVALUATION: + case PRODUCT_ENTERPRISE_S: + case PRODUCT_ENTERPRISE_S_EVALUATION: + case PRODUCT_ENTERPRISE_S_N: + case PRODUCT_ENTERPRISE_S_N_EVALUATION: + case PRODUCT_BUSINESS: + case PRODUCT_BUSINESS_N: + version_type_ = SUITE_ENTERPRISE; + break; + case PRODUCT_EDUCATION: + case PRODUCT_EDUCATION_N: + version_type_ = SUITE_EDUCATION; + break; + case PRODUCT_HOME_BASIC: + case PRODUCT_HOME_PREMIUM: + case PRODUCT_STARTER: + default: + version_type_ = SUITE_HOME; + break; + } + } else if (version_info.dwMajorVersion == 5 && + version_info.dwMinorVersion == 2) { + if (version_info.wProductType == VER_NT_WORKSTATION && + system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { + version_type_ = SUITE_PROFESSIONAL; + } else if (version_info.wSuiteMask & VER_SUITE_WH_SERVER) { + version_type_ = SUITE_HOME; + } else { + version_type_ = SUITE_SERVER; + } + } else if (version_info.dwMajorVersion == 5 && + version_info.dwMinorVersion == 1) { + if (version_info.wSuiteMask & VER_SUITE_PERSONAL) + version_type_ = SUITE_HOME; + else + version_type_ = SUITE_PROFESSIONAL; + } else { + // Windows is pre XP so we don't care but pick a safe default. + version_type_ = SUITE_HOME; + } +} + +OSInfo::~OSInfo() {} + +std::string OSInfo::processor_model_name() { + if (processor_model_name_.empty()) { + const wchar_t kProcessorNameString[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + RegKey key(HKEY_LOCAL_MACHINE, kProcessorNameString, KEY_READ); + std::wstring value; + key.ReadValue(L"ProcessorNameString", &value); + processor_model_name_ = rtc::ToUtf8(value); + } + return processor_model_name_; +} + +// static +OSInfo::WOW64Status OSInfo::GetWOW64StatusForProcess(HANDLE process_handle) { + typedef BOOL(WINAPI * IsWow64ProcessFunc)(HANDLE, PBOOL); + IsWow64ProcessFunc is_wow64_process = reinterpret_cast( + GetProcAddress(GetModuleHandle(L"kernel32.dll"), "IsWow64Process")); + if (!is_wow64_process) + return WOW64_DISABLED; + BOOL is_wow64 = FALSE; + if (!(*is_wow64_process)(process_handle, &is_wow64)) + return WOW64_UNKNOWN; + return is_wow64 ? WOW64_ENABLED : WOW64_DISABLED; +} + +Version GetVersion() { + return OSInfo::GetInstance()->version(); +} + +} // namespace rtc_win +} // namespace rtc diff --git a/rtc_base/win/windows_version.h b/rtc_base/win/windows_version.h new file mode 100644 index 0000000000..39d333e4d7 --- /dev/null +++ b/rtc_base/win/windows_version.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef RTC_BASE_WIN_WINDOWS_VERSION_H_ +#define RTC_BASE_WIN_WINDOWS_VERSION_H_ + +#include +#include + +#include "rtc_base/constructormagic.h" + +typedef void* HANDLE; + +namespace rtc { +namespace rtc_win { + +// The running version of Windows. This is declared outside OSInfo for +// syntactic sugar reasons; see the declaration of GetVersion() below. +// NOTE: Keep these in order so callers can do things like +// "if (rtc_win::GetVersion() >= rtc_win::VERSION_VISTA) ...". +// +// This enum is used in metrics histograms, so they shouldn't be reordered or +// removed. New values can be added before VERSION_WIN_LAST. +enum Version { + VERSION_PRE_XP = 0, // Not supported. + VERSION_XP = 1, + VERSION_SERVER_2003 = 2, // Also includes XP Pro x64 and Server 2003 R2. + VERSION_VISTA = 3, // Also includes Windows Server 2008. + VERSION_WIN7 = 4, // Also includes Windows Server 2008 R2. + VERSION_WIN8 = 5, // Also includes Windows Server 2012. + VERSION_WIN8_1 = 6, // Also includes Windows Server 2012 R2. + VERSION_WIN10 = 7, // Threshold 1: Version 1507, Build 10240. + VERSION_WIN10_TH2 = 8, // Threshold 2: Version 1511, Build 10586. + VERSION_WIN10_RS1 = 9, // Redstone 1: Version 1607, Build 14393. + VERSION_WIN10_RS2 = 10, // Redstone 2: Version 1703, Build 15063. + VERSION_WIN10_RS3 = 11, // Redstone 3: Version 1709, Build 16299. + VERSION_WIN10_RS4 = 12, // Redstone 4: Version 1803, Build 17134. + // On edit, update tools\metrics\histograms\enums.xml "WindowsVersion" and + // "GpuBlacklistFeatureTestResultsWindows2". + VERSION_WIN_LAST, // Indicates error condition. +}; + +// A rough bucketing of the available types of versions of Windows. This is used +// to distinguish enterprise enabled versions from home versions and potentially +// server versions. Keep these values in the same order, since they are used as +// is for metrics histogram ids. +enum VersionType { + SUITE_HOME = 0, + SUITE_PROFESSIONAL, + SUITE_SERVER, + SUITE_ENTERPRISE, + SUITE_EDUCATION, + SUITE_LAST, +}; + +// A singleton that can be used to query various pieces of information about the +// OS and process state. Note that this doesn't use the base Singleton class, so +// it can be used without an AtExitManager. +class OSInfo { + public: + struct VersionNumber { + int major; + int minor; + int build; + int patch; + }; + + struct ServicePack { + int major; + int minor; + }; + + // The processor architecture this copy of Windows natively uses. For + // example, given an x64-capable processor, we have three possibilities: + // 32-bit Chrome running on 32-bit Windows: X86_ARCHITECTURE + // 32-bit Chrome running on 64-bit Windows via WOW64: X64_ARCHITECTURE + // 64-bit Chrome running on 64-bit Windows: X64_ARCHITECTURE + enum WindowsArchitecture { + X86_ARCHITECTURE, + X64_ARCHITECTURE, + IA64_ARCHITECTURE, + OTHER_ARCHITECTURE, + }; + + // Whether a process is running under WOW64 (the wrapper that allows 32-bit + // processes to run on 64-bit versions of Windows). This will return + // WOW64_DISABLED for both "32-bit Chrome on 32-bit Windows" and "64-bit + // Chrome on 64-bit Windows". WOW64_UNKNOWN means "an error occurred", e.g. + // the process does not have sufficient access rights to determine this. + enum WOW64Status { + WOW64_DISABLED, + WOW64_ENABLED, + WOW64_UNKNOWN, + }; + + static OSInfo* GetInstance(); + + Version version() const { return version_; } + VersionNumber version_number() const { return version_number_; } + VersionType version_type() const { return version_type_; } + ServicePack service_pack() const { return service_pack_; } + std::string service_pack_str() const { return service_pack_str_; } + WindowsArchitecture architecture() const { return architecture_; } + int processors() const { return processors_; } + size_t allocation_granularity() const { return allocation_granularity_; } + WOW64Status wow64_status() const { return wow64_status_; } + std::string processor_model_name(); + + // Like wow64_status(), but for the supplied handle instead of the current + // process. This doesn't touch member state, so you can bypass the singleton. + static WOW64Status GetWOW64StatusForProcess(HANDLE process_handle); + + private: + OSInfo(); + ~OSInfo(); + + Version version_; + VersionNumber version_number_; + VersionType version_type_; + ServicePack service_pack_; + + // A string, such as "Service Pack 3", that indicates the latest Service Pack + // installed on the system. If no Service Pack has been installed, the string + // is empty. + std::string service_pack_str_; + WindowsArchitecture architecture_; + int processors_; + size_t allocation_granularity_; + WOW64Status wow64_status_; + std::string processor_model_name_; + + RTC_DISALLOW_COPY_AND_ASSIGN(OSInfo); +}; + +// Because this is by far the most commonly-requested value from the above +// singleton, we add a global-scope accessor here as syntactic sugar. +Version GetVersion(); + +} // namespace rtc_win +} // namespace rtc + +#endif // RTC_BASE_WIN_WINDOWS_VERSION_H_ diff --git a/rtc_base/win/windows_version_unittest.cc b/rtc_base/win/windows_version_unittest.cc new file mode 100644 index 0000000000..9e582e549f --- /dev/null +++ b/rtc_base/win/windows_version_unittest.cc @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "rtc_base/win/windows_version.h" + +#include "rtc_base/gunit.h" +#include "rtc_base/logging.h" + +namespace rtc { +namespace rtc_win { +namespace { + +void MethodSupportedOnWin10AndLater() { + RTC_DLOG(INFO) << "MethodSupportedOnWin10AndLater"; +} + +void MethodNotSupportedOnWin10AndLater() { + RTC_DLOG(INFO) << "MethodNotSupportedOnWin10AndLater"; +} + +// Use global GetVersion() and use it in a way a user would typically use it +// when checking for support of a certain API: +// "if (rtc_win::GetVersion() < VERSION_WIN10) ...". +TEST(WindowsVersion, GetVersionGlobalScopeAccessor) { + if (GetVersion() < VERSION_WIN10) { + MethodNotSupportedOnWin10AndLater(); + } else { + MethodSupportedOnWin10AndLater(); + } +} + +TEST(WindowsVersion, ProcessorModelName) { + std::string name = OSInfo::GetInstance()->processor_model_name(); + EXPECT_FALSE(name.empty()); + RTC_DLOG(INFO) << "processor_model_name: " << name; +} + +} // namespace +} // namespace rtc_win +} // namespace rtc