Reactos

[TASKKILL] Simplify children processes termination code (#7855)

Use process-tree Level-Order-Traversal to determine the children
processes to be terminated.

This avoids using recursion to establish the process tree, and also
allows termination in a fashion similar to Windows' taskkill.
The main difference with the latter is that we terminate parent
processes first before terminating their children, instead of doing
the reverse. (This allows avoiding the case where parent processes
respawn their children when they have been terminated.)

+84 -131
+84 -131
base/applications/cmdutils/taskkill/taskkill.c
··· 4 4 * Copyright 2008 Andrew Riedi 5 5 * Copyright 2010 Andrew Nguyen 6 6 * Copyright 2020 He Yang 7 + * Copyright 2025 Hermès Bélusca-Maïto 7 8 * 8 9 * This library is free software; you can redistribute it and/or 9 10 * modify it under the terms of the GNU Lesser General Public ··· 301 302 * obtained from the system, is such that any child process P[j] of a given 302 303 * parent process P[i] is enumerated *AFTER* its parent (i.e. i < j). 303 304 * 304 - * Because of these facts, the ReactOS recursive method is employed instead. 305 - * Note however that the Wine method (below) has been adapted for ease of 306 - * usage and comparison with that of ReactOS. 305 + * Because of these facts, the ReactOS method is employed instead. 306 + * Note however that the Wine method (below) has been adapted for 307 + * ease of usage and comparison with that of ReactOS. 307 308 */ 308 309 309 310 static BOOL find_parent(unsigned int process_index, unsigned int *parent_index) ··· 376 377 static BOOL get_pid_creation_time(DWORD pid, FILETIME *time) 377 378 { 378 379 HANDLE process; 379 - FILETIME t1 = { 0 }, t2 = { 0 }, t3 = { 0 }; 380 + FILETIME t1, t2, t3; 381 + BOOL success; 380 382 381 383 process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); 382 384 if (!process) 383 - { 384 385 return FALSE; 385 - } 386 386 387 - if (!GetProcessTimes(process, time, &t1, &t2, &t3)) 388 - { 389 - CloseHandle(process); 390 - return FALSE; 391 - } 392 - 387 + success = GetProcessTimes(process, time, &t1, &t2, &t3); 393 388 CloseHandle(process); 394 - return TRUE; 389 + 390 + return success; 395 391 } 396 392 397 - static void send_close_messages_tree(DWORD ppid) 393 + static void queue_children(DWORD ppid) 398 394 { 399 - FILETIME parent_creation_time = { 0 }; 400 - HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 401 - PROCESSENTRY32W pe = { 0 }; 402 - pe.dwSize = sizeof(PROCESSENTRY32W); 395 + FILETIME parent_creation_time; 396 + unsigned int i; 403 397 404 - if (!get_pid_creation_time(ppid, &parent_creation_time) || !h) 398 + if (!get_pid_creation_time(ppid, &parent_creation_time)) 399 + return; 400 + 401 + for (i = 0; i < process_count; ++i) 405 402 { 406 - CloseHandle(h); 407 - return; 403 + FILETIME child_creation_time; 404 + DWORD pid; 405 + 406 + // Ignore processes already marked for termination 407 + if (process_list[i].matched) 408 + continue; 409 + 410 + // Prevent self-termination if we are in the process tree 411 + pid = process_list[i].p.th32ProcessID; 412 + if (pid == self_pid) 413 + continue; 414 + 415 + if (!get_pid_creation_time(pid, &child_creation_time)) 416 + continue; 417 + 418 + // Compare creation time to avoid PID reuse cases 419 + if (process_list[i].p.th32ParentProcessID == ppid && 420 + CompareFileTime(&parent_creation_time, &child_creation_time) < 0) 421 + { 422 + // Process marked for termination 423 + WINE_TRACE("Adding child %04lx.\n", pid); 424 + process_list[i].matched = TRUE; 425 + pkill_list[pkill_size++] = i; 426 + } 408 427 } 428 + } 409 429 410 - if (Process32FirstW(h, &pe)) 430 + /* 431 + * Based on the root processes to terminate, we perform a level order traversal 432 + * (Breadth First Search) of the corresponding process trees, building the list 433 + * of all processes and children to terminate, 434 + * This allows terminating the processes, starting from parents down to their 435 + * children. Note that this is in the reverse order than what Windows' taskkill 436 + * does. The reason why we chose to do the reverse, is because there exist 437 + * (parent) processes that detect whether their children are terminated, and 438 + * if so, attempt to restart their terminated children. We want to avoid this 439 + * scenario in order to ensure no extra processes get started, while the user 440 + * wanted to terminate them. 441 + */ 442 + static void mark_child_processes(void) 443 + { 444 + /* 445 + * The temporary FIFO queue for BFS (starting empty), is embedded 446 + * inside the result list. The queue resides between the [front, end) 447 + * indices (if front == end, the queue is empty), and moves down 448 + * through the result list, generating the sought values in order. 449 + */ 450 + unsigned int front = 0; // end = 0; given by pkill_size 451 + 452 + /* The root processes have already been 453 + * enqueued in pkill_list[0..pkill_size] */ 454 + 455 + /* Now, find and enqueue the children processes */ 456 + while (pkill_size - front > 0) 411 457 { 412 - do 458 + /* Begin search at the next level */ 459 + unsigned int len = pkill_size - front; 460 + unsigned int i; 461 + for (i = 0; i < len; ++i) 413 462 { 414 - FILETIME child_creation_time = { 0 }; 415 - struct pid_close_info info = { pe.th32ProcessID }; 463 + /* Standard BFS would pop the element from the front of 464 + * the queue and push it to the end of the result list. 465 + * In our case, everything is already correctly positioned 466 + * so that we can just simply emulate queue front popping. */ 467 + DWORD pid = process_list[pkill_list[front++]].p.th32ProcessID; 416 468 417 - if (!get_pid_creation_time(pe.th32ProcessID, &child_creation_time)) 418 - { 419 - continue; 420 - } 421 - 422 - // Compare creation time to avoid reuse PID, thanks to @ThFabba 423 - if (pe.th32ParentProcessID == ppid && 424 - CompareFileTime(&parent_creation_time, &child_creation_time) < 0) 425 - { 426 - // Use recursion to browse all child processes 427 - send_close_messages_tree(pe.th32ProcessID); 428 - EnumWindows(pid_enum_proc, (LPARAM)&info); 429 - if (info.found) 430 - { 431 - taskkill_message_printfW(STRING_CLOSE_CHILD, pe.th32ProcessID, ppid); 432 - } 433 - } 434 - } while (Process32NextW(h, &pe)); 469 + /* Enqueue the children processes */ 470 + queue_children(pid); // Updates pkill_size accordingly. 471 + } 435 472 } 436 - 437 - CloseHandle(h); 438 473 } 439 474 440 475 #endif // __REACTOS__ ··· 461 496 #endif 462 497 463 498 info.pid = process_list[i].p.th32ProcessID; 464 - #ifdef __REACTOS__ 465 - if (info.pid == self_pid) 466 - { 467 - taskkill_message(STRING_SELF_TERMINATION); 468 - status_code = 1; 469 - continue; 470 - } 471 - // Send close messages to child first 472 - if (kill_child_processes) 473 - send_close_messages_tree(info.pid); 474 - #endif 475 499 process_name = process_list[i].p.szExeFile; 476 500 info.found = FALSE; 477 501 WINE_TRACE("Terminating pid %04lx.\n", info.pid); ··· 493 517 return status_code; 494 518 } 495 519 496 - #ifdef __REACTOS__ 497 - 498 - static void terminate_process_tree(DWORD ppid) 499 - { 500 - FILETIME parent_creation_time = { 0 }; 501 - HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 502 - PROCESSENTRY32W pe = { 0 }; 503 - pe.dwSize = sizeof(PROCESSENTRY32W); 504 - 505 - if (!get_pid_creation_time(ppid, &parent_creation_time) || !h) 506 - { 507 - CloseHandle(h); 508 - return; 509 - } 510 - 511 - if (Process32FirstW(h, &pe)) 512 - { 513 - do 514 - { 515 - FILETIME child_creation_time = { 0 }; 516 - 517 - if (!get_pid_creation_time(pe.th32ProcessID, &child_creation_time)) 518 - { 519 - continue; 520 - } 521 - 522 - // Compare creation time to avoid reuse PID, thanks to @ThFabba 523 - if (pe.th32ParentProcessID == ppid && 524 - CompareFileTime(&parent_creation_time, &child_creation_time) < 0) 525 - { 526 - HANDLE process; 527 - 528 - // Use recursion to browse all child processes 529 - terminate_process_tree(pe.th32ProcessID); 530 - process = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID); 531 - if (!process) 532 - { 533 - continue; 534 - } 535 - 536 - if (!TerminateProcess(process, 1)) 537 - { 538 - taskkill_message_printfW(STRING_TERM_CHILD_FAILED, pe.th32ProcessID, ppid); 539 - CloseHandle(process); 540 - continue; 541 - } 542 - 543 - taskkill_message_printfW(STRING_TERM_CHILD, pe.th32ProcessID, ppid); 544 - CloseHandle(process); 545 - } 546 - } while (Process32NextW(h, &pe)); 547 - } 548 - 549 - CloseHandle(h); 550 - } 551 - 552 - #endif // __REACTOS__ 553 - 554 520 static int terminate_processes(void) 555 521 { 556 522 const WCHAR *process_name; ··· 574 540 #endif 575 541 576 542 pid = process_list[i].p.th32ProcessID; 577 - #ifdef __REACTOS__ 578 - if (pid == self_pid) 579 - { 580 - taskkill_message(STRING_SELF_TERMINATION); 581 - status_code = 1; 582 - continue; 583 - } 584 - // Terminate child first 585 - if (kill_child_processes) 586 - terminate_process_tree(pid); 587 - #endif 588 543 process_name = process_list[i].p.szExeFile; 589 544 process = OpenProcess(PROCESS_TERMINATE, FALSE, pid); 590 545 if (!process) ··· 679 634 { 680 635 case OP_PARAM_FORCE_TERMINATE: 681 636 { 682 - if (force_termination == TRUE) 637 + if (force_termination) 683 638 { 684 639 // -f already specified 685 640 taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1); ··· 725 680 } 726 681 case OP_PARAM_HELP: 727 682 { 728 - if (has_help == TRUE) 683 + if (has_help) 729 684 { 730 685 // -? already specified 731 686 taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1); ··· 737 692 } 738 693 case OP_PARAM_TERMINATE_CHILD: 739 694 { 740 - if (kill_child_processes == TRUE) 695 + if (kill_child_processes) 741 696 { 742 697 // -t already specified 743 698 taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1); ··· 772 727 exit(0); 773 728 } 774 729 } 775 - else if ((!has_im) && (!has_pid)) // has_help == FALSE 730 + else if (!has_im && !has_pid) // has_help == FALSE 776 731 { 777 732 // both has_im and has_pid are missing (maybe -fi option is missing too, if implemented later) 778 733 taskkill_message(STRING_MISSING_OPTION); ··· 893 848 894 849 for (i = 0; i < task_count; ++i) 895 850 mark_task_process(task_list[i], &search_status); 896 - #ifndef __REACTOS__ 897 851 if (kill_child_processes) 898 852 mark_child_processes(); 899 - #endif 900 853 if (force_termination) 901 854 terminate_status = terminate_processes(); 902 855 else