From ae28d93a72850d5d48bc8e941af5e99c4f8f2202 Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 7 Jul 2004 17:27:50 -0400 Subject: ENH: Added kwsysProcess_Disown an kwsysProcess_Option_Detach to allow detached processes to be created. Currently implemented only on UNIX. --- Source/kwsys/Process.h.in | 28 +++++++- Source/kwsys/ProcessUNIX.c | 162 ++++++++++++++++++++++++++++++++++++++------ Source/kwsys/ProcessWin32.c | 7 ++ 3 files changed, 175 insertions(+), 22 deletions(-) diff --git a/Source/kwsys/Process.h.in b/Source/kwsys/Process.h.in index 9f2485f..380d1b4 100644 --- a/Source/kwsys/Process.h.in +++ b/Source/kwsys/Process.h.in @@ -32,6 +32,7 @@ #define kwsysProcess_SetWorkingDirectory kwsys_ns(Process_SetWorkingDirectory) #define kwsysProcess_SetPipeFile kwsys_ns(Process_SetPipeFile) #define kwsysProcess_SetPipeShared kwsys_ns(Process_SetPipeShared) +#define kwsysProcess_Option_Detach kwsys_ns(Process_Option_Detach) #define kwsysProcess_Option_HideWindow kwsys_ns(Process_Option_HideWindow) #define kwsysProcess_GetOption kwsys_ns(Process_GetOption) #define kwsysProcess_SetOption kwsys_ns(Process_SetOption) @@ -43,6 +44,7 @@ #define kwsysProcess_State_Exited kwsys_ns(Process_State_Exited) #define kwsysProcess_State_Expired kwsys_ns(Process_State_Expired) #define kwsysProcess_State_Killed kwsys_ns(Process_State_Killed) +#define kwsysProcess_State_Disowned kwsys_ns(Process_State_Disowned) #define kwsysProcess_GetState kwsys_ns(Process_GetState) #define kwsysProcess_State_e kwsys_ns(Process_State_e) #define kwsysProcess_Exception_None kwsys_ns(Process_Exception_None) @@ -58,6 +60,7 @@ #define kwsysProcess_GetErrorString kwsys_ns(Process_GetErrorString) #define kwsysProcess_GetExceptionString kwsys_ns(Process_GetExceptionString) #define kwsysProcess_Execute kwsys_ns(Process_Execute) +#define kwsysProcess_Disown kwsys_ns(Process_Disown) #define kwsysProcess_WaitForData kwsys_ns(Process_WaitForData) #define kwsysProcess_Pipes_e kwsys_ns(Process_Pipes_e) #define kwsysProcess_Pipe_None kwsys_ns(Process_Pipe_None) @@ -140,7 +143,11 @@ kwsysEXPORT void kwsysProcess_SetPipeShared(kwsysProcess* cp, int pipe, int shared); /** - * Get/Set a platform-specific option. Possible options are: + * Get/Set a possibly platform-specific option. Possible options are: + * + * kwsysProcess_Option_Detach = Whether to detach the process. + * 0 = No (default) + * 1 = Yes * * kwsysProcess_Option_HideWindow = Whether to hide window on Windows. * 0 = No (default) @@ -151,7 +158,8 @@ kwsysEXPORT void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value); enum kwsysProcess_Option_e { - kwsysProcess_Option_HideWindow + kwsysProcess_Option_HideWindow, + kwsysProcess_Option_Detach }; /** @@ -164,6 +172,7 @@ enum kwsysProcess_Option_e * kwsysProcess_State_Exited = Child process exited normally. * kwsysProcess_State_Expired = Child process's timeout expired. * kwsysProcess_State_Killed = Child process terminated by Kill method. + * kwsysProcess_State_Disowned = Child is no longer managed by this object. */ kwsysEXPORT int kwsysProcess_GetState(kwsysProcess* cp); enum kwsysProcess_State_e @@ -174,7 +183,8 @@ enum kwsysProcess_State_e kwsysProcess_State_Executing, kwsysProcess_State_Exited, kwsysProcess_State_Expired, - kwsysProcess_State_Killed + kwsysProcess_State_Killed, + kwsysProcess_State_Disowned }; /** @@ -237,6 +247,15 @@ kwsysEXPORT const char* kwsysProcess_GetExceptionString(kwsysProcess* cp); kwsysEXPORT void kwsysProcess_Execute(kwsysProcess* cp); /** + * Stop management of a detached child process. This closes any pipes + * being read. If the child was not created with the + * kwsysProcess_Option_Detach option, this method does nothing. This + * is because disowning a non-detached process will cause the child + * exit signal to be left unhandled until this process exits. + */ +kwsysEXPORT void kwsysProcess_Disown(kwsysProcess* cp); + +/** * Block until data are available on a pipe, a timeout expires, or the * child process terminates. Arguments are as follows: * @@ -318,6 +337,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp); # undef kwsysProcess_SetWorkingDirectory # undef kwsysProcess_SetPipeFile # undef kwsysProcess_SetPipeShared +# undef kwsysProcess_Option_Detach # undef kwsysProcess_Option_HideWindow # undef kwsysProcess_GetOption # undef kwsysProcess_SetOption @@ -329,6 +349,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp); # undef kwsysProcess_State_Exited # undef kwsysProcess_State_Expired # undef kwsysProcess_State_Killed +# undef kwsysProcess_State_Disowned # undef kwsysProcess_GetState # undef kwsysProcess_State_e # undef kwsysProcess_Exception_None @@ -344,6 +365,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp); # undef kwsysProcess_GetErrorString # undef kwsysProcess_GetExceptionString # undef kwsysProcess_Execute +# undef kwsysProcess_Disown # undef kwsysProcess_WaitForData # undef kwsysProcess_Pipes_e # undef kwsysProcess_Pipe_None diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c index d24f515..c91afb2 100644 --- a/Source/kwsys/ProcessUNIX.c +++ b/Source/kwsys/ProcessUNIX.c @@ -99,6 +99,8 @@ static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProc static void kwsysProcessSetExitException(kwsysProcess* cp, int sig); static void kwsysProcessChildErrorExit(int errorPipe); static void kwsysProcessRestoreDefaultSignalHandlers(void); +static pid_t kwsysProcessFork(kwsysProcess* cp, + kwsysProcessCreateInformation* si); static void kwsysProcessKill(pid_t process_id); /*--------------------------------------------------------------------------*/ @@ -127,6 +129,12 @@ struct kwsysProcess_s /* The working directory for the process. */ char* WorkingDirectory; + /* Whether to create the child as a detached process. */ + int OptionDetach; + + /* Whether the child was created as a detached process. */ + int Detached; + /* Time at which the child started. Negative for no timeout. */ kwsysProcessTime StartTime; @@ -217,7 +225,14 @@ void kwsysProcess_Delete(kwsysProcess* cp) /* If the process is executing, wait for it to finish. */ if(cp->State == kwsysProcess_State_Executing) { - kwsysProcess_WaitForExit(cp, 0); + if(cp->Detached) + { + kwsysProcess_Disown(cp); + } + else + { + kwsysProcess_WaitForExit(cp, 0); + } } /* Free memory. */ @@ -445,17 +460,31 @@ void kwsysProcess_SetPipeShared(kwsysProcess* cp, int pipe, int shared) /*--------------------------------------------------------------------------*/ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId) { - (void)cp; - (void)optionId; - return 0; + if(!cp) + { + return 0; + } + + switch(optionId) + { + case kwsysProcess_Option_Detach: return cp->OptionDetach; + default: return 0; + } } /*--------------------------------------------------------------------------*/ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value) { - (void)cp; - (void)optionId; - (void)value; + if(!cp) + { + return; + } + + switch(optionId) + { + case kwsysProcess_Option_Detach: cp->OptionDetach = value; break; + default: break; + } } /*--------------------------------------------------------------------------*/ @@ -673,6 +702,45 @@ void kwsysProcess_Execute(kwsysProcess* cp) /* The process has now started. */ cp->State = kwsysProcess_State_Executing; + cp->Detached = cp->OptionDetach; +} + +/*--------------------------------------------------------------------------*/ +kwsysEXPORT void kwsysProcess_Disown(kwsysProcess* cp) +{ + int i; + + /* Make sure a detached child process is running. */ + if(!cp || !cp->Detached || cp->State != kwsysProcess_State_Executing) + { + return; + } + + /* Close any pipes that are still open. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0) + { + /* If the pipe was reported by the last call to select, we must + read from it. Ignore the data. */ + if(FD_ISSET(cp->PipeReadEnds[i], &cp->PipeSet)) + { + /* 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((read(cp->PipeReadEnds[i], cp->PipeBuffer, + KWSYSPE_PIPE_BUFFER_SIZE) < 0) && (errno == EINTR)); + } + + /* We are done reading from this pipe. */ + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + --cp->PipesLeft; + } + } + + cp->State = kwsysProcess_State_Disowned; } /*--------------------------------------------------------------------------*/ @@ -901,21 +969,22 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) /* Wait for each child to terminate. The process should have already exited because KWSYSPE_PIPE_TERM has been closed by this point. Repeat the call until it is not interrupted. */ - { - int i; - for(i=0; i < cp->NumberOfCommands; ++i) + if(!cp->Detached) { - while(((result = waitpid(cp->ForkPIDs[i], - &cp->CommandExitCodes[i], 0)) < 0) && - (errno == EINTR)); - if(result <= 0 && cp->State != kwsysProcess_State_Error) + int i; + for(i=0; i < cp->NumberOfCommands; ++i) { - /* Unexpected error. Report the first time this happens. */ - strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); - cp->State = kwsysProcess_State_Error; + while(((result = waitpid(cp->ForkPIDs[i], + &cp->CommandExitCodes[i], 0)) < 0) && + (errno == EINTR)); + if(result <= 0 && cp->State != kwsysProcess_State_Error) + { + /* Unexpected error. Report the first time this happens. */ + strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); + cp->State = kwsysProcess_State_Error; + } } } - } /* Check if there was an error in one of the waitpid calls. */ if(cp->State == kwsysProcess_State_Error) @@ -1225,7 +1294,7 @@ static int kwsysProcessCreate(kwsysProcess* cp, int index, } /* Fork off a child process. */ - cp->ForkPIDs[index] = fork(); + cp->ForkPIDs[index] = kwsysProcessFork(cp, si); if(cp->ForkPIDs[index] < 0) { return 0; @@ -1720,6 +1789,61 @@ static void kwsysProcessRestoreDefaultSignalHandlers(void) } /*--------------------------------------------------------------------------*/ +static pid_t kwsysProcessFork(kwsysProcess* cp, + kwsysProcessCreateInformation* si) +{ + /* Create a detached process if requested. */ + if(cp->OptionDetach) + { + /* Create an intermediate process. */ + pid_t middle_pid = fork(); + if(middle_pid < 0) + { + /* Fork failed. Return as if we were not detaching. */ + return middle_pid; + } + else if(middle_pid == 0) + { + /* This is the intermediate process. Create the real child. */ + pid_t child_pid = fork(); + if(child_pid == 0) + { + /* This is the real child process. There is nothing to do here. */ + return 0; + } + else + { + /* Use the error pipe to report the pid to the real parent. */ + while((write(si->ErrorPipe[1], &child_pid, sizeof(child_pid)) < 0) && + (errno == EINTR)); + + /* Exit without cleanup. The parent holds all resources. */ + _exit(0); + } + } + else + { + /* This is the original parent process. The intermediate + process will use the error pipe to report the pid of the + detached child. */ + pid_t child_pid; + int status; + while((read(si->ErrorPipe[0], &child_pid, sizeof(child_pid)) < 0) && + (errno == EINTR)); + + /* Wait for the intermediate process to exit and clean it up. */ + while((waitpid(middle_pid, &status, 0) < 0) && (errno == EINTR)); + return child_pid; + } + } + else + { + /* Not creating a detached process. Use normal fork. */ + return fork(); + } +} + +/*--------------------------------------------------------------------------*/ static void kwsysProcessKill(pid_t process_id) { DIR* procdir; diff --git a/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c index 9e42e05..ad02951 100644 --- a/Source/kwsys/ProcessWin32.c +++ b/Source/kwsys/ProcessWin32.c @@ -1137,6 +1137,13 @@ void kwsysProcess_Execute(kwsysProcess* cp) } /*--------------------------------------------------------------------------*/ +void kwsysProcess_Disown(kwsysProcess* cp) +{ + /* TODO: Implement windows version. */ + (void)cp; +} + +/*--------------------------------------------------------------------------*/ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, double* userTimeout) -- cgit v0.12