aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Foundation/GTMServiceManagement.c602
-rw-r--r--Foundation/GTMServiceManagement.h69
-rw-r--r--Foundation/GTMServiceManagementTest.m153
-rw-r--r--GTM.xcodeproj/project.pbxproj20
-rw-r--r--GTMDefines.h4
5 files changed, 844 insertions, 4 deletions
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 <launch.h>
+#include <CoreFoundation/CoreFoundation.h>
+#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
+// <ServiceManagement/ServiceManagement.h> 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 = "<group>"; };
8B409F110F95352500DF540E /* GTMUILocalizerView2State.gtmUTState */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = GTMUILocalizerView2State.gtmUTState; sourceTree = "<group>"; };
8B409F120F95352500DF540E /* GTMUILocalizerView1State.gtmUTState */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = GTMUILocalizerView1State.gtmUTState; sourceTree = "<group>"; };
+ 8B414E861226FB1000D0064F /* GTMServiceManagement.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = GTMServiceManagement.c; sourceTree = "<group>"; };
+ 8B414E871226FB1000D0064F /* GTMServiceManagement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMServiceManagement.h; sourceTree = "<group>"; };
+ 8B414E8A1226FB1800D0064F /* GTMServiceManagementTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMServiceManagementTest.m; sourceTree = "<group>"; };
8B455F5D1193870A00ABD707 /* GTMLocalizedStringTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLocalizedStringTest.m; sourceTree = "<group>"; };
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 = "<absolute>"; };
@@ -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