diff options
| -rw-r--r-- | win32/process.c | 162 |
1 files changed, 161 insertions, 1 deletions
diff --git a/win32/process.c b/win32/process.c index f88a4898c..7840741c4 100644 --- a/win32/process.c +++ b/win32/process.c | |||
| @@ -412,13 +412,173 @@ UNUSED_PARAM | |||
| 412 | return sp; | 412 | return sp; |
| 413 | } | 413 | } |
| 414 | 414 | ||
| 415 | /* | ||
| 416 | * Terminates the process corresponding to the process ID and all of its | ||
| 417 | * directly and indirectly spawned subprocesses. | ||
| 418 | * | ||
| 419 | * This way of terminating the processes is not gentle: the processes get | ||
| 420 | * no chance of cleaning up after themselves (closing file handles, removing | ||
| 421 | * .lock files, terminating spawned processes (if any), etc). | ||
| 422 | */ | ||
| 423 | static int terminate_process_tree(HANDLE main_process, int exit_status) | ||
| 424 | { | ||
| 425 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); | ||
| 426 | PROCESSENTRY32 entry; | ||
| 427 | DWORD pids[16384]; | ||
| 428 | int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0; | ||
| 429 | pid_t pid = GetProcessId(main_process); | ||
| 430 | |||
| 431 | pids[0] = (DWORD)pid; | ||
| 432 | len = 1; | ||
| 433 | |||
| 434 | /* | ||
| 435 | * Even if Process32First()/Process32Next() seem to traverse the | ||
| 436 | * processes in topological order (i.e. parent processes before | ||
| 437 | * child processes), there is nothing in the Win32 API documentation | ||
| 438 | * suggesting that this is guaranteed. | ||
| 439 | * | ||
| 440 | * Therefore, run through them at least twice and stop when no more | ||
| 441 | * process IDs were added to the list. | ||
| 442 | */ | ||
| 443 | for (;;) { | ||
| 444 | int orig_len = len; | ||
| 445 | |||
| 446 | memset(&entry, 0, sizeof(entry)); | ||
| 447 | entry.dwSize = sizeof(entry); | ||
| 448 | |||
| 449 | if (!Process32First(snapshot, &entry)) | ||
| 450 | break; | ||
| 451 | |||
| 452 | do { | ||
| 453 | for (i = len - 1; i >= 0; i--) { | ||
| 454 | if (pids[i] == entry.th32ProcessID) | ||
| 455 | break; | ||
| 456 | if (pids[i] == entry.th32ParentProcessID) | ||
| 457 | pids[len++] = entry.th32ProcessID; | ||
| 458 | } | ||
| 459 | } while (len < max_len && Process32Next(snapshot, &entry)); | ||
| 460 | |||
| 461 | if (orig_len == len || len >= max_len) | ||
| 462 | break; | ||
| 463 | } | ||
| 464 | |||
| 465 | for (i = len - 1; i > 0; i--) { | ||
| 466 | HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]); | ||
| 467 | |||
| 468 | if (process) { | ||
| 469 | if (!TerminateProcess(process, exit_status)) | ||
| 470 | ret = -1; | ||
| 471 | CloseHandle(process); | ||
| 472 | } | ||
| 473 | } | ||
| 474 | if (!TerminateProcess(main_process, exit_status)) | ||
| 475 | ret = -1; | ||
| 476 | CloseHandle(main_process); | ||
| 477 | |||
| 478 | return ret; | ||
| 479 | } | ||
| 480 | |||
| 481 | /** | ||
| 482 | * Determine whether a process runs in the same architecture as the current | ||
| 483 | * one. That test is required before we assume that GetProcAddress() returns | ||
| 484 | * a valid address *for the target process*. | ||
| 485 | */ | ||
| 486 | static inline int process_architecture_matches_current(HANDLE process) | ||
| 487 | { | ||
| 488 | static BOOL current_is_wow = -1; | ||
| 489 | BOOL is_wow; | ||
| 490 | |||
| 491 | if (current_is_wow == -1 && | ||
| 492 | !IsWow64Process (GetCurrentProcess(), ¤t_is_wow)) | ||
| 493 | current_is_wow = -2; | ||
| 494 | if (current_is_wow == -2) | ||
| 495 | return 0; /* could not determine current process' WoW-ness */ | ||
| 496 | if (!IsWow64Process (process, &is_wow)) | ||
| 497 | return 0; /* cannot determine */ | ||
| 498 | return is_wow == current_is_wow; | ||
| 499 | } | ||
| 500 | |||
| 501 | /** | ||
| 502 | * This function tries to terminate a Win32 process, as gently as possible. | ||
| 503 | * | ||
| 504 | * At first, we will attempt to inject a thread that calls ExitProcess(). If | ||
| 505 | * that fails, we will fall back to terminating the entire process tree. | ||
| 506 | * | ||
| 507 | * Note: as kernel32.dll is loaded before any process, the other process and | ||
| 508 | * this process will have ExitProcess() at the same address. | ||
| 509 | * | ||
| 510 | * This function expects the process handle to have the access rights for | ||
| 511 | * CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, | ||
| 512 | * PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ. | ||
| 513 | * | ||
| 514 | * The idea comes from the Dr Dobb's article "A Safer Alternative to | ||
| 515 | * TerminateProcess()" by Andrew Tucker (July 1, 1999), | ||
| 516 | * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547 | ||
| 517 | * | ||
| 518 | * If this method fails, we fall back to running terminate_process_tree(). | ||
| 519 | */ | ||
| 520 | static int exit_process(HANDLE process, int exit_code) | ||
| 521 | { | ||
| 522 | DWORD code; | ||
| 523 | |||
| 524 | if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) { | ||
| 525 | static int initialized; | ||
| 526 | static LPTHREAD_START_ROUTINE exit_process_address; | ||
| 527 | PVOID arg = (PVOID)(intptr_t)exit_code; | ||
| 528 | DWORD thread_id; | ||
| 529 | HANDLE thread = NULL; | ||
| 530 | |||
| 531 | if (!initialized) { | ||
| 532 | HINSTANCE kernel32 = GetModuleHandle("kernel32"); | ||
| 533 | if (!kernel32) { | ||
| 534 | fprintf(stderr, "BUG: cannot find kernel32"); | ||
| 535 | return -1; | ||
| 536 | } | ||
| 537 | exit_process_address = (LPTHREAD_START_ROUTINE) | ||
| 538 | GetProcAddress(kernel32, "ExitProcess"); | ||
| 539 | initialized = 1; | ||
| 540 | } | ||
| 541 | if (!exit_process_address || | ||
| 542 | !process_architecture_matches_current(process)) | ||
| 543 | return terminate_process_tree(process, exit_code); | ||
| 544 | |||
| 545 | thread = CreateRemoteThread(process, NULL, 0, | ||
| 546 | exit_process_address, | ||
| 547 | arg, 0, &thread_id); | ||
| 548 | if (thread) { | ||
| 549 | DWORD result; | ||
| 550 | |||
| 551 | CloseHandle(thread); | ||
| 552 | /* | ||
| 553 | * If the process survives for 10 seconds (a completely | ||
| 554 | * arbitrary value picked from thin air), fall back to | ||
| 555 | * killing the process tree via TerminateProcess(). | ||
| 556 | */ | ||
| 557 | result = WaitForSingleObject(process, 10000); | ||
| 558 | if (result == WAIT_OBJECT_0) { | ||
| 559 | CloseHandle(process); | ||
| 560 | return 0; | ||
| 561 | } | ||
| 562 | } | ||
| 563 | |||
| 564 | return terminate_process_tree(process, exit_code); | ||
| 565 | } | ||
| 566 | |||
| 567 | return 0; | ||
| 568 | } | ||
| 569 | |||
| 415 | int kill(pid_t pid, int sig) | 570 | int kill(pid_t pid, int sig) |
| 416 | { | 571 | { |
| 417 | HANDLE h; | 572 | HANDLE h; |
| 418 | 573 | ||
| 419 | if (pid > 0 && sig == SIGTERM) { | 574 | if (pid > 0 && sig == SIGTERM) { |
| 575 | if ((h = OpenProcess(SYNCHRONIZE | PROCESS_CREATE_THREAD | | ||
| 576 | PROCESS_QUERY_INFORMATION | | ||
| 577 | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | | ||
| 578 | PROCESS_VM_READ, FALSE, pid))) | ||
| 579 | return exit_process(h, 128 + sig); | ||
| 420 | if ((h=OpenProcess(PROCESS_TERMINATE, FALSE, pid)) != NULL && | 580 | if ((h=OpenProcess(PROCESS_TERMINATE, FALSE, pid)) != NULL && |
| 421 | TerminateProcess(h, 0)) { | 581 | terminate_process_tree(h, 128 + sig)) { |
| 422 | CloseHandle(h); | 582 | CloseHandle(h); |
| 423 | return 0; | 583 | return 0; |
| 424 | } | 584 | } |
