dte test coverage


Directory: ./
File: src/util/fork-exec.c
Date: 2025-07-10 06:26:10
Exec Total Coverage
Lines: 61 70 87.1%
Functions: 5 5 100.0%
Branches: 26 42 61.9%

Line Branch Exec Source
1 #include "feature.h"
2 #include <errno.h>
3 #include <signal.h>
4 #include <stdlib.h>
5 #include <sys/wait.h>
6 #include <unistd.h>
7 #include "fork-exec.h"
8 #include "debug.h"
9 #include "fd.h"
10 #include "log.h"
11 #include "numtostr.h"
12 #include "terminal/ioctl.h"
13 #include "xreadwrite.h"
14
15 // Reset ignored signal dispositions (i.e. as originally set up by
16 // set_basic_signal_dispositions()) to SIG_DFL
17 21 static bool reset_ignored_signals(void)
18 {
19 // Note that handled signals don't need to be restored here, since
20 // they're necessarily and automatically reset after exec(3p)
21 21 static const int ignored_signals[] = {
22 SIGINT, SIGQUIT, SIGTSTP,
23 SIGTTIN, SIGTTOU, SIGXFSZ,
24 SIGPIPE, SIGUSR1, SIGUSR2,
25 };
26
27 21 struct sigaction action = {.sa_handler = SIG_DFL};
28
1/2
✓ Branch 0 (3→7) taken 21 times.
✗ Branch 1 (3→8) not taken.
21 if (unlikely(sigemptyset(&action.sa_mask) != 0)) {
29 return false;
30 }
31
32
2/2
✓ Branch 0 (7→4) taken 189 times.
✓ Branch 1 (7→8) taken 21 times.
210 for (size_t i = 0; i < ARRAYLEN(ignored_signals); i++) {
33 189 int r = sigaction(ignored_signals[i], &action, NULL);
34
1/2
✓ Branch 0 (5→6) taken 189 times.
✗ Branch 1 (5→8) not taken.
189 if (unlikely(r != 0)) {
35 return false;
36 }
37 }
38
39 return true;
40 }
41
42 // NOLINTNEXTLINE(readability-function-size)
43 21 static noreturn void child_process_exec (
44 const char **argv,
45 const int fd[3],
46 int error_fd, // Pipe to parent, for communicating pre-exec errors
47 unsigned int lines,
48 unsigned int columns,
49 bool drop_ctty
50 ) {
51
1/2
✓ Branch 0 (2→3) taken 21 times.
✗ Branch 1 (2→11) not taken.
21 if (drop_ctty) {
52 21 term_drop_controlling_tty(STDIN_FILENO);
53 }
54
55
2/2
✓ Branch 0 (12→4) taken 63 times.
✓ Branch 1 (12→13) taken 21 times.
84 for (int i = STDIN_FILENO; i <= STDERR_FILENO; i++) {
56 63 int f = fd[i];
57
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 63 times.
63 bool ok = (i == f) ? fd_set_cloexec(f, false) : xdup3(f, i, 0) >= 0;
58
1/2
✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→10) taken 63 times.
63 if (unlikely(!ok)) {
59 goto error;
60 }
61 }
62
63
2/2
✓ Branch 0 (13→14) taken 7 times.
✓ Branch 1 (13→16) taken 14 times.
21 if (lines) {
64 7 setenv("LINES", uint_to_str(lines), 1);
65 }
66
2/2
✓ Branch 0 (16→17) taken 7 times.
✓ Branch 1 (16→19) taken 14 times.
21 if (columns) {
67 7 setenv("COLUMNS", uint_to_str(columns), 1);
68 }
69
70
1/2
✗ Branch 0 (20→21) not taken.
✓ Branch 1 (20→22) taken 1 times.
21 if (unlikely(!reset_ignored_signals())) {
71 goto error;
72 }
73
74 1 execvp(argv[0], (char**)argv);
75
76 1 error:;
77 1 int error = errno;
78 1 error = xwrite(error_fd, &error, sizeof(error));
79 1 exit(42);
80 }
81
82 21 static pid_t xwaitpid(pid_t pid, int *status, int options)
83 {
84 21 pid_t ret;
85 42 do {
86 21 ret = waitpid(pid, status, options);
87
1/4
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 21 times.
✗ Branch 2 (5→3) not taken.
✗ Branch 3 (5→6) not taken.
21 } while (unlikely(ret < 0 && errno == EINTR));
88 21 return ret;
89 }
90
91 21 pid_t fork_exec (
92 const char **argv,
93 int fd[3],
94 unsigned int lines,
95 unsigned int columns,
96 bool drop_ctty
97 ) {
98 // Create an "error pipe" before forking, so that child_process_exec()
99 // can signal pre-exec errors and allow the parent differentiate them
100 // from a successful exec(3) with a non-zero exit status
101 21 int ep[2];
102
1/2
✓ Branch 0 (3→4) taken 21 times.
✗ Branch 1 (3→33) not taken.
21 if (xpipe2(ep, O_CLOEXEC) != 0) {
103 return -1;
104 }
105
106 21 BUG_ON(ep[0] <= STDERR_FILENO);
107 21 BUG_ON(ep[1] <= STDERR_FILENO);
108 21 BUG_ON(fd[0] <= STDERR_FILENO && fd[0] != 0);
109 21 BUG_ON(fd[1] <= STDERR_FILENO && fd[1] != 1);
110 21 BUG_ON(fd[2] <= STDERR_FILENO && fd[2] != 2);
111
112 21 const pid_t pid = fork();
113
1/2
✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→19) taken 42 times.
42 if (unlikely(pid == -1)) {
114 xclose(ep[0]);
115 xclose(ep[1]);
116 return -1;
117 }
118
119
2/2
✓ Branch 0 (19→20) taken 21 times.
✓ Branch 1 (19→21) taken 21 times.
42 if (pid == 0) {
120 // Child
121 21 child_process_exec(argv, fd, ep[1], lines, columns, drop_ctty);
122 BUG("child_process_exec() should never return");
123 return -1;
124 }
125
126 // Parent
127 21 xclose(ep[1]);
128 21 int error = 0;
129 21 ssize_t rc = xread(ep[0], &error, sizeof(error));
130 21 int xread_errno = errno;
131 21 xclose(ep[0]);
132 21 BUG_ON(rc > sizeof(error));
133
134
2/2
✓ Branch 0 (26→27) taken 1 times.
✓ Branch 1 (26→33) taken 20 times.
21 if (rc == 0) {
135 // Child exec was successful
136 return pid;
137 }
138
139
1/2
✗ Branch 0 (27→28) not taken.
✓ Branch 1 (27→31) taken 1 times.
1 if (unlikely(rc != sizeof(error))) {
140 error = (rc < 0) ? xread_errno : EPIPE;
141 }
142
143 1 int status;
144 1 xwaitpid(pid, &status, 0);
145 1 errno = error;
146 1 return -1;
147 }
148
149 20 int wait_child(pid_t pid)
150 {
151 20 int status;
152
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 20 times.
20 if (unlikely(xwaitpid(pid, &status, 0) < 0)) {
153 return -errno;
154 }
155
156
2/2
✓ Branch 0 (5→6) taken 18 times.
✓ Branch 1 (5→7) taken 2 times.
20 if (likely(WIFEXITED(status))) {
157 18 return WEXITSTATUS(status) & 0xFF;
158 }
159
160
1/2
✓ Branch 0 (7→8) taken 2 times.
✗ Branch 1 (7→9) not taken.
2 if (likely(WIFSIGNALED(status))) {
161 2 return WTERMSIG(status) << 8;
162 }
163
164 LOG_ERROR("unhandled waitpid() status: %d", status);
165 return -EINVAL;
166 }
167