/* Editor Settings: expandtabs and use 4 spaces for indentation * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: * * -*- mode: c, c-basic-offset: 4 -*- */ /* * Copyright Likewise Software 2004-2008 * All rights reserved. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the license, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy * of the GNU Lesser General Public License along with this program. If * not, see . * * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING * TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU * LESSER GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT * license@likewisesoftware.com */ #include "config.h" #include "ctshell.h" #include "ctarray.h" #include #include #define SIGIL '%' #ifndef HAVE__NSGETENVIRON extern char **environ; #endif typedef struct { int fds[2]; CTShellVar* variable; StringBuffer buffer; } Pipe; typedef struct { StringBuffer buffer; LWGHashTable* variables; DynamicArray pipes; unsigned int num_pipes; } Command; struct CTShellVar __CTVarInteger(const char* name, int value) { CTShellVar result; result.type = SVAR_INT; result.name = name; result.value.integer = value; return result; } struct CTShellVar __CTVarString(const char* name, const char* value) { CTShellVar result; result.type = SVAR_STR; result.name = name; result.value.string = value; return result; } struct CTShellVar __CTVarArray(const char* name, char const * const * value) { CTShellVar result; result.type = SVAR_ARR; result.name = name; result.value.array = value; return result; } struct CTShellVar __CTVarOut(const char* name, char** value) { CTShellVar result; result.type = SVAR_OUT; result.name = name; result.value.out = value; return result; } struct CTShellVar __CTVarZero(const char* name) { CTShellVar result; result.type = SVAR_ZERO; result.name = name; return result; } static unsigned int VarLength(const char* format, unsigned int offset) { unsigned int i; for (i = offset+1; format[i]; i++) { if (!isalnum((int) format[i]) && format[i] != '_') break; } return i - offset; } static unsigned int QuoteLength(const char* string, unsigned int index) { unsigned int i; for (i = index+1; string[i]; i++) { if (string[i] == '\\' && string[i+1]) i++; else if (string[i] == '\'') break; } return i - index + (string[i] != 0); } static void free_variable_key(void* _ptr) { char* key = (char*) _ptr - 1; CTFreeString(key); } static CENTERROR CountVariables(const char* format, unsigned int* result) { CENTERROR ceError = CENTERROR_SUCCESS; LWGHashTable* table = NULL; unsigned int i; table = lwg_hash_table_new_full (lwg_str_hash, lwg_str_equal, (LWGDestroyNotify)free_variable_key, NULL); if (!table) BAIL_ON_CENTERIS_ERROR(ceError = CENTERROR_OUT_OF_MEMORY); for (i = 0; format[i]; i++) { switch (format[i]) { case '\\': { break; } case SIGIL: { unsigned int length = VarLength(format, i); char* variable; BAIL_ON_CENTERIS_ERROR (ceError = CTStrndup(format+i, length, &variable)); if (!lwg_hash_table_lookup(table, variable+1)) { lwg_hash_table_insert(table, variable+1, variable+1); } i += length - 1; break; } case '\'': { unsigned int length = QuoteLength(format, i); i += length - 1; break; } } } *result = lwg_hash_table_size(table); error: if (table) lwg_hash_table_destroy(table); return ceError; } static void FreeVariableEntry(lwgpointer key, lwgpointer value, lwgpointer data) { CTFreeMemory(value); } static void DestroyVariableTable(LWGHashTable* table) { lwg_hash_table_foreach(table, FreeVariableEntry, NULL); lwg_hash_table_destroy(table); } static CENTERROR BuildVariableTable(unsigned int size, va_list ap, LWGHashTable** table) { unsigned int i; CENTERROR ceError = CENTERROR_SUCCESS; *table = lwg_hash_table_new(lwg_str_hash, lwg_str_equal); if (!*table) BAIL_ON_CENTERIS_ERROR(ceError = CENTERROR_OUT_OF_MEMORY); for (i = 0; i < size; i++) { CTShellVar *var; BAIL_ON_CENTERIS_ERROR( ceError = CTAllocateMemory(sizeof(CTShellVar), (PVOID*)&var) ); *var = va_arg(ap, CTShellVar); lwg_hash_table_insert(*table, (void*) var->name, var); } error: if (ceError) { if (*table) { DestroyVariableTable(*table); *table = NULL; } } return ceError; } static BOOLEAN NeedsEscape(char c, BOOLEAN inDouble) { if (inDouble) { switch(c) { case '\"': return TRUE; default: return FALSE; } } else { switch (c) { case '\\': case '"': case '$': case '\'': case ' ': case '\t': case '(': case ')': case '&': case ';': case '|': // Solaris treats ^ like | case '^': case '>': case '<': case '{': case '}': return TRUE; default: return FALSE; } } } static CENTERROR AppendStringEscaped(StringBuffer* buffer, const char* str, int length, BOOLEAN inDouble) { CENTERROR ceError = CENTERROR_SUCCESS; unsigned int i; if (inDouble) { BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\"')); } BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\'')); for (i = 0; str[i] && (length == -1 || i < (unsigned int) length); i++) { if (str[i] == '\'') { BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\'')); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\\')); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\'')); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\'')); } else { BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, str[i])); } } BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\'')); if (inDouble) { BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\"')); } error: return ceError; } static void FreePipe(Pipe* pipe) { if (pipe->buffer.data) CTStringBufferDestroy(&pipe->buffer); if (pipe->fds[0] >= 0) close(pipe->fds[0]); if (pipe->fds[1] >= 0) close(pipe->fds[1]); CTFreeMemory(pipe); } static CENTERROR AppendVariable(Command* result, struct CTShellVar* var, BOOLEAN inDouble) { CENTERROR ceError = CENTERROR_SUCCESS; char* str = NULL; char const * const * ptr = NULL; Pipe* ppipe = NULL; int fd; if (!var) BAIL_ON_CENTERIS_ERROR(ceError = CENTERROR_SHELL_VARIABLE_UNKNOWN); switch (var->type) { case SVAR_INT: BAIL_ON_CENTERIS_ERROR(ceError = CTAllocateStringPrintf(&str, "%i", var->value.integer)); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppend(&result->buffer, str)); break; case SVAR_STR: BAIL_ON_CENTERIS_ERROR(ceError = AppendStringEscaped(&result->buffer, var->value.string, -1, inDouble)); break; case SVAR_ARR: for (ptr = var->value.array; *ptr; ptr++) { if (ptr != var->value.array) BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(&result->buffer, ' ')); BAIL_ON_CENTERIS_ERROR(ceError = AppendStringEscaped(&result->buffer, *ptr, -1, inDouble)); } break; case SVAR_OUT: BAIL_ON_CENTERIS_ERROR(ceError = CTAllocateMemory(sizeof(Pipe), (PVOID*)&ppipe)); BAIL_ON_CENTERIS_ERROR(ceError = CTArrayAppend(&result->pipes, sizeof(Pipe*), &ppipe, 1)); ppipe->variable = var; if (pipe(ppipe->fds)) BAIL_ON_CENTERIS_ERROR(ceError = CTMapSystemError(errno)); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferConstruct(&ppipe->buffer)); fd = result->pipes.size - 1 + 3; BAIL_ON_CENTERIS_ERROR(ceError = CTAllocateStringPrintf(&str, "&%i %i>&-", fd, fd)); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppend(&result->buffer, str)); ppipe = NULL; break; case SVAR_ZERO: break; default: break; } error: if (ppipe) FreePipe(ppipe); CT_SAFE_FREE_STRING(str); return ceError; } static CENTERROR CommandConstruct(Command* command) { CENTERROR ceError = CENTERROR_SUCCESS; memset(command, 0, sizeof(Command)); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferConstruct(&command->buffer)); BAIL_ON_CENTERIS_ERROR(ceError = CTArrayConstruct(&command->pipes, sizeof(Pipe*))); error: if (ceError) { if (command->buffer.data) CTStringBufferDestroy(&command->buffer); if (command->pipes.data) CTArrayFree(&command->pipes); } return ceError; } static void CommandDestroy(Command* command) { if (command->buffer.data) { CTStringBufferDestroy(&command->buffer); } if (command->pipes.data) { Pipe** pipes = (Pipe**) command->pipes.data; int i; for (i = 0; i < command->pipes.size; i++) FreePipe(pipes[i]); CTArrayFree(&command->pipes); } if (command->variables) { DestroyVariableTable(command->variables); command->variables = NULL; } } static CENTERROR ConstructShellCommand(const char* format, Command *result) { CENTERROR ceError = CENTERROR_SUCCESS; unsigned int i, length; char* variable = NULL; StringBuffer* buffer = &result->buffer; BOOLEAN inDouble = FALSE; for (i = 0; format[i]; i++) { switch (format[i]) { case '\"': BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, format[i])); inDouble = !inDouble; break; case '\'': length = QuoteLength(format, i); BAIL_ON_CENTERIS_ERROR(ceError = AppendStringEscaped(buffer, format+i+1, length-2, inDouble)); i += length-1; break; case '\\': i++; if (NeedsEscape(format[i], inDouble)) BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\\')); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, format[i])); break; case SIGIL: length = VarLength(format, i); BAIL_ON_CENTERIS_ERROR(ceError = CTStrndup(format+i, length, &variable)); BAIL_ON_CENTERIS_ERROR(ceError = AppendVariable(result, lwg_hash_table_lookup(result->variables, (void*) (variable+1)), inDouble)); i += length-1; CT_SAFE_FREE_STRING(variable); variable = NULL; break; // Characters to pass through unmodified to the shell case '&': case '|': case ';': case '$': case '(': case ')': case '{': case '}': case '>': case '<': case ' ': case '\t': BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, format[i])); break; default: if (NeedsEscape(format[i], inDouble)) BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, '\\')); BAIL_ON_CENTERIS_ERROR(ceError = CTStringBufferAppendChar(buffer, format[i])); break; } } error: CT_SAFE_FREE_STRING(variable); return ceError; } CENTERROR ExecuteShellCommand(char * const envp[], Command* command) { CENTERROR ceError = CENTERROR_SUCCESS; pid_t pid, wpid; pid = fork(); if (pid < 0) { ceError = CTMapSystemError(errno); goto error; } else if (pid == 0) { // Child process Pipe** pipes = (Pipe**) command->pipes.data; unsigned int i; // Close unneeded fds for (i = 0; i < command->pipes.size; i++) { while (close(pipes[i]->fds[0])) { if (errno != EAGAIN) abort(); } } // Remap fds to lowest numbers possible for (i = 0; i < command->pipes.size; i++) { dup2(pipes[i]->fds[1], i+3); close(pipes[i]->fds[1]); } if (envp) execle("/bin/sh", (const char*) "sh", (const char*) "-c", (const char*) command->buffer.data, (const char*) NULL, envp); else execl("/bin/sh", (const char*) "sh", (const char*) "-c", (const char*) command->buffer.data, (const char*) NULL); exit(1); } else { int status = 0; while (1) { // Parent Pipe** pipes = (Pipe**) command->pipes.data; unsigned int i; fd_set readfds, exceptfds; BOOLEAN active = FALSE; int ready, maxfd = -1; FD_ZERO(&readfds); FD_ZERO(&exceptfds); for (i = 0; i < command->pipes.size; i++) { if (pipes[i]->fds[1] >= 0) { while (close(pipes[i]->fds[1])) { if (errno != EAGAIN) BAIL_ON_CENTERIS_ERROR(ceError = CTMapSystemError(errno)); } pipes[i]->fds[1] = -1; } if (pipes[i]->fds[0] >= 0) { if (maxfd < pipes[i]->fds[0]) maxfd = pipes[i]->fds[0]; FD_SET(pipes[i]->fds[0], &readfds); FD_SET(pipes[i]->fds[0], &exceptfds); active = TRUE; } } if (!active) break; ready = select(maxfd+1, &readfds, NULL, &exceptfds, NULL); if (ready < 0 && errno != EINTR) BAIL_ON_CENTERIS_ERROR(ceError = CTMapSystemError(errno)); if (ready > 0) { for (i = 0; i < command->pipes.size; i++) { if (pipes[i]->fds[0] >= 0 && FD_ISSET(pipes[i]->fds[0], &readfds)) { char buffer[1024]; int amount; while ((amount = read(pipes[i]->fds[0], buffer, sizeof(buffer)-1)) < 0) { if (errno != EAGAIN) BAIL_ON_CENTERIS_ERROR(ceError = CTMapSystemError(errno)); } if (!amount) { while (close(pipes[i]->fds[0])) { if (errno != EAGAIN) BAIL_ON_CENTERIS_ERROR(ceError = CTMapSystemError(errno)); } pipes[i]->fds[0] = -1; *(pipes[i]->variable->value.out) = CTStringBufferFreeze(&pipes[i]->buffer); } else { buffer[amount] = 0; BAIL_ON_CENTERIS_ERROR( ceError = CTStringBufferAppend(&pipes[i]->buffer, buffer) ); } } } } } do { wpid = waitpid(pid, &status, 0); } while (wpid != pid && errno == EINTR); if (wpid != pid) BAIL_ON_CENTERIS_ERROR(ceError = CTMapSystemError(errno)); else if (status) BAIL_ON_CENTERIS_ERROR(ceError = CENTERROR_COMMAND_FAILED); } error: return ceError; } CENTERROR CTShellEx(char * const envp[], const char* format, ...) { CENTERROR ceError = CENTERROR_SUCCESS; unsigned int numvars = 0; va_list ap; Command command; va_start(ap, format); BAIL_ON_CENTERIS_ERROR(ceError = CommandConstruct(&command)); BAIL_ON_CENTERIS_ERROR(ceError = CountVariables(format, &numvars)); BAIL_ON_CENTERIS_ERROR(ceError = BuildVariableTable(numvars, ap, &command.variables)); BAIL_ON_CENTERIS_ERROR(ceError = ConstructShellCommand(format, &command)); BAIL_ON_CENTERIS_ERROR(ceError = ExecuteShellCommand(envp, &command)); error: va_end (ap); CommandDestroy(&command); return ceError; } CENTERROR CTShell(const char* format, ...) { CENTERROR ceError = CENTERROR_SUCCESS; unsigned int numvars = 0; va_list ap; Command command; va_start(ap, format); BAIL_ON_CENTERIS_ERROR(ceError = CommandConstruct(&command)); BAIL_ON_CENTERIS_ERROR(ceError = CountVariables(format, &numvars)); BAIL_ON_CENTERIS_ERROR(ceError = BuildVariableTable(numvars, ap, &command.variables)); BAIL_ON_CENTERIS_ERROR(ceError = ConstructShellCommand(format, &command)); BAIL_ON_CENTERIS_ERROR(ceError = ExecuteShellCommand(NULL, &command)); error: va_end (ap); CommandDestroy(&command); return ceError; }