Use single virtiofs device for all shares (#41129)

Co-authored-by: Brian Perkins <bperkins@ntdev.microsoft.com> Co-authored-by: Daman Mulye <damanmulye@microsoft.com>

Daman Mulye committed Jul 22, 2026 at 15:06 UTC 9e58620e8931a01a51a56041b9c55d2425d528f0
19 files changed +434 -59
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.51-0" />
22 + <package id="Microsoft.WSL.DeviceHost" version="1.2.53-0" />
23 <package id="Microsoft.WSL.Kernel" version="6.18.35.2-1" targetFramework="native" />
24 <package id="Microsoft.WSL.LinuxSdk" version="1.20.0" targetFramework="native" />
25 <package id="Microsoft.WSL.TestData" version="0.5.0" />
src/linux/init/config.cpp
+19 -5
@@ -1714,7 +1714,8 @@ Return Value:
1714 // Do not consider bind mounts.
1715 //
1716
1717 - if (strcmp(MountEnum.Current().Root, "/") != 0)
1717 + if (strcmp(MountEnum.Current().Root, "/") != 0 &&
1718 + !ParseAggregateVirtioFsMountRoot(MountEnum.Current().Source, MountEnum.Current().Root))
1719 {
1720 continue;
1721 }
@@ -1739,7 +1740,7 @@ Return Value:
1740 }
1741 else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
1742 {
1742 - MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source);
1743 + MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source, MountEnum.Current().Root);
1744 }
1745 else
1746 {
@@ -2330,12 +2331,12 @@ try
2331 }
2332
2333 //
2333 - // Bind mounts which have a root other than / are currently not supported.
2334 + // Bind mounts which have a root other than / are currently not supported, except for aggregate virtio-fs shares.
2335 //
2336 // TODO_LX: Support bind mounts.
2337 //
2338
2338 - if (strcmp(MountEntry.Root, "/") != 0)
2339 + if (strcmp(MountEntry.Root, "/") != 0 && !ParseAggregateVirtioFsMountRoot(MountEntry.Source, MountEntry.Root))
2340 {
2341 continue;
2342 }
@@ -2357,6 +2358,11 @@ try
2358 {
2359 continue;
2360 }
2361 + else if (wsl::shared::string::StartsWith(std::string_view{MountEntry.MountPoint}, VIRTIOFS_MOUNT_DIR))
2362 + {
2363 + // Hidden aggregate mounts are inherited by the new namespace and must not be replaced by a child bind mount.
2364 + continue;
2365 + }
2366
2367 DrvfsMounts.emplace_back(std::move(MountEntry));
2368 }
@@ -2452,7 +2458,15 @@ try
2458 }
2459 else if (strcmp(MountEntry.FileSystemType, VIRTIO_FS_TYPE) == 0)
2460 {
2455 - RemountVirtioFs(MountEntry.Source, MountEntry.MountPoint, MountEntry.MountOptions, Message->Admin);
2461 + if (const auto aggregateRoot = ParseAggregateVirtioFsMountRoot(MountEntry.Source, MountEntry.Root))
2462 + {
2463 + const std::string childName{aggregateRoot->ChildName};
2464 + RemountVirtioFs(childName.c_str(), MountEntry.MountPoint, MountEntry.MountOptions, Message->Admin, aggregateRoot->SubPath);
2465 + }
2466 + else
2467 + {
2468 + RemountVirtioFs(MountEntry.Source, MountEntry.MountPoint, MountEntry.MountOptions, Message->Admin);
2469 + }
2470 }
2471 else
2472 {
src/linux/init/drvfs.cpp
+193 -9
@@ -14,6 +14,7 @@ Abstract:
14
15 #include "common.h"
16 #include <sys/mount.h>
17 +#include <sys/stat.h>
18 #include <stdarg.h>
19 #include <mountutilcpp.h>
20 #include "util.h"
@@ -22,7 +23,9 @@ Abstract:
23 #include "message.h"
24 #include <cassert>
25 #include <filesystem>
26 +#include <mutex>
27 #include <optional>
28 +#include <thread>
29
30 using namespace std::chrono_literals;
31
@@ -44,6 +47,144 @@ int MountFilesystem(const char* FsType, const char* Source, const char* Target,
47
48 int MountWithRetry(const char* Source, const char* Target, const char* FsType, const char* Options, int* ExitCode = nullptr);
49
50 +std::optional<VirtioFsMountRoot> ParseAggregateVirtioFsMountRoot(std::string_view Tag, std::string_view Root)
51 +{
52 + if ((Tag != LX_INIT_DRVFS_VIRTIO_TAG && Tag != LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) || Root.size() < 2 || Root.front() != '/')
53 + {
54 + return {};
55 + }
56 +
57 + Root.remove_prefix(1);
58 + const auto separator = Root.find('/');
59 + const auto childName = Root.substr(0, separator);
60 + if (!wsl::shared::string::ToGuid(childName))
61 + {
62 + return {};
63 + }
64 +
65 + const auto subPath = separator == std::string_view::npos ? std::string_view{"/"} : Root.substr(separator);
66 + return VirtioFsMountRoot{childName, subPath};
67 +}
68 +
69 +namespace {
70 +std::mutex g_virtiofsDeviceMutex;
71 +
72 +bool IsMountPoint(const std::string& Path)
73 +{
74 + struct stat self = {};
75 + struct stat parent = {};
76 + if (stat(Path.c_str(), &self) != 0)
77 + {
78 + return false;
79 + }
80 +
81 + const auto parentPath = Path + "/..";
82 + if (stat(parentPath.c_str(), &parent) != 0)
83 + {
84 + return false;
85 + }
86 +
87 + return self.st_dev != parent.st_dev;
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 = "/")
98 +{
99 + if ((strcmp(Tag, LX_INIT_DRVFS_VIRTIO_TAG) != 0 && strcmp(Tag, LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) != 0) ||
100 + !wsl::shared::string::ToGuid(ChildName) || SubPath.empty() || SubPath.front() != '/')
101 + {
102 + errno = EINVAL;
103 + return -1;
104 + }
105 +
106 + const auto rootTarget = std::format("{}/{}", VIRTIOFS_MOUNT_DIR, Tag);
107 + const auto childSource = std::format("{}/{}{}", rootTarget, ChildName, SubPath == "/" ? std::string_view{} : SubPath);
108 + {
109 + std::lock_guard<std::mutex> lock(g_virtiofsDeviceMutex);
110 + if (!IsMountPoint(rootTarget))
111 + {
112 + if (UtilMkdirPath(rootTarget.c_str(), 0755) < 0)
113 + {
114 + return -1;
115 + }
116 +
117 + if (MountWithRetry(Tag, rootTarget.c_str(), VIRTIO_FS_TYPE, "", ExitCode) < 0 && !IsMountPoint(rootTarget))
118 + {
119 + return -1;
120 + }
121 + }
122 + }
123 +
124 + if (UtilMkdirPath(Target, 0755) < 0)
125 + {
126 + return -1;
127 + }
128 +
129 + constexpr int c_maxAttempts = 5;
130 + int lastError = 0;
131 + for (int attempt = 0; attempt < c_maxAttempts; ++attempt)
132 + {
133 + if (mount(childSource.c_str(), Target, nullptr, MS_BIND, nullptr) == 0)
134 + {
135 + lastError = 0;
136 + break;
137 + }
138 +
139 + lastError = errno;
140 + if (lastError != ENOENT)
141 + {
142 + break;
143 + }
144 +
145 + std::this_thread::sleep_for(std::chrono::milliseconds{50});
146 + }
147 +
148 + if (lastError != 0)
149 + {
150 + errno = lastError;
151 + if (ExitCode != nullptr)
152 + {
153 + *ExitCode = c_exitCodeMountFail;
154 + }
155 +
156 + return -1;
157 + }
158 +
159 + const auto parsed = mountutil::MountParseFlags(Options ? Options : "");
160 + auto bindFlags = parsed.MountFlags;
161 + if ((bindFlags & (MS_NOATIME | MS_STRICTATIME)) == 0)
162 + {
163 + bindFlags |= MS_RELATIME;
164 + }
165 +
166 + if (mount(nullptr, Target, nullptr, MS_BIND | MS_REMOUNT | bindFlags, parsed.StringOptions.c_str()) < 0)
167 + {
168 + const auto savedError = errno;
169 + // Remove the successful bind before MountVirtioFs falls back to Plan 9.
170 + umount2(Target, MNT_DETACH);
171 + errno = savedError;
172 + if (ExitCode != nullptr)
173 + {
174 + *ExitCode = c_exitCodeMountFail;
175 + }
176 +
177 + return -1;
178 + }
179 +
180 + if (ExitCode != nullptr)
181 + {
182 + *ExitCode = 0;
183 + }
184 +
185 + return 0;
186 +}
187 +
188 void SaveVirtiofsTagMapping(const char* Tag, const char* Source)
189
190 /*++
@@ -127,7 +268,13 @@ Return Value:
268 while (!Options.empty())
269 {
270 auto Option = UtilStringNextToken(Options, ",");
130 - if ((Option == "metadata") || (StartsWith(Option, PLAN9_CASE_OPTION)) || (StartsWith(Option, "uid=")) ||
271 + if (Option == "ro")
272 + {
273 + Plan9Options += ";ro";
274 + StandardOptions += "ro,";
275 + }
276 + else if (
277 + (Option == "metadata") || (StartsWith(Option, PLAN9_CASE_OPTION)) || (StartsWith(Option, "uid=")) ||
278 (StartsWith(Option, "gid=")) || (StartsWith(Option, "umask=")) || (StartsWith(Option, "dmask=")) ||
279 (StartsWith(Option, "fmask=")) || (StartsWith(Option, PLAN9_SYMLINK_ROOT_OPTION)))
280 {
@@ -625,8 +772,24 @@ try
772 //
773
774 auto* Tag = wsl::shared::string::FromSpan(ResponseSpan, Response.TagOffset);
775 + auto* ChildName = wsl::shared::string::FromSpan(ResponseSpan, Response.ChildNameOffset);
776 auto* ResponseSource = wsl::shared::string::FromSpan(ResponseSpan, Response.SourceOffset);
629 - THROW_LAST_ERROR_IF(MountWithRetry(Tag, Target, VIRTIO_FS_TYPE, MountOptions.c_str(), ExitCode) < 0);
777 + const char* MappingName = Tag;
778 + if (*ChildName == '\0')
779 + {
780 + THROW_LAST_ERROR_IF(MountWithRetry(Tag, Target, VIRTIO_FS_TYPE, MountOptions.c_str(), ExitCode) < 0);
781 + }
782 + else if (MountVirtioFsChild(Tag, ChildName, Target, MountOptions.c_str(), ExitCode) < 0)
783 + {
784 + const auto childError = errno;
785 + LOG_WARNING("Mounting virtiofs child for {} failed {}, falling back to Plan9", Source, childError);
786 +
787 + return MountPlan9(Source, Target, Options, Admin, Config, ExitCode);
788 + }
789 + else
790 + {
791 + MappingName = ChildName;
792 + }
793
794 //
795 // Save the tag mapping.
@@ -634,13 +797,13 @@ try
797 // N.B. Use the source path from the response since the service canonicalizes it.
798 //
799
637 - SaveVirtiofsTagMapping(Tag, ResponseSource);
800 + SaveVirtiofsTagMapping(MappingName, ResponseSource);
801
802 return 0;
803 }
804 CATCH_RETURN_ERRNO()
805
643 -int RemountVirtioFs(const char* Tag, const char* Target, const char* Options, bool Admin)
806 +int RemountVirtioFs(const char* Tag, const char* Target, const char* Options, bool Admin, std::string_view SubPath)
807
808 /*++
809
@@ -692,16 +855,26 @@ try
855 }
856
857 auto* NewTag = wsl::shared::string::FromSpan(ResponseSpan, Response.TagOffset);
858 + auto* ChildName = wsl::shared::string::FromSpan(ResponseSpan, Response.ChildNameOffset);
859 auto* Source = wsl::shared::string::FromSpan(ResponseSpan, Response.SourceOffset);
696 - THROW_LAST_ERROR_IF(MountWithRetry(NewTag, Target, VIRTIO_FS_TYPE, Options) < 0);
860 + const char* MappingName = NewTag;
861 + if (*ChildName == '\0')
862 + {
863 + THROW_LAST_ERROR_IF(MountWithRetry(NewTag, Target, VIRTIO_FS_TYPE, Options) < 0);
864 + }
865 + else
866 + {
867 + THROW_LAST_ERROR_IF(MountVirtioFsChild(NewTag, ChildName, Target, Options, nullptr, SubPath) < 0);
868 + MappingName = ChildName;
869 + }
870
698 - SaveVirtiofsTagMapping(NewTag, Source);
871 + SaveVirtiofsTagMapping(MappingName, Source);
872
873 return 0;
874 }
875 CATCH_RETURN_ERRNO()
876
704 -std::string QueryVirtiofsMountSource(const char* Tag)
877 +std::string QueryVirtiofsMountSource(const char* Tag, const char* Root)
878
879 /*++
880
@@ -714,6 +887,8 @@ Arguments:
887
888 Tag - Supplies the virtiofs tag to query.
889
890 + Root - Optionally supplies the mountinfo root for an aggregate child.
891 +
892 Return Value:
893
894 The mount source, an empty string on failure.
@@ -731,7 +906,16 @@ try
906 // Validate the tag is a GUID.
907 //
908
734 - const auto Guid = wsl::shared::string::ToGuid(Tag);
909 + std::string mappingName{Tag};
910 + if (Root != nullptr)
911 + {
912 + if (const auto mountRoot = ParseAggregateVirtioFsMountRoot(Tag, Root))
913 + {
914 + mappingName = mountRoot->ChildName;
915 + }
916 + }
917 +
918 + const auto Guid = wsl::shared::string::ToGuid(mappingName);
919 if (!Guid)
920 {
921 return {};
@@ -741,7 +925,7 @@ try
925 // Read the symlink that maps this tag to its Windows source path.
926 //
927
744 - auto LinkPath = std::format("{}/{}", VIRTIOFS_TAG_DIR, Tag);
928 + auto LinkPath = std::format("{}/{}", VIRTIOFS_TAG_DIR, mappingName);
929 return std::filesystem::read_symlink(LinkPath).string();
930 }
931 catch (...)
src/linux/init/drvfs.h
+13 -2
@@ -13,12 +13,23 @@ Abstract:
13 --*/
14
15 #pragma once
16 +
17 +#define VIRTIOFS_MOUNT_DIR "/run/wsl/virtiofs-mounts"
18 #include <optional>
19 +#include <string_view>
20 #include "WslDistributionConfig.h"
21
22 #define DRVFS_FS_TYPE "drvfs"
23 #define MOUNT_DRVFS_NAME "mount.drvfs"
24
25 +struct VirtioFsMountRoot
26 +{
27 + std::string_view ChildName;
28 + std::string_view SubPath;
29 +};
30 +
31 +std::optional<VirtioFsMountRoot> ParseAggregateVirtioFsMountRoot(std::string_view Tag, std::string_view Root);
32 +
33 int MountDrvfs(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
34
35 int MountDrvfsEntry(int Argc, char* Argv[]);
@@ -29,6 +40,6 @@ int MountPlan9(const char* Source, const char* Target, const char* Options, std:
40
41 int MountVirtioFs(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
42
32 -int RemountVirtioFs(const char* Tag, const char* Target, const char* Options, bool Admin);
43 +int RemountVirtioFs(const char* Tag, const char* Target, const char* Options, bool Admin, std::string_view SubPath = "/");
44
34 -std::string QueryVirtiofsMountSource(const char* Tag);
45 +std::string QueryVirtiofsMountSource(const char* Tag, const char* Root = nullptr);
src/linux/init/util.cpp
+24 -3
@@ -876,6 +876,21 @@ try
876 size_t FoundPrefixLength = 0;
877 while (MountEnum.Next())
878 {
879 + //
880 + // Skip internal virtiofs device mounts. The aggregate virtiofs root and
881 + // its per-share child binds live under VIRTIOFS_MOUNT_DIR and carry the
882 + // same Windows source as the user-facing /mnt/<drive> bind mounts. If
883 + // they were considered, reverse (Windows->Linux) translation could
884 + // return an internal plumbing path (for example
885 + // /run/wsl/virtiofs-mounts/drvfsa/<guid>) instead of the real mount
886 + // point such as /mnt/c.
887 + //
888 +
889 + if (UtilIsPathPrefix(MountEnum.Current().MountPoint, VIRTIOFS_MOUNT_DIR, false) > 0)
890 + {
891 + continue;
892 + }
893 +
894 //
895 // If a mount point was previously found, and this mount point is a
896 // prefix of the path (or the previously found mount point, for Windows
@@ -911,6 +926,7 @@ try
926 //
927
928 std::string MountSource;
929 + std::string_view MountRoot{MountEnum.Current().Root};
930 if (strcmp(MountEnum.Current().FileSystemType, PLAN9_FS_TYPE) == 0)
931 {
932 MountSource = UtilParsePlan9MountSource(MountEnum.Current().SuperOptions);
@@ -923,13 +939,18 @@ try
939 }
940 else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
941 {
926 - MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source);
942 + const auto aggregateRoot = ParseAggregateVirtioFsMountRoot(MountEnum.Current().Source, MountRoot);
943 + MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source, MountEnum.Current().Root);
944 if (MountSource.empty())
945 {
946 continue;
947 }
948
949 MountEnum.Current().Source = MountSource.data();
950 + if (aggregateRoot)
951 + {
952 + MountRoot = aggregateRoot->SubPath;
953 + }
954 }
955 else if (strcmp(MountEnum.Current().FileSystemType, DRVFS_FS_TYPE) == 0)
956 {
@@ -961,10 +982,10 @@ try
982 //
983
984 std::string CombinedMountSource;
964 - if (strcmp(MountEnum.Current().Root, "/") != 0)
985 + if (MountRoot != "/")
986 {
987 CombinedMountSource += MountEnum.Current().Source;
967 - CombinedMountSource += MountEnum.Current().Root;
988 + CombinedMountSource += MountRoot;
989 UtilCanonicalisePathSeparator(CombinedMountSource, PATH_SEP_NT);
990 MountEnum.Current().Source = CombinedMountSource.data();
991 }
src/shared/inc/lxinitshared.h
+2 -1
@@ -1160,10 +1160,11 @@ typedef struct _LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE
1160 MESSAGE_HEADER Header;
1161 int Result;
1162 unsigned int TagOffset;
1163 + unsigned int ChildNameOffset;
1164 unsigned int SourceOffset;
1165 char Buffer[];
1166
1166 - PRETTY_PRINT(FIELD(Header), FIELD(Result), STRING_FIELD(TagOffset), STRING_FIELD(SourceOffset));
1167 + PRETTY_PRINT(FIELD(Header), FIELD(Result), STRING_FIELD(TagOffset), STRING_FIELD(ChildNameOffset), STRING_FIELD(SourceOffset));
1168 } LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE, *PLX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE;
1169
1170 typedef struct _LX_INIT_ADD_VIRTIOFS_SHARE_MESSAGE
src/windows/common/DeviceHostProxy.cpp
+20
@@ -170,6 +170,16 @@ GUID DeviceHostProxy::AddVirtiofsDevice(
170 return instanceId;
171 }
172
173 +void DeviceHostProxy::AddVirtiofsChild(const GUID& InstanceId, const std::wstring& Name, const std::wstring& RootPath, const std::wstring& MountOptions)
174 +{
175 + std::lock_guard lifecycleLock(m_deviceLifecycleLock);
176 +
177 + const auto name = wil::make_bstr(Name.c_str());
178 + const auto rootPath = wil::make_bstr(RootPath.c_str());
179 + const auto mountOptions = wil::make_bstr(MountOptions.c_str());
180 + THROW_IF_FAILED(GetVirtiofsDevice(InstanceId)->AddChild(name.get(), rootPath.get(), mountOptions.get()));
181 +}
182 +
183 GUID DeviceHostProxy::AddVirtioPmemDevice(_In_ HANDLE UserToken, const std::wstring& Path, bool Writable)
184 {
185 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
@@ -304,6 +314,16 @@ wil::com_ptr<IWslVirtioNetDevice> DeviceHostProxy::GetVirtioNetDevice(const GUID
314 return device->second.Device.query<IWslVirtioNetDevice>();
315 }
316
317 +wil::com_ptr<IWslVirtiofsDevice> DeviceHostProxy::GetVirtiofsDevice(const GUID& InstanceId)
318 +{
319 + auto lock = m_devicesLock.lock_shared();
320 + THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
321 +
322 + const auto device = m_devices.find(InstanceId);
323 + THROW_HR_IF(E_NOT_SET, device == m_devices.end() || device->second.ShuttingDown || !device->second.Device);
324 + return device->second.Device.query<IWslVirtiofsDevice>();
325 +}
326 +
327 void DeviceHostProxy::SetSwiotlb(UINT64 GpaBase, UINT64 SizeBytes)
328 {
329 if (GpaBase == 0 && SizeBytes == 0)
src/windows/common/DeviceHostProxy.h
+4
@@ -21,6 +21,8 @@ public:
21 GUID AddVirtiofsDevice(
22 _In_ HANDLE UserToken, const std::wstring& Label, const std::wstring& RootPath, VirtiofsShareKind Kind, UINT32 ShmemSizeMb, const std::wstring& MountOptions);
23
24 + void AddVirtiofsChild(const GUID& InstanceId, const std::wstring& Name, const std::wstring& RootPath, const std::wstring& MountOptions);
25 +
26 GUID AddVirtioPmemDevice(_In_ HANDLE UserToken, const std::wstring& Path, bool Writable);
27
28 void RemoveDevice(const GUID& InstanceId);
@@ -31,6 +33,8 @@ public:
33
34 wil::com_ptr<IWslVirtioNetDevice> GetVirtioNetDevice(const GUID& InstanceId);
35
36 + wil::com_ptr<IWslVirtiofsDevice> GetVirtiofsDevice(const GUID& InstanceId);
37 +
38 void SetSwiotlb(UINT64 GpaBase, UINT64 SizeBytes);
39
40 void Shutdown();
src/windows/common/GuestDeviceManager.cpp
+7
@@ -26,6 +26,13 @@ GUID GuestDeviceManager::AddVirtiofsDevice(_In_ PCWSTR Label, _In_opt_ PCWSTR Mo
26 UserToken, Label, RootPath, Options.Kind, Options.SharedMemorySizeMb, MountOptions ? MountOptions : L"");
27 }
28
29 +_Requires_lock_not_held_(m_lock)
30 +void GuestDeviceManager::AddVirtiofsChild(_In_ const GUID& InstanceId, _In_ PCWSTR Name, _In_opt_ PCWSTR MountOptions, _In_ PCWSTR RootPath)
31 +{
32 + auto guestDeviceLock = m_lock.lock_exclusive();
33 + m_deviceHostSupport->AddVirtiofsChild(InstanceId, Name, RootPath, MountOptions ? MountOptions : L"");
34 +}
35 +
36 _Requires_lock_not_held_(m_lock)
37 GUID GuestDeviceManager::AddVirtioPmemDevice(_In_ PCWSTR Path, bool ReadOnly, _In_ HANDLE UserToken)
38 {
src/windows/common/GuestDeviceManager.h
+3
@@ -24,6 +24,9 @@ public:
24 _Requires_lock_not_held_(m_lock)
25 GUID AddVirtiofsDevice(_In_ PCWSTR Label, _In_opt_ PCWSTR MountOptions, _In_ PCWSTR RootPath, _In_ HANDLE UserToken, VirtioFsShareOptions Options = {});
26
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 GUID AddVirtioPmemDevice(_In_ PCWSTR Path, bool ReadOnly, _In_ HANDLE UserToken);
32
src/windows/common/WslCoreConfig.cpp
+2 -1
@@ -127,7 +127,8 @@ void wsl::core::Config::ParseConfigFile(_In_opt_ LPCWSTR ConfigFilePath, _In_opt
127 ConfigKey(ConfigSetting::Experimental::IgnoredPorts, std::move(parseIgnoredPorts)),
128 ConfigKey(ConfigSetting::Experimental::HostAddressLoopback, EnableHostAddressLoopback),
129 ConfigKey(ConfigSetting::Experimental::SetVersionDebug, SetVersionDebug),
130 - ConfigKey(ConfigSetting::Experimental::Swiotlb, MemoryString(SwiotlbSizeBytes))};
130 + ConfigKey(ConfigSetting::Experimental::Swiotlb, MemoryString(SwiotlbSizeBytes)),
131 + ConfigKey(ConfigSetting::Experimental::VirtioFsAggregateShares, EnableVirtioFsAggregateShares)};
132
133 wil::unique_file ConfigFile;
134 if (ConfigFilePath != nullptr)
src/windows/common/WslCoreConfig.h
+9 -7
@@ -27,13 +27,13 @@ Abstract:
27 T_VALUE(c, EnableHostAddressLoopback), T_VALUE(c, EnableHostFileSystemAccess), T_VALUE(c, EnableIpv6), \
28 T_VALUE(c, EnableLocalhostRelay), T_VALUE(c, EnableNestedVirtualization), T_VALUE(c, EnableSafeMode), \
29 T_VALUE(c, EnableSparseVhd), T_VALUE(c, EnableVirtio), T_VALUE(c, EnableVirtio9p), T_VALUE(c, EnableVirtioFs), \
30 - T_ENUM(c, FirewallConfigPresence), T_VALUE(c, IsolateDistroCgroup), T_VALUE(c, KernelBootTimeout), \
31 - T_SET(c, KernelCommandLine), T_VALUE(c, KernelDebugPort), T_STRING(c, KernelModulesList), T_SET(c, KernelModulesPath), \
32 - T_SET(c, KernelPath), T_VALUE(c, LoadDefaultKernelModules), T_PRESENT(c, LoadKernelModulesPresence), \
33 - T_VALUE(c, MaximumMemorySizeBytes), T_VALUE(c, MaximumProcessorCount), T_ENUM(c, MemoryReclaim), \
34 - T_VALUE(c, MemorySizeBytes), T_VALUE(c, MountDeviceTimeout), T_ENUM(c, NetworkingMode), T_VALUE(c, ProcessorCount), \
35 - T_SET(c, SwapFilePath), T_VALUE(c, SwapSizeBytes), T_VALUE(c, SwiotlbSizeBytes), T_SET(c, SystemDistroPath), \
36 - T_VALUE(c, VhdSizeBytes), T_VALUE(c, VmIdleTimeout), T_SET(c, VmSwitch)
30 + T_VALUE(c, EnableVirtioFsAggregateShares), T_ENUM(c, FirewallConfigPresence), T_VALUE(c, IsolateDistroCgroup), \
31 + T_VALUE(c, KernelBootTimeout), T_SET(c, KernelCommandLine), T_VALUE(c, KernelDebugPort), T_STRING(c, KernelModulesList), \
32 + T_SET(c, KernelModulesPath), T_SET(c, KernelPath), T_VALUE(c, LoadDefaultKernelModules), \
33 + T_PRESENT(c, LoadKernelModulesPresence), T_VALUE(c, MaximumMemorySizeBytes), T_VALUE(c, MaximumProcessorCount), \
34 + T_ENUM(c, MemoryReclaim), T_VALUE(c, MemorySizeBytes), T_VALUE(c, MountDeviceTimeout), T_ENUM(c, NetworkingMode), \
35 + T_VALUE(c, ProcessorCount), T_SET(c, SwapFilePath), T_VALUE(c, SwapSizeBytes), T_VALUE(c, SwiotlbSizeBytes), \
36 + T_SET(c, SystemDistroPath), T_VALUE(c, VhdSizeBytes), T_VALUE(c, VmIdleTimeout), T_SET(c, VmSwitch)
37
38 namespace wsl::core {
39 constexpr auto ToString(ConfigKeyPresence key)
@@ -294,6 +294,7 @@ namespace ConfigSetting {
294 static constexpr auto HostAddressLoopback = "experimental.hostAddressLoopback";
295 static constexpr auto SetVersionDebug = "experimental.setVersionDebug";
296 static constexpr auto Swiotlb = "experimental.swiotlb";
297 + static constexpr auto VirtioFsAggregateShares = "experimental.virtioFsAggregateShares";
298
299 } // namespace Experimental
300 } // namespace ConfigSetting
@@ -329,6 +330,7 @@ struct Config
330 bool EnableVirtio9p = false;
331 bool EnableVirtio = !shared::Arm64 || windows::common::helpers::IsWindows11OrAbove();
332 bool EnableVirtioFs = false;
333 + bool EnableVirtioFsAggregateShares = true;
334 int KernelDebugPort = 0;
335 bool EnableGpuSupport = true;
336 bool EnableGuiApps = true;
src/windows/service/exe/WslCoreVm.cpp
+39 -14
@@ -2163,7 +2163,7 @@ void WslCoreVm::WaitForPmemDeviceInVm(_In_ ULONG PmemId)
2163 }
2164
2165 _Requires_lock_held_(m_guestDeviceLock)
2166 -std::pair<std::wstring, std::wstring> WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken)
2166 +std::tuple<std::wstring, std::wstring, std::wstring> WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken)
2167 {
2168 WI_ASSERT(m_vmConfig.EnableVirtioFs);
2169
@@ -2188,7 +2188,7 @@ std::pair<std::wstring, std::wstring> WslCoreVm::AddVirtioFsShare(_In_ bool Admi
2188
2189 // Check if a matching share already exists.
2190 bool created = false;
2191 - std::wstring tag;
2191 + std::wstring shareName;
2192 VirtioFsShare key(sharePath.c_str(), effectiveOptions.c_str(), Admin);
2193 if (!m_virtioFsShares.contains(key))
2194 {
@@ -2198,29 +2198,51 @@ std::pair<std::wstring, std::wstring> WslCoreVm::AddVirtioFsShare(_In_ bool Admi
2198 GUID tagGuid{};
2199 THROW_IF_FAILED(CoCreateGuid(&tagGuid));
2200
2201 - tag = wsl::shared::string::GuidToString<wchar_t>(tagGuid, wsl::shared::string::None);
2202 - WI_ASSERT(!FindVirtioFsShare(tag.c_str(), Admin));
2201 + shareName = wsl::shared::string::GuidToString<wchar_t>(tagGuid, wsl::shared::string::None);
2202 + WI_ASSERT(!FindVirtioFsShare(shareName.c_str(), Admin));
2203
2204 - (void)m_guestDeviceManager->AddVirtiofsDevice(tag.c_str(), key.OptionsString().c_str(), sharePath.c_str(), UserToken);
2204 + if (m_vmConfig.EnableVirtioFsAggregateShares)
2205 + {
2206 + auto& device = Admin ? m_adminVirtioFsDevice : m_virtioFsDevice;
2207 + const PCWSTR deviceTag = Admin ? TEXT(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) : TEXT(LX_INIT_DRVFS_VIRTIO_TAG);
2208 + if (!device.has_value())
2209 + {
2210 + VirtioFsShareOptions aggregateOptions{.Kind = VirtiofsShareKind_Aggregate};
2211 + device = m_guestDeviceManager->AddVirtiofsDevice(deviceTag, L"", L"", UserToken, aggregateOptions);
2212 + }
2213
2206 - m_virtioFsShares.emplace(std::move(key), tag);
2214 + m_guestDeviceManager->AddVirtiofsChild(device.value(), shareName.c_str(), key.OptionsString().c_str(), sharePath.c_str());
2215 + }
2216 + else
2217 + {
2218 + (void)m_guestDeviceManager->AddVirtiofsDevice(shareName.c_str(), key.OptionsString().c_str(), sharePath.c_str(), UserToken);
2219 + }
2220 +
2221 + m_virtioFsShares.emplace(std::move(key), shareName);
2222 created = true;
2223 }
2224 else
2225 {
2211 - tag = m_virtioFsShares[key];
2226 + shareName = m_virtioFsShares[key];
2227 }
2228
2229 + const std::wstring deviceTag = m_vmConfig.EnableVirtioFsAggregateShares
2230 + ? (Admin ? TEXT(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) : TEXT(LX_INIT_DRVFS_VIRTIO_TAG))
2231 + : shareName;
2232 + const std::wstring childName = m_vmConfig.EnableVirtioFsAggregateShares ? shareName : L"";
2233 +
2234 WSL_LOG(
2235 "WslCoreVmAddVirtioFsShare",
2236 TraceLoggingValue(Admin, "admin"),
2237 TraceLoggingValue(sharePath.c_str(), "path"),
2238 TraceLoggingValue(effectiveOptions.c_str(), "options"),
2219 - TraceLoggingValue(tag.c_str(), "tag"),
2239 + TraceLoggingValue(deviceTag.c_str(), "tag"),
2240 + TraceLoggingValue(childName.c_str(), "childName"),
2241 + TraceLoggingValue(m_vmConfig.EnableVirtioFsAggregateShares, "aggregate"),
2242 TraceLoggingValue(created, "created"),
2243 TraceLoggingValue(m_virtioFsShares.size(), "shareCount"));
2244
2223 - return {tag, sharePath};
2245 + return {deviceTag, childName, sharePath};
2246 }
2247
2248 void WslCoreVm::OnCrash(_In_ LPCWSTR Details)
@@ -2643,11 +2665,12 @@ std::vector<char> WslCoreVm::ProcessVirtioFsRequest(_In_ gsl::span<gsl::byte> Re
2665
2666 WSL_LOG("VirtiofsMessageRequest", TraceLoggingValue(header->PrettyPrint().c_str(), "Content"));
2667
2646 - auto buildResponse = [header](const std::wstring& tag, const std::wstring& source, HRESULT result) {
2668 + auto buildResponse = [header](const std::wstring& tag, const std::wstring& childName, const std::wstring& source, HRESULT result) {
2669 // Respond to the guest with the tag that should be used to mount the device.
2670 wsl::shared::MessageWriter<LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE> response(LxInitMessageAddVirtioFsDeviceResponse);
2671 response->Result = SUCCEEDED(result) ? 0 : EINVAL; // TODO: Improved HRESULT -> errno mapping.
2672 response.WriteString(response->TagOffset, tag);
2673 + response.WriteString(response->ChildNameOffset, childName);
2674 response.WriteString(response->SourceOffset, source);
2675
2676 // Echo the request's transaction id and mark the message as the first (and only) reply.
@@ -2663,6 +2686,7 @@ std::vector<char> WslCoreVm::ProcessVirtioFsRequest(_In_ gsl::span<gsl::byte> Re
2686 if (header->MessageType == LxInitMessageAddVirtioFsDevice)
2687 {
2688 std::wstring tag;
2689 + std::wstring childName;
2690 std::wstring source;
2691 const auto result = wil::ResultFromException([&]() {
2692 const auto* addShare = gslhelpers::try_get_struct<LX_INIT_ADD_VIRTIOFS_SHARE_MESSAGE>(Request);
@@ -2675,14 +2699,15 @@ std::vector<char> WslCoreVm::ProcessVirtioFsRequest(_In_ gsl::span<gsl::byte> Re
2699
2700 // Acquire the lock and attempt to add the device.
2701 auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
2678 - std::tie(tag, source) = AddVirtioFsShare(addShare->Admin, pathWide.c_str(), optionsWide.c_str());
2702 + std::tie(tag, childName, source) = AddVirtioFsShare(addShare->Admin, pathWide.c_str(), optionsWide.c_str());
2703 });
2704
2681 - return buildResponse(tag, source, result);
2705 + return buildResponse(tag, childName, source, result);
2706 }
2707 else if (header->MessageType == LxInitMessageRemountVirtioFsDevice)
2708 {
2709 std::wstring newTag;
2710 + std::wstring childName;
2711 std::wstring source;
2712 const auto result = wil::ResultFromException([&]() {
2713 const auto* remountShare = gslhelpers::try_get_struct<LX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE>(Request);
@@ -2694,13 +2719,13 @@ std::vector<char> WslCoreVm::ProcessVirtioFsRequest(_In_ gsl::span<gsl::byte> Re
2719 const auto foundShare = FindVirtioFsShare(tagWide.c_str(), !remountShare->Admin);
2720 THROW_HR_IF_MSG(E_UNEXPECTED, !foundShare.has_value(), "Unknown tag %ls", tagWide.c_str());
2721
2697 - std::tie(newTag, source) =
2722 + std::tie(newTag, childName, source) =
2723 AddVirtioFsShare(remountShare->Admin, foundShare->Path.c_str(), foundShare->OptionsString().c_str());
2724
2725 WI_ASSERT(source == foundShare->Path);
2726 });
2727
2703 - return buildResponse(newTag, source, result);
2728 + return buildResponse(newTag, childName, source, result);
2729 }
2730 else
2731 {
src/windows/service/exe/WslCoreVm.h
+4 -1
@@ -183,7 +183,8 @@ private:
183 void AddPlan9Share(_In_ PCWSTR AccessName, _In_ PCWSTR Path, _In_ UINT32 Port, _In_ wsl::windows::common::hcs::Plan9ShareFlags Flags, _In_ HANDLE UserToken, _In_ PCWSTR VirtIoTag);
184
185 _Requires_lock_held_(m_guestDeviceLock)
186 - std::pair<std::wstring, std::wstring> AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken = nullptr);
186 + std::tuple<std::wstring, std::wstring, std::wstring> AddVirtioFsShare(
187 + _In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken = nullptr);
188
189 _Requires_lock_held_(m_lock)
190 ULONG AttachDiskLockHeld(_In_ PCWSTR Disk, _In_ DiskType Type, _In_ MountFlags Flags, _In_ std::optional<ULONG> Lun, _In_ bool IsUserDisk, _In_ HANDLE UserToken);
@@ -265,6 +266,8 @@ private:
266 _Guarded_by_(m_guestDeviceLock) wil::unique_handle m_drvfsToken;
267 _Guarded_by_(m_guestDeviceLock) wil::unique_handle m_adminDrvfsToken;
268 _Guarded_by_(m_guestDeviceLock) std::map<VirtioFsShare, std::wstring> m_virtioFsShares;
269 + _Guarded_by_(m_guestDeviceLock) std::optional<GUID> m_virtioFsDevice;
270 + _Guarded_by_(m_guestDeviceLock) std::optional<GUID> m_adminVirtioFsDevice;
271 _Guarded_by_(m_guestDeviceLock) std::map<UINT32, wil::com_ptr<IPlan9FileSystem>> m_plan9Servers;
272 wil::srwlock m_lock;
273 _Guarded_by_(m_lock) wil::unique_event m_terminatingEvent { wil::EventOptions::ManualReset };
test/linux/unit_tests/drvfs.c
+5 -5
@@ -1740,10 +1740,10 @@ Return Value:
1740 // directory entries are cached.
1741 //
1742 // N.B. This is not the case with Plan 9 because Linux doesn't know the
1743 - // file system is case-insensitive.
1743 + // file system is case-insensitive. The same applies to virtiofs.
1744 //
1745
1746 - if (g_LxtFsInfo.FsType != LxtFsTypePlan9)
1746 + if (g_LxtFsInfo.FsType != LxtFsTypePlan9 && g_LxtFsInfo.FsType != LxtFsTypeVirtioFs)
1747 {
1748 LxtCheckErrno(Fd = open(DRVFS_CASE_INSENSITIVE_TEST_DIR "/foo", O_RDONLY));
1749 LxtCheckErrno(Fd2 = open(DRVFS_CASE_INSENSITIVE_TEST_DIR "/FOO", O_RDONLY));
@@ -1807,11 +1807,11 @@ Return Value:
1807 int Result;
1808
1809 //
1810 - // This test does not apply to VM mode because Plan 9 doesn't support
1811 - // junction point symlinks.
1810 + // This test does not apply to VM mode because Plan 9 and virtiofs don't
1811 + // support junction point symlinks.
1812 //
1813
1814 - if (g_LxtFsInfo.FsType == LxtFsTypePlan9)
1814 + if (g_LxtFsInfo.FsType == LxtFsTypePlan9 || g_LxtFsInfo.FsType == LxtFsTypeVirtioFs)
1815 {
1816 LxtLogInfo("This test is not relevant in VM mode.");
1817 Result = 0;
test/linux/unit_tests/lxtmount.c
+25 -5
@@ -59,6 +59,7 @@ Return Value:
59
60 {
61
62 + const char* ActualRoot;
63 int Direction;
64 const char* ExpectedSourceActual;
65 struct libmnt_fs* FileSystem;
@@ -66,6 +67,7 @@ Return Value:
67 int MountId;
68 int Result;
69 struct stat Stat;
70 + const char* SubPath;
71 struct libmnt_table* Table;
72
73 Table = NULL;
@@ -124,7 +126,24 @@ Return Value:
126 strcat(LocalPath, "//deleted");
127 }
128
127 - LxtCheckStringEqual(LocalPath, mnt_fs_get_root(FileSystem));
129 + //
130 + // Aggregate virtio-fs exposes each Windows share as a named child of a
131 + // single device, so a share mounted at <target> is a bind mount whose
132 + // mountinfo root is "/<share-name>[<subpath>]" rather than the bare
133 + // "<subpath>". The share name is a dynamically generated GUID, so strip the
134 + // leading component before comparing against the expected root. Only strip
135 + // when the root does not already match the expected value, so a virtio-fs
136 + // mount whose root is already correct is left alone.
137 + //
138 +
139 + ActualRoot = mnt_fs_get_root(FileSystem);
140 + if ((strcmp(ExpectedFsType, "virtiofs") == 0) && (strcmp(ActualRoot, LocalPath) != 0) && (ActualRoot[0] == '/'))
141 + {
142 + SubPath = strchr(ActualRoot + 1, '/');
143 + ActualRoot = (SubPath != NULL) ? SubPath : "/";
144 + }
145 +
146 + LxtCheckStringEqual(LocalPath, ActualRoot);
147 LxtCheckStringEqual(ExpectedMountOptions, mnt_fs_get_vfs_options(FileSystem));
148 if (ExpectedFsOptions != NULL)
149 {
@@ -630,15 +649,16 @@ Return Value:
649 struct libmnt_table* Table;
650
651 //
633 - // Find the mount ID of the directory. This is done by device because
634 - // it may not be a mount point.
652 + // Find the nearest mount containing the path without relying on the device
653 + // number, which is shared by all aggregate virtio-fs shares.
654 //
655
656 FileSystem = NULL;
657 Table = NULL;
658 LxtCheckErrnoZeroSuccess(stat(Path, &Stat));
640 - LxtCheckResult(MountFindMount(MOUNT_PROC_MOUNTINFO, NULL, Stat.st_dev, &Table, &FileSystem, MNT_ITER_BACKWARD));
641 -
659 + Table = mnt_new_table_from_file(MOUNT_PROC_MOUNTINFO);
660 + LxtCheckNotEqual(Table, NULL, "%p");
661 + FileSystem = mnt_table_find_mountpoint(Table, Path, MNT_ITER_BACKWARD);
662 LxtCheckNotEqual(FileSystem, NULL, "%p");
663 Result = mnt_fs_get_id(FileSystem);
664
test/windows/Common.cpp
+7
@@ -1645,6 +1645,13 @@ std::wstring LxssGenerateTestConfig(TestConfigDefaults Default)
1645 newConfig += L"[wsl2]\n";
1646 }
1647
1648 + if (Default.virtioFsAggregateShares.has_value())
1649 + {
1650 + newConfig += L"\n[experimental]\n";
1651 + newConfig += boolOptionToString(L"virtioFsAggregateShares", Default.virtioFsAggregateShares, true);
1652 + newConfig += L"[wsl2]\n";
1653 + }
1654 +
1655 // TODO: Remove once SetVersion() truncated archive error is root caused.
1656 newConfig += L"\n[experimental]\nSetVersionDebug=true\n[wsl2]\n";
1657
test/windows/Common.h
+1
@@ -555,6 +555,7 @@ struct TestConfigDefaults
555 std::optional<bool> earlyBootLogging;
556 std::optional<std::wstring> debugConsoleLogFile;
557 std::optional<DrvFsMode> drvFsMode;
558 + std::optional<bool> virtioFsAggregateShares;
559 std::optional<wsl::core::NetworkingMode> networkingMode;
560 const std::optional<std::wstring> vmSwitch;
561 const std::optional<std::wstring> macAddress;
test/windows/DrvFsTests.cpp
+56 -5
@@ -409,7 +409,7 @@ public:
409 VERIFY_IS_TRUE(out.find(L"test-file.txt") != std::wstring::npos);
410 }
411
412 - void DrvfsMountManyVirtioFsShares(DrvFsMode Mode)
412 + void DrvfsMountManyVirtioFsShares(DrvFsMode Mode, bool AggregateShares = true)
413 {
414 if (Mode != DrvFsMode::VirtioFs)
415 {
@@ -420,18 +420,26 @@ public:
420 WINDOWS_11_TEST_ONLY();
421 SKIP_TEST_ARM64();
422
423 - constexpr auto c_iterations = 15;
423 + std::optional<WslConfigChange> config;
424 + if (!AggregateShares)
425 + {
426 + config.emplace(LxssGenerateTestConfig({.drvFsMode = Mode, .virtioFsAggregateShares = false}));
427 + }
428 +
429 + const auto iterations = AggregateShares ? 32 : 12;
430 auto testDir = std::filesystem::current_path() / "virtiofs-loop-test";
431
432 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
433 + LxsstuLaunchWsl(L"umount /tmp/virtiofs-loop-test-*-nested");
434 LxsstuLaunchWsl(L"umount /tmp/virtiofs-loop-test-*");
435
436 std::error_code ec;
437 std::filesystem::remove_all(testDir, ec);
438 });
439
433 - for (int i = 0; i < c_iterations; ++i)
440 + for (int i = 0; i < iterations; ++i)
441 {
442 + const bool readOnly = (i % 2) != 0;
443 const auto sourceDir = testDir / std::to_string(i);
444 std::filesystem::create_directories(sourceDir);
445
@@ -445,7 +453,8 @@ public:
453
454 // Mount the share.
455 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mkdir -p '{}'", mountPoint)), 0);
448 - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mount -t drvfs '{}' '{}'", sourceDir.string(), mountPoint)), 0);
456 + const auto mountCommand = std::format(L"mount -t drvfs {}'{}' '{}'", readOnly ? L"-o ro " : L"", sourceDir.string(), mountPoint);
457 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand), 0);
458
459 // Validate that it can be accessed.
460 {
@@ -457,7 +466,44 @@ public:
466 {
467 auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"findmnt -ln '{}'", mountPoint));
468
460 - VerifyPatternMatch(wsl::shared::string::WideToMultiByte(out.c_str()), std::format("{} * virtiofs rw,relatime\n", mountPoint));
469 + VerifyPatternMatch(
470 + wsl::shared::string::WideToMultiByte(out.c_str()),
471 + std::format("{} * virtiofs {},relatime\n", mountPoint, readOnly ? "ro" : "rw"));
472 +
473 + if (!AggregateShares)
474 + {
475 + std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"findmnt -n -o SOURCE '{}'", mountPoint));
476 + VERIFY_IS_TRUE(out.ends_with(L'\n'));
477 + out.pop_back();
478 + VERIFY_IS_TRUE(wsl::shared::string::ToGuid(out).has_value());
479 + }
480 + }
481 +
482 + const auto writeResult = LxsstuLaunchWsl(std::format(L"touch '{}/write-test'", mountPoint));
483 + VERIFY_ARE_EQUAL(readOnly, writeResult != 0);
484 +
485 + if (AggregateShares && i == 0)
486 + {
487 + const auto nestedSourceDir = sourceDir / "nested";
488 + const auto nestedMountPoint = mountPoint + L"-nested";
489 + const auto nestedExpected = "nested virtiofs marker";
490 + std::filesystem::create_directory(nestedSourceDir);
491 + {
492 + std::ofstream markerFile(nestedSourceDir / "marker");
493 + markerFile << nestedExpected;
494 + }
495 +
496 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mkdir -p '{}'", nestedMountPoint)), 0);
497 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mount --bind '{}/nested' '{}'", mountPoint, nestedMountPoint)), 0);
498 +
499 + auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -w '{}'", nestedMountPoint));
500 + VERIFY_ARE_EQUAL(std::filesystem::canonical(nestedSourceDir).wstring() + L"\n", out);
501 +
502 + std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -u '{}'", nestedSourceDir.wstring()));
503 + VERIFY_ARE_EQUAL(nestedMountPoint + L"\n", out);
504 +
505 + std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}/marker'", nestedMountPoint));
506 + VERIFY_ARE_EQUAL(wsl::shared::string::MultiByteToWide(nestedExpected), out);
507 }
508 }
509 }
@@ -1361,6 +1407,11 @@ class WSL1 : public DrvFsTests
1407 { \
1408 DrvFsTests::DrvfsMountManyVirtioFsShares(DrvFsMode::##_mode##); \
1409 } \
1410 +\
1411 + WSL2_TEST_METHOD(DrvfsMountManyVirtioFsSharesLegacy) \
1412 + { \
1413 + DrvFsTests::DrvfsMountManyVirtioFsShares(DrvFsMode::##_mode##, false); \
1414 + } \
1415 }
1416
1417 WSL2_DRVFS_TEST_CLASS(Plan9);