From ee3e583aa5d7e30d5e147382f229c47bd5748b6e Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Thu, 15 Jun 2006 14:40:42 -0400
Subject: [PATCH] ENH: Added Option_Verbatim to run whole command lines
 directly.

---
 Process.h.in   |  12 +-
 ProcessUNIX.c  | 327 ++++++++++++++++++++++++++++++++++++++++----
 ProcessWin32.c | 361 ++++++++++++++++++++++++++++---------------------
 3 files changed, 517 insertions(+), 183 deletions(-)

diff --git a/Process.h.in b/Process.h.in
index 4bdd56b..3327e3d 100644
--- a/Process.h.in
+++ b/Process.h.in
@@ -36,6 +36,7 @@
 #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_Option_Verbatim     kwsys_ns(Process_Option_Verbatim)
 #define kwsysProcess_GetOption           kwsys_ns(Process_GetOption)
 #define kwsysProcess_SetOption           kwsys_ns(Process_SetOption)
 #define kwsysProcess_Option_e            kwsys_ns(Process_Option_e)
@@ -154,6 +155,13 @@ kwsysEXPORT void kwsysProcess_SetPipeShared(kwsysProcess* cp, int pipe,
  *  kwsysProcess_Option_HideWindow = Whether to hide window on Windows.
  *         0 = No (default)
  *         1 = Yes
+ *
+ *  kwsysProcess_Option_Verbatim = Whether SetCommand and AddCommand
+ *                                 should treat the first argument
+ *                                 as a verbatim command line
+ *                                 and ignore the rest of the arguments.
+ *         0 = No (default)
+ *         1 = Yes
  */
 kwsysEXPORT int kwsysProcess_GetOption(kwsysProcess* cp, int optionId);
 kwsysEXPORT void kwsysProcess_SetOption(kwsysProcess* cp, int optionId,
@@ -161,7 +169,8 @@ kwsysEXPORT void kwsysProcess_SetOption(kwsysProcess* cp, int optionId,
 enum kwsysProcess_Option_e
 {
   kwsysProcess_Option_HideWindow,
-  kwsysProcess_Option_Detach
+  kwsysProcess_Option_Detach,
+  kwsysProcess_Option_Verbatim
 };
 
 /**
@@ -343,6 +352,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
 # undef kwsysProcess_SetPipeShared
 # undef kwsysProcess_Option_Detach
 # undef kwsysProcess_Option_HideWindow
+# undef kwsysProcess_Option_Verbatim
 # undef kwsysProcess_GetOption
 # undef kwsysProcess_SetOption
 # undef kwsysProcess_Option_e
diff --git a/ProcessUNIX.c b/ProcessUNIX.c
index cfc55e6..49d08fe 100644
--- a/ProcessUNIX.c
+++ b/ProcessUNIX.c
@@ -60,6 +60,7 @@ do.
 #include <time.h>      /* gettimeofday */
 #include <signal.h>    /* sigaction */
 #include <dirent.h>    /* DIR, dirent */
+#include <ctype.h>     /* isspace */
 
 /* 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
@@ -122,6 +123,7 @@ static int kwsysProcessesAdd(kwsysProcess* cp);
 static void kwsysProcessesRemove(kwsysProcess* cp);
 static void kwsysProcessesSignalHandler(int signum, siginfo_t* info,
                                         void* ucontext);
+static char** kwsysProcessParseVerbatimCommand(const char* command);
 
 /*--------------------------------------------------------------------------*/
 /* Structure containing data used to implement the child's execution.  */
@@ -159,6 +161,9 @@ struct kwsysProcess_s
   /* Whether the child was created as a detached process.  */
   int Detached;
 
+  /* Whether to treat command lines as verbatim.  */
+  int Verbatim;
+
   /* Time at which the child started.  Negative for no timeout.  */
   kwsysProcessTime StartTime;
 
@@ -309,7 +314,7 @@ int kwsysProcess_AddCommand(kwsysProcess* cp, char const* const* command)
   char*** newCommands;
 
   /* Make sure we have a command to add.  */
-  if(!cp || !command)
+  if(!cp || !command || !*command)
     {
     return 0;
     }
@@ -332,39 +337,54 @@ int kwsysProcess_AddCommand(kwsysProcess* cp, char const* const* command)
   }
 
   /* Add the new command.  */
-  {
-  char const* const* c = command;
-  int n = 0;
-  int i = 0;
-  while(*c++);
-  n = c - command - 1;
-  newCommands[cp->NumberOfCommands] = (char**)malloc((n+1)*sizeof(char*));
-  if(!newCommands[cp->NumberOfCommands])
+  if(cp->Verbatim)
     {
-    /* Out of memory.  */
-    free(newCommands);
-    return 0;
-    }
-  for(i=0; i < n; ++i)
-    {
-    newCommands[cp->NumberOfCommands][i] = strdup(command[i]);
-    if(!newCommands[cp->NumberOfCommands][i])
+    /* In order to run the given command line verbatim we need to
+       parse it.  */
+    newCommands[cp->NumberOfCommands] =
+      kwsysProcessParseVerbatimCommand(*command);
+    if(!newCommands[cp->NumberOfCommands])
       {
-      break;
+      /* Out of memory.  */
+      free(newCommands);
+      return 0;
       }
     }
-  if(i < n)
+  else
     {
-    /* Out of memory.  */
-    for(;i > 0; --i)
+    /* Copy each argument string individually.  */
+    char const* const* c = command;
+    int n = 0;
+    int i = 0;
+    while(*c++);
+    n = c - command - 1;
+    newCommands[cp->NumberOfCommands] = (char**)malloc((n+1)*sizeof(char*));
+    if(!newCommands[cp->NumberOfCommands])
       {
-      free(newCommands[cp->NumberOfCommands][i-1]);
+      /* Out of memory.  */
+      free(newCommands);
+      return 0;
       }
-    free(newCommands);
-    return 0;
+    for(i=0; i < n; ++i)
+      {
+      newCommands[cp->NumberOfCommands][i] = strdup(command[i]);
+      if(!newCommands[cp->NumberOfCommands][i])
+        {
+        break;
+        }
+      }
+    if(i < n)
+      {
+      /* Out of memory.  */
+      for(;i > 0; --i)
+        {
+        free(newCommands[cp->NumberOfCommands][i-1]);
+        }
+      free(newCommands);
+      return 0;
+      }
+    newCommands[cp->NumberOfCommands][n] = 0;
     }
-  newCommands[cp->NumberOfCommands][n] = 0;
-  }
 
   /* Successfully allocated new command array.  Free the old array. */
   free(cp->Commands);
@@ -492,6 +512,7 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
   switch(optionId)
     {
     case kwsysProcess_Option_Detach: return cp->OptionDetach;
+    case kwsysProcess_Option_Verbatim: return cp->Verbatim;
     default: return 0;
     }
 }
@@ -507,6 +528,7 @@ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value)
   switch(optionId)
     {
     case kwsysProcess_Option_Detach: cp->OptionDetach = value; break;
+    case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
     default: break;
     }
 }
