[PATCH v3 0/2] MR11542: cmd: Initialize console mode on start.
-- v3: cmd: Set console mode in node_execute(). cmd/tests: Add test for console mode change. https://gitlab.winehq.org/wine/wine/-/merge_requests/11542
From: Paul Gofman <pgofman@codeweavers.com> --- programs/cmd/tests/Makefile.in | 1 + programs/cmd/tests/console.c | 262 +++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 programs/cmd/tests/console.c diff --git a/programs/cmd/tests/Makefile.in b/programs/cmd/tests/Makefile.in index 7eca0f23176..c50f59699e8 100644 --- a/programs/cmd/tests/Makefile.in +++ b/programs/cmd/tests/Makefile.in @@ -2,5 +2,6 @@ TESTDLL = cmd.exe SOURCES = \ batch.c \ + console.c \ directory.c \ rsrc.rc diff --git a/programs/cmd/tests/console.c b/programs/cmd/tests/console.c new file mode 100644 index 00000000000..5a0d9a4b0ac --- /dev/null +++ b/programs/cmd/tests/console.c @@ -0,0 +1,262 @@ +/* + * Copyright 2026 Paul Gofman 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" + +static HANDLE con_out, orig_stderr; +static FILE *child_log_file; + +#define ok_child(cond, format, ...) do { \ + ok( cond, format, __VA_ARGS__ ); \ + if (!(cond)) fprintf( child_log_file, "%u:"format, __LINE__, __VA_ARGS__ ); \ + } while (0) + +static void test_console_mode_change_grandchild( const char *exec_type, const char *log_fn ) +{ + HANDLE con = GetStdHandle( STD_OUTPUT_HANDLE ); + DWORD mode; + BOOL bret; + + if (log_fn) + { + child_log_file = fopen( log_fn, "w" ); + ok( !!child_log_file, "got NULL.\n" ); + } + else + { + child_log_file = stderr; + } + + bret = GetConsoleMode( con, &mode ); + ok_child( bret, "got error %lu.\n", GetLastError() ); + if (exec_type && !strcmp( exec_type, "startcmd" )) + { + todo_wine ok_child( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, + "got %#lx.\n", mode ); + } + else + { + /* Looks like before Win10 the console mode is not restored for external command and + * it is either internal cmd default or ENABLE_WRAP_AT_EOL_OUTPUT wheb left by previous + * child invocation within the same command. */ + todo_wine_if( exec_type && !strcmp( exec_type, "todo" )) + ok_child( mode == ENABLE_PROCESSED_OUTPUT || + broken(mode == ENABLE_WRAP_AT_EOL_OUTPUT + || mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT)) /* before Win10 */, + "got %#lx.\n", mode ); + } + + bret = SetConsoleMode( con, ENABLE_WRAP_AT_EOL_OUTPUT ); + ok_child( bret, "got error %lu.\n", GetLastError() ); + fprintf( child_log_file, "test error count: %ld\n", winetest_get_failures() ); +} + +static void test_console_mode_change(int argc, char *argv[]) +{ + STARTUPINFOA si = { sizeof(si) }; + PROCESS_INFORMATION info; + unsigned int error_count; + DWORD old_mode, mode; + HANDLE con_out_dup; + char cmd[MAX_PATH]; + char s[1024]; + DWORD ret; + BOOL bret; + FILE *f; + + bret = GetConsoleMode( con_out, &old_mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + ok( old_mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) + || old_mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING), + "got %#lx.\n", old_mode ); + + bret = DuplicateHandle( GetCurrentProcess(), con_out, GetCurrentProcess(), &con_out_dup, 0, TRUE, DUPLICATE_SAME_ACCESS ); + ok( bret, "got error %lu.\n", GetLastError() ); + + SetConsoleMode( con_out, ENABLE_PROCESSED_OUTPUT ); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle( STD_INPUT_HANDLE ); + si.hStdError = orig_stderr; + si.hStdOutput = con_out_dup; + strcpy( cmd, "cmd.exe /c nonexistent" ); + bret = CreateProcessA( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &info ); + ok( bret, "got error %lu.\n", GetLastError() ); + ret = WaitForSingleObject( info.hProcess, 5000 ); + ok( ret == WAIT_OBJECT_0, "got %lu.\n", ret ); + CloseHandle( info.hProcess ); + CloseHandle( info.hThread ); + bret = GetConsoleMode( con_out, &mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + todo_wine + ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, + "got %#lx.\n", mode ); + + SetConsoleMode( con_out, ENABLE_PROCESSED_OUTPUT ); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle( STD_INPUT_HANDLE ); + si.hStdError = orig_stderr; + si.hStdOutput = con_out_dup; + strcpy( cmd, "cmd.exe /c cls" ); + bret = CreateProcessA( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &info ); + ok( bret, "got error %lu.\n", GetLastError() ); + ret = WaitForSingleObject( info.hProcess, 5000 ); + ok( ret == WAIT_OBJECT_0, "got %lu.\n", ret ); + CloseHandle( info.hProcess ); + CloseHandle( info.hThread ); + bret = GetConsoleMode( con_out, &mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + todo_wine ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, + "got %#lx.\n", mode ); + + bret = SetConsoleMode( con_out, ENABLE_PROCESSED_OUTPUT ); + ok( bret, "got error %lu.\n", GetLastError() ); + DeleteFileA( "grandchild.out" ); + sprintf( cmd, "cmd.exe /c %s %s grandchild 2>grandchild.out", argv[0], argv[1] ); + bret = CreateProcessA( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &info ); + ok( bret, "got error %lu.\n", GetLastError() ); + wait_child_process( &info ); + if ((f = fopen( "grandchild.out", "r" ))) + { + while (fgets( s, sizeof(s), f )) + trace("grandchild: %s", s); + fclose(f); + } + bret = DeleteFileA( "grandchild.out" ); + ok( bret, "got error %lu.\n", GetLastError() ); + bret = GetConsoleMode( con_out, &mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + todo_wine + ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, + "got %#lx.\n", mode ); + + bret = SetConsoleMode( con_out, ENABLE_PROCESSED_OUTPUT ); + ok( bret, "got error %lu.\n", GetLastError() ); + sprintf( cmd, "cmd.exe /c %s %s grandchild cmd grandchild.out", argv[0], argv[1] ); + bret = CreateProcessA( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &info ); + ok( bret, "got error %lu.\n", GetLastError() ); + wait_child_process( &info ); + if ((f = fopen( "grandchild.out", "r" ))) + { + while (fgets( s, sizeof(s), f )) + trace( "grandchild no redirect: %s", s ); + fclose( f ); + } + bret = DeleteFileA( "grandchild.out" ); + ok( bret, "got error %lu.\n", GetLastError() ); + bret = GetConsoleMode( con_out, &mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + todo_wine + ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, + "got %#lx.\n", mode ); + + bret = SetConsoleMode( con_out, ENABLE_PROCESSED_OUTPUT ); + ok( bret, "got error %lu.\n", GetLastError() ); + DeleteFileA( "grandchild2.out" ); + sprintf( cmd, "cmd.exe /c \"start /b /wait %s %s grandchild startcmd\" 2>>grandchild.out", argv[0], argv[1] ); + bret = CreateProcessA( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &info ); + ok( bret, "got error %lu.\n", GetLastError() ); + wait_child_process( &info ); + if ((f = fopen( "grandchild.out", "r" ))) + { + while (fgets( s, sizeof(s), f )) + { + trace("grandchild start: %s", s); + if (sscanf( s, "test error count: %u.\n", &error_count )) + ok( !error_count, "got %u errors in grandchild.\n", error_count); + } + fclose( f ); + } + DeleteFileA( "grandchild.out" ); + bret = GetConsoleMode( con_out, &mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + todo_wine + ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, + "got %#lx.\n", mode ); + + bret = SetConsoleMode( con_out, ENABLE_PROCESSED_OUTPUT ); + ok( bret, "got error %lu.\n", GetLastError() ); + DeleteFileA( "grandchild.out" ); + sprintf( cmd, "cmd.exe /c %s %s grandchild 2>>grandchild.out & %s %s grandchild todo 2>>grandchild.out", + argv[0], argv[1], argv[0], argv[1] ); + bret = CreateProcessA( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &info ); + ok( bret, "got error %lu.\n", GetLastError() ); + wait_child_process( &info ); + if ((f = fopen( "grandchild.out", "r" ))) + { + while (fgets(s, sizeof(s), f)) + trace( "grandchild &: %s", s ); + fclose( f ); + } + DeleteFileA( "grandchild.out" ); + bret = GetConsoleMode( con_out, &mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + todo_wine + ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, + "got %#lx.\n", mode ); + + bret = SetConsoleMode( con_out, ENABLE_PROCESSED_OUTPUT ); + ok( bret, "got error %lu.\n", GetLastError() ); + strcpy( cmd, "cmd.exe /c echo text\r\n" ); + bret = CreateProcessA( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &info ); + ok( bret, "got error %lu.\n", GetLastError() ); + wait_child_process( &info ); + bret = GetConsoleMode( con_out, &mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + todo_wine ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, + "got %#lx.\n", mode ); + + bret = SetConsoleMode( con_out, old_mode ); + ok( bret, "got error %lu.\n", GetLastError() ); + CloseHandle( con_out_dup ); +} + +START_TEST(console) +{ + char **argv; + int argc; + BOOL bret; + + argc = winetest_get_mainargs(&argv); + if (argc > 2 && !strcmp(argv[2], "grandchild")) + { + test_console_mode_change_grandchild( argc > 3 ? argv[3] : NULL, argc > 4 ? argv[4] : NULL ); + return; + } + + /* Make sure console is functioonal. All the tests which need original console should go before. */ + orig_stderr = GetStdHandle( STD_ERROR_HANDLE ); + FreeConsole(); + bret = AllocConsole(); + ok( bret, "got error %lu.\n", GetLastError() ); + con_out = CreateFileA( "CONOUT$", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0 ); + ok( con_out != INVALID_HANDLE_VALUE, "got error %ld.\n", GetLastError() ); + /* disable winetest ANSI escape of errors (it tempers with console output mode) */ + SetEnvironmentVariableA( "WINETEST_COLOR", NULL ); + winetest_color = 0; + + test_console_mode_change( argc, argv ); +} -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11542
From: Paul Gofman <pgofman@codeweavers.com> And restore it before running external command. --- programs/cmd/tests/console.c | 9 ++++----- programs/cmd/wcmdmain.c | 9 +++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/programs/cmd/tests/console.c b/programs/cmd/tests/console.c index 5a0d9a4b0ac..ef2f213a474 100644 --- a/programs/cmd/tests/console.c +++ b/programs/cmd/tests/console.c @@ -47,7 +47,7 @@ static void test_console_mode_change_grandchild( const char *exec_type, const ch ok_child( bret, "got error %lu.\n", GetLastError() ); if (exec_type && !strcmp( exec_type, "startcmd" )) { - todo_wine ok_child( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + ok_child( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, "got %#lx.\n", mode ); } @@ -104,7 +104,6 @@ static void test_console_mode_change(int argc, char *argv[]) CloseHandle( info.hThread ); bret = GetConsoleMode( con_out, &mode ); ok( bret, "got error %lu.\n", GetLastError() ); - todo_wine ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, "got %#lx.\n", mode ); @@ -123,7 +122,7 @@ static void test_console_mode_change(int argc, char *argv[]) CloseHandle( info.hThread ); bret = GetConsoleMode( con_out, &mode ); ok( bret, "got error %lu.\n", GetLastError() ); - todo_wine ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, "got %#lx.\n", mode ); @@ -198,7 +197,7 @@ static void test_console_mode_change(int argc, char *argv[]) bret = SetConsoleMode( con_out, ENABLE_PROCESSED_OUTPUT ); ok( bret, "got error %lu.\n", GetLastError() ); DeleteFileA( "grandchild.out" ); - sprintf( cmd, "cmd.exe /c %s %s grandchild 2>>grandchild.out & %s %s grandchild todo 2>>grandchild.out", + sprintf( cmd, "cmd.exe /c %s %s grandchild 2>>grandchild.out & %s %s grandchild 2>>grandchild.out", argv[0], argv[1], argv[0], argv[1] ); bret = CreateProcessA( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &info ); ok( bret, "got error %lu.\n", GetLastError() ); @@ -225,7 +224,7 @@ static void test_console_mode_change(int argc, char *argv[]) wait_child_process( &info ); bret = GetConsoleMode( con_out, &mode ); ok( bret, "got error %lu.\n", GetLastError() ); - todo_wine ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + ok( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) || broken( mode == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT) ) /* before Win10 */, "got %#lx.\n", mode ); diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index d2250ce1bbd..fcb0ce814b6 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -68,6 +68,11 @@ static BOOL unicodeOutput = FALSE; static HANDLE console_input; BOOL echo_mode = TRUE; +/* Output handling */ +static DWORD orig_console_mode; +static DWORD internal_console_mode = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING; + /* Variables pertaining to paging */ static BOOL paged_mode; static const WCHAR *pagedMessage = NULL; @@ -1807,6 +1812,8 @@ static RETURN_CODE spawn_external_full_path(const WCHAR *file, WCHAR *full_cmdli console = SHGetFileInfoW(exe_path, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE); init_msvcrt_io_block(&si); + if (console && !HIWORD(console)) + SetConsoleMode( GetStdHandle(STD_OUTPUT_HANDLE), orig_console_mode ); ret = CreateProcessW(file, full_cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); free(si.lpReserved2); @@ -4698,6 +4705,7 @@ static RETURN_CODE node_execute_with_echo(CMD_NODE *node, BOOL with_echo) RETURN_CODE node_execute(CMD_NODE *node) { + SetConsoleMode( GetStdHandle(STD_OUTPUT_HANDLE), internal_console_mode ); return node_execute_with_echo(node, echo_mode && WCMD_is_in_context(NULL)); } @@ -4950,6 +4958,7 @@ static void WCMD_setup(void) /* init for loop context */ forloopcontext = NULL; WCMD_save_for_loop_context(TRUE); + GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &orig_console_mode); } /***************************************************************************** -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11542
v3: - include Eric's diff disabling winetest color mode; - fix some formatting inconsistences. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11542#note_147880
This merge request was approved by eric pouech. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11542
participants (3)
-
eric pouech (@epo) -
Paul Gofman -
Paul Gofman (@gofman)