WSLA: Add virtiofs support for mounted Windows paths (#13880)

* WSLA: Add virtiofs support for mounted Windows paths

* pr feedback and clarification on removing virtiofs shares

---------

Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
This commit is contained in:
Ben Hillis 2025-12-12 12:07:32 -08:00 committed by GitHub
parent c7e80fbd25
commit 9c01583fea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 174 additions and 67 deletions

View File

@ -345,6 +345,7 @@
</RegistryKey>
<!-- wsldevicehost.dll -->
<!-- TODO: WSL currently has two AppID registrations for wsldevicehost, is that needed? -->
<RegistryKey Root="HKCR" Key="AppID\{C457EA11-5486-4174-B90D-089909EDB170}">
<RegistryValue Name="DllSurrogate" Value="" Type="string" />
<RegistryValue Name="AppIDFlags" Value="2048" Type="integer" />
@ -354,9 +355,42 @@
<RegistryValue Name="LaunchPermission" Value="01000480580000006800000000000000140000000200440003000000000014000B00000001010000000000050B000000000014000B00000001010000000000050A000000000014000B0000000101000000000005120000000102000000000005200000002002000001020000000000052000000020020000" Type="binary" />
</RegistryKey>
<!-- WslDeviceHost_VirtioNet -->
<!-- WslDeviceHost WSLA_VIRTIO_FS_ADMIN_CLASS_ID -->
<RegistryKey Root="HKCR" Key="CLSID\{8F7C2A3B-D9E4-4C1F-A2B8-5E3D7C9F1A6E}">
<RegistryValue Value="WslDeviceHost_VirtioFsAdmin" Type="string" />
<RegistryValue Name="AppId" Value="{C457EA11-5486-4174-B90D-089909EDB170}" Type="string" />
<RegistryKey Key="InProcServer32">
<RegistryValue Value="[INSTALLDIR]wsldevicehost.dll" Type="string" />
<RegistryValue Name="ThreadingModel" Value="Both" Type="string" />
</RegistryKey>
</RegistryKey>
<!-- WslDeviceHost WSLA_VIRTIO_FS_CLASS_ID -->
<RegistryKey Root="HKCR" Key="CLSID\{06ED032F-C528-41C1-B75D-905EEE823BBA}">
<RegistryValue Value="WslDeviceHost_VirtioFs" Type="string" />
<RegistryValue Name="AppId" Value="{C457EA11-5486-4174-B90D-089909EDB170}" Type="string" />
<RegistryKey Key="InProcServer32">
<RegistryValue Value="[INSTALLDIR]wsldevicehost.dll" Type="string" />
<RegistryValue Name="ThreadingModel" Value="Both" Type="string" />
</RegistryKey>
</RegistryKey>
<!-- WslDeviceHost WSLA_VIRTIO_NET_CLASS_ID -->
<RegistryKey Root="HKCR" Key="CLSID\{7B3C9A42-8E1F-4D5A-9F2E-C4A7B8D3E6F1}">
<RegistryValue Value="WslDeviceHost_Net" Type="string" />
<RegistryValue Value="WslDeviceHost_VirtioNet" Type="string" />
<RegistryValue Name="AppId" Value="{C457EA11-5486-4174-B90D-089909EDB170}" Type="string" />
<RegistryKey Key="InProcServer32">
<RegistryValue Value="[INSTALLDIR]wsldevicehost.dll" Type="string" />
<RegistryValue Name="ThreadingModel" Value="Both" Type="string" />
</RegistryKey>
</RegistryKey>
<!-- WslDeviceHost WSLA_VIRTIO_PMEM_CLASS_ID -->
<RegistryKey Root="HKCR" Key="CLSID\{ABB755FC-1B86-4255-83E2-E5787ABCF6C2}">
<RegistryValue Value="WslDeviceHost_VirtioPmem" Type="string" />
<RegistryValue Name="AppId" Value="{C457EA11-5486-4174-B90D-089909EDB170}" Type="string" />
<RegistryKey Key="InProcServer32">

View File

@ -26,4 +26,3 @@
<clear />
</disabledPackageSources>
</configuration>

View File

@ -18,7 +18,7 @@
<package id="Microsoft.WSL.bsdtar" version="0.0.2-2" />
<package id="Microsoft.WSL.Dependencies.amd64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
<package id="Microsoft.WSL.Dependencies.arm64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
<package id="Microsoft.WSL.DeviceHost" version="1.0.0-20251202.1" />
<package id="Microsoft.WSL.DeviceHost" version="1.1.6-0" />
<package id="Microsoft.WSL.Kernel" version="6.6.114.1-1" targetFramework="native" />
<package id="Microsoft.WSL.LinuxSdk" version="1.20.0" targetFramework="native" />
<package id="Microsoft.WSL.TestDistro" version="2.5.7-47" />

View File

@ -1741,7 +1741,7 @@ Return Value:
TimeoutSeconds.value(),
[&]() {
errno = wil::ResultFromCaughtException();
return errno == ENOENT || errno == ENXIO || errno == EIO;
return errno == ENOENT || errno == ENXIO || errno == EIO || ((strcmp(Type, VIRTIO_FS_TYPE) == 0) && (errno == EINVAL));
});
}
else
@ -1752,7 +1752,7 @@ Return Value:
catch (...)
{
errno = wil::ResultFromCaughtException();
LOG_ERROR("mount({}, {}, {}, 0x{}x, {}) failed {}", Source, Target, Type, MountFlags, Options, errno);
LOG_ERROR("mount({}, {}, {}, {:#x}, {}) failed {}", Source, Target, Type, MountFlags, Options, errno);
return -errno;
}

