WSL/test/windows/DrvFsTests.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

1212 lines
No EOL
45 KiB
C++

/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
DrvFsTests.cpp
Abstract:
This file contains drvfs test cases.
--*/
#include "precomp.h"
#include "Common.h"
#include <AclAPI.h>
#include <fstream>
#include <filesystem>
#include "wslservice.h"
#include "registry.hpp"
#include "helpers.hpp"
#include "svccomm.hpp"
#include <userenv.h>
#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> 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()));
if (DrvFsMode.has_value() && DrvFsMode.value() == DrvFsMode::VirtioFs)
{
LogSkipped("TODO: debug test for virtiofs");
return;
}
//
// 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));
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() const
{
SKIP_TEST_ARM64();
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() const
{
SKIP_TEST_ARM64();
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();
if (Mode == DrvFsMode::VirtioFs)
{
LogSkipped("TODO: debug test for virtiofs");
return;
}
VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests xattr drvfs", L"xattr_drvfs"));
}
void DrvFsReFs() const
{
SKIP_TEST_ARM64();
WSL_TEST_VERSION_REQUIRED(wsl::windows::common::helpers::WindowsBuildNumbers::Germanium);
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<PACL, decltype(&::LocalFree), ::LocalFree> 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_DATA_BUFFER> 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{};
constexpr 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<DWORD>(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();
}
TEST_METHOD(DrvFsSmb)
{
WSL1_TEST_ONLY();
DrvFsTests::DrvFsSmb();
}
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<WslConfigChange> m_config; \
TEST_CLASS_SETUP(TestClassSetup) \
{ \
if (!LxsstuVmMode()) \
{ \
LogSkipped("This test class is only applicable to WSL2"); \
} \
else \
{ \
VERIFY_ARE_EQUAL(LxsstuInitialize(FALSE), TRUE); \
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(LXSST_TESTS_INSTALL_COMMAND_LINE), 0); \
m_config.reset(new WslConfigChange(LxssGenerateTestConfig({.drvFsMode = DrvFsMode::##_mode##}))); \
} \
\
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(); \
} \
\
TEST_METHOD(DrvFsSmb) \
{ \
WSL2_TEST_ONLY(); \
DrvFsTests::DrvFsSmb(); \
} \
\
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(); \
} \
}
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