From 207f05d1bff6d4b2d25a187c7eefb1e18e7d0c59 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:04:07 -0700 Subject: [PATCH 1/6] Init cidfile --- .../wslc/arguments/ArgumentDefinitions.h | 2 +- .../wslc/arguments/ArgumentValidation.cpp | 17 ++++++++ .../wslc/arguments/ArgumentValidation.h | 2 + .../wslc/commands/ContainerCreateCommand.cpp | 2 +- .../wslc/commands/ContainerRunCommand.cpp | 2 +- src/windows/wslc/services/ContainerModel.h | 1 + .../wslc/services/ContainerService.cpp | 30 +++++++++++++- src/windows/wslc/tasks/ContainerTasks.cpp | 5 +++ test/windows/wslc/CommandLineTestCases.h | 2 + .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 41 +++++++++++++++++++ .../wslc/e2e/WSLCE2EContainerRunTests.cpp | 40 ++++++++++++++++++ 11 files changed, 139 insertions(+), 5 deletions(-) diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index 3748a95cd..96729e0ec 100644 --- a/src/windows/wslc/arguments/ArgumentDefinitions.h +++ b/src/windows/wslc/arguments/ArgumentDefinitions.h @@ -36,7 +36,7 @@ Module Name: _(All, "all", L"a", Kind::Flag, Localization::WSLCCLI_AllArgDescription()) \ _(Attach, "attach", L"a", Kind::Flag, Localization::WSLCCLI_AttachArgDescription()) \ _(BuildArg, "build-arg", NO_ALIAS, Kind::Value, Localization::WSLCCLI_BuildArgDescription()) \ -/*_(CIDFile, "cidfile", NO_ALIAS, Kind::Value, Localization::WSLCCLI_CIDFileArgDescription())*/ \ +_(CIDFile, "cidfile", NO_ALIAS, Kind::Value, Localization::WSLCCLI_CIDFileArgDescription()) \ _(Command, "command", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_CommandArgDescription()) \ _(ContainerId, "container-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ContainerIdArgDescription()) \ _(Force, "force", L"f", Kind::Flag, Localization::WSLCCLI_ForceArgDescription()) \ diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index ea47a5f55..60bf29370 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -58,6 +58,12 @@ void Argument::Validate(const ArgMap& execArgs) const break; } + case ArgType::CIDFile: + { + validation::ValidateCidFile(execArgs.Get()); + break; + } + default: break; } @@ -97,6 +103,17 @@ void ValidateVolumeMount(const std::vector& values) } } +void ValidateCidFile(const std::wstring& value) +{ + std::error_code ec; + if (std::filesystem::exists(std::filesystem::path{value}, ec)) + { + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), std::format(L"CID file '{}' already exists", value)); + } + + THROW_HR_IF(E_INVALIDARG, ec.value() != 0); +} + // Convert string to WSLCSignal enum - accepts either signal name (e.g., "SIGKILL") or number (e.g., "9") WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName) { diff --git a/src/windows/wslc/arguments/ArgumentValidation.h b/src/windows/wslc/arguments/ArgumentValidation.h index 23208699c..0eadb15d4 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.h +++ b/src/windows/wslc/arguments/ArgumentValidation.h @@ -62,4 +62,6 @@ FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring void ValidateVolumeMount(const std::vector& values); +void ValidateCidFile(const std::wstring& values); + } // namespace wsl::windows::wslc::validation \ No newline at end of file diff --git a/src/windows/wslc/commands/ContainerCreateCommand.cpp b/src/windows/wslc/commands/ContainerCreateCommand.cpp index 4150d0af5..b2d1af395 100644 --- a/src/windows/wslc/commands/ContainerCreateCommand.cpp +++ b/src/windows/wslc/commands/ContainerCreateCommand.cpp @@ -31,7 +31,7 @@ std::vector ContainerCreateCommand::GetArguments() const Argument::Create(ArgType::ImageId, true), Argument::Create(ArgType::Command), Argument::Create(ArgType::ForwardArgs), - // Argument::Create(ArgType::CIDFile), + Argument::Create(ArgType::CIDFile), // Argument::Create(ArgType::DNS), // Argument::Create(ArgType::DNSDomain), // Argument::Create(ArgType::DNSOption), diff --git a/src/windows/wslc/commands/ContainerRunCommand.cpp b/src/windows/wslc/commands/ContainerRunCommand.cpp index e2810cd5f..751ad92db 100644 --- a/src/windows/wslc/commands/ContainerRunCommand.cpp +++ b/src/windows/wslc/commands/ContainerRunCommand.cpp @@ -31,7 +31,7 @@ std::vector ContainerRunCommand::GetArguments() const Argument::Create(ArgType::ImageId, true), Argument::Create(ArgType::Command), Argument::Create(ArgType::ForwardArgs), - // Argument::Create(ArgType::CIDFile), + Argument::Create(ArgType::CIDFile), Argument::Create(ArgType::Detach), // Argument::Create(ArgType::DNS), // Argument::Create(ArgType::DNSDomain), diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h index c4da81fab..ff03c5b87 100644 --- a/src/windows/wslc/services/ContainerModel.h +++ b/src/windows/wslc/services/ContainerModel.h @@ -42,6 +42,7 @@ struct ContainerOptions std::vector Entrypoint; std::optional User{}; std::vector Tmpfs; + std::optional CidFile{}; }; struct CreateContainerResult diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 05a5cc295..23ec76e02 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -20,6 +20,8 @@ Module Name: #include #include #include +#include +#include #include #include @@ -37,6 +39,27 @@ static void SetContainerArguments(WSLCProcessOptions& options, std::vector(argsStorage.size())}; } + +static void WriteContainerIdToFile(const std::optional& cidFilePath, const std::string& containerId) +{ + if (!cidFilePath.has_value()) + { + return; + } + + std::ofstream file(std::filesystem::path{*cidFilePath}, std::ios::out | std::ios::binary | std::ios::trunc); + if (!file.is_open() || !file.good()) + { + THROW_HR_WITH_USER_ERROR(E_INVALIDARG, std::format(L"CID file '{}' cannot be opened for writing", *cidFilePath)); + } + + file << containerId; + if (!file.good()) + { + THROW_HR_WITH_USER_ERROR(E_FAIL, std::format(L"Failed to write container ID to CID file '{}'", *cidFilePath)); + } +} + static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& session, const std::string& image, const ContainerOptions& options) { auto processFlags = WSLCProcessFlagsNone; @@ -294,6 +317,10 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO runningContainer.SetDeleteOnClose(false); auto& container = runningContainer.Get(); + WSLCContainerId containerId{}; + THROW_IF_FAILED(container.GetId(containerId)); + WriteContainerIdToFile(runOptions.CidFile, containerId); + // Start the created container WSLCContainerStartFlags startFlags{}; WI_SetFlagIf(startFlags, WSLCContainerStartFlagsAttach, !runOptions.Detach); @@ -306,8 +333,6 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO return consoleService.AttachToCurrentConsole(runningContainer.GetInitProcess()); } - WSLCContainerId containerId{}; - THROW_IF_FAILED(container.GetId(containerId)); PrintMessage(L"%hs", stdout, containerId); return 0; } @@ -319,6 +344,7 @@ CreateContainerResult ContainerService::Create(Session& session, const std::stri auto& container = runningContainer.Get(); WSLCContainerId id{}; THROW_IF_FAILED(container.GetId(id)); + WriteContainerIdToFile(runOptions.CidFile, id); return {.Id = id}; } diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 55e19e478..309379b51 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -197,6 +197,11 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context) { ContainerOptions options; + if (context.Args.Contains(ArgType::CIDFile)) + { + options.CidFile = context.Args.Get(); + } + if (context.Args.Contains(ArgType::Name)) { options.Name = WideToMultiByte(context.Args.Get()); diff --git a/test/windows/wslc/CommandLineTestCases.h b/test/windows/wslc/CommandLineTestCases.h index 14f3e60a0..6203c9dc0 100644 --- a/test/windows/wslc/CommandLineTestCases.h +++ b/test/windows/wslc/CommandLineTestCases.h @@ -57,6 +57,7 @@ COMMAND_LINE_TEST_CASE(L"container list --format badformat", L"list", false) COMMAND_LINE_TEST_CASE(L"run ubuntu", L"run", true) COMMAND_LINE_TEST_CASE(L"container run ubuntu bash -c 'echo Hello World'", L"run", true) COMMAND_LINE_TEST_CASE(L"container run ubuntu", L"run", true) +COMMAND_LINE_TEST_CASE(L"container run --cidfile C:\\temp\\cidfile ubuntu", L"run", true) COMMAND_LINE_TEST_CASE(L"container run -it --name foo ubuntu", L"run", true) COMMAND_LINE_TEST_CASE(L"container run --rm -it --name foo ubuntu", L"run", true) COMMAND_LINE_TEST_CASE(L"stop", L"stop", true) @@ -70,6 +71,7 @@ COMMAND_LINE_TEST_CASE(L"container start --attach cont", L"start", true) COMMAND_LINE_TEST_CASE(L"container start -a cont", L"start", true) COMMAND_LINE_TEST_CASE(L"create ubuntu:latest", L"create", true) COMMAND_LINE_TEST_CASE(L"container create --name foo ubuntu", L"create", true) +COMMAND_LINE_TEST_CASE(L"container create --cidfile C:\\temp\\cidfile --name foo ubuntu", L"create", true) COMMAND_LINE_TEST_CASE(L"exec cont1 echo Hello", L"exec", true) COMMAND_LINE_TEST_CASE(L"exec cont1", L"exec", false) // Missing required command argument COMMAND_LINE_TEST_CASE(L"container exec -it cont1 sh -c \"echo a && echo b\"", L"exec", true) // docker exec example diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index 7736bd784..5b1da59cc 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -120,6 +120,46 @@ class WSLCE2EContainerCreateTests VerifyContainerIsListed(containerId, L"created"); } + TEST_METHOD(WSLCE2E_Container_Create_CIDFile_Valid) + { + WSL2_TEST_ONLY(); + + // Prepare a CID file path that does not exist + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename(); + VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); + + auto result = RunWslc(std::format( + L"container create --cidfile \"{}\" --name {} {}", + EscapePath(cidFilePath.wstring()), + WslcContainerName, + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = S_OK}); + + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_TRUE(std::filesystem::exists(cidFilePath)); + VERIFY_ARE_EQUAL(containerId, ReadFileContent(cidFilePath.wstring())); + } + + TEST_METHOD(WSLCE2E_Container_Create_CIDFile_AlreadyExists) + { + WSL2_TEST_ONLY(); + + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename(); + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); + + auto result = RunWslc(std::format( + L"container create --cidfile \"{}\" --name {} {}", + EscapePath(cidFilePath.wstring()), + WslcContainerName, + DebianImage.NameAndTag())); + result.Dump(); + result.Verify( + {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), .ExitCode = 1}); + + VerifyContainerIsNotListed(WslcContainerName); + } + TEST_METHOD(WSLCE2E_Container_Create_DuplicateContainerName) { WSL2_TEST_ONLY(); @@ -1232,6 +1272,7 @@ class WSLCE2EContainerCreateTests { std::wstringstream options; options << L"The following options are available:\r\n" // + << L" --cidfile Write the container ID to the file\r\n" << L" --entrypoint Specifies the container init process executable\r\n" << L" -e,--env Key=Value pairs for environment variables\r\n" << L" --env-file File containing key=value pairs of env variables\r\n" diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 830c54d74..a89a612dd 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -62,6 +62,45 @@ class WSLCE2EContainerRunTests VerifyContainerIsListed(WslcContainerName, L"exited"); } + TEST_METHOD(WSLCE2E_Container_Run_CIDFile_Valid) + { + WSL2_TEST_ONLY(); + + // Prepare a CID file path that does not exist + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename(); + VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); + + auto result = RunWslc(std::format( + L"container run -d --cidfile \"{}\" --name {} {} sleep infinity", + EscapePath(cidFilePath.wstring()), + WslcContainerName, + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_TRUE(std::filesystem::exists(cidFilePath)); + VERIFY_ARE_EQUAL(containerId, ReadFileContent(cidFilePath.wstring())); + } + + TEST_METHOD(WSLCE2E_Container_Run_CIDFile_AlreadyExists) + { + WSL2_TEST_ONLY(); + + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename(); + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); + + auto result = RunWslc(std::format( + L"container run --cidfile \"{}\" --name {} {}", + EscapePath(cidFilePath.wstring()), + WslcContainerName, + DebianImage.NameAndTag())); + result.Verify( + {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), .ExitCode = 1}); + + VerifyContainerIsNotListed(WslcContainerName); + } + TEST_METHOD(WSLCE2E_Container_Run_Entrypoint) { WSL2_TEST_ONLY(); @@ -247,6 +286,7 @@ class WSLCE2EContainerRunTests { std::wstringstream options; options << L"The following options are available:\r\n" + << L" --cidfile Write the container ID to the file\r\n" << L" -d,--detach Run container in detached mode\r\n" << L" --entrypoint Specifies the container init process executable\r\n" << L" -e,--env Key=Value pairs for environment variables\r\n" From 4c2ffc57cad962ee913e34fd6f2076955a86efb3 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:04:57 -0700 Subject: [PATCH 2/6] Clang format --- src/windows/wslc/services/ContainerService.cpp | 1 - .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 13 ++++--------- test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp | 8 +++----- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 23ec76e02..a7f0349f9 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -39,7 +39,6 @@ static void SetContainerArguments(WSLCProcessOptions& options, std::vector(argsStorage.size())}; } - static void WriteContainerIdToFile(const std::optional& cidFilePath, const std::string& containerId) { if (!cidFilePath.has_value()) diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index 5b1da59cc..204e6087f 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -130,10 +130,7 @@ class WSLCE2EContainerCreateTests auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); auto result = RunWslc(std::format( - L"container create --cidfile \"{}\" --name {} {}", - EscapePath(cidFilePath.wstring()), - WslcContainerName, - DebianImage.NameAndTag())); + L"container create --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = S_OK}); const auto containerId = result.GetStdoutOneLine(); @@ -149,13 +146,11 @@ class WSLCE2EContainerCreateTests auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); auto result = RunWslc(std::format( - L"container create --cidfile \"{}\" --name {} {}", - EscapePath(cidFilePath.wstring()), - WslcContainerName, - DebianImage.NameAndTag())); + L"container create --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag())); result.Dump(); result.Verify( - {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), .ExitCode = 1}); + {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), + .ExitCode = 1}); VerifyContainerIsNotListed(WslcContainerName); } diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index a89a612dd..5e4f25872 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -91,12 +91,10 @@ class WSLCE2EContainerRunTests auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); auto result = RunWslc(std::format( - L"container run --cidfile \"{}\" --name {} {}", - EscapePath(cidFilePath.wstring()), - WslcContainerName, - DebianImage.NameAndTag())); + L"container run --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag())); result.Verify( - {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), .ExitCode = 1}); + {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), + .ExitCode = 1}); VerifyContainerIsNotListed(WslcContainerName); } From c981875eb9cec457a066c94bb208fc203b656d90 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:10:04 -0700 Subject: [PATCH 3/6] Loc --- localization/strings/en-US/Resources.resw | 6 +++++- .../wslc/arguments/ArgumentValidation.cpp | 12 +++++++---- .../wslc/arguments/ArgumentValidation.h | 2 +- .../wslc/services/ContainerService.cpp | 21 +++++++++++++------ .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 2 +- .../wslc/e2e/WSLCE2EContainerRunTests.cpp | 2 +- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 462c7b36a..e77662654 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2426,7 +2426,7 @@ On first run, creates the file with all settings commented out at their defaults Working directory inside the container - Write the container ID to the provided path. + Write the container ID to the provided path IP address of the DNS nameserver in resolv.conf @@ -2491,6 +2491,10 @@ On first run, creates the file with all settings commented out at their defaults Invalid {} value: {} is out of valid range ({}-{}). {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + CID file '{}' already exists + {FixedPlaceholder="{}"}File names and string inserts should not be translated + Image '{}' not found, pulling {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index 60bf29370..8729b29ee 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -103,15 +103,19 @@ void ValidateVolumeMount(const std::vector& values) } } -void ValidateCidFile(const std::wstring& value) +void ValidateCidFile(const std::wstring& cidFile) { std::error_code ec; - if (std::filesystem::exists(std::filesystem::path{value}, ec)) + if (std::filesystem::exists(std::filesystem::path{cidFile}, ec)) { - THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), std::format(L"CID file '{}' already exists", value)); + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), Localization::WSLCCLI_CIDFileAlreadyExistsError(cidFile)); } - THROW_HR_IF(E_INVALIDARG, ec.value() != 0); + if (ec) + { + const auto errorMessage = MultiByteToWide(ec.message()); + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ec.value()), Localization::MessageWslcFailedToOpenFile(cidFile, errorMessage)); + } } // Convert string to WSLCSignal enum - accepts either signal name (e.g., "SIGKILL") or number (e.g., "9") diff --git a/src/windows/wslc/arguments/ArgumentValidation.h b/src/windows/wslc/arguments/ArgumentValidation.h index 0eadb15d4..b1e7d8d71 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.h +++ b/src/windows/wslc/arguments/ArgumentValidation.h @@ -62,6 +62,6 @@ FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring void ValidateVolumeMount(const std::vector& values); -void ValidateCidFile(const std::wstring& values); +void ValidateCidFile(const std::wstring& cidFile); } // namespace wsl::windows::wslc::validation \ No newline at end of file diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index a7f0349f9..6514dc931 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -46,17 +46,26 @@ static void WriteContainerIdToFile(const std::optional& cidFilePat return; } - std::ofstream file(std::filesystem::path{*cidFilePath}, std::ios::out | std::ios::binary | std::ios::trunc); - if (!file.is_open() || !file.good()) + const auto path = std::filesystem::path(cidFilePath.value()); + HANDLE file = ::CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) { - THROW_HR_WITH_USER_ERROR(E_INVALIDARG, std::format(L"CID file '{}' cannot be opened for writing", *cidFilePath)); + const auto error = ::GetLastError(); + const auto errorMessage = wsl::shared::string::MultiByteToWide(std::system_category().message(error)); + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::MessageWslcFailedToOpenFile(*cidFilePath, errorMessage)); } - file << containerId; - if (!file.good()) + DWORD bytesWritten{}; + const bool writeSuccess = ::WriteFile(file, containerId.data(), static_cast(containerId.size()), &bytesWritten, nullptr) != FALSE; + const HRESULT closeResult = ::CloseHandle(file) ? S_OK : HRESULT_FROM_WIN32(GetLastError()); + if (!writeSuccess || bytesWritten != containerId.size()) { - THROW_HR_WITH_USER_ERROR(E_FAIL, std::format(L"Failed to write container ID to CID file '{}'", *cidFilePath)); + const auto error = writeSuccess ? ERROR_WRITE_FAULT : ::GetLastError(); + const auto errorMessage = wsl::shared::string::MultiByteToWide(std::system_category().message(error)); + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::MessageWslcFailedToOpenFile(*cidFilePath, errorMessage)); } + + THROW_IF_FAILED(closeResult); } static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& session, const std::string& image, const ContainerOptions& options) diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index 204e6087f..b930dd7d0 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -1267,7 +1267,7 @@ class WSLCE2EContainerCreateTests { std::wstringstream options; options << L"The following options are available:\r\n" // - << L" --cidfile Write the container ID to the file\r\n" + << L" --cidfile Write the container ID to the provided path\r\n" << L" --entrypoint Specifies the container init process executable\r\n" << L" -e,--env Key=Value pairs for environment variables\r\n" << L" --env-file File containing key=value pairs of env variables\r\n" diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 5e4f25872..5475c92dc 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -284,7 +284,7 @@ class WSLCE2EContainerRunTests { std::wstringstream options; options << L"The following options are available:\r\n" - << L" --cidfile Write the container ID to the file\r\n" + << L" --cidfile Write the container ID to the provided path\r\n" << L" -d,--detach Run container in detached mode\r\n" << L" --entrypoint Specifies the container init process executable\r\n" << L" -e,--env Key=Value pairs for environment variables\r\n" From 575d3cd5cbecad9a9f8c1a9c5bf0b8185d0730ff Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:20:21 -0700 Subject: [PATCH 4/6] Clang format --- localization/strings/en-US/Resources.resw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index e77662654..6074e96f1 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2493,7 +2493,7 @@ On first run, creates the file with all settings commented out at their defaults CID file '{}' already exists - {FixedPlaceholder="{}"}File names and string inserts should not be translated + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated Image '{}' not found, pulling From 86295a865dc1a9f0e28526ea589af5f98a7cccdf Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:35:28 -0700 Subject: [PATCH 5/6] Update test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index b930dd7d0..a673f9702 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -147,7 +147,6 @@ class WSLCE2EContainerCreateTests auto result = RunWslc(std::format( L"container create --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag())); - result.Dump(); result.Verify( {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), .ExitCode = 1}); From 801a7aca9e334a644103f7aa3f955a7f4e43dc0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:49:23 +0000 Subject: [PATCH 6/6] Fix ContainerService.cpp: use write-specific error message and special-case file-exists error Agent-Logs-Url: https://github.com/microsoft/WSL/sessions/8f4c0514-1fa5-45a7-aed2-11d0a878188b Co-authored-by: AmelBawa-msft <104940545+AmelBawa-msft@users.noreply.github.com> --- localization/strings/en-US/Resources.resw | 4 ++++ src/windows/wslc/services/ContainerService.cpp | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 6074e96f1..6d072ab24 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2118,6 +2118,10 @@ For privacy information about this product please visit https://aka.ms/privacy.< Failed to open '{}': {} {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + Failed to write to '{}': {} + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + No Containerfile or Dockerfile found in '{}' {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 6514dc931..737a20c6a 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -51,6 +51,11 @@ static void WriteContainerIdToFile(const std::optional& cidFilePat if (file == INVALID_HANDLE_VALUE) { const auto error = ::GetLastError(); + if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS) + { + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::WSLCCLI_CIDFileAlreadyExistsError(*cidFilePath)); + } + const auto errorMessage = wsl::shared::string::MultiByteToWide(std::system_category().message(error)); THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::MessageWslcFailedToOpenFile(*cidFilePath, errorMessage)); } @@ -62,7 +67,7 @@ static void WriteContainerIdToFile(const std::optional& cidFilePat { const auto error = writeSuccess ? ERROR_WRITE_FAULT : ::GetLastError(); const auto errorMessage = wsl::shared::string::MultiByteToWide(std::system_category().message(error)); - THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::MessageWslcFailedToOpenFile(*cidFilePath, errorMessage)); + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::MessageWslcFailedToWriteFile(*cidFilePath, errorMessage)); } THROW_IF_FAILED(closeResult);