xref: /haiku/src/tools/exec.c (revision 1a3518cf757c2da8006753f83962da5935bbc82b)
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', &currentArg, &currentArgLen,
104 				&currentArgBufferLen);
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, &currentArg, &currentArgLen,
182 				&currentArgBufferLen);
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], &currentArg, &currentArgLen,
193 				&currentArgBufferLen);
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