summaryrefslogtreecommitdiffstats
path: root/src/xspawn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/xspawn.c')
-rw-r--r--src/xspawn.c177
1 files changed, 128 insertions, 49 deletions
diff --git a/src/xspawn.c b/src/xspawn.c
index 2c64011..ee62c05 100644
--- a/src/xspawn.c
+++ b/src/xspawn.c
@@ -1,12 +1,15 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "xspawn.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
#include "list.h"
+#include "sighook.h"
+
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
@@ -16,11 +19,11 @@
#include <sys/types.h>
#include <unistd.h>
-#if BFS_USE_PATHS_H
+#if __has_include(<paths.h>)
# include <paths.h>
#endif
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
# include <spawn.h>
#endif
@@ -70,29 +73,42 @@ int bfs_spawn_init(struct bfs_spawn *ctx) {
ctx->flags = 0;
SLIST_INIT(ctx);
-#if _POSIX_SPAWN > 0
- ctx->flags |= BFS_SPAWN_USE_POSIX;
+#if BFS_POSIX_SPAWN >= 0
+ if (sysoption(SPAWN) > 0) {
+ ctx->flags |= BFS_SPAWN_USE_POSIX;
- errno = posix_spawn_file_actions_init(&ctx->actions);
- if (errno != 0) {
- return -1;
- }
+ errno = posix_spawn_file_actions_init(&ctx->actions);
+ if (errno != 0) {
+ return -1;
+ }
- errno = posix_spawnattr_init(&ctx->attr);
- if (errno != 0) {
- posix_spawn_file_actions_destroy(&ctx->actions);
- return -1;
+ errno = posix_spawnattr_init(&ctx->attr);
+ if (errno != 0) {
+ posix_spawn_file_actions_destroy(&ctx->actions);
+ return -1;
+ }
}
#endif
return 0;
}
-int bfs_spawn_destroy(struct bfs_spawn *ctx) {
-#if _POSIX_SPAWN > 0
- posix_spawnattr_destroy(&ctx->attr);
- posix_spawn_file_actions_destroy(&ctx->actions);
+/**
+ * Clear the BFS_SPAWN_USE_POSIX flag and free the attributes.
+ */
+static void bfs_spawn_clear_posix(struct bfs_spawn *ctx) {
+ if (ctx->flags & BFS_SPAWN_USE_POSIX) {
+ ctx->flags &= ~BFS_SPAWN_USE_POSIX;
+
+#if BFS_POSIX_SPAWN >= 0
+ posix_spawnattr_destroy(&ctx->attr);
+ posix_spawn_file_actions_destroy(&ctx->actions);
#endif
+ }
+}
+
+int bfs_spawn_destroy(struct bfs_spawn *ctx) {
+ bfs_spawn_clear_posix(ctx);
for_slist (struct bfs_spawn_action, action, ctx) {
free(action);
@@ -101,9 +117,9 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) {
return 0;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
/** Set some posix_spawnattr flags. */
-attr(maybe_unused)
+_maybe_unused
static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) {
short prev;
errno = posix_spawnattr_getflags(&ctx->attr, &prev);
@@ -121,7 +137,7 @@ static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) {
return 0;
}
-#endif // _POSIX_SPAWN > 0
+#endif
/** Allocate a spawn action. */
static struct bfs_spawn_action *bfs_spawn_action(enum bfs_spawn_op op) {
@@ -143,7 +159,7 @@ int bfs_spawn_addopen(struct bfs_spawn *ctx, int fd, const char *path, int flags
return -1;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
if (ctx->flags & BFS_SPAWN_USE_POSIX) {
errno = posix_spawn_file_actions_addopen(&ctx->actions, fd, path, flags, mode);
if (errno != 0) {
@@ -167,7 +183,7 @@ int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) {
return -1;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
if (ctx->flags & BFS_SPAWN_USE_POSIX) {
errno = posix_spawn_file_actions_addclose(&ctx->actions, fd);
if (errno != 0) {
@@ -188,7 +204,7 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) {
return -1;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
if (ctx->flags & BFS_SPAWN_USE_POSIX) {
errno = posix_spawn_file_actions_adddup2(&ctx->actions, oldfd, newfd);
if (errno != 0) {
@@ -216,19 +232,35 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) {
*/
#define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD__)
+/**
+ * NetBSD even resolves the executable before file actions with posix_spawn()!
+ */
+#define BFS_POSIX_SPAWN_AFTER_FCHDIR !__NetBSD__
+
int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) {
struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR);
if (!action) {
return -1;
}
+#if __APPLE__
+ // macOS has a bug that causes EBADF when an fchdir() action refers to a
+ // file opened by the file actions
+ for_slist (struct bfs_spawn_action, prev, ctx) {
+ if (fd == prev->out_fd) {
+ bfs_spawn_clear_posix(ctx);
+ break;
+ }
+ }
+#endif
+
#if BFS_HAS_POSIX_SPAWN_ADDFCHDIR
# define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir
#elif BFS_HAS_POSIX_SPAWN_ADDFCHDIR_NP
# define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir_np
#endif
-#if _POSIX_SPAWN > 0 && defined(BFS_POSIX_SPAWN_FCHDIR)
+#if BFS_POSIX_SPAWN >= 0 && defined(BFS_POSIX_SPAWN_ADDFCHDIR)
if (ctx->flags & BFS_SPAWN_USE_POSIX) {
errno = BFS_POSIX_SPAWN_ADDFCHDIR(&ctx->actions, fd);
if (errno != 0) {
@@ -237,7 +269,7 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) {
}
}
#else
- ctx->flags &= ~BFS_SPAWN_USE_POSIX;
+ bfs_spawn_clear_posix(ctx);
#endif
action->in_fd = fd;
@@ -261,7 +293,7 @@ int bfs_spawn_setrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit
goto fail;
}
#else
- ctx->flags &= ~BFS_SPAWN_USE_POSIX;
+ bfs_spawn_clear_posix(ctx);
#endif
action->resource = resource;
@@ -385,18 +417,40 @@ static bool bfs_resolve_relative(const struct bfs_resolver *res) {
return false;
}
+/** Check if the actions include fchdir(). */
+static bool bfs_spawn_will_chdir(const struct bfs_spawn *ctx) {
+ if (ctx) {
+ for_slist (const struct bfs_spawn_action, action, ctx) {
+ if (action->op == BFS_SPAWN_FCHDIR) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/** Check if we can call xfaccessat() before file actions. */
+static bool bfs_can_access_early(const struct bfs_resolver *res, const struct bfs_spawn *ctx) {
+ if (res->exe[0] == '/') {
+ return true;
+ }
+
+ if (bfs_spawn_will_chdir(ctx)) {
+ return false;
+ }
+
+ return true;
+}
+
/** Check if we can resolve the executable before file actions. */
static bool bfs_can_resolve_early(const struct bfs_resolver *res, const struct bfs_spawn *ctx) {
if (!bfs_resolve_relative(res)) {
return true;
}
- if (ctx) {
- for_slist (const struct bfs_spawn_action, action, ctx) {
- if (action->op == BFS_SPAWN_FCHDIR) {
- return false;
- }
- }
+ if (bfs_spawn_will_chdir(ctx)) {
+ return false;
}
return true;
@@ -426,17 +480,19 @@ static int bfs_resolve_early(struct bfs_resolver *res, const char *exe, const st
};
if (bfs_can_skip_resolve(res, ctx)) {
- // Do this check eagerly, even though posix_spawn()/execv() also
- // would, because:
- //
- // - faccessat() is faster than fork()/clone() + execv()
- // - posix_spawn() is not guaranteed to report ENOENT
- if (xfaccessat(AT_FDCWD, exe, X_OK) == 0) {
- res->done = true;
- return 0;
- } else {
- return -1;
+ if (bfs_can_access_early(res, ctx)) {
+ // Do this check eagerly, even though posix_spawn()/execv() also
+ // would, because:
+ //
+ // - faccessat() is faster than fork()/clone() + execv()
+ // - posix_spawn() is not guaranteed to report ENOENT
+ if (xfaccessat(AT_FDCWD, exe, X_OK) != 0) {
+ return -1;
+ }
}
+
+ res->done = true;
+ return 0;
}
res->path = getenv("PATH");
@@ -482,7 +538,7 @@ fail:
return -1;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
/** bfs_spawn() implementation using posix_spawn(). */
static pid_t bfs_posix_spawn(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) {
@@ -513,13 +569,20 @@ static bool bfs_use_posix_spawn(const struct bfs_resolver *res, const struct bfs
}
#endif
+#if !BFS_POSIX_SPAWN_AFTER_FCHDIR
+ if (res->exe[0] != '/' && bfs_spawn_will_chdir(ctx)) {
+ return false;
+ }
+#endif
+
return true;
}
-#endif // _POSIX_SPAWN > 0
+#endif // BFS_POSIX_SPAWN >= 0
/** Actually exec() the new process. */
-static noreturn void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) {
+_noreturn
+static void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, const sigset_t *mask, int pipefd[2]) {
xclose(pipefd[0]);
for_slist (const struct bfs_spawn_action, action, ctx) {
@@ -580,6 +643,18 @@ static noreturn void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_s
goto fail;
}
+ // Reset signal handlers to their original values before we unblock
+ // signals, so that handlers don't run in both the parent and the child
+ if (sigreset() != 0) {
+ goto fail;
+ }
+
+ // Restore the original signal mask for the child process
+ errno = pthread_sigmask(SIG_SETMASK, mask, NULL);
+ if (errno != 0) {
+ goto fail;
+ }
+
execve(res->exe, argv, envp);
fail:;
@@ -612,15 +687,19 @@ static pid_t bfs_fork_spawn(struct bfs_resolver *res, const struct bfs_spawn *ct
goto fail;
}
+#if BFS_HAS__FORK
+ pid_t pid = _Fork();
+#else
pid_t pid = fork();
+#endif
if (pid == 0) {
// Child
- bfs_spawn_exec(res, ctx, argv, envp, pipefd);
+ bfs_spawn_exec(res, ctx, argv, envp, &old_mask, pipefd);
}
// Restore the original signal mask
- int ret = pthread_sigmask(SIG_SETMASK, &old_mask, NULL);
- bfs_everify(ret == 0, "pthread_sigmask()");
+ errno = pthread_sigmask(SIG_SETMASK, &old_mask, NULL);
+ bfs_everify(errno == 0, "pthread_sigmask()");
if (pid < 0) {
// fork() failed
@@ -648,7 +727,7 @@ fail:
/** Call the right bfs_spawn() implementation. */
static pid_t bfs_spawn_impl(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) {
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
if (bfs_use_posix_spawn(res, ctx)) {
return bfs_posix_spawn(res, ctx, argv, envp);
}