diff --git a/util/mac/launchd.h b/util/mac/launchd.h new file mode 100644 index 00000000..f9876e2d --- /dev/null +++ b/util/mac/launchd.h @@ -0,0 +1,39 @@ +// 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_LAUNCHD_H_ +#define CRASHPAD_UTIL_MAC_LAUNCHD_H_ + +#include +#include + +namespace crashpad { + +//! \brief Converts a Core Foundation-type property list to a launchd-type +//! `launch_data_t`. +//! +//! \param[in] property_cf The Core Foundation-type property list to convert. +//! +//! \return The converted launchd-type `launch_data_t`. The caller takes +//! ownership of the returned value. On error, returns `NULL`. +//! +//! \note This function handles all `CFPropertyListRef` types except for +//! `CFDateRef`, because there’s no `launch_data_type_t` analogue. Not all +//! types supported in a launchd-type `launch_data_t` have +//! `CFPropertyListRef` analogues. +launch_data_t CFPropertyToLaunchData(CFPropertyListRef property_cf); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MAC_LAUNCHD_H_ diff --git a/util/mac/launchd.mm b/util/mac/launchd.mm new file mode 100644 index 00000000..5d9b5481 --- /dev/null +++ b/util/mac/launchd.mm @@ -0,0 +1,141 @@ +// 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/launchd.h" + +#import + +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_launch_data.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/sys_string_conversions.h" + +namespace crashpad { + +launch_data_t CFPropertyToLaunchData(CFPropertyListRef property_cf) { + @autoreleasepool { + // This function mixes Foundation and Core Foundation access to property + // list elements according to which is more convenient and correct for any + // specific task. + + launch_data_t data_launch = NULL; + CFTypeID type_id_cf = CFGetTypeID(property_cf); + + if (type_id_cf == CFDictionaryGetTypeID()) { + NSDictionary* dictionary_ns = base::mac::CFToNSCast( + base::mac::CFCastStrict(property_cf)); + base::mac::ScopedLaunchData dictionary_launch( + launch_data_alloc(LAUNCH_DATA_DICTIONARY)); + + for (NSString* key in dictionary_ns) { + if (![key isKindOfClass:[NSString class]]) { + return NULL; + } + + CFPropertyListRef value_cf = + static_cast([dictionary_ns objectForKey:key]); + launch_data_t value_launch = CFPropertyToLaunchData(value_cf); + if (!value_launch) { + return NULL; + } + + launch_data_dict_insert( + dictionary_launch, value_launch, [key UTF8String]); + } + + data_launch = dictionary_launch.release(); + + } else if (type_id_cf == CFArrayGetTypeID()) { + NSArray* array_ns = base::mac::CFToNSCast( + base::mac::CFCastStrict(property_cf)); + base::mac::ScopedLaunchData array_launch( + launch_data_alloc(LAUNCH_DATA_ARRAY)); + size_t index = 0; + + for (id element_ns in array_ns) { + CFPropertyListRef element_cf = + static_cast(element_ns); + launch_data_t element_launch = CFPropertyToLaunchData(element_cf); + if (!element_launch) { + return NULL; + } + + launch_data_array_set_index(array_launch, element_launch, index++); + } + + data_launch = array_launch.release(); + + } else if (type_id_cf == CFNumberGetTypeID()) { + CFNumberRef number_cf = base::mac::CFCastStrict(property_cf); + NSNumber* number_ns = base::mac::CFToNSCast(number_cf); + switch (CFNumberGetType(number_cf)) { + case kCFNumberSInt8Type: + case kCFNumberSInt16Type: + case kCFNumberSInt32Type: + case kCFNumberSInt64Type: + case kCFNumberCharType: + case kCFNumberShortType: + case kCFNumberIntType: + case kCFNumberLongType: + case kCFNumberLongLongType: + case kCFNumberCFIndexType: + case kCFNumberNSIntegerType: { + data_launch = launch_data_new_integer([number_ns longLongValue]); + break; + } + + case kCFNumberFloat32Type: + case kCFNumberFloat64Type: + case kCFNumberFloatType: + case kCFNumberDoubleType: { + data_launch = launch_data_new_real([number_ns doubleValue]); + break; + } + + default: { return NULL; } + } + + } else if (type_id_cf == CFBooleanGetTypeID()) { + CFBooleanRef boolean_cf = + base::mac::CFCastStrict(property_cf); + data_launch = launch_data_new_bool(CFBooleanGetValue(boolean_cf)); + + } else if (type_id_cf == CFStringGetTypeID()) { + NSString* string_ns = base::mac::CFToNSCast( + base::mac::CFCastStrict(property_cf)); + + // -fileSystemRepresentation might be more correct than -UTF8String, + // because these strings can hold paths. The analogous function in + // launchctl, CF2launch_data() (10.9.4 + // launchd-842.92.1/support/launchctl.c), uses UTF-8 instead of filesystem + // encoding, so do the same here. Note that there’s another occurrence of + // -UTF8String above, used for dictionary keys. + data_launch = launch_data_new_string([string_ns UTF8String]); + + } else if (type_id_cf == CFDataGetTypeID()) { + NSData* data_ns = base::mac::CFToNSCast( + base::mac::CFCastStrict(property_cf)); + data_launch = launch_data_new_opaque([data_ns bytes], [data_ns length]); + } else { + base::ScopedCFTypeRef type_name_cf( + CFCopyTypeIDDescription(type_id_cf)); + DLOG(ERROR) << "unable to convert CFTypeID " << type_id_cf << " (" + << base::SysCFStringRefToUTF8(type_name_cf) << ")"; + } + + return data_launch; + } +} + +} // namespace crashpad diff --git a/util/mac/launchd_test.mm b/util/mac/launchd_test.mm new file mode 100644 index 00000000..b4c8f34a --- /dev/null +++ b/util/mac/launchd_test.mm @@ -0,0 +1,303 @@ +// 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/launchd.h" + +#import +#include +#include + +#include +#include + +#include "base/basictypes.h" +#include "base/mac/scoped_launch_data.h" +#include "gtest/gtest.h" +#include "util/stdlib/objc.h" + +namespace { + +using namespace crashpad; + +TEST(Launchd, CFPropertyToLaunchData_Integer) { + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + NSNumber* integer_nses[] = { + @0, + @1, + @-1, + @0x7f, + @0x80, + @0xff, + @0x0100, + @0x7fff, + @0x8000, + @0xffff, + @0x00010000, + @0x7fffffff, + @0x80000000, + @0xffffffff, + @0x1000000000000000, + @0x7fffffffffffffff, + @0x8000000000000000, + @0xffffffffffffffff, + @0x0123456789abcdef, + @0xfedcba9876543210, + }; + + for (size_t index = 0; index < arraysize(integer_nses); ++index) { + NSNumber* integer_ns = integer_nses[index]; + launch_data.reset(CFPropertyToLaunchData(integer_ns)); + ASSERT_TRUE(launch_data.get()); + ASSERT_EQ(LAUNCH_DATA_INTEGER, launch_data_get_type(launch_data)); + EXPECT_EQ( + [integer_ns longLongValue], launch_data_get_integer(launch_data)) + << "index " << index; + } + } +} + +TEST(Launchd, CFPropertyToLaunchData_FloatingPoint) { + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + NSNumber* double_nses[] = { + @0.0, + @1.0, + @-1.0, + [NSNumber numberWithFloat:std::numeric_limits::min()], + [NSNumber numberWithFloat:std::numeric_limits::max()], + [NSNumber numberWithFloat:std::numeric_limits::min()], + [NSNumber numberWithFloat:std::numeric_limits::max()], + @3.1415926535897932, + [NSNumber numberWithFloat:std::numeric_limits::infinity()], + [NSNumber numberWithFloat:std::numeric_limits::quiet_NaN()], + [NSNumber numberWithFloat:std::numeric_limits::signaling_NaN()], + }; + + for (size_t index = 0; index < arraysize(double_nses); ++index) { + NSNumber* double_ns = double_nses[index]; + launch_data.reset(CFPropertyToLaunchData(double_ns)); + ASSERT_TRUE(launch_data.get()); + ASSERT_EQ(LAUNCH_DATA_REAL, launch_data_get_type(launch_data)); + double expected_double_value = [double_ns doubleValue]; + double observed_double_value = launch_data_get_real(launch_data); + bool expected_is_nan = std::isnan(expected_double_value); + EXPECT_EQ(expected_is_nan, std::isnan(observed_double_value)); + if (!expected_is_nan) { + EXPECT_DOUBLE_EQ(expected_double_value, observed_double_value) + << "index " << index; + } + } + } +} + +TEST(Launchd, CFPropertyToLaunchData_Boolean) { + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + NSNumber* bool_nses[] = { + @NO, + @YES, + }; + + for (size_t index = 0; index < arraysize(bool_nses); ++index) { + NSNumber* bool_ns = bool_nses[index]; + launch_data.reset(CFPropertyToLaunchData(bool_ns)); + ASSERT_TRUE(launch_data.get()); + ASSERT_EQ(LAUNCH_DATA_BOOL, launch_data_get_type(launch_data)); + if ([bool_ns boolValue]) { + EXPECT_TRUE(launch_data_get_bool(launch_data)); + } else { + EXPECT_FALSE(launch_data_get_bool(launch_data)); + } + } + } +} + +TEST(Launchd, CFPropertyToLaunchData_String) { + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + NSString* string_nses[] = { + @"", + @"string", + @"Üñîçø∂é", + }; + + for (size_t index = 0; index < arraysize(string_nses); ++index) { + NSString* string_ns = string_nses[index]; + launch_data.reset(CFPropertyToLaunchData(string_ns)); + ASSERT_TRUE(launch_data.get()); + ASSERT_EQ(LAUNCH_DATA_STRING, launch_data_get_type(launch_data)); + EXPECT_STREQ([string_ns UTF8String], launch_data_get_string(launch_data)); + } + } +} + +TEST(Launchd, CFPropertyToLaunchData_Data) { + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + const uint8_t data_c[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 3, 2}; + NSData* data_ns = [NSData dataWithBytes:data_c length:sizeof(data_c)]; + launch_data.reset(CFPropertyToLaunchData(data_ns)); + ASSERT_TRUE(launch_data.get()); + ASSERT_EQ(LAUNCH_DATA_OPAQUE, launch_data_get_type(launch_data)); + EXPECT_EQ(sizeof(data_c), launch_data_get_opaque_size(launch_data)); + EXPECT_EQ( + 0, memcmp(launch_data_get_opaque(launch_data), data_c, sizeof(data_c))); + } +} + +TEST(Launchd, CFPropertyToLaunchData_Dictionary) { + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + NSDictionary* dictionary_ns = @{ + @"key" : @"value", + }; + + launch_data.reset(CFPropertyToLaunchData(dictionary_ns)); + ASSERT_TRUE(launch_data.get()); + ASSERT_EQ(LAUNCH_DATA_DICTIONARY, launch_data_get_type(launch_data)); + EXPECT_EQ([dictionary_ns count], launch_data_dict_get_count(launch_data)); + + launch_data_t launch_lookup_data = + launch_data_dict_lookup(launch_data, "key"); + ASSERT_TRUE(launch_lookup_data); + ASSERT_EQ(LAUNCH_DATA_STRING, launch_data_get_type(launch_lookup_data)); + EXPECT_STREQ("value", launch_data_get_string(launch_lookup_data)); + } +} + +TEST(Launchd, CFPropertyToLaunchData_Array) { + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + NSArray* array_ns = @[ @"element_1", @"element_2", ]; + + launch_data.reset(CFPropertyToLaunchData(array_ns)); + ASSERT_TRUE(launch_data.get()); + ASSERT_EQ(LAUNCH_DATA_ARRAY, launch_data_get_type(launch_data)); + EXPECT_EQ([array_ns count], launch_data_array_get_count(launch_data)); + + launch_data_t launch_lookup_data = + launch_data_array_get_index(launch_data, 0); + ASSERT_TRUE(launch_lookup_data); + ASSERT_EQ(LAUNCH_DATA_STRING, launch_data_get_type(launch_lookup_data)); + EXPECT_STREQ("element_1", launch_data_get_string(launch_lookup_data)); + + launch_lookup_data = launch_data_array_get_index(launch_data, 1); + ASSERT_TRUE(launch_lookup_data); + ASSERT_EQ(LAUNCH_DATA_STRING, launch_data_get_type(launch_lookup_data)); + EXPECT_STREQ("element_2", launch_data_get_string(launch_lookup_data)); + } +} + +TEST(Launchd, CFPropertyToLaunchData_NSDate) { + // Test that NSDate conversions fail as advertised. There’s no type for + // storing date values in a launch_data_t. + + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + NSDate* date = [NSDate date]; + launch_data.reset(CFPropertyToLaunchData(date)); + EXPECT_FALSE(launch_data); + + NSDictionary* date_dictionary = @{ + @"key" : @"value", + @"date" : date, + }; + launch_data.reset(CFPropertyToLaunchData(date_dictionary)); + EXPECT_FALSE(launch_data); + + NSArray* date_array = @[ @"string_1", date, @"string_2", ]; + launch_data.reset(CFPropertyToLaunchData(date_array)); + EXPECT_FALSE(launch_data); + } +} + +TEST(Launchd, CFPropertyToLaunchData_RealWorldJobDictionary) { + @autoreleasepool { + base::mac::ScopedLaunchData launch_data; + + NSDictionary* job_dictionary = @{ + @LAUNCH_JOBKEY_LABEL : @"com.example.job.rebooter", + @LAUNCH_JOBKEY_ONDEMAND : @YES, + @LAUNCH_JOBKEY_PROGRAMARGUMENTS : + @[ @"/bin/bash", @"-c", @"/sbin/reboot", ], + @LAUNCH_JOBKEY_MACHSERVICES : @{ + @"com.example.service.rebooter" : @YES, + }, + }; + + launch_data.reset(CFPropertyToLaunchData(job_dictionary)); + ASSERT_TRUE(launch_data.get()); + ASSERT_EQ(LAUNCH_DATA_DICTIONARY, launch_data_get_type(launch_data)); + EXPECT_EQ(4u, launch_data_dict_get_count(launch_data)); + + launch_data_t launch_lookup_data = + launch_data_dict_lookup(launch_data, LAUNCH_JOBKEY_LABEL); + ASSERT_TRUE(launch_lookup_data); + ASSERT_EQ(LAUNCH_DATA_STRING, launch_data_get_type(launch_lookup_data)); + EXPECT_STREQ("com.example.job.rebooter", + launch_data_get_string(launch_lookup_data)); + + launch_lookup_data = + launch_data_dict_lookup(launch_data, LAUNCH_JOBKEY_ONDEMAND); + ASSERT_TRUE(launch_lookup_data); + ASSERT_EQ(LAUNCH_DATA_BOOL, launch_data_get_type(launch_lookup_data)); + EXPECT_TRUE(launch_data_get_bool(launch_lookup_data)); + + launch_lookup_data = + launch_data_dict_lookup(launch_data, LAUNCH_JOBKEY_PROGRAMARGUMENTS); + ASSERT_TRUE(launch_lookup_data); + ASSERT_EQ(LAUNCH_DATA_ARRAY, launch_data_get_type(launch_lookup_data)); + EXPECT_EQ(3u, launch_data_array_get_count(launch_lookup_data)); + + launch_data_t launch_sublookup_data = + launch_data_array_get_index(launch_lookup_data, 0); + ASSERT_TRUE(launch_sublookup_data); + ASSERT_EQ(LAUNCH_DATA_STRING, launch_data_get_type(launch_sublookup_data)); + EXPECT_STREQ("/bin/bash", launch_data_get_string(launch_sublookup_data)); + + launch_sublookup_data = launch_data_array_get_index(launch_lookup_data, 1); + ASSERT_TRUE(launch_sublookup_data); + ASSERT_EQ(LAUNCH_DATA_STRING, launch_data_get_type(launch_sublookup_data)); + EXPECT_STREQ("-c", launch_data_get_string(launch_sublookup_data)); + + launch_sublookup_data = launch_data_array_get_index(launch_lookup_data, 2); + ASSERT_TRUE(launch_sublookup_data); + ASSERT_EQ(LAUNCH_DATA_STRING, launch_data_get_type(launch_sublookup_data)); + EXPECT_STREQ("/sbin/reboot", launch_data_get_string(launch_sublookup_data)); + + launch_lookup_data = + launch_data_dict_lookup(launch_data, LAUNCH_JOBKEY_MACHSERVICES); + ASSERT_TRUE(launch_lookup_data); + ASSERT_EQ(LAUNCH_DATA_DICTIONARY, launch_data_get_type(launch_lookup_data)); + EXPECT_EQ(1u, launch_data_dict_get_count(launch_lookup_data)); + + launch_sublookup_data = launch_data_dict_lookup( + launch_lookup_data, "com.example.service.rebooter"); + ASSERT_TRUE(launch_sublookup_data); + ASSERT_EQ(LAUNCH_DATA_BOOL, launch_data_get_type(launch_sublookup_data)); + EXPECT_TRUE(launch_data_get_bool(launch_sublookup_data)); + } +} + +} // namespace diff --git a/util/mac/service_management.cc b/util/mac/service_management.cc index dc0ce71c..512a6059 100644 --- a/util/mac/service_management.cc +++ b/util/mac/service_management.cc @@ -14,88 +14,126 @@ #include "util/mac/service_management.h" +#include #include -#include +#include -#include "base/mac/foundation_util.h" -#include "base/mac/scoped_cftyperef.h" -#include "base/strings/sys_string_conversions.h" - -// ServiceManagement.framework is available on 10.6 and later, but it’s -// deprecated in 10.10. In case ServiceManagement.framework stops working in the -// future, an alternative implementation using launch_msg() is available. This -// implementation works on 10.5 and later, however, launch_msg() is also -// deprecated in 10.10. The alternative implementation can be resurrected from -// source control history. +#include "base/mac/scoped_launch_data.h" +#include "util/mac/launchd.h" namespace { -// Wraps the necessary functions from ServiceManagement.framework to avoid the -// deprecation warnings when using the 10.10 SDK. +launch_data_t LaunchDataDictionaryForJob(const std::string& label) { + base::mac::ScopedLaunchData request( + launch_data_alloc(LAUNCH_DATA_DICTIONARY)); + launch_data_dict_insert( + request, launch_data_new_string(label.c_str()), LAUNCH_KEY_GETJOB); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + base::mac::ScopedLaunchData response(launch_msg(request)); + if (launch_data_get_type(response) != LAUNCH_DATA_DICTIONARY) { + return NULL; + } -Boolean CallSMJobSubmit(CFStringRef domain, - CFDictionaryRef job, - AuthorizationRef authorization, - CFErrorRef *error) { - return SMJobSubmit(domain, job, authorization, error); + return response.release(); } -Boolean CallSMJobRemove(CFStringRef domain, - CFStringRef job_label, - AuthorizationRef authorization, - Boolean wait, - CFErrorRef *error) { - return SMJobRemove(domain, job_label, authorization, wait, error); -} - -CFDictionaryRef CallSMJobCopyDictionary( - CFStringRef domain, CFStringRef job_label) { - return SMJobCopyDictionary(domain, job_label); -} - -#pragma GCC diagnostic pop - } // namespace namespace crashpad { bool ServiceManagementSubmitJob(CFDictionaryRef job_cf) { - return CallSMJobSubmit(kSMDomainUserLaunchd, job_cf, NULL, NULL); + base::mac::ScopedLaunchData job_launch(CFPropertyToLaunchData(job_cf)); + if (!job_launch.get()) { + return false; + } + + base::mac::ScopedLaunchData jobs(launch_data_alloc(LAUNCH_DATA_ARRAY)); + launch_data_array_set_index(jobs, job_launch.release(), 0); + + base::mac::ScopedLaunchData request( + launch_data_alloc(LAUNCH_DATA_DICTIONARY)); + launch_data_dict_insert(request, jobs.release(), LAUNCH_KEY_SUBMITJOB); + + base::mac::ScopedLaunchData response(launch_msg(request)); + + if (launch_data_get_type(response) != LAUNCH_DATA_ARRAY) { + return false; + } + + if (launch_data_array_get_count(response) != 1) { + return false; + } + + launch_data_t response_element = launch_data_array_get_index(response, 0); + if (launch_data_get_type(response_element) != LAUNCH_DATA_ERRNO) { + return false; + } + + int err = launch_data_get_errno(response_element); + if (err != 0) { + return false; + } + + return true; } bool ServiceManagementRemoveJob(const std::string& label, bool wait) { - base::ScopedCFTypeRef label_cf( - base::SysUTF8ToCFStringRef(label)); - return CallSMJobRemove(kSMDomainUserLaunchd, label_cf, NULL, wait, NULL); + base::mac::ScopedLaunchData request( + launch_data_alloc(LAUNCH_DATA_DICTIONARY)); + launch_data_dict_insert( + request, launch_data_new_string(label.c_str()), LAUNCH_KEY_REMOVEJOB); + + base::mac::ScopedLaunchData response(launch_msg(request)); + if (launch_data_get_type(response) != LAUNCH_DATA_ERRNO) { + return false; + } + + int err = launch_data_get_errno(response); + if (err == EINPROGRESS) { + if (wait) { + // TODO(mark): Use a kqueue to wait for the process to exit. To avoid a + // race, the kqueue would need to be set up prior to asking launchd to + // remove the job. Even so, the job’s PID may change between the time it’s + // obtained and the time the kqueue is set up, so this is nontrivial. + do { + timespec sleep_time; + sleep_time.tv_sec = 0; + sleep_time.tv_nsec = 1E5; // 100 microseconds + nanosleep(&sleep_time, NULL); + } while (ServiceManagementIsJobLoaded(label)); + } + + return true; + } + + if (err != 0) { + return false; + } + + return true; } bool ServiceManagementIsJobLoaded(const std::string& label) { - base::ScopedCFTypeRef label_cf( - base::SysUTF8ToCFStringRef(label)); - base::ScopedCFTypeRef job_dictionary( - CallSMJobCopyDictionary(kSMDomainUserLaunchd, label_cf)); - return job_dictionary != NULL; + base::mac::ScopedLaunchData dictionary(LaunchDataDictionaryForJob(label)); + if (!dictionary) { + return false; + } + + return true; } pid_t ServiceManagementIsJobRunning(const std::string& label) { - base::ScopedCFTypeRef label_cf( - base::SysUTF8ToCFStringRef(label)); - base::ScopedCFTypeRef job_dictionary( - CallSMJobCopyDictionary(kSMDomainUserLaunchd, label_cf)); - if (job_dictionary != NULL) { - CFNumberRef pid_cf = base::mac::CFCast( - CFDictionaryGetValue(job_dictionary, CFSTR(LAUNCH_JOBKEY_PID))); - if (pid_cf) { - pid_t pid; - if (CFNumberGetValue(pid_cf, kCFNumberIntType, &pid)) { - return pid; - } - } + base::mac::ScopedLaunchData dictionary(LaunchDataDictionaryForJob(label)); + if (!dictionary) { + return 0; } - return 0; + + launch_data_t pid = launch_data_dict_lookup(dictionary, LAUNCH_JOBKEY_PID); + if (launch_data_get_type(pid) != LAUNCH_DATA_INTEGER) { + return 0; + } + + return launch_data_get_integer(pid); } } // namespace crashpad diff --git a/util/mac/service_management.h b/util/mac/service_management.h index 743a5a01..802e5449 100644 --- a/util/mac/service_management.h +++ b/util/mac/service_management.h @@ -41,13 +41,13 @@ bool ServiceManagementSubmitJob(CFDictionaryRef job_cf); //! //! \return `true` if the job was removed successfully or if an asynchronous //! attempt to remove the job was started successfully, otherwise `false`. -//! Unlike previous systems, on Mac OS X 10.10, this function returns `true` -//! even if \a label names a job that does not exist. This is filed as radar -//! 18268941. //! //! \note This function is provided because `SMJobRemove()` is deprecated in Mac -//! OS X 10.10. It may or may not be implemented using `SMJobRemove()` from -//! `ServiceManagement.framework`. +//! OS X 10.10. On Mac OS X 10.10, observed in DP8 14A361c, it also blocks +//! for far too long (`_block_until_job_exits()` contains a one-second +//! `sleep()`, filed as radar 18398683) and does not signal failure via its +//! return value when asked to remove a nonexistent job (filed as radar +//! 18268941). bool ServiceManagementRemoveJob(const std::string& label, bool wait); //! \brief Determines whether a specified job is loaded in the user launchd diff --git a/util/mac/service_management_test.mm b/util/mac/service_management_test.mm index 922c4d8a..3c1d6383 100644 --- a/util/mac/service_management_test.mm +++ b/util/mac/service_management_test.mm @@ -27,7 +27,6 @@ #include "base/strings/sys_string_conversions.h" #include "base/rand_util.h" #include "gtest/gtest.h" -#include "util/mac/mac_util.h" #include "util/posix/process_util.h" #include "util/stdlib/objc.h" @@ -56,7 +55,7 @@ void ExpectProcessIsRunning(pid_t pid, std::string& last_arg) { if (inner_tries > 0) { timespec sleep_time; sleep_time.tv_sec = 0; - sleep_time.tv_nsec = 1E5; // 100 microseconds + sleep_time.tv_nsec = 1E6; // 1 millisecond nanosleep(&sleep_time, NULL); } } while (inner_tries--); @@ -158,16 +157,8 @@ TEST(ServiceManagement, SubmitRemoveJob) { EXPECT_EQ(0, ServiceManagementIsJobRunning(kJobLabel)); // Now that the job is unloaded, a subsequent attempt to unload it should be - // an error. However, ServiceManagementRemoveJob does not properly report - // this error case on Mac OS X 10.10. - if (MacOSXMinorVersion() >= 10) { - // If this check starts failing because radar 18268941 is fixed, remove - // the OS version check here and revise the interface documentation for - // ServiceManagementRemoveJob(). - EXPECT_TRUE(ServiceManagementRemoveJob(kJobLabel, false)); - } else { - EXPECT_FALSE(ServiceManagementRemoveJob(kJobLabel, false)); - } + // an error. + EXPECT_FALSE(ServiceManagementRemoveJob(kJobLabel, false)); ExpectProcessIsNotRunning(job_pid, shell_script); } diff --git a/util/util.gyp b/util/util.gyp index 8464266d..d28238a6 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -25,11 +25,6 @@ '..', '<(INTERMEDIATE_DIR)', ], - 'link_settings': { - 'libraries': [ - '$(SDKROOT)/System/Library/Frameworks/ServiceManagement.framework', - ], - }, 'sources': [ 'file/fd_io.cc', 'file/fd_io.h', @@ -39,6 +34,8 @@ 'file/string_file_writer.h', 'mac/checked_mach_address_range.cc', 'mac/checked_mach_address_range.h', + 'mac/launchd.h', + 'mac/launchd.mm', 'mac/mac_util.cc', 'mac/mac_util.h', 'mac/mach_o_image_reader.cc', @@ -194,6 +191,7 @@ 'sources': [ 'file/string_file_writer_test.cc', 'mac/checked_mach_address_range_test.cc', + 'mac/launchd_test.mm', 'mac/mac_util_test.mm', 'mac/mach_o_image_reader_test.cc', 'mac/mach_o_image_segment_reader_test.cc',