diff --git a/src/windows/WslcSDK/WslcsdkPrivate.h b/src/windows/WslcSDK/WslcsdkPrivate.h index 2cd3729e7..339d7f855 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; WSLCContainerNetworkType 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 1f917b9d3..5987eca1b 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -417,7 +417,6 @@ try WSLCSessionSettings 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; @@ -432,16 +431,6 @@ try runtimeSettings.FeatureFlags = ConvertFlags(internalType->featureFlags); WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsVirtioFs); - // 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, WSLCSessionFlagsNone, &result->session)))) { wsl::windows::common::security::ConfigureForCOMImpersonation(result->session.get()); @@ -480,23 +469,54 @@ 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 { - 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_DYNAMIC); + + WSLCVolumeOptions 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(); + +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) +STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_opt_ const WslcVhdRequirements* vhdRequirements) try { auto internalType = CheckAndGetInternalType(sessionSettings); if (vhdRequirements) { + RETURN_HR_IF(E_INVALIDARG, vhdRequirements->sizeInBytes == 0); + RETURN_HR_IF(E_NOTIMPL, vhdRequirements->type != WSLC_VHD_TYPE_DYNAMIC); + internalType->vhdRequirements = *vhdRequirements; } else @@ -621,6 +641,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]; + WSLCNamedVolume& 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) { @@ -804,6 +841,27 @@ 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 @@ -851,12 +909,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.def b/src/windows/WslcSDK/wslcsdk.def index ecb290a65..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,13 +28,15 @@ WslcLoadSessionImage WslcLoadSessionImageFromFile WslcDeleteSessionImage WslcListSessionImages -WslcCreateSessionVhd +WslcCreateSessionVhdVolume +WslcDeleteSessionVhdVolume WslcSetContainerSettingsDomainName WslcSetContainerSettingsName WslcSetContainerSettingsNetworkingMode WslcSetContainerSettingsHostName WslcSetContainerSettingsVolumes +WslcSetContainerSettingsNamedVolumes WslcSetContainerSettingsInitProcess WslcSetContainerSettingsFlags WslcSetContainerSettingsPortMappings diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 52602a5c2..a581afd7f 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 @@ -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 @@ -66,6 +66,8 @@ typedef enum WslcVhdType typedef struct WslcVhdRequirements { + // Ignored by WslcSetSessionSettingsVHD + _In_z_ PCSTR name; _In_ uint64_t sizeInBytes; // Desired size (for create/expand) _In_ WslcVhdType type; } WslcVhdRequirements; @@ -96,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); @@ -132,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, @@ -180,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 WslcCreateSessionVhdVolume) 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); @@ -209,11 +224,7 @@ 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_ PCSTR* inspectData); +STDAPI WslcInspectContainer(_In_ WslcContainer container, _Outptr_result_z_ PSTR* inspectData); typedef enum WslcContainerState { @@ -470,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 14d2ce67d..3eb086007 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 "wslc_schema.h" #include extern std::wstring g_testDataPath; @@ -195,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)); @@ -250,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)); @@ -1062,6 +1063,28 @@ class WslcSdkTests } } + TEST_METHOD(ContainerInspect) + { + WSL2_TEST_ONLY(); + + UniqueContainer container; + WslcContainerSettings containerSettings; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr)); + + wil::unique_cotaskmem_ansistring inspectData; + VERIFY_SUCCEEDED(WslcInspectContainer(container.get(), &inspectData)); + + VERIFY_IS_NOT_NULL(inspectData); + + auto inspectObject = wsl::shared::FromJson(inspectData.get()); + + CHAR containerId[WSLC_CONTAINER_ID_BUFFER_SIZE]; + VERIFY_SUCCEEDED(WslcGetContainerID(container.get(), containerId)); + + VERIFY_ARE_EQUAL(containerId, inspectObject.Id); + } + TEST_METHOD(ContainerExec) { WSL2_TEST_ONLY(); @@ -1890,37 +1913,146 @@ class WslcSdkTests } // ----------------------------------------------------------------------- - // Stub tests for unimplemented (E_NOTIMPL) functions. - // Each of these confirms the current state of the SDK; once the underlying - // function is implemented the assertion below will catch it and the test - // should be updated to exercise the real behaviour. + // Storage tests // ----------------------------------------------------------------------- - TEST_METHOD(ContainerInspectNotImplemented) + TEST_METHOD(SessionCreateVhd) { WSL2_TEST_ONLY(); - UniqueContainer container; - WslcContainerSettings containerSettings; - VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); - VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr)); + constexpr auto c_volumeName = "wslc-test-data-vol"; + constexpr auto c_vhdSizeBytes = _1GB; - PCSTR inspectData = nullptr; - VERIFY_ARE_EQUAL(WslcInspectContainer(container.get(), &inspectData), E_NOTIMPL); + 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()); + } + }); - VERIFY_SUCCEEDED(WslcDeleteContainer(container.get(), WSLC_DELETE_CONTAINER_FLAG_NONE, nullptr)); - } + // 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)); - TEST_METHOD(SessionCreateVhdNotImplemented) - { - WSL2_TEST_ONLY(); + 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(WslcCreateSessionVhdVolume(session.get(), &vhd, &errorMsg)); - WslcVhdRequirements vhd{}; - vhd.sizeInBytes = 1024ull * 1024 * 1024; - vhd.type = WSLC_VHD_TYPE_DYNAMIC; - VERIFY_ARE_EQUAL(WslcCreateSessionVhd(m_defaultSession, &vhd, nullptr), E_NOTIMPL); + // 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"); + } + + // 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(WslcCreateSessionVhdVolume(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(WslcCreateSessionVhdVolume(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(WslcCreateSessionVhdVolume(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(WslcCreateSessionVhdVolume(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 + // function is implemented the assertion below will catch it and the test + // should be updated to exercise the real behaviour. + // ----------------------------------------------------------------------- + TEST_METHOD(InstallWithDependenciesNotImplemented) { WSL2_TEST_ONLY();