diff --git a/reactos/base/applications/cmdutils/CMakeLists.txt b/reactos/base/applications/cmdutils/CMakeLists.txt index b36bbb77325..bfb9679c9ff 100644 --- a/reactos/base/applications/cmdutils/CMakeLists.txt +++ b/reactos/base/applications/cmdutils/CMakeLists.txt @@ -5,4 +5,5 @@ add_subdirectory(hostname) add_subdirectory(lodctr) add_subdirectory(more) add_subdirectory(reg) +add_subdirectory(taskkill) add_subdirectory(xcopy) diff --git a/reactos/base/applications/cmdutils/taskkill/CMakeLists.txt b/reactos/base/applications/cmdutils/taskkill/CMakeLists.txt new file mode 100644 index 00000000000..4b5e3498c87 --- /dev/null +++ b/reactos/base/applications/cmdutils/taskkill/CMakeLists.txt @@ -0,0 +1,6 @@ + +add_executable(taskkill taskkill.c taskkill.rc) +target_link_libraries(taskkill wine) +set_module_type(taskkill win32cui UNICODE) +add_importlibs(taskkill psapi user32 msvcrt kernel32 ntdll) +add_cd_file(TARGET taskkill DESTINATION reactos/system32 FOR all) diff --git a/reactos/base/applications/cmdutils/taskkill/taskkill.c b/reactos/base/applications/cmdutils/taskkill/taskkill.c new file mode 100644 index 00000000000..7a4d7f43bc2 --- /dev/null +++ b/reactos/base/applications/cmdutils/taskkill/taskkill.c @@ -0,0 +1,540 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/reactos/base/applications/cmdutils/taskkill/taskkill.h b/reactos/base/applications/cmdutils/taskkill/taskkill.h new file mode 100644 index 00000000000..7972a156f3c --- /dev/null +++ b/reactos/base/applications/cmdutils/taskkill/taskkill.h @@ -0,0 +1,37 @@ +/* + * Task termination utility private definitions + * + * 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 + +/* Translation IDs. */ +#define STRING_USAGE 101 +#define STRING_INVALID_OPTION 102 +#define STRING_INVALID_PARAM 103 +#define STRING_MISSING_OPTION 104 +#define STRING_MISSING_PARAM 105 +#define STRING_MUTUAL_EXCLUSIVE 106 +#define STRING_CLOSE_PID_SEARCH 107 +#define STRING_CLOSE_PROC_SRCH 108 +#define STRING_TERM_PID_SEARCH 109 +#define STRING_TERM_PROC_SEARCH 110 +#define STRING_SEARCH_FAILED 111 +#define STRING_ENUM_FAILED 112 +#define STRING_TERMINATE_FAILED 113 +#define STRING_SELF_TERMINATION 114 diff --git a/reactos/base/applications/cmdutils/taskkill/taskkill.rc b/reactos/base/applications/cmdutils/taskkill/taskkill.rc new file mode 100644 index 00000000000..ddaeda84938 --- /dev/null +++ b/reactos/base/applications/cmdutils/taskkill/taskkill.rc @@ -0,0 +1,41 @@ +/* + * Task termination utility + * + * 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 "taskkill.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ + STRING_USAGE, "Usage: taskkill [/?] [/f] [/im ProcessName | /pid ProcessID]\n" + STRING_INVALID_OPTION, "Error: Unknown or invalid command line option specified.\n" + STRING_INVALID_PARAM, "Error: Invalid command line parameter specified.\n" + STRING_MISSING_OPTION, "Error: One of options /im or /pid must be specified.\n" + STRING_MISSING_PARAM, "Error: Option %1 expects a command line parameter.\n" + STRING_MUTUAL_EXCLUSIVE, "Error: Options /im and /pid are mutually exclusive.\n" + STRING_CLOSE_PID_SEARCH, "Close message sent to top-level windows of process with PID %1!u!.\n" + STRING_CLOSE_PROC_SRCH, "Close message sent to top-level windows of process ""%1"" with PID %2!u!.\n" + STRING_TERM_PID_SEARCH, "Process with PID %1!u! was forcibly terminated.\n" + STRING_TERM_PROC_SEARCH, "Process ""%1"" with PID %2!u! was forcibly terminated.\n" + STRING_SEARCH_FAILED, "Error: Could not find process ""%1"".\n" + STRING_ENUM_FAILED, "Error: Unable to enumerate the process list.\n" + STRING_TERMINATE_FAILED, "Error: Unable to terminate process ""%1"".\n" + STRING_SELF_TERMINATION, "Error: Process self-termination is not permitted.\n" +}