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