4 Copyright (C) 1995, 1996, 1997 Ovidiu Predescu and Mircea Oancea.
7 Author: Ovidiu Predescu <ovidiu@net-community.com>
9 Based on the code written by Aleksandr Savostyanov <sav@conextions.com>.
11 This file is part of libFoundation.
13 Permission to use, copy, modify, and distribute this software and its
14 documentation for any purpose and without fee is hereby granted, provided
15 that the above copyright notice appear in all copies and that both that
16 copyright notice and this permission notice appear in supporting
19 We disclaim all warranties with regard to this software, including all
20 implied warranties of merchantability and fitness, in no event shall
21 we be liable for any special, indirect or consequential damages or any
22 damages whatsoever resulting from loss of use, data or profits, whether in
23 an action of contract, negligence or other tortious action, arising out of
24 or in connection with the use or performance of this software.
29 #include <extensions/objc-runtime.h>
30 #include <Foundation/NSUserDefaults.h>
45 #include <sys/types.h>
48 # include <sys/wait.h>
49 # define WAIT_TYPE int
51 /* Old BSD union wait */
52 # define WAIT_TYPE union wait
55 # define WEXITSTATUS(stat_val) (int)(WIFEXITED(stat_val) \
56 ? (((stat_val.w_status) >> 8) & 0377) \
62 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
66 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
69 #if !defined(__MINGW32__)
70 # include <sys/resource.h>
74 # include <sys/param.h>
78 # include <sys/time.h> /* for struct timeval */
81 #include <Foundation/common.h>
82 #include <Foundation/NSArray.h>
83 #include <Foundation/NSAutoreleasePool.h>
84 #include <Foundation/NSDate.h>
85 #include <Foundation/NSDictionary.h>
86 #include <Foundation/NSException.h>
87 #include <Foundation/NSFileHandle.h>
88 #include <Foundation/NSFileManager.h>
89 #include <Foundation/NSMapTable.h>
90 #include <Foundation/NSNotification.h>
91 #include <Foundation/NSRunLoop.h>
92 #include <Foundation/NSString.h>
93 #include <Foundation/NSValue.h>
94 #include <Foundation/UnixSignalHandler.h>
95 #include "NSConcreteUnixTask.h"
97 #include <Foundation/exceptions/GeneralExceptions.h>
99 static NSMapTable *unixProcessToTask = NULL;
100 static BOOL gotSIGCHLD = NO;
102 @interface NSConcreteUnixTask(Privates)
103 + (void)_processExitCodes;
108 @implementation NSConcreteUnixTask
110 static int debugNSTask = -1;
114 static BOOL initialized = NO;
117 UnixSignalHandler *ush;
120 unixProcessToTask = NSCreateMapTable (NSIntMapKeyCallBacks,
121 NSNonRetainedObjectMapValueCallBacks, 19);
123 ush = [UnixSignalHandler sharedHandler];
125 #if !defined(__MINGW32__)
126 # if defined(SIGCHLD)
127 [ush addObserver:self
128 selector:@selector(_childFinished:)
130 immediatelyNotifyOnSignal:NO];
131 [ush addObserver:self
132 selector:@selector(_sigchldImmediate:)
134 immediatelyNotifyOnSignal:YES];
135 # elif defined(SIGCLD)
136 [ush addObserver:self
137 selector:@selector(_childFinished:)
139 immediatelyNotifyOnSignal:NO];
140 [ush addObserver:self
141 selector:@selector(_sigchldImmediate:)
143 immediatelyNotifyOnSignal:YES];
145 # erro how to watch for SIGCHLD on this platform ???
147 #endif /* !__MINGW32__ */
153 if ((self = [super init])) {
154 if (debugNSTask == -1) {
155 debugNSTask = [[NSUserDefaults standardUserDefaults]
156 boolForKey:@"NSDebugTask"] ? 1 : 0;
164 if (self->pid && self->isRunning) {
165 NSMapRemove (unixProcessToTask, (void*)(long)pid);
168 RELEASE(self->taskPath);
169 RELEASE(self->currentDirectory);
170 RELEASE(self->taskArguments);
171 RELEASE(self->taskEnvironment);
172 RELEASE(self->standardInput);
173 RELEASE(self->standardOutput);
174 RELEASE(self->standardError);
178 - (void)setLaunchPath:(NSString*)path
180 if (self->isRunning || self->childHasFinished) {
181 [NSException raise:NSInvalidArgumentException
182 format:@"task is already launched."];
184 ASSIGN(self->taskPath, path);
187 - (void)setArguments:(NSArray*)arguments
189 if (self->isRunning || self->childHasFinished) {
190 [NSException raise:NSInvalidArgumentException
191 format:@"task is already launched."];
193 ASSIGN(taskArguments, arguments);
196 - (void)setEnvironment:(NSDictionary*)dict
198 if (self->isRunning || self->childHasFinished) {
199 [NSException raise:NSInvalidArgumentException
200 format:@"task is already launched."];
202 ASSIGN(self->taskEnvironment, dict);
205 - (void)setCurrentDirectoryPath:(NSString*)path
207 if (self->isRunning || self->childHasFinished) {
208 [NSException raise:NSInvalidArgumentException
209 format:@"task is already launched."];
211 ASSIGN(currentDirectory, path);
214 - (void)setStandardInput:(id)input
216 if (self->isRunning || self->childHasFinished) {
217 [NSException raise:NSInvalidArgumentException
218 format:@"task is already launched."];
220 ASSIGN(self->standardInput, input);
224 return self->standardInput;
227 - (void)setStandardOutput:(id)output
229 if (self->isRunning || self->childHasFinished) {
230 [NSException raise:NSInvalidArgumentException
231 format:@"task is already launched."];
233 ASSIGN(self->standardOutput, output);
237 return self->standardOutput;
240 - (void)setStandardError:(id)error
242 if (self->isRunning || self->childHasFinished) {
243 [NSException raise:NSInvalidArgumentException
244 format:@"task is already launched."];
246 ASSIGN(self->standardError, error);
250 return self->standardError;
253 - (NSString *)launchPath
255 return self->taskPath;
257 - (NSArray *)arguments
259 return self->taskArguments;
261 - (NSDictionary *)environment
263 return self->taskEnvironment;
265 - (NSString *)currentDirectoryPath
267 return self->currentDirectory;
272 return self->isRunning;
274 - (unsigned int)processId
283 NSEnumerator *enumerator;
285 if (self->standardInput) {
290 fd = [self->standardInput isKindOfClass:[NSPipe class]]
291 ? [[self->standardInput fileHandleForReading] fileDescriptor]
292 : [self->standardInput fileDescriptor];
295 if (self->standardOutput) {
297 fd = [self->standardOutput isKindOfClass:[NSPipe class]]
298 ? [[self->standardOutput fileHandleForWriting] fileDescriptor]
299 : [self->standardOutput fileDescriptor];
302 if (self->standardError) {
304 fd = [self->standardError isKindOfClass:[NSPipe class]]
305 ? [[self->standardError fileHandleForWriting] fileDescriptor]
306 : [self->standardError fileDescriptor];
310 // close all descriptors but stdin, stdout and stderr (0,1 and 2)
311 // (this close procedure includes the pipe descriptors !)
312 #if !defined(__MINGW32__)
313 for (fd = 3; fd < NOFILE; fd++)
317 if (self->currentDirectory)
318 chdir([self->currentDirectory cString]);
320 count = [taskArguments count];
321 parg = (char**)Malloc ((count + 2) * sizeof(void*));
322 parg[0] = Strdup ([self->taskPath cString]);
323 for (i = 0; i < count; i++)
324 parg[i + 1] = Strdup ([[taskArguments objectAtIndex:i] cString]);
325 parg[count + 1] = NULL;
327 path = Strdup ([self->taskPath cString]);
329 if (taskEnvironment) {
332 count = [taskEnvironment count];
333 penv = (char**)Malloc ((count + 1) * sizeof(void*));
334 enumerator = [taskEnvironment keyEnumerator];
335 for (i = 0; i < count; i++) {
336 NSString* key = [enumerator nextObject];
337 const char* keyCString = [key cString];
338 const char* valueCString = [[taskEnvironment objectForKey:key]
340 char buffer[Strlen(keyCString) + Strlen(valueCString) + 2];
342 sprintf (buffer, "%s=%s", keyCString, valueCString);
343 penv[i] = Strdup (buffer);
347 if (execve (path, parg, penv) == -1) {
348 NSLog(@"Can't launch the child process, exec() failed!",
354 if (execvp (path, parg) == -1) {
355 NSLog(@"Can't launch the child process, exec() failed!: %s",
362 #if !defined(__MINGW32__)
365 if (gotSIGCHLD) [self _processExitCodes];
368 [[self class] _safePoint];
371 + (void)_processExitCodes {
372 /* Note: this may not allocate memory if
373 +doesNotifyNotificationObserversInSignalHandler
379 NSConcreteUnixTask *task;
380 UnixSignalHandler *ush;
382 extern BOOL UnixSignalHandlerIsProcessing;
384 if (UnixSignalHandlerIsProcessing) {
385 /* not really allowed to call print in a sig handler ... */
386 fprintf(stderr,"%s: called in sig handler context ...\n",
387 __PRETTY_FUNCTION__);
392 ush = [UnixSignalHandler sharedHandler];
393 [ush blockSignal:SIGCHLD];
394 gotSIGCHLD = NO; /* reset signal handler flag */
397 /* process all SIGCHLD signals */
398 if (unixProcessToTask) {
399 NSMutableArray *toBeNotified = nil;
400 int left = 0, terminated = 0;
402 e = NSEnumerateMapTable(unixProcessToTask); // THREAD ?
404 while (NSNextMapEnumeratorPair(&e,(void*)&unixPid,(void*)&task)) {
407 if (!task->isRunning) continue;
409 res = waitpid(unixPid, &s, WNOHANG);
411 if (res == unixPid) {
413 task->status = WEXITSTATUS(s);
415 /* task abnormally returned */
419 /* mark task object as terminated */
420 task->isRunning = NO;
421 task->childHasFinished = YES;
423 /* later post a notification ... */
424 if (toBeNotified == nil)
425 toBeNotified = [NSMutableArray arrayWithCapacity:16];
426 [toBeNotified addObject:task];
431 /* task is still running :-) */
434 else if (res == -1) {
436 if (errno != ECHILD /* child isn't up yet ;-) */) {
438 "ERROR(%s): waitpid(%u): %i %s\n",
440 unixPid, errno, strerror(errno));
445 /* different pid ??? */
447 "ERROR(%s): waitpid(%u) returned a different pid %u\n",
454 if (terminated > 1 || debugNSTask) {
456 "%s: %i task%s running, %i task%s terminated\n",
458 left, (left==1?"":"s"),
459 terminated, (terminated==1?"":"s"));
462 /* post notifications */
464 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
467 e = [toBeNotified objectEnumerator];
468 while ((task = [e nextObject])) {
469 /* should we delay posting to the runloop queue ASAP ? */
472 NSMapRemove(unixProcessToTask, (void*)(long)task->pid);
476 [nc postNotificationName:NSTaskDidTerminateNotification
482 NSLog(@"ERROR(%s): missing unixProcessToTask ...",
483 __PRETTY_FUNCTION__);
486 /* process a single SIGCHLD signal */
489 if ((long)unixPid != -1) {
490 /* lookup the task object of the terminated child */
491 if ((task = NSMapGet(unixProcessToTask, (void*)(long)unixPid)) == nil)
494 /* if task terminated via _exit(), retrieve and set the exit status */
496 task->status = WEXITSTATUS(s);
498 /* mark task object as terminated */
499 task->isRunning = NO;
500 task->childHasFinished = YES;
502 /* post notification that task did terminate (new in MacOSX-S) */
503 [[NSNotificationCenter defaultCenter]
504 postNotificationName:
505 NSTaskDidTerminateNotification
509 [ush enableSignal:SIGCHLD];
512 + (void)_sigchldImmediate:(int)signum
515 Do NOT do anything in this immediate signal handler !!!,
516 just set flags etc ...
519 /* this is disallowed in sighandlers !!! only for debugging */
521 printf("SIGCHLD ...\n");
523 printf("SIGCHLD (was set) ...\n");
527 + (void)_childFinished:(int)signum
531 We don't use self here because it sometimes isn't actually "self" (It's
533 Seems like there are problems with the UnixSignalHandler code
536 I have no idea how this can ever happen. My guess is that your GCC
537 break-optimizes the code since I _never_ had this issue.
539 Note that signal handlers run with a different stack.
542 [NSConcreteUnixTask _processExitCodes];
544 [self _processExitCodes];
548 #endif /* !defined(__MINGW32__) */
552 if (self->taskPath == nil) {
553 [[[InvalidArgumentException alloc]
554 initWithReason:@"the task's executable name is not setup!"] raise];
557 if (![[NSFileManager defaultManager]
558 isExecutableFileAtPath:self->taskPath]){
559 [[[InvalidArgumentException alloc]
560 initWithReason:@"the task's path is not executable!"] raise];
569 [[UnixSignalHandler sharedHandler] blockSignal:SIGCHLD];
570 #elif defined(SIGCLD)
571 [[UnixSignalHandler sharedHandler] blockSignal:SIGCLD];
574 #if defined(__MINGW32__)
575 # warning NSTask not supported yet with mingw32
586 NSLog(@"Can't launch the child process, vfork() failed: %s!",
592 [[UnixSignalHandler sharedHandler] enableSignal:SIGCHLD];
593 #elif defined(SIGCLD)
594 [[UnixSignalHandler sharedHandler] enableSignal:SIGCLD];
601 NSMapInsert (unixProcessToTask, (void*)(long)pid, self);
603 [[UnixSignalHandler sharedHandler] enableSignal:SIGCHLD];
604 #elif defined(SIGCLD)
605 [[UnixSignalHandler sharedHandler] enableSignal:SIGCLD];
607 // close handles of pipes
608 if ([self->standardInput isKindOfClass:[NSPipe class]])
609 [[self->standardInput fileHandleForReading] closeFile];
611 [self->standardInput closeFile];
613 if ([self->standardOutput isKindOfClass:[NSPipe class]])
614 [[self->standardOutput fileHandleForWriting] closeFile];
616 [self->standardOutput closeFile];
618 if ([self->standardError isKindOfClass:[NSPipe class]])
619 [[self->standardError fileHandleForWriting] closeFile];
621 [self->standardError closeFile];
627 #endif /* !MINGW32 */
634 if (self->childHasFinished) {
635 /* -terminate has been already sent and the child finished */
639 #if 0 // HH: this is wrong, the task could have terminated itself before ...
640 if (!self->isRunning) {
641 [([[InvalidArgumentException alloc]
642 initWithReason:@"task has not been launched yet!"] raise];
646 #if !defined(__MINGW32__)
647 /* send SIGTERM to process */
649 kill(self->pid, SIGTERM);
655 Post the termination notification. A better idea would be to post this
656 notification after the child has exited, by adding the task object
657 as an observer to UnixSignalHandler in the signal handler function.
658 But we keep here the same semantics with that in documentation.
661 That's now the case in MacOSX-S. The notification is posted when the
664 [[NSNotificationCenter defaultCenter]
666 [NSNotification notificationWithName:NSTaskDidTerminateNotification
669 #endif /* !MINGW32 */
675 if (self->childHasFinished) {
676 /* -terminate has been already sent and the child finished */
680 if (!self->isRunning) {
681 [[[InvalidArgumentException alloc]
682 initWithReason:@"task has not been launched yet!"] raise];
685 #if !defined(__MINGW32__)
686 /* send interrupt signal to process */
693 - (int)terminationStatus
696 if (self->isRunning) {
697 [[[InvalidArgumentException alloc]
698 initWithReason:@"task is still running!"] raise];
703 - (void)waitUntilExit
705 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
709 while (self->isRunning) {
711 CREATE_AUTORELEASE_POOL(pool);
714 aDate = [runLoop limitDateForMode:NSDefaultRunLoopMode];
716 [runLoop acceptInputForMode:NSDefaultRunLoopMode beforeDate:aDate];
725 - (NSString *)description
727 /* Don't use -[NSString stringWithFormat:] method because it can cause
728 infinite recursion. */
732 "<0x%08X<%s> isRunning=%s childHasFinished=%s pid=%d>",
733 (unsigned)self, (char*)object_get_class_name(self),
734 self->isRunning ? "YES" : "NO",
735 self->childHasFinished ? "YES" : "NO",
736 (unsigned)self->pid);
737 return [NSString stringWithCString:buffer];
740 @end /* NSConcreteUnixTask */