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