From 4dda2c3068d28bc44e7456d7178ee3b67c5677b9 Mon Sep 17 00:00:00 2001 From: Beena352 Date: Mon, 22 Dec 2025 17:37:50 -0800 Subject: [PATCH 1/8] Refactor wsladiag CLI parsing and fix shell PTY handling --- src/shared/inc/CommandLine.h | 12 +++- src/windows/wsladiag/wsladiag.cpp | 108 +++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/shared/inc/CommandLine.h b/src/shared/inc/CommandLine.h index 9966c5e65..d53991846 100644 --- a/src/shared/inc/CommandLine.h +++ b/src/shared/inc/CommandLine.h @@ -307,7 +307,8 @@ class ArgumentParser public: #ifdef WIN32 - ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1) : m_startIndex(StartIndex), m_name(Name) + ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool IgnoreUnknownArgs = false) : + m_startIndex(StartIndex), m_name(Name), m_ignoreUnknownArgs(IgnoreUnknownArgs) { m_argv.reset(CommandLineToArgvW(std::wstring(CommandLine).c_str(), &m_argc)); THROW_LAST_ERROR_IF(!m_argv); @@ -315,7 +316,8 @@ class ArgumentParser #else - ArgumentParser(int argc, const char* const* argv) : m_argc(argc), m_argv(argv), m_startIndex(1) + ArgumentParser(int argc, const char* const* argv, bool IgnoreUnknownArgs = false) : + m_argc(argc), m_argv(argv), m_startIndex(1), m_ignoreUnknownArgs(IgnoreUnknownArgs) { } @@ -429,6 +431,11 @@ class ArgumentParser if (!foundMatch) { + if (m_ignoreUnknownArgs) + { + break; + } + THROW_USER_ERROR(wsl::shared::Localization::MessageInvalidCommandLine(m_argv[i], m_name ? m_name : m_argv[0])); } @@ -535,6 +542,7 @@ class ArgumentParser int m_startIndex{}; const TChar* m_name{}; + bool m_ignoreUnknownArgs{false}; }; } // namespace wsl::shared diff --git a/src/windows/wsladiag/wsladiag.cpp b/src/windows/wsladiag/wsladiag.cpp index cabfcbbfd..41e057796 100644 --- a/src/windows/wsladiag/wsladiag.cpp +++ b/src/windows/wsladiag/wsladiag.cpp @@ -8,7 +8,7 @@ Module Name: Abstract: - Entry point for the wsladiag tool, performs WSL runtime initialization and parses --list/--help. + Entry point for the wsladiag tool, performs WSL runtime initialization and parses list/shell/help. --*/ @@ -42,9 +42,35 @@ static int ReportError(const std::wstring& context, HRESULT hr) return 1; } -// Handler for `wsladiag shell ` (TTY-backed interactive shell). -static int RunShellCommand(const std::wstring& sessionName, bool verbose) +// Handler for `wsladiag shell [--verbose]` command - launches TTY-backed interactive shell. +static int RunShellCommand(std::wstring_view commandLine) { + std::wstring sessionName; + bool verbose = false; + + ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2); // Skip "wsladiag.exe shell" to parse shell-specific args + parser.AddPositionalArgument(sessionName, 0); + parser.AddArgument(verbose, L"--verbose", L'v'); + + try + { + parser.Parse(); + } + catch (...) + { + const auto hr = wil::ResultFromCaughtException(); + if (hr == E_INVALIDARG) + { + return 1; + } + throw; + } + + if (sessionName.empty()) + { + return 1; + } + const auto log = [&](std::wstring_view msg) { if (verbose) { @@ -85,9 +111,11 @@ static int RunShellCommand(const std::wstring& sessionName, bool verbose) wsl::windows::common::WSLAProcessLauncher launcher{ shell, {shell, "--login"}, {"TERM=xterm-256color"}, wsl::windows::common::ProcessFlags::None}; - launcher.AddFd(WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput}); - launcher.AddFd(WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput}); - launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl}); + launcher.AddFd(WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput, .Path = nullptr}); + launcher.AddFd(WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput, .Path = nullptr}); + launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl, .Path = nullptr}); + + log(std::format(L"[diag] tty rows={} cols={}", rows, cols)); launcher.SetTtySize(rows, cols); log(L"[diag] launching shell process..."); @@ -96,7 +124,6 @@ static int RunShellCommand(const std::wstring& sessionName, bool verbose) auto ttyIn = process.GetStdHandle(0); auto ttyOut = process.GetStdHandle(1); - auto ttyControl = process.GetStdHandle(2); // Console handles. wil::unique_hfile conin{ @@ -119,11 +146,11 @@ static int RunShellCommand(const std::wstring& sessionName, bool verbose) const UINT originalOutCP = GetConsoleOutputCP(); const UINT originalInCP = GetConsoleCP(); - auto restoreConsole = wil::scope_exit([&] { - SetConsoleMode(consoleIn, originalInMode); - SetConsoleMode(consoleOut, originalOutMode); - SetConsoleOutputCP(originalOutCP); - SetConsoleCP(originalInCP); + auto restoreConsole = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { + LOG_IF_WIN32_BOOL_FALSE(SetConsoleMode(consoleIn, originalInMode)); + LOG_IF_WIN32_BOOL_FALSE(SetConsoleMode(consoleOut, originalOutMode)); + LOG_IF_WIN32_BOOL_FALSE(SetConsoleOutputCP(originalOutCP)); + LOG_IF_WIN32_BOOL_FALSE(SetConsoleCP(originalInCP)); }); // Console mode for interactive terminal. @@ -139,23 +166,46 @@ static int RunShellCommand(const std::wstring& sessionName, bool verbose) THROW_LAST_ERROR_IF(!SetConsoleOutputCP(CP_UTF8)); THROW_LAST_ERROR_IF(!SetConsoleCP(CP_UTF8)); - // Keep terminal control socket alive. auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset); - wsl::shared::SocketChannel controlChannel{wil::unique_socket{(SOCKET)ttyControl.release()}, "TerminalControl", exitEvent.get()}; + + std::optional controlChannel; + try + { + auto ttyControl = process.GetStdHandle(2); // only once + controlChannel.emplace(wil::unique_socket{(SOCKET)ttyControl.release()}, "TerminalControl", exitEvent.get()); + } + catch (const wil::ResultException& e) + { + const HRESULT hr = e.GetErrorCode(); + if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) + { + log(L"[diag] TerminalControl not available; live resize disabled"); + } + else + { + throw; // or ReportError + return + } + } auto updateTerminalSize = [&]() { + if (!controlChannel.has_value()) + { + return; + } + CONSOLE_SCREEN_BUFFER_INFOEX infoEx{}; infoEx.cbSize = sizeof(infoEx); THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfoEx(consoleOut, &infoEx)); WSLA_TERMINAL_CHANGED message{}; - message.Columns = infoEx.srWindow.Right - infoEx.srWindow.Left + 1; - message.Rows = infoEx.srWindow.Bottom - infoEx.srWindow.Top + 1; + message.Columns = static_cast(infoEx.srWindow.Right - infoEx.srWindow.Left + 1); + message.Rows = static_cast(infoEx.srWindow.Bottom - infoEx.srWindow.Top + 1); - controlChannel.SendMessage(message); + controlChannel->SendMessage(message); }; - // Relay console -> tty input. + // Start input relay thread to forward console input to TTY + // Runs in parallel with output relay (main thread) std::thread inputThread([&] { try { @@ -178,19 +228,19 @@ static int RunShellCommand(const std::wstring& sessionName, bool verbose) // Relay tty output -> console (blocks until output ends). wsl::windows::common::relay::InterruptableRelay(ttyOut.get(), consoleOut, exitEvent.get()); + // Output relay finished - signal input thread to stop and wait for process exit + exitEvent.SetEvent(); process.GetExitEvent().wait(); - auto exitCode = process.GetExitCode(); + auto [exitCode, signalled] = process.GetExitState(); std::wstring shellWide(shell.begin(), shell.end()); - std::wstring message = Localization::MessageWslaShellExited(shellWide.c_str(), exitCode); - - wslutil::PrintMessage(message, stdout); + wslutil::PrintMessage(Localization::MessageWslaShellExited(shellWide.c_str(), exitCode), stdout); return 0; } -static int RunListCommand(bool /*verbose*/) +static int RunListCommand() { wil::com_ptr userSession; THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); @@ -286,17 +336,13 @@ int wsladiag_main(std::wstring_view commandLine) THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &data)); auto wsaCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WSACleanup(); }); - ArgumentParser parser(std::wstring{commandLine}, L"wsladiag"); + ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 1, true); bool help = false; - bool verbose = false; std::wstring verb; - std::wstring shellSession; - parser.AddPositionalArgument(verb, 0); // "list" or "shell" - parser.AddPositionalArgument(shellSession, 1); // session name for "shell" + parser.AddPositionalArgument(verb, 0); parser.AddArgument(help, L"--help", L'h'); - parser.AddArgument(verbose, L"--verbose", L'v'); try { @@ -320,7 +366,7 @@ int wsladiag_main(std::wstring_view commandLine) } else if (verb == L"list") { - return RunListCommand(verbose); + return RunListCommand(); } else if (verb == L"shell") { @@ -329,7 +375,7 @@ int wsladiag_main(std::wstring_view commandLine) PrintUsage(); return 1; } - return RunShellCommand(shellSession, verbose); + return RunShellCommand(commandLine); } else { From 08a6f990fef0168f732e56e856e62557073207ae Mon Sep 17 00:00:00 2001 From: Beena352 Date: Tue, 23 Dec 2025 10:22:20 -0800 Subject: [PATCH 2/8] Rename ArgumentParser flag to stopOnUnknownArgs --- src/shared/inc/CommandLine.h | 8 ++-- src/windows/wsladiag/wsladiag.cpp | 66 ++++++------------------------- 2 files changed, 15 insertions(+), 59 deletions(-) diff --git a/src/shared/inc/CommandLine.h b/src/shared/inc/CommandLine.h index d53991846..bc242ecd5 100644 --- a/src/shared/inc/CommandLine.h +++ b/src/shared/inc/CommandLine.h @@ -308,7 +308,7 @@ class ArgumentParser #ifdef WIN32 ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool IgnoreUnknownArgs = false) : - m_startIndex(StartIndex), m_name(Name), m_ignoreUnknownArgs(IgnoreUnknownArgs) + m_startIndex(StartIndex), m_name(Name), m_stopUnknownArgs(IgnoreUnknownArgs) { m_argv.reset(CommandLineToArgvW(std::wstring(CommandLine).c_str(), &m_argc)); THROW_LAST_ERROR_IF(!m_argv); @@ -317,7 +317,7 @@ class ArgumentParser #else ArgumentParser(int argc, const char* const* argv, bool IgnoreUnknownArgs = false) : - m_argc(argc), m_argv(argv), m_startIndex(1), m_ignoreUnknownArgs(IgnoreUnknownArgs) + m_argc(argc), m_argv(argv), m_startIndex(1), m_stopUnknownArgs(IgnoreUnknownArgs) { } @@ -431,7 +431,7 @@ class ArgumentParser if (!foundMatch) { - if (m_ignoreUnknownArgs) + if (m_stopUnknownArgs) { break; } @@ -542,7 +542,7 @@ class ArgumentParser int m_startIndex{}; const TChar* m_name{}; - bool m_ignoreUnknownArgs{false}; + bool m_stopUnknownArgs{false}; }; } // namespace wsl::shared diff --git a/src/windows/wsladiag/wsladiag.cpp b/src/windows/wsladiag/wsladiag.cpp index 41e057796..75bf7cb5c 100644 --- a/src/windows/wsladiag/wsladiag.cpp +++ b/src/windows/wsladiag/wsladiag.cpp @@ -52,23 +52,11 @@ static int RunShellCommand(std::wstring_view commandLine) parser.AddPositionalArgument(sessionName, 0); parser.AddArgument(verbose, L"--verbose", L'v'); - try - { - parser.Parse(); - } - catch (...) - { - const auto hr = wil::ResultFromCaughtException(); - if (hr == E_INVALIDARG) - { - return 1; - } - throw; - } + parser.Parse(); if (sessionName.empty()) { - return 1; + THROW_HR(E_INVALIDARG); } const auto log = [&](std::wstring_view msg) { @@ -168,31 +156,10 @@ static int RunShellCommand(std::wstring_view commandLine) auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset); - std::optional controlChannel; - try - { - auto ttyControl = process.GetStdHandle(2); // only once - controlChannel.emplace(wil::unique_socket{(SOCKET)ttyControl.release()}, "TerminalControl", exitEvent.get()); - } - catch (const wil::ResultException& e) - { - const HRESULT hr = e.GetErrorCode(); - if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) - { - log(L"[diag] TerminalControl not available; live resize disabled"); - } - else - { - throw; // or ReportError + return - } - } + auto ttyControl = process.GetStdHandle(2); // TerminalControl + wsl::shared::SocketChannel controlChannel{wil::unique_socket{(SOCKET)ttyControl.release()}, "TerminalControl", exitEvent.get()}; auto updateTerminalSize = [&]() { - if (!controlChannel.has_value()) - { - return; - } - CONSOLE_SCREEN_BUFFER_INFOEX infoEx{}; infoEx.cbSize = sizeof(infoEx); THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfoEx(consoleOut, &infoEx)); @@ -201,7 +168,7 @@ static int RunShellCommand(std::wstring_view commandLine) message.Columns = static_cast(infoEx.srWindow.Right - infoEx.srWindow.Left + 1); message.Rows = static_cast(infoEx.srWindow.Bottom - infoEx.srWindow.Top + 1); - controlChannel->SendMessage(message); + controlChannel.SendMessage(message); }; // Start input relay thread to forward console input to TTY @@ -228,8 +195,6 @@ static int RunShellCommand(std::wstring_view commandLine) // Relay tty output -> console (blocks until output ends). wsl::windows::common::relay::InterruptableRelay(ttyOut.get(), consoleOut, exitEvent.get()); - // Output relay finished - signal input thread to stop and wait for process exit - exitEvent.SetEvent(); process.GetExitEvent().wait(); auto [exitCode, signalled] = process.GetExitState(); @@ -344,20 +309,7 @@ int wsladiag_main(std::wstring_view commandLine) parser.AddPositionalArgument(verb, 0); parser.AddArgument(help, L"--help", L'h'); - try - { - parser.Parse(); - } - catch (...) - { - const auto hr = wil::ResultFromCaughtException(); - if (hr == E_INVALIDARG) - { - PrintUsage(); - return 1; - } - throw; - } + parser.Parse(); // Let exceptions propagate to wmain for centralized handling if (help || verb.empty()) { @@ -381,7 +333,6 @@ int wsladiag_main(std::wstring_view commandLine) { wslutil::PrintMessage(Localization::MessageWslaUnknownCommand(verb.c_str()), stderr); PrintUsage(); - return 1; } } @@ -395,6 +346,11 @@ int wmain(int, wchar_t**) catch (...) { const auto hr = wil::ResultFromCaughtException(); + if (hr == E_INVALIDARG) + { + PrintUsage(); + return 1; + } return ReportError(L"wsladiag failed", hr); } } From 62443ea20a6eb54f5dfb7273f8e33cf2a390313f Mon Sep 17 00:00:00 2001 From: Beena352 Date: Wed, 24 Dec 2025 10:35:20 -0800 Subject: [PATCH 3/8] Rename ArgumentParser constructor flag to stopOnUnknownArgs --- src/shared/inc/CommandLine.h | 10 +++++----- src/windows/wsladiag/wsladiag.cpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/shared/inc/CommandLine.h b/src/shared/inc/CommandLine.h index bc242ecd5..3ef367ed4 100644 --- a/src/shared/inc/CommandLine.h +++ b/src/shared/inc/CommandLine.h @@ -307,8 +307,8 @@ class ArgumentParser public: #ifdef WIN32 - ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool IgnoreUnknownArgs = false) : - m_startIndex(StartIndex), m_name(Name), m_stopUnknownArgs(IgnoreUnknownArgs) + ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool stopUnknownArgs = false) : + m_startIndex(StartIndex), m_name(Name), m_stopUnknownArgs(stopUnknownArgs) { m_argv.reset(CommandLineToArgvW(std::wstring(CommandLine).c_str(), &m_argc)); THROW_LAST_ERROR_IF(!m_argv); @@ -316,8 +316,8 @@ class ArgumentParser #else - ArgumentParser(int argc, const char* const* argv, bool IgnoreUnknownArgs = false) : - m_argc(argc), m_argv(argv), m_startIndex(1), m_stopUnknownArgs(IgnoreUnknownArgs) + ArgumentParser(int argc, const char* const* argv, bool stopUnknownArgs = false) : + m_argc(argc), m_argv(argv), m_startIndex(1), m_stopUnknownArgs(stopUnknownArgs) { } @@ -402,7 +402,7 @@ class ArgumentParser const TChar* value = nullptr; if (e.Positional) { - value = m_argv[i]; // Positional arguments directly receive arvg[i] + value = m_argv[i]; // Positional arguments directly receive argv[i] } else if (i + 1 < m_argc) { diff --git a/src/windows/wsladiag/wsladiag.cpp b/src/windows/wsladiag/wsladiag.cpp index 75bf7cb5c..cfc2c65da 100644 --- a/src/windows/wsladiag/wsladiag.cpp +++ b/src/windows/wsladiag/wsladiag.cpp @@ -165,8 +165,8 @@ static int RunShellCommand(std::wstring_view commandLine) THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfoEx(consoleOut, &infoEx)); WSLA_TERMINAL_CHANGED message{}; - message.Columns = static_cast(infoEx.srWindow.Right - infoEx.srWindow.Left + 1); - message.Rows = static_cast(infoEx.srWindow.Bottom - infoEx.srWindow.Top + 1); + message.Columns = infoEx.srWindow.Right - infoEx.srWindow.Left + 1; + message.Rows = infoEx.srWindow.Bottom - infoEx.srWindow.Top + 1; controlChannel.SendMessage(message); }; From 9aaf850955f4e96db1f924cc6b89d54b5495b783 Mon Sep 17 00:00:00 2001 From: Beena352 Date: Fri, 2 Jan 2026 13:38:47 -0800 Subject: [PATCH 4/8] Remove E_INVALIDARG special-casing in wmain error handling --- src/windows/wsladiag/wsladiag.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/windows/wsladiag/wsladiag.cpp b/src/windows/wsladiag/wsladiag.cpp index cfc2c65da..2d4b0dc28 100644 --- a/src/windows/wsladiag/wsladiag.cpp +++ b/src/windows/wsladiag/wsladiag.cpp @@ -346,11 +346,6 @@ int wmain(int, wchar_t**) catch (...) { const auto hr = wil::ResultFromCaughtException(); - if (hr == E_INVALIDARG) - { - PrintUsage(); - return 1; - } return ReportError(L"wsladiag failed", hr); } } From 58c7e4f72c24a04f9e048665040ff5aec4ea573b Mon Sep 17 00:00:00 2001 From: Beena352 Date: Mon, 5 Jan 2026 09:46:39 -0800 Subject: [PATCH 5/8] initialize ExecutionContext in main to capture user-visible errors --- src/windows/wsladiag/wsladiag.cpp | 111 +++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 24 deletions(-) diff --git a/src/windows/wsladiag/wsladiag.cpp b/src/windows/wsladiag/wsladiag.cpp index 2d4b0dc28..87aa91638 100644 --- a/src/windows/wsladiag/wsladiag.cpp +++ b/src/windows/wsladiag/wsladiag.cpp @@ -18,6 +18,7 @@ Module Name: #include "wslaservice.h" #include "WslSecurity.h" #include "WSLAProcessLauncher.h" +#include "ExecutionContext.h" #include #include @@ -56,7 +57,8 @@ static int RunShellCommand(std::wstring_view commandLine) if (sessionName.empty()) { - THROW_HR(E_INVALIDARG); + THROW_HR_WITH_USER_ERROR( + E_INVALIDARG, wsl::shared::Localization::MessageMissingArgument(L"", L"wsladiag shell")); } const auto log = [&](std::wstring_view msg) { @@ -286,6 +288,7 @@ static void PrintUsage() int wsladiag_main(std::wstring_view commandLine) { + // Basic initialization that was previously in this function. wslutil::ConfigureCrt(); wslutil::InitializeWil(); @@ -301,40 +304,100 @@ int wsladiag_main(std::wstring_view commandLine) THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &data)); auto wsaCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WSACleanup(); }); - ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 1, true); + // Enable contextualized error collection and create an ExecutionContext so + // THROW_HR_WITH_USER_ERROR will save a user-visible message. + wsl::windows::common::EnableContextualizedErrors(false); - bool help = false; - std::wstring verb; + FILE* warningsFile = nullptr; + const char* disableWarnings = getenv("WSL_DISABLE_WARNINGS"); + if (disableWarnings == nullptr || strcmp(disableWarnings, "1") != 0) + { + warningsFile = stderr; + } - parser.AddPositionalArgument(verb, 0); - parser.AddArgument(help, L"--help", L'h'); + std::optional context; + context.emplace(wsl::windows::common::Context::Wsl, warningsFile); - parser.Parse(); // Let exceptions propagate to wmain for centralized handling + int exitCode = 0; + HRESULT result = S_OK; - if (help || verb.empty()) + try { - PrintUsage(); - return 0; + ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 1, true); + + bool help = false; + std::wstring verb; + + parser.AddPositionalArgument(verb, 0); + parser.AddArgument(help, L"--help", L'h'); + + parser.Parse(); // Let exceptions propagate to this try/catch + + if (help || verb.empty()) + { + PrintUsage(); + exitCode = 0; + } + else if (verb == L"list") + { + exitCode = RunListCommand(); + } + else if (verb == L"shell") + { + exitCode = RunShellCommand(commandLine); + } + else + { + wslutil::PrintMessage(std::format(L"Unknown command: '{}'", verb), stderr); + PrintUsage(); + exitCode = 1; + } } - else if (verb == L"list") + catch (...) { - return RunListCommand(); + // Capture the exception HRESULT and fall through to printing contextualized error. + result = wil::ResultFromCaughtException(); + // Default nonzero exit code on failure. + exitCode = 1; } - else if (verb == L"shell") + + wslutil::PrintMessage(Localization::MessageWslaUnknownCommand(verb.c_str()), stderr); + PrintUsage(); + return 1; + + // If there was a failure, attempt to print a contextualized error message collected + // by the ExecutionContext. Otherwise fall back to the HRESULT -> string path. + if (FAILED(result)) { - if (shellSession.empty()) + try { - PrintUsage(); - return 1; + std::wstring errorString{}; + if (context.has_value() && context->ReportedError().has_value()) + { + auto strings = wsl::windows::common::wslutil::ErrorToString(context->ReportedError().value()); + + // For most errors, show both message and code + errorString = wsl::shared::Localization::MessageErrorCode(strings.Message, strings.Code); + + // Log telemetry for user-visible errors (matches other tools). + WSL_LOG("UserVisibleError", TraceLoggingValue(strings.Code.c_str(), "ErrorCode")); + } + else + { + // Fallback: show basic HRESULT string. + errorString = wslutil::ErrorCodeToString(result); + } + + wslutil::PrintMessage(errorString, stderr); } - return RunShellCommand(commandLine); - } - else - { - wslutil::PrintMessage(Localization::MessageWslaUnknownCommand(verb.c_str()), stderr); - PrintUsage(); - return 1; + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + } + } + + return exitCode; } int wmain(int, wchar_t**) @@ -348,4 +411,4 @@ int wmain(int, wchar_t**) const auto hr = wil::ResultFromCaughtException(); return ReportError(L"wsladiag failed", hr); } -} +} \ No newline at end of file From 57a66542fce2e60038d1b3864406f4e159bd8233 Mon Sep 17 00:00:00 2001 From: Beena352 Date: Mon, 5 Jan 2026 15:31:31 -0800 Subject: [PATCH 6/8] Handle --verbose per command and restore support for list --- src/shared/inc/CommandLine.h | 12 ++++++------ src/windows/wsladiag/wsladiag.cpp | 24 ++++++++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/shared/inc/CommandLine.h b/src/shared/inc/CommandLine.h index 3ef367ed4..da4e2056e 100644 --- a/src/shared/inc/CommandLine.h +++ b/src/shared/inc/CommandLine.h @@ -307,8 +307,8 @@ class ArgumentParser public: #ifdef WIN32 - ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool stopUnknownArgs = false) : - m_startIndex(StartIndex), m_name(Name), m_stopUnknownArgs(stopUnknownArgs) + ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool ignoreUnknownArgs = false) : + m_startIndex(StartIndex), m_name(Name), m_ignoreUnknownArgs(ignoreUnknownArgs) { m_argv.reset(CommandLineToArgvW(std::wstring(CommandLine).c_str(), &m_argc)); THROW_LAST_ERROR_IF(!m_argv); @@ -316,8 +316,8 @@ class ArgumentParser #else - ArgumentParser(int argc, const char* const* argv, bool stopUnknownArgs = false) : - m_argc(argc), m_argv(argv), m_startIndex(1), m_stopUnknownArgs(stopUnknownArgs) + ArgumentParser(int argc, const char* const* argv, bool ignoreUnknownArgs = false) : + m_argc(argc), m_argv(argv), m_startIndex(1), m_ignoreUnknownArgs(ignoreUnknownArgs) { } @@ -431,7 +431,7 @@ class ArgumentParser if (!foundMatch) { - if (m_stopUnknownArgs) + if (m_ignoreUnknownArgs) { break; } @@ -542,7 +542,7 @@ class ArgumentParser int m_startIndex{}; const TChar* m_name{}; - bool m_stopUnknownArgs{false}; + bool m_ignoreUnknownArgs{false}; }; } // namespace wsl::shared diff --git a/src/windows/wsladiag/wsladiag.cpp b/src/windows/wsladiag/wsladiag.cpp index 87aa91638..4ad41d5a5 100644 --- a/src/windows/wsladiag/wsladiag.cpp +++ b/src/windows/wsladiag/wsladiag.cpp @@ -49,7 +49,7 @@ static int RunShellCommand(std::wstring_view commandLine) std::wstring sessionName; bool verbose = false; - ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2); // Skip "wsladiag.exe shell" to parse shell-specific args + ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2, false); // Skip "wsladiag.exe shell" to parse shell-specific args parser.AddPositionalArgument(sessionName, 0); parser.AddArgument(verbose, L"--verbose", L'v'); @@ -207,8 +207,15 @@ static int RunShellCommand(std::wstring_view commandLine) return 0; } -static int RunListCommand() +static int RunListCommand(std::wstring_view commandLine) { + bool verbose = false; + + ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2, false); // Skip "wsladiag.exe list" to parse list-specific args + parser.AddArgument(verbose, L"--verbose", L'v'); + + parser.Parse(); + wil::com_ptr userSession; THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); @@ -216,6 +223,11 @@ static int RunListCommand() wil::unique_cotaskmem_array_ptr sessions; THROW_IF_FAILED(userSession->ListSessions(&sessions, sessions.size_address())); + if (verbose) + { + wslutil::PrintMessage(std::format(L"[diag] Found {} session(s)", sessions.size()), stdout); + } + if (sessions.size() == 0) { wslutil::PrintMessage(Localization::MessageWslaNoSessionsFound(), stdout); @@ -340,7 +352,7 @@ int wsladiag_main(std::wstring_view commandLine) } else if (verb == L"list") { - exitCode = RunListCommand(); + exitCode = RunListCommand(commandLine); } else if (verb == L"shell") { @@ -348,7 +360,7 @@ int wsladiag_main(std::wstring_view commandLine) } else { - wslutil::PrintMessage(std::format(L"Unknown command: '{}'", verb), stderr); + wslutil::PrintMessage(Localization::MessageWslaUnknownCommand(verb.c_str()), stderr); PrintUsage(); exitCode = 1; } @@ -361,10 +373,6 @@ int wsladiag_main(std::wstring_view commandLine) exitCode = 1; } - wslutil::PrintMessage(Localization::MessageWslaUnknownCommand(verb.c_str()), stderr); - PrintUsage(); - return 1; - // If there was a failure, attempt to print a contextualized error message collected // by the ExecutionContext. Otherwise fall back to the HRESULT -> string path. if (FAILED(result)) From c60e0034f4e4e127f22ca90a84cfd79c15922fa0 Mon Sep 17 00:00:00 2001 From: Beena352 Date: Fri, 9 Jan 2026 16:27:53 -0800 Subject: [PATCH 7/8] Refine localized error handling --- src/windows/common/ExecutionContext.h | 1 + src/windows/common/wslutil.cpp | 3 +- src/windows/wsladiag/wsladiag.cpp | 187 ++++++++++---------------- 3 files changed, 71 insertions(+), 120 deletions(-) diff --git a/src/windows/common/ExecutionContext.h b/src/windows/common/ExecutionContext.h index 0fe473d9f..9c12ff644 100644 --- a/src/windows/common/ExecutionContext.h +++ b/src/windows/common/ExecutionContext.h @@ -66,6 +66,7 @@ enum Context : ULONGLONG UpdatePackage = 0x10000000000, QueryLatestGitHubRelease = 0x20000000000, VerifyChecksum = 0x40000000000, + WslaDiag = 0x80000000000, }; DEFINE_ENUM_FLAG_OPERATORS(Context) diff --git a/src/windows/common/wslutil.cpp b/src/windows/common/wslutil.cpp index dbe79296c..dc6b03c98 100644 --- a/src/windows/common/wslutil.cpp +++ b/src/windows/common/wslutil.cpp @@ -191,7 +191,8 @@ static const std::map g_contextStrings{ X(HNS), X(ReadDistroConfig), X(MoveDistro), - X(VerifyChecksum)}; + X(VerifyChecksum), + X(WslaDiag)}; #undef X diff --git a/src/windows/wsladiag/wsladiag.cpp b/src/windows/wsladiag/wsladiag.cpp index 4ad41d5a5..cce561c1f 100644 --- a/src/windows/wsladiag/wsladiag.cpp +++ b/src/windows/wsladiag/wsladiag.cpp @@ -8,7 +8,7 @@ Module Name: Abstract: - Entry point for the wsladiag tool, performs WSL runtime initialization and parses list/shell/help. + Entry point for the wsladiag tool. Provides diagnostic commands for WSLA sessions. --*/ @@ -24,32 +24,25 @@ Module Name: using namespace wsl::shared; namespace wslutil = wsl::windows::common::wslutil; +using wsl::windows::common::Context; +using wsl::windows::common::ExecutionContext; using wsl::windows::common::WSLAProcessLauncher; -// Adding a helper to factor error handling between all the arguments. +// Report an operation failure with localized context and HRESULT details. static int ReportError(const std::wstring& context, HRESULT hr) { - const std::wstring hrMessage = wslutil::GetErrorString(hr); - - if (!hrMessage.empty()) - { - wslutil::PrintMessage(std::format(L"{}: 0x{:08x} - {}", context, static_cast(hr), hrMessage), stderr); - } - else - { - wslutil::PrintMessage(std::format(L"{}: 0x{:08x}", context, static_cast(hr)), stderr); - } - + auto errorString = wsl::windows::common::wslutil::ErrorCodeToString(hr); + wslutil::PrintMessage(context, stderr); return 1; } -// Handler for `wsladiag shell [--verbose]` command - launches TTY-backed interactive shell. +// Handler for `wsladiag shell ` command. static int RunShellCommand(std::wstring_view commandLine) { std::wstring sessionName; bool verbose = false; - ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2, false); // Skip "wsladiag.exe shell" to parse shell-specific args + ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2, false); parser.AddPositionalArgument(sessionName, 0); parser.AddArgument(verbose, L"--verbose", L'v'); @@ -61,15 +54,6 @@ static int RunShellCommand(std::wstring_view commandLine) E_INVALIDARG, wsl::shared::Localization::MessageMissingArgument(L"", L"wsladiag shell")); } - const auto log = [&](std::wstring_view msg) { - if (verbose) - { - wslutil::PrintMessage(std::wstring(msg), stdout); - } - }; - - log(std::format(L"[diag] shell='{}'", sessionName)); - wil::com_ptr userSession; THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); @@ -87,8 +71,6 @@ static int RunShellCommand(std::wstring_view commandLine) return ReportError(Localization::MessageWslaOpenSessionFailed(sessionName.c_str()), hr); } - log(L"[diag] OpenSessionByName succeeded"); - // Console size for TTY. CONSOLE_SCREEN_BUFFER_INFO info{}; THROW_LAST_ERROR_IF(!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); @@ -105,12 +87,9 @@ static int RunShellCommand(std::wstring_view commandLine) launcher.AddFd(WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput, .Path = nullptr}); launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl, .Path = nullptr}); - log(std::format(L"[diag] tty rows={} cols={}", rows, cols)); launcher.SetTtySize(rows, cols); - log(L"[diag] launching shell process..."); auto process = launcher.Launch(*session); - log(L"[diag] shell launched (TTY)"); auto ttyIn = process.GetStdHandle(0); auto ttyOut = process.GetStdHandle(1); @@ -129,13 +108,12 @@ static int RunShellCommand(std::wstring_view commandLine) // Save/restore console state. DWORD originalInMode{}; DWORD originalOutMode{}; + const UINT originalOutCP = GetConsoleOutputCP(); + const UINT originalInCP = GetConsoleCP(); THROW_LAST_ERROR_IF(!GetConsoleMode(consoleIn, &originalInMode)); THROW_LAST_ERROR_IF(!GetConsoleMode(consoleOut, &originalOutMode)); - const UINT originalOutCP = GetConsoleOutputCP(); - const UINT originalInCP = GetConsoleCP(); - auto restoreConsole = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { LOG_IF_WIN32_BOOL_FALSE(SetConsoleMode(consoleIn, originalInMode)); LOG_IF_WIN32_BOOL_FALSE(SetConsoleMode(consoleOut, originalOutMode)); @@ -158,19 +136,13 @@ static int RunShellCommand(std::wstring_view commandLine) auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset); - auto ttyControl = process.GetStdHandle(2); // TerminalControl - wsl::shared::SocketChannel controlChannel{wil::unique_socket{(SOCKET)ttyControl.release()}, "TerminalControl", exitEvent.get()}; - auto updateTerminalSize = [&]() { CONSOLE_SCREEN_BUFFER_INFOEX infoEx{}; infoEx.cbSize = sizeof(infoEx); THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfoEx(consoleOut, &infoEx)); - WSLA_TERMINAL_CHANGED message{}; - message.Columns = infoEx.srWindow.Right - infoEx.srWindow.Left + 1; - message.Rows = infoEx.srWindow.Bottom - infoEx.srWindow.Top + 1; - - controlChannel.SendMessage(message); + LOG_IF_FAILED(process.Get().ResizeTty( + infoEx.srWindow.Bottom - infoEx.srWindow.Top + 1, infoEx.srWindow.Right - infoEx.srWindow.Left + 1)); }; // Start input relay thread to forward console input to TTY @@ -186,7 +158,7 @@ static int RunShellCommand(std::wstring_view commandLine) } }); - auto joinInput = wil::scope_exit([&] { + auto joinInput = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { exitEvent.SetEvent(); if (inputThread.joinable()) { @@ -199,22 +171,30 @@ static int RunShellCommand(std::wstring_view commandLine) process.GetExitEvent().wait(); - auto [exitCode, signalled] = process.GetExitState(); + auto exitCode = process.GetExitCode(); std::wstring shellWide(shell.begin(), shell.end()); - wslutil::PrintMessage(Localization::MessageWslaShellExited(shellWide.c_str(), exitCode), stdout); + wslutil::PrintMessage(wsl::shared::Localization::MessageWslaShellExited(shellWide.c_str(), static_cast(exitCode)), stdout); - return 0; + return static_cast(exitCode); } +// Handler for `wsladiag list` command. static int RunListCommand(std::wstring_view commandLine) { bool verbose = false; - ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2, false); // Skip "wsladiag.exe list" to parse list-specific args + ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2, false); parser.AddArgument(verbose, L"--verbose", L'v'); - parser.Parse(); + try + { + parser.Parse(); + } + catch (...) + { + THROW_HR_WITH_USER_ERROR(E_INVALIDARG, wsl::shared::Localization::MessageWsladiagUsage()); + } wil::com_ptr userSession; THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); @@ -235,7 +215,8 @@ static int RunListCommand(std::wstring_view commandLine) } wslutil::PrintMessage(Localization::MessageWslaSessionsFound(sessions.size(), sessions.size() == 1 ? L"" : L"s"), stdout); - // Compute column widths from headers + data. + + // Use localized headers const auto idHeader = Localization::MessageWslaHeaderId(); const auto pidHeader = Localization::MessageWslaHeaderCreatorPid(); const auto nameHeader = Localization::MessageWslaHeaderDisplayName(); @@ -279,7 +260,6 @@ static int RunListCommand(std::wstring_view commandLine) for (const auto& s : sessions) { const wchar_t* displayName = s.DisplayName ? s.DisplayName : L""; - wprintf( L"%-*lu %-*lu %-*ls\n", static_cast(idWidth), @@ -293,6 +273,7 @@ static int RunListCommand(std::wstring_view commandLine) return 0; } +// Print localized usage message to stderr. static void PrintUsage() { wslutil::PrintMessage(Localization::MessageWsladiagUsage(), stderr); @@ -300,7 +281,7 @@ static void PrintUsage() int wsladiag_main(std::wstring_view commandLine) { - // Basic initialization that was previously in this function. + // Initialize runtime and COM. wslutil::ConfigureCrt(); wslutil::InitializeWil(); @@ -316,107 +297,75 @@ int wsladiag_main(std::wstring_view commandLine) THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &data)); auto wsaCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WSACleanup(); }); - // Enable contextualized error collection and create an ExecutionContext so - // THROW_HR_WITH_USER_ERROR will save a user-visible message. - wsl::windows::common::EnableContextualizedErrors(false); + // Parse the top-level verb (list, shell, --help). + ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 1, true); + + bool help = false; + std::wstring verb; + + parser.AddPositionalArgument(verb, 0); + parser.AddArgument(help, L"--help", L'h'); - FILE* warningsFile = nullptr; - const char* disableWarnings = getenv("WSL_DISABLE_WARNINGS"); - if (disableWarnings == nullptr || strcmp(disableWarnings, "1") != 0) + parser.Parse(); + + if (help || verb.empty()) { - warningsFile = stderr; + PrintUsage(); + return 0; } - std::optional context; - context.emplace(wsl::windows::common::Context::Wsl, warningsFile); - - int exitCode = 0; - HRESULT result = S_OK; + if (verb == L"list") + { + return RunListCommand(commandLine); + } - try + if (verb == L"shell") { - ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 1, true); + return RunShellCommand(commandLine); + } - bool help = false; - std::wstring verb; + // Unknown verb - show usage and fail. + wslutil::PrintMessage(Localization::MessageWslaUnknownCommand(verb.c_str()), stderr); + PrintUsage(); + return 1; +} - parser.AddPositionalArgument(verb, 0); - parser.AddArgument(help, L"--help", L'h'); +int wmain(int, wchar_t**) +{ + wsl::windows::common::EnableContextualizedErrors(false); - parser.Parse(); // Let exceptions propagate to this try/catch + ExecutionContext context{Context::WslaDiag}; + int exitCode = 1; + HRESULT result = S_OK; - if (help || verb.empty()) - { - PrintUsage(); - exitCode = 0; - } - else if (verb == L"list") - { - exitCode = RunListCommand(commandLine); - } - else if (verb == L"shell") - { - exitCode = RunShellCommand(commandLine); - } - else - { - wslutil::PrintMessage(Localization::MessageWslaUnknownCommand(verb.c_str()), stderr); - PrintUsage(); - exitCode = 1; - } + try + { + exitCode = wsladiag_main(GetCommandLineW()); } catch (...) { - // Capture the exception HRESULT and fall through to printing contextualized error. result = wil::ResultFromCaughtException(); - // Default nonzero exit code on failure. - exitCode = 1; } - // If there was a failure, attempt to print a contextualized error message collected - // by the ExecutionContext. Otherwise fall back to the HRESULT -> string path. if (FAILED(result)) { try { - std::wstring errorString{}; - if (context.has_value() && context->ReportedError().has_value()) + if (auto reported = context.ReportedError()) { - auto strings = wsl::windows::common::wslutil::ErrorToString(context->ReportedError().value()); - - // For most errors, show both message and code - errorString = wsl::shared::Localization::MessageErrorCode(strings.Message, strings.Code); - - // Log telemetry for user-visible errors (matches other tools). - WSL_LOG("UserVisibleError", TraceLoggingValue(strings.Code.c_str(), "ErrorCode")); + auto strings = wsl::windows::common::wslutil::ErrorToString(*reported); + wslutil::PrintMessage(wsl::shared::Localization::MessageErrorCode(strings.Message, strings.Code), stderr); } else { - // Fallback: show basic HRESULT string. - errorString = wslutil::ErrorCodeToString(result); + wslutil::PrintMessage(wslutil::GetErrorString(result), stderr); } - - wslutil::PrintMessage(errorString, stderr); } catch (...) { LOG_CAUGHT_EXCEPTION(); } - } return exitCode; -} - -int wmain(int, wchar_t**) -{ - try - { - return wsladiag_main(GetCommandLineW()); - } - catch (...) - { - const auto hr = wil::ResultFromCaughtException(); - return ReportError(L"wsladiag failed", hr); - } } \ No newline at end of file From 3a436d25c7a22a4e3ffa6306600e9e8b08998383 Mon Sep 17 00:00:00 2001 From: Beena352 Date: Sat, 10 Jan 2026 13:05:33 -0800 Subject: [PATCH 8/8] fix --verbose for shell --- src/windows/wsladiag/wsladiag.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/windows/wsladiag/wsladiag.cpp b/src/windows/wsladiag/wsladiag.cpp index cce561c1f..e599c96aa 100644 --- a/src/windows/wsladiag/wsladiag.cpp +++ b/src/windows/wsladiag/wsladiag.cpp @@ -32,7 +32,7 @@ using wsl::windows::common::WSLAProcessLauncher; static int ReportError(const std::wstring& context, HRESULT hr) { auto errorString = wsl::windows::common::wslutil::ErrorCodeToString(hr); - wslutil::PrintMessage(context, stderr); + wslutil::PrintMessage(Localization::MessageErrorCode(context, errorString), stderr); return 1; } @@ -71,6 +71,11 @@ static int RunShellCommand(std::wstring_view commandLine) return ReportError(Localization::MessageWslaOpenSessionFailed(sessionName.c_str()), hr); } + if (verbose) + { + wslutil::PrintMessage(std::format(L"[diag] Session opened: '{}'", sessionName), stdout); + } + // Console size for TTY. CONSOLE_SCREEN_BUFFER_INFO info{}; THROW_LAST_ERROR_IF(!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); @@ -91,6 +96,11 @@ static int RunShellCommand(std::wstring_view commandLine) auto process = launcher.Launch(*session); + if (verbose) + { + wslutil::PrintMessage(L"[diag] Shell process launched", stdout); + } + auto ttyIn = process.GetStdHandle(0); auto ttyOut = process.GetStdHandle(1); @@ -205,7 +215,8 @@ static int RunListCommand(std::wstring_view commandLine) if (verbose) { - wslutil::PrintMessage(std::format(L"[diag] Found {} session(s)", sessions.size()), stdout); + const wchar_t* plural = sessions.size() == 1 ? L"" : L"s"; + wslutil::PrintMessage(std::format(L"[diag] Found {} session{}", sessions.size(), plural), stdout); } if (sessions.size() == 0)