1 /*
2 * Task termination utility
3 *
4 * Copyright 2008 Andrew Riedi
5 * Copyright 2010 Andrew Nguyen
6 * Copyright 2020 He Yang
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23 #include <stdlib.h>
24 #include <windows.h>
25 #include <psapi.h>
26 #include <wine/debug.h>
27 #include <wine/unicode.h>
28
29 #include "taskkill.h"
30
31 WINE_DEFAULT_DEBUG_CHANNEL(taskkill);
32
33 static BOOL force_termination = FALSE;
34
35 static WCHAR **task_list;
36 static unsigned int task_count;
37
38 #ifdef __REACTOS__
39
40 static WCHAR opForceTerminate[] = L"f";
41 static WCHAR opImage[] = L"im";
42 static WCHAR opPID[] = L"pid";
43 static WCHAR opHelp[] = L"?";
44 static WCHAR opTerminateChildren[] = L"t";
45
46 static PWCHAR opList[] = {opForceTerminate, opImage, opPID, opHelp, opTerminateChildren};
47
48 #define OP_PARAM_INVALID -1
49
50 #define OP_PARAM_FORCE_TERMINATE 0
51 #define OP_PARAM_IMAGE 1
52 #define OP_PARAM_PID 2
53 #define OP_PARAM_HELP 3
54 #define OP_PARAM_TERMINATE_CHILD 4
55
56 #endif // __REACTOS__
57
58 struct pid_close_info
59 {
60 DWORD pid;
61 BOOL found;
62 };
63
taskkill_vprintfW(const WCHAR * msg,__ms_va_list va_args)64 static int taskkill_vprintfW(const WCHAR *msg, __ms_va_list va_args)
65 {
66 int wlen;
67 DWORD count, ret;
68 WCHAR msg_buffer[8192];
69
70 wlen = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, msg_buffer,
71 ARRAY_SIZE(msg_buffer), &va_args);
72
73 ret = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), msg_buffer, wlen, &count, NULL);
74 if (!ret)
75 {
76 DWORD len;
77 char *msgA;
78
79 /* On Windows WriteConsoleW() fails if the output is redirected. So fall
80 * back to WriteFile(), assuming the console encoding is still the right
81 * one in that case.
82 */
83 len = WideCharToMultiByte(GetConsoleOutputCP(), 0, msg_buffer, wlen,
84 NULL, 0, NULL, NULL);
85 msgA = HeapAlloc(GetProcessHeap(), 0, len);
86 if (!msgA)
87 return 0;
88
89 WideCharToMultiByte(GetConsoleOutputCP(), 0, msg_buffer, wlen, msgA, len,
90 NULL, NULL);
91 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), msgA, len, &count, FALSE);
92 HeapFree(GetProcessHeap(), 0, msgA);
93 }
94
95 return count;
96 }
97
taskkill_printfW(const WCHAR * msg,...)98 static int WINAPIV taskkill_printfW(const WCHAR *msg, ...)
99 {
100 __ms_va_list va_args;
101 int len;
102
103 __ms_va_start(va_args, msg);
104 len = taskkill_vprintfW(msg, va_args);
105 __ms_va_end(va_args);
106
107 return len;
108 }
109
taskkill_message_printfW(int msg,...)110 static int WINAPIV taskkill_message_printfW(int msg, ...)
111 {
112 __ms_va_list va_args;
113 WCHAR msg_buffer[8192];
114 int len;
115
116 LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer));
117
118 __ms_va_start(va_args, msg);
119 len = taskkill_vprintfW(msg_buffer, va_args);
120 __ms_va_end(va_args);
121
122 return len;
123 }
124
taskkill_message(int msg)125 static int taskkill_message(int msg)
126 {
127 static const WCHAR formatW[] = {'%','1',0};
128 WCHAR msg_buffer[8192];
129
130 LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer));
131
132 return taskkill_printfW(formatW, msg_buffer);
133 }
134
135 /* Post WM_CLOSE to all top-level windows belonging to the process with specified PID. */
pid_enum_proc(HWND hwnd,LPARAM lParam)136 static BOOL CALLBACK pid_enum_proc(HWND hwnd, LPARAM lParam)
137 {
138 struct pid_close_info *info = (struct pid_close_info *)lParam;
139 DWORD hwnd_pid;
140
141 GetWindowThreadProcessId(hwnd, &hwnd_pid);
142
143 if (hwnd_pid == info->pid)
144 {
145 PostMessageW(hwnd, WM_CLOSE, 0, 0);
146 info->found = TRUE;
147 }
148
149 return TRUE;
150 }
151
enumerate_processes(DWORD * list_count)152 static DWORD *enumerate_processes(DWORD *list_count)
153 {
154 DWORD *pid_list, alloc_bytes = 1024 * sizeof(*pid_list), needed_bytes;
155
156 pid_list = HeapAlloc(GetProcessHeap(), 0, alloc_bytes);
157 if (!pid_list)
158 return NULL;
159
160 for (;;)
161 {
162 DWORD *realloc_list;
163
164 if (!EnumProcesses(pid_list, alloc_bytes, &needed_bytes))
165 {
166 HeapFree(GetProcessHeap(), 0, pid_list);
167 return NULL;
168 }
169
170 /* EnumProcesses can't signal an insufficient buffer condition, so the
171 * only way to possibly determine whether a larger buffer is required
172 * is to see whether the written number of bytes is the same as the
173 * buffer size. If so, the buffer will be reallocated to twice the
174 * size. */
175 if (alloc_bytes != needed_bytes)
176 break;
177
178 alloc_bytes *= 2;
179 realloc_list = HeapReAlloc(GetProcessHeap(), 0, pid_list, alloc_bytes);
180 if (!realloc_list)
181 {
182 HeapFree(GetProcessHeap(), 0, pid_list);
183 return NULL;
184 }
185 pid_list = realloc_list;
186 }
187
188 *list_count = needed_bytes / sizeof(*pid_list);
189 return pid_list;
190 }
191
get_process_name_from_pid(DWORD pid,WCHAR * buf,DWORD chars)192 static BOOL get_process_name_from_pid(DWORD pid, WCHAR *buf, DWORD chars)
193 {
194 HANDLE process;
195 HMODULE module;
196 DWORD required_size;
197
198 process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
199 if (!process)
200 return FALSE;
201
202 if (!EnumProcessModules(process, &module, sizeof(module), &required_size))
203 {
204 CloseHandle(process);
205 return FALSE;
206 }
207
208 if (!GetModuleBaseNameW(process, module, buf, chars))
209 {
210 CloseHandle(process);
211 return FALSE;
212 }
213
214 CloseHandle(process);
215 return TRUE;
216 }
217
218 /* The implemented task enumeration and termination behavior does not
219 * exactly match native behavior. On Windows:
220 *
221 * In the case of terminating by process name, specifying a particular
222 * process name more times than the number of running instances causes
223 * all instances to be terminated, but termination failure messages to
224 * be printed as many times as the difference between the specification
225 * quantity and the number of running instances.
226 *
227 * Successful terminations are all listed first in order, with failing
228 * terminations being listed at the end.
229 *
230 * A PID of zero causes taskkill to warn about the inability to terminate
231 * system processes. */
232
233 #ifndef __REACTOS__
234
send_close_messages(void)235 static int send_close_messages(void)
236 {
237 DWORD *pid_list, pid_list_size;
238 DWORD self_pid = GetCurrentProcessId();
239 unsigned int i;
240 int status_code = 0;
241
242 pid_list = enumerate_processes(&pid_list_size);
243 if (!pid_list)
244 {
245 taskkill_message(STRING_ENUM_FAILED);
246 return 1;
247 }
248
249 for (i = 0; i < task_count; i++)
250 {
251 WCHAR *p = task_list[i];
252 BOOL is_numeric = TRUE;
253
254 /* Determine whether the string is not numeric. */
255 while (*p)
256 {
257 if (!isdigitW(*p++))
258 {
259 is_numeric = FALSE;
260 break;
261 }
262 }
263
264 if (is_numeric)
265 {
266 DWORD pid = atoiW(task_list[i]);
267 struct pid_close_info info = { pid };
268
269 if (pid == self_pid)
270 {
271 taskkill_message(STRING_SELF_TERMINATION);
272 status_code = 1;
273 continue;
274 }
275
276 EnumWindows(pid_enum_proc, (LPARAM)&info);
277 if (info.found)
278 taskkill_message_printfW(STRING_CLOSE_PID_SEARCH, pid);
279 else
280 {
281 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
282 status_code = 128;
283 }
284 }
285 else
286 {
287 DWORD index;
288 BOOL found_process = FALSE;
289
290 for (index = 0; index < pid_list_size; index++)
291 {
292 WCHAR process_name[MAX_PATH];
293
294 if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) &&
295 !strcmpiW(process_name, task_list[i]))
296 {
297 struct pid_close_info info = { pid_list[index] };
298
299 found_process = TRUE;
300 if (pid_list[index] == self_pid)
301 {
302 taskkill_message(STRING_SELF_TERMINATION);
303 status_code = 1;
304 continue;
305 }
306
307 EnumWindows(pid_enum_proc, (LPARAM)&info);
308 taskkill_message_printfW(STRING_CLOSE_PROC_SRCH, process_name, pid_list[index]);
309 }
310 }
311
312 if (!found_process)
313 {
314 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
315 status_code = 128;
316 }
317 }
318 }
319
320 HeapFree(GetProcessHeap(), 0, pid_list);
321 return status_code;
322 }
323
324 #endif // __REACTOS__
325
326 #ifdef __REACTOS__
terminate_processes(BOOL force_termination)327 static int terminate_processes(BOOL force_termination)
328 #else
329 static int terminate_processes(void)
330 #endif
331 {
332 DWORD *pid_list, pid_list_size;
333 DWORD self_pid = GetCurrentProcessId();
334 unsigned int i;
335 int status_code = 0;
336
337 pid_list = enumerate_processes(&pid_list_size);
338 if (!pid_list)
339 {
340 taskkill_message(STRING_ENUM_FAILED);
341 return 1;
342 }
343
344 for (i = 0; i < task_count; i++)
345 {
346 WCHAR *p = task_list[i];
347 BOOL is_numeric = TRUE;
348
349 /* Determine whether the string is not numeric. */
350 while (*p)
351 {
352 if (!isdigitW(*p++))
353 {
354 is_numeric = FALSE;
355 break;
356 }
357 }
358
359 if (is_numeric)
360 {
361 DWORD pid = atoiW(task_list[i]);
362 #ifndef __REACTOS__
363 HANDLE process;
364 #endif
365
366 if (pid == self_pid)
367 {
368 taskkill_message(STRING_SELF_TERMINATION);
369 status_code = 1;
370 continue;
371 }
372
373 #ifdef __REACTOS__
374 if (force_termination)
375 {
376 HANDLE process;
377 #endif
378 process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
379 if (!process)
380 {
381 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
382 status_code = 128;
383 continue;
384 }
385
386 if (!TerminateProcess(process, 0))
387 {
388 taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]);
389 status_code = 1;
390 CloseHandle(process);
391 continue;
392 }
393
394 taskkill_message_printfW(STRING_TERM_PID_SEARCH, pid);
395 CloseHandle(process);
396 #ifdef __REACTOS__
397 }
398 else
399 {
400 struct pid_close_info info = { pid };
401
402 EnumWindows(pid_enum_proc, (LPARAM)&info);
403 if (info.found)
404 taskkill_message_printfW(STRING_CLOSE_PID_SEARCH, pid);
405 else
406 {
407 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
408 status_code = 128;
409 }
410 }
411 #endif
412 }
413 else
414 {
415 DWORD index;
416 BOOL found_process = FALSE;
417
418 for (index = 0; index < pid_list_size; index++)
419 {
420 WCHAR process_name[MAX_PATH];
421
422 if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) &&
423 !strcmpiW(process_name, task_list[i]))
424 {
425 #ifdef __REACTOS__
426 found_process = TRUE;
427 #else
428 HANDLE process;
429 #endif
430
431 if (pid_list[index] == self_pid)
432 {
433 taskkill_message(STRING_SELF_TERMINATION);
434 status_code = 1;
435 continue;
436 }
437
438 #ifdef __REACTOS__
439 if (force_termination)
440 {
441 HANDLE process;
442 #endif
443 process = OpenProcess(PROCESS_TERMINATE, FALSE, pid_list[index]);
444 if (!process)
445 {
446 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
447 status_code = 128;
448 continue;
449 }
450
451 if (!TerminateProcess(process, 0))
452 {
453 taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]);
454 status_code = 1;
455 CloseHandle(process);
456 continue;
457 }
458
459 #ifndef __REACTOS__
460 found_process = TRUE;
461 #endif
462 taskkill_message_printfW(STRING_TERM_PROC_SEARCH, task_list[i], pid_list[index]);
463 CloseHandle(process);
464 #ifdef __REACTOS__
465 }
466 else
467 {
468 struct pid_close_info info = { pid_list[index] };
469 EnumWindows(pid_enum_proc, (LPARAM)&info);
470 taskkill_message_printfW(STRING_CLOSE_PROC_SRCH, process_name, pid_list[index]);
471 }
472 #endif
473 }
474 }
475
476 if (!found_process)
477 {
478 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
479 status_code = 128;
480 }
481 }
482 }
483
484 HeapFree(GetProcessHeap(), 0, pid_list);
485 return status_code;
486 }
487
add_to_task_list(WCHAR * name)488 static BOOL add_to_task_list(WCHAR *name)
489 {
490 static unsigned int list_size = 16;
491
492 if (!task_list)
493 {
494 task_list = HeapAlloc(GetProcessHeap(), 0,
495 list_size * sizeof(*task_list));
496 if (!task_list)
497 return FALSE;
498 }
499 else if (task_count == list_size)
500 {
501 void *realloc_list;
502
503 list_size *= 2;
504 realloc_list = HeapReAlloc(GetProcessHeap(), 0, task_list,
505 list_size * sizeof(*task_list));
506 if (!realloc_list)
507 return FALSE;
508
509 task_list = realloc_list;
510 }
511
512 task_list[task_count++] = name;
513 return TRUE;
514 }
515
516 #ifdef __REACTOS__
517
get_argument_type(WCHAR * argument)518 static int get_argument_type(WCHAR* argument)
519 {
520 int i;
521
522 if (argument[0] != L'/' && argument[0] != L'-')
523 {
524 return OP_PARAM_INVALID;
525 }
526 argument++;
527
528 for (i = 0; i < _countof(opList); i++)
529 {
530 if (!strcmpiW(opList[i], argument))
531 {
532 return i;
533 }
534 }
535 return OP_PARAM_INVALID;
536 }
537
538 /* FIXME
539 argument T not supported
540 */
541
process_arguments(int argc,WCHAR * argv[])542 static BOOL process_arguments(int argc, WCHAR* argv[])
543 {
544 BOOL has_im = FALSE, has_pid = FALSE, has_help = FALSE;
545
546 if (argc > 1)
547 {
548 int i;
549 for (i = 1; i < argc; i++)
550 {
551 int argument = get_argument_type(argv[i]);
552
553 switch (argument)
554 {
555 case OP_PARAM_FORCE_TERMINATE:
556 {
557 if (force_termination == TRUE)
558 {
559 // -f already specified
560 taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1);
561 taskkill_message(STRING_USAGE);
562 return FALSE;
563 }
564 force_termination = TRUE;
565 break;
566 }
567 case OP_PARAM_IMAGE:
568 case OP_PARAM_PID:
569 {
570 if (!argv[i + 1])
571 {
572 taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]);
573 taskkill_message(STRING_USAGE);
574 return FALSE;
575 }
576
577 if (argument == OP_PARAM_IMAGE)
578 has_im = TRUE;
579 if (argument == OP_PARAM_PID)
580 has_pid = TRUE;
581
582 if (has_im && has_pid)
583 {
584 taskkill_message(STRING_MUTUAL_EXCLUSIVE);
585 taskkill_message(STRING_USAGE);
586 return FALSE;
587 }
588
589 if (get_argument_type(argv[i + 1]) != OP_PARAM_INVALID)
590 {
591 taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]);
592 taskkill_message(STRING_USAGE);
593 return FALSE;
594 }
595
596 if (!add_to_task_list(argv[++i])) // add next parameters to task_list
597 return FALSE;
598
599 break;
600 }
601 case OP_PARAM_HELP:
602 {
603 if (has_help == TRUE)
604 {
605 // -? already specified
606 taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1);
607 taskkill_message(STRING_USAGE);
608 return FALSE;
609 }
610 has_help = TRUE;
611 break;
612 }
613 case OP_PARAM_TERMINATE_CHILD:
614 {
615 WINE_FIXME("argument T not supported\n");
616 break;
617 }
618 case OP_PARAM_INVALID:
619 default:
620 {
621 taskkill_message(STRING_INVALID_OPTION);
622 taskkill_message(STRING_USAGE);
623 return FALSE;
624 }
625 }
626 }
627 }
628
629 if (has_help)
630 {
631 if (argc > 2) // any parameters other than -? is specified
632 {
633 taskkill_message(STRING_INVALID_SYNTAX);
634 taskkill_message(STRING_USAGE);
635 return FALSE;
636 }
637 else
638 {
639 taskkill_message(STRING_USAGE);
640 exit(0);
641 }
642 }
643 else if ((!has_im) && (!has_pid)) // has_help == FALSE
644 {
645 // both has_im and has_pid are missing (maybe -fi option is missing too, if implemented later)
646 taskkill_message(STRING_MISSING_OPTION);
647 taskkill_message(STRING_USAGE);
648 return FALSE;
649 }
650
651 return TRUE;
652 }
653
654 #else
655
656 /* FIXME Argument processing does not match behavior observed on Windows.
657 * Stringent argument counting and processing is performed, and unrecognized
658 * options are detected as parameters when placed after options that accept one. */
process_arguments(int argc,WCHAR * argv[])659 static BOOL process_arguments(int argc, WCHAR *argv[])
660 {
661 static const WCHAR opForceTerminate[] = {'f',0};
662 static const WCHAR opImage[] = {'i','m',0};
663 static const WCHAR opPID[] = {'p','i','d',0};
664 static const WCHAR opHelp[] = {'?',0};
665 static const WCHAR opTerminateChildren[] = {'t',0};
666
667 if (argc > 1)
668 {
669 int i;
670 WCHAR *argdata;
671 BOOL has_im = FALSE, has_pid = FALSE;
672
673 /* Only the lone help option is recognized. */
674 if (argc == 2)
675 {
676 argdata = argv[1];
677 if ((*argdata == '/' || *argdata == '-') && !strcmpW(opHelp, argdata + 1))
678 {
679 taskkill_message(STRING_USAGE);
680 exit(0);
681 }
682 }
683
684 for (i = 1; i < argc; i++)
685 {
686 BOOL got_im = FALSE, got_pid = FALSE;
687
688 argdata = argv[i];
689 if (*argdata != '/' && *argdata != '-')
690 goto invalid;
691 argdata++;
692
693 if (!strcmpiW(opTerminateChildren, argdata))
694 WINE_FIXME("argument T not supported\n");
695 if (!strcmpiW(opForceTerminate, argdata))
696 force_termination = TRUE;
697 /* Options /IM and /PID appear to behave identically, except for
698 * the fact that they cannot be specified at the same time. */
699 else if ((got_im = !strcmpiW(opImage, argdata)) ||
700 (got_pid = !strcmpiW(opPID, argdata)))
701 {
702 if (!argv[i + 1])
703 {
704 taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]);
705 taskkill_message(STRING_USAGE);
706 return FALSE;
707 }
708
709 if (got_im) has_im = TRUE;
710 if (got_pid) has_pid = TRUE;
711
712 if (has_im && has_pid)
713 {
714 taskkill_message(STRING_MUTUAL_EXCLUSIVE);
715 taskkill_message(STRING_USAGE);
716 return FALSE;
717 }
718
719 if (!add_to_task_list(argv[i + 1]))
720 return FALSE;
721 i++;
722 }
723 else
724 {
725 invalid:
726 taskkill_message(STRING_INVALID_OPTION);
727 taskkill_message(STRING_USAGE);
728 return FALSE;
729 }
730 }
731 }
732 else
733 {
734 taskkill_message(STRING_MISSING_OPTION);
735 taskkill_message(STRING_USAGE);
736 return FALSE;
737 }
738
739 return TRUE;
740 }
741
742 #endif // __REACTOS__
743
wmain(int argc,WCHAR * argv[])744 int wmain(int argc, WCHAR *argv[])
745 {
746 int status_code = 0;
747
748 if (!process_arguments(argc, argv))
749 {
750 HeapFree(GetProcessHeap(), 0, task_list);
751 return 1;
752 }
753
754 #ifdef __REACTOS__
755 status_code = terminate_processes(force_termination);
756 #else
757 if (force_termination)
758 status_code = terminate_processes();
759 else
760 status_code = send_close_messages();
761 #endif
762
763 HeapFree(GetProcessHeap(), 0, task_list);
764 return status_code;
765 }
766