| 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 |