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,12 +978,70 @@ 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)
while(cp->PipesLeft > 0 &&
!kwsysProcessWaitForPipe(cp, data, length, &wd)) {}
/* Update the user timeout. */
if(userTimeout)
{
kwsysProcessTime userEndTime = kwsysProcessTimeGetCurrent();
kwsysProcessTime difference = kwsysProcessTimeSubtract(userEndTime,
userStartTime);
double d = kwsysProcessTimeToDouble(difference);
*userTimeout -= d;
if(*userTimeout < 0)
{
*userTimeout = 0;
}
}
/* Check what happened. */
if(wd.PipeId)
{
/* Data are ready on a pipe. */
return wd.PipeId;
}
else if(wd.Expired)
{
/* A timeout has expired. */
if(wd.User)
{
/* The user timeout has expired. It has no time left. */
return kwsysProcess_Pipe_Timeout;
}
else
{
/* The process timeout has expired. Kill the children now. */
kwsysProcess_Kill(cp);
cp->Killed = 0;
cp->TimeoutExpired = 1;
return kwsysProcess_Pipe_None;
}
}
else
{
/* No pipes are left open. */
return kwsysProcess_Pipe_None;
}
}
/*--------------------------------------------------------------------------*/
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
@ -942,11 +1076,11 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
switch(i)
{
case KWSYSPE_PIPE_STDOUT:
pipeId = kwsysProcess_Pipe_STDOUT; break;
wd->PipeId = kwsysProcess_Pipe_STDOUT; break;
case KWSYSPE_PIPE_STDERR:
pipeId = kwsysProcess_Pipe_STDERR; break;
wd->PipeId = kwsysProcess_Pipe_STDERR; break;
};
break;
return 1;
}
}
else
@ -959,9 +1093,9 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
}
/* If we have data, break early. */
if(pipeId)
if(wd->PipeId)
{
break;
return 1;
}
/* Make sure the set is empty (it should always be empty here
@ -969,7 +1103,7 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
FD_ZERO(&cp->PipeSet);
/* Setup a timeout if required. */
if(timeoutTime.tv_sec < 0)
if(wd->TimeoutTime.tv_sec < 0)
{
timeout = 0;
}
@ -977,11 +1111,13 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
{
timeout = &timeoutLength;
}
if(kwsysProcessGetTimeoutLeft(&timeoutTime, user?userTimeout:0, &timeoutLength))
if(kwsysProcessGetTimeoutLeft(&wd->TimeoutTime,
wd->User?wd->UserTimeout:0,
&timeoutLength))
{
/* Timeout has already expired. */
expired = 1;
break;
wd->Expired = 1;
return 1;
}
/* Add the pipe reading ends that are still open. */
@ -1002,7 +1138,7 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
if(max < 0)
{
/* All pipes have closed. Child has terminated. */
break;
return 1;
}
/* Run select to block until data are available. Repeat call
@ -1014,8 +1150,8 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
if(numReady == 0)
{
/* Select's timeout expired. */
expired = 1;
break;
wd->Expired = 1;
return 1;
}
else if(numReady < 0)
{
@ -1028,50 +1164,94 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
cp->Killed = 0;
cp->SelectError = 1;
}
}
/* Update the user timeout. */
if(userTimeout)
return 0;
#else
/* Poll pipes for data since we do not have select. */
for(i=0; i < KWSYSPE_PIPE_COUNT; ++i)
{
kwsysProcessTime userEndTime = kwsysProcessTimeGetCurrent();
kwsysProcessTime difference = kwsysProcessTimeSubtract(userEndTime,
userStartTime);
double d = kwsysProcessTimeToDouble(difference);
*userTimeout -= d;
if(*userTimeout < 0)
if(cp->PipeReadEnds[i] >= 0)
{
*userTimeout = 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;
};
}
/* Check what happened. */
if(pipeId)
{
/* Data are ready on a pipe. */
return pipeId;
return 1;
}
else if(expired)
else if (n == 0) /* EOF */
{
/* A timeout has expired. */
if(user)
{
/* The user timeout has expired. It has no time left. */
return kwsysProcess_Pipe_Timeout;
/* We are done reading from this pipe. */
kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]);
--cp->PipesLeft;
}
else
else if (n < 0) /* error */
{
/* The process timeout has expired. Kill the children now. */
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->TimeoutExpired = 1;
return kwsysProcess_Pipe_None;
cp->SelectError = 1;
return 1;
}
}
else
}
}
/* If we have data, break early. */
if(wd->PipeId)
{
/* No pipes are left open. */
return kwsysProcess_Pipe_None;
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
}
/*--------------------------------------------------------------------------*/
@ -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
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. */
@ -2409,6 +2612,21 @@ static void kwsysProcessesSignalHandler(int signum, siginfo_t* info,
}
}
#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
}
/*--------------------------------------------------------------------------*/
static int kwsysProcessAppendByte(char* local,
char** begin, char** end,
@ -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)