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"