@@ -2256,3 +2278,256 @@ static void kwsysProcessesSignalHandler(int signum, siginfo_t* info,
     write(cp->SignalPipe, &buf, 1);
     }
 }
+
+/*--------------------------------------------------------------------------*/
+static int kwsysProcessAppendByte(char* local,
+                                  char** begin, char** end,
+                                  int* size, char c)
+{
+  /* Allocate space for the character.  */
+  if((*end - *begin) >= *size)
+    {
+    int length = *end - *begin;
+    char* newBuffer = (char*)malloc(*size*2);
+    if(!newBuffer)
+      {
+      return 0;
+      }
+    memcpy(newBuffer, *begin, length*sizeof(char));
+    if(*begin != local)
+      {
+      free(*begin);
+      }
+    *begin = newBuffer;
+    *end = *begin + length;
+    *size *= 2;
+    }
+
+  /* Store the character.  */
+  *(*end)++ = c;
+  return 1;
+}
+
+/*--------------------------------------------------------------------------*/
+static int kwsysProcessAppendArgument(char** local,
+                                      char*** begin, char*** end,
+                                      int* size,
+                                      char* arg_local,
+                                      char** arg_begin, char** arg_end,
+                                      int* arg_size)
+{
+  /* Append a null-terminator to the argument string.  */
+  if(!kwsysProcessAppendByte(arg_local, arg_begin, arg_end, arg_size, '\0'))
+    {
+    return 0;
+    }
+
+  /* Allocate space for the argument pointer.  */
+  if((*end - *begin) >= *size)
+    {
+    int length = *end - *begin;
+    char** newPointers = (char**)malloc(*size*2*sizeof(char*));
+    if(!newPointers)
+      {
+      return 0;
+      }
+    memcpy(newPointers, *begin, length*sizeof(char*));
+    if(*begin != local)
+      {
+      free(*begin);
+      }
+    *begin = newPointers;
+    *end = *begin + length;
+    *size *= 2;
+    }
+
+  /* Allocate space for the argument string.  */
+  **end = (char*)malloc(*arg_end - *arg_begin);
+  if(!**end)
+    {
+    return 0;
+    }
+
+  /* Store the argument in the command array.  */
+  memcpy(**end, *arg_begin, *arg_end - *arg_begin);
+  ++(*end);
+
+  /* Reset the argument to be empty.  */
+  *arg_end = *arg_begin;
+
+  return 1;
+}
+
+/*--------------------------------------------------------------------------*/
+#define KWSYSPE_LOCAL_BYTE_COUNT 1024
+#define KWSYSPE_LOCAL_ARGS_COUNT 32
+static char** kwsysProcessParseVerbatimCommand(const char* command)
+{
+  /* Create a buffer for argument pointers during parsing.  */
+  char* local_pointers[KWSYSPE_LOCAL_ARGS_COUNT];
+  int pointers_size = KWSYSPE_LOCAL_ARGS_COUNT;
+  char** pointer_begin = local_pointers;
+  char** pointer_end = pointer_begin;
+
+  /* Create a buffer for argument strings during parsing.  */
+  char local_buffer[KWSYSPE_LOCAL_BYTE_COUNT];
+  int buffer_size = KWSYSPE_LOCAL_BYTE_COUNT;
+  char* buffer_begin = local_buffer;
+  char* buffer_end = buffer_begin;
+
+  /* Parse the command string.  Try to behave like a UNIX shell.  */
+  char** newCommand = 0;
+  const char* c = command;
+  int in_argument = 0;
+  int in_escape = 0;
+  int in_single = 0;
+  int in_double = 0;
+  int failed = 0;
+  for(;*c; ++c)
+    {
+    if(in_escape)
+      {
+      /* This character is escaped so do no special handling.  */
+      if(!in_argument)
+        {
+        in_argument = 1;
+        }
+      if(!kwsysProcessAppendByte(local_buffer, &buffer_begin,
+                                 &buffer_end, &buffer_size, *c))
+        {
+        failed = 1;
+        break;
+        }
+      in_escape = 0;
+      }
+    else if(*c == '\\' && !in_single)
+      {
+      /* The next character should be escaped.  */
+      in_escape = 1;
+      }
+    else if(*c == '\'' && !in_double)
+      {
+      /* Enter or exit single-quote state.  */
+      if(in_single)
+        {
+        in_single = 0;
+        }
+      else
+        {
+        in_single = 1;
+        if(!in_argument)
+          {
+          in_argument = 1;
+          }
+        }
+      }
+    else if(*c == '"' && !in_single)
+      {
+      /* Enter or exit double-quote state.  */
+      if(in_double)
+        {
+        in_double = 0;
+        }
+      else
+        {
+        in_double = 1;
+        if(!in_argument)
+          {
+          in_argument = 1;
+          }
+        }
+      }
+    else if(isspace(*c))
+      {
+      if(in_argument)
+        {
+        if(in_single || in_double)
+          {
+          /* This space belongs to a quoted argument.  */
+          if(!kwsysProcessAppendByte(local_buffer, &buffer_begin,
+                                     &buffer_end, &buffer_size, *c))
+            {
+            failed = 1;
+            break;
+            }
+          }
+        else
+          {
+          /* This argument has been terminated by whitespace.  */
+          if(!kwsysProcessAppendArgument(local_pointers, &pointer_begin,
+                                         &pointer_end, &pointers_size,
+                                         local_buffer, &buffer_begin,
+                                         &buffer_end, &buffer_size))
+            {
+            failed = 1;
+            break;
+            }
+          in_argument = 0;
+          }
+        }
+      }
+    else
+      {
+      /* This character belong to an argument.  */
+      if(!in_argument)
+        {
+        in_argument = 1;
+        }
+      if(!kwsysProcessAppendByte(local_buffer, &buffer_begin,
+                                 &buffer_end, &buffer_size, *c))
+        {
+        failed = 1;
+        break;
+        }
+      }
+    }
+
+  /* Finish the last argument.  */
+  if(in_argument)
+    {
+    if(!kwsysProcessAppendArgument(local_pointers, &pointer_begin,
+                                   &pointer_end, &pointers_size,
+                                   local_buffer, &buffer_begin,
+                                   &buffer_end, &buffer_size))
+      {
+      failed = 1;
+      }
+    }
+
+  /* If we still have memory allocate space for the new command
+     buffer.  */
+  if(!failed)
+    {
+    int n = pointer_end - pointer_begin;
+    newCommand = (char**)malloc((n+1)*sizeof(char*));
+    }
+
+  if(newCommand)
+    {
+    /* Copy the arguments into the new command buffer.  */
+    int n = pointer_end - pointer_begin;
+    memcpy(newCommand, pointer_begin, sizeof(char*)*n);
+    newCommand[n] = 0;
+    }
+  else
+    {
+    /* Free arguments already allocated.  */
+    while(pointer_end != pointer_begin)
+      {
+      free(*(--pointer_end));
+      }
+    }
+
+  /* Free temporary buffers.  */
+  if(pointer_begin != local_pointers)
+    {
+    free(pointer_begin);
+    }
+  if(buffer_begin != local_buffer)
+    {
+    free(buffer_begin);
+    }
+
+  /* Return the final command buffer.  */
+  return newCommand;
+}
diff --git a/ProcessWin32.c b/ProcessWin32.c
index 76ae419..554f5bf 100644
--- a/ProcessWin32.c
+++ b/ProcessWin32.c
@@ -107,6 +107,11 @@ static void kwsysProcessCleanupHandle(PHANDLE h);
 static void kwsysProcessCleanupHandleSafe(PHANDLE h, DWORD nStdHandle);
 static void kwsysProcessCleanup(kwsysProcess* cp, int error);
 static void kwsysProcessCleanErrorMessage(kwsysProcess* cp);
