1 /****************************************************************************
2  * bfs                                                                      *
3  * Copyright (C) 2017-2022 Tavian Barnes <tavianator@tavianator.com>        *
4  *                                                                          *
5  * Permission to use, copy, modify, and/or distribute this software for any *
6  * purpose with or without fee is hereby granted.                           *
7  *                                                                          *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES *
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF         *
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR  *
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES   *
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN    *
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF  *
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.           *
15  ****************************************************************************/
16 
17 #include "exec.h"
18 #include "bftw.h"
19 #include "ctx.h"
20 #include "color.h"
21 #include "diag.h"
22 #include "dstring.h"
23 #include "spawn.h"
24 #include "util.h"
25 #include <assert.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <stdarg.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/resource.h>
34 #include <sys/wait.h>
35 #include <unistd.h>
36 
37 /** Print some debugging info. */
38 BFS_FORMATTER(2, 3)
bfs_exec_debug(const struct bfs_exec * execbuf,const char * format,...)39 static void bfs_exec_debug(const struct bfs_exec *execbuf, const char *format, ...) {
40 	const struct bfs_ctx *ctx = execbuf->ctx;
41 
42 	if (!bfs_debug(ctx, DEBUG_EXEC, "${blu}")) {
43 		return;
44 	}
45 
46 	if (execbuf->flags & BFS_EXEC_CONFIRM) {
47 		fputs("-ok", stderr);
48 	} else {
49 		fputs("-exec", stderr);
50 	}
51 	if (execbuf->flags & BFS_EXEC_CHDIR) {
52 		fputs("dir", stderr);
53 	}
54 	cfprintf(ctx->cerr, "${rs}: ");
55 
56 	va_list args;
57 	va_start(args, format);
58 	vfprintf(stderr, format, args);
59 	va_end(args);
60 }
61 
62 /** Determine the size of a single argument, for comparison to arg_max. */
bfs_exec_arg_size(const char * arg)63 static size_t bfs_exec_arg_size(const char *arg) {
64 	return sizeof(arg) + strlen(arg) + 1;
65 }
66 
67 /** Even if we can pass a bigger argument list, cap it here. */
68 #define BFS_EXEC_ARG_MAX (16 << 20)
69 
70 /** Determine the maximum argv size. */
bfs_exec_arg_max(const struct bfs_exec * execbuf)71 static size_t bfs_exec_arg_max(const struct bfs_exec *execbuf) {
72 	long arg_max = sysconf(_SC_ARG_MAX);
73 	bfs_exec_debug(execbuf, "ARG_MAX: %ld according to sysconf()\n", arg_max);
74 	if (arg_max < 0) {
75 		arg_max = BFS_EXEC_ARG_MAX;
76 		bfs_exec_debug(execbuf, "ARG_MAX: %ld assumed\n", arg_max);
77 	}
78 
79 	// We have to share space with the environment variables
80 	extern char **environ;
81 	for (char **envp = environ; *envp; ++envp) {
82 		arg_max -= bfs_exec_arg_size(*envp);
83 	}
84 	// Account for the terminating NULL entry
85 	arg_max -= sizeof(char *);
86 	bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after environment variables\n", arg_max);
87 
88 	// Account for the fixed arguments
89 	for (size_t i = 0; i < execbuf->tmpl_argc - 1; ++i) {
90 		arg_max -= bfs_exec_arg_size(execbuf->tmpl_argv[i]);
91 	}
92 	// Account for the terminating NULL entry
93 	arg_max -= sizeof(char *);
94 	bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after fixed arguments\n", arg_max);
95 
96 	// Assume arguments are counted with the granularity of a single page,
97 	// so allow a one page cushion to account for rounding up
98 	long page_size = sysconf(_SC_PAGESIZE);
99 	if (page_size < 4096) {
100 		page_size = 4096;
101 	}
102 	arg_max -= page_size;
103 	bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after page cushion\n", arg_max);
104 
105 	// POSIX recommends an additional 2048 bytes of headroom
106 	arg_max -= 2048;
107 	bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after headroom\n", arg_max);
108 
109 	if (arg_max < 0) {
110 		arg_max = 0;
111 	} else if (arg_max > BFS_EXEC_ARG_MAX) {
112 		arg_max = BFS_EXEC_ARG_MAX;
113 	}
114 
115 	bfs_exec_debug(execbuf, "ARG_MAX: %ld final value\n", arg_max);
116 	return arg_max;
117 }
118 
bfs_exec_parse(const struct bfs_ctx * ctx,char ** argv,enum bfs_exec_flags flags)119 struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs_exec_flags flags) {
120 	struct bfs_exec *execbuf = malloc(sizeof(*execbuf));
121 	if (!execbuf) {
122 		bfs_perror(ctx, "malloc()");
123 		goto fail;
124 	}
125 
126 	execbuf->flags = flags;
127 	execbuf->ctx = ctx;
128 	execbuf->argv = NULL;
129 	execbuf->argc = 0;
130 	execbuf->argv_cap = 0;
131 	execbuf->arg_size = 0;
132 	execbuf->arg_max = 0;
133 	execbuf->arg_min = 0;
134 	execbuf->wd_fd = -1;
135 	execbuf->wd_path = NULL;
136 	execbuf->wd_len = 0;
137 	execbuf->ret = 0;
138 
139 	size_t i;
140 	for (i = 1; ; ++i) {
141 		const char *arg = argv[i];
142 		if (!arg) {
143 			if (execbuf->flags & BFS_EXEC_CONFIRM) {
144 				bfs_error(ctx, "%s: Expected '... ;'.\n", argv[0]);
145 			} else {
146 				bfs_error(ctx, "%s: Expected '... ;' or '... {} +'.\n", argv[0]);
147 			}
148 			goto fail;
149 		} else if (strcmp(arg, ";") == 0) {
150 			break;
151 		} else if (strcmp(arg, "+") == 0) {
152 			if (!(execbuf->flags & BFS_EXEC_CONFIRM) && strcmp(argv[i - 1], "{}") == 0) {
153 				execbuf->flags |= BFS_EXEC_MULTI;
154 				break;
155 			}
156 		}
157 	}
158 
159 	execbuf->tmpl_argv = argv + 1;
160 	execbuf->tmpl_argc = i - 1;
161 
162 	if (execbuf->tmpl_argc == 0) {
163 		bfs_error(ctx, "%s: Missing command.\n", argv[0]);
164 		goto fail;
165 	}
166 
167 	execbuf->argv_cap = execbuf->tmpl_argc + 1;
168 	execbuf->argv = malloc(execbuf->argv_cap*sizeof(*execbuf->argv));
169 	if (!execbuf->argv) {
170 		bfs_perror(ctx, "malloc()");
171 		goto fail;
172 	}
173 
174 	if (execbuf->flags & BFS_EXEC_MULTI) {
175 		for (i = 0; i < execbuf->tmpl_argc - 1; ++i) {
176 			char *arg = execbuf->tmpl_argv[i];
177 			if (strstr(arg, "{}")) {
178 				bfs_error(ctx, "%s ... +: Only one '{}' is supported.\n", argv[0]);
179 				goto fail;
180 			}
181 			execbuf->argv[i] = arg;
182 		}
183 		execbuf->argc = execbuf->tmpl_argc - 1;
184 
185 		execbuf->arg_max = bfs_exec_arg_max(execbuf);
186 		execbuf->arg_min = execbuf->arg_max;
187 	}
188 
189 	return execbuf;
190 
191 fail:
192 	bfs_exec_free(execbuf);
193 	return NULL;
194 }
195 
196 /** Format the current path for use as a command line argument. */
bfs_exec_format_path(const struct bfs_exec * execbuf,const struct BFTW * ftwbuf)197 static char *bfs_exec_format_path(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
198 	if (!(execbuf->flags & BFS_EXEC_CHDIR)) {
199 		return strdup(ftwbuf->path);
200 	}
201 
202 	const char *name = ftwbuf->path + ftwbuf->nameoff;
203 
204 	if (name[0] == '/') {
205 		// Must be a root path ("/", "//", etc.)
206 		return strdup(name);
207 	}
208 
209 	// For compatibility with GNU find, use './name' instead of just 'name'
210 	char *path = malloc(2 + strlen(name) + 1);
211 	if (!path) {
212 		return NULL;
213 	}
214 
215 	strcpy(path, "./");
216 	strcpy(path + 2, name);
217 
218 	return path;
219 }
220 
221 /** Format an argument, expanding "{}" to the current path. */
bfs_exec_format_arg(char * arg,const char * path)222 static char *bfs_exec_format_arg(char *arg, const char *path) {
223 	char *match = strstr(arg, "{}");
224 	if (!match) {
225 		return arg;
226 	}
227 
228 	char *ret = dstralloc(0);
229 	if (!ret) {
230 		return NULL;
231 	}
232 
233 	char *last = arg;
234 	do {
235 		if (dstrncat(&ret, last, match - last) != 0) {
236 			goto err;
237 		}
238 		if (dstrcat(&ret, path) != 0) {
239 			goto err;
240 		}
241 
242 		last = match + 2;
243 		match = strstr(last, "{}");
244 	} while (match);
245 
246 	if (dstrcat(&ret, last) != 0) {
247 		goto err;
248 	}
249 
250 	return ret;
251 
252 err:
253 	dstrfree(ret);
254 	return NULL;
255 }
256 
257 /** Free a formatted argument. */
bfs_exec_free_arg(char * arg,const char * tmpl)258 static void bfs_exec_free_arg(char *arg, const char *tmpl) {
259 	if (arg != tmpl) {
260 		dstrfree(arg);
261 	}
262 }
263 
264 /** Open a file to use as the working directory. */
bfs_exec_openwd(struct bfs_exec * execbuf,const struct BFTW * ftwbuf)265 static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
266 	assert(execbuf->wd_fd < 0);
267 	assert(!execbuf->wd_path);
268 
269 	if (ftwbuf->at_fd != AT_FDCWD) {
270 		// Rely on at_fd being the immediate parent
271 		assert(ftwbuf->at_path == xbasename(ftwbuf->at_path));
272 
273 		execbuf->wd_fd = ftwbuf->at_fd;
274 		if (!(execbuf->flags & BFS_EXEC_MULTI)) {
275 			return 0;
276 		}
277 
278 		execbuf->wd_fd = dup_cloexec(execbuf->wd_fd);
279 		if (execbuf->wd_fd < 0) {
280 			return -1;
281 		}
282 	}
283 
284 	execbuf->wd_len = ftwbuf->nameoff;
285 	if (execbuf->wd_len == 0) {
286 		if (ftwbuf->path[0] == '/') {
287 			++execbuf->wd_len;
288 		} else {
289 			// The path is something like "foo", so we're already in the right directory
290 			return 0;
291 		}
292 	}
293 
294 	execbuf->wd_path = strndup(ftwbuf->path, execbuf->wd_len);
295 	if (!execbuf->wd_path) {
296 		return -1;
297 	}
298 
299 	if (execbuf->wd_fd < 0) {
300 		execbuf->wd_fd = open(execbuf->wd_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
301 	}
302 
303 	if (execbuf->wd_fd < 0) {
304 		return -1;
305 	}
306 
307 	return 0;
308 }
309 
310 /** Close the working directory. */
bfs_exec_closewd(struct bfs_exec * execbuf,const struct BFTW * ftwbuf)311 static void bfs_exec_closewd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
312 	if (execbuf->wd_fd >= 0) {
313 		if (!ftwbuf || execbuf->wd_fd != ftwbuf->at_fd) {
314 			xclose(execbuf->wd_fd);
315 		}
316 		execbuf->wd_fd = -1;
317 	}
318 
319 	if (execbuf->wd_path) {
320 		free(execbuf->wd_path);
321 		execbuf->wd_path = NULL;
322 		execbuf->wd_len = 0;
323 	}
324 }
325 
326 /** Actually spawn the process. */
bfs_exec_spawn(const struct bfs_exec * execbuf)327 static int bfs_exec_spawn(const struct bfs_exec *execbuf) {
328 	if (execbuf->flags & BFS_EXEC_CONFIRM) {
329 		for (size_t i = 0; i < execbuf->argc; ++i) {
330 			fprintf(stderr, "%s ", execbuf->argv[i]);
331 		}
332 		fprintf(stderr, "? ");
333 
334 		if (ynprompt() <= 0) {
335 			errno = 0;
336 			return -1;
337 		}
338 	}
339 
340 	if (execbuf->flags & BFS_EXEC_MULTI) {
341 		bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments] (size %zu)\n",
342 		               execbuf->argv[0], execbuf->argc - 1, execbuf->arg_size);
343 	} else {
344 		bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments]\n", execbuf->argv[0], execbuf->argc - 1);
345 	}
346 
347 	pid_t pid = -1;
348 	int error;
349 
350 	struct bfs_spawn ctx;
351 	if (bfs_spawn_init(&ctx) != 0) {
352 		return -1;
353 	}
354 
355 	if (bfs_spawn_setflags(&ctx, BFS_SPAWN_USEPATH) != 0) {
356 		goto fail;
357 	}
358 
359 	// Reset RLIMIT_NOFILE, to avoid breaking applications that use select()
360 	struct rlimit rl = {
361 		.rlim_cur = execbuf->ctx->nofile_soft,
362 		.rlim_max = execbuf->ctx->nofile_hard,
363 	};
364 	if (bfs_spawn_addsetrlimit(&ctx, RLIMIT_NOFILE, &rl) != 0) {
365 		goto fail;
366 	}
367 
368 	if (execbuf->wd_fd >= 0) {
369 		if (bfs_spawn_addfchdir(&ctx, execbuf->wd_fd) != 0) {
370 			goto fail;
371 		}
372 	}
373 
374 	pid = bfs_spawn(execbuf->argv[0], &ctx, execbuf->argv, NULL);
375 fail:
376 	error = errno;
377 	bfs_spawn_destroy(&ctx);
378 	if (pid < 0) {
379 		errno = error;
380 		return -1;
381 	}
382 
383 	int wstatus;
384 	if (waitpid(pid, &wstatus, 0) < 0) {
385 		return -1;
386 	}
387 
388 	int ret = -1;
389 
390 	if (WIFEXITED(wstatus)) {
391 		int status = WEXITSTATUS(wstatus);
392 		if (status == EXIT_SUCCESS) {
393 			ret = 0;
394 		} else {
395 			bfs_exec_debug(execbuf, "Command '%s' failed with status %d\n", execbuf->argv[0], status);
396 		}
397 	} else if (WIFSIGNALED(wstatus)) {
398 		int sig = WTERMSIG(wstatus);
399 		const char *str = strsignal(sig);
400 		if (!str) {
401 			str = "unknown";
402 		}
403 		bfs_warning(execbuf->ctx, "Command '${ex}%s${rs}' terminated by signal %d (%s)\n", execbuf->argv[0], sig, str);
404 	} else {
405 		bfs_warning(execbuf->ctx, "Command '${ex}%s${rs}' terminated abnormally\n", execbuf->argv[0]);
406 	}
407 
408 	errno = 0;
409 	return ret;
410 }
411 
412 /** exec() a command for a single file. */
bfs_exec_single(struct bfs_exec * execbuf,const struct BFTW * ftwbuf)413 static int bfs_exec_single(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
414 	int ret = -1, error = 0;
415 
416 	char *path = bfs_exec_format_path(execbuf, ftwbuf);
417 	if (!path) {
418 		goto out;
419 	}
420 
421 	size_t i;
422 	for (i = 0; i < execbuf->tmpl_argc; ++i) {
423 		execbuf->argv[i] = bfs_exec_format_arg(execbuf->tmpl_argv[i], path);
424 		if (!execbuf->argv[i]) {
425 			goto out_free;
426 		}
427 	}
428 	execbuf->argv[i] = NULL;
429 	execbuf->argc = i;
430 
431 	if (execbuf->flags & BFS_EXEC_CHDIR) {
432 		if (bfs_exec_openwd(execbuf, ftwbuf) != 0) {
433 			goto out_free;
434 		}
435 	}
436 
437 	ret = bfs_exec_spawn(execbuf);
438 
439 out_free:
440 	error = errno;
441 
442 	bfs_exec_closewd(execbuf, ftwbuf);
443 
444 	for (size_t j = 0; j < i; ++j) {
445 		bfs_exec_free_arg(execbuf->argv[j], execbuf->tmpl_argv[j]);
446 	}
447 
448 	free(path);
449 
450 	errno = error;
451 
452 out:
453 	return ret;
454 }
455 
456 /** Check if any arguments remain in the buffer. */
bfs_exec_args_remain(const struct bfs_exec * execbuf)457 static bool bfs_exec_args_remain(const struct bfs_exec *execbuf) {
458 	return execbuf->argc >= execbuf->tmpl_argc;
459 }
460 
461 /** Compute the current ARG_MAX estimate for binary search. */
bfs_exec_estimate_max(const struct bfs_exec * execbuf)462 static size_t bfs_exec_estimate_max(const struct bfs_exec *execbuf) {
463 	size_t min = execbuf->arg_min;
464 	size_t max = execbuf->arg_max;
465 	return min + (max - min)/2;
466 }
467 
468 /** Update the ARG_MAX lower bound from a successful execution. */
bfs_exec_update_min(struct bfs_exec * execbuf)469 static void bfs_exec_update_min(struct bfs_exec *execbuf) {
470 	if (execbuf->arg_size > execbuf->arg_min) {
471 		execbuf->arg_min = execbuf->arg_size;
472 
473 		// Don't let min exceed max
474 		if (execbuf->arg_min > execbuf->arg_max) {
475 			execbuf->arg_min = execbuf->arg_max;
476 		}
477 
478 		size_t estimate = bfs_exec_estimate_max(execbuf);
479 		bfs_exec_debug(execbuf, "ARG_MAX between [%zu, %zu], trying %zu\n",
480 		               execbuf->arg_min, execbuf->arg_max, estimate);
481 	}
482 }
483 
484 /** Update the ARG_MAX upper bound from a failed execution. */
bfs_exec_update_max(struct bfs_exec * execbuf)485 static size_t bfs_exec_update_max(struct bfs_exec *execbuf) {
486 	bfs_exec_debug(execbuf, "Got E2BIG, shrinking argument list...\n");
487 
488 	size_t size = execbuf->arg_size;
489 	if (size <= execbuf->arg_min) {
490 		// Lower bound was wrong, restart binary search.
491 		execbuf->arg_min = 0;
492 	}
493 
494 	// Trim a fraction off the max size to avoid repeated failures near the
495 	// top end of the working range
496 	size -= size/16;
497 	if (size < execbuf->arg_max) {
498 		execbuf->arg_max = size;
499 
500 		// Don't let min exceed max
501 		if (execbuf->arg_min > execbuf->arg_max) {
502 			execbuf->arg_min = execbuf->arg_max;
503 		}
504 	}
505 
506 	// Binary search for a more precise bound
507 	size_t estimate = bfs_exec_estimate_max(execbuf);
508 	bfs_exec_debug(execbuf, "ARG_MAX between [%zu, %zu], trying %zu\n",
509 	               execbuf->arg_min, execbuf->arg_max, estimate);
510 	return estimate;
511 }
512 
513 /** Execute the pending command from a BFS_EXEC_MULTI execbuf. */
bfs_exec_flush(struct bfs_exec * execbuf)514 static int bfs_exec_flush(struct bfs_exec *execbuf) {
515 	int ret = 0, error = 0;
516 
517 	size_t orig_argc = execbuf->argc;
518 	while (bfs_exec_args_remain(execbuf)) {
519 		execbuf->argv[execbuf->argc] = NULL;
520 		ret = bfs_exec_spawn(execbuf);
521 		error = errno;
522 		if (ret == 0) {
523 			bfs_exec_update_min(execbuf);
524 			break;
525 		} else if (error != E2BIG) {
526 			break;
527 		}
528 
529 		// Try to recover from E2BIG by trying fewer and fewer arguments
530 		// until they fit
531 		size_t new_max = bfs_exec_update_max(execbuf);
532 		while (execbuf->arg_size > new_max) {
533 			execbuf->argv[execbuf->argc] = execbuf->argv[execbuf->argc - 1];
534 			execbuf->arg_size -= bfs_exec_arg_size(execbuf->argv[execbuf->argc]);
535 			--execbuf->argc;
536 		}
537 	}
538 
539 	size_t new_argc = execbuf->argc;
540 	for (size_t i = execbuf->tmpl_argc - 1; i < new_argc; ++i) {
541 		free(execbuf->argv[i]);
542 	}
543 	execbuf->argc = execbuf->tmpl_argc - 1;
544 	execbuf->arg_size = 0;
545 
546 	if (new_argc < orig_argc) {
547 		// If we recovered from E2BIG, there are unused arguments at the
548 		// end of the list
549 		for (size_t i = new_argc + 1; i <= orig_argc; ++i) {
550 			if (error == 0) {
551 				execbuf->argv[execbuf->argc] = execbuf->argv[i];
552 				execbuf->arg_size += bfs_exec_arg_size(execbuf->argv[execbuf->argc]);
553 				++execbuf->argc;
554 			} else {
555 				free(execbuf->argv[i]);
556 			}
557 		}
558 	}
559 
560 	errno = error;
561 	return ret;
562 }
563 
564 /** Check if we need to flush the execbuf because we're changing directories. */
bfs_exec_changed_dirs(const struct bfs_exec * execbuf,const struct BFTW * ftwbuf)565 static bool bfs_exec_changed_dirs(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
566 	if (execbuf->flags & BFS_EXEC_CHDIR) {
567 		if (ftwbuf->nameoff > execbuf->wd_len
568 		    || (execbuf->wd_path && strncmp(ftwbuf->path, execbuf->wd_path, execbuf->wd_len) != 0)) {
569 			bfs_exec_debug(execbuf, "Changed directories, executing buffered command\n");
570 			return true;
571 		}
572 	}
573 
574 	return false;
575 }
576 
577 /** Check if we need to flush the execbuf because we're too big. */
bfs_exec_would_overflow(const struct bfs_exec * execbuf,const char * arg)578 static bool bfs_exec_would_overflow(const struct bfs_exec *execbuf, const char *arg) {
579 	size_t arg_max = bfs_exec_estimate_max(execbuf);
580 	size_t next_size = execbuf->arg_size + bfs_exec_arg_size(arg);
581 	if (next_size > arg_max) {
582 		bfs_exec_debug(execbuf, "Command size (%zu) would exceed maximum (%zu), executing buffered command\n",
583 		               next_size, arg_max);
584 		return true;
585 	}
586 
587 	return false;
588 }
589 
590 /** Push a new argument to a BFS_EXEC_MULTI execbuf. */
bfs_exec_push(struct bfs_exec * execbuf,char * arg)591 static int bfs_exec_push(struct bfs_exec *execbuf, char *arg) {
592 	execbuf->argv[execbuf->argc] = arg;
593 
594 	if (execbuf->argc + 1 >= execbuf->argv_cap) {
595 		size_t cap = 2*execbuf->argv_cap;
596 		char **argv = realloc(execbuf->argv, cap*sizeof(*argv));
597 		if (!argv) {
598 			return -1;
599 		}
600 		execbuf->argv = argv;
601 		execbuf->argv_cap = cap;
602 	}
603 
604 	++execbuf->argc;
605 	execbuf->arg_size += bfs_exec_arg_size(arg);
606 	return 0;
607 }
608 
609 /** Handle a new path for a BFS_EXEC_MULTI execbuf. */
bfs_exec_multi(struct bfs_exec * execbuf,const struct BFTW * ftwbuf)610 static int bfs_exec_multi(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
611 	int ret = 0;
612 
613 	char *arg = bfs_exec_format_path(execbuf, ftwbuf);
614 	if (!arg) {
615 		ret = -1;
616 		goto out;
617 	}
618 
619 	if (bfs_exec_changed_dirs(execbuf, ftwbuf)) {
620 		while (bfs_exec_args_remain(execbuf)) {
621 			ret |= bfs_exec_flush(execbuf);
622 		}
623 		bfs_exec_closewd(execbuf, ftwbuf);
624 	} else if (bfs_exec_would_overflow(execbuf, arg)) {
625 		ret |= bfs_exec_flush(execbuf);
626 	}
627 
628 	if ((execbuf->flags & BFS_EXEC_CHDIR) && execbuf->wd_fd < 0) {
629 		if (bfs_exec_openwd(execbuf, ftwbuf) != 0) {
630 			ret = -1;
631 			goto out_arg;
632 		}
633 	}
634 
635 	if (bfs_exec_push(execbuf, arg) != 0) {
636 		ret = -1;
637 		goto out_arg;
638 	}
639 
640 	// arg will get cleaned up later by bfs_exec_flush()
641 	goto out;
642 
643 out_arg:
644 	free(arg);
645 out:
646 	return ret;
647 }
648 
bfs_exec(struct bfs_exec * execbuf,const struct BFTW * ftwbuf)649 int bfs_exec(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
650 	if (execbuf->flags & BFS_EXEC_MULTI) {
651 		if (bfs_exec_multi(execbuf, ftwbuf) == 0) {
652 			errno = 0;
653 		} else {
654 			execbuf->ret = -1;
655 		}
656 		// -exec ... + never returns false
657 		return 0;
658 	} else {
659 		return bfs_exec_single(execbuf, ftwbuf);
660 	}
661 }
662 
bfs_exec_finish(struct bfs_exec * execbuf)663 int bfs_exec_finish(struct bfs_exec *execbuf) {
664 	if (execbuf->flags & BFS_EXEC_MULTI) {
665 		bfs_exec_debug(execbuf, "Finishing execution, executing buffered command\n");
666 		while (bfs_exec_args_remain(execbuf)) {
667 			execbuf->ret |= bfs_exec_flush(execbuf);
668 		}
669 		if (execbuf->ret != 0) {
670 			bfs_exec_debug(execbuf, "One or more executions of '%s' failed\n", execbuf->argv[0]);
671 		}
672 	}
673 	return execbuf->ret;
674 }
675 
bfs_exec_free(struct bfs_exec * execbuf)676 void bfs_exec_free(struct bfs_exec *execbuf) {
677 	if (execbuf) {
678 		bfs_exec_closewd(execbuf, NULL);
679 		free(execbuf->argv);
680 		free(execbuf);
681 	}
682 }
683