/* * Task termination utility * * Copyright 2008 Andrew Riedi * Copyright 2010 Andrew Nguyen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include <stdlib.h> #include <windows.h> #include <psapi.h> #include <wine/debug.h> #include <wine/unicode.h> #include "taskkill.h" WINE_DEFAULT_DEBUG_CHANNEL(taskkill); static int force_termination; static WCHAR **task_list; static unsigned int task_count; struct pid_close_info { DWORD pid; BOOL found; }; static int taskkill_vprintfW(const WCHAR *msg, __ms_va_list va_args) { int wlen; DWORD count, ret; WCHAR msg_buffer[8192]; wlen = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, msg_buffer, sizeof(msg_buffer)/sizeof(*msg_buffer), &va_args); ret = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), msg_buffer, wlen, &count, NULL); if (!ret) { DWORD len; char *msgA; /* On Windows WriteConsoleW() fails if the output is redirected. So fall * back to WriteFile(), assuming the console encoding is still the right * one in that case. */ len = WideCharToMultiByte(GetConsoleOutputCP(), 0, msg_buffer, wlen, NULL, 0, NULL, NULL); msgA = HeapAlloc(GetProcessHeap(), 0, len); if (!msgA) return 0; WideCharToMultiByte(GetConsoleOutputCP(), 0, msg_buffer, wlen, msgA, len, NULL, NULL); WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), msgA, len, &count, FALSE); HeapFree(GetProcessHeap(), 0, msgA); } return count; } static int CDECL taskkill_printfW(const WCHAR *msg, ...) { __ms_va_list va_args; int len; __ms_va_start(va_args, msg); len = taskkill_vprintfW(msg, va_args); __ms_va_end(va_args); return len; } static int CDECL taskkill_message_printfW(int msg, ...) { __ms_va_list va_args; WCHAR msg_buffer[8192]; int len; LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, sizeof(msg_buffer)/sizeof(WCHAR)); __ms_va_start(va_args, msg); len = taskkill_vprintfW(msg_buffer, va_args); __ms_va_end(va_args); return len; } static int taskkill_message(int msg) { static const WCHAR formatW[] = {'%','1',0}; WCHAR msg_buffer[8192]; LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, sizeof(msg_buffer)/sizeof(WCHAR)); return taskkill_printfW(formatW, msg_buffer); } /* Post WM_CLOSE to all top-level windows belonging to the process with specified PID. */ static BOOL CALLBACK pid_enum_proc(HWND hwnd, LPARAM lParam) { struct pid_close_info *info = (struct pid_close_info *)lParam; DWORD hwnd_pid; GetWindowThreadProcessId(hwnd, &hwnd_pid); if (hwnd_pid == info->pid) { PostMessageW(hwnd, WM_CLOSE, 0, 0); info->found = TRUE; } return TRUE; } static DWORD *enumerate_processes(DWORD *list_count) { DWORD *pid_list, alloc_bytes = 1024 * sizeof(*pid_list), needed_bytes; pid_list = HeapAlloc(GetProcessHeap(), 0, alloc_bytes); if (!pid_list) return NULL; for (;;) { DWORD *realloc_list; if (!EnumProcesses(pid_list, alloc_bytes, &needed_bytes)) { HeapFree(GetProcessHeap(), 0, pid_list); return NULL; } /* EnumProcesses can't signal an insufficient buffer condition, so the * only way to possibly determine whether a larger buffer is required * is to see whether the written number of bytes is the same as the * buffer size. If so, the buffer will be reallocated to twice the * size. */ if (alloc_bytes != needed_bytes) break; alloc_bytes *= 2; realloc_list = HeapReAlloc(GetProcessHeap(), 0, pid_list, alloc_bytes); if (!realloc_list) { HeapFree(GetProcessHeap(), 0, pid_list); return NULL; } pid_list = realloc_list; } *list_count = needed_bytes / sizeof(*pid_list); return pid_list; } static BOOL get_process_name_from_pid(DWORD pid, WCHAR *buf, DWORD chars) { HANDLE process; HMODULE module; DWORD required_size; process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); if (!process) return FALSE; if (!EnumProcessModules(process, &module, sizeof(module), &required_size)) { CloseHandle(process); return FALSE; } if (!GetModuleBaseNameW(process, module, buf, chars)) { CloseHandle(process); return FALSE; } CloseHandle(process); return TRUE; } /* The implemented task enumeration and termination behavior does not * exactly match native behavior. On Windows: * * In the case of terminating by process name, specifying a particular * process name more times than the number of running instances causes * all instances to be terminated, but termination failure messages to * be printed as many times as the difference between the specification * quantity and the number of running instances. * * Successful terminations are all listed first in order, with failing * terminations being listed at the end. * * A PID of zero causes taskkill to warn about the inability to terminate * system processes. */ static int send_close_messages(void) { DWORD *pid_list, pid_list_size; DWORD self_pid = GetCurrentProcessId(); unsigned int i; int status_code = 0; pid_list = enumerate_processes(&pid_list_size); if (!pid_list) { taskkill_message(STRING_ENUM_FAILED); return 1; } for (i = 0; i < task_count; i++) { WCHAR *p = task_list[i]; BOOL is_numeric = TRUE; /* Determine whether the string is not numeric. */ while (*p) { if (!isdigitW(*p++)) { is_numeric = FALSE; break; } } if (is_numeric) { DWORD pid = atoiW(task_list[i]); struct pid_close_info info = { pid }; if (pid == self_pid) { taskkill_message(STRING_SELF_TERMINATION); status_code = 1; continue; } EnumWindows(pid_enum_proc, (LPARAM)&info); if (info.found) taskkill_message_printfW(STRING_CLOSE_PID_SEARCH, pid); else { taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); status_code = 128; } } else { DWORD index; BOOL found_process = FALSE; for (index = 0; index < pid_list_size; index++) { WCHAR process_name[MAX_PATH]; if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) && !strcmpiW(process_name, task_list[i])) { struct pid_close_info info = { pid_list[index] }; found_process = TRUE; if (pid_list[index] == self_pid) { taskkill_message(STRING_SELF_TERMINATION); status_code = 1; continue; } EnumWindows(pid_enum_proc, (LPARAM)&info); taskkill_message_printfW(STRING_CLOSE_PROC_SRCH, process_name, pid_list[index]); } } if (!found_process) { taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); status_code = 128; } } } HeapFree(GetProcessHeap(), 0, pid_list); return status_code; } static int terminate_processes(void) { DWORD *pid_list, pid_list_size; DWORD self_pid = GetCurrentProcessId(); unsigned int i; int status_code = 0; pid_list = enumerate_processes(&pid_list_size); if (!pid_list) { taskkill_message(STRING_ENUM_FAILED); return 1; } for (i = 0; i < task_count; i++) { WCHAR *p = task_list[i]; BOOL is_numeric = TRUE; /* Determine whether the string is not numeric. */ while (*p) { if (!isdigitW(*p++)) { is_numeric = FALSE; break; } } if (is_numeric) { DWORD pid = atoiW(task_list[i]); HANDLE process; if (pid == self_pid) { taskkill_message(STRING_SELF_TERMINATION); status_code = 1; continue; } process = OpenProcess(PROCESS_TERMINATE, FALSE, pid); if (!process) { taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); status_code = 128; continue; } if (!TerminateProcess(process, 0)) { taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]); status_code = 1; CloseHandle(process); continue; } taskkill_message_printfW(STRING_TERM_PID_SEARCH, pid); CloseHandle(process); } else { DWORD index; BOOL found_process = FALSE; for (index = 0; index < pid_list_size; index++) { WCHAR process_name[MAX_PATH]; if (get_process_name_from_pid(pid_list[index], process_name, MAX_PATH) && !strcmpiW(process_name, task_list[i])) { HANDLE process; if (pid_list[index] == self_pid) { taskkill_message(STRING_SELF_TERMINATION); status_code = 1; continue; } process = OpenProcess(PROCESS_TERMINATE, FALSE, pid_list[index]); if (!process) { taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); status_code = 128; continue; } if (!TerminateProcess(process, 0)) { taskkill_message_printfW(STRING_TERMINATE_FAILED, task_list[i]); status_code = 1; CloseHandle(process); continue; } found_process = TRUE; taskkill_message_printfW(STRING_TERM_PROC_SEARCH, task_list[i], pid_list[index]); CloseHandle(process); } } if (!found_process) { taskkill_message_printfW(STRING_SEARCH_FAILED, task_list[i]); status_code = 128; } } } HeapFree(GetProcessHeap(), 0, pid_list); return status_code; } static BOOL add_to_task_list(WCHAR *name) { static unsigned int list_size = 16; if (!task_list) { task_list = HeapAlloc(GetProcessHeap(), 0, list_size * sizeof(*task_list)); if (!task_list) return FALSE; } else if (task_count == list_size) { void *realloc_list; list_size *= 2; realloc_list = HeapReAlloc(GetProcessHeap(), 0, task_list, list_size * sizeof(*task_list)); if (!realloc_list) return FALSE; task_list = realloc_list; } task_list[task_count++] = name; return TRUE; } /* FIXME Argument processing does not match behavior observed on Windows. * Stringent argument counting and processing is performed, and unrecognized * options are detected as parameters when placed after options that accept one. */ static BOOL process_arguments(int argc, WCHAR *argv[]) { static const WCHAR slashForceTerminate[] = {'/','f',0}; static const WCHAR slashImage[] = {'/','i','m',0}; static const WCHAR slashPID[] = {'/','p','i','d',0}; static const WCHAR slashHelp[] = {'/','?',0}; static const WCHAR slashTerminateChildren[] = {'/','t',0}; if (argc > 1) { int i; BOOL has_im = 0, has_pid = 0; /* Only the lone help option is recognized. */ if (argc == 2 && !strcmpW(slashHelp, argv[1])) { taskkill_message(STRING_USAGE); exit(0); } for (i = 1; i < argc; i++) { int got_im = 0, got_pid = 0; if (!strcmpiW(slashTerminateChildren, argv[i])) WINE_FIXME("/T not supported\n"); if (!strcmpiW(slashForceTerminate, argv[i])) force_termination = 1; /* Options /IM and /PID appear to behave identically, except for * the fact that they cannot be specified at the same time. */ else if ((got_im = !strcmpiW(slashImage, argv[i])) || (got_pid = !strcmpiW(slashPID, argv[i]))) { if (!argv[i + 1]) { taskkill_message_printfW(STRING_MISSING_PARAM, argv[i]); taskkill_message(STRING_USAGE); return FALSE; } if (got_im) has_im = 1; if (got_pid) has_pid = 1; if (has_im && has_pid) { taskkill_message(STRING_MUTUAL_EXCLUSIVE); taskkill_message(STRING_USAGE); return FALSE; } if (!add_to_task_list(argv[i + 1])) return FALSE; i++; } else { taskkill_message(STRING_INVALID_OPTION); taskkill_message(STRING_USAGE); return FALSE; } } } else { taskkill_message(STRING_MISSING_OPTION); taskkill_message(STRING_USAGE); return FALSE; } return TRUE; } int wmain(int argc, WCHAR *argv[]) { int status_code = 0; if (!process_arguments(argc, argv)) { HeapFree(GetProcessHeap(), 0, task_list); return 1; } if (force_termination) status_code = terminate_processes(); else status_code = send_close_messages(); HeapFree(GetProcessHeap(), 0, task_list); return status_code; }