| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | Plan9Tests.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains test cases for the plan9 logic. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "Common.h" |
| 17 | |
| 18 | #define LXSST_P9_PREFIX L"\\\\wsl.localhost\\" LXSS_DISTRO_NAME_TEST_L |
| 19 | #define LXSST_P9_TEST_DIR LXSST_P9_PREFIX L"\\data\\p9_test" |
| 20 | #define LXSST_P9_CLEANUP_COMMAND_LINE L"/bin/bash -c \"rm -rf /data/p9_test\"" |
| 21 | |
| 22 | #define VERIFY_LAST_ERROR(error) VERIFY_ARE_EQUAL(static_cast<DWORD>(error), GetLastError()) |
| 23 | |
| 24 | namespace Plan9Tests { |
| 25 | class Plan9Tests |
| 26 | { |
| 27 | WSL_TEST_CLASS(Plan9Tests) |
| 28 | |
| 29 | // Initialize the tests |
| 30 | TEST_CLASS_SETUP(TestClassSetup) |
| 31 | { |
| 32 | VERIFY_ARE_EQUAL(LxsstuInitialize(TRUE), TRUE); |
| 33 | |
| 34 | const auto result = std::filesystem::create_directories(LXSST_P9_TEST_DIR); |
| 35 | auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { |
| 36 | if (!result) |
| 37 | { |
| 38 | auto [out, _] = LxsstuLaunchPowershellAndCaptureOutput(L"(Get-Service P9rdr).Status", 0); |
| 39 | LogInfo("p9rdr state: %s", out.c_str()); |
| 40 | VERIFY_NO_THROW(LxsstuUninitialize(TRUE)); |
| 41 | } |
| 42 | }); |
| 43 | |
| 44 | VERIFY_IS_TRUE(result); |
| 45 | return true; |
| 46 | } |
| 47 | |
| 48 | // Uninitialize the tests. |
| 49 | TEST_CLASS_CLEANUP(TestClassCleanup) |
| 50 | { |
| 51 | LxsstuLaunchWsl(LXSST_P9_CLEANUP_COMMAND_LINE); |
| 52 | VERIFY_NO_THROW(LxsstuUninitialize(TRUE)); |
| 53 | |
| 54 | return true; |
| 55 | } |
| 56 | |
| 57 | TEST_METHOD_CLEANUP(MethodCleanup) |
| 58 | { |
| 59 | LxssLogKernelOutput(); |
| 60 | return true; |
| 61 | } |
| 62 | |
| 63 | // Tests creating a file, writing to it, and reading from it. |
| 64 | TEST_METHOD(TestReadWriteFile) |
| 65 | { |
| 66 | constexpr std::string_view data{"test data"}; |
| 67 | const auto file = CreateNewTestFile(L"\\readwritetest", data); |
| 68 | |
| 69 | VERIFY_WIN32_BOOL_SUCCEEDED(SetFilePointerEx(file.get(), {}, nullptr, FILE_BEGIN)); |
| 70 | char buffer[1024]; |
| 71 | DWORD bytes; |
| 72 | VERIFY_WIN32_BOOL_SUCCEEDED(ReadFile(file.get(), buffer, sizeof(buffer), &bytes, nullptr)); |
| 73 | VERIFY_ARE_EQUAL(data.size(), bytes); |
| 74 | VERIFY_ARE_EQUAL(data, std::string_view(buffer, bytes)); |
| 75 | } |
| 76 | |
| 77 | // Tests using a large buffer to read/write a file. |
| 78 | TEST_METHOD(TestReadWriteFileLarge) |
| 79 | { |
| 80 | const auto file = CreateTestFile(L"\\readwritelargetest", FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_CREATE); |
| 81 | char buffer[64 * 1024]; |
| 82 | for (size_t i = 0; i < sizeof(buffer); ++i) |
| 83 | { |
| 84 | buffer[i] = i % 26 + 'a'; |
| 85 | } |
| 86 | |
| 87 | for (int i = 0; i < 10; ++i) |
| 88 | { |
| 89 | DWORD bytesWritten; |
| 90 | VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(file.get(), buffer, sizeof(buffer), &bytesWritten, nullptr)); |
| 91 | VERIFY_ARE_EQUAL(sizeof(buffer), bytesWritten); |
| 92 | } |
| 93 | |
| 94 | VERIFY_WIN32_BOOL_SUCCEEDED(SetFilePointerEx(file.get(), {}, nullptr, FILE_BEGIN)); |
| 95 | char buffer2[64 * 1024]; |
| 96 | DWORD bytesRead; |
| 97 | for (int i = 0; i < 10; ++i) |
| 98 | { |
| 99 | VERIFY_WIN32_BOOL_SUCCEEDED(ReadFile(file.get(), buffer2, sizeof(buffer2), &bytesRead, nullptr)); |
| 100 | VERIFY_ARE_EQUAL(sizeof(buffer), bytesRead); |
| 101 | VERIFY_IS_TRUE(memcmp(buffer, buffer2, sizeof(buffer)) == 0); |
| 102 | } |
| 103 | |
| 104 | VERIFY_WIN32_BOOL_SUCCEEDED(ReadFile(file.get(), buffer2, sizeof(buffer2), &bytesRead, nullptr)); |
| 105 | VERIFY_ARE_EQUAL(0u, bytesRead); |
| 106 | } |
| 107 | |
| 108 | // Tests querying and setting file information. |
| 109 | TEST_METHOD(TestQuerySetInfo) |
| 110 | { |
| 111 | // Check the attributes on the test directory. |
| 112 | FILE_BASIC_INFO basicInfo{}; |
| 113 | auto file = CreateTestFile({}, FILE_READ_ATTRIBUTES); |
| 114 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileBasicInfo, &basicInfo, sizeof(basicInfo))); |
| 115 | VERIFY_IS_TRUE(WI_IsFlagSet(basicInfo.FileAttributes, FILE_ATTRIBUTE_DIRECTORY)); |
| 116 | VERIFY_ARE_NOT_EQUAL(0, basicInfo.ChangeTime.QuadPart); |
| 117 | VERIFY_ARE_NOT_EQUAL(0, basicInfo.CreationTime.QuadPart); |
| 118 | VERIFY_ARE_NOT_EQUAL(0, basicInfo.LastAccessTime.QuadPart); |
| 119 | VERIFY_ARE_NOT_EQUAL(0, basicInfo.LastWriteTime.QuadPart); |
| 120 | FILE_STANDARD_INFO standardInfo{}; |
| 121 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileStandardInfo, &standardInfo, sizeof(basicInfo))); |
| 122 | VERIFY_IS_TRUE(standardInfo.Directory); |
| 123 | VERIFY_IS_FALSE(standardInfo.DeletePending); |
| 124 | const auto id = GetFileId({}); |
| 125 | VERIFY_ARE_NOT_EQUAL(0ull, id); |
| 126 | |
| 127 | // Check attributes on a file. |
| 128 | file = CreateNewTestFile(L"\\queryinfotest", "0123456789"); |
| 129 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileBasicInfo, &basicInfo, sizeof(basicInfo))); |
| 130 | VERIFY_IS_FALSE(WI_IsFlagSet(basicInfo.FileAttributes, FILE_ATTRIBUTE_DIRECTORY)); |
| 131 | VERIFY_ARE_NOT_EQUAL(0, basicInfo.ChangeTime.QuadPart); |
| 132 | VERIFY_ARE_NOT_EQUAL(0, basicInfo.CreationTime.QuadPart); |
| 133 | VERIFY_ARE_NOT_EQUAL(0, basicInfo.LastAccessTime.QuadPart); |
| 134 | VERIFY_ARE_NOT_EQUAL(0, basicInfo.LastWriteTime.QuadPart); |
| 135 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileStandardInfo, &standardInfo, sizeof(basicInfo))); |
| 136 | VERIFY_IS_FALSE(standardInfo.Directory); |
| 137 | VERIFY_IS_FALSE(standardInfo.DeletePending); |
| 138 | VERIFY_ARE_EQUAL(1u, standardInfo.NumberOfLinks); |
| 139 | VERIFY_ARE_EQUAL(10, standardInfo.EndOfFile.QuadPart); |
| 140 | const auto id2 = GetFileId(L"\\queryinfotest"); |
| 141 | VERIFY_ARE_NOT_EQUAL(0ull, id2); |
| 142 | VERIFY_ARE_NOT_EQUAL(id, id2); |
| 143 | |
| 144 | // Try truncating the file. |
| 145 | LARGE_INTEGER size; |
| 146 | size.QuadPart = 5; |
| 147 | VERIFY_WIN32_BOOL_SUCCEEDED(SetFilePointerEx(file.get(), size, nullptr, FILE_BEGIN)); |
| 148 | VERIFY_WIN32_BOOL_SUCCEEDED(SetEndOfFile(file.get())); |
| 149 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandleEx(file.get(), FileStandardInfo, &standardInfo, sizeof(basicInfo))); |
| 150 | VERIFY_ARE_EQUAL(5, standardInfo.EndOfFile.QuadPart); |
| 151 | } |
| 152 | |
| 153 | // Tests deleting files and directories. |
| 154 | TEST_METHOD(TestDelete) |
| 155 | { |
| 156 | // Delete a file. |
| 157 | CreateNewTestFile(L"\\deletetestfile", "0123456789"); |
| 158 | VERIFY_IS_TRUE(CheckFileExists(L"\\deletetestfile")); |
| 159 | VERIFY_WIN32_BOOL_SUCCEEDED(DeleteFileW(LXSST_P9_TEST_DIR L"\\deletetestfile")); |
| 160 | VERIFY_IS_FALSE(CheckFileExists(L"\\deletetestfile")); |
| 161 | |
| 162 | // Delete a directory. |
| 163 | VERIFY_WIN32_BOOL_SUCCEEDED(CreateDirectory(LXSST_P9_TEST_DIR L"\\deletetestdir", nullptr)); |
| 164 | VERIFY_IS_TRUE(CheckFileExists(L"\\deletetestdir")); |
| 165 | VERIFY_WIN32_BOOL_SUCCEEDED(RemoveDirectory(LXSST_P9_TEST_DIR L"\\deletetestdir")); |
| 166 | VERIFY_IS_FALSE(CheckFileExists(L"\\deletetestdir")); |
| 167 | |
| 168 | // Try to delete non-empty directory. |
| 169 | VERIFY_WIN32_BOOL_SUCCEEDED(CreateDirectory(LXSST_P9_TEST_DIR L"\\deletetestdir", nullptr)); |
| 170 | CreateNewTestFile(L"\\deletetestdir\\testfile", "0123456789"); |
| 171 | VERIFY_WIN32_BOOL_FAILED(RemoveDirectory(LXSST_P9_TEST_DIR L"\\deletetestdir")); |
| 172 | VERIFY_LAST_ERROR(ERROR_DIR_NOT_EMPTY); |
| 173 | VERIFY_IS_TRUE(CheckFileExists(L"\\deletetestdir")); |
| 174 | } |
| 175 | |
| 176 | // Tests renaming files and directories. |
| 177 | TEST_METHOD(TestRename) |
| 178 | { |
| 179 | // Rename a file. |
| 180 | CreateNewTestFile(L"\\renametestfile", "0123456789"); |
| 181 | auto id = GetFileId(L"\\renametestfile"); |
| 182 | VERIFY_WIN32_BOOL_SUCCEEDED(MoveFile(LXSST_P9_TEST_DIR L"\\renametestfile", LXSST_P9_TEST_DIR L"\\renametestfile2")); |
| 183 | auto id2 = GetFileId(L"\\renametestfile2"); |
| 184 | VERIFY_ARE_EQUAL(id, id2); |
| 185 | VERIFY_IS_FALSE(CheckFileExists(L"\\renametestfile")); |
| 186 | CreateNewTestFile(L"\\renametestfile", "abcdefg"); |
| 187 | id = GetFileId(L"\\renametestfile"); |
| 188 | VERIFY_ARE_NOT_EQUAL(id, id2); |
| 189 | VERIFY_WIN32_BOOL_FAILED(MoveFile(LXSST_P9_TEST_DIR L"\\renametestfile", LXSST_P9_TEST_DIR L"\\renametestfile2")); |
| 190 | VERIFY_LAST_ERROR(ERROR_ALREADY_EXISTS); |
| 191 | VERIFY_WIN32_BOOL_SUCCEEDED( |
| 192 | MoveFileEx(LXSST_P9_TEST_DIR L"\\renametestfile", LXSST_P9_TEST_DIR L"\\renametestfile2", MOVEFILE_REPLACE_EXISTING)); |
| 193 | |
| 194 | id2 = GetFileId(L"\\renametestfile2"); |
| 195 | VERIFY_ARE_EQUAL(id, id2); |
| 196 | |
| 197 | // Rename a directory |
| 198 | VERIFY_WIN32_BOOL_SUCCEEDED(CreateDirectory(LXSST_P9_TEST_DIR L"\\renametestdir", nullptr)); |
| 199 | id = GetFileId(L"\\renametestdir"); |
| 200 | VERIFY_WIN32_BOOL_SUCCEEDED(MoveFile(LXSST_P9_TEST_DIR L"\\renametestdir", LXSST_P9_TEST_DIR L"\\renametestdir2")); |
| 201 | id2 = GetFileId(L"\\renametestdir2"); |
| 202 | VERIFY_ARE_EQUAL(id, id2); |
| 203 | VERIFY_IS_FALSE(CheckFileExists(L"\\renametestdir")); |
| 204 | |
| 205 | // Directory over a file. |
| 206 | VERIFY_WIN32_BOOL_FAILED( |
| 207 | MoveFileEx(LXSST_P9_TEST_DIR L"\\renametestdir2", LXSST_P9_TEST_DIR L"\\renametestfile2", MOVEFILE_REPLACE_EXISTING)); |
| 208 | |
| 209 | VERIFY_LAST_ERROR(ERROR_DIRECTORY); |
| 210 | |
| 211 | // File over a directory. |
| 212 | VERIFY_WIN32_BOOL_FAILED( |
| 213 | MoveFileEx(LXSST_P9_TEST_DIR L"\\renametestfile2", LXSST_P9_TEST_DIR L"\\renametestdir2", MOVEFILE_REPLACE_EXISTING)); |
| 214 | |
| 215 | VERIFY_LAST_ERROR(ERROR_ACCESS_DENIED); |
| 216 | } |
| 217 | |
| 218 | // Tests listing the files in a directory. |
| 219 | TEST_METHOD(TestReadDir) |
| 220 | { |
| 221 | constexpr int fileCount = 500; |
| 222 | VERIFY_WIN32_BOOL_SUCCEEDED(CreateDirectory(LXSST_P9_TEST_DIR L"\\readdirtest", nullptr)); |
| 223 | for (int i = 0; i < fileCount; ++i) |
| 224 | { |
| 225 | wchar_t path[MAX_PATH]{}; |
| 226 | swprintf_s(path, L"\\readdirtest\\%d", i); |
| 227 | CreateNewTestFile(path, "0123456789"); |
| 228 | } |
| 229 | |
| 230 | WIN32_FIND_DATA findData{}; |
| 231 | const wil::unique_hfind find{FindFirstFile(LXSST_P9_TEST_DIR L"\\readdirtest\\*", &findData)}; |
| 232 | VERIFY_WIN32_BOOL_SUCCEEDED(static_cast<bool>(find)); |
| 233 | int count{}; |
| 234 | bool foundFiles[fileCount]{}; |
| 235 | do |
| 236 | { |
| 237 | ++count; |
| 238 | VERIFY_ARE_NOT_EQUAL(0u, findData.dwFileAttributes); |
| 239 | VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast<PULONGLONG>(&findData.ftCreationTime)); |
| 240 | VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast<PULONGLONG>(&findData.ftLastAccessTime)); |
| 241 | VERIFY_ARE_NOT_EQUAL(0ull, *reinterpret_cast<PULONGLONG>(&findData.ftLastWriteTime)); |
| 242 | if (findData.cFileName[0] != L'.') |
| 243 | { |
| 244 | VERIFY_ARE_EQUAL(0u, findData.nFileSizeHigh); |
| 245 | VERIFY_ARE_EQUAL(10u, findData.nFileSizeLow); |
| 246 | int file = wcstol(findData.cFileName, nullptr, 10); |
| 247 | VERIFY_IS_GREATER_THAN_OR_EQUAL(file, 0); |
| 248 | VERIFY_IS_LESS_THAN(file, fileCount); |
| 249 | VERIFY_IS_FALSE(foundFiles[file]); |
| 250 | foundFiles[file] = true; |
| 251 | } |
| 252 | |
| 253 | } while (FindNextFile(find.get(), &findData)); |
| 254 | |
| 255 | VERIFY_LAST_ERROR(ERROR_NO_MORE_FILES); |
| 256 | VERIFY_ARE_EQUAL(fileCount + 2, count); |
| 257 | |
| 258 | for (int i = 0; i < fileCount; ++i) |
| 259 | { |
| 260 | VERIFY_IS_TRUE(foundFiles[i]); |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | // Tests using mount points inside the WSL instance. |
| 265 | TEST_METHOD(TestMounts) |
| 266 | { |
| 267 | // Check access into mounts like procfs is allowed. |
| 268 | wil::unique_hfile file{CreateFile( |
| 269 | LXSST_P9_PREFIX L"\\proc\\stat", |
| 270 | FILE_GENERIC_READ, |
| 271 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| 272 | nullptr, |
| 273 | OPEN_EXISTING, |
| 274 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, |
| 275 | nullptr)}; |
| 276 | |
| 277 | VERIFY_WIN32_BOOL_SUCCEEDED(static_cast<bool>(file)); |
| 278 | |
| 279 | char buffer[1024]; |
| 280 | DWORD bytes; |
| 281 | VERIFY_WIN32_BOOL_SUCCEEDED(ReadFile(file.get(), buffer, sizeof(buffer), &bytes, nullptr)); |
| 282 | VERIFY_IS_GREATER_THAN(bytes, 0u); |
| 283 | |
| 284 | // Check access into drvfs mounts is not allowed. |
| 285 | file.reset(CreateFile( |
| 286 | LXSST_P9_PREFIX L"\\mnt\\c", |
| 287 | FILE_GENERIC_READ, |
| 288 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| 289 | nullptr, |
| 290 | OPEN_EXISTING, |
| 291 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, |
| 292 | nullptr)); |
| 293 | |
| 294 | VERIFY_IS_FALSE(static_cast<bool>(file)); |
| 295 | VERIFY_LAST_ERROR(ERROR_ACCESS_DENIED); |
| 296 | |
| 297 | file.reset(CreateFile( |
| 298 | LXSST_P9_PREFIX L"\\mnt\\c\\Windows", |
| 299 | FILE_GENERIC_READ, |
| 300 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| 301 | nullptr, |
| 302 | OPEN_EXISTING, |
| 303 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, |
| 304 | nullptr)); |
| 305 | |
| 306 | VERIFY_IS_FALSE(static_cast<bool>(file)); |
| 307 | VERIFY_LAST_ERROR(ERROR_ACCESS_DENIED); |
| 308 | } |
| 309 | |
| 310 | TEST_METHOD(TestCreate) |
| 311 | { |
| 312 | wil::unique_hfile file; |
| 313 | IO_STATUS_BLOCK ioStatus; |
| 314 | |
| 315 | // Check error codes for non-existing files. |
| 316 | auto status = CreateFileNt(&file, LXSST_P9_PREFIX L"\\dat\\p9_test", FILE_GENERIC_READ, ioStatus); |
| 317 | VERIFY_ARE_EQUAL(STATUS_OBJECT_PATH_NOT_FOUND, status); |
| 318 | status = CreateFileNt(&file, LXSST_P9_PREFIX L"\\data\\foo", FILE_GENERIC_READ, ioStatus); |
| 319 | VERIFY_ARE_EQUAL(STATUS_OBJECT_NAME_NOT_FOUND, status); |
| 320 | status = CreateFileNt(&file, LXSST_P9_PREFIX L"\\etc\\resolve.conf\\foo", FILE_GENERIC_READ, ioStatus); |
| 321 | VERIFY_ARE_EQUAL(STATUS_OBJECT_PATH_NOT_FOUND, status); |
| 322 | |
| 323 | // Create a file. |
| 324 | VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_WRITE, ioStatus, FILE_CREATE)); |
| 325 | VERIFY_ARE_EQUAL(static_cast<ULONG_PTR>(FILE_CREATED), ioStatus.Information); |
| 326 | |
| 327 | // Write some test content. |
| 328 | const std::string contents{"hello"}; |
| 329 | DWORD bytes; |
| 330 | VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(file.get(), contents.data(), static_cast<DWORD>(contents.size()), &bytes, nullptr)); |
| 331 | VERIFY_ARE_EQUAL(contents.size(), bytes); |
| 332 | file.reset(); |
| 333 | |
| 334 | // Exclusive create should fail now. |
| 335 | status = CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_READ, ioStatus, FILE_CREATE); |
| 336 | VERIFY_ARE_EQUAL(STATUS_OBJECT_NAME_COLLISION, status); |
| 337 | |
| 338 | // Open-if existing file. |
| 339 | VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_READ, ioStatus, FILE_OPEN_IF)); |
| 340 | VERIFY_ARE_EQUAL(static_cast<ULONG_PTR>(FILE_OPENED), ioStatus.Information); |
| 341 | LARGE_INTEGER size; |
| 342 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileSizeEx(file.get(), &size)); |
| 343 | VERIFY_ARE_EQUAL(5, size.QuadPart); |
| 344 | |
| 345 | // Open-if new file. |
| 346 | VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile2", FILE_GENERIC_READ, ioStatus, FILE_OPEN_IF)); |
| 347 | VERIFY_ARE_EQUAL(static_cast<ULONG_PTR>(FILE_CREATED), ioStatus.Information); |
| 348 | |
| 349 | // Overwrite non-existing file. |
| 350 | status = CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile3", FILE_GENERIC_WRITE, ioStatus, FILE_OVERWRITE); |
| 351 | VERIFY_ARE_EQUAL(STATUS_OBJECT_NAME_NOT_FOUND, status); |
| 352 | |
| 353 | VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile3", FILE_GENERIC_WRITE, ioStatus, FILE_OVERWRITE_IF)); |
| 354 | VERIFY_ARE_EQUAL(static_cast<ULONG_PTR>(FILE_CREATED), ioStatus.Information); |
| 355 | |
| 356 | // Overwrite existing file. |
| 357 | VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_WRITE, ioStatus, FILE_OVERWRITE)); |
| 358 | VERIFY_ARE_EQUAL(static_cast<ULONG_PTR>(FILE_OVERWRITTEN), ioStatus.Information); |
| 359 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileSizeEx(file.get(), &size)); |
| 360 | VERIFY_ARE_EQUAL(0, size.QuadPart); |
| 361 | VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(file.get(), contents.data(), static_cast<DWORD>(contents.size()), &bytes, nullptr)); |
| 362 | VERIFY_ARE_EQUAL(contents.size(), bytes); |
| 363 | file.reset(); |
| 364 | VERIFY_NT_SUCCESS(CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_WRITE, ioStatus, FILE_OVERWRITE_IF)); |
| 365 | VERIFY_ARE_EQUAL(static_cast<ULONG_PTR>(FILE_OVERWRITTEN), ioStatus.Information); |
| 366 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileSizeEx(file.get(), &size)); |
| 367 | VERIFY_ARE_EQUAL(0, size.QuadPart); |
| 368 | |
| 369 | // Open a directory with FILE_NON_DIRECTORY_FILE. |
| 370 | status = CreateFileNt(&file, LXSST_P9_TEST_DIR, FILE_GENERIC_READ, ioStatus, FILE_OPEN, 0, FILE_NON_DIRECTORY_FILE); |
| 371 | VERIFY_ARE_EQUAL(STATUS_FILE_IS_A_DIRECTORY, status); |
| 372 | |
| 373 | // Open a file with FILE_DIRECTORY_FILE. |
| 374 | status = CreateFileNt(&file, LXSST_P9_TEST_DIR L"\\testfile", FILE_GENERIC_READ, ioStatus, FILE_OPEN, 0, FILE_DIRECTORY_FILE); |
| 375 | VERIFY_ARE_EQUAL(STATUS_NOT_A_DIRECTORY, status); |
| 376 | } |
| 377 | |
| 378 | static auto EnablePlan9Logging() |
| 379 | { |
| 380 | LxssWriteWslDistroConfig("[fileServer]\nlogFile=/plan9-logs.txt\nlogTruncate=false\nlogLevel=5"); |
| 381 | |
| 382 | return wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [] { |
| 383 | // clean up wsl.conf file |
| 384 | LxsstuLaunchWsl(L"rm /etc/wsl.conf"); |
| 385 | TerminateDistribution(); |
| 386 | }); |
| 387 | } |
| 388 | |
| 389 | TEST_METHOD(TestPlan9ServerTimeout) |
| 390 | { |
| 391 | // This test has proven to be unstable, most likely because another program opens a file inside the distro, which prevents it from terminating. |
| 392 | SKIP_TEST_UNSTABLE(); |
| 393 | |
| 394 | auto revertLogging = EnablePlan9Logging(); |
| 395 | |
| 396 | auto dumpLogs = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { |
| 397 | const auto output = LxsstuLaunchWslAndCaptureOutput(L"cat /plan9-logs.txt"); |
| 398 | LogInfo("Plan9 logs: %s", output.first.c_str()); |
| 399 | }); |
| 400 | |
| 401 | wsl::windows::common::SvcComm service; |
| 402 | auto distro = service.GetDefaultDistribution(); |
| 403 | service.TerminateInstance(&distro); |
| 404 | |
| 405 | auto getDistroState = [distro, &service]() -> LxssDistributionState { |
| 406 | auto distros = service.EnumerateDistributions(); |
| 407 | const auto it = |
| 408 | std::find_if(distros.begin(), distros.end(), [&](const auto& e) { return IsEqualGUID(distro, e.DistroGuid); }); |
| 409 | |
| 410 | VERIFY_ARE_NOT_EQUAL(it, distros.end()); |
| 411 | |
| 412 | return it->State; |
| 413 | }; |
| 414 | |
| 415 | VERIFY_ARE_EQUAL(getDistroState(), LxssDistributionStateInstalled); |
| 416 | |
| 417 | // Open a file via \\wsl.localhost and validate that the distro does not terminate |
| 418 | auto file = CreateTestFile(L"\\9p-test-file", GENERIC_ALL, FILE_CREATE, FILE_FLAG_DELETE_ON_CLOSE); |
| 419 | |
| 420 | // Now the distro should be running |
| 421 | VERIFY_ARE_EQUAL(getDistroState(), LxssDistributionStateRunning); |
| 422 | |
| 423 | // Validate that the distro does not terminate until the file is closed |
| 424 | // Note: Distributions time out after 10 seconds. |
| 425 | std::this_thread::sleep_for(std::chrono::seconds(20)); |
| 426 | |
| 427 | // Close the file and make sure that the distro terminates |
| 428 | file.reset(); |
| 429 | |
| 430 | // The distro should now time out and stop |
| 431 | const auto deadline = std::chrono::steady_clock::now() + std::chrono::minutes(1); |
| 432 | while (std::chrono::steady_clock::now() < deadline && getDistroState() != LxssDistributionStateInstalled) |
| 433 | { |
| 434 | std::this_thread::sleep_for(std::chrono::seconds(1)); |
| 435 | } |
| 436 | |
| 437 | VERIFY_ARE_EQUAL(getDistroState(), LxssDistributionStateInstalled); |
| 438 | } |
| 439 | |
| 440 | TEST_METHOD(TestPlan9AdditionalGroupAccess) |
| 441 | { |
| 442 | ULONG Uid{}; |
| 443 | ULONG Gid{}; |
| 444 | |
| 445 | // Create a user for this test |
| 446 | CreateUser(L"plan9testuser", &Uid, &Gid); |
| 447 | |
| 448 | // Create a folder that's unaccessible to plan9testuser |
| 449 | VERIFY_ARE_EQUAL( |
| 450 | LxsstuLaunchWsl(L"mkdir -p /tmp/plan9-group-test && groupadd -f plan9testgroup && chown root:plan9testgroup " |
| 451 | L"/tmp/plan9-group-test && " |
| 452 | L"echo -n foo > /tmp/plan9-group-test/bar && chmod 770 /tmp/plan9-group-test"), |
| 453 | 0u); |
| 454 | |
| 455 | auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { |
| 456 | LxsstuLaunchWsl(L"-u root rm -rf /etc/wsl.conf /tmp/plan9-group-test"); |
| 457 | TerminateDistribution(); |
| 458 | }); |
| 459 | |
| 460 | // Make plan9testuser the default |
| 461 | LxssWriteWslDistroConfig("[user]\ndefault=plan9testuser\n"); |
| 462 | TerminateDistribution(); |
| 463 | |
| 464 | // Validate that folder isn't accessible |
| 465 | constexpr auto path = L"\\\\wsl.localhost\\" LXSS_DISTRO_NAME_TEST "\\tmp\\plan9-group-test\\bar"; |
| 466 | std::wifstream file(path); |
| 467 | VERIFY_IS_FALSE(file.good()); |
| 468 | |
| 469 | // Add plan9testuser to plan9testgroup |
| 470 | VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"-u root usermod -G plan9testgroup -a plan9testuser"), 0u); |
| 471 | |
| 472 | // Validate that the file can be accessed now |
| 473 | TerminateDistribution(); |
| 474 | // There's a race condition on fe_release that can cause opening this file to fail. |
| 475 | try |
| 476 | { |
| 477 | wsl::shared::retry::RetryWithTimeout<void>( |
| 478 | [&file]() { |
| 479 | file.open(path); |
| 480 | LogInfo("Failed to open %ls, %d", path, errno); |
| 481 | THROW_HR_IF(E_ABORT, !file.good()); |
| 482 | }, |
| 483 | std::chrono::seconds(1), |
| 484 | std::chrono::minutes(2)); |
| 485 | } |
| 486 | catch (...) |
| 487 | { |
| 488 | LogError("Timed out trying to open: %ls", path); |
| 489 | VERIFY_FAIL(); |
| 490 | } |
| 491 | |
| 492 | std::wstring content(3, '\0'); |
| 493 | VERIFY_IS_TRUE(file.read(content.data(), content.size()).good()); |
| 494 | |
| 495 | VERIFY_ARE_EQUAL(content, L"foo"); |
| 496 | } |
| 497 | |
| 498 | /* Plan9 Test Helper Methods */ |
| 499 | |
| 500 | static wil::unique_hfile CreateTestFile(std::wstring_view path, DWORD desiredAccess, DWORD disposition = OPEN_EXISTING, DWORD flags = 0) |
| 501 | { |
| 502 | std::wstring fullPath{LXSST_P9_TEST_DIR}; |
| 503 | fullPath += path; |
| 504 | wil::unique_hfile file{CreateFile( |
| 505 | fullPath.c_str(), |
| 506 | desiredAccess, |
| 507 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| 508 | nullptr, |
| 509 | disposition, |
| 510 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | flags, |
| 511 | nullptr)}; |
| 512 | |
| 513 | VERIFY_WIN32_BOOL_SUCCEEDED(static_cast<bool>(file)); |
| 514 | |
| 515 | return file; |
| 516 | } |
| 517 | |
| 518 | wil::unique_hfile CreateNewTestFile(const std::wstring& path, std::string_view contents) |
| 519 | { |
| 520 | auto file = CreateTestFile(path, FILE_GENERIC_WRITE | FILE_GENERIC_READ, CREATE_NEW); |
| 521 | DWORD bytes; |
| 522 | VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(file.get(), contents.data(), static_cast<DWORD>(contents.size()), &bytes, nullptr)); |
| 523 | VERIFY_ARE_EQUAL(contents.size(), bytes); |
| 524 | return file; |
| 525 | } |
| 526 | |
| 527 | static NTSTATUS CreateFileNt( |
| 528 | PHANDLE handle, LPCWSTR name, ACCESS_MASK desiredAccess, IO_STATUS_BLOCK& ioStatus, ULONG disposition = FILE_OPEN, ULONG attributes = 0, ULONG createOptions = 0) |
| 529 | { |
| 530 | UNICODE_STRING pathu; |
| 531 | THROW_IF_NTSTATUS_FAILED(RtlDosPathNameToNtPathName_U_WithStatus(name, &pathu, nullptr, nullptr)); |
| 532 | wil::unique_process_heap_ptr<WCHAR> buffer{pathu.Buffer}; |
| 533 | OBJECT_ATTRIBUTES oa; |
| 534 | InitializeObjectAttributes(&oa, &pathu, OBJ_CASE_INSENSITIVE, nullptr, nullptr); |
| 535 | return NtCreateFile( |
| 536 | handle, |
| 537 | desiredAccess | SYNCHRONIZE, |
| 538 | &oa, |
| 539 | &ioStatus, |
| 540 | nullptr, |
| 541 | attributes, |
| 542 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| 543 | disposition, |
| 544 | createOptions | FILE_SYNCHRONOUS_IO_ALERT, |
| 545 | nullptr, |
| 546 | 0); |
| 547 | } |
| 548 | |
| 549 | static bool CheckFileExists(std::wstring_view path) |
| 550 | { |
| 551 | std::wstring fullPath{LXSST_P9_TEST_DIR}; |
| 552 | fullPath += path; |
| 553 | if (!PathFileExists(fullPath.c_str())) |
| 554 | { |
| 555 | VERIFY_LAST_ERROR(ERROR_FILE_NOT_FOUND); |
| 556 | return false; |
| 557 | } |
| 558 | |
| 559 | return true; |
| 560 | } |
| 561 | |
| 562 | ULONGLONG GetFileId(std::wstring_view path) |
| 563 | { |
| 564 | BY_HANDLE_FILE_INFORMATION info; |
| 565 | const auto file = CreateTestFile(path, FILE_READ_ATTRIBUTES); |
| 566 | VERIFY_WIN32_BOOL_SUCCEEDED(GetFileInformationByHandle(file.get(), &info)); |
| 567 | return static_cast<ULONGLONG>(info.nFileIndexHigh) << 32 | info.nFileIndexLow; |
| 568 | } |
| 569 | }; |
| 570 | } // namespace Plan9Tests |