Use canonical path when copying files from containers (#41190)

* Use canonical path when copying files from containers * Cleanup file

Blue committed Jul 28, 2026 at 20:56 UTC 8ff31106efa98786fd908d6175940b151aa2999e
2 files changed +35 -1
src/windows/wslc/tasks/ContainerTasks.cpp
+7 -1
@@ -398,7 +398,13 @@ void ContainerCp(CLIExecutionContext& context)
398 auto [containerId, srcPath] = parseContainerPath(source);
399 THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::WSLCCLI_CpInvalidSourceError(), containerId.empty() || srcPath.empty());
400
401 - auto absTarget = std::filesystem::absolute(target);
401 + // Resolve any symlinks in the target path since tar.exe refuses to extract through a symlink.
402 + std::error_code canonicalError;
403 + auto absTarget = std::filesystem::weakly_canonical(std::filesystem::absolute(target), canonicalError);
404 + if (canonicalError)
405 + {
406 + absTarget = std::filesystem::absolute(target); // Fall back to absolute if canonicalization fails.
407 + }
408
409 // Determine if target is a directory or a file destination.
410 // Treat as directory if: ends with separator, or already exists as a directory.
test/windows/wslc/e2e/WSLCE2EContainerCpTests.cpp
+28
@@ -432,6 +432,34 @@ class WSLCE2EContainerCpTests
432 VERIFY_IS_TRUE(std::filesystem::exists(extractedFile));
433 }
434
435 + WSLC_TEST_METHOD(WSLCE2E_Container_Cp_ContainerToLocal_ThroughSymlink)
436 + {
437 + auto runResult =
438 + RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
439 + runResult.Verify({.Stderr = L"", .ExitCode = 0});
440 +
441 + auto execResult = RunWslc(std::format(L"container exec {} touch /tmp/symfile.txt", WslcContainerName));
442 + execResult.Verify({.ExitCode = 0});
443 +
444 + auto linkDir = std::filesystem::current_path() / L"wslc-cp-symlink-link";
445 + auto cleanup = wil::scope_exit([&] {
446 + std::error_code ec;
447 + std::filesystem::remove(linkDir, ec);
448 + std::filesystem::remove(L"symfile.txt", ec);
449 + });
450 +
451 + // Creating a symlink requires Administrator privileges, which the E2E tests already run with.
452 + THROW_LAST_ERROR_IF(!CreateSymbolicLinkW(linkDir.c_str(), std::filesystem::current_path().c_str(), SYMBOLIC_LINK_FLAG_DIRECTORY));
453 +
454 + // Copy from the container into the symlink.
455 + const auto cpResult = RunWslc(std::format(L"container cp {}:/tmp/symfile.txt {}", WslcContainerName, linkDir.wstring()));
456 + cpResult.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
457 +
458 + // The file must land in the real directory (and therefore be visible through the symlink).
459 + VERIFY_IS_TRUE(std::filesystem::exists(std::filesystem::current_path() / L"symfile.txt"));
460 + VERIFY_IS_TRUE(std::filesystem::exists(linkDir / L"symfile.txt"));
461 + }
462 +
463 WSLC_TEST_METHOD(WSLCE2E_Container_Cp_ContainerToLocal_FileDestination)
464 {
465 // When the local target doesn't end with a separator and isn't an existing directory,