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