+static int kwsysProcessComputeCommandLength(kwsysProcess* cp,
+                                            char const* const* command);
+static void kwsysProcessComputeCommandLine(kwsysProcess* cp,
+                                           char const* const* command,
+                                           char* cmd);
 static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout,
                                       kwsysProcessTime* timeoutTime);
 static int kwsysProcessGetTimeoutLeft(kwsysProcessTime* timeoutTime,
@@ -206,6 +211,9 @@ struct kwsysProcess_s
   /* Whether to hide the child process's window.  */
   int HideWindow;
 
+  /* Whether to treat command lines as verbatim.  */
+  int Verbatim;
+
   /* On Win9x platforms, the path to the forwarding executable.  */
   char* Win9x;
 
@@ -646,7 +654,7 @@ int kwsysProcess_AddCommand(kwsysProcess* cp, char const* const* command)
   char** newCommands;
 
   /* Make sure we have a command to add.  */
-  if(!cp || !command)
+  if(!cp || !command || !*command)
     {
     return 0;
     }
@@ -669,77 +677,19 @@ int kwsysProcess_AddCommand(kwsysProcess* cp, char const* const* command)
   }
 
   /* We need to construct a single string representing the command
-       and its arguments.  We will surround each argument containing
-       spaces with double-quotes.  Inside a double-quoted argument, we
-       need to escape double-quotes and all backslashes before them.
-       We also need to escape backslashes at the end of an argument
-       because they come before the closing double-quote for the
-       argument.  */
+     and its arguments.  We will surround each argument containing
+     spaces with double-quotes.  Inside a double-quoted argument, we
+     need to escape double-quotes and all backslashes before them.
+     We also need to escape backslashes at the end of an argument
+     because they come before the closing double-quote for the
+     argument.  */
   {
-  char* cmd;
-  char const* const* arg;
-  int length = 0;
   /* First determine the length of the final string.  */
-  for(arg = command; *arg; ++arg)
-    {
-    /* Keep track of how many backslashes have been encountered in a
-       row in this argument.  */
-    int backslashes = 0;
-    int spaces = 0;
-    const char* c;
-
-    /* Scan the string for spaces.  If there are no spaces, we can
-         pass the argument verbatim.  */
-    for(c=*arg; *c; ++c)
-      {
-      if(*c == ' ' || *c == '\t')
-        {
-        spaces = 1;
-        break;
-        }
-      }
-
-    /* Add the length of the argument, plus 1 for the space
-         separating the arguments.  */
-    length += (int)strlen(*arg) + 1;
-
-    if(spaces)
-      {
-      /* Add 2 for double quotes since spaces are present.  */
-      length += 2;
-
-        /* Scan the string to find characters that need escaping.  */
-      for(c=*arg; *c; ++c)
-        {
-        if(*c == '\\')
-          {
-          /* Found a backslash.  It may need to be escaped later.  */
-          ++backslashes;
-          }
-        else if(*c == '"')
-          {
-          /* Found a double-quote.  We need to escape it and all
-             immediately preceding backslashes.  */
-          length += backslashes + 1;
-          backslashes = 0;
-          }
-        else
-          {
-          /* Found another character.  This eliminates the possibility
-             that any immediately preceding backslashes will be
-             escaped.  */
-          backslashes = 0;
-          }
-        }
-
-      /* We need to escape all ending backslashes. */
-      length += backslashes;
-      }
-    }
+  int length = kwsysProcessComputeCommandLength(cp, command);
 
   /* Allocate enough space for the command.  We do not need an extra
-       byte for the terminating null because we allocated a space for
-       the first argument that we will not use.  */
+     byte for the terminating null because we allocated a space for
+     the first argument that we will not use.  */
   newCommands[cp->NumberOfCommands] = (char*)malloc(length);
   if(!newCommands[cp->NumberOfCommands])
     {
@@ -749,94 +699,8 @@ int kwsysProcess_AddCommand(kwsysProcess* cp, char const* const* command)
     }
 
   /* Construct the command line in the allocated buffer.  */
-  cmd = newCommands[cp->NumberOfCommands];
-  for(arg = command; *arg; ++arg)
-    {
-    /* Keep track of how many backslashes have been encountered in a
-       row in an argument.  */
-    int backslashes = 0;
-    int spaces = 0;
-    const char* c;
-
-    /* Scan the string for spaces.  If there are no spaces, we can
-         pass the argument verbatim.  */
-    for(c=*arg; *c; ++c)
-      {
-      if(*c == ' ' || *c == '\t')
-        {
-        spaces = 1;
-        break;
-        }
-      }
-
-    /* Add the separating space if this is not the first argument.  */
-    if(arg != command)
-      {
-      *cmd++ = ' ';
-      }
-
-    if(spaces)
-      {
-      /* Add the opening double-quote for this argument.  */
-      *cmd++ = '"';
-
-        /* Add the characters of the argument, possibly escaping them.  */
-      for(c=*arg; *c; ++c)
-        {
-        if(*c == '\\')
-          {
-          /* Found a backslash.  It may need to be escaped later.  */
-          ++backslashes;
-          *cmd++ = '\\';
-          }
-        else if(*c == '"')
-          {
-          /* Add enough backslashes to escape any that preceded the
-             double-quote.  */
-          while(backslashes > 0)
-            {
-            --backslashes;
-            *cmd++ = '\\';
-            }
-
-          /* Add the backslash to escape the double-quote.  */
-          *cmd++ = '\\';
-
-          /* Add the double-quote itself.  */
-          *cmd++ = '"';
-          }
-        else
-          {
-          /* We encountered a normal character.  This eliminates any
-             escaping needed for preceding backslashes.  Add the
-             character.  */
-          backslashes = 0;
-          *cmd++ = *c;
-          }
-        }
-
-      /* Add enough backslashes to escape any trailing ones.  */
-      while(backslashes > 0)
-        {
-        --backslashes;
-        *cmd++ = '\\';
-        }
-
-      /* Add the closing double-quote for this argument.  */
-      *cmd++ = '"';
-      }
-    else
-      {
-      /* No spaces.  Add the argument verbatim.  */
-      for(c=*arg; *c; ++c)
-        {
-        *cmd++ = *c;
-        }
-      }
-    }
-
-  /* Add the terminating null character to the command line.  */
-  *cmd = 0;
+  kwsysProcessComputeCommandLine(cp, command,
+                                 newCommands[cp->NumberOfCommands]);
   }
 
   /* Save the new array of commands.  */