View File

@ -1584,9 +1584,10 @@ struct WSLA_MOUNT
enum MountType : uint8_t
{
None,
Chroot = 1,
OverlayFs = 2,
KernelModules = 4
ReadOnly = 1,
Chroot = 2,
OverlayFs = 4,
KernelModules = 8
};
char Buffer[];

View File

@ -1552,6 +1552,8 @@ int WslaShell(_In_ std::wstring_view commandLine)
parser.AddArgument(Utf8String(shell), L"--shell");
parser.AddArgument(
SetFlag<int, WslaFeatureFlagsDnsTunneling>(reinterpret_cast<int&>(sessionSettings.FeatureFlags)), L"--dns-tunneling");
parser.AddArgument(
SetFlag<int, WslaFeatureFlagsVirtioFs>(reinterpret_cast<int&>(sessionSettings.FeatureFlags)), L"--virtiofs");
parser.AddArgument(Integer(sessionSettings.MemoryMb), L"--memory");
parser.AddArgument(Integer(sessionSettings.CpuCount), L"--cpu");
parser.AddArgument(Utf8String(rootVhdTypeOverride), L"--fstype");
@ -1565,7 +1567,7 @@ int WslaShell(_In_ std::wstring_view commandLine)
if (help)
{
const auto usage = std::format(
LR"({} --wsla [--vhd </path/to/vhd>] [--shell </path/to/shell>] [--memory <memory-mb>] [--cpu <cpus>] [--dns-tunneling] [--networking-mode <mode>] [--fstype <fstype>] [--container-vhd </path/to/vhd>] [--help])",
LR"({} --wsla [--vhd </path/to/vhd>] [--shell </path/to/shell>] [--memory <memory-mb>] [--cpu <cpus>] [--dns-tunneling] [--virtiofs] [--networking-mode <mode>] [--fstype <fstype>] [--container-vhd </path/to/vhd>] [--help])",
WSL_BINARY_NAME);
wprintf(L"%ls\n", usage.c_str());

View File

@ -34,7 +34,10 @@ constexpr auto SAVED_STATE_FILE_PREFIX = L"saved-state-";
constexpr auto RECEIVE_TIMEOUT = 30 * 1000;
// WSLA-specific virtio device class IDs.
DEFINE_GUID(WSLA_VIRTIO_FS_ADMIN_CLASS_ID, 0x8F7C2A3B, 0xD9E4, 0x4C1F, 0xA2, 0xB8, 0x5E, 0x3D, 0x7C, 0x9F, 0x1A, 0x6E); // {8F7C2A3B-D9E4-4C1F-A2B8-5E3D7C9F1A6E}
DEFINE_GUID(WSLA_VIRTIO_FS_CLASS_ID, 0x06ED032F, 0xC528, 0x41C1, 0xB7, 0x5D, 0x90, 0x5E, 0xEE, 0x82, 0x3B, 0xBA); // {06ED032F-C528-41C1-B75D-905EEE823BBA}
DEFINE_GUID(WSLA_VIRTIO_NET_CLASS_ID, 0x7B3C9A42, 0x8E1F, 0x4D5A, 0x9F, 0x2E, 0xC4, 0xA7, 0xB8, 0xD3, 0xE6, 0xF1); // {7B3C9A42-8E1F-4D5A-9F2E-C4A7B8D3E6F1}
DEFINE_GUID(WSLA_VIRTIO_PMEM_CLASS_ID, 0xABB755FC, 0x1B86, 0x4255, 0x83, 0xE2, 0xE5, 0x78, 0x7A, 0xBC, 0xF6, 0xC2); // {ABB755FC-1B86-4255-83E2-E5787ABCF6C2}
WSLAVirtualMachine::WSLAVirtualMachine(WSLAVirtualMachine::Settings&& Settings, PSID UserSid) :
m_settings(std::move(Settings)), m_userSid(UserSid)
@ -306,8 +309,7 @@ void WSLAVirtualMachine::Start()
WI_ASSERT(IsEqualGUID(m_vmId, runtimeId));
// Initialize DeviceHostProxy for virtio device support.
// N.B. This is currently only needed for VirtioProxy networking mode but would also be needed for virtiofs.
if (m_settings.NetworkingMode == WSLANetworkingModeVirtioProxy)
if (FeatureEnabled(WslaFeatureFlagsVirtioFs) || m_settings.NetworkingMode == WSLANetworkingModeVirtioProxy)
{
m_guestDeviceManager = std::make_shared<GuestDeviceManager>(m_vmIdString, m_vmId);
}
@ -387,7 +389,7 @@ void WSLAVirtualMachine::ConfigureMounts()
if (FeatureEnabled(WslaFeatureFlagsGPU)) // TODO: re-think how GPU settings should work at the session level API.
{
MountGpuLibraries("/usr/lib/wsl/lib", "/usr/lib/wsl/drivers", WSLAMountFlagsNone);
MountGpuLibraries("/usr/lib/wsl/lib", "/usr/lib/wsl/drivers");
}
}
@ -931,6 +933,7 @@ void WSLAVirtualMachine::Mount(LPCSTR Source, LPCSTR Target, LPCSTR Type, LPCSTR
void WSLAVirtualMachine::Mount(shared::SocketChannel& Channel, LPCSTR Source, LPCSTR Target, LPCSTR Type, LPCSTR Options, ULONG Flags)
{
static_assert(WSLAMountFlagsNone == WSLA_MOUNT::None);
static_assert(WSLAMountFlagsReadOnly == WSLA_MOUNT::ReadOnly);
static_assert(WSLAMountFlagsChroot == WSLA_MOUNT::Chroot);
static_assert(WSLAMountFlagsWriteableOverlayFs == WSLA_MOUNT::OverlayFs);
@ -1151,64 +1154,101 @@ CATCH_RETURN();
HRESULT WSLAVirtualMachine::MountWindowsFolder(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ BOOL ReadOnly)
{
return MountWindowsFolderImpl(WindowsPath, LinuxPath, ReadOnly, WSLAMountFlagsNone);
return MountWindowsFolderImpl(WindowsPath, LinuxPath, ReadOnly ? WSLAMountFlagsReadOnly : WSLAMountFlagsNone);
}
HRESULT WSLAVirtualMachine::MountWindowsFolderImpl(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ BOOL ReadOnly, _In_ WSLAMountFlags Flags)
HRESULT WSLAVirtualMachine::MountWindowsFolderImpl(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ WSLAMountFlags Flags)
try
{
std::filesystem::path Path(WindowsPath);
THROW_HR_IF_MSG(E_INVALIDARG, !Path.is_absolute(), "Path is not absolute: '%ls'", WindowsPath);
std::filesystem::path path(WindowsPath);
THROW_HR_IF_MSG(E_INVALIDARG, !path.is_absolute(), "Path is not absolute: '%ls'", WindowsPath);
THROW_HR_IF_MSG(
HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), !std::filesystem::is_directory(Path), "Path is not a directory: '%ls'", WindowsPath);
HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), !std::filesystem::is_directory(path), "Path is not a directory: '%ls'", WindowsPath);
GUID shareGuid{};
THROW_IF_FAILED(CoCreateGuid(&shareGuid));
auto shareName = shared::string::GuidToString<wchar_t>(shareGuid, shared::string::None);
const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
{
// Create the plan9 share on the host
std::lock_guard lock(m_lock);
// Verify that this folder isn't already mounted.
auto it = m_plan9Mounts.find(LinuxPath);
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), it != m_plan9Mounts.end());
auto it = m_mountedWindowsFolders.find(LinuxPath);
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), it != m_mountedWindowsFolders.end());
hcs::AddPlan9Share(
m_computeSystem.get(),
shareName.c_str(),
shareName.c_str(),
WindowsPath,
LX_INIT_UTILITY_VM_PLAN9_PORT,
hcs::Plan9ShareFlags::AllowOptions | (ReadOnly ? hcs::Plan9ShareFlags::ReadOnly : hcs::Plan9ShareFlags::None),
wsl::windows::common::security::GetUserToken(TokenImpersonation).get());
if (!FeatureEnabled(WslaFeatureFlagsVirtioFs))
{
auto flags = hcs::Plan9ShareFlags::AllowOptions;
WI_SetFlagIf(flags, hcs::Plan9ShareFlags::ReadOnly, WI_IsFlagSet(Flags, WSLAMountFlagsReadOnly));
hcs::AddPlan9Share(
m_computeSystem.get(),
shareName.c_str(),
shareName.c_str(),
WindowsPath,
LX_INIT_UTILITY_VM_PLAN9_PORT,
flags,
userToken.get());
}
else
{
const bool admin = wsl::windows::common::security::IsTokenElevated(userToken.get());
m_guestDeviceManager->AddGuestDevice(
VIRTIO_FS_DEVICE_ID,
admin ? WSLA_VIRTIO_FS_ADMIN_CLASS_ID : WSLA_VIRTIO_FS_CLASS_ID,
shareName.c_str(),
L"",
WindowsPath,
VIRTIO_FS_FLAGS_TYPE_FILES,
userToken.get());
}
m_plan9Mounts.emplace(LinuxPath, shareName);
m_mountedWindowsFolders.emplace(LinuxPath, shareName);
}
auto deleteOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
std::lock_guard lock(m_lock);
LOG_HR_IF(E_UNEXPECTED, m_mountedWindowsFolders.erase(LinuxPath) != 1);
LOG_HR_IF(E_UNEXPECTED, m_plan9Mounts.erase(LinuxPath) != 1);
if (!FeatureEnabled(WslaFeatureFlagsVirtioFs))
{
hcs::RemovePlan9Share(m_computeSystem.get(), shareName.c_str(), LX_INIT_UTILITY_VM_PLAN9_PORT);
}
});
// Create the guest mount
auto [_, __, channel] = Fork(WSLA_FORK::Thread);
WSLA_CONNECT message;
message.HostPort = LX_INIT_UTILITY_VM_PLAN9_PORT;
auto fd = channel.Transaction(message).Result;
THROW_HR_IF_MSG(E_FAIL, fd < 0, "WSLA_CONNECT failed with %i", fd);
auto shareNameUtf8 = shared::string::WideToMultiByte(shareName);
auto mountOptions =
std::format("msize={},trans=fd,rfdno={},wfdno={},aname={},cache=mmap", LX_INIT_UTILITY_VM_PLAN9_BUFFER_SIZE, fd, fd, shareNameUtf8);
if (!FeatureEnabled(WslaFeatureFlagsVirtioFs))
{
auto [_, __, channel] = Fork(WSLA_FORK::Thread);
Mount(channel, shareNameUtf8.c_str(), LinuxPath, "9p", mountOptions.c_str(), Flags);
WSLA_CONNECT message;
message.HostPort = LX_INIT_UTILITY_VM_PLAN9_PORT;
auto fd = channel.Transaction(message).Result;
THROW_HR_IF_MSG(E_FAIL, fd < 0, "WSLA_CONNECT failed with %i", fd);
auto mountOptions = std::format(
"{},msize={},trans=fd,rfdno={},wfdno={},aname={},cache=mmap",
WI_IsFlagSet(Flags, WSLAMountFlagsReadOnly) ? "ro" : "rw",
LX_INIT_UTILITY_VM_PLAN9_BUFFER_SIZE,
fd,
fd,
shareNameUtf8);
Mount(channel, shareNameUtf8.c_str(), LinuxPath, "9p", mountOptions.c_str(), Flags);
}
else
{
std::string options = WI_IsFlagSet(Flags, WSLAMountFlagsReadOnly) ? "ro" : "rw";
Mount(m_initChannel, shareNameUtf8.c_str(), LinuxPath, "virtiofs", options.c_str(), Flags);
}
deleteOnFailure.release();
return S_OK;
}
CATCH_RETURN();
@ -1219,23 +1259,27 @@ try
std::lock_guard lock(m_lock);
// Verify that this folder is mounted.
auto it = m_plan9Mounts.find(LinuxPath);
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), it == m_plan9Mounts.end());
auto it = m_mountedWindowsFolders.find(LinuxPath);
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), it == m_mountedWindowsFolders.end());
// Unmount the folder from the guest. If the mount is not found, this most likely means that the guest unmounted it.
auto result = Unmount(LinuxPath);
THROW_HR_IF(result, FAILED(result) && result != HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
// Remove the share from the host
hcs::RemovePlan9Share(m_computeSystem.get(), it->second.c_str(), LX_INIT_UTILITY_VM_PLAN9_PORT);
// Remove the share from the host.
// N.B. VirtioFs shares are present until the VM is destroyed, since HCS currently lacks support for removing them dynamically.
if (!FeatureEnabled(WslaFeatureFlagsVirtioFs))
{
hcs::RemovePlan9Share(m_computeSystem.get(), it->second.c_str(), LX_INIT_UTILITY_VM_PLAN9_PORT);
}
m_plan9Mounts.erase(it);
m_mountedWindowsFolders.erase(it);
return S_OK;
}
CATCH_RETURN();
void WSLAVirtualMachine::MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint, _In_ DWORD Flags)
void WSLAVirtualMachine::MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint)
{
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_CONFIG_VALUE), !FeatureEnabled(WslaFeatureFlagsGPU));
@ -1245,7 +1289,7 @@ void WSLAVirtualMachine::MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_
// Mount drivers.
THROW_IF_FAILED(MountWindowsFolderImpl(
std::format(L"{}\\System32\\DriverStore\\FileRepository", windowsPath).c_str(), DriversMountpoint, true, static_cast<WSLAMountFlags>(Flags)));
std::format(L"{}\\System32\\DriverStore\\FileRepository", windowsPath).c_str(), DriversMountpoint, WSLAMountFlagsReadOnly));
// Mount the inbox libraries.
auto inboxLibPath = std::format(L"{}\\System32\\lxss\\lib", windowsPath);
@ -1253,7 +1297,7 @@ void WSLAVirtualMachine::MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_
if (std::filesystem::is_directory(inboxLibPath))
{
inboxLibMountPoint = std::format("{}/inbox", LibrariesMountPoint);
THROW_IF_FAILED(MountWindowsFolder(inboxLibPath.c_str(), inboxLibMountPoint->c_str(), true));
THROW_IF_FAILED(MountWindowsFolderImpl(inboxLibPath.c_str(), inboxLibMountPoint->c_str(), WSLAMountFlagsReadOnly));
}
// Mount the packaged libraries.
@ -1269,7 +1313,7 @@ void WSLAVirtualMachine::MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_
#endif
auto packagedLibMountPoint = std::format("{}/packaged", LibrariesMountPoint);
THROW_IF_FAILED(MountWindowsFolder(packagedLibPath.c_str(), packagedLibMountPoint.c_str(), true));
THROW_IF_FAILED(MountWindowsFolderImpl(packagedLibPath.c_str(), packagedLibMountPoint.c_str(), WSLAMountFlagsReadOnly));
// Mount an overlay containing both inbox and packaged libraries (the packaged mount takes precedence).
std::string options = "lowerdir=" + packagedLibMountPoint;
@ -1278,7 +1322,7 @@ void WSLAVirtualMachine::MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_
options += ":" + inboxLibMountPoint.value();
}
Mount(m_initChannel, "none", LibrariesMountPoint, "overlay", options.c_str(), Flags);
Mount(m_initChannel, "none", LibrariesMountPoint, "overlay", options.c_str(), 0);
}
std::filesystem::path WSLAVirtualMachine::GetCrashDumpFolder()

