/*++ 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 PreviousState; }; class MountTests { std::wstring DiskDevice; std::wstring VhdDevice; wil::unique_tokeninfo_ptr User = wil::get_token_information(); std::unique_ptr PrivilegeState; DWORD DiskNumber = 0; SetAutoMountPolicy AutoMountPolicy{false}; struct ExpectedMountState { size_t PartitionIndex; std::optional Type; std::optional Options; }; struct ExpectedDiskState { std::wstring Path; std::vector 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 TEST_METHOD(TestBareMountVhd) { TestBareMountImpl(true); } // Mount one partition using --vhd and validate that options are correctly applied TEST_METHOD(TestMountOnePartitionVhd) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountOnePartitionImpl(true); } // Mount two partitions using --vhd on the same disk TEST_METHOD(TestMountTwoPartitionsVhd) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountTwoPartitionsImpl(true); } // Run a bare mount using --vhd and then mount a partition TEST_METHOD(TestAttachThenMountVhd) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestAttachThenMountImpl(true); } // Mount the disk directly TEST_METHOD(TestMountWholeDiskVhd) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountWholeDiskImpl(true); } // Test that mount state is deleted on shutdown (--vhd) TEST_METHOD(TestMountStateIsDeletedOnShutdownVhd) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountStateIsDeletedOnShutdownImpl(true); } TEST_METHOD(TestFilesystemDetectionWholeDisk) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestFilesystemDetectionWholeDiskImpl(false); } TEST_METHOD(TestFilesystemDetectionWholeDiskVhd) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestFilesystemDetectionWholeDiskImpl(true); } TEST_METHOD(TestMountTwoPartitionsWithDetection) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountTwoPartitionsWithDetectionImpl(false); } TEST_METHOD(TestMountTwoPartitionsWithDetectionVhd) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountTwoPartitionsWithDetectionImpl(true); } TEST_METHOD(TestFilesystemDetectionFail) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestFilesystemDetectionFailImpl(false); } TEST_METHOD(TestFilesystemDetectionFailVhd) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestFilesystemDetectionFailImpl(true); } // Test specifying a mount name for a vhd TEST_METHOD(SpecifyMountName) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(SpecifyMountNameCollision) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(SpecifyMountNameTwoPartitions) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(RelativePathUnmount) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(RelativePathUnmountNoFileExists) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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); } TEST_METHOD(AbsolutePathVhdUnmount) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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); } // Attach a disk, but don't mount it TEST_METHOD(TestBareMount) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestBareMountImpl(false); } // Validate that attached disks that were offline when attached // are still offline when detached TEST_METHOD(TestOfflineDiskStaysOffline) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(TestMountOnePartition) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountOnePartitionImpl(false); } // Mount two partitions on the same disk TEST_METHOD(TestMountTwoPartitions) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountTwoPartitionsImpl(false); } // Mount a fat partition TEST_METHOD(TestMountFatPartition) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(TestMountWholeDisk) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountWholeDiskImpl(false); } TEST_METHOD(TestMountStateIsDeletedOnShutdown) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestMountStateIsDeletedOnShutdownImpl(false); } // Validate that a failure to mount a disk isn't fatal TEST_METHOD(TestMountFailuresArentFatal) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(UnmountWithoutAnyDisk) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount"), (DWORD)0); } // Mount two partitions on the same disk and validate that the mount is restored TEST_METHOD(TestMountTwoPartitionsAfterTimeout) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(TestMount1PartitionAndRemountAsNonAdmin) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(TestAttachThenMount) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); TestAttachThenMountImpl(false); } // Validate that unmounting works when the UVM is not running TEST_METHOD(TestMountOnePartitionAfterTimeout) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(TestMountPartitionWithWrongFs) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(TestMountPartitionWithBadPartitionIndex) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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 TEST_METHOD(TestDeviceCantBeMountedIfInUse) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); { // 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(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)); } TEST_METHOD(TestMountWithFlagOption) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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(); } TEST_METHOD(TestAttachFailsWithoutWsl2Distro) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL1_TEST_ONLY(); // 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); } TEST_METHOD(VhdWithSpaces) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); WSL2_TEST_ONLY(); 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> 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 { 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 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& 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& Mountpoint, const std::optional& ExpectedOption = {}, const std::optional& 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 specifed, 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) { WSL2_TEST_ONLY(); 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) { WSL2_TEST_ONLY(); 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) { WSL2_TEST_ONLY(); 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) { WSL2_TEST_ONLY(); 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) { WSL2_TEST_ONLY(); 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) { WSL2_TEST_ONLY(); 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) { WSL2_TEST_ONLY(); 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) { WSL2_TEST_ONLY(); 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) { WSL2_TEST_ONLY(); 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