v2 / vlib / os / execute_capture_nix.h
178 lines · 173 sloc · 5.81 KB · b6dfae560188a549bedd098464d43b22170fcf99
Raw
1// v_os_execute_set_cloexec marks an fd as close-on-exec. When multiple threads
2// each call os.execute, every pipe() they create is briefly visible to all of
3// them; without FD_CLOEXEC, one thread's spawned child can inherit another
4// thread's pipe write end, keeping that pipe open after the intended writer
5// exits. The reader then never observes EOF and the captured output ends up
6// empty (seen on macOS arm64 in CI). Setting CLOEXEC closes the fd
7// automatically across exec, which fixes the race for both fork/execvp and
8// posix_spawn paths.
9static void v_os_execute_set_cloexec(int fd) {
10 int flags = fcntl(fd, F_GETFD, 0);
11 if (flags >= 0) {
12 fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
13 }
14}
15
16#if defined(__ANDROID__) && (!defined(__ANDROID_API__) || __ANDROID_API__ < 28)
17// Android API levels below 28 do not provide posix_spawn(). Fall back to
18// fork()/execvp() with a pipe; this is what popen() does internally and
19// is sufficient for capturing the merged stdout+stderr of a shell command.
20static int v_os_execute_capture_start(const char *cmd, int *child_pid, int *read_fd) {
21 int pipefd[2];
22 if (pipe(pipefd) != 0) {
23 return -1;
24 }
25 v_os_execute_set_cloexec(pipefd[0]);
26 v_os_execute_set_cloexec(pipefd[1]);
27 pid_t pid = fork();
28 if (pid < 0) {
29 close(pipefd[0]);
30 close(pipefd[1]);
31 return -1;
32 }
33 if (pid == 0) {
34 // child: redirect stdout+stderr to the pipe, then exec the shell.
35 // dup2 clears FD_CLOEXEC on the destination, so STDOUT/STDERR stay
36 // open across exec while the original pipe fds are auto-closed.
37 dup2(pipefd[1], STDOUT_FILENO);
38 dup2(pipefd[1], STDERR_FILENO);
39 close(pipefd[0]);
40 close(pipefd[1]);
41 execlp("sh", "sh", "-c", cmd, (char *)NULL);
42 _exit(127);
43 }
44 close(pipefd[1]);
45 *child_pid = (int)pid;
46 *read_fd = pipefd[0];
47 return 0;
48}
49
50static int v_os_exec_capture_start(char *const argv[], int *child_pid, int *read_fd) {
51 if (argv == NULL || argv[0] == NULL) {
52 return -1;
53 }
54 int pipefd[2];
55 if (pipe(pipefd) != 0) {
56 return -1;
57 }
58 v_os_execute_set_cloexec(pipefd[0]);
59 v_os_execute_set_cloexec(pipefd[1]);
60 pid_t pid = fork();
61 if (pid < 0) {
62 close(pipefd[0]);
63 close(pipefd[1]);
64 return -1;
65 }
66 if (pid == 0) {
67 dup2(pipefd[1], STDOUT_FILENO);
68 dup2(pipefd[1], STDERR_FILENO);
69 close(pipefd[0]);
70 close(pipefd[1]);
71 execvp(argv[0], argv);
72 _exit(127);
73 }
74 close(pipefd[1]);
75 *child_pid = (int)pid;
76 *read_fd = pipefd[0];
77 return 0;
78}
79#else
80// Use opaque void* declarations for posix_spawn instead of #include <spawn.h>.
81// Including <spawn.h> transitively pulls in <features.h>/<sys/cdefs.h>, which
82// breaks under musl-gcc on the Ubuntu docker image where <sys/cdefs.h> is not
83// on the include path. The 128-byte buffer is comfortably larger than the
84// real posix_spawn_file_actions_t on glibc (~80 bytes) and musl (~40 bytes);
85// posix_spawn_file_actions_init() initializes the buffer, so its true layout
86// is not needed here. We pass NULL for posix_spawnattr_t, so it is not
87// declared. Calling these via void* is ABI-compatible: pointer parameters
88// are passed identically regardless of pointee type.
89typedef struct { unsigned char _opaque[128]; } v_posix_spawn_file_actions_t;
90#ifdef __cplusplus
91extern "C" {
92#endif
93extern int posix_spawn(pid_t *, const char *, const void *, const void *, char *const [], char *const []);
94extern int posix_spawnp(pid_t *, const char *, const void *, const void *, char *const [], char *const []);
95extern int posix_spawn_file_actions_init(void *);
96extern int posix_spawn_file_actions_destroy(void *);
97extern int posix_spawn_file_actions_adddup2(void *, int, int);
98extern int posix_spawn_file_actions_addclose(void *, int);
99
100extern char **environ;
101#ifdef __cplusplus
102}
103#endif
104
105static int v_os_execute_capture_start(const char *cmd, int *child_pid, int *read_fd) {
106 int pipefd[2];
107 if (pipe(pipefd) != 0) {
108 return -1;
109 }
110 v_os_execute_set_cloexec(pipefd[0]);
111 v_os_execute_set_cloexec(pipefd[1]);
112 v_posix_spawn_file_actions_t actions;
113 if (posix_spawn_file_actions_init(&actions) != 0) {
114 close(pipefd[0]);
115 close(pipefd[1]);
116 return -1;
117 }
118 int err = 0;
119 if ((err = posix_spawn_file_actions_adddup2(&actions, pipefd[1], STDOUT_FILENO)) != 0
120 || (err = posix_spawn_file_actions_adddup2(&actions, pipefd[1], STDERR_FILENO)) != 0
121 || (err = posix_spawn_file_actions_addclose(&actions, pipefd[0])) != 0
122 || (err = posix_spawn_file_actions_addclose(&actions, pipefd[1])) != 0) {
123 posix_spawn_file_actions_destroy(&actions);
124 close(pipefd[0]);
125 close(pipefd[1]);
126 return -1;
127 }
128 char *const argv[] = {(char *)"/bin/sh", (char *)"-c", (char *)cmd, NULL};
129 err = posix_spawn(child_pid, "/bin/sh", &actions, NULL, argv, environ);
130 posix_spawn_file_actions_destroy(&actions);
131 if (err != 0) {
132 close(pipefd[0]);
133 close(pipefd[1]);
134 return -1;
135 }
136 close(pipefd[1]);
137 *read_fd = pipefd[0];
138 return 0;
139}
140
141static int v_os_exec_capture_start(char *const argv[], int *child_pid, int *read_fd) {
142 if (argv == NULL || argv[0] == NULL) {
143 return -1;
144 }
145 int pipefd[2];
146 if (pipe(pipefd) != 0) {
147 return -1;
148 }
149 v_os_execute_set_cloexec(pipefd[0]);
150 v_os_execute_set_cloexec(pipefd[1]);
151 v_posix_spawn_file_actions_t actions;
152 if (posix_spawn_file_actions_init(&actions) != 0) {
153 close(pipefd[0]);
154 close(pipefd[1]);
155 return -1;
156 }
157 int err = 0;
158 if ((err = posix_spawn_file_actions_adddup2(&actions, pipefd[1], STDOUT_FILENO)) != 0
159 || (err = posix_spawn_file_actions_adddup2(&actions, pipefd[1], STDERR_FILENO)) != 0
160 || (err = posix_spawn_file_actions_addclose(&actions, pipefd[0])) != 0
161 || (err = posix_spawn_file_actions_addclose(&actions, pipefd[1])) != 0) {
162 posix_spawn_file_actions_destroy(&actions);
163 close(pipefd[0]);
164 close(pipefd[1]);
165 return -1;
166 }
167 err = posix_spawnp(child_pid, argv[0], &actions, NULL, argv, environ);
168 posix_spawn_file_actions_destroy(&actions);
169 if (err != 0) {
170 close(pipefd[0]);
171 close(pipefd[1]);
172 return -1;
173 }
174 close(pipefd[1]);
175 *read_fd = pipefd[0];
176 return 0;
177}
178#endif
179