Implement WSLA API to unmount & detach disks (#13364)

* Implement WSLA API to unmount & detach disks

* Add WSL2_TEST_ONLY();

* Fix wslg path
This commit is contained in:
Blue 2025-08-11 12:27:23 -07:00 committed by Blue
parent 3451e8213d
commit 28bcfe39d6
9 changed files with 175 additions and 14 deletions

View File

@ -427,6 +427,18 @@ void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_SIGNAL& Me
Channel.SendResultMessage(result < 0 ? errno : 0);
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_UNMOUNT& Message, const gsl::span<gsl::byte>& Buffer)
{
Channel.SendResultMessage<int32_t>(umount(Message.Buffer) == 0 ? 0 : errno);
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_DETACH& Message, const gsl::span<gsl::byte>& Buffer)
{
sync();
Channel.SendResultMessage<int32_t>(DetachScsiDisk(Message.Lun));
}
template <typename TMessage, typename... Args>
void HandleMessage(wsl::shared::SocketChannel& Channel, LX_MESSAGE_TYPE Type, const gsl::span<gsl::byte>& Buffer)
{
@ -461,7 +473,7 @@ void ProcessMessage(wsl::shared::SocketChannel& Channel, LX_MESSAGE_TYPE Type, c
{
try
{
HandleMessage<LSW_GET_DISK, LSW_MOUNT, LSW_EXEC, LSW_FORK, LSW_CONNECT, LSW_WAITPID, LSW_SIGNAL, LSW_TTY_RELAY, LSW_PORT_RELAY>(
HandleMessage<LSW_GET_DISK, LSW_MOUNT, LSW_EXEC, LSW_FORK, LSW_CONNECT, LSW_WAITPID, LSW_SIGNAL, LSW_TTY_RELAY, LSW_PORT_RELAY, LSW_UNMOUNT, LSW_DETACH>(
Channel, Type, Buffer);
}
catch (...)

View File

@ -384,6 +384,8 @@ typedef enum _LX_MESSAGE_TYPE
LxMessageLswMapPort,
LxMessageLswConnectRelay,
LxMessageLswPortRelay,
LxMessageLswUnmount,
LxMessageLswDetach,
} LX_MESSAGE_TYPE,
*PLX_MESSAGE_TYPE;
@ -488,6 +490,8 @@ inline auto ToString(LX_MESSAGE_TYPE messageType)
X(LxMessageLswMapPort)
X(LxMessageLswConnectRelay)
X(LxMessageLswPortRelay)
X(LxMessageLswUnmount)
X(LxMessageLswDetach)
default:
return "<unexpected LX_MESSAGE_TYPE>";
}
@ -1760,6 +1764,31 @@ struct LSW_PORT_RELAY
PRETTY_PRINT(FIELD(Header));
};
struct LSW_UNMOUNT
{
static inline auto Type = LxMessageLswUnmount;
using TResponse = RESULT_MESSAGE<int32_t>;
DECLARE_MESSAGE_CTOR(LSW_UNMOUNT);
MESSAGE_HEADER Header;
char Buffer[];
PRETTY_PRINT(FIELD(Header), FIELD(Buffer));
};
struct LSW_DETACH
{
static inline auto Type = LxMessageLswDetach;
using TResponse = RESULT_MESSAGE<int32_t>;
DECLARE_MESSAGE_CTOR(LSW_DETACH);
MESSAGE_HEADER Header;
unsigned int Lun;
PRETTY_PRINT(FIELD(Header), FIELD(Lun));
};
typedef struct _LX_MINI_INIT_IMPORT_RESULT
{
static inline auto Type = LxMiniInitMessageImportResult;

View File

@ -111,9 +111,9 @@ CATCH_RETURN();
HRESULT WslAttachDisk(LSWVirtualMachineHandle VirtualMachine, const DiskAttachSettings* Settings, AttachedDiskInformation* AttachedDisk)
{
wil::unique_cotaskmem_ansistring device;
RETURN_IF_FAILED(reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)->AttachDisk(Settings->WindowsPath, Settings->ReadOnly, &device));
RETURN_IF_FAILED(reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)
->AttachDisk(Settings->WindowsPath, Settings->ReadOnly, &device, &AttachedDisk->ScsiLun));
// TODO: wire LUN
auto deviceSize = strlen(device.get());
WI_VERIFY(deviceSize < sizeof(AttachedDiskInformation::Device));
@ -285,6 +285,15 @@ try
}
CATCH_RETURN();
HRESULT WslUnmount(LSWVirtualMachineHandle VirtualMachine, const char* Path)
{
return reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)->Unmount(Path);
}
HRESULT WslDetachDisk(LSWVirtualMachineHandle VirtualMachine, ULONG Lun)
{
return reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)->DetachDisk(Lun);
}
EXTERN_C BOOL STDAPICALLTYPE DllMain(_In_ HINSTANCE Instance, _In_ DWORD Reason, _In_opt_ LPVOID Reserved)
{
wil::DLLMain(Instance, Reason, Reserved);

View File

@ -179,6 +179,10 @@ HRESULT WslMapPort(LSWVirtualMachineHandle VirtualMachine, const struct PortMapp
HRESULT WslUnmapPort(LSWVirtualMachineHandle VirtualMachine, const struct PortMappingSettings* Settings);
HRESULT WslUnmount(LSWVirtualMachineHandle VirtualMachine, const char* Path);
HRESULT WslDetachDisk(LSWVirtualMachineHandle VirtualMachine, ULONG Lun);
enum WslInstallComponent
{
WslInstallComponentNone = 0,

View File

@ -14,6 +14,8 @@ EXPORTS
WslLaunchDebugShell
WslMapPort
WslUnmapPort
WslDetachDisk
WslUnmount
WslQueryMissingComponents
WslInstallComponents
WslSetPackageUrl
WslSetPackageUrl

View File

@ -331,7 +331,7 @@ void LSWVirtualMachine::OnExit(_In_ const HCS_EVENT* Event)
}
}
HRESULT LSWVirtualMachine::AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly, _Out_ LPSTR* Device)
HRESULT LSWVirtualMachine::AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly, _Out_ LPSTR* Device, _Out_ ULONG* Lun)
try
{
*Device = nullptr;
@ -345,35 +345,35 @@ try
wsl::windows::common::hcs::GrantVmAccess(m_vmIdString.c_str(), Path);
}
ULONG lun = 0;
while (m_attachedDisks.find(lun) != m_attachedDisks.end())
*Lun = 0;
while (m_attachedDisks.find(*Lun) != m_attachedDisks.end())
{
lun++;
(*Lun)++;
}
bool vhdAdded = false;
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
if (vhdAdded)
{
wsl::windows::common::hcs::RemoveScsiDisk(m_computeSystem.get(), lun);
wsl::windows::common::hcs::RemoveScsiDisk(m_computeSystem.get(), *Lun);
}
wsl::windows::common::hcs::RevokeVmAccess(m_vmIdString.c_str(), Path);
});
wsl::windows::common::hcs::AddVhd(m_computeSystem.get(), Path, lun, ReadOnly);
wsl::windows::common::hcs::AddVhd(m_computeSystem.get(), Path, *Lun, ReadOnly);
vhdAdded = true;
LSW_GET_DISK message{};
message.Header.MessageSize = sizeof(message);
message.Header.MessageType = LSW_GET_DISK::Type;
message.ScsiLun = lun;
message.ScsiLun = *Lun;
const auto& response = m_initChannel.Transaction(message);
THROW_HR_IF_MSG(E_FAIL, response.Result != 0, "Failed to attach disk, init returned: %lu", response.Result);
cleanup.release();
m_attachedDisks.emplace(lun, AttachedDisk{Path, response.Buffer});
m_attachedDisks.emplace(*Lun, AttachedDisk{Path, response.Buffer});
*Device = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(response.Buffer).release();
});
@ -431,6 +431,49 @@ try
}
CATCH_RETURN();
HRESULT LSWVirtualMachine::Unmount(_In_ const char* Path)
try
{
auto [pid, _, subChannel] = Fork(LSW_FORK::Thread);
wsl::shared::MessageWriter<LSW_UNMOUNT> message;
message.WriteString(Path);
const auto& response = subChannel.Transaction<LSW_UNMOUNT>(message.Span());
// TODO: Return errno to caller
THROW_HR_IF(E_FAIL, response.Result != 0);
return S_OK;
}
CATCH_RETURN()
HRESULT LSWVirtualMachine::DetachDisk(_In_ ULONG Lun)
try
{
std::lock_guard lock{m_lock};
// Find the disk
auto it = m_attachedDisks.find(Lun);
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), it == m_attachedDisks.end());
// Detach it from the guest
LSW_DETACH message;
message.Lun = Lun;
const auto& response = m_initChannel.Transaction(message);
// TODO: Return errno to caller
THROW_HR_IF(E_FAIL, response.Result != 0);
// Remove it from the VM
m_attachedDisks.erase(it);
hcs::RemoveScsiDisk(m_computeSystem.get(), Lun);
return S_OK;
}
CATCH_RETURN()
std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> LSWVirtualMachine::Fork(enum LSW_FORK::ForkType Type)
{
std::lock_guard lock{m_lock};

View File

@ -28,7 +28,7 @@ public:
void Start();
IFACEMETHOD(AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly, _Out_ LPSTR* Device)) override;
IFACEMETHOD(AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly, _Out_ LPSTR* Device, _Out_ ULONG* Lun)) override;
IFACEMETHOD(Mount(_In_ LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags)) override;
IFACEMETHOD(CreateLinuxProcess(
_In_ const LSW_CREATE_PROCESS_OPTIONS* Options, _In_ ULONG FdCount, _In_ LSW_PROCESS_FD* Fd, _Out_ HANDLE* Handles, _Out_ LSW_CREATE_PROCESS_RESULT* Result)) override;
@ -38,6 +38,8 @@ public:
IFACEMETHOD(RegisterCallback(_In_ ITerminationCallback* callback)) override;
IFACEMETHOD(GetDebugShellPipe(_Out_ LPWSTR* pipePath)) override;
IFACEMETHOD(MapPort(_In_ int Family, _In_ short WindowsPort, _In_ short LinuxPort, _In_ BOOL Remove)) override;
IFACEMETHOD(Unmount(_In_ const char* Path)) override;
IFACEMETHOD(DetachDisk(_In_ ULONG Lun)) override;
private:
static void CALLBACK s_OnExit(_In_ HCS_EVENT* Event, _In_opt_ void* Context);

