@samitouri / QOSAMI-WSL / commits / 478b83e3

CLI: Initialize cidfile option (#40455)

* Initialize cidfile option Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

AmirMS committed May 7, 2026 at 19:22 UTC 478b83e3dff7055d7a976dc5aaa8cc4eec0158d9
11 files changed +156 -6
localization/strings/en-US/Resources.resw
+5 -1
@@ -2678,7 +2678,7 @@ On first run, creates the file with all settings commented out at their defaults
2678 <value>Working directory inside the container</value>
2679 </data>
2680 <data name="WSLCCLI_CIDFileArgDescription" xml:space="preserve">
2681 - <value>Write the container ID to the provided path.</value>
2681 + <value>Write the container ID to the provided path</value>
2682 </data>
2683 <data name="WSLCCLI_DNSArgDescription" xml:space="preserve">
2684 <value>IP address of the DNS nameserver in resolv.conf</value>
@@ -2758,6 +2758,10 @@ On first run, creates the file with all settings commented out at their defaults
2758 <value>Invalid {} argument value: '{}'. Expected a memory size (e.g. 256M, 1G)</value>
2759 <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated{Locked="256M"}{Locked="1G"}</comment>
2760 </data>
2761 + <data name="WSLCCLI_CIDFileAlreadyExistsError" xml:space="preserve">
2762 + <value>CID file '{}' already exists</value>
2763 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2764 + </data>
2765 <data name="WSLCCLI_ImageNotFoundPulling" xml:space="preserve">
2766 <value>Image '{}' not found, pulling</value>
2767 <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
src/windows/wslc/arguments/ArgumentDefinitions.h
+1 -1
@@ -38,7 +38,7 @@ _(Attach, "attach", L"a", Kind::Flag, L
38 _(BuildArg, "build-arg", NO_ALIAS, Kind::Value, Localization::WSLCCLI_BuildArgDescription()) \
39 _(BuildPull, "pull", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_BuildPullArgDescription()) \
40 _(BuildTarget, "target", NO_ALIAS, Kind::Value, Localization::WSLCCLI_BuildTargetArgDescription()) \
41 -/*_(CIDFile, "cidfile", NO_ALIAS, Kind::Value, Localization::WSLCCLI_CIDFileArgDescription())*/ \
41 +_(CIDFile, "cidfile", NO_ALIAS, Kind::Value, Localization::WSLCCLI_CIDFileArgDescription()) \
42 _(Command, "command", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_CommandArgDescription()) \
43 _(ContainerId, "container-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ContainerIdArgDescription()) \
44 _(Force, "force", L"f", Kind::Flag, Localization::WSLCCLI_ForceArgDescription()) \
src/windows/wslc/commands/ContainerCreateCommand.cpp
+1 -1
@@ -31,7 +31,7 @@ std::vector<Argument> ContainerCreateCommand::GetArguments() const
31 Argument::Create(ArgType::ImageId, true),
32 Argument::Create(ArgType::Command),
33 Argument::Create(ArgType::ForwardArgs),
34 - // Argument::Create(ArgType::CIDFile),
34 + Argument::Create(ArgType::CIDFile),
35 Argument::Create(ArgType::DNS, false, NO_LIMIT),
36 // Argument::Create(ArgType::DNSDomain),
37 Argument::Create(ArgType::DNSOption, false, NO_LIMIT),
src/windows/wslc/commands/ContainerRunCommand.cpp
+1 -1
@@ -31,7 +31,7 @@ std::vector<Argument> ContainerRunCommand::GetArguments() const
31 Argument::Create(ArgType::ImageId, true),
32 Argument::Create(ArgType::Command),
33 Argument::Create(ArgType::ForwardArgs),
34 - // Argument::Create(ArgType::CIDFile),
34 + Argument::Create(ArgType::CIDFile),
35 Argument::Create(ArgType::Detach),
36 Argument::Create(ArgType::DNS, false, NO_LIMIT),
37 // Argument::Create(ArgType::DNSDomain),
src/windows/wslc/services/ContainerModel.cpp
+46
@@ -332,4 +332,50 @@ TmpfsMount TmpfsMount::Parse(const std::string& value)
332 result.m_options = value.substr(colonPos + 1);
333 return result;
334 }
335 +
336 +CidFile::CidFile(const std::optional<std::wstring>& path)
337 +{
338 + if (!path.has_value())
339 + {
340 + return;
341 + }
342 +
343 + m_path = *path;
344 + auto [file, openError] = wil::try_create_new_file(std::filesystem::path(*m_path).c_str(), GENERIC_WRITE);
345 + if (!file.is_valid())
346 + {
347 + if (openError == ERROR_FILE_EXISTS || openError == ERROR_ALREADY_EXISTS)
348 + {
349 + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(openError), Localization::WSLCCLI_CIDFileAlreadyExistsError(*m_path));
350 + }
351 +
352 + const auto errorMessage = wsl::windows::common::wslutil::GetSystemErrorString(HRESULT_FROM_WIN32(openError));
353 + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(openError), Localization::MessageWslcFailedToOpenFile(*m_path, errorMessage));
354 + }
355 +
356 + m_file = std::move(file);
357 +}
358 +
359 +CidFile::~CidFile()
360 +{
361 + if (m_committed || !m_path.has_value())
362 + {
363 + return;
364 + }
365 +
366 + m_file.reset();
367 + std::error_code ec;
368 + std::filesystem::remove(std::filesystem::path(*m_path), ec);
369 +}
370 +
371 +void CidFile::Commit(const std::string& containerId)
372 +{
373 + if (m_file)
374 + {
375 + DWORD bytesWritten{};
376 + THROW_IF_WIN32_BOOL_FALSE(::WriteFile(m_file.get(), containerId.data(), static_cast<DWORD>(containerId.size()), &bytesWritten, nullptr));
377 + }
378 +
379 + m_committed = true;
380 +}
381 } // namespace wsl::windows::wslc::models
src/windows/wslc/services/ContainerModel.h
+18
@@ -52,6 +52,7 @@ struct ContainerOptions
52 std::vector<std::string> DnsOptions;
53 std::vector<std::string> Tmpfs;
54 std::vector<std::pair<std::string, std::string>> Labels;
55 + std::optional<std::wstring> CidFile{};
56 };
57
58 struct CreateContainerResult
@@ -290,4 +291,21 @@ private:
291 std::string m_containerPath;
292 std::string m_options;
293 };
294 +
295 +class CidFile
296 +{
297 +public:
298 + explicit CidFile(const std::optional<std::wstring>& path);
299 + ~CidFile();
300 +
301 + NON_COPYABLE(CidFile);
302 + NON_MOVABLE(CidFile);
303 +
304 + void Commit(const std::string& containerId);
305 +
306 +private:
307 + std::optional<std::wstring> m_path{};
308 + wil::unique_hfile m_file;
309 + bool m_committed = false;
310 +};
311 } // namespace wsl::windows::wslc::models
src/windows/wslc/services/ContainerService.cpp
+12 -2
@@ -20,6 +20,7 @@ Abstract:
20 #include <wslutil.h>
21 #include <WSLCProcessLauncher.h>
22 #include <CommandLine.h>
23 +#include <filesystem>
24 #include <unordered_map>
25 #include <wslc.h>
26
@@ -341,10 +342,18 @@ std::wstring ContainerService::FormatPorts(WSLCContainerState state, const std::
342
343 int ContainerService::Run(Session& session, const std::string& image, ContainerOptions runOptions)
344 {
345 + // Reserve the CID file (fails if it already exists) before creating the container so a
346 + // container isn't created when the caller-requested path can't be written. The file is
347 + // removed automatically if we don't reach Commit() below.
348 + CidFile cidFile(runOptions.CidFile);
349 +
350 // Create the container
351 auto runningContainer = CreateInternal(session, image, runOptions);
352 auto& container = runningContainer.Get();
353
354 + WSLCContainerId containerId{};
355 + THROW_IF_FAILED(container.GetId(containerId));
356 +
357 // Start the created container
358 WSLCContainerStartFlags startFlags{};
359 WI_SetFlagIf(startFlags, WSLCContainerStartFlagsAttach, !runOptions.Detach);
@@ -352,6 +361,7 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO
361
362 // Disable auto-delete only after successful start
363 runningContainer.SetDeleteOnClose(false);
364 + cidFile.Commit(containerId);
365
366 // Handle attach if requested
367 if (WI_IsFlagSet(startFlags, WSLCContainerStartFlagsAttach))
@@ -360,19 +370,19 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO
370 return consoleService.AttachToCurrentConsole(runningContainer.GetInitProcess());
371 }
372
363 - WSLCContainerId containerId{};
364 - THROW_IF_FAILED(container.GetId(containerId));
373 PrintMessage(L"%hs", stdout, containerId);
374 return 0;
375 }
376
377 CreateContainerResult ContainerService::Create(Session& session, const std::string& image, ContainerOptions runOptions)
378 {
379 + CidFile cidFile(runOptions.CidFile);
380 auto runningContainer = CreateInternal(session, image, runOptions);
381 runningContainer.SetDeleteOnClose(false);
382 auto& container = runningContainer.Get();
383 WSLCContainerId id{};
384 THROW_IF_FAILED(container.GetId(id));
385 + cidFile.Commit(id);
386 return {.Id = id};
387 }
388
src/windows/wslc/tasks/ContainerTasks.cpp
+5
@@ -226,6 +226,11 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
226 {
227 ContainerOptions options;
228
229 + if (context.Args.Contains(ArgType::CIDFile))
230 + {
231 + options.CidFile = context.Args.Get<ArgType::CIDFile>();
232 + }
233 +
234 if (context.Args.Contains(ArgType::Name))
235 {
236 options.Name = WideToMultiByte(context.Args.Get<ArgType::Name>());
test/windows/wslc/CommandLineTestCases.h
+2
@@ -71,6 +71,7 @@ COMMAND_LINE_TEST_CASE(
71 true)
72 COMMAND_LINE_TEST_CASE(L"container run ubuntu bash -c 'echo Hello World'", L"run", true)
73 COMMAND_LINE_TEST_CASE(L"container run ubuntu", L"run", true)
74 +COMMAND_LINE_TEST_CASE(L"container run --cidfile C:\\temp\\cidfile ubuntu", L"run", true)
75 COMMAND_LINE_TEST_CASE(L"container run -it --name foo ubuntu", L"run", true)
76 COMMAND_LINE_TEST_CASE(L"container run --rm -it --name foo ubuntu", L"run", true)
77 COMMAND_LINE_TEST_CASE(L"stop", L"stop", true)
@@ -84,6 +85,7 @@ COMMAND_LINE_TEST_CASE(L"container start --attach cont", L"start", true)
85 COMMAND_LINE_TEST_CASE(L"container start -a cont", L"start", true)
86 COMMAND_LINE_TEST_CASE(L"create ubuntu:latest", L"create", true)
87 COMMAND_LINE_TEST_CASE(L"container create --name foo ubuntu", L"create", true)
88 +COMMAND_LINE_TEST_CASE(L"container create --cidfile C:\\temp\\cidfile --name foo ubuntu", L"create", true)
89 COMMAND_LINE_TEST_CASE(L"create --workdir /app ubuntu", L"create", true)
90 COMMAND_LINE_TEST_CASE(L"create -w /app ubuntu", L"create", true)
91 COMMAND_LINE_TEST_CASE(L"container create --workdir /app ubuntu sh", L"create", true)
test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
+31
@@ -106,6 +106,36 @@ class WSLCE2EContainerCreateTests
106 VerifyContainerIsListed(containerId, L"created");
107 }
108
109 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_CIDFile_Valid)
110 + {
111 + // Prepare a CID file path that does not exist
112 + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename();
113 + VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str()));
114 + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); });
115 +
116 + auto result = RunWslc(std::format(
117 + L"container create --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag()));
118 + result.Verify({.Stderr = L"", .ExitCode = 0});
119 +
120 + const auto containerId = result.GetStdoutOneLine();
121 + VERIFY_IS_TRUE(std::filesystem::exists(cidFilePath));
122 + VERIFY_ARE_EQUAL(containerId, ReadFileContent(cidFilePath.wstring()));
123 + }
124 +
125 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_CIDFile_AlreadyExists)
126 + {
127 + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename();
128 + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); });
129 +
130 + auto result = RunWslc(std::format(
131 + L"container create --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag()));
132 + result.Verify(
133 + {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_FILE_EXISTS\r\n", EscapePath(cidFilePath.wstring())),
134 + .ExitCode = 1});
135 +
136 + VerifyContainerIsNotListed(WslcContainerName);
137 + }
138 +
139 WSLC_TEST_METHOD(WSLCE2E_Container_Create_DuplicateContainerName)
140 {
141 VerifyContainerIsNotListed(WslcContainerName);
@@ -797,6 +827,7 @@ private:
827 {
828 std::wstringstream options;
829 options << L"The following options are available:\r\n" //
830 + << L" --cidfile Write the container ID to the provided path\r\n"
831 << L" --dns IP address of the DNS nameserver in resolv.conf\r\n"
832 << L" --dns-option Set DNS options\r\n"
833 << L" --dns-search Set DNS search domains\r\n"
test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
+34
@@ -88,6 +88,39 @@ class WSLCE2EContainerRunTests
88 VerifyContainerIsListed(WslcContainerName, L"exited");
89 }
90
91 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_CIDFile_Valid)
92 + {
93 + // Prepare a CID file path that does not exist
94 + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename();
95 + VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str()));
96 + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); });
97 +
98 + auto result = RunWslc(std::format(
99 + L"container run -d --cidfile \"{}\" --name {} {} sleep infinity",
100 + EscapePath(cidFilePath.wstring()),
101 + WslcContainerName,
102 + DebianImage.NameAndTag()));
103 + result.Verify({.Stderr = L"", .ExitCode = 0});
104 +
105 + const auto containerId = result.GetStdoutOneLine();
106 + VERIFY_IS_TRUE(std::filesystem::exists(cidFilePath));
107 + VERIFY_ARE_EQUAL(containerId, ReadFileContent(cidFilePath.wstring()));
108 + }
109 +
110 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_CIDFile_AlreadyExists)
111 + {
112 + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename();
113 + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); });
114 +
115 + auto result = RunWslc(std::format(
116 + L"container run --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag()));
117 + result.Verify(
118 + {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_FILE_EXISTS\r\n", EscapePath(cidFilePath.wstring())),
119 + .ExitCode = 1});
120 +
121 + VerifyContainerIsNotListed(WslcContainerName);
122 + }
123 +
124 WSLC_TEST_METHOD(WSLCE2E_Container_Run_Entrypoint)
125 {
126 auto result = RunWslc(std::format(L"container run --rm --entrypoint /bin/whoami {}", DebianImage.NameAndTag()));
@@ -786,6 +819,7 @@ private:
819 {
820 std::wstringstream options;
821 options << L"The following options are available:\r\n"
822 + << L" --cidfile Write the container ID to the provided path\r\n"
823 << L" -d,--detach Run container in detached mode\r\n"
824 << L" --dns IP address of the DNS nameserver in resolv.conf\r\n"
825 << L" --dns-option Set DNS options\r\n"