master
c 355 lines 13.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "spawn_server_internals.h"
4
5 #if defined(SPAWN_SERVER_VERSION_POSIX_SPAWN)
6
7 #ifdef __APPLE__
8 #include <crt_externs.h>
9 #define environ (*_NSGetEnviron())
10 #else
11 extern char **environ;
12 #endif
13
14 int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
15 int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
16 void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
17 void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
18 pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return si->child_pid; }
19
20 static struct {
21 bool sigchld_initialized;
22 SPINLOCK spinlock;
23 SPAWN_INSTANCE *instances;
24 } spawn_globals = {
25 .spinlock = SPINLOCK_INITIALIZER,
26 .instances = NULL,
27 };
28
29 //static void sigchld_handler(int signum __maybe_unused) {
30 // pid_t pid;
31 // int status;
32 //
33 // while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
34 // // Find the SPAWN_INSTANCE corresponding to this pid
35 // spinlock_lock(&spawn_globals.spinlock);
36 // for(SPAWN_INSTANCE *si = spawn_globals.instances; si ;si = si->next) {
37 // if (si->child_pid == pid) {
38 // __atomic_store_n(&si->waitpid_status, status, __ATOMIC_RELAXED);
39 // __atomic_store_n(&si->exited, true, __ATOMIC_RELAXED);
40 // break;
41 // }
42 // }
43 // spinlock_unlock(&spawn_globals.spinlock);
44 // }
45 //}
46
47 SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
48 SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
49
50 if (name)
51 server->name = strdupz(name);
52 else
53 server->name = strdupz("unnamed");
54
55 if(!spawn_globals.sigchld_initialized) {
56 spawn_globals.sigchld_initialized = true;
57
58 // struct sigaction sa;
59 // sa.sa_handler = sigchld_handler;
60 // sigemptyset(&sa.sa_mask);
61 // sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
62 // if (sigaction(SIGCHLD, &sa, NULL) == -1) {
63 // nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Failed to set SIGCHLD handler");
64 // freez((void *)server->name);
65 // freez(server);
66 // return NULL;
67 // }
68 }
69
70 return server;
71 }
72
73 void spawn_server_destroy(SPAWN_SERVER *server) {
74 if (!server) return;
75 freez((void *)server->name);
76 freez(server);
77 }
78
79 SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
80 if (type != SPAWN_INSTANCE_TYPE_EXEC)
81 return NULL;
82
83 CLEAN_BUFFER *cmdline_wb = argv_to_cmdline_buffer(argv);
84 const char *cmdline = buffer_tostring(cmdline_wb);
85
86 SPAWN_INSTANCE *si = callocz(1, sizeof(SPAWN_INSTANCE));
87 si->child_pid = -1;
88 si->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
89
90 int stdin_pipe[2] = { -1, -1 };
91 int stdout_pipe[2] = { -1, -1 };
92
93 if (pipe(stdin_pipe) == -1) {
94 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: stdin pipe() failed: %s", cmdline);
95 freez(si);
96 return NULL;
97 }
98
99 if (pipe(stdout_pipe) == -1) {
100 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: stdout pipe() failed: %s", cmdline);
101 close(stdin_pipe[PIPE_READ]);
102 close(stdin_pipe[PIPE_WRITE]);
103 freez(si);
104 return NULL;
105 }
106
107 posix_spawn_file_actions_t file_actions;
108 posix_spawnattr_t attr;
109
110 if (posix_spawn_file_actions_init(&file_actions) != 0) {
111 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawn_file_actions_init() failed: %s", cmdline);
112 close(stdin_pipe[PIPE_READ]);
113 close(stdin_pipe[PIPE_WRITE]);
114 close(stdout_pipe[PIPE_READ]);
115 close(stdout_pipe[PIPE_WRITE]);
116 freez(si);
117 return NULL;
118 }
119
120 posix_spawn_file_actions_adddup2(&file_actions, stdin_pipe[PIPE_READ], STDIN_FILENO);
121 posix_spawn_file_actions_adddup2(&file_actions, stdout_pipe[PIPE_WRITE], STDOUT_FILENO);
122 posix_spawn_file_actions_addclose(&file_actions, stdin_pipe[PIPE_READ]);
123 posix_spawn_file_actions_addclose(&file_actions, stdin_pipe[PIPE_WRITE]);
124 posix_spawn_file_actions_addclose(&file_actions, stdout_pipe[PIPE_READ]);
125 posix_spawn_file_actions_addclose(&file_actions, stdout_pipe[PIPE_WRITE]);
126 if(stderr_fd != STDERR_FILENO) {
127 posix_spawn_file_actions_adddup2(&file_actions, stderr_fd, STDERR_FILENO);
128 posix_spawn_file_actions_addclose(&file_actions, stderr_fd);
129 }
130
131 if (posix_spawnattr_init(&attr) != 0) {
132 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_init() failed: %s", cmdline);
133 posix_spawn_file_actions_destroy(&file_actions);
134 close(stdin_pipe[PIPE_READ]);
135 close(stdin_pipe[PIPE_WRITE]);
136 close(stdout_pipe[PIPE_READ]);
137 close(stdout_pipe[PIPE_WRITE]);
138 freez(si);
139 return NULL;
140 }
141
142 // Set the flags to reset the signal mask and signal actions
143 sigset_t empty_mask;
144 sigemptyset(&empty_mask);
145 if (posix_spawnattr_setsigmask(&attr, &empty_mask) != 0) {
146 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setsigmask() failed: %s", cmdline);
147 posix_spawn_file_actions_destroy(&file_actions);
148 posix_spawnattr_destroy(&attr);
149 return false;
150 }
151
152 short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
153 if (posix_spawnattr_setflags(&attr, flags) != 0) {
154 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setflags() failed: %s", cmdline);
155 posix_spawn_file_actions_destroy(&file_actions);
156 posix_spawnattr_destroy(&attr);
157 return false;
158 }
159
160 spinlock_lock(&spawn_globals.spinlock);
161 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
162 spinlock_unlock(&spawn_globals.spinlock);
163
164 // unfortunately, on CYGWIN/MSYS posix_spawn() is not thread safe
165 // so, we run it one by one.
166 static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
167 spinlock_lock(&spinlock);
168
169 int fds[3] = { stdin_pipe[PIPE_READ], stdout_pipe[PIPE_WRITE], stderr_fd };
170 os_close_all_non_std_open_fds_except(fds, 3, CLOSE_RANGE_CLOEXEC);
171
172 errno_clear();
173 if (posix_spawn(&si->child_pid, argv[0], &file_actions, &attr, (char * const *)argv, environ) != 0) {
174 spinlock_unlock(&spinlock);
175 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: posix_spawn() failed: %s", cmdline);
176
177 spinlock_lock(&spawn_globals.spinlock);
178 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
179 spinlock_unlock(&spawn_globals.spinlock);
180
181 posix_spawnattr_destroy(&attr);
182 posix_spawn_file_actions_destroy(&file_actions);
183
184 close(stdin_pipe[PIPE_READ]);
185 close(stdin_pipe[PIPE_WRITE]);
186 close(stdout_pipe[PIPE_READ]);
187 close(stdout_pipe[PIPE_WRITE]);
188 freez(si);
189 return NULL;
190 }
191 spinlock_unlock(&spinlock);
192
193 // Destroy the posix_spawnattr_t and posix_spawn_file_actions_t structures
194 posix_spawnattr_destroy(&attr);
195 posix_spawn_file_actions_destroy(&file_actions);
196
197 // Close the read end of the stdin pipe and the write end of the stdout pipe in the parent process
198 close(stdin_pipe[PIPE_READ]);
199 close(stdout_pipe[PIPE_WRITE]);
200
201 si->write_fd = stdin_pipe[PIPE_WRITE];
202 si->read_fd = stdout_pipe[PIPE_READ];
203 si->cmdline = strdupz(cmdline);
204
205 nd_log(NDLS_COLLECTORS, NDLP_INFO,
206 "SPAWN SERVER: process created with pid %d: %s",
207 si->child_pid, cmdline);
208 return si;
209 }
210
211 int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *si, int timeout_ms) {
212 if (!si) return -1;
213
214 if (kill(si->child_pid, SIGTERM))
215 nd_log(NDLS_COLLECTORS, NDLP_ERR,
216 "SPAWN PARENT: kill() of pid %d failed: %s",
217 si->child_pid, si->cmdline);
218
219 // escalate to SIGKILL if the child does not exit promptly after SIGTERM (or if the wait could
220 // not be completed), so a SIGTERM-ignoring child cannot make the final wait block forever.
221 // the caller's timeout_ms is the SIGTERM grace; fall back to a default when not specified.
222 int grace_ms = timeout_ms > 0 ? timeout_ms : SPAWN_KILL_DEFAULT_GRACE_MS;
223 int status;
224 if(spawn_server_exec_timedwait(server, si, grace_ms, &status) != SPAWN_TIMEDWAIT_EXITED) {
225 if(kill(si->child_pid, SIGKILL) != 0)
226 // a failed SIGKILL almost always means the child is already gone (ESRCH); the wait
227 // below then returns immediately. SIGKILL is uncatchable, so it cannot be ignored by
228 // a live child - the only unbounded case left is uninterruptible (D-state) sleep,
229 // which no signal or timeout can resolve.
230 nd_log(NDLS_COLLECTORS, NDLP_ERR,
231 "SPAWN PARENT: SIGKILL of pid %d failed: %s", si->child_pid, si->cmdline);
232 }
233 else
234 return status;
235
236 return spawn_server_exec_wait(server, si);
237 }
238
239 static int spawn_server_waitpid(SPAWN_INSTANCE *si) {
240 int status;
241 pid_t pid;
242
243 pid = waitpid(si->child_pid, &status, 0);
244
245 if(pid != si->child_pid) {
246 nd_log(NDLS_COLLECTORS, NDLP_ERR,
247 "SPAWN PARENT: failed to wait for pid %d: %s",
248 si->child_pid, si->cmdline);
249
250 return -1;
251 }
252
253 errno_clear();
254
255 if(WIFEXITED(status)) {
256 if(WEXITSTATUS(status))
257 nd_log(NDLS_COLLECTORS, NDLP_INFO,
258 "SPAWN SERVER: child with pid %d (request %zu) exited with exit code %d: %s",
259 pid, si->request_id, WEXITSTATUS(status), si->cmdline);
260 }
261 else if(WIFSIGNALED(status)) {
262 if(WCOREDUMP(status))
263 nd_log(NDLS_COLLECTORS, NDLP_INFO,
264 "SPAWN SERVER: child with pid %d (request %zu) coredump'd due to signal %d: %s",
265 pid, si->request_id, WTERMSIG(status), si->cmdline);
266 else
267 nd_log(NDLS_COLLECTORS, NDLP_INFO,
268 "SPAWN SERVER: child with pid %d (request %zu) killed by signal %d: %s",
269 pid, si->request_id, WTERMSIG(status), si->cmdline);
270 }
271 else if(WIFSTOPPED(status)) {
272 nd_log(NDLS_COLLECTORS, NDLP_INFO,
273 "SPAWN SERVER: child with pid %d (request %zu) stopped due to signal %d: %s",
274 pid, si->request_id, WSTOPSIG(status), si->cmdline);
275 }
276 else if(WIFCONTINUED(status)) {
277 nd_log(NDLS_COLLECTORS, NDLP_INFO,
278 "SPAWN SERVER: child with pid %d (request %zu) continued due to signal %d: %s",
279 pid, si->request_id, SIGCONT, si->cmdline);
280 }
281 else {
282 nd_log(NDLS_COLLECTORS, NDLP_INFO,
283 "SPAWN SERVER: child with pid %d (request %zu) reports unhandled status: %s",
284 pid, si->request_id, si->cmdline);
285 }
286
287 return status;
288 }
289
290 SPAWN_TIMEDWAIT_RESULT spawn_server_exec_timedwait(SPAWN_SERVER *server, SPAWN_INSTANCE *si, int timeout_ms, int *status) {
291 if (!si) { if(status) *status = -1; return SPAWN_TIMEDWAIT_EXITED; }
292
293 // close the child pipes to force it to exit, matching spawn_server_exec_wait and the
294 // other backends; otherwise a child blocked on stdin/stdout would never see EOF and
295 // would stay alive until the deadline forces a SIGKILL
296 if (si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
297 if (si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
298
299 // a negative timeout would become a huge usec_t deadline (= unbounded wait); clamp to poll-once
300 if(timeout_ms < 0) timeout_ms = 0;
301 usec_t deadline_ut = now_monotonic_usec() + (usec_t)timeout_ms * USEC_PER_MS;
302
303 while(!__atomic_load_n(&si->exited, __ATOMIC_RELAXED)) {
304 int wstatus = 0;
305 pid_t pid = waitpid(si->child_pid, &wstatus, WNOHANG);
306 if(pid == si->child_pid) {
307 __atomic_store_n(&si->waitpid_status, wstatus, __ATOMIC_RELAXED);
308 __atomic_store_n(&si->exited, true, __ATOMIC_RELAXED);
309 break;
310 }
311
312 if(pid < 0 && errno != EINTR)
313 // child reaped elsewhere (e.g. ECHILD) - let the blocking wait resolve it immediately
314 break;
315
316 // pid == 0 (still running) or EINTR (interrupted before any state change):
317 // keep waiting, but never past the deadline
318 if(now_monotonic_usec() >= deadline_ut)
319 return SPAWN_TIMEDWAIT_RUNNING;
320
321 sleep_usec(10 * USEC_PER_MS);
322 }
323
324 int st = spawn_server_exec_wait(server, si);
325 if(status) *status = st;
326 return SPAWN_TIMEDWAIT_EXITED;
327 }
328
329 int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
330 if (!si) return -1;
331
332 // Close all pipe descriptors to force the child to exit
333 if (si->read_fd != -1) close(si->read_fd);
334 if (si->write_fd != -1) close(si->write_fd);
335
336 // Wait for the process to exit
337 int status = __atomic_load_n(&si->waitpid_status, __ATOMIC_RELAXED);
338 bool exited = __atomic_load_n(&si->exited, __ATOMIC_RELAXED);
339 if(!exited)
340 status = spawn_server_waitpid(si);
341 else
342 nd_log(NDLS_COLLECTORS, NDLP_INFO,
343 "SPAWN PARENT: child with pid %d exited with status %d (sighandler): %s",
344 si->child_pid, status, si->cmdline);
345
346 spinlock_lock(&spawn_globals.spinlock);
347 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
348 spinlock_unlock(&spawn_globals.spinlock);
349
350 freez((void *)si->cmdline);
351 freez(si);
352 return status;
353 }
354
355 #endif