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
append_char(char c,char ** arg,int * argLen,int * argBufferLen)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
parse_quoted(const char * str,int * pos,char ** currentArg,int * currentArgLen,int * currentArgBufferLen)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
main(int argc,const char * argv[])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