@@ -968,6 +832,7 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
     {
     case kwsysProcess_Option_Detach: return cp->OptionDetach;
     case kwsysProcess_Option_HideWindow: return cp->HideWindow;
+    case kwsysProcess_Option_Verbatim: return cp->Verbatim;
     default: return 0;
     }
 }
@@ -984,6 +849,7 @@ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value)
     {
     case kwsysProcess_Option_Detach: cp->OptionDetach = value; break;
     case kwsysProcess_Option_HideWindow: cp->HideWindow = value; break;
+    case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
     default: break;
     }
 }
@@ -2198,6 +2064,189 @@ void kwsysProcessCleanErrorMessage(kwsysProcess* cp)
     }
 }
 
+/*--------------------------------------------------------------------------*/
+int kwsysProcessComputeCommandLength(kwsysProcess* cp,
+                                     char const* const* command)
+{
+  int length = 0;
+  if(cp->Verbatim)
+    {
+    /* Treat the first argument as a verbatim command line.  Use its
+       length directly and add space for the null-terminator.  */
+    length = strlen(*command)+1;
+    }
+  else
+    {
+    /* Compute the length of the command line when it is converted to
+       a single string.  Space for the null-terminator is allocated by
+       the whitespace character allocated for the first argument that
+       will not be used.  */
+    char const* const* arg;
+    for(arg = command; *arg; ++arg)
+      {
+      /* Keep track of how many backslashes have been encountered in a
+         row in this argument.  */
+      int backslashes = 0;
+      int spaces = 0;
+      const char* c;
+
+      /* Scan the string for spaces.  If there are no spaces, we can
+         pass the argument verbatim.  */
+      for(c=*arg; *c; ++c)
+        {
+        if(*c == ' ' || *c == '\t')
+          {
+          spaces = 1;
+          break;
+          }
+        }
+
+      /* Add the length of the argument, plus 1 for the space
+         separating the arguments.  */
+      length += (int)strlen(*arg) + 1;
+
+      if(spaces)
+        {
+        /* Add 2 for double quotes since spaces are present.  */
+        length += 2;
+
+        /* Scan the string to find characters that need escaping.  */
+        for(c=*arg; *c; ++c)
+          {
+          if(*c == '\\')
+            {
+            /* Found a backslash.  It may need to be escaped later.  */
+            ++backslashes;
+            }
+          else if(*c == '"')
+            {
+            /* Found a double-quote.  We need to escape it and all
+               immediately preceding backslashes.  */
+            length += backslashes + 1;
+            backslashes = 0;
+            }
+          else
+            {
+            /* Found another character.  This eliminates the possibility
+               that any immediately preceding backslashes will be
+               escaped.  */
+            backslashes = 0;
+            }
+          }
+
+        /* We need to escape all ending backslashes. */
+        length += backslashes;
+        }
+      }
+    }
+
+  return length;
+}
+
+/*--------------------------------------------------------------------------*/
+void kwsysProcessComputeCommandLine(kwsysProcess* cp,
+                                    char const* const* command,
+                                    char* cmd)
+{
+  if(cp->Verbatim)
+    {
+    /* Copy the verbatim command line into the buffer.  */
+    strcpy(cmd, *command);
+    }
+  else
+    {
+    /* Construct the command line in the allocated buffer.  */
+    char const* const* arg;
+    for(arg = command; *arg; ++arg)
+      {
+      /* Keep track of how many backslashes have been encountered in a
+         row in an argument.  */
+      int backslashes = 0;
+      int spaces = 0;
+      const char* c;
+
+      /* Scan the string for spaces.  If there are no spaces, we can
+         pass the argument verbatim.  */
+      for(c=*arg; *c; ++c)
+        {
+        if(*c == ' ' || *c == '\t')
+          {
+          spaces = 1;
+          break;
+          }
+        }
+
+      /* Add the separating space if this is not the first argument.  */
+      if(arg != command)
+        {
+        *cmd++ = ' ';
+        }
+
+      if(spaces)
+        {
+        /* Add the opening double-quote for this argument.  */
+        *cmd++ = '"';
+
+        /* Add the characters of the argument, possibly escaping them.  */
+        for(c=*arg; *c; ++c)
+          {
+          if(*c == '\\')
+            {
+            /* Found a backslash.  It may need to be escaped later.  */
+            ++backslashes;
+            *cmd++ = '\\';
+            }
+          else if(*c == '"')
+            {
+            /* Add enough backslashes to escape any that preceded the
+               double-quote.  */
+            while(backslashes > 0)
+              {
+              --backslashes;
+              *cmd++ = '\\';
+              }
+
+            /* Add the backslash to escape the double-quote.  */
+            *cmd++ = '\\';
+
+            /* Add the double-quote itself.  */
+            *cmd++ = '"';
+            }
+          else
+            {
+            /* We encountered a normal character.  This eliminates any
+               escaping needed for preceding backslashes.  Add the
+               character.  */
+            backslashes = 0;
+            *cmd++ = *c;
+            }
+          }
+
+        /* Add enough backslashes to escape any trailing ones.  */
+        while(backslashes > 0)
+          {
+          --backslashes;
+          *cmd++ = '\\';
+          }
+
+        /* Add the closing double-quote for this argument.  */
+        *cmd++ = '"';
+        }
+      else
+        {
+        /* No spaces.  Add the argument verbatim.  */
+        for(c=*arg; *c; ++c)
+          {
+          *cmd++ = *c;
+          }
+        }
+      }
+
+    /* Add the terminating null character to the command line.  */
+    *cmd = 0;
+    }
+}
+
 /*--------------------------------------------------------------------------*/
 /* Get the time at which either the process or user timeout will
    expire.  Returns 1 if the user timeout is first, and 0 otherwise.  */
-- 
GitLab