Repository navigation
Use unified kernel artifacts VHD layout - #41267
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates WSL2’s kernel packaging to ship a unified artifacts.vhd containing kernel modules plus additional developer artifacts (kernel headers and perf), and wires up the Linux init + Windows service paths to mount these version-matched resources into the distro environment.
Changes:
- Switch kernel artifact packaging/paths from
modules.vhdto a unifiedartifacts.vhdacross build/dev shortcuts and MSI payloads. - Extend Linux init to detect the new versioned/nested artifacts layout and (when present) expose headers via
/lib/modules/<release>/buildand perf via/usr/bin/perf. - Add/adjust Windows tests and documentation to cover the new artifacts behavior and mounting locations.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| UserConfig.cmake.sample | Updates dev-copy and ACL setup to use artifacts.vhd. |
| test/windows/UnitTests.cpp | Updates modules mount expectation and adds a new KernelArtifacts test for headers + perf. |
| src/windows/service/exe/WslCoreVm.cpp | Updates default kernel artifacts VHD filename to artifacts.vhd. |
| src/windows/service/exe/HcsVirtualMachine.cpp | Updates default tools VHD filename to artifacts.vhd. |
| src/shared/inc/lxinitshared.h | Adds env vars for passing headers/perf mount + target paths into distro init. |
| src/linux/init/main.cpp | Adds nested-layout detection and binds headers/perf payloads from the artifacts VHD. |
| src/linux/init/config.cpp | Moves mounts into the distro namespace and sets up /lib/modules/<release>/build and /usr/bin/perf. |
| packages.config | Bumps Microsoft.WSL.Kernel dependency version. |
| msipackage/package.wix.in | Ships artifacts.vhd in the MSI instead of modules.vhd. |
| doc/docs/technical-documentation/boot-process.md | Documents the new headers/perf mounting behavior. |
| CMakeLists.txt | Updates dev compile-time path definition to point at artifacts.vhd. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/linux/init/util.cpp:1745
- UtilMountFile() now stats the source path, but it only checks for existence. If Source exists but is not a regular file (e.g., a directory due to a packaging/layout issue), the subsequent bind mount will fail after creating Destination, potentially leaving an empty executable stub behind (e.g., /usr/bin/perf). Validate the source is a regular file before creating/mutating the destination.
struct stat sourceInfo{};
THROW_LAST_ERROR_IF(stat(Source, &sourceInfo) < 0);
// Is the file is a symlink, delete it since that would break the mount.
if (std::filesystem::is_symlink(Destination))
src/linux/init/config.cpp:1158
- When exposing kernel headers, the code creates /lib/modules//build as a symlink but ignores EEXIST. If the distro already has a stale/wrong symlink (or a leftover file) at that path, WSL will silently keep pointing at the wrong headers even though the correct headers were mounted. Consider removing an existing non-directory entry before creating the symlink, and log a clear warning if a directory prevents creating the symlink.
if (UtilMkdirPath(modulesDir.c_str(), 0755) == 0)
{
const std::string linkPath = modulesDir + "/build";
if ((symlink(headersRoot.c_str(), linkPath.c_str()) < 0) && (errno != EEXIST))
{
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (1)
test/windows/UnitTests.cpp:3021
- KernelArtifacts test compiles a C program with
cc, which adds a dependency on a compiler toolchain being installed in the test distro. This is likely to be missing in minimal images and can make CI runs flaky/unrelated to kernel artifacts. Prefer validating the headers’ presence/recency by checking for expected symbols directly in the mounted header tree (no compiler required).
LxsstuLaunchWsl(
LR"BASH(bash -ec '
d=$(mktemp -d)
trap "rm -rf $d" EXIT
cat > "$d/t.c" <<EOF
2c470ca to
7b17a3e
Compare
2c470ca to
7b17a3e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/linux/init/main.cpp:1634
- The comment says distro init will move the kernel headers mount to
/usr/src/linux-headers-<uname -r>/include, but the code passes a target of/usr/src/linux-headers-<release>and later expects headers under.../include/.... This mismatch is confusing and makes it harder to reason about the mount layout.
// If kernel headers were mounted, move them to a temporary location and pass the desired
// target path to the distro init via an environment variable. Distro init will move the
// mount to /usr/src/linux-headers-<uname -r>/include and create the
// /lib/modules/<release>/build symlink.
test/windows/UnitTests.cpp:3033
stat -Lcdereferences symlinks, so this check can pass even if/usr/bin/perfis just a symlink (or otherwise not a mount point). The laterumount /usr/bin/perfin this test would then fail. It’s more robust to first assert/usr/bin/perfis actually mounted (e.g., via/proc/self/mountinfo) and compare inode/device without-L.
VERIFY_ARE_EQUAL(
LxsstuLaunchWsl(
L"test \"$(stat -Lc %d:%i /usr/bin/perf)\" = \"$(stat -Lc %d:%i /usr/lib/linux-tools/$(uname -r)/bin/perf)\"", nullptr, nullptr, nullptr, nullptr),
0u);
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/linux/init/main.cpp:1634
- This comment says the headers mount is moved to
/usr/src/linux-headers-<uname -r>/include, but the code setsConfig.KernelHeadersTargetto/usr/src/linux-headers-<release>(no/include) and the tests expect/lib/modules/<release>/build/include/...to exist. The comment should match the actual mount target.
// If kernel headers were mounted, move them to a temporary location and pass the desired
// target path to the distro init via an environment variable. Distro init will move the
// mount to /usr/src/linux-headers-<uname -r>/include and create the
// /lib/modules/<release>/build symlink.
src/linux/init/WSLCInit.cpp:858
- In WSLC_MOUNT_MODULES handling, g_state.ModulesMountPoint is assigned before the bind-mount succeeds. If UtilMount() throws after the assignment, ModulesMountPoint remains set and later chroot mounts will attempt to MS_MOVE a non-existent mount, and subsequent WSLC_MOUNT_MODULES requests will hit the assert(!ModulesMountPoint). Also, this path always bind-mounts "//modules" and doesn’t fall back to the legacy flat-layout VHD (modules at filesystem root), which previously worked via the old WSLC_MOUNT::KernelModules path.
assert(!g_state.ModulesMountPoint.has_value());
utsname unameBuffer{};
THROW_LAST_ERROR_IF(uname(&unameBuffer) < 0);
const char* source = wsl::shared::string::FromSpan(Buffer, Message.SourceIndex);
THROW_LAST_ERROR_IF(UtilMount(source, c_kernelModulesVhdMountPoint, "ext4", MS_RDONLY, nullptr, c_defaultRetryTimeout) < 0);
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/linux/init/util.cpp:1745
- Grammar in this comment is incorrect and it refers to the destination path, not the source file.
// Is the file is a symlink, delete it since that would break the mount.
src/linux/init/config.cpp:1192
- The perf shadowing logic only bind-mounts over /usr/bin/perf when it is a regular file. On many distros /usr/bin/perf is a symlink (e.g. alternatives), so the kernel-matched perf won’t be used and the distro perf may be picked up instead, contradicting the intent to always expose the kernel-matched perf when the distro provides one.
struct stat existing{};
if ((lstat(PERF_BINARY_PATH, &existing) == 0) && S_ISREG(existing.st_mode))
{
const std::string perfBinary = target + "/bin/perf";
if (UtilMountFile(perfBinary.c_str(), PERF_BINARY_PATH) < 0)
{
LOG_ERROR("UtilMountFile({}, {}) failed {}", perfBinary, PERF_BINARY_PATH, errno);
}
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/linux/init/config.cpp:1199
lstatclassifies/usr/bin/perfsymlinks asS_IFLNK, so this skips the bind mount for a distro whose perf entry is a symlink to a regular binary. BecauseConfigAppendToPathappends the artifacts directory, that earlier/usr/bin/perfstill wins and the kernel-matched tool is not used. Follow the symlink here;UtilMountFilealready removes a symlink destination before creating the bind-mount target.
if ((lstat(PERF_BINARY_PATH, &existing) == 0) && S_ISREG(existing.st_mode))
test/windows/UnitTests.cpp:3020
- This prefix comparison can accept mismatched headers: for example, header version
6.18.4matches a running release beginning with6.18.40. Require a release-component/suffix boundary after$vso the test actually verifies the package's headers match the running kernel.
case "$(uname -r)" in "$v"*) exit 0 ;; *) exit 8 ;; esac
src/linux/init/main.cpp:3215
- The new flat-layout branch is the backward-compatibility path for existing custom module-only VHDs, but the updated
KernelModulestest only exercises the nested artifact lowerdir and no test supplies a root-levelmodules.dep. Add a flat-layout VHD case that boots with custom kernel/modules and verifies module loading, otherwise this promised compatibility can regress unnoticed.
const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
27a3416 to
4f8fe90
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (4)
src/linux/init/main.cpp:1633
- This comment names the wrong mount target:
KernelHeadersTargetis/usr/src/linux-headers-<release>, and the test accesses itsincludechild through thebuildsymlink. Saying the mount itself moves to.../includemakes the documented flow disagree with the implementation.
// mount to /usr/src/linux-headers-<uname -r>/include and create the
src/linux/init/main.cpp:3215
- The deprecated flat-layout fallback is a compatibility promise introduced here, but the updated
KernelModulestest only verifies the nested default artifact path. Add coverage that boots with a flat module-only VHD and verifies the overlay/module loading path before relying on this fallback for existing custom VHD users.
const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
src/linux/init/main.cpp:3210
- The newly introduced locals in this artifacts-layout block use PascalCase (
Release,ArtifactsBase,NestedModules, and others), while the project convention requires camelCase for local variables. Rename them (for example,releaseandartifactsBase) and update their references.
const std::string Release{UnameBuffer.release};
const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
const std::string NestedModules = ArtifactsBase + "/modules";
src/linux/init/config.cpp:1205
lstatexcludes distributions where/usr/bin/perfis a symlink to their packaged perf binary. Because the bundled tools directory is appended toPATH, that symlink continues to resolve first and users run the stale distro perf. Follow the symlink for classification and bind over its resolved regular-file target so the distro filesystem is restored when the mount disappears.
if ((lstat(PERF_BINARY_PATH, &existing) == 0) && S_ISREG(existing.st_mode))
4f8fe90 to
7d9b8f3
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/linux/init/config.cpp:1199
- A distro-provided
perfcan also be a symlink.lstatclassifies that asS_IFLNK, so this branch skips the bind mount; becauseConfigAppendToPathappends the artifacts directory after/usr/bin, the stale distro entry still wins. Include symlinks here—UtilMountFilealready removes a destination symlink before mounting the matching binary.
if ((lstat(PERF_BINARY_PATH, &existing) == 0) && S_ISREG(existing.st_mode))
src/linux/init/main.cpp:3268
- The updated
KernelModulestest only uses the packaged nestedartifacts.vhd; no test exercises this new legacy flat-layout branch. Since retaining that compatibility is an explicit behavior of this change, add coverage using a VHD withmodules.depat its root and verify that its modules still mount/load without headers or perf.
const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
src/linux/init/main.cpp:3264
- These newly introduced local variables use PascalCase, while WSL's naming convention requires camelCase for locals. Rename these and the other new locals in this block (for example,
release,artifactsBase,nestedModules,nestedLayout,headersSource, andperfSource).
const std::string Release{UnameBuffer.release};
const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
const std::string NestedModules = ArtifactsBase + "/modules";
7d9b8f3 to
de84ca5
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/linux/init/main.cpp:3277
- The legacy compatibility branch is not covered by the updated kernel tests: every test now uses the packaged nested
artifacts.vhd, and no test supplies a flat VHD withmodules.depat its root. Because this fallback preserves existing customkernelModulesconfigurations, add a regression test that boots with the legacy layout and verifies that its modules are mounted or loaded.
const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
Update the packaged kernel dependency from modules.vhd to artifacts.vhd and load kernel modules from the versioned <release>/modules directory. Preserve support for legacy flat module VHDs in the standard WSL boot path and teach WSLC sessions to mount modules from the nested layout. Update development paths and kernel module tests for the renamed VHD and new mount source. Co-authored-by: Copilot <[email protected]> Copilot-Session: a7cc3086-6311-4bc8-a591-49fd0e6b03fd
de84ca5 to
1f307a9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/linux/init/main.cpp:3246
- The new locals
Error,NestedLayout,ModulesLower, andLegacyLayoutalso use PascalCase instead of the required camelCase local-variable convention. Rename them and all subsequent references.
std::error_code Error{};
const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
src/linux/init/main.cpp:3241
- The newly introduced local variables
Release,ArtifactsBase, andNestedModulesuse PascalCase, while WSL local variables use camelCase. Rename these variables and their references accordingly.
This issue also appears on line 3243 of the same file.
const std::string Release{UnameBuffer.release};
const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
const std::string NestedModules = ArtifactsBase + "/modules";
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (4)
src/linux/init/main.cpp:3246
- These newly introduced locals also violate the required camelCase convention. Please rename
Error,NestedLayout,ModulesLower, andLegacyLayoutand update all uses in this branch.
std::error_code Error{};
const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
src/linux/init/main.cpp:3246
- The compatibility branch for legacy flat module VHDs is not exercised by the updated
KernelModulestest, which only verifies the new nested package layout. Please add a regression case using a flat VHD withmodules.depat its root and verify that the overlay uses that root and modules still load; otherwise this promised backward-compatibility path can regress unnoticed.
const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
src/windows/wslcsession/WSLCVirtualMachine.cpp:367
- The new WSLC-specific mount protocol is not directly covered by a test. Existing session tests prove that a session starts, but do not verify that
/lib/modules/$(uname -r)exposes the nested<release>/modulestree. Please add a WSLC session/E2E assertion for a known module artifact (for examplemodules.dep) so this second boot path is validated.
MountModules(m_initChannel, modulesDevice.c_str());
src/linux/init/main.cpp:3241
- These newly introduced local variables use PascalCase, while the WSL coding guideline requires local variables to use camelCase. Please rename
Release,ArtifactsBase, andNestedModules(and update their uses) torelease,artifactsBase, andnestedModules.
This issue also appears on line 3243 of the same file.
const std::string Release{UnameBuffer.release};
const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
const std::string NestedModules = ArtifactsBase + "/modules";
Blue (OneBlue)
left a comment
There was a problem hiding this comment.
LGTM.
Reading this I also realized that the whole ModulesMountPoint logic isn't useful anymore since we always mount the modules after chroot, but that's outside the scope of this change.
I'll clean this up once merged
Summary
artifacts.vhdinstead ofmodules.vhd<release>/modulesdirectory for both standard WSL distributions and WSLC sessionsKernel headers and perf exposure are intentionally deferred to a follow-up PR.
Testing
bin\x64\Debug\test.bat /name:*KernelModules*(1 passed)