virtiofs: Use aggregate share feature in WSLC (#41151)
* initial work * bump nuget * Address old and new feedback * Use dedicated message for wslc virtiofs mount child * formatting * Last format change --------- Co-authored-by: Daman Mulye <damanmulye@microsoft.com>
Daman Mulye committed
Jul 24, 2026 at 18:28 UTC
0f8ad5b3a92450fb9ae88a4ace419772752438a7
16 files changed
+247
-146
packages.config
+1
-1
@@ -19,7 +19,7 @@
19
<package id="Microsoft.WSL.bsdtar" version="0.0.2-2" />
20
<package id="Microsoft.WSL.Dependencies.amd64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
21
<package id="Microsoft.WSL.Dependencies.arm64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
22
- <package id="Microsoft.WSL.DeviceHost" version="1.2.53-0" />
22
+ <package id="Microsoft.WSL.DeviceHost" version="1.2.54-0" />
23
<package id="Microsoft.WSL.Kernel" version="6.18.35.2-1" targetFramework="native" />
24
<package id="Microsoft.WSL.LinuxSdk" version="1.23.0" targetFramework="native" />
25
<package id="Microsoft.WSL.TestData" version="0.5.0" />
src/linux/init/WSLCInit.cpp
+30
-6
@@ -13,6 +13,7 @@ Abstract:
13
--*/
14
15
#include "util.h"
16
+#include "drvfs.h"
17
#include "SocketChannel.h"
18
#include "message.h"
19
#include "localhost.h"
@@ -668,8 +669,9 @@ void HandleMessageImpl(
669
Transaction.Send(Response);
670
}
671
671
-void HandleMessageImpl(
672
- wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_MOUNT& Message, const gsl::span<gsl::byte>& Buffer)
672
+template <typename TMessage>
673
+void HandleMountMessage(
674
+ wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const TMessage& Message, const gsl::span<gsl::byte>& Buffer)
675
{
676
WSLC_MOUNT_RESULT response{};
677
response.Header.MessageType = WSLC_MOUNT_RESULT::Type;
@@ -686,10 +688,11 @@ void HandleMessageImpl(
688
return "";
689
};
690
689
- mountutil::ParsedOptions options;
691
+ const char* mountOptions = readField(Message.OptionsIndex);
692
+ mountutil::ParsedOptions options{};
693
if (Message.OptionsIndex > 0)
694
{
692
- options = mountutil::MountParseFlags(wsl::shared::string::FromSpan(Buffer, Message.OptionsIndex));
695
+ options = mountutil::MountParseFlags(mountOptions);
696
}
697
698
const char* source = readField(Message.SourceIndex);
@@ -716,7 +719,16 @@ void HandleMessageImpl(
719
THROW_ERRNO_IF(EINVAL, WI_IsFlagSet(Message.Flags, WSLC_MOUNT::Chroot) && !WI_IsFlagSet(Message.Flags, WSLC_MOUNT::OverlayFs));
720
721
auto type = readField(Message.TypeIndex);
719
- THROW_LAST_ERROR_IF(UtilMount(source, target, type, options.MountFlags, options.StringOptions.c_str(), c_defaultRetryTimeout) < 0);
722
+ if constexpr (std::is_same_v<TMessage, WSLC_MOUNT_VIRTIOFS>)
723
+ {
724
+ const char* childName = readField(Message.ChildNameIndex);
725
+ THROW_ERRNO_IF(EINVAL, !wsl::shared::string::IsEqual(type, VIRTIO_FS_TYPE));
726
+ THROW_LAST_ERROR_IF(MountVirtioFsChild(source, childName, target, mountOptions) < 0);
727
+ }
728
+ else
729
+ {
730
+ THROW_LAST_ERROR_IF(UtilMount(source, target, type, options.MountFlags, options.StringOptions.c_str(), c_defaultRetryTimeout) < 0);
731
+ }
732
733
// Workaround for a Linux bug where virtiofs permissions aren't properly propagated when an overlay is mounted on top of a virtiofs share before the permissions have been fetched.
734
// TODO: Remove once fixed upstream.
@@ -818,6 +830,18 @@ void HandleMessageImpl(
830
Transaction.Send<WSLC_MOUNT_RESULT>(response);
831
}
832
833
+void HandleMessageImpl(
834
+ wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_MOUNT& Message, const gsl::span<gsl::byte>& Buffer)
835
+{
836
+ HandleMountMessage(Channel, Transaction, Message, Buffer);
837
+}
838
+
839
+void HandleMessageImpl(
840
+ wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_MOUNT_VIRTIOFS& Message, const gsl::span<gsl::byte>& Buffer)
841
+{
842
+ HandleMountMessage(Channel, Transaction, Message, Buffer);
843
+}
844
+
845
void HandleMessageImpl(
846
wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_EXEC& Message, const gsl::span<gsl::byte>& Buffer)
847
{
@@ -1021,7 +1045,7 @@ void ProcessMessage(wsl::shared::SocketChannel& Channel, wsl::shared::Transactio
1045
{
1046
try
1047
{
1024
- HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT, WSLC_GET_GUEST_CAPABILITIES, WSLC_LISTDIR>(
1048
+ HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_MOUNT_VIRTIOFS, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT, WSLC_GET_GUEST_CAPABILITIES, WSLC_LISTDIR>(
1049
Channel, Transaction, Type, Buffer);
1050
}
1051
catch (...)
src/linux/init/drvfs.cpp
+1
-11
@@ -88,13 +88,7 @@ bool IsMountPoint(const std::string& Path)
88
}
89
} // namespace
90
91
-int MountVirtioFsChild(
92
- const char* Tag,
93
- const char* ChildName,
94
- const char* Target,
95
- const char* Options,
96
- int* ExitCode = nullptr,
97
- std::string_view SubPath = "/")
91
+int MountVirtioFsChild(const char* Tag, const char* ChildName, const char* Target, const char* Options, int* ExitCode, std::string_view SubPath)
92
{
93
if ((strcmp(Tag, LX_INIT_DRVFS_VIRTIO_TAG) != 0 && strcmp(Tag, LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) != 0) ||
94
!wsl::shared::string::ToGuid(ChildName) || SubPath.empty() || SubPath.front() != '/')
@@ -902,10 +896,6 @@ try
896
return {};
897
}
898
905
- //
906
- // Validate the tag is a GUID.
907
- //
908
-
899
std::string mappingName{Tag};
900
if (Root != nullptr)
901
{
src/linux/init/drvfs.h
+8
@@ -30,6 +30,14 @@ struct VirtioFsMountRoot
30
31
std::optional<VirtioFsMountRoot> ParseAggregateVirtioFsMountRoot(std::string_view Tag, std::string_view Root);
32
33
+int MountVirtioFsChild(
34
+ const char* Tag,
35
+ const char* ChildName,
36
+ const char* Target,
37
+ const char* Options,
38
+ int* ExitCode = nullptr,
39
+ std::string_view SubPath = "/");
40
+
41
int MountDrvfs(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
42
43
int MountDrvfsEntry(int Argc, char* Argv[]);
src/linux/init/util.cpp
+6
-6
@@ -878,16 +878,16 @@ try
878
while (MountEnum.Next())
879
{
880
//
881
- // Skip internal virtiofs device mounts. The aggregate virtiofs root and
882
- // its per-share child binds live under VIRTIOFS_MOUNT_DIR and carry the
883
- // same Windows source as the user-facing /mnt/<drive> bind mounts. If
884
- // they were considered, reverse (Windows->Linux) translation could
885
- // return an internal plumbing path (for example
881
+ // When translating Windows paths to Linux, skip internal virtiofs
882
+ // device mounts. The aggregate virtiofs root and its per-share child
883
+ // binds live under VIRTIOFS_MOUNT_DIR and carry the same Windows source
884
+ // as the user-facing /mnt/<drive> bind mounts. If they were considered,
885
+ // translation could return an internal plumbing path (for example
886
// /run/wsl/virtiofs-mounts/drvfsa/<guid>) instead of the real mount
887
// point such as /mnt/c.
888
//
889
890
- if (UtilIsPathPrefix(MountEnum.Current().MountPoint, VIRTIOFS_MOUNT_DIR, false) > 0)
890
+ if (WinPath && UtilIsPathPrefix(MountEnum.Current().MountPoint, VIRTIOFS_MOUNT_DIR, false) > 0)
891
{
892
continue;
893
}
src/shared/inc/defs.h
-5
@@ -52,11 +52,6 @@ inline constexpr std::uint32_t VersionMinor = WSL_PACKAGE_VERSION_MINOR;
52
inline constexpr std::uint32_t VersionRevision = WSL_PACKAGE_VERSION_REVISION;
53
inline constexpr std::tuple<uint32_t, uint32_t, uint32_t> PackageVersion{VersionMajor, VersionMinor, VersionRevision};
54
55
-// Maximum number of virtiofs shares that can be mounted (with different paths) over the lifetime of a VM.
56
-// This limit is there to avoid a hang when too many virtiofs shares are mounted.
57
-// TODO: Remove once we can use the same PCI devices for all shares.
58
-inline constexpr size_t c_maxVirtioFsShares = 15;
59
-
55
#ifdef WSL_OFFICIAL_BUILD
56
57
inline constexpr bool OfficialBuild = true;
src/shared/inc/lxinitshared.h
+21
@@ -413,6 +413,7 @@ typedef enum _LX_MESSAGE_TYPE
413
LxMessageWSLCGetGuestCapabilitiesResult,
414
LxMessageWSLCListDir,
415
LxMessageWSLCListDirResult,
416
+ LxMessageWSLCMountVirtioFs,
417
} LX_MESSAGE_TYPE,
418
*PLX_MESSAGE_TYPE;
419
@@ -527,6 +528,7 @@ inline auto ToString(LX_MESSAGE_TYPE messageType)
528
X(LxMessageWSLCGetGuestCapabilitiesResult)
529
X(LxMessageWSLCListDir)
530
X(LxMessageWSLCListDirResult)
531
+ X(LxMessageWSLCMountVirtioFs)
532
533
default:
534
return "<unexpected LX_MESSAGE_TYPE>";
@@ -1657,6 +1659,25 @@ struct WSLC_MOUNT
1659
PRETTY_PRINT(FIELD(Header), STRING_FIELD(SourceIndex), STRING_FIELD(DestinationIndex), STRING_FIELD(TypeIndex), STRING_FIELD(OptionsIndex));
1660
};
1661
1662
+struct WSLC_MOUNT_VIRTIOFS
1663
+{
1664
+ static inline auto Type = LxMessageWSLCMountVirtioFs;
1665
+ using TResponse = WSLC_MOUNT_RESULT;
1666
+
1667
+ DECLARE_MESSAGE_CTOR(WSLC_MOUNT_VIRTIOFS);
1668
+
1669
+ MESSAGE_HEADER Header{};
1670
+ unsigned int SourceIndex{};
1671
+ unsigned int DestinationIndex{};
1672
+ unsigned int TypeIndex{};
1673
+ unsigned int OptionsIndex{};
1674
+ unsigned int Flags{};
1675
+ unsigned int ChildNameIndex{};
1676
+ char Buffer[];
1677
+
1678
+ PRETTY_PRINT(FIELD(Header), STRING_FIELD(SourceIndex), STRING_FIELD(DestinationIndex), STRING_FIELD(TypeIndex), STRING_FIELD(OptionsIndex), STRING_FIELD(ChildNameIndex));
1679
+};
1680
+
1681
struct WSLC_EXEC
1682
{
1683
static inline auto Type = LxMessageWSLCExec;
src/windows/common/DeviceHostProxy.cpp
+8
@@ -180,6 +180,14 @@ void DeviceHostProxy::AddVirtiofsChild(const GUID& InstanceId, const std::wstrin
180
THROW_IF_FAILED(GetVirtiofsDevice(InstanceId)->AddChild(name.get(), rootPath.get(), mountOptions.get()));
181
}
182
183
+void DeviceHostProxy::RemoveVirtiofsChild(const GUID& InstanceId, const std::wstring& Name)
184
+{
185
+ std::lock_guard lifecycleLock(m_deviceLifecycleLock);
186
+
187
+ const auto name = wil::make_bstr(Name.c_str());
188
+ THROW_IF_FAILED(GetVirtiofsDevice(InstanceId)->RemoveChild(name.get()));
189
+}
190
+
191
GUID DeviceHostProxy::AddVirtioPmemDevice(_In_ HANDLE UserToken, const std::wstring& Path, bool Writable)
192
{
193
std::lock_guard lifecycleLock(m_deviceLifecycleLock);
src/windows/common/DeviceHostProxy.h
+2
@@ -23,6 +23,8 @@ public:
23
24
void AddVirtiofsChild(const GUID& InstanceId, const std::wstring& Name, const std::wstring& RootPath, const std::wstring& MountOptions);
25
26
+ void RemoveVirtiofsChild(const GUID& InstanceId, const std::wstring& Name);
27
+
28
GUID AddVirtioPmemDevice(_In_ HANDLE UserToken, const std::wstring& Path, bool Writable);
29
30
void RemoveDevice(const GUID& InstanceId);
src/windows/common/GuestDeviceManager.cpp
+7
@@ -33,6 +33,13 @@ void GuestDeviceManager::AddVirtiofsChild(_In_ const GUID& InstanceId, _In_ PCWS
33
m_deviceHostSupport->AddVirtiofsChild(InstanceId, Name, RootPath, MountOptions ? MountOptions : L"");
34
}
35
36
+_Requires_lock_not_held_(m_lock)
37
+void GuestDeviceManager::RemoveVirtiofsChild(_In_ const GUID& InstanceId, _In_ PCWSTR Name)
38
+{
39
+ auto guestDeviceLock = m_lock.lock_exclusive();
40
+ m_deviceHostSupport->RemoveVirtiofsChild(InstanceId, Name);
41
+}
42
+
43
_Requires_lock_not_held_(m_lock)
44
GUID GuestDeviceManager::AddVirtioPmemDevice(_In_ PCWSTR Path, bool ReadOnly, _In_ HANDLE UserToken)
45
{
src/windows/common/GuestDeviceManager.h
+3
@@ -27,6 +27,9 @@ public:
27
_Requires_lock_not_held_(m_lock)
28
void AddVirtiofsChild(_In_ const GUID& InstanceId, _In_ PCWSTR Name, _In_opt_ PCWSTR MountOptions, _In_ PCWSTR RootPath);
29
30
+ _Requires_lock_not_held_(m_lock)
31
+ void RemoveVirtiofsChild(_In_ const GUID& InstanceId, _In_ PCWSTR Name);
32
+
33
_Requires_lock_not_held_(m_lock)
34
GUID AddVirtioPmemDevice(_In_ PCWSTR Path, bool ReadOnly, _In_ HANDLE UserToken);
35
src/windows/service/exe/HcsVirtualMachine.cpp
+11
-2
@@ -620,7 +620,15 @@ try
620
{
621
std::wstring options = ReadOnly ? L"ro" : L"";
622
623
- it->second = m_guestDeviceManager->AddVirtiofsDevice(shareName.c_str(), options.c_str(), WindowsPath, m_userToken.get());
623
+ if (!m_virtioFsDevice.has_value())
624
+ {
625
+ VirtioFsShareOptions aggregateOptions{.Kind = VirtiofsShareKind_Aggregate};
626
+ m_virtioFsDevice =
627
+ m_guestDeviceManager->AddVirtiofsDevice(TEXT(LX_INIT_DRVFS_VIRTIO_TAG), L"", L"", m_userToken.get(), aggregateOptions);
628
+ }
629
+
630
+ m_guestDeviceManager->AddVirtiofsChild(m_virtioFsDevice.value(), shareName.c_str(), options.c_str(), WindowsPath);
631
+ it->second = m_virtioFsDevice;
632
}
633
634
cleanup.release();
@@ -645,7 +653,8 @@ try
653
}
654
else
655
{
648
- m_guestDeviceManager->RemoveGuestDevice(it->second.value());
656
+ auto shareName = wsl::shared::string::GuidToString<wchar_t>(it->first, wsl::shared::string::None);
657
+ m_guestDeviceManager->RemoveVirtiofsChild(it->second.value(), shareName.c_str());
658
}
659
660
m_shares.erase(it);
src/windows/service/exe/HcsVirtualMachine.h
+2
-1
@@ -97,8 +97,9 @@ private:
97
std::map<ULONG, DiskInfo> m_attachedDisks;
98
std::bitset<MAX_VHD_COUNT> m_lunBitmap;
99
100
- // Shares: key is ShareId, value is nullopt for Plan9 or DeviceInstanceId for VirtioFS
100
+ // Shares: key is ShareId, value is nullopt for Plan9 or the aggregate DeviceInstanceId for VirtioFS.
101
std::map<GUID, std::optional<GUID>, wsl::windows::common::helpers::GuidLess> m_shares;
102
+ std::optional<GUID> m_virtioFsDevice;
103
104
std::filesystem::path m_vmSavedStateFile;
105
std::filesystem::path m_crashDumpFolder;
src/windows/wslcsession/WSLCVirtualMachine.cpp
+30
-43
@@ -886,6 +886,30 @@ void WSLCVirtualMachine::Mount(shared::SocketChannel& Channel, LPCSTR Source, LP
886
THROW_HR_IF(E_FAIL, response.Result != 0);
887
}
888
889
+void WSLCVirtualMachine::MountVirtioFsChild(shared::SocketChannel& Channel, LPCSTR Source, LPCSTR ChildName, LPCSTR Target, LPCSTR Options, ULONG Flags)
890
+{
891
+ wsl::shared::MessageWriter<WSLC_MOUNT_VIRTIOFS> message;
892
+ message.WriteString(message->SourceIndex, Source);
893
+ message.WriteString(message->ChildNameIndex, ChildName);
894
+ message.WriteString(message->DestinationIndex, Target);
895
+ message.WriteString(message->TypeIndex, "virtiofs");
896
+ message.WriteString(message->OptionsIndex, Options);
897
+ message->Flags = Flags;
898
+
899
+ const auto& response = Channel.Transaction<WSLC_MOUNT_VIRTIOFS>(message.Span());
900
+
901
+ WSL_LOG(
902
+ "WSLCMountVirtioFsChild",
903
+ TraceLoggingValue(Source, "Source"),
904
+ TraceLoggingValue(ChildName, "ChildName"),
905
+ TraceLoggingValue(Target, "Target"),
906
+ TraceLoggingValue(Options, "Options"),
907
+ TraceLoggingValue(Flags, "Flags"),
908
+ TraceLoggingValue(response.Result, "Result"));
909
+
910
+ THROW_HR_IF(E_FAIL, response.Result != 0);
911
+}
912
+
913
int32_t WSLCVirtualMachine::ExpectClosedChannelOrError(wsl::shared::SocketChannel& Channel)
914
{
915
auto [response, span] = Channel.ReceiveMessageOrClosed<RESULT_MESSAGE<int32_t>>();
@@ -1077,9 +1101,7 @@ try
1101
THROW_HR_IF_MSG(E_INVALIDARG, LinuxPath[0] != '/', "Mountpoint is not absolute: '%hs'", LinuxPath);
1102
1103
const bool readOnly = WI_IsFlagSet(Flags, WSLCMountFlagsReadOnly);
1080
- auto normalizedPath = std::filesystem::weakly_canonical(path).wstring();
1104
GUID shareGuid{};
1082
- bool reusingShare = false;
1105
1106
{
1107
std::lock_guard lock(m_lock);
@@ -1088,34 +1110,8 @@ try
1110
auto it = m_mountedWindowsFolders.find(LinuxPath);
1111
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), it != m_mountedWindowsFolders.end());
1112
1091
- // In VirtioFs mode, try to reuse an existing share for the same Windows path and access mode.
1092
- if (FeatureEnabled(WslcFeatureFlagsVirtioFs))
1093
- {
1094
- auto shareIt = m_virtioFsShares.find({normalizedPath, readOnly});
1095
- if (shareIt != m_virtioFsShares.end())
1096
- {
1097
- shareGuid = shareIt->second;
1098
- reusingShare = true;
1099
- }
1100
- else
1101
- {
1102
- THROW_HR_WITH_USER_ERROR_IF(
1103
- E_OUTOFMEMORY,
1104
- shared::Localization::MessageWslcTooManyVirtioFsShares(shared::c_maxVirtioFsShares),
1105
- m_virtioFsShares.size() >= shared::c_maxVirtioFsShares);
1106
- }
1107
- }
1108
-
1109
- if (!reusingShare)
1110
- {
1111
- // Delegate to IWSLCVirtualMachine for the privileged share creation
1112
- THROW_IF_FAILED(m_vm->AddShare(WindowsPath, readOnly, &shareGuid));
1113
-
1114
- if (FeatureEnabled(WslcFeatureFlagsVirtioFs))
1115
- {
1116
- m_virtioFsShares[{normalizedPath, readOnly}] = shareGuid;
1117
- }
1118
- }
1113
+ // Delegate to IWSLCVirtualMachine for the privileged share creation.
1114
+ THROW_IF_FAILED(m_vm->AddShare(WindowsPath, readOnly, &shareGuid));
1115
1116
m_mountedWindowsFolders.emplace(LinuxPath, shareGuid);
1117
}
@@ -1126,11 +1122,7 @@ try
1122
if (WI_VERIFY(mountIt != m_mountedWindowsFolders.end()))
1123
{
1124
m_mountedWindowsFolders.erase(mountIt);
1129
- if (!FeatureEnabled(WslcFeatureFlagsVirtioFs))
1130
- {
1131
- m_virtioFsShares.erase({normalizedPath, readOnly});
1132
- LOG_IF_FAILED(m_vm->RemoveShare(shareGuid));
1133
- }
1125
+ LOG_IF_FAILED(m_vm->RemoveShare(shareGuid));
1126
}
1127
});
1128
@@ -1154,7 +1146,7 @@ try
1146
else
1147
{
1148
std::string options = readOnly ? "ro" : "rw";
1157
- Mount(m_initChannel, shareName.c_str(), LinuxPath, "virtiofs", options.c_str(), Flags);
1149
+ MountVirtioFsChild(m_initChannel, LX_INIT_DRVFS_VIRTIO_TAG, shareName.c_str(), LinuxPath, options.c_str(), Flags);
1150
}
1151
1152
deleteOnFailure.release();
@@ -1178,13 +1170,8 @@ try
1170
1171
auto shareId = it->second;
1172
1181
- // Keep the share mounted in virtiofs mode to avoid accumulating devices, which can cause a hang when reached.
1182
- // TODO: Actually remove the device once this is supported by the device host.
1183
- if (!FeatureEnabled(WslcFeatureFlagsVirtioFs))
1184
- {
1185
- // Delegate to IWSLCVirtualMachine for the privileged share removal
1186
- THROW_IF_FAILED(m_vm->RemoveShare(shareId));
1187
- }
1173
+ // Delegate to IWSLCVirtualMachine for the privileged share removal.
1174
+ THROW_IF_FAILED(m_vm->RemoveShare(shareId));
1175
1176
m_mountedWindowsFolders.erase(it);
1177
src/windows/wslcsession/WSLCVirtualMachine.h
+2
-4
@@ -204,6 +204,8 @@ private:
204
void ReadGuestCapabilities();
205
206
static void Mount(wsl::shared::SocketChannel& Channel, LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags);
207
+ static void MountVirtioFsChild(
208
+ wsl::shared::SocketChannel& Channel, _In_ LPCSTR Source, _In_ LPCSTR ChildName, _In_ LPCSTR Target, _In_ LPCSTR Options, _In_ ULONG Flags);
209
void MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint);
210
211
Microsoft::WRL::ComPtr<WSLCProcess> CreateLinuxProcessImpl(
@@ -280,10 +282,6 @@ private:
282
std::map<ULONG, AttachedDisk> m_attachedDisks;
283
std::map<std::string, GUID> m_mountedWindowsFolders;
284
283
- // VirtioFs share cache: maps (normalized WindowsPath, readOnly) to share GUID.
284
- // Shares are kept alive after unmount for reuse on subsequent mounts of the same folder.
285
- std::map<std::pair<std::wstring, bool>, GUID> m_virtioFsShares;
286
-
285
std::recursive_mutex m_lock;
286
std::mutex m_portRelaylock;
287
};
test/windows/WSLCTests.cpp
+115
-67
@@ -3763,19 +3763,20 @@ class WSLCTests
3763
VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path", true));
3764
ExpectMount(session.get(), "/win-path", expectedMountOptions(true));
3765
3766
- // Capture the mount source and type, unmount, then remount without read-only.
3766
+ // Remount a bind of the share as read-write to ensure the device host still enforces read-only access.
3767
ExpectCommandResult(
3768
session.get(),
3769
{"/bin/sh",
3770
"-c",
3771
- "src=$(findmnt -n -o SOURCE /win-path) && "
3772
- "fstype=$(findmnt -n -o FSTYPE /win-path) && "
3773
- "umount /win-path && "
3774
- "mount -t $fstype $src /win-path"},
3771
+ "mkdir -p /win-path-rw && "
3772
+ "mount --bind /win-path /win-path-rw && "
3773
+ "mount -o remount,bind,rw /win-path-rw && "
3774
+ "findmnt -n -o VFS-OPTIONS /win-path-rw | grep -qE '(^|,)rw(,|$)'"},
3775
0);
3776
3777
- // Verify the folder is still not writeable.
3778
- ExpectCommandResult(session.get(), {"/bin/sh", "-c", "echo -n content > /win-path/file.txt"}, 1);
3777
+ // Verify the folder is still not writeable through the read-write bind.
3778
+ ExpectCommandResult(session.get(), {"/bin/sh", "-c", "echo -n content > /win-path-rw/file.txt"}, 1);
3779
+ ExpectCommandResult(session.get(), {"/bin/sh", "-c", "umount /win-path-rw && rmdir /win-path-rw"}, 0);
3780
3781
VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path"));
3782
ExpectMount(session.get(), "/win-path", {});
@@ -3809,107 +3810,154 @@ class WSLCTests
3810
ValidateWindowsMounts(true);
3811
}
3812
3812
- // Validates that VirtioFs shares are reused across mount/unmount cycles for the same Windows folder.
3813
- WSLC_TEST_METHOD(WindowsMountsVirtioFsShareReuse)
3813
+ // Validates that each mount owns an independent child on the shared aggregate device.
3814
+ WSLC_TEST_METHOD(WindowsMountsVirtioFsIndependentShares)
3815
{
3815
- auto settings = GetDefaultSessionSettings(L"virtiofs-share-reuse-test");
3816
+ auto settings = GetDefaultSessionSettings(L"virtiofs-independent-shares-test");
3817
WI_SetFlag(settings.FeatureFlags, WslcFeatureFlagsVirtioFs);
3818
3819
auto createNewSession = !WI_IsFlagSet(m_defaultSessionSettings.FeatureFlags, WslcFeatureFlagsVirtioFs);
3820
auto session = createNewSession ? CreateSession(settings) : m_defaultSession;
3821
3821
- auto testFolder = std::filesystem::current_path() / "test-folder-share-reuse";
3822
+ auto testFolder = std::filesystem::current_path() / "test-folder-independent-shares";
3823
std::filesystem::create_directories(testFolder);
3824
+ std::ofstream(testFolder / "marker.txt") << "content";
3825
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(testFolder); });
3826
3825
- auto getMountSource = [&](const char* mountPoint) -> std::string {
3826
- auto cmd = std::format("findmnt -n -o SOURCE {}", mountPoint);
3827
+ auto getMountField = [&](const char* mountPoint, const char* field) -> std::string {
3828
+ auto cmd = std::format("findmnt -n -o {} {}", field, mountPoint);
3829
auto result = ExpectCommandResult(session.get(), {"/bin/sh", "-c", cmd}, 0);
3830
return result.Output[1];
3831
};
3832
3831
- // Mount, capture the source (share GUID), unmount, remount, verify same GUID is reused.
3833
+ // Concurrent mounts of the same host path use distinct children on the same aggregate device.
3834
{
3833
- VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path", false));
3834
- auto firstSource = getMountSource("/win-path");
3835
- VERIFY_IS_FALSE(firstSource.empty());
3835
+ VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path-1", false));
3836
+ VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path-2", false));
3837
3837
- VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path"));
3838
- ExpectMount(session.get(), "/win-path", {});
3838
+ auto firstDevice = getMountField("/win-path-1", "MAJ:MIN");
3839
+ auto secondDevice = getMountField("/win-path-2", "MAJ:MIN");
3840
+ auto firstRoot = getMountField("/win-path-1", "FSROOT");
3841
+ auto secondRoot = getMountField("/win-path-2", "FSROOT");
3842
+ VERIFY_ARE_EQUAL(firstDevice, secondDevice);
3843
+ VERIFY_ARE_NOT_EQUAL(firstRoot, secondRoot);
3844
+ VERIFY_IS_TRUE(firstRoot.starts_with('/'));
3845
+ VERIFY_IS_TRUE(firstRoot.ends_with('\n'));
3846
+ VERIFY_IS_TRUE(secondRoot.starts_with('/'));
3847
+ VERIFY_IS_TRUE(secondRoot.ends_with('\n'));
3848
+ firstRoot.pop_back();
3849
+ secondRoot.pop_back();
3850
3840
- // Remount the same folder - should reuse the same share GUID.
3841
- VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path", false));
3842
- auto secondSource = getMountSource("/win-path");
3851
+ const auto firstChild = std::format("/run/wsl/virtiofs-mounts/{}{}", LX_INIT_DRVFS_VIRTIO_TAG, firstRoot);
3852
+ const auto secondChild = std::format("/run/wsl/virtiofs-mounts/{}{}", LX_INIT_DRVFS_VIRTIO_TAG, secondRoot);
3853
3844
- VERIFY_ARE_EQUAL(firstSource, secondSource);
3854
+ VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path-1"));
3855
+ ExpectCommandResult(session.get(), {"/bin/cat", "/win-path-2/marker.txt"}, 0);
3856
+ ExpectCommandResult(session.get(), {"/usr/bin/test", "!", "-e", firstChild}, 0);
3857
+ ExpectCommandResult(session.get(), {"/usr/bin/test", "-e", secondChild}, 0);
3858
3846
- VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path"));
3859
+ VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path-2"));
3860
+ ExpectCommandResult(session.get(), {"/usr/bin/test", "!", "-e", secondChild}, 0);
3861
}
3862
3849
- // Verify that changing the read-only flag produces a different share GUID.
3863
+ // Verify that read-write and read-only shares use different children on the same aggregate device.
3864
{
3851
- VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path", false));
3852
- auto rwSource = getMountSource("/win-path");
3865
+ VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path-rw", false));
3866
+ VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path-ro", true));
3867
3854
- VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path"));
3868
+ auto rwDevice = getMountField("/win-path-rw", "MAJ:MIN");
3869
+ auto roDevice = getMountField("/win-path-ro", "MAJ:MIN");
3870
+ auto rwRoot = getMountField("/win-path-rw", "FSROOT");
3871
+ auto roRoot = getMountField("/win-path-ro", "FSROOT");
3872
3856
- VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), "/win-path", true));
3857
- auto roSource = getMountSource("/win-path");
3873
+ VERIFY_ARE_EQUAL(rwDevice, roDevice);
3874
+ VERIFY_ARE_NOT_EQUAL(rwRoot, roRoot);
3875
3859
- VERIFY_ARE_NOT_EQUAL(rwSource, roSource);
3860
-
3861
- VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path"));
3876
+ VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path-rw"));
3877
+ VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/win-path-ro"));
3878
}
3879
}
3880
3865
- // Validate that the correct error is returned when too many virtiofs shares are mounted.
3866
- WSLC_TEST_METHOD(VirtiofsVolumesLimit)
3881
+ WSLC_TEST_METHOD(WindowsMountsVirtioFsRemoveChild)
3882
{
3868
- constexpr size_t c_maxVirtioFsShares = wsl::shared::c_maxVirtioFsShares;
3883
+ auto settings = GetDefaultSessionSettings(L"virtiofs-remove-child-test");
3884
+ WI_SetFlag(settings.FeatureFlags, WslcFeatureFlagsVirtioFs);
3885
+ auto session = CreateSession(settings);
3886
+
3887
+ const auto testRoot = std::filesystem::current_path() / "test-folder-remove-child";
3888
+ const auto firstFolder = testRoot / "first";
3889
+ const auto secondFolder = testRoot / "second";
3890
+ std::filesystem::create_directories(firstFolder);
3891
+ std::filesystem::create_directories(secondFolder);
3892
+ std::ofstream(firstFolder / "marker.txt") << "first";
3893
+ std::ofstream(secondFolder / "marker.txt") << "second";
3894
+ auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(testRoot); });
3895
+
3896
+ auto getMountRoot = [&](const char* mountPoint) {
3897
+ const auto command = std::format("findmnt -n -o FSROOT {}", mountPoint);
3898
+ auto root = ExpectCommandResult(session.get(), {"/bin/sh", "-c", command}, 0).Output.at(1);
3899
+ VERIFY_IS_TRUE(root.starts_with('/'));
3900
+ VERIFY_IS_TRUE(root.ends_with('\n'));
3901
+ root.pop_back();
3902
+ return root;
3903
+ };
3904
+
3905
+ VERIFY_SUCCEEDED(session->MountWindowsFolder(firstFolder.c_str(), "/remove-child-first", false));
3906
+ VERIFY_SUCCEEDED(session->MountWindowsFolder(secondFolder.c_str(), "/remove-child-second", false));
3907
3870
- auto settings = GetDefaultSessionSettings(L"virtiofs-share-limit-test");
3908
+ const auto firstRoot = getMountRoot("/remove-child-first");
3909
+ const auto secondRoot = getMountRoot("/remove-child-second");
3910
+ VERIFY_ARE_NOT_EQUAL(firstRoot, secondRoot);
3911
+
3912
+ const auto aggregateRoot = std::format("/run/wsl/virtiofs-mounts/{}", LX_INIT_DRVFS_VIRTIO_TAG);
3913
+ const auto firstChild = aggregateRoot + firstRoot;
3914
+ const auto secondChild = aggregateRoot + secondRoot;
3915
+ ExpectCommandResult(session.get(), {"/usr/bin/test", "-e", firstChild}, 0);
3916
+ ExpectCommandResult(session.get(), {"/usr/bin/test", "-e", secondChild}, 0);
3917
+
3918
+ VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/remove-child-first"));
3919
+ ExpectCommandResult(session.get(), {"/usr/bin/test", "!", "-e", firstChild}, 0);
3920
+ ExpectCommandResult(session.get(), {"/bin/cat", "/remove-child-second/marker.txt"}, 0);
3921
+ ExpectCommandResult(session.get(), {"/usr/bin/test", "-e", secondChild}, 0);
3922
+
3923
+ VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/remove-child-second"));
3924
+ ExpectCommandResult(session.get(), {"/usr/bin/test", "!", "-e", secondChild}, 0);
3925
+ }
3926
+
3927
+ // Validate that enough VirtioFs shares can be mounted to exceed the old per-device aperture limit.
3928
+ WSLC_TEST_METHOD(VirtiofsMountManyVolumes)
3929
+ {
3930
+ constexpr size_t c_shareCount = 32;
3931
+
3932
+ auto settings = GetDefaultSessionSettings(L"virtiofs-many-shares-test");
3933
WI_SetFlag(settings.FeatureFlags, WslcFeatureFlagsVirtioFs);
3934
3873
- // Use a dedicated session so the share count starts at zero (no GPU libraries are mounted).
3935
auto session = CreateSession(settings);
3936
3876
- auto testRoot = std::filesystem::current_path() / "test-folder-share-limit";
3937
+ auto testRoot = std::filesystem::current_path() / "test-folder-many-shares";
3938
std::filesystem::create_directories(testRoot);
3939
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(testRoot); });
3940
+ std::vector<std::string> mountPoints;
3941
3880
- auto folderForIndex = [&](size_t index) {
3881
- auto folder = testRoot / std::to_string(index);
3882
- std::filesystem::create_directories(folder);
3883
- return folder;
3884
- };
3885
-
3886
- // Mount distinct Windows folders (each creates a new share) until the limit is reached.
3887
- size_t mounted = 0;
3888
- HRESULT lastResult = S_OK;
3889
- for (size_t i = 0; i <= c_maxVirtioFsShares; ++i)
3942
+ for (size_t index = 0; index < c_shareCount; ++index)
3943
{
3891
- auto folder = folderForIndex(i);
3892
- auto mountPoint = std::format("/vfs-limit-{}", i);
3944
+ const auto folder = testRoot / std::to_string(index);
3945
+ std::filesystem::create_directories(folder);
3946
+ std::ofstream(folder / "marker.txt") << index;
3947
3894
- lastResult = session->MountWindowsFolder(folder.c_str(), mountPoint.c_str(), false);
3895
- if (FAILED(lastResult))
3896
- {
3897
- break;
3898
- }
3948
+ const auto mountPoint = std::format("/vfs-many-{}", index);
3949
+ VERIFY_SUCCEEDED(session->MountWindowsFolder(folder.c_str(), mountPoint.c_str(), false));
3950
+ mountPoints.emplace_back(mountPoint);
3951
3900
- mounted++;
3952
+ const auto command = std::format("cat {}/marker.txt", mountPoint);
3953
+ const auto result = ExpectCommandResult(session.get(), {"/bin/sh", "-c", command}, 0);
3954
+ VERIFY_ARE_EQUAL(std::to_string(index), result.Output.at(1));
3955
}
3956
3903
- VERIFY_ARE_EQUAL(mounted, c_maxVirtioFsShares);
3904
- VERIFY_ARE_EQUAL(lastResult, E_OUTOFMEMORY);
3905
- ValidateCOMErrorMessage(
3906
- L"Too many volumes have been mounted (limit: 15). Restart the session to mount more volumes. This will be fixed in a "
3907
- L"future release.");
3908
-
3909
- // Reusing an already-created share must still succeed.
3910
- auto reusedFolder = folderForIndex(0);
3911
- VERIFY_SUCCEEDED(session->MountWindowsFolder(reusedFolder.c_str(), "/vfs-limit-reuse", false));
3912
- VERIFY_SUCCEEDED(session->UnmountWindowsFolder("/vfs-limit-reuse"));
3957
+ for (const auto& mountPoint : mountPoints)
3958
+ {
3959
+ VERIFY_SUCCEEDED(session->UnmountWindowsFolder(mountPoint.c_str()));
3960
+ }
3961
}
3962
3963
// This test case validates that no file descriptors are leaked to user processes.