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).

This commit is contained in:
Brad King 2006-12-04 14:42:47 -05:00
parent d045ae45f2
commit de8ffcaef4
2 changed files with 382 additions and 149 deletions

View File

@ -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 <be/kernel/OS.h>
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,9 +821,24 @@ 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);
@ -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]);
@ -872,20 +937,31 @@ kwsysEXPORT void kwsysProcess_Disown(kwsysProcess* cp)
cp->State = kwsysProcess_State_Disowned;
}
/*--------------------------------------------------------------------------*/
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;
@ -1074,6 +1030,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)
{
@ -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;
@ -1351,6 +1533,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;
}

View File

@ -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 <be/kernel/OS.h>
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)