dte test coverage


Directory: ./
File: src/util/fork-exec.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 65 91 71.4%
Functions: 5 5 100.0%
Branches: 33 66 50.0%

Line Branch Exec Source
1 #include <errno.h>
2 #include <signal.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <unistd.h>
6 #include "fork-exec.h"
7 #include "debug.h"
8 #include "fd.h"
9 #include "log.h"
10 #include "terminal/ioctl.h"
11 #include "xreadwrite.h"
12
13 // Reset ignored signal dispositions (i.e. as originally set up by
14 // set_basic_signal_dispositions()) to SIG_DFL
15 21 static bool reset_ignored_signals(void)
16 {
17 // Note that handled signals don't need to be restored here, since
18 // they're necessarily and automatically reset after exec(3p)
19 21 static const int ignored_signals[] = {
20 SIGINT, SIGQUIT, SIGTSTP,
21 SIGTTIN, SIGTTOU, SIGXFSZ,
22 SIGPIPE, SIGUSR1, SIGUSR2,
23 };
24
25 21 struct sigaction action = {.sa_handler = SIG_DFL};
26
1/2
✓ Branch 0 (3→7) taken 21 times.
✗ Branch 1 (3→8) not taken.
21 if (unlikely(sigemptyset(&action.sa_mask) != 0)) {
27 return false;
28 }
29
30
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++) {
31 189 int r = sigaction(ignored_signals[i], &action, NULL);
32
1/2
✓ Branch 0 (5→6) taken 189 times.
✗ Branch 1 (5→8) not taken.
189 if (unlikely(r != 0)) {
33 return false;
34 }
35 }
36
37 return true;
38 }
39
40 21 static noreturn void child_process_exec (
41 const char **argv,
42 const char **env,
43 int fd[3],
44 int error_fd,
45 bool drop_ctty
46 ) {
47 21 int error;
48 21 int nr_fds = 3;
49 21 bool move = error_fd < nr_fds;
50 21 int max = error_fd;
51
52
1/2
✓ Branch 0 (2→3) taken 21 times.
✗ Branch 1 (2→4) not taken.
21 if (drop_ctty) {
53 21 term_drop_controlling_tty(STDIN_FILENO);
54 }
55
56 // Find if we must move fds out of the way
57
2/2
✓ Branch 0 (10→5) taken 63 times.
✓ Branch 1 (10→11) taken 21 times.
84 for (int i = 0; i < nr_fds; i++) {
58
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→7) taken 63 times.
63 if (fd[i] > max) {
59 max = fd[i];
60 }
61
1/2
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 63 times.
63 if (fd[i] < i) {
62 move = true;
63 }
64 }
65
66
1/2
✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→23) taken 21 times.
21 if (move) {
67 int next_free = max + 1;
68 if (error_fd < nr_fds) {
69 error_fd = xdup3(error_fd, next_free++, O_CLOEXEC);
70 if (error_fd < 0) {
71 goto error;
72 }
73 }
74 for (int i = 0; i < nr_fds; i++) {
75 if (fd[i] < i) {
76 fd[i] = xdup3(fd[i], next_free++, O_CLOEXEC);
77 if (fd[i] < 0) {
78 goto error;
79 }
80 }
81 }
82 }
83
84 // Now it is safe to duplicate fds in this order
85
2/2
✓ Branch 0 (32→24) taken 63 times.
✓ Branch 1 (32→33) taken 21 times.
84 for (int i = 0; i < nr_fds; i++) {
86
1/2
✗ Branch 0 (24→25) not taken.
✓ Branch 1 (24→28) taken 63 times.
63 if (i == fd[i]) {
87 // Clear FD_CLOEXEC flag
88 if (!fd_set_cloexec(fd[i], false)) {
89 goto error;
90 }
91 } else {
92
1/2
✗ Branch 0 (29→30) not taken.
✓ Branch 1 (29→31) taken 63 times.
63 if (xdup3(fd[i], i, 0) < 0) {
93 goto error;
94 }
95 }
96 }
97
98
2/2
✓ Branch 0 (33→40) taken 5 times.
✓ Branch 1 (33→41) taken 16 times.
21 if (env) {
99
2/2
✓ Branch 0 (40→34) taken 10 times.
✓ Branch 1 (40→41) taken 5 times.
15 for (size_t i = 0; env[i]; i += 2) {
100 10 const char *name = env[i];
101 10 const char *value = env[i + 1];
102
1/2
✓ Branch 0 (34→35) taken 10 times.
✗ Branch 1 (34→36) not taken.
10 int r = value ? setenv(name, value, 1) : unsetenv(name);
103
1/2
✗ Branch 0 (37→38) not taken.
✓ Branch 1 (37→39) taken 10 times.
10 if (unlikely(r != 0)) {
104 goto error;
105 }
106 }
107 }
108
109
1/2
✗ Branch 0 (42→43) not taken.
✓ Branch 1 (42→44) taken 1 times.
21 if (!reset_ignored_signals()) {
110 goto error;
111 }
112
113 1 execvp(argv[0], (char**)argv);
114
115 1 error:
116 1 error = errno;
117 1 error = xwrite(error_fd, &error, sizeof(error));
118 1 exit(42);
119 }
120
121 21 static pid_t xwaitpid(pid_t pid, int *status, int options)
122 {
123 21 pid_t ret;
124 42 do {
125 21 ret = waitpid(pid, status, options);
126
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));
127 21 return ret;
128 }
129
130 21 pid_t fork_exec(const char **argv, const char **env, int fd[3], bool drop_ctty)
131 {
132 21 int ep[2];
133
1/2
✓ Branch 0 (3→4) taken 21 times.
✗ Branch 1 (3→23) not taken.
21 if (xpipe2(ep, O_CLOEXEC) != 0) {
134 return -1;
135 }
136
137 21 const pid_t pid = fork();
138
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→9) taken 42 times.
42 if (unlikely(pid == -1)) {
139 int saved_errno = errno;
140 xclose(ep[0]);
141 xclose(ep[1]);
142 errno = saved_errno;
143 return -1;
144 }
145
146
2/2
✓ Branch 0 (9→10) taken 21 times.
✓ Branch 1 (9→11) taken 21 times.
42 if (pid == 0) {
147 // Child
148 21 child_process_exec(argv, env, fd, ep[1], drop_ctty);
149 BUG("child_process_exec() should never return");
150 return -1;
151 }
152
153 // Parent
154 21 xclose(ep[1]);
155 21 int error = 0;
156 21 ssize_t rc = xread(ep[0], &error, sizeof(error));
157 21 int xread_errno = errno;
158 21 xclose(ep[0]);
159 21 BUG_ON(rc > sizeof(error));
160
161
2/2
✓ Branch 0 (16→17) taken 1 times.
✓ Branch 1 (16→23) taken 20 times.
21 if (rc == 0) {
162 // Child exec was successful
163 return pid;
164 }
165
166
1/2
✗ Branch 0 (17→18) not taken.
✓ Branch 1 (17→21) taken 1 times.
1 if (unlikely(rc != sizeof(error))) {
167 error = (rc < 0) ? xread_errno : EPIPE;
168 }
169
170 1 int status;
171 1 xwaitpid(pid, &status, 0);
172 1 errno = error;
173 1 return -1;
174 }
175
176 20 int wait_child(pid_t pid)
177 {
178 20 int status;
179
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 20 times.
20 if (unlikely(xwaitpid(pid, &status, 0) < 0)) {
180 return -errno;
181 }
182
183
2/2
✓ Branch 0 (5→6) taken 18 times.
✓ Branch 1 (5→7) taken 2 times.
20 if (likely(WIFEXITED(status))) {
184 18 return WEXITSTATUS(status) & 0xFF;
185 }
186
187
1/2
✓ Branch 0 (7→8) taken 2 times.
✗ Branch 1 (7→9) not taken.
2 if (likely(WIFSIGNALED(status))) {
188 2 return WTERMSIG(status) << 8;
189 }
190
191 LOG_ERROR("unhandled waitpid() status: %d", status);
192 return -EINVAL;
193 }
194