mirror of
https://github.com/chromium/crashpad.git
synced 2025-01-14 01:08:01 +08:00
Add mac_util, including MacOSXMinorVersion(), MacOSXVersion(), and
MacModelAndBoard(), along with their tests. TEST=util_test MacUtil.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/473023002
This commit is contained in:
parent
a70edb3b7f
commit
8fe32b7b9c
282
util/mac/mac_util.cc
Normal file
282
util/mac/mac_util.cc
Normal file
@ -0,0 +1,282 @@
|
||||
// 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 <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#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 NULL;
|
||||
}
|
||||
|
||||
CFDictionaryRef TryCFCopyServerVersionDictionary() {
|
||||
if (_CFCopyServerVersionDictionary) {
|
||||
return _CFCopyServerVersionDictionary();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const void* TryCFDictionaryGetValue(CFDictionaryRef dictionary,
|
||||
const void* value) {
|
||||
if (value) {
|
||||
return CFDictionaryGetValue(dictionary, value);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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<CFTypeRef> property(
|
||||
IORegistryEntryCreateCFProperty(entry, key, kCFAllocatorDefault, 0));
|
||||
CFDataRef data = base::mac::CFCast<CFDataRef>(property);
|
||||
if (data && CFDataGetLength(data) > 0) {
|
||||
return reinterpret_cast<const char*>(CFDataGetBytePtr(data));
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
int MacOSXMinorVersion() {
|
||||
// The Darwin major version is always 4 greater than the Mac OS X 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<CFDictionaryRef> 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<CFStringRef>(
|
||||
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<CFStringRef>(
|
||||
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<CFStringRef>(
|
||||
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<CFStringRef>(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<io_service_t> 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
|
73
util/mac/mac_util.h
Normal file
73
util/mac/mac_util.h
Normal file
@ -0,0 +1,73 @@
|
||||
// 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_MAC_MAC_UTIL_H_
|
||||
#define CRASHPAD_UTIL_MAC_MAC_UTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Returns the version of the running operating system.
|
||||
//!
|
||||
//! \return The minor version of the operating system, such as `9` for Mac OS X
|
||||
//! 10.9.2.
|
||||
//!
|
||||
//! \note This is similar to the base::mac::IsOS*() family of functions, but
|
||||
//! is provided for situations where the caller needs to obtain version
|
||||
//! information beyond what is provided by Chromium’s base, or for when the
|
||||
//! caller needs the actual minor version value.
|
||||
int MacOSXMinorVersion();
|
||||
|
||||
//! \brief Returns the version of the running operating system.
|
||||
//!
|
||||
//! All parameters are required. No parameter may be `NULL`.
|
||||
//!
|
||||
//! \param[out] major The major version of the operating system, such as `10`
|
||||
//! for Mac OS X 10.9.2.
|
||||
//! \param[out] minor The major version of the operating system, such as `9` for
|
||||
//! Mac OS X 10.9.2.
|
||||
//! \param[out] bugfix The bugfix version of the operating system, such as `2`
|
||||
//! for Mac OS X 10.9.2.
|
||||
//! \param[out] build The operating system’s build string, such as "13C64" for
|
||||
//! Mac OS X 10.9.2.
|
||||
//! \param[out] server `true` for a Mac OS X Server installation, `false`
|
||||
//! otherwise (for a desktop/laptop, client, or workstation system).
|
||||
//! \param[out] version_string A string representing the full operating system
|
||||
//! version, such as `"Mac OS X 10.9.2 (13C64)"`.
|
||||
//!
|
||||
//! \return `true` on success, `false` on failure, with an error message logged.
|
||||
//! A failure is considered to have occurred if any element could not be
|
||||
//! determined. When this happens, their values will be untouched, but other
|
||||
//! values that could be determined will still be set properly.
|
||||
bool MacOSXVersion(int* major,
|
||||
int* minor,
|
||||
int* bugfix,
|
||||
std::string* build,
|
||||
bool* server,
|
||||
std::string* version_string);
|
||||
|
||||
//! \brief Returns the model name and board ID of the running system.
|
||||
//!
|
||||
//! \param[out] model The system’s model name. A mid-2012 15" MacBook Pro would
|
||||
//! report “MacBookPro10,1”.
|
||||
//! \param[out] board_id The system’s board ID. A mid-2012 15" MacBook Pro would
|
||||
//! report “Mac-C3EC7CD22292981F”.
|
||||
//!
|
||||
//! If a value cannot be determined, its string is cleared.
|
||||
void MacModelAndBoard(std::string* model, std::string* board_id);
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_MAC_MAC_UTIL_H_
|
149
util/mac/mac_util_test.mm
Normal file
149
util/mac/mac_util_test.mm
Normal file
@ -0,0 +1,149 @@
|
||||
// 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"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifdef __GLIBCXX__
|
||||
// When C++ exceptions are disabled, libstdc++ from GCC 4.2 defines |try| and
|
||||
// |catch| so as to allow exception-expecting C++ code to build properly when
|
||||
// language support for exceptions is not present. These macros interfere with
|
||||
// the use of |@try| and |@catch| in Objective-C files such as this one.
|
||||
// Undefine these macros here, after everything has been #included, since there
|
||||
// will be no C++ uses and only Objective-C uses from this point on.
|
||||
#undef try
|
||||
#undef catch
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace crashpad;
|
||||
|
||||
// Runs /usr/bin/sw_vers with a single argument, |argument|, and places the
|
||||
// command’s standard output into |output| after stripping the trailing newline.
|
||||
// Fatal gtest assertions report tool failures, which the caller should check
|
||||
// for with testing::Test::HasFatalFailure().
|
||||
void SwVers(NSString* argument, std::string* output) {
|
||||
@autoreleasepool {
|
||||
base::scoped_nsobject<NSPipe> pipe([[NSPipe alloc] init]);
|
||||
base::scoped_nsobject<NSTask> task([[NSTask alloc] init]);
|
||||
[task setStandardOutput:pipe];
|
||||
[task setLaunchPath:@"/usr/bin/sw_vers"];
|
||||
[task setArguments:@[ argument ]];
|
||||
|
||||
@try {
|
||||
[task launch];
|
||||
}
|
||||
@catch (NSException* exception) {
|
||||
FAIL() << [[exception name] UTF8String] << ": "
|
||||
<< [[exception reason] UTF8String];
|
||||
}
|
||||
|
||||
NSData* data = [[pipe fileHandleForReading] readDataToEndOfFile];
|
||||
[task waitUntilExit];
|
||||
|
||||
ASSERT_EQ(NSTaskTerminationReasonExit, [task terminationReason]);
|
||||
ASSERT_EQ(EXIT_SUCCESS, [task terminationStatus]);
|
||||
|
||||
output->assign(reinterpret_cast<const char*>([data bytes]), [data length]);
|
||||
|
||||
EXPECT_EQ('\n', output->at(output->size() - 1));
|
||||
output->resize(output->size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MacUtil, MacOSXVersion) {
|
||||
int major;
|
||||
int minor;
|
||||
int bugfix;
|
||||
std::string build;
|
||||
bool server;
|
||||
std::string version_string;
|
||||
ASSERT_TRUE(
|
||||
MacOSXVersion(&major, &minor, &bugfix, &build, &server, &version_string));
|
||||
|
||||
std::string version;
|
||||
if (bugfix) {
|
||||
version = base::StringPrintf("%d.%d.%d", major, minor, bugfix);
|
||||
} else {
|
||||
// 10.x.0 releases report their version string as simply 10.x.
|
||||
version = base::StringPrintf("%d.%d", major, minor);
|
||||
}
|
||||
|
||||
std::string expected_product_version;
|
||||
SwVers(@"-productVersion", &expected_product_version);
|
||||
if (Test::HasFatalFailure()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_EQ(expected_product_version, version);
|
||||
|
||||
std::string expected_build_version;
|
||||
SwVers(@"-buildVersion", &expected_build_version);
|
||||
if (Test::HasFatalFailure()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_EQ(expected_build_version, build);
|
||||
|
||||
std::string expected_product_name;
|
||||
SwVers(@"-productName", &expected_product_name);
|
||||
if (Test::HasFatalFailure()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for a space after the product name in the complete version string.
|
||||
expected_product_name += ' ';
|
||||
EXPECT_EQ(0u, version_string.find(expected_product_name));
|
||||
}
|
||||
|
||||
TEST(MacUtil, MacOSXMinorVersion) {
|
||||
// Make sure that MacOSXMinorVersion() and MacOSXVersion() agree. The two have
|
||||
// their own distinct implementations, and the latter was checked against
|
||||
// sw_vers above.
|
||||
int major;
|
||||
int minor;
|
||||
int bugfix;
|
||||
std::string build;
|
||||
bool server;
|
||||
std::string version_string;
|
||||
ASSERT_TRUE(
|
||||
MacOSXVersion(&major, &minor, &bugfix, &build, &server, &version_string));
|
||||
|
||||
EXPECT_EQ(minor, MacOSXMinorVersion());
|
||||
}
|
||||
|
||||
TEST(MacUtil, MacModelAndBoard) {
|
||||
// There’s not much that can be done to test these, so just make sure they’re
|
||||
// not empty. The model could be compared against the parsed output of
|
||||
// “system_profiler SPHardwareDataType”, but the board doesn’t show up
|
||||
// anywhere other than the I/O Registry, and that’s exactly how
|
||||
// MacModelAndBoard() gets the data, so it wouldn’t be a very useful test.
|
||||
std::string model;
|
||||
std::string board;
|
||||
MacModelAndBoard(&model, &board);
|
||||
|
||||
EXPECT_FALSE(model.empty());
|
||||
EXPECT_FALSE(board.empty());
|
||||
}
|
||||
|
||||
} // namespace
|
@ -34,6 +34,8 @@
|
||||
'file/string_file_writer.h',
|
||||
'mac/launchd.h',
|
||||
'mac/launchd.mm',
|
||||
'mac/mac_util.cc',
|
||||
'mac/mac_util.h',
|
||||
'mac/service_management.cc',
|
||||
'mac/service_management.h',
|
||||
'mach/task_memory.cc',
|
||||
@ -89,6 +91,7 @@
|
||||
'sources': [
|
||||
'file/string_file_writer_test.cc',
|
||||
'mac/launchd_test.mm',
|
||||
'mac/mac_util_test.mm',
|
||||
'mac/service_management_test.mm',
|
||||
'mach/task_memory_test.cc',
|
||||
'misc/initialization_state_dcheck_test.cc',
|
||||
|
Loading…
x
Reference in New Issue
Block a user