View File

@ -27,8 +27,9 @@ namespace wsl::windows::service::wsla {
enum WSLAMountFlags
{
WSLAMountFlagsNone = 0,
WSLAMountFlagsChroot = 1,
WSLAMountFlagsWriteableOverlayFs = 2,
WSLAMountFlagsReadOnly = 1,
WSLAMountFlagsChroot = 2,
WSLAMountFlagsWriteableOverlayFs = 4,
};
class WSLAUserSessionImpl;
@ -74,7 +75,6 @@ public:
IFACEMETHOD(Unmount(_In_ const char* Path)) override;
IFACEMETHOD(MountWindowsFolder(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ BOOL ReadOnly)) override;
IFACEMETHOD(UnmountWindowsFolder(_In_ LPCSTR LinuxPath)) override;
void MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint, _In_ DWORD Flags);
void OnProcessReleased(int Pid);
void RegisterCallback(_In_ ITerminationCallback* callback);
@ -88,6 +88,7 @@ public:
private:
static void Mount(wsl::shared::SocketChannel& Channel, LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags);
void MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint);
static void CALLBACK s_OnExit(_In_ HCS_EVENT* Event, _In_opt_ void* Context);
static bool ParseTtyInformation(
const WSLA_PROCESS_FD* Fds, ULONG FdCount, const WSLA_PROCESS_FD** TtyInput, const WSLA_PROCESS_FD** TtyOutput, const WSLA_PROCESS_FD** TtyControl);
@ -116,7 +117,7 @@ private:
Microsoft::WRL::ComPtr<WSLAProcess> CreateLinuxProcessImpl(
_In_ const WSLA_PROCESS_OPTIONS& Options, int* Errno = nullptr, const TPrepareCommandLine& PrepareCommandLine = [](const auto&) {});
HRESULT MountWindowsFolderImpl(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ BOOL ReadOnly, _In_ WSLAMountFlags Flags);
HRESULT MountWindowsFolderImpl(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ WSLAMountFlags Flags = WSLAMountFlagsNone);
void WatchForExitedProcesses(wsl::shared::SocketChannel& Channel);
@ -162,7 +163,7 @@ private:
wil::unique_handle m_portRelayChannelWrite;
std::map<ULONG, AttachedDisk> m_attachedDisks;
std::map<std::string, std::wstring> m_plan9Mounts;
std::map<std::string, std::wstring> m_mountedWindowsFolders;
std::recursive_mutex m_lock;
std::mutex m_portRelaylock;
};