View File

@ -449,7 +449,7 @@ interface ITerminationCallback : IUnknown
]
interface ILSWVirtualMachine : IUnknown
{
HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out] LPSTR* Device);
HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out] LPSTR* Device, [out] ULONG* Lun);
HRESULT Mount([in, unique] LPCSTR Source, [in] LPCSTR Target, [in] LPCSTR Type, [in] LPCSTR Options, [in] ULONG Flags);
HRESULT CreateLinuxProcess([in] const LSW_CREATE_PROCESS_OPTIONS* Options, [in] ULONG FdCount, [in, unique, size_is(FdCount)] LSW_PROCESS_FD* Fds, [out, size_is(FdCount)] HVSOCKET_HANDLE* Handles, [out] LSW_CREATE_PROCESS_RESULT* Result);
HRESULT WaitPid([in] LONG Pid, [in] ULONGLONG TimeoutMs, [out] ULONG* State, [out] int* Code);
@ -458,6 +458,8 @@ interface ILSWVirtualMachine : IUnknown
HRESULT RegisterCallback([in] ITerminationCallback* terminationCallback);
HRESULT GetDebugShellPipe([out] LPWSTR* pipePath);
HRESULT MapPort([in] int Family, [in] short WindowsPort, [in] short LinuxPort, [in] BOOL Remove);
HRESULT Unmount([in] LPCSTR Path);
HRESULT DetachDisk([in] ULONG Lun);
}
typedef

