Company of Heroes: Battle of Crete needs a functioning findstr.exe to exit properly. Freemake Video Converter 4.1 installer also needs this.
From: Zhiyi Zhang zzhang@codeweavers.com
Company of Heroes: Battle of Crete needs a functioning findstr.exe to exit properly. Freemake Video Converter 4.1 installer also needs this.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=35254 --- configure | 1 + configure.ac | 1 + programs/findstr/Makefile.in | 3 + programs/findstr/findstr.h | 38 +++++ programs/findstr/findstr.rc | 31 ++++ programs/findstr/main.c | 251 ++++++++++++++++++++++++++++- programs/findstr/tests/Makefile.in | 4 + programs/findstr/tests/findstr.c | 208 ++++++++++++++++++++++++ 8 files changed, 532 insertions(+), 5 deletions(-) create mode 100644 programs/findstr/findstr.h create mode 100644 programs/findstr/findstr.rc create mode 100644 programs/findstr/tests/Makefile.in create mode 100644 programs/findstr/tests/findstr.c
diff --git a/configure b/configure index 836437fb89a..245a0d98d81 100755 --- a/configure +++ b/configure @@ -22045,6 +22045,7 @@ wine_fn_config_makefile programs/fc enable_fc wine_fn_config_makefile programs/find enable_find wine_fn_config_makefile programs/find/tests enable_tests wine_fn_config_makefile programs/findstr enable_findstr +wine_fn_config_makefile programs/findstr/tests enable_tests wine_fn_config_makefile programs/fsutil enable_fsutil wine_fn_config_makefile programs/fsutil/tests enable_tests wine_fn_config_makefile programs/hh enable_hh diff --git a/configure.ac b/configure.ac index fde3f29c770..342a91aff96 100644 --- a/configure.ac +++ b/configure.ac @@ -3330,6 +3330,7 @@ WINE_CONFIG_MAKEFILE(programs/fc) WINE_CONFIG_MAKEFILE(programs/find) WINE_CONFIG_MAKEFILE(programs/find/tests) WINE_CONFIG_MAKEFILE(programs/findstr) +WINE_CONFIG_MAKEFILE(programs/findstr/tests) WINE_CONFIG_MAKEFILE(programs/fsutil) WINE_CONFIG_MAKEFILE(programs/fsutil/tests) WINE_CONFIG_MAKEFILE(programs/hh) diff --git a/programs/findstr/Makefile.in b/programs/findstr/Makefile.in index e97ec9c20e3..d3f8f728854 100644 --- a/programs/findstr/Makefile.in +++ b/programs/findstr/Makefile.in @@ -1,6 +1,9 @@ MODULE = findstr.exe +IMPORTS = user32
EXTRADLLFLAGS = -mconsole -municode
C_SRCS = \ main.c + +RC_SRCS = findstr.rc diff --git a/programs/findstr/findstr.h b/programs/findstr/findstr.h new file mode 100644 index 00000000000..bb78fda7f06 --- /dev/null +++ b/programs/findstr/findstr.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_BAD_COMMAND_LINE 102 +#define STRING_CANNOT_OPEN 103 +#define STRING_IGNORED 104 + +struct findstr_string +{ + const WCHAR *string; + struct findstr_string *next; +}; + +struct findstr_file +{ + FILE *file; + struct findstr_file *next; +}; diff --git a/programs/findstr/findstr.rc b/programs/findstr/findstr.rc new file mode 100644 index 00000000000..e92981e3bad --- /dev/null +++ b/programs/findstr/findstr.rc @@ -0,0 +1,31 @@ +/* + * 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 "findstr.h" + +#pragma makedep po + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ + STRING_USAGE, "Usage: findstr /options string\r\n" + STRING_BAD_COMMAND_LINE, "FINDSTR: Bad command line\r\n" + STRING_CANNOT_OPEN, "FINDSTR: Cannot open %s\r\n" + STRING_IGNORED, "FINDSTR: /%c ignored\r\n" +} diff --git a/programs/findstr/main.c b/programs/findstr/main.c index d25e1965f6e..67378739c96 100644 --- a/programs/findstr/main.c +++ b/programs/findstr/main.c @@ -1,5 +1,6 @@ /* * Copyright 2012 Qian Hong + * 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,258 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include <windows.h> +#include <stdio.h> +#include "findstr.h" #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(findstr);
+static int findstr_vprintfW(HANDLE handle, const WCHAR *msg, va_list va_args) +{ + WCHAR msg_buffer[MAXSTRING]; + DWORD count; + int len_w; + + len_w = vswprintf(msg_buffer, ARRAY_SIZE(msg_buffer), msg, va_args); + + if (!WriteConsoleW(handle, msg_buffer, len_w, &count, NULL)) + { + char *msg_a; + DWORD len_a; + + /* On Windows WriteConsoleW() fails if the output is redirected. So fall back to WriteFile() + * with OEM code page. */ + len_a = WideCharToMultiByte(GetOEMCP(), 0, msg_buffer, len_w, NULL, 0, NULL, NULL); + msg_a = malloc(len_a); + if (!msg_a) + return 0; + + WideCharToMultiByte(GetOEMCP(), 0, msg_buffer, len_w, msg_a, len_a, NULL, NULL); + WriteFile(handle, msg_a, len_a, &count, FALSE); + free(msg_a); + } + + return count; +} + +static int WINAPIV findstr_printfW(HANDLE handle, const WCHAR *msg, ...) +{ + va_list va_args; + int len; + + va_start(va_args, msg); + len = findstr_vprintfW(handle, msg, va_args); + va_end(va_args); + + return len; +} + +static int WINAPIV findstr_error_printfW(int msg, ...) +{ + WCHAR msg_buffer[MAXSTRING]; + va_list va_args; + int len; + + LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); + va_start(va_args, msg); + len = findstr_vprintfW(GetStdHandle(STD_ERROR_HANDLE), msg_buffer, va_args); + va_end(va_args); + + return len; +} + +static int findstr_message(int msg) +{ + WCHAR msg_buffer[MAXSTRING]; + + LoadStringW(GetModuleHandleW(NULL), msg, msg_buffer, ARRAY_SIZE(msg_buffer)); + return findstr_printfW(GetStdHandle(STD_OUTPUT_HANDLE), msg_buffer); +} + +static BOOL add_file(struct findstr_file **head, const WCHAR *path) +{ + struct findstr_file **ptr, *new_file; + + ptr = head; + while (*ptr) + ptr = &((*ptr)->next); + + new_file = calloc(1, sizeof(*new_file)); + if (!new_file) + { + WINE_ERR("Out of memory.\n"); + return FALSE; + } + + if (!path) + { + new_file->file = stdin; + } + else + { + new_file->file = _wfopen(path, L"rt,ccs=unicode"); + if (!new_file->file) + { + findstr_error_printfW(STRING_CANNOT_OPEN, path); + return FALSE; + } + } + + *ptr = new_file; + return TRUE; +} + +static void add_string(struct findstr_string **head, const WCHAR *string) +{ + struct findstr_string **ptr, *new_string; + + ptr = head; + while (*ptr) + ptr = &((*ptr)->next); + + new_string = calloc(1, sizeof(*new_string)); + if (!new_string) + { + WINE_ERR("Out of memory.\n"); + return; + } + + new_string->string = string; + *ptr = new_string; +} + int __cdecl wmain(int argc, WCHAR *argv[]) { - int i; + struct findstr_string *string_head = NULL, *current_string, *next_string; + struct findstr_file *file_head = NULL, *current_file, *next_file; + WCHAR *string, *ptr, *buffer, line[MAXSTRING]; + BOOL has_string = FALSE, has_file = FALSE; + int ret = 1, i, j;
- 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"); + + if (argc == 1) + { + findstr_error_printfW(STRING_BAD_COMMAND_LINE); + return 2; + } + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '/') + { + if (argv[i][1] == '\0') + { + findstr_error_printfW(STRING_BAD_COMMAND_LINE); + return 2; + } + + j = 1; + while (argv[i][j] != '\0') + { + switch(argv[i][j]) + { + case '?': + findstr_message(STRING_USAGE); + ret = 0; + goto done; + case 'C': + case 'c': + if (argv[i][j + 1] == ':') + { + ptr = argv[i] + j + 2; + if (*ptr == '"') + ptr++; + + string = ptr; + while (*ptr != '"' && *ptr != '\0' ) + ptr++; + *ptr = '\0'; + j = ptr - argv[i] - 1; + add_string(&string_head, string); + has_string = TRUE; + } + break; + default: + findstr_error_printfW(STRING_IGNORED, argv[i][j]); + break; + } + + j++; + } + } + else if (!has_string) + { + string = wcstok(argv[i], L" ", &buffer); + if (string) + { + add_string(&string_head, string); + has_string = TRUE; + } + while ((string = wcstok(NULL, L" ", &buffer))) + add_string(&string_head, string); + } + else + { + if (!add_file(&file_head, argv[i])) + goto done; + has_file = TRUE; + } + } + + if (!has_string) + { + findstr_error_printfW(STRING_BAD_COMMAND_LINE); + ret = 2; + goto done; + } + + if (!has_file) + add_file(&file_head, NULL); + + current_file = file_head; + while (current_file) + { + while (fgetws(line, ARRAY_SIZE(line), current_file->file)) + { + current_string = string_head; + while (current_string) + { + if (wcsstr(line, current_string->string)) + { + findstr_printfW(GetStdHandle(STD_OUTPUT_HANDLE), line); + if (current_file->file == stdin) + findstr_printfW(GetStdHandle(STD_OUTPUT_HANDLE), L"\r\n"); + ret = 0; + } + + current_string = current_string->next; + } + } + + current_file = current_file->next; + } + +done: + current_file = file_head; + while (current_file) + { + next_file = current_file->next; + if (current_file->file != stdin) + fclose(current_file->file); + free(current_file); + current_file = next_file; + }
- return 0; + current_string = string_head; + while (current_string) + { + next_string = current_string->next; + free(current_string); + current_string = next_string; + } + return ret; } diff --git a/programs/findstr/tests/Makefile.in b/programs/findstr/tests/Makefile.in new file mode 100644 index 00000000000..b900619c10b --- /dev/null +++ b/programs/findstr/tests/Makefile.in @@ -0,0 +1,4 @@ +TESTDLL = findstr.exe + +C_SRCS = \ + findstr.c diff --git a/programs/findstr/tests/findstr.c b/programs/findstr/tests/findstr.c new file mode 100644 index 00000000000..1f565210e79 --- /dev/null +++ b/programs/findstr/tests/findstr.c @@ -0,0 +1,208 @@ +/* + * 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; + } +} + +static void write_to_handle(HANDLE handle, const char *str, int len) +{ + DWORD dummy; + + WriteFile(handle, str, len, &dummy, NULL); +} + +#define run_find_stdin(a, b, c) _run_find_stdin(__FILE__, __LINE__, a, b, c) +static void _run_find_stdin(const char *file, int line, const char *commandline, const char *input, + int exitcode_expected) +{ + HANDLE parent_stdin_write, parent_stdout_read, parent_stderr_read; + HANDLE child_stdin_read, child_stdout_write, child_stderr_write; + SECURITY_ATTRIBUTES security_attributes = {0}; + PROCESS_INFORMATION process_info = {0}; + STARTUPINFOA startup_info = {0}; + char cmd[4096]; + DWORD exitcode; + + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = TRUE; + + CreatePipe(&child_stdin_read, &parent_stdin_write, &security_attributes, 0); + CreatePipe(&parent_stdout_read, &child_stdout_write, &security_attributes, 0); + CreatePipe(&parent_stderr_read, &child_stderr_write, &security_attributes, 0); + + SetHandleInformation(parent_stdin_write, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(parent_stdout_read, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(parent_stderr_read, HANDLE_FLAG_INHERIT, 0); + + startup_info.cb = sizeof(STARTUPINFOA); + startup_info.hStdInput = child_stdin_read; + startup_info.hStdOutput = child_stdout_write; + startup_info.hStdError = child_stderr_write; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + + sprintf(cmd, "findstr.exe %s", commandline); + + CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info); + CloseHandle(child_stdin_read); + CloseHandle(child_stdout_write); + CloseHandle(child_stderr_write); + + write_to_handle(parent_stdin_write, input, lstrlenA(input)); + CloseHandle(parent_stdin_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); +} + +#define run_find_file(a, b, c) _run_find_file(__FILE__, __LINE__, a, b, c) +static void _run_find_file(const char *file, int line, const char *commandline, const char *input, + int exitcode_expected) +{ + char path_temp_file[MAX_PATH], path_temp_dir[MAX_PATH], commandline_new[MAX_PATH]; + HANDLE handle_file; + + GetTempPathA(ARRAY_SIZE(path_temp_dir), path_temp_dir); + GetTempFileNameA(path_temp_dir, "", 0, path_temp_file); + handle_file = CreateFileA(path_temp_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + write_to_handle(handle_file, input, lstrlenA(input)); + CloseHandle(handle_file); + + sprintf(commandline_new, "%s %s", commandline, path_temp_file); + _run_find_stdin(file, line, commandline_new, "", exitcode_expected); + + DeleteFileA(path_temp_file); +} + +static void test_basic(void) +{ + int ret; + + /* No options */ + run_find_stdin("", "", 2); + ok(stdout_size == 0, "Unexpected stdout buffer size %ld.\n", stdout_size); + ok(stderr_size > 0, "Unexpected stderr buffer size %ld.\n", stderr_size); + ret = strcmp(stderr_buffer, "FINDSTR: Bad command line\r\n"); + ok(!ret, "Got the wrong result.\n"); + + /* /? */ + run_find_stdin("/?", "", 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); + + /* find string in stdin */ + run_find_stdin("abc", "abc", 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); + ret = strcmp(stdout_buffer, "abc\r\n"); + ok(!ret, "Got the wrong result.\n"); + + /* find string in stdin fails */ + run_find_stdin("abc", "cba", 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); + + /* find string in file */ + run_find_file("abc", "abc", 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); + ret = strcmp(stdout_buffer, "abc"); + ok(!ret, "Got the wrong result.\n"); + + /* find string in file fails */ + run_find_file("abc", "cba", 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); + + /* find string in stdin with space separator */ + run_find_stdin(""abc cba"", "abc", 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); + ret = strcmp(stdout_buffer, "abc\r\n"); + ok(!ret, "Got the wrong result.\n"); + + /* find string in stdin with /C: */ + run_find_stdin("/C:abc", "abc", 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); + ret = strcmp(stdout_buffer, "abc\r\n"); + ok(!ret, "Got the wrong result.\n"); + + /* find string in stdin with /C:"abc" */ + run_find_stdin("/C:"abc"", "abc", 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); + ret = strcmp(stdout_buffer, "abc\r\n"); + ok(!ret, "Got the wrong result.\n"); + + /* find string in stdin with /C:"abc cba" fails */ + run_find_stdin("/C:"abc cba"", "abc", 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); + + /* find string in stdin with /C: fails */ + run_find_stdin("/C:abc", "cba", 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); +} + +START_TEST(findstr) +{ + if (PRIMARYLANGID(GetUserDefaultUILanguage()) != LANG_ENGLISH) + { + skip("Tests only work with English locale.\n"); + return; + } + + test_basic(); +}
Just a nitpick, but why not use the CRT functions for output (instead of WriteConsole+WriteFile bits). Lots of .exe in programs/ use that construct because they've been written at a time where using msvcrt was a pain. This is no longer the case.
On Fri Apr 21 07:36:11 2023 +0000, eric pouech wrote:
Just a nitpick, but why not use the CRT functions for output (instead of WriteConsole+WriteFile bits). Lots of .exe in programs/ use that construct because they've been written at a time where using msvcrt was a pain. This is no longer the case.
I see. I wondered about that too and decided to copy-paste the helper.