diff options
author | トトも <85485984+ElectronicsArchiver@users.noreply.github.com> | 2022-04-16 20:18:56 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-16 14:18:56 -0400 |
commit | 33cc3b9dd7bf3dae1c6cf86e46bb4923f96e7fff (patch) | |
tree | 02fb808d19aee560ac9d381ca5a52509881cdd44 /src/xspawn.c | |
parent | 8f5a73a6585bd425807430fd80ce1e3a737f4c5f (diff) | |
download | bfs-33cc3b9dd7bf3dae1c6cf86e46bb4923f96e7fff.tar.xz |
Source / Include Folder (#88)
Moved Source Files Into `src` Folder
Diffstat (limited to 'src/xspawn.c')
-rw-r--r-- | src/xspawn.c | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/src/xspawn.c b/src/xspawn.c new file mode 100644 index 0000000..93c270a --- /dev/null +++ b/src/xspawn.c @@ -0,0 +1,318 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2018-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 "xspawn.h" +#include "util.h" +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +/** + * Types of spawn actions. + */ +enum bfs_spawn_op { + BFS_SPAWN_CLOSE, + BFS_SPAWN_DUP2, + BFS_SPAWN_FCHDIR, + BFS_SPAWN_SETRLIMIT, +}; + +/** + * A spawn action. + */ +struct bfs_spawn_action { + struct bfs_spawn_action *next; + + enum bfs_spawn_op op; + int in_fd; + int out_fd; + int resource; + struct rlimit rlimit; +}; + +int bfs_spawn_init(struct bfs_spawn *ctx) { + ctx->flags = 0; + ctx->actions = NULL; + ctx->tail = &ctx->actions; + return 0; +} + +int bfs_spawn_destroy(struct bfs_spawn *ctx) { + struct bfs_spawn_action *action = ctx->actions; + while (action) { + struct bfs_spawn_action *next = action->next; + free(action); + action = next; + } + return 0; +} + +int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { + ctx->flags = flags; + return 0; +} + +/** Add a spawn action to the chain. */ +static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { + struct bfs_spawn_action *action = malloc(sizeof(*action)); + if (action) { + action->next = NULL; + action->op = op; + action->in_fd = -1; + action->out_fd = -1; + + *ctx->tail = action; + ctx->tail = &action->next; + } + return action; +} + +int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { + if (fd < 0) { + errno = EBADF; + return -1; + } + + struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_CLOSE); + if (action) { + action->out_fd = fd; + return 0; + } else { + return -1; + } +} + +int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { + if (oldfd < 0 || newfd < 0) { + errno = EBADF; + return -1; + } + + struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_DUP2); + if (action) { + action->in_fd = oldfd; + action->out_fd = newfd; + return 0; + } else { + return -1; + } +} + +int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { + if (fd < 0) { + errno = EBADF; + return -1; + } + + struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_FCHDIR); + if (action) { + action->in_fd = fd; + return 0; + } else { + return -1; + } +} + +int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) { + struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_SETRLIMIT); + if (action) { + action->resource = resource; + action->rlimit = *rl; + return 0; + } else { + return -1; + } +} + +/** Actually exec() the new process. */ +static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { + int error; + const struct bfs_spawn_action *actions = ctx ? ctx->actions : NULL; + + xclose(pipefd[0]); + + for (const struct bfs_spawn_action *action = actions; action; action = action->next) { + // Move the error-reporting pipe out of the way if necessary... + if (action->out_fd == pipefd[1]) { + int fd = dup_cloexec(pipefd[1]); + if (fd < 0) { + goto fail; + } + xclose(pipefd[1]); + pipefd[1] = fd; + } + + // ... and pretend the pipe doesn't exist + if (action->in_fd == pipefd[1]) { + errno = EBADF; + goto fail; + } + + switch (action->op) { + case BFS_SPAWN_CLOSE: + if (close(action->out_fd) != 0) { + goto fail; + } + break; + case BFS_SPAWN_DUP2: + if (dup2(action->in_fd, action->out_fd) < 0) { + goto fail; + } + break; + case BFS_SPAWN_FCHDIR: + if (fchdir(action->in_fd) != 0) { + goto fail; + } + break; + case BFS_SPAWN_SETRLIMIT: + if (setrlimit(action->resource, &action->rlimit) != 0) { + goto fail; + } + break; + } + } + + execve(exe, argv, envp); + +fail: + error = errno; + + // In case of a write error, the parent will still see that we exited + // unsuccessfully, but won't know why + (void) xwrite(pipefd[1], &error, sizeof(error)); + + xclose(pipefd[1]); + _Exit(127); +} + +pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { + extern char **environ; + if (!envp) { + envp = environ; + } + + enum bfs_spawn_flags flags = ctx ? ctx->flags : 0; + char *resolved = NULL; + if (flags & BFS_SPAWN_USEPATH) { + exe = resolved = bfs_spawn_resolve(exe); + if (!resolved) { + return -1; + } + } + + // Use a pipe to report errors from the child + int pipefd[2]; + if (pipe_cloexec(pipefd) != 0) { + free(resolved); + return -1; + } + + pid_t pid = fork(); + if (pid < 0) { + close_quietly(pipefd[1]); + close_quietly(pipefd[0]); + free(resolved); + return -1; + } else if (pid == 0) { + // Child + bfs_spawn_exec(exe, ctx, argv, envp, pipefd); + } + + // Parent + xclose(pipefd[1]); + free(resolved); + + int error; + ssize_t nbytes = xread(pipefd[0], &error, sizeof(error)); + xclose(pipefd[0]); + if (nbytes == sizeof(error)) { + int wstatus; + waitpid(pid, &wstatus, 0); + errno = error; + return -1; + } + + return pid; +} + +char *bfs_spawn_resolve(const char *exe) { + if (strchr(exe, '/')) { + return strdup(exe); + } + + const char *path = getenv("PATH"); + + char *confpath = NULL; + if (!path) { + path = confpath = xconfstr(_CS_PATH); + if (!path) { + return NULL; + } + } + + size_t cap = 0; + char *ret = NULL; + while (true) { + const char *end = strchr(path, ':'); + size_t len = end ? (size_t)(end - path) : strlen(path); + + // POSIX 8.3: "A zero-length prefix is a legacy feature that + // indicates the current working directory." + if (len == 0) { + path = "."; + len = 1; + } + + size_t total = len + 1 + strlen(exe) + 1; + if (cap < total) { + char *grown = realloc(ret, total); + if (!grown) { + goto fail; + } + ret = grown; + cap = total; + } + + memcpy(ret, path, len); + if (ret[len - 1] != '/') { + ret[len++] = '/'; + } + strcpy(ret + len, exe); + + if (xfaccessat(AT_FDCWD, ret, X_OK) == 0) { + break; + } + + if (!end) { + errno = ENOENT; + goto fail; + } + + path = end + 1; + } + + free(confpath); + return ret; + +fail: + free(confpath); + free(ret); + return NULL; +} |