-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Refactor wsladiag CLI parsing and fix shell PTY handling #13965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/wsl-for-apps
Are you sure you want to change the base?
Refactor wsladiag CLI parsing and fix shell PTY handling #13965
Conversation
src/windows/wsladiag/wsladiag.cpp
Outdated
| } | ||
| catch (...) | ||
| { | ||
| const auto hr = wil::ResultFromCaughtException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend letting the exception go up and handle the error at the main() level, so we can have one error handler for all arguments
| throw; | ||
| } | ||
|
|
||
| if (sessionName.empty()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should throw an error here since that sessionName being empty would mean that the command line was invalid
src/windows/wsladiag/wsladiag.cpp
Outdated
| wsl::shared::SocketChannel controlChannel{wil::unique_socket{(SOCKET)ttyControl.release()}, "TerminalControl", exitEvent.get()}; | ||
|
|
||
| std::optional<wsl::shared::SocketChannel> controlChannel; | ||
| try |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for adding a try/catch here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there isn’t a strong reason to special-case this. I’ve removed the try/catch and the optional and now treat TerminalControl as required; any failure will propagate and be handled by the existing top-level error handling.
src/windows/wsladiag/wsladiag.cpp
Outdated
| 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the reason for adding this ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the input relay is already stopped and joined via the existing scope_exit cleanup, so this extra signal was redundant. I’ve removed it and kept a single shutdown path.
src/shared/inc/CommandLine.h
Outdated
| #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) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'd recommend naming this argument something like stopOnUnknownArgs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also rename the constructor argument to match the same name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also stopUnknownArgs -> stopOnUnknownArgs
src/windows/wsladiag/wsladiag.cpp
Outdated
| 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<unsigned short>(infoEx.srWindow.Right - infoEx.srWindow.Left + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for this change ?
7a264c4 to
a7841e2
Compare
src/windows/wsladiag/wsladiag.cpp
Outdated
| catch (...) | ||
| { | ||
| const auto hr = wil::ResultFromCaughtException(); | ||
| if (hr == E_INVALIDARG) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E_INVALIDARG can be thrown for other reasons for I would not recommend having a specific catch block here.
Once we have ExecutionContext based errors, we can use those to show command line parsing specific errors
a7841e2 to
dbc865f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors wsladiag's command-line argument parsing to use per-verb parsing, enabling each subcommand (list, shell) to define and validate its own arguments independently. It also fixes several PTY-related issues in the interactive shell path, including TTY size initialization, live terminal resize handling, and console cleanup diagnostics.
Key changes:
- Introduces a relaxed parsing mode in ArgumentParser via the
stopUnknownArgsparameter to allow top-level verb dispatch without throwing on subcommand arguments - Fixes TTY size initialization by passing rows and columns in the correct order and restores live terminal resize functionality through the TerminalControl FD
- Improves console cleanup by using
scope_exit_logand logging failures for better diagnostics
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/windows/wsladiag/wsladiag.cpp | Refactors RunShellCommand to parse its own arguments, moves ttyControl initialization closer to use, improves console cleanup logging, updates Abstract comment to reflect verb-based parsing |
| src/shared/inc/CommandLine.h | Adds stopUnknownArgs parameter to ArgumentParser constructors to enable relaxed parsing mode, fixes typo in comment ("arvg" → "argv") |
| 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'); | ||
|
|
||
| parser.Parse(); | ||
|
|
||
| if (sessionName.empty()) | ||
| { | ||
| THROW_HR(E_INVALIDARG); | ||
| } |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ArgumentParser.Parse() call here will throw THROW_HR_WITH_USER_ERROR with E_INVALIDARG if parsing fails, which sets a user-friendly error message in the ExecutionContext. However, wsladiag doesn't use ExecutionContext (unlike wsl.exe which does), so when the exception propagates to wmain and ReportError is called, only the HRESULT will be shown using ErrorCodeToString, and the user-friendly error message will be lost. This means users won't see helpful messages like "Missing argument" or "Invalid command line" - they'll just see "0x80070057" (E_INVALIDARG).
Consider either: 1) wrapping the Parse() call in a try-catch to call PrintUsage() on E_INVALIDARG, or 2) setting up an ExecutionContext in wsladiag_main so error messages are captured, or 3) using a simpler error handling approach that doesn't rely on THROW_HR_WITH_USER_ERROR.
| ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool stopUnknownArgs = false) : | ||
| m_startIndex(StartIndex), m_name(Name), m_stopUnknownArgs(stopUnknownArgs) |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameter name "stopUnknownArgs" is unclear and potentially confusing. The behavior is to stop parsing (break out of the loop) when an unknown argument is encountered, rather than throwing an error. A clearer name would be "ignoreUnknownArgs" or "allowUnknownArgs" to better convey that unknown arguments are tolerated rather than causing an error.
Summary of the Pull Request:
This PR refactors wsladiag command-line argument handling to use per-verb parsing and fixes multiple correctness and robustness issues in the interactive shell (PTY) path. It improves live terminal resize behavior, simplifies process lifecycle handling, and makes console cleanup more diagnosable, while preserving existing behavior by default.
Detailed Description:
Argument parsing
Refactors argument handling to be per-verb (list, shell), allowing each command to define and validate its own arguments.
Adds an opt-in relaxed mode to ArgumentParser to allow top-level verb dispatch without throwing on subcommand arguments. The default parsing behavior remains unchanged.
This makes the CLI easier to extend as additional wsladiag commands are added.
Shell / PTY handling
Fixes TTY size initialization by passing rows and columns in the correct order.
Restores and correctly handles live terminal resize using the TerminalControl FD.
Validation Steps Performed
Ran wsladiag list to verify session enumeration.
Ran wsladiag shell to verify interactive shell launch.
Verified interactive input/output behavior inside the shell.
Verified live terminal resize by resizing the console window while running commands such as top.
Verified clean process exit without hangs and correct console state restoration.