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