/*++ Copyright (c) Microsoft. All rights reserved. Module Name: Plan9Tests.cpp Abstract: This file contains test cases for the plan9 logic. --*/ #include "precomp.h" #include "Common.h" #define LXSST_P9_PREFIX L"\\\\wsl.localhost\\" LXSS_DISTRO_NAME_TEST_L #define LXSST_P9_TEST_DIR LXSST_P9_PREFIX L"\\data\\p9_test" #define LXSST_P9_CLEANUP_COMMAND_LINE L"/bin/bash -c \"rm -rf /data/p9_test\"" #define VERIFY_LAST_ERROR(error) VERIFY_ARE_EQUAL(static_cast(error), GetLastError()) namespace Plan9Tests { class Plan9Tests { WSL_TEST_CLASS(Plan9Tests) // Initialize the tests TEST_CLASS_SETUP(TestClassSetup) { VERIFY_ARE_EQUAL(LxsstuInitialize(TRUE), TRUE); const auto result = std::filesystem::create_directories(LXSST_P9_TEST_DIR); auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { if (!result) { auto [out, _] = LxsstuLaunchPowershellAndCaptureOutput(L"(Get-Service P9rdr).Status", 0); LogInfo("p9rdr state: %s", out.c_str()); VERIFY_NO_THROW(LxsstuUninitialize(TRUE)); } }); VERIFY_IS_TRUE(result); return true; } // Uninitialize the tests. TEST_CLASS_CLEANUP(TestClassCleanup) { LxsstuLaunchWsl(LXSST_P9_CLEANUP_COMMAND_LINE); VERIFY_NO_THROW(LxsstuUninitialize(TRUE)); return true; } TEST_METHOD_CLEANUP(MethodCleanup) { LxssLogKernelOutput(); return true; } // Tests creating a file, writing to it, and reading from it. TEST_METHOD(TestReadWriteFile) { constexpr std::string_view data{"test data"}; const auto file = CreateNewTestFile(L"\\readwritetest", data); VERIFY_WIN32_BOOL_SUCCEEDED(SetFilePointerEx(file.get(), {}, nullptr, FILE_BEGIN)); char buffer[1024]; DWORD bytes; VERIFY_WIN32_BOOL_SUCCEEDED(ReadFile(file.get(), buffer, sizeof(buffer), &bytes, nullptr)); VERIFY_ARE_EQUAL(data.size(), bytes); VERIFY_ARE_EQUAL(data, std::string_view(buffer, bytes)); } // Tests using a large buffer to read/write a file. TEST_METHOD(TestReadWriteFileLarge) { const auto file = CreateTestFile(L"\\readwritelargetest", FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_CREATE); char buffer[64 * 1024]; for (size_t i = 0; i < sizeof(buffer); ++i) { buffer[i] = i % 26 + 'a'; } for (int i = 0; i < 10; ++i) { DWORD bytesWritten; VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(file.get(), buffer, sizeof(buffer), &bytesWritten, nullptr)); VERIFY_ARE_EQUAL(sizeof(buffer), bytesWritten); } VERIFY_WIN32_BOOL_SUCCEEDED(SetFilePointerEx(file.get(), {}, nullptr, FILE_BEGIN)); char buffer2[64 * 1024]; DWORD bytesRead; for (int i = 0; i < 10; ++i) { VERIFY_WIN32_BOOL_SUCCEEDED(ReadFile(file.get(), buffer2, sizeof(buffer2), &bytesRead, nullptr)); VERIFY_ARE_EQUAL(sizeof(buffer), bytesRead); VERIFY_IS_TRUE(memcmp(buffer, buffer2, sizeof(buffer)) == 0); } VERIFY_WIN32_BOOL_SUCCEEDED(ReadFile(file.get(), buffer2, sizeof(buffer2), &bytesRead, nullptr)); VERIFY_ARE_EQUAL(0u, bytesRead); } // Tests querying and setting file information. TEST_METHOD(TestQuerySetInfo) { // Check the attributes on the test directory. FILE_BASIC_INFO basicInfo{}; auto file = CreateTestFile({}, FILE_READ_ATTRIBUTES); VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileBasicInfo, &basicInfo, sizeof(basicInfo))); VERIFY_IS_TRUE(WI_IsFlagSet(basicInfo.FileAttributes, FILE_ATTRIBUTE_DIRECTORY)); VERIFY_ARE_NOT_EQUAL(0, basicInfo.ChangeTime.QuadPart); VERIFY_ARE_NOT_EQUAL(0, basicInfo.CreationTime.QuadPart); VERIFY_ARE_NOT_EQUAL(0, basicInfo.LastAccessTime.QuadPart); VERIFY_ARE_NOT_EQUAL(0, basicInfo.LastWriteTime.QuadPart); FILE_STANDARD_INFO standardInfo{}; VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileStandardInfo, &standardInfo, sizeof(basicInfo))); VERIFY_IS_TRUE(standardInfo.Directory); VERIFY_IS_FALSE(standardInfo.DeletePending); const auto id = GetFileId({}); VERIFY_ARE_NOT_EQUAL(0ull, id); // Check attributes on a file. file = CreateNewTestFile(L"\\queryinfotest", "0123456789"); VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileBasicInfo, &basicInfo, sizeof(basicInfo))); VERIFY_IS_FALSE(WI_IsFlagSet(basicInfo.FileAttributes, FILE_ATTRIBUTE_DIRECTORY)); VERIFY_ARE_NOT_EQUAL(0, basicInfo.ChangeTime.QuadPart); VERIFY_ARE_NOT_EQUAL(0, basicInfo.CreationTime.QuadPart); VERIFY_ARE_NOT_EQUAL(0, basicInfo.LastAccessTime.QuadPart); VERIFY_ARE_NOT_EQUAL(0, basicInfo.LastWriteTime.QuadPart); VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileStandardInfo, &standardInfo, sizeof(basicInfo))); VERIFY_IS_FALSE(standardInfo.Directory); VERIFY_IS_FALSE(standardInfo.DeletePending); VERIFY_ARE_EQUAL(1u, standardInfo.NumberOfLinks); VERIFY_ARE_EQUAL(10, standardInfo.EndOfFile.QuadPart); const auto id2 = GetFileId(L"\\queryinfotest"); VERIFY_ARE_NOT_EQUAL(0ull, id2); VERIFY_ARE_NOT_EQUAL(id, id2); // Try truncating the file. LARGE_INTEGER size; size.QuadPart = 5; VERIFY_WIN32_BOOL_SUCCEEDED(SetFilePointerEx(file.get(), size, nullptr, FILE_BEGIN)); VERIFY_WIN32_BOOL_SUCCEEDED(SetEndOfFile(file.get())); VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileStandardInfo, &standardInfo, sizeof(basicInfo))); VERIFY_ARE_EQUAL(5, standardInfo.EndOfFile.QuadPart); } // Tests deleting files and directories. TEST_METHOD(TestDelete) { // Delete a file. CreateNewTestFile(L"\\deletetestfile", "0123456789"); VERIFY_IS_TRUE(CheckFileExists(L"\\deletetestfile")); VERIFY_WIN32_BOOL_SUCCEEDED(DeleteFileW(LXSST_P9_TEST_DIR L"\\deletetestfile")); VERIFY_IS_FALSE(CheckFileExists(L"\\deletetestfile")); // Delete a directory. VERIFY_WIN32_BOOL_SUCCEEDED(CreateDirectory(LXSST_P9_TEST_DIR L"\\deletetestdir", nullptr)); VERIFY_IS_TRUE(CheckFileExists(L"\\deletetestdir")); VERIFY_WIN32_BOOL_SUCCEEDED(RemoveDirectory(LXSST_P9_TEST_DIR L"\\deletetestdir")); VERIFY_IS_FALSE(CheckFileExists(L"\\deletetestdir")); // Try to delete non-empty directory. VERIFY_WIN32_BOOL_SUCCEEDED(CreateDirectory(LXSST_P9_TEST_DIR L"\\deletetestdir", nullptr)); CreateNewTestFile(L"\\deletetestdir\\testfile", "0123456789"); VERIFY_WIN32_BOOL_FAILED(RemoveDirectory(LXSST_P9_TEST_DIR L"\\deletetestdir")); VERIFY_LAST_ERROR(ERROR_DIR_NOT_EMPTY); VERIFY_IS_TRUE(CheckFileExists(L"\\deletetestdir")); } // Tests renaming files and directories. TEST_METHOD(TestRename) { // Rename a file. CreateNewTestFile(L"\\renametestfile", "0123456789"); auto id = GetFileId(L"\\renametestfile"); VERIFY_WIN32_BOOL_SUCCEEDED(MoveFile(LXSST_P9_TEST_DIR L"\\renametestfile", LXSST_P9_TEST_DIR L"\\renametestfile2")); auto id2 = GetFileId(L"\\renametestfile2"); VERIFY_ARE_EQUAL(id, id2); VERIFY_IS_FALSE(CheckFileExists(L"\\renametestfile")); CreateNewTestFile(L"\\renametestfile", "abcdefg"); id = GetFileId(L"\\renametestfile"); VERIFY_ARE_NOT_EQUAL(id, id2); VERIFY_WIN32_BOOL_FAILED(MoveFile(LXSST_P9_TEST_DIR L"\\renametestfile", LXSST_P9_TEST_DIR L"\\renametestfile2")); VERIFY_LAST_ERROR(ERROR_ALREADY_EXISTS); VERIFY_WIN32_BOOL_SUCCEEDED( MoveFileEx(LXSST_P9_TEST_DIR L"\\renametestfile", LXSST_P9_TEST_DIR L"\\renametestfile2", MOVEFILE_REPLACE_EXISTING)); id2 = GetFileId(L"\\renametestfile2"); VERIFY_ARE_EQUAL(id, id2); // Rename a directory VERIFY_WIN32_BOOL_SUCCEEDED(CreateDirectory(LXSST_P9_TEST_DIR L"\\renametestdir", nullptr)); id = GetFileId(L"\\renametestdir"); VERIFY_WIN32_BOOL_SUCCEEDED(MoveFile(LXSST_P9_TEST_DIR L"\\renametestdir", LXSST_P9_TEST_DIR L"\\renametestdir2")); id2 = GetFileId(L"\\renametestdir2"); VERIFY_ARE_EQUAL(id, id2); VERIFY_IS_FALSE(CheckFileExists(L"\\renametestdir")); // Directory over a file. VERIFY_WIN32_BOOL_FAILED( MoveFileEx(LXSST_P9_TEST_DIR L"\\renametestdir2", LXSST_P9_TEST_DIR L"\\renametestfile2", MOVEFILE_REPLACE_EXISTING)); VERIFY_LAST_ERROR(ERROR_DIRECTORY); // File over a directory. VERIFY_WIN32_BOOL_FAILED( MoveFileEx(LXSST_P9_TEST_DIR L"\\renametestfile2", LXSST_P9_TEST_DIR L"\\renametestdir2", MOVEFILE_REPLACE_EXISTING)); VERIFY_LAST_ERROR(ERROR_ACCESS_DENIED); } // Tests listing the files in a directory. TEST_METHOD(TestReadDir) { constexpr int fileCount = 500; VERIFY_WIN32_BOOL_SUCCEEDED(CreateDirectory(LXSST_P9_TEST_DIR L"\\readdirtest", nullptr)); for (int i = 0; i < fileCount; ++i) { wchar_t path[MAX_PATH]{}; swprintf_s(path, L"\\readdirtest\\%d", i); CreateNewTestFile(path, "0123456789"); } WIN32_FIND_DATA findData{}; const wil::unique_hfind find{FindFirstFile(LXSST_P9_TEST_DIR L"\\readdirtest\\*", &findData)}; VERIFY_WIN32_BOOL_SUCCEEDED(static_cast(find)); int count{}; bool foundFiles[fileCount]{}; do { ++count; VERIFY_ARE_NOT_EQUAL(0u, findData.dwFileAttributes); VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast(&findData.ftCreationTime)); VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast(&findData.ftLastAccessTime)); VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast(&findData.ftLastWriteTime)); if (findData.cFileName[0] != L'.') { VERIFY_ARE_EQUAL(0u, findData.nFileSizeHigh); VERIFY_ARE_EQUAL(10u, findData.nFileSizeLow); int file = wcstol(findData.cFileName, nullptr, 10); VERIFY_IS_GREATER_THAN_OR_EQUAL(file, 0); VERIFY_IS_LESS_THAN(file, fileCount); VERIFY_IS_FALSE(foundFiles[file]); foundFiles[file] = true; } } while (FindNextFile(find.get(), &findData)); VERIFY_LAST_ERROR(ERROR_NO_MORE_FILES); VERIFY_ARE_EQUAL(fileCount + 2, count); for (int i = 0; i < fileCount; ++i) { VERIFY_IS_TRUE(foundFiles[i]); } } // Tests using mount points inside the WSL instance. TEST_METHOD(TestMounts) { // Check access into mounts like procfs is allowed. wil::unique_hfile file{CreateFile( LXSST_P9_PREFIX L"\\proc\\stat", FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr)}; VERIFY_WIN32_BOOL_SUCCEEDED(static_cast(file)); char buffer[1024]; DWORD bytes; VERIFY_WIN32_BOOL_SUCCEEDED(ReadFile(file.get(), buffer, sizeof(buffer), &bytes, nullptr)); VERIFY_IS_GREATER_THAN(bytes, 0u); // Check access into drvfs mounts is not allowed. file.reset(CreateFile( LXSST_P9_PREFIX L"\\mnt\\c", FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr)); VERIFY_IS_FALSE(static_cast(file)); VERIFY_LAST_ERROR(ERROR_ACCESS_DENIED); file.reset(CreateFile( LXSST_P9_PREFIX L"\\mnt\\c\\Windows", FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr)); VERIFY_IS_FALSE(static_cast(file)); VERIFY_LAST_ERROR(ERROR_ACCESS_DENIED); } TEST_METHOD(TestCreate) { wil::unique_hfile file; IO_STATUS_BLOCK ioStatus; // Check error codes for non-existing files. auto status = CreateFileNt(&file, LXSST_P9_PREFIX L"\\dat\\p9_test", FILE_GENERIC_READ, ioStatus); VERIFY_ARE_EQUAL(STATUS_OBJECT_PATH_NOT_FOUND, status); status = CreateFileNt(&file, LXSST_P9_PREFIX L"\\data\\foo", FILE_GENERIC_READ, ioStatus); VERIFY_ARE_EQUAL(STATUS_OBJECT_NAME_NOT_FOUND, status); status = CreateFileNt(&file, LXSST_P9_PREFIX L"\\etc\\resolve.conf\\foo", FILE_GENERIC_READ, ioStatus); VERIFY_ARE_EQUAL(STATUS_OBJECT_PATH_NOT_FOUND, status); // Create a file. VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_WRITE, ioStatus, FILE_CREATE)); VERIFY_ARE_EQUAL(static_cast(FILE_CREATED), ioStatus.Information); // Write some test content. const std::string contents{"hello"}; DWORD bytes; VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(file.get(), contents.data(), static_cast(contents.size()), &bytes, nullptr)); VERIFY_ARE_EQUAL(contents.size(), bytes); file.reset(); // Exclusive create should fail now. status = CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_READ, ioStatus, FILE_CREATE); VERIFY_ARE_EQUAL(STATUS_OBJECT_NAME_COLLISION, status); // Open-if existing file. VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_READ, ioStatus, FILE_OPEN_IF)); VERIFY_ARE_EQUAL(static_cast(FILE_OPENED), ioStatus.Information); LARGE_INTEGER size; VERIFY_WIN32_BOOL_SUCCEEDED(GetFileSizeEx(file.get(), &size)); VERIFY_ARE_EQUAL(5, size.QuadPart); // Open-if new file. VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile2", FILE_GENERIC_READ, ioStatus, FILE_OPEN_IF)); VERIFY_ARE_EQUAL(static_cast(FILE_CREATED), ioStatus.Information); // Overwrite non-existing file. status = CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile3", FILE_GENERIC_WRITE, ioStatus, FILE_OVERWRITE); VERIFY_ARE_EQUAL(STATUS_OBJECT_NAME_NOT_FOUND, status); VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile3", FILE_GENERIC_WRITE, ioStatus, FILE_OVERWRITE_IF)); VERIFY_ARE_EQUAL(static_cast(FILE_CREATED), ioStatus.Information); // Overwrite existing file. VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_WRITE, ioStatus, FILE_OVERWRITE)); VERIFY_ARE_EQUAL(static_cast(FILE_OVERWRITTEN), ioStatus.Information); VERIFY_WIN32_BOOL_SUCCEEDED(GetFileSizeEx(file.get(), &size)); VERIFY_ARE_EQUAL(0, size.QuadPart); VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(file.get(), contents.data(), static_cast(contents.size()), &bytes, nullptr)); VERIFY_ARE_EQUAL(contents.size(), bytes); file.reset(); VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_WRITE, ioStatus, FILE_OVERWRITE_IF)); VERIFY_ARE_EQUAL(static_cast(FILE_OVERWRITTEN), ioStatus.Information); VERIFY_WIN32_BOOL_SUCCEEDED(GetFileSizeEx(file.get(), &size)); VERIFY_ARE_EQUAL(0, size.QuadPart); // Open a directory with FILE_NON_DIRECTORY_FILE. status = CreateFileNt(&file, LXSST_P9_TEST_DIR, FILE_GENERIC_READ, ioStatus, FILE_OPEN, 0, FILE_NON_DIRECTORY_FILE); VERIFY_ARE_EQUAL(STATUS_FILE_IS_A_DIRECTORY, status); // Open a file with FILE_DIRECTORY_FILE. status = CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_READ, ioStatus, FILE_OPEN, 0, FILE_DIRECTORY_FILE); VERIFY_ARE_EQUAL(STATUS_NOT_A_DIRECTORY, status); } static auto EnablePlan9Logging() { LxssWriteWslDistroConfig("[fileServer]\nlogFile=/plan9-logs.txt\nlogTruncate=false\nlogLevel=5"); return wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [] { // clean up wsl.conf file LxsstuLaunchWsl(L"rm /etc/wsl.conf"); TerminateDistribution(); }); } TEST_METHOD(TestPlan9ServerTimeout) { // This test has proven to be unstable, most likely because another program opens a file inside the distro, which prevents it from terminating. SKIP_TEST_UNSTABLE(); auto revertLogging = EnablePlan9Logging(); auto dumpLogs = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { const auto output = LxsstuLaunchWslAndCaptureOutput(L"cat /plan9-logs.txt"); LogInfo("Plan9 logs: %s", output.first.c_str()); }); wsl::windows::common::SvcComm service; auto distro = service.GetDefaultDistribution(); service.TerminateInstance(&distro); auto getDistroState = [distro, &service]() -> LxssDistributionState { auto distros = service.EnumerateDistributions(); const auto it = std::find_if(distros.begin(), distros.end(), [&](const auto& e) { return IsEqualGUID(distro, e.DistroGuid); }); VERIFY_ARE_NOT_EQUAL(it, distros.end()); return it->State; }; VERIFY_ARE_EQUAL(getDistroState(), LxssDistributionStateInstalled); // Open a file via \\wsl.localhost and validate that the distro does not terminate auto file = CreateTestFile(L"\\9p-test-file", GENERIC_ALL, FILE_CREATE, FILE_FLAG_DELETE_ON_CLOSE); // Now the distro should be running VERIFY_ARE_EQUAL(getDistroState(), LxssDistributionStateRunning); // Validate that the distro does not terminate until the file is closed // Note: Distributions time out after 10 seconds. std::this_thread::sleep_for(std::chrono::seconds(20)); // Close the file and make sure that the distro terminates file.reset(); // The distro should now time out and stop const auto deadline = std::chrono::steady_clock::now() + std::chrono::minutes(1); while (std::chrono::steady_clock::now() < deadline && getDistroState() != LxssDistributionStateInstalled) { std::this_thread::sleep_for(std::chrono::seconds(1)); } VERIFY_ARE_EQUAL(getDistroState(), LxssDistributionStateInstalled); } TEST_METHOD(TestPlan9AdditionalGroupAccess) { ULONG Uid{}; ULONG Gid{}; // Create a user for this test CreateUser(L"plan9testuser", &Uid, &Gid); // Create a folder that's unaccessible to plan9testuser VERIFY_ARE_EQUAL( LxsstuLaunchWsl(L"mkdir -p /tmp/plan9-group-test && groupadd -f plan9testgroup && chown root:plan9testgroup " L"/tmp/plan9-group-test && " L"echo -n foo > /tmp/plan9-group-test/bar && chmod 770 /tmp/plan9-group-test"), 0u); auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LxsstuLaunchWsl(L"-u root rm -rf /etc/wsl.conf /tmp/plan9-group-test"); TerminateDistribution(); }); // Make plan9testuser the default LxssWriteWslDistroConfig("[user]\ndefault=plan9testuser\n"); TerminateDistribution(); // Validate that folder isn't accessible constexpr auto path = L"\\\\wsl.localhost\\" LXSS_DISTRO_NAME_TEST "\\tmp\\plan9-group-test\\bar"; std::wifstream file(path); VERIFY_IS_FALSE(file.good()); // Add plan9testuser to plan9testgroup VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"-u root usermod -G plan9testgroup -a plan9testuser"), 0u); // Validate that the file can be accessed now TerminateDistribution(); // There's a race condition on fe_release that can cause opening this file to fail. try { wsl::shared::retry::RetryWithTimeout( [&file]() { file.open(path); LogInfo("Failed to open %ls, %d", path, errno); THROW_HR_IF(E_ABORT, !file.good()); }, std::chrono::seconds(1), std::chrono::minutes(2)); } catch (...) { LogError("Timed out trying to open: %ls", path); VERIFY_FAIL(); } std::wstring content(3, '\0'); VERIFY_IS_TRUE(file.read(content.data(), content.size()).good()); VERIFY_ARE_EQUAL(content, L"foo"); } /* Plan9 Test Helper Methods */ static wil::unique_hfile CreateTestFile(std::wstring_view path, DWORD desiredAccess, DWORD disposition = OPEN_EXISTING, DWORD flags = 0) { std::wstring fullPath{LXSST_P9_TEST_DIR}; fullPath += path; wil::unique_hfile file{CreateFile( fullPath.c_str(), desiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, disposition, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | flags, nullptr)}; VERIFY_WIN32_BOOL_SUCCEEDED(static_cast(file)); return file; } wil::unique_hfile CreateNewTestFile(const std::wstring& path, std::string_view contents) { auto file = CreateTestFile(path, FILE_GENERIC_WRITE | FILE_GENERIC_READ, CREATE_NEW); DWORD bytes; VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(file.get(), contents.data(), static_cast(contents.size()), &bytes, nullptr)); VERIFY_ARE_EQUAL(contents.size(), bytes); return file; } static NTSTATUS CreateFileNt( PHANDLE handle, LPCWSTR name, ACCESS_MASK desiredAccess, IO_STATUS_BLOCK& ioStatus, ULONG disposition = FILE_OPEN, ULONG attributes = 0, ULONG createOptions = 0) { UNICODE_STRING pathu; THROW_IF_NTSTATUS_FAILED(RtlDosPathNameToNtPathName_U_WithStatus(name, &pathu, nullptr, nullptr)); wil::unique_process_heap_ptr buffer{pathu.Buffer}; OBJECT_ATTRIBUTES oa; InitializeObjectAttributes(&oa, &pathu, OBJ_CASE_INSENSITIVE, nullptr, nullptr); return NtCreateFile( handle, desiredAccess | SYNCHRONIZE, &oa, &ioStatus, nullptr, attributes, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, disposition, createOptions | FILE_SYNCHRONOUS_IO_ALERT, nullptr, 0); } static bool CheckFileExists(std::wstring_view path) { std::wstring fullPath{LXSST_P9_TEST_DIR}; fullPath += path; if (!PathFileExists(fullPath.c_str())) { VERIFY_LAST_ERROR(ERROR_FILE_NOT_FOUND); return false; } return true; } ULONGLONG GetFileId(std::wstring_view path) { BY_HANDLE_FILE_INFORMATION info; const auto file = CreateTestFile(path, FILE_READ_ATTRIBUTES); VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandle(file.get(), &info)); return static_cast(info.nFileIndexHigh) << 32 | info.nFileIndexLow; } }; } // namespace Plan9Tests