View File

@ -227,10 +227,11 @@ typedef enum _WSLANetworkingMode
typedef enum _WSLAFeatureFlags
{
WslaFeatureFlagsNone = 0,
WslaFeatureFlagsDnsTunneling = 1,
WslaFeatureFlagsEarlyBootDmesg = 2,
WslaFeatureFlagsGPU = 4,
WslaFeatureFlagsNone = 0,
WslaFeatureFlagsDnsTunneling = 1,
WslaFeatureFlagsEarlyBootDmesg = 2,
WslaFeatureFlagsGPU = 4,
WslaFeatureFlagsVirtioFs = 8,
} WSLAFeatureFlags;
struct WSLA_SESSION_SETTINGS {
@ -246,7 +247,7 @@ struct WSLA_SESSION_SETTINGS {
ULONG DmesgOutput;
// Below options are used for debugging purposes only.
[unique] LPCWSTR RootVhdOverride;
[unique] LPCWSTR RootVhdOverride;
[unique] LPCSTR RootVhdTypeOverride;
};

View File

@ -711,16 +711,29 @@ class WSLATests
StopWslaService();
}
TEST_METHOD(WindowsMounts)
void ValidateWindowsMounts(bool enableVirtioFs)
{
WSL2_TEST_ONLY();
auto settings = GetDefaultSessionSettings();
WI_SetFlagIf(settings.FeatureFlags, WslaFeatureFlagsVirtioFs, enableVirtioFs);
auto session = CreateSession();
auto session = CreateSession(settings);
wil::com_ptr<IWSLAVirtualMachine> vm;
VERIFY_SUCCEEDED(session->GetVirtualMachine(&vm));
wsl::windows::common::security::ConfigureForCOMImpersonation(vm.get());
auto expectedMountOptions = [&](bool readOnly) -> std::string {
if (enableVirtioFs)
{
return std::format("/win-path*virtiofs*{},relatime*", readOnly ? "ro" : "rw");
}
else
{
return std::format(
"/win-path*9p*{},relatime,aname=*,cache=5,access=client,msize=65536,trans=fd,rfd=*,wfd=*", readOnly ? "ro" : "rw");
}
};
auto expectMount = [&](const std::string& target, const std::optional<std::string>& options) {
auto cmd = std::format("set -o pipefail ; findmnt '{}' | tail -n 1", target);
@ -749,7 +762,7 @@ class WSLATests
// Validate writeable mount.
{
VERIFY_SUCCEEDED(vm->MountWindowsFolder(testFolder.c_str(), "/win-path", false));
expectMount("/win-path", "/win-path*9p*rw,relatime,aname=*,cache=5,access=client,msize=65536,trans=fd,rfd=*,wfd=*");
expectMount("/win-path", expectedMountOptions(false));
// Validate that mount can't be stacked on each other
VERIFY_ARE_EQUAL(vm->MountWindowsFolder(testFolder.c_str(), "/win-path", false), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
@ -765,7 +778,7 @@ class WSLATests
// Validate read-only mount.
{
VERIFY_SUCCEEDED(vm->MountWindowsFolder(testFolder.c_str(), "/win-path", true));
expectMount("/win-path", "/win-path*9p*rw,relatime,aname=*,cache=5,access=client,msize=65536,trans=fd,rfd=*,wfd=*");
expectMount("/win-path", expectedMountOptions(true));
// Validate that folder is not writeable from linux
ExpectCommandResult(session.get(), {"/bin/sh", "-c", "echo -n content > /win-path/file.txt"}, 1);
@ -783,13 +796,25 @@ class WSLATests
// Validate that folders that are manually unmounted from the guest are handled properly
VERIFY_SUCCEEDED(vm->MountWindowsFolder(testFolder.c_str(), "/win-path", true));
expectMount("/win-path", "/win-path*9p*rw,relatime,aname=*,cache=5,access=client,msize=65536,trans=fd,rfd=*,wfd=*");
expectMount("/win-path", expectedMountOptions(true));
ExpectCommandResult(session.get(), {"/usr/bin/umount", "/win-path"}, 0);
VERIFY_SUCCEEDED(vm->UnmountWindowsFolder("/win-path"));
}
}
TEST_METHOD(WindowsMounts)
{
WSL2_TEST_ONLY();
ValidateWindowsMounts(false);
}
TEST_METHOD(WindowsMountsVirtioFs)
{
WSL2_TEST_ONLY();
ValidateWindowsMounts(true);
}
// This test case validates that no file descriptors are leaked to user processes.
TEST_METHOD(Fd)
{