Release 1.5.29.
[wine] / programs / taskkill / taskkill.c
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 int force_termination;
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 CDECL 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 CDECL 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 slashForceTerminate[] = {'/','f',0};
451     static const WCHAR slashImage[] = {'/','i','m',0};
452     static const WCHAR slashPID[] = {'/','p','i','d',0};
453     static const WCHAR slashHelp[] = {'/','?',0};
454     static const WCHAR slashTerminateChildren[] = {'/','t',0};
455
456     if (argc > 1)
457     {
458         int i;
459         BOOL has_im = 0, has_pid = 0;
460
461         /* Only the lone help option is recognized. */
462         if (argc == 2 && !strcmpW(slashHelp, argv[1]))
463         {
464             taskkill_message(STRING_USAGE);
465             exit(0);
466         }
467
468         for (i = 1; i < argc; i++)
469         {
470             int got_im = 0, got_pid = 0;
471
472             if (!strcmpiW(slashTerminateChildren, argv[i]))
473                 WINE_FIXME("/T not supported\n");
474             if (!strcmpiW(slashForceTerminate, argv[i]))
475                 force_termination = 1;
476             /* Options /IM and /PID appear to behave identically, except for
477              * the fact that they cannot be specified at the same time. */
478             else if ((got_im = !strcmpiW(slashImage, argv[i])) ||
479                      (got_pid = !strcmpiW(slashPID, argv[i])))
480             {
481                 if (!argv[i + 1])
482                 {
483                     taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]);
484                     taskkill_message(STRING_USAGE);
485                     return FALSE;
486                 }
487
488                 if (got_im) has_im = 1;
489                 if (got_pid) has_pid = 1;
490
491                 if (has_im && has_pid)
492                 {
493                     taskkill_message(STRING_MUTUAL_EXCLUSIVE);
494                     taskkill_message(STRING_USAGE);
495                     return FALSE;
496                 }
497
498                 if (!add_to_task_list(argv[i + 1]))
499                     return FALSE;
500                 i++;
501             }
502             else
503             {
504                 taskkill_message(STRING_INVALID_OPTION);
505                 taskkill_message(STRING_USAGE);
506                 return FALSE;
507             }
508         }
509     }
510     else
511     {
512         taskkill_message(STRING_MISSING_OPTION);
513         taskkill_message(STRING_USAGE);
514         return FALSE;
515     }
516
517     return TRUE;
518 }
519
520 int wmain(int argc, WCHAR *argv[])
521 {
522     int status_code = 0;
523
524     if (!process_arguments(argc, argv))
525     {
526         HeapFree(GetProcessHeap(), 0, task_list);
527         return 1;
528     }
529
530     if (force_termination)
531         status_code = terminate_processes();
532     else
533         status_code = send_close_messages();
534
535     HeapFree(GetProcessHeap(), 0, task_list);
536     return status_code;
537 }