1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "../libnetdata.h"
4
+
5
+#include "spawn_server.h"
6
+
7
+#if defined(OS_WINDOWS)
8
+#include <windows.h>
9
+#include <io.h>
10
+#include <fcntl.h>
11
+#include <process.h>
12
+#include <sys/cygwin.h>
13
+#endif
14
+
15
+struct spawn_server {
16
+ size_t id;
17
+ size_t request_id;
18
+ const char *name;
19
+#if !defined(OS_WINDOWS)
20
+ int pipe[2];
21
+ int server_sock;
22
+ pid_t server_pid;
23
+ char *path;
24
+ spawn_request_callback_t cb;
25
+
26
+ int argc;
27
+ const char **argv;
28
+ size_t argv0_size;
29
+#endif
30
+};
31
+
32
+struct spawm_instance {
33
+ size_t request_id;
34
+ int client_sock;
35
+ int write_fd;
36
+ int read_fd;
37
+ pid_t child_pid;
38
+
39
+#if defined(OS_WINDOWS)
40
+ HANDLE process_handle;
41
+ HANDLE read_handle;
42
+ HANDLE write_handle;
43
+#endif
44
+};
45
+
46
+int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
47
+int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
48
+pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return si->child_pid; }
49
+void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
50
+void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
51
+
52
+#if defined(OS_WINDOWS)
53
+
54
+SPAWN_SERVER* spawn_server_create(const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
55
+ SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
56
+ if(name)
57
+ server->name = strdupz(name);
58
+ return server;
59
+}
60
+
61
+void spawn_server_destroy(SPAWN_SERVER *server) {
62
+ if (server) {
63
+ if(server->name) freez((void *)server->name);
64
+ freez(server);
65
+ }
66
+}
67
+
68
+static BUFFER *argv_to_windows(const char **argv) {
69
+ BUFFER *wb = buffer_create(0, NULL);
70
+
71
+ // argv[0] is the path
72
+ char b[strlen(argv[0]) * 2 + 1024];
73
+ cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, sizeof(b));
74
+
75
+ buffer_strcat(wb, "cmd.exe /C ");
76
+
77
+ for(size_t i = 0; argv[i] ;i++) {
78
+ const char *s = (i == 0) ? b : argv[i];
79
+ size_t len = strlen(s);
80
+ buffer_need_bytes(wb, len * 2 + 1);
81
+
82
+ bool needs_quotes = false;
83
+ for(const char *c = s; !needs_quotes && *c ; c++) {
84
+ switch(*c) {
85
+ case ' ':
86
+ case '\v':
87
+ case '\t':
88
+ case '\n':
89
+ case '"':
90
+ needs_quotes = true;
91
+ break;
92
+
93
+ default:
94
+ break;
95
+ }
96
+ }
97
+
98
+ if(needs_quotes && buffer_strlen(wb))
99
+ buffer_strcat(wb, " \"");
100
+ else
101
+ buffer_putc(wb, ' ');
102
+
103
+ for(const char *c = s; *c ; c++) {
104
+ switch(*c) {
105
+ case '"':
106
+ buffer_putc(wb, '\\');
107
+ // fall through
108
+
109
+ default:
110
+ buffer_putc(wb, *c);
111
+ break;
112
+ }
113
+ }
114
+
115
+ if(needs_quotes)
116
+ buffer_strcat(wb, "\"");
117
+ }
118
+
119
+ return wb;
120
+}
121
+
122
+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) {
123
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
124
+
125
+ if (type != SPAWN_INSTANCE_TYPE_EXEC)
126
+ return NULL;
127
+
128
+ int pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 };
129
+
130
+ errno_clear();
131
+
132
+ SPAWN_INSTANCE *instance = callocz(1, sizeof(*instance));
133
+ instance->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
134
+
135
+ CLEAN_BUFFER *wb = argv_to_windows(argv);
136
+ char *command = (char *)buffer_tostring(wb);
137
+
138
+ if (pipe(pipe_stdin) == -1) {
139
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
140
+ "SPAWN PARENT: Cannot create stdin pipe() for request No %zu, command: %s",
141
+ instance->request_id, command);
142
+ goto cleanup;
143
+ }
144
+
145
+ if (pipe(pipe_stdout) == -1) {
146
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
147
+ "SPAWN PARENT: Cannot create stdout pipe() for request No %zu, command: %s",
148
+ instance->request_id, command);
149
+ goto cleanup;
150
+ }
151
+
152
+ // do not run multiple times this section
153
+ // to prevent handles leaking
154
+ spinlock_lock(&spinlock);
155
+
156
+ // Convert POSIX file descriptors to Windows handles
157
+ HANDLE stdin_read_handle = (HANDLE)_get_osfhandle(pipe_stdin[0]);
158
+ HANDLE stdout_write_handle = (HANDLE)_get_osfhandle(pipe_stdout[1]);
159
+ HANDLE stderr_handle = (HANDLE)_get_osfhandle(stderr_fd);
160
+
161
+ if (stdin_read_handle == INVALID_HANDLE_VALUE || stdout_write_handle == INVALID_HANDLE_VALUE || stderr_handle == INVALID_HANDLE_VALUE) {
162
+ spinlock_unlock(&spinlock);
163
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
164
+ "SPAWN PARENT: Invalid handle value(s) for request No %zu, command: %s",
165
+ instance->request_id, command);
166
+ goto cleanup;
167
+ }
168
+
169
+ // Set handle inheritance
170
+ if (!SetHandleInformation(stdin_read_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
171
+ !SetHandleInformation(stdout_write_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
172
+ !SetHandleInformation(stderr_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
173
+ spinlock_unlock(&spinlock);
174
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
175
+ "SPAWN PARENT: Cannot set handle(s) inheritance for request No %zu, command: %s",
176
+ instance->request_id, command);
177
+ goto cleanup;
178
+ }
179
+
180
+ // Set up the STARTUPINFO structure
181
+ STARTUPINFO si;
182
+ PROCESS_INFORMATION pi;
183
+ ZeroMemory(&si, sizeof(si));
184
+ si.cb = sizeof(si);
185
+ si.dwFlags = STARTF_USESTDHANDLES;
186
+ si.hStdInput = stdin_read_handle;
187
+ si.hStdOutput = stdout_write_handle;
188
+ si.hStdError = stderr_handle;
189
+
190
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
191
+ "SPAWN PARENT: Running request No %zu, command: %s",
192
+ instance->request_id, command);
193
+
194
+ // Spawn the process
195
+ if (!CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
196
+ spinlock_unlock(&spinlock);
197
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
198
+ "SPAWN PARENT: cannot CreateProcess() for request No %zu, command: %s",
199
+ instance->request_id, command);
200
+ goto cleanup;
201
+ }
202
+
203
+ CloseHandle(pi.hThread);
204
+
205
+ // end of the critical section
206
+ spinlock_unlock(&spinlock);
207
+
208
+ // Close unused pipe ends
209
+ close(pipe_stdin[0]); pipe_stdin[0] = -1;
210
+ close(pipe_stdout[1]); pipe_stdout[1] = -1;
211
+
212
+ // Store process information in instance
213
+ instance->child_pid = cygwin_winpid_to_pid(pi.dwProcessId);
214
+ if(instance->child_pid == -1) instance->child_pid = pi.dwProcessId;
215
+
216
+ instance->process_handle = pi.hProcess;
217
+
218
+ // Convert handles to POSIX file descriptors
219
+ instance->write_fd = pipe_stdin[1];
220
+ instance->read_fd = pipe_stdout[0];
221
+
222
+ errno_clear();
223
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
224
+ "SPAWN PARENT: created process for request No %zu, pid %d, command: %s",
225
+ instance->request_id, (int)instance->child_pid, command);
226
+
227
+ return instance;
228
+
229
+cleanup:
230
+ if (pipe_stdin[0] >= 0) close(pipe_stdin[0]);
231
+ if (pipe_stdin[1] >= 0) close(pipe_stdin[1]);
232
+ if (pipe_stdout[0] >= 0) close(pipe_stdout[0]);
233
+ if (pipe_stdout[1] >= 0) close(pipe_stdout[1]);
234
+ freez(instance);
235
+ return NULL;
236
+}
237
+
238
+int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) {
239
+ if(instance->read_fd != -1) { close(instance->read_fd); instance->read_fd = -1; }
240
+ if(instance->write_fd != -1) { close(instance->write_fd); instance->write_fd = -1; }
241
+ CloseHandle(instance->read_handle); instance->read_handle = NULL;
242
+ CloseHandle(instance->write_handle); instance->write_handle = NULL;
243
+
244
+ TerminateProcess(instance->process_handle, 0);
245
+
246
+ DWORD exit_code;
247
+ GetExitCodeProcess(instance->process_handle, &exit_code);
248
+ CloseHandle(instance->process_handle);
249
+
250
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
251
+ "SPAWN PARENT: child of request No %zu, pid %d, killed and exited with code %d",
252
+ instance->request_id, (int)instance->child_pid, (int)exit_code);
253
+
254
+ freez(instance);
255
+ return (int)exit_code;
256
+}
257
+
258
+int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) {
259
+ if(instance->read_fd != -1) { close(instance->read_fd); instance->read_fd = -1; }
260
+ if(instance->write_fd != -1) { close(instance->write_fd); instance->write_fd = -1; }
261
+ CloseHandle(instance->read_handle); instance->read_handle = NULL;
262
+ CloseHandle(instance->write_handle); instance->write_handle = NULL;
263
+
264
+ WaitForSingleObject(instance->process_handle, INFINITE);
265
+
266
+ DWORD exit_code = -1;
267
+ GetExitCodeProcess(instance->process_handle, &exit_code);
268
+ CloseHandle(instance->process_handle);
269
+
270
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
271
+ "SPAWN PARENT: child of request No %zu, pid %d, waited and exited with code %d",
272
+ instance->request_id, (int)instance->child_pid, (int)exit_code);
273
+
274
+ freez(instance);
275
+ return (int)exit_code;
276
+}
277
+
278
+#else // !OS_WINDOWS
279
+
280
+#ifdef __APPLE__
281
+#include <crt_externs.h>
282
+#define environ (*_NSGetEnviron())
283
+#else
284
+extern char **environ;
285
+#endif
286
+
287
+static size_t spawn_server_id = 0;
288
+static volatile bool spawn_server_exit = false;
289
+static volatile bool spawn_server_sigchld = false;
290
+static SPAWN_REQUEST *spawn_server_requests = NULL;
291
+
292
+// --------------------------------------------------------------------------------------------------------------------
293
+
294
+static int connect_to_spawn_server(const char *path, bool log) {
295
+ int sock = -1;
296
+
297
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
298
+ if(log)
299
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: cannot create socket() to connect to spawn server.");
300
+ return -1;
301
+ }
302
+
303
+ struct sockaddr_un server_addr = {
304
+ .sun_family = AF_UNIX,
305
+ };
306
+ strcpy(server_addr.sun_path, path);
307
+
308
+ if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
309
+ if(log)
310
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Cannot connect() to spawn server.");
311
+ close(sock);
312
+ return -1;
313
+ }
314
+
315
+ return sock;
316
+}
317
+
318
+// --------------------------------------------------------------------------------------------------------------------
319
+// the child created by the spawn server
320
+
321
+typedef enum __attribute__((packed)) {
322
+ STATUS_REPORT_STARTED,
323
+ STATUS_REPORT_FAILED,
324
+ STATUS_REPORT_EXITED,
325
+ STATUS_REPORT_PING,
326
+} STATUS_REPORT;
327
+
328
+struct status_report {
329
+ STATUS_REPORT status;
330
+ union {
331
+ struct {
332
+ pid_t pid;
333
+ } started;
334
+
335
+ struct {
336
+ int err_no;
337
+ } failed;
338
+
339
+ struct {
340
+ int waitpid_status;
341
+ } exited;
342
+ };
343
+};
344
+
345
+static void spawn_server_send_status_ping(int fd) {
346
+ struct status_report sr = {
347
+ .status = STATUS_REPORT_PING,
348
+ };
349
+
350
+ if(write(fd, &sr, sizeof(sr)) != sizeof(sr))
351
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Cannot send ping status report");
352
+}
353
+
354
+static void spawn_server_send_status_success(int fd) {
355
+ const struct status_report sr = {
356
+ .status = STATUS_REPORT_STARTED,
357
+ .started = {
358
+ .pid = getpid(),
359
+ },
360
+ };
361
+
362
+ if(write(fd, &sr, sizeof(sr)) != sizeof(sr))
363
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Cannot send success status report");
364
+}
365
+
366
+static void spawn_server_send_status_failure(int fd) {
367
+ struct status_report sr = {
368
+ .status = STATUS_REPORT_FAILED,
369
+ .failed = {
370
+ .err_no = errno,
371
+ },
372
+ };
373
+
374
+ if(write(fd, &sr, sizeof(sr)) != sizeof(sr))
375
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Cannot send failure status report");
376
+}
377
+
378
+static void spawn_server_send_status_exit(int fd, int waitpid_status) {
379
+ struct status_report sr = {
380
+ .status = STATUS_REPORT_EXITED,
381
+ .exited = {
382
+ .waitpid_status = waitpid_status,
383
+ },
384
+ };
385
+
386
+ if(write(fd, &sr, sizeof(sr)) != sizeof(sr))
387
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Cannot send exit status report");
388
+}
389
+
390
+static void spawn_server_run_child(SPAWN_SERVER *server, SPAWN_REQUEST *request) {
391
+ // fprintf(stderr, "CHILD: running request %zu on pid %d\n", request->request_id, getpid());
392
+
393
+ // close the server sockets;
394
+ close(server->server_sock); server->server_sock = -1;
395
+ if(server->pipe[0] != -1) { close(server->pipe[0]); server->pipe[0] = -1; }
396
+ if(server->pipe[1] != -1) { close(server->pipe[1]); server->pipe[1] = -1; }
397
+
398
+ // set the process name
399
+ {
400
+ char buf[15];
401
+ snprintfz(buf, sizeof(buf), "chld-%zu-r%zu", server->id, request->request_id);
402
+ os_setproctitle(buf, server->argc, server->argv);
403
+ }
404
+
405
+ // get the fds from the request
406
+ int stdin_fd = request->fds[0];
407
+ int stdout_fd = request->fds[1];
408
+ int stderr_fd = request->fds[2];
409
+ int custom_fd = request->fds[3];
410
+
411
+ // change stdio fds to the ones in the request
412
+ if (dup2(stdin_fd, STDIN_FILENO) == -1) {
413
+ spawn_server_send_status_failure(stdout_fd);
414
+ exit(1);
415
+ }
416
+ if (dup2(stdout_fd, STDOUT_FILENO) == -1) {
417
+ spawn_server_send_status_failure(stdout_fd);
418
+ exit(1);
419
+ }
420
+ if (dup2(stderr_fd, STDERR_FILENO) == -1) {
421
+ spawn_server_send_status_failure(stdout_fd);
422
+ exit(1);
423
+ }
424
+
425
+ // close the excess fds
426
+ close(stdin_fd); stdin_fd = request->fds[0] = STDIN_FILENO;
427
+ close(stdout_fd); stdout_fd = request->fds[1] = STDOUT_FILENO;
428
+ close(stderr_fd); stderr_fd = request->fds[2] = STDERR_FILENO;
429
+
430
+ // overwrite the process environment
431
+ environ = (char **)request->environment;
432
+
433
+ // Perform different actions based on the type
434
+ switch (request->type) {
435
+
436
+ case SPAWN_INSTANCE_TYPE_EXEC:
437
+ spawn_server_send_status_success(request->socket);
438
+ close(request->socket); request->socket = -1;
439
+ close(custom_fd); custom_fd = -1;
440
+ execvp(request->argv[0], (char **)request->argv);
441
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
442
+ "SPAWN SERVER: Failed to execute command of request No %zu (argv[0] = '%s')",
443
+ request->request_id, request->argv[0]);
444
+ exit(1);
445
+ break;
446
+
447
+ case SPAWN_INSTANCE_TYPE_CALLBACK:
448
+ if(server->cb == NULL) {
449
+ errno = ENOENT;
450
+ spawn_server_send_status_failure(request->socket);
451
+ close(request->socket); request->socket = -1;
452
+ exit(1);
453
+ }
454
+ spawn_server_send_status_success(request->socket);
455
+ close(request->socket); request->socket = -1;
456
+ server->cb(request);
457
+ exit(0);
458
+ break;
459
+
460
+ default:
461
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: unknown request type %u", request->type);
462
+ exit(1);
463
+ }
464
+}
465
+
466
+// --------------------------------------------------------------------------------------------------------------------
467
+// Encoding and decoding of spawn server request argv type of data
468
+
469
+// Function to encode argv or envp
470
+static void* encode_argv(const char **argv, size_t *out_size) {
471
+ size_t buffer_size = 1024; // Initial buffer size
472
+ size_t buffer_used = 0;
473
+ char *buffer = mallocz(buffer_size);
474
+
475
+ if(argv) {
476
+ for (const char **p = argv; *p != NULL; p++) {
477
+ if (strlen(*p) == 0)
478
+ continue; // Skip empty strings
479
+
480
+ size_t len = strlen(*p) + 1;
481
+ size_t wanted_size = buffer_used + len + 1;
482
+
483
+ if (wanted_size >= buffer_size) {
484
+ buffer_size *= 2;
485
+
486
+ if(buffer_size < wanted_size)
487
+ buffer_size = wanted_size;
488
+
489
+ buffer = reallocz(buffer, buffer_size);
490
+ }
491
+
492
+ memcpy(&buffer[buffer_used], *p, len);
493
+ buffer_used += len;
494
+ }
495
+ }
496
+
497
+ buffer[buffer_used++] = '\0'; // Final empty string
498
+ *out_size = buffer_used;
499
+
500
+ return buffer;
501
+}
502
+
503
+// Function to decode argv or envp
504
+static const char** decode_argv(const char *buffer, size_t size) {
505
+ size_t count = 0;
506
+ const char *ptr = buffer;
507
+ while (ptr < buffer + size) {
508
+ if(ptr && *ptr) {
509
+ count++;
510
+ ptr += strlen(ptr) + 1;
511
+ }
512
+ else
513
+ break;
514
+ }
515
+
516
+ const char **argv = mallocz((count + 1) * sizeof(char *));
517
+
518
+ ptr = buffer;
519
+ for (size_t i = 0; i < count; i++) {
520
+ argv[i] = ptr;
521
+ ptr += strlen(ptr) + 1;
522
+ }
523
+ argv[count] = NULL; // Null-terminate the array
524
+
525
+ return argv;
526
+}
527
+
528
+// --------------------------------------------------------------------------------------------------------------------
529
+// Sending and receiving requests
530
+
531
+typedef enum __attribute__((packed)) {
532
+ SPAWN_SERVER_MSG_INVALID = 0,
533
+ SPAWN_SERVER_MSG_REQUEST,
534
+ SPAWN_SERVER_MSG_PING,
535
+} SPAWN_SERVER_MSG;
536
+
537
+static bool spawn_server_is_running(const char *path) {
538
+ struct msghdr msg = {0};
539
+ struct iovec iov[6];
540
+ SPAWN_SERVER_MSG msg_type = SPAWN_SERVER_MSG_PING;
541
+ size_t dummy_size = 0;
542
+ SPAWN_INSTANCE_TYPE dummy_type = 0;
543
+ char cmsgbuf[CMSG_SPACE(sizeof(int))];
544
+
545
+ iov[0].iov_base = &msg_type;
546
+ iov[0].iov_len = sizeof(msg_type);
547
+
548
+ iov[1].iov_base = &dummy_size;
549
+ iov[1].iov_len = sizeof(dummy_size);
550
+
551
+ iov[2].iov_base = &dummy_size;
552
+ iov[2].iov_len = sizeof(dummy_size);
553
+
554
+ iov[3].iov_base = &dummy_size;
555
+ iov[3].iov_len = sizeof(dummy_size);
556
+
557
+ iov[4].iov_base = &dummy_size;
558
+ iov[4].iov_len = sizeof(dummy_size);
559
+
560
+ iov[5].iov_base = &dummy_type;
561
+ iov[5].iov_len = sizeof(dummy_type);
562
+
563
+ msg.msg_iov = iov;
564
+ msg.msg_iovlen = 6;
565
+ msg.msg_control = cmsgbuf;
566
+ msg.msg_controllen = sizeof(cmsgbuf);
567
+
568
+ int sock = connect_to_spawn_server(path, false);
569
+ if(sock == -1)
570
+ return false;
571
+
572
+ int rc = sendmsg(sock, &msg, 0);
573
+ if (rc < 0) {
574
+ // cannot send the message
575
+ close(sock);
576
+ return false;
577
+ }
578
+
579
+ // Receive response
580
+ struct status_report sr = { 0 };
581
+ if (read(sock, &sr, sizeof(sr)) != sizeof(sr)) {
582
+ // cannot receive a ping reply
583
+ close(sock);
584
+ return false;
585
+ }
586
+
587
+ close(sock);
588
+ return sr.status == STATUS_REPORT_PING;
589
+}
590
+
591
+static bool spawn_server_send_request(SPAWN_REQUEST *request) {
592
+ bool ret = false;
593
+
594
+ size_t env_size = 0;
595
+ void *encoded_env = encode_argv(request->environment, &env_size);
596
+ if (!encoded_env)
597
+ goto cleanup;
598
+
599
+ size_t argv_size = 0;
600
+ void *encoded_argv = encode_argv(request->argv, &argv_size);
601
+ if (!encoded_argv)
602
+ goto cleanup;
603
+
604
+ struct msghdr msg = {0};
605
+ struct cmsghdr *cmsg;
606
+ SPAWN_SERVER_MSG msg_type = SPAWN_SERVER_MSG_REQUEST;
607
+ char cmsgbuf[CMSG_SPACE(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS)];
608
+ struct iovec iov[10];
609
+
610
+
611
+ // We send 1 request with 10 iovec in it
612
+ // The request will be received in 2 parts
613
+ // 1. the first 6 iovec which include the sizes of the memory allocations required
614
+ // 2. the last 4 iovec which require the memory allocations to be received
615
+
616
+ iov[0].iov_base = &msg_type;
617
+ iov[0].iov_len = sizeof(msg_type);
618
+
619
+ iov[1].iov_base = &request->request_id;
620
+ iov[1].iov_len = sizeof(request->request_id);
621
+
622
+ iov[2].iov_base = &env_size;
623
+ iov[2].iov_len = sizeof(env_size);
624
+
625
+ iov[3].iov_base = &argv_size;
626
+ iov[3].iov_len = sizeof(argv_size);
627
+
628
+ iov[4].iov_base = &request->data_size;
629
+ iov[4].iov_len = sizeof(request->data_size);
630
+
631
+ iov[5].iov_base = &request->type; // Added this line
632
+ iov[5].iov_len = sizeof(request->type);
633
+
634
+ iov[6].iov_base = encoded_env;
635
+ iov[6].iov_len = env_size;
636
+
637
+ iov[7].iov_base = encoded_argv;
638
+ iov[7].iov_len = argv_size;
639
+
640
+ iov[8].iov_base = (char *)request->data;
641
+ iov[8].iov_len = request->data_size;
642
+
643
+ iov[9].iov_base = NULL;
644
+ iov[9].iov_len = 0;
645
+
646
+ msg.msg_iov = iov;
647
+ msg.msg_iovlen = 10;
648
+ msg.msg_control = cmsgbuf;
649
+ msg.msg_controllen = CMSG_SPACE(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS);
650
+
651
+ cmsg = CMSG_FIRSTHDR(&msg);
652
+ cmsg->cmsg_level = SOL_SOCKET;
653
+ cmsg->cmsg_type = SCM_RIGHTS;
654
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS);
655
+
656
+ memcpy(CMSG_DATA(cmsg), request->fds, sizeof(int) * SPAWN_SERVER_TRANSFER_FDS);
657
+
658
+ int rc = sendmsg(request->socket, &msg, 0);
659
+
660
+ if (rc < 0) {
661
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Failed to sendmsg() request to spawn server using socket %d.", request->socket);
662
+ goto cleanup;
663
+ }
664
+ else {
665
+ ret = true;
666
+ // fprintf(stderr, "PARENT: sent request %zu on socket %d (fds: %d, %d, %d, %d) from tid %d\n",
667
+ // request->request_id, request->socket, request->fds[0], request->fds[1], request->fds[2], request->fds[3], os_gettid());
668
+ }
669
+
670
+cleanup:
671
+ freez(encoded_env);
672
+ freez(encoded_argv);
673
+ return ret;
674
+}
675
+
676
+static void spawn_server_receive_request(int sock, SPAWN_SERVER *server) {
677
+ struct msghdr msg = {0};
678
+ struct iovec iov[6];
679
+ SPAWN_SERVER_MSG msg_type = SPAWN_SERVER_MSG_INVALID;
680
+ size_t request_id;
681
+ size_t env_size;
682
+ size_t argv_size;
683
+ size_t data_size;
684
+ SPAWN_INSTANCE_TYPE type;
685
+ char cmsgbuf[CMSG_SPACE(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS)];
686
+ char *envp = NULL, *argv = NULL, *data = NULL;
687
+ int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1, custom_fd = -1;
688
+
689
+ // First recvmsg() to read sizes and control message
690
+ iov[0].iov_base = &msg_type;
691
+ iov[0].iov_len = sizeof(msg_type);
692
+ iov[1].iov_base = &request_id;
693
+ iov[1].iov_len = sizeof(request_id);
694
+ iov[2].iov_base = &env_size;
695
+ iov[2].iov_len = sizeof(env_size);
696
+ iov[3].iov_base = &argv_size;
697
+ iov[3].iov_len = sizeof(argv_size);
698
+ iov[4].iov_base = &data_size;
699
+ iov[4].iov_len = sizeof(data_size);
700
+ iov[5].iov_base = &type;
701
+ iov[5].iov_len = sizeof(type);
702
+
703
+ msg.msg_iov = iov;
704
+ msg.msg_iovlen = 6;
705
+ msg.msg_control = cmsgbuf;
706
+ msg.msg_controllen = sizeof(cmsgbuf);
707
+
708
+ if (recvmsg(sock, &msg, 0) < 0) {
709
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: failed to recvmsg() the first part of the request.");
710
+ return;
711
+ }
712
+
713
+ if(msg_type == SPAWN_SERVER_MSG_PING) {
714
+ spawn_server_send_status_ping(sock);
715
+ return;
716
+ }
717
+
718
+ // Extract file descriptors from control message
719
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
720
+ if (cmsg == NULL || cmsg->cmsg_len != CMSG_LEN(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS)) {
721
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
722
+ "SPAWN SERVER: Received invalid control message (expected %zu bytes, received %zu bytes)",
723
+ CMSG_LEN(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS), cmsg?cmsg->cmsg_len:0);
724
+ return;
725
+ }
726
+
727
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
728
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Received unexpected control message type.");
729
+ return;
730
+ }
731
+
732
+ int *fds = (int *)CMSG_DATA(cmsg);
733
+ stdin_fd = fds[0];
734
+ stdout_fd = fds[1];
735
+ stderr_fd = fds[2];
736
+ custom_fd = fds[3];
737
+
738
+ if (stdin_fd < 0 || stdout_fd < 0 || stderr_fd < 0) {
739
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
740
+ "SPAWN SERVER: invalid file descriptors received, stdin = %d, stdout = %d, stderr = %d",
741
+ stdin_fd, stdout_fd, stderr_fd);
742
+ goto cleanup;
743
+ }
744
+
745
+ // Second recvmsg() to read buffer contents
746
+ iov[0].iov_base = envp = mallocz(env_size);
747
+ iov[0].iov_len = env_size;
748
+ iov[1].iov_base = argv = mallocz(argv_size);
749
+ iov[1].iov_len = argv_size;
750
+ iov[2].iov_base = data = mallocz(data_size);
751
+ iov[2].iov_len = data_size;
752
+
753
+ msg.msg_iov = iov;
754
+ msg.msg_iovlen = 3;
755
+ msg.msg_control = NULL;
756
+ msg.msg_controllen = 0;
757
+
758
+ ssize_t total_bytes_received = recvmsg(sock, &msg, 0);
759
+ if (total_bytes_received < 0) {
760
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: failed to recvmsg() the second part of the request.");
761
+ goto cleanup;
762
+ }
763
+
764
+ // fprintf(stderr, "SPAWN SERVER: received request %zu (fds: %d, %d, %d, %d)\n", request_id,
765
+ // stdin_fd, stdout_fd, stderr_fd, custom_fd);
766
+
767
+ SPAWN_REQUEST *request = mallocz(sizeof(*request));
768
+ *request = (SPAWN_REQUEST){
769
+ .pid = 0,
770
+ .request_id = request_id,
771
+ .socket = sock,
772
+ .fds = {
773
+ [0] = stdin_fd,
774
+ [1] = stdout_fd,
775
+ [2] = stderr_fd,
776
+ [3] = custom_fd,
777
+ },
778
+ .environment = decode_argv(envp, env_size),
779
+ .argv = decode_argv(argv, argv_size),
780
+ .data = data,
781
+ .data_size = data_size,
782
+ .type = type
783
+ };
784
+
785
+ pid_t pid = fork();
786
+ if (pid == 0) {
787
+ // the child
788
+ spawn_server_run_child(server, request);
789
+ exit(1);
790
+
791
+ }
792
+ else if (pid > 0) {
793
+ // the parent
794
+ request->pid = pid;
795
+ request->environment = NULL;
796
+ request->argv = NULL;
797
+ request->data = NULL;
798
+ request->data_size = 0;
799
+ request->fds[0] = -1;
800
+ request->fds[1] = -1;
801
+ request->fds[2] = -1;
802
+ request->fds[3] = -1;
803
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(spawn_server_requests, request, prev, next);
804
+
805
+ // do not fork this socket on other children
806
+ sock_setcloexec(request->socket);
807
+ }
808
+ else {
809
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to fork() child.");
810
+ spawn_server_send_status_failure(stdout_fd);
811
+ freez(request);
812
+ }
813
+
814
+cleanup:
815
+ if(stdin_fd != -1) close(stdin_fd);
816
+ if(stdout_fd != -1) close(stdout_fd);
817
+ if(stderr_fd != -1) close(stderr_fd);
818
+ if(custom_fd != -1) close(custom_fd);
819
+ freez(envp);
820
+ freez(argv);
821
+ freez(data);
822
+}
823
+
824
+// --------------------------------------------------------------------------------------------------------------------
825
+// the spawn server main event loop
826
+
827
+static void spawn_server_sigchld_handler(int signo __maybe_unused) {
828
+ spawn_server_sigchld = true;
829
+}
830
+
831
+static void spawn_server_sigterm_handler(int signo __maybe_unused) {
832
+ spawn_server_exit = true;
833
+}
834
+
835
+static SPAWN_REQUEST *find_request_by_pid(pid_t pid) {
836
+ for(SPAWN_REQUEST *rq = spawn_server_requests; rq ;rq = rq->next)
837
+ if(rq->pid == pid)
838
+ return rq;
839
+
840
+ return NULL;
841
+}
842
+
843
+static void spawn_server_process_sigchld(void) {
844
+ // nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: checking for exited children");
845
+
846
+ int status;
847
+ pid_t pid;
848
+
849
+ // Loop to check for exited child processes
850
+ while ((pid = waitpid((pid_t)(-1), &status, WNOHANG)) != 0) {
851
+ if(pid == -1)
852
+ break;
853
+
854
+ SPAWN_REQUEST *rq = find_request_by_pid(pid);
855
+ size_t request_id = rq ? rq->request_id : 0;
856
+ bool send_report_remove_request = false;
857
+
858
+ if(WIFEXITED(status)) {
859
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
860
+ "SPAWN SERVER: child with pid %d (request %zu) exited normally with exit code %d",
861
+ pid, request_id, WEXITSTATUS(status));
862
+ send_report_remove_request = true;
863
+ }
864
+ else if(WIFSIGNALED(status)) {
865
+ if(WCOREDUMP(status))
866
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
867
+ "SPAWN SERVER: child with pid %d (request %zu) coredump'd due to signal %d",
868
+ pid, request_id, WTERMSIG(status));
869
+ else
870
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
871
+ "SPAWN SERVER: child with pid %d (request %zu) killed by signal %d",
872
+ pid, request_id, WTERMSIG(status));
873
+ send_report_remove_request = true;
874
+ }
875
+ else if(WIFSTOPPED(status)) {
876
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
877
+ "SPAWN SERVER: child with pid %d (request %zu) stopped due to signal %d",
878
+ pid, request_id, WSTOPSIG(status));
879
+ send_report_remove_request = false;
880
+ }
881
+ else if(WIFCONTINUED(status)) {
882
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
883
+ "SPAWN SERVER: child with pid %d (request %zu) continued due to signal %d",
884
+ pid, request_id, SIGCONT);
885
+ send_report_remove_request = false;
886
+ }
887
+ else {
888
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
889
+ "SPAWN SERVER: child with pid %d (request %zu) reports unhandled status",
890
+ pid, request_id);
891
+ send_report_remove_request = false;
892
+ }
893
+
894
+ if(send_report_remove_request && rq) {
895
+ spawn_server_send_status_exit(rq->socket, status);
896
+ close(rq->socket);
897
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(spawn_server_requests, rq, prev, next);
898
+ freez(rq);
899
+ }
900
+ }
901
+}
902
+
903
+static void signals_unblock(void) {
904
+ sigset_t sigset;
905
+ sigfillset(&sigset);
906
+
907
+ if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
908
+ netdata_log_error("SIGNAL: Could not unblock signals for threads");
909
+ }
910
+}
911
+
912
+static void spawn_server_event_loop(SPAWN_SERVER *server) {
913
+ int pipe_fd = server->pipe[1];
914
+ close(server->pipe[0]); server->pipe[0] = -1;
915
+
916
+ signals_unblock();
917
+
918
+ // Set up the signal handler for SIGCHLD and SIGTERM
919
+ struct sigaction sa;
920
+ sa.sa_handler = spawn_server_sigchld_handler;
921
+ sigemptyset(&sa.sa_mask);
922
+ sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
923
+ if (sigaction(SIGCHLD, &sa, NULL) == -1) {
924
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: sigaction() failed for SIGCHLD");
925
+ exit(1);
926
+ }
927
+
928
+ sa.sa_handler = spawn_server_sigterm_handler;
929
+ if (sigaction(SIGTERM, &sa, NULL) == -1) {
930
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: sigaction() failed for SIGTERM");
931
+ exit(1);
932
+ }
933
+
934
+ struct status_report sr = {
935
+ .status = STATUS_REPORT_STARTED,
936
+ .started = {
937
+ .pid = getpid(),
938
+ },
939
+ };
940
+ if (write(pipe_fd, &sr, sizeof(sr)) != sizeof(sr)) {
941
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: failed to write initial status report.");
942
+ exit(1);
943
+ }
944
+
945
+ struct pollfd fds[2];
946
+ fds[0].fd = server->server_sock;
947
+ fds[0].events = POLLIN;
948
+ fds[1].fd = pipe_fd;
949
+ fds[1].events = POLLHUP | POLLERR;
950
+
951
+ while(!spawn_server_exit) {
952
+ int ret = poll(fds, 2, -1);
953
+ if (spawn_server_sigchld) {
954
+ spawn_server_sigchld = false;
955
+ spawn_server_process_sigchld();
956
+
957
+ if(ret == -1)
958
+ continue;
959
+ }
960
+
961
+ if (ret == -1) {
962
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: poll() failed");
963
+ break;
964
+ }
965
+
966
+ if (fds[1].revents & (POLLHUP|POLLERR)) {
967
+ // Pipe has been closed (parent has exited)
968
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "SPAWN SERVER: Parent process has exited");
969
+ break;
970
+ }
971
+
972
+ if (fds[0].revents & POLLIN) {
973
+ int client_sock = accept(server->server_sock, NULL, NULL);
974
+ if (client_sock == -1) {
975
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: accept() failed");
976
+ continue;
977
+ }
978
+
979
+ spawn_server_receive_request(client_sock, server);
980
+ }
981
+ }
982
+
983
+ // Cleanup before exiting
984
+ unlink(server->path);
985
+
986
+ // stop all children
987
+ if(spawn_server_requests) {
988
+ // nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: killing all children...");
989
+ size_t killed = 0;
990
+ for(SPAWN_REQUEST *rq = spawn_server_requests; rq ; rq = rq->next) {
991
+ kill(rq->pid, SIGTERM);
992
+ killed++;
993
+ }
994
+ while(spawn_server_requests) {
995
+ spawn_server_process_sigchld();
996
+ tinysleep();
997
+ }
998
+ // nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: all %zu children finished", killed);
999
+ }
1000
+
1001
+ exit(1);
1002
+}
1003
+
1004
+// --------------------------------------------------------------------------------------------------------------------
1005
+// management of the spawn server
1006
+
1007
+void spawn_server_destroy(SPAWN_SERVER *server) {
1008
+ if(server->pipe[0] != -1) close(server->pipe[0]);
1009
+ if(server->pipe[1] != -1) close(server->pipe[1]);
1010
+ if(server->server_sock != -1) close(server->server_sock);
1011
+
1012
+ if(server->server_pid) {
1013
+ kill(server->server_pid, SIGTERM);
1014
+ waitpid(server->server_pid, NULL, 0);
1015
+ }
1016
+
1017
+ if(server->path) {
1018
+ unlink(server->path);
1019
+ freez(server->path);
1020
+ }
1021
+
1022
+ freez((void *)server->name);
1023
+ freez(server);
1024
+}
1025
+
1026
+static bool spawn_server_create_listening_socket(SPAWN_SERVER *server) {
1027
+ if(spawn_server_is_running(server->path)) {
1028
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Server is already listening on path '%s'", server->path);
1029
+ return false;
1030
+ }
1031
+
1032
+ if ((server->server_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
1033
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to create socket()");
1034
+ return false;
1035
+ }
1036
+
1037
+ struct sockaddr_un server_addr = {
1038
+ .sun_family = AF_UNIX,
1039
+ };
1040
+ strcpy(server_addr.sun_path, server->path);
1041
+ unlink(server->path);
1042
+ errno = 0;
1043
+
1044
+ if (bind(server->server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
1045
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to bind()");
1046
+ return false;
1047
+ }
1048
+
1049
+ if (listen(server->server_sock, 5) == -1) {
1050
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to listen()");
1051
+ return false;
1052
+ }
1053
+
1054
+ return true;
1055
+}
1056
+
1057
+static void replace_stdio_with_dev_null() {
1058
+ int dev_null_fd = open("/dev/null", O_RDWR);
1059
+ if (dev_null_fd == -1) {
1060
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to open /dev/null: %s", strerror(errno));
1061
+ return;
1062
+ }
1063
+
1064
+ // Redirect stdin (fd 0)
1065
+ if (dup2(dev_null_fd, STDIN_FILENO) == -1) {
1066
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to redirect stdin to /dev/null: %s", strerror(errno));
1067
+ close(dev_null_fd);
1068
+ return;
1069
+ }
1070
+
1071
+ // Redirect stdout (fd 1)
1072
+ if (dup2(dev_null_fd, STDOUT_FILENO) == -1) {
1073
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to redirect stdout to /dev/null: %s", strerror(errno));
1074
+ close(dev_null_fd);
1075
+ return;
1076
+ }
1077
+
1078
+ // Close the original /dev/null file descriptor
1079
+ close(dev_null_fd);
1080
+}
1081
+
1082
+SPAWN_SERVER* spawn_server_create(const char *name, spawn_request_callback_t child_callback, int argc, const char **argv) {
1083
+ SPAWN_SERVER *server = callocz(1, sizeof(SPAWN_SERVER));
1084
+ server->pipe[0] = -1;
1085
+ server->pipe[1] = -1;
1086
+ server->server_sock = -1;
1087
+ server->cb = child_callback;
1088
+ server->argc = argc;
1089
+ server->argv = argv;
1090
+ server->argv0_size = (argv && argv[0]) ? strlen(argv[0]) : 0;
1091
+
1092
+ server->id = __atomic_add_fetch(&spawn_server_id, 1, __ATOMIC_RELAXED);
1093
+
1094
+ char *runtime_directory = getenv("NETDATA_CACHE_DIR");
1095
+ if(runtime_directory && !*runtime_directory) runtime_directory = NULL;
1096
+ if (runtime_directory) {
1097
+ struct stat statbuf;
1098
+
1099
+ if(!*runtime_directory)
1100
+ // it is empty
1101
+ runtime_directory = NULL;
1102
+
1103
+ else if (stat(runtime_directory, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
1104
+ // it exists and it is a directory
1105
+
1106
+ if (access(runtime_directory, W_OK) != 0) {
1107
+ // it is not writable by us
1108
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Runtime directory '%s' is not writable, falling back to '/tmp'", runtime_directory);
1109
+ runtime_directory = NULL;
1110
+ }
1111
+ }
1112
+ else {
1113
+ // it does not exist
1114
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Runtime directory '%s' does not exist, falling back to '/tmp'", runtime_directory);
1115
+ runtime_directory = NULL;
1116
+ }
1117
+ }
1118
+ if(!runtime_directory)
1119
+ runtime_directory = "/tmp";
1120
+
1121
+ char path[1024];
1122
+ if(name && *name) {
1123
+ server->name = strdupz(name);
1124
+ snprintf(path, sizeof(path), "%s/.netdata-spawn-%s.sock", runtime_directory, name);
1125
+ }
1126
+ else {
1127
+ snprintfz(path, sizeof(path), "%d-%zu", getpid(), server->id);
1128
+ server->name = strdupz(path);
1129
+ snprintf(path, sizeof(path), "%s/.netdata-spawn-%d-%zu.sock", runtime_directory, getpid(), server->id);
1130
+ }
1131
+
1132
+ server->path = strdupz(path);
1133
+
1134
+ if (!spawn_server_create_listening_socket(server))
1135
+ goto cleanup;
1136
+
1137
+ if (pipe(server->pipe) == -1) {
1138
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Cannot create status pipe()");
1139
+ goto cleanup;
1140
+ }
1141
+
1142
+ pid_t pid = fork();
1143
+ if (pid == 0) {
1144
+ // the child - the spawn server
1145
+ {
1146
+ char buf[15];
1147
+ snprintfz(buf, sizeof(buf), "spawn-%s", server->name);
1148
+ os_setproctitle(buf, server->argc, server->argv);
1149
+ }
1150
+
1151
+ replace_stdio_with_dev_null();
1152
+ os_close_all_non_std_open_fds_except((int[]){ server->server_sock, server->pipe[1] }, 2);
1153
+ spawn_server_event_loop(server);
1154
+ }
1155
+ else if (pid > 0) {
1156
+ // the parent
1157
+ server->server_pid = pid;
1158
+ close(server->server_sock); server->server_sock = -1;
1159
+ close(server->pipe[1]); server->pipe[1] = -1;
1160
+
1161
+ struct status_report sr = { 0 };
1162
+ if (read(server->pipe[0], &sr, sizeof(sr)) != sizeof(sr)) {
1163
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: cannot read() initial status report from spawn server");
1164
+ goto cleanup;
1165
+ }
1166
+
1167
+ if(sr.status != STATUS_REPORT_STARTED) {
1168
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: server did not respond with success.");
1169
+ goto cleanup;
1170
+ }
1171
+
1172
+ if(sr.started.pid != server->server_pid) {
1173
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: server sent pid %d but we have created %d.", sr.started.pid, server->server_pid);
1174
+ goto cleanup;
1175
+ }
1176
+
1177
+ return server;
1178
+ }
1179
+
1180
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Cannot fork()");
1181
+
1182
+cleanup:
1183
+ spawn_server_destroy(server);
1184
+ return NULL;
1185
+}
1186
+
1187
+// --------------------------------------------------------------------------------------------------------------------
1188
+// creating spawn server instances
1189
+
1190
+void spawn_server_exec_destroy(SPAWN_INSTANCE *instance) {
1191
+ if(instance->child_pid) kill(instance->child_pid, SIGTERM);
1192
+ if(instance->write_fd != -1) close(instance->write_fd);
1193
+ if(instance->read_fd != -1) close(instance->read_fd);
1194
+ if(instance->client_sock != -1) close(instance->client_sock);
1195
+ freez(instance);
1196
+}
1197
+
1198
+int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) {
1199
+ int rc = -1;
1200
+
1201
+ // close the child pipes, to make it exit
1202
+ if(instance->write_fd != -1) { close(instance->write_fd); instance->write_fd = -1; }
1203
+ if(instance->read_fd != -1) { close(instance->read_fd); instance->read_fd = -1; }
1204
+
1205
+ // get the result
1206
+ struct status_report sr = { 0 };
1207
+ if(read(instance->client_sock, &sr, sizeof(sr)) != sizeof(sr))
1208
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: failed to receive final status report for child %d, request %zu", instance->child_pid, instance->request_id);
1209
+
1210
+ else switch(sr.status) {
1211
+ case STATUS_REPORT_EXITED:
1212
+ rc = sr.exited.waitpid_status;
1213
+ break;
1214
+
1215
+ case STATUS_REPORT_STARTED:
1216
+ case STATUS_REPORT_FAILED:
1217
+ default:
1218
+ errno = 0;
1219
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: invalid status report to exec spawn request %zu for pid %d (status = %u)", instance->request_id, instance->child_pid, sr.status);
1220
+ break;
1221
+ }
1222
+
1223
+ instance->child_pid = 0;
1224
+ spawn_server_exec_destroy(instance);
1225
+ return rc;
1226
+}
1227
+
1228
+int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *instance) {
1229
+ // kill the child, if it is still running
1230
+ if(instance->child_pid) kill(instance->child_pid, SIGTERM);
1231
+ return spawn_server_exec_wait(server, instance);
1232
+}
1233
+
1234
+SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd, const char **argv, const void *data, size_t data_size, SPAWN_INSTANCE_TYPE type) {
1235
+ int pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 };
1236
+
1237
+ SPAWN_INSTANCE *instance = callocz(1, sizeof(SPAWN_INSTANCE));
1238
+ instance->read_fd = -1;
1239
+ instance->write_fd = -1;
1240
+
1241
+ instance->client_sock = connect_to_spawn_server(server->path, true);
1242
+ if(instance->client_sock == -1)
1243
+ goto cleanup;
1244
+
1245
+ if (pipe(pipe_stdin) == -1) {
1246
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Cannot create stdin pipe()");
1247
+ goto cleanup;
1248
+ }
1249
+
1250
+ if (pipe(pipe_stdout) == -1) {
1251
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Cannot create stdout pipe()");
1252
+ goto cleanup;
1253
+ }
1254
+
1255
+ SPAWN_REQUEST request = {
1256
+ .request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED),
1257
+ .socket = instance->client_sock,
1258
+ .fds = {
1259
+ [0] = pipe_stdin[0],
1260
+ [1] = pipe_stdout[1],
1261
+ [2] = stderr_fd,
1262
+ [3] = custom_fd,
1263
+ },
1264
+ .environment = (const char **)environ,
1265
+ .argv = argv,
1266
+ .data = data,
1267
+ .data_size = data_size,
1268
+ .type = type
1269
+ };
1270
+
1271
+ if(!spawn_server_send_request(&request))
1272
+ goto cleanup;
1273
+
1274
+ close(pipe_stdin[0]); pipe_stdin[0] = -1;
1275
+ instance->write_fd = pipe_stdin[1]; pipe_stdin[1] = -1;
1276
+
1277
+ close(pipe_stdout[1]); pipe_stdout[1] = -1;
1278
+ instance->read_fd = pipe_stdout[0]; pipe_stdout[0] = -1;
1279
+
1280
+ struct status_report sr = { 0 };
1281
+ if(read(instance->client_sock, &sr, sizeof(sr)) != sizeof(sr)) {
1282
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Failed to exec spawn request %zu (cannot get initial status report)", request.request_id);
1283
+ goto cleanup;
1284
+ }
1285
+
1286
+ switch(sr.status) {
1287
+ case STATUS_REPORT_STARTED:
1288
+ instance->child_pid = sr.started.pid;
1289
+ return instance;
1290
+
1291
+ case STATUS_REPORT_FAILED:
1292
+ errno = sr.failed.err_no;
1293
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Failed to exec spawn request %zu (check errno #1)", request.request_id);
1294
+ errno = 0;
1295
+ break;
1296
+
1297
+ case STATUS_REPORT_EXITED:
1298
+ errno = ENOEXEC;
1299
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Failed to exec spawn request %zu (check errno #2)", request.request_id);
1300
+ errno = 0;
1301
+ break;
1302
+
1303
+ default:
1304
+ errno = 0;
1305
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Invalid status report to exec spawn request %zu (received invalid data)", request.request_id);
1306
+ break;
1307
+ }
1308
+
1309
+cleanup:
1310
+ if (pipe_stdin[0] >= 0) close(pipe_stdin[0]);
1311
+ if (pipe_stdin[1] >= 0) close(pipe_stdin[1]);
1312
+ if (pipe_stdout[0] >= 0) close(pipe_stdout[0]);
1313
+ if (pipe_stdout[1] >= 0) close(pipe_stdout[1]);
1314
+ spawn_server_exec_destroy(instance);
1315
+ return NULL;
1316
+}
1317
+
1318
+#endif // !OS_WINDOWS