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