Company of Heroes: Battle of Crete needs a functioning tasklist.exe to exit properly.
-- v2: tasklist: Partially support '\fi' option. tasklist: Support '\fo' option. tasklist: Support '\nh' option. tasklist: Add basic functionality.
From: Zhiyi Zhang zzhang@codeweavers.com
Company of Heroes: Battle of Crete needs a functioning tasklist.exe to exit properly. --- configure | 1 + configure.ac | 1 + programs/tasklist/Makefile.in | 3 +- programs/tasklist/tasklist.c | 192 +++++++++++++++++- programs/tasklist/tasklist.h | 38 ++++ programs/tasklist/{version.rc => tasklist.rc} | 18 ++ programs/tasklist/tests/Makefile.in | 4 + programs/tasklist/tests/tasklist.c | 120 +++++++++++ 8 files changed, 373 insertions(+), 4 deletions(-) create mode 100644 programs/tasklist/tasklist.h rename programs/tasklist/{version.rc => tasklist.rc} (69%) create mode 100644 programs/tasklist/tests/Makefile.in create mode 100644 programs/tasklist/tests/tasklist.c
diff --git a/configure b/configure index 836437fb89a..b2a5021e033 100755 --- a/configure +++ b/configure @@ -22100,6 +22100,7 @@ wine_fn_config_makefile programs/svchost enable_svchost wine_fn_config_makefile programs/systeminfo enable_systeminfo wine_fn_config_makefile programs/taskkill enable_taskkill wine_fn_config_makefile programs/tasklist enable_tasklist +wine_fn_config_makefile programs/tasklist/tests enable_tests wine_fn_config_makefile programs/taskmgr enable_taskmgr wine_fn_config_makefile programs/termsv enable_termsv wine_fn_config_makefile programs/uninstaller enable_uninstaller diff --git a/configure.ac b/configure.ac index fde3f29c770..e7fcf4c0479 100644 --- a/configure.ac +++ b/configure.ac @@ -3385,6 +3385,7 @@ WINE_CONFIG_MAKEFILE(programs/svchost) WINE_CONFIG_MAKEFILE(programs/systeminfo) WINE_CONFIG_MAKEFILE(programs/taskkill) WINE_CONFIG_MAKEFILE(programs/tasklist) +WINE_CONFIG_MAKEFILE(programs/tasklist/tests) WINE_CONFIG_MAKEFILE(programs/taskmgr) WINE_CONFIG_MAKEFILE(programs/termsv) WINE_CONFIG_MAKEFILE(programs/uninstaller) diff --git a/programs/tasklist/Makefile.in b/programs/tasklist/Makefile.in index 2163ca5ce76..8b1c206fe0e 100644 --- a/programs/tasklist/Makefile.in +++ b/programs/tasklist/Makefile.in @@ -1,7 +1,8 @@ MODULE = tasklist.exe +IMPORTS = user32
EXTRADLLFLAGS = -mconsole -municode
C_SRCS = tasklist.c
-RC_SRCS = version.rc +RC_SRCS = tasklist.rc diff --git a/programs/tasklist/tasklist.c b/programs/tasklist/tasklist.c index fd03f50dae6..6d52e721201 100644 --- a/programs/tasklist/tasklist.c +++ b/programs/tasklist/tasklist.c @@ -1,5 +1,6 @@ /* * Copyright 2013 Austin English + * Copyright 2023 Zhiyi Zhang for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,18 +17,203 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include <windows.h> +#include <psapi.h> +#include "tasklist.h" #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(tasklist);
+static int tasklist_message(int msg) +{ + WCHAR msg_buffer[MAXSTRING]; + + LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); + return wprintf(msg_buffer); +} + +static DWORD *enumerate_processes(DWORD *list_count) +{ + DWORD *pid_list, *realloc_list, alloc_bytes, needed_bytes; + + *list_count = 0; + + alloc_bytes = 1024 * sizeof(*pid_list); + pid_list = malloc(alloc_bytes); + if (!pid_list) + return NULL; + + for (;;) + { + if (!EnumProcesses(pid_list, alloc_bytes, &needed_bytes)) + { + free(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 (needed_bytes != alloc_bytes) + break; + + alloc_bytes *= 2; + realloc_list = realloc(pid_list, alloc_bytes); + if (!realloc_list) + { + free(pid_list); + return NULL; + } + pid_list = realloc_list; + } + + *list_count = needed_bytes / sizeof(*pid_list); + return pid_list; +} + +static NUMBERFMTW *tasklist_get_memory_format(void) +{ + static NUMBERFMTW format = {0, 0, 0, NULL, NULL, 1}; + static WCHAR grouping[3], decimal[2], thousand[2]; + static BOOL initialized; + + if (initialized) + return &format; + + if (!GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_ILZERO | LOCALE_RETURN_NUMBER, + (WCHAR *)&format.LeadingZero, 2)) + format.LeadingZero = 0; + + if (!GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping))) + format.Grouping = 3; + else + format.Grouping = (grouping[2] == '2' ? 32 : grouping[0] - '0'); + + if (!GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal))) + wcscpy(decimal, L"."); + + if (!GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thousand, ARRAY_SIZE(thousand))) + wcscpy(thousand, L","); + + format.lpDecimalSep = decimal; + format.lpThousandSep = thousand; + initialized = TRUE; + return &format; +} + +static void tasklist_get_header(struct tasklist_process_info *header) +{ + HMODULE module; + + module = GetModuleHandleW(NULL); + LoadStringW(module, STRING_IMAGE_NAME, header->image_name, ARRAY_SIZE(header->image_name)); + LoadStringW(module, STRING_PID, header->pid, ARRAY_SIZE(header->pid)); + LoadStringW(module, STRING_SESSION_NAME, header->session_name, ARRAY_SIZE(header->session_name)); + LoadStringW(module, STRING_SESSION_NUMBER, header->session_number, ARRAY_SIZE(header->session_number)); + LoadStringW(module, STRING_MEM_USAGE, header->memory_usage, ARRAY_SIZE(header->memory_usage)); +} + +static BOOL tasklist_get_process_info(DWORD pid, struct tasklist_process_info *info) +{ + PROCESS_MEMORY_COUNTERS memory_counters; + BOOL ret = FALSE; + DWORD session_id; + WCHAR buffer[16]; + HANDLE process; + + memset(info, 0, sizeof(*info)); + process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (!process) + { + WINE_FIXME("Failed to open process, %lu.\n", GetLastError()); + return FALSE; + } + + if (!GetModuleBaseNameW(process, NULL, info->image_name, ARRAY_SIZE(info->image_name) - 1)) + { + WINE_FIXME("Failed to get process module base name, %lu.\n", GetLastError()); + goto done; + } + + if (!ProcessIdToSessionId(pid, &session_id)) + { + WINE_FIXME("Failed to get process session id, %lu.\n", GetLastError()); + goto done; + } + + if (GetProcessMemoryInfo(process, &memory_counters, sizeof(memory_counters))) + { + swprintf(buffer, ARRAY_SIZE(buffer), L"%u", memory_counters.WorkingSetSize / 1024); + if (!GetNumberFormatW(LOCALE_USER_DEFAULT, 0, buffer, tasklist_get_memory_format(), + info->memory_usage, ARRAY_SIZE(info->memory_usage))) + { + WINE_FIXME("Failed to format memory usage number, %lu.\n", GetLastError()); + goto done; + } + + LoadStringW(GetModuleHandleW(NULL), STRING_K, buffer, ARRAY_SIZE(buffer)); + wcscat(info->memory_usage, L" "); + wcscat(info->memory_usage, buffer); + } + else + { + wcscpy(info->memory_usage, L"N/A"); + } + + swprintf(info->pid, ARRAY_SIZE(info->pid), L"%u", pid); + wcscpy(info->session_name, session_id == 0 ? L"Services" : L"Console"); + swprintf(info->session_number, ARRAY_SIZE(info->session_number), L"%u", session_id); + ret = TRUE; + +done: + CloseHandle(process); + return ret; +} + +static void tasklist_print(void) +{ + struct tasklist_process_info header, info; + DWORD *pid_list, list_count, i; + + wprintf(L"\n"); + tasklist_get_header(&header); + wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12.12s\n" + L"========================= ======== ================ =========== ============\n", + header.image_name, header.pid, header.session_name, header.session_number, header.memory_usage); + + pid_list = enumerate_processes(&list_count); + for (i = 0; i < list_count; ++i) + { + if (!tasklist_get_process_info(pid_list[i], &info)) + continue; + + wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12s\n", + info.image_name, info.pid, info.session_name, info.session_number, info.memory_usage); + } + free(pid_list); +} + int __cdecl wmain(int argc, WCHAR *argv[]) { int i;
- WINE_FIXME("stub:"); for (i = 0; i < argc; i++) - WINE_FIXME(" %s", wine_dbgstr_w(argv[i])); - WINE_FIXME("\n"); + WINE_TRACE("%s ", wine_dbgstr_w(argv[i])); + WINE_TRACE("\n"); + + for (i = 1; i < argc; i++) + { + if (!wcscmp(argv[i], L"/?")) + { + tasklist_message(STRING_USAGE); + return 0; + } + else + { + WINE_WARN("Ignoring option %s\n", wine_dbgstr_w(argv[i])); + } + }
+ tasklist_print(); return 0; } diff --git a/programs/tasklist/tasklist.h b/programs/tasklist/tasklist.h new file mode 100644 index 00000000000..707121680ec --- /dev/null +++ b/programs/tasklist/tasklist.h @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Zhiyi Zhang for CodeWeavers + * + * 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 <windef.h> + +#define MAXSTRING 8192 + +#define STRING_USAGE 101 +#define STRING_IMAGE_NAME 102 +#define STRING_PID 103 +#define STRING_SESSION_NAME 104 +#define STRING_SESSION_NUMBER 105 +#define STRING_MEM_USAGE 106 +#define STRING_K 107 + +struct tasklist_process_info +{ + WCHAR image_name[32]; + WCHAR pid[32]; + WCHAR session_name[32]; + WCHAR session_number[32]; + WCHAR memory_usage[32]; +}; diff --git a/programs/tasklist/version.rc b/programs/tasklist/tasklist.rc similarity index 69% rename from programs/tasklist/version.rc rename to programs/tasklist/tasklist.rc index d6eecb55df2..5b5e72c4a67 100644 --- a/programs/tasklist/version.rc +++ b/programs/tasklist/tasklist.rc @@ -1,5 +1,6 @@ /* * Copyright 2013 Austin English + * Copyright 2023 Zhiyi Zhang for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,6 +17,23 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include "tasklist.h" + +#pragma makedep po + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ + STRING_USAGE, "Usage: tasklist [/?]\n" + STRING_IMAGE_NAME, "Image Name" + STRING_PID, "PID" + STRING_SESSION_NAME, "Session Name" + STRING_SESSION_NUMBER, "Session#" + STRING_MEM_USAGE, "Mem Usage" + STRING_K, "K" +} + #define WINE_FILEDESCRIPTION_STR "Wine tasklist" #define WINE_FILENAME_STR "tasklist.exe" #define WINE_FILEVERSION 6,1,7601,16385 diff --git a/programs/tasklist/tests/Makefile.in b/programs/tasklist/tests/Makefile.in new file mode 100644 index 00000000000..ac6a97d0853 --- /dev/null +++ b/programs/tasklist/tests/Makefile.in @@ -0,0 +1,4 @@ +TESTDLL = tasklist.exe + +C_SRCS = \ + tasklist.c diff --git a/programs/tasklist/tests/tasklist.c b/programs/tasklist/tests/tasklist.c new file mode 100644 index 00000000000..cd4a2be0178 --- /dev/null +++ b/programs/tasklist/tests/tasklist.c @@ -0,0 +1,120 @@ +/* + * Copyright 2023 Zhiyi Zhang for CodeWeavers + * + * 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 <windows.h> +#include "wine/test.h" + +#define MAX_BUFFER 65536 + +static char stdout_buffer[MAX_BUFFER], stderr_buffer[MAX_BUFFER]; +static DWORD stdout_size, stderr_size; + +static void read_all_from_handle(HANDLE handle, char *buffer, DWORD *size) +{ + char bytes[4096]; + DWORD bytes_read; + + memset(buffer, 0, MAX_BUFFER); + *size = 0; + for (;;) + { + BOOL success = ReadFile(handle, bytes, sizeof(bytes), &bytes_read, NULL); + if (!success || !bytes_read) + break; + if (*size + bytes_read > MAX_BUFFER) + { + ok(FALSE, "Insufficient buffer.\n"); + break; + } + memcpy(buffer + *size, bytes, bytes_read); + *size += bytes_read; + } +} + +#define run_tasklist(a, b) _run_tasklist(__FILE__, __LINE__, a, b) +static void _run_tasklist(const char *file, int line, const char *commandline, int exitcode_expected) +{ + HANDLE child_stdout_write, child_stderr_write, parent_stdout_read, parent_stderr_read; + SECURITY_ATTRIBUTES security_attributes = {0}; + PROCESS_INFORMATION process_info = {0}; + STARTUPINFOA startup_info = {0}; + DWORD exitcode; + char cmd[256]; + + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = TRUE; + + CreatePipe(&parent_stdout_read, &child_stdout_write, &security_attributes, 0); + CreatePipe(&parent_stderr_read, &child_stderr_write, &security_attributes, 0); + SetHandleInformation(parent_stdout_read, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(parent_stderr_read, HANDLE_FLAG_INHERIT, 0); + + startup_info.cb = sizeof(STARTUPINFOA); + startup_info.hStdOutput = child_stdout_write; + startup_info.hStdError = child_stderr_write; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + + sprintf(cmd, "tasklist.exe %s", commandline); + CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info); + CloseHandle(child_stdout_write); + CloseHandle(child_stderr_write); + + read_all_from_handle(parent_stdout_read, stdout_buffer, &stdout_size); + read_all_from_handle(parent_stderr_read, stderr_buffer, &stderr_size); + CloseHandle(parent_stdout_read); + CloseHandle(parent_stderr_read); + + WaitForSingleObject(process_info.hProcess, INFINITE); + GetExitCodeProcess(process_info.hProcess, &exitcode); + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + ok_(file, line)(exitcode == exitcode_expected, "Expected exitcode %d, got %ld\n", + exitcode_expected, exitcode); +} + +static void test_basic(void) +{ + char *pos; + + /* No options */ + run_tasklist("", 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, "\r\n" + "Image Name PID Session Name Session# Mem Usage\r\n" + "========================= ======== ================ =========== ============\r\n"); + ok(pos == stdout_buffer, "Got the wrong first line.\n"); + pos = strstr(stdout_buffer, "tasklist.exe"); + ok(!!pos, "Failed to list tasklist.exe.\n"); + + /* /? */ + run_tasklist("/?", 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); +} + +START_TEST(tasklist) +{ + if (PRIMARYLANGID(GetUserDefaultUILanguage()) != LANG_ENGLISH) + { + skip("Tests only work with English locale.\n"); + return; + } + + test_basic(); +}
From: Zhiyi Zhang zzhang@codeweavers.com
--- programs/tasklist/tasklist.c | 21 +++++++++++++++------ programs/tasklist/tasklist.h | 5 +++++ programs/tasklist/tests/tasklist.c | 15 +++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/programs/tasklist/tasklist.c b/programs/tasklist/tasklist.c index 6d52e721201..5cab53aa431 100644 --- a/programs/tasklist/tasklist.c +++ b/programs/tasklist/tasklist.c @@ -170,16 +170,20 @@ done: return ret; }
-static void tasklist_print(void) +static void tasklist_print(const struct tasklist_options *options) { struct tasklist_process_info header, info; DWORD *pid_list, list_count, i;
wprintf(L"\n"); - tasklist_get_header(&header); - wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12.12s\n" - L"========================= ======== ================ =========== ============\n", - header.image_name, header.pid, header.session_name, header.session_number, header.memory_usage); + + if (!options->no_header) + { + tasklist_get_header(&header); + wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12.12s\n" + L"========================= ======== ================ =========== ============\n", + header.image_name, header.pid, header.session_name, header.session_number, header.memory_usage); + }
pid_list = enumerate_processes(&list_count); for (i = 0; i < list_count; ++i) @@ -195,6 +199,7 @@ static void tasklist_print(void)
int __cdecl wmain(int argc, WCHAR *argv[]) { + struct tasklist_options options = {0}; int i;
for (i = 0; i < argc; i++) @@ -208,12 +213,16 @@ int __cdecl wmain(int argc, WCHAR *argv[]) tasklist_message(STRING_USAGE); return 0; } + else if (!wcsicmp(argv[i], L"/nh")) + { + options.no_header = TRUE; + } else { WINE_WARN("Ignoring option %s\n", wine_dbgstr_w(argv[i])); } }
- tasklist_print(); + tasklist_print(&options); return 0; } diff --git a/programs/tasklist/tasklist.h b/programs/tasklist/tasklist.h index 707121680ec..6add1f30277 100644 --- a/programs/tasklist/tasklist.h +++ b/programs/tasklist/tasklist.h @@ -36,3 +36,8 @@ struct tasklist_process_info WCHAR session_number[32]; WCHAR memory_usage[32]; }; + +struct tasklist_options +{ + BOOL no_header; +}; diff --git a/programs/tasklist/tests/tasklist.c b/programs/tasklist/tests/tasklist.c index cd4a2be0178..2c4d77855b0 100644 --- a/programs/tasklist/tests/tasklist.c +++ b/programs/tasklist/tests/tasklist.c @@ -108,6 +108,20 @@ static void test_basic(void) ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); }
+static void test_no_header(void) +{ + char *pos; + + /* /nh */ + run_tasklist("/nh", 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, "Image Name"); + ok(!pos, "Got header.\n"); + pos = strstr(stdout_buffer, "="); + ok(!pos, "Got header.\n"); +} + START_TEST(tasklist) { if (PRIMARYLANGID(GetUserDefaultUILanguage()) != LANG_ENGLISH) @@ -117,4 +131,5 @@ START_TEST(tasklist) }
test_basic(); + test_no_header(); }
From: Zhiyi Zhang zzhang@codeweavers.com
--- programs/tasklist/tasklist.c | 79 +++++++++++++++++++++++++++--- programs/tasklist/tasklist.h | 9 ++++ programs/tasklist/tasklist.rc | 1 + programs/tasklist/tests/tasklist.c | 43 ++++++++++++++++ 4 files changed, 124 insertions(+), 8 deletions(-)
diff --git a/programs/tasklist/tasklist.c b/programs/tasklist/tasklist.c index 5cab53aa431..dea72cf79f8 100644 --- a/programs/tasklist/tasklist.c +++ b/programs/tasklist/tasklist.c @@ -32,6 +32,14 @@ static int tasklist_message(int msg) return wprintf(msg_buffer); }
+static int tasklist_error(int msg) +{ + WCHAR msg_buffer[MAXSTRING]; + + LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); + return fwprintf(stderr, msg_buffer); +} + static DWORD *enumerate_processes(DWORD *list_count) { DWORD *pid_list, *realloc_list, alloc_bytes, needed_bytes; @@ -101,7 +109,8 @@ static NUMBERFMTW *tasklist_get_memory_format(void) return &format; }
-static void tasklist_get_header(struct tasklist_process_info *header) +static void tasklist_get_header(const struct tasklist_options *options, + struct tasklist_process_info *header) { HMODULE module;
@@ -111,6 +120,14 @@ static void tasklist_get_header(struct tasklist_process_info *header) LoadStringW(module, STRING_SESSION_NAME, header->session_name, ARRAY_SIZE(header->session_name)); LoadStringW(module, STRING_SESSION_NUMBER, header->session_number, ARRAY_SIZE(header->session_number)); LoadStringW(module, STRING_MEM_USAGE, header->memory_usage, ARRAY_SIZE(header->memory_usage)); + if (options->format == LIST) + { + wcscat(header->image_name, L":"); + wcscat(header->pid, L":"); + wcscat(header->session_name, L":"); + wcscat(header->session_number, L":"); + wcscat(header->memory_usage, L":"); + } }
static BOOL tasklist_get_process_info(DWORD pid, struct tasklist_process_info *info) @@ -175,14 +192,19 @@ static void tasklist_print(const struct tasklist_options *options) struct tasklist_process_info header, info; DWORD *pid_list, list_count, i;
- wprintf(L"\n"); + if (options->format == TABLE) + wprintf(L"\n");
+ tasklist_get_header(options, &header); if (!options->no_header) { - tasklist_get_header(&header); - wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12.12s\n" - L"========================= ======== ================ =========== ============\n", - header.image_name, header.pid, header.session_name, header.session_number, header.memory_usage); + if (options->format == TABLE) + wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12.12s\n" + L"========================= ======== ================ =========== ============\n", + header.image_name, header.pid, header.session_name, header.session_number, header.memory_usage); + else if (options->format == CSV) + wprintf(L""%s","%s","%s","%s","%s"\n", + header.image_name, header.pid, header.session_name, header.session_number, header.memory_usage); }
pid_list = enumerate_processes(&list_count); @@ -191,8 +213,24 @@ static void tasklist_print(const struct tasklist_options *options) if (!tasklist_get_process_info(pid_list[i], &info)) continue;
- wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12s\n", - info.image_name, info.pid, info.session_name, info.session_number, info.memory_usage); + if (options->format == TABLE) + wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12s\n", + info.image_name, info.pid, info.session_name, info.session_number, info.memory_usage); + else if (options->format == CSV) + wprintf(L""%s","%s","%s","%s","%s"\n", + info.image_name, info.pid, info.session_name, info.session_number, info.memory_usage); + else if (options->format == LIST) + wprintf(L"\n" + L"%-13.13s %s\n" + L"%-13.13s %s\n" + L"%-13.13s %s\n" + L"%-13.13s %s\n" + L"%-13.13s %s\n", + header.image_name, info.image_name, + header.pid, info.pid, + header.session_name, info.session_name, + header.session_number, info.session_number, + header.memory_usage, info.memory_usage); } free(pid_list); } @@ -217,6 +255,31 @@ int __cdecl wmain(int argc, WCHAR *argv[]) { options.no_header = TRUE; } + else if (!wcsicmp(argv[i], L"/fo")) + { + if (i + 1 >= argc) + { + tasklist_error(STRING_INVALID_SYNTAX); + return 1; + } + else if (!wcsicmp(argv[i + 1], L"TABLE")) + { + options.format = TABLE; + } + else if (!wcsicmp(argv[i + 1], L"CSV")) + { + options.format = CSV; + } + else if (!wcsicmp(argv[i + 1], L"LIST")) + { + options.format = LIST; + } + else + { + tasklist_error(STRING_INVALID_SYNTAX); + return 1; + } + } else { WINE_WARN("Ignoring option %s\n", wine_dbgstr_w(argv[i])); diff --git a/programs/tasklist/tasklist.h b/programs/tasklist/tasklist.h index 6add1f30277..5e4e90960ea 100644 --- a/programs/tasklist/tasklist.h +++ b/programs/tasklist/tasklist.h @@ -27,6 +27,14 @@ #define STRING_SESSION_NUMBER 105 #define STRING_MEM_USAGE 106 #define STRING_K 107 +#define STRING_INVALID_SYNTAX 108 + +enum tasklist_format +{ + TABLE = 0, + CSV = 1, + LIST = 2, +};
struct tasklist_process_info { @@ -40,4 +48,5 @@ struct tasklist_process_info struct tasklist_options { BOOL no_header; + enum tasklist_format format; }; diff --git a/programs/tasklist/tasklist.rc b/programs/tasklist/tasklist.rc index 5b5e72c4a67..f92b0348f72 100644 --- a/programs/tasklist/tasklist.rc +++ b/programs/tasklist/tasklist.rc @@ -32,6 +32,7 @@ STRINGTABLE STRING_SESSION_NUMBER, "Session#" STRING_MEM_USAGE, "Mem Usage" STRING_K, "K" + STRING_INVALID_SYNTAX, "ERROR: Invalid syntax\n" }
#define WINE_FILEDESCRIPTION_STR "Wine tasklist" diff --git a/programs/tasklist/tests/tasklist.c b/programs/tasklist/tests/tasklist.c index 2c4d77855b0..011bfdeb382 100644 --- a/programs/tasklist/tests/tasklist.c +++ b/programs/tasklist/tests/tasklist.c @@ -122,6 +122,48 @@ static void test_no_header(void) ok(!pos, "Got header.\n"); }
+static void test_format(void) +{ + char *pos; + + /* /fo */ + run_tasklist("/fo", 1); + ok(stdout_size == 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size > 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + + /* /fo invalid */ + run_tasklist("/fo invalid", 1); + ok(stdout_size == 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size > 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + + /* /fo TABLE */ + run_tasklist("/fo TABLE", 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, "\r\n" + "Image Name PID Session Name Session# Mem Usage\r\n" + "========================= ======== ================ =========== ============\r\n"); + ok(pos == stdout_buffer, "Got the wrong first line.\n"); + pos = strstr(stdout_buffer, "tasklist.exe"); + ok(!!pos, "Failed to list tasklist.exe.\n"); + + /* /fo CSV */ + run_tasklist("/fo CSV", 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, ""Image Name","PID","Session Name","Session#","Mem Usage""); + ok(pos == stdout_buffer, "Got the wrong first line.\n"); + pos = strstr(stdout_buffer, ""tasklist.exe","); + ok(!!pos, "Failed to list tasklist.exe.\n"); + + /* /fo LIST */ + run_tasklist("/fo LIST", 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, "Image Name: tasklist.exe"); + ok(!!pos, "Failed to list tasklist.exe.\n"); +} + START_TEST(tasklist) { if (PRIMARYLANGID(GetUserDefaultUILanguage()) != LANG_ENGLISH) @@ -132,4 +174,5 @@ START_TEST(tasklist)
test_basic(); test_no_header(); + test_format(); }
From: Zhiyi Zhang zzhang@codeweavers.com
Some filters such as STATUS and CPUTIME are not implemented.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=48596 --- programs/tasklist/tasklist.c | 173 ++++++++++++++++++++++++++++- programs/tasklist/tasklist.h | 32 ++++++ programs/tasklist/tasklist.rc | 1 + programs/tasklist/tests/tasklist.c | 104 +++++++++++++++++ 4 files changed, 305 insertions(+), 5 deletions(-)
diff --git a/programs/tasklist/tasklist.c b/programs/tasklist/tasklist.c index dea72cf79f8..94b4cd6efed 100644 --- a/programs/tasklist/tasklist.c +++ b/programs/tasklist/tasklist.c @@ -177,6 +177,9 @@ static BOOL tasklist_get_process_info(DWORD pid, struct tasklist_process_info *i wcscpy(info->memory_usage, L"N/A"); }
+ info->pid_value = pid; + info->memory_usage_value = memory_counters.WorkingSetSize / 1024; + info->session_id_value = session_id; swprintf(info->pid, ARRAY_SIZE(info->pid), L"%u", pid); wcscpy(info->session_name, session_id == 0 ? L"Services" : L"Console"); swprintf(info->session_number, ARRAY_SIZE(info->session_number), L"%u", session_id); @@ -187,6 +190,67 @@ done: return ret; }
+static BOOL tasklist_check_filters(const struct tasklist_filter *filter, + const struct tasklist_process_info *info) +{ + DWORD left_dword_operand, right_dword_operand; + const WCHAR *left_string_operand = NULL; + BOOL eval; + + while (filter) + { + left_string_operand = NULL; + left_dword_operand = 0; + eval = FALSE; + + if (filter->name == IMAGENAME) + left_string_operand = info->image_name; + else if (filter->name == SESSIONNAME) + left_string_operand = info->session_name; + else if (filter->name == PID) + left_dword_operand = info->pid_value; + else if (filter->name == SESSION) + left_dword_operand = info->session_id_value; + else if (filter->name == MEMUSAGE) + left_dword_operand = info->memory_usage_value; + + if (left_string_operand) + { + eval = wcsicmp(left_string_operand, filter->value); + if (filter->op == EQ) + eval = !eval; + } + else + { + if (swscanf(filter->value, L"%lu", &right_dword_operand) != 1) + { + WINE_ERR("Invalid filter operand %s.\n", wine_dbgstr_w(filter->value)); + return FALSE; + } + + if (filter->op == EQ) + eval = left_dword_operand == right_dword_operand; + else if (filter->op == NE) + eval = left_dword_operand != right_dword_operand; + else if (filter->op == GT) + eval = left_dword_operand > right_dword_operand; + else if (filter->op == LT) + eval = left_dword_operand < right_dword_operand; + else if (filter->op == GE) + eval = left_dword_operand >= right_dword_operand; + else if (filter->op == LE) + eval = left_dword_operand <= right_dword_operand; + } + + if (!eval) + return FALSE; + + filter = filter->next; + } + + return TRUE; +} + static void tasklist_print(const struct tasklist_options *options) { struct tasklist_process_info header, info; @@ -213,6 +277,9 @@ static void tasklist_print(const struct tasklist_options *options) if (!tasklist_get_process_info(pid_list[i], &info)) continue;
+ if (!tasklist_check_filters(options->filters, &info)) + continue; + if (options->format == TABLE) wprintf(L"%-25.25s %8.8s %-16.16s %11.11s %12s\n", info.image_name, info.pid, info.session_name, info.session_number, info.memory_usage); @@ -238,7 +305,9 @@ static void tasklist_print(const struct tasklist_options *options) int __cdecl wmain(int argc, WCHAR *argv[]) { struct tasklist_options options = {0}; - int i; + struct tasklist_filter *filter, *next, **filter_ptr = &options.filters; + WCHAR *filter_name, *filter_op, *buffer; + int i, ret = 0;
for (i = 0; i < argc; i++) WINE_TRACE("%s ", wine_dbgstr_w(argv[i])); @@ -249,7 +318,7 @@ int __cdecl wmain(int argc, WCHAR *argv[]) if (!wcscmp(argv[i], L"/?")) { tasklist_message(STRING_USAGE); - return 0; + goto done; } else if (!wcsicmp(argv[i], L"/nh")) { @@ -260,7 +329,8 @@ int __cdecl wmain(int argc, WCHAR *argv[]) if (i + 1 >= argc) { tasklist_error(STRING_INVALID_SYNTAX); - return 1; + ret = 1; + goto done; } else if (!wcsicmp(argv[i + 1], L"TABLE")) { @@ -277,9 +347,93 @@ int __cdecl wmain(int argc, WCHAR *argv[]) else { tasklist_error(STRING_INVALID_SYNTAX); - return 1; + ret = 1; + goto done; } } + else if (!wcsicmp(argv[i], L"/fi")) + { + if (i + 1 >= argc || !(filter_name = wcstok(argv[i + 1], L" ", &buffer))) + { + tasklist_error(STRING_INVALID_SYNTAX); + ret = 1; + goto done; + } + + filter = calloc(1, sizeof(*filter)); + if (!filter) + { + WINE_ERR("Out of memory.\n"); + ret = 1; + goto done; + } + + if (!wcsicmp(filter_name, L"IMAGENAME")) + filter->name = IMAGENAME; + else if (!wcsicmp(filter_name, L"PID")) + filter->name = PID; + else if (!wcsicmp(filter_name, L"SESSION")) + filter->name = SESSION; + else if (!wcsicmp(filter_name, L"SESSIONNAME")) + filter->name = SESSIONNAME; + else if (!wcsicmp(filter_name, L"MEMUSAGE")) + filter->name = MEMUSAGE; + else + { + WINE_WARN("Ignoring filter %s.\n", wine_dbgstr_w(filter_name)); + free(filter); + continue; + } + + filter_op = wcstok(NULL, L" ", &buffer); + if (!filter_op) + { + tasklist_error(STRING_FILTER_NOT_RECOGNIZED); + free(filter); + ret = 1; + goto done; + } + + if (!wcsicmp(filter_op, L"EQ")) + filter->op = EQ; + else if (!wcsicmp(filter_op, L"NE")) + filter->op = NE; + else if (!wcsicmp(filter_op, L"GT")) + filter->op = GT; + else if (!wcsicmp(filter_op, L"LT")) + filter->op = LT; + else if (!wcsicmp(filter_op, L"GE")) + filter->op = GE; + else if (!wcsicmp(filter_op, L"LE")) + filter->op = LE; + else + { + tasklist_error(STRING_FILTER_NOT_RECOGNIZED); + free(filter); + ret = 1; + goto done; + } + + if (filter->op >= GT && filter->name != PID && filter->name != SESSION && filter->name != MEMUSAGE) + { + tasklist_error(STRING_FILTER_NOT_RECOGNIZED); + free(filter); + ret = 1; + goto done; + } + + filter->value = wcstok(NULL, L" ", &buffer); + if (!filter->value) + { + tasklist_error(STRING_FILTER_NOT_RECOGNIZED); + free(filter); + ret = 1; + goto done; + } + + *filter_ptr = filter; + filter_ptr = &filter->next; + } else { WINE_WARN("Ignoring option %s\n", wine_dbgstr_w(argv[i])); @@ -287,5 +441,14 @@ int __cdecl wmain(int argc, WCHAR *argv[]) }
tasklist_print(&options); - return 0; + +done: + next = options.filters; + while (next) + { + filter = next->next; + free(next); + next = filter; + } + return ret; } diff --git a/programs/tasklist/tasklist.h b/programs/tasklist/tasklist.h index 5e4e90960ea..7b05224757f 100644 --- a/programs/tasklist/tasklist.h +++ b/programs/tasklist/tasklist.h @@ -28,6 +28,7 @@ #define STRING_MEM_USAGE 106 #define STRING_K 107 #define STRING_INVALID_SYNTAX 108 +#define STRING_FILTER_NOT_RECOGNIZED 109
enum tasklist_format { @@ -36,8 +37,38 @@ enum tasklist_format LIST = 2, };
+enum tasklist_filter_name +{ + IMAGENAME = 1, + PID = 2, + SESSION = 3, + SESSIONNAME = 4, + MEMUSAGE = 5, +}; + +enum tasklist_filter_operator +{ + EQ = 0, + NE = 1, + GT = 2, + LT = 3, + GE = 4, + LE = 5, +}; + +struct tasklist_filter +{ + enum tasklist_filter_name name; + enum tasklist_filter_operator op; + WCHAR *value; + struct tasklist_filter *next; +}; + struct tasklist_process_info { + DWORD pid_value; + DWORD memory_usage_value; + DWORD session_id_value; WCHAR image_name[32]; WCHAR pid[32]; WCHAR session_name[32]; @@ -49,4 +80,5 @@ struct tasklist_options { BOOL no_header; enum tasklist_format format; + struct tasklist_filter *filters; }; diff --git a/programs/tasklist/tasklist.rc b/programs/tasklist/tasklist.rc index f92b0348f72..b5aaccc5391 100644 --- a/programs/tasklist/tasklist.rc +++ b/programs/tasklist/tasklist.rc @@ -33,6 +33,7 @@ STRINGTABLE STRING_MEM_USAGE, "Mem Usage" STRING_K, "K" STRING_INVALID_SYNTAX, "ERROR: Invalid syntax\n" + STRING_FILTER_NOT_RECOGNIZED, "ERROR: The search filter cannot be recognized.\n" }
#define WINE_FILEDESCRIPTION_STR "Wine tasklist" diff --git a/programs/tasklist/tests/tasklist.c b/programs/tasklist/tests/tasklist.c index 011bfdeb382..28c15ed3984 100644 --- a/programs/tasklist/tests/tasklist.c +++ b/programs/tasklist/tests/tasklist.c @@ -17,6 +17,7 @@ */
#include <windows.h> +#include <psapi.h> #include "wine/test.h"
#define MAX_BUFFER 65536 @@ -164,6 +165,108 @@ static void test_format(void) ok(!!pos, "Failed to list tasklist.exe.\n"); }
+static void test_filter(void) +{ + char options[256], *pos, basename[64]; + HANDLE current_process; + DWORD current_pid; + + current_pid = GetCurrentProcessId(); + current_process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, current_pid); + GetModuleBaseNameA(current_process, NULL, basename, ARRAY_SIZE(basename)); + CloseHandle(current_process); + + /* /fi */ + /* no value for fi */ + run_tasklist("/fi", 1); + ok(stdout_size == 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size > 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + + /* IMAGENAME eq */ + sprintf(options, "/fi "IMAGENAME eq %s"", basename); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!!pos, "Failed to list %s.\n", basename); + + /* IMAGENAME ne */ + sprintf(options, "/fi "IMAGENAME ne %s"", basename); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!pos, "Got %s.\n", basename); + pos = strstr(stdout_buffer, "tasklist.exe"); + ok(!!pos, "Failed to list tasklist.exe.\n"); + + /* PID eq */ + sprintf(options, "/fi "PID eq %ld"", current_pid); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!!pos, "Failed to list %s.\n", basename); + + /* PID ne */ + sprintf(options, "/fi "PID ne %ld"", current_pid); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!pos, "Got %s.\n", basename); + pos = strstr(stdout_buffer, "tasklist.exe"); + ok(!!pos, "Failed to list tasklist.exe.\n"); + + /* PID gt */ + sprintf(options, "/fi "PID gt %ld"", current_pid - 1); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!!pos, "Failed to list %s.\n", basename); + + /* PID lt */ + sprintf(options, "/fi "PID lt %ld"", current_pid + 1); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!!pos, "Failed to list %s.\n", basename); + + /* PID ge */ + sprintf(options, "/fi "PID ge %ld"", current_pid); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!!pos, "Failed to list %s.\n", basename); + + /* PID le */ + sprintf(options, "/fi "PID le %ld"", current_pid); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!!pos, "Failed to list %s.\n", basename); + + /* IMAGENAME eq + PID eq */ + sprintf(options, "/fi "IMAGENAME eq %s" /fi "PID eq %ld"", basename, current_pid); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!!pos, "Failed to list %s.\n", basename); + + /* IMAGENAME eq + PID eq with wrong PID */ + sprintf(options, "/fi "IMAGENAME eq %s" /fi "PID eq %ld"", basename, current_pid + 1); + run_tasklist(options, 0); + ok(stdout_size > 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size == 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + pos = strstr(stdout_buffer, basename); + ok(!pos, "Got %s.\n", basename); +} + START_TEST(tasklist) { if (PRIMARYLANGID(GetUserDefaultUILanguage()) != LANG_ENGLISH) @@ -175,4 +278,5 @@ START_TEST(tasklist) test_basic(); test_no_header(); test_format(); + test_filter(); }
v2: Use CRT stdio functions. Fix test failures.