master
c 424 lines 14.6 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_UV)
6
7 int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
8 int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
9 void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
10 void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
11 pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return uv_process_get_pid(&si->process); }
12
13 typedef struct work_item {
14 int stderr_fd;
15 const char **argv;
16 uv_sem_t sem;
17 SPAWN_INSTANCE *instance;
18 struct work_item *prev;
19 struct work_item *next;
20 } work_item;
21
22 int uv_errno_to_errno(int uv_err) {
23 switch (uv_err) {
24 case 0: return 0;
25 case UV_E2BIG: return E2BIG;
26 case UV_EACCES: return EACCES;
27 case UV_EADDRINUSE: return EADDRINUSE;
28 case UV_EADDRNOTAVAIL: return EADDRNOTAVAIL;
29 case UV_EAFNOSUPPORT: return EAFNOSUPPORT;
30 case UV_EAGAIN: return EAGAIN;
31 case UV_EAI_ADDRFAMILY: return EAI_ADDRFAMILY;
32 case UV_EAI_AGAIN: return EAI_AGAIN;
33 case UV_EAI_BADFLAGS: return EAI_BADFLAGS;
34 #if defined(EAI_CANCELED)
35 case UV_EAI_CANCELED: return EAI_CANCELED;
36 #endif
37 case UV_EAI_FAIL: return EAI_FAIL;
38 case UV_EAI_FAMILY: return EAI_FAMILY;
39 case UV_EAI_MEMORY: return EAI_MEMORY;
40 case UV_EAI_NODATA: return EAI_NODATA;
41 case UV_EAI_NONAME: return EAI_NONAME;
42 case UV_EAI_OVERFLOW: return EAI_OVERFLOW;
43 case UV_EAI_SERVICE: return EAI_SERVICE;
44 case UV_EAI_SOCKTYPE: return EAI_SOCKTYPE;
45 case UV_EALREADY: return EALREADY;
46 case UV_EBADF: return EBADF;
47 case UV_EBUSY: return EBUSY;
48 case UV_ECANCELED: return ECANCELED;
49 case UV_ECHARSET: return EILSEQ; // No direct mapping, using EILSEQ
50 case UV_ECONNABORTED: return ECONNABORTED;
51 case UV_ECONNREFUSED: return ECONNREFUSED;
52 case UV_ECONNRESET: return ECONNRESET;
53 case UV_EDESTADDRREQ: return EDESTADDRREQ;
54 case UV_EEXIST: return EEXIST;
55 case UV_EFAULT: return EFAULT;
56 case UV_EFBIG: return EFBIG;
57 case UV_EHOSTUNREACH: return EHOSTUNREACH;
58 case UV_EINTR: return EINTR;
59 case UV_EINVAL: return EINVAL;
60 case UV_EIO: return EIO;
61 case UV_EISCONN: return EISCONN;
62 case UV_EISDIR: return EISDIR;
63 case UV_ELOOP: return ELOOP;
64 case UV_EMFILE: return EMFILE;
65 case UV_EMSGSIZE: return EMSGSIZE;
66 case UV_ENAMETOOLONG: return ENAMETOOLONG;
67 case UV_ENETDOWN: return ENETDOWN;
68 case UV_ENETUNREACH: return ENETUNREACH;
69 case UV_ENFILE: return ENFILE;
70 case UV_ENOBUFS: return ENOBUFS;
71 case UV_ENODEV: return ENODEV;
72 case UV_ENOENT: return ENOENT;
73 case UV_ENOMEM: return ENOMEM;
74 case UV_ENONET: return ENONET;
75 case UV_ENOSPC: return ENOSPC;
76 case UV_ENOSYS: return ENOSYS;
77 case UV_ENOTCONN: return ENOTCONN;
78 case UV_ENOTDIR: return ENOTDIR;
79 case UV_ENOTEMPTY: return ENOTEMPTY;
80 case UV_ENOTSOCK: return ENOTSOCK;
81 case UV_ENOTSUP: return ENOTSUP;
82 case UV_ENOTTY: return ENOTTY;
83 case UV_ENXIO: return ENXIO;
84 case UV_EPERM: return EPERM;
85 case UV_EPIPE: return EPIPE;
86 case UV_EPROTO: return EPROTO;
87 case UV_EPROTONOSUPPORT: return EPROTONOSUPPORT;
88 case UV_EPROTOTYPE: return EPROTOTYPE;
89 case UV_ERANGE: return ERANGE;
90 case UV_EROFS: return EROFS;
91 case UV_ESHUTDOWN: return ESHUTDOWN;
92 case UV_ESPIPE: return ESPIPE;
93 case UV_ESRCH: return ESRCH;
94 case UV_ETIMEDOUT: return ETIMEDOUT;
95 case UV_ETXTBSY: return ETXTBSY;
96 case UV_EXDEV: return EXDEV;
97 default: return EINVAL; // Use EINVAL for unknown libuv errors
98 }
99 }
100
101 static void server_thread(void *arg) {
102 SPAWN_SERVER *server = (SPAWN_SERVER *)arg;
103 nd_log(NDLS_COLLECTORS, NDLP_ERR,
104 "SPAWN SERVER: started");
105
106 // this thread needs to process SIGCHLD (by libuv)
107 // otherwise the on_exit() callback is never run
108 signals_unblock_one(SIGCHLD);
109
110 // run the event loop
111 uv_run(server->loop, UV_RUN_DEFAULT);
112
113 nd_log(NDLS_COLLECTORS, NDLP_ERR,
114 "SPAWN SERVER: ended");
115 }
116
117 static void on_process_exit(uv_process_t *req, int64_t exit_status, int term_signal) {
118 SPAWN_INSTANCE *si = (SPAWN_INSTANCE *)req->data;
119 si->exit_code = (int)(term_signal ? term_signal : exit_status << 8);
120 uv_close((uv_handle_t *)req, NULL); // Properly close the process handle
121
122 nd_log(NDLS_COLLECTORS, NDLP_ERR,
123 "SPAWN SERVER: process with pid %d exited with code %d and term_signal %d",
124 si->child_pid, (int)exit_status, term_signal);
125
126 uv_sem_post(&si->sem); // Signal that the process has exited
127 }
128
129 static SPAWN_INSTANCE *spawn_process_with_libuv(uv_loop_t *loop, int stderr_fd, const char **argv) {
130 SPAWN_INSTANCE *si = NULL;
131 bool si_sem_init = false;
132
133 int stdin_pipe[2] = { -1, -1 };
134 int stdout_pipe[2] = { -1, -1 };
135
136 if (pipe(stdin_pipe) == -1) {
137 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: stdin pipe() failed");
138 goto cleanup;
139 }
140
141 if (pipe(stdout_pipe) == -1) {
142 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: stdout pipe() failed");
143 goto cleanup;
144 }
145
146 si = callocz(1, sizeof(SPAWN_INSTANCE));
147 si->exit_code = -1;
148
149 if (uv_sem_init(&si->sem, 0)) {
150 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: uv_sem_init() failed");
151 goto cleanup;
152 }
153 si_sem_init = true;
154
155 uv_stdio_container_t stdio[3] = { 0 };
156 stdio[0].flags = UV_INHERIT_FD;
157 stdio[0].data.fd = stdin_pipe[PIPE_READ];
158 stdio[1].flags = UV_INHERIT_FD;
159 stdio[1].data.fd = stdout_pipe[PIPE_WRITE];
160 stdio[2].flags = UV_INHERIT_FD;
161 stdio[2].data.fd = stderr_fd;
162
163 uv_process_options_t options = { 0 };
164 options.stdio_count = 3;
165 options.stdio = stdio;
166 options.exit_cb = on_process_exit;
167 options.file = argv[0];
168 options.args = (char **)argv;
169 options.env = (char **)environ;
170
171 // uv_spawn() does not close all other open file descriptors
172 // we have to close them manually
173 int fds[3] = { stdio[0].data.fd, stdio[1].data.fd, stdio[2].data.fd };
174 os_close_all_non_std_open_fds_except(fds, 3, CLOSE_RANGE_CLOEXEC);
175
176 int rc = uv_spawn(loop, &si->process, &options);
177 if (rc) {
178 errno = uv_errno_to_errno(rc);
179 nd_log(NDLS_COLLECTORS, NDLP_ERR,
180 "SPAWN SERVER: uv_spawn() failed with error %s, %s",
181 uv_err_name(rc), uv_strerror(rc));
182 goto cleanup;
183 }
184
185 // Successfully spawned
186
187 // get the pid of the process spawned
188 si->child_pid = uv_process_get_pid(&si->process);
189
190 // on_process_exit() needs this to find the si
191 si->process.data = si;
192
193 nd_log(NDLS_COLLECTORS, NDLP_INFO,
194 "SPAWN SERVER: process created with pid %d", si->child_pid);
195
196 // close the child sides of the pipes
197 close(stdin_pipe[PIPE_READ]);
198 si->write_fd = stdin_pipe[PIPE_WRITE];
199 si->read_fd = stdout_pipe[PIPE_READ];
200 close(stdout_pipe[PIPE_WRITE]);
201
202 return si;
203
204 cleanup:
205 if(stdin_pipe[PIPE_READ] != -1) close(stdin_pipe[PIPE_READ]);
206 if(stdin_pipe[PIPE_WRITE] != -1) close(stdin_pipe[PIPE_WRITE]);
207 if(stdout_pipe[PIPE_READ] != -1) close(stdout_pipe[PIPE_READ]);
208 if(stdout_pipe[PIPE_WRITE] != -1) close(stdout_pipe[PIPE_WRITE]);
209 if(si) {
210 if(si_sem_init)
211 uv_sem_destroy(&si->sem);
212
213 freez(si);
214 }
215 return NULL;
216 }
217
218 static void async_callback(uv_async_t *handle) {
219 nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: dequeue commands started");
220 SPAWN_SERVER *server = (SPAWN_SERVER *)handle->data;
221
222 // Check if the server is stopping
223 if (__atomic_load_n(&server->stopping, __ATOMIC_RELAXED)) {
224 nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: stopping...");
225 uv_stop(server->loop);
226 return;
227 }
228
229 work_item *item;
230 spinlock_lock(&server->spinlock);
231 while (server->work_queue) {
232 item = server->work_queue;
233 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(server->work_queue, item, prev, next);
234 spinlock_unlock(&server->spinlock);
235
236 item->instance = spawn_process_with_libuv(server->loop, item->stderr_fd, item->argv);
237 uv_sem_post(&item->sem);
238
239 spinlock_lock(&server->spinlock);
240 }
241 spinlock_unlock(&server->spinlock);
242
243 nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: dequeue commands done");
244 }
245
246
247 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) {
248 SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
249 spinlock_init(&server->spinlock);
250
251 if (name)
252 server->name = strdupz(name);
253 else
254 server->name = strdupz("unnamed");
255
256 server->loop = callocz(1, sizeof(uv_loop_t));
257 if (uv_loop_init(server->loop)) {
258 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_loop_init() failed");
259 freez(server->loop);
260 freez((void *)server->name);
261 freez(server);
262 return NULL;
263 }
264
265 if (uv_async_init(server->loop, &server->async, async_callback)) {
266 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_async_init() failed");
267 uv_loop_close(server->loop);
268 freez(server->loop);
269 freez((void *)server->name);
270 freez(server);
271 return NULL;
272 }
273 server->async.data = server;
274
275 if (uv_thread_create(&server->thread, server_thread, server)) {
276 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_thread_create() failed");
277 uv_close((uv_handle_t*)&server->async, NULL);
278 uv_loop_close(server->loop);
279 freez(server->loop);
280 freez((void *)server->name);
281 freez(server);
282 return NULL;
283 }
284
285 return server;
286 }
287
288 static void close_handle(uv_handle_t* handle, void* arg __maybe_unused) {
289 if (!uv_is_closing(handle)) {
290 uv_close(handle, NULL);
291 }
292 }
293
294 void spawn_server_destroy(SPAWN_SERVER *server) {
295 if (!server) return;
296
297 __atomic_store_n(&server->stopping, true, __ATOMIC_RELAXED);
298
299 // Trigger the async callback to stop the event loop
300 uv_async_send(&server->async);
301
302 // Wait for the server thread to finish
303 uv_thread_join(&server->thread);
304
305 uv_stop(server->loop);
306 uv_close((uv_handle_t*)&server->async, NULL);
307
308 // Walk through and close any remaining handles
309 uv_walk(server->loop, close_handle, NULL);
310
311 uv_loop_close(server->loop);
312 freez(server->loop);
313 freez((void *)server->name);
314 freez(server);
315 }
316
317 SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd __maybe_unused, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
318 if (type != SPAWN_INSTANCE_TYPE_EXEC)
319 return NULL;
320
321 work_item item = { 0 };
322 item.stderr_fd = stderr_fd;
323 item.argv = argv;
324
325 if (uv_sem_init(&item.sem, 0)) {
326 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_sem_init() failed");
327 return NULL;
328 }
329
330 spinlock_lock(&server->spinlock);
331 // item is in the stack, but the server will remove it before sending to us
332 // the semaphore, so it is safe to have the item in the stack.
333 work_item *item_ptr = &item;
334 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(server->work_queue, item_ptr, prev, next);
335 spinlock_unlock(&server->spinlock);
336
337 uv_async_send(&server->async);
338
339 nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: queued command");
340
341 // Wait for the command to be executed
342 uv_sem_wait(&item.sem);
343 uv_sem_destroy(&item.sem);
344
345 if (!item.instance) {
346 nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: process failed to be started");
347 return NULL;
348 }
349
350 nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: process started");
351
352 return item.instance;
353 }
354
355 int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si, int timeout_ms) {
356 if(!si) return -1;
357
358 // close all pipe descriptors to force the child to exit
359 if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
360 if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
361
362 if (uv_process_kill(&si->process, SIGTERM)) {
363 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_process_kill() failed");
364 return -1;
365 }
366
367 // escalate to SIGKILL if the child does not exit promptly after SIGTERM (or if the wait could
368 // not be completed), so a SIGTERM-ignoring child cannot make the final wait block forever.
369 // the caller's timeout_ms is the SIGTERM grace; fall back to a default when not specified.
370 int grace_ms = timeout_ms > 0 ? timeout_ms : SPAWN_KILL_DEFAULT_GRACE_MS;
371 int status;
372 if(spawn_server_exec_timedwait(server, si, grace_ms, &status) != SPAWN_TIMEDWAIT_EXITED) {
373 if(uv_process_kill(&si->process, SIGKILL))
374 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_process_kill(SIGKILL) failed");
375 }
376 else
377 return status;
378
379 return spawn_server_exec_wait(server, si);
380 }
381
382 SPAWN_TIMEDWAIT_RESULT spawn_server_exec_timedwait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si, int timeout_ms, int *status) {
383 if (!si) { if(status) *status = -1; return SPAWN_TIMEDWAIT_EXITED; }
384
385 // close all pipe descriptors to force the child to exit
386 if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
387 if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
388
389 // a negative timeout would become a huge usec_t deadline (= unbounded wait); clamp to poll-once
390 if(timeout_ms < 0) timeout_ms = 0;
391 usec_t deadline_ut = now_monotonic_usec() + (usec_t)timeout_ms * USEC_PER_MS;
392
393 while(uv_sem_trywait(&si->sem) != 0) {
394 if(now_monotonic_usec() >= deadline_ut)
395 return SPAWN_TIMEDWAIT_RUNNING;
396
397 sleep_usec(10 * USEC_PER_MS);
398 }
399
400 // the semaphore is consumed - finish exactly like spawn_server_exec_wait()
401 int st = si->exit_code;
402 uv_sem_destroy(&si->sem);
403 freez(si);
404 if(status) *status = st;
405 return SPAWN_TIMEDWAIT_EXITED;
406 }
407
408 int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
409 if (!si) return -1;
410
411 // close all pipe descriptors to force the child to exit
412 if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
413 if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
414
415 // Wait for the process to exit
416 uv_sem_wait(&si->sem);
417 int exit_code = si->exit_code;
418
419 uv_sem_destroy(&si->sem);
420 freez(si);
421 return exit_code;
422 }
423
424 #endif