From 03967f70f2706b7981a12173e14d3160ab6f8d05 Mon Sep 17 00:00:00 2001 From: "gtm.daemon" Date: Fri, 27 Aug 2010 19:35:47 +0000 Subject: [Author: dmaclach] Initial look at GTMServiceManagement. Added tests and project changes. R=thomasvl DELTA=844 (840 added, 0 deleted, 4 changed) --- Foundation/GTMServiceManagement.c | 602 ++++++++++++++++++++++++++++++++++ Foundation/GTMServiceManagement.h | 69 ++++ Foundation/GTMServiceManagementTest.m | 153 +++++++++ GTM.xcodeproj/project.pbxproj | 20 +- GTMDefines.h | 4 + 5 files changed, 844 insertions(+), 4 deletions(-) create mode 100644 Foundation/GTMServiceManagement.c create mode 100644 Foundation/GTMServiceManagement.h create mode 100644 Foundation/GTMServiceManagementTest.m diff --git a/Foundation/GTMServiceManagement.c b/Foundation/GTMServiceManagement.c new file mode 100644 index 0000000..49d110c --- /dev/null +++ b/Foundation/GTMServiceManagement.c @@ -0,0 +1,602 @@ +// +// GTMServiceManagement.c +// +// Copyright 2010 Google Inc. +// +// 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. +// + +// Note: launch_data_t have different ownership semantics than CFType/NSObjects. +// In general if you create one, you are responsible for releasing it. +// However, if you add it to a collection (LAUNCH_DATA_DICTIONARY, +// LAUNCH_DATA_ARRAY), you no longer own it, and are no longer +// responsible for releasing it (you may be responsible for the array +// or dictionary of course). A corrollary of this is that a +// launch_data_t can only be in one collection at any given time. + +#include "GTMServiceManagement.h" + +typedef struct { + CFMutableDictionaryRef dict; + bool convert_non_standard_objects; + CFErrorRef *error; +} GTMLToCFDictContext; + +typedef struct { + launch_data_t dict; + CFErrorRef *error; +} GTMCFToLDictContext; + +static CFErrorRef GTMCFLaunchCreateUnlocalizedError(CFIndex code, + CFStringRef format, ...) CF_FORMAT_FUNCTION(2, 3) { + CFDictionaryRef user_info = NULL; + if (format) { + va_list args; + va_start(args, format); + CFStringRef string + = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, + NULL, + format, + args); + user_info = CFDictionaryCreate(kCFAllocatorDefault, + (const void **)&kCFErrorDescriptionKey, + (const void **)&string, + 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFRelease(string); + va_end(args); + } + CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, + kCFErrorDomainPOSIX, + code, + user_info); + if (user_info) { + CFRelease(user_info); + } + return error; +} + +static void GTMConvertCFDictEntryToLaunchDataDictEntry(const void *key, + const void *value, + void *context) { + GTMCFToLDictContext *local_context = (GTMCFToLDictContext *)context; + if (*(local_context->error)) return; + + launch_data_t launch_value + = GTMLaunchDataCreateFromCFType(value, local_context->error); + if (launch_value) { + launch_data_t launch_key + = GTMLaunchDataCreateFromCFType(key, local_context->error); + if (launch_key) { + bool goodInsert + = launch_data_dict_insert(local_context->dict, + launch_value, + launch_data_get_string(launch_key)); + if (!goodInsert) { + *(local_context->error) + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("launch_data_dict_insert " + "failed key: %@ value: %@"), + key, + value); + launch_data_free(launch_value); + } + launch_data_free(launch_key); + } + } +} + +static void GTMConvertLaunchDataDictEntryToCFDictEntry(const launch_data_t value, + const char *key, + void *context) { + GTMLToCFDictContext *local_context = (GTMLToCFDictContext *)context; + if (*(local_context->error)) return; + + CFTypeRef cf_value + = GTMCFTypeCreateFromLaunchData(value, + local_context->convert_non_standard_objects, + local_context->error); + if (cf_value) { + CFStringRef cf_key = CFStringCreateWithCString(kCFAllocatorDefault, + key, + kCFStringEncodingUTF8); + if (cf_key) { + CFDictionarySetValue(local_context->dict, cf_key, cf_value); + CFRelease(cf_key); + } else { + *(local_context->error) + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("Unable to create key %s"), + key); + } + CFRelease(cf_value); + } +} + +static launch_data_t GTMPerformOnLabel(const char *verb, + CFStringRef jobLabel, + CFErrorRef *error) { + launch_data_t resp = NULL; + launch_data_t label = GTMLaunchDataCreateFromCFType(jobLabel, error); + if (*error == NULL) { + launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + launch_data_dict_insert(msg, label, verb); + resp = launch_msg(msg); + launch_data_free(msg); + if (!resp) { + *error = GTMCFLaunchCreateUnlocalizedError(errno, NULL); + } + } + return resp; +} + +launch_data_t GTMLaunchDataCreateFromCFType(CFTypeRef cf_type_ref, + CFErrorRef *error) { + launch_data_t result = NULL; + CFErrorRef local_error = NULL; + if (cf_type_ref == NULL) { + local_error = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("NULL CFType")); + goto exit; + } + + CFTypeID cf_type = CFGetTypeID(cf_type_ref); + if (cf_type == CFStringGetTypeID()) { + CFIndex length = CFStringGetLength(cf_type_ref); + CFIndex max_length + = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; + char *buffer = calloc(max_length, sizeof(char)); + size_t buffer_size = max_length * sizeof(char); + if (buffer) { + if (CFStringGetCString(cf_type_ref, + buffer, + buffer_size, + kCFStringEncodingUTF8)) { + result = launch_data_alloc(LAUNCH_DATA_STRING); + launch_data_set_string(result, buffer); + } else { + local_error + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("CFStringGetCString failed %@"), + cf_type_ref); + } + free(buffer); + } else { + local_error = GTMCFLaunchCreateUnlocalizedError(ENOMEM, + CFSTR("calloc of %zu failed"), + buffer_size); + } + } else if (cf_type == CFBooleanGetTypeID()) { + result = launch_data_alloc(LAUNCH_DATA_BOOL); + launch_data_set_bool(result, CFBooleanGetValue(cf_type_ref)); + } else if (cf_type == CFArrayGetTypeID()) { + CFIndex count = CFArrayGetCount(cf_type_ref); + result = launch_data_alloc(LAUNCH_DATA_ARRAY); + for (CFIndex i = 0; i < count; i++) { + CFTypeRef array_value = CFArrayGetValueAtIndex(cf_type_ref, i); + if (array_value) { + launch_data_t launch_value + = GTMLaunchDataCreateFromCFType(array_value, &local_error); + if (local_error) break; + launch_data_array_set_index(result, launch_value, i); + } + } + } else if (cf_type == CFDictionaryGetTypeID()) { + result = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + GTMCFToLDictContext context = { result, &local_error }; + CFDictionaryApplyFunction(cf_type_ref, + GTMConvertCFDictEntryToLaunchDataDictEntry, + &context); + } else if (cf_type == CFDataGetTypeID()) { + result = launch_data_alloc(LAUNCH_DATA_OPAQUE); + launch_data_set_opaque(result, + CFDataGetBytePtr(cf_type_ref), + CFDataGetLength(cf_type_ref)); + } else if (cf_type == CFNumberGetTypeID()) { + CFNumberType cf_number_type = CFNumberGetType(cf_type_ref); + switch (cf_number_type) { + case kCFNumberSInt8Type: + case kCFNumberSInt16Type: + case kCFNumberSInt32Type: + case kCFNumberSInt64Type: + case kCFNumberCharType: + case kCFNumberShortType: + case kCFNumberIntType: + case kCFNumberLongType: + case kCFNumberLongLongType: + case kCFNumberCFIndexType: + case kCFNumberNSIntegerType:{ + long long value; + if (CFNumberGetValue(cf_type_ref, kCFNumberLongLongType, &value)) { + result = launch_data_alloc(LAUNCH_DATA_INTEGER); + launch_data_set_integer(result, value); + } else { + local_error + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("Unknown to convert: %@"), + cf_type_ref); + } + break; + } + + case kCFNumberFloat32Type: + case kCFNumberFloat64Type: + case kCFNumberFloatType: + case kCFNumberDoubleType: + case kCFNumberCGFloatType: { + double value; + if (CFNumberGetValue(cf_type_ref, kCFNumberDoubleType, &value)) { + result = launch_data_alloc(LAUNCH_DATA_REAL); + launch_data_set_real(result, value); + } else { + local_error + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("Unknown to convert: %@"), + cf_type_ref); + } + break; + } + + default: + local_error + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("Unknown CFNumberType %lld"), + (long long)cf_number_type); + break; + } + } else { + local_error + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("Unknown CFTypeID %lu"), + cf_type); + } + +exit: + if (error) { + *error = local_error; + } else if (local_error) { + CFShow(local_error); + CFRelease(local_error); + } + return result; +} + +CFTypeRef GTMCFTypeCreateFromLaunchData(launch_data_t ldata, + bool convert_non_standard_objects, + CFErrorRef *error) { + CFTypeRef cf_type_ref = NULL; + CFErrorRef local_error = NULL; + if (ldata == NULL) { + local_error = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("NULL ldata")); + goto exit; + } + + launch_data_type_t ldata_type = launch_data_get_type(ldata); + switch (ldata_type) { + case LAUNCH_DATA_STRING: + cf_type_ref + = CFStringCreateWithCString(kCFAllocatorDefault, + launch_data_get_string(ldata), + kCFStringEncodingUTF8); + break; + + case LAUNCH_DATA_INTEGER: { + long long value = launch_data_get_integer(ldata); + cf_type_ref = CFNumberCreate(kCFAllocatorDefault, + kCFNumberLongLongType, + &value); + break; + } + + case LAUNCH_DATA_REAL: { + double value = launch_data_get_real(ldata); + cf_type_ref = CFNumberCreate(kCFAllocatorDefault, + kCFNumberDoubleType, + &value); + break; + } + + case LAUNCH_DATA_BOOL: { + bool value = launch_data_get_bool(ldata); + cf_type_ref = value ? kCFBooleanTrue : kCFBooleanFalse; + CFRetain(cf_type_ref); + break; + } + + case LAUNCH_DATA_OPAQUE: { + size_t size = launch_data_get_opaque_size(ldata); + void *data = launch_data_get_opaque(ldata); + cf_type_ref = CFDataCreate(kCFAllocatorDefault, data, size); + break; + } + + case LAUNCH_DATA_ARRAY: { + size_t count = launch_data_array_get_count(ldata); + cf_type_ref = CFArrayCreateMutable(kCFAllocatorDefault, + count, + &kCFTypeArrayCallBacks); + if (cf_type_ref) { + for (size_t i = 0; !local_error && i < count; i++) { + launch_data_t l_sub_data = launch_data_array_get_index(ldata, i); + CFTypeRef cf_sub_type + = GTMCFTypeCreateFromLaunchData(l_sub_data, + convert_non_standard_objects, + &local_error); + if (cf_sub_type) { + CFArrayAppendValue((CFMutableArrayRef)cf_type_ref, cf_sub_type); + CFRelease(cf_sub_type); + } + } + } + break; + } + + case LAUNCH_DATA_DICTIONARY: + cf_type_ref = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (cf_type_ref) { + GTMLToCFDictContext context = { + (CFMutableDictionaryRef)cf_type_ref, + convert_non_standard_objects, + &local_error + }; + launch_data_dict_iterate(ldata, + GTMConvertLaunchDataDictEntryToCFDictEntry, + &context); + } + break; + + case LAUNCH_DATA_FD: + if (convert_non_standard_objects) { + int file_descriptor = launch_data_get_fd(ldata); + cf_type_ref = CFNumberCreate(kCFAllocatorDefault, + kCFNumberIntType, + &file_descriptor); + } + break; + + case LAUNCH_DATA_MACHPORT: + if (convert_non_standard_objects) { + mach_port_t port = launch_data_get_machport(ldata); + cf_type_ref = CFNumberCreate(kCFAllocatorDefault, + kCFNumberIntType, + &port); + } + break; + + default: + local_error = + GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("Unknown launchd type %d"), + ldata_type); + break; + } +exit: + if (error) { + *error = local_error; + } else if (local_error) { + CFShow(local_error); + CFRelease(local_error); + } + return cf_type_ref; +} + +Boolean GTMSMJobSubmit(CFDictionaryRef cf_job, CFErrorRef *error) { + CFErrorRef local_error = NULL; + launch_data_t launch_job = GTMLaunchDataCreateFromCFType(cf_job, + &local_error); + if (!local_error) { + launch_data_t jobs = launch_data_alloc(LAUNCH_DATA_ARRAY); + launch_data_array_set_index(jobs, launch_job, 0); + launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + launch_data_dict_insert(msg, jobs, LAUNCH_KEY_SUBMITJOB); + launch_data_t resp = launch_msg(msg); + if (resp) { + launch_data_type_t resp_type = launch_data_get_type(resp); + switch (resp_type) { + case LAUNCH_DATA_ARRAY: + for (size_t i = 0; i < launch_data_array_get_count(jobs); i++) { + launch_data_t job_response = launch_data_array_get_index(resp, i); + launch_data_t job = launch_data_array_get_index(jobs, i); + launch_data_t job_label + = launch_data_dict_lookup(job, LAUNCH_JOBKEY_LABEL); + const char *job_string + = job_label ? launch_data_get_string(job_label) : "Unlabeled job"; + if (LAUNCH_DATA_ERRNO == launch_data_get_type(job_response)) { + int job_err = launch_data_get_errno(job_response); + if (job_err != 0) { + CFStringRef format_string = NULL; + switch (job_err) { + case EEXIST: + format_string = CFSTR("%s already loaded"); + break; + case ESRCH: + format_string = CFSTR("%s not loaded"); + break; + default: + format_string = CFSTR("%s failed to load"); + break; + } + local_error = GTMCFLaunchCreateUnlocalizedError(job_err, + format_string, + job_string); + } + } + } + break; + + case LAUNCH_DATA_ERRNO: { + int e = launch_data_get_errno(resp); + if (e) { + local_error = GTMCFLaunchCreateUnlocalizedError(e, NULL); + } + break; + } + + default: + local_error + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("unknown response from launchd %d"), + resp_type); + break; + } + launch_data_free(resp); + launch_data_free(msg); + } else { + local_error = GTMCFLaunchCreateUnlocalizedError(errno, NULL); + } + + } + if (error) { + *error = local_error; + } else if (local_error) { + CFShow(local_error); + CFRelease(local_error); + } + return local_error == NULL; +} + +CFDictionaryRef GTMSMJobCheckIn(CFErrorRef *error) { + CFErrorRef local_error = NULL; + CFDictionaryRef check_in_dict = NULL; + launch_data_t msg = launch_data_new_string(LAUNCH_KEY_CHECKIN); + launch_data_t resp = launch_msg(msg); + launch_data_free(msg); + if (resp) { + launch_data_type_t resp_type = launch_data_get_type(resp); + switch (resp_type) { + case LAUNCH_DATA_DICTIONARY: + check_in_dict = GTMCFTypeCreateFromLaunchData(resp, true, &local_error); + break; + + case LAUNCH_DATA_ERRNO: { + int e = launch_data_get_errno(resp); + if (e) { + local_error = GTMCFLaunchCreateUnlocalizedError(e, NULL); + } + break; + } + + default: + local_error + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("unknown response from launchd %d"), + resp_type); + break; + } + launch_data_free(resp); + } else { + local_error = GTMCFLaunchCreateUnlocalizedError(errno, NULL); + } + if (error) { + *error = local_error; + } else if (local_error) { + CFShow(local_error); + CFRelease(local_error); + } + return check_in_dict; +} + +Boolean GTMSMJobRemove(CFStringRef jobLabel, CFErrorRef *error) { + CFErrorRef local_error = NULL; + launch_data_t resp = GTMPerformOnLabel(LAUNCH_KEY_REMOVEJOB, + jobLabel, + &local_error); + if (resp) { + launch_data_type_t resp_type = launch_data_get_type(resp); + switch (resp_type) { + case LAUNCH_DATA_ERRNO: { + int e = launch_data_get_errno(resp); + if (e) { + local_error = GTMCFLaunchCreateUnlocalizedError(e, NULL); + } + break; + } + + default: + local_error + = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("unknown response from launchd %d"), + resp_type); + break; + } + launch_data_free(resp); + } else { + local_error = GTMCFLaunchCreateUnlocalizedError(errno, NULL); + } + if (error) { + *error = local_error; + } else if (local_error) { + CFShow(local_error); + CFRelease(local_error); + } + return local_error == NULL; +} + +CFDictionaryRef GTMSMJobCopyDictionary(CFStringRef jobLabel) { + CFDictionaryRef dict = NULL; + CFErrorRef error = NULL; + launch_data_t resp = GTMPerformOnLabel(LAUNCH_KEY_GETJOB, + jobLabel, + &error); + if (resp) { + launch_data_type_t ldata_Type = launch_data_get_type(resp); + if (ldata_Type == LAUNCH_DATA_DICTIONARY) { + dict = GTMCFTypeCreateFromLaunchData(resp, true, &error); + } else { + error = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("Unknown launchd type %d"), + ldata_Type); + } + launch_data_free(resp); + } + if (error) { + CFShow(error); + CFRelease(error); + } + return dict; +} + +CFDictionaryRef GTMSMCopyAllJobDictionaries(void) { + CFDictionaryRef dict = NULL; + launch_data_t msg = launch_data_new_string(LAUNCH_KEY_GETJOBS); + launch_data_t resp = launch_msg(msg); + launch_data_free(msg); + CFErrorRef error = NULL; + + if (resp) { + launch_data_type_t ldata_Type = launch_data_get_type(resp); + if (ldata_Type == LAUNCH_DATA_DICTIONARY) { + dict = GTMCFTypeCreateFromLaunchData(resp, true, &error); + } else { + error = GTMCFLaunchCreateUnlocalizedError(EINVAL, + CFSTR("Unknown launchd type %d"), + ldata_Type); + } + launch_data_free(resp); + } else { + error + = GTMCFLaunchCreateUnlocalizedError(errno, NULL); + } + if (error) { + CFShow(error); + CFRelease(error); + } + return dict; +} + + diff --git a/Foundation/GTMServiceManagement.h b/Foundation/GTMServiceManagement.h new file mode 100644 index 0000000..edca3ab --- /dev/null +++ b/Foundation/GTMServiceManagement.h @@ -0,0 +1,69 @@ +// +// GTMServiceManagement.h +// +// Copyright 2010 Google Inc. +// +// 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 +#include +#include "GTMDefines.h" + +GTM_EXTERN_C_BEGIN + +// Done in C as opposed to Objective-C as lots of services may not want +// to bring in Obj-C libraries. + +// For rough documentation on these methods please see +// from the 10.6 sdk. +// This reimplements some of the ServiceManagement framework on 10.4 and 10.5. +// (not tested on 10.4, but should work YMMV). +// Caller takes ownership of error if necessary. + +Boolean GTMSMJobSubmit(CFDictionaryRef job, CFErrorRef *error); +Boolean GTMSMJobRemove(CFStringRef jobLabel, CFErrorRef *error); + +// Caller takes ownership of the returned type. +// Note that the dictionary returned will have 0 for machports. +// To get a machport, use bootstrap_look_up, or NSMachBootstrapServer. +CFDictionaryRef GTMSMJobCopyDictionary(CFStringRef jobLabel); + +// This one is conspiciously absent from the ServiceManagement framework. +// Performs a check-in for the running process and returns its dictionary with +// the appropriate sockets and machports filled in. +// Caller takes ownership of the returned type. +CFDictionaryRef GTMSMJobCheckIn(CFErrorRef *error); + +// The official ServiceManagement version returns an array of job dictionaries. +// This returns a dictionary of job dictionaries where the key is the label +// of the job, and the value is the dictionary for the job of that label. +// Caller takes ownership of the returned type. +CFDictionaryRef GTMSMCopyAllJobDictionaries(void); + + +// Convert a CFType (and any of it's subitems) into a launch_data_t. +// Caller takes ownership of the returned type if it isn't added to a launch +// data container type. +launch_data_t GTMLaunchDataCreateFromCFType(CFTypeRef cf_type_ref, + CFErrorRef *error); + +// Convert a launch_data_t (and any of it's subitems) into a CFType. +// If |convert_non_standard_objects| is true, file descriptors and machports +// will be included in the returned dictionary, otherwise they will be ignored. +// Caller is takes ownership of the returned type. +CFTypeRef GTMCFTypeCreateFromLaunchData(launch_data_t ldata, + bool convert_non_standard_objects, + CFErrorRef *error); + +GTM_EXTERN_C_END diff --git a/Foundation/GTMServiceManagementTest.m b/Foundation/GTMServiceManagementTest.m new file mode 100644 index 0000000..a0d0c20 --- /dev/null +++ b/Foundation/GTMServiceManagementTest.m @@ -0,0 +1,153 @@ +// +// GTMServiceManagementTest.m +// +// Copyright 2010 Google Inc. +// +// 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. +// + +#import "GTMSenTestCase.h" +#import "GTMServiceManagement.h" +#import "GTMGarbageCollection.h" + +#define STANDARD_JOB_LABEL "com.apple.launchctl.Background" +#define OUR_JOB_LABEL "com.google.gtm.GTMServiceManagementTest.job" +#define BAD_JOB_LABEL "com.google.gtm.GTMServiceManagementTest.badjob" + +@interface GTMServiceManagementTest : GTMTestCase +@end + +@implementation GTMServiceManagementTest + +- (void)testDataConversion { + const char *someData = "someData"; + NSDictionary *subDict + = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:1], @"BoolValue", + [NSNumber numberWithInt:2], @"IntValue", + [NSNumber numberWithDouble:0.3], @"DoubleValue", + @"A String", @"StringValue", + [NSData dataWithBytes:someData length:strlen(someData)], @"DataValue", + nil]; + NSArray *subArray + = [NSArray arrayWithObjects:@"1", [NSNumber numberWithInt:2], nil]; + NSDictionary *topDict = [NSDictionary dictionaryWithObjectsAndKeys: + subDict, @"SubDict", + subArray, @"SubArray", + @"Random String", @"RandomString", + nil]; + CFErrorRef error = NULL; + launch_data_t launchDict = GTMLaunchDataCreateFromCFType(topDict, &error); + STAssertNotNULL(launchDict, nil); + STAssertNULL(error, @"Error: %@", error); + NSDictionary *nsDict + = GTMCFAutorelease(GTMCFTypeCreateFromLaunchData(launchDict, + NO, + &error)); + STAssertNotNil(nsDict, nil); + STAssertNULL(error, @"Error: %@", error); + STAssertEqualObjects(nsDict, topDict, @""); + + launch_data_free(launchDict); + + // Test a bad type + NSURL *url = [NSURL URLWithString:@"http://www.google.com"]; + STAssertNotNil(url, nil); + launchDict = GTMLaunchDataCreateFromCFType(url, &error); + STAssertNULL(launchDict, nil); + STAssertNotNULL(error, nil); + STAssertEqualObjects((id)CFErrorGetDomain(error), + (id)kCFErrorDomainPOSIX, nil); + STAssertEquals(CFErrorGetCode(error), (CFIndex)EINVAL, nil); + CFRelease(error); + + CFTypeRef cfType = GTMCFTypeCreateFromLaunchData(NULL, YES, &error); + STAssertNULL(cfType, nil); + STAssertNotNULL(error, nil); + CFRelease(error); +} + +- (void)testJobDictionaries { + NSDictionary *jobs = GTMCFAutorelease(GTMSMCopyAllJobDictionaries()); + STAssertNotNil(jobs, nil); + // A job that should always be around + NSDictionary *job + = GTMCFAutorelease(GTMSMJobCopyDictionary(CFSTR(STANDARD_JOB_LABEL))); + STAssertNotNil(job, nil); + + // A job that should never be around + CFTypeRef type = GTMSMJobCopyDictionary(CFSTR(BAD_JOB_LABEL)); + STAssertNULL(type, nil); +} + +- (void)testLaunching { + CFErrorRef error = NULL; + Boolean isGood = GTMSMJobSubmit(NULL, &error); + STAssertFalse(isGood, nil); + STAssertNotNULL(error, nil); + CFRelease(error); + + NSDictionary *empty = [NSDictionary dictionary]; + isGood = GTMSMJobSubmit((CFDictionaryRef)empty, &error); + STAssertFalse(isGood, nil); + STAssertNotNULL(error, nil); + CFRelease(error); + + NSDictionary *alreadyThere + = [NSDictionary dictionaryWithObject:@STANDARD_JOB_LABEL + forKey:@LAUNCH_JOBKEY_LABEL]; + isGood = GTMSMJobSubmit((CFDictionaryRef)alreadyThere, &error); + STAssertFalse(isGood, nil); + STAssertEquals([(NSError *)error code], (NSInteger)EEXIST, nil); + CFRelease(error); + + NSDictionary *emptyJob + = [NSDictionary dictionaryWithObject:@"Empty Job" + forKey:@LAUNCH_JOBKEY_LABEL]; + isGood = GTMSMJobSubmit((CFDictionaryRef)emptyJob, &error); + STAssertFalse(isGood, nil); + STAssertEquals([(NSError *)error code], (NSInteger)EINVAL, nil); + CFRelease(error); + + NSDictionary *goodJob + = [NSDictionary dictionaryWithObjectsAndKeys: + @OUR_JOB_LABEL, @LAUNCH_JOBKEY_LABEL, + @"/bin/test", @LAUNCH_JOBKEY_PROGRAM, + nil]; + isGood = GTMSMJobSubmit((CFDictionaryRef)goodJob, &error); + STAssertTrue(isGood, nil); + STAssertNULL(error, nil); + + isGood = GTMSMJobRemove(CFSTR(OUR_JOB_LABEL), &error); + STAssertTrue(isGood, @"You may need to run launchctl remove %s", OUR_JOB_LABEL); + STAssertNULL(error, nil); + + isGood = GTMSMJobRemove(CFSTR(OUR_JOB_LABEL), &error); + STAssertFalse(isGood, nil); + STAssertNotNULL(error, nil); + CFRelease(error); +} + +- (void)testCheckin { + CFErrorRef error = NULL; + // TODO(dmaclach): to actually test checkin we would need a subprocess to + // launch, which is too complex at this point. Perhaps + // in the future if bugs are found. + NSDictionary *badTest + = GTMCFAutorelease(GTMSMJobCheckIn(CFSTR(BAD_JOB_LABEL), &error)); + STAssertNil(badTest, nil); + STAssertNotNULL(error, nil); + CFRelease(error); +} + +@end diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj index 6249f22..4f471d5 100644 --- a/GTM.xcodeproj/project.pbxproj +++ b/GTM.xcodeproj/project.pbxproj @@ -135,6 +135,9 @@ 8B409F060F95341E00DF540E /* GTMUILocalizerTestView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8B409F050F95341E00DF540E /* GTMUILocalizerTestView.xib */; }; 8B409F130F95352500DF540E /* GTMUILocalizerView2State.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B409F110F95352500DF540E /* GTMUILocalizerView2State.gtmUTState */; }; 8B409F140F95352500DF540E /* GTMUILocalizerView1State.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B409F120F95352500DF540E /* GTMUILocalizerView1State.gtmUTState */; }; + 8B414E881226FB1000D0064F /* GTMServiceManagement.c in Sources */ = {isa = PBXBuildFile; fileRef = 8B414E861226FB1000D0064F /* GTMServiceManagement.c */; }; + 8B414E891226FB1000D0064F /* GTMServiceManagement.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B414E871226FB1000D0064F /* GTMServiceManagement.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8B414E8B1226FB1800D0064F /* GTMServiceManagementTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B414E8A1226FB1800D0064F /* GTMServiceManagementTest.m */; }; 8B455F5E1193870A00ABD707 /* GTMLocalizedStringTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B455F5D1193870A00ABD707 /* GTMLocalizedStringTest.m */; }; 8B45A03A0DA46A2A001148C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; 8B45A0B80DA46A2F001148C5 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; }; @@ -651,6 +654,9 @@ 8B409F050F95341E00DF540E /* GTMUILocalizerTestView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GTMUILocalizerTestView.xib; sourceTree = ""; }; 8B409F110F95352500DF540E /* GTMUILocalizerView2State.gtmUTState */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = GTMUILocalizerView2State.gtmUTState; sourceTree = ""; }; 8B409F120F95352500DF540E /* GTMUILocalizerView1State.gtmUTState */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = GTMUILocalizerView1State.gtmUTState; sourceTree = ""; }; + 8B414E861226FB1000D0064F /* GTMServiceManagement.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = GTMServiceManagement.c; sourceTree = ""; }; + 8B414E871226FB1000D0064F /* GTMServiceManagement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMServiceManagement.h; sourceTree = ""; }; + 8B414E8A1226FB1800D0064F /* GTMServiceManagementTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMServiceManagementTest.m; sourceTree = ""; }; 8B455F5D1193870A00ABD707 /* GTMLocalizedStringTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLocalizedStringTest.m; sourceTree = ""; }; 8B45A0280DA4696C001148C5 /* UnitTest - UnitTesting.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - UnitTesting.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; 8B45A1990DA46AAA001148C5 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = /System/Library/Frameworks/QuartzCore.framework; sourceTree = ""; }; @@ -1495,6 +1501,9 @@ F47A79850D746EE9002302AB /* GTMScriptRunner.h */, F47A79860D746EE9002302AB /* GTMScriptRunner.m */, F47A79870D746EE9002302AB /* GTMScriptRunnerTest.m */, + 8B414E871226FB1000D0064F /* GTMServiceManagement.h */, + 8B414E861226FB1000D0064F /* GTMServiceManagement.c */, + 8B414E8A1226FB1800D0064F /* GTMServiceManagementTest.m */, F41A6F7F0E02EC3600788A6C /* GTMSignalHandler.h */, F41A6F800E02EC3600788A6C /* GTMSignalHandler.m */, F41A6F810E02EC3600788A6C /* GTMSignalHandlerTest.m */, @@ -1678,6 +1687,7 @@ 8BB7802E11B6C4EA00AB31AF /* GTMGoogleSearch.h in Headers */, 8BCB59F011C00ED6009B6C40 /* GTMNSScanner+Unsigned.h in Headers */, 8B29080A11F8E1670064F50F /* GTMNSFileHandle+UniqueName.h in Headers */, + 8B414E891226FB1000D0064F /* GTMServiceManagement.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2086,7 +2096,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Run the unit tests in this test bundle.\nGTM_REMOVE_TARGET_GCOV_ONLY=1 GTM_ONE_TEST_AT_A_TIME=1 \"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\""; + shellScript = "# Run the unit tests in this test bundle.\n# Set OTHER_TEST_FLAGS=\"-SenTest NameOfTestClass\" or OTHER_TEST_FLAGS=\"-SenTest NameOfTestClass/NameOfTest\" if you want to only test a limited amount.\nGTM_REMOVE_TARGET_GCOV_ONLY=1 GTM_ONE_TEST_AT_A_TIME=1 \"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\""; }; 8BFE13F10FB0F2D8001BE894 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -2099,7 +2109,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Run the unit tests in this test bundle.\nGTM_REMOVE_TARGET_GCOV_ONLY=1 GTM_DISABLE_ZOMBIES=1 \"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\"\n"; + shellScript = "# Run the unit tests in this test bundle.\n# Set OTHER_TEST_FLAGS=\"-SenTest NameOfTestClass\" or OTHER_TEST_FLAGS=\"-SenTest NameOfTestClass/NameOfTest\" if you want to only test a limited amount.\nGTM_REMOVE_TARGET_GCOV_ONLY=1 GTM_DISABLE_ZOMBIES=1 \"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\"\n"; }; F42E081D0D19987200D5DDE0 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -2112,7 +2122,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Run the unit tests in this test bundle.\nGTM_REMOVE_TARGET_GCOV_ONLY=1 \"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\""; + shellScript = "# Run the unit tests in this test bundle.\n# Set OTHER_TEST_FLAGS=\"-SenTest NameOfTestClass\" or OTHER_TEST_FLAGS=\"-SenTest NameOfTestClass/NameOfTest\" if you want to only test a limited amount.\nGTM_REMOVE_TARGET_GCOV_ONLY=1 \"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\""; }; F48FE2620D198C1E009257D2 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -2125,7 +2135,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Run the unit tests in this test bundle.\nGTM_REMOVE_TARGET_GCOV_ONLY=1 GTM_ONE_TEST_AT_A_TIME=1 \"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\"\n"; + shellScript = "# Run the unit tests in this test bundle.\n# Set OTHER_TEST_FLAGS=\"-SenTest NameOfTestClass\" or OTHER_TEST_FLAGS=\"-SenTest NameOfTestClass/NameOfTest\" if you want to only test a limited amount.\nGTM_REMOVE_TARGET_GCOV_ONLY=1 GTM_ONE_TEST_AT_A_TIME=1 \"${SRCROOT}/UnitTesting/RunMacOSUnitTests.sh\"\n"; }; F4E4297B10B753C600F28A35 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -2240,6 +2250,7 @@ 8BCB59F311C00EF6009B6C40 /* GTMNSScanner+UnsignedTest.m in Sources */, 8B29078711F8D1BF0064F50F /* GTMNSFileHandle+UniqueName.m in Sources */, 8B29078811F8D1BF0064F50F /* GTMNSFileHandle+UniqueNameTest.m in Sources */, + 8B414E8B1226FB1800D0064F /* GTMServiceManagementTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2318,6 +2329,7 @@ 8BB77A0611B5A0A100AB31AF /* GTMGoogleSearch.m in Sources */, 8BCB59F111C00ED6009B6C40 /* GTMNSScanner+Unsigned.m in Sources */, 8B29080911F8E1630064F50F /* GTMNSFileHandle+UniqueName.m in Sources */, + 8B414E881226FB1000D0064F /* GTMServiceManagement.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/GTMDefines.h b/GTMDefines.h index af035ef..c8685d9 100644 --- a/GTMDefines.h +++ b/GTMDefines.h @@ -83,8 +83,12 @@ #if !defined (GTM_EXTERN) #if defined __cplusplus #define GTM_EXTERN extern "C" + #define GTM_EXTERN_C_BEGIN extern "C" { + #define GTM_EXTERN_C_END } #else #define GTM_EXTERN extern + #define GTM_EXTERN_C_BEGIN + #define GTM_EXTERN_C_END #endif #endif -- cgit v1.2.3