Company of Heroes: Battle of Crete needs a functioning tasklist.exe to exit properly.
-- v4: 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 | 186 +++++++++++++++++- 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, 367 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..687b8bf6985 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,197 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include <windows.h> +#include <psapi.h> +#include <tlhelp32.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 PROCESSENTRY32W *enumerate_processes(DWORD *process_count) +{ + unsigned int alloc_count = 128; + PROCESSENTRY32W *process_list; + void *realloc_list; + HANDLE snapshot; + + *process_count = 0; + + snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) + return NULL; + + process_list = malloc(alloc_count * sizeof(*process_list)); + if (!process_list) + { + CloseHandle(snapshot); + return NULL; + } + + process_list[0].dwSize = sizeof(*process_list); + if (!Process32FirstW(snapshot, &process_list[0])) + { + CloseHandle(snapshot); + free(process_list); + return NULL; + } + + do + { + (*process_count)++; + if (*process_count == alloc_count) + { + alloc_count *= 2; + realloc_list = realloc(process_list, alloc_count * sizeof(*process_list)); + if (!realloc_list) + { + CloseHandle(snapshot); + free(process_list); + return NULL; + } + process_list = realloc_list; + } + process_list[*process_count].dwSize = sizeof(*process_list); + } while (Process32NextW(snapshot, &process_list[*process_count])); + CloseHandle(snapshot); + return process_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(const PROCESSENTRY32W *process_entry, struct tasklist_process_info *info) +{ + PROCESS_MEMORY_COUNTERS memory_counters; + DWORD session_id; + WCHAR buffer[16]; + HANDLE process; + + memset(info, 0, sizeof(*info)); + + if (!ProcessIdToSessionId(process_entry->th32ProcessID, &session_id)) + { + WINE_FIXME("Failed to get process session id, %lu.\n", GetLastError()); + return FALSE; + } + + process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_entry->th32ProcessID); + if (process && 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))) + { + LoadStringW(GetModuleHandleW(NULL), STRING_K, buffer, ARRAY_SIZE(buffer)); + wcscat(info->memory_usage, L" "); + wcscat(info->memory_usage, buffer); + } + } + if (process) + CloseHandle(process); + if (info->memory_usage[0] == '\0') + wcscpy(info->memory_usage, L"N/A"); + + wcscpy(info->image_name, process_entry->szExeFile); + swprintf(info->pid, ARRAY_SIZE(info->pid), L"%u", process_entry->th32ProcessID); + wcscpy(info->session_name, session_id == 0 ? L"Services" : L"Console"); + swprintf(info->session_number, ARRAY_SIZE(info->session_number), L"%u", session_id); + return TRUE; +} + +static void tasklist_print(void) +{ + struct tasklist_process_info header, info; + PROCESSENTRY32W *process_list; + DWORD process_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); + + process_list = enumerate_processes(&process_count); + for (i = 0; i < process_count; ++i) + { + if (!tasklist_get_process_info(&process_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(process_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 687b8bf6985..48d9b438e84 100644 --- a/programs/tasklist/tasklist.c +++ b/programs/tasklist/tasklist.c @@ -163,17 +163,21 @@ static BOOL tasklist_get_process_info(const PROCESSENTRY32W *process_entry, stru return TRUE; }
-static void tasklist_print(void) +static void tasklist_print(const struct tasklist_options *options) { struct tasklist_process_info header, info; PROCESSENTRY32W *process_list; DWORD process_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); + }
process_list = enumerate_processes(&process_count); for (i = 0; i < process_count; ++i) @@ -189,6 +193,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++) @@ -202,12 +207,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 48d9b438e84..c8cb8804c3a 100644 --- a/programs/tasklist/tasklist.c +++ b/programs/tasklist/tasklist.c @@ -33,6 +33,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 PROCESSENTRY32W *enumerate_processes(DWORD *process_count) { unsigned int alloc_count = 128; @@ -112,7 +120,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;
@@ -122,6 +131,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(const PROCESSENTRY32W *process_entry, struct tasklist_process_info *info) @@ -169,14 +186,19 @@ static void tasklist_print(const struct tasklist_options *options) PROCESSENTRY32W *process_list; DWORD process_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); }
process_list = enumerate_processes(&process_count); @@ -185,8 +207,24 @@ static void tasklist_print(const struct tasklist_options *options) if (!tasklist_get_process_info(&process_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(process_list); } @@ -211,6 +249,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 c8cb8804c3a..4f866f6b727 100644 --- a/programs/tasklist/tasklist.c +++ b/programs/tasklist/tasklist.c @@ -173,6 +173,9 @@ static BOOL tasklist_get_process_info(const PROCESSENTRY32W *process_entry, stru if (info->memory_usage[0] == '\0') wcscpy(info->memory_usage, L"N/A");
+ info->pid_value = process_entry->th32ProcessID; + info->memory_usage_value = memory_counters.WorkingSetSize / 1024; + info->session_id_value = session_id; wcscpy(info->image_name, process_entry->szExeFile); swprintf(info->pid, ARRAY_SIZE(info->pid), L"%u", process_entry->th32ProcessID); wcscpy(info->session_name, session_id == 0 ? L"Services" : L"Console"); @@ -180,6 +183,67 @@ static BOOL tasklist_get_process_info(const PROCESSENTRY32W *process_entry, stru return TRUE; }
+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; @@ -207,6 +271,9 @@ static void tasklist_print(const struct tasklist_options *options) if (!tasklist_get_process_info(&process_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); @@ -232,7 +299,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])); @@ -243,7 +312,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")) { @@ -254,7 +323,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")) { @@ -271,9 +341,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])); @@ -281,5 +435,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(); }
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=132135
Your paranoid android.
=== debian11 (32 bit report) ===
ws2_32: protocol.c:2476: Test succeeded inside todo block: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 0: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 1: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 2: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 3: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 4: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 5: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 12: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 13: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 14: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 15: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 16: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 28: IPv6 address is returned. protocol.c:2494: Test succeeded inside todo block: Test 29: IPv6 address is returned.