summaryrefslogtreecommitdiffstats
path: root/exec.c
diff options
context:
space:
mode:
Diffstat (limited to 'exec.c')
-rw-r--r--exec.c715
1 files changed, 0 insertions, 715 deletions
diff --git a/exec.c b/exec.c
deleted file mode 100644
index 0130317..0000000
--- a/exec.c
+++ /dev/null
@@ -1,715 +0,0 @@
-/****************************************************************************
- * bfs *
- * Copyright (C) 2017-2022 Tavian Barnes <tavianator@tavianator.com> *
- * *
- * Permission to use, copy, modify, and/or distribute this software for any *
- * purpose with or without fee is hereby granted. *
- * *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES *
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF *
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR *
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES *
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN *
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *
- ****************************************************************************/
-
-#include "exec.h"
-#include "bftw.h"
-#include "ctx.h"
-#include "color.h"
-#include "diag.h"
-#include "dstring.h"
-#include "util.h"
-#include "xspawn.h"
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/resource.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-/** Print some debugging info. */
-BFS_FORMATTER(2, 3)
-static void bfs_exec_debug(const struct bfs_exec *execbuf, const char *format, ...) {
- const struct bfs_ctx *ctx = execbuf->ctx;
-
- if (!bfs_debug(ctx, DEBUG_EXEC, "${blu}")) {
- return;
- }
-
- if (execbuf->flags & BFS_EXEC_CONFIRM) {
- fputs("-ok", stderr);
- } else {
- fputs("-exec", stderr);
- }
- if (execbuf->flags & BFS_EXEC_CHDIR) {
- fputs("dir", stderr);
- }
- cfprintf(ctx->cerr, "${rs}: ");
-
- va_list args;
- va_start(args, format);
- vfprintf(stderr, format, args);
- va_end(args);
-}
-
-/** Determine the size of a single argument, for comparison to arg_max. */
-static size_t bfs_exec_arg_size(const char *arg) {
- return sizeof(arg) + strlen(arg) + 1;
-}
-
-/** Even if we can pass a bigger argument list, cap it here. */
-#define BFS_EXEC_ARG_MAX (16 << 20)
-
-/** Determine the maximum argv size. */
-static size_t bfs_exec_arg_max(const struct bfs_exec *execbuf) {
- long arg_max = sysconf(_SC_ARG_MAX);
- bfs_exec_debug(execbuf, "ARG_MAX: %ld according to sysconf()\n", arg_max);
- if (arg_max < 0) {
- arg_max = BFS_EXEC_ARG_MAX;
- bfs_exec_debug(execbuf, "ARG_MAX: %ld assumed\n", arg_max);
- }
-
- // We have to share space with the environment variables
- extern char **environ;
- for (char **envp = environ; *envp; ++envp) {
- arg_max -= bfs_exec_arg_size(*envp);
- }
- // Account for the terminating NULL entry
- arg_max -= sizeof(char *);
- bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after environment variables\n", arg_max);
-
- // Account for the fixed arguments
- for (size_t i = 0; i < execbuf->tmpl_argc - 1; ++i) {
- arg_max -= bfs_exec_arg_size(execbuf->tmpl_argv[i]);
- }
- // Account for the terminating NULL entry
- arg_max -= sizeof(char *);
- bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after fixed arguments\n", arg_max);
-
- // Assume arguments are counted with the granularity of a single page,
- // so allow a one page cushion to account for rounding up
- long page_size = sysconf(_SC_PAGESIZE);
- if (page_size < 4096) {
- page_size = 4096;
- }
- arg_max -= page_size;
- bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after page cushion\n", arg_max);
-
- // POSIX recommends an additional 2048 bytes of headroom
- arg_max -= 2048;
- bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after headroom\n", arg_max);
-
- if (arg_max < 0) {
- arg_max = 0;
- } else if (arg_max > BFS_EXEC_ARG_MAX) {
- arg_max = BFS_EXEC_ARG_MAX;
- }
-
- bfs_exec_debug(execbuf, "ARG_MAX: %ld final value\n", arg_max);
- return arg_max;
-}
-
-/** Highlight part of the command line as an error. */
-static void bfs_exec_parse_error(const struct bfs_ctx *ctx, const struct bfs_exec *execbuf) {
- char **argv = execbuf->tmpl_argv - 1;
- size_t argc = execbuf->tmpl_argc + 1;
- if (argv[argc]) {
- ++argc;
- }
-
- bool args[ctx->argc];
- for (size_t i = 0; i < ctx->argc; ++i) {
- args[i] = false;
- }
-
- size_t i = argv - ctx->argv;
- for (size_t j = 0; j < argc; ++j) {
- args[i + j] = true;
- }
-
- bfs_argv_error(ctx, args);
-}
-
-struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs_exec_flags flags) {
- struct bfs_exec *execbuf = malloc(sizeof(*execbuf));
- if (!execbuf) {
- bfs_perror(ctx, "malloc()");
- goto fail;
- }
-
- execbuf->flags = flags;
- execbuf->ctx = ctx;
- execbuf->tmpl_argv = argv + 1;
- execbuf->tmpl_argc = 0;
- execbuf->argv = NULL;
- execbuf->argc = 0;
- execbuf->argv_cap = 0;
- execbuf->arg_size = 0;
- execbuf->arg_max = 0;
- execbuf->arg_min = 0;
- execbuf->wd_fd = -1;
- execbuf->wd_path = NULL;
- execbuf->wd_len = 0;
- execbuf->ret = 0;
-
- while (true) {
- const char *arg = execbuf->tmpl_argv[execbuf->tmpl_argc];
- if (!arg) {
- if (execbuf->flags & BFS_EXEC_CONFIRM) {
- bfs_exec_parse_error(ctx, execbuf);
- bfs_error(ctx, "Expected '... ;'.\n");
- } else {
- bfs_exec_parse_error(ctx, execbuf);
- bfs_error(ctx, "Expected '... ;' or '... {} +'.\n");
- }
- goto fail;
- } else if (strcmp(arg, ";") == 0) {
- break;
- } else if (strcmp(arg, "+") == 0) {
- const char *prev = execbuf->tmpl_argv[execbuf->tmpl_argc - 1];
- if (!(execbuf->flags & BFS_EXEC_CONFIRM) && strcmp(prev, "{}") == 0) {
- execbuf->flags |= BFS_EXEC_MULTI;
- break;
- }
- }
-
- ++execbuf->tmpl_argc;
- }
-
- if (execbuf->tmpl_argc == 0) {
- bfs_exec_parse_error(ctx, execbuf);
- bfs_error(ctx, "Missing command.\n");
- goto fail;
- }
-
- execbuf->argv_cap = execbuf->tmpl_argc + 1;
- execbuf->argv = malloc(execbuf->argv_cap*sizeof(*execbuf->argv));
- if (!execbuf->argv) {
- bfs_perror(ctx, "malloc()");
- goto fail;
- }
-
- if (execbuf->flags & BFS_EXEC_MULTI) {
- for (size_t i = 0; i < execbuf->tmpl_argc - 1; ++i) {
- char *arg = execbuf->tmpl_argv[i];
- if (strstr(arg, "{}")) {
- bfs_exec_parse_error(ctx, execbuf);
- bfs_error(ctx, "Only one '{}' is supported.\n");
- goto fail;
- }
- execbuf->argv[i] = arg;
- }
- execbuf->argc = execbuf->tmpl_argc - 1;
-
- execbuf->arg_max = bfs_exec_arg_max(execbuf);
- execbuf->arg_min = execbuf->arg_max;
- }
-
- return execbuf;
-
-fail:
- bfs_exec_free(execbuf);
- return NULL;
-}
-
-/** Format the current path for use as a command line argument. */
-static char *bfs_exec_format_path(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
- if (!(execbuf->flags & BFS_EXEC_CHDIR)) {
- return strdup(ftwbuf->path);
- }
-
- const char *name = ftwbuf->path + ftwbuf->nameoff;
-
- if (name[0] == '/') {
- // Must be a root path ("/", "//", etc.)
- return strdup(name);
- }
-
- // For compatibility with GNU find, use './name' instead of just 'name'
- char *path = malloc(2 + strlen(name) + 1);
- if (!path) {
- return NULL;
- }
-
- strcpy(path, "./");
- strcpy(path + 2, name);
-
- return path;
-}
-
-/** Format an argument, expanding "{}" to the current path. */
-static char *bfs_exec_format_arg(char *arg, const char *path) {
- char *match = strstr(arg, "{}");
- if (!match) {
- return arg;
- }
-
- char *ret = dstralloc(0);
- if (!ret) {
- return NULL;
- }
-
- char *last = arg;
- do {
- if (dstrncat(&ret, last, match - last) != 0) {
- goto err;
- }
- if (dstrcat(&ret, path) != 0) {
- goto err;
- }
-
- last = match + 2;
- match = strstr(last, "{}");
- } while (match);
-
- if (dstrcat(&ret, last) != 0) {
- goto err;
- }
-
- return ret;
-
-err:
- dstrfree(ret);
- return NULL;
-}
-
-/** Free a formatted argument. */
-static void bfs_exec_free_arg(char *arg, const char *tmpl) {
- if (arg != tmpl) {
- dstrfree(arg);
- }
-}
-
-/** Open a file to use as the working directory. */
-static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
- assert(execbuf->wd_fd < 0);
- assert(!execbuf->wd_path);
-
- if (ftwbuf->at_fd != AT_FDCWD) {
- // Rely on at_fd being the immediate parent
- assert(ftwbuf->at_path == xbasename(ftwbuf->at_path));
-
- execbuf->wd_fd = ftwbuf->at_fd;
- if (!(execbuf->flags & BFS_EXEC_MULTI)) {
- return 0;
- }
-
- execbuf->wd_fd = dup_cloexec(execbuf->wd_fd);
- if (execbuf->wd_fd < 0) {
- return -1;
- }
- }
-
- execbuf->wd_len = ftwbuf->nameoff;
- if (execbuf->wd_len == 0) {
- if (ftwbuf->path[0] == '/') {
- ++execbuf->wd_len;
- } else {
- // The path is something like "foo", so we're already in the right directory
- return 0;
- }
- }
-
- execbuf->wd_path = strndup(ftwbuf->path, execbuf->wd_len);
- if (!execbuf->wd_path) {
- return -1;
- }
-
- if (execbuf->wd_fd < 0) {
- execbuf->wd_fd = open(execbuf->wd_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- }
-
- if (execbuf->wd_fd < 0) {
- return -1;
- }
-
- return 0;
-}
-
-/** Close the working directory. */
-static void bfs_exec_closewd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
- if (execbuf->wd_fd >= 0) {
- if (!ftwbuf || execbuf->wd_fd != ftwbuf->at_fd) {
- xclose(execbuf->wd_fd);
- }
- execbuf->wd_fd = -1;
- }
-
- if (execbuf->wd_path) {
- free(execbuf->wd_path);
- execbuf->wd_path = NULL;
- execbuf->wd_len = 0;
- }
-}
-
-/** Actually spawn the process. */
-static int bfs_exec_spawn(const struct bfs_exec *execbuf) {
- if (execbuf->flags & BFS_EXEC_CONFIRM) {
- for (size_t i = 0; i < execbuf->argc; ++i) {
- if (fprintf(stderr, "%s ", execbuf->argv[i]) < 0) {
- return -1;
- }
- }
- if (fprintf(stderr, "? ") < 0) {
- return -1;
- }
-
- if (ynprompt() <= 0) {
- errno = 0;
- return -1;
- }
- }
-
- // Flush cached state for consistency with the external process
- bfs_ctx_flush(execbuf->ctx);
-
- if (execbuf->flags & BFS_EXEC_MULTI) {
- bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments] (size %zu)\n",
- execbuf->argv[0], execbuf->argc - 1, execbuf->arg_size);
- } else {
- bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments]\n", execbuf->argv[0], execbuf->argc - 1);
- }
-
- pid_t pid = -1;
- int error;
-
- struct bfs_spawn ctx;
- if (bfs_spawn_init(&ctx) != 0) {
- return -1;
- }
-
- if (bfs_spawn_setflags(&ctx, BFS_SPAWN_USEPATH) != 0) {
- goto fail;
- }
-
- // Reset RLIMIT_NOFILE, to avoid breaking applications that use select()
- struct rlimit rl = {
- .rlim_cur = execbuf->ctx->nofile_soft,
- .rlim_max = execbuf->ctx->nofile_hard,
- };
- if (bfs_spawn_addsetrlimit(&ctx, RLIMIT_NOFILE, &rl) != 0) {
- goto fail;
- }
-
- if (execbuf->wd_fd >= 0) {
- if (bfs_spawn_addfchdir(&ctx, execbuf->wd_fd) != 0) {
- goto fail;
- }
- }
-
- pid = bfs_spawn(execbuf->argv[0], &ctx, execbuf->argv, NULL);
-fail:
- error = errno;
- bfs_spawn_destroy(&ctx);
- if (pid < 0) {
- errno = error;
- return -1;
- }
-
- int wstatus;
- if (waitpid(pid, &wstatus, 0) < 0) {
- return -1;
- }
-
- int ret = -1;
-
- if (WIFEXITED(wstatus)) {
- int status = WEXITSTATUS(wstatus);
- if (status == EXIT_SUCCESS) {
- ret = 0;
- } else {
- bfs_exec_debug(execbuf, "Command '%s' failed with status %d\n", execbuf->argv[0], status);
- }
- } else if (WIFSIGNALED(wstatus)) {
- int sig = WTERMSIG(wstatus);
- const char *str = strsignal(sig);
- if (!str) {
- str = "unknown";
- }
- bfs_warning(execbuf->ctx, "Command '${ex}%s${rs}' terminated by signal %d (%s)\n", execbuf->argv[0], sig, str);
- } else {
- bfs_warning(execbuf->ctx, "Command '${ex}%s${rs}' terminated abnormally\n", execbuf->argv[0]);
- }
-
- errno = 0;
- return ret;
-}
-
-/** exec() a command for a single file. */
-static int bfs_exec_single(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
- int ret = -1, error = 0;
-
- char *path = bfs_exec_format_path(execbuf, ftwbuf);
- if (!path) {
- goto out;
- }
-
- size_t i;
- for (i = 0; i < execbuf->tmpl_argc; ++i) {
- execbuf->argv[i] = bfs_exec_format_arg(execbuf->tmpl_argv[i], path);
- if (!execbuf->argv[i]) {
- goto out_free;
- }
- }
- execbuf->argv[i] = NULL;
- execbuf->argc = i;
-
- if (execbuf->flags & BFS_EXEC_CHDIR) {
- if (bfs_exec_openwd(execbuf, ftwbuf) != 0) {
- goto out_free;
- }
- }
-
- ret = bfs_exec_spawn(execbuf);
-
-out_free:
- error = errno;
-
- bfs_exec_closewd(execbuf, ftwbuf);
-
- for (size_t j = 0; j < i; ++j) {
- bfs_exec_free_arg(execbuf->argv[j], execbuf->tmpl_argv[j]);
- }
-
- free(path);
-
- errno = error;
-
-out:
- return ret;
-}
-
-/** Check if any arguments remain in the buffer. */
-static bool bfs_exec_args_remain(const struct bfs_exec *execbuf) {
- return execbuf->argc >= execbuf->tmpl_argc;
-}
-
-/** Compute the current ARG_MAX estimate for binary search. */
-static size_t bfs_exec_estimate_max(const struct bfs_exec *execbuf) {
- size_t min = execbuf->arg_min;
- size_t max = execbuf->arg_max;
- return min + (max - min)/2;
-}
-
-/** Update the ARG_MAX lower bound from a successful execution. */
-static void bfs_exec_update_min(struct bfs_exec *execbuf) {
- if (execbuf->arg_size > execbuf->arg_min) {
- execbuf->arg_min = execbuf->arg_size;
-
- // Don't let min exceed max
- if (execbuf->arg_min > execbuf->arg_max) {
- execbuf->arg_min = execbuf->arg_max;
- }
-
- size_t estimate = bfs_exec_estimate_max(execbuf);
- bfs_exec_debug(execbuf, "ARG_MAX between [%zu, %zu], trying %zu\n",
- execbuf->arg_min, execbuf->arg_max, estimate);
- }
-}
-
-/** Update the ARG_MAX upper bound from a failed execution. */
-static size_t bfs_exec_update_max(struct bfs_exec *execbuf) {
- bfs_exec_debug(execbuf, "Got E2BIG, shrinking argument list...\n");
-
- size_t size = execbuf->arg_size;
- if (size <= execbuf->arg_min) {
- // Lower bound was wrong, restart binary search.
- execbuf->arg_min = 0;
- }
-
- // Trim a fraction off the max size to avoid repeated failures near the
- // top end of the working range
- size -= size/16;
- if (size < execbuf->arg_max) {
- execbuf->arg_max = size;
-
- // Don't let min exceed max
- if (execbuf->arg_min > execbuf->arg_max) {
- execbuf->arg_min = execbuf->arg_max;
- }
- }
-
- // Binary search for a more precise bound
- size_t estimate = bfs_exec_estimate_max(execbuf);
- bfs_exec_debug(execbuf, "ARG_MAX between [%zu, %zu], trying %zu\n",
- execbuf->arg_min, execbuf->arg_max, estimate);
- return estimate;
-}
-
-/** Execute the pending command from a BFS_EXEC_MULTI execbuf. */
-static int bfs_exec_flush(struct bfs_exec *execbuf) {
- int ret = 0, error = 0;
-
- size_t orig_argc = execbuf->argc;
- while (bfs_exec_args_remain(execbuf)) {
- execbuf->argv[execbuf->argc] = NULL;
- ret = bfs_exec_spawn(execbuf);
- error = errno;
- if (ret == 0) {
- bfs_exec_update_min(execbuf);
- break;
- } else if (error != E2BIG) {
- break;
- }
-
- // Try to recover from E2BIG by trying fewer and fewer arguments
- // until they fit
- size_t new_max = bfs_exec_update_max(execbuf);
- while (execbuf->arg_size > new_max) {
- execbuf->argv[execbuf->argc] = execbuf->argv[execbuf->argc - 1];
- execbuf->arg_size -= bfs_exec_arg_size(execbuf->argv[execbuf->argc]);
- --execbuf->argc;
- }
- }
-
- size_t new_argc = execbuf->argc;
- for (size_t i = execbuf->tmpl_argc - 1; i < new_argc; ++i) {
- free(execbuf->argv[i]);
- }
- execbuf->argc = execbuf->tmpl_argc - 1;
- execbuf->arg_size = 0;
-
- if (new_argc < orig_argc) {
- // If we recovered from E2BIG, there are unused arguments at the
- // end of the list
- for (size_t i = new_argc + 1; i <= orig_argc; ++i) {
- if (error == 0) {
- execbuf->argv[execbuf->argc] = execbuf->argv[i];
- execbuf->arg_size += bfs_exec_arg_size(execbuf->argv[execbuf->argc]);
- ++execbuf->argc;
- } else {
- free(execbuf->argv[i]);
- }
- }
- }
-
- errno = error;
- return ret;
-}
-
-/** Check if we need to flush the execbuf because we're changing directories. */
-static bool bfs_exec_changed_dirs(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
- if (execbuf->flags & BFS_EXEC_CHDIR) {
- if (ftwbuf->nameoff > execbuf->wd_len
- || (execbuf->wd_path && strncmp(ftwbuf->path, execbuf->wd_path, execbuf->wd_len) != 0)) {
- bfs_exec_debug(execbuf, "Changed directories, executing buffered command\n");
- return true;
- }
- }
-
- return false;
-}
-
-/** Check if we need to flush the execbuf because we're too big. */
-static bool bfs_exec_would_overflow(const struct bfs_exec *execbuf, const char *arg) {
- size_t arg_max = bfs_exec_estimate_max(execbuf);
- size_t next_size = execbuf->arg_size + bfs_exec_arg_size(arg);
- if (next_size > arg_max) {
- bfs_exec_debug(execbuf, "Command size (%zu) would exceed maximum (%zu), executing buffered command\n",
- next_size, arg_max);
- return true;
- }
-
- return false;
-}
-
-/** Push a new argument to a BFS_EXEC_MULTI execbuf. */
-static int bfs_exec_push(struct bfs_exec *execbuf, char *arg) {
- execbuf->argv[execbuf->argc] = arg;
-
- if (execbuf->argc + 1 >= execbuf->argv_cap) {
- size_t cap = 2*execbuf->argv_cap;
- char **argv = realloc(execbuf->argv, cap*sizeof(*argv));
- if (!argv) {
- return -1;
- }
- execbuf->argv = argv;
- execbuf->argv_cap = cap;
- }
-
- ++execbuf->argc;
- execbuf->arg_size += bfs_exec_arg_size(arg);
- return 0;
-}
-
-/** Handle a new path for a BFS_EXEC_MULTI execbuf. */
-static int bfs_exec_multi(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
- int ret = 0;
-
- char *arg = bfs_exec_format_path(execbuf, ftwbuf);
- if (!arg) {
- ret = -1;
- goto out;
- }
-
- if (bfs_exec_changed_dirs(execbuf, ftwbuf)) {
- while (bfs_exec_args_remain(execbuf)) {
- ret |= bfs_exec_flush(execbuf);
- }
- bfs_exec_closewd(execbuf, ftwbuf);
- } else if (bfs_exec_would_overflow(execbuf, arg)) {
- ret |= bfs_exec_flush(execbuf);
- }
-
- if ((execbuf->flags & BFS_EXEC_CHDIR) && execbuf->wd_fd < 0) {
- if (bfs_exec_openwd(execbuf, ftwbuf) != 0) {
- ret = -1;
- goto out_arg;
- }
- }
-
- if (bfs_exec_push(execbuf, arg) != 0) {
- ret = -1;
- goto out_arg;
- }
-
- // arg will get cleaned up later by bfs_exec_flush()
- goto out;
-
-out_arg:
- free(arg);
-out:
- return ret;
-}
-
-int bfs_exec(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) {
- if (execbuf->flags & BFS_EXEC_MULTI) {
- if (bfs_exec_multi(execbuf, ftwbuf) == 0) {
- errno = 0;
- } else {
- execbuf->ret = -1;
- }
- // -exec ... + never returns false
- return 0;
- } else {
- return bfs_exec_single(execbuf, ftwbuf);
- }
-}
-
-int bfs_exec_finish(struct bfs_exec *execbuf) {
- if (execbuf->flags & BFS_EXEC_MULTI) {
- bfs_exec_debug(execbuf, "Finishing execution, executing buffered command\n");
- while (bfs_exec_args_remain(execbuf)) {
- execbuf->ret |= bfs_exec_flush(execbuf);
- }
- if (execbuf->ret != 0) {
- bfs_exec_debug(execbuf, "One or more executions of '%s' failed\n", execbuf->argv[0]);
- }
- }
- return execbuf->ret;
-}
-
-void bfs_exec_free(struct bfs_exec *execbuf) {
- if (execbuf) {
- bfs_exec_closewd(execbuf, NULL);
- free(execbuf->argv);
- free(execbuf);
- }
-}