Fix: add systemd unit to make /mnt/wsl recursive slave before umount (#41086)

Systemd globally unmounts shared mounts under /mnt/wsl during graceful shutdown. This PR adds a systemd unit to set /mnt/wsl as recursive slave during shutdown before unmounting to avoid the unmount from propagating to other namespaces.

Feng Wang committed Aug 3, 2026 at 10:30 UTC 5a182d8ad30fff779ac40c223a7f061b9a3e8112
2 files changed +66
src/linux/init/init.cpp
+23
@@ -344,6 +344,11 @@ int GenerateSystemdUnits(int Argc, char** Argv)
344 File.reset();
345 }
346
347 + if (automountRoot.empty() || automountRoot.back() != '/')
348 + {
349 + automountRoot += '/';
350 + }
351 +
352 // Mask systemd-networkd-wait-online.service since WSL always ensures that networking is configured during boot.
353 // That unit can cause systemd boot timeouts since WSL's network interface is unmanaged by systemd.
354 THROW_LAST_ERROR_IF(symlink("/dev/null", std::format("{}/systemd-networkd-wait-online.service", installPath).c_str()) < 0);
@@ -355,6 +360,24 @@ int GenerateSystemdUnits(int Argc, char** Argv)
360 // When multiple distros are running, the second distro's getty fails because the tty is already held.
361 THROW_LAST_ERROR_IF(symlink("/dev/null", std::format("{}/console-getty.service", installPath).c_str()) < 0);
362
363 + const auto sharedMountPath = automountRoot + "wsl";
364 + const auto mountGuardUnitContent = std::format(
365 + R"(# Note: This file is generated by WSL to prevent shutdown unmounts from propagating to other distributions.
366 +
367 +[Unit]
368 +Description=WSL Cross-Distribution Mount Guard
369 +After=local-fs.target remote-fs.target
370 +ConditionPathIsMountPoint={}
371 +
372 +[Service]
373 +Type=oneshot
374 +RemainAfterExit=yes
375 +ExecStart=/bin/true
376 +ExecStop=-/bin/mount --make-rslave {})",
377 + sharedMountPath,
378 + sharedMountPath);
379 + InstallSystemdUnit(installPath, "wsl-mnt-guard", mountGuardUnitContent.c_str());
380 +
381 // Only create the wslg unit if both enabled in wsl.conf, and if the wslg folder actually exists.
382 if (enableGuiApps && access("/mnt/wslg/runtime-dir", F_OK) == 0)
383 {
test/windows/UnitTests.cpp
+43
@@ -518,6 +518,49 @@ class UnitTests
518 }
519 }
520
521 + WSL2_TEST_METHOD(SharedMountSurvivesDistroTermination)
522 + {
523 + constexpr auto peerDistroName = L"mount-guard-peer-test";
524 +
525 + auto validate = [&](const std::string& automountRoot) {
526 + const auto extraConfig = automountRoot.empty() ? "" : std::format("[automount]\nroot={}\n", automountRoot);
527 + const auto effectiveAutomountRoot = automountRoot.empty() ? "/mnt" : automountRoot;
528 + const auto mountPoint = std::format(L"{}/wsl/mount-guard-test", wsl::shared::string::MultiByteToWide(effectiveAutomountRoot));
529 +
530 + auto cleanupVm = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslShutdown(); });
531 + auto cleanupSystemd = EnableSystemd(extraConfig);
532 +
533 + LxsstuLaunchWsl(std::format(L"--unregister {}", peerDistroName));
534 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"--import {} . \"{}\" --version 2", peerDistroName, g_testDistroPath)), 0L);
535 + auto cleanupPeer = wil::scope_exit_log(
536 + WI_DIAGNOSTICS_INFO, [&]() { LxsstuLaunchWsl(std::format(L"--unregister {}", peerDistroName)); });
537 +
538 + auto cleanupPeerSystemd = EnableSystemd(extraConfig, peerDistroName);
539 +
540 + VERIFY_ARE_EQUAL(
541 + LxsstuLaunchWsl(std::format(L"-d {} -- sh -c \"systemctl is-system-running | grep -Eq 'running|degraded'\"", peerDistroName)), 0L);
542 +
543 + VERIFY_ARE_EQUAL(
544 + LxsstuLaunchWsl(std::format(
545 + L"sh -c 'mkdir -p {0} && mount -t tmpfs -o size=4M mount-guard-test {0} && echo survived > {0}/marker'", mountPoint)),
546 + 0L);
547 + auto cleanupMount = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
548 + LxsstuLaunchWsl(std::format(L"sh -c 'umount {0} 2>/dev/null || true; rmdir {0} 2>/dev/null || true'", mountPoint));
549 + });
550 +
551 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-d {} -- findmnt -n {}", peerDistroName, mountPoint)), 0L);
552 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-d {} -- grep -qx survived {}/marker", peerDistroName, mountPoint)), 0L);
553 +
554 + TerminateDistribution(peerDistroName);
555 +
556 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"findmnt -n {}", mountPoint)), 0L);
557 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"grep -qx survived {}/marker", mountPoint)), 0L);
558 + };
559 +
560 + validate("");
561 + validate("/wsl-test-mount");
562 + }
563 +
564 WSL2_TEST_METHOD(ConfigUpdateLanguage)
565 {
566 // Validates that init populates $LANG from the distro locale configuration file.