xref: /aosp_15_r20/external/vboot_reference/host/lib/subprocess.c (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
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