1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
13 #include <sys/resource.h>
14 #include <sys/types.h>
23 * Types of spawn actions.
35 struct bfs_spawn_action {
36 struct bfs_spawn_action *next;
45 int bfs_spawn_init(struct bfs_spawn *ctx) {
51 int bfs_spawn_destroy(struct bfs_spawn *ctx) {
52 for_slist (struct bfs_spawn_action, action, ctx) {
59 int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) {
64 /** Add a spawn action to the chain. */
65 static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) {
66 struct bfs_spawn_action *action = ALLOC(struct bfs_spawn_action);
71 SLIST_ITEM_INIT(action);
76 SLIST_APPEND(ctx, action);
80 int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) {
86 struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_CLOSE);
95 int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) {
96 if (oldfd < 0 || newfd < 0) {
101 struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_DUP2);
103 action->in_fd = oldfd;
104 action->out_fd = newfd;
111 int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) {
117 struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_FCHDIR);
126 int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) {
127 struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_SETRLIMIT);
129 action->resource = resource;
130 action->rlimit = *rl;
137 /** Actually exec() the new process. */
138 static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) {
141 for_slist (const struct bfs_spawn_action, action, ctx) {
142 // Move the error-reporting pipe out of the way if necessary...
143 if (action->out_fd == pipefd[1]) {
144 int fd = dup_cloexec(pipefd[1]);
152 // ... and pretend the pipe doesn't exist
153 if (action->in_fd == pipefd[1]) {
158 switch (action->op) {
159 case BFS_SPAWN_CLOSE:
160 if (close(action->out_fd) != 0) {
165 if (dup2(action->in_fd, action->out_fd) < 0) {
169 case BFS_SPAWN_FCHDIR:
170 if (fchdir(action->in_fd) != 0) {
174 case BFS_SPAWN_SETRLIMIT:
175 if (setrlimit(action->resource, &action->rlimit) != 0) {
182 execve(exe, argv, envp);
188 // In case of a write error, the parent will still see that we exited
189 // unsuccessfully, but won't know why
190 (void)xwrite(pipefd[1], &error, sizeof(error));
196 pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) {
197 extern char **environ;
202 char *resolved = NULL;
203 if (ctx->flags & BFS_SPAWN_USEPATH) {
204 exe = resolved = bfs_spawn_resolve(exe);
210 // Use a pipe to report errors from the child
212 if (pipe_cloexec(pipefd) != 0) {
219 close_quietly(pipefd[1]);
220 close_quietly(pipefd[0]);
223 } else if (pid == 0) {
225 bfs_spawn_exec(exe, ctx, argv, envp, pipefd);
233 ssize_t nbytes = xread(pipefd[0], &error, sizeof(error));
235 if (nbytes == sizeof(error)) {
237 xwaitpid(pid, &wstatus, 0);
245 char *bfs_spawn_resolve(const char *exe) {
246 if (strchr(exe, '/')) {
250 const char *path = getenv("PATH");
252 char *confpath = NULL;
254 #if defined(_CS_PATH)
255 path = confpath = xconfstr(_CS_PATH);
256 #elif defined(_PATH_DEFPATH)
257 path = _PATH_DEFPATH;
269 const char *end = strchr(path, ':');
270 size_t len = end ? (size_t)(end - path) : strlen(path);
272 // POSIX 8.3: "A zero-length prefix is a legacy feature that
273 // indicates the current working directory."
279 size_t total = len + 1 + strlen(exe) + 1;
281 char *grown = realloc(ret, total);
289 memcpy(ret, path, len);
290 if (ret[len - 1] != '/') {
293 strcpy(ret + len, exe);
295 if (xfaccessat(AT_FDCWD, ret, X_OK) == 0) {