virtiofs: update logic so querying virtiofs mount source does not require a call to the service (#14380)

* virtiofs: update logic so querying virtiofs mount source does not require a call to the service * more pr feedback * use std::filesystem::read_symlink * pr feedback and use canonical path in virtiofs symlink * make sure canonical path is always used --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>

Ben Hillis committed Mar 13, 2026 at 10:18 UTC 81dc9a3004063b91524ece24a85bfbc8e0b81ac2
4 files changed +100 -63
src/linux/init/drvfs.cpp
+83 -23
@@ -21,6 +21,7 @@ Abstract:
21 #include "config.h"
22 #include "message.h"
23 #include <cassert>
24 +#include <filesystem>
25 #include <optional>
26
27 using namespace std::chrono_literals;
@@ -32,6 +33,8 @@ using namespace std::chrono_literals;
33 #define PLAN9_SYMLINK_ROOT_OPTION "symlinkroot="
34 #define PLAN9_UNC_PREFIX_LENGTH (2)
35
36 +#define VIRTIOFS_TAG_DIR "/run/wsl/virtiofs"
37 +
38 #define LOG_STDERR(_errno) fprintf(stderr, "mount: %s\n", strerror(_errno))
39
40 constexpr int c_exitCodeInvalidUsage = 1;
@@ -41,6 +44,62 @@ int MountFilesystem(const char* FsType, const char* Source, const char* Target,
44
45 int MountWithRetry(const char* Source, const char* Target, const char* FsType, const char* Options, int* ExitCode = nullptr);
46
47 +void SaveVirtiofsTagMapping(const char* Tag, const char* Source)
48 +
49 +/*++
50 +
51 +Routine Description:
52 +
53 + This routine creates a symlink in VIRTIOFS_TAG_DIR that maps a virtiofs tag
54 + to its Windows mount source path. This allows QueryVirtiofsMountSource to
55 + resolve tags without talking to the service.
56 +
57 +Arguments:
58 +
59 + Tag - Supplies the virtiofs tag.
60 +
61 + Source - Supplies the Windows path the tag refers to.
62 +
63 +Return Value:
64 +
65 + None.
66 +
67 +--*/
68 +
69 +{
70 + //
71 + // Validate the tag is a GUID to prevent path traversal.
72 + //
73 +
74 + const auto Guid = wsl::shared::string::ToGuid(Tag);
75 + if (!Guid)
76 + {
77 + LOG_WARNING("Invalid virtiofs tag {}", Tag);
78 + return;
79 + }
80 +
81 + //
82 + // Canonicalize path separators to backslashes before persisting.
83 + //
84 +
85 + std::string CanonicalSource{Source};
86 + UtilCanonicalisePathSeparator(CanonicalSource, PATH_SEP_NT);
87 +
88 + UtilMkdirPath(VIRTIOFS_TAG_DIR, 0755);
89 +
90 + auto LinkPath = std::format("{}/{}", VIRTIOFS_TAG_DIR, Tag);
91 +
92 + //
93 + // Remove any existing symlink for this tag before creating a new one.
94 + //
95 +
96 + unlink(LinkPath.c_str());
97 + if (symlink(CanonicalSource.c_str(), LinkPath.c_str()) < 0)
98 + {
99 + LOG_WARNING("Failed to create virtiofs tag symlink {} -> {}: {}", LinkPath, CanonicalSource, errno);
100 + }
101 +}
102 +
103 std::pair<std::string, std::string> ConvertDrvfsMountOptionsToPlan9(std::string_view Options, const wsl::linux::WslDistributionConfig& Config)
104
105 /*++
@@ -565,7 +624,18 @@ try
624 //
625
626 auto* Tag = wsl::shared::string::FromSpan(ResponseSpan, Response.TagOffset);
568 - return MountWithRetry(Tag, Target, VIRTIO_FS_TYPE, MountOptions.c_str(), ExitCode);
627 + auto* ResponseSource = wsl::shared::string::FromSpan(ResponseSpan, Response.SourceOffset);
628 + THROW_LAST_ERROR_IF(MountWithRetry(Tag, Target, VIRTIO_FS_TYPE, MountOptions.c_str(), ExitCode) < 0);
629 +
630 + //
631 + // Save the tag mapping.
632 + //
633 + // N.B. Use the source path from the response since the service canonicalizes it.
634 + //
635 +
636 + SaveVirtiofsTagMapping(Tag, ResponseSource);
637 +
638 + return 0;
639 }
640 CATCH_RETURN_ERRNO()
641
@@ -620,8 +690,13 @@ try
690 return -1;
691 }
692
623 - Tag = wsl::shared::string::FromSpan(ResponseSpan, Response.TagOffset);
624 - return MountWithRetry(Tag, Target, VIRTIO_FS_TYPE, Options);
693 + auto* NewTag = wsl::shared::string::FromSpan(ResponseSpan, Response.TagOffset);
694 + auto* Source = wsl::shared::string::FromSpan(ResponseSpan, Response.SourceOffset);
695 + THROW_LAST_ERROR_IF(MountWithRetry(NewTag, Target, VIRTIO_FS_TYPE, Options) < 0);
696 +
697 + SaveVirtiofsTagMapping(NewTag, Source);
698 +
699 + return 0;
700 }
701 CATCH_RETURN_ERRNO()
702
@@ -631,7 +706,8 @@ std::string QueryVirtiofsMountSource(const char* Tag)
706
707 Routine Description:
708
634 - This routine takes a virtiofs tag and determines the Windows path it refers to.
709 + This routine takes a virtiofs tag and determines the Windows path it refers to
710 + by reading the symlink created during mount.
711
712 Arguments:
713
@@ -660,28 +736,12 @@ try
736 return {};
737 }
738
663 - wsl::shared::MessageWriter<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE> QueryShare(LxInitMessageQueryVirtioFsDevice);
664 - QueryShare.WriteString(QueryShare->TagOffset, Tag);
665 -
739 //
667 - // Connect to the host and send the query request.
740 + // Read the symlink that maps this tag to its Windows source path.
741 //
742
670 - wsl::shared::SocketChannel Channel{UtilConnectVsock(LX_INIT_UTILITY_VM_VIRTIOFS_PORT, true), "QueryVirtioFs"};
671 - if (Channel.Socket() < 0)
672 - {
673 - return {};
674 - }
675 -
676 - gsl::span<gsl::byte> ResponseSpan;
677 - const auto& Response = Channel.Transaction<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE>(QueryShare.Span(), &ResponseSpan);
678 - if (Response.Result != 0)
679 - {
680 - LOG_ERROR("Query virtiofs share for {} failed {}", Tag, Response.Result);
681 - return {};
682 - }
683 -
684 - return wsl::shared::string::FromSpan(ResponseSpan, Response.TagOffset);
743 + auto LinkPath = std::format("{}/{}", VIRTIOFS_TAG_DIR, Tag);
744 + return std::filesystem::read_symlink(LinkPath).string();
745 }
746 catch (...)
747 {
src/shared/inc/lxinitshared.h
+2 -13
@@ -302,7 +302,6 @@ typedef enum _LX_MESSAGE_TYPE
302 LxInitMessageAddVirtioFsDevice,
303 LxInitMessageAddVirtioFsDeviceResponse,
304 LxInitMessageRemountVirtioFsDevice,
305 - LxInitMessageQueryVirtioFsDevice,
305 LxInitMessageStartDistroInit,
306 LxInitMessageCreateLoginSession,
307 LxInitMessageStopPlan9Server,
@@ -1073,9 +1072,10 @@ typedef struct _LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE
1072 MESSAGE_HEADER Header;
1073 int Result;
1074 unsigned int TagOffset;
1075 + unsigned int SourceOffset;
1076 char Buffer[];
1077
1078 - PRETTY_PRINT(FIELD(Header), FIELD(Result), STRING_FIELD(TagOffset));
1078 + PRETTY_PRINT(FIELD(Header), FIELD(Result), STRING_FIELD(TagOffset), STRING_FIELD(SourceOffset));
1079 } LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE, *PLX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE;
1080
1081 typedef struct _LX_INIT_ADD_VIRTIOFS_SHARE_MESSAGE
@@ -1105,17 +1105,6 @@ 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;
1108 //
1109 // The messages that can be sent to mini_init.
1110 //
src/windows/service/exe/WslCoreVm.cpp
+14 -26
@@ -2128,7 +2128,7 @@ void WslCoreVm::WaitForPmemDeviceInVm(_In_ ULONG PmemId)
2128 }
2129
2130 _Requires_lock_held_(m_guestDeviceLock)
2131 -std::wstring WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken)
2131 +std::pair<std::wstring, std::wstring> WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken)
2132 {
2133 WI_ASSERT(m_vmConfig.EnableVirtioFs);
2134
@@ -2190,7 +2190,7 @@ std::wstring WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_
2190 TraceLoggingValue(created, "created"),
2191 TraceLoggingValue(m_virtioFsShares.size(), "shareCount"));
2192
2193 - return tag;
2193 + return {tag, sharePath};
2194 }
2195
2196 void WslCoreVm::OnCrash(_In_ LPCWSTR Details)
@@ -2581,12 +2581,13 @@ try
2581 return;
2582 }
2583
2584 - auto respondWithTag = [&](const std::wstring& tag, HRESULT result) {
2584 + auto respondWithTag = [&](const std::wstring& tag, const std::wstring& source, HRESULT result) {
2585 // Respond to the guest with the tag that should be used to mount the device.
2586
2587 wsl::shared::MessageWriter<LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE> response(LxInitMessageAddVirtioFsDeviceResponse);
2588 response->Result = SUCCEEDED(result) ? 0 : EINVAL; // TODO: Improved HRESULT -> errno mapping.
2589 response.WriteString(response->TagOffset, tag);
2590 + response.WriteString(response->SourceOffset, source);
2591
2592 channel.SendMessage<LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE>(response.Span());
2593 };
@@ -2594,7 +2595,8 @@ try
2595 if (message->MessageType == LxInitMessageAddVirtioFsDevice)
2596 {
2597 std::wstring tag;
2597 - const auto result = wil::ResultFromException([this, span, &tag]() {
2598 + std::wstring source;
2599 + const auto result = wil::ResultFromException([this, span, &tag, &source]() {
2600 const auto* addShare = gslhelpers::try_get_struct<LX_INIT_ADD_VIRTIOFS_SHARE_MESSAGE>(span);
2601 THROW_HR_IF(E_UNEXPECTED, !addShare);
2602
@@ -2605,15 +2607,16 @@ try
2607
2608 // Acquire the lock and attempt to add the device.
2609 auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
2608 - tag = AddVirtioFsShare(addShare->Admin, pathWide.c_str(), optionsWide.c_str());
2610 + std::tie(tag, source) = AddVirtioFsShare(addShare->Admin, pathWide.c_str(), optionsWide.c_str());
2611 });
2612
2611 - respondWithTag(tag, result);
2613 + respondWithTag(tag, source, result);
2614 }
2615 else if (message->MessageType == LxInitMessageRemountVirtioFsDevice)
2616 {
2617 std::wstring newTag;
2616 - const auto result = wil::ResultFromException([this, span, &newTag]() {
2618 + std::wstring source;
2619 + const auto result = wil::ResultFromException([this, span, &newTag, &source]() {
2620 const auto* remountShare = gslhelpers::try_get_struct<LX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE>(span);
2621 THROW_HR_IF(E_UNEXPECTED, !remountShare);
2622
@@ -2623,28 +2626,13 @@ try
2626 const auto foundShare = FindVirtioFsShare(tagWide.c_str(), !remountShare->Admin);
2627 THROW_HR_IF_MSG(E_UNEXPECTED, !foundShare.has_value(), "Unknown tag %ls", tagWide.c_str());
2628
2626 - newTag = AddVirtioFsShare(remountShare->Admin, foundShare->Path.c_str(), foundShare->OptionsString().c_str());
2627 - });
2628 -
2629 - respondWithTag(newTag, result);
2630 - }
2631 - else if (message->MessageType == LxInitMessageQueryVirtioFsDevice)
2632 - {
2633 - std::wstring newTag;
2634 - const auto result = wil::ResultFromException([this, span, &newTag]() {
2635 - const auto* query = gslhelpers::try_get_struct<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE>(span);
2636 - THROW_HR_IF(E_UNEXPECTED, !query);
2637 -
2638 - const std::string tag = wsl::shared::string::FromSpan(span, query->TagOffset);
2639 - const auto tagWide = wsl::shared::string::MultiByteToWide(tag);
2640 - auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
2641 - const auto foundShare = FindVirtioFsShare(tagWide.c_str());
2642 - THROW_HR_IF_MSG(E_UNEXPECTED, !foundShare.has_value(), "Unknown tag %ls", tagWide.c_str());
2629 + std::tie(newTag, source) =
2630 + AddVirtioFsShare(remountShare->Admin, foundShare->Path.c_str(), foundShare->OptionsString().c_str());
2631
2644 - newTag = foundShare->Path;
2632 + WI_ASSERT(source == foundShare->Path);
2633 });
2634
2647 - respondWithTag(newTag, result);
2635 + respondWithTag(newTag, source, result);
2636 }
2637 else
2638 {
src/windows/service/exe/WslCoreVm.h
+1 -1
@@ -183,7 +183,7 @@ 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::wstring AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken = nullptr);
186 + std::pair<std::wstring, std::wstring> AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken = nullptr);
187
188 _Requires_lock_held_(m_lock)
189 ULONG AttachDiskLockHeld(_In_ PCWSTR Disk, _In_ DiskType Type, _In_ MountFlags Flags, _In_ std::optional<ULONG> Lun, _In_ bool IsUserDisk, _In_ HANDLE UserToken);