Add CDI for WSLC GPU (#40583)

Kevin Vega committed May 28, 2026 at 13:47 UTC b0445f565addcccd1c78469fe29a957054b5fb25
12 files changed +214 -76
src/linux/init/WSLCInit.cpp
+93 -5
@@ -33,6 +33,10 @@ Abstract:
33 #include <mutex>
34 #include "mountutilcpp.h"
35 #include <filesystem>
36 +#include <iostream>
37 +#include "JsonUtils.h"
38 +#include "cdi_schema.h"
39 +#include "lxfsshares.h"
40
41 extern int InitializeLogging(bool SetStderr, wil::LogFunction* ExceptionCallback) noexcept;
42
@@ -64,10 +68,89 @@ struct WSLCState
68
69 static WSLCState g_state;
70
67 -int CreateCaptureCrashSymlink()
71 +void WriteWslcCdiSpec()
72 try
73 {
70 - THROW_LAST_ERROR_IF(symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0);
74 + wsl::shared::cdi::DeviceNode dxg{};
75 + dxg.path = "/dev/dxg";
76 + dxg.permissions = "rwm";
77 +
78 + wsl::shared::cdi::Mount libs{};
79 + libs.hostPath = LXSS_LIB_PATH;
80 + libs.containerPath = LXSS_LIB_PATH;
81 + libs.options = {"ro", "rbind"};
82 +
83 + wsl::shared::cdi::Mount drivers{};
84 + drivers.hostPath = LXSS_GPU_DRIVERS_PATH;
85 + drivers.containerPath = LXSS_GPU_DRIVERS_PATH;
86 + drivers.options = {"ro", "rbind"};
87 +
88 + wsl::shared::cdi::Hook hook{};
89 + hook.hookName = "createContainer";
90 + hook.path = "/" LX_INIT_WSLC_GPU_HOOK;
91 + hook.args = {LX_INIT_WSLC_GPU_HOOK};
92 +
93 + wsl::shared::cdi::Device gpu{};
94 + gpu.name = "gpu";
95 + gpu.containerEdits.deviceNodes.push_back(std::move(dxg));
96 + gpu.containerEdits.mounts.push_back(std::move(libs));
97 + gpu.containerEdits.mounts.push_back(std::move(drivers));
98 + gpu.containerEdits.hooks.push_back(std::move(hook));
99 +
100 + wsl::shared::cdi::Spec spec{};
101 + spec.cdiVersion = "0.6.0";
102 + spec.kind = LX_WSLC_CDI_KIND;
103 + spec.devices.push_back(std::move(gpu));
104 +
105 + THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/cdi", 0755) < 0);
106 + THROW_LAST_ERROR_IF(
107 + WriteToFile("/etc/cdi/microsoft.com-wslc.json", nlohmann::json(spec).dump().c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0);
108 +}
109 +CATCH_LOG()
110 +
111 +void WriteDockerDaemonConfig()
112 +try
113 +{
114 + constexpr auto c_daemonConfigPath = "/etc/docker/daemon.json";
115 +
116 + THROW_ERRNO_IF(EEXIST, std::filesystem::exists(c_daemonConfigPath));
117 +
118 + nlohmann::json config = nlohmann::json::object();
119 + config["features"]["cdi"] = true;
120 +
121 + THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/docker", 0755) < 0);
122 + THROW_LAST_ERROR_IF(WriteToFile(c_daemonConfigPath, config.dump().c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0);
123 +}
124 +CATCH_LOG()
125 +
126 +int WslcGpuHookEntry()
127 +try
128 +{
129 + // OCI runtime hooks receive the container state as JSON on stdin.
130 + const auto state = nlohmann::json::parse(std::cin);
131 + const std::filesystem::path bundle = state.at("bundle").get<std::string>();
132 + THROW_ERRNO_IF(EINVAL, !bundle.is_absolute());
133 +
134 + // Read the OCI spec's root.path from <bundle>/config.json. This is either an absolute path to
135 + // the overlay-merged rootfs or a path relative to the bundle directory.
136 + const auto spec = nlohmann::json::parse(UtilReadFileContent((bundle / "config.json").native()));
137 + std::filesystem::path rootfsPath = spec.at("root").at("path").get<std::string>();
138 + if (rootfsPath.is_relative())
139 + {
140 + rootfsPath = bundle / rootfsPath;
141 + }
142 +
143 + rootfsPath = std::filesystem::canonical(rootfsPath);
144 + THROW_ERRNO_IF(EINVAL, rootfsPath == "/");
145 +
146 + THROW_LAST_ERROR_IF(chroot(rootfsPath.c_str()) < 0);
147 +
148 + THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/ld.so.conf.d", 0755) < 0);
149 + THROW_LAST_ERROR_IF(WriteToFile("/etc/ld.so.conf.d/ld.wsl.conf", LXSS_LIB_PATH "\n", O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0);
150 +
151 + // Run the container's own ldconfig so it updates /etc/ld.so.cache.
152 + const char* const ldArgv[] = {LDCONFIG_COMMAND, nullptr};
153 + THROW_LAST_ERROR_IF(UtilCreateProcessAndWait(ldArgv[0], ldArgv) < 0);
154
155 return 0;
156 }
@@ -75,8 +158,9 @@ CATCH_RETURN_ERRNO()
158
159 void WSLCEnableCrashDumpCollection()
160 {
78 - if (CreateCaptureCrashSymlink() < 0)
161 + if (symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0 && errno != EEXIST)
162 {
163 + LOG_ERROR("symlink(/init, /" LX_INIT_WSL_CAPTURE_CRASH ") failed {}", errno);
164 return;
165 }
166
@@ -637,8 +721,12 @@ void HandleMessageImpl(
721 {
722 THROW_LAST_ERROR_IF(Chroot(target) < 0);
723
640 - // Recreate the crash dump symlink inside the new root.
641 - CreateCaptureCrashSymlink();
724 + // Recreate the /init symlinks inside the new root.
725 + THROW_LAST_ERROR_IF(symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0 && errno != EEXIST);
726 + THROW_LAST_ERROR_IF(symlink("/init", "/" LX_INIT_WSLC_GPU_HOOK) < 0 && errno != EEXIST);
727 +
728 + WriteWslcCdiSpec();
729 + WriteDockerDaemonConfig();
730
731 // Start the memory reduction thread now that procfs is in its final location.
732 static std::once_flag memoryReductionFlag;
src/linux/init/init.cpp
+6
@@ -164,6 +164,8 @@ wil::unique_fd UnmarshalConsoleFromServer(int MessageFd, LXBUS_IPC_CONSOLE_ID Co
164
165 int WslInitWatcher(int Argc, char** Argv);
166
167 +int WslcGpuHookEntry();
168 +
169 int WslEntryPoint(int Argc, char* Argv[])
170 {
171 //
@@ -230,6 +232,10 @@ int WslEntryPoint(int Argc, char* Argv[])
232 {
233 ExitCode = WslInitWatcher(Argc, Argv);
234 }
235 + else if (strcmp(BaseName, LX_INIT_WSLC_GPU_HOOK) == 0)
236 + {
237 + ExitCode = WslcGpuHookEntry();
238 + }
239 else
240 {
241 // Handle the special case for import result messages, everything else is sent to the binfmt interpreter.
src/linux/init/util.cpp
+6 -2
@@ -3359,7 +3359,7 @@ uint16_t UtilWinAfToLinuxAf(uint16_t WinAddressFamily)
3359 return LinuxAddressFamily;
3360 }
3361
3362 -int WriteToFile(const char* Path, const char* Content, int permissions)
3362 +int WriteToFile(const char* Path, const char* Content, int OpenFlags, int Permissions)
3363
3364 /*++
3365
@@ -3373,6 +3373,10 @@ Arguments:
3373
3374 Content - Supplies the content to be written to the file.
3375
3376 + OpenFlags - Supplies the flags passed to open().
3377 +
3378 + Permissions - Supplies the file mode used when O_CREAT causes the file to be created.
3379 +
3380 Return Value:
3381
3382 0 on success, -1 on failure.
@@ -3380,7 +3384,7 @@ Return Value:
3384 --*/
3385
3386 {
3383 - wil::unique_fd Fd{open(Path, (O_WRONLY | O_CLOEXEC | O_CREAT), permissions)};
3387 + wil::unique_fd Fd{open(Path, OpenFlags, Permissions)};
3388 if (!Fd)
3389 {
3390 int errnoPrev = errno;
src/linux/init/util.h
+1 -1
@@ -324,7 +324,7 @@ HvPciSwiotlbPool UtilReadHvPciSwiotlbPool();
324
325 uint16_t UtilWinAfToLinuxAf(uint16_t AddressFamily);
326
327 -int WriteToFile(const char* Path, const char* Content, int permissions = 0644);
327 +int WriteToFile(const char* Path, const char* Content, int OpenFlags = O_WRONLY | O_CLOEXEC | O_CREAT, int Permissions = 0644);
328
329 // Starts a background thread that performs memory compaction and optional cache reclaim when the VM is idle.
330 void StartMemoryReductionThread(LX_MINI_INIT_MEMORY_RECLAIM_MODE Mode);
src/shared/inc/cdi_schema.h new
+74
@@ -0,0 +1,74 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + cdi_schema.h
8 +
9 +Abstract:
10 +
11 + Schema for Container Device Interface (CDI) specs.
12 + See https://github.com/cncf-tags/container-device-interface/blob/main/SPEC.md
13 +
14 +--*/
15 +
16 +#pragma once
17 +
18 +#include "JsonUtils.h"
19 +
20 +namespace wsl::shared::cdi {
21 +
22 +struct DeviceNode
23 +{
24 + std::string path;
25 + std::string permissions;
26 +
27 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(DeviceNode, path, permissions);
28 +};
29 +
30 +struct Mount
31 +{
32 + std::string hostPath;
33 + std::string containerPath;
34 + std::vector<std::string> options;
35 +
36 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Mount, hostPath, containerPath, options);
37 +};
38 +
39 +struct Hook
40 +{
41 + std::string hookName;
42 + std::string path;
43 + std::vector<std::string> args;
44 +
45 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Hook, hookName, path, args);
46 +};
47 +
48 +struct ContainerEdits
49 +{
50 + std::vector<DeviceNode> deviceNodes;
51 + std::vector<Mount> mounts;
52 + std::vector<Hook> hooks;
53 +
54 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerEdits, deviceNodes, mounts, hooks);
55 +};
56 +
57 +struct Device
58 +{
59 + std::string name;
60 + ContainerEdits containerEdits;
61 +
62 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Device, name, containerEdits);
63 +};
64 +
65 +struct Spec
66 +{
67 + std::string cdiVersion;
68 + std::string kind;
69 + std::vector<Device> devices;
70 +
71 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Spec, cdiVersion, kind, devices);
72 +};
73 +
74 +} // namespace wsl::shared::cdi
src/shared/inc/lxfsshares.h
+2 -1
@@ -23,6 +23,7 @@ typedef struct _LXSS_SHARED_DIRECTORY
23
24 #define LXSS_LIB_PREFIX "/usr/lib/wsl"
25 #define LXSS_LIB_PATH LXSS_LIB_PREFIX "/lib"
26 +#define LXSS_GPU_DRIVERS_PATH LXSS_LIB_PREFIX "/drivers"
27 #define LXSS_GPU_DRIVERS_SHARE "drivers"
28 #define LXSS_GPU_LIB_SHARE "lib"
29 #define LXSS_GPU_INBOX_LIB_SHARE LXSS_GPU_LIB_SHARE "_inbox"
@@ -32,4 +33,4 @@ typedef struct _LXSS_SHARED_DIRECTORY
33 // Shared directories for GPU compute support.
34 //
35
35 -constexpr LXSS_SHARED_DIRECTORY g_gpuShares[] = {{LXSS_GPU_DRIVERS_SHARE, LXSS_LIB_PREFIX "/drivers"}, {LXSS_GPU_LIB_SHARE, LXSS_LIB_PATH}};
36 +constexpr LXSS_SHARED_DIRECTORY g_gpuShares[] = {{LXSS_GPU_DRIVERS_SHARE, LXSS_GPU_DRIVERS_PATH}, {LXSS_GPU_LIB_SHARE, LXSS_LIB_PATH}};
src/shared/inc/lxinitshared.h
+5
@@ -247,6 +247,11 @@ Abstract:
247
248 #define LX_INIT_WSL_INIT_WATCHER "init-watcher"
249
250 +#define LX_INIT_WSLC_GPU_HOOK "wsl-gpu-hook"
251 +
252 +#define LX_WSLC_CDI_KIND "microsoft.com/wslc"
253 +#define LX_WSLC_GPU_CDI_DEVICE LX_WSLC_CDI_KIND "=gpu"
254 +
255 //
256 // WSL2-specific environment variables.
257 //
src/windows/inc/docker_schema.h
+10 -1
@@ -215,6 +215,14 @@ struct Ulimit
215 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Ulimit, Name, Soft, Hard);
216 };
217
218 +struct DeviceRequest
219 +{
220 + std::string Driver;
221 + std::vector<std::string> DeviceIDs;
222 +
223 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(DeviceRequest, Driver, DeviceIDs);
224 +};
225 +
226 struct HostConfig
227 {
228 std::vector<Mount> Mounts;
@@ -230,6 +238,7 @@ struct HostConfig
238 // the field — so we don't bother with std::optional here.
239 std::int64_t ShmSize{};
240 std::optional<std::vector<DeviceMapping>> Devices;
241 + std::optional<std::vector<DeviceRequest>> DeviceRequests;
242
243 // Per-container resource limits. 0 means "no limit" (Docker default).
244 std::int64_t Memory{};
@@ -237,7 +246,7 @@ struct HostConfig
246 std::optional<std::vector<Ulimit>> Ulimits;
247
248 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
240 - HostConfig, Mounts, PortBindings, NetworkMode, Init, Dns, DnsSearch, DnsOptions, Binds, Tmpfs, Devices, ShmSize, Memory, NanoCpus, Ulimits);
249 + HostConfig, Mounts, PortBindings, NetworkMode, Init, Dns, DnsSearch, DnsOptions, Binds, Tmpfs, Devices, DeviceRequests, ShmSize, Memory, NanoCpus, Ulimits);
250 };
251
252 struct EndpointSettings
src/windows/wslcsession/WSLCContainer.cpp
+2 -39
@@ -506,29 +506,6 @@ void ProcessAdditionalNetworks(
506 }
507 }
508
509 -void ConfigureLdPathForGpu(std::vector<std::string>& Env)
510 -{
511 - static constexpr std::string_view ldLibraryPathPrefix = "LD_LIBRARY_PATH=";
512 - auto it = std::ranges::find_if(Env, [](const std::string& e) { return e.starts_with(ldLibraryPathPrefix); });
513 -
514 - if (it != Env.end())
515 - {
516 - // If the user already has an LD_LIBRARY_PATH, append the GPU library paths to it.
517 - auto ldPath = it->substr(ldLibraryPathPrefix.size());
518 - if (!ldPath.empty() && !ldPath.ends_with(":"))
519 - {
520 - it->append(":");
521 - }
522 -
523 - it->append(WSLCVirtualMachine::c_gpuLibrariesPath);
524 - }
525 - else
526 - {
527 - // Otherwise create a new entry.
528 - Env.emplace_back(std::format("LD_LIBRARY_PATH={}", WSLCVirtualMachine::c_gpuLibrariesPath));
529 - }
530 -}
531 -
509 } // namespace
510
511 ContainerPortMapping::ContainerPortMapping(VMPortMapping&& VmMapping, uint16_t ContainerPort) :
@@ -1215,11 +1192,6 @@ void WSLCContainerImpl::Exec(const WSLCProcessOptions* Options, LPCSTR DetachKey
1192 request.DetachKeys = DetachKeys;
1193 }
1194
1218 - if (WI_IsFlagSet(m_containerFlags, WSLCContainerFlagsGpu))
1219 - {
1220 - ConfigureLdPathForGpu(request.Env);
1221 - }
1222 -
1195 try
1196 {
1197 auto result = m_dockerClient.CreateExec(m_id, request);
@@ -1607,17 +1579,8 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
1579 !virtualMachine.FeatureEnabled(WslcFeatureFlagsGPU),
1580 "WSLCContainerFlagsGpu requires GPU support enabled on the session");
1581
1610 - if (!request.HostConfig.Binds.has_value())
1611 - {
1612 - request.HostConfig.Binds = std::vector<std::string>{};
1613 - }
1614 -
1615 - request.HostConfig.Binds->push_back(std::format("{0}:{0}:ro", WSLCVirtualMachine::c_gpuLibrariesPath));
1616 - request.HostConfig.Binds->push_back(std::format("{0}:{0}:ro", WSLCVirtualMachine::c_gpuDriversPath));
1617 -
1618 - request.HostConfig.Devices = {{"/dev/dxg", "/dev/dxg", "rwm"}};
1619 -
1620 - ConfigureLdPathForGpu(request.Env);
1582 + // Request the WSL GPU device via CDI.
1583 + request.HostConfig.DeviceRequests = std::vector<common::docker_schema::DeviceRequest>{{"cdi", {LX_WSLC_GPU_CDI_DEVICE}}};
1584 }
1585
1586 // Prepare port mappings from container options.
test/windows/WSLCTests.cpp
+5 -22
@@ -3379,17 +3379,6 @@ class WSLCTests
3379
3380 auto session = CreateSession(settings);
3381
3382 - // Validate that the GPU is correctly configured for containers init process.
3383 - {
3384 - WSLCContainerLauncher launcher(
3385 - "debian:latest", "test-container-init-gpu", {"/bin/sh", "-c", "test -c /dev/dxg && echo $LD_LIBRARY_PATH"});
3386 - launcher.SetContainerFlags(WSLCContainerFlagsGpu);
3387 -
3388 - auto container = launcher.Launch(*session);
3389 -
3390 - ValidateContainerOutput(container, {{1, "/usr/lib/wsl/lib\n"}}, 0);
3391 - }
3392 -
3382 // Validate that GPU resources are available inside a container when WSLCContainerFlagsGpu is set.
3383 {
3384 WSLCContainerLauncher launcher("debian:latest", "test-container-gpu", {"sleep", "99999"});
@@ -3405,8 +3394,8 @@ class WSLCTests
3394 ValidateProcessOutput(process, expectedOutput, exitCode);
3395 };
3396
3408 - // Validate that /dev/dxg is available as a character device.
3409 - expect({"/bin/sh", "-c", "test -c /dev/dxg"}, 0);
3397 + // Validate that /dev/dxg is available as a character device with read/write permissions.
3398 + expect({"/bin/sh", "-c", "test -c /dev/dxg && test -r /dev/dxg && test -w /dev/dxg"}, 0);
3399
3400 // Validate that the GPU library directory is mounted and contains libraries.
3401 expect({"/bin/sh", "-c", "test -d /usr/lib/wsl/lib && ls /usr/lib/wsl/lib | grep -q ."}, 0);
@@ -3418,15 +3407,9 @@ class WSLCTests
3407 expect({"/usr/bin/touch", "/usr/lib/wsl/lib/test"}, 1);
3408 expect({"/usr/bin/touch", "/usr/lib/wsl/drivers/test"}, 1);
3409
3421 - // Validate that LD_LIBRARY_PATH is set to include the GPU library path.
3422 - expect({"/bin/sh", "-c", "echo $LD_LIBRARY_PATH"}, 0, {{1, "/usr/lib/wsl/lib\n"}});
3423 -
3424 - // Validate that exec with a pre-existing LD_LIBRARY_PATH appends the GPU path.
3425 - expect({"/bin/sh", "-c", "echo $LD_LIBRARY_PATH"}, 0, {{1, "/custom/path:/usr/lib/wsl/lib\n"}}, {"LD_LIBRARY_PATH=/custom/path"});
3426 -
3427 - // Validate that exec with a trailing colon in LD_LIBRARY_PATH doesn't produce a double colon.
3428 - expect({"/bin/sh", "-c", "echo $LD_LIBRARY_PATH"}, 0, {{1, "/custom/path:/usr/lib/wsl/lib\n"}}, {"LD_LIBRARY_PATH=/custom/path:"});
3429 - expect({"/bin/sh", "-c", "echo $LD_LIBRARY_PATH"}, 0, {{1, "/usr/lib/wsl/lib\n"}}, {"LD_LIBRARY_PATH="});
3410 + // Validate that the dynamic linker is configured to resolve the WSL GPU libraries.
3411 + expect({"/bin/sh", "-c", "cat /etc/ld.so.conf.d/ld.wsl.conf"}, 0, {{1, "/usr/lib/wsl/lib\n"}});
3412 + expect({"/bin/sh", "-c", "ldconfig -p | grep -q ' => /usr/lib/wsl/lib/'"}, 0);
3413 }
3414
3415 // Validate that containers without the GPU flag do not have GPU resources.
test/windows/WslcSdkTests.cpp
+4 -2
@@ -2509,9 +2509,11 @@ class WslcSdkTests
2509 VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &gpuSession, nullptr));
2510 THROW_IF_FAILED(WslcLoadSessionImageFromFile(gpuSession.get(), GetTestImagePath("debian:latest").c_str(), nullptr, nullptr));
2511
2512 - // Validate /dev/dxg is available and LD_LIBRARY_PATH is set via the container init command.
2512 + // Validate /dev/dxg is available and the dynamic linker is configured to resolve the WSL
2513 + // GPU libraries.
2514 {
2514 - const char* initArgv[] = {"/bin/sh", "-c", "test -c /dev/dxg && echo $LD_LIBRARY_PATH"};
2515 + const char* initArgv[] = {
2516 + "/bin/sh", "-c", "test -c /dev/dxg && test -r /dev/dxg && test -w /dev/dxg && cat /etc/ld.so.conf.d/ld.wsl.conf"};
2517
2518 auto output = RunContainerAndCapture(
2519 gpuSession.get(), "debian:latest", {initArgv[0], initArgv[1], initArgv[2]}, WSLC_CONTAINER_FLAG_ENABLE_GPU);
test/windows/WslcSdkWinRTTests.cpp
+6 -3
@@ -1656,11 +1656,14 @@ class WslcSdkWinRtTests
1656 const auto debianTar = GetTestImagePath("debian:latest");
1657 gpuSession.LoadImageAsync(debianTar.wstring()).get();
1658
1659 - // Positive: /dev/dxg must be available and LD_LIBRARY_PATH set in a GPU container.
1659 + // Positive: /dev/dxg must be available with read/write permissions, and the dynamic linker must be configured to resolve
1660 + // the WSL GPU libraries inside a GPU container.
1661 {
1662 auto procSettings = WSLCSDK::ProcessSettings();
1662 - procSettings.CmdLine(
1663 - winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"test -c /dev/dxg && echo $LD_LIBRARY_PATH"}));
1663 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>(
1664 + {L"/bin/sh",
1665 + L"-c",
1666 + L"test -c /dev/dxg && test -r /dev/dxg && test -w /dev/dxg && cat /etc/ld.so.conf.d/ld.wsl.conf"}));
1667 procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Stream);
1668
1669 auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");