diff options
author | Ron Yorston <rmy@pobox.com> | 2017-08-27 08:47:20 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2017-08-27 08:47:20 +0100 |
commit | c8544e127085b79891003af39d32e4b89c54aab4 (patch) | |
tree | f97aec2baaf962346591d9924dc908ae87ccf650 | |
parent | b122f9ae692eb5986f0e84826435aea8f2cf35e1 (diff) | |
download | busybox-w32-c8544e127085b79891003af39d32e4b89c54aab4.tar.gz busybox-w32-c8544e127085b79891003af39d32e4b89c54aab4.tar.bz2 busybox-w32-c8544e127085b79891003af39d32e4b89c54aab4.zip |
win32: improvements to implementation of kill(2)
Extend the implementation of kill(2) so that:
- Sending the TERM signal asks the target process to exit. As on
Unix it may not comply.
- Sending the KILL signal forcibly terminates the target process.
- Using a negative pid treats the target process as a process
group leader and signals it and all of its descendants.
- Using a pid of zero treats the current process as a process
group leader and signals it and all of its descendants.
Signed-off-by: Ron Yorston <rmy@pobox.com>
-rw-r--r-- | win32/process.c | 181 |
1 files changed, 94 insertions, 87 deletions
diff --git a/win32/process.c b/win32/process.c index 8ecd14176..db2cf87d1 100644 --- a/win32/process.c +++ b/win32/process.c | |||
@@ -401,23 +401,25 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags UNUSED_PAR | |||
401 | return sp; | 401 | return sp; |
402 | } | 402 | } |
403 | 403 | ||
404 | /* | 404 | /** |
405 | * Terminates the process corresponding to the process ID and all of its | 405 | * If the process ID is positive invoke the callback for that process |
406 | * directly and indirectly spawned subprocesses. | 406 | * only. If negative or zero invoke the callback for all descendants |
407 | * | 407 | * of the indicated process. Zero indicates the current process; negative |
408 | * This way of terminating the processes is not gentle: the processes get | 408 | * indicates the process with process ID -pid. |
409 | * no chance of cleaning up after themselves (closing file handles, removing | ||
410 | * .lock files, terminating spawned processes (if any), etc). | ||
411 | */ | 409 | */ |
412 | static int terminate_process_tree(HANDLE main_process, int exit_status) | 410 | typedef int (*kill_callback)(pid_t pid, int exit_code); |
411 | |||
412 | static int kill_pids(pid_t pid, int exit_code, kill_callback killer) | ||
413 | { | 413 | { |
414 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); | ||
415 | PROCESSENTRY32 entry; | ||
416 | DWORD pids[16384]; | 414 | DWORD pids[16384]; |
417 | int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0; | 415 | int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0; |
418 | pid_t pid = GetProcessId(main_process); | ||
419 | 416 | ||
420 | pids[0] = (DWORD)pid; | 417 | if(pid > 0) |
418 | pids[0] = (DWORD)pid; | ||
419 | else if (pid == 0) | ||
420 | pids[0] = (DWORD)getpid(); | ||
421 | else | ||
422 | pids[0] = (DWORD)-pid; | ||
421 | len = 1; | 423 | len = 1; |
422 | 424 | ||
423 | /* | 425 | /* |
@@ -429,40 +431,46 @@ static int terminate_process_tree(HANDLE main_process, int exit_status) | |||
429 | * Therefore, run through them at least twice and stop when no more | 431 | * Therefore, run through them at least twice and stop when no more |
430 | * process IDs were added to the list. | 432 | * process IDs were added to the list. |
431 | */ | 433 | */ |
432 | for (;;) { | 434 | if (pid <= 0) { |
433 | int orig_len = len; | 435 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); |
434 | 436 | ||
435 | memset(&entry, 0, sizeof(entry)); | 437 | if (snapshot == INVALID_HANDLE_VALUE) { |
436 | entry.dwSize = sizeof(entry); | 438 | errno = err_win_to_posix(GetLastError()); |
439 | return -1; | ||
440 | } | ||
437 | 441 | ||
438 | if (!Process32First(snapshot, &entry)) | 442 | for (;;) { |
439 | break; | 443 | PROCESSENTRY32 entry; |
444 | int orig_len = len; | ||
440 | 445 | ||
441 | do { | 446 | memset(&entry, 0, sizeof(entry)); |
442 | for (i = len - 1; i >= 0; i--) { | 447 | entry.dwSize = sizeof(entry); |
443 | if (pids[i] == entry.th32ProcessID) | ||
444 | break; | ||
445 | if (pids[i] == entry.th32ParentProcessID) | ||
446 | pids[len++] = entry.th32ProcessID; | ||
447 | } | ||
448 | } while (len < max_len && Process32Next(snapshot, &entry)); | ||
449 | 448 | ||
450 | if (orig_len == len || len >= max_len) | 449 | if (!Process32First(snapshot, &entry)) |
451 | break; | 450 | break; |
452 | } | 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 | } | ||
453 | 464 | ||
454 | for (i = len - 1; i > 0; i--) { | 465 | CloseHandle(snapshot); |
455 | HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]); | 466 | } |
456 | 467 | ||
457 | if (process) { | 468 | for (i = len - 1; i >= 0; i--) { |
458 | if (!TerminateProcess(process, exit_status)) | 469 | if (killer(pids[i], exit_code)) { |
459 | ret = -1; | 470 | errno = err_win_to_posix(GetLastError()); |
460 | CloseHandle(process); | 471 | ret = -1; |
461 | } | 472 | } |
462 | } | 473 | } |
463 | if (!TerminateProcess(main_process, exit_status)) | ||
464 | ret = -1; | ||
465 | CloseHandle(main_process); | ||
466 | 474 | ||
467 | return ret; | 475 | return ret; |
468 | } | 476 | } |
@@ -488,96 +496,95 @@ static inline int process_architecture_matches_current(HANDLE process) | |||
488 | } | 496 | } |
489 | 497 | ||
490 | /** | 498 | /** |
491 | * This function tries to terminate a Win32 process, as gently as possible. | 499 | * This function tries to terminate a Win32 process, as gently as possible, |
492 | * | 500 | * by injecting a thread that calls ExitProcess(). |
493 | * At first, we will attempt to inject a thread that calls ExitProcess(). If | ||
494 | * that fails, we will fall back to terminating the entire process tree. | ||
495 | * | 501 | * |
496 | * Note: as kernel32.dll is loaded before any process, the other process and | 502 | * Note: as kernel32.dll is loaded before any process, the other process and |
497 | * this process will have ExitProcess() at the same address. | 503 | * this process will have ExitProcess() at the same address. |
498 | * | 504 | * |
499 | * This function expects the process handle to have the access rights for | ||
500 | * CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, | ||
501 | * PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ. | ||
502 | * | ||
503 | * The idea comes from the Dr Dobb's article "A Safer Alternative to | 505 | * The idea comes from the Dr Dobb's article "A Safer Alternative to |
504 | * TerminateProcess()" by Andrew Tucker (July 1, 1999), | 506 | * TerminateProcess()" by Andrew Tucker (July 1, 1999), |
505 | * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547 | 507 | * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547 |
506 | * | 508 | * |
507 | * If this method fails, we fall back to running terminate_process_tree(). | ||
508 | */ | 509 | */ |
509 | static int exit_process(HANDLE process, int exit_code) | 510 | static int exit_process(pid_t pid, int exit_code) |
510 | { | 511 | { |
512 | HANDLE process; | ||
511 | DWORD code; | 513 | DWORD code; |
514 | int ret = 0; | ||
515 | |||
516 | if (!(process = OpenProcess(SYNCHRONIZE | PROCESS_CREATE_THREAD | | ||
517 | PROCESS_QUERY_INFORMATION | | ||
518 | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | | ||
519 | PROCESS_VM_READ, FALSE, pid))) { | ||
520 | return -1; | ||
521 | } | ||
512 | 522 | ||
513 | if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) { | 523 | if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) { |
514 | static int initialized; | 524 | static int initialized; |
515 | static LPTHREAD_START_ROUTINE exit_process_address; | 525 | static LPTHREAD_START_ROUTINE exit_process_address; |
516 | PVOID arg = (PVOID)(intptr_t)exit_code; | 526 | PVOID arg = (PVOID)(intptr_t)exit_code; |
517 | DWORD thread_id; | 527 | DWORD thread_id; |
518 | HANDLE thread = NULL; | 528 | HANDLE thread; |
519 | 529 | ||
520 | if (!initialized) { | 530 | if (!initialized) { |
521 | HINSTANCE kernel32 = GetModuleHandle("kernel32"); | 531 | HINSTANCE kernel32 = GetModuleHandle("kernel32"); |
522 | if (!kernel32) { | 532 | if (!kernel32) { |
523 | fprintf(stderr, "BUG: cannot find kernel32"); | 533 | fprintf(stderr, "BUG: cannot find kernel32"); |
524 | return -1; | 534 | ret = -1; |
535 | goto finish; | ||
525 | } | 536 | } |
526 | exit_process_address = (LPTHREAD_START_ROUTINE) | 537 | exit_process_address = (LPTHREAD_START_ROUTINE) |
527 | GetProcAddress(kernel32, "ExitProcess"); | 538 | GetProcAddress(kernel32, "ExitProcess"); |
528 | initialized = 1; | 539 | initialized = 1; |
529 | } | 540 | } |
530 | if (!exit_process_address || | 541 | if (!exit_process_address || |
531 | !process_architecture_matches_current(process)) | 542 | !process_architecture_matches_current(process)) { |
532 | return terminate_process_tree(process, exit_code); | 543 | ret = -1; |
544 | goto finish; | ||
545 | } | ||
533 | 546 | ||
534 | thread = CreateRemoteThread(process, NULL, 0, | 547 | if ((thread = CreateRemoteThread(process, NULL, 0, |
535 | exit_process_address, | 548 | exit_process_address, |
536 | arg, 0, &thread_id); | 549 | arg, 0, &thread_id))) { |
537 | if (thread) { | ||
538 | DWORD result; | ||
539 | |||
540 | CloseHandle(thread); | 550 | CloseHandle(thread); |
541 | /* | ||
542 | * If the process survives for 10 seconds (a completely | ||
543 | * arbitrary value picked from thin air), fall back to | ||
544 | * killing the process tree via TerminateProcess(). | ||
545 | */ | ||
546 | result = WaitForSingleObject(process, 10000); | ||
547 | if (result == WAIT_OBJECT_0) { | ||
548 | CloseHandle(process); | ||
549 | return 0; | ||
550 | } | ||
551 | } | 551 | } |
552 | |||
553 | return terminate_process_tree(process, exit_code); | ||
554 | } | 552 | } |
555 | 553 | ||
556 | return 0; | 554 | finish: |
555 | CloseHandle(process); | ||
556 | return ret; | ||
557 | } | 557 | } |
558 | 558 | ||
559 | int kill(pid_t pid, int sig) | 559 | /* |
560 | * This way of terminating processes is not gentle: they get no chance to | ||
561 | * clean up after themselves (closing file handles, removing .lock files, | ||
562 | * terminating spawned processes (if any), etc). | ||
563 | */ | ||
564 | static int terminate_process(pid_t pid, int exit_code) | ||
560 | { | 565 | { |
561 | HANDLE h; | 566 | HANDLE process; |
562 | 567 | int ret; | |
563 | if (pid > 0 && sig == SIGTERM) { | ||
564 | if ((h = OpenProcess(SYNCHRONIZE | PROCESS_CREATE_THREAD | | ||
565 | PROCESS_QUERY_INFORMATION | | ||
566 | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | | ||
567 | PROCESS_VM_READ, FALSE, pid))) | ||
568 | return exit_process(h, 128 + sig); | ||
569 | if ((h=OpenProcess(PROCESS_TERMINATE, FALSE, pid)) != NULL && | ||
570 | terminate_process_tree(h, 128 + sig)) { | ||
571 | CloseHandle(h); | ||
572 | return 0; | ||
573 | } | ||
574 | 568 | ||
575 | errno = err_win_to_posix(GetLastError()); | 569 | if (!(process=OpenProcess(PROCESS_TERMINATE, FALSE, pid))) { |
576 | if (h != NULL) | ||
577 | CloseHandle(h); | ||
578 | return -1; | 570 | return -1; |
579 | } | 571 | } |
580 | 572 | ||
573 | ret = !TerminateProcess(process, exit_code); | ||
574 | CloseHandle(process); | ||
575 | |||
576 | return ret; | ||
577 | } | ||
578 | |||
579 | int kill(pid_t pid, int sig) | ||
580 | { | ||
581 | if (sig == SIGTERM) { | ||
582 | return kill_pids(pid, 128+sig, exit_process); | ||
583 | } | ||
584 | else if (sig == SIGKILL) { | ||
585 | return kill_pids(pid, 128+sig, terminate_process); | ||
586 | } | ||
587 | |||
581 | errno = EINVAL; | 588 | errno = EINVAL; |
582 | return -1; | 589 | return -1; |
583 | } | 590 | } |