From 45fbde69e2f5eb08e7f7dff9d681db1d82b5d101 Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Wed, 7 Jul 2004 17:27:50 -0400
Subject: [PATCH] ENH: Added kwsysProcess_Disown an kwsysProcess_Option_Detach
 to allow detached processes to be created.  Currently implemented only on
 UNIX.

---
 Process.h.in   |  28 ++++++++-
 ProcessUNIX.c  | 162 +++++++++++++++++++++++++++++++++++++++++++------
 ProcessWin32.c |   7 +++
 3 files changed, 175 insertions(+), 22 deletions(-)

diff --git a/Process.h.in b/Process.h.in
index 829283a1..0d6c4be5 100644
--- a/Process.h.in
+++ b/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
 };
 
 /**
@@ -236,6 +246,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/ProcessUNIX.c b/ProcessUNIX.c
index 72072cf8..7b328e7a 100644
--- a/ProcessUNIX.c
+++ b/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;
@@ -1719,6 +1788,61 @@ static void kwsysProcessRestoreDefaultSignalHandlers(void)
 #endif
 }
 
+/*--------------------------------------------------------------------------*/
+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)
 {
diff --git a/ProcessWin32.c b/ProcessWin32.c
index 1f1a0766..f3e8ca3a 100644
--- a/ProcessWin32.c
+++ b/ProcessWin32.c
@@ -1136,6 +1136,13 @@ void kwsysProcess_Execute(kwsysProcess* cp)
   cp->State = kwsysProcess_State_Executing;
 }
 
+/*--------------------------------------------------------------------------*/
+void kwsysProcess_Disown(kwsysProcess* cp)
+{
+  /* TODO: Implement windows version.  */
+  (void)cp;
+}
+
 /*--------------------------------------------------------------------------*/
 
 int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
-- 
GitLab