dte test coverage


Directory: ./
File: src/spawn.c
Date: 2025-09-07 23:01:39
Exec Total Coverage
Lines: 105 220 47.7%
Functions: 7 11 63.6%
Branches: 53 128 41.4%

Line Branch Exec Source
1 #include <errno.h>
2 #include <poll.h>
3 #include <stddef.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include "spawn.h"
8 #include "command/error.h"
9 #include "regexp.h"
10 #include "util/debug.h"
11 #include "util/fd.h"
12 #include "util/fork-exec.h"
13 #include "util/str-util.h"
14 #include "util/strtonum.h"
15 #include "util/xmalloc.h"
16 #include "util/xreadwrite.h"
17 #include "util/xstdio.h"
18
19 enum {
20 IN = STDIN_FILENO,
21 OUT = STDOUT_FILENO,
22 ERR = STDERR_FILENO,
23 };
24
25 static void handle_error_msg(const Compiler *c, MessageList *msgs, char *str)
26 {
27 if (str[0] == '\0' || str[0] == '\n') {
28 return;
29 }
30
31 size_t str_len = str_replace_byte(str, '\t', ' ');
32 if (str[str_len - 1] == '\n') {
33 str[--str_len] = '\0';
34 }
35
36 for (size_t i = 0, n = c->error_formats.count; i < n; i++) {
37 const ErrorFormat *p = c->error_formats.ptrs[i];
38 regmatch_t m[ERRORFMT_CAPTURE_MAX];
39 if (!regexp_exec(&p->re, str, str_len, ARRAYLEN(m), m, 0)) {
40 continue;
41 }
42 if (p->ignore) {
43 return;
44 }
45
46 int mi = (int)p->capture_index[ERRFMT_MESSAGE];
47 if (m[mi].rm_so < 0) {
48 mi = 0;
49 }
50
51 Message *msg = new_message(str + m[mi].rm_so, m[mi].rm_eo - m[mi].rm_so);
52 msg->loc = new_file_location(NULL, 0, 0, 0);
53
54 int fi = (int)p->capture_index[ERRFMT_FILE];
55 if (fi >= 0 && m[fi].rm_so >= 0) {
56 msg->loc->filename = xstrslice(str, m[fi].rm_so, m[fi].rm_eo);
57
58 unsigned long *const ptrs[] = {
59 [ERRFMT_LINE] = &msg->loc->line,
60 [ERRFMT_COLUMN] = &msg->loc->column,
61 };
62
63 static_assert(ARRAYLEN(ptrs) == 3);
64 static_assert(ERRFMT_LINE == 1);
65 static_assert(ERRFMT_COLUMN == 2);
66
67 for (size_t j = ERRFMT_LINE; j < ARRAYLEN(ptrs); j++) {
68 int ci = (int)p->capture_index[j];
69 if (ci >= 0 && m[ci].rm_so >= 0) {
70 size_t len = m[ci].rm_eo - m[ci].rm_so;
71 unsigned long val;
72 if (len == buf_parse_ulong(str + m[ci].rm_so, len, &val)) {
73 *ptrs[j] = val;
74 }
75 }
76 }
77 }
78
79 add_message(msgs, msg);
80 return;
81 }
82
83 add_message(msgs, new_message(str, str_len));
84 }
85
86 static void read_errors(const Compiler *c, MessageList *msgs, int fd, bool quiet)
87 {
88 FILE *f = fdopen(fd, "r");
89 if (unlikely(!f)) {
90 return;
91 }
92 char line[4096];
93 while (xfgets(line, sizeof(line), f)) {
94 if (!quiet) {
95 xfputs(line, stderr);
96 }
97 handle_error_msg(c, msgs, line);
98 }
99 fclose(f);
100 }
101
102 11 static void handle_piped_data(int f[3], SpawnContext *ctx)
103 {
104 11 BUG_ON(f[IN] < 0 && f[OUT] < 0 && f[ERR] < 0);
105 11 BUG_ON(IS_STD_FD(f[IN]));
106 11 BUG_ON(IS_STD_FD(f[OUT]));
107 11 BUG_ON(IS_STD_FD(f[ERR]));
108
109
2/2
✓ Branch 0 (12→13) taken 5 times.
✓ Branch 1 (12→16) taken 6 times.
11 if (ctx->input.length == 0) {
110 5 xclose(f[IN]);
111 5 f[IN] = -1;
112
4/4
✓ Branch 0 (14→15) taken 2 times.
✓ Branch 1 (14→16) taken 3 times.
✓ Branch 2 (15→16) taken 1 times.
✓ Branch 3 (15→63) taken 1 times.
5 if (f[OUT] < 0 && f[ERR] < 0) {
113 return;
114 }
115 }
116
117 10 struct pollfd fds[] = {
118 10 {.fd = f[IN], .events = POLLOUT},
119 {.fd = f[OUT], .events = POLLIN},
120 {.fd = f[ERR], .events = POLLIN},
121 };
122
123 10 size_t wlen = 0;
124 25 while (1) {
125
1/2
✗ Branch 0 (19→20) not taken.
✓ Branch 1 (19→38) taken 25 times.
25 if (unlikely(poll(fds, ARRAYLEN(fds), -1) < 0)) {
126 if (errno == EINTR) {
127 continue;
128 }
129 error_msg_errno(ctx->ebuf, "poll");
130 return;
131 }
132
133
2/2
✓ Branch 0 (38→24) taken 50 times.
✓ Branch 1 (38→39) taken 25 times.
75 for (size_t i = 0; i < ARRAYLEN(ctx->outputs); i++) {
134 50 struct pollfd *pfd = fds + i + 1;
135
2/2
✓ Branch 0 (24→25) taken 11 times.
✓ Branch 1 (24→37) taken 39 times.
50 if (pfd->revents & POLLIN) {
136 11 String *output = &ctx->outputs[i];
137 11 char *buf = string_reserve_space(output, 4096);
138 11 ssize_t rc = xread(pfd->fd, buf, output->alloc - output->len);
139
1/2
✗ Branch 0 (27→28) not taken.
✓ Branch 1 (27→30) taken 11 times.
11 if (unlikely(rc < 0)) {
140 error_msg_errno(ctx->ebuf, "read");
141 return;
142 }
143
1/2
✗ Branch 0 (30→31) not taken.
✓ Branch 1 (30→36) taken 11 times.
11 if (rc == 0) { // EOF
144 if (xclose(pfd->fd)) {
145 error_msg_errno(ctx->ebuf, "close");
146 return;
147 }
148 pfd->fd = -1;
149 continue;
150 }
151 11 output->len += rc;
152 }
153 }
154
155
2/2
✓ Branch 0 (39→40) taken 4 times.
✓ Branch 1 (39→50) taken 21 times.
25 if (fds[IN].revents & POLLOUT) {
156 4 ssize_t rc = xwrite(fds[IN].fd, ctx->input.data + wlen, ctx->input.length - wlen);
157
1/2
✗ Branch 0 (41→42) not taken.
✓ Branch 1 (41→44) taken 4 times.
4 if (unlikely(rc < 0)) {
158 error_msg_errno(ctx->ebuf, "write");
159 return;
160 }
161 4 wlen += (size_t) rc;
162
1/2
✓ Branch 0 (44→45) taken 4 times.
✗ Branch 1 (44→50) not taken.
4 if (wlen == ctx->input.length) {
163
1/2
✗ Branch 0 (46→47) not taken.
✓ Branch 1 (46→49) taken 4 times.
4 if (xclose(fds[IN].fd)) {
164 error_msg_errno(ctx->ebuf, "close");
165 return;
166 }
167 4 fds[IN].fd = -1;
168 }
169 }
170
171 25 size_t active_fds = ARRAYLEN(fds);
172
2/2
✓ Branch 0 (61→51) taken 75 times.
✓ Branch 1 (61→62) taken 25 times.
100 for (size_t i = 0; i < ARRAYLEN(fds); i++) {
173 75 int rev = fds[i].revents;
174
3/4
✓ Branch 0 (51→52) taken 38 times.
✓ Branch 1 (51→53) taken 37 times.
✗ Branch 2 (52→53) not taken.
✓ Branch 3 (52→54) taken 38 times.
75 if (fds[i].fd < 0 || rev & POLLNVAL) {
175 37 fds[i].fd = -1;
176 37 active_fds--;
177 37 continue;
178 }
179
3/4
✓ Branch 0 (54→55) taken 38 times.
✗ Branch 1 (54→56) not taken.
✓ Branch 2 (55→56) taken 16 times.
✓ Branch 3 (55→60) taken 22 times.
38 if (rev & POLLERR || (rev & (POLLHUP | POLLIN)) == POLLHUP) {
180
1/2
✗ Branch 0 (57→58) not taken.
✓ Branch 1 (57→59) taken 16 times.
16 if (xclose(fds[i].fd)) {
181 error_msg_errno(ctx->ebuf, "close");
182 }
183 16 fds[i].fd = -1;
184 16 active_fds--;
185 }
186 }
187
2/2
✓ Branch 0 (62→17) taken 15 times.
✓ Branch 1 (62→63) taken 10 times.
25 if (active_fds == 0) {
188 return;
189 }
190 }
191 }
192
193 36 static int open_dev_null(ErrorBuffer *ebuf, int flags)
194 {
195 36 int fd = xopen("/dev/null", flags | O_CLOEXEC, 0);
196
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 36 times.
36 if (unlikely(fd < 0)) {
197 error_msg_errno(ebuf, "Error opening /dev/null");
198 }
199
200 // Prevented by init_std_fds() and relied upon by fork_exec()
201 36 BUG_ON(fd <= STDERR_FILENO);
202
203 36 return fd;
204 }
205
206 21 static bool open_pipe(ErrorBuffer *ebuf, int fds[2])
207 {
208
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 21 times.
21 if (unlikely(xpipe2(fds, O_CLOEXEC) != 0)) {
209 return error_msg_errno(ebuf, "xpipe2");
210 }
211
212 // Prevented by init_std_fds() and relied upon by fork_exec()
213 21 BUG_ON(fds[0] <= STDERR_FILENO);
214 21 BUG_ON(fds[1] <= STDERR_FILENO);
215
216 return true;
217 }
218
219 static int handle_child_error(ErrorBuffer *ebuf, pid_t pid)
220 {
221 int ret = wait_child(pid);
222 if (ret < 0) {
223 error_msg_errno(ebuf, "waitpid");
224 } else if (ret >= 256) {
225 int sig = ret >> 8;
226 const char *str = strsignal(sig);
227 error_msg(ebuf, "Child received signal %d (%s)", sig, str ? str : "??");
228 } else if (ret) {
229 error_msg(ebuf, "Child returned %d", ret);
230 }
231 return ret;
232 }
233
234 1 static void exec_error(SpawnContext *ctx)
235 {
236 1 error_msg(ctx->ebuf, "Unable to exec '%s': %s", ctx->argv[0], strerror(errno));
237 1 }
238
239 bool spawn_compiler(SpawnContext *ctx, const Compiler *c, MessageList *msgs, bool read_stdout)
240 {
241 BUG_ON(!ctx->argv);
242 BUG_ON(!ctx->argv[0]);
243 BUG_ON(!ctx->ebuf);
244
245 int fd[3];
246 fd[IN] = open_dev_null(ctx->ebuf, O_RDONLY);
247 if (fd[IN] < 0) {
248 return false;
249 }
250
251 int dev_null = open_dev_null(ctx->ebuf, O_WRONLY);
252 if (dev_null < 0) {
253 xclose(fd[IN]);
254 return false;
255 }
256
257 int p[2];
258 if (!open_pipe(ctx->ebuf, p)) {
259 xclose(dev_null);
260 xclose(fd[IN]);
261 return false;
262 }
263
264 bool quiet = ctx->quiet;
265 if (read_stdout) {
266 fd[OUT] = p[1];
267 fd[ERR] = quiet ? dev_null : ERR;
268 } else {
269 fd[OUT] = quiet ? dev_null : OUT;
270 fd[ERR] = p[1];
271 }
272
273 pid_t pid = fork_exec(ctx->argv, fd, ctx->lines, ctx->columns, quiet);
274
275 // Note that the write end of the pipe must be closed before
276 // read_errors() is called, otherwise the read end never gets EOF
277 xclose(p[1]);
278
279 if (pid == -1) {
280 exec_error(ctx);
281 } else {
282 read_errors(c, msgs, p[0], quiet);
283 handle_child_error(ctx->ebuf, pid);
284 }
285
286 xclose(p[0]);
287 xclose(dev_null);
288 xclose(fd[IN]);
289 return (pid != -1);
290 }
291
292 // Close each fd only if valid (positive) and not stdin/stdout/stderr
293 86 static void safe_xclose_all(int fds[], size_t nr_fds)
294 {
295
2/2
✓ Branch 0 (6→3) taken 234 times.
✓ Branch 1 (6→7) taken 86 times.
320 for (size_t i = 0; i < nr_fds; i++) {
296
2/2
✓ Branch 0 (3→4) taken 77 times.
✓ Branch 1 (3→5) taken 157 times.
234 if (fds[i] > STDERR_FILENO) {
297 77 xclose(fds[i]);
298 }
299 // Also prevent use-after-close errors
300 234 fds[i] = -1;
301 }
302 86 }
303
304 24 UNITTEST {
305 24 int fds[] = {-2, -3, -4};
306 24 safe_xclose_all(fds, 2);
307 24 BUG_ON(fds[0] != -1);
308 24 BUG_ON(fds[1] != -1);
309 24 BUG_ON(fds[2] != -4);
310 24 safe_xclose_all(fds, 3);
311 24 BUG_ON(fds[2] != -1);
312 24 }
313
314 19 int spawn(SpawnContext *ctx)
315 {
316 19 BUG_ON(!ctx->argv);
317 19 BUG_ON(!ctx->argv[0]);
318 19 BUG_ON(!ctx->ebuf);
319
320 19 int child_fds[3] = {-1, -1, -1};
321 19 int parent_fds[3] = {-1, -1, -1};
322 19 bool quiet = ctx->quiet;
323 19 size_t nr_pipes = 0;
324
325
2/2
✓ Branch 0 (32→9) taken 57 times.
✓ Branch 1 (32→33) taken 19 times.
76 for (size_t i = 0; i < ARRAYLEN(child_fds); i++) {
326
3/4
✓ Branch 0 (9→10) taken 30 times.
✓ Branch 1 (9→12) taken 6 times.
✓ Branch 2 (9→15) taken 21 times.
✗ Branch 3 (9→30) not taken.
57 switch (ctx->actions[i]) {
327 30 case SPAWN_TTY:
328
1/2
✗ Branch 0 (10→11) not taken.
✓ Branch 1 (10→12) taken 30 times.
30 if (!quiet) {
329 child_fds[i] = i;
330 break;
331 }
332 // Fallthrough
333 case SPAWN_NULL:
334 36 child_fds[i] = open_dev_null(ctx->ebuf, O_RDWR);
335
1/2
✗ Branch 0 (13→14) not taken.
✓ Branch 1 (13→31) taken 36 times.
36 if (child_fds[i] < 0) {
336 goto error;
337 }
338 break;
339 21 case SPAWN_PIPE: {
340 21 int p[2];
341
1/2
✗ Branch 0 (16→17) not taken.
✓ Branch 1 (16→18) taken 21 times.
21 if (!open_pipe(ctx->ebuf, p)) {
342 goto error;
343 }
344
2/2
✓ Branch 0 (18→19) taken 16 times.
✓ Branch 1 (18→20) taken 5 times.
21 child_fds[i] = i ? p[1] : p[0];
345
2/2
✓ Branch 0 (21→22) taken 16 times.
✓ Branch 1 (21→23) taken 5 times.
21 parent_fds[i] = i ? p[0] : p[1];
346
1/2
✗ Branch 0 (25→26) not taken.
✓ Branch 1 (25→28) taken 21 times.
21 if (!fd_set_nonblock(parent_fds[i], true)) {
347 error_msg_errno(ctx->ebuf, "fcntl");
348 goto error;
349 }
350 21 nr_pipes++;
351 21 break;
352 }
353 default:
354 BUG("unhandled action type");
355 goto error;
356 }
357 }
358
359 19 pid_t pid = fork_exec(ctx->argv, child_fds, ctx->lines, ctx->columns, quiet);
360
2/2
✓ Branch 0 (34→35) taken 1 times.
✓ Branch 1 (34→37) taken 18 times.
19 if (pid == -1) {
361 1 exec_error(ctx);
362 1 goto error;
363 }
364
365 18 safe_xclose_all(child_fds, ARRAYLEN(child_fds));
366
2/2
✓ Branch 0 (38→39) taken 11 times.
✓ Branch 1 (38→40) taken 7 times.
18 if (nr_pipes > 0) {
367 11 handle_piped_data(parent_fds, ctx);
368 }
369
370 18 safe_xclose_all(parent_fds, ARRAYLEN(parent_fds));
371 18 int err = wait_child(pid);
372
1/2
✗ Branch 0 (42→43) not taken.
✓ Branch 1 (42→47) taken 18 times.
18 if (err < 0) {
373 error_msg_errno(ctx->ebuf, "waitpid");
374 }
375
376 return err;
377
378 1 error:
379 1 safe_xclose_all(child_fds, ARRAYLEN(child_fds));
380 1 safe_xclose_all(parent_fds, ARRAYLEN(parent_fds));
381 1 return -1;
382 }
383