/* * Copyright 2019, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Augustin Cavalier */ /* Pass this tool a string, and it will parse it into an argv and execvp(). */ #include #include #include #include static void append_char(char c, char** arg, int* argLen, int* argBufferLen) { if ((*argLen + 1) >= *argBufferLen) { *arg = realloc(*arg, *argBufferLen + 32); if (*arg == NULL) { puts("exec: oom"); exit(1); } *argBufferLen += 32; } (*arg)[*argLen] = c; (*argLen)++; } static void parse_quoted(const char* str, int* pos, char** currentArg, int* currentArgLen, int* currentArgBufferLen) { char end = str[*pos]; while (1) { char c; (*pos)++; c = str[*pos]; if (c == '\0') { puts("exec: mismatched quotes"); exit(1); } if (c == end) break; switch (c) { case '\\': (*pos)++; // fall through default: append_char(str[*pos], currentArg, currentArgLen, currentArgBufferLen); break; } } } int main(int argc, const char* argv[]) { char** args = NULL, *currentArg = NULL; const char* str; int argsLen = 0, argsBufferLen = 0, currentArgLen = 0, currentArgBufferLen = 0, encounteredNewlineAt = -1, modifiedEnvironment = 0, pos; if (argc != 2) { printf("usage: %s \"program arg 'arg1' ...\"\n", argv[0]); return 1; } str = argv[1]; pos = 0; while (1) { switch (str[pos]) { case '\r': case '\n': // In normal shells, this would imply a second command. // We don't support that here, so we need to make sure // that either we have not parsed any arguments yet, // or there are no more arguments pushed after this. if (argsLen == 0 && currentArgLen == 0 && !modifiedEnvironment) break; encounteredNewlineAt = argsLen + 1; // fall through case ' ': case '\t': case '\0': if (currentArgLen == 0) break; // do nothing if (encounteredNewlineAt == argsLen) { puts("exec: running multiple commands not supported!"); return 1; } // the current argument hasn't been terminated, do that now append_char('\0', ¤tArg, ¤tArgLen, ¤tArgBufferLen); // handle environs { char* val; if (argsLen == 0 && (val = strstr(currentArg, "=")) != NULL) { const char* dollar; char* newVal = NULL; *val = '\0'; val++; // handle trivial variable substitution, i.e. VAL=$VAL:... dollar = strstr(val, "$"); if (dollar != NULL) { const char* oldVal; int oldValLen, valLen, nameLen; if (dollar != val) { puts("exec: environ expansion not at start of " "line unsupported"); return 1; } val++; // skip the $ valLen = strlen(val); nameLen = strlen(currentArg); // if the new value does not start with the environ name // (which is broken by a non-alphanumeric character), bail. if (strncmp(val, currentArg, nameLen) != 0 || isalnum(val[nameLen])) { puts("exec: environ expansion of other variables " "unsupported"); return 1; } // get the old value and actually do the expansion oldVal = getenv(currentArg); oldValLen = strlen(oldVal); newVal = malloc(valLen + oldValLen + 1); memcpy(newVal, oldVal, oldValLen); memcpy(newVal + oldValLen, val + nameLen, valLen + 1); val = newVal; } setenv(currentArg, val, 1); free(newVal); modifiedEnvironment = 1; free(currentArg); currentArg = NULL; currentArgLen = 0; currentArgBufferLen = 0; break; } } // actually add the argument to the array if ((argsLen + 2) >= argsBufferLen) { args = realloc(args, (argsBufferLen + 8) * sizeof(char*)); if (args == NULL) { puts("exec: oom"); return 1; } argsBufferLen += 8; } args[argsLen] = currentArg; args[argsLen + 1] = NULL; argsLen++; currentArg = NULL; currentArgLen = 0; currentArgBufferLen = 0; break; case '\'': case '"': parse_quoted(str, &pos, ¤tArg, ¤tArgLen, ¤tArgBufferLen); break; case '\\': pos++; // don't append newlines to the current argument if (str[pos] == '\r' || str[pos] == '\n') break; // fall through default: append_char(str[pos], ¤tArg, ¤tArgLen, ¤tArgBufferLen); break; } if (str[pos] == '\0') break; pos++; } pos = execvp(args[0], args); if (pos != 0) printf("exec failed: %s\n", strerror(errno)); return pos; }