1 /* 2 * Task termination utility 3 * 4 * Copyright 2008 Andrew Riedi 5 * Copyright 2010 Andrew Nguyen 6 * Copyright 2020 He Yang 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Lesser General Public 10 * License as published by the Free Software Foundation; either 11 * version 2.1 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public 19 * License along with this library; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 21 */ 22 23 #include <stdlib.h> 24 #include <windows.h> 25 #include <psapi.h> 26 #include <wine/debug.h> 27 #include <wine/unicode.h> 28 29 #include "taskkill.h" 30 31 WINE_DEFAULT_DEBUG_CHANNEL(taskkill); 32 33 static BOOL force_termination = FALSE; 34 35 static WCHAR **task_list; 36 static unsigned int task_count; 37 38 #ifdef __REACTOS__ 39 40 static WCHAR opForceTerminate[] = L"f"; 41 static WCHAR opImage[] = L"im"; 42 static WCHAR opPID[] = L"pid"; 43 static WCHAR opHelp[] = L"?"; 44 static WCHAR opTerminateChildren[] = L"t"; 45 46 static PWCHAR opList[] = {opForceTerminate, opImage, opPID, opHelp, opTerminateChildren}; 47 48 #define OP_PARAM_INVALID -1 49 50 #define OP_PARAM_FORCE_TERMINATE 0 51 #define OP_PARAM_IMAGE 1 52 #define OP_PARAM_PID 2 53 #define OP_PARAM_HELP 3 54 #define OP_PARAM_TERMINATE_CHILD 4 55 56 #endif // __REACTOS__ 57 58 struct pid_close_info 59 { 60 DWORD pid; 61 BOOL found; 62 }; 63 64 static int taskkill_vprintfW(const WCHAR *msg, __ms_va_list va_args) 65 { 66 int wlen; 67 DWORD count, ret; 68 WCHAR msg_buffer[8192]; 69 70 wlen = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, msg_buffer, 71 ARRAY_SIZE(msg_buffer), &va_args); 72 73 ret = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), msg_buffer, wlen, &count, NULL); 74 if (!ret) 75 { 76 DWORD len; 77 char *msgA; 78 79 /* On Windows WriteConsoleW() fails if the output is redirected. So fall 80 * back to WriteFile(), assuming the console encoding is still the right 81 * one in that case. 82 */ 83 len = WideCharToMultiByte(GetConsoleOutputCP(), 0, msg_buffer, wlen, 84 NULL, 0, NULL, NULL); 85 msgA = HeapAlloc(GetProcessHeap(), 0, len); 86 if (!msgA) 87 return 0; 88 89 WideCharToMultiByte(GetConsoleOutputCP(), 0, msg_buffer, wlen, msgA, len, 90 NULL, NULL); 91 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), msgA, len, &count, FALSE); 92 HeapFree(GetProcessHeap(), 0, msgA); 93 } 94 95 return count; 96 } 97 98 static int WINAPIV taskkill_printfW(const WCHAR *msg, ...) 99 { 100 __ms_va_list va_args; 101 int len; 102 103 __ms_va_start(va_args, msg); 104 len = taskkill_vprintfW(msg, va_args); 105 __ms_va_end(va_args); 106 107 return len; 108 } 109 110 static int WINAPIV taskkill_message_printfW(int msg, ...) 111 { 112 __ms_va_list va_args; 113 WCHAR msg_buffer[8192]; 114 int len; 115 116 LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); 117 118 __ms_va_start(va_args, msg); 119 len = taskkill_vprintfW(msg_buffer, va_args); 120 __ms_va_end(va_args); 121 122 return len; 123 } 124 125 static int taskkill_message(int msg) 126 { 127 static const WCHAR formatW[] = {'%','1',0}; 128 WCHAR msg_buffer[8192]; 129 130 LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); 131 132 return taskkill_printfW(formatW, msg_buffer); 133 } 134 135 /* Post WM_CLOSE to all top-level windows belonging to the process with specified PID. */ 136 static BOOL CALLBACK pid_enum_proc(HWND hwnd, LPARAM lParam) 137 { 138 struct pid_close_info *info = (struct pid_close_info *)lParam; 139 DWORD hwnd_pid; 140 141 GetWindowThreadProcessId(hwnd, &hwnd_pid); 142 143 if (hwnd_pid == info->pid) 144 { 145 PostMessageW(hwnd, WM_CLOSE, 0, 0); 146 info->found = TRUE; 147 } 148 149 return TRUE; 150 } 151 152 static DWORD *enumerate_processes(DWORD *list_count) 153 { 154 DWORD *pid_list, alloc_bytes = 1024 * sizeof(*pid_list), needed_bytes; 155 156 pid_list = HeapAlloc(GetProcessHeap(), 0, alloc_bytes); 157 if (!pid_list) 158 return NULL; 159 160 for (;;) 161 { 162 DWORD *realloc_list; 163 164 if (!EnumProcesses(pid_list, alloc_bytes, &needed_bytes)) 165 { 166 HeapFree(GetProcessHeap(), 0, pid_list); 167 return NULL; 168 } 169 170 /* EnumProcesses can't signal an insufficient buffer condition, so the 171 * only way to possibly determine whether a larger buffer is required 172 * is to see whether the written number of bytes is the same as the 173 * buffer size. If so, the buffer will be reallocated to twice the 174 * size. */ 175 if (alloc_bytes != needed_bytes) 176 break; 177 178 alloc_bytes *= 2; 179 realloc_list = HeapReAlloc(GetProcessHeap(), 0, pid_list, alloc_bytes); 180 if (!realloc_list) 181 { 182 HeapFree(GetProcessHeap(), 0, pid_list); 183 return NULL; 184 } 185 pid_list = realloc_list; 186 } 187 188 *list_count = needed_bytes / sizeof(*pid_list); 189 return pid_list; 190 } 191 192 static BOOL get_process_name_from_pid(DWORD pid, WCHAR *buf, DWORD chars) 193 { 194 HANDLE process; 195 HMODULE module; 196 DWORD required_size; 197 198 process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); 199 if (!process) 200 return FALSE; 201 202 if (!EnumProcessModules(process, &module, sizeof(module), &required_size)) 203 { 204 CloseHandle(process); 205 return FALSE; 206 } 207 208 if (!GetModuleBaseNameW(process, module, buf, chars)) 209 { 210 CloseHandle(process); 211 return FALSE; 212 } 213 214 CloseHandle(process); 215 return TRUE; 216 } 217 218 /* The implemented task enumeration and termination behavior does not 219 * exactly match native behavior. On Windows: 220 * 221 * In the case of terminating by process name, specifying a particular 222 * process name more times than the number of running instances causes 223 * all instances to be terminated, but termination failure messages to 224 * be printed as many times as the difference between the specification 225 * quantity and the number of running instances. 226 * 227 * Successful terminations are all listed first in order, with failing 228 * terminations being listed at the end. 229 * 230 * A PID of zero causes taskkill to warn about the inability to terminate 231 * system processes. */ 232 233 #ifndef __REACTOS__ 234 235 static int send_close_messages(void) 236 { 237 DWORD *pid_list, pid_list_size; 238 DWORD self_pid = GetCurrentProcessId(); 239 unsigned int i; 240 int status_code = 0; 241 242 pid_list = enumerate_processes(&pid_list_size); 243 if (!pid_list) 244 { 245 taskkill_message(STRING_ENUM_FAILED); 246 return 1; 247 } 248 249 for (i = 0; i < task_count; i++) 250 { 251 WCHAR *p = task_list[i]; 252 BOOL is_numeric = TRUE; 253 254 /* Determine whether the string is not numeric. */ 255 while (*p) 256 { 257 if (!isdigitW(*p++)) 258 { 259 is_numeric = FALSE; 260 break; 261 } 262 } 263 264 if (is_numeric) 265 { 266 DWORD pid = atoiW(task_list[i]); 267 struct pid_close_info info = { pid }; 268 269 if (pid == 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 if (info.found) 278 taskkill_message_printfW(STRING_CLOSE_PID_SEARCH, pid); 279 else 280 { 281 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); 282 status_code = 128; 283 } 284 } 285 else 286 { 287 DWORD index; 288 BOOL found_process = FALSE; 289 290 for (index = 0; index < pid_list_size; index++) 291 { 292 WCHAR process_name[MAX_PATH]; 293 294 if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) && 295 !strcmpiW(process_name, task_list[i])) 296 { 297 struct pid_close_info info = { pid_list[index] }; 298 299 found_process = TRUE; 300 if (pid_list[index] == self_pid) 301 { 302 taskkill_message(STRING_SELF_TERMINATION); 303 status_code = 1; 304 continue; 305 } 306 307 EnumWindows(pid_enum_proc, (LPARAM)&info); 308 taskkill_message_printfW(STRING_CLOSE_PROC_SRCH, process_name, pid_list[index]); 309 } 310 } 311 312 if (!found_process) 313 { 314 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); 315 status_code = 128; 316 } 317 } 318 } 319 320 HeapFree(GetProcessHeap(), 0, pid_list); 321 return status_code; 322 } 323 324 #endif // __REACTOS__ 325 326 #ifdef __REACTOS__ 327 static int terminate_processes(BOOL force_termination) 328 #else 329 static int terminate_processes(void) 330 #endif 331 { 332 DWORD *pid_list, pid_list_size; 333 DWORD self_pid = GetCurrentProcessId(); 334 unsigned int i; 335 int status_code = 0; 336 337 pid_list = enumerate_processes(&pid_list_size); 338 if (!pid_list) 339 { 340 taskkill_message(STRING_ENUM_FAILED); 341 return 1; 342 } 343 344 for (i = 0; i < task_count; i++) 345 { 346 WCHAR *p = task_list[i]; 347 BOOL is_numeric = TRUE; 348 349 /* Determine whether the string is not numeric. */ 350 while (*p) 351 { 352 if (!isdigitW(*p++)) 353 { 354 is_numeric = FALSE; 355 break; 356 } 357 } 358 359 if (is_numeric) 360 { 361 DWORD pid = atoiW(task_list[i]); 362 #ifndef __REACTOS__ 363 HANDLE process; 364 #endif 365 366 if (pid == self_pid) 367 { 368 taskkill_message(STRING_SELF_TERMINATION); 369 status_code = 1; 370 continue; 371 } 372 373 #ifdef __REACTOS__ 374 if (force_termination) 375 { 376 HANDLE process; 377 #endif 378 process = OpenProcess(PROCESS_TERMINATE, FALSE, pid); 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 taskkill_message_printfW(STRING_TERM_PID_SEARCH, pid); 395 CloseHandle(process); 396 #ifdef __REACTOS__ 397 } 398 else 399 { 400 struct pid_close_info info = { pid }; 401 402 EnumWindows(pid_enum_proc, (LPARAM)&info); 403 if (info.found) 404 taskkill_message_printfW(STRING_CLOSE_PID_SEARCH, pid); 405 else 406 { 407 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); 408 status_code = 128; 409 } 410 } 411 #endif 412 } 413 else 414 { 415 DWORD index; 416 BOOL found_process = FALSE; 417 418 for (index = 0; index < pid_list_size; index++) 419 { 420 WCHAR process_name[MAX_PATH]; 421 422 if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) && 423 !strcmpiW(process_name, task_list[i])) 424 { 425 #ifdef __REACTOS__ 426 found_process = TRUE; 427 #else 428 HANDLE process; 429 #endif 430 431 if (pid_list[index] == self_pid) 432 { 433 taskkill_message(STRING_SELF_TERMINATION); 434 status_code = 1; 435 continue; 436 } 437 438 #ifdef __REACTOS__ 439 if (force_termination) 440 { 441 HANDLE process; 442 #endif 443 process = OpenProcess(PROCESS_TERMINATE, FALSE, pid_list[index]); 444 if (!process) 445 { 446 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); 447 status_code = 128; 448 continue; 449 } 450 451 if (!TerminateProcess(process, 0)) 452 { 453 taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]); 454 status_code = 1; 455 CloseHandle(process); 456 continue; 457 } 458 459 #ifndef __REACTOS__ 460 found_process = TRUE; 461 #endif 462 taskkill_message_printfW(STRING_TERM_PROC_SEARCH, task_list[i], pid_list[index]); 463 CloseHandle(process); 464 #ifdef __REACTOS__ 465 } 466 else 467 { 468 struct pid_close_info info = { pid_list[index] }; 469 EnumWindows(pid_enum_proc, (LPARAM)&info); 470 taskkill_message_printfW(STRING_CLOSE_PROC_SRCH, process_name, pid_list[index]); 471 } 472 #endif 473 } 474 } 475 476 if (!found_process) 477 { 478 taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); 479 status_code = 128; 480 } 481 } 482 } 483 484 HeapFree(GetProcessHeap(), 0, pid_list); 485 return status_code; 486 } 487 488 static BOOL add_to_task_list(WCHAR *name) 489 { 490 static unsigned int list_size = 16; 491 492 if (!task_list) 493 { 494 task_list = HeapAlloc(GetProcessHeap(), 0, 495 list_size * sizeof(*task_list)); 496 if (!task_list) 497 return FALSE; 498 } 499 else if (task_count == list_size) 500 { 501 void *realloc_list; 502 503 list_size *= 2; 504 realloc_list = HeapReAlloc(GetProcessHeap(), 0, task_list, 505 list_size * sizeof(*task_list)); 506 if (!realloc_list) 507 return FALSE; 508 509 task_list = realloc_list; 510 } 511 512 task_list[task_count++] = name; 513 return TRUE; 514 } 515 516 #ifdef __REACTOS__ 517 518 static int get_argument_type(WCHAR* argument) 519 { 520 int i; 521 522 if (argument[0] != L'/' && argument[0] != L'-') 523 { 524 return OP_PARAM_INVALID; 525 } 526 argument++; 527 528 for (i = 0; i < _countof(opList); i++) 529 { 530 if (!strcmpiW(opList[i], argument)) 531 { 532 return i; 533 } 534 } 535 return OP_PARAM_INVALID; 536 } 537 538 /* FIXME 539 argument T not supported 540 */ 541 542 static BOOL process_arguments(int argc, WCHAR* argv[]) 543 { 544 BOOL has_im = FALSE, has_pid = FALSE, has_help = FALSE; 545 546 if (argc > 1) 547 { 548 int i; 549 for (i = 1; i < argc; i++) 550 { 551 int argument = get_argument_type(argv[i]); 552 553 switch (argument) 554 { 555 case OP_PARAM_FORCE_TERMINATE: 556 { 557 if (force_termination == TRUE) 558 { 559 // -f already specified 560 taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1); 561 taskkill_message(STRING_USAGE); 562 return FALSE; 563 } 564 force_termination = TRUE; 565 break; 566 } 567 case OP_PARAM_IMAGE: 568 case OP_PARAM_PID: 569 { 570 if (!argv[i + 1]) 571 { 572 taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]); 573 taskkill_message(STRING_USAGE); 574 return FALSE; 575 } 576 577 if (argument == OP_PARAM_IMAGE) 578 has_im = TRUE; 579 if (argument == OP_PARAM_PID) 580 has_pid = TRUE; 581 582 if (has_im && has_pid) 583 { 584 taskkill_message(STRING_MUTUAL_EXCLUSIVE); 585 taskkill_message(STRING_USAGE); 586 return FALSE; 587 } 588 589 if (get_argument_type(argv[i + 1]) != OP_PARAM_INVALID) 590 { 591 taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]); 592 taskkill_message(STRING_USAGE); 593 return FALSE; 594 } 595 596 if (!add_to_task_list(argv[++i])) // add next parameters to task_list 597 return FALSE; 598 599 break; 600 } 601 case OP_PARAM_HELP: 602 { 603 if (has_help == TRUE) 604 { 605 // -? already specified 606 taskkill_message_printfW(STRING_PARAM_TOO_MUCH, argv[i], 1); 607 taskkill_message(STRING_USAGE); 608 return FALSE; 609 } 610 has_help = TRUE; 611 break; 612 } 613 case OP_PARAM_TERMINATE_CHILD: 614 { 615 WINE_FIXME("argument T not supported\n"); 616 break; 617 } 618 case OP_PARAM_INVALID: 619 default: 620 { 621 taskkill_message(STRING_INVALID_OPTION); 622 taskkill_message(STRING_USAGE); 623 return FALSE; 624 } 625 } 626 } 627 } 628 629 if (has_help) 630 { 631 if (argc > 2) // any parameters other than -? is specified 632 { 633 taskkill_message(STRING_INVALID_SYNTAX); 634 taskkill_message(STRING_USAGE); 635 return FALSE; 636 } 637 else 638 { 639 taskkill_message(STRING_USAGE); 640 exit(0); 641 } 642 } 643 else if ((!has_im) && (!has_pid)) // has_help == FALSE 644 { 645 // both has_im and has_pid are missing (maybe -fi option is missing too, if implemented later) 646 taskkill_message(STRING_MISSING_OPTION); 647 taskkill_message(STRING_USAGE); 648 return FALSE; 649 } 650 651 return TRUE; 652 } 653 654 #else 655 656 /* FIXME Argument processing does not match behavior observed on Windows. 657 * Stringent argument counting and processing is performed, and unrecognized 658 * options are detected as parameters when placed after options that accept one. */ 659 static BOOL process_arguments(int argc, WCHAR *argv[]) 660 { 661 static const WCHAR opForceTerminate[] = {'f',0}; 662 static const WCHAR opImage[] = {'i','m',0}; 663 static const WCHAR opPID[] = {'p','i','d',0}; 664 static const WCHAR opHelp[] = {'?',0}; 665 static const WCHAR opTerminateChildren[] = {'t',0}; 666 667 if (argc > 1) 668 { 669 int i; 670 WCHAR *argdata; 671 BOOL has_im = FALSE, has_pid = FALSE; 672 673 /* Only the lone help option is recognized. */ 674 if (argc == 2) 675 { 676 argdata = argv[1]; 677 if ((*argdata == '/' || *argdata == '-') && !strcmpW(opHelp, argdata + 1)) 678 { 679 taskkill_message(STRING_USAGE); 680 exit(0); 681 } 682 } 683 684 for (i = 1; i < argc; i++) 685 { 686 BOOL got_im = FALSE, got_pid = FALSE; 687 688 argdata = argv[i]; 689 if (*argdata != '/' && *argdata != '-') 690 goto invalid; 691 argdata++; 692 693 if (!strcmpiW(opTerminateChildren, argdata)) 694 WINE_FIXME("argument T not supported\n"); 695 if (!strcmpiW(opForceTerminate, argdata)) 696 force_termination = TRUE; 697 /* Options /IM and /PID appear to behave identically, except for 698 * the fact that they cannot be specified at the same time. */ 699 else if ((got_im = !strcmpiW(opImage, argdata)) || 700 (got_pid = !strcmpiW(opPID, argdata))) 701 { 702 if (!argv[i + 1]) 703 { 704 taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]); 705 taskkill_message(STRING_USAGE); 706 return FALSE; 707 } 708 709 if (got_im) has_im = TRUE; 710 if (got_pid) has_pid = TRUE; 711 712 if (has_im && has_pid) 713 { 714 taskkill_message(STRING_MUTUAL_EXCLUSIVE); 715 taskkill_message(STRING_USAGE); 716 return FALSE; 717 } 718 719 if (!add_to_task_list(argv[i + 1])) 720 return FALSE; 721 i++; 722 } 723 else 724 { 725 invalid: 726 taskkill_message(STRING_INVALID_OPTION); 727 taskkill_message(STRING_USAGE); 728 return FALSE; 729 } 730 } 731 } 732 else 733 { 734 taskkill_message(STRING_MISSING_OPTION); 735 taskkill_message(STRING_USAGE); 736 return FALSE; 737 } 738 739 return TRUE; 740 } 741 742 #endif // __REACTOS__ 743 744 int wmain(int argc, WCHAR *argv[]) 745 { 746 int status_code = 0; 747 748 if (!process_arguments(argc, argv)) 749 { 750 HeapFree(GetProcessHeap(), 0, task_list); 751 return 1; 752 } 753 754 #ifdef __REACTOS__ 755 status_code = terminate_processes(force_termination); 756 #else 757 if (force_termination) 758 status_code = terminate_processes(); 759 else 760 status_code = send_close_messages(); 761 #endif 762 763 HeapFree(GetProcessHeap(), 0, task_list); 764 return status_code; 765 } 766