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