From de8ffcaef492e23af57ed5489dd8a21fdd7ad5d8 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 4 Dec 2006 14:42:47 -0500 Subject: ENH: Changes based on patch from Ryan C. Gordon to enable process execution on BeOS. There seems to be no way to implement it without polling (or threads). --- Source/kwsys/ProcessUNIX.c | 515 ++++++++++++++++++++++++++++++++------------- Source/kwsys/testProcess.c | 16 +- 2 files changed, 382 insertions(+), 149 deletions(-) diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c index d0d29a7..eca007c 100644 --- a/Source/kwsys/ProcessUNIX.c +++ b/Source/kwsys/ProcessUNIX.c @@ -75,6 +75,36 @@ typedef ssize_t kwsysProcess_ssize_t; typedef int kwsysProcess_ssize_t; #endif +#if defined(__BEOS__) && !defined(__ZETA__) +/* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */ +# include +static inline void kwsysProcess_usleep(unsigned int msec) +{ + snooze(msec); +} +#else +# define kwsysProcess_usleep usleep +#endif + +/* + * BeOS's select() works like WinSock: it's for networking only, and + * doesn't work with Unix file handles...socket and file handles are + * different namespaces (the same descriptor means different things in + * each context!) + * + * So on Unix-like systems where select() is flakey, we'll set the + * pipes' file handles to be non-blocking and just poll them directly + * without select(). + */ +#if !defined(__BEOS__) +# define KWSYSPE_USE_SELECT 1 +#endif + +/* BeOS does not have siginfo on its signal handlers. */ +#if !defined(__BEOS__) +# define KWSYSPE_USE_SIGINFO 1 +#endif + /* The number of pipes for the child's output. The standard stdout and stderr pipes are the first two. One more pipe is used to detect when the child process has terminated. The third pipe is @@ -111,6 +141,7 @@ typedef struct kwsysProcessCreateInformation_s static int kwsysProcessInitialize(kwsysProcess* cp); static void kwsysProcessCleanup(kwsysProcess* cp, int error); static void kwsysProcessCleanupDescriptor(int* pfd); +static int kwsysProcessSetNonBlocking(int fd); static int kwsysProcessCreate(kwsysProcess* cp, int prIndex, kwsysProcessCreateInformation* si, int* readEnd); static void kwsysProcessDestroy(kwsysProcess* cp); @@ -135,8 +166,12 @@ static pid_t kwsysProcessFork(kwsysProcess* cp, static void kwsysProcessKill(pid_t process_id); static int kwsysProcessesAdd(kwsysProcess* cp); static void kwsysProcessesRemove(kwsysProcess* cp); +#if KWSYSPE_USE_SIGINFO static void kwsysProcessesSignalHandler(int signum, siginfo_t* info, void* ucontext); +#else +static void kwsysProcessesSignalHandler(int signum); +#endif static char** kwsysProcessParseVerbatimCommand(const char* command); /*--------------------------------------------------------------------------*/ @@ -190,8 +225,10 @@ struct kwsysProcess_s /* The number of pipes left open during execution. */ int PipesLeft; +#if KWSYSPE_USE_SELECT /* File descriptor set for call to select. */ fd_set PipeSet; +#endif /* The number of children still executing. */ int CommandsLeft; @@ -731,6 +768,15 @@ void kwsysProcess_Execute(kwsysProcess* cp) kwsysProcessCleanupDescriptor(&si.StdErr); return; } + +#if !KWSYSPE_USE_SELECT + if(!kwsysProcessSetNonBlocking(p[0])) + { + kwsysProcessCleanup(cp, 1); + kwsysProcessCleanupDescriptor(&si.StdErr); + return; + } +#endif } /* Replace the stderr pipe with a file if requested. In this case @@ -775,10 +821,25 @@ void kwsysProcess_Execute(kwsysProcess* cp) /* Create the pipeline of processes. */ { int readEnd = -1; + int failed = 0; for(i=0; i < cp->NumberOfCommands; ++i) { if(!kwsysProcessCreate(cp, i, &si, &readEnd)) { + failed = 1; + } + +#if !KWSYSPE_USE_SELECT + /* Set the output pipe of the last process to be non-blocking so + we can poll it. */ + if(i == cp->NumberOfCommands-1 && !kwsysProcessSetNonBlocking(readEnd)) + { + failed = 1; + } +#endif + + if(failed) + { kwsysProcessCleanup(cp, 1); /* Release resources that may have been allocated for this @@ -846,8 +907,11 @@ kwsysEXPORT void kwsysProcess_Disown(kwsysProcess* cp) { if(cp->PipeReadEnds[i] >= 0) { +#if KWSYSPE_USE_SELECT /* If the pipe was reported by the last call to select, we must - read from it. Ignore the data. */ + read from it. This is needed to satisfy the suggestions from + "man select_tut" and is not needed for the polling + implementation. Ignore the data. */ if(FD_ISSET(cp->PipeReadEnds[i], &cp->PipeSet)) { /* We are handling this pipe now. Remove it from the set. */ @@ -858,6 +922,7 @@ kwsysEXPORT void kwsysProcess_Disown(kwsysProcess* cp) while((read(cp->PipeReadEnds[i], cp->PipeBuffer, KWSYSPE_PIPE_BUFFER_SIZE) < 0) && (errno == EINTR)); } +#endif /* We are done reading from this pipe. */ kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); @@ -873,19 +938,30 @@ kwsysEXPORT void kwsysProcess_Disown(kwsysProcess* cp) } /*--------------------------------------------------------------------------*/ +typedef struct kwsysProcessWaitData_s +{ + int Expired; + int PipeId; + int User; + double* UserTimeout; + kwsysProcessTime TimeoutTime; +} kwsysProcessWaitData; +static int kwsysProcessWaitForPipe(kwsysProcess* cp, char** data, int* length, + kwsysProcessWaitData* wd); + +/*--------------------------------------------------------------------------*/ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, double* userTimeout) { - int i; - int max = -1; - kwsysProcessTimeNative* timeout = 0; - kwsysProcessTimeNative timeoutLength; - kwsysProcessTime timeoutTime; kwsysProcessTime userStartTime = {0, 0}; - int user = 0; - int expired = 0; - int pipeId = kwsysProcess_Pipe_None; - int numReady = 0; + kwsysProcessWaitData wd = + { + 0, + kwsysProcess_Pipe_None, + 0, + userTimeout, + {0, 0} + }; /* Make sure we are executing a process. */ if(!cp || cp->State != kwsysProcess_State_Executing || cp->Killed || @@ -902,140 +978,20 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, /* Calculate the time at which a timeout will expire, and whether it is the user or process timeout. */ - user = kwsysProcessGetTimeoutTime(cp, userTimeout, &timeoutTime); + wd.User = kwsysProcessGetTimeoutTime(cp, userTimeout, + &wd.TimeoutTime); /* Data can only be available when pipes are open. If the process is not running, cp->PipesLeft will be 0. */ - while(cp->PipesLeft > 0) - { - /* Check for any open pipes with data reported ready by the last - call to select. According to "man select_tut" we must deal - with all descriptors reported by a call to select before - passing them to another select call. */ - for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) - { - if(cp->PipeReadEnds[i] >= 0 && - FD_ISSET(cp->PipeReadEnds[i], &cp->PipeSet)) - { - kwsysProcess_ssize_t n; - - /* We are handling this pipe now. Remove it from the set. */ - FD_CLR(cp->PipeReadEnds[i], &cp->PipeSet); - - /* The pipe is ready to read without blocking. Keep trying to - read until the operation is not interrupted. */ - while(((n = read(cp->PipeReadEnds[i], cp->PipeBuffer, - KWSYSPE_PIPE_BUFFER_SIZE)) < 0) && (errno == EINTR)); - if(n > 0) - { - /* We have data on this pipe. */ - if(i == KWSYSPE_PIPE_SIGNAL) - { - /* A child process has terminated. */ - kwsysProcessDestroy(cp); - } - else if(data && length) - { - /* Report this data. */ - *data = cp->PipeBuffer; - *length = n; - switch(i) - { - case KWSYSPE_PIPE_STDOUT: - pipeId = kwsysProcess_Pipe_STDOUT; break; - case KWSYSPE_PIPE_STDERR: - pipeId = kwsysProcess_Pipe_STDERR; break; - }; - break; - } - } - else - { - /* We are done reading from this pipe. */ - kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); - --cp->PipesLeft; - } - } - } - - /* If we have data, break early. */ - if(pipeId) - { - break; - } - - /* Make sure the set is empty (it should always be empty here - anyway). */ - FD_ZERO(&cp->PipeSet); - - /* Setup a timeout if required. */ - if(timeoutTime.tv_sec < 0) - { - timeout = 0; - } - else - { - timeout = &timeoutLength; - } - if(kwsysProcessGetTimeoutLeft(&timeoutTime, user?userTimeout:0, &timeoutLength)) - { - /* Timeout has already expired. */ - expired = 1; - break; - } - - /* Add the pipe reading ends that are still open. */ - max = -1; - for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) - { - if(cp->PipeReadEnds[i] >= 0) - { - FD_SET(cp->PipeReadEnds[i], &cp->PipeSet); - if(cp->PipeReadEnds[i] > max) - { - max = cp->PipeReadEnds[i]; - } - } - } - - /* Make sure we have a non-empty set. */ - if(max < 0) - { - /* All pipes have closed. Child has terminated. */ - break; - } - - /* Run select to block until data are available. Repeat call - until it is not interrupted. */ - while(((numReady = select(max+1, &cp->PipeSet, 0, 0, timeout)) < 0) && - (errno == EINTR)); - - /* Check result of select. */ - if(numReady == 0) - { - /* Select's timeout expired. */ - expired = 1; - break; - } - else if(numReady < 0) - { - /* Select returned an error. Leave the error description in the - pipe buffer. */ - strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); - - /* Kill the children now. */ - kwsysProcess_Kill(cp); - cp->Killed = 0; - cp->SelectError = 1; - } - } + while(cp->PipesLeft > 0 && + !kwsysProcessWaitForPipe(cp, data, length, &wd)) {} /* Update the user timeout. */ if(userTimeout) { kwsysProcessTime userEndTime = kwsysProcessTimeGetCurrent(); kwsysProcessTime difference = kwsysProcessTimeSubtract(userEndTime, - userStartTime); + userStartTime); double d = kwsysProcessTimeToDouble(difference); *userTimeout -= d; if(*userTimeout < 0) @@ -1045,15 +1001,15 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, } /* Check what happened. */ - if(pipeId) + if(wd.PipeId) { /* Data are ready on a pipe. */ - return pipeId; + return wd.PipeId; } - else if(expired) + else if(wd.Expired) { /* A timeout has expired. */ - if(user) + if(wd.User) { /* The user timeout has expired. It has no time left. */ return kwsysProcess_Pipe_Timeout; @@ -1075,6 +1031,230 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, } /*--------------------------------------------------------------------------*/ +static int kwsysProcessWaitForPipe(kwsysProcess* cp, char** data, int* length, + kwsysProcessWaitData* wd) +{ + int i; + kwsysProcessTimeNative timeoutLength; + +#if KWSYSPE_USE_SELECT + int numReady = 0; + int max = -1; + kwsysProcessTimeNative* timeout = 0; + + /* Check for any open pipes with data reported ready by the last + call to select. According to "man select_tut" we must deal + with all descriptors reported by a call to select before + passing them to another select call. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0 && + FD_ISSET(cp->PipeReadEnds[i], &cp->PipeSet)) + { + kwsysProcess_ssize_t n; + + /* We are handling this pipe now. Remove it from the set. */ + FD_CLR(cp->PipeReadEnds[i], &cp->PipeSet); + + /* The pipe is ready to read without blocking. Keep trying to + read until the operation is not interrupted. */ + while(((n = read(cp->PipeReadEnds[i], cp->PipeBuffer, + KWSYSPE_PIPE_BUFFER_SIZE)) < 0) && (errno == EINTR)); + if(n > 0) + { + /* We have data on this pipe. */ + if(i == KWSYSPE_PIPE_SIGNAL) + { + /* A child process has terminated. */ + kwsysProcessDestroy(cp); + } + else if(data && length) + { + /* Report this data. */ + *data = cp->PipeBuffer; + *length = n; + switch(i) + { + case KWSYSPE_PIPE_STDOUT: + wd->PipeId = kwsysProcess_Pipe_STDOUT; break; + case KWSYSPE_PIPE_STDERR: + wd->PipeId = kwsysProcess_Pipe_STDERR; break; + }; + return 1; + } + } + else + { + /* We are done reading from this pipe. */ + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + --cp->PipesLeft; + } + } + } + + /* If we have data, break early. */ + if(wd->PipeId) + { + return 1; + } + + /* Make sure the set is empty (it should always be empty here + anyway). */ + FD_ZERO(&cp->PipeSet); + + /* Setup a timeout if required. */ + if(wd->TimeoutTime.tv_sec < 0) + { + timeout = 0; + } + else + { + timeout = &timeoutLength; + } + if(kwsysProcessGetTimeoutLeft(&wd->TimeoutTime, + wd->User?wd->UserTimeout:0, + &timeoutLength)) + { + /* Timeout has already expired. */ + wd->Expired = 1; + return 1; + } + + /* Add the pipe reading ends that are still open. */ + max = -1; + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0) + { + FD_SET(cp->PipeReadEnds[i], &cp->PipeSet); + if(cp->PipeReadEnds[i] > max) + { + max = cp->PipeReadEnds[i]; + } + } + } + + /* Make sure we have a non-empty set. */ + if(max < 0) + { + /* All pipes have closed. Child has terminated. */ + return 1; + } + + /* Run select to block until data are available. Repeat call + until it is not interrupted. */ + while(((numReady = select(max+1, &cp->PipeSet, 0, 0, timeout)) < 0) && + (errno == EINTR)); + + /* Check result of select. */ + if(numReady == 0) + { + /* Select's timeout expired. */ + wd->Expired = 1; + return 1; + } + else if(numReady < 0) + { + /* Select returned an error. Leave the error description in the + pipe buffer. */ + strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); + + /* Kill the children now. */ + kwsysProcess_Kill(cp); + cp->Killed = 0; + cp->SelectError = 1; + } + + return 0; +#else + /* Poll pipes for data since we do not have select. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0) + { + const int fd = cp->PipeReadEnds[i]; + int n = read(fd, cp->PipeBuffer, KWSYSPE_PIPE_BUFFER_SIZE); + if(n > 0) + { + /* We have data on this pipe. */ + if(i == KWSYSPE_PIPE_SIGNAL) + { + /* A child process has terminated. */ + kwsysProcessDestroy(cp); + } + else if(data && length) + { + /* Report this data. */ + *data = cp->PipeBuffer; + *length = n; + switch(i) + { + case KWSYSPE_PIPE_STDOUT: + wd->PipeId = kwsysProcess_Pipe_STDOUT; break; + case KWSYSPE_PIPE_STDERR: + wd->PipeId = kwsysProcess_Pipe_STDERR; break; + }; + } + return 1; + } + else if (n == 0) /* EOF */ + { + /* We are done reading from this pipe. */ + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + --cp->PipesLeft; + } + else if (n < 0) /* error */ + { + if((errno != EINTR) && (errno != EAGAIN)) + { + strncpy(cp->ErrorMessage,strerror(errno), + KWSYSPE_PIPE_BUFFER_SIZE); + /* Kill the children now. */ + kwsysProcess_Kill(cp); + cp->Killed = 0; + cp->SelectError = 1; + return 1; + } + } + } + } + + /* If we have data, break early. */ + if(wd->PipeId) + { + return 1; + } + + if(kwsysProcessGetTimeoutLeft(&wd->TimeoutTime, wd->User?wd->UserTimeout:0, + &timeoutLength)) + { + /* Timeout has already expired. */ + wd->Expired = 1; + return 1; + } + + if((timeoutLength.tv_sec == 0) && (timeoutLength.tv_usec == 0)) + { + /* Timeout has already expired. */ + wd->Expired = 1; + return 1; + } + + /* Sleep a little, try again. */ + { + unsigned int msec = ((timeoutLength.tv_sec * 1000) + + (timeoutLength.tv_usec / 1000)); + if (msec > 100000) + { + msec = 100000; /* do not sleep more than 100 milliseconds at a time */ + } + kwsysProcess_usleep(msec); + } + return 0; +#endif +} + +/*--------------------------------------------------------------------------*/ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) { int status = 0; @@ -1215,7 +1395,9 @@ static int kwsysProcessInitialize(kwsysProcess* cp) cp->TimeoutExpired = 0; cp->PipesLeft = 0; cp->CommandsLeft = 0; +#if KWSYSPE_USE_SELECT FD_ZERO(&cp->PipeSet); +#endif cp->State = kwsysProcess_State_Starting; cp->Killed = 0; cp->ExitException = kwsysProcess_Exception_None; @@ -1352,6 +1534,17 @@ static void kwsysProcessCleanupDescriptor(int* pfd) } /*--------------------------------------------------------------------------*/ +static int kwsysProcessSetNonBlocking(int fd) +{ + int flags = fcntl(fd, F_GETFL); + if(flags >= 0) + { + flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } + return flags >= 0; +} + +/*--------------------------------------------------------------------------*/ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex, kwsysProcessCreateInformation* si, int* readEnd) { @@ -2260,10 +2453,8 @@ static int kwsysProcessesAdd(kwsysProcess* cp) /* Switch the pipe to non-blocking mode so that reading a byte can be an atomic test-and-set. */ - if((oldfl[0] = fcntl(p[0], F_GETFL) < 0) || - (oldfl[1] = fcntl(p[1], F_GETFL) < 0) || - (fcntl(p[0], F_SETFL, oldfl[0] | O_NONBLOCK) < 0) || - (fcntl(p[1], F_SETFL, oldfl[1] | O_NONBLOCK) < 0)) + if(!kwsysProcessSetNonBlocking(p[0]) || + !kwsysProcessSetNonBlocking(p[1])) { return 0; } @@ -2327,10 +2518,15 @@ static int kwsysProcessesAdd(kwsysProcess* cp) interrupted. */ struct sigaction newSigChldAction; memset(&newSigChldAction, 0, sizeof(struct sigaction)); +#if KWSYSPE_USE_SIGINFO newSigChldAction.sa_sigaction = kwsysProcessesSignalHandler; newSigChldAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; -#ifdef SA_RESTART +# ifdef SA_RESTART newSigChldAction.sa_flags |= SA_RESTART; +# endif +#else + newSigChldAction.sa_handler = kwsysProcessesSignalHandler; + newSigChldAction.sa_flags = SA_NOCLDSTOP; #endif while((sigaction(SIGCHLD, &newSigChldAction, &kwsysProcessesOldSigChldAction) < 0) && @@ -2391,14 +2587,21 @@ static void kwsysProcessesRemove(kwsysProcess* cp) } /*--------------------------------------------------------------------------*/ -static void kwsysProcessesSignalHandler(int signum, siginfo_t* info, - void* ucontext) +static void kwsysProcessesSignalHandler(int signum +#if KWSYSPE_USE_SIGINFO + , siginfo_t* info, void* ucontext +#endif + ) { - /* Signal all process objects that a child has terminated. */ - int i; (void)signum; +#if KWSYSPE_USE_SIGINFO (void)info; (void)ucontext; +#endif + + /* Signal all process objects that a child has terminated. */ + { + int i; for(i=0; i < kwsysProcesses.Count; ++i) { /* Set the pipe in a signalled state. */ @@ -2407,6 +2610,21 @@ static void kwsysProcessesSignalHandler(int signum, siginfo_t* info, read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1); write(cp->SignalPipe, &buf, 1); } + } + +#if !KWSYSPE_USE_SIGINFO + /* Re-Install our handler for SIGCHLD. Repeat call until it is not + interrupted. */ + { + struct sigaction newSigChldAction; + memset(&newSigChldAction, 0, sizeof(struct sigaction)); + newSigChldAction.sa_handler = kwsysProcessesSignalHandler; + newSigChldAction.sa_flags = SA_NOCLDSTOP; + while((sigaction(SIGCHLD, &newSigChldAction, + &kwsysProcessesOldSigChldAction) < 0) && + (errno == EINTR)); + } +#endif } /*--------------------------------------------------------------------------*/ @@ -2661,3 +2879,4 @@ static char** kwsysProcessParseVerbatimCommand(const char* command) /* Return the final command buffer. */ return newCommand; } + diff --git a/Source/kwsys/testProcess.c b/Source/kwsys/testProcess.c index e9fc224..8ca56ed 100644 --- a/Source/kwsys/testProcess.c +++ b/Source/kwsys/testProcess.c @@ -34,6 +34,17 @@ # pragma warn -8060 /* possibly incorrect assignment */ #endif +#if defined(__BEOS__) && !defined(__ZETA__) +/* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */ +# include +static inline void testProcess_usleep(unsigned int msec) +{ + snooze(msec); +} +#else +# define testProcess_usleep usleep +#endif + int runChild(const char* cmd[], int state, int exception, int value, int share, int output, int delay, double timeout, int poll, int repeat, int disown); @@ -76,6 +87,9 @@ int test4(int argc, const char* argv[]) #if defined(_WIN32) /* Avoid error diagnostic popups since we are crashing on purpose. */ SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); +#elif defined(__BEOS__) + /* Avoid error diagnostic popups since we are crashing on purpose. */ + disable_debugger(1); #endif (void)argc; (void)argv; fprintf(stdout, "Output before crash on stdout from crash test.\n"); @@ -264,7 +278,7 @@ int runChild2(kwsysProcess* kp, #if defined(_WIN32) Sleep(100); #else - usleep(100000); + testProcess_usleep(100000); #endif } if(delay) -- cgit v0.12