Skip to content

Commit 94a650c

Browse files
committed
[TASKKILL] Simplify children processes termination code (reactos#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.)
1 parent 1519a67 commit 94a650c

File tree

1 file changed

+84
-131
lines changed

1 file changed

+84
-131
lines changed

base/applications/cmdutils/taskkill/taskkill.c

Lines changed: 84 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Copyright 2008 Andrew Riedi
55
* Copyright 2010 Andrew Nguyen
66
* Copyright 2020 He Yang
7+
* Copyright 2025 Hermès Bélusca-Maïto
78
*
89
* This library is free software; you can redistribute it and/or
910
* modify it under the terms of the GNU Lesser General Public
@@ -301,9 +302,9 @@ static void taskkill_message_print_process(int msg, unsigned int index)
301302
* obtained from the system, is such that any child process P[j] of a given
302303
* parent process P[i] is enumerated *AFTER* its parent (i.e. i < j).
303304
*
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.
307308
*/
308309

309310
static BOOL find_parent(unsigned int process_index, unsigned int *parent_index)
@@ -376,65 +377,99 @@ static void mark_child_processes(void)
376377
static BOOL get_pid_creation_time(DWORD pid, FILETIME *time)
377378
{
378379
HANDLE process;
379-
FILETIME t1 = { 0 }, t2 = { 0 }, t3 = { 0 };
380+
FILETIME t1, t2, t3;
381+
BOOL success;
380382

381383
process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
382384
if (!process)
383-
{
384-
return FALSE;
385-
}
386-
387-
if (!GetProcessTimes(process, time, &t1, &t2, &t3))
388-
{
389-
CloseHandle(process);
390385
return FALSE;
391-
}
392386

387+
success = GetProcessTimes(process, time, &t1, &t2, &t3);
393388
CloseHandle(process);
394-
return TRUE;
389+
390+
return success;
395391
}
396392

397-
static void send_close_messages_tree(DWORD ppid)
393+
static void queue_children(DWORD ppid)
398394
{
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;
403397

404-
if (!get_pid_creation_time(ppid, &parent_creation_time) || !h)
405-
{
406-
CloseHandle(h);
398+
if (!get_pid_creation_time(ppid, &parent_creation_time))
407399
return;
408-
}
409400

410-
if (Process32FirstW(h, &pe))
401+
for (i = 0; i < process_count; ++i)
411402
{
412-
do
413-
{
414-
FILETIME child_creation_time = { 0 };
415-
struct pid_close_info info = { pe.th32ProcessID };
403+
FILETIME child_creation_time;
404+
DWORD pid;
416405

417-
if (!get_pid_creation_time(pe.th32ProcessID, &child_creation_time))
418-
{
419-
continue;
420-
}
406+
// Ignore processes already marked for termination
407+
if (process_list[i].matched)
408+
continue;
421409

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));
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+
}
435427
}
428+
}
436429

437-
CloseHandle(h);
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)
457+
{
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)
462+
{
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;
468+
469+
/* Enqueue the children processes */
470+
queue_children(pid); // Updates pkill_size accordingly.
471+
}
472+
}
438473
}
439474

440475
#endif // __REACTOS__
@@ -461,17 +496,6 @@ static int send_close_messages(void)
461496
#endif
462497

463498
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
475499
process_name = process_list[i].p.szExeFile;
476500
info.found = FALSE;
477501
WINE_TRACE("Terminating pid %04lx.\n", info.pid);
@@ -493,64 +517,6 @@ static int send_close_messages(void)
493517
return status_code;
494518
}
495519

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-
554520
static int terminate_processes(void)
555521
{
556522
const WCHAR *process_name;
@@ -574,17 +540,6 @@ static int terminate_processes(void)
574540
#endif
575541

576542
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
588543
process_name = process_list[i].p.szExeFile;
589544
process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
590545
if (!process)
@@ -679,7 +634,7 @@ static BOOL process_arguments(int argc, WCHAR* argv[])
679634
{
680635
case OP_PARAM_FORCE_TERMINATE:
681636
{
682-
if (force_termination == TRUE)
637+
if (force_termination)
683638
{
684639
// -f already specified
685640
taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1);
@@ -725,7 +680,7 @@ static BOOL process_arguments(int argc, WCHAR* argv[])
725680
}
726681
case OP_PARAM_HELP:
727682
{
728-
if (has_help == TRUE)
683+
if (has_help)
729684
{
730685
// -? already specified
731686
taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1);
@@ -737,7 +692,7 @@ static BOOL process_arguments(int argc, WCHAR* argv[])
737692
}
738693
case OP_PARAM_TERMINATE_CHILD:
739694
{
740-
if (kill_child_processes == TRUE)
695+
if (kill_child_processes)
741696
{
742697
// -t already specified
743698
taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1);
@@ -772,7 +727,7 @@ static BOOL process_arguments(int argc, WCHAR* argv[])
772727
exit(0);
773728
}
774729
}
775-
else if ((!has_im) && (!has_pid)) // has_help == FALSE
730+
else if (!has_im && !has_pid) // has_help == FALSE
776731
{
777732
// both has_im and has_pid are missing (maybe -fi option is missing too, if implemented later)
778733
taskkill_message(STRING_MISSING_OPTION);
@@ -893,10 +848,8 @@ int wmain(int argc, WCHAR *argv[])
893848

894849
for (i = 0; i < task_count; ++i)
895850
mark_task_process(task_list[i], &search_status);
896-
#ifndef __REACTOS__
897851
if (kill_child_processes)
898852
mark_child_processes();
899-
#endif
900853
if (force_termination)
901854
terminate_status = terminate_processes();
902855
else

0 commit comments

Comments
 (0)