View File

@ -122,6 +122,64 @@ class LSWTests
return vm;
}
TEST_METHOD(AttachDetach)
{
WSL2_TEST_ONLY();
VirtualMachineSettings settings{};
settings.CPU.CpuCount = 4;
settings.DisplayName = L"LSW";
settings.Memory.MemoryMb = 1024;
settings.Options.BootTimeoutMs = 30000;
auto vm = CreateVm(&settings);
#ifdef WSL_DEV_INSTALL_PATH
auto vhdPath = std::filesystem::path(WSL_DEV_INSTALL_PATH) / "system.vhd";
#else
auto msiPath = wsl::windows::common::wslutil::GetMsiPackagePath();
VERIFY_IS_TRUE(msiPath.has_value());
auto vhdPath = std::filesystem::path(msiPath.value()) / "system.vhd";
#endif
auto blockDeviceExists = [&](ULONG Lun) {
std::string device = std::format("/sys/bus/scsi/devices/0:0:0:{}", Lun);
std::vector<const char*> cmd{"/usr/bin/test", "-d", device.c_str()};
return RunCommand(vm.get(), cmd) == 0;
};
// Attach the disk.
DiskAttachSettings attachSettings{vhdPath.c_str(), true};
AttachedDiskInformation attachedDisk{};
VERIFY_SUCCEEDED(WslAttachDisk(vm.get(), &attachSettings, &attachedDisk));
VERIFY_IS_TRUE(blockDeviceExists(attachedDisk.ScsiLun));
// Mount it to /mnt.
MountSettings mountSettings{attachedDisk.Device, "/mnt", "ext4", "ro"};
VERIFY_SUCCEEDED(WslMount(vm.get(), &mountSettings));
// Validate that the mountpoint is present.
std::vector<const char*> cmd{"/usr/bin/mountpoint", "/mnt"};
VERIFY_ARE_EQUAL(RunCommand(vm.get(), cmd), 0L);
// Unmount /mnt.
VERIFY_SUCCEEDED(WslUnmount(vm.get(), "/mnt"));
VERIFY_ARE_EQUAL(RunCommand(vm.get(), cmd), 32L);
// Verify that unmount fails now.
VERIFY_ARE_EQUAL(WslUnmount(vm.get(), "/mnt"), E_FAIL);
// Detach the disk
VERIFY_SUCCEEDED(WslDetachDisk(vm.get(), attachedDisk.ScsiLun));
VERIFY_IS_FALSE(blockDeviceExists(attachedDisk.ScsiLun));
// Verify that disk can't be detached twice
VERIFY_ARE_EQUAL(WslDetachDisk(vm.get(), attachedDisk.ScsiLun), HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
}
TEST_METHOD(CustomDmesgOutput)
{
WSL2_TEST_ONLY();