| // Copyright 2014 The Crashpad Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "util/mac/mac_util.h" |
| |
| #include <Availability.h> |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <IOKit/IOKitLib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/utsname.h> |
| |
| #include "base/apple/foundation_util.h" |
| #include "base/apple/scoped_cftyperef.h" |
| #include "base/check_op.h" |
| #include "base/logging.h" |
| #include "base/mac/scoped_ioobject.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "build/build_config.h" |
| #include "util/mac/sysctl.h" |
| |
| extern "C" { |
| // Private CoreFoundation internals. See 10.9.2 CF-855.14/CFPriv.h and |
| // CF-855.14/CFUtilities.c. These are marked for weak import because they’re |
| // private and subject to change. |
| |
| #define WEAK_IMPORT __attribute__((weak_import)) |
| |
| // Don’t call these functions directly, call them through the |
| // TryCFCopy*VersionDictionary() helpers to account for the possibility that |
| // they may not be present at runtime. |
| CFDictionaryRef _CFCopySystemVersionDictionary() WEAK_IMPORT; |
| |
| // Don’t use these constants with CFDictionaryGetValue() directly, use them with |
| // the TryCFDictionaryGetValue() wrapper to account for the possibility that |
| // they may not be present at runtime. |
| extern const CFStringRef _kCFSystemVersionProductNameKey WEAK_IMPORT; |
| extern const CFStringRef _kCFSystemVersionProductVersionKey WEAK_IMPORT; |
| extern const CFStringRef _kCFSystemVersionProductVersionExtraKey WEAK_IMPORT; |
| extern const CFStringRef _kCFSystemVersionBuildVersionKey WEAK_IMPORT; |
| |
| #undef WEAK_IMPORT |
| |
| } // extern "C" |
| |
| namespace { |
| |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_13_4 |
| // Returns the running system’s Darwin major version. Don’t call this, it’s an |
| // implementation detail and its result is meant to be cached by |
| // MacOSVersionNumber(). |
| // |
| // This is very similar to Chromium’s base/mac/mac_util.mm |
| // DarwinMajorVersionInternal(). |
| int DarwinMajorVersion() { |
| // base::OperatingSystemVersionNumbers calls Gestalt(), which is a |
| // higher-level function than is needed. It might perform unnecessary |
| // operations. On 10.6, it was observed to be able to spawn threads (see |
| // https://crbug.com/53200). It might also read files or perform other |
| // blocking operations. Actually, nobody really knows for sure just what |
| // Gestalt() might do, or what it might be taught to do in the future. |
| // |
| // uname(), on the other hand, is implemented as a simple series of sysctl() |
| // system calls to obtain the relevant data from the kernel. The data is |
| // compiled right into the kernel, so no threads or blocking or other funny |
| // business is necessary. |
| |
| utsname uname_info; |
| int rv = uname(&uname_info); |
| PCHECK(rv == 0) << "uname"; |
| |
| DCHECK_EQ(strcmp(uname_info.sysname, "Darwin"), 0) |
| << "unexpected sysname " << uname_info.sysname; |
| |
| char* dot = strchr(uname_info.release, '.'); |
| CHECK(dot); |
| |
| int darwin_major_version = 0; |
| CHECK(base::StringToInt( |
| base::StringPiece(uname_info.release, dot - uname_info.release), |
| &darwin_major_version)); |
| |
| return darwin_major_version; |
| } |
| #endif // DT < 10.13.4 |
| |
| // Helpers for the weak-imported private CoreFoundation internals. |
| |
| CFDictionaryRef TryCFCopySystemVersionDictionary() { |
| if (_CFCopySystemVersionDictionary) { |
| return _CFCopySystemVersionDictionary(); |
| } |
| return nullptr; |
| } |
| |
| const void* TryCFDictionaryGetValue(CFDictionaryRef dictionary, |
| const void* value) { |
| if (value) { |
| return CFDictionaryGetValue(dictionary, value); |
| } |
| return nullptr; |
| } |
| |
| // Converts |version| to a triplet of version numbers on behalf of |
| // MacOSVersionNumber() and MacOSVersionComponents(). Returns true on success. |
| // If |version| does not have the expected format, returns false. |version| must |
| // be in the form "10.9.2" or just "10.9". In the latter case, |bugfix| will be |
| // set to 0. |
| bool StringToVersionNumbers(const std::string& version, |
| int* major, |
| int* minor, |
| int* bugfix) { |
| size_t first_dot = version.find_first_of('.'); |
| if (first_dot == 0 || first_dot == std::string::npos || |
| first_dot == version.length() - 1) { |
| LOG(ERROR) << "version has unexpected format"; |
| return false; |
| } |
| if (!base::StringToInt(base::StringPiece(&version[0], first_dot), major)) { |
| LOG(ERROR) << "version has unexpected format"; |
| return false; |
| } |
| |
| size_t second_dot = version.find_first_of('.', first_dot + 1); |
| if (second_dot == version.length() - 1) { |
| LOG(ERROR) << "version has unexpected format"; |
| return false; |
| } else if (second_dot == std::string::npos) { |
| second_dot = version.length(); |
| } |
| |
| if (!base::StringToInt(base::StringPiece(&version[first_dot + 1], |
| second_dot - first_dot - 1), |
| minor)) { |
| LOG(ERROR) << "version has unexpected format"; |
| return false; |
| } |
| |
| if (second_dot == version.length()) { |
| *bugfix = 0; |
| } else if (!base::StringToInt( |
| base::StringPiece(&version[second_dot + 1], |
| version.length() - second_dot - 1), |
| bugfix)) { |
| LOG(ERROR) << "version has unexpected format"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::string IORegistryEntryDataPropertyAsString(io_registry_entry_t entry, |
| CFStringRef key) { |
| base::apple::ScopedCFTypeRef<CFTypeRef> property( |
| IORegistryEntryCreateCFProperty(entry, key, kCFAllocatorDefault, 0)); |
| CFDataRef data = base::apple::CFCast<CFDataRef>(property.get()); |
| if (data && CFDataGetLength(data) > 0) { |
| return reinterpret_cast<const char*>(CFDataGetBytePtr(data)); |
| } |
| |
| return std::string(); |
| } |
| |
| } // namespace |
| |
| namespace crashpad { |
| |
| int MacOSVersionNumber() { |
| static int macos_version_number = []() { |
| // kern.osproductversion is a lightweight way to get the operating system |
| // version from the kernel without having to open any files or spin up any |
| // threads, but it’s only available in macOS 10.13.4 and later. |
| std::string macos_version_number_string = ReadStringSysctlByName( |
| "kern.osproductversion", |
| __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_13_4); |
| if (!macos_version_number_string.empty()) { |
| int major; |
| int minor; |
| int bugfix; |
| if (StringToVersionNumbers( |
| macos_version_number_string, &major, &minor, &bugfix)) { |
| DCHECK_GE(major, 10); |
| DCHECK_LE(major, 99); |
| DCHECK_GE(minor, 0); |
| DCHECK_LE(minor, 99); |
| DCHECK_GE(bugfix, 0); |
| DCHECK_LE(bugfix, 99); |
| return major * 1'00'00 + minor * 1'00 + bugfix; |
| } |
| } |
| |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_13_4 |
| // On macOS 10.13.4 and later, the sysctlbyname above should have been |
| // successful. |
| NOTREACHED(); |
| #else // DT >= 10.13.4 |
| // The Darwin major version is always 4 greater than the macOS minor version |
| // for Darwin versions beginning with 6, corresponding to Mac OS X 10.2, |
| // through Darwin 19, corresponding to macOS 10.15. |
| int darwin_major_version = DarwinMajorVersion(); |
| DCHECK_GE(darwin_major_version, 6); |
| DCHECK_LE(darwin_major_version, 19); |
| |
| int macos_version_number = 10'00'00 + (darwin_major_version - 4) * 1'00; |
| |
| // On macOS 10.13.4 and later, the sysctlbyname above should have been |
| // successful. |
| DCHECK_LT(macos_version_number, 10'13'04); |
| |
| return macos_version_number; |
| #endif // DT >= 10.13.4 |
| }(); |
| |
| return macos_version_number; |
| } |
| |
| bool MacOSVersionComponents(int* major, |
| int* minor, |
| int* bugfix, |
| std::string* build, |
| std::string* version_string) { |
| base::apple::ScopedCFTypeRef<CFDictionaryRef> dictionary( |
| TryCFCopySystemVersionDictionary()); |
| if (!dictionary) { |
| LOG(ERROR) << "_CFCopySystemVersionDictionary failed"; |
| return false; |
| } |
| |
| bool success = true; |
| |
| CFStringRef version_cf = |
| base::apple::CFCast<CFStringRef>(TryCFDictionaryGetValue( |
| dictionary.get(), _kCFSystemVersionProductVersionKey)); |
| std::string version; |
| if (!version_cf) { |
| LOG(ERROR) << "version_cf not found"; |
| success = false; |
| } else { |
| version = base::SysCFStringRefToUTF8(version_cf); |
| if (!StringToVersionNumbers(version, major, minor, bugfix)) { |
| success = false; |
| } else { |
| DCHECK_GE(*major, 10); |
| DCHECK_LE(*major, 99); |
| DCHECK_GE(*minor, 0); |
| DCHECK_LE(*minor, 99); |
| DCHECK_GE(*bugfix, 0); |
| DCHECK_LE(*bugfix, 99); |
| } |
| } |
| |
| CFStringRef build_cf = |
| base::apple::CFCast<CFStringRef>(TryCFDictionaryGetValue( |
| dictionary.get(), _kCFSystemVersionBuildVersionKey)); |
| if (!build_cf) { |
| LOG(ERROR) << "build_cf not found"; |
| success = false; |
| } else { |
| build->assign(base::SysCFStringRefToUTF8(build_cf)); |
| } |
| |
| CFStringRef product_cf = |
| base::apple::CFCast<CFStringRef>(TryCFDictionaryGetValue( |
| dictionary.get(), _kCFSystemVersionProductNameKey)); |
| std::string product; |
| if (!product_cf) { |
| LOG(ERROR) << "product_cf not found"; |
| success = false; |
| } else { |
| product = base::SysCFStringRefToUTF8(product_cf); |
| } |
| |
| // This key is not required, and in fact is normally not present. |
| CFStringRef extra_cf = |
| base::apple::CFCast<CFStringRef>(TryCFDictionaryGetValue( |
| dictionary.get(), _kCFSystemVersionProductVersionExtraKey)); |
| std::string extra; |
| if (extra_cf) { |
| extra = base::SysCFStringRefToUTF8(extra_cf); |
| } |
| |
| if (!product.empty() || !version.empty() || !build->empty()) { |
| if (!extra.empty()) { |
| version_string->assign(base::StringPrintf("%s %s %s (%s)", |
| product.c_str(), |
| version.c_str(), |
| extra.c_str(), |
| build->c_str())); |
| } else { |
| version_string->assign(base::StringPrintf( |
| "%s %s (%s)", product.c_str(), version.c_str(), build->c_str())); |
| } |
| } |
| |
| return success; |
| } |
| |
| void MacModelAndBoard(std::string* model, std::string* board_id) { |
| base::mac::ScopedIOObject<io_service_t> platform_expert( |
| IOServiceGetMatchingService(kIOMasterPortDefault, |
| IOServiceMatching("IOPlatformExpertDevice"))); |
| if (platform_expert) { |
| model->assign(IORegistryEntryDataPropertyAsString(platform_expert.get(), |
| CFSTR("model"))); |
| #if defined(ARCH_CPU_X86_FAMILY) |
| CFStringRef kBoardProperty = CFSTR("board-id"); |
| #elif defined(ARCH_CPU_ARM64) |
| // TODO(https://crashpad.chromium.org/bug/352): When production arm64 |
| // hardware is available, determine whether board-id works and switch to it |
| // if feasible, otherwise, determine whether target-type remains a viable |
| // alternative. |
| CFStringRef kBoardProperty = CFSTR("target-type"); |
| #endif |
| board_id->assign(IORegistryEntryDataPropertyAsString(platform_expert.get(), |
| kBoardProperty)); |
| } else { |
| model->clear(); |
| board_id->clear(); |
| } |
| } |
| |
| } // namespace crashpad |