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:
Mark Mentovai 2014-08-15 22:33:14 -07:00
parent a70edb3b7f
commit 8fe32b7b9c
4 changed files with 507 additions and 0 deletions

282
util/mac/mac_util.cc Normal file
View 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 theyre
// private and subject to change.
#define WEAK_IMPORT __attribute__((weak_import))
// Dont 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;
// Dont 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 systems Darwin major version. Dont call this, its an
// implementation detail and its result is meant to be cached by
// MacOSXMinorVersion().
//
// This is very similar to Chromiums 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
View 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 Chromiums 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 systems 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 systems model name. A mid-2012 15" MacBook Pro would
//! report “MacBookPro10,1”.
//! \param[out] board_id The systems 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
View 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
// commands 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) {
// Theres not much that can be done to test these, so just make sure theyre
// not empty. The model could be compared against the parsed output of
// “system_profiler SPHardwareDataType”, but the board doesnt show up
// anywhere other than the I/O Registry, and thats exactly how
// MacModelAndBoard() gets the data, so it wouldnt be a very useful test.
std::string model;
std::string board;
MacModelAndBoard(&model, &board);
EXPECT_FALSE(model.empty());
EXPECT_FALSE(board.empty());
}
} // namespace

View File

@ -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',