WSL/test/windows/MountTests.cpp
WSL Team 697572d664 Initial open source commit for WSL.
Many Microsoft employees have contributed to the Windows Subsystem for Linux, this commit is the result of their work since 2016.

The entire history of the Windows Subsystem for Linux can't be shared here, but here's an overview of WSL's history after it moved to it own repository in 2021:

Number of commits on the main branch: 2930
Number of contributors: 31

Head over https://github.com/microsoft/WSL/releases for a more detailed history of the features added to WSL since 2021.
2025-05-15 12:09:45 -07:00

1520 lines
No EOL
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
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<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));
}
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<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 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