1 /****************************************************************************
2 * bfs *
3 * Copyright (C) 2018-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 "spawn.h"
18 #include "util.h"
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26
27 /**
28 * Types of spawn actions.
29 */
30 enum bfs_spawn_op {
31 BFS_SPAWN_CLOSE,
32 BFS_SPAWN_DUP2,
33 BFS_SPAWN_FCHDIR,
34 BFS_SPAWN_SETRLIMIT,
35 };
36
37 /**
38 * A spawn action.
39 */
40 struct bfs_spawn_action {
41 struct bfs_spawn_action *next;
42
43 enum bfs_spawn_op op;
44 int in_fd;
45 int out_fd;
46 int resource;
47 struct rlimit rlimit;
48 };
49
bfs_spawn_init(struct bfs_spawn * ctx)50 int bfs_spawn_init(struct bfs_spawn *ctx) {
51 ctx->flags = 0;
52 ctx->actions = NULL;
53 ctx->tail = &ctx->actions;
54 return 0;
55 }
56
bfs_spawn_destroy(struct bfs_spawn * ctx)57 int bfs_spawn_destroy(struct bfs_spawn *ctx) {
58 struct bfs_spawn_action *action = ctx->actions;
59 while (action) {
60 struct bfs_spawn_action *next = action->next;
61 free(action);
62 action = next;
63 }
64 return 0;
65 }
66
bfs_spawn_setflags(struct bfs_spawn * ctx,enum bfs_spawn_flags flags)67 int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) {
68 ctx->flags = flags;
69 return 0;
70 }
71
72 /** Add a spawn action to the chain. */
bfs_spawn_add(struct bfs_spawn * ctx,enum bfs_spawn_op op)73 static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) {
74 struct bfs_spawn_action *action = malloc(sizeof(*action));
75 if (action) {
76 action->next = NULL;
77 action->op = op;
78 action->in_fd = -1;
79 action->out_fd = -1;
80
81 *ctx->tail = action;
82 ctx->tail = &action->next;
83 }
84 return action;
85 }
86
bfs_spawn_addclose(struct bfs_spawn * ctx,int fd)87 int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) {
88 if (fd < 0) {
89 errno = EBADF;
90 return -1;
91 }
92
93 struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_CLOSE);
94 if (action) {
95 action->out_fd = fd;
96 return 0;
97 } else {
98 return -1;
99 }
100 }
101
bfs_spawn_adddup2(struct bfs_spawn * ctx,int oldfd,int newfd)102 int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) {
103 if (oldfd < 0 || newfd < 0) {
104 errno = EBADF;
105 return -1;
106 }
107
108 struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_DUP2);
109 if (action) {
110 action->in_fd = oldfd;
111 action->out_fd = newfd;
112 return 0;
113 } else {
114 return -1;
115 }
116 }
117
bfs_spawn_addfchdir(struct bfs_spawn * ctx,int fd)118 int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) {
119 if (fd < 0) {
120 errno = EBADF;
121 return -1;
122 }
123
124 struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_FCHDIR);
125 if (action) {
126 action->in_fd = fd;
127 return 0;
128 } else {
129 return -1;
130 }
131 }
132
bfs_spawn_addsetrlimit(struct bfs_spawn * ctx,int resource,const struct rlimit * rl)133 int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) {
134 struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_SETRLIMIT);
135 if (action) {
136 action->resource = resource;
137 action->rlimit = *rl;
138 return 0;
139 } else {
140 return -1;
141 }
142 }
143
144 /** Actually exec() the new process. */
bfs_spawn_exec(const char * exe,const struct bfs_spawn * ctx,char ** argv,char ** envp,int pipefd[2])145 static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) {
146 int error;
147 const struct bfs_spawn_action *actions = ctx ? ctx->actions : NULL;
148
149 xclose(pipefd[0]);
150
151 for (const struct bfs_spawn_action *action = actions; action; action = action->next) {
152 // Move the error-reporting pipe out of the way if necessary...
153 if (action->out_fd == pipefd[1]) {
154 int fd = dup_cloexec(pipefd[1]);
155 if (fd < 0) {
156 goto fail;
157 }
158 xclose(pipefd[1]);
159 pipefd[1] = fd;
160 }
161
162 // ... and pretend the pipe doesn't exist
163 if (action->in_fd == pipefd[1]) {
164 errno = EBADF;
165 goto fail;
166 }
167
168 switch (action->op) {
169 case BFS_SPAWN_CLOSE:
170 if (close(action->out_fd) != 0) {
171 goto fail;
172 }
173 break;
174 case BFS_SPAWN_DUP2:
175 if (dup2(action->in_fd, action->out_fd) < 0) {
176 goto fail;
177 }
178 break;
179 case BFS_SPAWN_FCHDIR:
180 if (fchdir(action->in_fd) != 0) {
181 goto fail;
182 }
183 break;
184 case BFS_SPAWN_SETRLIMIT:
185 if (setrlimit(action->resource, &action->rlimit) != 0) {
186 goto fail;
187 }
188 break;
189 }
190 }
191
192 execve(exe, argv, envp);
193
194 fail:
195 error = errno;
196
197 // In case of a write error, the parent will still see that we exited
198 // unsuccessfully, but won't know why
199 (void) xwrite(pipefd[1], &error, sizeof(error));
200
201 xclose(pipefd[1]);
202 _Exit(127);
203 }
204
bfs_spawn(const char * exe,const struct bfs_spawn * ctx,char ** argv,char ** envp)205 pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) {
206 extern char **environ;
207 if (!envp) {
208 envp = environ;
209 }
210
211 enum bfs_spawn_flags flags = ctx ? ctx->flags : 0;
212 char *resolved = NULL;
213 if (flags & BFS_SPAWN_USEPATH) {
214 exe = resolved = bfs_spawn_resolve(exe);
215 if (!resolved) {
216 return -1;
217 }
218 }
219
220 // Use a pipe to report errors from the child
221 int pipefd[2];
222 if (pipe_cloexec(pipefd) != 0) {
223 free(resolved);
224 return -1;
225 }
226
227 pid_t pid = fork();
228 if (pid < 0) {
229 close_quietly(pipefd[1]);
230 close_quietly(pipefd[0]);
231 free(resolved);
232 return -1;
233 } else if (pid == 0) {
234 // Child
235 bfs_spawn_exec(exe, ctx, argv, envp, pipefd);
236 }
237
238 // Parent
239 xclose(pipefd[1]);
240 free(resolved);
241
242 int error;
243 ssize_t nbytes = xread(pipefd[0], &error, sizeof(error));
244 xclose(pipefd[0]);
245 if (nbytes == sizeof(error)) {
246 int wstatus;
247 waitpid(pid, &wstatus, 0);
248 errno = error;
249 return -1;
250 }
251
252 return pid;
253 }
254
bfs_spawn_resolve(const char * exe)255 char *bfs_spawn_resolve(const char *exe) {
256 if (strchr(exe, '/')) {
257 return strdup(exe);
258 }
259
260 const char *path = getenv("PATH");
261
262 char *confpath = NULL;
263 if (!path) {
264 path = confpath = xconfstr(_CS_PATH);
265 if (!path) {
266 return NULL;
267 }
268 }
269
270 size_t cap = 0;
271 char *ret = NULL;
272 while (true) {
273 const char *end = strchr(path, ':');
274 size_t len = end ? (size_t)(end - path) : strlen(path);
275
276 // POSIX 8.3: "A zero-length prefix is a legacy feature that
277 // indicates the current working directory."
278 if (len == 0) {
279 path = ".";
280 len = 1;
281 }
282
283 size_t total = len + 1 + strlen(exe) + 1;
284 if (cap < total) {
285 char *grown = realloc(ret, total);
286 if (!grown) {
287 goto fail;
288 }
289 ret = grown;
290 cap = total;
291 }
292
293 memcpy(ret, path, len);
294 if (ret[len - 1] != '/') {
295 ret[len++] = '/';
296 }
297 strcpy(ret + len, exe);
298
299 if (xfaccessat(AT_FDCWD, ret, X_OK) == 0) {
300 break;
301 }
302
303 if (!end) {
304 errno = ENOENT;
305 goto fail;
306 }
307
308 path = end + 1;
309 }
310
311 free(confpath);
312 return ret;
313
314 fail:
315 free(confpath);
316 free(ret);
317 return NULL;
318 }
319