]> err.no Git - sope/blob - libFoundation/Foundation/NSConcreteUnixTask.m
bringing libFoundation-1.0.75 into the main branch
[sope] / libFoundation / Foundation / NSConcreteUnixTask.m
1 /* 
2    NSConcreteUnixTask.m
3
4    Copyright (C) 1995, 1996, 1997 Ovidiu Predescu and Mircea Oancea.
5    All rights reserved.
6
7    Author: Ovidiu Predescu <ovidiu@net-community.com>
8
9    Based on the code written by Aleksandr Savostyanov <sav@conextions.com>.
10
11    This file is part of libFoundation.
12
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
17    documentation.
18
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.
25 */
26
27
28 #include <config.h>
29 #include <extensions/objc-runtime.h>
30 #include <Foundation/NSUserDefaults.h>
31
32 #ifdef HAVE_LIBC_H
33 #  include <libc.h>
34 #else
35 #  include <unistd.h>
36 #endif
37
38 #if HAVE_VFORK_H
39 # include <vfork.h>
40 #endif
41
42 #include <errno.h>
43 #include <signal.h>
44
45 #include <sys/types.h>
46
47 #if HAVE_SYS_WAIT_H
48 # include <sys/wait.h>
49 # define WAIT_TYPE int
50 #else
51   /* Old BSD union wait */
52 # define WAIT_TYPE union wait
53
54 # ifndef WEXITSTATUS
55 #  define WEXITSTATUS(stat_val) (int)(WIFEXITED(stat_val) \
56                                         ? (((stat_val.w_status) >> 8) & 0377) \
57                                         : -1)
58 # endif
59 #endif
60
61 #ifndef WEXITSTATUS
62 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
63 #endif
64
65 #ifndef WIFEXITED
66 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
67 #endif
68
69 #if !defined(__MINGW32__)
70 #  include <sys/resource.h>
71 #endif
72
73 #if HAVE_SYS_PARAM_H
74 # include <sys/param.h>
75 #endif
76
77 #if HAVE_SYS_TIME_H
78 # include <sys/time.h>  /* for struct timeval */
79 #endif
80
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"
96
97 #include <Foundation/exceptions/GeneralExceptions.h>
98
99 static NSMapTable *unixProcessToTask = NULL;
100 static BOOL gotSIGCHLD = NO;
101
102 @interface NSConcreteUnixTask(Privates)
103 + (void)_processExitCodes;
104 + (void)_safePoint;
105 - (void)_safePoint;
106 @end
107
108 @implementation NSConcreteUnixTask
109
110 static int debugNSTask = -1;
111
112 + (void)initialize
113 {
114     static BOOL initialized = NO;
115     
116     if (!initialized) {
117         UnixSignalHandler *ush;
118         initialized = YES;
119         
120         unixProcessToTask = NSCreateMapTable (NSIntMapKeyCallBacks,
121                                     NSNonRetainedObjectMapValueCallBacks, 19);
122         
123         ush = [UnixSignalHandler sharedHandler];
124         
125 #if !defined(__MINGW32__)
126 #  if defined(SIGCHLD)
127         [ush addObserver:self
128              selector:@selector(_childFinished:)
129              forSignal:SIGCHLD
130              immediatelyNotifyOnSignal:NO];
131         [ush addObserver:self
132              selector:@selector(_sigchldImmediate:)
133              forSignal:SIGCHLD
134              immediatelyNotifyOnSignal:YES];
135 #  elif defined(SIGCLD)
136         [ush addObserver:self
137              selector:@selector(_childFinished:)
138              forSignal:SIGCLD
139              immediatelyNotifyOnSignal:NO];
140         [ush addObserver:self
141              selector:@selector(_sigchldImmediate:)
142              forSignal:SIGCLD
143              immediatelyNotifyOnSignal:YES];
144 #  else
145 #    erro how to watch for SIGCHLD on this platform ???
146 #endif
147 #endif /* !__MINGW32__ */
148     }
149 }
150
151 - (id)init
152 {
153     if ((self = [super init])) {
154         if (debugNSTask == -1) {
155             debugNSTask = [[NSUserDefaults standardUserDefaults]
156                                            boolForKey:@"NSDebugTask"] ? 1 : 0;
157         }
158     }
159     return self;
160 }
161
162 - (void)dealloc
163 {
164     if (self->pid && self->isRunning) {
165         NSMapRemove (unixProcessToTask, (void*)(long)pid);
166     }
167     
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);
175     [super dealloc];
176 }
177
178 - (void)setLaunchPath:(NSString*)path
179 {
180     if (self->isRunning || self->childHasFinished) {
181         [NSException raise:NSInvalidArgumentException
182                      format:@"task is already launched."];
183     }
184     ASSIGN(self->taskPath, path);
185 }
186
187 - (void)setArguments:(NSArray*)arguments
188 {
189     if (self->isRunning || self->childHasFinished) {
190         [NSException raise:NSInvalidArgumentException
191                      format:@"task is already launched."];
192     }
193     ASSIGN(taskArguments, arguments);
194 }
195
196 - (void)setEnvironment:(NSDictionary*)dict
197 {
198     if (self->isRunning || self->childHasFinished) {
199         [NSException raise:NSInvalidArgumentException
200                      format:@"task is already launched."];
201     }
202     ASSIGN(self->taskEnvironment, dict);
203 }
204
205 - (void)setCurrentDirectoryPath:(NSString*)path
206 {
207     if (self->isRunning || self->childHasFinished) {
208         [NSException raise:NSInvalidArgumentException
209                      format:@"task is already launched."];
210     }
211     ASSIGN(currentDirectory, path);
212 }
213
214 - (void)setStandardInput:(id)input
215 {
216     if (self->isRunning || self->childHasFinished) {
217         [NSException raise:NSInvalidArgumentException
218                      format:@"task is already launched."];
219     }
220     ASSIGN(self->standardInput, input);
221 }
222 - (id)standardInput
223 {
224     return self->standardInput;
225 }
226
227 - (void)setStandardOutput:(id)output
228 {
229     if (self->isRunning || self->childHasFinished) {
230         [NSException raise:NSInvalidArgumentException
231                      format:@"task is already launched."];
232     }
233     ASSIGN(self->standardOutput, output);
234 }
235 - (id)standardOutput
236 {
237     return self->standardOutput;
238 }
239
240 - (void)setStandardError:(id)error
241 {
242     if (self->isRunning || self->childHasFinished) {
243         [NSException raise:NSInvalidArgumentException
244                      format:@"task is already launched."];
245     }
246     ASSIGN(self->standardError, error);
247 }
248 - (id)standardError
249 {
250     return self->standardError;
251 }
252
253 - (NSString *)launchPath
254 {
255     return self->taskPath;
256 }
257 - (NSArray *)arguments
258 {
259     return self->taskArguments;
260 }
261 - (NSDictionary *)environment
262 {
263     return self->taskEnvironment;
264 }
265 - (NSString *)currentDirectoryPath
266 {
267     return self->currentDirectory;
268 }
269
270 - (BOOL)isRunning
271 {
272     return self->isRunning;
273 }
274 - (unsigned int)processId
275 {
276     return self->pid;
277 }
278
279 - (void)_execChild
280 {
281     int          i, count, fd;
282     char         *path, **parg;
283     NSEnumerator *enumerator;
284
285     if (self->standardInput) {
286         int fd;
287         
288         close(0);
289
290         fd = [self->standardInput isKindOfClass:[NSPipe class]]
291             ? [[self->standardInput fileHandleForReading] fileDescriptor]
292             : [self->standardInput fileDescriptor];
293         dup2(fd, 0);
294     }
295     if (self->standardOutput) {
296         close(1);
297         fd = [self->standardOutput isKindOfClass:[NSPipe class]]
298             ? [[self->standardOutput fileHandleForWriting] fileDescriptor]
299             : [self->standardOutput fileDescriptor];
300         dup2(fd, 1);
301     }
302     if (self->standardError) {
303         close(2);
304         fd = [self->standardError isKindOfClass:[NSPipe class]]
305             ? [[self->standardError fileHandleForWriting] fileDescriptor]
306             : [self->standardError fileDescriptor];
307         dup2(fd, 2);
308     }
309
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++)
314         close(fd);
315 #endif
316
317     if (self->currentDirectory)
318         chdir([self->currentDirectory cString]);
319
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;
326     
327     path = Strdup ([self->taskPath cString]);
328     
329     if (taskEnvironment) {
330         char **penv;
331         
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]
339                                                          cString];
340             char buffer[Strlen(keyCString) + Strlen(valueCString) + 2];
341
342             sprintf (buffer, "%s=%s", keyCString, valueCString);
343             penv[i] = Strdup (buffer);
344         }
345         penv[count] = NULL;
346         
347         if (execve (path, parg, penv) == -1) {
348             NSLog(@"Can't launch the child process, exec() failed!",
349                   strerror (errno));
350         }
351         lfFree(penv);
352     }
353     else {
354         if (execvp (path, parg) == -1) {
355             NSLog(@"Can't launch the child process, exec() failed!: %s",
356                   strerror (errno));
357         }
358     }
359     lfFree(parg);
360 }
361
362 #if !defined(__MINGW32__)
363
364 + (void)_safePoint {
365     if (gotSIGCHLD) [self _processExitCodes];
366 }
367 - (void)_safePoint {
368     [[self class] _safePoint];
369 }
370
371 + (void)_processExitCodes {
372     /* Note: this may not allocate memory if
373        +doesNotifyNotificationObserversInSignalHandler
374        returns YES.
375        (it shouldn't)
376     */
377     WAIT_TYPE          s;
378     pid_t              unixPid;
379     NSConcreteUnixTask *task;
380     UnixSignalHandler  *ush;
381     NSMapEnumerator    e;
382     extern BOOL UnixSignalHandlerIsProcessing;
383     
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__);
388         fflush(stderr);
389         return;
390     }
391     
392     ush = [UnixSignalHandler sharedHandler];
393     [ush blockSignal:SIGCHLD];
394     gotSIGCHLD = NO; /* reset signal handler flag */
395     
396 #if 1
397     /* process all SIGCHLD signals */
398     if (unixProcessToTask) {
399         NSMutableArray *toBeNotified = nil;
400         int left = 0, terminated = 0;
401         
402         e = NSEnumerateMapTable(unixProcessToTask); // THREAD ?
403         
404         while (NSNextMapEnumeratorPair(&e,(void*)&unixPid,(void*)&task)) {
405             pid_t res;
406             
407             if (!task->isRunning) continue;
408             
409             res = waitpid(unixPid, &s, WNOHANG);
410             
411             if (res == unixPid) {
412                 if (WIFEXITED(s))
413                     task->status = WEXITSTATUS(s);
414                 else {
415                     /* task abnormally returned */
416                     task->status = -1;
417                 }
418                 
419                 /* mark task object as terminated */
420                 task->isRunning        = NO;
421                 task->childHasFinished = YES;
422                 
423                 /* later post a notification ... */
424                 if (toBeNotified == nil)
425                     toBeNotified = [NSMutableArray arrayWithCapacity:16];
426                 [toBeNotified addObject:task];
427                 
428                 terminated++;
429             }
430             else if (res == 0) {
431                 /* task is still running :-) */
432                 left++;
433             }
434             else if (res == -1) {
435                 /* error */
436                 if (errno != ECHILD /* child isn't up yet ;-) */) {
437                     fprintf(stderr,
438                             "ERROR(%s): waitpid(%u): %i %s\n",
439                             __PRETTY_FUNCTION__,
440                             unixPid, errno, strerror(errno));
441                     fflush(stderr);
442                 }
443             }
444             else {
445                 /* different pid ??? */
446                 fprintf(stderr,
447                         "ERROR(%s): waitpid(%u) returned a different pid %u\n",
448                         __PRETTY_FUNCTION__,
449                         unixPid, res);
450                 fflush(stderr);
451             }
452         }
453         
454         if (terminated > 1 || debugNSTask) {
455             fprintf(stderr,
456                     "%s: %i task%s running, %i task%s terminated\n",
457                     __PRETTY_FUNCTION__,
458                     left,       (left==1?"":"s"),
459                     terminated, (terminated==1?"":"s"));
460         }
461         
462         /* post notifications */
463         if (toBeNotified) {
464             NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
465             NSEnumerator *e;
466             
467             e = [toBeNotified objectEnumerator];
468             while ((task = [e nextObject])) {
469                 /* should we delay posting to the runloop queue ASAP ? */
470                 
471                 if (task->pid) {
472                     NSMapRemove(unixProcessToTask, (void*)(long)task->pid);
473                     task->pid = 0;
474                 }
475                 
476                 [nc postNotificationName:NSTaskDidTerminateNotification
477                     object:task];
478             }
479         }
480     }
481     else {
482         NSLog(@"ERROR(%s): missing unixProcessToTask ...",
483               __PRETTY_FUNCTION__);
484     }
485 #else
486     /* process a single SIGCHLD signal */
487     unixPid = wait(&s);
488     
489     if ((long)unixPid != -1) {
490         /* lookup the task object of the terminated child */
491         if ((task = NSMapGet(unixProcessToTask, (void*)(long)unixPid)) == nil)
492             return;
493         
494         /* if task terminated via _exit(), retrieve and set the exit status */
495         if (WIFEXITED(s))
496             task->status = WEXITSTATUS(s);
497     
498         /* mark task object as terminated */
499         task->isRunning        = NO;
500         task->childHasFinished = YES;
501     
502         /* post notification that task did terminate (new in MacOSX-S) */
503         [[NSNotificationCenter defaultCenter]
504                                postNotificationName:
505                                NSTaskDidTerminateNotification
506                                object:task];
507     }
508 #endif
509     [ush enableSignal:SIGCHLD];
510 }
511
512 + (void)_sigchldImmediate:(int)signum
513 {
514     /*
515       Do NOT do anything in this immediate signal handler !!!,
516       just set flags etc ...
517     */
518 #if DEBUG && 0
519     /* this is disallowed in sighandlers !!! only for debugging */
520     if (!gotSIGCHLD)
521         printf("SIGCHLD ...\n");
522     else
523         printf("SIGCHLD (was set) ...\n");
524 #endif
525     gotSIGCHLD = YES;
526 }
527 + (void)_childFinished:(int)signum
528 {
529     /* 
530        Florian:
531        We don't use self here because it sometimes isn't actually "self" (It's
532        NSFrameInvocation)
533        Seems like there are problems with the UnixSignalHandler code
534        
535        HH:
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.
538        
539        Note that signal handlers run with a different stack.
540     */
541 #if 1
542     [NSConcreteUnixTask _processExitCodes];
543 #else
544     [self _processExitCodes];
545 #endif
546 }
547
548 #endif /* !defined(__MINGW32__) */
549
550 - (void)launch
551 {
552     if (self->taskPath == nil) {
553         [[[InvalidArgumentException alloc]
554                   initWithReason:@"the task's executable name is not setup!"] raise];
555     }
556
557     if (![[NSFileManager defaultManager]
558                          isExecutableFileAtPath:self->taskPath]){
559         [[[InvalidArgumentException alloc]
560                   initWithReason:@"the task's path is not executable!"] raise];
561     }
562     
563     if (isRunning)
564         return;
565     
566     status = 0;
567
568 #if defined(SIGCHLD)
569     [[UnixSignalHandler sharedHandler] blockSignal:SIGCHLD];
570 #elif defined(SIGCLD)
571     [[UnixSignalHandler sharedHandler] blockSignal:SIGCLD];
572 #endif
573
574 #if defined(__MINGW32__)
575 #  warning NSTask not supported yet with mingw32
576 #else
577
578 #ifdef linux
579     self->pid = fork();
580 #else
581     self->pid = vfork();
582 #endif
583     
584     switch (self->pid) {
585         case -1:        /* error */
586             NSLog(@"Can't launch the child process, vfork() failed: %s!",
587                   strerror (errno));
588             break;
589
590         case 0:
591 #if defined(SIGCHLD)
592             [[UnixSignalHandler sharedHandler] enableSignal:SIGCHLD];
593 #elif defined(SIGCLD)
594             [[UnixSignalHandler sharedHandler] enableSignal:SIGCLD];
595 #endif
596             [self _execChild];
597             break;
598             
599         default:
600             isRunning = YES;
601             NSMapInsert (unixProcessToTask, (void*)(long)pid, self);
602 #if defined(SIGCHLD)
603             [[UnixSignalHandler sharedHandler] enableSignal:SIGCHLD];
604 #elif defined(SIGCLD)
605             [[UnixSignalHandler sharedHandler] enableSignal:SIGCLD];
606 #endif
607             // close handles of pipes
608             if ([self->standardInput isKindOfClass:[NSPipe class]])
609                 [[self->standardInput fileHandleForReading] closeFile];
610             else
611                 [self->standardInput closeFile];
612             
613             if ([self->standardOutput isKindOfClass:[NSPipe class]])
614                 [[self->standardOutput fileHandleForWriting] closeFile];
615             else
616                 [self->standardOutput closeFile];
617                 
618             if ([self->standardError isKindOfClass:[NSPipe class]])
619                 [[self->standardError fileHandleForWriting] closeFile];
620             else
621                 [self->standardError closeFile];
622
623             break;
624     }
625     
626     [self _safePoint];
627 #endif /* !MINGW32 */
628 }
629
630 - (void)terminate
631 {
632     [self _safePoint];
633     
634     if (self->childHasFinished) {
635         /* -terminate has been already sent and the child finished */
636         return;
637     }
638
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];
643     }
644 #endif
645
646 #if !defined(__MINGW32__)
647     /* send SIGTERM to process */
648     if (self->pid)
649         kill(self->pid, SIGTERM);
650
651     [self _safePoint];
652 #if 0
653     /*
654       Ovidiu wrote:
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.
659
660       Helge says: ;-)
661        That's now the case in MacOSX-S. The notification is posted when the
662        child did exit.
663     */
664     [[NSNotificationCenter defaultCenter]
665         postNotification:
666             [NSNotification notificationWithName:NSTaskDidTerminateNotification
667                             object:self]];
668 #endif
669 #endif /* !MINGW32 */
670 }
671
672 - (void)interrupt
673 {
674     [self _safePoint];
675     if (self->childHasFinished) {
676         /* -terminate has been already sent and the child finished */
677         return;
678     }
679
680     if (!self->isRunning) {
681         [[[InvalidArgumentException alloc]
682                     initWithReason:@"task has not been launched yet!"] raise];
683     }
684
685 #if !defined(__MINGW32__)
686     /* send interrupt signal to process */
687     if (pid)
688         kill(pid, SIGINT);
689     [self _safePoint];
690 #endif
691 }
692
693 - (int)terminationStatus
694 {
695     [self _safePoint];
696     if (self->isRunning) {
697         [[[InvalidArgumentException alloc]
698                   initWithReason:@"task is still running!"] raise];
699     }
700     return status;
701 }
702
703 - (void)waitUntilExit
704 {
705     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
706     
707     [self _safePoint];
708     
709     while (self->isRunning) {
710         NSDate *aDate;
711         CREATE_AUTORELEASE_POOL(pool);
712         {
713             [self _safePoint];
714             aDate = [runLoop limitDateForMode:NSDefaultRunLoopMode];
715             [self _safePoint];
716             [runLoop acceptInputForMode:NSDefaultRunLoopMode beforeDate:aDate];
717             [self _safePoint];
718         }
719         RELEASE(pool);
720     }
721 }
722
723 /* description */
724
725 - (NSString *)description
726 {
727     /* Don't use -[NSString stringWithFormat:] method because it can cause
728        infinite recursion. */
729     char buffer[512];
730
731     sprintf(buffer,
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];
738 }
739
740 @end /* NSConcreteUnixTask */
741
742 /*
743   Local Variables:
744   c-basic-offset: 4
745   tab-width: 8
746   End:
747 */