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 
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 
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 
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 
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. */
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 
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 
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 
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__
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 
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 
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 
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. */
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 
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