/*++ Copyright (c) Microsoft. All rights reserved. Module Name: DrvFsTests.cpp Abstract: This file contains drvfs test cases. --*/ #include "precomp.h" #include "Common.h" #include #include #include #include "wslservice.h" #include "registry.hpp" #include "helpers.hpp" #include "svccomm.hpp" #include #include "Distribution.h" #define LXSST_DRVFS_TEST_DIR L"C:\\drvfstest" #define LXSST_DRVFS_RWX_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\rwx" #define LXSST_DRVFS_READONLY_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\readonly" #define LXSST_DRVFS_WRITEONLY_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\writeonly" #define LXSST_DRVFS_EXECUTEONLY_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\executeonly" #define LXSST_DRVFS_READONLYATTR_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\readonlyattr" #define LXSST_DRVFS_READONLYATTRDEL_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\readonlyattrdel" #define LXSST_DRVFS_EXECUTEONLY_TEST_DIR LXSST_DRVFS_TEST_DIR L"\\executeonlydir" #define LXSST_DRVFS_EXECUTEONLY_TEST_DIR_CHILD LXSST_DRVFS_EXECUTEONLY_TEST_DIR L"\\child" #define LXSST_DRVFS_READONLY_TEST_DIR LXSST_DRVFS_TEST_DIR L"\\noexecutedir" #define LXSST_DRVFS_METADATA_TEST_DIR L"C:\\metadatatest" #define LXSST_DRVFS_REPARSE_TEST_DIR L"C:\\reparsetest" #define LXSST_DRVFS_SYMLINK_TEST_DIR L"C:\\symlink" #define LXSST_DRVFS_METADATA_TEST_MODE (5) #define LXSST_TESTS_INSTALL_COMMAND_LINE L"/bin/bash -c 'cd /data/test; ./build_tests.sh'" #define LXSST_METADATA_EA_NAME_LENGTH (RTL_NUMBER_OF(LX_FILE_METADATA_UID_EA_NAME) - 1) #define LX_DRVFS_DISABLE_NONE (0) #define LX_DRVFS_DISABLE_QUERY_BY_NAME (1) #define LX_DRVFS_DISABLE_QUERY_BY_NAME_AND_STAT_INFO (2) using wsl::windows::common::wslutil::GetSystemErrorString; namespace DrvFsTests { class DrvFsTests { public: std::wstring SkipUnstableTestEnvVar = L"WSL_DISABLE_VB_UNSTABLE_TESTS=" + std::wstring{wsl::windows::common::helpers::IsWindows11OrAbove() ? L"0" : L"1"}; void DrvFsCommon(int TestMode, std::optional DrvFsMode = {}) const { auto cleanup = wil::scope_exit([TestMode] { RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\junction"); RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\absolutelink"); DeleteFileW(LXSST_DRVFS_REPARSE_TEST_DIR L"\\filelink"); RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\relativelink"); RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\linktarget"); DeleteFileW(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\filetarget"); RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test"); DeleteFileW(LXSST_DRVFS_REPARSE_TEST_DIR L"\\v1link"); DeleteFileW(LXSST_DRVFS_REPARSE_TEST_DIR L"\\appexeclink"); RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR); SetFileAttributes(LXSST_DRVFS_RWX_TEST_FILE, FILE_ATTRIBUTE_NORMAL); DeleteFileW(LXSST_DRVFS_RWX_TEST_FILE); DeleteFileW(LXSST_DRVFS_READONLY_TEST_FILE); DeleteFileW(LXSST_DRVFS_WRITEONLY_TEST_FILE); DeleteFileW(LXSST_DRVFS_EXECUTEONLY_TEST_FILE); DeleteFileW(LXSST_DRVFS_EXECUTEONLY_TEST_DIR_CHILD); SetFileAttributes(LXSST_DRVFS_READONLYATTR_TEST_FILE, FILE_ATTRIBUTE_NORMAL); DeleteFileW(LXSST_DRVFS_READONLYATTR_TEST_FILE); SetFileAttributes(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE, FILE_ATTRIBUTE_NORMAL); DeleteFileW(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE); RemoveDirectory(LXSST_DRVFS_EXECUTEONLY_TEST_DIR); RemoveDirectory(LXSST_DRVFS_READONLY_TEST_DIR); RemoveDirectory(LXSST_DRVFS_TEST_DIR); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\file.txt"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR L"\\foo\uf03abar"); RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR "\\dir"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink1"); RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink2"); RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink3"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink4"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink5"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink6"); RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink7"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink8"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink1"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink2"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink3"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink4"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink5"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink6"); DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink7"); RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR); if (TestMode == LXSST_DRVFS_METADATA_TEST_MODE) { DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\baduid"); DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\badgid"); DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\badmode"); DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\badtype1"); DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\badtype2"); DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\nondevice"); RemoveDirectory(LXSST_DRVFS_METADATA_TEST_DIR); } }); VERIFY_NO_THROW(CreateDrvFsTestFiles(TestMode == LXSST_DRVFS_METADATA_TEST_MODE)); std::wstringstream Command; Command << L"/bin/bash -c \""; Command << SkipUnstableTestEnvVar; Command << " /data/test/wsl_unit_tests drvfs -d $(wslpath '"; Command << LxsstuGetLxssDirectory(); Command << L"') -m "; Command << TestMode; Command << L"\""; std::wstringstream Logfile; Logfile << L"drvfs"; Logfile << TestMode; VERIFY_NO_THROW(LxsstuRunTest(Command.str().c_str(), Logfile.str().c_str())); // // Check that the read-only attribute has been changed. // DWORD Attributes = GetFileAttributes(LXSST_DRVFS_READONLYATTR_TEST_FILE); DWORD Expected = FILE_ATTRIBUTE_NORMAL; VERIFY_ARE_EQUAL(Expected, Attributes); Attributes = GetFileAttributes(LXSST_DRVFS_RWX_TEST_FILE); Expected = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE; VERIFY_ARE_EQUAL(Expected, Attributes); // // Check that the second read-only file was deleted. // Expected = INVALID_FILE_ATTRIBUTES; Attributes = GetFileAttributes(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE); VERIFY_ARE_EQUAL(Expected, Attributes); // // Check the NT symlinks. // VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink1", L"file.txt", false)); VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink2", L"dir", true)); VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink3", L"..", true)); VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink4", L"..\\symlink\\file.txt", false)); VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink5", L"dir\\..\\file.txt", false)); VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink6", L"ntlink1", false)); VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink7", L"ntlink2", true)); VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink8", L"foo\uf03abar", false)); if (DrvFsMode.has_value() && DrvFsMode.value() == DrvFsMode::VirtioFs) { LogInfo("TODO: debug VerifyDrvFsLxSymlink variations on virtiofs"); } else { VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink1")); VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink2")); } // Since target resolution is done on the Windows side in Plan 9, it is able to create an NT // link if the target path traverses an existing NT link (this is actually better than WSL 1). if (LxsstuVmMode()) { VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink3", L"ntlink2\\..\\file.txt", false)); } else { VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink3")); } VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink4")); VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink5")); VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink6")); // Plan 9 doesn't know about the Linux mount point on "dir", so it creates an NT link in this case. if (LxsstuVmMode()) { VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink7", L"dir\\..\\file.txt", false)); } else { VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink7")); } // // Check metadata is readable using Windows APIs. // if (TestMode == LXSST_DRVFS_METADATA_TEST_MODE) { VerifyDrvFsMetadata(); } } static void VfsAccessDrvFs() { VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests vfsaccess drvfs", L"vfsaccess_drvfs")); } static void FsCommonDrvFs() { VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests fscommon drvfs", L"fscommon_drvfs")); } void DrvFs(DrvFsMode Mode) { SKIP_TEST_ARM64(); VERIFY_NO_THROW(DrvFsCommon(LX_DRVFS_DISABLE_NONE, Mode)); } void DrvFsFat(DrvFsMode Mode) { SKIP_TEST_ARM64(); if (Mode == DrvFsMode::VirtioFs) { LogSkipped("VirtioFS currently only supports mounting full drives"); return; } constexpr auto MountPoint = "C:\\lxss_fat"; constexpr auto VhdPath = "C:\\lxss_fat.vhdx"; auto Cleanup = wil::scope_exit([MountPoint, VhdPath] { DeleteVolume(MountPoint, VhdPath); }); VERIFY_NO_THROW(CreateVolume("fat32", 100, MountPoint, VhdPath)); VERIFY_NO_THROW( LxsstuRunTest((L"bash -c '" + SkipUnstableTestEnvVar + L" /data/test/wsl_unit_tests drvfs -m 3'").c_str(), L"drvfs3")); } void DrvFsSmb(DrvFsMode Mode) { SKIP_TEST_ARM64(); if (Mode == DrvFsMode::VirtioFs) { LogSkipped("TODO: debug virtiofs handling of //localhost/C$ style paths"); return; } VERIFY_NO_THROW( LxsstuRunTest((L"bash -c '" + SkipUnstableTestEnvVar + L" /data/test/wsl_unit_tests drvfs -m 4'").c_str(), L"drvfs4")); } void DrvFsMetadata(DrvFsMode Mode) { SKIP_TEST_ARM64(); VERIFY_NO_THROW(DrvFsCommon(LXSST_DRVFS_METADATA_TEST_MODE, Mode)); } void DrvfsMountElevated(DrvFsMode Mode) { WSL2_TEST_ONLY(); WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added SKIP_TEST_ARM64(); TerminateDistribution(); WslKeepAlive keelAlive; ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, Mode); } void DrvfsMountElevatedDifferentConsole(DrvFsMode Mode) { WSL2_TEST_ONLY(); WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added SKIP_TEST_ARM64(); TerminateDistribution(); WslKeepAlive keelAlive; ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, Mode); } void DrvfsMountNonElevated(DrvFsMode Mode) { WSL2_TEST_ONLY(); WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added SKIP_TEST_ARM64(); TerminateDistribution(); const auto nonElevatedToken = GetNonElevatedToken(); WslKeepAlive keelAlive(nonElevatedToken.get()); ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, Mode); } void DrvfsMountNonElevatedDifferentConsole(DrvFsMode Mode) { WSL2_TEST_ONLY(); WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added SKIP_TEST_ARM64(); TerminateDistribution(); const auto nonElevatedToken = GetNonElevatedToken(); WslKeepAlive keelAlive(nonElevatedToken.get()); ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, Mode); } static void XattrDrvFs(DrvFsMode Mode) { SKIP_TEST_ARM64(); VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests xattr drvfs", L"xattr_drvfs")); } void DrvFsReFs(DrvFsMode Mode) { SKIP_TEST_ARM64(); WSL_TEST_VERSION_REQUIRED(wsl::windows::common::helpers::WindowsBuildNumbers::Germanium); if (Mode == DrvFsMode::VirtioFs) { LogSkipped("VirtioFS currently only supports mounting full drives"); return; } constexpr auto MountPoint = "C:\\lxss_refs"; constexpr auto VhdPath = "C:\\lxss_refs.vhdx"; auto Cleanup = wil::scope_exit([MountPoint, VhdPath] { DeleteVolume(MountPoint, VhdPath); }); VERIFY_NO_THROW(CreateVolume("refs", 50000, MountPoint, VhdPath)); VERIFY_NO_THROW( LxsstuRunTest((L"bash -c '" + SkipUnstableTestEnvVar + L" /data/test/wsl_unit_tests drvfs -m 6'").c_str(), L"drvfs6")); } // DrvFsTests Private Methods private: static VOID CreateDrvFsTestFiles(bool Metadata) { THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_TEST_DIR, NULL)); // // The rwx and readonlyattr test files need read/write EA permission for // the metadata test mode because chmod will be called on them. // CreateTestFile(LXSST_DRVFS_RWX_TEST_FILE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_EXECUTE | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE); CreateTestFile(LXSST_DRVFS_READONLY_TEST_FILE, FILE_GENERIC_READ | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE); CreateTestFile(LXSST_DRVFS_WRITEONLY_TEST_FILE, FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES | FILE_READ_EA | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE); CreateTestFile( LXSST_DRVFS_EXECUTEONLY_TEST_DIR, FILE_TRAVERSE | FILE_DELETE_CHILD | FILE_ADD_FILE | FILE_READ_ATTRIBUTES | FILE_READ_EA | DELETE | SYNCHRONIZE | READ_CONTROL, TRUE, INVALID_HANDLE_VALUE); CreateTestFile(LXSST_DRVFS_EXECUTEONLY_TEST_DIR_CHILD, FILE_GENERIC_READ | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE); CreateTestFile(LXSST_DRVFS_READONLY_TEST_DIR, FILE_GENERIC_READ | DELETE | SYNCHRONIZE, TRUE, INVALID_HANDLE_VALUE); CreateTestFile(LXSST_DRVFS_READONLYATTR_TEST_FILE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_EXECUTE | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE); THROW_LAST_ERROR_IF(!SetFileAttributes(LXSST_DRVFS_READONLYATTR_TEST_FILE, FILE_ATTRIBUTE_READONLY)); CreateTestFile(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_EXECUTE | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE); THROW_LAST_ERROR_IF(!SetFileAttributes(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE, FILE_ATTRIBUTE_READONLY)); // // Copy the wsl_unit_tests executable to an execute-only file on DrvFs. // const std::wstring Path = L"\\\\wsl.localhost\\" LXSS_DISTRO_NAME_TEST_L L"\\data\\test\\wsl_unit_tests"; const wil::unique_hfile File(CreateFile( Path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); THROW_LAST_ERROR_IF(!File); CreateTestFile( LXSST_DRVFS_EXECUTEONLY_TEST_FILE, FILE_EXECUTE | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | DELETE | SYNCHRONIZE | READ_CONTROL, FALSE, File.get()); THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_REPARSE_TEST_DIR, nullptr)); THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test", nullptr)); THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\linktarget", nullptr)); THROW_LAST_ERROR_IF(!CreateSymbolicLink( LXSST_DRVFS_REPARSE_TEST_DIR L"\\absolutelink", LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\linktarget", SYMBOLIC_LINK_FLAG_DIRECTORY)); THROW_LAST_ERROR_IF(!CreateSymbolicLink(LXSST_DRVFS_REPARSE_TEST_DIR L"\\relativelink", L"test\\linktarget", SYMBOLIC_LINK_FLAG_DIRECTORY)); { const wil::unique_hfile TargetFile(CreateFile( LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\filetarget", FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); THROW_LAST_ERROR_IF(!TargetFile); } THROW_LAST_ERROR_IF(!CreateSymbolicLink(LXSST_DRVFS_REPARSE_TEST_DIR L"\\filelink", L"test\\filetarget", 0)); CreateJunction(LXSST_DRVFS_REPARSE_TEST_DIR L"\\junction", LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\linktarget"); // DrvFs does not create V1 symlinks anymore; create one here manually to ensure it can still // read them. CreateV1Symlink(LXSST_DRVFS_REPARSE_TEST_DIR L"\\v1link", "/v1/symlink/target"); CreateAppExecLink(LXSST_DRVFS_REPARSE_TEST_DIR L"\\appexeclink"); if (Metadata != false) { THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_METADATA_TEST_DIR, nullptr)); CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\baduid", LX_UID_INVALID, 3001, LX_S_IFREG | 0644, 0, 0, false); CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\badgid", 3000, LX_GID_INVALID, LX_S_IFREG | 0644, 0, 0, false); CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\badmode", 3000, 3001, 0x10000 | LX_S_IFREG | 0644, 0, 0, false); CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\badtype1", 3000, 3001, LX_S_IFDIR | 0755, 0, 0, false); CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\badtype2", 3000, 3001, LX_S_IFLNK | 0777, 0, 0, false); CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\nondevice", 3000, 3001, LX_S_IFREG | 0644, 1, 2, true); } } static VOID CreateTestFile(_In_z_ LPCWSTR Filename, _In_ DWORD Permissions, _In_ BOOLEAN Directory, _In_ HANDLE SourceFile) { BYTE Buffer[4096]; DWORD BytesRead; // // Create the SID for the BUILTIN\Administrators group. // auto [AdminSid, SidBuffer] = wsl::windows::common::security::CreateSid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); // // Set the permissions for the SID. // EXPLICIT_ACCESS Access; RtlZeroMemory(&Access, sizeof(Access)); Access.grfAccessPermissions = Permissions; Access.grfAccessMode = SET_ACCESS; Access.grfInheritance = NO_INHERITANCE; Access.Trustee.TrusteeForm = TRUSTEE_IS_SID; Access.Trustee.TrusteeType = TRUSTEE_IS_GROUP; Access.Trustee.ptstrName = (LPTSTR)AdminSid; // // Allocate an ACL with the permissions. // wil::unique_any Acl; THROW_IF_WIN32_ERROR(SetEntriesInAcl(1, &Access, NULL, &Acl)); // // Create a security descriptor and set the ACL. // const wil::unique_hlocal_security_descriptor Descriptor(::LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH)); THROW_LAST_ERROR_IF(!Descriptor); THROW_LAST_ERROR_IF(!InitializeSecurityDescriptor(Descriptor.get(), SECURITY_DESCRIPTOR_REVISION)); THROW_LAST_ERROR_IF(!SetSecurityDescriptorDacl(Descriptor.get(), TRUE, Acl.get(), FALSE)); // // Create security attributes that point to the descriptor. // SECURITY_ATTRIBUTES Attributes; RtlZeroMemory(&Attributes, sizeof(Attributes)); Attributes.nLength = sizeof(SECURITY_ATTRIBUTES); Attributes.lpSecurityDescriptor = Descriptor.get(); Attributes.bInheritHandle = FALSE; // // Create a file or directory with the security attributes. // if (Directory == FALSE) { const wil::unique_hfile File( CreateFile(Filename, GENERIC_WRITE | SYNCHRONIZE, 0, &Attributes, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL)); THROW_LAST_ERROR_IF(!File); // // If a source file was specified, copy its contents. // if (SourceFile != INVALID_HANDLE_VALUE) { THROW_LAST_ERROR_IF(!ReadFile(SourceFile, Buffer, sizeof(Buffer), &BytesRead, NULL)); while (BytesRead > 0) { THROW_LAST_ERROR_IF(!WriteFile(File.get(), Buffer, BytesRead, NULL, NULL)); THROW_LAST_ERROR_IF(!ReadFile(SourceFile, Buffer, sizeof(Buffer), &BytesRead, NULL)); } } } else { THROW_LAST_ERROR_IF(!CreateDirectory(Filename, &Attributes)); } } static VOID CreateMetadataTestFile( _In_ LPCWSTR Filename, _In_ ULONG Uid, _In_ ULONG Gid, _In_ ULONG Mode, _In_ ULONG DeviceIdMajor, _In_ ULONG DeviceIdMinor, _In_ bool IncludeDeviceId) { // // Each individual EA entry must be aligned on a 4 byte boundary, but the // value inside each EA struct must not be. Therefore, set packing to 1 // byte, and add padding to manually align the entries. // #pragma pack(push, 1) struct { struct { union { FILE_FULL_EA_INFORMATION Header; CHAR Buffer[FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + LXSST_METADATA_EA_NAME_LENGTH + 1]; }; ULONG Uid; } Uid; CHAR Padding1; struct { union { FILE_FULL_EA_INFORMATION Header; CHAR Buffer[FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + LXSST_METADATA_EA_NAME_LENGTH + 1]; }; ULONG Gid; } Gid; CHAR Padding2; struct { union { FILE_FULL_EA_INFORMATION Header; CHAR Buffer[FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + LXSST_METADATA_EA_NAME_LENGTH + 1]; }; ULONG Mode; } Mode; CHAR Padding3; struct { union { FILE_FULL_EA_INFORMATION Header; CHAR Buffer[FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + LXSST_METADATA_EA_NAME_LENGTH + 1]; }; ULONG DeviceIdMajor; ULONG DeviceIdMinor; } DeviceId; } EaBuffer; #pragma pack(pop) RtlZeroMemory(&EaBuffer, sizeof(EaBuffer)); EaBuffer.Uid.Header.EaNameLength = LXSST_METADATA_EA_NAME_LENGTH; EaBuffer.Uid.Header.EaValueLength = sizeof(ULONG); RtlCopyMemory(EaBuffer.Uid.Header.EaName, LX_FILE_METADATA_UID_EA_NAME, LXSST_METADATA_EA_NAME_LENGTH); EaBuffer.Uid.Uid = Uid; EaBuffer.Uid.Header.NextEntryOffset = (ULONG)((PUCHAR)&EaBuffer.Gid - (PUCHAR)&EaBuffer.Uid); EaBuffer.Gid.Header.EaNameLength = LXSST_METADATA_EA_NAME_LENGTH; EaBuffer.Gid.Header.EaValueLength = sizeof(ULONG); RtlCopyMemory(EaBuffer.Gid.Header.EaName, LX_FILE_METADATA_GID_EA_NAME, LXSST_METADATA_EA_NAME_LENGTH); EaBuffer.Gid.Gid = Gid; EaBuffer.Gid.Header.NextEntryOffset = (ULONG)((PUCHAR)&EaBuffer.Mode - (PUCHAR)&EaBuffer.Gid); EaBuffer.Mode.Header.EaNameLength = LXSST_METADATA_EA_NAME_LENGTH; EaBuffer.Mode.Header.EaValueLength = sizeof(ULONG); RtlCopyMemory(EaBuffer.Mode.Header.EaName, LX_FILE_METADATA_MODE_EA_NAME, LXSST_METADATA_EA_NAME_LENGTH); EaBuffer.Mode.Mode = Mode; if (IncludeDeviceId != false) { EaBuffer.Mode.Header.NextEntryOffset = (ULONG)((PUCHAR)&EaBuffer.DeviceId - (PUCHAR)&EaBuffer.Mode); EaBuffer.DeviceId.Header.EaNameLength = LXSST_METADATA_EA_NAME_LENGTH; EaBuffer.DeviceId.Header.EaValueLength = sizeof(ULONG); RtlCopyMemory(EaBuffer.DeviceId.Header.EaName, LX_FILE_METADATA_DEVICE_ID_EA_NAME, LXSST_METADATA_EA_NAME_LENGTH); EaBuffer.DeviceId.DeviceIdMajor = DeviceIdMajor; EaBuffer.DeviceId.DeviceIdMinor = DeviceIdMinor; } const std::wstring NtPath{std::wstring(L"\\DosDevices\\") + Filename}; UNICODE_STRING Name; RtlInitUnicodeString(&Name, NtPath.c_str()); OBJECT_ATTRIBUTES Attributes; InitializeObjectAttributes(&Attributes, &Name, 0, nullptr, 0); wil::unique_hfile File; IO_STATUS_BLOCK IoStatus; THROW_IF_NTSTATUS_FAILED(NtCreateFile( &File, FILE_GENERIC_READ, &Attributes, &IoStatus, nullptr, FILE_ATTRIBUTE_NORMAL, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), FILE_CREATE, 0, &EaBuffer, sizeof(EaBuffer))); } static VOID VerifyDrvFsMetadata() { wil::unique_hfile File; IO_STATUS_BLOCK IoStatus; UNICODE_STRING Name; RtlInitUnicodeString(&Name, L"\\DosDevices\\" LXSST_DRVFS_METADATA_TEST_DIR); OBJECT_ATTRIBUTES Attributes; InitializeObjectAttributes(&Attributes, &Name, 0, nullptr, 0); THROW_IF_NTSTATUS_FAILED(NtCreateFile( &File, FILE_READ_EA, &Attributes, &IoStatus, nullptr, 0, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), FILE_OPEN, FILE_DIRECTORY_FILE, nullptr, 0)); UCHAR Buffer[1000]; THROW_IF_NTSTATUS_FAILED(ZwQueryEaFile(File.get(), &IoStatus, Buffer, sizeof(Buffer), FALSE, nullptr, 0, nullptr, TRUE)); bool FoundUid = false; bool FoundGid = false; bool FoundMode = false; PFILE_FULL_EA_INFORMATION EaInfo = (PFILE_FULL_EA_INFORMATION)Buffer; for (;;) { VERIFY_ARE_EQUAL(EaInfo->EaNameLength, 6); VERIFY_ARE_EQUAL(EaInfo->EaValueLength, 4); std::string EaName{EaInfo->EaName}; ULONG Value = *(PULONG)((PCHAR)EaInfo->EaName + EaInfo->EaNameLength + 1); if (EaName == LX_FILE_METADATA_UID_EA_NAME) { FoundUid = true; VERIFY_ARE_EQUAL(Value, 0x11223344ul); } else if (EaName == LX_FILE_METADATA_GID_EA_NAME) { FoundGid = true; VERIFY_ARE_EQUAL(Value, 0x55667788ul); } else if (EaName == LX_FILE_METADATA_MODE_EA_NAME) { FoundMode = true; VERIFY_ARE_EQUAL(Value, (ULONG)(LX_S_IFDIR | 0775)); } else { VERIFY_FAIL(L"Unexpected EA on file."); } if (EaInfo->NextEntryOffset == 0) { break; } EaInfo = (PFILE_FULL_EA_INFORMATION)((PCHAR)EaInfo + EaInfo->NextEntryOffset); }; VERIFY_IS_TRUE(FoundUid); VERIFY_IS_TRUE(FoundGid); VERIFY_IS_TRUE(FoundMode); } static VOID CreateJunction(_In_ const std::wstring& Junction, _In_ const std::wstring& Target) { // // The logic for creating a junction was taken from mklink. // THROW_LAST_ERROR_IF(!CreateDirectory(Junction.c_str(), NULL)); const wil::unique_hfile Dir(CreateFile( Junction.c_str(), FILE_GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr)); THROW_LAST_ERROR_IF(!Dir); UNICODE_STRING LinkPath = {0}; auto Cleanup = wil::scope_exit([&] { if (LinkPath.Buffer != nullptr) { FREE(LinkPath.Buffer); } }); THROW_IF_NTSTATUS_FAILED(RtlDosPathNameToNtPathName_U_WithStatus(Target.c_str(), &LinkPath, nullptr, nullptr)); // // The buffer needs space for the substitute name and the print name, with // NULL characters. This can't overflow since they are all paths with // lengths less than MAXUSHORT. // const ULONG ReparseBufferSize = (ULONG)(FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer[0]) + Target.length() * sizeof(WCHAR) + LinkPath.Length + 2 * sizeof(UNICODE_NULL)); // // Allocate the reparse data buffer. // const std::unique_ptr Reparse((PREPARSE_DATA_BUFFER) new char[ReparseBufferSize]); ZeroMemory(Reparse.get(), ReparseBufferSize); Reparse->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; // // The data length is the buffer size excluding the header. // Reparse->ReparseDataLength = (USHORT)(ReparseBufferSize - REPARSE_DATA_BUFFER_HEADER_SIZE); // // Copy the NT path into the buffer for the substitute name. // Reparse->MountPointReparseBuffer.SubstituteNameLength = LinkPath.Length; RtlCopyMemory(Reparse->MountPointReparseBuffer.PathBuffer, LinkPath.Buffer, LinkPath.Length); const USHORT Offset = LinkPath.Length + sizeof(UNICODE_NULL); // // Copy the DOS path into the buffer for the print name. // Reparse->MountPointReparseBuffer.PrintNameOffset = Offset; Reparse->MountPointReparseBuffer.PrintNameLength = (USHORT)(Target.length() * sizeof(WCHAR)); RtlCopyMemory((Reparse->MountPointReparseBuffer.PathBuffer + (Offset / sizeof(WCHAR))), Target.c_str(), Target.length() * sizeof(WCHAR)); // // Set the reparse point on the file. // IO_STATUS_BLOCK IoStatus; THROW_IF_NTSTATUS_FAILED(NtFsControlFile( Dir.get(), nullptr, nullptr, nullptr, &IoStatus, FSCTL_SET_REPARSE_POINT, Reparse.get(), ReparseBufferSize, nullptr, 0)); return; } static VOID CreateV1Symlink(const std::wstring& Symlink, std::string_view Target) { // // Create a symlink using the V1 LX symlink format, where the target is // stored in the file data. The reparse data only contains a version // number. // union { REPARSE_DATA_BUFFER Header; struct { CHAR Buffer[REPARSE_DATA_BUFFER_HEADER_SIZE]; ULONG Version; } Data; } Reparse{}; const ULONG ReparseBufferSize = REPARSE_DATA_BUFFER_HEADER_SIZE + sizeof(ULONG); // // The data length is the buffer size excluding the header. // Reparse.Header.ReparseTag = IO_REPARSE_TAG_LX_SYMLINK; Reparse.Header.ReparseDataLength = sizeof(ULONG); Reparse.Data.Version = 1; const auto File = CreateReparsePoint(Symlink, &Reparse, ReparseBufferSize); // // Write the target to the file. // DWORD written; THROW_IF_WIN32_BOOL_FALSE(WriteFile(File.get(), Target.data(), gsl::narrow_cast(Target.size()), &written, nullptr)); VERIFY_ARE_EQUAL(Target.size(), written); return; } static VOID CreateAppExecLink(const std::wstring& Link) { // // This link will not be valid from Windows's perspective, since it only // contains the header and not any actual reparse data. However, it has the // right reparse tag which is sufficient to test drvfs's behavior. // REPARSE_DATA_BUFFER Reparse{}; Reparse.ReparseTag = IO_REPARSE_TAG_APPEXECLINK; Reparse.ReparseDataLength = 0; CreateReparsePoint(Link, &Reparse, REPARSE_DATA_BUFFER_HEADER_SIZE); } static wil::unique_hfile CreateReparsePoint(_In_ const std::wstring& Path, _In_ PVOID ReparseBuffer, _In_ ULONG ReparseBufferSize) { wil::unique_hfile File{CreateFile( Path.c_str(), FILE_GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)}; THROW_LAST_ERROR_IF(!File); IO_STATUS_BLOCK IoStatus; THROW_IF_NTSTATUS_FAILED(NtFsControlFile( File.get(), nullptr, nullptr, nullptr, &IoStatus, FSCTL_SET_REPARSE_POINT, ReparseBuffer, ReparseBufferSize, nullptr, 0)); return File; } static VOID CreateVolume(LPCSTR FileSystem, ULONG MaxSizeInMb, LPCSTR MountPoint, LPCSTR VhdPath) { THROW_LAST_ERROR_IF(!CreateDirectoryA(MountPoint, NULL)); const auto CreateScript = std::vformat( "create vdisk file={} maximum={} type=expandable\n" "select vdisk file={}\n" "attach vdisk\n" "create partition primary\n" "select partition 1\n" "online volume\n" "format fs={} quick\n" "assign mount={}\n", std::make_format_args(VhdPath, MaxSizeInMb, VhdPath, FileSystem, MountPoint)); RunDiskpartScript(CreateScript.c_str()); } static VOID RunDiskpartScript(LPCSTR Script) { const std::wstring ScriptFileName = wsl::windows::common::filesystem::GetTempFilename(); std::ofstream ScriptFile(ScriptFileName); THROW_LAST_ERROR_IF(!ScriptFile); auto Cleanup = wil::scope_exit([&] { DeleteFileW(ScriptFileName.c_str()); }); ScriptFile << Script; ScriptFile.close(); std::wstring CommandLine = L"diskpart.exe /s " + ScriptFileName; THROW_HR_IF(E_FAIL, ((wsl::windows::common::helpers::RunProcess(CommandLine)) != 0)); } static VOID DeleteVolume(LPCSTR MountPoint, LPCSTR VhdPath) { const auto CleanupScript = std::vformat( "select vdisk file={}\n" "select partition 1\n" "remove all\n" "detach vdisk\n", std::make_format_args(VhdPath)); RunDiskpartScript(CleanupScript.c_str()); RemoveDirectoryA(MountPoint); DeleteFileA(VhdPath); } static void ValidateDrvfsMounts(DWORD CreateProcessFlags, DrvFsMode Mode) { auto validate = [CreateProcessFlags](const std::wstring& expectedType, HANDLE token) { const auto commandLine = LxssGenerateWslCommandLine(L"mount | grep -F '/mnt/c type'"); wsl::windows::common::SubProcess process(nullptr, commandLine.c_str(), CreateProcessFlags); process.SetToken(token); process.SetShowWindow(SW_HIDE); const auto output = process.RunAndCaptureOutput(); const auto lines = LxssSplitString(output.Stdout, L"\n"); VERIFY_ARE_EQUAL(lines.size(), 1); VERIFY_IS_TRUE(output.Stdout.find(expectedType) == 0); }; std::wstring elevatedType; std::wstring nonElevatedType; switch (Mode) { case DrvFsMode::Plan9: elevatedType = L"C:\\"; nonElevatedType = L"C:\\"; break; case DrvFsMode::Virtio9p: elevatedType = L"drvfsa"; nonElevatedType = L"drvfs"; break; case DrvFsMode::VirtioFs: elevatedType = L"drvfsaC"; nonElevatedType = L"drvfsC"; break; default: LogError("Unexpected mode %d", (int)Mode); return; } // Validate that mount types are correct in both namespaces validate(elevatedType, nullptr); const auto nonElevatedToken = GetNonElevatedToken(); validate(nonElevatedType, nonElevatedToken.get()); } static VOID VerifyDrvFsSymlink(const std::wstring& Path, const std::wstring& ExpectedTarget, bool Directory) { const std::wstring NtPath = L"\\DosDevices\\" + Path; UNICODE_STRING Name; RtlInitUnicodeString(&Name, NtPath.c_str()); OBJECT_ATTRIBUTES Attributes; InitializeObjectAttributes(&Attributes, &Name, 0, nullptr, 0); wil::unique_hfile Symlink; IO_STATUS_BLOCK IoStatus; THROW_IF_NTSTATUS_FAILED(NtCreateFile( &Symlink, FILE_GENERIC_READ, &Attributes, &IoStatus, nullptr, 0, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), FILE_OPEN, FILE_OPEN_REPARSE_POINT, NULL, 0)); FILE_ATTRIBUTE_TAG_INFORMATION Info; THROW_IF_NTSTATUS_FAILED(NtQueryInformationFile(Symlink.get(), &IoStatus, &Info, sizeof(Info), FileAttributeTagInformation)); VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0); if (Directory != false) { VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); } else { VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0); } VERIFY_ARE_EQUAL(Info.ReparseTag, IO_REPARSE_TAG_SYMLINK); union { char Buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; REPARSE_DATA_BUFFER Data; } ReparseData; THROW_IF_NTSTATUS_FAILED(NtFsControlFile( Symlink.get(), nullptr, nullptr, nullptr, &IoStatus, FSCTL_GET_REPARSE_POINT, nullptr, 0, &ReparseData, sizeof(ReparseData))); VERIFY_ARE_EQUAL(ReparseData.Data.ReparseTag, IO_REPARSE_TAG_SYMLINK); VERIFY_ARE_EQUAL(ReparseData.Data.SymbolicLinkReparseBuffer.Flags, (ULONG)SYMLINK_FLAG_RELATIVE); const std::wstring Target( (PWCHAR)((PCHAR)ReparseData.Data.SymbolicLinkReparseBuffer.PathBuffer + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameOffset), (PWCHAR)((PCHAR)ReparseData.Data.SymbolicLinkReparseBuffer.PathBuffer + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameOffset + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameLength)); VERIFY_ARE_EQUAL(Target, ExpectedTarget); const std::wstring Target2( (PWCHAR)((PCHAR)ReparseData.Data.SymbolicLinkReparseBuffer.PathBuffer + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameOffset), (PWCHAR)((PCHAR)ReparseData.Data.SymbolicLinkReparseBuffer.PathBuffer + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameOffset + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameLength)); VERIFY_ARE_EQUAL(Target2, ExpectedTarget); } static VOID VerifyDrvFsLxSymlink(const std::wstring& Path) { const std::wstring NtPath = L"\\DosDevices\\" + Path; UNICODE_STRING Name; RtlInitUnicodeString(&Name, NtPath.c_str()); OBJECT_ATTRIBUTES Attributes; InitializeObjectAttributes(&Attributes, &Name, 0, nullptr, 0); IO_STATUS_BLOCK IoStatus; FILE_STAT_INFORMATION Info; THROW_IF_NTSTATUS_FAILED(NtQueryInformationByName(&Attributes, &IoStatus, &Info, sizeof(Info), FileStatInformation)); VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0); VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0); VERIFY_ARE_EQUAL(Info.ReparseTag, IO_REPARSE_TAG_LX_SYMLINK); } }; class WSL1 : public DrvFsTests { WSL_TEST_CLASS(WSL1) bool m_initialized{false}; TEST_CLASS_SETUP(TestClassSetup) { if (LxsstuVmMode()) { LogSkipped("This test class is only applicable to WSL1"); } else { VERIFY_ARE_EQUAL(LxsstuInitialize(FALSE), TRUE); VERIFY_ARE_EQUAL(LxsstuLaunchWsl(LXSST_TESTS_INSTALL_COMMAND_LINE), 0); m_initialized = true; } return true; } TEST_CLASS_CLEANUP(TestClassCleanup) { if (m_initialized) { LxsstuUninitialize(FALSE); } return true; } TEST_METHOD(DrvFsDisableQueryByName) { WSL1_TEST_ONLY(); VERIFY_NO_THROW(DrvFsCommon(LX_DRVFS_DISABLE_QUERY_BY_NAME)); } TEST_METHOD(DrvFsDisableQueryByNameAndStatInfo) { WSL1_TEST_ONLY(); VERIFY_NO_THROW(DrvFsCommon(LX_DRVFS_DISABLE_QUERY_BY_NAME_AND_STAT_INFO)); } TEST_METHOD(VfsAccessDrvFs) { WSL1_TEST_ONLY(); DrvFsTests::VfsAccessDrvFs(); } TEST_METHOD(FsCommonDrvFs) { WSL1_TEST_ONLY(); DrvFsTests::FsCommonDrvFs(); } TEST_METHOD(DrvFs) { WSL1_TEST_ONLY(); DrvFsTests::DrvFs(DrvFsMode::WSL1); } TEST_METHOD(DrvFsFat) { WSL1_TEST_ONLY(); DrvFsTests::DrvFsFat(DrvFsMode::WSL1); } TEST_METHOD(DrvFsSmb) { WSL1_TEST_ONLY(); DrvFsTests::DrvFsSmb(DrvFsMode::WSL1); } TEST_METHOD(DrvFsMetadata) { WSL1_TEST_ONLY(); DrvFsTests::DrvFsMetadata(DrvFsMode::WSL1); } TEST_METHOD(XattrDrvFs) { WSL1_TEST_ONLY(); DrvFsTests::XattrDrvFs(DrvFsMode::WSL1); } }; #define WSL2_DRVFS_TEST_CLASS(_mode) \ class WSL2##_mode## : public DrvFsTests \ { \ WSL_TEST_CLASS(WSL2##_mode##) \ std::unique_ptr m_config; \ TEST_CLASS_SETUP(TestClassSetup) \ { \ if (!LxsstuVmMode()) \ { \ LogSkipped("This test class is only applicable to WSL2"); \ } \ else \ { \ VERIFY_ARE_EQUAL(LxsstuInitialize(FALSE), TRUE); \ m_config.reset(new WslConfigChange(LxssGenerateTestConfig({.drvFsMode = DrvFsMode::##_mode##}))); \ VERIFY_ARE_EQUAL(LxsstuLaunchWsl(LXSST_TESTS_INSTALL_COMMAND_LINE), 0); \ } \ \ return true; \ } \ \ TEST_CLASS_CLEANUP(TestClassCleanup) \ { \ if (m_config) \ { \ m_config.reset(); \ LxsstuUninitialize(FALSE); \ } \ \ return true; \ } \ \ TEST_METHOD(VfsAccessDrvFs) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::VfsAccessDrvFs(); \ } \ \ TEST_METHOD(FsCommonDrvFs) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::FsCommonDrvFs(); \ } \ \ TEST_METHOD(DrvFs) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvFs(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(DrvFsFat) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvFsFat(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(DrvFsSmb) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvFsSmb(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(DrvFsMetadata) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvFsMetadata(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(DrvfsMountElevated) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvfsMountElevated(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(DrvfsMountElevatedDifferentConsole) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvfsMountElevatedDifferentConsole(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(DrvfsMountNonElevated) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvfsMountNonElevated(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(DrvfsMountNonElevatedDifferentConsole) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvfsMountNonElevatedDifferentConsole(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(XattrDrvFs) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::XattrDrvFs(DrvFsMode::##_mode##); \ } \ \ TEST_METHOD(DrvFsReFs) \ { \ WSL2_TEST_ONLY(); \ DrvFsTests::DrvFsReFs(DrvFsMode::##_mode##); \ } \ } WSL2_DRVFS_TEST_CLASS(Plan9); // Disabled while an issue with the 6.1 Linux kernel causing disk corruption is investigated. // TODO: Enable again once the issue is resolved // WSL2_DRVFS_TEST_CLASS(Virtio9p); // Disabled because it causes too much noise. // TODO: Enable again once virtiofs is stable // WSL2_DRVFS_TEST_CLASS(VirtioFs); } // namespace DrvFsTests