@samitouri / QOSAMI-WSL / commits / 4096e6d7

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)