1 /*
2  * Task termination utility
3  *
4  * Copyright 2008 Andrew Riedi
5  * Copyright 2010 Andrew Nguyen
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include <stdlib.h>
23 #include <windows.h>
24 #include <psapi.h>
25 #include <wine/debug.h>
26 #include <wine/unicode.h>
27 
28 #include "taskkill.h"
29 
30 WINE_DEFAULT_DEBUG_CHANNEL(taskkill);
31 
32 static BOOL force_termination = FALSE;
33 
34 static WCHAR **task_list;
35 static unsigned int task_count;
36 
37 struct pid_close_info
38 {
39     DWORD pid;
40     BOOL found;
41 };
42 
43 static int taskkill_vprintfW(const WCHAR *msg, __ms_va_list va_args)
44 {
45     int wlen;
46     DWORD count, ret;
47     WCHAR msg_buffer[8192];
48 
49     wlen = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, msg_buffer,
50                           sizeof(msg_buffer)/sizeof(*msg_buffer), &va_args);
51 
52     ret = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), msg_buffer, wlen, &count, NULL);
53     if (!ret)
54     {
55         DWORD len;
56         char *msgA;
57 
58         /* On Windows WriteConsoleW() fails if the output is redirected. So fall
59          * back to WriteFile(), assuming the console encoding is still the right
60          * one in that case.
61          */
62         len = WideCharToMultiByte(GetConsoleOutputCP(), 0, msg_buffer, wlen,
63             NULL, 0, NULL, NULL);
64         msgA = HeapAlloc(GetProcessHeap(), 0, len);
65         if (!msgA)
66             return 0;
67 
68         WideCharToMultiByte(GetConsoleOutputCP(), 0, msg_buffer, wlen, msgA, len,
69             NULL, NULL);
70         WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), msgA, len, &count, FALSE);
71         HeapFree(GetProcessHeap(), 0, msgA);
72     }
73 
74     return count;
75 }
76 
77 static int WINAPIV taskkill_printfW(const WCHAR *msg, ...)
78 {
79     __ms_va_list va_args;
80     int len;
81 
82     __ms_va_start(va_args, msg);
83     len = taskkill_vprintfW(msg, va_args);
84     __ms_va_end(va_args);
85 
86     return len;
87 }
88 
89 static int WINAPIV taskkill_message_printfW(int msg, ...)
90 {
91     __ms_va_list va_args;
92     WCHAR msg_buffer[8192];
93     int len;
94 
95     LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer,
96         sizeof(msg_buffer)/sizeof(WCHAR));
97 
98     __ms_va_start(va_args, msg);
99     len = taskkill_vprintfW(msg_buffer, va_args);
100     __ms_va_end(va_args);
101 
102     return len;
103 }
104 
105 static int taskkill_message(int msg)
106 {
107     static const WCHAR formatW[] = {'%','1',0};
108     WCHAR msg_buffer[8192];
109 
110     LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer,
111         sizeof(msg_buffer)/sizeof(WCHAR));
112 
113     return taskkill_printfW(formatW, msg_buffer);
114 }
115 
116 /* Post WM_CLOSE to all top-level windows belonging to the process with specified PID. */
117 static BOOL CALLBACK pid_enum_proc(HWND hwnd, LPARAM lParam)
118 {
119     struct pid_close_info *info = (struct pid_close_info *)lParam;
120     DWORD hwnd_pid;
121 
122     GetWindowThreadProcessId(hwnd, &hwnd_pid);
123 
124     if (hwnd_pid == info->pid)
125     {
126         PostMessageW(hwnd, WM_CLOSE, 0, 0);
127         info->found = TRUE;
128     }
129 
130     return TRUE;
131 }
132 
133 static DWORD *enumerate_processes(DWORD *list_count)
134 {
135     DWORD *pid_list, alloc_bytes = 1024 * sizeof(*pid_list), needed_bytes;
136 
137     pid_list = HeapAlloc(GetProcessHeap(), 0, alloc_bytes);
138     if (!pid_list)
139         return NULL;
140 
141     for (;;)
142     {
143         DWORD *realloc_list;
144 
145         if (!EnumProcesses(pid_list, alloc_bytes, &needed_bytes))
146         {
147             HeapFree(GetProcessHeap(), 0, pid_list);
148             return NULL;
149         }
150 
151         /* EnumProcesses can't signal an insufficient buffer condition, so the
152          * only way to possibly determine whether a larger buffer is required
153          * is to see whether the written number of bytes is the same as the
154          * buffer size. If so, the buffer will be reallocated to twice the
155          * size. */
156         if (alloc_bytes != needed_bytes)
157             break;
158 
159         alloc_bytes *= 2;
160         realloc_list = HeapReAlloc(GetProcessHeap(), 0, pid_list, alloc_bytes);
161         if (!realloc_list)
162         {
163             HeapFree(GetProcessHeap(), 0, pid_list);
164             return NULL;
165         }
166         pid_list = realloc_list;
167     }
168 
169     *list_count = needed_bytes / sizeof(*pid_list);
170     return pid_list;
171 }
172 
173 static BOOL get_process_name_from_pid(DWORD pid, WCHAR *buf, DWORD chars)
174 {
175     HANDLE process;
176     HMODULE module;
177     DWORD required_size;
178 
179     process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
180     if (!process)
181         return FALSE;
182 
183     if (!EnumProcessModules(process, &module, sizeof(module), &required_size))
184     {
185         CloseHandle(process);
186         return FALSE;
187     }
188 
189     if (!GetModuleBaseNameW(process, module, buf, chars))
190     {
191         CloseHandle(process);
192         return FALSE;
193     }
194 
195     CloseHandle(process);
196     return TRUE;
197 }
198 
199 /* The implemented task enumeration and termination behavior does not
200  * exactly match native behavior. On Windows:
201  *
202  * In the case of terminating by process name, specifying a particular
203  * process name more times than the number of running instances causes
204  * all instances to be terminated, but termination failure messages to
205  * be printed as many times as the difference between the specification
206  * quantity and the number of running instances.
207  *
208  * Successful terminations are all listed first in order, with failing
209  * terminations being listed at the end.
210  *
211  * A PID of zero causes taskkill to warn about the inability to terminate
212  * system processes. */
213 static int send_close_messages(void)
214 {
215     DWORD *pid_list, pid_list_size;
216     DWORD self_pid = GetCurrentProcessId();
217     unsigned int i;
218     int status_code = 0;
219 
220     pid_list = enumerate_processes(&pid_list_size);
221     if (!pid_list)
222     {
223         taskkill_message(STRING_ENUM_FAILED);
224         return 1;
225     }
226 
227     for (i = 0; i < task_count; i++)
228     {
229         WCHAR *p = task_list[i];
230         BOOL is_numeric = TRUE;
231 
232         /* Determine whether the string is not numeric. */
233         while (*p)
234         {
235             if (!isdigitW(*p++))
236             {
237                 is_numeric = FALSE;
238                 break;
239             }
240         }
241 
242         if (is_numeric)
243         {
244             DWORD pid = atoiW(task_list[i]);
245             struct pid_close_info info = { pid };
246 
247             if (pid == self_pid)
248             {
249                 taskkill_message(STRING_SELF_TERMINATION);
250                 status_code = 1;
251                 continue;
252             }
253 
254             EnumWindows(pid_enum_proc, (LPARAM)&info);
255             if (info.found)
256                 taskkill_message_printfW(STRING_CLOSE_PID_SEARCH, pid);
257             else
258             {
259                 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
260                 status_code = 128;
261             }
262         }
263         else
264         {
265             DWORD index;
266             BOOL found_process = FALSE;
267 
268             for (index = 0; index < pid_list_size; index++)
269             {
270                 WCHAR process_name[MAX_PATH];
271 
272                 if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) &&
273                     !strcmpiW(process_name, task_list[i]))
274                 {
275                     struct pid_close_info info = { pid_list[index] };
276 
277                     found_process = TRUE;
278                     if (pid_list[index] == self_pid)
279                     {
280                         taskkill_message(STRING_SELF_TERMINATION);
281                         status_code = 1;
282                         continue;
283                     }
284 
285                     EnumWindows(pid_enum_proc, (LPARAM)&info);
286                     taskkill_message_printfW(STRING_CLOSE_PROC_SRCH, process_name, pid_list[index]);
287                 }
288             }
289 
290             if (!found_process)
291             {
292                 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
293                 status_code = 128;
294             }
295         }
296     }
297 
298     HeapFree(GetProcessHeap(), 0, pid_list);
299     return status_code;
300 }
301 
302 static int terminate_processes(void)
303 {
304     DWORD *pid_list, pid_list_size;
305     DWORD self_pid = GetCurrentProcessId();
306     unsigned int i;
307     int status_code = 0;
308 
309     pid_list = enumerate_processes(&pid_list_size);
310     if (!pid_list)
311     {
312         taskkill_message(STRING_ENUM_FAILED);
313         return 1;
314     }
315 
316     for (i = 0; i < task_count; i++)
317     {
318         WCHAR *p = task_list[i];
319         BOOL is_numeric = TRUE;
320 
321         /* Determine whether the string is not numeric. */
322         while (*p)
323         {
324             if (!isdigitW(*p++))
325             {
326                 is_numeric = FALSE;
327                 break;
328             }
329         }
330 
331         if (is_numeric)
332         {
333             DWORD pid = atoiW(task_list[i]);
334             HANDLE process;
335 
336             if (pid == self_pid)
337             {
338                 taskkill_message(STRING_SELF_TERMINATION);
339                 status_code = 1;
340                 continue;
341             }
342 
343             process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
344             if (!process)
345             {
346                 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
347                 status_code = 128;
348                 continue;
349             }
350 
351             if (!TerminateProcess(process, 0))
352             {
353                 taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]);
354                 status_code = 1;
355                 CloseHandle(process);
356                 continue;
357             }
358 
359             taskkill_message_printfW(STRING_TERM_PID_SEARCH, pid);
360             CloseHandle(process);
361         }
362         else
363         {
364             DWORD index;
365             BOOL found_process = FALSE;
366 
367             for (index = 0; index < pid_list_size; index++)
368             {
369                 WCHAR process_name[MAX_PATH];
370 
371                 if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) &&
372                     !strcmpiW(process_name, task_list[i]))
373                 {
374                     HANDLE process;
375 
376                     if (pid_list[index] == self_pid)
377                     {
378                         taskkill_message(STRING_SELF_TERMINATION);
379                         status_code = 1;
380                         continue;
381                     }
382 
383                     process = OpenProcess(PROCESS_TERMINATE, FALSE, pid_list[index]);
384                     if (!process)
385                     {
386                         taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
387                         status_code = 128;
388                         continue;
389                     }
390 
391                     if (!TerminateProcess(process, 0))
392                     {
393                         taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]);
394                         status_code = 1;
395                         CloseHandle(process);
396                         continue;
397                     }
398 
399                     found_process = TRUE;
400                     taskkill_message_printfW(STRING_TERM_PROC_SEARCH, task_list[i], pid_list[index]);
401                     CloseHandle(process);
402                 }
403             }
404 
405             if (!found_process)
406             {
407                 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
408                 status_code = 128;
409             }
410         }
411     }
412 
413     HeapFree(GetProcessHeap(), 0, pid_list);
414     return status_code;
415 }
416 
417 static BOOL add_to_task_list(WCHAR *name)
418 {
419     static unsigned int list_size = 16;
420 
421     if (!task_list)
422     {
423         task_list = HeapAlloc(GetProcessHeap(), 0,
424                                    list_size * sizeof(*task_list));
425         if (!task_list)
426             return FALSE;
427     }
428     else if (task_count == list_size)
429     {
430         void *realloc_list;
431 
432         list_size *= 2;
433         realloc_list = HeapReAlloc(GetProcessHeap(), 0, task_list,
434                                    list_size * sizeof(*task_list));
435         if (!realloc_list)
436             return FALSE;
437 
438         task_list = realloc_list;
439     }
440 
441     task_list[task_count++] = name;
442     return TRUE;
443 }
444 
445 /* FIXME Argument processing does not match behavior observed on Windows.
446  * Stringent argument counting and processing is performed, and unrecognized
447  * options are detected as parameters when placed after options that accept one. */
448 static BOOL process_arguments(int argc, WCHAR *argv[])
449 {
450     static const WCHAR opForceTerminate[] = {'f',0};
451     static const WCHAR opImage[] = {'i','m',0};
452     static const WCHAR opPID[] = {'p','i','d',0};
453     static const WCHAR opHelp[] = {'?',0};
454     static const WCHAR opTerminateChildren[] = {'t',0};
455 
456     if (argc > 1)
457     {
458         int i;
459         WCHAR *argdata;
460         BOOL has_im = FALSE, has_pid = FALSE;
461 
462         /* Only the lone help option is recognized. */
463         if (argc == 2)
464         {
465             argdata = argv[1];
466             if ((*argdata == '/' || *argdata == '-') && !strcmpW(opHelp, argdata + 1))
467             {
468                 taskkill_message(STRING_USAGE);
469                 exit(0);
470             }
471         }
472 
473         for (i = 1; i < argc; i++)
474         {
475             BOOL got_im = FALSE, got_pid = FALSE;
476 
477             argdata = argv[i];
478             if (*argdata != '/' && *argdata != '-')
479                 goto invalid;
480             argdata++;
481 
482             if (!strcmpiW(opTerminateChildren, argdata))
483                 WINE_FIXME("argument T not supported\n");
484             if (!strcmpiW(opForceTerminate, argdata))
485                 force_termination = TRUE;
486             /* Options /IM and /PID appear to behave identically, except for
487              * the fact that they cannot be specified at the same time. */
488             else if ((got_im = !strcmpiW(opImage, argdata)) ||
489                      (got_pid = !strcmpiW(opPID, argdata)))
490             {
491                 if (!argv[i + 1])
492                 {
493                     taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]);
494                     taskkill_message(STRING_USAGE);
495                     return FALSE;
496                 }
497 
498                 if (got_im) has_im = TRUE;
499                 if (got_pid) has_pid = TRUE;
500 
501                 if (has_im && has_pid)
502                 {
503                     taskkill_message(STRING_MUTUAL_EXCLUSIVE);
504                     taskkill_message(STRING_USAGE);
505                     return FALSE;
506                 }
507 
508                 if (!add_to_task_list(argv[i + 1]))
509                     return FALSE;
510                 i++;
511             }
512             else
513             {
514                 invalid:
515                 taskkill_message(STRING_INVALID_OPTION);
516                 taskkill_message(STRING_USAGE);
517                 return FALSE;
518             }
519         }
520     }
521     else
522     {
523         taskkill_message(STRING_MISSING_OPTION);
524         taskkill_message(STRING_USAGE);
525         return FALSE;
526     }
527 
528     return TRUE;
529 }
530 
531 int wmain(int argc, WCHAR *argv[])
532 {
533     int status_code = 0;
534 
535     if (!process_arguments(argc, argv))
536     {
537         HeapFree(GetProcessHeap(), 0, task_list);
538         return 1;
539     }
540 
541     if (force_termination)
542         status_code = terminate_processes();
543     else
544         status_code = send_close_messages();
545 
546     HeapFree(GetProcessHeap(), 0, task_list);
547     return status_code;
548 }
549