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);