virtiofs: add support for mounting directories (not just full volumes) (#14073)
* virtiofs: add support for mounting directories (not just full volumes) * disable virtiofs tests for now * spelling --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
Ben Hillis committed
Jan 29, 2026 at 09:16 UTC
4096e6d77d0a0ac7588490ec1b6e40d3067e34e7
13 files changed
+400
-213
src/linux/init/config.cpp
+7
-13
@@ -1729,10 +1729,6 @@ Return Value:
1729
if (strcmp(MountEnum.Current().FileSystemType, PLAN9_FS_TYPE) == 0)
1730
{
1731
MountSource = UtilParsePlan9MountSource(MountEnum.Current().SuperOptions);
1732
- if (MountSource.empty())
1733
- {
1734
- continue;
1735
- }
1732
}
1733
else if (strcmp(MountEnum.Current().FileSystemType, DRVFS_FS_TYPE) == 0)
1734
{
@@ -1741,13 +1737,18 @@ Return Value:
1737
}
1738
else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
1739
{
1744
- MountSource = UtilParseVirtiofsMountSource(MountEnum.Current().Source);
1740
+ MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source);
1741
}
1742
else
1743
{
1744
continue;
1745
}
1746
1747
+ if (MountSource.empty())
1748
+ {
1749
+ continue;
1750
+ }
1751
+
1752
auto letter = ConfigGetDriveLetter(MountSource);
1753
if (letter.has_value())
1754
{
@@ -2445,17 +2446,10 @@ try
2446
NewMountOptions += ',';
2447
}
2448
2448
- MountPlan9Filesystem(NewSource, MountEntry.MountPoint, NewMountOptions.c_str(), Message->Admin, Config);
2449
+ MountPlan9Share(NewSource, MountEntry.MountPoint, NewMountOptions.c_str(), Message->Admin, Config);
2450
}
2451
else if (strcmp(MountEntry.FileSystemType, VIRTIO_FS_TYPE) == 0)
2452
{
2452
- std::string_view Source = MountEntry.Source;
2453
- std::string_view OldTag = Message->Admin ? LX_INIT_DRVFS_VIRTIO_TAG : LX_INIT_DRVFS_ADMIN_VIRTIO_TAG;
2454
- if (!wsl::shared::string::StartsWith(Source, OldTag))
2455
- {
2456
- continue;
2457
- }
2458
-
2453
RemountVirtioFs(MountEntry.Source, MountEntry.MountPoint, MountEntry.MountOptions, Message->Admin);
2454
}
2455
else
src/linux/init/drvfs.cpp
+143
-68
@@ -298,70 +298,12 @@ try
298
{
299
return MountFilesystem(DRVFS_FS_TYPE, Source, Target, Options, ExitCode);
300
}
301
-
302
- // Use virtiofs if the source of the mount is the root of a drive; otherwise, use 9p.
303
- if (WSL_USE_VIRTIO_FS(Config))
304
- {
305
- if (wsl::shared::string::IsDriveRoot(Source))
306
- {
307
- return MountVirtioFs(Source, Target, Options, Admin, Config, ExitCode);
308
- }
309
-
310
- LOG_WARNING("virtiofs is only supported for mounting full drives, using 9p to mount {}", Source);
311
- }
312
-
313
- //
314
- // Check if the path is a UNC path.
315
- //
316
-
317
- const char* Plan9Source;
318
- std::string UncSource;
319
- if ((strlen(Source) >= PLAN9_UNC_PREFIX_LENGTH) && ((Source[0] == '/') || (Source[0] == '\\')) &&
320
- ((Source[1] == '/') || (Source[1] == '\\')))
301
+ else if (WSL_USE_VIRTIO_FS(Config))
302
{
322
- UncSource = PLAN9_UNC_TRANSLATED_PREFIX;
323
- UncSource += &Source[PLAN9_UNC_PREFIX_LENGTH];
324
- Plan9Source = UncSource.c_str();
325
- }
326
- else
327
- {
328
- Plan9Source = Source;
303
+ return MountVirtioFs(Source, Target, Options, Admin, Config, ExitCode);
304
}
305
331
- //
332
- // Check whether to use the elevated or regular 9p server.
333
- //
334
-
335
- bool Elevated = Admin.has_value() ? Admin.value() : IsDrvfsElevated();
336
-
337
- //
338
- // Initialize mount options.
339
- //
340
-
341
- auto Plan9Options = std::format("{};path={}", PLAN9_ANAME_DRVFS, Plan9Source);
342
-
343
- //
344
- // N.B. The cache option is added to the start of this so if the user
345
- // specifies one explicitly, it will override the default.
346
- //
347
-
348
- std::string MountOptions = "cache=mmap,";
349
- auto ParsedOptions = ConvertDrvfsMountOptionsToPlan9(Options ? Options : "", Config);
350
- Plan9Options += ParsedOptions.first;
351
- MountOptions += ParsedOptions.second;
352
-
353
- //
354
- // Append the 9p mount options to the end of the other mount options and perform the mount operation.
355
- //
356
-
357
- MountOptions += Plan9Options;
358
-
359
- if (MountPlan9Filesystem(Source, Target, MountOptions.c_str(), Elevated, Config, ExitCode) < 0)
360
- {
361
- return -1;
362
- }
363
-
364
- return 0;
306
+ return MountPlan9(Source, Target, Options, Admin, Config, ExitCode);
307
}
308
CATCH_RETURN_ERRNO()
309
@@ -407,7 +349,7 @@ Return Value:
349
return ExitCode;
350
}
351
410
-int MountPlan9Filesystem(const char* Source, const char* Target, const char* Options, bool Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode)
352
+int MountPlan9Share(const char* Source, const char* Target, const char* Options, bool Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode)
353
354
/*++
355
@@ -425,6 +367,8 @@ Arguments:
367
368
Admin - Supplies a boolean specifying if the admin share should be used.
369
370
+ Config - Supplies the distribution configuration.
371
+
372
ExitCode - Supplies an optional pointer that receives the exit code.
373
374
Return Value:
@@ -452,10 +396,95 @@ Return Value:
396
397
MountOptions =
398
std::format("msize={},trans=fd,rfdno={},wfdno={},{}", LX_INIT_UTILITY_VM_PLAN9_BUFFER_SIZE, Fd.get(), Fd.get(), Options);
399
+
400
return MountFilesystem(PLAN9_FS_TYPE, Source, Target, MountOptions.c_str(), ExitCode);
401
}
402
}
403
404
+int MountPlan9(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode)
405
+
406
+/*++
407
+
408
+Routine Description:
409
+
410
+ This routine will perform a DrvFs mount using Plan9.
411
+
412
+Arguments:
413
+
414
+ Source - Supplies the mount source.
415
+
416
+ Target - Supplies the mount target.
417
+
418
+ Options - Supplies the mount options.
419
+
420
+ Admin - Supplies an optional boolean to specify if the admin or non-admin share should be used.
421
+
422
+ Config - Supplies the distribution configuration.
423
+
424
+ ExitCode - Supplies an optional pointer that receives the exit code.
425
+
426
+Return Value:
427
+
428
+ 0 on success, -1 on failure.
429
+
430
+--*/
431
+
432
+try
433
+{
434
+ //
435
+ // Check if the path is a UNC path.
436
+ //
437
+
438
+ const char* Plan9Source;
439
+ std::string UncSource;
440
+ if ((strlen(Source) >= PLAN9_UNC_PREFIX_LENGTH) && ((Source[0] == '/') || (Source[0] == '\\')) &&
441
+ ((Source[1] == '/') || (Source[1] == '\\')))
442
+ {
443
+ UncSource = PLAN9_UNC_TRANSLATED_PREFIX;
444
+ UncSource += &Source[PLAN9_UNC_PREFIX_LENGTH];
445
+ Plan9Source = UncSource.c_str();
446
+ }
447
+ else
448
+ {
449
+ Plan9Source = Source;
450
+ }
451
+
452
+ //
453
+ // Check whether to use the elevated or regular 9p server.
454
+ //
455
+
456
+ bool Elevated = Admin.has_value() ? Admin.value() : IsDrvfsElevated();
457
+
458
+ //
459
+ // Initialize mount options.
460
+ //
461
+
462
+ auto Plan9Options = std::format("{};path={}", PLAN9_ANAME_DRVFS, Plan9Source);
463
+
464
+ //
465
+ // N.B. The cache option is added to the start of this so if the user
466
+ // specifies one explicitly, it will override the default.
467
+ //
468
+
469
+ std::string MountOptions = "cache=mmap,";
470
+ auto ParsedOptions = ConvertDrvfsMountOptionsToPlan9(Options ? Options : "", Config);
471
+ Plan9Options += ParsedOptions.first;
472
+ MountOptions += ParsedOptions.second;
473
+
474
+ //
475
+ // Append the 9p mount options to the end of the other mount options and perform the mount operation.
476
+ //
477
+
478
+ MountOptions += Plan9Options;
479
+ if (MountPlan9Share(Source, Target, MountOptions.c_str(), Elevated, Config, ExitCode) < 0)
480
+ {
481
+ return -1;
482
+ }
483
+
484
+ return 0;
485
+}
486
+CATCH_RETURN_ERRNO()
487
+
488
int MountVirtioFs(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode)
489
490
/*++
@@ -486,8 +515,6 @@ Return Value:
515
516
try
517
{
489
- assert(wsl::shared::string::IsDriveRoot(Source));
490
-
518
//
519
// Check whether to use the elevated or non-elevated virtiofs server.
520
//
@@ -516,7 +543,7 @@ try
543
AddShare.WriteString(AddShare->OptionsOffset, Plan9Options);
544
545
//
519
- // Connect to the wsl service to add the virtiofs share.
546
+ // Connect to the wsl service to add the virtiofs share. If adding the share fails, fallback to mounting using Plan9.
547
//
548
549
wsl::shared::SocketChannel Channel{UtilConnectVsock(LX_INIT_UTILITY_VM_VIRTIOFS_PORT, true), "VirtoFs"};
@@ -527,11 +554,10 @@ try
554
555
gsl::span<gsl::byte> ResponseSpan;
556
const auto& Response = Channel.Transaction<LX_INIT_ADD_VIRTIOFS_SHARE_MESSAGE>(AddShare.Span(), &ResponseSpan);
530
-
557
if (Response.Result != 0)
558
{
533
- LOG_ERROR("Add virtiofs share for {} failed {}", Source, Response.Result);
534
- return -1;
559
+ LOG_WARNING("Add virtiofs share for {} failed {}, falling back to Plan9", Source, Response.Result);
560
+ return MountPlan9(Source, Target, Options, Admin, Config, ExitCode);
561
}
562
563
//
@@ -596,3 +622,52 @@ try
622
return MountWithRetry(Tag, Target, VIRTIO_FS_TYPE, Options);
623
}
624
CATCH_RETURN_ERRNO()
625
+
626
+std::string QueryVirtiofsMountSource(const char* Tag)
627
+
628
+/*++
629
+
630
+Routine Description:
631
+
632
+ This routine takes a virtiofs tag and determines the Windows path it refers to.
633
+
634
+Arguments:
635
+
636
+ Tag - Supplies the virtiofs tag to query.
637
+
638
+Return Value:
639
+
640
+ The mount source, an empty string on failure.
641
+
642
+--*/
643
+
644
+try
645
+{
646
+ wsl::shared::MessageWriter<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE> QueryShare(LxInitMessageQueryVirtioFsDevice);
647
+ QueryShare.WriteString(QueryShare->TagOffset, Tag);
648
+
649
+ //
650
+ // Connect to the host and send the query request.
651
+ //
652
+
653
+ wsl::shared::SocketChannel Channel{UtilConnectVsock(LX_INIT_UTILITY_VM_VIRTIOFS_PORT, true), "QueryVirtioFs"};
654
+ if (Channel.Socket() < 0)
655
+ {
656
+ return {};
657
+ }
658
+
659
+ gsl::span<gsl::byte> ResponseSpan;
660
+ const auto& Response = Channel.Transaction<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE>(QueryShare.Span(), &ResponseSpan);
661
+ if (Response.Result != 0)
662
+ {
663
+ LOG_ERROR("Query virtiofs share for {} failed {}", Tag, Response.Result);
664
+ return {};
665
+ }
666
+
667
+ return wsl::shared::string::FromSpan(ResponseSpan, Response.TagOffset);
668
+}
669
+catch (...)
670
+{
671
+ LOG_CAUGHT_EXCEPTION();
672
+ return {};
673
+}
src/linux/init/drvfs.h
+5
-2
@@ -23,9 +23,12 @@ int MountDrvfs(const char* Source, const char* Target, const char* Options, std:
23
24
int MountDrvfsEntry(int Argc, char* Argv[]);
25
26
-int MountPlan9Filesystem(
27
- const char* Source, const char* Target, const char* Options, bool Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
26
+int MountPlan9Share(const char* Source, const char* Target, const char* Options, bool Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
27
+
28
+int MountPlan9(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode);
29
30
int MountVirtioFs(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
31
32
int RemountVirtioFs(const char* Tag, const char* Target, const char* Options, bool Admin);
33
+
34
+std::string QueryVirtiofsMountSource(const char* Tag);
src/linux/init/util.cpp
+2
-36
@@ -902,11 +902,12 @@ try
902
}
903
else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
904
{
905
- MountSource = UtilParseVirtiofsMountSource(MountEnum.Current().Source);
905
+ MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source);
906
if (MountSource.empty())
907
{
908
continue;
909
}
910
+
911
MountEnum.Current().Source = MountSource.data();
912
}
913
else if (strcmp(MountEnum.Current().FileSystemType, DRVFS_FS_TYPE) == 0)
@@ -2026,41 +2027,6 @@ Return Value:
2027
return {};
2028
}
2029
2029
-std::string UtilParseVirtiofsMountSource(std::string_view Source)
2030
-
2031
-/*++
2032
-
2033
-Routine Description:
2034
-
2035
- This routine parses the mount source to determine the actual source of a
2036
- a VirtioFs mount.
2037
-
2038
-Arguments:
2039
-
2040
- Source - Supplies the source string.
2041
-
2042
-Return Value:
2043
-
2044
- The mount source, or NULL if the source is not valid.
2045
-
2046
---*/
2047
-
2048
-{
2049
- std::string MountSource{};
2050
- if (wsl::shared::string::StartsWith(Source, LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) && (Source.size() >= sizeof(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG)))
2051
- {
2052
- MountSource = Source[sizeof(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) - 1];
2053
- MountSource += ":";
2054
- }
2055
- else if (wsl::shared::string::StartsWith(Source, LX_INIT_DRVFS_VIRTIO_TAG) && (Source.size() >= sizeof(LX_INIT_DRVFS_VIRTIO_TAG)))
2056
- {
2057
- MountSource = Source[sizeof(LX_INIT_DRVFS_VIRTIO_TAG) - 1];
2058
- MountSource += ":";
2059
- }
2060
-
2061
- return MountSource;
2062
-}
2063
-
2030
std::vector<char> UtilParseWslEnv(char* NtEnvironment)
2031
2032
/*++
src/linux/init/util.h
-2
@@ -257,8 +257,6 @@ int UtilParseCgroupsLine(char* Line, char** SubsystemName, bool* Enabled);
257
258
std::string UtilParsePlan9MountSource(std::string_view MountOptions);
259
260
-std::string UtilParseVirtiofsMountSource(std::string_view MountOptions);
261
-
260
std::vector<char> UtilParseWslEnv(char* NtEnvironment);
261
262
int UtilProcessChildExitCode(int Status, const char* Name, int ExpectedStatus = 0, bool PrintError = true);
src/shared/inc/lxinitshared.h
+12
@@ -302,6 +302,7 @@ typedef enum _LX_MESSAGE_TYPE
302
LxInitMessageAddVirtioFsDevice,
303
LxInitMessageAddVirtioFsDeviceResponse,
304
LxInitMessageRemountVirtioFsDevice,
305
+ LxInitMessageQueryVirtioFsDevice,
306
LxInitMessageStartDistroInit,
307
LxInitMessageCreateLoginSession,
308
LxInitMessageStopPlan9Server,
@@ -1104,6 +1105,17 @@ typedef struct _LX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE
1105
PRETTY_PRINT(FIELD(Header), FIELD(Admin), STRING_FIELD(TagOffset));
1106
} LX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE, *PLX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE;
1107
1108
+typedef struct _LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE
1109
+{
1110
+ static inline auto Type = LxInitMessageQueryVirtioFsDevice;
1111
+ using TResponse = LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE;
1112
+
1113
+ MESSAGE_HEADER Header;
1114
+ unsigned int TagOffset;
1115
+ char Buffer[];
1116
+
1117
+ PRETTY_PRINT(FIELD(Header), STRING_FIELD(TagOffset));
1118
+} LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE, *PLX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE;
1119
//
1120
// The messages that can be sent to mini_init.
1121
//
src/shared/inc/stringshared.h
-20
@@ -52,26 +52,6 @@ inline unsigned int CopyToSpan(const std::string_view String, const gsl::span<gs
52
return PreviousOffset;
53
}
54
55
-inline bool IsDriveRoot(const std::string_view Path)
56
-{
57
- bool IsRoot = true;
58
- if (Path.length() == 3)
59
- {
60
- IsRoot &= Path[2] == '\\';
61
- }
62
-
63
- if (Path.length() == 2 || Path.length() == 3)
64
- {
65
- IsRoot &= isalpha(Path[0]) && Path[1] == ':';
66
- }
67
- else
68
- {
69
- IsRoot = false;
70
- }
71
-
72
- return IsRoot;
73
-}
74
-
55
template <class T>
56
inline bool EndsWith(const std::basic_string<T>& String, const std::basic_string_view<T> Suffix)
57
{
src/windows/service/exe/WslCoreVm.cpp
+31
-26
@@ -855,9 +855,6 @@ void WslCoreVm::AddDrvFsShare(_In_ bool Admin, _In_ HANDLE UserToken)
855
{
856
// Add virtiofs devices associating indices with paths from the fixed drive bitmap. These devices support
857
// multiple mounts in the guest, so this only needs to be done once.
858
- // EX: drvfsC1 => C:\
859
- // drvfsD2 => D:\
860
- // drvfsaC3 => C:\ (elevated)
858
auto fixedDrives = wsl::windows::common::filesystem::EnumerateFixedDrives(UserToken).first;
859
while (fixedDrives != 0)
860
{
@@ -2125,7 +2122,7 @@ void WslCoreVm::WaitForPmemDeviceInVm(_In_ ULONG PmemId)
2122
_Requires_lock_held_(m_guestDeviceLock)
2123
std::wstring WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken)
2124
{
2128
- WI_ASSERT(m_vmConfig.EnableVirtioFs && wsl::shared::string::IsDriveRoot(wsl::shared::string::WideToMultiByte(Path)));
2125
+ WI_ASSERT(m_vmConfig.EnableVirtioFs);
2126
2127
if (!ARGUMENT_PRESENT(UserToken))
2128
{
@@ -2136,22 +2133,27 @@ std::wstring WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_
2133
WI_ASSERT(Admin == wsl::windows::common::security::IsTokenElevated(UserToken));
2134
2135
// Ensure that the path has a trailing path separator.
2139
- std::wstring sharePath{Path};
2140
- if (sharePath.back() != L'\\')
2136
+ std::wstring sharePath(Path);
2137
+ if (!sharePath.ends_with(L'\\') && !sharePath.ends_with(L'/'))
2138
{
2142
- sharePath += L'\\';
2139
+ sharePath.push_back(L'\\');
2140
}
2141
2142
+ sharePath = std::filesystem::weakly_canonical(sharePath).wstring();
2143
+
2144
// Check if a matching share already exists.
2145
bool created = false;
2146
std::wstring tag;
2147
VirtioFsShare key(sharePath.c_str(), Options, Admin);
2148
if (!m_virtioFsShares.contains(key))
2149
{
2151
- // Generate a new tag for the share.
2152
- tag = Admin ? TEXT(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) : TEXT(LX_INIT_DRVFS_VIRTIO_TAG);
2153
- tag += sharePath[0];
2154
- tag += std::to_wstring(m_virtioFsShares.size());
2150
+ // Generate a new unique tag for the share.
2151
+ //
2152
+ // N.B. The tag can be maximum 36 characters long so a GUID without braces fits perfectly.
2153
+ GUID tagGuid{};
2154
+ THROW_IF_FAILED(CoCreateGuid(&tagGuid));
2155
+
2156
+ tag = wsl::shared::string::GuidToString<wchar_t>(tagGuid, wsl::shared::string::None);
2157
WI_ASSERT(!FindVirtioFsShare(tag.c_str(), Admin));
2158
2159
(void)m_guestDeviceManager->AddGuestDevice(
@@ -2585,8 +2587,6 @@ try
2587
THROW_HR_IF(E_UNEXPECTED, !addShare);
2588
2589
const auto path = wsl::shared::string::FromSpan(span, addShare->PathOffset);
2588
- THROW_HR_IF_MSG(E_INVALIDARG, !wsl::shared::string::IsDriveRoot(path), "%hs is not the root of a drive", path);
2589
-
2590
const auto pathWide = wsl::shared::string::MultiByteToWide(path);
2591
const auto options = wsl::shared::string::FromSpan(span, addShare->OptionsOffset);
2592
const auto optionsWide = wsl::shared::string::MultiByteToWide(options);
@@ -2606,19 +2606,6 @@ try
2606
THROW_HR_IF(E_UNEXPECTED, !remountShare);
2607
2608
const std::string tag = wsl::shared::string::FromSpan(span, remountShare->TagOffset);
2609
- if (tag.find(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG, 0) == 0)
2610
- {
2611
- THROW_HR_IF(E_UNEXPECTED, remountShare->Admin);
2612
- }
2613
- else if (tag.find(LX_INIT_DRVFS_VIRTIO_TAG, 0) == 0)
2614
- {
2615
- THROW_HR_IF(E_UNEXPECTED, !remountShare->Admin);
2616
- }
2617
- else
2618
- {
2619
- THROW_HR_MSG(E_UNEXPECTED, "Unexpected tag %hs", tag.data());
2620
- }
2621
-
2609
const auto tagWide = wsl::shared::string::MultiByteToWide(tag);
2610
auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
2611
const auto foundShare = FindVirtioFsShare(tagWide.c_str(), !remountShare->Admin);
@@ -2629,6 +2616,24 @@ try
2616
2617
respondWithTag(newTag, result);
2618
}
2619
+ else if (message->MessageType == LxInitMessageQueryVirtioFsDevice)
2620
+ {
2621
+ std::wstring newTag;
2622
+ const auto result = wil::ResultFromException([this, span, &newTag]() {
2623
+ const auto* query = gslhelpers::try_get_struct<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE>(span);
2624
+ THROW_HR_IF(E_UNEXPECTED, !query);
2625
+
2626
+ const std::string tag = wsl::shared::string::FromSpan(span, query->TagOffset);
2627
+ const auto tagWide = wsl::shared::string::MultiByteToWide(tag);
2628
+ auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
2629
+ const auto foundShare = FindVirtioFsShare(tagWide.c_str());
2630
+ THROW_HR_IF_MSG(E_UNEXPECTED, !foundShare.has_value(), "Unknown tag %ls", tagWide.c_str());
2631
+
2632
+ newTag = foundShare->Path;
2633
+ });
2634
+
2635
+ respondWithTag(newTag, result);
2636
+ }
2637
else
2638
{
2639
THROW_HR_MSG(E_UNEXPECTED, "Unexpected MessageType %d", message->MessageType);
test/linux/unit_tests/lxtfs.c
+1
-1
@@ -294,7 +294,7 @@ Return Value:
294
snprintf(
295
Plan9Options,
296
sizeof(Plan9Options),
297
- "aname=drvfs;path=%s%s;symlinkroot=/mnt/,cache=5,access=client,msize=65536,trans=fd,rfd=4,wfd=4",
297
+ "aname=drvfs;path=%s%s;symlinkroot=/mnt/,cache=5,access=client,msize=65536,trans=fd,rfd=*,wfd=*",
298
Plan9Source,
299
Temp);
300
}
test/linux/unit_tests/lxtmount.c
+112
-1
@@ -28,6 +28,7 @@ Abstract:
28
#define PATH_MAX (4096)
29
30
void MountEscapeString(const char* Source, char* Dest, size_t Length);
31
+int MountCheckFsOptionsPattern(const char* Pattern, const char* Actual);
32
33
int MountCheckIsMount(
34
const char* Path,
@@ -127,7 +128,14 @@ Return Value:
128
LxtCheckStringEqual(ExpectedMountOptions, mnt_fs_get_vfs_options(FileSystem));
129
if (ExpectedFsOptions != NULL)
130
{
130
- LxtCheckStringEqual(ExpectedFsOptions, mnt_fs_get_fs_options(FileSystem));
131
+ if (strchr(ExpectedFsOptions, '*') != NULL)
132
+ {
133
+ LxtCheckResult(MountCheckFsOptionsPattern(ExpectedFsOptions, mnt_fs_get_fs_options(FileSystem)));
134
+ }
135
+ else
136
+ {
137
+ LxtCheckStringEqual(ExpectedFsOptions, mnt_fs_get_fs_options(FileSystem));
138
+ }
139
}
140
141
LxtCheckEqual(Stat.st_dev, mnt_fs_get_devno(FileSystem), "%lu");
@@ -235,6 +243,109 @@ ErrorExit:
243
return Result;
244
}
245
246
+int MountCheckFsOptionsPattern(const char* Pattern, const char* Actual)
247
+
248
+/*++
249
+
250
+Description:
251
+
252
+ This routine checks if mount options match a pattern with wildcards.
253
+ The '*' wildcard matches any sequence of characters.
254
+
255
+Arguments:
256
+
257
+ Pattern - Supplies the expected pattern (e.g., "rfd=*,wfd=*").
258
+
259
+ Actual - Supplies the actual mount options string.
260
+
261
+Return Value:
262
+
263
+ Returns 0 on success, -1 on failure.
264
+
265
+--*/
266
+
267
+{
268
+
269
+ const char* ActualPtr;
270
+ const char* MatchPosition;
271
+ const char* PatternPtr;
272
+ int Result;
273
+ const char* StarPosition;
274
+
275
+ PatternPtr = Pattern;
276
+ ActualPtr = Actual;
277
+ StarPosition = NULL;
278
+ MatchPosition = NULL;
279
+ Result = LXT_RESULT_FAILURE;
280
+
281
+ while (*ActualPtr != '\0')
282
+ {
283
+ if (*PatternPtr == '*')
284
+ {
285
+ //
286
+ // Remember position of * and where we are in actual string.
287
+ //
288
+
289
+ StarPosition = PatternPtr++;
290
+ MatchPosition = ActualPtr;
291
+ }
292
+ else if (*PatternPtr == *ActualPtr)
293
+ {
294
+ //
295
+ // Characters match, advance both.
296
+ //
297
+
298
+ PatternPtr++;
299
+ ActualPtr++;
300
+ }
301
+ else if (StarPosition != NULL)
302
+ {
303
+ //
304
+ // Mismatch but we have a *, backtrack and try matching one more character.
305
+ //
306
+
307
+ PatternPtr = StarPosition + 1;
308
+ ActualPtr = ++MatchPosition;
309
+ }
310
+ else
311
+ {
312
+ //
313
+ // Mismatch and no * to backtrack to.
314
+ //
315
+
316
+ goto ErrorExit;
317
+ }
318
+ }
319
+
320
+ //
321
+ // Consume any trailing *'s in pattern.
322
+ //
323
+
324
+ while (*PatternPtr == '*')
325
+ {
326
+ PatternPtr++;
327
+ }
328
+
329
+ //
330
+ // Both should be at end of string.
331
+ //
332
+
333
+ if (*PatternPtr != '\0')
334
+ {
335
+ goto ErrorExit;
336
+ }
337
+
338
+ Result = LXT_RESULT_SUCCESS;
339
+
340
+ErrorExit:
341
+ if (!LXT_SUCCESS(Result))
342
+ {
343
+ LxtLogError("Mount options mismatch: expected '%s', got '%s'", Pattern, Actual);
344
+ }
345
+
346
+ return Result;
347
+}
348
+
349
void MountEscapeString(const char* Source, char* Dest, size_t Length)
350
351
/*++
test/windows/Common.cpp
+1
-5
@@ -1668,11 +1668,7 @@ Return Value:
1668
1669
if (LxsstuVmMode())
1670
{
1671
- std::wstring Command = L"/bin/cp /data/test/log/";
1672
- Command += LogFileToken;
1673
- Command += L" $(wslpath '";
1674
- Command += LinuxLogPath;
1675
- Command += L"')";
1671
+ std::wstring Command = std::format(L"/bin/cp /data/test/log/{} $(wslpath '{}')", LogFileToken, LinuxLogPath);
1672
VERIFY_NO_THROW(LxsstuRunTest(Command.c_str()));
1673
}
1674
test/windows/DrvFsTests.cpp
+86
-15
@@ -226,12 +226,6 @@ public:
226
{
227
SKIP_TEST_ARM64();
228
229
- if (Mode == DrvFsMode::VirtioFs)
230
- {
231
- LogSkipped("VirtioFS currently only supports mounting full drives");
232
- return;
233
- }
234
-
229
constexpr auto MountPoint = "C:\\lxss_fat";
230
constexpr auto VhdPath = "C:\\lxss_fat.vhdx";
231
auto Cleanup = wil::scope_exit([MountPoint, VhdPath] { DeleteVolume(MountPoint, VhdPath); });
@@ -352,12 +346,6 @@ public:
346
SKIP_TEST_ARM64();
347
WSL_TEST_VERSION_REQUIRED(wsl::windows::common::helpers::WindowsBuildNumbers::Germanium);
348
355
- if (Mode == DrvFsMode::VirtioFs)
356
- {
357
- LogSkipped("VirtioFS currently only supports mounting full drives");
358
- return;
359
- }
360
-
349
constexpr auto MountPoint = "C:\\lxss_refs";
350
constexpr auto VhdPath = "C:\\lxss_refs.vhdx";
351
auto Cleanup = wil::scope_exit([MountPoint, VhdPath] { DeleteVolume(MountPoint, VhdPath); });
@@ -367,6 +355,68 @@ public:
355
LxsstuRunTest((L"bash -c '" + SkipUnstableTestEnvVar + L" /data/test/wsl_unit_tests drvfs -m 6'").c_str(), L"drvfs6"));
356
}
357
358
+ void WslPath(DrvFsMode Mode)
359
+ {
360
+ VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests wslpath", L"wslpath"));
361
+
362
+ auto testWslPath = [](const std::wstring& testDir) {
363
+ auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(testDir); });
364
+
365
+ std::filesystem::create_directory(testDir);
366
+
367
+ auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -aw '{}'", testDir));
368
+ VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
369
+
370
+ std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -wa '{}'", testDir));
371
+ VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
372
+
373
+ std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath '{}'", testDir));
374
+ VERIFY_ARE_EQUAL(std::format(L"{}\n", testDir), out);
375
+
376
+ std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -a '{}'", testDir));
377
+ VERIFY_IS_TRUE(out.find(L"/mnt/") == 0);
378
+ };
379
+
380
+ testWslPath(L"wslpath-test-dir");
381
+ testWslPath(L"wslpath-测试目录-テスト");
382
+ }
383
+
384
+ void DrvFsMountUnicodePath(DrvFsMode Mode)
385
+ {
386
+ WSL2_TEST_ONLY();
387
+
388
+ // Create a Windows directory with unicode characters
389
+ constexpr auto unicodeDir = L"C:\\drvfs-测试-テスト";
390
+ auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(unicodeDir); });
391
+
392
+ std::filesystem::create_directory(unicodeDir);
393
+
394
+ // Create a test file inside the directory
395
+ const auto testFilePath = std::filesystem::path(unicodeDir) / L"test-file.txt";
396
+ {
397
+ std::ofstream testFile(testFilePath);
398
+ testFile << "hello from unicode path";
399
+ }
400
+
401
+ // Mount the unicode directory using mount -t drvfs
402
+ constexpr auto mountPoint = L"/tmp/unicode-mount-test";
403
+ auto unmountCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
404
+ LxsstuLaunchWsl(std::format(L"-u root umount '{}'", mountPoint).c_str());
405
+ LxsstuLaunchWsl(std::format(L"-u root rmdir '{}'", mountPoint).c_str());
406
+ });
407
+
408
+ VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-u root mkdir -p '{}'", mountPoint).c_str()), 0);
409
+ VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-u root mount -t drvfs '{}' '{}'", unicodeDir, mountPoint).c_str()), 0);
410
+
411
+ // Verify we can read the test file through the mount
412
+ auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}/test-file.txt'", mountPoint));
413
+ VERIFY_ARE_EQUAL(L"hello from unicode path", out);
414
+
415
+ // Verify we can list the directory
416
+ std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"ls '{}'", mountPoint));
417
+ VERIFY_IS_TRUE(out.find(L"test-file.txt") != std::wstring::npos);
418
+ }
419
+
420
// DrvFsTests Private Methods
421
private:
422
static VOID CreateDrvFsTestFiles(bool Metadata)
@@ -935,7 +985,11 @@ private:
985
const auto lines = LxssSplitString(output.Stdout, L"\n");
986
987
VERIFY_ARE_EQUAL(lines.size(), 1);
938
- VERIFY_IS_TRUE(output.Stdout.find(expectedType) == 0);
988
+
989
+ if (!expectedType.empty())
990
+ {
991
+ VERIFY_IS_TRUE(output.Stdout.find(expectedType) == 0);
992
+ }
993
};
994
995
std::wstring elevatedType;
@@ -951,8 +1005,7 @@ private:
1005
nonElevatedType = L"drvfs";
1006
break;
1007
case DrvFsMode::VirtioFs:
954
- elevatedType = L"drvfsaC";
955
- nonElevatedType = L"drvfsC";
1008
+ // VirtioFs uses GUIDs as the tag so the value is not predictable.
1009
break;
1010
1011
default:
@@ -1149,6 +1202,12 @@ class WSL1 : public DrvFsTests
1202
WSL1_TEST_ONLY();
1203
DrvFsTests::XattrDrvFs(DrvFsMode::WSL1);
1204
}
1205
+
1206
+ TEST_METHOD(WslPath)
1207
+ {
1208
+ WSL1_TEST_ONLY();
1209
+ DrvFsTests::WslPath(DrvFsMode::WSL1);
1210
+ }
1211
};
1212
1213
#define WSL2_DRVFS_TEST_CLASS(_mode) \
@@ -1266,6 +1325,18 @@ class WSL1 : public DrvFsTests
1325
WSL2_TEST_ONLY(); \
1326
DrvFsTests::DrvFsReFs(DrvFsMode::##_mode##); \
1327
} \
1328
+\
1329
+ TEST_METHOD(WslPath) \
1330
+ { \
1331
+ WSL2_TEST_ONLY(); \
1332
+ DrvFsTests::WslPath(DrvFsMode::##_mode##); \
1333
+ } \
1334
+\
1335
+ TEST_METHOD(DrvFsMountUnicodePath) \
1336
+ { \
1337
+ WSL2_TEST_ONLY(); \
1338
+ DrvFsTests::DrvFsMountUnicodePath(DrvFsMode::##_mode##); \
1339
+ } \
1340
}
1341
1342
WSL2_DRVFS_TEST_CLASS(Plan9);
test/windows/UnitTests.cpp
-24
@@ -878,11 +878,6 @@ class UnitTests
878
}
879
}
880
881
- TEST_METHOD(WslPath)
882
- {
883
- VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests wslpath", L"wslpath"));
884
- }
885
-
881
TEST_METHOD(FsTab)
882
{
883
//
@@ -5958,25 +5953,6 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
5953
VERIFY_IS_TRUE(a);
5954
VERIFY_ARE_EQUAL(pos, L"-");
5955
}
5961
-
5962
- {
5963
- constexpr auto testDir = "wslpath-test-dir";
5964
- auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { std::filesystem::remove_all(testDir); });
5965
-
5966
- std::filesystem::create_directory(testDir);
5967
-
5968
- auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -aw {}", testDir));
5969
- VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
5970
-
5971
- std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -wa {}", testDir));
5972
- VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
5973
-
5974
- std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath {}", testDir));
5975
- VERIFY_ARE_EQUAL(std::format(L"{}\n", testDir), out);
5976
-
5977
- std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -a {}", testDir));
5978
- VERIFY_IS_TRUE(out.find(L"/mnt/") == 0);
5979
- }
5956
}
5957
5958
TEST_METHOD(CaseSensitivity)