Fix p9 drvfs read only mount regression (#41487)

#41129 introduces a regression where the ";ro" option is passed to the host for p9 shares. However, that option is not supported by the p9 server and causes the mount to fail. This PR removes the special handling of "ro" in the common parser. And use MountParseFlags to add the required virtio option.

Feng Wang committed Sep 7, 2026 at 10:41 UTC 74c6ac9abb77f336bd4c796146c23cdb90877ed7
2 files changed +65 -7
src/linux/init/drvfs.cpp
+8 -7
@@ -248,6 +248,8 @@ Arguments:
248
249 Options - Supplies the DrvFs mount options.
250
251 + Config - Supplies the distribution configuration.
252 +
253 Return Value:
254
255 A pair representing the 9p mount options, and standard mount options.
@@ -262,13 +264,7 @@ Return Value:
264 while (!Options.empty())
265 {
266 auto Option = UtilStringNextToken(Options, ",");
265 - if (Option == "ro")
266 - {
267 - Plan9Options += ";ro";
268 - StandardOptions += "ro,";
269 - }
270 - else if (
271 - (Option == "metadata") || (StartsWith(Option, PLAN9_CASE_OPTION)) || (StartsWith(Option, "uid=")) ||
267 + if ((Option == "metadata") || (StartsWith(Option, PLAN9_CASE_OPTION)) || (StartsWith(Option, "uid=")) ||
268 (StartsWith(Option, "gid=")) || (StartsWith(Option, "umask=")) || (StartsWith(Option, "dmask=")) ||
269 (StartsWith(Option, "fmask=")) || (StartsWith(Option, PLAN9_SYMLINK_ROOT_OPTION)))
270 {
@@ -733,6 +729,11 @@ try
729 //
730
731 auto [Plan9Options, MountOptions] = ConvertDrvfsMountOptionsToPlan9(Options ? Options : "", Config);
732 + const auto ParsedOptions = mountutil::MountParseFlags(MountOptions);
733 + if ((ParsedOptions.MountFlags & MS_RDONLY) != 0)
734 + {
735 + Plan9Options += ";ro";
736 + }
737
738 //
739 // Construct a request to add a virtiofs share.
test/windows/DrvFsTests.cpp
+57
@@ -507,6 +507,58 @@ public:
507 }
508 }
509
510 + static void DrvfsMountReadOnly()
511 + {
512 + const auto testDir = std::filesystem::current_path() / "drvfs-read-only-test";
513 + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
514 + std::error_code ec;
515 + std::filesystem::remove_all(testDir, ec);
516 + });
517 +
518 + struct TestCase
519 + {
520 + std::wstring_view Options;
521 + bool ReadOnly;
522 + };
523 +
524 + constexpr TestCase testCases[] = {
525 + {L"ro,umask=222", true},
526 + {L"ro,rw", false},
527 + {L"rw,ro", true},
528 + };
529 +
530 + for (size_t index = 0; index < std::size(testCases); ++index)
531 + {
532 + const auto& testCase = testCases[index];
533 + const auto sourceDir = testDir / std::to_string(index);
534 + const auto mountPoint = std::format(L"/tmp/drvfs-read-only-test-{}", index);
535 + auto unmountCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
536 + LxsstuLaunchWsl(std::format(L"umount '{}'", mountPoint));
537 + LxsstuLaunchWsl(std::format(L"rmdir '{}'", mountPoint));
538 + });
539 +
540 + std::filesystem::create_directories(sourceDir);
541 + constexpr auto expected = "read-only mount marker";
542 + {
543 + std::ofstream markerFile(sourceDir / "marker");
544 + markerFile << expected;
545 + }
546 +
547 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mkdir -p '{}'", mountPoint)), 0);
548 + VERIFY_ARE_EQUAL(
549 + LxsstuLaunchWsl(std::format(L"mount -t drvfs -o '{}' '{}' '{}'", testCase.Options, sourceDir.string(), mountPoint)), 0);
550 +
551 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"findmnt -rn -M '{}' -O {}", mountPoint, testCase.ReadOnly ? L"ro" : L"rw")), 0);
552 +
553 + auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}/marker'", mountPoint));
554 + VERIFY_ARE_EQUAL(wsl::shared::string::MultiByteToWide(expected), out);
555 +
556 + const auto writeResult = LxsstuLaunchWsl(std::format(L"touch '{}/write-test'", mountPoint));
557 + VERIFY_ARE_EQUAL(testCase.ReadOnly, writeResult != 0);
558 + VERIFY_ARE_EQUAL(!testCase.ReadOnly, std::filesystem::exists(sourceDir / "write-test"));
559 + }
560 + }
561 +
562 // DrvFsTests Private Methods
563 private:
564 static VOID CreateDrvFsTestFiles(bool Metadata)
@@ -1411,6 +1463,11 @@ class WSL1 : public DrvFsTests
1463 { \
1464 DrvFsTests::DrvfsMountManyVirtioFsShares(DrvFsMode::##_mode##, false); \
1465 } \
1466 +\
1467 + WSL2_TEST_METHOD(DrvfsMountReadOnly) \
1468 + { \
1469 + DrvFsTests::DrvfsMountReadOnly(); \
1470 + } \
1471 }
1472
1473 WSL2_DRVFS_TEST_CLASS(Plan9);