Add wslc container cp command for tar archive upload (#40835)
* Add wslc container cp command for tar archive upload Implements 'wslc container cp - CONTAINER:PATH' to copy a tar archive from stdin into a running container via Docker's PUT /containers/{id}/archive API. Usage: tar.exe -cf - files | wslc container cp - my_container:/dest Changes across all layers: - IDL: Added UploadArchive to IWSLCContainer - DockerHTTPClient: Added PutArchive method (omits Content-Length for pipes) - WSLCContainerImpl: Relay stdin to Docker socket with SD_SEND on EOF - ContainerService: Added CopyToContainer static method - ContainerTasks: Added CopyToContainer task with CONTAINER:PATH parsing - ContainerCpCommand: New command registered under 'container cp' - Localization: Added all user-facing strings - Tests: Added CLI parsing test cases and updated e2e command list Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add unit and e2e tests for container cp command - Add 5 new CLI parsing test cases for cp in CommandLineTestCases.h - Add RunWslcWithStdinFile helper to pipe file contents to wslc stdin - Add WSLCE2EContainerCpTests with 11 e2e test methods covering: - Help output, missing arguments, invalid target formats - Stdin terminal detection, source validation - Container not found error handling - Successful tar upload to running container with exec verification - Copy to stopped container (Docker PUT /archive filesystem operation) - CreateTestTarFile builds minimal POSIX tar at runtime for tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address code review comments - Fix ContainerCpDesc to accurately describe stdin-only tar upload (was misleadingly implying bidirectional file copy) - Change PutArchive ContentLength parameter to std::optional<uint64_t> to distinguish 'unknown size' (pipe) from 'known zero size' (empty file) - WSLCContainerImpl::UploadArchive passes std::nullopt when ContentSize is 0 Note: Kept UploadArchive on IWSLCContainer (not a separate IWSLCContainer2) because wslc interfaces are internal — client and server are always deployed together from the same build, so there is no ABI compatibility concern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove accidentally committed test VHD files * Fix clang format errors * Add -a/--archive flag to container cp command - Add Archive argument type in ArgumentDefinitions.h - Add --archive/-a flag to ContainerCpCommand arguments - Add WSLCCLI_ArchiveArgDescription localization string - Add 2 CLI parsing test cases for -a and --archive - Add 2 e2e tests: ArchiveFlag (-a) and ArchiveFlagLongForm (--archive) The flag is accepted for docker cp compatibility. Since the tar stream is relayed directly to Docker's PUT /archive API, uid/gid information from the source tar is always preserved (equivalent to -a behavior). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Support boolean values for flag arguments (-a=true/false, --archive=true/false) Add ParseBoolValue() helper to ArgumentParser that accepts true/false/1/0 (case-insensitive). Modify ProcessNamedArgument() and ProcessAliasArgument() to parse adjoined boolean values for flag-type arguments. - --flag=true and -f=true set the flag (equivalent to --flag / -f) - --flag=false and -f=false leave the flag unset - Invalid values produce FlagInvalidBooleanError - Add 8 CLI parsing unit test cases for boolean flag values - Add 5 e2e tests for -a/--archive boolean value variants - All 143 unit tests and 18 e2e tests pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address code review feedback: validation, consistency, robustness - Validate DestPath is non-empty in UploadArchive (E_INVALIDARG) - Unify CONTAINER:DEST_PATH to CONTAINER:PATH in long description - Guard FromJson with try-catch for non-JSON error responses - Fix RunWslcWithStdinFile declaration line length (>130 col) - Add comment explaining archive flag is intentional no-op for stdin tar Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix clang format errors * Implement bidirectional container cp (local<->container) Extend wslc container cp to support all copy directions: - Local filesystem to container (file or directory) - Container to local filesystem (file or directory) - Stdin to container (existing behavior) Implementation details: - Add GetArchive to DockerHTTPClient (GET /containers/{id}/archive) - Add DownloadArchive to IWSLCContainer IDL interface - Implement DownloadArchive in WSLCContainerImpl with adaptive handling of chunked vs non-chunked transfer encoding - Add CopyFromContainer to ContainerService - Refactor CopyToContainer task to detect direction via CONTAINER:PATH pattern and dispatch to appropriate code path - Use Windows tar.exe for archive creation/extraction on local paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix trailing backslash tar bug and add container cp e2e tests Fix: Strip trailing path separators before embedding paths in tar.exe command lines. Windows CRT parses a trailing backslash-quote as an escaped quote character, causing tar.exe to receive a mangled directory path and fail with 'could not chdir'. Affects both upload and download. New e2e tests: - Local file to container (auto-tar upload) - Local file not found error - Container to local (download + extract) - Container to local with trailing backslash (regression) - Nonexistent path in container error - Copy from stopped container - Invalid direction (local to local) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix DownloadArchive/Export hang on HTTP error responses For non-200 responses, read the error body synchronously with a receive timeout instead of using the async io.Run() path. The ReadHandle-based approach hangs because HTTP/1.1 keep-alive holds the socket open indefinitely after the error body is sent. Also adds e2e tests for nonexistent file, nonexistent directory, and nonexistent container download scenarios. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review comments for container cp - Fix CreateProcessW error handling: only report 'tar.exe not found' for ERROR_FILE_NOT_FOUND/ERROR_PATH_NOT_FOUND, surface real error otherwise (Copilot feedback) - Add WSLCCLI_CpInvalidSourceError for source path validation instead of reusing the destination error string (Copilot feedback) - Update --archive/-a description to clarify it's accepted for Docker CLI compatibility only (Copilot feedback) - Use std::error_code overloads for filesystem::exists and create_directories to avoid uncontrolled exceptions (Copilot feedback) - Add error message validation to failure tests (OneBlue feedback) - Replace manual tar byte construction with tar.exe in test helper (OneBlue feedback) - Refactor RunWslc to accept optional stdin HANDLE parameter, simplify RunWslcWithStdinFile to delegate to RunWslc (OneBlue feedback) - Fix test comment for SourceNotStdin (now tests source-not-found) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix clang format errors * Ensure custom stdin handle is inheritable in RunWslc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Log setsockopt SO_RCVTIMEO failures instead of ignoring Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add try/catch for JSON parsing in Export error path Mirrors DownloadArchive's pattern: if the error body is empty or truncated (e.g. timeout), fall back to surfacing the raw body text instead of letting FromJson throw an unhelpful exception. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix trailing separator stripping for root paths in container→local cp Use size() > 1 guard to preserve root paths like C:\ (matching the upload path logic), preventing tar.exe from receiving invalid 'C:'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename task function CopyToContainer -> ContainerCp The task handles both directions (local→container and container→local), so the old name was misleading. ContainerService::CopyToContainer (upload-only) keeps its name since it genuinely only copies to. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix clang format issues * Fix file-destination semantics for container-to-local copy When the target path does not end with a separator and is not an existing directory, treat it as a file destination instead of a directory. This matches docker cp behavior where 'cp CONTAINER:/file.txt C:\local\out.txt' creates out.txt as a file rather than extracting into a directory named out.txt. Implementation: extract to temp dir, then rename/copy the extracted file to the target path. Falls back to copy+delete if rename fails across volumes. Also adds an e2e test verifying this behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * clang format fixes * fix clang format errors * Restore WaitForContainerOutput lost during merge conflict resolution The function was accidentally deleted during merge conflict resolution. Restores the original implementation from master which launches 'container logs -f' as a subprocess and uses WaitForOutput to poll until the expected string appears in stdout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Addressed code review comments * Localize hardcoded error string in container cp Replace hardcoded "No file extracted from container archive" with Localization::WSLCCLI_CpNoFileExtractedError() and add the corresponding entry to Resources.resw. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix comments: explain hvsocket overlapped EOF delay (not keep-alive) The synchronous recv workaround exists because overlapped ReadFile on hvsockets has delayed EOF detection (~5s stall), not because of HTTP/1.1 keep-alive. Connection: close is already set and the server closes promptly — synchronous recv sees EOF immediately while overlapped ReadFile takes ~5s to signal completion on hvsockets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix end to end test error message * Propagate create_directories failure in container cp file target path Check the error code from create_directories when ensuring the parent directory exists for a single-file container-to-local copy, matching the pattern used in the directory-target branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert Export/DownloadArchive error paths to async ReadHandle The synchronous recv workaround for hvsocket EOF delay is no longer needed — the hang is not reproducible. Revert to the simpler async ReadHandle + io.Run() pattern used before this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Refactor container cp to use SubProcess and TempFile Replace raw CreateProcessW + manual temp file management with the repo's SubProcess and filesystem::TempFile utilities: - Local→container: TempFile with DeleteOnClose + InheritHandle, tar writes to stdout redirected to the temp file handle, rewind and upload from the same handle. - Container→local: TempFile for the downloaded archive, SubProcess for tar extraction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix 404 error code in UploadArchive/DownloadArchive to ERROR_PATH_NOT_FOUND Since UploadArchive and DownloadArchive operate on an already-resolved container handle, a 404 from the Docker API means the path was not found inside the container, not that the container itself is missing. Use HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) instead of WSLC_E_CONTAINER_NOT_FOUND. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove redundant try/catch in UploadArchive/DownloadArchive error paths The try/catch(wil::ResultException)/catch(...) pattern was re-throwing WIL exceptions and wrapping JSON parse failures. Since the outer CATCH_RETURN() at the COM boundary handles all exceptions, the inner try/catch is unnecessary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Validate error messages in container cp failure tests Assert specific error substrings instead of just checking stderr is non-empty, so tests catch regressions in error reporting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use exact string equality for exec stdout verification in cp tests Replace substring finds with VERIFY_ARE_EQUAL for exec cat output, ensuring we validate the complete expected output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Replace Sleep(1000) with container exec for file creation in cp tests Instead of running a shell command that creates a file and then sleeping to wait for it, start the container with 'sleep infinity' and use a synchronous 'container exec' to create the test file. This eliminates the race condition and removes all Sleep calls from the tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use current directory for test temp files instead of system temp path Match the pattern used by other e2e tests (ImageBuild, Helpers) which use std::filesystem::current_path() for temporary test artifacts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Eliminate temp directory from container-to-local copy path Replace temp directory approach with streamlined extraction: - Directory target: pipe download directly to tar stdin for extraction (no temp files at all) - File target: single download to temp file with exclusive write handle, validate with tar -t, then extract with tar -x -O to target file. The exclusive handle prevents tampering by other processes. This eliminates the temp directory and its associated ACL security concerns. The file case uses a temp file (safe with exclusive handle) to avoid double-downloading from the container. Add WSLCCLI_CpSourceIsDirectoryError for when a directory source is copied to a file destination. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix e2e test assertions: container exec returns LF not CRLF Container exec cat output uses Unix line endings (\n). The tests were incorrectly asserting \r\n which fails in the TAEF test runner that captures stdout in binary mode. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix localization comment: remove invalid Locked token The {Locked="wslc container cp"} token must appear in the string value for validation to pass. Remove it since the error message does not contain that text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Read tar -t output via pipe instead of buffering to memory Replace RunAndCaptureOutput() with pipe-based reading for the tar -tf validation step. This avoids buffering the entire listing in memory and enables early exit as soon as a directory entry or second file is detected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add e2e test for stdin copy from pipe (no content-length) Validates that 'wslc container cp - container:/path' works correctly when stdin is a pipe rather than a file, exercising the chunked transfer code path where GetFileSize is not available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix clang format errors * Kill tar -t process after reading instead of waiting for broken pipe Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Pooja Trivedi <trivedipooja@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>