winhttp: Implement IWinHttpRequest::SetCredentials.
[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     DWORD self_pid = GetCurrentProcessId();
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             if (pid == self_pid)
239             {
240                 taskkill_message(STRING_SELF_TERMINATION);
241                 status_code = 1;
242                 continue;
243             }
244
245             EnumWindows(pid_enum_proc, (LPARAM)&info);
246             if (info.found)
247                 taskkill_message_printfW(STRING_CLOSE_PID_SEARCH, pid);
248             else
249             {
250                 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
251                 status_code = 128;
252             }
253         }
254         else
255         {
256             DWORD index;
257             BOOL found_process = FALSE;
258
259             for (index = 0; index < pid_list_size; index++)
260             {
261                 WCHAR process_name[MAX_PATH];
262
263                 if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) &&
264                     !strcmpiW(process_name, task_list[i]))
265                 {
266                     struct pid_close_info info = { pid_list[index] };
267
268                     found_process = TRUE;
269                     if (pid_list[index] == self_pid)
270                     {
271                         taskkill_message(STRING_SELF_TERMINATION);
272                         status_code = 1;
273                         continue;
274                     }
275
276                     EnumWindows(pid_enum_proc, (LPARAM)&info);
277                     taskkill_message_printfW(STRING_CLOSE_PROC_SRCH, process_name, pid_list[index]);
278                 }
279             }
280
281             if (!found_process)
282             {
283                 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
284                 status_code = 128;
285             }
286         }
287     }
288
289     HeapFree(GetProcessHeap(), 0, pid_list);
290     return status_code;
291 }
292
293 static int terminate_processes(void)
294 {
295     DWORD *pid_list, pid_list_size;
296     DWORD self_pid = GetCurrentProcessId();
297     unsigned int i;
298     int status_code = 0;
299
300     pid_list = enumerate_processes(&pid_list_size);
301     if (!pid_list)
302     {
303         taskkill_message(STRING_ENUM_FAILED);
304         return 1;
305     }
306
307     for (i = 0; i < task_count; i++)
308     {
309         WCHAR *p = task_list[i];
310         BOOL is_numeric = TRUE;
311
312         /* Determine whether the string is not numeric. */
313         while (*p)
314         {
315             if (!isdigitW(*p++))
316             {
317                 is_numeric = FALSE;
318                 break;
319             }
320         }
321
322         if (is_numeric)
323         {
324             DWORD pid = atoiW(task_list[i]);
325             HANDLE process;
326
327             if (pid == self_pid)
328             {
329                 taskkill_message(STRING_SELF_TERMINATION);
330                 status_code = 1;
331                 continue;
332             }
333
334             process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
335             if (!process)
336             {
337                 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
338                 status_code = 128;
339                 continue;
340             }
341
342             if (!TerminateProcess(process, 0))
343             {
344                 taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]);
345                 status_code = 1;
346                 CloseHandle(process);
347                 continue;
348             }
349
350             taskkill_message_printfW(STRING_TERM_PID_SEARCH, pid);
351             CloseHandle(process);
352         }
353         else
354         {
355             DWORD index;
356             BOOL found_process = FALSE;
357
358             for (index = 0; index < pid_list_size; index++)
359             {
360                 WCHAR process_name[MAX_PATH];
361
362                 if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) &&
363                     !strcmpiW(process_name, task_list[i]))
364                 {
365                     HANDLE process;
366
367                     if (pid_list[index] == self_pid)
368                     {
369                         taskkill_message(STRING_SELF_TERMINATION);
370                         status_code = 1;
371                         continue;
372                     }
373
374                     process = OpenProcess(PROCESS_TERMINATE, FALSE, pid_list[index]);
375                     if (!process)
376                     {
377                         taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
378                         status_code = 128;
379                         continue;
380                     }
381
382                     if (!TerminateProcess(process, 0))
383                     {
384                         taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]);
385                         status_code = 1;
386                         CloseHandle(process);
387                         continue;
388                     }
389
390                     found_process = TRUE;
391                     taskkill_message_printfW(STRING_TERM_PROC_SEARCH, task_list[i], pid_list[index]);
392                     CloseHandle(process);
393                 }
394             }
395
396             if (!found_process)
397             {
398                 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]);
399                 status_code = 128;
400             }
401         }
402     }
403
404     HeapFree(GetProcessHeap(), 0, pid_list);
405     return status_code;
406 }
407
408 static BOOL add_to_task_list(WCHAR *name)
409 {
410     static unsigned int list_size = 16;
411
412     if (!task_list)
413     {
414         task_list = HeapAlloc(GetProcessHeap(), 0,
415                                    list_size * sizeof(*task_list));
416         if (!task_list)
417             return FALSE;
418     }
419     else if (task_count == list_size)
420     {
421         void *realloc_list;
422
423         list_size *= 2;
424         realloc_list = HeapReAlloc(GetProcessHeap(), 0, task_list,
425                                    list_size * sizeof(*task_list));
426         if (!realloc_list)
427             return FALSE;
428
429         task_list = realloc_list;
430     }
431
432     task_list[task_count++] = name;
433     return TRUE;
434 }
435
436 /* FIXME Argument processing does not match behavior observed on Windows.
437  * Stringent argument counting and processing is performed, and unrecognized
438  * options are detected as parameters when placed after options that accept one. */
439 static BOOL process_arguments(int argc, WCHAR *argv[])
440 {
441     static const WCHAR slashForceTerminate[] = {'/','f',0};
442     static const WCHAR slashImage[] = {'/','i','m',0};
443     static const WCHAR slashPID[] = {'/','p','i','d',0};
444     static const WCHAR slashHelp[] = {'/','?',0};
445
446     if (argc > 1)
447     {
448         int i;
449         BOOL has_im = 0, has_pid = 0;
450
451         /* Only the lone help option is recognized. */
452         if (argc == 2 && !strcmpW(slashHelp, argv[1]))
453         {
454             taskkill_message(STRING_USAGE);
455             exit(0);
456         }
457
458         for (i = 1; i < argc; i++)
459         {
460             int got_im = 0, got_pid = 0;
461
462             if (!strcmpiW(slashForceTerminate, argv[i]))
463                 force_termination = 1;
464             /* Options /IM and /PID appear to behave identically, except for
465              * the fact that they cannot be specified at the same time. */
466             else if ((got_im = !strcmpiW(slashImage, argv[i])) ||
467                      (got_pid = !strcmpiW(slashPID, argv[i])))
468             {
469                 if (!argv[i + 1])
470                 {
471                     taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]);
472                     taskkill_message(STRING_USAGE);
473                     return FALSE;
474                 }
475
476                 if (got_im) has_im = 1;
477                 if (got_pid) has_pid = 1;
478
479                 if (has_im && has_pid)
480                 {
481                     taskkill_message(STRING_MUTUAL_EXCLUSIVE);
482                     taskkill_message(STRING_USAGE);
483                     return FALSE;
484                 }
485
486                 if (!add_to_task_list(argv[i + 1]))
487                     return FALSE;
488                 i++;
489             }
490             else
491             {
492                 taskkill_message(STRING_INVALID_OPTION);
493                 taskkill_message(STRING_USAGE);
494                 return FALSE;
495             }
496         }
497     }
498     else
499     {
500         taskkill_message(STRING_MISSING_OPTION);
501         taskkill_message(STRING_USAGE);
502         return FALSE;
503     }
504
505     return TRUE;
506 }
507
508 int wmain(int argc, WCHAR *argv[])
509 {
510     int status_code = 0;
511
512     if (!process_arguments(argc, argv))
513     {
514         HeapFree(GetProcessHeap(), 0, task_list);
515         return 1;
516     }
517
518     if (force_termination)
519         status_code = terminate_processes();
520     else
521         status_code = send_close_messages();
522
523     HeapFree(GetProcessHeap(), 0, task_list);
524     return status_code;
525 }