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