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");