1 /* Copyright 2019 The ChromiumOS Authors
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <stdbool.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/wait.h>
12 #include <unistd.h>
13
14 #include "2common.h"
15 #include "subprocess.h"
16
17 #define MAX_CB_BUF_SIZE 2048
18
init_target_private(struct subprocess_target * target)19 static int init_target_private(struct subprocess_target *target)
20 {
21 switch (target->type) {
22 case TARGET_BUFFER:
23 case TARGET_BUFFER_NULL_TERMINATED:
24 case TARGET_CALLBACK:
25 return pipe(target->priv.pipefd);
26 default:
27 return 0;
28 }
29 }
30
flags_for_fd(int fd)31 static int flags_for_fd(int fd)
32 {
33 switch (fd) {
34 case STDIN_FILENO:
35 return O_RDONLY;
36 case STDOUT_FILENO:
37 case STDERR_FILENO:
38 return O_WRONLY;
39 default:
40 return -1;
41 }
42 }
43
connect_process_target(struct subprocess_target * target,int fd)44 static int connect_process_target(struct subprocess_target *target, int fd)
45 {
46 int target_fd;
47
48 switch (target->type) {
49 case TARGET_NULL:
50 target_fd = open("/dev/null", flags_for_fd(fd));
51 break;
52 case TARGET_FD:
53 target_fd = target->fd;
54 break;
55 case TARGET_FILE:
56 target_fd = fileno(target->file);
57 break;
58 case TARGET_BUFFER:
59 case TARGET_BUFFER_NULL_TERMINATED:
60 case TARGET_CALLBACK:
61 switch (fd) {
62 case STDIN_FILENO:
63 target_fd = target->priv.pipefd[0];
64 close(target->priv.pipefd[1]);
65 break;
66 case STDOUT_FILENO:
67 case STDERR_FILENO:
68 target_fd = target->priv.pipefd[1];
69 close(target->priv.pipefd[0]);
70 break;
71 default:
72 return -1;
73 }
74 break;
75 default:
76 return -1;
77 }
78
79 return dup2(target_fd, fd);
80 }
81
process_target_input_buffer(struct subprocess_target * target)82 static int process_target_input_buffer(struct subprocess_target *target)
83 {
84 ssize_t write_rv;
85 size_t bytes_to_write;
86 char *buf;
87
88 switch (target->type) {
89 case TARGET_BUFFER:
90 bytes_to_write = target->buffer.size;
91 break;
92 case TARGET_BUFFER_NULL_TERMINATED:
93 bytes_to_write = strlen(target->buffer.buf);
94 break;
95 default:
96 return -1;
97 }
98
99 buf = target->buffer.buf;
100 while (bytes_to_write) {
101 write_rv = write(target->priv.pipefd[1], buf, bytes_to_write);
102 if (write_rv <= 0)
103 return -1;
104 buf += write_rv;
105 bytes_to_write -= write_rv;
106 }
107
108 return 0;
109 }
110
process_target_input_cb(struct subprocess_target * target)111 static int process_target_input_cb(struct subprocess_target *target)
112 {
113 ssize_t write_rv, bytes_to_write;
114 char buf[MAX_CB_BUF_SIZE];
115 char *bufptr;
116
117 for (;;) {
118 bytes_to_write = target->callback.cb(buf, MAX_CB_BUF_SIZE,
119 target->callback.data);
120 if (bytes_to_write < 0 || bytes_to_write > MAX_CB_BUF_SIZE)
121 return -1;
122 if (bytes_to_write == 0)
123 return 0;
124
125 bufptr = buf;
126 while (bytes_to_write) {
127 write_rv = write(target->priv.pipefd[1], bufptr,
128 bytes_to_write);
129 if (write_rv <= 0)
130 return -1;
131 bufptr += write_rv;
132 bytes_to_write -= write_rv;
133 }
134 }
135 }
136
process_target_input(struct subprocess_target * target)137 static int process_target_input(struct subprocess_target *target)
138 {
139 int rv;
140
141 switch (target->type) {
142 case TARGET_BUFFER:
143 case TARGET_BUFFER_NULL_TERMINATED:
144 case TARGET_CALLBACK:
145 break;
146 default:
147 return 0;
148 }
149
150 close(target->priv.pipefd[0]);
151 switch (target->type) {
152 case TARGET_BUFFER:
153 case TARGET_BUFFER_NULL_TERMINATED:
154 rv = process_target_input_buffer(target);
155 break;
156 case TARGET_CALLBACK:
157 rv = process_target_input_cb(target);
158 break;
159 default:
160 return -1;
161 }
162
163 close(target->priv.pipefd[1]);
164 return rv;
165 }
166
process_target_output_buffer(struct subprocess_target * target)167 static int process_target_output_buffer(struct subprocess_target *target)
168 {
169 ssize_t read_rv;
170 size_t bytes_remaining;
171
172 switch (target->type) {
173 case TARGET_BUFFER:
174 bytes_remaining = target->buffer.size;
175 break;
176 case TARGET_BUFFER_NULL_TERMINATED:
177 if (target->buffer.size == 0)
178 return -1;
179 bytes_remaining = target->buffer.size - 1;
180 break;
181 default:
182 return 0;
183 }
184
185 target->buffer.bytes_consumed = 0;
186 while (bytes_remaining) {
187 read_rv = read(
188 target->priv.pipefd[0],
189 target->buffer.buf + target->buffer.bytes_consumed,
190 bytes_remaining);
191 if (read_rv < 0)
192 return -1;
193 if (read_rv == 0)
194 break;
195 target->buffer.bytes_consumed += read_rv;
196 bytes_remaining -= read_rv;
197 }
198
199 if (target->type == TARGET_BUFFER_NULL_TERMINATED)
200 target->buffer.buf[target->buffer.bytes_consumed] = '\0';
201 return 0;
202 }
203
process_target_output_cb(struct subprocess_target * target)204 static int process_target_output_cb(struct subprocess_target *target)
205 {
206 char buf[MAX_CB_BUF_SIZE];
207 ssize_t rv;
208
209 for (;;) {
210 rv = read(target->priv.pipefd[0], buf, MAX_CB_BUF_SIZE);
211 if (rv < 0)
212 return -1;
213 if (rv == 0)
214 break;
215 if (target->callback.cb(buf, rv, target->callback.data) < 0)
216 return -1;
217 }
218
219 return 0;
220 }
221
process_target_output(struct subprocess_target * target)222 static int process_target_output(struct subprocess_target *target)
223 {
224 int rv;
225
226 switch (target->type) {
227 case TARGET_BUFFER:
228 case TARGET_BUFFER_NULL_TERMINATED:
229 case TARGET_CALLBACK:
230 break;
231 default:
232 return 0;
233 }
234
235 close(target->priv.pipefd[1]);
236 switch (target->type) {
237 case TARGET_BUFFER:
238 case TARGET_BUFFER_NULL_TERMINATED:
239 rv = process_target_output_buffer(target);
240 break;
241 case TARGET_CALLBACK:
242 rv = process_target_output_cb(target);
243 break;
244 default:
245 return -1;
246 }
247
248 close(target->priv.pipefd[0]);
249 return rv;
250 }
251
contains_spaces(const char * s)252 static bool contains_spaces(const char *s)
253 {
254 for (size_t i = 0; s[i]; i++) {
255 if (isspace(s[i]))
256 return true;
257 }
258 return false;
259 }
260
subprocess_log_call(const char * const argv[])261 static void subprocess_log_call(const char *const argv[])
262 {
263 VB2_DEBUG("Run:");
264
265 for (size_t i = 0; argv[i]; i++) {
266 if (contains_spaces(argv[i]))
267 VB2_DEBUG_RAW(" '%s'", argv[i]);
268 else
269 VB2_DEBUG_RAW(" %s", argv[i]);
270 }
271 VB2_DEBUG_RAW("\n");
272 }
273
274 struct subprocess_target subprocess_null = {
275 .type = TARGET_NULL,
276 };
277
278 struct subprocess_target subprocess_stdin = {
279 .type = TARGET_FD,
280 .fd = STDIN_FILENO,
281 };
282
283 struct subprocess_target subprocess_stdout = {
284 .type = TARGET_FD,
285 .fd = STDOUT_FILENO,
286 };
287
288 struct subprocess_target subprocess_stderr = {
289 .type = TARGET_FD,
290 .fd = STDERR_FILENO,
291 };
292
293 test_mockable
subprocess_run(const char * const argv[],struct subprocess_target * input,struct subprocess_target * output,struct subprocess_target * error)294 int subprocess_run(const char *const argv[],
295 struct subprocess_target *input,
296 struct subprocess_target *output,
297 struct subprocess_target *error)
298 {
299 int status;
300 pid_t pid = -1;
301
302 subprocess_log_call(argv);
303
304 if (!input)
305 input = &subprocess_stdin;
306 if (!output)
307 output = &subprocess_stdout;
308 if (!error)
309 error = &subprocess_stderr;
310
311 if (init_target_private(input) < 0)
312 goto fail;
313 if (init_target_private(output) < 0)
314 goto fail;
315 if (init_target_private(error) < 0)
316 goto fail;
317
318 if ((pid = fork()) < 0)
319 goto fail;
320 if (pid == 0) {
321 /* Child process */
322 if (connect_process_target(input, STDIN_FILENO) < 0)
323 goto fail;
324 if (connect_process_target(output, STDOUT_FILENO) < 0)
325 goto fail;
326 if (connect_process_target(error, STDERR_FILENO) < 0)
327 goto fail;
328 execvp(*argv, (char *const *)argv);
329 goto fail;
330 }
331
332 /* Parent process */
333 if (process_target_input(input) < 0)
334 goto fail;
335 if (process_target_output(output) < 0)
336 goto fail;
337 if (process_target_output(error) < 0)
338 goto fail;
339
340 if (waitpid(pid, &status, 0) < 0)
341 goto fail;
342
343 if (WIFEXITED(status))
344 return WEXITSTATUS(status);
345
346 fail:
347 VB2_DEBUG("Failed to execute external command: %s\n", strerror(errno));
348 if (pid == 0)
349 exit(127);
350 return -1;
351 }
352