Introduce a new wsl.conf config value to allow distributions to opt-in to cgroupv1 mounts (#13546)

* Introduce a new wsl.conf config value to allow distributions to opt-in to cgroupv1 mounts * Add test coverage * Fix tmpfs on wsl1 --------- Co-authored-by: Ben Hillis <benhillis@gmail.com>

Blue committed Oct 2, 2025 at 20:36 UTC 65eea7b31cdce11b0f5001de56d7bc330bb1db18
5 files changed +115 -35
src/linux/init/WslDistributionConfig.cpp
+1
@@ -28,6 +28,7 @@ WslDistributionConfig::WslDistributionConfig(const char* configFilePath)
28 ConfigKey("automount.options", DrvFsOptions),
29 ConfigKey(c_ConfigMountFsTabOption, MountFsTab),
30 ConfigKey(c_ConfigLinkOsLibsOption, LinkOsLibs),
31 + ConfigKey("automount.cgroups", {{"v1", CGroupVersion::v1}, {"v2", CGroupVersion::v2}}, CGroup, nullptr),
32
33 ConfigKey("filesystem.umask", Umask),
34
src/linux/init/WslDistributionConfig.h
+7
@@ -48,6 +48,12 @@ struct WslDistributionConfig
48 WslDistributionConfig(WslDistributionConfig&&) = default;
49 WslDistributionConfig& operator=(WslDistributionConfig&&) = default;
50
51 + enum class CGroupVersion
52 + {
53 + v1 = 0,
54 + v2 = 1
55 + };
56 +
57 bool AutoMount = true;
58 bool AutoUpdateTimezone = true;
59 std::optional<std::string> BootCommand;
@@ -71,6 +77,7 @@ struct WslDistributionConfig
77 bool AppendGpuLibPath = true;
78 bool GpuEnabled = true;
79 bool LinkOsLibs = true;
80 + CGroupVersion CGroup = CGroupVersion::v2;
81
82 //
83 // Values not set by /etc/wsl.conf.
src/linux/init/config.cpp
+68 -34
@@ -73,6 +73,8 @@ Abstract:
73 #define MOUNTS_DEVICE_FIELD 0
74 #define MOUNTS_FSTYPE_FIELD 2
75
76 +using wsl::linux::WslDistributionConfig;
77 +
78 static void ConfigApplyWindowsLibPath(const wsl::linux::WslDistributionConfig& Config);
79
80 static bool CreateLoginSession(const wsl::linux::WslDistributionConfig& Config, const char* Username, uid_t Uid);
@@ -567,7 +569,7 @@ Return Value:
569 // Initialize cgroups based on what the kernel supports.
570 //
571
570 - ConfigInitializeCgroups();
572 + ConfigInitializeCgroups(Config);
573
574 //
575 // Attempt to register the NT interop binfmt extension.
@@ -1783,7 +1785,7 @@ Return Value:
1785 {LX_WSL2_GUI_APP_SUPPORT_ENV, "1"}};
1786 }
1787
1786 -void ConfigInitializeCgroups(void)
1788 +void ConfigInitializeCgroups(wsl::linux::WslDistributionConfig& Config)
1789
1790 /*++
1791
@@ -1795,7 +1797,7 @@ Routine Description:
1797
1798 Arguments:
1799
1798 - None.
1800 + Config - Supplies the distribution configuration.
1801
1802 Return Value:
1803
@@ -1805,50 +1807,82 @@ Return Value:
1807
1808 try
1809 {
1808 -
1809 - //
1810 - // For WSL2 mount cgroup v2.
1811 - //
1812 - // N.B. Cgroup v2 is not implemented for WSL1.
1813 - //
1810 + std::vector<std::string> DisabledControllers;
1811
1812 if (UtilIsUtilityVm())
1813 {
1817 - const auto Target = CGROUP_MOUNTPOINT;
1814 + if (Config.CGroup == WslDistributionConfig::CGroupVersion::v1)
1815 + {
1816 + auto commandLine = UtilReadFileContent("/proc/cmdline");
1817 + auto position = commandLine.find(CGROUPS_NO_V1);
1818 + if (position != std::string::npos)
1819 + {
1820 + auto list = commandLine.substr(position + sizeof(CGROUPS_NO_V1) - 1);
1821 + auto end = list.find_first_of(" \n");
1822 + if (end != std::string::npos)
1823 + {
1824 + list = list.substr(0, end);
1825 + }
1826 +
1827 + if (list == "all")
1828 + {
1829 + LOG_WARNING("Distribution has cgroupv1 enabled, but kernel command line has {}all. Falling back to cgroupv2", CGROUPS_NO_V1);
1830 + Config.CGroup = WslDistributionConfig::CGroupVersion::v2;
1831 + }
1832 + else
1833 + {
1834 + DisabledControllers = wsl::shared::string::Split(list, ',');
1835 + }
1836 + }
1837 + }
1838 +
1839 + if (Config.CGroup == WslDistributionConfig::CGroupVersion::v1)
1840 + {
1841 + THROW_LAST_ERROR_IF(mount("tmpfs", CGROUP_MOUNTPOINT, "tmpfs", (MS_NOSUID | MS_NODEV | MS_NOEXEC), "mode=755") < 0);
1842 + }
1843 +
1844 + const auto Target = Config.CGroup == WslDistributionConfig::CGroupVersion::v1 ? CGROUP_MOUNTPOINT "/unified" : CGROUP_MOUNTPOINT;
1845 THROW_LAST_ERROR_IF(
1846 UtilMount(CGROUP2_DEVICE, Target, CGROUP2_DEVICE, (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME), "nsdelegate") < 0);
1847 +
1848 + if (Config.CGroup == WslDistributionConfig::CGroupVersion::v2)
1849 + {
1850 + return;
1851 + }
1852 }
1853 else
1854 {
1823 - //
1824 - // Mount cgroup v1 when running in WSL1 mode.
1825 - //
1826 - // Open the /proc/cgroups file and parse each line, ignoring malformed
1827 - // lines and disabled controllers.
1828 - //
1829 -
1855 THROW_LAST_ERROR_IF(mount("tmpfs", CGROUP_MOUNTPOINT, "tmpfs", (MS_NOSUID | MS_NODEV | MS_NOEXEC), "mode=755") < 0);
1856 + }
1857
1832 - wil::unique_file Cgroups{fopen(CGROUPS_FILE, "r")};
1833 - THROW_LAST_ERROR_IF(!Cgroups);
1858 + //
1859 + // Mount cgroup v1 when running in WSL1 mode or when a WSL2 distro has automount.cgroups=v1 specified.
1860 + //
1861 + // Open the /proc/cgroups file and parse each line, ignoring malformed
1862 + // lines and disabled controllers.
1863 + //
1864
1835 - ssize_t BytesRead;
1836 - char* Line = nullptr;
1837 - auto LineCleanup = wil::scope_exit([&]() { free(Line); });
1838 - size_t LineLength = 0;
1839 - while ((BytesRead = getline(&Line, &LineLength, Cgroups.get())) != -1)
1840 - {
1841 - char* Subsystem = nullptr;
1842 - bool Enabled = false;
1843 - if ((UtilParseCgroupsLine(Line, &Subsystem, &Enabled) < 0) || (Enabled == false))
1844 - {
1845 - continue;
1846 - }
1865 + wil::unique_file Cgroups{fopen(CGROUPS_FILE, "r")};
1866 + THROW_LAST_ERROR_IF(!Cgroups);
1867
1848 - auto Target = std::format("{}/{}", CGROUP_MOUNTPOINT, Subsystem);
1849 - THROW_LAST_ERROR_IF(
1850 - UtilMount(CGROUP_DEVICE, Target.c_str(), CGROUP_DEVICE, (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME), Subsystem) < 0);
1868 + ssize_t BytesRead;
1869 + char* Line = nullptr;
1870 + auto LineCleanup = wil::scope_exit([&]() { free(Line); });
1871 + size_t LineLength = 0;
1872 + while ((BytesRead = getline(&Line, &LineLength, Cgroups.get())) != -1)
1873 + {
1874 + char* Subsystem = nullptr;
1875 + bool Enabled = false;
1876 + if ((UtilParseCgroupsLine(Line, &Subsystem, &Enabled) < 0) || (Enabled == false) ||
1877 + std::find(DisabledControllers.begin(), DisabledControllers.end(), Subsystem) != DisabledControllers.end())
1878 +
1879 + {
1880 + continue;
1881 }
1882 +
1883 + auto Target = std::format("{}/{}", CGROUP_MOUNTPOINT, Subsystem);
1884 + THROW_LAST_ERROR_IF(
1885 + UtilMount(CGROUP_DEVICE, Target.c_str(), CGROUP_DEVICE, (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME), Subsystem) < 0);
1886 }
1887 }
1888 CATCH_LOG()
src/linux/init/config.h
+1 -1
@@ -406,7 +406,7 @@ void ConfigHandleInteropMessage(
406 const MESSAGE_HEADER* Header,
407 const wsl::linux::WslDistributionConfig& Config);
408
409 -void ConfigInitializeCgroups(void);
409 +void ConfigInitializeCgroups(wsl::linux::WslDistributionConfig& Config);
410
411 int ConfigInitializeInstance(wsl::shared::SocketChannel& Channel, gsl::span<gsl::byte> Buffer, wsl::linux::WslDistributionConfig& Config);
412
test/windows/UnitTests.cpp
+38
@@ -6156,5 +6156,43 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
6156 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"dmesg | grep -iF 'vmbus_send_tl_connect_request'"), 0L);
6157 }
6158
6159 + TEST_METHOD(CGroupv1)
6160 + {
6161 + WSL2_TEST_ONLY();
6162 +
6163 + auto expectedMount = [](const char* path, const wchar_t* expected) {
6164 + auto [out, _] = LxsstuLaunchWslAndCaptureOutput(std::format(L"findmnt -ln '{}' || true", path));
6165 +
6166 + VERIFY_ARE_EQUAL(out, expected);
6167 + };
6168 +
6169 + // Validate that cgroupv2 is mounted by default.
6170 + expectedMount("/sys/fs/cgroup", L"/sys/fs/cgroup cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate\n");
6171 +
6172 + // Validate that setting cgroup=v1 causes unified cgroups to be mounted.
6173 + DistroFileChange wslConf(L"/etc/wsl.conf", false);
6174 + wslConf.SetContent(L"[automount]\ncgroups=v1");
6175 +
6176 + TerminateDistribution();
6177 +
6178 + expectedMount(
6179 + "/sys/fs/cgroup/unified", L"/sys/fs/cgroup/unified cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate\n");
6180 +
6181 + // Validate that the cgroupv1 mounts are present.
6182 + expectedMount("/sys/fs/cgroup/cpu", L"/sys/fs/cgroup/cpu cgroup cgroup rw,nosuid,nodev,noexec,relatime,cpu\n");
6183 +
6184 + // Validate that having cgroup_no_v1=all causes the distribution to fall back to v2.
6185 + WslConfigChange wslConfig(LxssGenerateTestConfig({.kernelCommandLine = L"cgroup_no_v1=all"}));
6186 +
6187 + expectedMount("/sys/fs/cgroup/unified", L"");
6188 + expectedMount("/sys/fs/cgroup", L"/sys/fs/cgroup cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate\n");
6189 +
6190 + auto [dmesg, __] = LxsstuLaunchWslAndCaptureOutput(L"dmesg");
6191 + VERIFY_ARE_NOT_EQUAL(
6192 + dmesg.find(
6193 + L"Distribution has cgroupv1 enabled, but kernel command line has cgroup_no_v1=all. Falling back to cgroupv2"),
6194 + std::wstring::npos);
6195 + }
6196 +
6197 }; // namespace UnitTests
6198 } // namespace UnitTests