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