From ba81b75d6f654dd3a8190fcfdb76f2ef9ee02aba Mon Sep 17 00:00:00 2001 From: kvega005 Date: Thu, 5 Mar 2026 11:09:04 -0800 Subject: [PATCH 01/10] Add hourly upstream sync workflow for feature/wsl-for-apps --- .github/workflows/sync-upstream.yml | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 000000000..fb0c13bfc --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,49 @@ +name: Sync fork with upstream + +on: + schedule: + # Hourly during Pacific business hours (8 AM - 6 PM PT = 16:00 - 02:00 UTC next day) + - cron: '0 16-23 * * 1-5' # Mon-Fri 16:00-23:00 UTC (8 AM - 3 PM PT) + - cron: '0 0-1 * * 2-6' # Tue-Sat 00:00-01:00 UTC (4 PM - 5 PM PT prev day) + workflow_dispatch: # Allow manual trigger + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout fork + uses: actions/checkout@v4 + with: + ref: feature/wsl-for-apps + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Add upstream remote + run: git remote add upstream https://github.com/Microsoft/WSL.git + + - name: Fetch upstream + run: git fetch upstream feature/wsl-for-apps + + - name: Check if update needed + id: check + run: | + LOCAL=$(git rev-parse HEAD) + UPSTREAM=$(git rev-parse upstream/feature/wsl-for-apps) + echo "local=$LOCAL" >> "$GITHUB_OUTPUT" + echo "upstream=$UPSTREAM" >> "$GITHUB_OUTPUT" + if [ "$LOCAL" = "$UPSTREAM" ]; then + echo "needs_update=false" >> "$GITHUB_OUTPUT" + echo "Already up to date." + else + echo "needs_update=true" >> "$GITHUB_OUTPUT" + echo "Update needed: $LOCAL -> $UPSTREAM" + fi + + - name: Fast-forward merge + if: steps.check.outputs.needs_update == 'true' + run: | + git merge --ff-only upstream/feature/wsl-for-apps + + - name: Push changes + if: steps.check.outputs.needs_update == 'true' + run: git push origin feature/wsl-for-apps From 50f9ce0c5c8b5b200b670fd3c707cfd1eb7f3f15 Mon Sep 17 00:00:00 2001 From: kvega005 Date: Thu, 5 Mar 2026 11:19:36 -0800 Subject: [PATCH 02/10] Remove upstream sync workflow --- .github/workflows/sync-upstream.yml | 49 ----------------------------- 1 file changed, 49 deletions(-) delete mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml deleted file mode 100644 index fb0c13bfc..000000000 --- a/.github/workflows/sync-upstream.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Sync fork with upstream - -on: - schedule: - # Hourly during Pacific business hours (8 AM - 6 PM PT = 16:00 - 02:00 UTC next day) - - cron: '0 16-23 * * 1-5' # Mon-Fri 16:00-23:00 UTC (8 AM - 3 PM PT) - - cron: '0 0-1 * * 2-6' # Tue-Sat 00:00-01:00 UTC (4 PM - 5 PM PT prev day) - workflow_dispatch: # Allow manual trigger - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - name: Checkout fork - uses: actions/checkout@v4 - with: - ref: feature/wsl-for-apps - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Add upstream remote - run: git remote add upstream https://github.com/Microsoft/WSL.git - - - name: Fetch upstream - run: git fetch upstream feature/wsl-for-apps - - - name: Check if update needed - id: check - run: | - LOCAL=$(git rev-parse HEAD) - UPSTREAM=$(git rev-parse upstream/feature/wsl-for-apps) - echo "local=$LOCAL" >> "$GITHUB_OUTPUT" - echo "upstream=$UPSTREAM" >> "$GITHUB_OUTPUT" - if [ "$LOCAL" = "$UPSTREAM" ]; then - echo "needs_update=false" >> "$GITHUB_OUTPUT" - echo "Already up to date." - else - echo "needs_update=true" >> "$GITHUB_OUTPUT" - echo "Update needed: $LOCAL -> $UPSTREAM" - fi - - - name: Fast-forward merge - if: steps.check.outputs.needs_update == 'true' - run: | - git merge --ff-only upstream/feature/wsl-for-apps - - - name: Push changes - if: steps.check.outputs.needs_update == 'true' - run: git push origin feature/wsl-for-apps From 3a514c82142b26835890ce8f30adda9eed6e6e92 Mon Sep 17 00:00:00 2001 From: kvega005 Date: Mon, 18 May 2026 13:31:36 -0700 Subject: [PATCH 03/10] Add CDI for wslc gpu support --- src/linux/init/WSLCInit.cpp | 107 +++++++++++++++++++++- src/linux/init/init.cpp | 6 ++ src/shared/inc/cdi_schema.h | 74 +++++++++++++++ src/shared/inc/lxinitshared.h | 5 + src/windows/inc/docker_schema.h | 11 ++- src/windows/wslcsession/WSLCContainer.cpp | 41 +-------- test/windows/WSLCTests.cpp | 24 +---- 7 files changed, 204 insertions(+), 64 deletions(-) create mode 100644 src/shared/inc/cdi_schema.h diff --git a/src/linux/init/WSLCInit.cpp b/src/linux/init/WSLCInit.cpp index 5def741aa..343a82785 100644 --- a/src/linux/init/WSLCInit.cpp +++ b/src/linux/init/WSLCInit.cpp @@ -32,6 +32,8 @@ Module Name: #include #include "mountutilcpp.h" #include +#include "JsonUtils.h" +#include "cdi_schema.h" extern int InitializeLogging(bool SetStderr, wil::LogFunction* ExceptionCallback) noexcept; @@ -56,6 +58,9 @@ extern int g_LogFd; extern void WSLCEnableCrashDumpCollection(); +#define WSLC_GPU_LIB_PATH "/usr/lib/wsl/lib" +#define WSLC_GPU_DRIVERS_PATH "/usr/lib/wsl/drivers" + struct WSLCState { std::optional ModulesMountPoint; @@ -63,10 +68,99 @@ struct WSLCState static WSLCState g_state; -int CreateCaptureCrashSymlink() +int CreateSymlink(PCSTR LinkName) +try +{ + THROW_LAST_ERROR_IF(symlink("/init", LinkName) < 0); + return 0; +} +CATCH_RETURN_ERRNO() + +void WriteWslcCdiSpec() try { - THROW_LAST_ERROR_IF(symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0); + wsl::shared::cdi::DeviceNode dxg; + dxg.path = "/dev/dxg"; + dxg.permissions = "rwm"; + + wsl::shared::cdi::Mount libs; + libs.hostPath = WSLC_GPU_LIB_PATH; + libs.containerPath = WSLC_GPU_LIB_PATH; + libs.options = {"ro", "rbind"}; + + wsl::shared::cdi::Mount drivers; + drivers.hostPath = WSLC_GPU_DRIVERS_PATH; + drivers.containerPath = WSLC_GPU_DRIVERS_PATH; + drivers.options = {"ro", "rbind"}; + + wsl::shared::cdi::Hook hook; + hook.hookName = "createContainer"; + hook.path = "/" LX_INIT_WSLC_GPU_HOOK; + hook.args = {LX_INIT_WSLC_GPU_HOOK}; + + wsl::shared::cdi::Device gpu; + gpu.name = "gpu"; + gpu.containerEdits.deviceNodes.push_back(std::move(dxg)); + gpu.containerEdits.mounts.push_back(std::move(libs)); + gpu.containerEdits.mounts.push_back(std::move(drivers)); + gpu.containerEdits.hooks.push_back(std::move(hook)); + + wsl::shared::cdi::Spec spec; + spec.cdiVersion = "0.6.0"; + spec.kind = LX_WSLC_CDI_KIND; + spec.devices.push_back(std::move(gpu)); + + THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/cdi", 0755) < 0); + THROW_LAST_ERROR_IF(WriteToFile("/etc/cdi/microsoft.com-wslc.json", nlohmann::json(spec).dump().c_str()) < 0); +} +CATCH_LOG() + +void WriteDockerDaemonConfig() +try +{ + nlohmann::json config; + config["features"]["cdi"] = true; + + THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/docker", 0755) < 0); + THROW_LAST_ERROR_IF(WriteToFile("/etc/docker/daemon.json", config.dump().c_str()) < 0); +} +CATCH_LOG() + +int WslcGpuHookEntry(int Argc, char* Argv[]) +try +{ + // OCI runtime hooks receive the container state as JSON on stdin. + std::string stateJson; + char buf[4096]; + ssize_t n; + while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) + { + stateJson.append(buf, n); + } + THROW_LAST_ERROR_IF(n < 0); + + const auto state = nlohmann::json::parse(stateJson); + const std::string bundle = state.at("bundle").get(); + + // Read the OCI spec's root.path from /config.json. For Docker/containerd this is an + // absolute path to the overlay-merged rootfs accessible from the runtime namespace. + const auto spec = nlohmann::json::parse(UtilReadFileContent(bundle + "/config.json")); + std::string rootfs = spec.at("root").at("path").get(); + if (!rootfs.empty() && rootfs.front() != '/') + { + rootfs = bundle + "/" + rootfs; + } + + const std::string confDir = rootfs + "/etc/ld.so.conf.d"; + const std::string confPath = confDir + "/ld.wsl.conf"; + + THROW_LAST_ERROR_IF(UtilMkdirPath(confDir.c_str(), 0755) < 0); + THROW_LAST_ERROR_IF(WriteToFile(confPath.c_str(), WSLC_GPU_LIB_PATH "\n") < 0); + + // chroots into the rootfs and uses the container's own /etc/ld.so.conf chain, + // writing /etc/ld.so.cache inside the container. + const char* const ldArgv[] = {"/sbin/ldconfig", "-r", rootfs.c_str(), nullptr}; + THROW_LAST_ERROR_IF(UtilCreateProcessAndWait(ldArgv[0], ldArgv) < 0); return 0; } @@ -74,7 +168,7 @@ CATCH_RETURN_ERRNO() void WSLCEnableCrashDumpCollection() { - if (CreateCaptureCrashSymlink() < 0) + if (CreateSymlink("/" LX_INIT_WSL_CAPTURE_CRASH) < 0) { return; } @@ -620,7 +714,12 @@ void HandleMessageImpl( THROW_LAST_ERROR_IF(Chroot(target) < 0); // Recreate the crash dump symlink inside the new root. - CreateCaptureCrashSymlink(); + CreateSymlink("/" LX_INIT_WSL_CAPTURE_CRASH); + + CreateSymlink("/" LX_INIT_WSLC_GPU_HOOK); + + WriteWslcCdiSpec(); + WriteDockerDaemonConfig(); } response.Result = 0; diff --git a/src/linux/init/init.cpp b/src/linux/init/init.cpp index 6b95e98bc..47b35dbc7 100644 --- a/src/linux/init/init.cpp +++ b/src/linux/init/init.cpp @@ -162,6 +162,8 @@ wil::unique_fd UnmarshalConsoleFromServer(int MessageFd, LXBUS_IPC_CONSOLE_ID Co int WslInitWatcher(int Argc, char** Argv); +int WslcGpuHookEntry(int Argc, char* Argv[]); + int WslEntryPoint(int Argc, char* Argv[]) { // @@ -228,6 +230,10 @@ int WslEntryPoint(int Argc, char* Argv[]) { ExitCode = WslInitWatcher(Argc, Argv); } + else if (strcmp(BaseName, LX_INIT_WSLC_GPU_HOOK) == 0) + { + ExitCode = WslcGpuHookEntry(Argc, Argv); + } else { // Handle the special case for import result messages, everything else is sent to the binfmt interpreter. diff --git a/src/shared/inc/cdi_schema.h b/src/shared/inc/cdi_schema.h new file mode 100644 index 000000000..150d6448a --- /dev/null +++ b/src/shared/inc/cdi_schema.h @@ -0,0 +1,74 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + cdi_schema.h + +Abstract: + + Schema for Container Device Interface (CDI) specs. + See https://github.com/cncf-tags/container-device-interface/blob/main/SPEC.md + +--*/ + +#pragma once + +#include "JsonUtils.h" + +namespace wsl::shared::cdi { + +struct DeviceNode +{ + std::string path; + std::string permissions; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(DeviceNode, path, permissions); +}; + +struct Mount +{ + std::string hostPath; + std::string containerPath; + std::vector options; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Mount, hostPath, containerPath, options); +}; + +struct Hook +{ + std::string hookName; + std::string path; + std::vector args; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Hook, hookName, path, args); +}; + +struct ContainerEdits +{ + std::vector deviceNodes; + std::vector mounts; + std::vector hooks; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerEdits, deviceNodes, mounts, hooks); +}; + +struct Device +{ + std::string name; + ContainerEdits containerEdits; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Device, name, containerEdits); +}; + +struct Spec +{ + std::string cdiVersion; + std::string kind; + std::vector devices; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Spec, cdiVersion, kind, devices); +}; + +} // namespace wsl::shared::cdi diff --git a/src/shared/inc/lxinitshared.h b/src/shared/inc/lxinitshared.h index eef6fc8a9..e892a73d4 100644 --- a/src/shared/inc/lxinitshared.h +++ b/src/shared/inc/lxinitshared.h @@ -247,6 +247,11 @@ Module Name: #define LX_INIT_WSL_INIT_WATCHER "init-watcher" +#define LX_INIT_WSLC_GPU_HOOK "wsl-gpu-hook" + +#define LX_WSLC_CDI_KIND "microsoft.com/wslc" +#define LX_WSLC_GPU_CDI_DEVICE LX_WSLC_CDI_KIND "=gpu" + // // WSL2-specific environment variables. // diff --git a/src/windows/inc/docker_schema.h b/src/windows/inc/docker_schema.h index 916524b0e..4eaa0125f 100644 --- a/src/windows/inc/docker_schema.h +++ b/src/windows/inc/docker_schema.h @@ -212,6 +212,14 @@ struct Ulimit NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Ulimit, Name, Soft, Hard); }; +struct DeviceRequest +{ + std::string Driver; + std::vector DeviceIDs; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(DeviceRequest, Driver, DeviceIDs); +}; + struct HostConfig { std::vector Mounts; @@ -225,6 +233,7 @@ struct HostConfig std::map Tmpfs; std::optional ShmSize; std::optional> Devices; + std::optional> DeviceRequests; // Per-container resource limits. 0 means "no limit" (Docker default). std::int64_t Memory{}; @@ -232,7 +241,7 @@ struct HostConfig std::optional> Ulimits; NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( - HostConfig, Mounts, PortBindings, NetworkMode, Init, Dns, DnsSearch, DnsOptions, Binds, Tmpfs, Devices, ShmSize, Memory, NanoCpus, Ulimits); + HostConfig, Mounts, PortBindings, NetworkMode, Init, Dns, DnsSearch, DnsOptions, Binds, Tmpfs, Devices, DeviceRequests, ShmSize, Memory, NanoCpus, Ulimits); }; struct EndpointSettings diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index aaa602b00..6a99f15e7 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -446,29 +446,6 @@ void ProcessAdditionalNetworks( } } -void ConfigureLdPathForGpu(std::vector& Env) -{ - static constexpr std::string_view ldLibraryPathPrefix = "LD_LIBRARY_PATH="; - auto it = std::ranges::find_if(Env, [](const std::string& e) { return e.starts_with(ldLibraryPathPrefix); }); - - if (it != Env.end()) - { - // If the user already has an LD_LIBRARY_PATH, append the GPU library paths to it. - auto ldPath = it->substr(ldLibraryPathPrefix.size()); - if (!ldPath.empty() && !ldPath.ends_with(":")) - { - it->append(":"); - } - - it->append(WSLCVirtualMachine::c_gpuLibrariesPath); - } - else - { - // Otherwise create a new entry. - Env.emplace_back(std::format("LD_LIBRARY_PATH={}", WSLCVirtualMachine::c_gpuLibrariesPath)); - } -} - } // namespace ContainerPortMapping::ContainerPortMapping(VMPortMapping&& VmMapping, uint16_t ContainerPort) : @@ -1119,11 +1096,6 @@ void WSLCContainerImpl::Exec(const WSLCProcessOptions* Options, LPCSTR DetachKey request.DetachKeys = DetachKeys; } - if (WI_IsFlagSet(m_containerFlags, WSLCContainerFlagsGpu)) - { - ConfigureLdPathForGpu(request.Env); - } - try { auto result = m_dockerClient.CreateExec(m_id, request); @@ -1510,17 +1482,8 @@ std::unique_ptr WSLCContainerImpl::Create( !virtualMachine.FeatureEnabled(WslcFeatureFlagsGPU), "WSLCContainerFlagsGpu requires GPU support enabled on the session"); - if (!request.HostConfig.Binds.has_value()) - { - request.HostConfig.Binds = std::vector{}; - } - - request.HostConfig.Binds->push_back(std::format("{0}:{0}:ro", WSLCVirtualMachine::c_gpuLibrariesPath)); - request.HostConfig.Binds->push_back(std::format("{0}:{0}:ro", WSLCVirtualMachine::c_gpuDriversPath)); - - request.HostConfig.Devices = {{"/dev/dxg", "/dev/dxg", "rwm"}}; - - ConfigureLdPathForGpu(request.Env); + // Request the WSL GPU device via CDI. + request.HostConfig.DeviceRequests = std::vector{{"cdi", {LX_WSLC_GPU_CDI_DEVICE}}}; } // Prepare port mappings from container options. diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index cf7511e57..2cc4d58a0 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -3446,17 +3446,6 @@ class WSLCTests auto session = CreateSession(settings); - // Validate that the GPU is correctly configured for containers init process. - { - WSLCContainerLauncher launcher( - "debian:latest", "test-container-init-gpu", {"/bin/sh", "-c", "test -c /dev/dxg && echo $LD_LIBRARY_PATH"}); - launcher.SetContainerFlags(WSLCContainerFlagsGpu); - - auto container = launcher.Launch(*session); - - ValidateContainerOutput(container, {{1, "/usr/lib/wsl/lib\n"}}, 0); - } - // Validate that GPU resources are available inside a container when WSLCContainerFlagsGpu is set. { WSLCContainerLauncher launcher("debian:latest", "test-container-gpu", {"sleep", "99999"}); @@ -3485,15 +3474,10 @@ class WSLCTests expect({"/usr/bin/touch", "/usr/lib/wsl/lib/test"}, 1); expect({"/usr/bin/touch", "/usr/lib/wsl/drivers/test"}, 1); - // Validate that LD_LIBRARY_PATH is set to include the GPU library path. - expect({"/bin/sh", "-c", "echo $LD_LIBRARY_PATH"}, 0, {{1, "/usr/lib/wsl/lib\n"}}); - - // Validate that exec with a pre-existing LD_LIBRARY_PATH appends the GPU path. - expect({"/bin/sh", "-c", "echo $LD_LIBRARY_PATH"}, 0, {{1, "/custom/path:/usr/lib/wsl/lib\n"}}, {"LD_LIBRARY_PATH=/custom/path"}); - - // Validate that exec with a trailing colon in LD_LIBRARY_PATH doesn't produce a double colon. - expect({"/bin/sh", "-c", "echo $LD_LIBRARY_PATH"}, 0, {{1, "/custom/path:/usr/lib/wsl/lib\n"}}, {"LD_LIBRARY_PATH=/custom/path:"}); - expect({"/bin/sh", "-c", "echo $LD_LIBRARY_PATH"}, 0, {{1, "/usr/lib/wsl/lib\n"}}, {"LD_LIBRARY_PATH="}); + // Validate that the wsl-gpu runtime dropped the ld.so.conf fragment and pre-populated the + // ld.so.cache so that /usr/lib/wsl/lib is resolvable via the dynamic linker. + expect({"/bin/sh", "-c", "cat /etc/ld.so.conf.d/ld.wsl.conf"}, 0, {{1, "/usr/lib/wsl/lib\n"}}); + expect({"/bin/sh", "-c", "ldconfig -p | grep -q ' => /usr/lib/wsl/lib/'"}, 0); } // Validate that containers without the GPU flag do not have GPU resources. From 66740bb8d0db37088abc4f4e0203706f107ad86d Mon Sep 17 00:00:00 2001 From: kvega005 Date: Mon, 18 May 2026 15:48:07 -0700 Subject: [PATCH 04/10] Address feedback --- src/linux/init/WSLCInit.cpp | 40 +++++++++++++++++++++++++++---------- src/linux/init/init.cpp | 4 ++-- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/linux/init/WSLCInit.cpp b/src/linux/init/WSLCInit.cpp index 343a82785..f05ec211f 100644 --- a/src/linux/init/WSLCInit.cpp +++ b/src/linux/init/WSLCInit.cpp @@ -34,6 +34,7 @@ Module Name: #include #include "JsonUtils.h" #include "cdi_schema.h" +#include "lxfsshares.h" extern int InitializeLogging(bool SetStderr, wil::LogFunction* ExceptionCallback) noexcept; @@ -58,8 +59,7 @@ extern int g_LogFd; extern void WSLCEnableCrashDumpCollection(); -#define WSLC_GPU_LIB_PATH "/usr/lib/wsl/lib" -#define WSLC_GPU_DRIVERS_PATH "/usr/lib/wsl/drivers" +#define WSLC_GPU_DRIVERS_PATH LXSS_LIB_PREFIX "/drivers" struct WSLCState { @@ -71,7 +71,7 @@ static WSLCState g_state; int CreateSymlink(PCSTR LinkName) try { - THROW_LAST_ERROR_IF(symlink("/init", LinkName) < 0); + THROW_LAST_ERROR_IF(symlink("/init", LinkName) < 0 && errno != EEXIST) return 0; } CATCH_RETURN_ERRNO() @@ -84,8 +84,8 @@ try dxg.permissions = "rwm"; wsl::shared::cdi::Mount libs; - libs.hostPath = WSLC_GPU_LIB_PATH; - libs.containerPath = WSLC_GPU_LIB_PATH; + libs.hostPath = LXSS_LIB_PATH; + libs.containerPath = LXSS_LIB_PATH; libs.options = {"ro", "rbind"}; wsl::shared::cdi::Mount drivers; @@ -118,26 +118,44 @@ CATCH_LOG() void WriteDockerDaemonConfig() try { - nlohmann::json config; + constexpr auto c_daemonConfigPath = "/etc/docker/daemon.json"; + + // Merge into any existing daemon.json rather than overwriting it, so user/image-provided settings are preserved. + nlohmann::json config = nlohmann::json::object(); + if (std::filesystem::exists(c_daemonConfigPath)) + { + try + { + auto existing = nlohmann::json::parse(UtilReadFileContent(c_daemonConfigPath)); + + if (existing.is_object()) + { + config = std::move(existing); + } + } + CATCH_LOG() + } + config["features"]["cdi"] = true; THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/docker", 0755) < 0); - THROW_LAST_ERROR_IF(WriteToFile("/etc/docker/daemon.json", config.dump().c_str()) < 0); + THROW_LAST_ERROR_IF(WriteToFile(c_daemonConfigPath, config.dump().c_str()) < 0); } CATCH_LOG() -int WslcGpuHookEntry(int Argc, char* Argv[]) +int WslcGpuHookEntry() try { // OCI runtime hooks receive the container state as JSON on stdin. std::string stateJson; char buf[4096]; ssize_t n; - while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) + while ((n = TEMP_FAILURE_RETRY(read(STDIN_FILENO, buf, sizeof(buf)))) > 0) { stateJson.append(buf, n); } THROW_LAST_ERROR_IF(n < 0); + THROW_ERRNO_IF(EINVAL, stateJson.empty()); const auto state = nlohmann::json::parse(stateJson); const std::string bundle = state.at("bundle").get(); @@ -155,11 +173,11 @@ try const std::string confPath = confDir + "/ld.wsl.conf"; THROW_LAST_ERROR_IF(UtilMkdirPath(confDir.c_str(), 0755) < 0); - THROW_LAST_ERROR_IF(WriteToFile(confPath.c_str(), WSLC_GPU_LIB_PATH "\n") < 0); + THROW_LAST_ERROR_IF(WriteToFile(confPath.c_str(), LXSS_LIB_PATH "\n") < 0); // chroots into the rootfs and uses the container's own /etc/ld.so.conf chain, // writing /etc/ld.so.cache inside the container. - const char* const ldArgv[] = {"/sbin/ldconfig", "-r", rootfs.c_str(), nullptr}; + const char* const ldArgv[] = {LDCONFIG_COMMAND, "-r", rootfs.c_str(), nullptr}; THROW_LAST_ERROR_IF(UtilCreateProcessAndWait(ldArgv[0], ldArgv) < 0); return 0; diff --git a/src/linux/init/init.cpp b/src/linux/init/init.cpp index 47b35dbc7..da1ad4e34 100644 --- a/src/linux/init/init.cpp +++ b/src/linux/init/init.cpp @@ -162,7 +162,7 @@ wil::unique_fd UnmarshalConsoleFromServer(int MessageFd, LXBUS_IPC_CONSOLE_ID Co int WslInitWatcher(int Argc, char** Argv); -int WslcGpuHookEntry(int Argc, char* Argv[]); +int WslcGpuHookEntry(); int WslEntryPoint(int Argc, char* Argv[]) { @@ -232,7 +232,7 @@ int WslEntryPoint(int Argc, char* Argv[]) } else if (strcmp(BaseName, LX_INIT_WSLC_GPU_HOOK) == 0) { - ExitCode = WslcGpuHookEntry(Argc, Argv); + ExitCode = WslcGpuHookEntry(); } else { From dcfd971e029e8a21e17bb1919ddecc897301f8f6 Mon Sep 17 00:00:00 2001 From: kvega005 Date: Mon, 18 May 2026 16:01:05 -0700 Subject: [PATCH 05/10] Fix typo --- src/linux/init/WSLCInit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/init/WSLCInit.cpp b/src/linux/init/WSLCInit.cpp index f05ec211f..5591c17ba 100644 --- a/src/linux/init/WSLCInit.cpp +++ b/src/linux/init/WSLCInit.cpp @@ -71,7 +71,7 @@ static WSLCState g_state; int CreateSymlink(PCSTR LinkName) try { - THROW_LAST_ERROR_IF(symlink("/init", LinkName) < 0 && errno != EEXIST) + THROW_LAST_ERROR_IF(symlink("/init", LinkName) < 0 && errno != EEXIST); return 0; } CATCH_RETURN_ERRNO() From 00522e11ece319c018754fbc762c63f8e697ae6d Mon Sep 17 00:00:00 2001 From: kvega005 Date: Tue, 19 May 2026 11:20:25 -0700 Subject: [PATCH 06/10] Address feedback --- src/linux/init/WSLCInit.cpp | 77 ++++++++++++++++--------------------- src/linux/init/util.cpp | 8 +++- src/linux/init/util.h | 2 +- src/shared/inc/lxfsshares.h | 3 +- test/windows/WSLCTests.cpp | 32 ++++++++++++++- 5 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/linux/init/WSLCInit.cpp b/src/linux/init/WSLCInit.cpp index 5591c17ba..539c57074 100644 --- a/src/linux/init/WSLCInit.cpp +++ b/src/linux/init/WSLCInit.cpp @@ -32,6 +32,7 @@ Module Name: #include #include "mountutilcpp.h" #include +#include #include "JsonUtils.h" #include "cdi_schema.h" #include "lxfsshares.h" @@ -59,8 +60,6 @@ extern int g_LogFd; extern void WSLCEnableCrashDumpCollection(); -#define WSLC_GPU_DRIVERS_PATH LXSS_LIB_PREFIX "/drivers" - struct WSLCState { std::optional ModulesMountPoint; @@ -68,50 +67,43 @@ struct WSLCState static WSLCState g_state; -int CreateSymlink(PCSTR LinkName) -try -{ - THROW_LAST_ERROR_IF(symlink("/init", LinkName) < 0 && errno != EEXIST); - return 0; -} -CATCH_RETURN_ERRNO() - void WriteWslcCdiSpec() try { - wsl::shared::cdi::DeviceNode dxg; + wsl::shared::cdi::DeviceNode dxg{}; dxg.path = "/dev/dxg"; dxg.permissions = "rwm"; - wsl::shared::cdi::Mount libs; + wsl::shared::cdi::Mount libs{}; libs.hostPath = LXSS_LIB_PATH; libs.containerPath = LXSS_LIB_PATH; libs.options = {"ro", "rbind"}; - wsl::shared::cdi::Mount drivers; - drivers.hostPath = WSLC_GPU_DRIVERS_PATH; - drivers.containerPath = WSLC_GPU_DRIVERS_PATH; + wsl::shared::cdi::Mount drivers{}; + drivers.hostPath = LXSS_GPU_DRIVERS_PATH; + drivers.containerPath = LXSS_GPU_DRIVERS_PATH; drivers.options = {"ro", "rbind"}; - wsl::shared::cdi::Hook hook; + wsl::shared::cdi::Hook hook{}; hook.hookName = "createContainer"; hook.path = "/" LX_INIT_WSLC_GPU_HOOK; hook.args = {LX_INIT_WSLC_GPU_HOOK}; - wsl::shared::cdi::Device gpu; + wsl::shared::cdi::Device gpu{}; gpu.name = "gpu"; gpu.containerEdits.deviceNodes.push_back(std::move(dxg)); gpu.containerEdits.mounts.push_back(std::move(libs)); gpu.containerEdits.mounts.push_back(std::move(drivers)); gpu.containerEdits.hooks.push_back(std::move(hook)); - wsl::shared::cdi::Spec spec; + wsl::shared::cdi::Spec spec{}; spec.cdiVersion = "0.6.0"; spec.kind = LX_WSLC_CDI_KIND; spec.devices.push_back(std::move(gpu)); THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/cdi", 0755) < 0); - THROW_LAST_ERROR_IF(WriteToFile("/etc/cdi/microsoft.com-wslc.json", nlohmann::json(spec).dump().c_str()) < 0); + THROW_LAST_ERROR_IF( + WriteToFile("/etc/cdi/microsoft.com-wslc.json", nlohmann::json(spec).dump().c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0); } CATCH_LOG() @@ -139,7 +131,7 @@ try config["features"]["cdi"] = true; THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/docker", 0755) < 0); - THROW_LAST_ERROR_IF(WriteToFile(c_daemonConfigPath, config.dump().c_str()) < 0); + THROW_LAST_ERROR_IF(WriteToFile(c_daemonConfigPath, config.dump().c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0); } CATCH_LOG() @@ -147,37 +139,36 @@ int WslcGpuHookEntry() try { // OCI runtime hooks receive the container state as JSON on stdin. - std::string stateJson; - char buf[4096]; - ssize_t n; - while ((n = TEMP_FAILURE_RETRY(read(STDIN_FILENO, buf, sizeof(buf)))) > 0) - { - stateJson.append(buf, n); - } - THROW_LAST_ERROR_IF(n < 0); + const std::string stateJson{std::istreambuf_iterator(std::cin), {}}; THROW_ERRNO_IF(EINVAL, stateJson.empty()); const auto state = nlohmann::json::parse(stateJson); - const std::string bundle = state.at("bundle").get(); + const std::filesystem::path bundle = state.at("bundle").get(); + THROW_ERRNO_IF(EINVAL, !bundle.is_absolute()); // Read the OCI spec's root.path from /config.json. For Docker/containerd this is an - // absolute path to the overlay-merged rootfs accessible from the runtime namespace. - const auto spec = nlohmann::json::parse(UtilReadFileContent(bundle + "/config.json")); - std::string rootfs = spec.at("root").at("path").get(); - if (!rootfs.empty() && rootfs.front() != '/') + // absolute path to the overlay-merged rootfs accessible from the runtime namespace or a path + // relative to the bundle directory. + const auto spec = nlohmann::json::parse(UtilReadFileContent((bundle / "config.json").native())); + std::filesystem::path rootfsPath = spec.at("root").at("path").get(); + if (rootfsPath.is_relative()) { - rootfs = bundle + "/" + rootfs; + rootfsPath = bundle / rootfsPath; } - const std::string confDir = rootfs + "/etc/ld.so.conf.d"; - const std::string confPath = confDir + "/ld.wsl.conf"; + rootfsPath = std::filesystem::canonical(rootfsPath); + THROW_ERRNO_IF(EINVAL, rootfsPath == "/"); + THROW_ERRNO_IF(ENOTDIR, !std::filesystem::is_directory(rootfsPath)); + + const auto confDir = rootfsPath / "etc/ld.so.conf.d"; + const auto confPath = confDir / "ld.wsl.conf"; THROW_LAST_ERROR_IF(UtilMkdirPath(confDir.c_str(), 0755) < 0); - THROW_LAST_ERROR_IF(WriteToFile(confPath.c_str(), LXSS_LIB_PATH "\n") < 0); + THROW_LAST_ERROR_IF(WriteToFile(confPath.c_str(), LXSS_LIB_PATH "\n", O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC | O_NOFOLLOW) < 0); // chroots into the rootfs and uses the container's own /etc/ld.so.conf chain, // writing /etc/ld.so.cache inside the container. - const char* const ldArgv[] = {LDCONFIG_COMMAND, "-r", rootfs.c_str(), nullptr}; + const char* const ldArgv[] = {LDCONFIG_COMMAND, "-r", rootfsPath.c_str(), nullptr}; THROW_LAST_ERROR_IF(UtilCreateProcessAndWait(ldArgv[0], ldArgv) < 0); return 0; @@ -186,8 +177,9 @@ CATCH_RETURN_ERRNO() void WSLCEnableCrashDumpCollection() { - if (CreateSymlink("/" LX_INIT_WSL_CAPTURE_CRASH) < 0) + if (symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0 && errno != EEXIST) { + LOG_ERROR("symlink(/init, /" LX_INIT_WSL_CAPTURE_CRASH ") failed {}", errno); return; } @@ -731,10 +723,9 @@ void HandleMessageImpl( { THROW_LAST_ERROR_IF(Chroot(target) < 0); - // Recreate the crash dump symlink inside the new root. - CreateSymlink("/" LX_INIT_WSL_CAPTURE_CRASH); - - CreateSymlink("/" LX_INIT_WSLC_GPU_HOOK); + // Recreate the /init symlinks inside the new root. + THROW_LAST_ERROR_IF(symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0 && errno != EEXIST); + THROW_LAST_ERROR_IF(symlink("/init", "/" LX_INIT_WSLC_GPU_HOOK) < 0 && errno != EEXIST); WriteWslcCdiSpec(); WriteDockerDaemonConfig(); diff --git a/src/linux/init/util.cpp b/src/linux/init/util.cpp index 2c6f3ddae..a7dfdd7d3 100644 --- a/src/linux/init/util.cpp +++ b/src/linux/init/util.cpp @@ -3338,7 +3338,7 @@ uint16_t UtilWinAfToLinuxAf(uint16_t WinAddressFamily) return LinuxAddressFamily; } -int WriteToFile(const char* Path, const char* Content, int permissions) +int WriteToFile(const char* Path, const char* Content, int OpenFlags, int Permissions) /*++ @@ -3352,6 +3352,10 @@ Routine Description: Content - Supplies the content to be written to the file. + Permissions - Supplies the file mode used when O_CREAT causes the file to be created. + + OpenFlags - Supplies the flags passed to open(). Defaults to O_WRONLY | O_CLOEXEC | O_CREAT. + Return Value: 0 on success, -1 on failure. @@ -3359,7 +3363,7 @@ Return Value: --*/ { - wil::unique_fd Fd{open(Path, (O_WRONLY | O_CLOEXEC | O_CREAT), permissions)}; + wil::unique_fd Fd{open(Path, OpenFlags, Permissions)}; if (!Fd) { int errnoPrev = errno; diff --git a/src/linux/init/util.h b/src/linux/init/util.h index 1d287edd0..0a180cbe4 100644 --- a/src/linux/init/util.h +++ b/src/linux/init/util.h @@ -313,6 +313,6 @@ std::string UtilReadFileContent(std::string_view path); uint16_t UtilWinAfToLinuxAf(uint16_t AddressFamily); -int WriteToFile(const char* Path, const char* Content, int permissions = 0644); +int WriteToFile(const char* Path, const char* Content, int OpenFlags = O_WRONLY | O_CLOEXEC | O_CREAT, int Permissions = 0644); int ProcessCreateProcessMessage(wsl::shared::Transaction& Transaction, gsl::span Buffer); \ No newline at end of file diff --git a/src/shared/inc/lxfsshares.h b/src/shared/inc/lxfsshares.h index e7be3d0a9..23fd21922 100644 --- a/src/shared/inc/lxfsshares.h +++ b/src/shared/inc/lxfsshares.h @@ -23,6 +23,7 @@ typedef struct _LXSS_SHARED_DIRECTORY #define LXSS_LIB_PREFIX "/usr/lib/wsl" #define LXSS_LIB_PATH LXSS_LIB_PREFIX "/lib" +#define LXSS_GPU_DRIVERS_PATH LXSS_LIB_PREFIX "/drivers" #define LXSS_GPU_DRIVERS_SHARE "drivers" #define LXSS_GPU_LIB_SHARE "lib" #define LXSS_GPU_INBOX_LIB_SHARE LXSS_GPU_LIB_SHARE "_inbox" @@ -32,4 +33,4 @@ typedef struct _LXSS_SHARED_DIRECTORY // Shared directories for GPU compute support. // -constexpr LXSS_SHARED_DIRECTORY g_gpuShares[] = {{LXSS_GPU_DRIVERS_SHARE, LXSS_LIB_PREFIX "/drivers"}, {LXSS_GPU_LIB_SHARE, LXSS_LIB_PATH}}; +constexpr LXSS_SHARED_DIRECTORY g_gpuShares[] = {{LXSS_GPU_DRIVERS_SHARE, LXSS_GPU_DRIVERS_PATH}, {LXSS_GPU_LIB_SHARE, LXSS_LIB_PATH}}; diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index ec6bb4b30..160c9928b 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -3384,7 +3384,37 @@ class WSLCTests WSLCContainerLauncher launcher("debian:latest", "test-container-gpu", {"sleep", "99999"}); launcher.SetContainerFlags(WSLCContainerFlagsGpu); - auto container = launcher.Launch(*session); + auto [launchResult, maybeContainer] = launcher.LaunchNoThrow(*session); + if (FAILED(launchResult)) + { + LogError("Launch failed with 0x%08x; dumping session diagnostics for triage", launchResult); + + auto dumpCommand = [&](const std::vector& cmd) { + auto cmdStr = wsl::shared::string::Join(cmd, ' '); + auto result = RunCommand(session.get(), cmd); + LogError( + "$ %hs\n exit=%i\n stdout=%hs\n stderr=%hs", + cmdStr.c_str(), + result.Code, + result.Output[1].c_str(), + result.Output[2].c_str()); + }; + + dumpCommand({"/bin/dmesg"}); + dumpCommand({"/bin/cat", "/etc/cdi/microsoft.com-wslc.json"}); + dumpCommand({"/bin/cat", "/etc/docker/daemon.json"}); + dumpCommand({"/usr/bin/docker", "info", "-f", "{{json .}}"}); + dumpCommand({"/bin/ls", "-la", "/wsl-gpu-hook", "/init"}); + dumpCommand({"/usr/bin/docker", "ps", "-a"}); + dumpCommand( + {"/bin/sh", + "-c", + "for id in $(docker ps -aq); do echo \"=== $id ===\"; docker inspect $id; echo \"--- logs ---\"; docker " + "logs $id 2>&1; done"}); + } + VERIFY_SUCCEEDED(launchResult); + + auto container = std::move(maybeContainer.value()); auto expect = [&](const std::vector command, int exitCode, From e2d97fd11a9a778d3b3ebbead3d6ce76dc92275d Mon Sep 17 00:00:00 2001 From: kvega005 Date: Tue, 19 May 2026 13:36:26 -0700 Subject: [PATCH 07/10] Address more feedback --- src/linux/init/WSLCInit.cpp | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/linux/init/WSLCInit.cpp b/src/linux/init/WSLCInit.cpp index 539c57074..fcc78508c 100644 --- a/src/linux/init/WSLCInit.cpp +++ b/src/linux/init/WSLCInit.cpp @@ -112,22 +112,9 @@ try { constexpr auto c_daemonConfigPath = "/etc/docker/daemon.json"; - // Merge into any existing daemon.json rather than overwriting it, so user/image-provided settings are preserved. - nlohmann::json config = nlohmann::json::object(); - if (std::filesystem::exists(c_daemonConfigPath)) - { - try - { - auto existing = nlohmann::json::parse(UtilReadFileContent(c_daemonConfigPath)); - - if (existing.is_object()) - { - config = std::move(existing); - } - } - CATCH_LOG() - } + THROW_ERRNO_IF(EEXIST, std::filesystem::exists(c_daemonConfigPath)); + nlohmann::json config = nlohmann::json::object(); config["features"]["cdi"] = true; THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/docker", 0755) < 0); From e93a2ec35c3fcd6d03c3f3f7643efa3a9ebf99ae Mon Sep 17 00:00:00 2001 From: kvega005 Date: Tue, 19 May 2026 13:43:40 -0700 Subject: [PATCH 08/10] Update comments --- src/linux/init/WSLCInit.cpp | 5 ++--- test/windows/WSLCTests.cpp | 32 +------------------------------- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/src/linux/init/WSLCInit.cpp b/src/linux/init/WSLCInit.cpp index fcc78508c..3b3f2748b 100644 --- a/src/linux/init/WSLCInit.cpp +++ b/src/linux/init/WSLCInit.cpp @@ -133,9 +133,8 @@ try const std::filesystem::path bundle = state.at("bundle").get(); THROW_ERRNO_IF(EINVAL, !bundle.is_absolute()); - // Read the OCI spec's root.path from /config.json. For Docker/containerd this is an - // absolute path to the overlay-merged rootfs accessible from the runtime namespace or a path - // relative to the bundle directory. + // Read the OCI spec's root.path from /config.json. This is either an absolute path to + // the overlay-merged rootfs or a path relative to the bundle directory. const auto spec = nlohmann::json::parse(UtilReadFileContent((bundle / "config.json").native())); std::filesystem::path rootfsPath = spec.at("root").at("path").get(); if (rootfsPath.is_relative()) diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 160c9928b..ec6bb4b30 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -3384,37 +3384,7 @@ class WSLCTests WSLCContainerLauncher launcher("debian:latest", "test-container-gpu", {"sleep", "99999"}); launcher.SetContainerFlags(WSLCContainerFlagsGpu); - auto [launchResult, maybeContainer] = launcher.LaunchNoThrow(*session); - if (FAILED(launchResult)) - { - LogError("Launch failed with 0x%08x; dumping session diagnostics for triage", launchResult); - - auto dumpCommand = [&](const std::vector& cmd) { - auto cmdStr = wsl::shared::string::Join(cmd, ' '); - auto result = RunCommand(session.get(), cmd); - LogError( - "$ %hs\n exit=%i\n stdout=%hs\n stderr=%hs", - cmdStr.c_str(), - result.Code, - result.Output[1].c_str(), - result.Output[2].c_str()); - }; - - dumpCommand({"/bin/dmesg"}); - dumpCommand({"/bin/cat", "/etc/cdi/microsoft.com-wslc.json"}); - dumpCommand({"/bin/cat", "/etc/docker/daemon.json"}); - dumpCommand({"/usr/bin/docker", "info", "-f", "{{json .}}"}); - dumpCommand({"/bin/ls", "-la", "/wsl-gpu-hook", "/init"}); - dumpCommand({"/usr/bin/docker", "ps", "-a"}); - dumpCommand( - {"/bin/sh", - "-c", - "for id in $(docker ps -aq); do echo \"=== $id ===\"; docker inspect $id; echo \"--- logs ---\"; docker " - "logs $id 2>&1; done"}); - } - VERIFY_SUCCEEDED(launchResult); - - auto container = std::move(maybeContainer.value()); + auto container = launcher.Launch(*session); auto expect = [&](const std::vector command, int exitCode, From ae3c9d3ec46d76fb7ba3cf062b19fb580835b4c8 Mon Sep 17 00:00:00 2001 From: kvega005 Date: Tue, 19 May 2026 13:56:18 -0700 Subject: [PATCH 09/10] Update tests --- test/windows/WSLCTests.cpp | 5 +++-- test/windows/WslcSdkTests.cpp | 6 ++++-- test/windows/WslcSdkWinRTTests.cpp | 9 ++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index ec6bb4b30..0cffc2438 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -3394,8 +3394,9 @@ class WSLCTests ValidateProcessOutput(process, expectedOutput, exitCode); }; - // Validate that /dev/dxg is available as a character device. - expect({"/bin/sh", "-c", "test -c /dev/dxg"}, 0); + // Validate that /dev/dxg is available as a character device with the + // read/write/mknod permissions. + expect({"/bin/sh", "-c", "test -c /dev/dxg && test -r /dev/dxg && test -w /dev/dxg"}, 0); // Validate that the GPU library directory is mounted and contains libraries. expect({"/bin/sh", "-c", "test -d /usr/lib/wsl/lib && ls /usr/lib/wsl/lib | grep -q ."}, 0); diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index 5644c68d6..e659e3165 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -2509,9 +2509,11 @@ class WslcSdkTests VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &gpuSession, nullptr)); THROW_IF_FAILED(WslcLoadSessionImageFromFile(gpuSession.get(), GetTestImagePath("debian:latest").c_str(), nullptr, nullptr)); - // Validate /dev/dxg is available and LD_LIBRARY_PATH is set via the container init command. + // Validate /dev/dxg is available and the wsl-gpu hook configured the dynamic linker + // via the container init command. { - const char* initArgv[] = {"/bin/sh", "-c", "test -c /dev/dxg && echo $LD_LIBRARY_PATH"}; + const char* initArgv[] = { + "/bin/sh", "-c", "test -c /dev/dxg && test -r /dev/dxg && test -w /dev/dxg && cat /etc/ld.so.conf.d/ld.wsl.conf"}; auto output = RunContainerAndCapture( gpuSession.get(), "debian:latest", {initArgv[0], initArgv[1], initArgv[2]}, WSLC_CONTAINER_FLAG_ENABLE_GPU); diff --git a/test/windows/WslcSdkWinRTTests.cpp b/test/windows/WslcSdkWinRTTests.cpp index 171a9395e..ccd00d82b 100644 --- a/test/windows/WslcSdkWinRTTests.cpp +++ b/test/windows/WslcSdkWinRTTests.cpp @@ -1656,11 +1656,14 @@ class WslcSdkWinRtTests const auto debianTar = GetTestImagePath("debian:latest"); gpuSession.LoadImageAsync(debianTar.wstring()).get(); - // Positive: /dev/dxg must be available and LD_LIBRARY_PATH set in a GPU container. + // Positive: /dev/dxg must be available with rwm permissions and the wsl-gpu hook must have + // configured the dynamic linker inside a GPU container. { auto procSettings = WSLCSDK::ProcessSettings(); - procSettings.CmdLine( - winrt::single_threaded_vector({L"/bin/sh", L"-c", L"test -c /dev/dxg && echo $LD_LIBRARY_PATH"})); + procSettings.CmdLine(winrt::single_threaded_vector( + {L"/bin/sh", + L"-c", + L"test -c /dev/dxg && test -r /dev/dxg && test -w /dev/dxg && cat /etc/ld.so.conf.d/ld.wsl.conf"})); procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Stream); auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest"); From 5ac6de07ac0cc2017d41270ed0242b52bd9c8c6f Mon Sep 17 00:00:00 2001 From: kvega005 Date: Tue, 19 May 2026 14:00:15 -0700 Subject: [PATCH 10/10] Update test comments --- test/windows/WSLCTests.cpp | 3 +-- test/windows/WslcSdkTests.cpp | 4 ++-- test/windows/WslcSdkWinRTTests.cpp | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 0cffc2438..1464a6427 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -3408,8 +3408,7 @@ class WSLCTests expect({"/usr/bin/touch", "/usr/lib/wsl/lib/test"}, 1); expect({"/usr/bin/touch", "/usr/lib/wsl/drivers/test"}, 1); - // Validate that the wsl-gpu runtime dropped the ld.so.conf fragment and pre-populated the - // ld.so.cache so that /usr/lib/wsl/lib is resolvable via the dynamic linker. + // Validate that the dynamic linker is configured to resolve the WSL GPU libraries. expect({"/bin/sh", "-c", "cat /etc/ld.so.conf.d/ld.wsl.conf"}, 0, {{1, "/usr/lib/wsl/lib\n"}}); expect({"/bin/sh", "-c", "ldconfig -p | grep -q ' => /usr/lib/wsl/lib/'"}, 0); } diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index e659e3165..4305d12e2 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -2509,8 +2509,8 @@ class WslcSdkTests VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &gpuSession, nullptr)); THROW_IF_FAILED(WslcLoadSessionImageFromFile(gpuSession.get(), GetTestImagePath("debian:latest").c_str(), nullptr, nullptr)); - // Validate /dev/dxg is available and the wsl-gpu hook configured the dynamic linker - // via the container init command. + // Validate /dev/dxg is available and the dynamic linker is configured to resolve the WSL + // GPU libraries. { const char* initArgv[] = { "/bin/sh", "-c", "test -c /dev/dxg && test -r /dev/dxg && test -w /dev/dxg && cat /etc/ld.so.conf.d/ld.wsl.conf"}; diff --git a/test/windows/WslcSdkWinRTTests.cpp b/test/windows/WslcSdkWinRTTests.cpp index ccd00d82b..2eb679625 100644 --- a/test/windows/WslcSdkWinRTTests.cpp +++ b/test/windows/WslcSdkWinRTTests.cpp @@ -1656,8 +1656,8 @@ class WslcSdkWinRtTests const auto debianTar = GetTestImagePath("debian:latest"); gpuSession.LoadImageAsync(debianTar.wstring()).get(); - // Positive: /dev/dxg must be available with rwm permissions and the wsl-gpu hook must have - // configured the dynamic linker inside a GPU container. + // Positive: /dev/dxg must be available with rwm permissions and the dynamic linker must be + // configured to resolve the WSL GPU libraries inside a GPU container. { auto procSettings = WSLCSDK::ProcessSettings(); procSettings.CmdLine(winrt::single_threaded_vector(