// // BMScript.m // BMScriptTest // // Created by Andre Berg on 11.09.09. // Copyright 2009 Berg Media. 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. /// @cond HIDDEN #import "BMScript.h" #if BMSCRIPT_ENABLE_DTRACE #import "BMScriptProbes.h" /* dtrace probes auto-generated from .d file(s) */ #endif #include /* for usleep */ #include /* for pthread_* */ #define BMSCRIPT_INSERTION_TOKEN @"%@" /* used by templates to mark locations where a replacement insertions should occur */ #define BM_NSSTRING_TRUNCATE_LENGTH 20 /* used by -truncate, defined in NSString (BMScriptUtilities) */ #ifndef BMSCRIPT_DEBUG_HISTORY #define BMSCRIPT_DEBUG_HISTORY 0 #endif #if (BMSCRIPT_THREAD_SAFE && BMSCRIPT_ENABLE_DTRACE) #if BMSCRIPT_FAST_LOCK #define BM_LOCK(name) \ BM_PROBE(ACQUIRE_LOCK_START, (char *) [BMStringFromBOOL(BMSCRIPT_FAST_LOCK) UTF8String]); \ static pthread_mutex_t mtx_##name = PTHREAD_MUTEX_INITIALIZER; \ if (pthread_mutex_lock(&mtx_##name)) {\ printf("*** Warning: Lock failed! Application behaviour may be undefined. Exiting...");\ exit(EXIT_FAILURE);\ } #define BM_UNLOCK(name) \ if ((pthread_mutex_unlock(&mtx_##name) != 0)) {\ printf("*** Warning: Unlock failed! Application behaviour may be undefined. Exiting...");\ exit(EXIT_FAILURE);\ }\ BM_PROBE(ACQUIRE_LOCK_END, (char *) [BMStringFromBOOL(BMSCRIPT_FAST_LOCK) UTF8String]); #else #define BM_LOCK(name) \ BM_PROBE(ACQUIRE_LOCK_START, (char *) [BMStringFromBOOL(BMSCRIPT_FAST_LOCK) UTF8String]);\ static id const sync_##name##_ref = @""#name;\ @synchronized(sync_##name##_ref) { #define BM_UNLOCK(name) }\ BM_PROBE(ACQUIRE_LOCK_END, (char *) [BMStringFromBOOL(BMSCRIPT_FAST_LOCK) UTF8String]); #endif #elif (BMSCRIPT_THREAD_SAFE && !BMSCRIPT_ENABLE_DTRACE) #if BMSCRIPT_FAST_LOCK #define BM_LOCK(name) \ static pthread_mutex_t mtx_##name = PTHREAD_MUTEX_INITIALIZER; \ if (pthread_mutex_lock(&mtx_##name)) {\ printf("*** Warning: Lock failed! Application behaviour may be undefined. Exiting...");\ exit(EXIT_FAILURE);\ } #define BM_UNLOCK(name) \ if ((pthread_mutex_unlock(&mtx_##name) != 0)) {\ printf("*** Warning: Unlock failed! Application behaviour may be undefined. Exiting...");\ exit(EXIT_FAILURE);\ }; #else #define BM_LOCK(name) \ static id const sync_##name##_ref = @""#name;\ @synchronized(sync_##name##_ref) { #define BM_UNLOCK(name) }; #endif #else #define BM_LOCK(name) #define BM_UNLOCK(name) #endif NSString * const BMScriptOptionsTaskLaunchPathKey = @"BMScriptOptionsTaskLaunchPathKey"; NSString * const BMScriptOptionsTaskArgumentsKey = @"BMScriptOptionsTaskArgumentsKey"; NSString * const BMScriptOptionsVersionKey = @"BMScriptOptionsVersionKey"; NSString * const BMScriptTaskDidEndNotification = @"BMScriptTaskDidEndNotification"; NSString * const BMScriptNotificationTaskResults = @"BMScriptNotificationTaskResults"; NSString * const BMScriptNotificationTaskTerminationStatus = @"BMScriptNotificationTaskTerminationStatus"; NSString * const BMScriptTemplateArgumentMissingException = @"BMScriptTemplateArgumentMissingException"; NSString * const BMScriptTemplateArgumentsMissingException = @"BMScriptTemplateArgumentsMissingException"; NSString * const BMScriptLanguageProtocolDoesNotConformException = @"BMScriptLanguageProtocolDoesNotConformException"; NSString * const BMScriptLanguageProtocolMethodMissingException = @"BMScriptLanguageProtocolMethodMissingException"; NSString * const BMScriptLanguageProtocolIllegalAccessException = @"BMScriptLanguageProtocolIllegalAccessException"; /* Empty braces means this is an "Extension" as opposed to a Category */ @interface BMScript () @property (BM_ATOMIC copy, readwrite) NSString * result; @property (BM_ATOMIC assign) NSInteger returnValue; @property (BM_ATOMIC assign) NSInteger bgTaskReturnValue; @property (BM_ATOMIC copy) NSString * partialResult; @property (BM_ATOMIC assign) BOOL isTemplate; @property (BM_ATOMIC retain) NSTask * task; @property (BM_ATOMIC retain) NSPipe * pipe; @property (BM_ATOMIC retain) NSTask * bgTask; @property (BM_ATOMIC retain) NSPipe * bgPipe; - (void) stopTask; - (BOOL) setupTask; - (void) cleanupTask:(NSTask *)whichTask; - (TerminationStatus) launchTaskAndStoreResult; - (void) setupAndLaunchBackgroundTask; - (void) taskTerminated:(NSNotification *)aNotification; - (void) appendData:(NSData *)d; - (void) dataReceived:(NSNotification *)aNotification; - (const char *) gdbDataFormatter; @end @implementation BMScript @dynamic delegate; @synthesize script; @synthesize options; @synthesize partialResult; @synthesize result; @synthesize isTemplate; @synthesize history; @synthesize task; @synthesize pipe; @synthesize bgTask; @synthesize bgPipe; @synthesize returnValue; @synthesize bgTaskReturnValue; //=========================================================== // delegate //=========================================================== - (id)delegate { return delegate; } - (void)setDelegate:(id)newDelegate { BM_LOCK(delegate) if (delegate != newDelegate) { delegate = newDelegate; } BM_UNLOCK(delegate) } // MARK: Description - (NSString *) description { return [NSString stringWithFormat:@"%@\n" @" script: '%@'\n" @" result: '%@'\n" @"delegate: '%@'\n" @" options: '%@'", [super description], [script quote], [result quote], (delegate == self? (id)@"self" : delegate), [options descriptionInStringsFileFormat]]; } - (NSString *) debugDescription { return [NSString stringWithFormat:@"%@\n" @" history (%d item%@): '%@'\n" @" task: '%@'\n" @" pipe: '%@'\n" @" bgTask: '%@'\n" @" bgPipe: '%@'\n", [self description], [history count], ([history count] == 1 ? @"" : @"s"), history, task, pipe, bgTask, bgPipe ]; } - (const char *) gdbDataFormatter { NSString * launchPath = [options objectForKey:BMScriptOptionsTaskLaunchPathKey]; NSArray * args = [options objectForKey:BMScriptOptionsTaskArgumentsKey]; NSMutableString * accString = [NSMutableString string]; if (args) { for (NSString * arg in args) { [accString appendFormat:@" %@%@", arg, ([arg isEqualToString:@""] ? @"" : @", ")]; } } NSString * desc = [NSString stringWithFormat:@"options = %@%@script = %@, result = %@, isTemplate = %@", (launchPath ? launchPath : @"nil, "), (accString ? accString : @"nil, "), (script ? [[script quote] truncate] : @"nil"), (result ? [[result quote] truncate] : @"nil"), BMStringFromBOOL(isTemplate)]; return [desc UTF8String]; } // MARK: Deallocation - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; if (BM_EXPECTED([task isRunning], 0)) [task terminate]; if (BM_EXPECTED([bgTask isRunning], 0)) [bgTask terminate]; [script release], script = nil; [history release], history = nil; [options release], options = nil; [result release], result = nil; [partialResult release], partialResult = nil; [task release], task = nil; [pipe release], pipe = nil; [bgTask release], bgTask = nil; [bgPipe release], bgPipe = nil; [super dealloc]; } - (void) finalize { if (BM_EXPECTED([task isRunning], 0)) [task terminate]; if (BM_EXPECTED([bgTask isRunning], 0)) [bgTask terminate]; [super finalize]; } // MARK: Initializer Methods - (id)init { NSLog(@"BMScript Warning: Initializing instance %@ with default values! " @"(options = \"/bin/echo\", \"\", script source = '