// Copyright 2014 The Crashpad Authors. All rights reserved. // // 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 #include #include #include #include #include "base/logging.h" #include "base/mac/foundation_util.h" #include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_ioobject.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" 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; CFDictionaryRef _CFCopyServerVersionDictionary() 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 { // 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 // MacOSXMinorVersion(). // // 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 // http://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; } // Helpers for the weak-imported private CoreFoundation internals. CFDictionaryRef TryCFCopySystemVersionDictionary() { if (_CFCopySystemVersionDictionary) { return _CFCopySystemVersionDictionary(); } return nullptr; } CFDictionaryRef TryCFCopyServerVersionDictionary() { if (_CFCopyServerVersionDictionary) { return _CFCopyServerVersionDictionary(); } 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 // MacOSXVersion(). 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::ScopedCFTypeRef property( IORegistryEntryCreateCFProperty(entry, key, kCFAllocatorDefault, 0)); CFDataRef data = base::mac::CFCast(property); if (data && CFDataGetLength(data) > 0) { return reinterpret_cast(CFDataGetBytePtr(data)); } return std::string(); } } // namespace namespace crashpad { int MacOSXMinorVersion() { // 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. static int mac_os_x_minor_version = DarwinMajorVersion() - 4; DCHECK(mac_os_x_minor_version >= 2); return mac_os_x_minor_version; } bool MacOSXVersion(int* major, int* minor, int* bugfix, std::string* build, bool* server, std::string* version_string) { base::ScopedCFTypeRef dictionary( TryCFCopyServerVersionDictionary()); if (dictionary) { *server = true; } else { dictionary.reset(TryCFCopySystemVersionDictionary()); if (!dictionary) { LOG(ERROR) << "_CFCopySystemVersionDictionary failed"; return false; } *server = false; } bool success = true; CFStringRef version_cf = base::mac::CFCast( TryCFDictionaryGetValue(dictionary, _kCFSystemVersionProductVersionKey)); std::string version; if (!version_cf) { LOG(ERROR) << "version_cf not found"; success = false; } else { version = base::SysCFStringRefToUTF8(version_cf); success &= StringToVersionNumbers(version, major, minor, bugfix); } CFStringRef build_cf = base::mac::CFCast( TryCFDictionaryGetValue(dictionary, _kCFSystemVersionBuildVersionKey)); if (!build_cf) { LOG(ERROR) << "build_cf not found"; success = false; } else { build->assign(base::SysCFStringRefToUTF8(build_cf)); } CFStringRef product_cf = base::mac::CFCast( TryCFDictionaryGetValue(dictionary, _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::mac::CFCast(TryCFDictionaryGetValue( dictionary, _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 platform_expert( IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))); if (platform_expert) { model->assign( IORegistryEntryDataPropertyAsString(platform_expert, CFSTR("model"))); board_id->assign(IORegistryEntryDataPropertyAsString(platform_expert, CFSTR("board-id"))); } else { model->clear(); board_id->clear(); } } } // namespace crashpad