From 7cec921145b4a3662240f746b07634d5d2f92444 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 25 Mar 2026 17:38:02 -0700 Subject: [PATCH 1/8] Implement inspect and test --- src/windows/WslcSDK/wslcsdk.cpp | 17 +++++++++++++---- src/windows/WslcSDK/wslcsdk.h | 2 +- test/windows/WslcSdkTests.cpp | 16 ++++++++++++---- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 4d43168df..64af856c2 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -773,12 +773,21 @@ try } CATCH_RETURN(); -STDAPI WslcInspectContainer(_In_ WslcContainer container, _Outptr_result_z_ PCSTR* inspectData) +STDAPI WslcInspectContainer(_In_ WslcContainer container, _Outptr_result_z_ PSTR* inspectData) try { - UNREFERENCED_PARAMETER(container); - UNREFERENCED_PARAMETER(inspectData); - return E_NOTIMPL; + auto internalType = CheckAndGetInternalType(container); + RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->container); + RETURN_HR_IF_NULL(E_POINTER, inspectData); + + *inspectData = nullptr; + + wil::unique_cotaskmem_ansistring result; + RETURN_IF_FAILED(internalType->container->Inspect(&result)); + + *inspectData = result.release(); + + return S_OK; } CATCH_RETURN(); diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index e08394383..6664d98b2 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -213,7 +213,7 @@ STDAPI WslcGetContainerInitProcess(_In_ WslcContainer container, _Out_ WslcProce // Notes: // - The caller must pass a non-null pointer to a PCSTR variable. // - The returned string is immutable and must not be modified by the caller. -STDAPI WslcInspectContainer(_In_ WslcContainer container, _Outptr_result_z_ PCSTR* inspectData); +STDAPI WslcInspectContainer(_In_ WslcContainer container, _Outptr_result_z_ PSTR* inspectData); typedef enum WslcContainerState { diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index 5cbec4a07..dbdd05829 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -15,6 +15,7 @@ Module Name: #include "precomp.h" #include "Common.h" #include "wslcsdk.h" +#include "wsla_schema.h" #include extern std::wstring g_testDataPath; @@ -1891,7 +1892,7 @@ class WslcSdkTests VERIFY_ARE_EQUAL(WslcCanRun(&canRun, &missing), E_NOTIMPL); } - TEST_METHOD(ContainerInspectNotImplemented) + TEST_METHOD(ContainerInspect) { WSL2_TEST_ONLY(); @@ -1900,10 +1901,17 @@ class WslcSdkTests VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr)); - PCSTR inspectData = nullptr; - VERIFY_ARE_EQUAL(WslcInspectContainer(container.get(), &inspectData), E_NOTIMPL); + wil::unique_cotaskmem_ansistring inspectData; + VERIFY_SUCCEEDED(WslcInspectContainer(container.get(), &inspectData)); - VERIFY_SUCCEEDED(WslcDeleteContainer(container.get(), WSLC_DELETE_CONTAINER_FLAG_NONE, nullptr)); + VERIFY_IS_NOT_NULL(inspectData); + + auto inspectObject = wsl::shared::FromJson(inspectData.get()); + + CHAR containerId[WSLC_CONTAINER_ID_LENGTH]; + VERIFY_SUCCEEDED(WslcGetContainerID(container.get(), containerId)); + + VERIFY_ARE_EQUAL(containerId, inspectObject.Id); } TEST_METHOD(SessionCreateVhdNotImplemented) From 93281ee023410d49ac96df7593586c396c61e83d Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 26 Mar 2026 10:07:33 -0700 Subject: [PATCH 2/8] Implement session VHD create --- src/windows/WslcSDK/wslcsdk.cpp | 37 ++++++++++++++++++++------------- src/windows/WslcSDK/wslcsdk.h | 4 +++- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 64af856c2..289ebdefa 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -340,7 +340,6 @@ try WSLASessionSettings runtimeSettings{}; runtimeSettings.DisplayName = internalType->displayName; runtimeSettings.StoragePath = internalType->storagePath; - // TODO: Is this the intended use for vhdRequirements.sizeInBytes? runtimeSettings.MaximumStorageSizeMb = internalType->vhdRequirements.sizeInBytes / _1MB; runtimeSettings.CpuCount = internalType->cpuCount; runtimeSettings.MemoryMb = internalType->memoryMb; @@ -354,16 +353,6 @@ try } runtimeSettings.FeatureFlags = ConvertFlags(internalType->featureFlags); - // TODO: Debug message output? No user control? Expects a handle value as a ULONG (to write debug info to?) - // runtimeSettings.DmesgOutput; - - // TODO: VHD overrides; I'm not sure if we intend these to be provided. - // runtimeSettings.RootVhdOverride = internalType->vhdRequirements.path; - // TODO: I don't think that this VHD type override can be reused from the VHD requirements type - // Tracking the code suggests that this is the `filesystemtype` to the linux `mount` function. - // Not clear how to map dynamic and fixed to values like `ext4` and `tmpfs`. - // runtimeSettings.RootVhdTypeOverride = ConvertType(internalType->vhdRequirements.type); - if (SUCCEEDED(errorInfoWrapper.CaptureResult(sessionManager->CreateSession(&runtimeSettings, WSLASessionFlagsNone, &result->session)))) { wsl::windows::common::security::ConfigureForCOMImpersonation(result->session.get()); @@ -405,10 +394,25 @@ CATCH_RETURN(); STDAPI WslcCreateSessionVhd(_In_ WslcSession session, _In_ const WslcVhdRequirements* options, _Outptr_opt_result_z_ PWSTR* errorMessage) try { - UNREFERENCED_PARAMETER(session); - UNREFERENCED_PARAMETER(options); - UNREFERENCED_PARAMETER(errorMessage); - return E_NOTIMPL; + ErrorInfoWrapper errorInfoWrapper{errorMessage}; + + auto internalType = CheckAndGetInternalType(session); + RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->session); + RETURN_HR_IF_NULL(E_POINTER, options); + + RETURN_HR_IF_NULL(E_INVALIDARG, options->name); + RETURN_HR_IF(E_INVALIDARG, options->sizeInBytes == 0); + RETURN_HR_IF(E_NOTIMPL, options->type == WSLC_VHD_TYPE_FIXED); + + WSLAVolumeOptions volumeOptions{}; + volumeOptions.Name = options->name; + // Only supported value currently + volumeOptions.Type = "vhd"; + + auto dynamicOptions = std::format(R"({{ "SizeBytes": {} }})", options->sizeInBytes); + volumeOptions.Options = dynamicOptions.c_str(); + + return errorInfoWrapper.CaptureResult(internalType->session->CreateVolume(&volumeOptions)); } CATCH_RETURN(); @@ -419,6 +423,9 @@ try if (vhdRequirements) { + RETURN_HR_IF(E_INVALIDARG, vhdRequirements->sizeInBytes == 0); + RETURN_HR_IF(E_NOTIMPL, vhdRequirements->type == WSLC_VHD_TYPE_FIXED); + internalType->vhdRequirements = *vhdRequirements; } else diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 6664d98b2..62714bc73 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -21,7 +21,7 @@ Module Name: EXTERN_C_START // Session values -#define WSLC_SESSION_OPTIONS_SIZE 72 +#define WSLC_SESSION_OPTIONS_SIZE 80 #define WSLC_SESSION_OPTIONS_ALIGNMENT 8 typedef struct WslcSessionSettings @@ -66,6 +66,8 @@ typedef enum WslcVhdType typedef struct WslcVhdRequirements { + // Required for WslcCreateSessionVhd + _In_opt_ PCSTR name; _In_ uint64_t sizeInBytes; // Desired size (for create/expand) _In_ WslcVhdType type; } WslcVhdRequirements; From a7ffc5761c2582fa5590fd079fab232608ea93f4 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 26 Mar 2026 11:42:35 -0700 Subject: [PATCH 3/8] Add named volume support and test for creating VHD+named volume usage --- src/windows/WslcSDK/WslcsdkPrivate.h | 10 +- src/windows/WslcSDK/wslcsdk.cpp | 42 ++++++++- src/windows/WslcSDK/wslcsdk.def | 1 + src/windows/WslcSDK/wslcsdk.h | 15 ++- test/windows/WslcSdkTests.cpp | 134 +++++++++++++++++++++++++-- 5 files changed, 186 insertions(+), 16 deletions(-) diff --git a/src/windows/WslcSDK/WslcsdkPrivate.h b/src/windows/WslcSDK/WslcsdkPrivate.h index 438a872d9..a68fe224a 100644 --- a/src/windows/WslcSDK/WslcsdkPrivate.h +++ b/src/windows/WslcSDK/WslcsdkPrivate.h @@ -64,10 +64,10 @@ typedef struct WslcContainerProcessOptionsInternal static_assert( sizeof(WslcContainerProcessOptionsInternal) == WSLC_CONTAINER_PROCESS_OPTIONS_SIZE, - "WSLC_CONTAINER_PROCESS_OPTIONS_INTERNAL must be 72 bytes"); + "WSLC_CONTAINER_PROCESS_OPTIONS_INTERNAL size mismatch"); static_assert( __alignof(WslcContainerProcessOptionsInternal) == WSLC_CONTAINER_PROCESS_OPTIONS_ALIGNMENT, - "WSLC_CONTAINER_PROCESS_OPTIONS_INTERNAL must be 8-byte aligned"); + "WSLC_CONTAINER_PROCESS_OPTIONS_INTERNAL alignment mismatch"); static_assert(std::is_trivial_v, "WSLC_CONTAINER_PROCESS_OPTIONS_INTERNAL must be trivial"); @@ -84,6 +84,8 @@ typedef struct WslcContainerOptionsInternal uint32_t portsCount; const WslcContainerVolume* volumes; uint32_t volumesCount; + const WslcContainerNamedVolume* namedVolumes; + uint32_t namedVolumesCount; const WslcContainerProcessOptionsInternal* initProcessOptions; WSLAContainerNetworkType networking; WslcContainerFlags containerFlags; @@ -91,10 +93,10 @@ typedef struct WslcContainerOptionsInternal } WslcContainerOptionsInternal; static_assert( - sizeof(WslcContainerOptionsInternal) == WSLC_CONTAINER_OPTIONS_SIZE, "WSLC_CONTAINER_OPTIONS_INTERNAL must be 80 bytes"); + sizeof(WslcContainerOptionsInternal) == WSLC_CONTAINER_OPTIONS_SIZE, "WSLC_CONTAINER_OPTIONS_INTERNAL size mismatch"); static_assert( __alignof(WslcContainerOptionsInternal) == WSLC_CONTAINER_OPTIONS_ALIGNMENT, - "WSLC_CONTAINER_OPTIONS_INTERNAL must be 8-byte aligned"); + "WSLC_CONTAINER_OPTIONS_INTERNAL alignment mismatch"); static_assert(std::is_trivial_v, "WSLC_CONTAINER_OPTIONS_INTERNAL must be trivial"); diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 289ebdefa..e14d3d019 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -409,7 +409,7 @@ try // Only supported value currently volumeOptions.Type = "vhd"; - auto dynamicOptions = std::format(R"({{ "SizeBytes": {} }})", options->sizeInBytes); + auto dynamicOptions = std::format(R"({{ "SizeBytes": "{}" }})", options->sizeInBytes); volumeOptions.Options = dynamicOptions.c_str(); return errorInfoWrapper.CaptureResult(internalType->session->CreateVolume(&volumeOptions)); @@ -550,6 +550,23 @@ try containerOptions.VolumesCount = static_cast(internalContainerSettings->volumesCount); } + std::unique_ptr convertedNamedVolumes; + if (internalContainerSettings->namedVolumes && internalContainerSettings->namedVolumesCount) + { + convertedNamedVolumes = std::make_unique(internalContainerSettings->namedVolumesCount); + for (uint32_t i = 0; i < internalContainerSettings->namedVolumesCount; ++i) + { + const WslcContainerNamedVolume& internalVolume = internalContainerSettings->namedVolumes[i]; + WSLANamedVolume& convertedVolume = convertedNamedVolumes[i]; + + convertedVolume.Name = internalVolume.name; + convertedVolume.ContainerPath = internalVolume.containerPath; + convertedVolume.ReadOnly = internalVolume.readOnly; + } + containerOptions.NamedVolumes = convertedNamedVolumes.get(); + containerOptions.NamedVolumesCount = static_cast(internalContainerSettings->namedVolumesCount); + } + std::unique_ptr convertedPorts; if (internalContainerSettings->ports && internalContainerSettings->portsCount) { @@ -733,6 +750,29 @@ try } CATCH_RETURN(); +STDAPI WslcSetContainerSettingsNamedVolumes( + _In_ WslcContainerSettings* containerSettings, + _In_reads_opt_(namedVolumeCount) const WslcContainerNamedVolume* namedVolumes, + _In_ uint32_t namedVolumeCount) +try +{ + auto internalType = CheckAndGetInternalType(containerSettings); + RETURN_HR_IF(E_INVALIDARG, (namedVolumes == nullptr && namedVolumeCount != 0) || (namedVolumes != nullptr && namedVolumeCount == 0)); + + for (uint32_t i = 0; i < namedVolumeCount; ++i) + { + RETURN_HR_IF_NULL(E_INVALIDARG, namedVolumes[i].name); + RETURN_HR_IF_NULL(E_INVALIDARG, namedVolumes[i].containerPath); + EnsureAbsolutePath(namedVolumes[i].containerPath, true); + } + + internalType->namedVolumes = namedVolumes; + internalType->namedVolumesCount = namedVolumeCount; + + return S_OK; +} +CATCH_RETURN(); + STDAPI WslcCreateContainerProcess( _In_ WslcContainer container, _In_ WslcProcessSettings* newProcessSettings, _Out_ WslcProcess* newProcess, _Outptr_opt_result_z_ PWSTR* errorMessage) try diff --git a/src/windows/WslcSDK/wslcsdk.def b/src/windows/WslcSDK/wslcsdk.def index ecb290a65..3e66238ec 100644 --- a/src/windows/WslcSDK/wslcsdk.def +++ b/src/windows/WslcSDK/wslcsdk.def @@ -35,6 +35,7 @@ WslcSetContainerSettingsName WslcSetContainerSettingsNetworkingMode WslcSetContainerSettingsHostName WslcSetContainerSettingsVolumes +WslcSetContainerSettingsNamedVolumes WslcSetContainerSettingsInitProcess WslcSetContainerSettingsFlags WslcSetContainerSettingsPortMappings diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 62714bc73..3c1dd9635 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -32,7 +32,7 @@ typedef struct WslcSessionSettings DECLARE_HANDLE(WslcSession); // Container values -#define WSLC_CONTAINER_OPTIONS_SIZE 80 +#define WSLC_CONTAINER_OPTIONS_SIZE 96 #define WSLC_CONTAINER_OPTIONS_ALIGNMENT 8 typedef struct WslcContainerSettings @@ -134,6 +134,13 @@ typedef struct WslcContainerVolume _In_ BOOL readOnly; } WslcContainerVolume; +typedef struct WslcContainerNamedVolume +{ + _In_z_ PCSTR name; // Name of the session volume (from WslcVhdRequirements.name) + _In_z_ PCSTR containerPath; // Absolute path inside the container + _In_ BOOL readOnly; +} WslcContainerNamedVolume; + typedef enum WslcContainerFlags { WSLC_CONTAINER_FLAG_NONE = 0x00000000, @@ -182,6 +189,12 @@ STDAPI WslcSetContainerSettingsPortMappings( STDAPI WslcSetContainerSettingsVolumes( _In_ WslcContainerSettings* containerSettings, _In_reads_opt_(volumeCount) const WslcContainerVolume* volumes, _In_ uint32_t volumeCount); +// Add named session volumes (created via WslcCreateSessionVhd) to the container settings +STDAPI WslcSetContainerSettingsNamedVolumes( + _In_ WslcContainerSettings* containerSettings, + _In_reads_opt_(namedVolumeCount) const WslcContainerNamedVolume* namedVolumes, + _In_ uint32_t namedVolumeCount); + STDAPI WslcCreateContainerProcess( _In_ WslcContainer container, _In_ WslcProcessSettings* newProcessSettings, _Out_ WslcProcess* newProcess, _Outptr_opt_result_z_ PWSTR* errorMessage); diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index dbdd05829..3c9b0d225 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1876,6 +1876,130 @@ class WslcSdkTests VERIFY_ARE_EQUAL(stdoutData.size(), c_expectedBytes); } + // ----------------------------------------------------------------------- + // Storage tests + // ----------------------------------------------------------------------- + + TEST_METHOD(SessionCreateVhd) + { + WSL2_TEST_ONLY(); + + constexpr auto c_volumeName = "wslc-test-data-vol"; + constexpr auto c_vhdSizeBytes = _1GB; + + std::filesystem::path vhdSessionStorage = m_storagePath / "wslc-vhd-test-storage"; + auto removeStorage = wil::scope_exit([&]() { + std::error_code error; + std::filesystem::remove_all(vhdSessionStorage, error); + if (error) + { + LogError("Failed to remove VHD test storage %ws: %hs", vhdSessionStorage.c_str(), error.message().c_str()); + } + }); + + // Create a dedicated session so that volume creation does not affect the shared default session. + WslcSessionSettings sessionSettings; + VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-vhd-test", vhdSessionStorage.c_str(), &sessionSettings)); + + WslcVhdRequirements sessionVhd{}; + sessionVhd.sizeInBytes = 4 * _1GB; + sessionVhd.type = WSLC_VHD_TYPE_DYNAMIC; + VERIFY_SUCCEEDED(WslcSetSessionSettingsVHD(&sessionSettings, &sessionVhd)); + + UniqueSession session; + VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &session, nullptr)); + + // Load debian so we have a container image to work with. + std::filesystem::path debianTar = GetTestImagePath("debian:latest"); + VERIFY_SUCCEEDED(WslcLoadSessionImageFromFile(session.get(), debianTar.c_str(), nullptr, nullptr)); + + // Positive: create a named VHD volume in the session. + { + WslcVhdRequirements vhd{}; + vhd.name = c_volumeName; + vhd.sizeInBytes = c_vhdSizeBytes; + vhd.type = WSLC_VHD_TYPE_DYNAMIC; + wil::unique_cotaskmem_string errorMsg; + VERIFY_SUCCEEDED(WslcCreateSessionVhd(session.get(), &vhd, &errorMsg)); + + // The backing VHD file must exist on disk. + std::filesystem::path expectedVhdPath = vhdSessionStorage / "volumes" / (std::string(c_volumeName) + ".vhdx"); + VERIFY_IS_TRUE(std::filesystem::exists(expectedVhdPath)); + } + + // Positive: write a marker via a container that mounts the named volume. + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + const char* argv[] = {"/bin/sh", "-c", "echo wslc-vhd-test > /data/marker.txt"}; + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv))); + + WslcContainerSettings containerSettings; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings)); + + WslcContainerNamedVolume namedVol{}; + namedVol.name = c_volumeName; + namedVol.containerPath = "/data"; + namedVol.readOnly = FALSE; + VERIFY_SUCCEEDED(WslcSetContainerSettingsNamedVolumes(&containerSettings, &namedVol, 1)); + + auto output = RunContainerAndCapture(session.get(), containerSettings); + VERIFY_IS_TRUE(output.stderrOutput.empty()); + } + + // Positive: read back the marker in a second container (read-only mount). + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + const char* argv[] = {"/bin/sh", "-c", "cat /data/marker.txt"}; + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv))); + + WslcContainerSettings containerSettings; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings)); + + WslcContainerNamedVolume namedVol{}; + namedVol.name = c_volumeName; + namedVol.containerPath = "/data"; + namedVol.readOnly = TRUE; + VERIFY_SUCCEEDED(WslcSetContainerSettingsNamedVolumes(&containerSettings, &namedVol, 1)); + + auto output = RunContainerAndCapture(session.get(), containerSettings); + VERIFY_ARE_EQUAL(output.stdoutOutput, "wslc-vhd-test\n"); + } + + // Negative: null options pointer must fail. + VERIFY_ARE_EQUAL(WslcCreateSessionVhd(session.get(), nullptr, nullptr), E_POINTER); + + // Negative: null name must fail. + { + WslcVhdRequirements vhd{}; + vhd.name = nullptr; + vhd.sizeInBytes = c_vhdSizeBytes; + vhd.type = WSLC_VHD_TYPE_DYNAMIC; + VERIFY_ARE_EQUAL(WslcCreateSessionVhd(session.get(), &vhd, nullptr), E_INVALIDARG); + } + + // Negative: zero sizeInBytes must fail. + { + WslcVhdRequirements vhd{}; + vhd.name = c_volumeName; + vhd.sizeInBytes = 0; + vhd.type = WSLC_VHD_TYPE_DYNAMIC; + VERIFY_ARE_EQUAL(WslcCreateSessionVhd(session.get(), &vhd, nullptr), E_INVALIDARG); + } + + // Negative: fixed VHD type is not yet supported. + { + WslcVhdRequirements vhd{}; + vhd.name = c_volumeName; + vhd.sizeInBytes = c_vhdSizeBytes; + vhd.type = WSLC_VHD_TYPE_FIXED; + VERIFY_ARE_EQUAL(WslcCreateSessionVhd(session.get(), &vhd, nullptr), E_NOTIMPL); + } + } + // ----------------------------------------------------------------------- // Stub tests for unimplemented (E_NOTIMPL) functions. // Each of these confirms the current state of the SDK; once the underlying @@ -1914,16 +2038,6 @@ class WslcSdkTests VERIFY_ARE_EQUAL(containerId, inspectObject.Id); } - TEST_METHOD(SessionCreateVhdNotImplemented) - { - WSL2_TEST_ONLY(); - - WslcVhdRequirements vhd{}; - vhd.sizeInBytes = 1024ull * 1024 * 1024; - vhd.type = WSLC_VHD_TYPE_DYNAMIC; - VERIFY_ARE_EQUAL(WslcCreateSessionVhd(m_defaultSession, &vhd, nullptr), E_NOTIMPL); - } - TEST_METHOD(InstallWithDependenciesNotImplemented) { WSL2_TEST_ONLY(); From 05e77ca7377fe523cbabaa884372fc89b2793cb5 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 26 Mar 2026 11:43:54 -0700 Subject: [PATCH 4/8] move tokens about --- src/windows/WslcSDK/wslcsdk.cpp | 4 +--- test/windows/WslcSdkTests.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index e14d3d019..bf53b9482 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -751,9 +751,7 @@ try CATCH_RETURN(); STDAPI WslcSetContainerSettingsNamedVolumes( - _In_ WslcContainerSettings* containerSettings, - _In_reads_opt_(namedVolumeCount) const WslcContainerNamedVolume* namedVolumes, - _In_ uint32_t namedVolumeCount) + _In_ WslcContainerSettings* containerSettings, _In_reads_opt_(namedVolumeCount) const WslcContainerNamedVolume* namedVolumes, _In_ uint32_t namedVolumeCount) try { auto internalType = CheckAndGetInternalType(containerSettings); diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index 3c9b0d225..2a01bc12a 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1895,7 +1895,7 @@ class WslcSdkTests { LogError("Failed to remove VHD test storage %ws: %hs", vhdSessionStorage.c_str(), error.message().c_str()); } - }); + }); // Create a dedicated session so that volume creation does not affect the shared default session. WslcSessionSettings sessionSettings; From 80de3683121db4589296e043e7ea22afc8c7e154 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 26 Mar 2026 14:25:40 -0700 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=A4=96=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/windows/WslcSDK/wslcsdk.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 3c1dd9635..30b896207 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -66,8 +66,8 @@ typedef enum WslcVhdType typedef struct WslcVhdRequirements { - // Required for WslcCreateSessionVhd - _In_opt_ PCSTR name; + // Ignored by WslcSetSessionSettingsVHD + _In_z_ PCSTR name; _In_ uint64_t sizeInBytes; // Desired size (for create/expand) _In_ WslcVhdType type; } WslcVhdRequirements; @@ -224,10 +224,6 @@ STDAPI WslcGetContainerInitProcess(_In_ WslcContainer container, _Out_ WslcProce // // Return Value: // S_OK on success. Otherwise, an HRESULT error code indicating the failure. -// -// Notes: -// - The caller must pass a non-null pointer to a PCSTR variable. -// - The returned string is immutable and must not be modified by the caller. STDAPI WslcInspectContainer(_In_ WslcContainer container, _Outptr_result_z_ PSTR* inspectData); typedef enum WslcContainerState From bc6563971138d7ec033938494b91c7a879afd6ab Mon Sep 17 00:00:00 2001 From: John McPherson Date: Fri, 27 Mar 2026 09:40:45 -0700 Subject: [PATCH 6/8] PR feedback: function name, add delete function, condition change, test update --- src/windows/WslcSDK/wslcsdk.cpp | 21 +++++++++++++++++---- src/windows/WslcSDK/wslcsdk.def | 5 +++-- src/windows/WslcSDK/wslcsdk.h | 5 +++-- test/windows/WslcSdkTests.cpp | 26 ++++++++++++++++++-------- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index bf53b9482..80a83765c 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -391,7 +391,7 @@ try } CATCH_RETURN(); -STDAPI WslcCreateSessionVhd(_In_ WslcSession session, _In_ const WslcVhdRequirements* options, _Outptr_opt_result_z_ PWSTR* errorMessage) +STDAPI WslcCreateSessionVhdVolume(_In_ WslcSession session, _In_ const WslcVhdRequirements* options, _Outptr_opt_result_z_ PWSTR* errorMessage) try { ErrorInfoWrapper errorInfoWrapper{errorMessage}; @@ -402,7 +402,7 @@ try RETURN_HR_IF_NULL(E_INVALIDARG, options->name); RETURN_HR_IF(E_INVALIDARG, options->sizeInBytes == 0); - RETURN_HR_IF(E_NOTIMPL, options->type == WSLC_VHD_TYPE_FIXED); + RETURN_HR_IF(E_NOTIMPL, options->type != WSLC_VHD_TYPE_DYNAMIC); WSLAVolumeOptions volumeOptions{}; volumeOptions.Name = options->name; @@ -416,7 +416,20 @@ try } CATCH_RETURN(); -STDAPI WslcSetSessionSettingsVHD(_In_ WslcSessionSettings* sessionSettings, _In_ const WslcVhdRequirements* vhdRequirements) +STDAPI WslcDeleteSessionVhdVolume(_In_ WslcSession session, _In_z_ PCSTR name, _Outptr_opt_result_z_ PWSTR* errorMessage) +try +{ + ErrorInfoWrapper errorInfoWrapper{errorMessage}; + + auto internalType = CheckAndGetInternalType(session); + RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->session); + RETURN_HR_IF_NULL(E_POINTER, name); + + return errorInfoWrapper.CaptureResult(internalType->session->DeleteVolume(name)); +} +CATCH_RETURN(); + +STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_ const WslcVhdRequirements* vhdRequirements) try { auto internalType = CheckAndGetInternalType(sessionSettings); @@ -424,7 +437,7 @@ try if (vhdRequirements) { RETURN_HR_IF(E_INVALIDARG, vhdRequirements->sizeInBytes == 0); - RETURN_HR_IF(E_NOTIMPL, vhdRequirements->type == WSLC_VHD_TYPE_FIXED); + RETURN_HR_IF(E_NOTIMPL, vhdRequirements->type != WSLC_VHD_TYPE_DYNAMIC); internalType->vhdRequirements = *vhdRequirements; } diff --git a/src/windows/WslcSDK/wslcsdk.def b/src/windows/WslcSDK/wslcsdk.def index 3e66238ec..6f0fdbdf0 100644 --- a/src/windows/WslcSDK/wslcsdk.def +++ b/src/windows/WslcSDK/wslcsdk.def @@ -18,7 +18,7 @@ WslcSetSessionSettingsTerminationCallback WslcSetSessionSettingsCpuCount WslcSetSessionSettingsMemory WslcSetSessionSettingsTimeout -WslcSetSessionSettingsVHD +WslcSetSessionSettingsVhd WslcTerminateSession WslcPullSessionImage @@ -28,7 +28,8 @@ WslcLoadSessionImage WslcLoadSessionImageFromFile WslcDeleteSessionImage WslcListSessionImages -WslcCreateSessionVhd +WslcCreateSessionVhdVolume +WslcDeleteSessionVhdVolume WslcSetContainerSettingsDomainName WslcSetContainerSettingsName diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 30b896207..7b7b9ad6a 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -98,7 +98,7 @@ STDAPI WslcSetSessionSettingsCpuCount(_In_ WslcSessionSettings* sessionSettings, STDAPI WslcSetSessionSettingsMemory(_In_ WslcSessionSettings* sessionSettings, _In_ uint32_t memoryMb); STDAPI WslcSetSessionSettingsTimeout(_In_ WslcSessionSettings* sessionSettings, _In_ uint32_t timeoutMS); -STDAPI WslcSetSessionSettingsVHD(_In_ WslcSessionSettings* sessionSettings, _In_ const WslcVhdRequirements* vhdRequirements); +STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_ const WslcVhdRequirements* vhdRequirements); STDAPI WslcSetSessionSettingsFeatureFlags(_In_ WslcSessionSettings* sessionSettings, _In_ WslcSessionFeatureFlags flags); @@ -481,7 +481,8 @@ STDAPI WslcListSessionImages(_In_ WslcSession session, _Outptr_result_buffer_(*c // STORAGE -STDAPI WslcCreateSessionVhd(_In_ WslcSession session, _In_ const WslcVhdRequirements* options, _Outptr_opt_result_z_ PWSTR* errorMessage); +STDAPI WslcCreateSessionVhdVolume(_In_ WslcSession session, _In_ const WslcVhdRequirements* options, _Outptr_opt_result_z_ PWSTR* errorMessage); +STDAPI WslcDeleteSessionVhdVolume(_In_ WslcSession session, _In_z_ PCSTR name, _Outptr_opt_result_z_ PWSTR* errorMessage); // INSTALL diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index 2a01bc12a..0082cb762 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -196,7 +196,7 @@ class WslcSdkTests WslcVhdRequirements vhdReqs{}; vhdReqs.sizeInBytes = 4096ull * 1024 * 1024; // 4 GB vhdReqs.type = WSLC_VHD_TYPE_DYNAMIC; - VERIFY_SUCCEEDED(WslcSetSessionSettingsVHD(&sessionSettings, &vhdReqs)); + VERIFY_SUCCEEDED(WslcSetSessionSettingsVhd(&sessionSettings, &vhdReqs)); VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &m_defaultSession, nullptr)); @@ -251,7 +251,7 @@ class WslcSdkTests WslcVhdRequirements vhdReqs{}; vhdReqs.sizeInBytes = 1024ull * 1024 * 1024; // 1 GB vhdReqs.type = WSLC_VHD_TYPE_DYNAMIC; - VERIFY_SUCCEEDED(WslcSetSessionSettingsVHD(&sessionSettings, &vhdReqs)); + VERIFY_SUCCEEDED(WslcSetSessionSettingsVhd(&sessionSettings, &vhdReqs)); UniqueSession session; VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &session, nullptr)); @@ -1904,7 +1904,7 @@ class WslcSdkTests WslcVhdRequirements sessionVhd{}; sessionVhd.sizeInBytes = 4 * _1GB; sessionVhd.type = WSLC_VHD_TYPE_DYNAMIC; - VERIFY_SUCCEEDED(WslcSetSessionSettingsVHD(&sessionSettings, &sessionVhd)); + VERIFY_SUCCEEDED(WslcSetSessionSettingsVhd(&sessionSettings, &sessionVhd)); UniqueSession session; VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &session, nullptr)); @@ -1920,7 +1920,7 @@ class WslcSdkTests vhd.sizeInBytes = c_vhdSizeBytes; vhd.type = WSLC_VHD_TYPE_DYNAMIC; wil::unique_cotaskmem_string errorMsg; - VERIFY_SUCCEEDED(WslcCreateSessionVhd(session.get(), &vhd, &errorMsg)); + VERIFY_SUCCEEDED(WslcCreateSessionVhdVolume(session.get(), &vhd, &errorMsg)); // The backing VHD file must exist on disk. std::filesystem::path expectedVhdPath = vhdSessionStorage / "volumes" / (std::string(c_volumeName) + ".vhdx"); @@ -1969,8 +1969,18 @@ class WslcSdkTests VERIFY_ARE_EQUAL(output.stdoutOutput, "wslc-vhd-test\n"); } + // Positive: delete the volume. + { + wil::unique_cotaskmem_string errorMsg; + VERIFY_SUCCEEDED(WslcDeleteSessionVhdVolume(session.get(), c_volumeName, &errorMsg)); + + // The backing VHD file must not exist on disk. + std::filesystem::path expectedVhdPath = vhdSessionStorage / "volumes" / (std::string(c_volumeName) + ".vhdx"); + VERIFY_IS_FALSE(std::filesystem::exists(expectedVhdPath)); + } + // Negative: null options pointer must fail. - VERIFY_ARE_EQUAL(WslcCreateSessionVhd(session.get(), nullptr, nullptr), E_POINTER); + VERIFY_ARE_EQUAL(WslcCreateSessionVhdVolume(session.get(), nullptr, nullptr), E_POINTER); // Negative: null name must fail. { @@ -1978,7 +1988,7 @@ class WslcSdkTests vhd.name = nullptr; vhd.sizeInBytes = c_vhdSizeBytes; vhd.type = WSLC_VHD_TYPE_DYNAMIC; - VERIFY_ARE_EQUAL(WslcCreateSessionVhd(session.get(), &vhd, nullptr), E_INVALIDARG); + VERIFY_ARE_EQUAL(WslcCreateSessionVhdVolume(session.get(), &vhd, nullptr), E_INVALIDARG); } // Negative: zero sizeInBytes must fail. @@ -1987,7 +1997,7 @@ class WslcSdkTests vhd.name = c_volumeName; vhd.sizeInBytes = 0; vhd.type = WSLC_VHD_TYPE_DYNAMIC; - VERIFY_ARE_EQUAL(WslcCreateSessionVhd(session.get(), &vhd, nullptr), E_INVALIDARG); + VERIFY_ARE_EQUAL(WslcCreateSessionVhdVolume(session.get(), &vhd, nullptr), E_INVALIDARG); } // Negative: fixed VHD type is not yet supported. @@ -1996,7 +2006,7 @@ class WslcSdkTests vhd.name = c_volumeName; vhd.sizeInBytes = c_vhdSizeBytes; vhd.type = WSLC_VHD_TYPE_FIXED; - VERIFY_ARE_EQUAL(WslcCreateSessionVhd(session.get(), &vhd, nullptr), E_NOTIMPL); + VERIFY_ARE_EQUAL(WslcCreateSessionVhdVolume(session.get(), &vhd, nullptr), E_NOTIMPL); } } From 76358a67d942b7b43477e3606fdf89915ce9298a Mon Sep 17 00:00:00 2001 From: John McPherson Date: Fri, 27 Mar 2026 10:06:48 -0700 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=A4=96=20feedback:=20SAL=20and=20comm?= =?UTF-8?q?ent=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/windows/WslcSDK/wslcsdk.cpp | 2 +- src/windows/WslcSDK/wslcsdk.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 80a83765c..d94f8d28d 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -429,7 +429,7 @@ try } CATCH_RETURN(); -STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_ const WslcVhdRequirements* vhdRequirements) +STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_opt_ const WslcVhdRequirements* vhdRequirements) try { auto internalType = CheckAndGetInternalType(sessionSettings); diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 7b7b9ad6a..4d614eb29 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -98,7 +98,7 @@ STDAPI WslcSetSessionSettingsCpuCount(_In_ WslcSessionSettings* sessionSettings, STDAPI WslcSetSessionSettingsMemory(_In_ WslcSessionSettings* sessionSettings, _In_ uint32_t memoryMb); STDAPI WslcSetSessionSettingsTimeout(_In_ WslcSessionSettings* sessionSettings, _In_ uint32_t timeoutMS); -STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_ const WslcVhdRequirements* vhdRequirements); +STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_opt_ const WslcVhdRequirements* vhdRequirements); STDAPI WslcSetSessionSettingsFeatureFlags(_In_ WslcSessionSettings* sessionSettings, _In_ WslcSessionFeatureFlags flags); @@ -189,7 +189,7 @@ STDAPI WslcSetContainerSettingsPortMappings( STDAPI WslcSetContainerSettingsVolumes( _In_ WslcContainerSettings* containerSettings, _In_reads_opt_(volumeCount) const WslcContainerVolume* volumes, _In_ uint32_t volumeCount); -// Add named session volumes (created via WslcCreateSessionVhd) to the container settings +// Add named session volumes (created via WslcCreateSessionVhdVolume) to the container settings STDAPI WslcSetContainerSettingsNamedVolumes( _In_ WslcContainerSettings* containerSettings, _In_reads_opt_(namedVolumeCount) const WslcContainerNamedVolume* namedVolumes, From 842291c4d2eed8d753fce149fdf243647256b19b Mon Sep 17 00:00:00 2001 From: John McPherson Date: Fri, 27 Mar 2026 14:04:07 -0700 Subject: [PATCH 8/8] More a->c conversions, use correct size definition --- src/windows/WslcSDK/wslcsdk.cpp | 4 ++-- test/windows/WslcSdkTests.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index fa5b0b63c..f8293c1c9 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -404,7 +404,7 @@ try RETURN_HR_IF(E_INVALIDARG, options->sizeInBytes == 0); RETURN_HR_IF(E_NOTIMPL, options->type != WSLC_VHD_TYPE_DYNAMIC); - WSLAVolumeOptions volumeOptions{}; + WSLCVolumeOptions volumeOptions{}; volumeOptions.Name = options->name; // Only supported value currently volumeOptions.Type = "vhd"; @@ -570,7 +570,7 @@ try for (uint32_t i = 0; i < internalContainerSettings->namedVolumesCount; ++i) { const WslcContainerNamedVolume& internalVolume = internalContainerSettings->namedVolumes[i]; - WSLANamedVolume& convertedVolume = convertedNamedVolumes[i]; + WSLCNamedVolume& convertedVolume = convertedNamedVolumes[i]; convertedVolume.Name = internalVolume.name; convertedVolume.ContainerPath = internalVolume.containerPath; diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index 744ba9cd0..bba4fe149 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -15,7 +15,7 @@ Module Name: #include "precomp.h" #include "Common.h" #include "wslcsdk.h" -#include "wsla_schema.h" +#include "wslc_schema.h" #include extern std::wstring g_testDataPath; @@ -2040,9 +2040,9 @@ class WslcSdkTests VERIFY_IS_NOT_NULL(inspectData); - auto inspectObject = wsl::shared::FromJson(inspectData.get()); + auto inspectObject = wsl::shared::FromJson(inspectData.get()); - CHAR containerId[WSLC_CONTAINER_ID_LENGTH]; + CHAR containerId[WSLC_CONTAINER_ID_BUFFER_SIZE]; VERIFY_SUCCEEDED(WslcGetContainerID(container.get(), containerId)); VERIFY_ARE_EQUAL(containerId, inspectObject.Id);