From 4c27cd8904a007d24acf62280f470ee10370020e Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Thu, 16 Jul 2026 12:42:11 +0800 Subject: [PATCH 1/5] Add systemd unit to make /mnt/wsl recursive slave before umount --- src/linux/init/init.cpp | 14 ++++++++++++++ test/windows/UnitTests.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/linux/init/init.cpp b/src/linux/init/init.cpp index 5bd4c793e4..0e4923d9e9 100644 --- a/src/linux/init/init.cpp +++ b/src/linux/init/init.cpp @@ -355,6 +355,20 @@ int GenerateSystemdUnits(int Argc, char** Argv) // When multiple distros are running, the second distro's getty fails because the tty is already held. THROW_LAST_ERROR_IF(symlink("/dev/null", std::format("{}/console-getty.service", installPath).c_str()) < 0); + constexpr auto* mountGuardUnitContent = R"(# Note: This file is generated by WSL to prevent shutdown unmounts from propagating to other distributions. + +[Unit] +Description=WSL Cross-Distribution Mount Guard +After=local-fs.target remote-fs.target +ConditionPathIsMountPoint=/mnt/wsl + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/true +ExecStop=-/bin/mount --make-rslave /mnt/wsl)"; + InstallSystemdUnit(installPath, "wsl-mnt-guard", mountGuardUnitContent); + // Only create the wslg unit if both enabled in wsl.conf, and if the wslg folder actually exists. if (enableGuiApps && access("/mnt/wslg/runtime-dir", F_OK) == 0) { diff --git a/test/windows/UnitTests.cpp b/test/windows/UnitTests.cpp index 43d26041fc..10f7ef3e21 100644 --- a/test/windows/UnitTests.cpp +++ b/test/windows/UnitTests.cpp @@ -516,6 +516,40 @@ class UnitTests } } + WSL2_TEST_METHOD(SharedMountSurvivesDistroTermination) + { + constexpr auto peerDistroName = L"mount-guard-peer-test"; + constexpr auto mountPoint = L"/mnt/wsl/mount-guard-test"; + + auto cleanupVm = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslShutdown(); }); + auto cleanupSystemd = EnableSystemd(); + + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"--import {} . \"{}\" --version 2", peerDistroName, g_testDistroPath)), 0L); + auto cleanupPeer = + wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LxsstuLaunchWsl(std::format(L"--unregister {}", peerDistroName)); }); + + auto cleanupPeerSystemd = EnableSystemd("", peerDistroName); + + VERIFY_ARE_EQUAL( + LxsstuLaunchWsl(std::format(L"-d {} -- sh -c \"systemctl is-system-running | grep -Eq 'running|degraded'\"", peerDistroName)), + 0L); + + VERIFY_ARE_EQUAL( + LxsstuLaunchWsl(std::format(L"sh -c 'mkdir -p {0} && mount -t tmpfs -o size=4M mount-guard-test {0} && echo survived > {0}/marker'", mountPoint)), + 0L); + auto cleanupMount = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { + LxsstuLaunchWsl(std::format(L"sh -c 'umount {0} 2>/dev/null || true; rmdir {0} 2>/dev/null || true'", mountPoint)); + }); + + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-d {} -- findmnt -n {}", peerDistroName, mountPoint)), 0L); + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-d {} -- grep -qx survived {}/marker", peerDistroName, mountPoint)), 0L); + + TerminateDistribution(peerDistroName); + + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"findmnt -n {}", mountPoint)), 0L); + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"grep -qx survived {}/marker", mountPoint)), 0L); + } + WSL2_TEST_METHOD(ConfigUpdateLanguage) { // Validates that init populates $LANG from the distro locale configuration file. From bd331f332ddece73a05914f3c11cf2221ec6f848 Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Thu, 16 Jul 2026 13:14:13 +0800 Subject: [PATCH 2/5] format code --- test/windows/UnitTests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/windows/UnitTests.cpp b/test/windows/UnitTests.cpp index 10f7ef3e21..0a0201c138 100644 --- a/test/windows/UnitTests.cpp +++ b/test/windows/UnitTests.cpp @@ -531,11 +531,11 @@ class UnitTests auto cleanupPeerSystemd = EnableSystemd("", peerDistroName); VERIFY_ARE_EQUAL( - LxsstuLaunchWsl(std::format(L"-d {} -- sh -c \"systemctl is-system-running | grep -Eq 'running|degraded'\"", peerDistroName)), - 0L); + LxsstuLaunchWsl(std::format(L"-d {} -- sh -c \"systemctl is-system-running | grep -Eq 'running|degraded'\"", peerDistroName)), 0L); VERIFY_ARE_EQUAL( - LxsstuLaunchWsl(std::format(L"sh -c 'mkdir -p {0} && mount -t tmpfs -o size=4M mount-guard-test {0} && echo survived > {0}/marker'", mountPoint)), + LxsstuLaunchWsl(std::format( + L"sh -c 'mkdir -p {0} && mount -t tmpfs -o size=4M mount-guard-test {0} && echo survived > {0}/marker'", mountPoint)), 0L); auto cleanupMount = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LxsstuLaunchWsl(std::format(L"sh -c 'umount {0} 2>/dev/null || true; rmdir {0} 2>/dev/null || true'", mountPoint)); From 5c116076dff94872c8f8f6b3021014166fe3a4dd Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Thu, 16 Jul 2026 14:44:42 +0800 Subject: [PATCH 3/5] handle configurable auto mount root --- src/linux/init/init.cpp | 17 ++++++++++--- test/windows/UnitTests.cpp | 50 +++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/linux/init/init.cpp b/src/linux/init/init.cpp index 0e4923d9e9..1e19a3f52b 100644 --- a/src/linux/init/init.cpp +++ b/src/linux/init/init.cpp @@ -355,19 +355,28 @@ int GenerateSystemdUnits(int Argc, char** Argv) // When multiple distros are running, the second distro's getty fails because the tty is already held. THROW_LAST_ERROR_IF(symlink("/dev/null", std::format("{}/console-getty.service", installPath).c_str()) < 0); - constexpr auto* mountGuardUnitContent = R"(# Note: This file is generated by WSL to prevent shutdown unmounts from propagating to other distributions. + if (automountRoot.empty() || automountRoot.back() != '/') + { + automountRoot += '/'; + } + + const auto sharedMountPath = automountRoot + "wsl"; + const auto mountGuardUnitContent = std::format( + R"(# Note: This file is generated by WSL to prevent shutdown unmounts from propagating to other distributions. [Unit] Description=WSL Cross-Distribution Mount Guard After=local-fs.target remote-fs.target -ConditionPathIsMountPoint=/mnt/wsl +ConditionPathIsMountPoint={} [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/true -ExecStop=-/bin/mount --make-rslave /mnt/wsl)"; - InstallSystemdUnit(installPath, "wsl-mnt-guard", mountGuardUnitContent); +ExecStop=-/bin/mount --make-rslave {})", + sharedMountPath, + sharedMountPath); + InstallSystemdUnit(installPath, "wsl-mnt-guard", mountGuardUnitContent.c_str()); // Only create the wslg unit if both enabled in wsl.conf, and if the wslg folder actually exists. if (enableGuiApps && access("/mnt/wslg/runtime-dir", F_OK) == 0) diff --git a/test/windows/UnitTests.cpp b/test/windows/UnitTests.cpp index 0a0201c138..7329859d0a 100644 --- a/test/windows/UnitTests.cpp +++ b/test/windows/UnitTests.cpp @@ -518,36 +518,42 @@ class UnitTests WSL2_TEST_METHOD(SharedMountSurvivesDistroTermination) { - constexpr auto peerDistroName = L"mount-guard-peer-test"; - constexpr auto mountPoint = L"/mnt/wsl/mount-guard-test"; + auto validate = [&](LPCWSTR peerDistroName, const std::string& automountRoot) { + const auto extraConfig = automountRoot.empty() ? "" : std::format("[automount]\nroot={}\n", automountRoot); + const auto effectiveAutomountRoot = automountRoot.empty() ? "/mnt" : automountRoot; + const auto mountPoint = std::format(L"{}/wsl/mount-guard-test", wsl::shared::string::MultiByteToWide(effectiveAutomountRoot)); - auto cleanupVm = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslShutdown(); }); - auto cleanupSystemd = EnableSystemd(); + auto cleanupVm = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslShutdown(); }); + auto cleanupSystemd = EnableSystemd(extraConfig); - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"--import {} . \"{}\" --version 2", peerDistroName, g_testDistroPath)), 0L); - auto cleanupPeer = - wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LxsstuLaunchWsl(std::format(L"--unregister {}", peerDistroName)); }); + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"--import {} . \"{}\" --version 2", peerDistroName, g_testDistroPath)), 0L); + auto cleanupPeer = wil::scope_exit_log( + WI_DIAGNOSTICS_INFO, [&]() { LxsstuLaunchWsl(std::format(L"--unregister {}", peerDistroName)); }); - auto cleanupPeerSystemd = EnableSystemd("", peerDistroName); + auto cleanupPeerSystemd = EnableSystemd(extraConfig, peerDistroName); - VERIFY_ARE_EQUAL( - LxsstuLaunchWsl(std::format(L"-d {} -- sh -c \"systemctl is-system-running | grep -Eq 'running|degraded'\"", peerDistroName)), 0L); + VERIFY_ARE_EQUAL( + LxsstuLaunchWsl(std::format(L"-d {} -- sh -c \"systemctl is-system-running | grep -Eq 'running|degraded'\"", peerDistroName)), 0L); - VERIFY_ARE_EQUAL( - LxsstuLaunchWsl(std::format( - L"sh -c 'mkdir -p {0} && mount -t tmpfs -o size=4M mount-guard-test {0} && echo survived > {0}/marker'", mountPoint)), - 0L); - auto cleanupMount = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { - LxsstuLaunchWsl(std::format(L"sh -c 'umount {0} 2>/dev/null || true; rmdir {0} 2>/dev/null || true'", mountPoint)); - }); + VERIFY_ARE_EQUAL( + LxsstuLaunchWsl(std::format( + L"sh -c 'mkdir -p {0} && mount -t tmpfs -o size=4M mount-guard-test {0} && echo survived > {0}/marker'", mountPoint)), + 0L); + auto cleanupMount = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { + LxsstuLaunchWsl(std::format(L"sh -c 'umount {0} 2>/dev/null || true; rmdir {0} 2>/dev/null || true'", mountPoint)); + }); - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-d {} -- findmnt -n {}", peerDistroName, mountPoint)), 0L); - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-d {} -- grep -qx survived {}/marker", peerDistroName, mountPoint)), 0L); + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-d {} -- findmnt -n {}", peerDistroName, mountPoint)), 0L); + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-d {} -- grep -qx survived {}/marker", peerDistroName, mountPoint)), 0L); - TerminateDistribution(peerDistroName); + TerminateDistribution(peerDistroName); + + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"findmnt -n {}", mountPoint)), 0L); + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"grep -qx survived {}/marker", mountPoint)), 0L); + }; - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"findmnt -n {}", mountPoint)), 0L); - VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"grep -qx survived {}/marker", mountPoint)), 0L); + validate(L"mount-guard-default-root-peer-test", ""); + validate(L"mount-guard-custom-root-peer-test", "/wsl-test-mount"); } WSL2_TEST_METHOD(ConfigUpdateLanguage) From 318c4e30152db50b4857283c63bfe27ba907a495 Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Thu, 16 Jul 2026 14:46:13 +0800 Subject: [PATCH 4/5] move parameter parsing location --- src/linux/init/init.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/linux/init/init.cpp b/src/linux/init/init.cpp index 1e19a3f52b..531166b444 100644 --- a/src/linux/init/init.cpp +++ b/src/linux/init/init.cpp @@ -344,6 +344,11 @@ int GenerateSystemdUnits(int Argc, char** Argv) File.reset(); } + if (automountRoot.empty() || automountRoot.back() != '/') + { + automountRoot += '/'; + } + // Mask systemd-networkd-wait-online.service since WSL always ensures that networking is configured during boot. // That unit can cause systemd boot timeouts since WSL's network interface is unmanaged by systemd. THROW_LAST_ERROR_IF(symlink("/dev/null", std::format("{}/systemd-networkd-wait-online.service", installPath).c_str()) < 0); @@ -355,11 +360,6 @@ int GenerateSystemdUnits(int Argc, char** Argv) // When multiple distros are running, the second distro's getty fails because the tty is already held. THROW_LAST_ERROR_IF(symlink("/dev/null", std::format("{}/console-getty.service", installPath).c_str()) < 0); - if (automountRoot.empty() || automountRoot.back() != '/') - { - automountRoot += '/'; - } - const auto sharedMountPath = automountRoot + "wsl"; const auto mountGuardUnitContent = std::format( R"(# Note: This file is generated by WSL to prevent shutdown unmounts from propagating to other distributions. From 3414b74f2b63dd2143766f051caa59e1822811fd Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Thu, 16 Jul 2026 15:07:08 +0800 Subject: [PATCH 5/5] use the same secondary test distro name --- test/windows/UnitTests.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/windows/UnitTests.cpp b/test/windows/UnitTests.cpp index 7329859d0a..9f920c9307 100644 --- a/test/windows/UnitTests.cpp +++ b/test/windows/UnitTests.cpp @@ -518,7 +518,9 @@ class UnitTests WSL2_TEST_METHOD(SharedMountSurvivesDistroTermination) { - auto validate = [&](LPCWSTR peerDistroName, const std::string& automountRoot) { + constexpr auto peerDistroName = L"mount-guard-peer-test"; + + auto validate = [&](const std::string& automountRoot) { const auto extraConfig = automountRoot.empty() ? "" : std::format("[automount]\nroot={}\n", automountRoot); const auto effectiveAutomountRoot = automountRoot.empty() ? "/mnt" : automountRoot; const auto mountPoint = std::format(L"{}/wsl/mount-guard-test", wsl::shared::string::MultiByteToWide(effectiveAutomountRoot)); @@ -526,6 +528,7 @@ class UnitTests auto cleanupVm = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslShutdown(); }); auto cleanupSystemd = EnableSystemd(extraConfig); + LxsstuLaunchWsl(std::format(L"--unregister {}", peerDistroName)); VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"--import {} . \"{}\" --version 2", peerDistroName, g_testDistroPath)), 0L); auto cleanupPeer = wil::scope_exit_log( WI_DIAGNOSTICS_INFO, [&]() { LxsstuLaunchWsl(std::format(L"--unregister {}", peerDistroName)); }); @@ -552,8 +555,8 @@ class UnitTests VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"grep -qx survived {}/marker", mountPoint)), 0L); }; - validate(L"mount-guard-default-root-peer-test", ""); - validate(L"mount-guard-custom-root-peer-test", "/wsl-test-mount"); + validate(""); + validate("/wsl-test-mount"); } WSL2_TEST_METHOD(ConfigUpdateLanguage)