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

570 lines
No EOL
24 KiB
C++

/*++
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<DWORD>(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<bool>(find));
int count{};
bool foundFiles[fileCount]{};
do
{
++count;
VERIFY_ARE_NOT_EQUAL(0u, findData.dwFileAttributes);
VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast<PULONGLONG>(&findData.ftCreationTime));
VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast<PULONGLONG>(&findData.ftLastAccessTime));
VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast<PULONGLONG>(&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<bool>(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<bool>(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<bool>(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<ULONG_PTR>(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<DWORD>(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<ULONG_PTR>(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<ULONG_PTR>(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<ULONG_PTR>(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<ULONG_PTR>(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<DWORD>(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<ULONG_PTR>(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<void>(
[&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<bool>(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<DWORD>(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<WCHAR> 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<ULONGLONG>(info.nFileIndexHigh) << 32 | info.nFileIndexLow;
}
};
} // namespace Plan9Tests