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