1 /* 2 * Copyright 2019, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Augustin Cavalier <waddlesplash> 7 */ 8 9 /* Pass this tool a string, and it will parse it into an argv and execvp(). */ 10 11 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <errno.h> 15 #include <unistd.h> 16 17 18 static void 19 append_char(char c, char** arg, int* argLen, int* argBufferLen) 20 { 21 if ((*argLen + 1) >= *argBufferLen) { 22 *arg = realloc(*arg, *argBufferLen + 32); 23 if (*arg == NULL) { 24 puts("exec: oom"); 25 exit(1); 26 } 27 *argBufferLen += 32; 28 } 29 30 (*arg)[*argLen] = c; 31 (*argLen)++; 32 } 33 34 35 static void 36 parse_quoted(const char* str, int* pos, char** currentArg, int* currentArgLen, 37 int* currentArgBufferLen) 38 { 39 char end = str[*pos]; 40 while (1) { 41 char c; 42 (*pos)++; 43 c = str[*pos]; 44 if (c == '\0') { 45 puts("exec: mismatched quotes"); 46 exit(1); 47 } 48 if (c == end) 49 break; 50 51 switch (c) { 52 case '\\': 53 (*pos)++; 54 // fall through 55 default: 56 append_char(str[*pos], currentArg, currentArgLen, 57 currentArgBufferLen); 58 break; 59 } 60 } 61 } 62 63 64 int 65 main(int argc, const char* argv[]) 66 { 67 char** args = NULL, *currentArg = NULL; 68 const char* str; 69 int argsLen = 0, argsBufferLen = 0, currentArgLen = 0, 70 currentArgBufferLen = 0, encounteredNewlineAt = -1, 71 modifiedEnvironment = 0, pos; 72 73 if (argc != 2) { 74 printf("usage: %s \"program arg 'arg1' ...\"\n", argv[0]); 75 return 1; 76 } 77 78 str = argv[1]; 79 pos = 0; 80 while (1) { 81 switch (str[pos]) { 82 case '\r': 83 case '\n': 84 // In normal shells, this would imply a second command. 85 // We don't support that here, so we need to make sure 86 // that either we have not parsed any arguments yet, 87 // or there are no more arguments pushed after this. 88 if (argsLen == 0 && currentArgLen == 0 && !modifiedEnvironment) 89 break; 90 encounteredNewlineAt = argsLen + 1; 91 // fall through 92 case ' ': 93 case '\t': 94 case '\0': 95 if (currentArgLen == 0) 96 break; // do nothing 97 if (encounteredNewlineAt == argsLen) { 98 puts("exec: running multiple commands not supported!"); 99 return 1; 100 } 101 102 // the current argument hasn't been terminated, do that now 103 append_char('\0', ¤tArg, ¤tArgLen, 104 ¤tArgBufferLen); 105 106 // handle environs 107 { 108 char* val; 109 if (argsLen == 0 && (val = strstr(currentArg, "=")) != NULL) { 110 const char* dollar; 111 char* newVal = NULL; 112 *val = '\0'; 113 val++; 114 115 // handle trivial variable substitution, i.e. VAL=$VAL:... 116 dollar = strstr(val, "$"); 117 if (dollar != NULL) { 118 const char* oldVal; 119 int oldValLen, valLen, nameLen; 120 121 if (dollar != val) { 122 puts("exec: environ expansion not at start of " 123 "line unsupported"); 124 return 1; 125 } 126 val++; // skip the $ 127 valLen = strlen(val); 128 nameLen = strlen(currentArg); 129 130 // if the new value does not start with the environ name 131 // (which is broken by a non-alphanumeric character), bail. 132 if (strncmp(val, currentArg, nameLen) != 0 133 || isalnum(val[nameLen])) { 134 puts("exec: environ expansion of other variables " 135 "unsupported"); 136 return 1; 137 } 138 139 // get the old value and actually do the expansion 140 oldVal = getenv(currentArg); 141 oldValLen = strlen(oldVal); 142 newVal = malloc(valLen + oldValLen + 1); 143 memcpy(newVal, oldVal, oldValLen); 144 memcpy(newVal + oldValLen, val + nameLen, valLen + 1); 145 val = newVal; 146 } 147 148 setenv(currentArg, val, 1); 149 free(newVal); 150 modifiedEnvironment = 1; 151 152 free(currentArg); 153 currentArg = NULL; 154 currentArgLen = 0; 155 currentArgBufferLen = 0; 156 break; 157 } 158 } 159 160 // actually add the argument to the array 161 if ((argsLen + 2) >= argsBufferLen) { 162 args = realloc(args, (argsBufferLen + 8) * sizeof(char*)); 163 if (args == NULL) { 164 puts("exec: oom"); 165 return 1; 166 } 167 argsBufferLen += 8; 168 } 169 170 args[argsLen] = currentArg; 171 args[argsLen + 1] = NULL; 172 argsLen++; 173 174 currentArg = NULL; 175 currentArgLen = 0; 176 currentArgBufferLen = 0; 177 break; 178 179 case '\'': 180 case '"': 181 parse_quoted(str, &pos, ¤tArg, ¤tArgLen, 182 ¤tArgBufferLen); 183 break; 184 185 case '\\': 186 pos++; 187 // don't append newlines to the current argument 188 if (str[pos] == '\r' || str[pos] == '\n') 189 break; 190 // fall through 191 default: 192 append_char(str[pos], ¤tArg, ¤tArgLen, 193 ¤tArgBufferLen); 194 break; 195 } 196 if (str[pos] == '\0') 197 break; 198 pos++; 199 } 200 201 pos = execvp(args[0], args); 202 if (pos != 0) 203 printf("exec failed: %s\n", strerror(errno)); 204 return pos; 205 } 206