mirror of
https://github.com/aphrody-labs/WSL.git
synced 2026-10-14 13:49:03 +00:00
759 lines
41 KiB
C++
759 lines
41 KiB
C++
// Copyright (C) Microsoft Corporation. All rights reserved.
|
|
|
|
#include "precomp.h"
|
|
#include "Common.h"
|
|
#include "HcsVirtualMachineBackend.h"
|
|
#include "VirtualMachineBackendTestHelpers.h"
|
|
|
|
using namespace VirtualMachineBackendTestHelpers;
|
|
|
|
namespace {
|
|
|
|
constexpr HRESULT c_notSupported = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
|
constexpr HRESULT c_invalidState = HRESULT_FROM_WIN32(ERROR_INVALID_STATE);
|
|
|
|
std::filesystem::path CreateTestDirectory()
|
|
{
|
|
GUID id{};
|
|
THROW_IF_FAILED(CoCreateGuid(&id));
|
|
const auto path = std::filesystem::temp_directory_path() /
|
|
(L"HcsVirtualMachineBackendTests-" +
|
|
wsl::shared::string::GuidToString<wchar_t>(id, wsl::shared::string::GuidToStringFlags::None));
|
|
THROW_IF_WIN32_BOOL_FALSE(CreateDirectoryW(path.c_str(), nullptr));
|
|
return path;
|
|
}
|
|
|
|
void CreateVhd(const std::filesystem::path& Path)
|
|
{
|
|
VIRTUAL_STORAGE_TYPE storageType{};
|
|
storageType.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHDX;
|
|
storageType.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT;
|
|
|
|
CREATE_VIRTUAL_DISK_PARAMETERS parameters{};
|
|
parameters.Version = CREATE_VIRTUAL_DISK_VERSION_2;
|
|
parameters.Version2.BlockSizeInBytes = 1024 * 1024;
|
|
parameters.Version2.MaximumSize = 8 * c_mib;
|
|
|
|
wil::unique_hfile vhd;
|
|
THROW_IF_WIN32_ERROR(CreateVirtualDisk(
|
|
&storageType, Path.c_str(), VIRTUAL_DISK_ACCESS_NONE, nullptr, CREATE_VIRTUAL_DISK_FLAG_SUPPORT_COMPRESSED_VOLUMES, 0, ¶meters, nullptr, &vhd));
|
|
}
|
|
|
|
std::wstring GetDaclAces(const std::filesystem::path& Path)
|
|
{
|
|
PACL acl = nullptr;
|
|
wil::unique_hlocal descriptor;
|
|
THROW_IF_WIN32_ERROR(
|
|
GetNamedSecurityInfoW(Path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &acl, nullptr, &descriptor));
|
|
wil::unique_hlocal_string value;
|
|
THROW_IF_WIN32_BOOL_FALSE(ConvertSecurityDescriptorToStringSecurityDescriptorW(
|
|
descriptor.get(), SDDL_REVISION_1, DACL_SECURITY_INFORMATION, &value, nullptr));
|
|
const std::wstring dacl{value.get()};
|
|
const auto firstAce = dacl.find(L'(');
|
|
return firstAce == std::wstring::npos ? std::wstring{} : dacl.substr(firstAce);
|
|
}
|
|
|
|
// Tests require elevation, so the test process token is the elevated counterpart of
|
|
// GetNonElevatedToken() and stands in for an administrator's DrvFs share.
|
|
wil::unique_handle GetElevatedTestToken()
|
|
{
|
|
wil::unique_handle token;
|
|
THROW_IF_WIN32_BOOL_FALSE(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token));
|
|
wil::unique_handle impersonationToken;
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
DuplicateTokenEx(token.get(), TOKEN_ALL_ACCESS, nullptr, SecurityImpersonation, TokenImpersonation, &impersonationToken));
|
|
return impersonationToken;
|
|
}
|
|
|
|
VmDiskRequest CreateDiskRequest(const std::filesystem::path& Path, std::optional<UINT32> Lun = std::nullopt)
|
|
{
|
|
VmDiskRequest request;
|
|
request.Source = VmVirtualDiskSource{Path, VmDiskFormat::Vhdx};
|
|
if (Lun)
|
|
{
|
|
request.Placement = VmScsiPlacement{{0, Lun.value()}};
|
|
}
|
|
|
|
return request;
|
|
}
|
|
|
|
VmNetworkAdapterRequest CreateNetworkRequest()
|
|
{
|
|
VmNetworkAdapterRequest request;
|
|
request.Tag = L"eth0";
|
|
VmUserModeNatNetwork configuration;
|
|
configuration.Configuration.clientIp.value = htonl(0x0a000002);
|
|
constexpr std::array<BYTE, 6> clientMac{0x00, 0x15, 0x5d, 0x01, 0x02, 0x03};
|
|
std::copy(clientMac.begin(), clientMac.end(), std::begin(configuration.Configuration.clientMac.bytes));
|
|
configuration.Configuration.gatewayIp.value = htonl(0x0a000001);
|
|
constexpr std::array<BYTE, 6> gatewayMac{0x52, 0x55, 0x0a, 0x00, 0x00, 0x01};
|
|
std::copy(gatewayMac.begin(), gatewayMac.end(), std::begin(configuration.Configuration.gatewayMac.bytes));
|
|
constexpr std::array<BYTE, 6> gatewayMacIpv6{0x52, 0x55, 0x0a, 0x00, 0x01, 0x02};
|
|
std::copy(gatewayMacIpv6.begin(), gatewayMacIpv6.end(), std::begin(configuration.Configuration.gatewayMacIpv6.bytes));
|
|
configuration.Configuration.netmask.value = htonl(0xffffff00);
|
|
request.Configuration = configuration;
|
|
return request;
|
|
}
|
|
|
|
std::filesystem::path ChangePathCase(const std::filesystem::path& Path)
|
|
{
|
|
auto value = Path.native();
|
|
std::transform(value.begin(), value.end(), value.begin(), [](wchar_t Character) {
|
|
return static_cast<wchar_t>(std::towupper(Character));
|
|
});
|
|
return value;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace HcsVirtualMachineBackendTests {
|
|
|
|
class HcsVirtualMachineBackendTests
|
|
{
|
|
WSL_TEST_CLASS(HcsVirtualMachineBackendTests)
|
|
|
|
TEST_CLASS_SETUP(TestClassSetup)
|
|
{
|
|
WSADATA data{};
|
|
THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &data));
|
|
return true;
|
|
}
|
|
|
|
TEST_CLASS_CLEANUP(TestClassCleanup)
|
|
{
|
|
const auto module = GetModuleHandleW(L"wsldevicehostproxystub.dll");
|
|
if (module)
|
|
{
|
|
const auto canUnload = reinterpret_cast<HRESULT(STDAPICALLTYPE*)()>(GetProcAddress(module, "DllCanUnloadNow"));
|
|
VERIFY_IS_NOT_NULL(canUnload);
|
|
LogInfo("Device-host proxy unload status before COM cleanup: 0x%08x", canUnload());
|
|
}
|
|
CoFreeUnusedLibrariesEx(0, 0);
|
|
VERIFY_IS_NULL(GetModuleHandleW(L"wsldevicehostproxystub.dll"));
|
|
VERIFY_ARE_EQUAL(0, WSACleanup());
|
|
return true;
|
|
}
|
|
|
|
TEST_METHOD(FileSystemRequestsDefaultToVirtioFs)
|
|
{
|
|
const VmFileSystemDeviceRequest device;
|
|
VERIFY_IS_TRUE(std::holds_alternative<VmVirtioFsDevice>(device.Transport));
|
|
VERIFY_ARE_EQUAL(VmVirtioFsLayout::Aggregate, std::get<VmVirtioFsDevice>(device.Transport).Layout);
|
|
const VmFileSystemShareRequest share;
|
|
VERIFY_IS_TRUE(std::holds_alternative<VmVirtioFsShareOptions>(share.Options));
|
|
VERIFY_IS_TRUE(std::get<VmVirtioFsShareOptions>(share.Options).MountOptions.empty());
|
|
VERIFY_IS_TRUE(share.ReadOnly);
|
|
const VmFileSystemShare result;
|
|
VERIFY_IS_TRUE(std::holds_alternative<VmVirtioFsShareAddress>(result.GuestAddress));
|
|
}
|
|
|
|
TEST_METHOD(PreservesCallerIdentityAndBootInputs)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
auto request = CreateRunnableRequest();
|
|
request.Boot.KernelCommandLine = L"panic=-1 console=hvc0 custom=value";
|
|
request.Memory.SizeBytes += 123;
|
|
request.Memory.AllowOvercommit = VmFeatureRequest::Required;
|
|
request.Memory.DeferredCommit = VmFeatureRequest::Required;
|
|
request.Memory.ColdDiscard = VmFeatureRequest::Required;
|
|
request.Memory.SmallPageBacking = VmFeatureRequest::Preferred;
|
|
request.Memory.FaultClusterSizeShift = 4;
|
|
request.Memory.DirectMapFaultClusterSizeShift = 4;
|
|
request.Memory.PageReportingOrder = 5;
|
|
request.Memory.HostingProcessNameSuffix = L"WSL";
|
|
request.Mmio.HighWindowSizeBytes = 24 * c_mib;
|
|
request.Mmio.MaximumGuestAddressBits = 36;
|
|
auto backend = HcsVirtualMachineBackend::Create(request);
|
|
const auto description = backend->GetDescription();
|
|
|
|
VERIFY_IS_NOT_NULL(backend->GetComputeSystemHandle());
|
|
VERIFY_IS_NOT_NULL(backend->GetGuestDeviceManager().get());
|
|
VERIFY_IS_TRUE(IsEqualGUID(request.Identity.VmId, description.Identity.VmId));
|
|
VERIFY_ARE_EQUAL(BackendKind::Hcs, description.Backend);
|
|
VERIFY_ARE_EQUAL(request.Processor.Count, description.Processor.Count);
|
|
VERIFY_ARE_EQUAL((request.Memory.SizeBytes / c_mib) * c_mib, description.Memory.SizeBytes);
|
|
VERIFY_ARE_EQUAL(wsl::windows::common::hcs::IsSmallPageMemorySupported(), description.Memory.SmallPageBacking);
|
|
VERIFY_ARE_EQUAL(UINT32{4}, description.Memory.FaultClusterSizeShift.value());
|
|
VERIFY_ARE_EQUAL(UINT32{4}, description.Memory.DirectMapFaultClusterSizeShift.value());
|
|
VERIFY_ARE_EQUAL(UINT32{5}, description.Memory.PageReportingOrder.value());
|
|
VERIFY_ARE_EQUAL(wsl::windows::common::helpers::IsVmemmSuffixSupported(), description.Memory.HostingProcessNameSuffix.has_value());
|
|
if (description.Memory.HostingProcessNameSuffix)
|
|
{
|
|
VERIFY_ARE_EQUAL(std::wstring{L"WSL"}, description.Memory.HostingProcessNameSuffix.value());
|
|
}
|
|
VERIFY_ARE_EQUAL(UINT64{24 * c_mib}, description.Memory.HighMmioSizeBytes.value());
|
|
VERIFY_ARE_EQUAL((UINT64{1} << 36) - (24 * c_mib), description.Memory.HighMmioBaseBytes.value());
|
|
VERIFY_ARE_EQUAL(VmBootMethod::LinuxDirect, description.Boot.Method);
|
|
VERIFY_ARE_EQUAL(request.Boot.KernelCommandLine, description.Boot.KernelCommandLine);
|
|
const auto capabilities = backend->GetCapabilities();
|
|
VERIFY_ARE_EQUAL(BackendKind::Hcs, capabilities.Backend);
|
|
VERIFY_ARE_EQUAL(
|
|
wsl::windows::common::helpers::IsWindows11OrAbove(),
|
|
capabilities.Features.test(static_cast<size_t>(VmFeature::SavedStateOnCrash)));
|
|
VERIFY_IS_TRUE(capabilities.Features.test(static_cast<size_t>(VmFeature::SerialConsole)));
|
|
VERIFY_IS_TRUE(capabilities.Features.test(static_cast<size_t>(VmFeature::VirtioConsole)));
|
|
VERIFY_IS_TRUE(capabilities.Features.test(static_cast<size_t>(VmFeature::VirtioFsFileBacked)));
|
|
VERIFY_IS_TRUE(capabilities.Features.test(static_cast<size_t>(VmFeature::UserModeNatNetwork)));
|
|
VERIFY_IS_TRUE(capabilities.Features.test(static_cast<size_t>(VmFeature::TcpPortBinding)));
|
|
VERIFY_IS_TRUE(capabilities.Features.test(static_cast<size_t>(VmFeature::UdpPortBinding)));
|
|
VERIFY_IS_TRUE(capabilities.Features.test(static_cast<size_t>(VmFeature::Ipv6PortBinding)));
|
|
backend->Terminate();
|
|
}
|
|
|
|
TEST_METHOD(BootsAndTerminates)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
VerifyBootsAndTerminates(HcsVirtualMachineBackend::Create(CreateRunnableRequest()));
|
|
}
|
|
|
|
TEST_METHOD(AllocatesFirstFreeBootDiskLun)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
const auto directory = CreateTestDirectory();
|
|
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { std::filesystem::remove_all(directory); });
|
|
const auto exactPath = directory / L"exact.vhdx";
|
|
const auto automaticPath = directory / L"automatic.vhdx";
|
|
CreateVhd(exactPath);
|
|
CreateVhd(automaticPath);
|
|
|
|
auto request = CreateRunnableRequest();
|
|
VmBootDiskRequest exact;
|
|
exact.Key = L"exact";
|
|
exact.Disk = CreateDiskRequest(exactPath, 253);
|
|
VmBootDiskRequest automatic;
|
|
automatic.Key = L"automatic";
|
|
automatic.Disk = CreateDiskRequest(automaticPath);
|
|
request.BootDisks = {std::move(exact), std::move(automatic)};
|
|
|
|
auto backend = HcsVirtualMachineBackend::Create(request);
|
|
const auto& bootDisks = backend->GetDescription().BootDisks;
|
|
VERIFY_ARE_EQUAL(UINT32{253}, bootDisks.at(L"exact").GuestAddress.Lun);
|
|
VERIFY_ARE_EQUAL(UINT32{0}, bootDisks.at(L"automatic").GuestAddress.Lun);
|
|
backend->Terminate();
|
|
}
|
|
|
|
TEST_METHOD(RollsBackBootDiskAccessOnConfigurationFailure)
|
|
{
|
|
const auto directory = CreateTestDirectory();
|
|
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { std::filesystem::remove_all(directory); });
|
|
const auto firstPath = directory / L"first.vhdx";
|
|
CreateVhd(firstPath);
|
|
|
|
const auto firstDaclAces = GetDaclAces(firstPath);
|
|
auto request = CreateRunnableRequest();
|
|
request.Identity.UserToken = wil::shared_handle{GetElevatedTestToken().release()};
|
|
VmBootDiskRequest first;
|
|
first.Key = L"first";
|
|
first.Disk = CreateDiskRequest(firstPath);
|
|
first.GrantHostAccess = true;
|
|
VmBootDiskRequest invalid;
|
|
invalid.Key = L"first";
|
|
invalid.Disk = CreateDiskRequest(firstPath);
|
|
invalid.GrantHostAccess = true;
|
|
request.BootDisks = {std::move(first), std::move(invalid)};
|
|
|
|
VERIFY_ARE_NOT_EQUAL(S_OK, OperationResult([&] { HcsVirtualMachineBackend::Create(request); }));
|
|
VERIFY_ARE_EQUAL(firstDaclAces, GetDaclAces(firstPath));
|
|
}
|
|
|
|
TEST_METHOD(ManagesDiskPlacementsAndLifetime)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
const auto directory = CreateTestDirectory();
|
|
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { std::filesystem::remove_all(directory); });
|
|
const auto automaticPath = directory / L"automatic.vhdx";
|
|
const auto exactPath = directory / L"exact.vhdx";
|
|
const auto replacementPath = directory / L"replacement.vhdx";
|
|
const auto invalidPath = directory / L"invalid.vhdx";
|
|
CreateVhd(automaticPath);
|
|
CreateVhd(exactPath);
|
|
CreateVhd(replacementPath);
|
|
CreateVhd(invalidPath);
|
|
|
|
auto backend = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
auto guest = StartGuest(*backend);
|
|
const auto automatic = backend->AttachDisk(CreateDiskRequest(automaticPath));
|
|
const auto exact = backend->AttachDisk(CreateDiskRequest(exactPath, 2));
|
|
const auto replacement = backend->AttachDisk(CreateDiskRequest(replacementPath));
|
|
|
|
VERIFY_ARE_EQUAL(UINT32{0}, automatic.GuestAddress.Lun);
|
|
VERIFY_ARE_EQUAL(UINT32{2}, exact.GuestAddress.Lun);
|
|
VERIFY_ARE_EQUAL(UINT32{1}, replacement.GuestAddress.Lun);
|
|
VERIFY_IS_TRUE(IsEqualGUID(backend->GetDescription().Identity.VmId, automatic.Id.Owner.VmId));
|
|
|
|
const auto duplicate = backend->AttachDisk(CreateDiskRequest(automaticPath, 3));
|
|
VERIFY_ARE_EQUAL(automatic.Id.Value, duplicate.Id.Value);
|
|
VERIFY_ARE_EQUAL(automatic.GuestAddress.Lun, duplicate.GuestAddress.Lun);
|
|
const auto differentlyCased = backend->AttachDisk(CreateDiskRequest(ChangePathCase(automaticPath)));
|
|
VERIFY_ARE_EQUAL(automatic.Id.Value, differentlyCased.Id.Value);
|
|
VERIFY_ARE_EQUAL(automatic.GuestAddress.Lun, differentlyCased.GuestAddress.Lun);
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] {
|
|
backend->AttachDisk(CreateDiskRequest(invalidPath, exact.GuestAddress.Lun));
|
|
}));
|
|
|
|
auto invalidOwner = automatic.Id;
|
|
THROW_IF_FAILED(CoCreateGuid(&invalidOwner.Owner.VmId));
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->DetachDisk(invalidOwner); }));
|
|
|
|
auto unknown = automatic.Id;
|
|
unknown.Value = UINT64_MAX;
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), OperationResult([&] { backend->DetachDisk(unknown); }));
|
|
|
|
backend->DetachDisk(replacement.Id);
|
|
const auto reused = backend->AttachDisk(CreateDiskRequest(invalidPath, replacement.GuestAddress.Lun));
|
|
VERIFY_ARE_EQUAL(replacement.GuestAddress.Lun, reused.GuestAddress.Lun);
|
|
VERIFY_ARE_NOT_EQUAL(replacement.Id.Value, reused.Id.Value);
|
|
|
|
backend->DetachDisk(reused.Id);
|
|
backend->DetachDisk(exact.Id);
|
|
backend->DetachDisk(automatic.Id);
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), OperationResult([&] { backend->DetachDisk(automatic.Id); }));
|
|
backend->Terminate();
|
|
}
|
|
|
|
TEST_METHOD(KeepsDiskIdsVmScopedAndEnforcesPlacementBounds)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
const auto directory = CreateTestDirectory();
|
|
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { std::filesystem::remove_all(directory); });
|
|
const auto firstPath = directory / L"first.vhdx";
|
|
const auto secondPath = directory / L"second.vhdx";
|
|
CreateVhd(firstPath);
|
|
CreateVhd(secondPath);
|
|
|
|
auto firstBackend = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
auto firstGuest = StartGuest(*firstBackend);
|
|
const auto first = firstBackend->AttachDisk(CreateDiskRequest(firstPath, 253));
|
|
|
|
VERIFY_ARE_EQUAL(UINT32{253}, first.GuestAddress.Lun);
|
|
VERIFY_ARE_EQUAL(c_notSupported, OperationResult([&] { firstBackend->AttachDisk(CreateDiskRequest(secondPath, 254)); }));
|
|
|
|
firstBackend->DetachDisk(first.Id);
|
|
firstBackend->Terminate();
|
|
|
|
// Disk IDs are local to a VM and can be reused after another VM has terminated.
|
|
auto secondBackend = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
auto secondGuest = StartGuest(*secondBackend);
|
|
const auto second = secondBackend->AttachDisk(CreateDiskRequest(secondPath));
|
|
|
|
VERIFY_ARE_EQUAL(first.Id.Value, second.Id.Value);
|
|
VERIFY_IS_FALSE(IsEqualGUID(first.Id.Owner.VmId, second.Id.Owner.VmId));
|
|
|
|
secondBackend->DetachDisk(second.Id);
|
|
secondBackend->Terminate();
|
|
}
|
|
|
|
TEST_METHOD(RejectsInvalidIdentity)
|
|
{
|
|
auto request = CreateRunnableRequest();
|
|
request.Identity.VmId = GUID_NULL;
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { HcsVirtualMachineBackend::Create(request); }));
|
|
}
|
|
|
|
TEST_METHOD(DefersNetworkAdapterCreationUntilVmStarts)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
auto request = CreateRunnableRequest();
|
|
request.NetworkAdapters.push_back({});
|
|
auto backend = HcsVirtualMachineBackend::Create(request);
|
|
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->Start(); }));
|
|
backend->Terminate();
|
|
}
|
|
|
|
TEST_METHOD(ReusesDuplicateNetworkAdapterRequests)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
auto backend = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
auto guest = StartGuest(*backend);
|
|
|
|
const auto request = CreateNetworkRequest();
|
|
const auto first = backend->AddNetworkAdapter(request);
|
|
const auto duplicate = backend->AddNetworkAdapter(request);
|
|
VERIFY_ARE_EQUAL(first.Id.Value, duplicate.Id.Value);
|
|
VERIFY_ARE_EQUAL(first.Tag, duplicate.Tag);
|
|
VERIFY_IS_TRUE(first.GuestInstanceId == duplicate.GuestInstanceId);
|
|
const auto description = backend->GetDescription();
|
|
VERIFY_ARE_EQUAL(size_t{1}, description.NetworkAdapters.size());
|
|
VERIFY_ARE_EQUAL(first.Id.Value, description.NetworkAdapters.at(request.Tag).Id.Value);
|
|
|
|
backend->RemoveNetworkAdapter(first.Id);
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->RemoveNetworkAdapter(duplicate.Id); }));
|
|
VERIFY_IS_TRUE(backend->GetDescription().NetworkAdapters.empty());
|
|
backend->Terminate();
|
|
}
|
|
|
|
TEST_METHOD(KeepsOtherVmDeviceHostsAlive)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
const auto directory = CreateTestDirectory();
|
|
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { std::filesystem::remove_all(directory); });
|
|
|
|
auto first = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
auto firstGuest = StartGuest(*first);
|
|
first->CreateFileSystemDevice({VmVirtioFsDevice{L"first", VmVirtioFsLayout::Aggregate}});
|
|
|
|
auto second = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
auto secondGuest = StartGuest(*second);
|
|
const auto device = second->CreateFileSystemDevice({VmVirtioFsDevice{L"second", VmVirtioFsLayout::Aggregate}});
|
|
|
|
first.reset();
|
|
|
|
VmFileSystemShareRequest request;
|
|
request.HostPath = directory;
|
|
request.Options = VmVirtioFsShareOptions{};
|
|
const auto share = second->AddFileSystemShare(device.Id, request);
|
|
VERIFY_ARE_EQUAL(device.Id.Value, share.Device.Value);
|
|
second->RemoveFileSystemShare(share.Id);
|
|
second->Terminate();
|
|
}
|
|
|
|
TEST_METHOD(NotifiesTerminationCallbacksWithoutHoldingBackendLock)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
auto backend = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
wil::unique_event callbackEvent{wil::EventOptions::ManualReset};
|
|
GUID callbackVmId{};
|
|
HRESULT callbackResult = S_OK;
|
|
backend->RegisterTerminationCallback([&](GUID VmId) {
|
|
callbackVmId = VmId;
|
|
callbackResult = OperationResult([&] { backend->ConnectGuest({}); });
|
|
callbackEvent.SetEvent();
|
|
});
|
|
|
|
backend->Terminate();
|
|
VERIFY_IS_TRUE(callbackEvent.wait(30 * 1000));
|
|
VERIFY_IS_TRUE(IsEqualGUID(backend->GetDescription().Identity.VmId, callbackVmId));
|
|
VERIFY_ARE_EQUAL(c_invalidState, callbackResult);
|
|
|
|
auto lateBackend = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
lateBackend->Terminate();
|
|
wil::unique_event lateCallbackEvent{wil::EventOptions::ManualReset};
|
|
GUID lateCallbackVmId{};
|
|
lateBackend->RegisterTerminationCallback([&](GUID VmId) {
|
|
lateCallbackVmId = VmId;
|
|
lateCallbackEvent.SetEvent();
|
|
});
|
|
|
|
VERIFY_IS_TRUE(lateCallbackEvent.wait(30 * 1000));
|
|
VERIFY_IS_TRUE(IsEqualGUID(lateBackend->GetDescription().Identity.VmId, lateCallbackVmId));
|
|
}
|
|
|
|
TEST_METHOD(RejectsUnsupportedResourceOperations)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
auto backend = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
const auto identity = backend->GetDescription().Identity;
|
|
const VmDeviceId device{identity, 1};
|
|
const VmShareId share{identity, 1};
|
|
const VmPortBindingId binding{identity, 1};
|
|
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->CreateFileSystemDevice({VmPlan9SocketDevice{}}); }));
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddFileSystemShare(device, {}); }));
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] {
|
|
VmFileSystemShareRequest request{};
|
|
request.HostPath = L"C:\\";
|
|
backend->AddFileSystemShare(device, request);
|
|
}));
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->RemoveFileSystemShare(share); }));
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddNetworkAdapter({}); }));
|
|
VmPortBindingRequest bindingRequest{};
|
|
bindingRequest.ListenAddress.family = IpAddressFamily_V4;
|
|
const auto loopbackAddress = htonl(INADDR_LOOPBACK);
|
|
std::memcpy(bindingRequest.ListenAddress.bytes, &loopbackAddress, sizeof(loopbackAddress));
|
|
bindingRequest.GuestPort = 80;
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->BindPort(device, bindingRequest); }));
|
|
bindingRequest.ListenScopeId = 1;
|
|
VERIFY_ARE_EQUAL(c_notSupported, OperationResult([&] { backend->BindPort(device, bindingRequest); }));
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->UnbindPort(binding); }));
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddPersistentMemory({}); }));
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddSharedMemory({}); }));
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->ConfigureGuestDma({}); }));
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->GetFileSystemDeviceStatus(device); }));
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->RemoveDevice(device); }));
|
|
backend->Terminate();
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), OperationResult([&] { backend->GetComputeSystemHandle(); }));
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), OperationResult([&] { backend->GetGuestDeviceManager(); }));
|
|
}
|
|
|
|
TEST_METHOD(SharesHostDirectoriesPerElevationLevel)
|
|
{
|
|
SKIP_TEST_ARM64();
|
|
const auto directory = CreateTestDirectory();
|
|
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { std::filesystem::remove_all(directory); });
|
|
|
|
auto backend = HcsVirtualMachineBackend::Create(CreateRunnableRequest());
|
|
auto guest = StartGuest(*backend);
|
|
|
|
// Elevated and unelevated callers share one VM, so each elevation level gets its own device
|
|
// just as WslCoreVm::AddDrvFsShare uses a separate Plan 9 port and virtio-fs tag for each.
|
|
// An aggregate device is created with the identity it serves its children under.
|
|
const auto userDevice = backend->CreateFileSystemDevice({VmVirtioFsDevice{L"drvfs-user", VmVirtioFsLayout::Aggregate}});
|
|
const auto adminDevice = backend->CreateFileSystemDevice({VmVirtioFsDevice{
|
|
L"drvfs-admin", VmVirtioFsLayout::Aggregate, VmVirtioFsShareOptions{{}, wil::shared_handle{GetElevatedTestToken().release()}}}});
|
|
VERIFY_ARE_EQUAL(VmFileSystemDeviceState::Serving, userDevice.State);
|
|
VERIFY_IS_TRUE(userDevice.GuestInstanceId.has_value());
|
|
VERIFY_ARE_NOT_EQUAL(userDevice.Id.Value, adminDevice.Id.Value);
|
|
VERIFY_ARE_EQUAL(VmFileSystemDeviceState::Serving, backend->GetFileSystemDeviceStatus(userDevice.Id).State);
|
|
|
|
// Leaving the token unset serves the share through the identity that created the VM.
|
|
VmFileSystemShareRequest request;
|
|
request.HostPath = directory;
|
|
request.Options = VmVirtioFsShareOptions{};
|
|
request.ReadOnly = false;
|
|
|
|
const auto share = backend->AddFileSystemShare(userDevice.Id, request);
|
|
VERIFY_ARE_EQUAL(userDevice.Id.Value, share.Device.Value);
|
|
VERIFY_IS_TRUE(IsEqualGUID(backend->GetDescription().Identity.VmId, share.Id.Owner.VmId));
|
|
VERIFY_IS_FALSE(share.ReadOnly);
|
|
|
|
const auto& address = std::get<VmVirtioFsShareAddress>(share.GuestAddress);
|
|
VERIFY_ARE_EQUAL(std::wstring{L"drvfs-user"}, address.Tag);
|
|
VERIFY_IS_TRUE(address.ChildName.has_value());
|
|
|
|
// The host path is canonicalized so that requests naming the same directory resolve to a
|
|
// single share. GetCanonicalPath removes the separator NormalizeSharePath appends before
|
|
// canonicalization.
|
|
VERIFY_ARE_EQUAL(std::filesystem::canonical(directory).native(), share.EffectiveHostPath.native());
|
|
|
|
// Repeating a request reuses the share instead of adding a second child for one directory.
|
|
const auto reused = backend->AddFileSystemShare(userDevice.Id, request);
|
|
VERIFY_ARE_EQUAL(share.Id.Value, reused.Id.Value);
|
|
|
|
auto namedRequest = request;
|
|
namedRequest.Name = L"first";
|
|
const auto firstNamedShare = backend->AddFileSystemShare(userDevice.Id, namedRequest);
|
|
const auto& firstNamedAddress = std::get<VmVirtioFsShareAddress>(firstNamedShare.GuestAddress);
|
|
VERIFY_IS_TRUE(firstNamedAddress.ChildName.has_value());
|
|
VERIFY_ARE_EQUAL(std::wstring{L"first"}, firstNamedAddress.ChildName.value());
|
|
VERIFY_ARE_EQUAL(firstNamedShare.Id.Value, backend->AddFileSystemShare(userDevice.Id, namedRequest).Id.Value);
|
|
|
|
auto conflictingNamedRequest = namedRequest;
|
|
conflictingNamedRequest.ReadOnly = true;
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] {
|
|
backend->AddFileSystemShare(userDevice.Id, conflictingNamedRequest);
|
|
}));
|
|
conflictingNamedRequest = namedRequest;
|
|
conflictingNamedRequest.HostPath = directory.parent_path();
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] {
|
|
backend->AddFileSystemShare(userDevice.Id, conflictingNamedRequest);
|
|
}));
|
|
conflictingNamedRequest = namedRequest;
|
|
std::get<VmVirtioFsShareOptions>(conflictingNamedRequest.Options).MountOptions[L"dax"] = L"";
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] {
|
|
backend->AddFileSystemShare(userDevice.Id, conflictingNamedRequest);
|
|
}));
|
|
|
|
namedRequest.Name = L"second";
|
|
const auto secondNamedShare = backend->AddFileSystemShare(userDevice.Id, namedRequest);
|
|
const auto& secondNamedAddress = std::get<VmVirtioFsShareAddress>(secondNamedShare.GuestAddress);
|
|
VERIFY_ARE_NOT_EQUAL(firstNamedShare.Id.Value, secondNamedShare.Id.Value);
|
|
VERIFY_IS_TRUE(secondNamedAddress.ChildName.has_value());
|
|
VERIFY_ARE_EQUAL(std::wstring{L"second"}, secondNamedAddress.ChildName.value());
|
|
|
|
// Mount options are part of a share's identity, so a read-only mount is a separate share.
|
|
auto readOnlyRequest = request;
|
|
readOnlyRequest.ReadOnly = true;
|
|
const auto readOnlyShare = backend->AddFileSystemShare(userDevice.Id, readOnlyRequest);
|
|
VERIFY_ARE_NOT_EQUAL(share.Id.Value, readOnlyShare.Id.Value);
|
|
VERIFY_IS_TRUE(readOnlyShare.ReadOnly);
|
|
|
|
auto invalidShare = share.Id;
|
|
THROW_IF_FAILED(CoCreateGuid(&invalidShare.Owner.VmId));
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->RemoveFileSystemShare(invalidShare); }));
|
|
|
|
backend->RemoveFileSystemShare(share.Id);
|
|
const auto replacementShare = backend->AddFileSystemShare(userDevice.Id, request);
|
|
VERIFY_ARE_NOT_EQUAL(share.Id.Value, replacementShare.Id.Value);
|
|
backend->RemoveFileSystemShare(replacementShare.Id);
|
|
VERIFY_ARE_EQUAL(
|
|
HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->RemoveFileSystemShare(replacementShare.Id); }));
|
|
|
|
const auto singleShareDevice = backend->CreateFileSystemDevice({VmVirtioFsDevice{L"drvfs-single", VmVirtioFsLayout::SingleShare}});
|
|
// A single-share device inherits the options of the share that it serves, so device-level
|
|
// options would never reach the guest.
|
|
VERIFY_ARE_EQUAL(
|
|
E_INVALIDARG, OperationResult([&] {
|
|
backend->CreateFileSystemDevice({VmVirtioFsDevice{
|
|
L"drvfs-single-options", VmVirtioFsLayout::SingleShare, VmVirtioFsShareOptions{{{L"dax", L""}}}}});
|
|
}));
|
|
const auto singleShare = backend->AddFileSystemShare(singleShareDevice.Id, request);
|
|
VERIFY_IS_FALSE(std::get<VmVirtioFsShareAddress>(singleShare.GuestAddress).ChildName.has_value());
|
|
VERIFY_ARE_EQUAL(singleShare.Id.Value, backend->AddFileSystemShare(singleShareDevice.Id, request).Id.Value);
|
|
auto singleShareTokenRequest = request;
|
|
singleShareTokenRequest.UserToken = wil::shared_handle{GetElevatedTestToken().release()};
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] {
|
|
backend->AddFileSystemShare(singleShareDevice.Id, singleShareTokenRequest);
|
|
}));
|
|
singleShareTokenRequest.UserToken.reset();
|
|
std::get<VmVirtioFsShareOptions>(singleShareTokenRequest.Options).UserToken = wil::shared_handle{GetElevatedTestToken().release()};
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] {
|
|
backend->AddFileSystemShare(singleShareDevice.Id, singleShareTokenRequest);
|
|
}));
|
|
auto singleShareNamedRequest = request;
|
|
singleShareNamedRequest.Name = L"invalid";
|
|
VERIFY_ARE_EQUAL(
|
|
E_INVALIDARG, OperationResult([&] { backend->AddFileSystemShare(singleShareDevice.Id, singleShareNamedRequest); }));
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_BUSY), OperationResult([&] { backend->RemoveDevice(singleShareDevice.Id); }));
|
|
backend->RemoveFileSystemShare(singleShare.Id);
|
|
const auto replacementSingleShare = backend->AddFileSystemShare(singleShareDevice.Id, request);
|
|
VERIFY_ARE_NOT_EQUAL(singleShare.Id.Value, replacementSingleShare.Id.Value);
|
|
backend->RemoveFileSystemShare(replacementSingleShare.Id);
|
|
const auto tokenShare = backend->AddFileSystemShare(singleShareDevice.Id, singleShareTokenRequest);
|
|
VERIFY_ARE_EQUAL(tokenShare.Id.Value, backend->AddFileSystemShare(singleShareDevice.Id, singleShareTokenRequest).Id.Value);
|
|
auto equivalentTokenRequest = request;
|
|
equivalentTokenRequest.UserToken = std::get<VmVirtioFsShareOptions>(singleShareTokenRequest.Options).UserToken;
|
|
VERIFY_ARE_EQUAL(tokenShare.Id.Value, backend->AddFileSystemShare(singleShareDevice.Id, equivalentTokenRequest).Id.Value);
|
|
backend->RemoveFileSystemShare(tokenShare.Id);
|
|
backend->RemoveDevice(singleShareDevice.Id);
|
|
VERIFY_ARE_EQUAL(
|
|
HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->GetFileSystemDeviceStatus(singleShareDevice.Id); }));
|
|
|
|
// The elevated device reaches the same directory under its own identity without reusing the
|
|
// share that the unelevated device serves. A child of an aggregate device cannot name a
|
|
// second identity, so a share token is rejected rather than silently ignored.
|
|
const auto adminShare = backend->AddFileSystemShare(adminDevice.Id, request);
|
|
VERIFY_ARE_EQUAL(adminDevice.Id.Value, adminShare.Device.Value);
|
|
VERIFY_ARE_NOT_EQUAL(share.Id.Value, adminShare.Id.Value);
|
|
VERIFY_ARE_EQUAL(std::wstring{L"drvfs-admin"}, std::get<VmVirtioFsShareAddress>(adminShare.GuestAddress).Tag);
|
|
VERIFY_ARE_EQUAL(share.EffectiveHostPath.native(), adminShare.EffectiveHostPath.native());
|
|
|
|
auto tokenRequest = request;
|
|
tokenRequest.UserToken = wil::shared_handle{GetElevatedTestToken().release()};
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddFileSystemShare(userDevice.Id, tokenRequest); }));
|
|
|
|
// Exercise the remaining file-system transports as well: Plan 9 socket and Plan 9 virtio.
|
|
const auto createPlan9Server = [](HANDLE userToken) {
|
|
return wsl::windows::common::wslutil::CreateComServerAsUser<p9fs::Plan9FileSystem, IPlan9FileSystem>(userToken);
|
|
};
|
|
|
|
VmPlan9SocketDevice socketDevice{GuestServicePort{LX_INIT_UTILITY_VM_PLAN9_PORT}, createPlan9Server};
|
|
// The port is handed straight to the Plan 9 server, so an unassigned one must be rejected
|
|
// rather than listened on.
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] {
|
|
backend->CreateFileSystemDevice({VmPlan9SocketDevice{GuestServicePort{}, createPlan9Server}});
|
|
}));
|
|
const auto socketDeviceResult = backend->CreateFileSystemDevice({socketDevice});
|
|
VERIFY_ARE_EQUAL(VmFileSystemDeviceState::Serving, socketDeviceResult.State);
|
|
VmFileSystemShareRequest socketRequest;
|
|
socketRequest.HostPath = directory;
|
|
socketRequest.Options = VmPlan9ShareOptions{};
|
|
socketRequest.ReadOnly = false;
|
|
socketRequest.Name = L"socket-share";
|
|
const auto verifyPlan9NameCollisions = [&](VmDeviceId device, const VmFileSystemShareRequest& original) {
|
|
auto conflicting = original;
|
|
conflicting.HostPath = directory.parent_path();
|
|
VERIFY_ARE_EQUAL(
|
|
HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] { backend->AddFileSystemShare(device, conflicting); }));
|
|
conflicting = original;
|
|
conflicting.ReadOnly = !original.ReadOnly;
|
|
VERIFY_ARE_EQUAL(
|
|
HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] { backend->AddFileSystemShare(device, conflicting); }));
|
|
for (const auto flag :
|
|
{&VmPlan9ShareOptions::LinuxMetadata,
|
|
&VmPlan9ShareOptions::CaseSensitive,
|
|
&VmPlan9ShareOptions::UseShareRootIdentity,
|
|
&VmPlan9ShareOptions::AllowOptions,
|
|
&VmPlan9ShareOptions::AllowSubPaths})
|
|
{
|
|
conflicting = original;
|
|
auto& options = std::get<VmPlan9ShareOptions>(conflicting.Options);
|
|
options.*flag = !(options.*flag);
|
|
VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] {
|
|
backend->AddFileSystemShare(device, conflicting);
|
|
}));
|
|
}
|
|
conflicting = original;
|
|
conflicting.UserToken = wil::shared_handle{GetElevatedTestToken().release()};
|
|
VERIFY_ARE_EQUAL(
|
|
HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), OperationResult([&] { backend->AddFileSystemShare(device, conflicting); }));
|
|
conflicting = original;
|
|
conflicting.Options = VmVirtioFsShareOptions{};
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddFileSystemShare(device, conflicting); }));
|
|
conflicting = original;
|
|
conflicting.UserToken = original.UserToken ? original.UserToken : backend->GetDescription().Identity.UserToken;
|
|
VERIFY_ARE_EQUAL(
|
|
backend->AddFileSystemShare(device, original).Id.Value, backend->AddFileSystemShare(device, conflicting).Id.Value);
|
|
wil::unique_handle duplicatedToken;
|
|
THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(
|
|
GetCurrentProcess(), conflicting.UserToken->get(), GetCurrentProcess(), duplicatedToken.put(), 0, FALSE, DUPLICATE_SAME_ACCESS));
|
|
conflicting.UserToken = wil::shared_handle{duplicatedToken.release()};
|
|
VERIFY_ARE_EQUAL(
|
|
backend->AddFileSystemShare(device, original).Id.Value, backend->AddFileSystemShare(device, conflicting).Id.Value);
|
|
};
|
|
const auto socketShare = backend->AddFileSystemShare(socketDeviceResult.Id, socketRequest);
|
|
VERIFY_ARE_EQUAL(socketDevice.Port.Value, std::get<VmPlan9SocketShareAddress>(socketShare.GuestAddress).Port.Value);
|
|
VERIFY_IS_FALSE(socketShare.ReadOnly);
|
|
const auto duplicateSocketShare = backend->AddFileSystemShare(socketDeviceResult.Id, socketRequest);
|
|
VERIFY_ARE_EQUAL(socketShare.Id.Value, duplicateSocketShare.Id.Value);
|
|
verifyPlan9NameCollisions(socketDeviceResult.Id, socketRequest);
|
|
|
|
// A Plan 9 device keeps serving after one of its shares is removed, so the share can be added again.
|
|
backend->RemoveFileSystemShare(socketShare.Id);
|
|
VERIFY_ARE_EQUAL(
|
|
HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->RemoveFileSystemShare(socketShare.Id); }));
|
|
const auto replacementSocketShare = backend->AddFileSystemShare(socketDeviceResult.Id, socketRequest);
|
|
VERIFY_ARE_NOT_EQUAL(socketShare.Id.Value, replacementSocketShare.Id.Value);
|
|
backend->RemoveFileSystemShare(replacementSocketShare.Id);
|
|
backend->RemoveDevice(socketDeviceResult.Id);
|
|
|
|
VmPlan9HostedDevice hostedDevice{GuestServicePort{LX_INIT_UTILITY_VM_PLAN9_PORT}};
|
|
const auto hostedDeviceResult = backend->CreateFileSystemDevice({hostedDevice});
|
|
const auto hostedShare = backend->AddFileSystemShare(hostedDeviceResult.Id, socketRequest);
|
|
VERIFY_ARE_EQUAL(hostedDevice.Port.Value, std::get<VmPlan9SocketShareAddress>(hostedShare.GuestAddress).Port.Value);
|
|
verifyPlan9NameCollisions(hostedDeviceResult.Id, socketRequest);
|
|
backend->RemoveFileSystemShare(hostedShare.Id);
|
|
backend->RemoveDevice(hostedDeviceResult.Id);
|
|
|
|
const auto sharedMemory = backend->AddSharedMemory({L"test-memory", L"test-memory", 8 * c_mib});
|
|
VERIFY_ARE_EQUAL(UINT64{8 * c_mib}, sharedMemory.SizeBytes);
|
|
VERIFY_ARE_EQUAL(std::wstring{L"test-memory"}, sharedMemory.Tag);
|
|
VERIFY_IS_FALSE(IsEqualGUID(GUID_NULL, sharedMemory.GuestInstanceId));
|
|
backend->RemoveDevice(sharedMemory.Id);
|
|
|
|
VmPlan9VirtioDevice virtioDevice{L"plan9-virtio"};
|
|
virtioDevice.FileSystemClassId = __uuidof(p9fs::Plan9FileSystem);
|
|
virtioDevice.DeviceType = VIRTIO_PLAN9_DEVICE_ID;
|
|
virtioDevice.ServerFactory = createPlan9Server;
|
|
const auto virtioDeviceResult = backend->CreateFileSystemDevice({virtioDevice});
|
|
VERIFY_ARE_EQUAL(VmFileSystemDeviceState::Serving, virtioDeviceResult.State);
|
|
VmFileSystemShareRequest virtioRequest;
|
|
virtioRequest.HostPath = directory;
|
|
virtioRequest.Options = VmPlan9ShareOptions{};
|
|
virtioRequest.ReadOnly = false;
|
|
virtioRequest.Name = L"virtio-share";
|
|
const auto virtioShare = backend->AddFileSystemShare(virtioDeviceResult.Id, virtioRequest);
|
|
VERIFY_ARE_EQUAL(std::wstring{L"plan9-virtio"}, std::get<VmPlan9VirtioShareAddress>(virtioShare.GuestAddress).Tag);
|
|
VERIFY_IS_FALSE(virtioShare.ReadOnly);
|
|
verifyPlan9NameCollisions(virtioDeviceResult.Id, virtioRequest);
|
|
backend->RemoveFileSystemShare(virtioShare.Id);
|
|
const auto replacementVirtioShare = backend->AddFileSystemShare(virtioDeviceResult.Id, virtioRequest);
|
|
VERIFY_ARE_NOT_EQUAL(virtioShare.Id.Value, replacementVirtioShare.Id.Value);
|
|
backend->RemoveFileSystemShare(replacementVirtioShare.Id);
|
|
|
|
// A host path is required, and the options must match the device that serves them.
|
|
VmFileSystemShareRequest pathless;
|
|
pathless.Options = VmVirtioFsShareOptions{};
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddFileSystemShare(userDevice.Id, pathless); }));
|
|
|
|
auto plan9Options = request;
|
|
plan9Options.Options = VmPlan9ShareOptions{};
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddFileSystemShare(userDevice.Id, plan9Options); }));
|
|
|
|
auto unknownDevice = userDevice.Id;
|
|
unknownDevice.Value = UINT64_MAX;
|
|
VERIFY_ARE_EQUAL(
|
|
HRESULT_FROM_WIN32(ERROR_NOT_FOUND), OperationResult([&] { backend->AddFileSystemShare(unknownDevice, request); }));
|
|
|
|
auto foreignDevice = userDevice.Id;
|
|
THROW_IF_FAILED(CoCreateGuid(&foreignDevice.Owner.VmId));
|
|
VERIFY_ARE_EQUAL(E_INVALIDARG, OperationResult([&] { backend->AddFileSystemShare(foreignDevice, request); }));
|
|
|
|
// A torn-down VM reports the same error as WslCoreVm::AddDrvFsShare.
|
|
backend->Terminate();
|
|
VERIFY_ARE_EQUAL(HCS_E_TERMINATED, OperationResult([&] { backend->AddFileSystemShare(userDevice.Id, request); }));
|
|
}
|
|
};
|
|
|
|
} // namespace HcsVirtualMachineBackendTests
|