From 6438bec4c9355960f47e4b853f27b548a55ec5e0 Mon Sep 17 00:00:00 2001 From: Brad King Date: Thu, 15 Jun 2006 14:40:42 -0400 Subject: ENH: Added Option_Verbatim to run whole command lines directly. --- Source/kwsys/Process.h.in | 12 +- Source/kwsys/ProcessUNIX.c | 327 +++++++++++++++++++++++++++++++++++---- Source/kwsys/ProcessWin32.c | 361 +++++++++++++++++++++++++------------------- 3 files changed, 517 insertions(+), 183 deletions(-) diff --git a/Source/kwsys/Process.h.in b/Source/kwsys/Process.h.in index 96d3225..3d2db7b 100644 --- a/Source/kwsys/Process.h.in +++ b/Source/kwsys/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/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c index 49b939e..03ecc26 100644 --- a/Source/kwsys/ProcessUNIX.c +++ b/Source/kwsys/ProcessUNIX.c @@ -60,6 +60,7 @@ do. #include /* gettimeofday */ #include /* sigaction */ #include /* DIR, dirent */ +#include /* 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/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c index 26b77f9..406a187 100644 --- a/Source/kwsys/ProcessWin32.c +++ b/Source/kwsys/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; } } @@ -2199,6 +2065,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. */ int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, -- cgit v0.12