master
c 497 lines 17.5 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_WINDOWS)
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
12 pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) {
13 if(si->child_pid != -1)
14 return si->child_pid;
15
16 return (pid_t)si->dwProcessId;
17 }
18
19 static void update_cygpath_env(void) {
20 static volatile bool done = false;
21
22 if(done) return;
23 done = true;
24
25 char win_path[MAX_PATH];
26
27 // Convert Cygwin root path to Windows path
28 cygwin_conv_path(CCP_POSIX_TO_WIN_A, "/", win_path, sizeof(win_path));
29
30 nd_setenv("NETDATA_CYGWIN_BASE_PATH", win_path, 1);
31
32 nd_log(NDLS_COLLECTORS, NDLP_INFO, "Cygwin/MSYS2 base path set to '%s'", win_path);
33 }
34
35 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) {
36 update_cygpath_env();
37
38 SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
39 if(name)
40 server->name = strdupz(name);
41 else
42 server->name = strdupz("unnamed");
43
44 server->log_forwarder = log_forwarder_start();
45
46 return server;
47 }
48
49 void spawn_server_destroy(SPAWN_SERVER *server) {
50 if (server) {
51 if (server->log_forwarder) {
52 log_forwarder_stop(server->log_forwarder);
53 server->log_forwarder = NULL;
54 }
55 freez((void *)server->name);
56 freez(server);
57 }
58 }
59
60 static BUFFER *argv_to_windows(const char **argv) {
61 BUFFER *wb = buffer_create(0, NULL);
62
63 // argv[0] is the path
64 size_t b_size = strlen(argv[0]) * 2 + FILENAME_MAX;
65 CLEAN_CHAR_P *b = mallocz(b_size);
66 cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, b_size);
67
68 for(size_t i = 0; argv[i] ;i++) {
69 const char *s = (i == 0) ? b : argv[i];
70 size_t len = strlen(s);
71 buffer_need_bytes(wb, len * 2 + 1);
72
73 bool needs_quotes = false;
74 for(const char *c = s; !needs_quotes && *c ; c++) {
75 switch(*c) {
76 case ' ':
77 case '\v':
78 case '\t':
79 case '\n':
80 case '"':
81 needs_quotes = true;
82 break;
83
84 default:
85 break;
86 }
87 }
88
89 if(buffer_strlen(wb)) {
90 if (needs_quotes)
91 buffer_strcat(wb, " \"");
92 else
93 buffer_putc(wb, ' ');
94 }
95 else if (needs_quotes)
96 buffer_putc(wb, '"');
97
98 for(const char *c = s; *c ; c++) {
99 switch(*c) {
100 case '"':
101 buffer_putc(wb, '\\');
102 // fall through
103
104 default:
105 buffer_putc(wb, *c);
106 break;
107 }
108 }
109
110 if(needs_quotes)
111 buffer_strcat(wb, "\"");
112 }
113
114 return wb;
115 }
116
117 int set_fd_blocking(int fd) {
118 int flags = fcntl(fd, F_GETFL, 0);
119 if (flags == -1) {
120 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: fcntl(F_GETFL) failed");
121 return -1;
122 }
123
124 flags &= ~O_NONBLOCK;
125 if (fcntl(fd, F_SETFL, flags) == -1) {
126 nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: fcntl(F_SETFL) failed");
127 return -1;
128 }
129
130 return 0;
131 }
132
133 //static void print_environment_block(char *env_block) {
134 // if (env_block == NULL) {
135 // fprintf(stderr, "Environment block is NULL\n");
136 // return;
137 // }
138 //
139 // char *env = env_block;
140 // while (*env) {
141 // fprintf(stderr, "ENVIRONMENT: %s\n", env);
142 // // Move to the next string in the block
143 // env += strlen(env) + 1;
144 // }
145 //}
146
147 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) {
148 static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
149
150 if (type != SPAWN_INSTANCE_TYPE_EXEC)
151 return NULL;
152
153 if(!argv || !argv[0] || !*argv[0])
154 return NULL;
155
156 int pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 }, pipe_stderr[2] = { -1, -1 };
157
158 errno_clear();
159
160 SPAWN_INSTANCE *instance = callocz(1, sizeof(*instance));
161 instance->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
162
163 CLEAN_BUFFER *wb = argv_to_windows(argv);
164 char *command = (char *)buffer_tostring(wb);
165
166 if (pipe(pipe_stdin) == -1) {
167 nd_log(NDLS_COLLECTORS, NDLP_ERR,
168 "SPAWN PARENT: Cannot create stdin pipe() for request No %zu, command: %s",
169 instance->request_id, command);
170 goto cleanup;
171 }
172
173 if (pipe(pipe_stdout) == -1) {
174 nd_log(NDLS_COLLECTORS, NDLP_ERR,
175 "SPAWN PARENT: Cannot create stdout pipe() for request No %zu, command: %s",
176 instance->request_id, command);
177 goto cleanup;
178 }
179
180 if (pipe(pipe_stderr) == -1) {
181 nd_log(NDLS_COLLECTORS, NDLP_ERR,
182 "SPAWN PARENT: Cannot create stderr pipe() for request No %zu, command: %s",
183 instance->request_id, command);
184 goto cleanup;
185 }
186
187 // Ensure pipes are in blocking mode
188 if (set_fd_blocking(pipe_stdin[PIPE_READ]) == -1 || set_fd_blocking(pipe_stdin[PIPE_WRITE]) == -1 ||
189 set_fd_blocking(pipe_stdout[PIPE_READ]) == -1 || set_fd_blocking(pipe_stdout[PIPE_WRITE]) == -1 ||
190 set_fd_blocking(pipe_stderr[PIPE_READ]) == -1 || set_fd_blocking(pipe_stderr[PIPE_WRITE]) == -1) {
191 nd_log(NDLS_COLLECTORS, NDLP_ERR,
192 "SPAWN PARENT: Failed to set blocking I/O on pipes for request No %zu, command: %s",
193 instance->request_id, command);
194 goto cleanup;
195 }
196
197 // do not run multiple times this section
198 // to prevent handles leaking
199 spinlock_lock(&spinlock);
200
201 // Convert POSIX file descriptors to Windows handles
202 HANDLE stdin_read_handle = (HANDLE)_get_osfhandle(pipe_stdin[PIPE_READ]);
203 HANDLE stdout_write_handle = (HANDLE)_get_osfhandle(pipe_stdout[PIPE_WRITE]);
204 HANDLE stderr_write_handle = (HANDLE)_get_osfhandle(pipe_stderr[PIPE_WRITE]);
205
206 if (stdin_read_handle == INVALID_HANDLE_VALUE || stdout_write_handle == INVALID_HANDLE_VALUE || stderr_write_handle == INVALID_HANDLE_VALUE) {
207 spinlock_unlock(&spinlock);
208 nd_log(NDLS_COLLECTORS, NDLP_ERR,
209 "SPAWN PARENT: Invalid handle value(s) for request No %zu, command: %s",
210 instance->request_id, command);
211 goto cleanup;
212 }
213
214 // Set handle inheritance
215 if (!SetHandleInformation(stdin_read_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
216 !SetHandleInformation(stdout_write_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
217 !SetHandleInformation(stderr_write_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
218 spinlock_unlock(&spinlock);
219 nd_log(NDLS_COLLECTORS, NDLP_ERR,
220 "SPAWN PARENT: Cannot set handle(s) inheritance for request No %zu, command: %s",
221 instance->request_id, command);
222 goto cleanup;
223 }
224
225 // Set up the STARTUPINFO structure
226 STARTUPINFO si;
227 PROCESS_INFORMATION pi;
228 ZeroMemory(&si, sizeof(si));
229 si.cb = sizeof(si);
230 si.dwFlags = STARTF_USESTDHANDLES;
231 si.hStdInput = stdin_read_handle;
232 si.hStdOutput = stdout_write_handle;
233 si.hStdError = stderr_write_handle;
234
235 // Retrieve the current environment block
236 char* env_block = GetEnvironmentStrings();
237 // print_environment_block(env_block);
238
239 nd_log(NDLS_COLLECTORS, NDLP_INFO,
240 "SPAWN PARENT: Running request No %zu, command: '%s'",
241 instance->request_id, command);
242
243 int fds_to_keep_open[] = { pipe_stdin[PIPE_READ], pipe_stdout[PIPE_WRITE], pipe_stderr[PIPE_WRITE] };
244 os_close_all_non_std_open_fds_except(fds_to_keep_open, 3, CLOSE_RANGE_CLOEXEC);
245
246 // Spawn the process
247 errno_clear();
248 if (!CreateProcess(NULL, command, NULL, NULL, TRUE, 0, env_block, NULL, &si, &pi)) {
249 spinlock_unlock(&spinlock);
250 nd_log(NDLS_COLLECTORS, NDLP_ERR,
251 "SPAWN PARENT: cannot CreateProcess() for request No %zu, command: %s",
252 instance->request_id, command);
253 goto cleanup;
254 }
255
256 FreeEnvironmentStrings(env_block);
257
258 // When we create a process with the CreateProcess function, it returns two handles:
259 // - one for the process (pi.hProcess) and
260 // - one for the primary thread of the new process (pi.hThread).
261 // Both of these handles need to be explicitly closed when they are no longer needed.
262 CloseHandle(pi.hThread);
263
264 // end of the critical section
265 spinlock_unlock(&spinlock);
266
267 // Close unused pipe ends
268 close(pipe_stdin[PIPE_READ]); pipe_stdin[PIPE_READ] = -1;
269 close(pipe_stdout[PIPE_WRITE]); pipe_stdout[PIPE_WRITE] = -1;
270 close(pipe_stderr[PIPE_WRITE]); pipe_stderr[PIPE_WRITE] = -1;
271
272 // Store process information in instance
273 instance->dwProcessId = pi.dwProcessId;
274 instance->child_pid = cygwin_winpid_to_pid((pid_t)pi.dwProcessId);
275 instance->process_handle = pi.hProcess;
276
277 // Convert handles to POSIX file descriptors
278 instance->write_fd = pipe_stdin[PIPE_WRITE];
279 instance->read_fd = pipe_stdout[PIPE_READ];
280 instance->stderr_fd = pipe_stderr[PIPE_READ];
281
282 // Add stderr_fd to the log forwarder
283 log_forwarder_add_fd(server->log_forwarder, instance->stderr_fd);
284 log_forwarder_annotate_fd_name(server->log_forwarder, instance->stderr_fd, command);
285 log_forwarder_annotate_fd_pid(server->log_forwarder, instance->stderr_fd, spawn_server_instance_pid(instance));
286
287 errno_clear();
288 nd_log(NDLS_COLLECTORS, NDLP_INFO,
289 "SPAWN PARENT: created process for request No %zu, pid %d (winpid %d), command: %s",
290 instance->request_id, (int)instance->child_pid, (int)pi.dwProcessId, command);
291
292 return instance;
293
294 cleanup:
295 if (pipe_stdin[PIPE_READ] >= 0) close(pipe_stdin[PIPE_READ]);
296 if (pipe_stdin[PIPE_WRITE] >= 0) close(pipe_stdin[PIPE_WRITE]);
297 if (pipe_stdout[PIPE_READ] >= 0) close(pipe_stdout[PIPE_READ]);
298 if (pipe_stdout[PIPE_WRITE] >= 0) close(pipe_stdout[PIPE_WRITE]);
299 if (pipe_stderr[PIPE_READ] >= 0) close(pipe_stderr[PIPE_READ]);
300 if (pipe_stderr[PIPE_WRITE] >= 0) close(pipe_stderr[PIPE_WRITE]);
301 freez(instance);
302 return NULL;
303 }
304
305 static char* GetErrorString(DWORD errorCode) {
306 DWORD lastError = GetLastError();
307
308 LPVOID lpMsgBuf;
309 DWORD bufLen = FormatMessage(
310 FORMAT_MESSAGE_ALLOCATE_BUFFER |
311 FORMAT_MESSAGE_FROM_SYSTEM |
312 FORMAT_MESSAGE_IGNORE_INSERTS,
313 NULL,
314 errorCode,
315 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
316 (LPTSTR) &lpMsgBuf,
317 0, NULL );
318
319 SetLastError(lastError);
320
321 if (bufLen) {
322 char* errorString = (char*)LocalAlloc(LMEM_FIXED, bufLen + 1);
323 if (errorString) {
324 strcpy(errorString, (char*)lpMsgBuf);
325 }
326 LocalFree(lpMsgBuf);
327 return errorString;
328 }
329
330 return NULL;
331 }
332
333 static void TerminateChildProcesses(SPAWN_INSTANCE *si) {
334 HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
335 if (hSnapshot == INVALID_HANDLE_VALUE)
336 return;
337
338 PROCESSENTRY32 pe;
339 pe.dwSize = sizeof(PROCESSENTRY32);
340
341 if (Process32First(hSnapshot, &pe)) {
342 do {
343 if (pe.th32ParentProcessID == si->dwProcessId) {
344 HANDLE hChildProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID);
345 if (hChildProcess) {
346 nd_log(NDLS_COLLECTORS, NDLP_WARNING,
347 "SPAWN PARENT: killing subprocess %u of request No %zu, pid %d (winpid %u)",
348 pe.th32ProcessID, si->request_id, (int)si->child_pid, si->dwProcessId);
349
350 TerminateProcess(hChildProcess, STATUS_CONTROL_C_EXIT);
351 CloseHandle(hChildProcess);
352 }
353 }
354 } while (Process32Next(hSnapshot, &pe));
355 }
356
357 CloseHandle(hSnapshot);
358 }
359
360 int map_status_code_to_signal(DWORD status_code) {
361 switch (status_code) {
362 case STATUS_ACCESS_VIOLATION:
363 return SIGSEGV;
364 case STATUS_ILLEGAL_INSTRUCTION:
365 return SIGILL;
366 case STATUS_FLOAT_DIVIDE_BY_ZERO:
367 case STATUS_INTEGER_DIVIDE_BY_ZERO:
368 case STATUS_ARRAY_BOUNDS_EXCEEDED:
369 case STATUS_FLOAT_OVERFLOW:
370 case STATUS_FLOAT_UNDERFLOW:
371 case STATUS_FLOAT_INVALID_OPERATION:
372 return SIGFPE;
373 case STATUS_BREAKPOINT:
374 case STATUS_SINGLE_STEP:
375 return SIGTRAP;
376 case STATUS_STACK_OVERFLOW:
377 case STATUS_INVALID_HANDLE:
378 case STATUS_INVALID_PARAMETER:
379 case STATUS_NO_MEMORY:
380 case STATUS_PRIVILEGED_INSTRUCTION:
381 case STATUS_DLL_NOT_FOUND:
382 case STATUS_DLL_INIT_FAILED:
383 case STATUS_ORDINAL_NOT_FOUND:
384 case STATUS_ENTRYPOINT_NOT_FOUND:
385 case STATUS_CONTROL_STACK_VIOLATION:
386 case STATUS_STACK_BUFFER_OVERRUN:
387 case STATUS_ASSERTION_FAILURE:
388 case STATUS_INVALID_CRUNTIME_PARAMETER:
389 case STATUS_HEAP_CORRUPTION:
390 return SIGABRT;
391 case STATUS_CONTROL_C_EXIT:
392 return SIGTERM; // we use this internally as such
393 case STATUS_FATAL_APP_EXIT:
394 return SIGTERM;
395 default:
396 return (status_code & 0xFF) << 8;
397 }
398 }
399
400 int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si, int timeout_ms __maybe_unused) {
401 // this gives some warnings at the spawn-tester, but it is generally better
402 // to have them, to avoid abnormal shutdown of the plugins
403 if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
404 if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
405
406 if(timeout_ms > 0)
407 WaitForSingleObject(si->process_handle, timeout_ms);
408
409 errno_clear();
410 if(si->child_pid != -1 && kill(si->child_pid, SIGTERM) != 0)
411 nd_log(NDLS_COLLECTORS, NDLP_ERR,
412 "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), failed to be killed",
413 si->request_id, (int)si->child_pid, si->dwProcessId);
414
415 errno_clear();
416 if(TerminateProcess(si->process_handle, STATUS_CONTROL_C_EXIT) == 0)
417 nd_log(NDLS_COLLECTORS, NDLP_ERR,
418 "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), failed to be terminated",
419 si->request_id, (int)si->child_pid, si->dwProcessId);
420
421 errno_clear();
422 TerminateChildProcesses(si);
423
424 if(si->stderr_fd != -1) {
425 if(!log_forwarder_del_and_close_fd(server->log_forwarder, si->stderr_fd))
426 close(si->stderr_fd);
427
428 si->stderr_fd = -1;
429 }
430
431 return spawn_server_exec_wait(server, si);
432 }
433
434 SPAWN_TIMEDWAIT_RESULT spawn_server_exec_timedwait(SPAWN_SERVER *server, SPAWN_INSTANCE *si, int timeout_ms, int *status) {
435 if(!si) { if(status) *status = -1; return SPAWN_TIMEDWAIT_EXITED; }
436
437 if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
438 if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
439
440 // a negative timeout would become a huge DWORD (~INFINITE) to WaitForSingleObject; clamp to poll-once
441 if(timeout_ms < 0) timeout_ms = 0;
442
443 DWORD wait_rc = WaitForSingleObject(si->process_handle, (DWORD)timeout_ms);
444 if(wait_rc == WAIT_TIMEOUT)
445 // the process is still running; the caller decides whether to keep waiting or kill it
446 return SPAWN_TIMEDWAIT_RUNNING;
447
448 if(wait_rc == WAIT_FAILED) {
449 // the handle is unusable, so we cannot confirm the process exited. We must NOT resolve as
450 // EXITED and free the instance (it may still be alive), and we must NOT report RUNNING
451 // either (a caller looping on RUNNING with a 0/"wait forever" timeout would spin forever).
452 // Report ERROR: the caller keeps the instance and reclaims it by killing it.
453 nd_log(NDLS_COLLECTORS, NDLP_ERR,
454 "SPAWN PARENT: WaitForSingleObject() failed (err %lu) for request No %zu, pid %d (winpid %u)",
455 (unsigned long)GetLastError(), si->request_id, (int)si->child_pid, si->dwProcessId);
456 return SPAWN_TIMEDWAIT_ERROR;
457 }
458
459 // WAIT_OBJECT_0: the process exited; the blocking wait returns immediately now.
460 int st = spawn_server_exec_wait(server, si);
461 if(status) *status = st;
462 return SPAWN_TIMEDWAIT_EXITED;
463 }
464
465 int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
466 if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
467 if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
468
469 // wait for the process to end
470 WaitForSingleObject(si->process_handle, INFINITE);
471
472 DWORD exit_code = -1;
473 GetExitCodeProcess(si->process_handle, &exit_code);
474 CloseHandle(si->process_handle);
475
476 char *err = GetErrorString(exit_code);
477
478 nd_log(NDLS_COLLECTORS, NDLP_INFO,
479 "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), exited with code %u (0x%x): %s",
480 si->request_id, (int)si->child_pid, si->dwProcessId,
481 (unsigned)exit_code, (unsigned)exit_code, err ? err : "(no reason text)");
482
483 if(err)
484 LocalFree(err);
485
486 if(si->stderr_fd != -1) {
487 if(!log_forwarder_del_and_close_fd(server->log_forwarder, si->stderr_fd))
488 close(si->stderr_fd);
489
490 si->stderr_fd = -1;
491 }
492
493 freez(si);
494 return map_status_code_to_signal(exit_code);
495 }
496
497 #endif