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