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.