mirror of
https://github.com/microsoft/WSL.git
synced 2026-06-01 11:39:37 -05:00
* test: enable virtiofs tests and enable WSLG during testing (#14387) * test: enable virtiofs tests and enable WSLG during testing * test fix --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * chore(distributions): Almalinux auto-update - 20260311 14:52:02 (#14404) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Fix CVE-2026-26127: bump .NET runtime from 10.0.0 to 10.0.4 (#14421) Addresses Dependabot alerts #10 and #11. The Microsoft.NETCore.App.Runtime packages (win-x64 and win-arm64) at version 10.0.0 are vulnerable to a denial of service via out-of-bounds read when decoding malformed Base64Url input (CVSS 7.5 High). Bumped to 10.0.4 which includes the fix. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Notice change from build: 141806547 (#14423) Co-authored-by: WSL notice <noreply@microsoft.com> * Ship initrd.img in MSI using build-time generation via powershell script (#14424) * Ship initrd.img in MSI using build-time generation via tar.exe Replace the install-time CreateInitrd/RemoveInitrd custom actions with a build-time step that generates initrd.img using the Windows built-in tar.exe (libarchive/bsdtar) and ships it directly in the MSI. The install-time approach had a race condition: wsl.exe could launch before the CreateInitrd custom action completed, causing ERROR_FILE_NOT_FOUND for initrd.img. Changes: - Add CMake custom command to generate initrd.img via tar.exe --format=newc - Add initrd.img as a regular file in the MSI tools component - Remove CreateInitrd/RemoveInitrd custom actions from WiX, DllMain, and wslinstall.def - Remove CreateCpioInitrd helper and its tests (no longer needed) - Update pipeline build targets to build initramfs instead of init * pr feedback * more pr feedback * switch to using a powershell script instead of tar.exe * powershell script feedback * hopefully final pr feedback --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * virtiofs: update logic so querying virtiofs mount source does not require a call to the service (#14380) * virtiofs: update logic so querying virtiofs mount source does not require a call to the service * more pr feedback * use std::filesystem::read_symlink * pr feedback and use canonical path in virtiofs symlink * make sure canonical path is always used --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * virtio networking: add support for ipv6 (#14350) * VirtioProxy: Add IPv6 address, gateway, and route support - Add PreferredIpv6Address field and GetBestGatewayV6* methods to NetworkSettings - Extend GetHostEndpointSettings() to discover IPv6 unicast address and gateway - Add UpdateIpv6Address() using ModifyGuestEndpointSettingRequest<IPAddress> - Push IPv6 default route to guest via UpdateDefaultRoute(AF_INET6) - Remove AF_INET6 early return in ModifyOpenPorts, use INETADDR_PORT() - Add EndpointRoute::DefaultRoute() static factory - Pass client_ip_ipv6 in devicehost options (not yet parsed by devicehost) - Remove gateway_ip from devicehost options (only needed for DHCP) - Include IPv6 DNS servers in non-tunneling DNS settings - Add ConfigurationV6 and DnsResolutionAAAA tests * cleanup and add more ipv6 tests * added test coverage and minor updates * clang format * pr feedback * format source * pr feedback * test fixes --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Track `bind` syscall when port is 0 (#14333) * Initial work * . * pr feedback and add unit test * minor tweaks an fix use after free in logging statement * implement PR feedback * hopefully final pr feedback * pr feedback in test function * Address PR feedback: add try/catch to TrackPort and PortZeroBind queue push --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Add iptables to list of apps to install in WSL (#14459) There were instructions already on how to install tcpdump in WSL, but iptables are also needed for the log collection to be complete, so this PR adds instructions on how to also install iptables. Co-authored-by: Andre Muezerie <andremue@linux.microsoft.com> * Update Microsoft.WSL.DeviceHost to version 1.1.39-0 (#14460) Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Moves all Ubuntu distros to the tar-based format (#14463) * Move all supported Ubuntu images to the new format We backported the build pipeline so all current LTSes come out in the new tar-based format * Remove the appx based distros All WSL users can run tar-based distros by now, right? There is no benefit in maintaining both formats. * Enable DNS tunneling for VirtioProxy networking mode (#14461) - Allow VirtioProxy to keep EnableDnsTunneling=true in config, but clear socket-specific options (BestEffortDnsParsing, DnsTunnelingIpAddress) - Suppress dedicated DNS tunneling hvsocket for VirtioProxy; tunneling is handled through the VirtioNetworking device host instead - Set DnsTunneling flag on VirtioNetworkingFlags so the device host knows to tunnel DNS - Expand SWIOTLB kernel cmdline to cover VirtioFs and VirtioProxy - Bump DeviceHost package to 1.1.39-0 - Add VirtioProxy DNS test coverage for tunneling on/off - Skip GuestPortIsReleasedV6 on Windows 10 Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * test: disable LoopbackExplicit due to OS build 29555 regression (#14477) Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Refactor: trim unnecessary DLL deps from COMMON_LINK_LIBRARIES (#14426) * Refactor: trim unnecessary DLL deps from COMMON_LINK_LIBRARIES - Split MSI/Wintrust install functions from wslutil.cpp into install.cpp - Remove MI.lib, wsldeps.lib, msi.lib, Wintrust.lib, computecore.lib, computenetwork.lib, Iphlpapi.lib from COMMON_LINK_LIBRARIES - Add per-target MSI_LINK_LIBRARIES, HCS_LINK_LIBRARIES, SERVICE_LINK_LIBRARIES - Delay-load msi.dll and WINTRUST.dll for wsl.exe and wslg.exe - Result: wslhost, wslrelay, wslcsdk, testplugin lose msi/wintrust startup imports; wsl.exe and wslg.exe defer msi/wintrust loading until actually needed; wslservice is the only target that imports computecore/computenetwork/Iphlpapi * minor fixes to install.cpp that were caught during PR * move to wsl::windows::common::install namespace --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Fix wsl stuck when misconfigured cifs mount presents (#14466) * detach terminal before running mount -a * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * use _exit on error before execv in child process to avoid unintentional resource release * Add regression test * Fix clang format issue * fix all clang format issue * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * resolve ai comments * move test to unit test * Fix string literal * Overwrite fstab to resolve pipeline missing file issue --------- Co-authored-by: Feng Wang <wangfen@microsoft.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update localization and notice scripts to target the branch that the pipeline is running on (#14492) * test: Add arm64 test distro support (#14500) * test: Add arm64 test distro support * update unit test baseline * more test baseline updates --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * test: remove duplicated DNS test coverage (#14522) * test: remove duplicated DNS test coverage * format source --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Fix: Fail and warn the user when --uninstall is given parameters (#14524) Fail and warn the user when --uninstall is given parameters. * Localization change from build: 142847827 (#14525) Co-authored-by: WSL localization <noreply@microsoft.com> * virito net: revert to previous DNS behavior while we debug an issue with DNS over TCP (#14532) Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * devicehost: update to latest devicehost nuget with tracing improvements (#14531) Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Localization change from build: 142949177 (#14542) Co-authored-by: WSL localization <noreply@microsoft.com> * Revert "test: enable virtiofs tests and enable WSLG during testing (#14387)" (#14538) * Revert "test: enable virtiofs tests and enable WSLG during testing (#14387)" * enable wslg for SystemdNoClearTmpUnit test --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Localization change from build: 143033415 (#14553) Co-authored-by: WSL localization <noreply@microsoft.com> * Update cgmanifest to match CMakeLists.txt (#14550) * Update cgmanifest to match CMakeLists.txt * Update CMakeLists.txt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Notice change from build: 143064110 (#14556) Co-authored-by: WSL notice <noreply@microsoft.com> * Update Microsoft.WSL.DeviceHost to version 1.1.48-0 (#40036) * Update Microsoft.WSL.DeviceHost to version 1.1.48-0 (#14575) Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Re-enable WSLG during testing. This reverts commitbf759a092b. * add back config change (will work with new default, but makes test explicit) --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Bump Kali to 2026.1 (#14574) Release notes: https://www.kali.org/blog/kali-linux-2026-1-release/ * socketshared: add maximum message size to avoid very large allocations (#40050) Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Fix: bind interrupted by seccomp signal race (#14554) * Use wait_for_completion_killable instead of the default wait_for_completion_interruptible * retry if kernel does not support the new flag * archlinux: Release 2026.04.01.162669 (#40059) This is an automated release [1]. [1] https://gitlab.archlinux.org/archlinux/archlinux-wsl/-/blob/main/.gitlab-ci.yml * triage: pass action inputs via env vars instead of inline expansion (#40060) Move inputs.comment, inputs.issue, and inputs.token into the env block, consistent with how inputs.previous_body is already handled. This avoids issues with special characters in input values being misinterpreted during shell evaluation. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Set Distro Env HOSTTYPE to aarch64 for ARM64 build (#40048) * Set Distro Env HOSTTYPE to aarch64 for ARM64 build --------- Co-authored-by: Xin Wang (from Dev Box) <xiwang4@microsoft.com> * devicehost: stop re-signing and fix MSI installer failing to replace wsldevicehost.dll (#40075) * devicehost: stop re-signing and fix MSI installer failing to replace wsldevicehost.dll * pr feedback * use MSIRMSHUTDOWN = 1 instead of custom action --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * docs: overhaul Copilot instructions with coding conventions and prompt files (#40113) * docs: overhaul Copilot instructions with coding conventions and prompt files Major update to .github/copilot-instructions.md: - Add coding conventions (naming, error handling, RAII, strings, headers, synchronization, localization, telemetry, formatting, IDL/COM, config) - Add test authoring summary pointing to detailed test.md prompt - Add namespace-to-directory map for top-level namespaces - Add key source files list (defs.h, WslTelemetry.h, wslc.idl, etc.) - Replace clang-format references with .\FormatSource.ps1 - Consolidate duplicate timing info into single reference table New files: - .github/copilot/review.md: Review prompt focused on high-risk areas (ABI breaks, missing localization, resource safety) - .github/copilot/test.md: Test generation prompt with TAEF patterns - .github/copilot/commit.md: Commit message guidelines - .editorconfig: Editor settings for non-C++ files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: address PR review feedback - Scope precomp.h guidance to Windows components (Linux doesn't use it) - Fix review.md reference to .github/copilot-instructions.md - Restore clang-format as Linux formatting option alongside FormatSource.ps1 - Note FormatSource.ps1 requires cmake . first - Fix en-us -> en-US casing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Suppress MSI-initiated reboots during Store updates (#40079) When the WSL MSIX package is updated via the Microsoft Store, the WslInstaller service automatically upgrades the MSI package by calling MsiInstallProduct. This call was made with INSTALLUILEVEL_NONE (silent install) but without setting the REBOOT=ReallySuppress property. Per Windows Installer documentation, when a silent install encounters files in use and REBOOT is not suppressed, the system reboots automatically without any user prompt. This could cause unexpected machine restarts after a Store update when WSL binaries (e.g. wslservice.exe) were in use during the upgrade. Every deployment script in the repo already passes /norestart to msiexec (deploy-to-host.ps1, deploy-to-vm.ps1, install-latest-wsl.ps1, test-setup.ps1), but the programmatic MsiInstallProduct path used by the WslInstaller service lacked the equivalent property. This change: - Always appends REBOOT=ReallySuppress to MsiInstallProduct arguments in UpgradeViaMsi, preventing Windows Installer from ever initiating a system restart during install/upgrade. - Switches UninstallViaMsi from MsiConfigureProduct to MsiConfigureProductEx so we can pass REBOOT=ReallySuppress during uninstall as well. - Propagates ERROR_SUCCESS_REBOOT_REQUIRED (3010) to callers instead of swallowing it. User-facing paths (wsl --update, wsl --uninstall) print a reboot-needed message to stderr. The background WslInstaller service silently treats 3010 as success since it has no console. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix UnicodeEncodeError in create-release.py on cp1252 consoles (#40127) * Fix UnicodeEncodeError in create-release.py on cp1252 consoles Reconfigure stdout/stderr with errors='backslashreplace' so commit messages containing characters outside the console code-page (e.g. U+2225) are escaped instead of crashing the script. Also redirect the 'failed to extract PR number' warning to stderr for consistency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix return type annotation for get_github_pr_message() Update the return annotation from str to tuple[str | None, str | None] to match the actual return values (pr_body, pr_number). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * build: add Source Link to embed GitHub source mappings in PDBs (#40055) * build: add Source Link to embed GitHub source mappings in PDBs * Gate Source Link on pipeline builds only Only generate sourcelink.json and pass /SOURCELINK to the linker when PIPELINE_BUILD_ID is defined (i.e. during CI pipeline builds). This avoids unnecessary Source Link artifacts in local developer builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add /attachdebugger option to automatically launch WinDbgX for test debugging (#40116) * Add /attachdebugger option to automatically launch WinDbgX for test debugging When /attachdebugger is passed to test.bat, run-tests.ps1 now: - Starts te.exe with /waitfordebugger in the background - Polls for the TE.ProcessHost.exe child process via WMI - Launches WinDbgX attached directly to the test host PID - With /inproc, attaches to TE.exe itself instead This replaces the manual workflow of running /waitfordebugger, reading the PID from the output, and launching WinDbgX separately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * run-tests: use /inproc with /attachdebugger, simplify exit Per review feedback from @OneBlue: - Add /inproc when /attachdebugger is set so WinDbgX attaches directly to TE.exe instead of polling for TE.ProcessHost.exe - Simplify exit to pass through TE.exe exit code directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: update /attachdebugger to reflect /inproc behavior The script now always adds /inproc, so update the README to match: WinDbgX attaches directly to TE.exe, no ProcessHost polling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Replace pre-commit hook with CMake-generated clang-format check (#40136) * Replace pre-commit hook with CMake-generated clang-format check Replace the old pre-commit hook that shelled out to PowerShell and never blocked commits (-NoFail) with a CMake-generated hook that calls clang-format directly on staged C/C++ files. - Add tools/hooks/pre-commit.in as a CMake template - CMake resolves the clang-format path at configure time via LLVM_INSTALL_DIR, matching the existing FormatSource.ps1.in pattern - Hook blocks commits on formatting errors, skips gracefully if clang-format is not available (cmake not yet run) - ~5x faster than the old PowerShell approach (~0.5s vs ~2.6s) * Make pre-commit hook behavior configurable via WSL_PRE_COMMIT_MODE Add WSL_PRE_COMMIT_MODE CMake cache variable with three modes: - warn (default): report formatting issues without blocking commit - error: block commit when formatting issues are found - fix: auto-format files and re-stage them Also addresses PR feedback: - Generate hook into build tree, copy to source tree for out-of-source builds - Use repo-local tools/clang-format.exe instead of LLVM_INSTALL_DIR path - Use @ONLY in configure_file to avoid shell variable substitution issues - Document modes in dev-loop.md and UserConfig.cmake.sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Split x64/arm64 builds into parallel pipeline stages (#14497) * Split x64/arm64 builds into parallel pipeline stages Restructure the CI/CD pipeline to build x64 and arm64 in parallel instead of sequentially, reducing end-to-end build time. Pipeline shapes: - PR: build_x64 ∥ build_arm64 → test (uses installer.msix directly) - Nightly: build_x64 ∥ build_arm64 → package → test (dev-cert bundle) - Release: build_x64 ∥ build_arm64 → package → test (ESRP-signed bundle) Key changes: - Extract shared build-job.yml template parameterized by platform - Add package-stage.yml that creates msixbundle from both platform artifacts, ESRP-signs for release, dev-cert signs for nightly - PR tests run immediately after x64 build using installer.msix (no package stage, no bundle needed) - Release/nightly tests wait for the package stage and test the real signed bundle that gets published - CloudTest configs are parameterized: release tests pull the bundle from the [package] artifact, PR tests use installer.msix from [drop] - arm64 + formatting checks always run in parallel with x64 but don't block the PR test gate - CodeQL runs in the arm64 stage (off the critical path) - flight-stage and nuget-stage updated for new stage names * formatting * Remove redundant runtime conditions on release-only tasks These tasks are already wrapped in compile-time conditionals which prevent them from being added to the pipeline definition for non-release builds. The runtime condition checks can never evaluate to false at that point, so they are pure noise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback from OneBlue - Remove wslcsdk NuGet staging (wslc is not in master) - Always include [package] provider in TestMap.xml.in instead of conditionally injecting via PACKAGE_PROVIDER_BLOCK cmake variable. PR builds simply use [drop] as TEST_PACKAGE_PROVIDER; the [package] provider exists but is unused. - Add BUNDLE_ONLY cmake option so the package pipeline stage reuses cmake's existing bundle target instead of forking makeappx logic. This locks the SDK version via cmake (CMAKE_SYSTEM_VERSION) and keeps bundle creation logic in one place (msixinstaller/CMakeLists.txt). The pipeline now copies msix files to expected paths, runs a fast cmake configure with -DBUNDLE_ONLY=TRUE, and builds the bundle target. - Remove dead NuGet binary restore step in package stage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review feedback - Fix PACKAGE_VERSION regex: escape dots so only A.B.C.D is accepted - Consolidate CMake defaults (build type, config types, output dir) before BUNDLE_ONLY block to avoid duplication - Nightly tests now use the full bundle from the package stage instead of installer.msix (new INCLUDE_PACKAGE_STAGE cmake variable) - Package stage reuses version output from build stage instead of recomputing it Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Mask console-getty.service to prevent multi-distro failures (#13595) (#14490) * Mask console-getty.service to prevent multi-distro failures (#13595) When multiple WSL distros run concurrently, /dev/tty devices are shared at the VM level. The second distro's console-getty.service fails because the tty is already held by the first, causing systemd to report failed units and triggering user@UID.service failures. Mask console-getty.service during WSL systemd unit generation, similar to the existing masking of networkd-wait-online. This service provides no value in WSL since users don't connect to the underlying tty. Fixes #13595 * format source * pr feedback --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> * Fix random "ERROR_FILE_NOT_FOUND" when unmounting with absolute path (#40092) Fix ERROR_FILE_NOT_FOUND when unmounting a vhd with absolute path after the vm timeouts. * Refactor tests: use TAEF metadata for WSL version filtering (#40140) Cherry-pick WSL1/WSL2 test changes from9c4dba91(feature/wsl-for-apps). Replace runtime WSL1_TEST_ONLY()/WSL2_TEST_ONLY() skip macros with WSL1_TEST_METHOD()/WSL2_TEST_METHOD() TAEF metadata macros. This moves version filtering to the test runner level via /select: queries, so inapplicable tests are excluded entirely instead of appearing as skipped. Updated files: - test/windows/Common.h: New macros + removed old skip macros - test/windows/*.cpp: Converted all test methods - tools/test/run-tests.ps1: Auto-add /select: when no user filter - cloudtest/TestGroup.xml.in: Add version filter to TAEF args - test/README.md: Document new macros Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix mount test build: use WSL2_TEST_METHOD macro for AbsolutePathVhdUnmountAfterVMTimeout (#40162) Replace raw TEST_METHOD + WSL2_TEST_ONLY() with WSL2_TEST_METHOD macro, consistent with the refactor in #40140. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix various build issues if the repo path contains a space (#40160) * Add git entries for libarchive and boost in cgmanifest.json (#40155) The OSPO notice@0 task cannot resolve licenses for components registered with type 'other' (source tarballs). Add parallel type 'git' entries pointing at the GitHub repos with the exact commit SHAs for the release tags (v3.7.7 and boost-1.90.0). The existing 'other' entries are kept for accurate provenance tracking of the actual tarball downloads used by CMake FetchContent. The 'git' entries enable the notice generator to look up licenses from ClearlyDefined. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fixes to route mirroring (#40099) * route fixes from protonvpn testing * add unit test, fix log * ai code review * format with vs 2022 * review * remove optional has_value checks * harden contracts for to, via having or not having values * update IsOnlink check * fix constructor parameters * fix build --------- Co-authored-by: Catalin-Emil Fetoiu <cfetoiu@microsoft.com> * re enable (#40156) Co-authored-by: Catalin-Emil Fetoiu <cfetoiu@microsoft.com> * fix wrong variable checking (#40185) Co-authored-by: Xin Wang (from Dev Box) <xiwang4@microsoft.com> * Add warning to collect-wsl-logs.ps1 to be displayed when tool is missing (#14447) * Added warning message to collect-wsl-logs.ps1 * Add warning to collect-wsl-logs.ps1 to be displayed when tool is missing On executing the log collection script, it will first confirm the required tools tcpdump and iptables are installed. For each tool missing a warning is displayed, reminding the user that the tool should be installed prior to executing the script to get a more complete log collection. * Eliminated reduntant parameter and added handling for situation where WSL might be unavailable * Remove try/catch from function's implementation --------- Co-authored-by: Andre Muezerie <andremue@linux.microsoft.com> * Bump Microsoft.NETCore.App.Runtime to 10.0.6 (CVE-2026-32178) (#40207) Update Microsoft.NETCore.App.Runtime.win-x64 and Microsoft.NETCore.App.Runtime.win-arm64 from 10.0.4 to 10.0.6 to resolve CVE-2026-32178 (.NET Spoofing Vulnerability). Fixes Dependabot alerts #12 and #13. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix VHD ownership after cross-volume move to prevent E_ACCESSDENIED (#40159) * Fix VHD ownership after cross-volume move to prevent E_ACCESSDENIED When MoveDistribution moves a VHD across volumes, MoveFileEx copies the file and the new file's owner may not be the user's SID. This causes HcsGrantVmAccess to fail with E_ACCESSDENIED when later launching the distro, because the impersonated user lacks WRITE_DAC on the file (only implicitly granted to the owner). Fix by explicitly setting the VHD owner to the user's SID after the move, matching what CreateVhd already does at creation time. Uses handle-based SetSecurityInfo with FILE_FLAG_OPEN_REPARSE_POINT to avoid TOCTOU races and symlink following. Also fixes a pre-existing build break in MountTests.cpp from the test refactor (WSL2_TEST_ONLY -> WSL2_TEST_METHOD). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve original VHD owner instead of using GetUserSid() Instead of unconditionally setting the VHD owner to the caller's SID after a cross-volume move, read the original owner before the move and restore it afterward. This avoids changing ownership to someone who didn't originally own the file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use {} format specifier instead of %s in Linux LOG_ERROR path (#40227) The Linux #else branch of SocketChannel.h uses LOG_ERROR which expects fmt-style {} placeholders, but the channel name was using printf-style %s. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update MoveVhdOwnership test to WSL2 only (#40223) * Initial plan * Update MoveVhdOwnership test to WSL2 only Agent-Logs-Url: https://github.com/microsoft/WSL/sessions/65d80936-791c-411b-8da2-d8c1bc06e651 Co-authored-by: benhillis <17727402+benhillis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: benhillis <17727402+benhillis@users.noreply.github.com> * fix: use ssize_t for readlinkat return value in p9file.cpp (#40226) readlinkat() returns ssize_t, not int. On 64-bit systems this could silently truncate the return value for very long symlink targets. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * cleanup: extract SkipSignal helper to deduplicate signal skip lists (#40228) * cleanup: extract SkipSignal helper to deduplicate signal skip lists UtilSaveSignalHandlers and UtilSetSignalHandlers had identical switch statements for skipping non-settable signals. Extract a shared helper to keep the skip list in one place. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address review: document SkipSignal signals and update function headers Add comments explaining why each signal is skipped (POSIX non-settable, NPTL internal signals 32-34, SIGHUP handled separately). Update UtilSaveSignalHandlers and UtilSetSignalHandlers descriptions to reference SkipSignal() instead of just mentioning SIGHUP. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use correct GetAddrInfoTestEntry handler for get_addr_info test (#40225) The get_addr_info test entry was incorrectly mapped to GetSetIdTestEntry instead of GetAddrInfoTestEntry, causing the wrong test handler to run. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Upload binaries when publishing symbols (#40208) * Experiment with symbols * Don't delete dlls * Cleanup diff * Fix clang-format violation in UnitTests.cpp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Include VirtioProxy in IsDnsTunnelingSupported assert The feature branch adds NetworkingMode::VirtioProxy which supports DNS tunneling, but master's assert in IsDnsTunnelingSupported() only expected Nat or Mirrored. This would fire in debug builds when VirtioProxy is selected (e.g. after NAT fallback). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add wslcsdk.dll and TestData to test artifacts in build-job.yml Master's pipeline refactor missed copying wslcsdk.dll (runtime dependency of wsltests.dll) and the Microsoft.WSL.TestData package to the test artifact staging area. This caused all test stages to fail with ERROR_MOD_NOT_FOUND when loading wsltests.dll. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use dynamic package path for WSLC tests The WSLC test group template was hardcoding the msixbundle path, which doesn't exist in PR builds where the package stage is skipped. Use the same TEST_PACKAGE_PROVIDER/TEST_PACKAGE_PATH/TEST_PACKAGE_FILE variables as the WSL1/WSL2 test template, so WSLC tests use installer.msix in PR builds and the bundle in release/nightly builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Stage wslcsdk.lib and wslcsdk.dll for Containers nuget package The package stage needs wslcsdk.lib and wslcsdk.dll to pack Microsoft.WSL.Containers.nuspec, but the split pipeline doesn't build wslcsdk in the package stage. Stage these files as build artifacts from both x64 and arm64 build jobs, then copy them to the expected bin paths in the package stage before nuget pack. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: AlmaLinux Autobot <107999298+almalinuxautobot@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Blue <OneBlue@users.noreply.github.com> Co-authored-by: WSL notice <noreply@microsoft.com> Co-authored-by: Daman Mulye <daman_mulye@hotmail.com> Co-authored-by: Andre Muezerie <108841174+andremueiot@users.noreply.github.com> Co-authored-by: Andre Muezerie <andremue@linux.microsoft.com> Co-authored-by: Carlos Nihelton <carlos.santanadeoliveira@canonical.com> Co-authored-by: Feng Wang <wang6922@outlook.com> Co-authored-by: Feng Wang <wangfen@microsoft.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: g0tmi1k <535942+g0tmi1k@users.noreply.github.com> Co-authored-by: Arch Linux Technical User <65091038+archlinux-github@users.noreply.github.com> Co-authored-by: wangxin12 <sbwap@vip.qq.com> Co-authored-by: Xin Wang (from Dev Box) <xiwang4@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: FetoiuCatalin <fetoiucatalin@gmail.com> Co-authored-by: Catalin-Emil Fetoiu <cfetoiu@microsoft.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: benhillis <17727402+benhillis@users.noreply.github.com>
1486 lines
52 KiB
C++
1486 lines
52 KiB
C++
/*++
|
|
|
|
Copyright (c) Microsoft. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
MountTests.cpp
|
|
|
|
Abstract:
|
|
|
|
This file contains test cases for the disk mounting logic.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include "Common.h"
|
|
|
|
#define TEST_MOUNT_DISK L"TestDisk.vhd"
|
|
#define TEST_MOUNT_VHD L"TestVhd.vhd"
|
|
#define TEST_UNMOUNT_VHD_DNE L"TestVhdNotHere.vhd"
|
|
#define TEST_MOUNT_NAME L"testmount"
|
|
|
|
#define SKIP_UNSUPPORTED_ARM64_MOUNT_TEST() \
|
|
if constexpr (wsl::shared::Arm64) \
|
|
{ \
|
|
WSL_TEST_VERSION_REQUIRED(27653); \
|
|
}
|
|
|
|
namespace MountTests {
|
|
|
|
// Disks sometimes take a bit of time to become available when attached back to the host.
|
|
constexpr auto c_diskOpenTimeoutMs = 120000;
|
|
|
|
class SetAutoMountPolicy
|
|
{
|
|
public:
|
|
SetAutoMountPolicy() = delete;
|
|
SetAutoMountPolicy(const SetAutoMountPolicy&) = delete;
|
|
SetAutoMountPolicy& operator=(const SetAutoMountPolicy&) = delete;
|
|
|
|
SetAutoMountPolicy(SetAutoMountPolicy&& Other) = default;
|
|
SetAutoMountPolicy& operator=(SetAutoMountPolicy&&) = default;
|
|
|
|
SetAutoMountPolicy(bool Enable) : PreviousState(GetAutoMountState())
|
|
{
|
|
if (Enable != PreviousState)
|
|
{
|
|
SetAutoMountState(Enable);
|
|
}
|
|
else
|
|
{
|
|
PreviousState.reset();
|
|
}
|
|
}
|
|
|
|
~SetAutoMountPolicy()
|
|
{
|
|
if (PreviousState.has_value())
|
|
{
|
|
SetAutoMountState(PreviousState.value());
|
|
}
|
|
}
|
|
|
|
private:
|
|
static bool GetAutoMountStateFromOutput(const std::wstring& Output)
|
|
{
|
|
if (Output.find(L"Automatic mounting of new volumes enabled") != std::wstring::npos)
|
|
{
|
|
return true;
|
|
}
|
|
else if (Output.find(L"Automatic mounting of new volumes disabled") != std::wstring::npos)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LogError("Unexpected diskpart output: '%s'", Output.c_str());
|
|
VERIFY_FAIL(L"Failed to parse diskpart's output");
|
|
return false;
|
|
}
|
|
|
|
static bool GetAutoMountState()
|
|
{
|
|
std::wstring cmd = L"diskpart.exe";
|
|
return GetAutoMountStateFromOutput(LxsstuLaunchCommandAndCaptureOutput(cmd.data(), "automount\r\n").first);
|
|
}
|
|
|
|
static void SetAutoMountState(bool Enabled)
|
|
{
|
|
LogInfo("Setting automount policy to %i", Enabled);
|
|
|
|
std::wstring cmd = L"diskpart.exe";
|
|
const auto input = std::string("automount ") + (Enabled ? "enable\r\n" : "disable\r\n");
|
|
auto [output, _] = LxsstuLaunchCommandAndCaptureOutput(cmd.data(), input.c_str());
|
|
|
|
VERIFY_ARE_EQUAL(Enabled, GetAutoMountStateFromOutput(output));
|
|
}
|
|
|
|
std::optional<bool> PreviousState;
|
|
};
|
|
|
|
class MountTests
|
|
{
|
|
std::wstring DiskDevice;
|
|
std::wstring VhdDevice;
|
|
wil::unique_tokeninfo_ptr<TOKEN_USER> User = wil::get_token_information<TOKEN_USER>();
|
|
std::unique_ptr<wsl::windows::common::security::privilege_context> PrivilegeState;
|
|
DWORD DiskNumber = 0;
|
|
SetAutoMountPolicy AutoMountPolicy{false};
|
|
|
|
struct ExpectedMountState
|
|
{
|
|
size_t PartitionIndex;
|
|
std::optional<std::wstring> Type;
|
|
std::optional<std::wstring> Options;
|
|
};
|
|
|
|
struct ExpectedDiskState
|
|
{
|
|
std::wstring Path;
|
|
std::vector<ExpectedMountState> Mounts;
|
|
};
|
|
|
|
WSL_TEST_CLASS(MountTests)
|
|
|
|
TEST_CLASS_SETUP(TestClassSetup)
|
|
{
|
|
VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
|
|
|
|
if (!LxsstuVmMode())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Needed to open processes under te
|
|
PrivilegeState = wsl::windows::common::security::AcquirePrivilege(SE_DEBUG_NAME);
|
|
|
|
// Create a 20MB vhd for testing mounting passthrough disks
|
|
DeleteFileW(TEST_MOUNT_DISK);
|
|
|
|
try
|
|
{
|
|
LxsstuLaunchPowershellAndCaptureOutput(L"New-Vhd -Path " TEST_MOUNT_DISK " -SizeBytes 20MB");
|
|
}
|
|
CATCH_LOG()
|
|
|
|
// Mount it in Windows
|
|
auto [output, _] = LxsstuLaunchPowershellAndCaptureOutput(L"(Mount-VHD " TEST_MOUNT_DISK " -PassThru | Get-Disk).Number");
|
|
|
|
Trim(output);
|
|
DiskNumber = std::stoul(output);
|
|
|
|
// Construct the disk path
|
|
DiskDevice = L"\\\\.\\PhysicalDrive" + output;
|
|
LogInfo("Mounted the passthrough test vhd as %ls", DiskDevice.c_str());
|
|
|
|
// Create a 20MB vhd for testing mount --vhd
|
|
DeleteFileW(TEST_MOUNT_VHD);
|
|
|
|
LxsstuLaunchPowershellAndCaptureOutput(L"New-Vhd -Path " TEST_MOUNT_VHD " -SizeBytes 20MB");
|
|
|
|
VhdDevice = wsl::windows::common::filesystem::GetFullPath(TEST_MOUNT_VHD);
|
|
LogInfo("Create mount --vhd test vhd as %ls", VhdDevice.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
// Uninitialize the tests.
|
|
TEST_CLASS_CLEANUP(TestClassCleanup)
|
|
{
|
|
if (LxsstuVmMode())
|
|
{
|
|
PrivilegeState.reset();
|
|
|
|
LxsstuLaunchWsl(L"--unmount");
|
|
WaitForDiskReady();
|
|
|
|
try
|
|
{
|
|
LxsstuLaunchPowershellAndCaptureOutput(L"Dismount-Vhd -Path " TEST_MOUNT_DISK);
|
|
}
|
|
CATCH_LOG()
|
|
|
|
DeleteFileW(TEST_MOUNT_DISK);
|
|
DeleteFileW(TEST_MOUNT_VHD);
|
|
}
|
|
|
|
VERIFY_NO_THROW(LxsstuUninitialize(false));
|
|
return true;
|
|
}
|
|
|
|
TEST_METHOD_CLEANUP(MethodCleanup)
|
|
{
|
|
if (!LxsstuVmMode())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
LxssLogKernelOutput();
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount"), (DWORD)0);
|
|
WaitForDiskReady();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Attach a vhd, but don't mount it
|
|
WSL2_TEST_METHOD(TestBareMountVhd)
|
|
{
|
|
TestBareMountImpl(true);
|
|
}
|
|
|
|
// Mount one partition using --vhd and validate that options are correctly applied
|
|
WSL2_TEST_METHOD(TestMountOnePartitionVhd)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountOnePartitionImpl(true);
|
|
}
|
|
|
|
// Mount two partitions using --vhd on the same disk
|
|
WSL2_TEST_METHOD(TestMountTwoPartitionsVhd)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountTwoPartitionsImpl(true);
|
|
}
|
|
|
|
// Run a bare mount using --vhd and then mount a partition
|
|
WSL2_TEST_METHOD(TestAttachThenMountVhd)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestAttachThenMountImpl(true);
|
|
}
|
|
|
|
// Mount the disk directly
|
|
WSL2_TEST_METHOD(TestMountWholeDiskVhd)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountWholeDiskImpl(true);
|
|
}
|
|
|
|
// Test that mount state is deleted on shutdown (--vhd)
|
|
WSL2_TEST_METHOD(TestMountStateIsDeletedOnShutdownVhd)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountStateIsDeletedOnShutdownImpl(true);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(TestFilesystemDetectionWholeDisk)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestFilesystemDetectionWholeDiskImpl(false);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(TestFilesystemDetectionWholeDiskVhd)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestFilesystemDetectionWholeDiskImpl(true);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(TestMountTwoPartitionsWithDetection)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountTwoPartitionsWithDetectionImpl(false);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(TestMountTwoPartitionsWithDetectionVhd)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountTwoPartitionsWithDetectionImpl(true);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(TestFilesystemDetectionFail)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestFilesystemDetectionFailImpl(false);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(TestFilesystemDetectionFailVhd)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestFilesystemDetectionFailImpl(true);
|
|
}
|
|
|
|
// Test specifying a mount name for a vhd
|
|
WSL2_TEST_METHOD(SpecifyMountName)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
const auto mountCommand = L"--mount " + VhdDevice + L" --vhd --name " + TEST_MOUNT_NAME;
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition
|
|
FormatDisk({L"ext4"}, true);
|
|
|
|
// Mount it
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
|
|
auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
const std::wstring diskName(TEST_MOUNT_NAME);
|
|
auto mountTarget = L"/mnt/wsl/" + diskName;
|
|
|
|
ValidateMountPoint(disk + L"1", mountTarget);
|
|
|
|
ValidateDiskState({VhdDevice, {{1, {}, {}}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + VhdDevice), (DWORD)0);
|
|
WaitForDiskReady();
|
|
|
|
// Validate that the mount folder was deleted
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -e " + mountTarget), (DWORD)1);
|
|
|
|
// Mount the same partition, but with a specific mount option
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1 --options \"data=ordered\""), (DWORD)0);
|
|
|
|
// Validate that the mount option was properly passed
|
|
disk = GetBlockDeviceInWsl();
|
|
ValidateMountPoint(disk + L"1", mountTarget, L"data=ordered");
|
|
ValidateDiskState({VhdDevice, {{1, {}, L"data=ordered"}}}, keepAlive);
|
|
|
|
// Let the VM timeout
|
|
WaitForVmTimeout(keepAlive);
|
|
|
|
// Validate that the disk is re-mounted in the same place
|
|
disk = GetBlockDeviceInWsl();
|
|
ValidateMountPoint(disk + L"1", mountTarget);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + VhdDevice), (DWORD)0);
|
|
WaitForDiskReady();
|
|
}
|
|
|
|
// Test ensuring that name collision detection works in --mount --name
|
|
WSL2_TEST_METHOD(SpecifyMountNameCollision)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
const auto mountCommand = L"--mount " + VhdDevice + L" --vhd --name " + TEST_MOUNT_NAME;
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition and one fat partitions
|
|
FormatDisk({L"ext4", L"vfat"}, true);
|
|
|
|
// Attempt to mount both partitions with the same mount name; partition 2 should fail
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
|
|
VERIFY_ARE_NOT_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 2 --type vfat"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount first mount did succeed
|
|
const std::wstring diskName(TEST_MOUNT_NAME);
|
|
ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + diskName, {}, L"ext4");
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + VhdDevice), (DWORD)0);
|
|
WaitForDiskReady();
|
|
}
|
|
|
|
// Test that multiple partitions can be mounted with --name
|
|
WSL2_TEST_METHOD(SpecifyMountNameTwoPartitions)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
const auto mountCommandOne = L"--mount " + VhdDevice + L" --vhd --name " + TEST_MOUNT_NAME + L"p1";
|
|
const auto mountCommandTwo = L"--mount " + VhdDevice + L" --vhd --name " + TEST_MOUNT_NAME + L"p2";
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition and one fat partitions
|
|
FormatDisk({L"ext4", L"vfat"}, true);
|
|
|
|
// Attempt to mount both partitions with the same mount name; partition 2 should fail
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommandOne + L" --partition 1"), (DWORD)0);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommandTwo + L" --partition 2 --type vfat"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount first mount did succeed
|
|
const std::wstring diskName(TEST_MOUNT_NAME);
|
|
ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + diskName + L"p1", {}, L"ext4");
|
|
ValidateMountPoint(disk + L"2", L"/mnt/wsl/" + diskName + L"p2", {}, L"vfat");
|
|
ValidateDiskState({VhdDevice, {{1, {}, {}}, {2, {L"vfat"}, {}}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + VhdDevice), (DWORD)0);
|
|
WaitForDiskReady();
|
|
}
|
|
|
|
// Test relative mount/unmounting of a --vhd
|
|
WSL2_TEST_METHOD(RelativePathUnmount)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " TEST_MOUNT_VHD L" --vhd --bare"), (DWORD)0);
|
|
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " TEST_MOUNT_VHD), (DWORD)0);
|
|
}
|
|
|
|
// Test relative mount/unmounting of a --vhd that does not exist
|
|
WSL2_TEST_METHOD(RelativePathUnmountNoFileExists)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " TEST_MOUNT_VHD L" --vhd --bare"), (DWORD)0);
|
|
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Try unmounting a VHD not created and verify that it was not successful
|
|
VERIFY_ARE_NOT_EQUAL(LxsstuLaunchWsl(L"--unmount " TEST_UNMOUNT_VHD_DNE), (DWORD)0);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(AbsolutePathVhdUnmount)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " TEST_MOUNT_VHD L" --vhd --bare"), (DWORD)0);
|
|
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
const auto absolutePath = std::filesystem::absolute(TEST_MOUNT_VHD);
|
|
|
|
// Validate that the vhd path doesn't start with '\\?'
|
|
VERIFY_IS_FALSE(absolutePath.wstring().starts_with(L"\\"));
|
|
|
|
// Validate the unmounting by absolute path is successful
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + absolutePath.wstring()), (DWORD)0);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(AbsolutePathVhdUnmountAfterVMTimeout)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " TEST_MOUNT_VHD L" --vhd --bare"), (DWORD)0);
|
|
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
WaitForVmTimeout(keepAlive);
|
|
|
|
const auto absolutePath = std::filesystem::absolute(TEST_MOUNT_VHD);
|
|
|
|
// Validate that the vhd path doesn't start with '\\?'
|
|
VERIFY_IS_FALSE(absolutePath.wstring().starts_with(L"\\"));
|
|
|
|
// Validate the unmounting by absolute path is successful
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + absolutePath.wstring()), (DWORD)0);
|
|
}
|
|
|
|
// Attach a disk, but don't mount it
|
|
WSL2_TEST_METHOD(TestBareMount)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestBareMountImpl(false);
|
|
}
|
|
|
|
// Validate that attached disks that were offline when attached
|
|
// are still offline when detached
|
|
WSL2_TEST_METHOD(TestOfflineDiskStaysOffline)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
auto diskHandle = wsl::windows::common::disk::OpenDevice(DiskDevice.c_str(), GENERIC_ALL, c_diskOpenTimeoutMs);
|
|
wsl::windows::common::disk::SetOnline(diskHandle.get(), false);
|
|
diskHandle.reset();
|
|
|
|
ValidateOffline(true);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --bare"), (DWORD)0);
|
|
|
|
auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
ValidateDiskState({DiskDevice, {}}, keepAlive);
|
|
|
|
disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_FALSE(GetBlockDeviceMount(disk).has_value());
|
|
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
|
|
ValidateOffline(true);
|
|
diskHandle = wsl::windows::common::disk::OpenDevice(DiskDevice.c_str(), GENERIC_ALL, c_diskOpenTimeoutMs);
|
|
wsl::windows::common::disk::SetOnline(diskHandle.get(), true);
|
|
diskHandle.reset();
|
|
|
|
ValidateOffline(false);
|
|
}
|
|
|
|
// Mount one partition and validate that options are correctly applied
|
|
WSL2_TEST_METHOD(TestMountOnePartition)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountOnePartitionImpl(false);
|
|
}
|
|
|
|
// Mount two partitions on the same disk
|
|
WSL2_TEST_METHOD(TestMountTwoPartitions)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountTwoPartitionsImpl(false);
|
|
}
|
|
|
|
// Mount a fat partition
|
|
WSL2_TEST_METHOD(TestMountFatPartition)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ntfs partition
|
|
FormatDisk({L"vfat"}, false);
|
|
|
|
// Mount it
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1" + L" --type vfat"), (DWORD)0);
|
|
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(DiskDevice);
|
|
Trim(trimmedDiskName);
|
|
auto mountTarget = L"/mnt/wsl/" + trimmedDiskName + L"p1";
|
|
ValidateMountPoint(disk + L"1", mountTarget, {}, L"vfat");
|
|
ValidateDiskState({DiskDevice, {{1, {L"vfat"}, {}}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
WaitForDiskReady();
|
|
ValidateOffline(false);
|
|
}
|
|
|
|
// Mount the disk directly
|
|
WSL2_TEST_METHOD(TestMountWholeDisk)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountWholeDiskImpl(false);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(TestMountStateIsDeletedOnShutdown)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestMountStateIsDeletedOnShutdownImpl(false);
|
|
}
|
|
|
|
// Validate that a failure to mount a disk isn't fatal
|
|
WSL2_TEST_METHOD(TestMountFailuresArentFatal)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition
|
|
FormatDisk({L"ext4"}, false);
|
|
|
|
// Mount it
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --type ext4"), (DWORD)0);
|
|
auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
ValidateDiskState({DiskDevice, {{1, {L"ext4"}, {}}}}, keepAlive);
|
|
|
|
// Check that the disk is still mounted properly (ValidateDiskState restarts the VM)
|
|
disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
std::wstring trimmedDiskName(DiskDevice);
|
|
Trim(trimmedDiskName);
|
|
ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
|
|
|
|
// Wait for vm timeout
|
|
WaitForVmTimeout(keepAlive);
|
|
|
|
// Voluntarily set a wrong filesystem in the saved state
|
|
auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
|
|
auto subKeys = wsl::windows::common::registry::EnumKeys(key.get(), KEY_ALL_ACCESS);
|
|
VERIFY_ARE_EQUAL(subKeys.size(), 1);
|
|
|
|
wsl::windows::common::registry::WriteString(subKeys.begin()->second.get(), L"1", L"Type", L"badfs");
|
|
keepAlive.Set();
|
|
|
|
// The disk should be present
|
|
disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// But not mounted
|
|
ValidateMountPoint(disk + L"1", {});
|
|
|
|
// Now put a bad disk path, so that the disk fails to attach
|
|
WaitForVmTimeout(keepAlive);
|
|
key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
|
|
subKeys = wsl::windows::common::registry::EnumKeys(key.get(), KEY_ALL_ACCESS);
|
|
VERIFY_ARE_EQUAL(subKeys.size(), 1);
|
|
wsl::windows::common::registry::WriteString(subKeys.begin()->second.get(), nullptr, L"Disk", L"BadDisk");
|
|
keepAlive.Reset();
|
|
|
|
// Restart the service
|
|
RestartWslService();
|
|
|
|
// Run a dummy command to trigger a VM start
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"echo foo"), (DWORD)0);
|
|
|
|
// The disk should still be online, because it failed to attach
|
|
ValidateOffline(false);
|
|
}
|
|
|
|
// wsl --unmount should succeed even when no disk is mounted
|
|
WSL2_TEST_METHOD(UnmountWithoutAnyDisk)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount"), (DWORD)0);
|
|
}
|
|
|
|
// Mount two partitions on the same disk and validate that the mount is restored
|
|
WSL2_TEST_METHOD(TestMountTwoPartitionsAfterTimeout)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition and one fat partitions
|
|
FormatDisk({L"ext4", L"vfat"}, false);
|
|
|
|
// Mount then both
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1"), (DWORD)0);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 2 --type vfat"), (DWORD)0);
|
|
|
|
ValidateDiskState({DiskDevice, {{1, {}, {}}, {2, {L"vfat"}, {}}}}, keepAlive);
|
|
|
|
// Validate that our disk is still mounted
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(DiskDevice);
|
|
Trim(trimmedDiskName);
|
|
|
|
ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
|
|
ValidateMountPoint(disk + L"2", L"/mnt/wsl/" + trimmedDiskName + L"p2", {}, L"vfat");
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
}
|
|
|
|
// Validate that non-admin can remount saved disks
|
|
WSL2_TEST_METHOD(TestMount1PartitionAndRemountAsNonAdmin)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
FormatDisk({L"ext4"}, false);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1"), (DWORD)0);
|
|
|
|
ValidateDiskState({DiskDevice, {{1, {}, {}}}}, keepAlive);
|
|
auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Let the UVM timeout
|
|
WaitForVmTimeout(keepAlive);
|
|
|
|
// Restart wsl as a non-elevated user
|
|
const auto nonElevatedToken = GetNonElevatedToken();
|
|
|
|
// Launch wsl non-elevated
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"echo dummy", nullptr, nullptr, nullptr, nonElevatedToken.get()), (DWORD)0);
|
|
keepAlive.Set();
|
|
|
|
// Validate that our disk is still attached
|
|
disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(DiskDevice);
|
|
Trim(trimmedDiskName);
|
|
|
|
ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
}
|
|
|
|
// Run a bare mount and then mount a partition
|
|
WSL2_TEST_METHOD(TestAttachThenMount)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
|
|
TestAttachThenMountImpl(false);
|
|
}
|
|
|
|
// Validate that unmounting works when the UVM is not running
|
|
WSL2_TEST_METHOD(TestMountOnePartitionAfterTimeout)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition
|
|
FormatDisk({L"ext4"}, false);
|
|
|
|
// Mount it
|
|
ValidateOffline(false);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
ValidateOffline(true);
|
|
|
|
// Wait for vm timeout
|
|
WaitForVmTimeout(keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
|
|
// The UVM shouldn't be running
|
|
VERIFY_IS_FALSE(GetVmmempPid().has_value());
|
|
|
|
// No state should be left in registry
|
|
const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
|
|
VERIFY_ARE_EQUAL(wsl::windows::common::registry::EnumKeys(key.get(), KEY_READ).size(), 0);
|
|
}
|
|
|
|
// Validate that the proper mount error is returned if the filesystem type is wrong
|
|
WSL2_TEST_METHOD(TestMountPartitionWithWrongFs)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition
|
|
FormatDisk({L"ext4"}, false);
|
|
|
|
// Mount it
|
|
wsl::windows::common::SvcComm service;
|
|
VERIFY_ARE_EQUAL(service.AttachDisk(DiskDevice.c_str(), LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH), S_OK);
|
|
|
|
const auto result = service.MountDisk(DiskDevice.c_str(), LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH, 1, nullptr, L"vfat", nullptr);
|
|
|
|
VERIFY_ARE_EQUAL(result.Result, -22); //-EINVAL
|
|
VERIFY_ARE_EQUAL(result.Step, 3); // LxMiniInitMountStepMount
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
}
|
|
|
|
// Validate that the proper mount error is returned if the partition can't be found
|
|
WSL2_TEST_METHOD(TestMountPartitionWithBadPartitionIndex)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 fat partition
|
|
FormatDisk({L"vfat"}, false);
|
|
|
|
// Try to mount a partition that doesn't exist
|
|
wsl::windows::common::SvcComm service;
|
|
VERIFY_ARE_EQUAL(service.AttachDisk(DiskDevice.c_str(), LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH), S_OK);
|
|
|
|
const auto result = service.MountDisk(DiskDevice.c_str(), LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH, 2, nullptr, L"vfat", nullptr);
|
|
|
|
VERIFY_ARE_EQUAL(result.Result, -2); // -ENOENT
|
|
VERIFY_ARE_EQUAL(result.Step, 2); // LxMiniInitMountStepFindPartition
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
}
|
|
|
|
// Validate that disk aren't detached if in use by other processes
|
|
WSL2_TEST_METHOD(TestDeviceCantBeMountedIfInUse)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
{
|
|
// Format-Volume fails without automount enabled
|
|
SetAutoMountPolicy AutoMountPolicy{true};
|
|
|
|
// Reset the disk
|
|
LxsstuLaunchPowershellAndCaptureOutput(L"Clear-Disk -confirm:$false -RemoveData -Number " + std::to_wstring(DiskNumber));
|
|
|
|
LxsstuLaunchPowershellAndCaptureOutput(L"Initialize-Disk -confirm:$false -Number " + std::to_wstring(DiskNumber));
|
|
|
|
// Create one fat partition
|
|
LxsstuLaunchPowershellAndCaptureOutput(
|
|
L"New-Partition -DiskNumber " + std::to_wstring(DiskNumber) +
|
|
L" -UseMaximumSize \
|
|
| Format-Volume -FileSystem FAT");
|
|
}
|
|
|
|
// Mount it in Windows
|
|
auto [letter, _] = LxsstuLaunchPowershellAndCaptureOutput(
|
|
L"Set-Partition -DiskNumber " + std::to_wstring(DiskNumber) + L" -PartitionNumber 1" + L" -NewDriveLetter Y");
|
|
|
|
// Open a file under that partition
|
|
wil::unique_handle file(CreateFile(L"Y:\\foo.txt", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr));
|
|
|
|
const char* fileContent = "LOW!";
|
|
THROW_LAST_ERROR_IF(!WriteFile(file.get(), fileContent, static_cast<DWORD>(strlen(fileContent)), nullptr, nullptr));
|
|
|
|
// Validate that the disk can't be mounted (TODO: Find a way to validate the failure reason)
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --type vfat"), (DWORD)-1);
|
|
|
|
// Close the file and mount it
|
|
file.reset();
|
|
WaitForDiskReady();
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --type vfat"), (DWORD)0);
|
|
|
|
// Validate that the file content is correct
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(DiskDevice);
|
|
Trim(trimmedDiskName);
|
|
|
|
ValidateMountPoint(disk + L"1", {L"/mnt/wsl/" + trimmedDiskName + L"p1"}, {}, L"vfat");
|
|
auto [output, __] = LxsstuLaunchWslAndCaptureOutput(L"cat /mnt/wsl/" + trimmedDiskName + L"p1/foo.txt");
|
|
|
|
VERIFY_ARE_EQUAL(output, wsl::shared::string::MultiByteToWide(fileContent));
|
|
}
|
|
|
|
WSL2_TEST_METHOD(TestMountWithFlagOption)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition
|
|
FormatDisk({L"ext4"}, false);
|
|
|
|
// Mount it
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --options sync"), (DWORD)0);
|
|
auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(DiskDevice);
|
|
Trim(trimmedDiskName);
|
|
auto mountTarget = L"/mnt/wsl/" + trimmedDiskName + L"p1";
|
|
|
|
ValidateMountPoint(disk + L"1", mountTarget, L"sync");
|
|
ValidateDiskState({DiskDevice, {{1, {}, L"sync"}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
WaitForDiskReady();
|
|
|
|
// Mount the same partition, but with both a flag and a non-flag option
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --options data=ordered,sync"), (DWORD)0);
|
|
|
|
// Validate that the mount option was properly passed
|
|
disk = GetBlockDeviceInWsl();
|
|
|
|
ValidateMountPoint(disk + L"1", mountTarget, L"ync,relatime,data=ordered");
|
|
|
|
// Note: relatime is set by default
|
|
ValidateDiskState({DiskDevice, {{1, {}, L"data=ordered,sync"}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
|
|
WaitForDiskReady();
|
|
}
|
|
|
|
WSL1_TEST_METHOD(TestAttachFailsWithoutWsl2Distro)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
// Attempt to mount a disk with only a WSL1 distro
|
|
wsl::windows::common::SvcComm service;
|
|
VERIFY_ARE_EQUAL(service.AttachDisk(L"Dummy", LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH), WSL_E_WSL2_NEEDED);
|
|
}
|
|
|
|
WSL2_TEST_METHOD(VhdWithSpaces)
|
|
{
|
|
SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
|
|
LxsstuLaunchPowershellAndCaptureOutput(L"New-Vhd -Path 'vhd with spaces.vhdx' -SizeBytes 20MB");
|
|
|
|
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() {
|
|
WslShutdown();
|
|
|
|
if (!DeleteFile(L"vhd with spaces.vhdx"))
|
|
{
|
|
LogInfo("Failed to delete vhd, %i", GetLastError());
|
|
};
|
|
});
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Validate that relative path mounting and unmounting works
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount \"vhd with spaces.vhdx\" --bare --vhd"), (DWORD)0);
|
|
auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount \"vhd with spaces.vhdx\""), (DWORD)0);
|
|
|
|
// Validate that absolute path mounting and unmounting works
|
|
const std::wstring fullPath = wsl::windows::common::filesystem::GetFullPath(L"vhd with spaces.vhdx");
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount \"" + fullPath + L"\" --bare --vhd"), (DWORD)0);
|
|
disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount \"" + fullPath + L"\""), (DWORD)0);
|
|
}
|
|
|
|
void WaitForDiskReady() const
|
|
{
|
|
const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(30);
|
|
while (timeout > std::chrono::steady_clock::now())
|
|
{
|
|
try
|
|
{
|
|
auto disk = wsl::windows::common::disk::OpenDevice(DiskDevice.c_str(), GENERIC_READ, c_diskOpenTimeoutMs);
|
|
wsl::windows::common::disk::ValidateDiskVolumesAreReady(disk.get());
|
|
return;
|
|
}
|
|
catch (...)
|
|
{
|
|
auto error = std::system_category().message(wil::ResultFromCaughtException());
|
|
LogInfo("Caught '%S' while waiting for disk", error.c_str());
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
VERIFY_FAIL(L"Timeout waiting for disk");
|
|
}
|
|
|
|
void ValidateOffline(bool offline) const
|
|
{
|
|
const auto disk = wsl::windows::common::disk::OpenDevice(DiskDevice.c_str(), FILE_READ_ATTRIBUTES, c_diskOpenTimeoutMs);
|
|
VERIFY_ARE_EQUAL(!offline, wsl::windows::common::disk::IsDiskOnline(disk.get()));
|
|
}
|
|
|
|
static std::wstring GetBlockDeviceInWsl()
|
|
{
|
|
// Wait for the disk to be attached
|
|
const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(30);
|
|
|
|
bool done = false;
|
|
while (true)
|
|
{
|
|
for (wchar_t name = 'a'; name < 'z'; name++)
|
|
{
|
|
std::wstring cmd = L"-u root blockdev --getsize64 /dev/sd";
|
|
cmd += name;
|
|
|
|
std::wstring out;
|
|
try
|
|
{
|
|
out = LxsstuLaunchWslAndCaptureOutput(cmd.data()).first;
|
|
}
|
|
CATCH_LOG()
|
|
|
|
Trim(out);
|
|
|
|
// Disk size is 20MB, so 20 * 1024 * 1024 bytes
|
|
if (out == L"20971520")
|
|
{
|
|
return std::wstring(L"/dev/sd") + name;
|
|
}
|
|
}
|
|
|
|
if (done)
|
|
{
|
|
break;
|
|
}
|
|
|
|
done = std::chrono::steady_clock::now() > timeout;
|
|
}
|
|
|
|
VERIFY_FAIL(L"Failed to find the block device in WSL");
|
|
|
|
// Unreachable.
|
|
return {};
|
|
}
|
|
|
|
static bool IsBlockDevicePresent(const std::wstring& Device)
|
|
{
|
|
const auto Cmd = L"test -e " + Device;
|
|
return LxsstuLaunchWsl(Cmd.data()) == 0;
|
|
}
|
|
|
|
static std::optional<std::vector<std::wstring>> GetBlockDeviceMount(const std::wstring& device)
|
|
{
|
|
const std::wstring cmd(L"cat /proc/mounts");
|
|
auto [out, _] = LxsstuLaunchWslAndCaptureOutput(cmd.data());
|
|
|
|
LogInfo("/proc/mounts content: '%ls'", out.c_str());
|
|
std::wistringstream output(out);
|
|
std::wstring line;
|
|
|
|
while (std::getline(output, line))
|
|
{
|
|
if (wcsstr(line.data(), device.data()) == line.data())
|
|
{
|
|
return LxssSplitString(line);
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void ValidateDiskState(const ExpectedDiskState& State, WslKeepAlive& KeepAlive)
|
|
{
|
|
WaitForVmTimeout(KeepAlive);
|
|
const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
|
|
const auto subKeys = wsl::windows::common::registry::EnumKeys(key.get(), KEY_READ);
|
|
|
|
VERIFY_ARE_EQUAL(subKeys.size(), 1);
|
|
|
|
const auto& diskKey = subKeys.begin()->second;
|
|
|
|
auto read = [](const auto& Key, LPCWSTR Name) -> std::optional<std::wstring> {
|
|
try
|
|
{
|
|
return wsl::windows::common::registry::ReadString(Key.get(), nullptr, Name);
|
|
}
|
|
catch (...)
|
|
{
|
|
return {};
|
|
}
|
|
};
|
|
|
|
VERIFY_ARE_EQUAL(read(diskKey, L"Disk").value(), State.Path);
|
|
VERIFY_ARE_EQUAL(wsl::windows::common::registry::EnumKeys(diskKey.get(), KEY_READ).size(), State.Mounts.size());
|
|
|
|
for (const auto& e : State.Mounts)
|
|
{
|
|
auto keyName = std::to_wstring(e.PartitionIndex);
|
|
|
|
auto mountKey = wsl::windows::common::registry::OpenKey(diskKey.get(), keyName.c_str(), KEY_READ);
|
|
|
|
VERIFY_ARE_EQUAL(read(mountKey, L"Options"), e.Options);
|
|
VERIFY_ARE_EQUAL(read(mountKey, L"Type"), e.Type);
|
|
}
|
|
|
|
KeepAlive.Set();
|
|
}
|
|
|
|
void WaitForVmTimeout(WslKeepAlive& KeepAlive)
|
|
{
|
|
const auto pid = GetVmmempPid();
|
|
VERIFY_IS_TRUE(pid.has_value());
|
|
KeepAlive.Reset();
|
|
const std::wstring cmd = std::wstring(L"-t ") + std::wstring(LXSS_DISTRO_NAME_TEST_L);
|
|
|
|
// Terminate the distro to make the vm timeout faster
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(cmd.c_str()), (DWORD)0);
|
|
|
|
const wil::unique_process_handle process(OpenProcess(SYNCHRONIZE, false, pid.value()));
|
|
VERIFY_IS_NOT_NULL(process.get());
|
|
|
|
VERIFY_ARE_EQUAL((DWORD)WAIT_OBJECT_0, WaitForSingleObject(process.get(), INFINITE));
|
|
}
|
|
|
|
static std::optional<DWORD> GetVmmempPid()
|
|
{
|
|
for (auto pid : wsl::windows::common::wslutil::ListRunningProcesses())
|
|
{
|
|
wil::unique_process_handle process(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid));
|
|
if (!process)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::wstring imageName(MAX_PATH, '\0');
|
|
const DWORD length = GetProcessImageFileName(process.get(), imageName.data(), (DWORD)imageName.size() + 1);
|
|
if (length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
imageName.resize(length);
|
|
if (imageName == L"vmmemWSL" || (!wsl::windows::common::helpers::IsWindows11OrAbove() && imageName == L"vmmem"))
|
|
{
|
|
return pid;
|
|
}
|
|
}
|
|
|
|
return {}; // Unreachable
|
|
}
|
|
|
|
void FormatDisk(const std::vector<std::wstring>& Partitions, bool isVhdTest)
|
|
{
|
|
WaitForDiskReady();
|
|
const auto deviceName = (isVhdTest) ? VhdDevice : DiskDevice;
|
|
if (isVhdTest)
|
|
{
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + deviceName + L" --vhd --bare"), (DWORD)0);
|
|
}
|
|
else
|
|
{
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + deviceName + L" --bare"), (DWORD)0);
|
|
}
|
|
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Create a partition table
|
|
std::wstringstream Cmd;
|
|
Cmd << "bash -c \"(";
|
|
Cmd << L"echo -e o\n"; // Create a new partition table
|
|
|
|
for (size_t i = 0; i < Partitions.size(); i++)
|
|
{
|
|
Cmd << L"echo -e n\n"; // Add a new partition
|
|
Cmd << L"echo -e p\n"; // Primary partition
|
|
Cmd << L"echo -e " << (i + 1) << L"\n"; // Partition number
|
|
Cmd << L"echo -e\n"; // First sector (Accept default)
|
|
Cmd << L"echo " << 2049 + (i + 1) * 4096 << L"\n"; // Last sector
|
|
}
|
|
|
|
Cmd << L"echo -e w\n"; // Write changes
|
|
Cmd << L") | fdisk " + disk + L"\"";
|
|
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(Cmd.str()), (DWORD)0);
|
|
|
|
for (size_t i = 1; i <= Partitions.size(); i++)
|
|
{
|
|
auto partition = disk + std::to_wstring(i);
|
|
|
|
// mkfs.ext4 interactively asks for confirmation, -F disables that behavior
|
|
const auto forceFlag = Partitions[i - 1] == L"ext4" ? L" -F " : L"";
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"mkfs." + Partitions[i - 1] + forceFlag + L" " + partition), (DWORD)0);
|
|
}
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
|
|
if (!isVhdTest)
|
|
{
|
|
WaitForDiskReady();
|
|
}
|
|
}
|
|
|
|
void ValidateMountPoint(
|
|
const std::wstring& BlockDevice,
|
|
const std::optional<std::wstring>& Mountpoint,
|
|
const std::optional<std::wstring>& ExpectedOption = {},
|
|
const std::optional<std::wstring>& ExpectedType = {})
|
|
{
|
|
auto mount = GetBlockDeviceMount(BlockDevice);
|
|
if (Mountpoint.has_value())
|
|
{
|
|
VERIFY_IS_TRUE(mount.has_value());
|
|
}
|
|
else
|
|
{
|
|
VERIFY_IS_FALSE(mount.has_value());
|
|
return;
|
|
}
|
|
|
|
VERIFY_ARE_EQUAL(mount.value()[1], Mountpoint.value());
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -d " + Mountpoint.value()), (DWORD)0);
|
|
|
|
// If specified, validate that ExpectedOption is in the mount options
|
|
// (We don't want to do a direct compare because the kernel might add some like rw, ...)
|
|
if (ExpectedOption.has_value())
|
|
{
|
|
VERIFY_ARE_NOT_EQUAL(mount.value()[3].find(ExpectedOption.value()), std::string::npos);
|
|
}
|
|
|
|
// If specified, validate the filesystem
|
|
if (ExpectedType.has_value())
|
|
{
|
|
VERIFY_ARE_EQUAL(mount.value()[2], ExpectedType.value());
|
|
}
|
|
}
|
|
|
|
void TestBareMountImpl(bool isVhd)
|
|
{
|
|
WslKeepAlive keepAlive;
|
|
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
if (isVhd)
|
|
{
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
|
|
}
|
|
else
|
|
{
|
|
ValidateOffline(false);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
|
|
ValidateOffline(true);
|
|
}
|
|
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
VERIFY_IS_FALSE(GetBlockDeviceMount(disk).has_value());
|
|
|
|
ValidateDiskState({deviceName, {}}, keepAlive);
|
|
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
|
|
if (!isVhd)
|
|
{
|
|
ValidateOffline(false);
|
|
}
|
|
}
|
|
|
|
void TestMountOnePartitionImpl(bool isVhd)
|
|
{
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition
|
|
FormatDisk({L"ext4"}, isVhd);
|
|
|
|
// Mount it
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
|
|
auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(deviceName);
|
|
Trim(trimmedDiskName);
|
|
auto mountTarget = L"/mnt/wsl/" + trimmedDiskName + L"p1";
|
|
|
|
ValidateMountPoint(disk + L"1", mountTarget);
|
|
|
|
ValidateDiskState({deviceName, {{1, {}, {}}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
WaitForDiskReady();
|
|
|
|
if (!isVhd)
|
|
{
|
|
ValidateOffline(false);
|
|
}
|
|
|
|
// Validate that the mount folder was deleted
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -e " + mountTarget), (DWORD)1);
|
|
|
|
// Mount the same partition, but with a specific mount option
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1 --options \"data=ordered\""), (DWORD)0);
|
|
|
|
// Validate that the mount option was properly passed
|
|
disk = GetBlockDeviceInWsl();
|
|
ValidateMountPoint(disk + L"1", mountTarget, L"data=ordered");
|
|
ValidateDiskState({deviceName, {{1, {}, L"data=ordered"}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
WaitForDiskReady();
|
|
|
|
if (!isVhd)
|
|
{
|
|
ValidateOffline(false);
|
|
}
|
|
}
|
|
|
|
void TestMountTwoPartitionsImpl(bool isVhd)
|
|
{
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition and one fat partitions
|
|
FormatDisk({L"ext4", L"vfat"}, isVhd);
|
|
|
|
// Mount then both
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 2 --type vfat"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(deviceName);
|
|
Trim(trimmedDiskName);
|
|
|
|
ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
|
|
ValidateMountPoint(disk + L"2", L"/mnt/wsl/" + trimmedDiskName + L"p2", {}, L"vfat");
|
|
ValidateDiskState({deviceName, {{1, {}, {}}, {2, {L"vfat"}, {}}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
WaitForDiskReady();
|
|
|
|
if (!isVhd)
|
|
{
|
|
ValidateOffline(false);
|
|
}
|
|
}
|
|
|
|
void TestAttachThenMountImpl(bool isVhd)
|
|
{
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
FormatDisk({L"ext4"}, isVhd);
|
|
|
|
// Mount then both
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
|
|
|
|
ValidateDiskState({deviceName, {{1, {}, {}}}}, keepAlive);
|
|
|
|
// Validate that our disk is still mounted
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(deviceName);
|
|
Trim(trimmedDiskName);
|
|
|
|
ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, {});
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
}
|
|
|
|
void TestMountWholeDiskImpl(bool isVhd)
|
|
{
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Format the volume as ext4
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"mkfs.ext4 -F " + disk), (DWORD)0);
|
|
|
|
// Then mount it
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --type ext4"), (DWORD)0);
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(deviceName);
|
|
Trim(trimmedDiskName);
|
|
auto mountTarget = L"/mnt/wsl/" + trimmedDiskName;
|
|
ValidateMountPoint(disk, mountTarget, {}, L"ext4");
|
|
ValidateDiskState({deviceName, {{0, {L"ext4"}, {}}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
|
|
if (!isVhd)
|
|
{
|
|
WaitForDiskReady();
|
|
ValidateOffline(false);
|
|
}
|
|
}
|
|
|
|
void TestMountStateIsDeletedOnShutdownImpl(bool isVhd)
|
|
{
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition
|
|
FormatDisk({L"ext4"}, isVhd);
|
|
|
|
// Mount it
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1 --type ext4"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
ValidateDiskState({deviceName, {{1, {L"ext4"}, {}}}}, keepAlive);
|
|
keepAlive.Reset();
|
|
|
|
// wsl --shutdown clears any disk state
|
|
WslShutdown();
|
|
|
|
if (!isVhd)
|
|
{
|
|
ValidateOffline(false);
|
|
}
|
|
|
|
// No state should be left in registry
|
|
const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
|
|
VERIFY_ARE_EQUAL(wsl::windows::common::registry::EnumKeys(key.get(), KEY_READ).size(), 0);
|
|
}
|
|
|
|
void TestFilesystemDetectionWholeDiskImpl(bool isVhd)
|
|
{
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Format the volume as fat
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"mkfs.fat --mbr=no -I " + disk), (DWORD)0);
|
|
|
|
// Then mount it. The filesystem should be autodetected
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand), (DWORD)0);
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(deviceName);
|
|
Trim(trimmedDiskName);
|
|
auto mountTarget = L"/mnt/wsl/" + trimmedDiskName;
|
|
ValidateMountPoint(disk, mountTarget, {}, L"vfat");
|
|
ValidateDiskState({deviceName, {{0, {}, {}}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
|
|
if (!isVhd)
|
|
{
|
|
WaitForDiskReady();
|
|
ValidateOffline(false);
|
|
}
|
|
}
|
|
|
|
void TestMountTwoPartitionsWithDetectionImpl(bool isVhd)
|
|
{
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Create a MBR disk with 1 ext4 partition and one fat partitions
|
|
FormatDisk({L"ext4", L"vfat"}, isVhd);
|
|
|
|
// Mount then both (filesystems should be detected).
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 2"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
|
|
// Validate that the mount succeeded
|
|
std::wstring trimmedDiskName(deviceName);
|
|
Trim(trimmedDiskName);
|
|
|
|
ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
|
|
ValidateMountPoint(disk + L"2", L"/mnt/wsl/" + trimmedDiskName + L"p2", {}, L"vfat");
|
|
ValidateDiskState({deviceName, {{1, {}, {}}, {2, {}, {}}}}, keepAlive);
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
|
|
if (!isVhd)
|
|
{
|
|
WaitForDiskReady();
|
|
ValidateOffline(false);
|
|
}
|
|
}
|
|
|
|
void TestFilesystemDetectionFailImpl(bool isVhd)
|
|
{
|
|
const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
|
|
const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
|
|
|
|
WslKeepAlive keepAlive;
|
|
|
|
// Write zeroes in the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
|
|
const auto disk = GetBlockDeviceInWsl();
|
|
VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"dd bs=4M count=1 if=/dev/zero of=" + disk), (DWORD)0);
|
|
|
|
// Then try to mount it
|
|
wsl::windows::common::SvcComm service;
|
|
const auto result = service.MountDisk(
|
|
deviceName.c_str(), isVhd ? LXSS_ATTACH_MOUNT_FLAGS_VHD : LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH, 0, nullptr, nullptr, nullptr);
|
|
|
|
// Validate that the mount fail because the filesystem couldn't be detected
|
|
VERIFY_ARE_EQUAL(result.Result, -1); //-EINVAL
|
|
VERIFY_ARE_EQUAL(result.Step, 6); // LxMiniInitMountStepDetectFilesystem
|
|
|
|
// Unmount the disk
|
|
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
|
|
|
|
if (!isVhd)
|
|
{
|
|
WaitForDiskReady();
|
|
ValidateOffline(false);
|
|
}
|
|
}
|
|
};
|
|
} // namespace MountTests
|