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(); +}