master
cpp 1,481 lines 57.4 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 DrvFsTests.cpp
8
9 Abstract:
10
11 This file contains drvfs test cases.
12
13 --*/
14
15 #include "precomp.h"
16
17 #include "Common.h"
18 #include <AclAPI.h>
19 #include <fstream>
20 #include <filesystem>
21 #include "wslservice.h"
22 #include "registry.hpp"
23 #include "helpers.hpp"
24 #include "svccomm.hpp"
25 #include <userenv.h>
26 #include "Distribution.h"
27
28 #define LXSST_DRVFS_TEST_DIR L"C:\\drvfstest"
29 #define LXSST_DRVFS_RWX_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\rwx"
30 #define LXSST_DRVFS_READONLY_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\readonly"
31 #define LXSST_DRVFS_WRITEONLY_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\writeonly"
32 #define LXSST_DRVFS_EXECUTEONLY_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\executeonly"
33 #define LXSST_DRVFS_READONLYATTR_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\readonlyattr"
34 #define LXSST_DRVFS_READONLYATTRDEL_TEST_FILE LXSST_DRVFS_TEST_DIR L"\\readonlyattrdel"
35 #define LXSST_DRVFS_EXECUTEONLY_TEST_DIR LXSST_DRVFS_TEST_DIR L"\\executeonlydir"
36 #define LXSST_DRVFS_EXECUTEONLY_TEST_DIR_CHILD LXSST_DRVFS_EXECUTEONLY_TEST_DIR L"\\child"
37 #define LXSST_DRVFS_READONLY_TEST_DIR LXSST_DRVFS_TEST_DIR L"\\noexecutedir"
38 #define LXSST_DRVFS_METADATA_TEST_DIR L"C:\\metadatatest"
39
40 #define LXSST_DRVFS_REPARSE_TEST_DIR L"C:\\reparsetest"
41
42 #define LXSST_DRVFS_SYMLINK_TEST_DIR L"C:\\symlink"
43
44 #define LXSST_DRVFS_METADATA_TEST_MODE (5)
45
46 #define LXSST_METADATA_EA_NAME_LENGTH (RTL_NUMBER_OF(LX_FILE_METADATA_UID_EA_NAME) - 1)
47
48 #define LX_DRVFS_DISABLE_NONE (0)
49 #define LX_DRVFS_DISABLE_QUERY_BY_NAME (1)
50 #define LX_DRVFS_DISABLE_QUERY_BY_NAME_AND_STAT_INFO (2)
51
52 using wsl::windows::common::wslutil::GetSystemErrorString;
53
54 namespace DrvFsTests {
55
56 class DrvFsTests
57 {
58 public:
59 std::wstring SkipUnstableTestEnvVar =
60 L"WSL_DISABLE_VB_UNSTABLE_TESTS=" + std::wstring{wsl::windows::common::helpers::IsWindows11OrAbove() ? L"0" : L"1"};
61
62 void DrvFsCommon(int TestMode, std::optional<DrvFsMode> DrvFsMode = {}) const
63 {
64 auto cleanup = wil::scope_exit([TestMode] {
65 RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\junction");
66 RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\absolutelink");
67 DeleteFileW(LXSST_DRVFS_REPARSE_TEST_DIR L"\\filelink");
68 RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\relativelink");
69 RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\linktarget");
70 DeleteFileW(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\filetarget");
71 RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test");
72 DeleteFileW(LXSST_DRVFS_REPARSE_TEST_DIR L"\\v1link");
73 DeleteFileW(LXSST_DRVFS_REPARSE_TEST_DIR L"\\appexeclink");
74 RemoveDirectory(LXSST_DRVFS_REPARSE_TEST_DIR);
75 SetFileAttributes(LXSST_DRVFS_RWX_TEST_FILE, FILE_ATTRIBUTE_NORMAL);
76 DeleteFileW(LXSST_DRVFS_RWX_TEST_FILE);
77 DeleteFileW(LXSST_DRVFS_READONLY_TEST_FILE);
78 DeleteFileW(LXSST_DRVFS_WRITEONLY_TEST_FILE);
79 DeleteFileW(LXSST_DRVFS_EXECUTEONLY_TEST_FILE);
80 DeleteFileW(LXSST_DRVFS_EXECUTEONLY_TEST_DIR_CHILD);
81 SetFileAttributes(LXSST_DRVFS_READONLYATTR_TEST_FILE, FILE_ATTRIBUTE_NORMAL);
82
83 DeleteFileW(LXSST_DRVFS_READONLYATTR_TEST_FILE);
84 SetFileAttributes(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE, FILE_ATTRIBUTE_NORMAL);
85
86 DeleteFileW(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE);
87 RemoveDirectory(LXSST_DRVFS_EXECUTEONLY_TEST_DIR);
88 RemoveDirectory(LXSST_DRVFS_READONLY_TEST_DIR);
89 RemoveDirectory(LXSST_DRVFS_TEST_DIR);
90 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\file.txt");
91 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR L"\\foo\uf03abar");
92 RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR "\\dir");
93 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink1");
94 RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink2");
95 RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink3");
96 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink4");
97 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink5");
98 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink6");
99 RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink7");
100 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink8");
101 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink1");
102 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink2");
103 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink3");
104 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink4");
105 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink5");
106 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink6");
107 DeleteFileW(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink7");
108 RemoveDirectory(LXSST_DRVFS_SYMLINK_TEST_DIR);
109 if (TestMode == LXSST_DRVFS_METADATA_TEST_MODE)
110 {
111 DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\baduid");
112 DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\badgid");
113 DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\badmode");
114 DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\badtype1");
115 DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\badtype2");
116 DeleteFileW(LXSST_DRVFS_METADATA_TEST_DIR L"\\nondevice");
117 RemoveDirectory(LXSST_DRVFS_METADATA_TEST_DIR);
118 }
119 });
120
121 VERIFY_NO_THROW(CreateDrvFsTestFiles(TestMode == LXSST_DRVFS_METADATA_TEST_MODE));
122 std::wstringstream Command;
123
124 Command << L"/bin/bash -c \"";
125 Command << SkipUnstableTestEnvVar;
126 Command << " /data/test/wsl_unit_tests drvfs -d $(wslpath '";
127 Command << LxsstuGetLxssDirectory();
128 Command << L"') -m ";
129 Command << TestMode;
130 Command << L"\"";
131 std::wstringstream Logfile;
132 Logfile << L"drvfs";
133 Logfile << TestMode;
134 VERIFY_NO_THROW(LxsstuRunTest(Command.str().c_str(), Logfile.str().c_str()));
135
136 //
137 // Check that the read-only attribute has been changed.
138 //
139
140 DWORD Attributes = GetFileAttributes(LXSST_DRVFS_READONLYATTR_TEST_FILE);
141 DWORD Expected = FILE_ATTRIBUTE_NORMAL;
142 VERIFY_ARE_EQUAL(Expected, Attributes);
143 Attributes = GetFileAttributes(LXSST_DRVFS_RWX_TEST_FILE);
144 Expected = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE;
145 VERIFY_ARE_EQUAL(Expected, Attributes);
146
147 //
148 // Check that the second read-only file was deleted.
149 //
150
151 Expected = INVALID_FILE_ATTRIBUTES;
152 Attributes = GetFileAttributes(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE);
153 VERIFY_ARE_EQUAL(Expected, Attributes);
154
155 //
156 // Check the NT symlinks.
157 //
158
159 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink1", L"file.txt", false));
160 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink2", L"dir", true));
161 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink3", L"..", true));
162 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink4", L"..\\symlink\\file.txt", false));
163 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink5", L"dir\\..\\file.txt", false));
164 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink6", L"ntlink1", false));
165 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink7", L"ntlink2", true));
166 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\ntlink8", L"foo\uf03abar", false));
167
168 VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink1"));
169 VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink2"));
170
171 // Since target resolution is done on the Windows side in Plan 9 and VirtioFs, it is able to create an NT
172 // link if the target path traverses an existing NT link (this is actually better than WSL 1).
173 if (LxsstuVmMode())
174 {
175 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink3", L"ntlink2\\..\\file.txt", false));
176 }
177 else
178 {
179 VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink3"));
180 }
181
182 VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink4"));
183 VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink5"));
184 VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink6"));
185
186 // Plan 9 and VirtioFs don't know about the Linux mount point on "dir", so it creates an NT link in this case.
187 if (LxsstuVmMode())
188 {
189 VERIFY_NO_THROW(VerifyDrvFsSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink7", L"dir\\..\\file.txt", false));
190 }
191 else
192 {
193 VERIFY_NO_THROW(VerifyDrvFsLxSymlink(LXSST_DRVFS_SYMLINK_TEST_DIR "\\lxlink7"));
194 }
195
196 //
197 // Check metadata is readable using Windows APIs.
198 //
199
200 if (TestMode == LXSST_DRVFS_METADATA_TEST_MODE)
201 {
202 VerifyDrvFsMetadata();
203 }
204 }
205
206 static void VfsAccessDrvFs()
207 {
208 VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests vfsaccess drvfs", L"vfsaccess_drvfs"));
209 }
210
211 static void FsCommonDrvFs()
212 {
213 VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests fscommon drvfs", L"fscommon_drvfs"));
214 }
215
216 void DrvFs(DrvFsMode Mode)
217 {
218 SKIP_TEST_ARM64();
219
220 VERIFY_NO_THROW(DrvFsCommon(LX_DRVFS_DISABLE_NONE, Mode));
221 }
222
223 void DrvFsFat(DrvFsMode Mode)
224 {
225 SKIP_TEST_ARM64();
226
227 constexpr auto MountPoint = "C:\\lxss_fat";
228 constexpr auto VhdPath = "C:\\lxss_fat.vhdx";
229 auto Cleanup = wil::scope_exit([MountPoint, VhdPath] { DeleteVolume(MountPoint, VhdPath); });
230
231 VERIFY_NO_THROW(CreateVolume("fat32", 100, MountPoint, VhdPath));
232 VERIFY_NO_THROW(
233 LxsstuRunTest((L"bash -c '" + SkipUnstableTestEnvVar + L" /data/test/wsl_unit_tests drvfs -m 3'").c_str(), L"drvfs3"));
234 }
235
236 void DrvFsSmb(DrvFsMode Mode)
237 {
238 SKIP_TEST_ARM64();
239
240 if (Mode == DrvFsMode::VirtioFs)
241 {
242 LogSkipped("TODO: debug virtiofs handling of //localhost/C$ style paths");
243 return;
244 }
245
246 VERIFY_NO_THROW(
247 LxsstuRunTest((L"bash -c '" + SkipUnstableTestEnvVar + L" /data/test/wsl_unit_tests drvfs -m 4'").c_str(), L"drvfs4"));
248 }
249
250 void DrvFsMetadata(DrvFsMode Mode)
251 {
252 SKIP_TEST_ARM64();
253
254 VERIFY_NO_THROW(DrvFsCommon(LXSST_DRVFS_METADATA_TEST_MODE, Mode));
255 }
256
257 void DrvfsMountElevated(DrvFsMode Mode)
258 {
259 WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added
260 SKIP_TEST_ARM64();
261
262 TerminateDistribution();
263 WslKeepAlive keepAlive;
264
265 ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, Mode);
266 }
267
268 void DrvfsMountElevatedDifferentConsole(DrvFsMode Mode)
269 {
270 WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added
271 SKIP_TEST_ARM64();
272
273 TerminateDistribution();
274 WslKeepAlive keepAlive;
275
276 ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, Mode);
277 }
278
279 void DrvfsMountNonElevated(DrvFsMode Mode)
280 {
281 WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added
282 SKIP_TEST_ARM64();
283
284 TerminateDistribution();
285
286 const auto nonElevatedToken = GetNonElevatedToken();
287 WslKeepAlive keepAlive(nonElevatedToken.get());
288
289 ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, Mode);
290 }
291
292 void DrvfsMountNonElevatedDifferentConsole(DrvFsMode Mode)
293 {
294 WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added
295 SKIP_TEST_ARM64();
296
297 TerminateDistribution();
298
299 const auto nonElevatedToken = GetNonElevatedToken();
300 WslKeepAlive keepAlive(nonElevatedToken.get());
301
302 ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, Mode);
303 }
304
305 void DrvfsMountElevatedSystemDistroEnabled(DrvFsMode Mode)
306 {
307 WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added
308 SKIP_TEST_ARM64();
309
310 WslConfigChange config(LxssGenerateTestConfig({.guiApplications = true, .drvFsMode = Mode}));
311 WslKeepAlive keepAlive;
312
313 ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, Mode);
314 }
315
316 void DrvfsMountNonElevatedSystemDistroEnabled(DrvFsMode Mode)
317 {
318 WINDOWS_11_TEST_ONLY(); // TODO: Enable on Windows 10 when virtio support is added
319 SKIP_TEST_ARM64();
320
321 WslConfigChange config(LxssGenerateTestConfig({.guiApplications = true, .drvFsMode = Mode}));
322
323 const auto nonElevatedToken = GetNonElevatedToken();
324 WslKeepAlive keepAlive(nonElevatedToken.get());
325
326 ValidateDrvfsMounts(CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, Mode);
327 }
328
329 static void XattrDrvFs(DrvFsMode Mode)
330 {
331 SKIP_TEST_ARM64();
332
333 VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests xattr drvfs", L"xattr_drvfs"));
334 }
335
336 void DrvFsReFs(DrvFsMode Mode)
337 {
338 SKIP_TEST_ARM64();
339 WSL_TEST_VERSION_REQUIRED(wsl::windows::common::helpers::WindowsBuildNumbers::Germanium);
340
341 constexpr auto MountPoint = "C:\\lxss_refs";
342 constexpr auto VhdPath = "C:\\lxss_refs.vhdx";
343 auto Cleanup = wil::scope_exit([MountPoint, VhdPath] { DeleteVolume(MountPoint, VhdPath); });
344
345 VERIFY_NO_THROW(CreateVolume("refs", 50000, MountPoint, VhdPath));
346 VERIFY_NO_THROW(
347 LxsstuRunTest((L"bash -c '" + SkipUnstableTestEnvVar + L" /data/test/wsl_unit_tests drvfs -m 6'").c_str(), L"drvfs6"));
348 }
349
350 void WslPath(DrvFsMode Mode)
351 {
352 VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests wslpath", L"wslpath"));
353
354 auto testWslPath = [](const std::wstring& testDir) {
355 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(testDir); });
356
357 std::filesystem::create_directory(testDir);
358
359 auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -aw '{}'", testDir));
360 VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
361
362 std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -wa '{}'", testDir));
363 VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
364
365 std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath '{}'", testDir));
366 VERIFY_ARE_EQUAL(std::format(L"{}\n", testDir), out);
367
368 std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -a '{}'", testDir));
369 VERIFY_IS_TRUE(out.find(L"/mnt/") == 0);
370 };
371
372 testWslPath(L"wslpath-test-dir");
373 testWslPath(L"wslpath-测试目录-テスト");
374 }
375
376 void DrvFsMountUnicodePath(DrvFsMode Mode)
377 {
378 // Create a Windows directory with unicode characters
379 constexpr auto unicodeDir = L"C:\\drvfs-测试-テスト";
380 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(unicodeDir); });
381
382 std::filesystem::create_directory(unicodeDir);
383
384 // Create a test file inside the directory
385 const auto testFilePath = std::filesystem::path(unicodeDir) / L"test-file.txt";
386 {
387 std::ofstream testFile(testFilePath);
388 testFile << "hello from unicode path";
389 }
390
391 // Mount the unicode directory using mount -t drvfs
392 constexpr auto mountPoint = L"/tmp/unicode-mount-test";
393 auto unmountCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
394 LxsstuLaunchWsl(std::format(L"-u root umount '{}'", mountPoint).c_str());
395 LxsstuLaunchWsl(std::format(L"-u root rmdir '{}'", mountPoint).c_str());
396 });
397
398 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-u root mkdir -p '{}'", mountPoint).c_str()), 0);
399 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-u root mount -t drvfs '{}' '{}'", unicodeDir, mountPoint).c_str()), 0);
400
401 // Verify we can read the test file through the mount
402 auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}/test-file.txt'", mountPoint));
403 VERIFY_ARE_EQUAL(L"hello from unicode path", out);
404
405 // Verify we can list the directory
406 std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"ls '{}'", mountPoint));
407 VERIFY_IS_TRUE(out.find(L"test-file.txt") != std::wstring::npos);
408 }
409
410 void DrvfsMountManyVirtioFsShares(DrvFsMode Mode, bool AggregateShares = true)
411 {
412 if (Mode != DrvFsMode::VirtioFs)
413 {
414 LogSkipped("This test is only applicable to VirtioFs");
415 return;
416 }
417
418 WINDOWS_11_TEST_ONLY();
419 SKIP_TEST_ARM64();
420
421 std::optional<WslConfigChange> config;
422 if (!AggregateShares)
423 {
424 config.emplace(LxssGenerateTestConfig({.drvFsMode = Mode, .virtioFsAggregateShares = false}));
425 }
426
427 const auto iterations = AggregateShares ? 32 : 12;
428 auto testDir = std::filesystem::current_path() / "virtiofs-loop-test";
429
430 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
431 LxsstuLaunchWsl(L"umount /tmp/virtiofs-loop-test-*-nested");
432 LxsstuLaunchWsl(L"umount /tmp/virtiofs-loop-test-*");
433
434 std::error_code ec;
435 std::filesystem::remove_all(testDir, ec);
436 });
437
438 for (int i = 0; i < iterations; ++i)
439 {
440 const bool readOnly = (i % 2) != 0;
441 const auto sourceDir = testDir / std::to_string(i);
442 std::filesystem::create_directories(sourceDir);
443
444 const auto expected = std::format("virtiofs share {}", i);
445 {
446 std::ofstream markerFile(std::filesystem::path(sourceDir) / L"marker");
447 markerFile << expected;
448 }
449
450 const auto mountPoint = std::format(L"/tmp/virtiofs-loop-test-{}", i);
451
452 // Mount the share.
453 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mkdir -p '{}'", mountPoint)), 0);
454 const auto mountCommand = std::format(L"mount -t drvfs {}'{}' '{}'", readOnly ? L"-o ro " : L"", sourceDir.string(), mountPoint);
455 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand), 0);
456
457 // Validate that it can be accessed.
458 {
459 auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}/marker'", mountPoint));
460 VERIFY_ARE_EQUAL(out, wsl::shared::string::MultiByteToWide(expected));
461 }
462
463 // Validate the mount options.
464 {
465 auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"findmnt -ln '{}'", mountPoint));
466
467 VerifyPatternMatch(
468 wsl::shared::string::WideToMultiByte(out.c_str()),
469 std::format("{} * virtiofs {},relatime\n", mountPoint, readOnly ? "ro" : "rw"));
470
471 if (!AggregateShares)
472 {
473 std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"findmnt -n -o SOURCE '{}'", mountPoint));
474 VERIFY_IS_TRUE(out.ends_with(L'\n'));
475 out.pop_back();
476 VERIFY_IS_TRUE(wsl::shared::string::ToGuid(out).has_value());
477 }
478 }
479
480 const auto writeResult = LxsstuLaunchWsl(std::format(L"touch '{}/write-test'", mountPoint));
481 VERIFY_ARE_EQUAL(readOnly, writeResult != 0);
482
483 if (AggregateShares && i == 0)
484 {
485 const auto nestedSourceDir = sourceDir / "nested";
486 const auto nestedMountPoint = mountPoint + L"-nested";
487 const auto nestedExpected = "nested virtiofs marker";
488 std::filesystem::create_directory(nestedSourceDir);
489 {
490 std::ofstream markerFile(nestedSourceDir / "marker");
491 markerFile << nestedExpected;
492 }
493
494 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mkdir -p '{}'", nestedMountPoint)), 0);
495 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mount --bind '{}/nested' '{}'", mountPoint, nestedMountPoint)), 0);
496
497 const auto canonicalNestedSourceDir = std::filesystem::canonical(nestedSourceDir).wstring();
498 auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -w '{}'", nestedMountPoint));
499 VERIFY_ARE_EQUAL(canonicalNestedSourceDir + L"\n", out);
500
501 std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -u '{}'", canonicalNestedSourceDir));
502 VERIFY_ARE_EQUAL(nestedMountPoint + L"\n", out);
503
504 std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}/marker'", nestedMountPoint));
505 VERIFY_ARE_EQUAL(wsl::shared::string::MultiByteToWide(nestedExpected), out);
506 }
507 }
508 }
509
510 static void DrvfsMountReadOnly()
511 {
512 const auto testDir = std::filesystem::current_path() / "drvfs-read-only-test";
513 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
514 std::error_code ec;
515 std::filesystem::remove_all(testDir, ec);
516 });
517
518 struct TestCase
519 {
520 std::wstring_view Options;
521 bool ReadOnly;
522 };
523
524 constexpr TestCase testCases[] = {
525 {L"ro,umask=222", true},
526 {L"ro,rw", false},
527 {L"rw,ro", true},
528 };
529
530 for (size_t index = 0; index < std::size(testCases); ++index)
531 {
532 const auto& testCase = testCases[index];
533 const auto sourceDir = testDir / std::to_string(index);
534 const auto mountPoint = std::format(L"/tmp/drvfs-read-only-test-{}", index);
535 auto unmountCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
536 LxsstuLaunchWsl(std::format(L"umount '{}'", mountPoint));
537 LxsstuLaunchWsl(std::format(L"rmdir '{}'", mountPoint));
538 });
539
540 std::filesystem::create_directories(sourceDir);
541 constexpr auto expected = "read-only mount marker";
542 {
543 std::ofstream markerFile(sourceDir / "marker");
544 markerFile << expected;
545 }
546
547 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"mkdir -p '{}'", mountPoint)), 0);
548 VERIFY_ARE_EQUAL(
549 LxsstuLaunchWsl(std::format(L"mount -t drvfs -o '{}' '{}' '{}'", testCase.Options, sourceDir.string(), mountPoint)), 0);
550
551 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"findmnt -rn -M '{}' -O {}", mountPoint, testCase.ReadOnly ? L"ro" : L"rw")), 0);
552
553 auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}/marker'", mountPoint));
554 VERIFY_ARE_EQUAL(wsl::shared::string::MultiByteToWide(expected), out);
555
556 const auto writeResult = LxsstuLaunchWsl(std::format(L"touch '{}/write-test'", mountPoint));
557 VERIFY_ARE_EQUAL(testCase.ReadOnly, writeResult != 0);
558 VERIFY_ARE_EQUAL(!testCase.ReadOnly, std::filesystem::exists(sourceDir / "write-test"));
559 }
560 }
561
562 // DrvFsTests Private Methods
563 private:
564 static VOID CreateDrvFsTestFiles(bool Metadata)
565 {
566
567 THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_TEST_DIR, NULL));
568
569 //
570 // The rwx and readonlyattr test files need read/write EA permission for
571 // the metadata test mode because chmod will be called on them.
572 //
573
574 CreateTestFile(LXSST_DRVFS_RWX_TEST_FILE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_EXECUTE | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE);
575
576 CreateTestFile(LXSST_DRVFS_READONLY_TEST_FILE, FILE_GENERIC_READ | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE);
577
578 CreateTestFile(LXSST_DRVFS_WRITEONLY_TEST_FILE, FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES | FILE_READ_EA | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE);
579
580 CreateTestFile(
581 LXSST_DRVFS_EXECUTEONLY_TEST_DIR,
582 FILE_TRAVERSE | FILE_DELETE_CHILD | FILE_ADD_FILE | FILE_READ_ATTRIBUTES | FILE_READ_EA | DELETE | SYNCHRONIZE | READ_CONTROL,
583 TRUE,
584 INVALID_HANDLE_VALUE);
585
586 CreateTestFile(LXSST_DRVFS_EXECUTEONLY_TEST_DIR_CHILD, FILE_GENERIC_READ | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE);
587
588 CreateTestFile(LXSST_DRVFS_READONLY_TEST_DIR, FILE_GENERIC_READ | DELETE | SYNCHRONIZE, TRUE, INVALID_HANDLE_VALUE);
589
590 CreateTestFile(LXSST_DRVFS_READONLYATTR_TEST_FILE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_EXECUTE | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE);
591
592 THROW_LAST_ERROR_IF(!SetFileAttributes(LXSST_DRVFS_READONLYATTR_TEST_FILE, FILE_ATTRIBUTE_READONLY));
593
594 CreateTestFile(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_EXECUTE | DELETE | SYNCHRONIZE, FALSE, INVALID_HANDLE_VALUE);
595
596 THROW_LAST_ERROR_IF(!SetFileAttributes(LXSST_DRVFS_READONLYATTRDEL_TEST_FILE, FILE_ATTRIBUTE_READONLY));
597
598 //
599 // Copy the wsl_unit_tests executable to an execute-only file on DrvFs.
600 //
601
602 const std::wstring Path = L"\\\\wsl.localhost\\" LXSS_DISTRO_NAME_TEST_L L"\\data\\test\\wsl_unit_tests";
603 const wil::unique_hfile File(CreateFile(
604 Path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
605
606 THROW_LAST_ERROR_IF(!File);
607 CreateTestFile(
608 LXSST_DRVFS_EXECUTEONLY_TEST_FILE,
609 FILE_EXECUTE | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | DELETE | SYNCHRONIZE | READ_CONTROL,
610 FALSE,
611 File.get());
612
613 THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_REPARSE_TEST_DIR, nullptr));
614
615 THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test", nullptr));
616
617 THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\linktarget", nullptr));
618
619 THROW_LAST_ERROR_IF(!CreateSymbolicLink(
620 LXSST_DRVFS_REPARSE_TEST_DIR L"\\absolutelink", LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\linktarget", SYMBOLIC_LINK_FLAG_DIRECTORY));
621
622 THROW_LAST_ERROR_IF(!CreateSymbolicLink(LXSST_DRVFS_REPARSE_TEST_DIR L"\\relativelink", L"test\\linktarget", SYMBOLIC_LINK_FLAG_DIRECTORY));
623
624 {
625 const wil::unique_hfile TargetFile(CreateFile(
626 LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\filetarget",
627 FILE_GENERIC_READ | FILE_GENERIC_WRITE,
628 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
629 nullptr,
630 CREATE_NEW,
631 FILE_ATTRIBUTE_NORMAL,
632 nullptr));
633
634 THROW_LAST_ERROR_IF(!TargetFile);
635 }
636
637 THROW_LAST_ERROR_IF(!CreateSymbolicLink(LXSST_DRVFS_REPARSE_TEST_DIR L"\\filelink", L"test\\filetarget", 0));
638
639 CreateJunction(LXSST_DRVFS_REPARSE_TEST_DIR L"\\junction", LXSST_DRVFS_REPARSE_TEST_DIR L"\\test\\linktarget");
640
641 // DrvFs does not create V1 symlinks anymore; create one here manually to ensure it can still
642 // read them.
643 CreateV1Symlink(LXSST_DRVFS_REPARSE_TEST_DIR L"\\v1link", "/v1/symlink/target");
644 CreateAppExecLink(LXSST_DRVFS_REPARSE_TEST_DIR L"\\appexeclink");
645
646 if (Metadata != false)
647 {
648 THROW_LAST_ERROR_IF(!CreateDirectory(LXSST_DRVFS_METADATA_TEST_DIR, nullptr));
649
650 CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\baduid", LX_UID_INVALID, 3001, LX_S_IFREG | 0644, 0, 0, false);
651
652 CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\badgid", 3000, LX_GID_INVALID, LX_S_IFREG | 0644, 0, 0, false);
653
654 CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\badmode", 3000, 3001, 0x10000 | LX_S_IFREG | 0644, 0, 0, false);
655
656 CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\badtype1", 3000, 3001, LX_S_IFDIR | 0755, 0, 0, false);
657
658 CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\badtype2", 3000, 3001, LX_S_IFLNK | 0777, 0, 0, false);
659
660 CreateMetadataTestFile(LXSST_DRVFS_METADATA_TEST_DIR "\\nondevice", 3000, 3001, LX_S_IFREG | 0644, 1, 2, true);
661 }
662 }
663
664 static VOID CreateTestFile(_In_z_ LPCWSTR Filename, _In_ DWORD Permissions, _In_ BOOLEAN Directory, _In_ HANDLE SourceFile)
665 {
666
667 BYTE Buffer[4096];
668 DWORD BytesRead;
669
670 //
671 // Create the SID for the BUILTIN\Administrators group.
672 //
673
674 auto [AdminSid, SidBuffer] =
675 wsl::windows::common::security::CreateSid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
676
677 //
678 // Set the permissions for the SID.
679 //
680
681 EXPLICIT_ACCESS Access;
682 RtlZeroMemory(&Access, sizeof(Access));
683 Access.grfAccessPermissions = Permissions;
684 Access.grfAccessMode = SET_ACCESS;
685 Access.grfInheritance = NO_INHERITANCE;
686 Access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
687 Access.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
688 Access.Trustee.ptstrName = (LPTSTR)AdminSid;
689
690 //
691 // Allocate an ACL with the permissions.
692 //
693
694 wil::unique_any<PACL, decltype(&::LocalFree), ::LocalFree> Acl;
695 THROW_IF_WIN32_ERROR(SetEntriesInAcl(1, &Access, NULL, &Acl));
696
697 //
698 // Create a security descriptor and set the ACL.
699 //
700
701 const wil::unique_hlocal_security_descriptor Descriptor(::LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH));
702
703 THROW_LAST_ERROR_IF(!Descriptor);
704 THROW_LAST_ERROR_IF(!InitializeSecurityDescriptor(Descriptor.get(), SECURITY_DESCRIPTOR_REVISION));
705
706 THROW_LAST_ERROR_IF(!SetSecurityDescriptorDacl(Descriptor.get(), TRUE, Acl.get(), FALSE));
707
708 //
709 // Create security attributes that point to the descriptor.
710 //
711
712 SECURITY_ATTRIBUTES Attributes;
713 RtlZeroMemory(&Attributes, sizeof(Attributes));
714 Attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
715 Attributes.lpSecurityDescriptor = Descriptor.get();
716 Attributes.bInheritHandle = FALSE;
717
718 //
719 // Create a file or directory with the security attributes.
720 //
721
722 if (Directory == FALSE)
723 {
724 const wil::unique_hfile File(
725 CreateFile(Filename, GENERIC_WRITE | SYNCHRONIZE, 0, &Attributes, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL));
726
727 THROW_LAST_ERROR_IF(!File);
728
729 //
730 // If a source file was specified, copy its contents.
731 //
732
733 if (SourceFile != INVALID_HANDLE_VALUE)
734 {
735 THROW_LAST_ERROR_IF(!ReadFile(SourceFile, Buffer, sizeof(Buffer), &BytesRead, NULL));
736
737 while (BytesRead > 0)
738 {
739 THROW_LAST_ERROR_IF(!WriteFile(File.get(), Buffer, BytesRead, NULL, NULL));
740
741 THROW_LAST_ERROR_IF(!ReadFile(SourceFile, Buffer, sizeof(Buffer), &BytesRead, NULL));
742 }
743 }
744 }
745 else
746 {
747 THROW_LAST_ERROR_IF(!CreateDirectory(Filename, &Attributes));
748 }
749 }
750
751 static VOID CreateMetadataTestFile(
752 _In_ LPCWSTR Filename, _In_ ULONG Uid, _In_ ULONG Gid, _In_ ULONG Mode, _In_ ULONG DeviceIdMajor, _In_ ULONG DeviceIdMinor, _In_ bool IncludeDeviceId)
753 {
754
755 //
756 // Each individual EA entry must be aligned on a 4 byte boundary, but the
757 // value inside each EA struct must not be. Therefore, set packing to 1
758 // byte, and add padding to manually align the entries.
759 //
760
761 #pragma pack(push, 1)
762
763 struct
764 {
765 struct
766 {
767 union
768 {
769 FILE_FULL_EA_INFORMATION Header;
770 CHAR Buffer[FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + LXSST_METADATA_EA_NAME_LENGTH + 1];
771 };
772
773 ULONG Uid;
774 } Uid;
775
776 CHAR Padding1;
777 struct
778 {
779 union
780 {
781 FILE_FULL_EA_INFORMATION Header;
782 CHAR Buffer[FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + LXSST_METADATA_EA_NAME_LENGTH + 1];
783 };
784
785 ULONG Gid;
786 } Gid;
787
788 CHAR Padding2;
789 struct
790 {
791 union
792 {
793 FILE_FULL_EA_INFORMATION Header;
794 CHAR Buffer[FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + LXSST_METADATA_EA_NAME_LENGTH + 1];
795 };
796
797 ULONG Mode;
798 } Mode;
799
800 CHAR Padding3;
801 struct
802 {
803 union
804 {
805 FILE_FULL_EA_INFORMATION Header;
806 CHAR Buffer[FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + LXSST_METADATA_EA_NAME_LENGTH + 1];
807 };
808
809 ULONG DeviceIdMajor;
810 ULONG DeviceIdMinor;
811 } DeviceId;
812 } EaBuffer;
813
814 #pragma pack(pop)
815
816 RtlZeroMemory(&EaBuffer, sizeof(EaBuffer));
817 EaBuffer.Uid.Header.EaNameLength = LXSST_METADATA_EA_NAME_LENGTH;
818 EaBuffer.Uid.Header.EaValueLength = sizeof(ULONG);
819 RtlCopyMemory(EaBuffer.Uid.Header.EaName, LX_FILE_METADATA_UID_EA_NAME, LXSST_METADATA_EA_NAME_LENGTH);
820
821 EaBuffer.Uid.Uid = Uid;
822 EaBuffer.Uid.Header.NextEntryOffset = (ULONG)((PUCHAR)&EaBuffer.Gid - (PUCHAR)&EaBuffer.Uid);
823 EaBuffer.Gid.Header.EaNameLength = LXSST_METADATA_EA_NAME_LENGTH;
824 EaBuffer.Gid.Header.EaValueLength = sizeof(ULONG);
825 RtlCopyMemory(EaBuffer.Gid.Header.EaName, LX_FILE_METADATA_GID_EA_NAME, LXSST_METADATA_EA_NAME_LENGTH);
826
827 EaBuffer.Gid.Gid = Gid;
828 EaBuffer.Gid.Header.NextEntryOffset = (ULONG)((PUCHAR)&EaBuffer.Mode - (PUCHAR)&EaBuffer.Gid);
829 EaBuffer.Mode.Header.EaNameLength = LXSST_METADATA_EA_NAME_LENGTH;
830 EaBuffer.Mode.Header.EaValueLength = sizeof(ULONG);
831 RtlCopyMemory(EaBuffer.Mode.Header.EaName, LX_FILE_METADATA_MODE_EA_NAME, LXSST_METADATA_EA_NAME_LENGTH);
832
833 EaBuffer.Mode.Mode = Mode;
834 if (IncludeDeviceId != false)
835 {
836 EaBuffer.Mode.Header.NextEntryOffset = (ULONG)((PUCHAR)&EaBuffer.DeviceId - (PUCHAR)&EaBuffer.Mode);
837 EaBuffer.DeviceId.Header.EaNameLength = LXSST_METADATA_EA_NAME_LENGTH;
838 EaBuffer.DeviceId.Header.EaValueLength = sizeof(ULONG);
839 RtlCopyMemory(EaBuffer.DeviceId.Header.EaName, LX_FILE_METADATA_DEVICE_ID_EA_NAME, LXSST_METADATA_EA_NAME_LENGTH);
840
841 EaBuffer.DeviceId.DeviceIdMajor = DeviceIdMajor;
842 EaBuffer.DeviceId.DeviceIdMinor = DeviceIdMinor;
843 }
844
845 const std::wstring NtPath{std::wstring(L"\\DosDevices\\") + Filename};
846 UNICODE_STRING Name;
847 RtlInitUnicodeString(&Name, NtPath.c_str());
848 OBJECT_ATTRIBUTES Attributes;
849 InitializeObjectAttributes(&Attributes, &Name, 0, nullptr, 0);
850 wil::unique_hfile File;
851 IO_STATUS_BLOCK IoStatus;
852 THROW_IF_NTSTATUS_FAILED(NtCreateFile(
853 &File,
854 FILE_GENERIC_READ,
855 &Attributes,
856 &IoStatus,
857 nullptr,
858 FILE_ATTRIBUTE_NORMAL,
859 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
860 FILE_CREATE,
861 0,
862 &EaBuffer,
863 sizeof(EaBuffer)));
864 }
865
866 static VOID VerifyDrvFsMetadata()
867 {
868 wil::unique_hfile File;
869 IO_STATUS_BLOCK IoStatus;
870 UNICODE_STRING Name;
871 RtlInitUnicodeString(&Name, L"\\DosDevices\\" LXSST_DRVFS_METADATA_TEST_DIR);
872 OBJECT_ATTRIBUTES Attributes;
873 InitializeObjectAttributes(&Attributes, &Name, 0, nullptr, 0);
874 THROW_IF_NTSTATUS_FAILED(NtCreateFile(
875 &File, FILE_READ_EA, &Attributes, &IoStatus, nullptr, 0, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), FILE_OPEN, FILE_DIRECTORY_FILE, nullptr, 0));
876
877 UCHAR Buffer[1000];
878 THROW_IF_NTSTATUS_FAILED(ZwQueryEaFile(File.get(), &IoStatus, Buffer, sizeof(Buffer), FALSE, nullptr, 0, nullptr, TRUE));
879
880 bool FoundUid = false;
881 bool FoundGid = false;
882 bool FoundMode = false;
883 PFILE_FULL_EA_INFORMATION EaInfo = (PFILE_FULL_EA_INFORMATION)Buffer;
884 for (;;)
885 {
886 VERIFY_ARE_EQUAL(EaInfo->EaNameLength, 6);
887 VERIFY_ARE_EQUAL(EaInfo->EaValueLength, 4);
888 std::string EaName{EaInfo->EaName};
889 ULONG Value = *(PULONG)((PCHAR)EaInfo->EaName + EaInfo->EaNameLength + 1);
890 if (EaName == LX_FILE_METADATA_UID_EA_NAME)
891 {
892 FoundUid = true;
893 VERIFY_ARE_EQUAL(Value, 0x11223344ul);
894 }
895 else if (EaName == LX_FILE_METADATA_GID_EA_NAME)
896 {
897 FoundGid = true;
898 VERIFY_ARE_EQUAL(Value, 0x55667788ul);
899 }
900 else if (EaName == LX_FILE_METADATA_MODE_EA_NAME)
901 {
902 FoundMode = true;
903 VERIFY_ARE_EQUAL(Value, (ULONG)(LX_S_IFDIR | 0775));
904 }
905 else
906 {
907 VERIFY_FAIL(L"Unexpected EA on file.");
908 }
909
910 if (EaInfo->NextEntryOffset == 0)
911 {
912 break;
913 }
914
915 EaInfo = (PFILE_FULL_EA_INFORMATION)((PCHAR)EaInfo + EaInfo->NextEntryOffset);
916 };
917
918 VERIFY_IS_TRUE(FoundUid);
919 VERIFY_IS_TRUE(FoundGid);
920 VERIFY_IS_TRUE(FoundMode);
921 }
922
923 static VOID CreateJunction(_In_ const std::wstring& Junction, _In_ const std::wstring& Target)
924 {
925
926 //
927 // The logic for creating a junction was taken from mklink.
928 //
929
930 THROW_LAST_ERROR_IF(!CreateDirectory(Junction.c_str(), NULL));
931 const wil::unique_hfile Dir(CreateFile(
932 Junction.c_str(), FILE_GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
933
934 THROW_LAST_ERROR_IF(!Dir);
935
936 UNICODE_STRING LinkPath = {0};
937 auto Cleanup = wil::scope_exit([&] {
938 if (LinkPath.Buffer != nullptr)
939 {
940 FREE(LinkPath.Buffer);
941 }
942 });
943
944 THROW_IF_NTSTATUS_FAILED(RtlDosPathNameToNtPathName_U_WithStatus(Target.c_str(), &LinkPath, nullptr, nullptr));
945
946 //
947 // The buffer needs space for the substitute name and the print name, with
948 // NULL characters. This can't overflow since they are all paths with
949 // lengths less than MAXUSHORT.
950 //
951
952 const ULONG ReparseBufferSize = (ULONG)(FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer[0]) +
953 Target.length() * sizeof(WCHAR) + LinkPath.Length + 2 * sizeof(UNICODE_NULL));
954
955 //
956 // Allocate the reparse data buffer.
957 //
958
959 const std::unique_ptr<REPARSE_DATA_BUFFER> Reparse((PREPARSE_DATA_BUFFER) new char[ReparseBufferSize]);
960
961 ZeroMemory(Reparse.get(), ReparseBufferSize);
962 Reparse->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
963
964 //
965 // The data length is the buffer size excluding the header.
966 //
967
968 Reparse->ReparseDataLength = (USHORT)(ReparseBufferSize - REPARSE_DATA_BUFFER_HEADER_SIZE);
969
970 //
971 // Copy the NT path into the buffer for the substitute name.
972 //
973
974 Reparse->MountPointReparseBuffer.SubstituteNameLength = LinkPath.Length;
975 RtlCopyMemory(Reparse->MountPointReparseBuffer.PathBuffer, LinkPath.Buffer, LinkPath.Length);
976
977 const USHORT Offset = LinkPath.Length + sizeof(UNICODE_NULL);
978
979 //
980 // Copy the DOS path into the buffer for the print name.
981 //
982
983 Reparse->MountPointReparseBuffer.PrintNameOffset = Offset;
984 Reparse->MountPointReparseBuffer.PrintNameLength = (USHORT)(Target.length() * sizeof(WCHAR));
985
986 RtlCopyMemory((Reparse->MountPointReparseBuffer.PathBuffer + (Offset / sizeof(WCHAR))), Target.c_str(), Target.length() * sizeof(WCHAR));
987
988 //
989 // Set the reparse point on the file.
990 //
991
992 IO_STATUS_BLOCK IoStatus;
993 THROW_IF_NTSTATUS_FAILED(NtFsControlFile(
994 Dir.get(), nullptr, nullptr, nullptr, &IoStatus, FSCTL_SET_REPARSE_POINT, Reparse.get(), ReparseBufferSize, nullptr, 0));
995
996 return;
997 }
998
999 static VOID CreateV1Symlink(const std::wstring& Symlink, std::string_view Target)
1000 {
1001
1002 //
1003 // Create a symlink using the V1 LX symlink format, where the target is
1004 // stored in the file data. The reparse data only contains a version
1005 // number.
1006 //
1007
1008 union
1009 {
1010 REPARSE_DATA_BUFFER Header;
1011 struct
1012 {
1013 CHAR Buffer[REPARSE_DATA_BUFFER_HEADER_SIZE];
1014 ULONG Version;
1015 } Data;
1016 } Reparse{};
1017
1018 const ULONG ReparseBufferSize = REPARSE_DATA_BUFFER_HEADER_SIZE + sizeof(ULONG);
1019
1020 //
1021 // The data length is the buffer size excluding the header.
1022 //
1023
1024 Reparse.Header.ReparseTag = IO_REPARSE_TAG_LX_SYMLINK;
1025 Reparse.Header.ReparseDataLength = sizeof(ULONG);
1026 Reparse.Data.Version = 1;
1027
1028 const auto File = CreateReparsePoint(Symlink, &Reparse, ReparseBufferSize);
1029
1030 //
1031 // Write the target to the file.
1032 //
1033
1034 DWORD written;
1035 THROW_IF_WIN32_BOOL_FALSE(WriteFile(File.get(), Target.data(), gsl::narrow_cast<DWORD>(Target.size()), &written, nullptr));
1036 VERIFY_ARE_EQUAL(Target.size(), written);
1037
1038 return;
1039 }
1040
1041 static VOID CreateAppExecLink(const std::wstring& Link)
1042 {
1043 //
1044 // This link will not be valid from Windows's perspective, since it only
1045 // contains the header and not any actual reparse data. However, it has the
1046 // right reparse tag which is sufficient to test drvfs's behavior.
1047 //
1048
1049 REPARSE_DATA_BUFFER Reparse{};
1050 Reparse.ReparseTag = IO_REPARSE_TAG_APPEXECLINK;
1051 Reparse.ReparseDataLength = 0;
1052 CreateReparsePoint(Link, &Reparse, REPARSE_DATA_BUFFER_HEADER_SIZE);
1053 }
1054
1055 static wil::unique_hfile CreateReparsePoint(_In_ const std::wstring& Path, _In_ PVOID ReparseBuffer, _In_ ULONG ReparseBufferSize)
1056 {
1057 wil::unique_hfile File{CreateFile(
1058 Path.c_str(), FILE_GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)};
1059
1060 THROW_LAST_ERROR_IF(!File);
1061 IO_STATUS_BLOCK IoStatus;
1062 THROW_IF_NTSTATUS_FAILED(NtFsControlFile(
1063 File.get(), nullptr, nullptr, nullptr, &IoStatus, FSCTL_SET_REPARSE_POINT, ReparseBuffer, ReparseBufferSize, nullptr, 0));
1064
1065 return File;
1066 }
1067
1068 static VOID CreateVolume(LPCSTR FileSystem, ULONG MaxSizeInMb, LPCSTR MountPoint, LPCSTR VhdPath)
1069 {
1070 THROW_LAST_ERROR_IF(!CreateDirectoryA(MountPoint, NULL));
1071
1072 const auto CreateScript = std::vformat(
1073 "create vdisk file={} maximum={} type=expandable\n"
1074 "select vdisk file={}\n"
1075 "attach vdisk\n"
1076 "create partition primary\n"
1077 "select partition 1\n"
1078 "online volume\n"
1079 "format fs={} quick\n"
1080 "assign mount={}\n",
1081 std::make_format_args(VhdPath, MaxSizeInMb, VhdPath, FileSystem, MountPoint));
1082
1083 RunDiskpartScript(CreateScript.c_str());
1084 }
1085
1086 static VOID RunDiskpartScript(LPCSTR Script)
1087 {
1088 const std::wstring ScriptFileName = wsl::windows::common::filesystem::GetTempFilename();
1089
1090 std::ofstream ScriptFile(ScriptFileName);
1091 THROW_LAST_ERROR_IF(!ScriptFile);
1092
1093 auto Cleanup = wil::scope_exit([&] { DeleteFileW(ScriptFileName.c_str()); });
1094
1095 ScriptFile << Script;
1096 ScriptFile.close();
1097
1098 std::wstring CommandLine = L"diskpart.exe /s " + ScriptFileName;
1099 THROW_HR_IF(E_FAIL, ((wsl::windows::common::helpers::RunProcess(CommandLine)) != 0));
1100 }
1101
1102 static VOID DeleteVolume(LPCSTR MountPoint, LPCSTR VhdPath)
1103 {
1104 const auto CleanupScript = std::vformat(
1105 "select vdisk file={}\n"
1106 "select partition 1\n"
1107 "remove all\n"
1108 "detach vdisk\n",
1109 std::make_format_args(VhdPath));
1110
1111 RunDiskpartScript(CleanupScript.c_str());
1112
1113 RemoveDirectoryA(MountPoint);
1114 DeleteFileA(VhdPath);
1115 }
1116
1117 static void ValidateDrvfsMounts(DWORD CreateProcessFlags, DrvFsMode Mode)
1118 {
1119 auto validate = [CreateProcessFlags](const std::wstring& expectedType, HANDLE token) {
1120 const auto commandLine = LxssGenerateWslCommandLine(L"mount | grep -F '/mnt/c type'");
1121
1122 wsl::windows::common::SubProcess process(nullptr, commandLine.c_str(), CreateProcessFlags);
1123 process.SetToken(token);
1124 process.SetShowWindow(SW_HIDE);
1125
1126 const auto output = process.RunAndCaptureOutput();
1127 const auto lines = LxssSplitString(output.Stdout, L"\n");
1128
1129 VERIFY_ARE_EQUAL(lines.size(), 1);
1130
1131 if (!expectedType.empty())
1132 {
1133 VERIFY_IS_TRUE(output.Stdout.find(expectedType) == 0);
1134 }
1135 };
1136
1137 std::wstring elevatedType;
1138 std::wstring nonElevatedType;
1139 switch (Mode)
1140 {
1141 case DrvFsMode::Plan9:
1142 elevatedType = L"C:\\";
1143 nonElevatedType = L"C:\\";
1144 break;
1145 case DrvFsMode::Virtio9p:
1146 elevatedType = L"drvfsa";
1147 nonElevatedType = L"drvfs";
1148 break;
1149 case DrvFsMode::VirtioFs:
1150 // VirtioFs uses GUIDs as the tag so the value is not predictable.
1151 break;
1152
1153 default:
1154 LogError("Unexpected mode %d", (int)Mode);
1155 return;
1156 }
1157
1158 // Validate that mount types are correct in both namespaces
1159 validate(elevatedType, nullptr);
1160
1161 const auto nonElevatedToken = GetNonElevatedToken();
1162 validate(nonElevatedType, nonElevatedToken.get());
1163
1164 // Elevated token should be able to create files at the root of the drive (/mnt/c)
1165 {
1166 const auto commandLine =
1167 LxssGenerateWslCommandLine(L"touch /mnt/c/elevated_test_file.tmp && rm /mnt/c/elevated_test_file.tmp");
1168
1169 wsl::windows::common::SubProcess process(nullptr, commandLine.c_str(), CreateProcessFlags);
1170 process.SetToken(nullptr);
1171 process.SetShowWindow(SW_HIDE);
1172
1173 const auto output = process.RunAndCaptureOutput();
1174 VERIFY_ARE_EQUAL(0, output.ExitCode, L"Elevated token should be able to create files at /mnt/c");
1175 }
1176
1177 // Non-elevated token should NOT be able to create files at the root of the drive (/mnt/c)
1178 {
1179 const auto commandLine = LxssGenerateWslCommandLine(L"touch /mnt/c/nonelevated_test_file.tmp");
1180
1181 wsl::windows::common::SubProcess process(nullptr, commandLine.c_str(), CreateProcessFlags);
1182 process.SetToken(nonElevatedToken.get());
1183 process.SetShowWindow(SW_HIDE);
1184
1185 const auto output = process.RunAndCaptureOutput();
1186 VERIFY_ARE_NOT_EQUAL(0, output.ExitCode, L"Non-elevated token should NOT be able to create files at /mnt/c (C:\\)");
1187 }
1188 }
1189
1190 static VOID VerifyDrvFsSymlink(const std::wstring& Path, const std::wstring& ExpectedTarget, bool Directory)
1191 {
1192
1193 const std::wstring NtPath = L"\\DosDevices\\" + Path;
1194 UNICODE_STRING Name;
1195 RtlInitUnicodeString(&Name, NtPath.c_str());
1196 OBJECT_ATTRIBUTES Attributes;
1197 InitializeObjectAttributes(&Attributes, &Name, 0, nullptr, 0);
1198
1199 wil::unique_hfile Symlink;
1200 IO_STATUS_BLOCK IoStatus;
1201 THROW_IF_NTSTATUS_FAILED(NtCreateFile(
1202 &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));
1203
1204 FILE_ATTRIBUTE_TAG_INFORMATION Info;
1205 THROW_IF_NTSTATUS_FAILED(NtQueryInformationFile(Symlink.get(), &IoStatus, &Info, sizeof(Info), FileAttributeTagInformation));
1206
1207 VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0);
1208 if (Directory != false)
1209 {
1210 VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
1211 }
1212 else
1213 {
1214 VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
1215 }
1216
1217 VERIFY_ARE_EQUAL(Info.ReparseTag, IO_REPARSE_TAG_SYMLINK);
1218
1219 union
1220 {
1221 char Buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
1222 REPARSE_DATA_BUFFER Data;
1223 } ReparseData;
1224
1225 THROW_IF_NTSTATUS_FAILED(NtFsControlFile(
1226 Symlink.get(), nullptr, nullptr, nullptr, &IoStatus, FSCTL_GET_REPARSE_POINT, nullptr, 0, &ReparseData, sizeof(ReparseData)));
1227
1228 VERIFY_ARE_EQUAL(ReparseData.Data.ReparseTag, IO_REPARSE_TAG_SYMLINK);
1229 VERIFY_ARE_EQUAL(ReparseData.Data.SymbolicLinkReparseBuffer.Flags, (ULONG)SYMLINK_FLAG_RELATIVE);
1230 const std::wstring Target(
1231 (PWCHAR)((PCHAR)ReparseData.Data.SymbolicLinkReparseBuffer.PathBuffer + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameOffset),
1232 (PWCHAR)((PCHAR)ReparseData.Data.SymbolicLinkReparseBuffer.PathBuffer + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameOffset +
1233 ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameLength));
1234
1235 VERIFY_ARE_EQUAL(Target, ExpectedTarget);
1236 const std::wstring Target2(
1237 (PWCHAR)((PCHAR)ReparseData.Data.SymbolicLinkReparseBuffer.PathBuffer + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameOffset),
1238 (PWCHAR)((PCHAR)ReparseData.Data.SymbolicLinkReparseBuffer.PathBuffer + ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameOffset +
1239 ReparseData.Data.SymbolicLinkReparseBuffer.SubstituteNameLength));
1240
1241 VERIFY_ARE_EQUAL(Target2, ExpectedTarget);
1242 }
1243
1244 static VOID VerifyDrvFsLxSymlink(const std::wstring& Path)
1245 {
1246
1247 const std::wstring NtPath = L"\\DosDevices\\" + Path;
1248 UNICODE_STRING Name;
1249 RtlInitUnicodeString(&Name, NtPath.c_str());
1250 OBJECT_ATTRIBUTES Attributes;
1251 InitializeObjectAttributes(&Attributes, &Name, 0, nullptr, 0);
1252
1253 IO_STATUS_BLOCK IoStatus;
1254 FILE_STAT_INFORMATION Info;
1255 THROW_IF_NTSTATUS_FAILED(NtQueryInformationByName(&Attributes, &IoStatus, &Info, sizeof(Info), FileStatInformation));
1256
1257 VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0);
1258 VERIFY_IS_TRUE((Info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
1259 VERIFY_ARE_EQUAL(Info.ReparseTag, IO_REPARSE_TAG_LX_SYMLINK);
1260 }
1261 };
1262
1263 class WSL1 : public DrvFsTests
1264 {
1265 WSL_TEST_CLASS(WSL1)
1266
1267 bool m_initialized{false};
1268 TEST_CLASS_SETUP(TestClassSetup)
1269 {
1270 if (LxsstuVmMode())
1271 {
1272 LogSkipped("This test class is only applicable to WSL1");
1273 }
1274 else
1275 {
1276 VERIFY_ARE_EQUAL(LxsstuInitialize(FALSE), TRUE);
1277 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(LXSST_TESTS_INSTALL_COMMAND_LINE), 0);
1278 m_initialized = true;
1279 }
1280
1281 return true;
1282 }
1283
1284 TEST_CLASS_CLEANUP(TestClassCleanup)
1285 {
1286 if (m_initialized)
1287 {
1288 LxsstuUninitialize(FALSE);
1289 }
1290
1291 return true;
1292 }
1293
1294 WSL1_TEST_METHOD(DrvFsDisableQueryByName)
1295 {
1296 VERIFY_NO_THROW(DrvFsCommon(LX_DRVFS_DISABLE_QUERY_BY_NAME));
1297 }
1298
1299 WSL1_TEST_METHOD(DrvFsDisableQueryByNameAndStatInfo)
1300 {
1301 VERIFY_NO_THROW(DrvFsCommon(LX_DRVFS_DISABLE_QUERY_BY_NAME_AND_STAT_INFO));
1302 }
1303
1304 WSL1_TEST_METHOD(VfsAccessDrvFs)
1305 {
1306 DrvFsTests::VfsAccessDrvFs();
1307 }
1308
1309 WSL1_TEST_METHOD(FsCommonDrvFs)
1310 {
1311 DrvFsTests::FsCommonDrvFs();
1312 }
1313
1314 WSL1_TEST_METHOD(DrvFs)
1315 {
1316 DrvFsTests::DrvFs(DrvFsMode::WSL1);
1317 }
1318
1319 WSL1_TEST_METHOD(DrvFsFat)
1320 {
1321 DrvFsTests::DrvFsFat(DrvFsMode::WSL1);
1322 }
1323
1324 WSL1_TEST_METHOD(DrvFsSmb)
1325 {
1326 DrvFsTests::DrvFsSmb(DrvFsMode::WSL1);
1327 }
1328
1329 WSL1_TEST_METHOD(DrvFsMetadata)
1330 {
1331 DrvFsTests::DrvFsMetadata(DrvFsMode::WSL1);
1332 }
1333
1334 WSL1_TEST_METHOD(XattrDrvFs)
1335 {
1336 DrvFsTests::XattrDrvFs(DrvFsMode::WSL1);
1337 }
1338
1339 WSL1_TEST_METHOD(WslPath)
1340 {
1341 DrvFsTests::WslPath(DrvFsMode::WSL1);
1342 }
1343 };
1344
1345 #define WSL2_DRVFS_TEST_CLASS(_mode) \
1346 class WSL2##_mode## : public DrvFsTests \
1347 { \
1348 WSL_TEST_CLASS(WSL2##_mode##) \
1349 std::unique_ptr<WslConfigChange> m_config; \
1350 TEST_CLASS_SETUP(TestClassSetup) \
1351 { \
1352 if (!LxsstuVmMode()) \
1353 { \
1354 LogSkipped("This test class is only applicable to WSL2"); \
1355 } \
1356 else \
1357 { \
1358 VERIFY_ARE_EQUAL(LxsstuInitialize(FALSE), TRUE); \
1359 m_config.reset(new WslConfigChange(LxssGenerateTestConfig({.drvFsMode = DrvFsMode::##_mode##}))); \
1360 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(LXSST_TESTS_INSTALL_COMMAND_LINE), 0); \
1361 } \
1362 \
1363 return true; \
1364 } \
1365 \
1366 TEST_CLASS_CLEANUP(TestClassCleanup) \
1367 { \
1368 if (m_config) \
1369 { \
1370 m_config.reset(); \
1371 LxsstuUninitialize(FALSE); \
1372 } \
1373 \
1374 return true; \
1375 } \
1376 \
1377 WSL2_TEST_METHOD(VfsAccessDrvFs) \
1378 { \
1379 DrvFsTests::VfsAccessDrvFs(); \
1380 } \
1381 \
1382 WSL2_TEST_METHOD(FsCommonDrvFs) \
1383 { \
1384 DrvFsTests::FsCommonDrvFs(); \
1385 } \
1386 \
1387 WSL2_TEST_METHOD(DrvFs) \
1388 { \
1389 DrvFsTests::DrvFs(DrvFsMode::##_mode##); \
1390 } \
1391 \
1392 WSL2_TEST_METHOD(DrvFsFat) \
1393 { \
1394 DrvFsTests::DrvFsFat(DrvFsMode::##_mode##); \
1395 } \
1396 \
1397 WSL2_TEST_METHOD(DrvFsSmb) \
1398 { \
1399 DrvFsTests::DrvFsSmb(DrvFsMode::##_mode##); \
1400 } \
1401 \
1402 WSL2_TEST_METHOD(DrvFsMetadata) \
1403 { \
1404 DrvFsTests::DrvFsMetadata(DrvFsMode::##_mode##); \
1405 } \
1406 \
1407 WSL2_TEST_METHOD(DrvfsMountElevated) \
1408 { \
1409 DrvFsTests::DrvfsMountElevated(DrvFsMode::##_mode##); \
1410 } \
1411 \
1412 WSL2_TEST_METHOD(DrvfsMountElevatedDifferentConsole) \
1413 { \
1414 DrvFsTests::DrvfsMountElevatedDifferentConsole(DrvFsMode::##_mode##); \
1415 } \
1416 \
1417 WSL2_TEST_METHOD(DrvfsMountNonElevated) \
1418 { \
1419 DrvFsTests::DrvfsMountNonElevated(DrvFsMode::##_mode##); \
1420 } \
1421 \
1422 WSL2_TEST_METHOD(DrvfsMountNonElevatedDifferentConsole) \
1423 { \
1424 DrvFsTests::DrvfsMountNonElevatedDifferentConsole(DrvFsMode::##_mode##); \
1425 } \
1426 \
1427 WSL2_TEST_METHOD(DrvfsMountElevatedSystemDistroEnabled) \
1428 { \
1429 DrvFsTests::DrvfsMountElevatedSystemDistroEnabled(DrvFsMode::##_mode##); \
1430 } \
1431 \
1432 WSL2_TEST_METHOD(DrvfsMountNonElevatedSystemDistroEnabled) \
1433 { \
1434 DrvFsTests::DrvfsMountNonElevatedSystemDistroEnabled(DrvFsMode::##_mode##); \
1435 } \
1436 \
1437 WSL2_TEST_METHOD(XattrDrvFs) \
1438 { \
1439 DrvFsTests::XattrDrvFs(DrvFsMode::##_mode##); \
1440 } \
1441 \
1442 WSL2_TEST_METHOD(DrvFsReFs) \
1443 { \
1444 DrvFsTests::DrvFsReFs(DrvFsMode::##_mode##); \
1445 } \
1446 \
1447 WSL2_TEST_METHOD(WslPath) \
1448 { \
1449 DrvFsTests::WslPath(DrvFsMode::##_mode##); \
1450 } \
1451 \
1452 WSL2_TEST_METHOD(DrvFsMountUnicodePath) \
1453 { \
1454 DrvFsTests::DrvFsMountUnicodePath(DrvFsMode::##_mode##); \
1455 } \
1456 \
1457 WSL2_TEST_METHOD(DrvfsMountManyVirtioFsShares) \
1458 { \
1459 DrvFsTests::DrvfsMountManyVirtioFsShares(DrvFsMode::##_mode##); \
1460 } \
1461 \
1462 WSL2_TEST_METHOD(DrvfsMountManyVirtioFsSharesLegacy) \
1463 { \
1464 DrvFsTests::DrvfsMountManyVirtioFsShares(DrvFsMode::##_mode##, false); \
1465 } \
1466 \
1467 WSL2_TEST_METHOD(DrvfsMountReadOnly) \
1468 { \
1469 DrvFsTests::DrvfsMountReadOnly(); \
1470 } \
1471 }
1472
1473 WSL2_DRVFS_TEST_CLASS(Plan9);
1474
1475 WSL2_DRVFS_TEST_CLASS(VirtioFs);
1476
1477 // Disabled while an issue with the 6.1 Linux kernel causing disk corruption is investigated.
1478 // TODO: Enable again once the issue is resolved
1479 // WSL2_DRVFS_TEST_CLASS(Virtio9p);
1480
1481 } // namespace DrvFsTests