wslc: add --follow-link to cp - #41501
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
It adds new behavior without an e2e test validating -L actually follows symlinks, and it introduces at least one naming/doc inconsistency that should be addressed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR adds --follow-link / -L support to wslc container cp to match docker cp -L semantics by resolving symlinks in the source path (both local→container and container→local) before copying.
Changes:
- Adds a new CLI flag (
ArgType::FollowLink,--follow-link, alias-L) and wires it intocontainer cp. - Implements container-side symlink resolution by statting
/containers/{id}/archiveand decodingX-Docker-Container-Path-Stat, exposed via a newIWSLCContainer::ResolveArchiveSymlinkmethod. - Adds parser/unit/e2e coverage for argument presence and help output surfacing.
File summaries
| File | Description |
|---|---|
| test/windows/wslc/WSLCCLICommandUnitTests.cpp | Adds a unit test to pin --follow-link name/alias/kind. |
| test/windows/wslc/e2e/WSLCE2EContainerCpTests.cpp | Adds an e2e test ensuring container cp --help lists --follow-link/-L. |
| test/windows/wslc/CommandLineTestCases.h | Adds command-line parsing cases for -L/--follow-link (incl. boolean forms and negatives). |
| src/windows/wslcsession/WSLCContainer.h | Adds ResolveArchiveSymlink to the container implementation and COM interface class. |
| src/windows/wslcsession/WSLCContainer.cpp | Implements ResolveArchiveSymlink and exposes it via COM. |
| src/windows/wslcsession/DockerHTTPClient.h | Declares StatArchivePath helper for reading X-Docker-Container-Path-Stat. |
| src/windows/wslcsession/DockerHTTPClient.cpp | Implements StatArchivePath (GET archive, read stat header, close socket). |
| src/windows/wslc/tasks/ContainerTasks.cpp | Adds followLink behavior for container cp in both copy directions. |
| src/windows/wslc/services/ContainerService.h | Adds ResolveContainerSymlink service helper declaration. |
| src/windows/wslc/services/ContainerService.cpp | Implements ResolveContainerSymlink by calling IWSLCContainer::ResolveArchiveSymlink. |
| src/windows/wslc/commands/ContainerCpCommand.cpp | Registers ArgType::FollowLink for the container cp command. |
| src/windows/wslc/arguments/ArgumentDefinitions.h | Defines the new argument type and localization hook. |
| src/windows/service/inc/wslc.idl | Extends IWSLCContainer with ResolveArchiveSymlink. |
| src/windows/inc/docker_schema.h | Adds ContainerPathStat schema for the archive stat header payload. |
| localization/strings/en-US/Resources.resw | Adds localized help text for --follow-link. |
Review details
- Files reviewed: 15/15 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
DockerHTTPClient::StatArchivePath currently treats all non-200 responses (and even missing stat headers on 200) as “path not found,” which can silently mask real engine errors and cause --follow-link to behave incorrectly.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/windows/wslcsession/DockerHTTPClient.cpp:449
- StatArchivePath() currently returns nullopt for any non-200 response, but its contract says nullopt means the path does not exist. This can silently mask real engine errors (e.g., 500/401) and cause --follow-link to fall back to copying the symlink instead of failing fast.
This issue also appears on line 451 of the same file.
src/windows/wslcsession/DockerHTTPClient.cpp:455
- On a 200 response, a missing X-Docker-Container-Path-Stat header indicates an unexpected daemon/proxy behavior and should not be treated the same as "path does not exist"; otherwise --follow-link can silently do the wrong thing.
const auto header = response["X-Docker-Container-Path-Stat"];
if (header.empty())
{
return std::nullopt;
}
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
DockerHTTPClient::StatArchivePath currently treats all non-200 responses (and missing stat headers) as “not found,” which can mask real engine/protocol failures and should be tightened to surface errors correctly.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
src/windows/wslcsession/DockerHTTPClient.cpp:455
- If the response is 200 but missing the
X-Docker-Container-Path-Statheader, returningnulloptwill make the caller treat it as "not a symlink" and proceed, which can mask a protocol/engine mismatch. It should be treated as an unexpected error so callers can fail fast with a clear diagnostic.
const auto header = response["X-Docker-Container-Path-Stat"];
if (header.empty())
{
return std::nullopt;
}
src/windows/wslcsession/DockerHTTPClient.h:150
- The comment for
StatArchivePathsays it returnsnulloptwhen the path does not exist, but the implementation also treats other non-200 responses (and a missing stat header) asnullopt. Update the comment to reflect the actual contract (e.g., 404 =>nullopt, other errors => exception) to avoid misleading future callers.
// Reads the X-Docker-Container-Path-Stat header for a container path. Returns nullopt if the path does not exist.
std::optional<common::docker_schema::ContainerPathStat> StatArchivePath(const std::string& ContainerID, const std::string& Path);
- Files reviewed: 15/15 changed files
- Comments generated: 1
- Review effort level: Lite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
There is a confirmed path-handling bug in the container cp local→container flow where stripping a trailing separator can turn a drive-root parent directory (e.g. C:\) into C:, altering tar.exe -C behavior and potentially breaking copies from drive roots.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/windows/wslc/tasks/ContainerTasks.cpp:387
- When SRC_PATH is at a drive root (e.g. "C:\file"), absPath.parent_path() becomes "C:\". The subsequent trailing-separator trimming in this block turns that into "C:", which changes tar.exe -C semantics ("C:" means the current directory on that drive) and can break copies from the drive root. Preserve root paths when stripping trailing separators.
src/windows/wslc/services/ContainerService.h:68 - This comment says ResolveContainerSymlink returns nullopt only when the path is not a symlink, but the underlying service method also returns nullopt when the path does not exist. Update the comment to match the actual behavior.
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The new container-side symlink resolution currently holds a shared lock across network I/O, and the new local→container follow-link behavior lacks corresponding automated coverage.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/windows/wslc/tasks/ContainerTasks.cpp:384
- The new --follow-link behavior for local → container relies on std::filesystem::canonical() to resolve the source path before archiving. There is E2E coverage for container → local symlink following, but no test coverage for this local → container path-resolution behavior, so regressions here would be easy to miss.
src/windows/wslcsession/WSLCContainer.cpp:2688 - ResolveArchiveSymlink holds the container shared lock while issuing a Docker API request (StatArchivePath). This can block other container operations longer than necessary; other archive operations (e.g., DownloadArchive/UploadArchive) release the lock before doing potentially long I/O.
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
ResolveArchiveSymlink currently resolves only a single symlink hop, which can make --follow-link copy an intermediate symlink instead of the final target for chained links.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 15/15 changed files
- Comments generated: 1
- Review effort level: Lite
# Conflicts: # src/windows/service/inc/wslc.idl # src/windows/wslc/services/ContainerService.h
There was a problem hiding this comment.
🟡 Changes recommended
There is a correctness issue in a new E2E test (missing container setup) and the new stat request should use HEAD to avoid unnecessary archive streaming work.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 15/15 changed files
- Comments generated: 2
- Review effort level: Lite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Three moderate issues remain in ContainerTasks.cpp, and the interface-change description needs correction.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
src/windows/service/inc/wslc.idl:587
- The PR description says a new
IWSLCContainer::ResolveArchiveSymlinkinterface method was added, but this IDL changes onlyDownloadArchiveand noResolveArchiveSymlinkdeclaration or implementation exists in the repository. Please update the description so the documented interface change matches the patch.
// When FollowLink is set and SrcPath names a symbolic link, the archive is taken from the link's target.
HRESULT DownloadArchive([in, string] LPCSTR SrcPath, [in] BOOL FollowLink, [in] WSLCHandle OutHandle);
src/windows/wslc/tasks/ContainerTasks.cpp:518
- The staging root is created under the system temp directory without checking whether it is inside
resolved. A valid source symlink can point at%TEMP%(or a directory containing that staging root); the recursive copy then targets a newly created child of its own source, so it can recurse into the staging tree or fail after excessive path/space use. Choose a staging location outside the resolved source tree, or avoid recursive filesystem staging for this case.
stagingDir = MakeStagingDirectory(std::filesystem::temp_directory_path());
std::error_code copyError;
std::filesystem::copy(
resolved,
stagingDir / absPath.filename(),
std::filesystem::copy_options::recursive | std::filesystem::copy_options::copy_symlinks,
- Files reviewed: 13/13 changed files
- Comments generated: 2
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Critical destination path traversal and additional symlink-handling correctness issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
src/windows/service/inc/wslc.idl:587
- The detailed description says this change adds
IWSLCContainer::ResolveArchiveSymlink, but the interface change here instead modifiesDownloadArchiveand noResolveArchiveSymlinkmethod is present. Please reconcile the PR description with the actual API change so consumers and reviewers are not misled.
// When FollowLink is set and SrcPath names a symbolic link, the archive is taken from the link's target.
HRESULT DownloadArchive([in, string] LPCSTR SrcPath, [in] BOOL FollowLink, [in] WSLCHandle OutHandle);
src/windows/wslc/tasks/ContainerTasks.cpp:520
- When a local symlink to a directory is passed with a trailing separator,
std::filesystem::absolutepreserves that separator andabsPath.filename()is empty. The staged copy then targetsstagingDiritself, so the archive is named after the generated staging directory (or the copy fails) instead of the requested link name. Derive the source leaf after removing trailing separators before using it here.
stagingDir / absPath.filename(),
std::filesystem::copy_options::recursive | std::filesystem::copy_options::copy_symlinks,
copyError);
THROW_HR_IF_MSG(HRESULT_FROM_WIN32(copyError.value()), !!copyError, "Failed to copy from: %ls", resolved.c_str());
src/windows/wslcsession/WSLCContainer.cpp:1859
- The added E2E coverage only exercises an absolute link target. The
resolved.front() != '/'branch is the implementation for common relative links such asln -s ../target link; without a test for that case, a regression in rebasing against the source's parent would leave-Lbroken while the current tests still pass. Add an E2E case with a relative container symlink and verify the copied content and requested name.
if (resolved.front() != '/')
{
const auto separator = effectivePath.find_last_of('/');
if (separator != std::string::npos)
{
src/windows/wslcsession/WSLCContainer.cpp:1860
- The relative-target join preserves
./..segments instead of normalizing them like Docker'sfilepath.Join/SplitPathDirEntry. For a link whose target isdir/.(or.), the GET is made with a trailing/.; Docker would archive the directory under its basename, but this can produce an archive rooted at.. The laterstaged.size()heuristic can then rename a single child to the link name (or move nothing for an empty directory), yielding a file or missing directory instead of the followed directory. Normalize the POSIX path beforeGetArchiveand add coverage fordir/.and empty/single-entry targets.
const auto separator = effectivePath.find_last_of('/');
if (separator != std::string::npos)
{
resolved = effectivePath.substr(0, separator + 1) + resolved;
- Files reviewed: 13/13 changed files
- Comments generated: 1
- Review effort level: Lite
…X names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved moderate correctness, performance, and coverage issues remain.
Review details
Suppressed comments (7)
Previously missed (1) — in code that hasn't changed since the last review.
src/windows/wslcsession/WSLCContainer.cpp:1860
- The new relative-link rebasing branch is not covered by the E2E tests: the added container-to-local test creates an absolute target (
ln -s /tmp/linktarget.txt ...). Add a case with a relative target such asln -s linktarget.txt thelink.txtand verify that the target contents are copied under the link name, since parent-path joining is the behavior introduced here.
src/windows/service/inc/wslc.idl:587
- The PR description says a new
IWSLCContainer::ResolveArchiveSymlinkmethod is being added, but this interface instead changesDownloadArchiveand noResolveArchiveSymlinkmethod exists. Please update the description to reflect the actual interface/API change so the documented contract matches the shipped surface.
// When FollowLink is set and SrcPath names a symbolic link, the archive is taken from the link's target.
HRESULT DownloadArchive([in, string] LPCSTR SrcPath, [in] BOOL FollowLink, [in] WSLCHandle OutHandle);
src/windows/wslc/commands/ContainerCpCommand.cpp:19
- The PR description claims a
ContainerCpCommand_HasFollowLinkArgumentWithDockerAliasunit test and 13 parser cases, but the repository contains no such unit test and this diff adds only 11 parser cases (all undercontainer cp). Please add the claimed coverage or update the validation section to match the submitted changes.
Argument::Create(ArgType::FollowLink),
src/windows/wslc/tasks/ContainerTasks.cpp:613
- With
--follow-link, this stages every container-to-local copy, including ordinary regular files and directories that are not symlinks. A largecp -L container:/regular-dir existing-dirtherefore materializes a second full tree before moving it, adding unnecessary I/O and disk usage and potentially failing with disk-full where direct extraction would succeed. Use the stat result to stage only when the source is actually a symlink, or rebase the archive stream without staging.
auto extractRoot = absTarget;
if (followLink)
{
stagingDir = MakeStagingDirectory(absTarget);
extractRoot = stagingDir;
src/windows/wslc/tasks/ContainerTasks.cpp:76
- This fallback silently loses the requested basename for valid POSIX container names containing
:or\\(for example, a link/tmp/link:name):requestedNamebecomes empty, so the target's basename is used at line 664 instead of the link's name. Since those characters cannot be represented as ordinary Windows filenames, reject the operation with a clear error (or otherwise handle it explicitly) rather than copying to a different name.
return name.find_first_of("\\/:") == std::string::npos ? name : std::string{};
src/windows/wslc/tasks/ContainerTasks.cpp:523
- The local-to-container directory-symlink path is also untested: the new E2E case only exercises the
dereferencefile branch, while this staging logic is what preserves the link name and nested symlinks when the target is a directory. Add a directory-link case (including at least one nested link) before relying on this path.
if (std::filesystem::is_directory(resolved))
{
// The archive has to carry the link's name while holding the target's tree, and tar.exe
// cannot rename entries, so the tree is staged under that name with its own links intact.
stagingDir = MakeStagingDirectory(std::filesystem::temp_directory_path());
src/windows/wslcsession/WSLCContainer.cpp:1860
- When
SrcPathends in/,find_last_of('/')returns that trailing separator, so a relative target for/dir/link/is rebased to/dir/link/targetinstead of/dir/target. Since cp accepts trailing separators, trim trailing separators (while preserving/) before finding the link's parent.
const auto separator = effectivePath.find_last_of('/');
if (separator != std::string::npos)
{
resolved = effectivePath.substr(0, separator + 1) + resolved;
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Lite
David Bennett (dkbennett)
left a comment
There was a problem hiding this comment.
This is fine for now, but the entire container cp methods are way too low level to be at the task layer and should be refactored. Several of the helpers should probably go into common as well along with unit tests for them.
Happy to make a follow up PR to address this if you want me to |
Looking at some of copilots latest comments, I've changed my mind and would like to see those addressed first.
David Bennett (dkbennett)
left a comment
There was a problem hiding this comment.
Please address the latest comments from Copilot about correctness.
Recommend moving some of these filesystem methods into common and adding unit tests for them to verify the behavior.
As a followup, I think the cp method in general needs to be refactored into the service, way too much stuff going on in the tasks. If you want to do that feel free, otherwise I'll probably do it in a few days.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Follow-link staging can fail for valid POSIX target names before requested-name rebasing.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
src/windows/wslc/tasks/ContainerTasks.cpp:624
- This predicate does not cover all names that the Win32 filesystem cannot represent: a component ending in
.or a space is normalized, and DOS device names such asCON/NULare reserved even without invalid characters. A container symlink namedfoo.orCONcan therefore be copied under a different object (or a device) instead of retaining the requested name. Validate the complete Windows filename-component rules before callingMoveOver(and add cases for these names).
constexpr std::wstring_view reserved = L"<>:\"/\\|?*";
for (const auto character : requestedName)
{
if (character < L' ' || reserved.find(character) != std::wstring_view::npos)
{
- Files reviewed: 18/18 changed files
- Comments generated: 1
- Review effort level: Lite
…p-follow-link # Conflicts: # test/windows/wslc/WSLCCLICommandUnitTests.cpp
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate correctness issues remain in name handling, directory merging, and archive staging.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
src/windows/wslc/tasks/ContainerTasks.cpp:608
- The new container-to-local
--follow-linkstaging/move path is not exercised by the added E2E tests: the container-to-local cases only follow file links, while the directory-tree case is local-to-container. Please add a container directory-link case that verifies the requested destination name and nested links, since that is the behavior implemented by this block and is where archive shape andMoveOverhandling can regress.
if (followLink)
{
// Moving entries invalidates an open directory iterator, so the listing is taken first.
std::vector<std::filesystem::path> staged;
for (const auto& entry : std::filesystem::directory_iterator(stagingDir))
{
staged.push_back(entry.path());
src/windows/wslc/tasks/ContainerTasks.cpp:85
- When
renamefails because the destination directory already exists, this fallback does not mergeFromintoTo:std::filesystem::copytreats an existing directory destination as a container and copies the source underTo/From.filename(). Copying a followed directory link into an existing destination can therefore create an extra nested directory instead of matching tar's merge semantics. Enumerate and merge the source children intoTowhile retaining the type checks.
std::filesystem::copy(
From,
To,
std::filesystem::copy_options::recursive | std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::copy_symlinks,
copyError);
src/windows/wslc/tasks/ContainerTasks.cpp:578
- If the symlink points to a POSIX-only target basename such as
a:bwhile the requested link name is valid, this staging path still askstar.exeto extract the archive under the target's basename before the later rebase. Windows tar cannot create that entry, so--follow-linkfails even though the copy could be named after the valid link. The archive needs to be extracted under a sanitized staging name or have its entry names rewritten before extraction.
auto extractRoot = absTarget;
if (followLink)
{
stagingDir = wsl::windows::common::filesystem::MakeStagingDirectory(absTarget);
extractRoot = stagingDir;
}
- Files reviewed: 18/18 changed files
- Comments generated: 1
- Review effort level: Lite
David Bennett (dkbennett)
left a comment
There was a problem hiding this comment.
This looks good enough to me to checkin. The correctness issues from copilot stem from possible failure cases and robustness in the validation, which can be addressed in a followup. The test coverage looks good as does the refactor.
I will double check the latest copilot comment, but a few of those are incorrect and ours behaves the same as docker |
We behave the same as docker for all of the cases presented by copilot |
* Fix tracked routes being collapsed by incomplete comparison (#41393) Mirrored route tracking used an incomplete comparator that considered only route class, destination address, and metric. Distinct routes with different prefix lengths or next hops could therefore be treated as equivalent and silently omitted. The fix preserves the existing route-class ordering while using the complete EndpointRoute comparison for route identity. Regression tests cover prefix length, next hop, metric, exact duplicates, and dependency ordering. * Add github issue suggestion in user visible error (#41432) This PR adds a "search or file issue on github" suggestion in all user visible errors to: Help users find solutions faster. Collect more user reported issues to help reliability improvements. This PR also updates all tests checking the error message to use a unified function for creating the expected error message. * Match Docker output for prune operations and container inspection (#41430) Cleanup various miscellaneous output divergences Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * CLI: Mount PR followup & fix two docker parser bugs (#41436) * Invalid the TestImageRegistry cache after running prune --all (#41442) * Fix unit test build failure on arm64 (#41437) * Solve various issues found by verifier (#41445) * Save state * Save state * Save state * Cleanup diff * Add a command line option to run the tests under verifier (#41440) * Save state * Save state * Save state * Add a /verifier option to run-tests.ps1 to run the test under verifier * Localization change from build: 155859879 (#41450) Co-authored-by: WSL localization <noreply@microsoft.com> * Fix various arm64 test failures (#41444) * Fix various arm64 test failures * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix LF * Format * Cleanup diff --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix unity build ODR collisions on duplicate file-local constants (#41446) * CLI: Align alias listing with docker, Apple and other CLIs (#41439) * Align container list format with Docker specifications (#41375) wslc: match column order, status text, and json shape for container list Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Localization change from build: 156161979 (#41479) Co-authored-by: WSL localization <noreply@microsoft.com> * wslc: alias -f to --format on inspect commands for docker parity (#41463) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * CLI: Add explicit per-command argument overrides (#41478) * archlinux: Release 2026.09.01.176721 (#41493) This is an automated release [1]. [1] https://gitlab.archlinux.org/archlinux/archlinux-wsl/-/blob/main/.gitlab-ci.yml * Fix WSLC parser unit test argument overrides (#41496) Update the remaining parser test call site to use ArgumentOverrides after the Argument::Create API change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Copilot-Session: eb7946ff-cc0b-4ff1-a059-571334b1c988 * Fix systemd-tmpfiles failure on systemd v261+ with systemd boot disabled (#41491) In systemd v261, the systemd's tmpfiles.d/x11.conf was changed from D! to D. systemd/systemd@5474cad. This means the systemd-tmpfiles command will try to execute it when called out of the boot sequence. In the linked issue's case, it's called by the post install script by dpkg. This will fail as the wsl override is not in place when systemd is disabled. This PR enables the wsl override file generation for all distros with wslg enabled, regardless of if the distro boots with systemd. * Fix unvalidated TerminalProfileSize during distribution import (#41495) * Fix unvalidated TerminalProfileSize when importing a distribution _ProcessImportResultMessage constructed the terminal profile string_view using the message-supplied TerminalProfileSize without validating it against the received buffer length. Use the bounds-checked two-argument span::subspan() overload (matching the existing ShortcutIconSize handling a few lines above) so an inconsistent size value throws instead of producing a string_view that runs past the end of the buffer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35281c30-3d08-4f05-8c84-2ce4711023d5 * format source --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35281c30-3d08-4f05-8c84-2ce4711023d5 * Bump WSL DeviceHost to 1.2.62 (#41499) * Bump WSL DeviceHost to 1.2.62 Co-authored-by: damanm24 <9593793+damanm24@users.noreply.github.com> * Correct WSL DeviceHost version to 1.2.62-0 Co-authored-by: damanm24 <9593793+damanm24@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: damanm24 <9593793+damanm24@users.noreply.github.com> * Enable unity build repo-wide via WSL_UNITY_BATCH_SIZE (#41441) Enable unity build repo-wide via WSL_UNITY_BATCH_SIZE Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add wslc system info command (#41408) * updated source code paths in the wslservice tab (#41509) Co-authored-by: Tega Ajise <tegaajise@Tegas-MacBook-Pro-2.local> * Harden Windows macros against dangling-else ambiguity (#41513) * Harden Windows macros against dangling-else ambiguity * Harden Windows macros against dangling-else ambiguity * Fix dangling-else bugs in FAIL_FAST_IF and EMIT_USER_WARNING macros (#41504) * Fix dangling-else bugs in FAIL_FAST_IF and EMIT_USER_WARNING macros Both macros were bare if-statements without do/while(0) guards, causing the dangling else problem when used as a single statement under an if. * Fix dangling-else bugs in FAIL_FAST_IF and EMIT_USER_WARNING macros * harden EMIT_USER_WARNING macro on Windows against dangling-else * Fix dangling-else bugs in FAIL_FAST_IF and EMIT_USER_WARNING macros * Fix intermittent ImportDistroInvalidTar test failures (#41490) * Fix test to be more resilient * Make WSL1 more lenient, make WSL2 exact * Wslc events (#40971) * Revert "Mount plugin folders on behalf of the user owning the wsl session" (#41331) (#41515) Temporarily reverting the identity-based plugin folder mount change (both the WslCoreVm.cpp behavior change and the accompanying MountFolderAccess test coverage) introduced in #41331. This reverts commit 78b9cf2. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add container restart runtime support (#41454) * Fix p9 drvfs read only mount regression (#41487) #41129 introduces a regression where the ";ro" option is passed to the host for p9 shares. However, that option is not supported by the p9 server and causes the mount to fail. This PR removes the special handling of "ro" in the common parser. And use MountParseFlags to add the required virtio option. * Fix WSLC Plan9 mount and image-build failures (#41535) * Fix WSLC Plan9 mounts using a per-user server * remove debug code * wslc: add --size to inspect for docker parity (#41489) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump actions/deploy-pages in the github-actions group (#41537) Bumps the github-actions group with 1 update: [actions/deploy-pages](https://github.com/actions/deploy-pages). Updates `actions/deploy-pages` from 5.0.0 to 5.0.1 - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](actions/deploy-pages@cd2ce8f...368f825) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add wslc container restart command (#41435) * init: fix dhcpcd option name in bridged-mode config (#41538) * init: fix dhcpcd option name in bridged-mode config dhcpcd has no option named "broadcast"; option 28 is "broadcast_address". dhcpcd 10 rejects the whole "option" line, so DNS servers, domain, search list, hostname and MTU were never requested from the DHCP server, leaving /mnt/wsl/resolv.conf without nameservers on servers that honour the PRL. * init: apply clang-format to dhcpcd config string The longer broadcast_address option name pushed the literal past the 130-column limit in .clang-format. Split it as clang-format does; the concatenated value is unchanged. * Update SLE15SP7 [QU5] (#41506) * Host plugin Plan9 shares as the session user (#41548) * Host plugin Plan9 shares as the session user Use a dedicated per-user Plan9 server for plugin folder mounts so host filesystem permissions are preserved without relying on HCS-managed share identity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c20b1da0-c613-489d-92a3-9c27527c0c54 * Simplify plugin Plan9 port plumbing Use the fixed plugin port directly in mini_init and mirror the existing per-user Plan9 server lifecycle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c20b1da0-c613-489d-92a3-9c27527c0c54 * Recreate stopped plugin Plan9 servers Recreate the per-user server before adding a share when its process is no longer running. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c20b1da0-c613-489d-92a3-9c27527c0c54 --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Copilot-Session: c20b1da0-c613-489d-92a3-9c27527c0c54 * Fix virtiofs bind mounts exposing files as root-owned (#40719) (#40733) * Fix virtiofs bind mounts exposing files as root-owned (#40719) Add the 'metadata' option to virtiofs shares created via HcsVirtualMachine::AddShare (the WSLC/Docker container path). Without this option, the virtiofs device host cannot persist per-file uid/gid in NTFS extended attributes, so all files default to uid=0/gid=0 regardless of the creating user. This matches the behavior of the regular distro mount path (WslCoreVm::AddVirtioFsShare) which receives metadata/uid/gid options from the Linux init's ConvertDrvfsMountOptionsToPlan9. Also adds a regression test (WindowsMountsVirtioFsFileOwnership) that verifies file ownership is preserved on virtiofs mounts. Fixes #40719 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address code review feedback - Add inline comment explaining why 'metadata' is required - Use unique mount point (/virtiofs-ownership-test) to avoid test interference - Use numeric UID (su '#65534') instead of username to avoid environment dependency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: use 'nobody' user instead of numeric UID in virtiofs ownership test The su command with numeric UID syntax ('#65534') requires the user to exist in /etc/passwd. Use the 'nobody' account directly since it is already available in the test VHD. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: wrap long test command strings to satisfy clang-format 130-col limit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: unmount inline with VERIFY_SUCCEEDED to match file convention Replace the scope_exit unmount cleanup with an inline VERIFY_SUCCEEDED call at the end of the test, matching every other mount test in this file (e.g. WindowsMountsVirtioFsShareReuse). This also asserts the unmount HRESULT rather than silently swallowing it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9213b7da-c5c8-4d9c-89ab-e80048d288a2 * Fix Windows mount test API calls Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Blue <OneBlue@users.noreply.github.com> Copilot-Session: 9213b7da-c5c8-4d9c-89ab-e80048d288a2 * wslc: match docker prune semantics (confirmation prompt, -f aliases --force) (#41455) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * wslc: add --all to image list for docker parity (#41456) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * wslc: add --details to container logs for docker parity (#41467) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: JohnMcPMS <johnmcp@microsoft.com> * Localization change from build: 157218885 (#41559) Co-authored-by: WSL localization <noreply@microsoft.com> * Don't fail the installation if DeprovisionMsix() fails (#41453) * Don't fail the installation if DeprovisionMsix() fails * Apply PR feedback * Notice change from build: 157227119 (#41564) Co-authored-by: WSL notice <noreply@microsoft.com> * wslc: add --size to container list for docker parity (#41477) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * wslc: add --all-tags to push for docker parity (#41500) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix build issue for wslsettings, and add more logging to the pipelines (#40388) * Validate variable-length message strings (#41567) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Copilot-Session: 3086bdaf-bc43-4fed-88d1-3a95a21fd14e * Fix WSLC fallback gateway collision (#41547) Avoid selecting the guest IPv4 address as its synthesized default gateway when the host adapter does not expose one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Copilot-Session: ce168659-cb9d-4f0e-8fd1-2834d065ba9d * Reduce distro termination log noise (#41541) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Copilot-Session: adf24228-67c0-452e-9cc2-c698a8d7b3b7 * Localization change from build: 157277214 (#41571) Co-authored-by: WSL localization <noreply@microsoft.com> * CLI: Add global options to root help, adjust options usage (#41534) * Add global options to root help, adjust options usage to match CLI conventions * Trim some unnecessary test code * Localization change from build: 157310027 (#41574) Co-authored-by: WSL localization <noreply@microsoft.com> * wslc: add docker --quiet to image load, image push and container cp (#41466) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tear down the stale plugin Plan9 server before recreating it (#41565) When the per-user plugin Plan9 server is no longer running, the previous instance was dropped without a Teardown call, so it could still hold the Plan9 port when the replacement tries to bind it. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0d028ad4-fe5e-4ef1-9832-18ee0e4825cc * Use a locally imported version of docker/dockerfile instead of downloading it from the default registry (#41575) * Use a locally imported version of docker/dockerfile instead of downloading it from the default registry * Cleanup diff * Use canonical path in VolumeMount_Parse_ReturnExpectedResult (#41577) * Document WSL security model (#41556) * Document WSL security model Clarify WSL trust boundaries and explain that configuration settings do not establish a sandbox. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c77eaf09-799a-416e-b4b4-19f37a4201ef * Update WSL security model explanation Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Clarify WSL isolation guidance Distinguish functional distribution separation from a security boundary and recommend a separately managed VM for untrusted workloads. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c77eaf09-799a-416e-b4b4-19f37a4201ef * Document shared WSL VM trust model Clarify that elevated and non-elevated sessions can share utility VM state and are not separate guest security boundaries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c77eaf09-799a-416e-b4b4-19f37a4201ef --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Copilot-Session: c77eaf09-799a-416e-b4b4-19f37a4201ef * Bump GitPython to 3.1.59 (#41580) Resolves open Dependabot alerts for GitPython <= 3.1.58 in distributions/requirements.txt and tools/devops/requirements.txt. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6fcfe0f1-6be5-4913-ab36-71823443cc36 * Remove two noisy warnings from the distro validation scripts (#41579) * Remove two noisy warnings from the distro validation scripts * Cleanup diff * Fix formatting of USR_SHARE_WSL assignment Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix activating a stopping service treated as OOM (#41460) The current factory function for LxssUserSession and WSLCSessionManager translates the error code CO_E_SERVER_STOPPING to S_FALSE. Which combined with *ppCreated == NULL causes COM to treat this as an OOM. Leading to error messages like this when activating a stopping service: Not enough memory resources are available to complete this operation. Error code: Wsl/E_OUTOFMEMORY This PR removes this conversion. So, COM actually retries when the service is stopping. And returns CO_E_SERVER_EXEC_FAILURE if the retry times out. * Set distributionStartTimeout to 2 minutes in the tests to solve distribution start timeouts errors (#41583) * Set distributionStartTimeout to 2 minutes in the tests to solve distribution start timeouts errors * Update tests * Localization change from build: 157504221 (#41602) Co-authored-by: WSL localization <noreply@microsoft.com> * Enable RedirectionGuard process mitigation (#41542) Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Updates Ubuntu latest LTS images (#41465) * Updates Ubuntu 26.04 to the .1 release Just announced today. Also published the up-to-date Ubuntu package to MS Store, but I'm not referencing it here. * Updates the 24.04.5 images just released today. * Use the cdimages.u.c host for consistency --------- Co-authored-by: Carlos Nihelton <carlos.nihelton@canonical.com> * diagnostics: improve collect-wsl-logs for analysis (summary.json, README, profile info, WSL/guest state) (#40776) * diagnostics: record capture profile in collected logs collect-wsl-logs.ps1 did not record which WPR profile was used for a capture. When analyzing an archive (e.g. a networking-only capture that lacks the WSL core trace providers), there was no way to tell which profile produced it without inferring it from the provider mix. Write a collection-info.txt into the log folder capturing the selected LogProfile, the mapped WPRP profile and file, the Dump and RestartWslReproMode switches, and the collection timestamp. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * diagnostics: collect WSL version, guest state, and drop empty dumps Add three further debugging improvements to collect-wsl-logs.ps1: - Collect wsl --version / --status / --list --verbose into wsl-info.txt instead of forcing analyzers to infer the version and distro layout from the appx package and registry. - Collect guest-side state (dmesg, free, uptime, ulimit, pid_max, threads-max, process/thread counts, top RSS) into linux_diagnostics.log after the repro. This is the data needed to diagnose in-distro failures such as 'Resource temporarily unavailable' (EAGAIN) from resource limits. - Remove 0-byte dump files left behind when MiniDumpWriteDump fails, so the archive only contains real dumps. Also set WSL_UTF8 and the console output encoding so wsl.exe output is captured to the log files readably. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * diagnostics: add summary.json and README.md to log archive Make collected log archives easier to analyze (by a human or an agent) without having to run tools or infer state from individual artifacts: - summary.json: machine-readable overview of the capture - profile, WSL/Windows versions, networking mode, installed distributions and their state, .wslconfig presence, and an inventory of non-empty dumps. - README.md: an index of the archive contents describing each file, plus a note on how to decode logs.etl and how to tell when a non-default log profile was used. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * diagnostics: address PR feedback (utf8 wsl-info, wsl.exe timeouts, slimmer summary.json) - Write wsl-info.txt as UTF-8 instead of the PS5.1 default UTF-16LE. - Guard every newly-added wsl.exe call (wsl-info and guest diagnostics) with a timeout via a background job so a deadlocked service or bad VM state cannot hang log collection. - Drop the duplicated distro-registry enumeration and .wslconfig networkingMode parsing from summary.json; that state is readily derived from HKCU.txt and the archived .wslconfig. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * diagnostics: drop wsl-info.txt and guest diagnostics collection Remove the unconditional wsl.exe calls this PR introduced (wsl-info.txt and linux_diagnostics.log) along with the now-unused timeout helper, per review feedback that the log collection script should not call wsl.exe (which can hang if the service is deadlocked or the VM is in a bad state). Pre-existing networking-profile wsl.exe calls are left untouched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * collect-wsl-logs: drop bundled summary.json and README per review OneBlue noted the agent-readable index and the summary values are redundant in every archive (an analyzer can derive them from the archive contents). Keep only collection-info.txt, which records non-derivable capture provenance (which WPR profile/switches were used). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move security model under technical documentation (#41605) * Move security model under technical documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 43c7b692-ffc5-43bf-88b9-ecc927a1aa55 * Move security model into technical documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 43c7b692-ffc5-43bf-88b9-ecc927a1aa55 --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Copilot-Session: 43c7b692-ffc5-43bf-88b9-ecc927a1aa55 * Fix typos (#41589) * Don't stop parsing linux config files on invalid lines (#41606) * wslc: add --all-tags to pull for docker parity (#41494) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Added WSL container to OOBE (#41402) * Pre merge Localization strings prior to GA (#41585) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * wslc: keep image list json CreatedSince locale-invariant (#41609) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Localization change from build: 157621275 (#41610) Co-authored-by: WSL localization <noreply@microsoft.com> * Warn on an escaped CR in wsl.conf instead of dropping it silently (#41591) `case '\r': break;` made a backslash before a CR the only unrecognised escape the parser accepts without a diagnostic. In a CRLF file that left the value truncated at the backslash and the remainder parsed as its own line, with nothing reported. Letting it fall into the default case gives the same MessageConfigInvalidEscape warning as any other bad escape. As with those, the line is then discarded rather than kept truncated. * Localization change from build: 157667513 (#41614) Co-authored-by: WSL localization <noreply@microsoft.com> * Set a restricted ephemeral port range for test case ConsommeTests::PortZeroBindIsTracked (#41616) * repo: modify CODEOWNERS to change wsl-maintainers to wsl-reviewers team (#41617) Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Localization change from build: 157736413 (#41621) Co-authored-by: WSL localization <noreply@microsoft.com> * CLI: Global option scopes for nested commands (related to compose support) (#41546) * Change default relay buffer size to 64KiB (#41603) The current logic uses a fixed 4KiB buffer for stdio relay on the Windows side. And uses an initial 4KiB buffer for stdio relay on the Linux side, which grows only if the message won't fit. This can severely limit the relay performance in some situations. For example, in #41572, when redirecting stdout to a SMB share. This PR increases thedefault relay buffer size to 64KiB. Which shows significant performance improvements according to buffer size tests. * Fix redirect stdout and stderror to the same file overlapping (#41611) Currently the stdout and stderr relay tracks the output offset separately. And when redirected to the same file, the writes could overlap. This PR uses the append mode for overlap io writes instead of the separate offsets in the relay. So, the file write offset is correctly tracked by the system. * move installer log collection after repro (#41620) The installer log files were collected before the log collection starts. Which will miss the user repro. This PR moves it after the user repro. * wslc: add --digests to image list for docker parity (#41457) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Localization change from build: 157843092 (#41630) Co-authored-by: WSL localization <noreply@microsoft.com> * Explicitely return SOCKET from WSLCPluginAPI_ProcessGetFd() (#41626) * In mirrored mode, handle route update as add instead of replace (#41391) In mirrored mode, handle route update as add instead of replace Remove usage of route update as it can overwrite routes on other interfaces. add tests that verify host routing changes are correctly reflected in Linux --------- Co-authored-by: Catalin-Emil Fetoiu <cfetoiu@microsoft.com> * Add wslc events command (#41608) * Don't fail the UnitTests::Warnings test case if GlobalSecureAccess VPN is running (#41638) * Don't fail the UnitTests::Warnings test case if GlobalSecureAccess VPN is running * Cleanup diff * Fix test * Wait for wslservice to be started when running tests (#41639) * Wait for wslservice to be started when running tests * Fail on timeout * Improve check * Format * Create new namespaces for distro cgroups (#41512) In 2.9.8, the distro processes are separated into their own cgroups. But they remain in the same cgroup namespace. That caused compatibility issues with softwares that assume a fixed systemd cgroup layout. For example, rootless docker and nerdctl. Fixes on Moby and nertctl are being worked on. However, to avoid issues with other software, WSL's cgroup handling should also be improved. This PR creates new cgroup namespaces for the distros. So, to the non-critical distro processes, the systemd cgroup layout stays the same as before. This PR also introduces a cgroup structure change to accomplish this. The systemd init is moved from wsl-user/distro-N/systemd to wsl-user/distro-N. And the initialization of the distro-N controllers is handled by systemd instead. The processes are also moved into the non-systemd cgroup in systemdless distros. This makes sure that sub-group controllers can be enabled in the distro root. Cgroup v2 is now enforced when distro isolation is enabled. Instead of constructing an unusable cgroup v1 layout when cgroup v1 and distro isolation are both enabled. * Fix unnecessary delay in the port tracking loop (#41472) Before this change, an additional 10ms delay was added in the port tracker loop presumably to make the main loop slower than the worker loop. So, the worker result does not get super dated. This is not reliable. And the 10ms delay also slows down every consecutive bind call. This PR refactors the thread synchronization method. So, the timing between those two loops is more deterministic. And removes the performance penalty for all bind calls. * Localization change from build: 157954210 (#41643) Co-authored-by: WSL localization <noreply@microsoft.com> * wslc: add --follow-link to cp (#41501) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Disable unstable bind cap test and re-enable Loopback test that was incorrectly disabled- #41648 (#41648) Co-authored-by: Catalin-Emil Fetoiu <cfetoiu@microsoft.com> * Move objects shared by wslc.exe and the tests to src/windows/common (#41619) * Move objects shared by wslc.exe and the tests to src/windows/common Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Link yaml-cpp, advapi32 and Crypt32 into common Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Drop yaml-cpp, advapi32 and crypt32 from wslclib now that common provides them Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move wslc code in common under common/wslc and namespace CLI types as wslc::cli Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix various protocol parsing issues (#41637) * Save state * Use proper arrays * Format * Add new tests * Cleanup tests * Cleanup diff * Format * Apply PR feedback * Apply PR feedback * Format * Apply PR feedback * Move logging call * Apply PR feedback * Apply PR feedback * Localization change from build: 157980831 (#41647) Co-authored-by: WSL localization <noreply@microsoft.com> * Add WSLC network lifecycle events (#41576) * Add WSLC network lifecycle events * Localize pending network prune errors * Fix network operation wait ownership and cleanup ordering * Fix prune event correlation after timed-out * Fix network event rollback and timeout recovery * Format * Forward Docker network events directly * Restore lock_guard after removing event waits * fix test * align event stream test helpers after merge * feedback * Fix potential out of bound access when pretty-printing string field in init messages (#41664) * Localization change from build: 158197536 (#41662) Co-authored-by: WSL localization <noreply@microsoft.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Feng Wang <wang6922@outlook.com> Co-authored-by: ggarzia-MSFT <gavingarzia@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: David Bennett <dbennett-msft@outlook.com> Co-authored-by: WSL localization <noreply@microsoft.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: beena352 <beenachauhan@microsoft.com> Co-authored-by: Arch Linux Technical User <65091038+archlinux-github@users.noreply.github.com> Co-authored-by: Ben Hillis <benhillis@gmail.com> Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: damanm24 <9593793+damanm24@users.noreply.github.com> Co-authored-by: tega-ajise <tegjise15@gmail.com> Co-authored-by: Tega Ajise <tegaajise@Tegas-MacBook-Pro-2.local> Co-authored-by: Eamon <eamon112009@gmail.com> Co-authored-by: Kevin Vega <40717198+kvega005@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: DesertRatUa <desertratua@gmail.com> Co-authored-by: Scott Bradnick <84082961+sbradnick@users.noreply.github.com> Co-authored-by: Stephen Halter <shalter+msft@microsoft.com> Co-authored-by: JohnMcPMS <johnmcp@microsoft.com> Co-authored-by: Flor Chacón <14323496+florelis@users.noreply.github.com> Co-authored-by: Carlos Nihelton <cnihelton@ubuntu.com> Co-authored-by: Carlos Nihelton <carlos.nihelton@canonical.com> Co-authored-by: Anton Kesy <antonkesy@gmail.com> Co-authored-by: Craig Loewen <crloewen@microsoft.com> Co-authored-by: Leo Camus <leo.camus23@gmail.com> Co-authored-by: FetoiuCatalin <fetoiucatalin@gmail.com> Co-authored-by: Catalin-Emil Fetoiu <cfetoiu@microsoft.com> Copilot-Session: eb7946ff-cc0b-4ff1-a059-571334b1c988 Copilot-Session: 35281c30-3d08-4f05-8c84-2ce4711023d5 Copilot-Session: c20b1da0-c613-489d-92a3-9c27527c0c54 Copilot-Session: 9213b7da-c5c8-4d9c-89ab-e80048d288a2 Copilot-Session: 3086bdaf-bc43-4fed-88d1-3a95a21fd14e Copilot-Session: ce168659-cb9d-4f0e-8fd1-2834d065ba9d Copilot-Session: adf24228-67c0-452e-9cc2-c698a8d7b3b7 Copilot-Session: 0d028ad4-fe5e-4ef1-9832-18ee0e4825cc Copilot-Session: c77eaf09-799a-416e-b4b4-19f37a4201ef Copilot-Session: 6fcfe0f1-6be5-4913-ab36-71823443cc36 Copilot-Session: 43c7b692-ffc5-43bf-88b9-ecc927a1aa55
Summary of the Pull Request
Adds
--follow-link/-Ltowslc container cpWithout
-Lthe behavior is unchanged: the symlink itself is copied.PR Checklist
Detailed Description of the Pull Request / Additional comments
CLI
ArgumentDefinitions.hgainsArgType::FollowLink(--follow-link, aliasL,Kind::Flag).ContainerCpCommandregisters it alongside the existing--archive.Container → local: statting a path over the Docker Engine API
StatArchivePathissuesHEAD /containers/{id}/archive?path=...and reads theX-Docker-Container-Path-Statresponse header. This is the same callContainerStatPathmakes in the engine's own client —cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)inmoby/moby:client/container_copy.go— which is whatdocker cpuses to resolve a symlink.HEADis a good fit forDockerHTTPClient:SendRequestreturns as soon as the response header has been consumed and parsed, handing the still-open socket back to the caller, so it never waits on a body.StatArchivePathdrops the socket immediately. Each request gets its own freshly-connected socket viaSendRequestImpl, so releasing it has no effect on subsequent calls.On the engine side, the
HEADroute maps toheadContainersArchive, which sets the header viasetContainerPathStatHeaderand writes no body (moby/moby:daemon/server/router/container/copy.go);GETstreams the tar in addition to that header. A missing container or path yields404, which maps tostd::nullopt.One consequence of
HEAD: error responses carry no body, soDockerHTTPExceptionis constructed with an empty body and message and callers have only the status code to work with.The header is base64-encoded JSON, decoded with
wslutil::Base64Decodeinto a newdocker_schema::ContainerPathStat. The symlink test uses Go'sos.ModeSymlinkbit (1 << 27), since themodefield is a marshalled Goos.FileMode.When the target is relative, it is joined against the parent directory of the source path — the same rebasing docker performs via
archive.SplitPathDirEntry.Keeping the requested name
A followed link produces an archive named after the link's target, while the copy has to keep the name that was asked for. Container → local extraction therefore goes to a staging directory and the entries are moved up under the requested name afterwards; local → container stages the target's tree under the link's name (with its own nested links intact) because
tar.execannot rename entries.The requested name comes from
wsl::windows::common::filesystem::PosixBaseName. A POSIX name may hold characters that no Windows file name can (:and\are legal in POSIX), so the copy is rejected withWSLCCLI_CpSourceNameNotRepresentableErrorrather than silently landing under the target's name instead.Interface changes
IWSLCContainer::DownloadArchive(wslc.idl) gains aFollowLinkparameter:IWSLCContaineris internal and non-stable — it is rebuilt and ships in lockstep with its only clients — so changing an existing method's signature is safe here. The SDK-facingIWSLCCompatContainerinWSLCCompat.idlis untouched.Validation Steps Performed
Full
cmake --build .— clean.wslc container cp --helplists the new option:11 new parser cases in
CommandLineTestCases.hcovering-L,--follow-link, both copy directions,=true/=falseon both spellings, combination with-a, and the negatives-L=invalid,--followlinkand-l(aliases are case-sensitive).ContainerCpCommand_HasFollowLinkArgumentWithDockerAliaspins the name, alias, kind and optionality.E2E coverage in
WSLCE2EContainerCpTests.cpp:WSLCE2E_Container_Cp_HelpListsFollowLinkasserts the flag is surfaced in help output.WSLCE2E_Container_Cp_LocalToContainer_FollowLinkCopiesTargetContents— file link, dereferenced into the container under the link's name.WSLCE2E_Container_Cp_LocalToContainer_FollowLinkCopiesDirectoryTree— directory link, staged under the link's name, with a nested link preserved as a link.WSLCE2E_Container_Cp_ContainerToLocal_FollowLinkCopiesTargetContents— absolute link target.WSLCE2E_Container_Cp_ContainerToLocal_FollowLinkResolvesRelativeTarget— relative link target, which only resolves against the directory holding the link.