master
cpp 1,576 lines 56.3 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 MountTests.cpp
8
9 Abstract:
10
11 This file contains test cases for the disk mounting logic.
12
13 --*/
14
15 #include "precomp.h"
16 #include "Common.h"
17
18 #define TEST_MOUNT_DISK L"TestDisk.vhd"
19 #define TEST_MOUNT_VHD L"TestVhd.vhd"
20 #define TEST_UNMOUNT_VHD_DNE L"TestVhdNotHere.vhd"
21 #define TEST_MOUNT_NAME L"testmount"
22
23 #define SKIP_UNSUPPORTED_ARM64_MOUNT_TEST() \
24 if constexpr (wsl::shared::Arm64) \
25 { \
26 WSL_TEST_VERSION_REQUIRED(27653); \
27 }
28
29 namespace MountTests {
30
31 // Disks sometimes take a bit of time to become available when attached back to the host.
32 constexpr auto c_diskOpenTimeoutMs = 120000;
33
34 class SetAutoMountPolicy
35 {
36 public:
37 SetAutoMountPolicy() = delete;
38 SetAutoMountPolicy(const SetAutoMountPolicy&) = delete;
39 SetAutoMountPolicy& operator=(const SetAutoMountPolicy&) = delete;
40
41 SetAutoMountPolicy(SetAutoMountPolicy&& Other) = default;
42 SetAutoMountPolicy& operator=(SetAutoMountPolicy&&) = default;
43
44 SetAutoMountPolicy(bool Enable) : PreviousState(GetAutoMountState())
45 {
46 if (Enable != PreviousState)
47 {
48 SetAutoMountState(Enable);
49 }
50 else
51 {
52 PreviousState.reset();
53 }
54 }
55
56 ~SetAutoMountPolicy()
57 {
58 if (PreviousState.has_value())
59 {
60 SetAutoMountState(PreviousState.value());
61 }
62 }
63
64 private:
65 static bool GetAutoMountStateFromOutput(const std::wstring& Output)
66 {
67 if (Output.find(L"Automatic mounting of new volumes enabled") != std::wstring::npos)
68 {
69 return true;
70 }
71 else if (Output.find(L"Automatic mounting of new volumes disabled") != std::wstring::npos)
72 {
73 return false;
74 }
75
76 LogError("Unexpected diskpart output: '%s'", Output.c_str());
77 VERIFY_FAIL(L"Failed to parse diskpart's output");
78 return false;
79 }
80
81 static bool GetAutoMountState()
82 {
83 std::wstring cmd = L"diskpart.exe";
84 return GetAutoMountStateFromOutput(LxsstuLaunchCommandAndCaptureOutput(cmd.data(), "automount\r\n").first);
85 }
86
87 static void SetAutoMountState(bool Enabled)
88 {
89 LogInfo("Setting automount policy to %i", Enabled);
90
91 std::wstring cmd = L"diskpart.exe";
92 const auto input = std::string("automount ") + (Enabled ? "enable\r\n" : "disable\r\n");
93 auto [output, _] = LxsstuLaunchCommandAndCaptureOutput(cmd.data(), input.c_str());
94
95 VERIFY_ARE_EQUAL(Enabled, GetAutoMountStateFromOutput(output));
96 }
97
98 std::optional<bool> PreviousState;
99 };
100
101 class MountTests
102 {
103 std::wstring DiskDevice;
104 std::wstring VhdDevice;
105 wil::unique_tokeninfo_ptr<TOKEN_USER> User = wil::get_token_information<TOKEN_USER>();
106 std::unique_ptr<wsl::windows::common::security::privilege_context> PrivilegeState;
107 DWORD DiskNumber = 0;
108 SetAutoMountPolicy AutoMountPolicy{false};
109
110 struct ExpectedMountState
111 {
112 size_t PartitionIndex;
113 std::optional<std::wstring> Type;
114 std::optional<std::wstring> Options;
115 };
116
117 struct ExpectedDiskState
118 {
119 std::wstring Path;
120 std::vector<ExpectedMountState> Mounts;
121 };
122
123 WSL_TEST_CLASS(MountTests)
124
125 TEST_CLASS_SETUP(TestClassSetup)
126 {
127 VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
128
129 if (!LxsstuVmMode())
130 {
131 return true;
132 }
133
134 // Needed to open processes under te
135 PrivilegeState = wsl::windows::common::security::AcquirePrivilege(SE_DEBUG_NAME);
136
137 // Create a 20MB vhd for testing mounting passthrough disks
138 DeleteFileW(TEST_MOUNT_DISK);
139
140 try
141 {
142 LxsstuLaunchPowershellAndCaptureOutput(L"New-Vhd -Path " TEST_MOUNT_DISK " -SizeBytes 20MB");
143 }
144 CATCH_LOG()
145
146 // Mount it in Windows
147 auto [output, _] = LxsstuLaunchPowershellAndCaptureOutput(L"(Mount-VHD " TEST_MOUNT_DISK " -PassThru | Get-Disk).Number");
148
149 Trim(output);
150 DiskNumber = std::stoul(output);
151
152 // Construct the disk path
153 DiskDevice = L"\\\\.\\PhysicalDrive" + output;
154 LogInfo("Mounted the passthrough test vhd as %ls", DiskDevice.c_str());
155
156 // Create a 20MB vhd for testing mount --vhd
157 DeleteFileW(TEST_MOUNT_VHD);
158
159 LxsstuLaunchPowershellAndCaptureOutput(L"New-Vhd -Path " TEST_MOUNT_VHD " -SizeBytes 20MB");
160
161 VhdDevice = wsl::windows::common::filesystem::GetFullPath(TEST_MOUNT_VHD);
162 LogInfo("Create mount --vhd test vhd as %ls", VhdDevice.c_str());
163
164 return true;
165 }
166
167 // Uninitialize the tests.
168 TEST_CLASS_CLEANUP(TestClassCleanup)
169 {
170 if (LxsstuVmMode())
171 {
172 PrivilegeState.reset();
173
174 LxsstuLaunchWsl(L"--unmount");
175 WaitForDiskReady();
176
177 try
178 {
179 LxsstuLaunchPowershellAndCaptureOutput(L"Dismount-Vhd -Path " TEST_MOUNT_DISK);
180 }
181 CATCH_LOG()
182
183 DeleteFileW(TEST_MOUNT_DISK);
184 DeleteFileW(TEST_MOUNT_VHD);
185 }
186
187 VERIFY_NO_THROW(LxsstuUninitialize(false));
188 return true;
189 }
190
191 TEST_METHOD_CLEANUP(MethodCleanup)
192 {
193 if (!LxsstuVmMode())
194 {
195 return true;
196 }
197
198 LxssLogKernelOutput();
199 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount"), (DWORD)0);
200 WaitForDiskReady();
201
202 return true;
203 }
204
205 // Attach a vhd, but don't mount it
206 WSL2_TEST_METHOD(TestBareMountVhd)
207 {
208 TestBareMountImpl(true);
209 }
210
211 // Mount one partition using --vhd and validate that options are correctly applied
212 WSL2_TEST_METHOD(TestMountOnePartitionVhd)
213 {
214 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
215
216 TestMountOnePartitionImpl(true);
217 }
218
219 // Mount two partitions using --vhd on the same disk
220 WSL2_TEST_METHOD(TestMountTwoPartitionsVhd)
221 {
222 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
223
224 TestMountTwoPartitionsImpl(true);
225 }
226
227 // Run a bare mount using --vhd and then mount a partition
228 WSL2_TEST_METHOD(TestAttachThenMountVhd)
229 {
230 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
231
232 TestAttachThenMountImpl(true);
233 }
234
235 // Mount the disk directly
236 WSL2_TEST_METHOD(TestMountWholeDiskVhd)
237 {
238 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
239
240 TestMountWholeDiskImpl(true);
241 }
242
243 // Test that mount state is deleted on shutdown (--vhd)
244 WSL2_TEST_METHOD(TestMountStateIsDeletedOnShutdownVhd)
245 {
246 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
247
248 TestMountStateIsDeletedOnShutdownImpl(true);
249 }
250
251 WSL2_TEST_METHOD(TestFilesystemDetectionWholeDisk)
252 {
253 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
254
255 TestFilesystemDetectionWholeDiskImpl(false);
256 }
257
258 WSL2_TEST_METHOD(TestFilesystemDetectionWholeDiskVhd)
259 {
260 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
261
262 TestFilesystemDetectionWholeDiskImpl(true);
263 }
264
265 WSL2_TEST_METHOD(TestMountTwoPartitionsWithDetection)
266 {
267 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
268
269 TestMountTwoPartitionsWithDetectionImpl(false);
270 }
271
272 WSL2_TEST_METHOD(TestMountTwoPartitionsWithDetectionVhd)
273 {
274 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
275
276 TestMountTwoPartitionsWithDetectionImpl(true);
277 }
278
279 WSL2_TEST_METHOD(TestFilesystemDetectionFail)
280 {
281 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
282
283 TestFilesystemDetectionFailImpl(false);
284 }
285
286 WSL2_TEST_METHOD(TestFilesystemDetectionFailVhd)
287 {
288 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
289
290 TestFilesystemDetectionFailImpl(true);
291 }
292
293 // Test specifying a mount name for a vhd
294 WSL2_TEST_METHOD(SpecifyMountName)
295 {
296 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
297 const auto mountCommand = L"--mount " + VhdDevice + L" --vhd --name " + TEST_MOUNT_NAME;
298
299 WslKeepAlive keepAlive;
300
301 // Create a MBR disk with 1 ext4 partition
302 FormatDisk({L"ext4"}, true);
303
304 // Mount it
305 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
306 auto disk = GetBlockDeviceInWsl();
307 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
308
309 // Validate that the mount succeeded
310 const std::wstring diskName(TEST_MOUNT_NAME);
311 auto mountTarget = L"/mnt/wsl/" + diskName;
312
313 ValidateMountPoint(disk + L"1", mountTarget);
314
315 ValidateDiskState({VhdDevice, {{1, {}, {}}}}, keepAlive);
316
317 // Unmount the disk
318 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + VhdDevice), (DWORD)0);
319 WaitForDiskReady();
320
321 // Validate that the mount folder was deleted
322 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -e " + mountTarget), (DWORD)1);
323
324 // Mount the same partition, but with a specific mount option
325 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1 --options \"data=ordered\""), (DWORD)0);
326
327 // Validate that the mount option was properly passed
328 disk = GetBlockDeviceInWsl();
329 ValidateMountPoint(disk + L"1", mountTarget, L"data=ordered");
330 ValidateDiskState({VhdDevice, {{1, {}, L"data=ordered"}}}, keepAlive);
331
332 // Let the VM timeout
333 WaitForVmTimeout(keepAlive);
334
335 // Validate that the disk is re-mounted in the same place
336 disk = GetBlockDeviceInWsl();
337 ValidateMountPoint(disk + L"1", mountTarget);
338
339 // Unmount the disk
340 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + VhdDevice), (DWORD)0);
341 WaitForDiskReady();
342 }
343
344 WSL2_TEST_METHOD(SpecifyInvalidMountName)
345 {
346 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
347
348 FormatDisk({L"ext4"}, true);
349
350 for (const auto* name : {L"\"\"", L".", L"..", L"foo/bar"})
351 {
352 const auto mountCommand = std::format(L"--mount {} --vhd --name {} --partition 1", VhdDevice, name);
353 const auto [output, error] = LxsstuLaunchWslAndCaptureOutput(mountCommand, -1);
354 VERIFY_ARE_EQUAL(
355 output,
356 FormatErrorMessage(
357 L"The mount name cannot be empty, '.', '..', or contain '/'. Please retry with a valid mount name.",
358 L"Wsl/Service/MountDisk/WSL_E_VM_MODE_INVALID_MOUNT_NAME"),
359 name);
360 VERIFY_ARE_EQUAL(error, L"", name);
361 }
362
363 const auto disk = GetBlockDeviceInWsl();
364 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
365 VERIFY_IS_FALSE(GetBlockDeviceMount(disk + L"1").has_value());
366 }
367
368 // Test ensuring that name collision detection works in --mount --name
369 WSL2_TEST_METHOD(SpecifyMountNameCollision)
370 {
371 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
372 const auto mountCommand = L"--mount " + VhdDevice + L" --vhd --name " + TEST_MOUNT_NAME;
373
374 WslKeepAlive keepAlive;
375
376 // Create a MBR disk with 1 ext4 partition and one fat partitions
377 FormatDisk({L"ext4", L"vfat"}, true);
378
379 // Attempt to mount both partitions with the same mount name; partition 2 should fail
380 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
381 VERIFY_ARE_NOT_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 2 --type vfat"), (DWORD)0);
382 const auto disk = GetBlockDeviceInWsl();
383 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
384
385 // Validate that the mount first mount did succeed
386 const std::wstring diskName(TEST_MOUNT_NAME);
387 ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + diskName, {}, L"ext4");
388
389 // Unmount the disk
390 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + VhdDevice), (DWORD)0);
391 WaitForDiskReady();
392 }
393
394 // Test that multiple partitions can be mounted with --name
395 WSL2_TEST_METHOD(SpecifyMountNameTwoPartitions)
396 {
397 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
398 const auto mountCommandOne = L"--mount " + VhdDevice + L" --vhd --name " + TEST_MOUNT_NAME + L"p1";
399 const auto mountCommandTwo = L"--mount " + VhdDevice + L" --vhd --name " + TEST_MOUNT_NAME + L"p2";
400
401 WslKeepAlive keepAlive;
402
403 // Create a MBR disk with 1 ext4 partition and one fat partitions
404 FormatDisk({L"ext4", L"vfat"}, true);
405
406 // Attempt to mount both partitions with the same mount name; partition 2 should fail
407 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommandOne + L" --partition 1"), (DWORD)0);
408 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommandTwo + L" --partition 2 --type vfat"), (DWORD)0);
409 const auto disk = GetBlockDeviceInWsl();
410 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
411
412 // Validate that the mount first mount did succeed
413 const std::wstring diskName(TEST_MOUNT_NAME);
414 ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + diskName + L"p1", {}, L"ext4");
415 ValidateMountPoint(disk + L"2", L"/mnt/wsl/" + diskName + L"p2", {}, L"vfat");
416 ValidateDiskState({VhdDevice, {{1, {}, {}}, {2, {L"vfat"}, {}}}}, keepAlive);
417
418 // Unmount the disk
419 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + VhdDevice), (DWORD)0);
420 WaitForDiskReady();
421 }
422
423 // Test relative mount/unmounting of a --vhd
424 WSL2_TEST_METHOD(RelativePathUnmount)
425 {
426 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
427 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " TEST_MOUNT_VHD L" --vhd --bare"), (DWORD)0);
428
429 const auto disk = GetBlockDeviceInWsl();
430 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
431
432 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " TEST_MOUNT_VHD), (DWORD)0);
433 }
434
435 // Test relative mount/unmounting of a --vhd that does not exist
436 WSL2_TEST_METHOD(RelativePathUnmountNoFileExists)
437 {
438 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
439 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " TEST_MOUNT_VHD L" --vhd --bare"), (DWORD)0);
440
441 const auto disk = GetBlockDeviceInWsl();
442 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
443
444 // Try unmounting a VHD not created and verify that it was not successful
445 VERIFY_ARE_NOT_EQUAL(LxsstuLaunchWsl(L"--unmount " TEST_UNMOUNT_VHD_DNE), (DWORD)0);
446 }
447
448 WSL2_TEST_METHOD(AbsolutePathVhdUnmount)
449 {
450 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
451 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " TEST_MOUNT_VHD L" --vhd --bare"), (DWORD)0);
452
453 const auto disk = GetBlockDeviceInWsl();
454 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
455
456 const auto absolutePath = std::filesystem::absolute(TEST_MOUNT_VHD);
457
458 // Validate that the vhd path doesn't start with '\\?'
459 VERIFY_IS_FALSE(absolutePath.wstring().starts_with(L"\\"));
460
461 // Validate the unmounting by absolute path is successful
462 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + absolutePath.wstring()), (DWORD)0);
463 }
464
465 WSL2_TEST_METHOD(AbsolutePathVhdUnmountAfterVMTimeout)
466 {
467 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
468
469 WslKeepAlive keepAlive;
470
471 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " TEST_MOUNT_VHD L" --vhd --bare"), (DWORD)0);
472
473 const auto disk = GetBlockDeviceInWsl();
474 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
475
476 WaitForVmTimeout(keepAlive);
477
478 const auto absolutePath = std::filesystem::absolute(TEST_MOUNT_VHD);
479
480 // Validate that the vhd path doesn't start with '\\?'
481 VERIFY_IS_FALSE(absolutePath.wstring().starts_with(L"\\"));
482
483 // Validate the unmounting by absolute path is successful
484 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + absolutePath.wstring()), (DWORD)0);
485 }
486
487 // A VHD whose path is a symbolic link is a legitimate, supported scenario: the link is
488 // followed and the real VHD is attached. Access is granted while impersonating the user,
489 // so the user can only ever attach a file they can already reach; there is no need to
490 // reject reparse points in the path.
491 WSL2_TEST_METHOD(MountVhdThroughSymlinkSucceeds)
492 {
493 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
494
495 const auto symlink = std::filesystem::absolute(L"TestVhdSymlink.vhd");
496 DeleteFileW(symlink.c_str());
497
498 const auto absoluteTarget = std::filesystem::absolute(TEST_MOUNT_VHD);
499
500 // Create a file symbolic link pointing at the real VHD.
501 VERIFY_IS_TRUE(CreateSymbolicLinkW(symlink.c_str(), absoluteTarget.c_str(), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE));
502
503 auto cleanup = wil::scope_exit([&]() { DeleteFileW(symlink.c_str()); });
504
505 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + symlink.wstring() + L" --vhd --bare"), (DWORD)0);
506
507 const auto disk = GetBlockDeviceInWsl();
508 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
509
510 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + symlink.wstring()), (DWORD)0);
511 }
512
513 // A symlinked VHD must still be restored after the VM is torn down on idle. Restore runs
514 // under the mounting user's identity (the disk-mount state is stored per-SID for the
515 // current boot), so the same access check applies and the symlinked VHD re-attaches.
516 WSL2_TEST_METHOD(MountVhdThroughSymlinkSurvivesVmTimeout)
517 {
518 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
519
520 const auto symlink = std::filesystem::absolute(L"TestVhdSymlinkRestore.vhd");
521 DeleteFileW(symlink.c_str());
522
523 const auto absoluteTarget = std::filesystem::absolute(TEST_MOUNT_VHD);
524
525 VERIFY_IS_TRUE(CreateSymbolicLinkW(symlink.c_str(), absoluteTarget.c_str(), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE));
526
527 auto cleanup = wil::scope_exit([&]() { DeleteFileW(symlink.c_str()); });
528
529 WslKeepAlive keepAlive;
530
531 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + symlink.wstring() + L" --vhd --bare"), (DWORD)0);
532
533 auto disk = GetBlockDeviceInWsl();
534 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
535
536 WaitForVmTimeout(keepAlive);
537
538 // Recreating the VM restores the persisted disk mount; the symlinked VHD must re-attach. The
539 // block device name is not guaranteed to be stable across the VM teardown, so re-query it.
540 disk = GetBlockDeviceInWsl();
541 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
542
543 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + symlink.wstring()), (DWORD)0);
544 }
545
546 // Attach a disk, but don't mount it
547 WSL2_TEST_METHOD(TestBareMount)
548 {
549 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
550
551 TestBareMountImpl(false);
552 }
553
554 // Validate that attached disks that were offline when attached
555 // are still offline when detached
556 WSL2_TEST_METHOD(TestOfflineDiskStaysOffline)
557 {
558 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
559 WslKeepAlive keepAlive;
560
561 auto diskHandle = wsl::windows::common::disk::OpenDevice(DiskDevice.c_str(), GENERIC_ALL, c_diskOpenTimeoutMs);
562 wsl::windows::common::disk::SetOnline(diskHandle.get(), false);
563 diskHandle.reset();
564
565 ValidateOffline(true);
566 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --bare"), (DWORD)0);
567
568 auto disk = GetBlockDeviceInWsl();
569 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
570
571 ValidateDiskState({DiskDevice, {}}, keepAlive);
572
573 disk = GetBlockDeviceInWsl();
574 VERIFY_IS_FALSE(GetBlockDeviceMount(disk).has_value());
575
576 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
577
578 ValidateOffline(true);
579 diskHandle = wsl::windows::common::disk::OpenDevice(DiskDevice.c_str(), GENERIC_ALL, c_diskOpenTimeoutMs);
580 wsl::windows::common::disk::SetOnline(diskHandle.get(), true);
581 diskHandle.reset();
582
583 ValidateOffline(false);
584 }
585
586 // Mount one partition and validate that options are correctly applied
587 WSL2_TEST_METHOD(TestMountOnePartition)
588 {
589 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
590
591 TestMountOnePartitionImpl(false);
592 }
593
594 // Mount two partitions on the same disk
595 WSL2_TEST_METHOD(TestMountTwoPartitions)
596 {
597 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
598
599 TestMountTwoPartitionsImpl(false);
600 }
601
602 // Mount a fat partition
603 WSL2_TEST_METHOD(TestMountFatPartition)
604 {
605 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
606 WslKeepAlive keepAlive;
607
608 // Create a MBR disk with 1 ntfs partition
609 FormatDisk({L"vfat"}, false);
610
611 // Mount it
612 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1" + L" --type vfat"), (DWORD)0);
613
614 const auto disk = GetBlockDeviceInWsl();
615 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
616
617 // Validate that the mount succeeded
618 std::wstring trimmedDiskName(DiskDevice);
619 Trim(trimmedDiskName);
620 auto mountTarget = L"/mnt/wsl/" + trimmedDiskName + L"p1";
621 ValidateMountPoint(disk + L"1", mountTarget, {}, L"vfat");
622 ValidateDiskState({DiskDevice, {{1, {L"vfat"}, {}}}}, keepAlive);
623
624 // Unmount the disk
625 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
626 WaitForDiskReady();
627 ValidateOffline(false);
628 }
629
630 // Mount the disk directly
631 WSL2_TEST_METHOD(TestMountWholeDisk)
632 {
633 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
634
635 TestMountWholeDiskImpl(false);
636 }
637
638 WSL2_TEST_METHOD(TestMountStateIsDeletedOnShutdown)
639 {
640 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
641
642 TestMountStateIsDeletedOnShutdownImpl(false);
643 }
644
645 // Validate that a failure to mount a disk isn't fatal
646 WSL2_TEST_METHOD(TestMountFailuresArentFatal)
647 {
648 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
649 WslKeepAlive keepAlive;
650
651 // Create a MBR disk with 1 ext4 partition
652 FormatDisk({L"ext4"}, false);
653
654 // Mount it
655 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --type ext4"), (DWORD)0);
656 auto disk = GetBlockDeviceInWsl();
657 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
658
659 ValidateDiskState({DiskDevice, {{1, {L"ext4"}, {}}}}, keepAlive);
660
661 // Check that the disk is still mounted properly (ValidateDiskState restarts the VM)
662 disk = GetBlockDeviceInWsl();
663 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
664 std::wstring trimmedDiskName(DiskDevice);
665 Trim(trimmedDiskName);
666 ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
667
668 // Wait for vm timeout
669 WaitForVmTimeout(keepAlive);
670
671 // Voluntarily set a wrong filesystem in the saved state
672 auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
673 auto subKeys = wsl::windows::common::registry::EnumKeys(key.get(), KEY_ALL_ACCESS);
674 VERIFY_ARE_EQUAL(subKeys.size(), 1);
675
676 wsl::windows::common::registry::WriteString(subKeys.begin()->second.get(), L"1", L"Type", L"badfs");
677 keepAlive.Set();
678
679 // The disk should be present
680 disk = GetBlockDeviceInWsl();
681 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
682
683 // But not mounted
684 ValidateMountPoint(disk + L"1", {});
685
686 // Now put a bad disk path, so that the disk fails to attach
687 WaitForVmTimeout(keepAlive);
688 key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
689 subKeys = wsl::windows::common::registry::EnumKeys(key.get(), KEY_ALL_ACCESS);
690 VERIFY_ARE_EQUAL(subKeys.size(), 1);
691 wsl::windows::common::registry::WriteString(subKeys.begin()->second.get(), nullptr, L"Disk", L"BadDisk");
692 keepAlive.Reset();
693
694 // Restart the service
695 RestartWslService();
696
697 // Run a dummy command to trigger a VM start
698 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"echo foo"), (DWORD)0);
699
700 // The disk should still be online, because it failed to attach
701 ValidateOffline(false);
702 }
703
704 // wsl --unmount should succeed even when no disk is mounted
705 WSL2_TEST_METHOD(UnmountWithoutAnyDisk)
706 {
707 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
708 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount"), (DWORD)0);
709 }
710
711 // Mount two partitions on the same disk and validate that the mount is restored
712 WSL2_TEST_METHOD(TestMountTwoPartitionsAfterTimeout)
713 {
714 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
715 WslKeepAlive keepAlive;
716
717 // Create a MBR disk with 1 ext4 partition and one fat partitions
718 FormatDisk({L"ext4", L"vfat"}, false);
719
720 // Mount then both
721 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1"), (DWORD)0);
722 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 2 --type vfat"), (DWORD)0);
723
724 ValidateDiskState({DiskDevice, {{1, {}, {}}, {2, {L"vfat"}, {}}}}, keepAlive);
725
726 // Validate that our disk is still mounted
727 const auto disk = GetBlockDeviceInWsl();
728 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
729
730 // Validate that the mount succeeded
731 std::wstring trimmedDiskName(DiskDevice);
732 Trim(trimmedDiskName);
733
734 ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
735 ValidateMountPoint(disk + L"2", L"/mnt/wsl/" + trimmedDiskName + L"p2", {}, L"vfat");
736
737 // Unmount the disk
738 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
739 }
740
741 // Validate that non-admin can remount saved disks
742 WSL2_TEST_METHOD(TestMount1PartitionAndRemountAsNonAdmin)
743 {
744 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
745 WslKeepAlive keepAlive;
746
747 FormatDisk({L"ext4"}, false);
748 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1"), (DWORD)0);
749
750 ValidateDiskState({DiskDevice, {{1, {}, {}}}}, keepAlive);
751 auto disk = GetBlockDeviceInWsl();
752 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
753
754 // Let the UVM timeout
755 WaitForVmTimeout(keepAlive);
756
757 // Restart wsl as a non-elevated user
758 const auto nonElevatedToken = GetNonElevatedToken();
759
760 // Launch wsl non-elevated
761 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"echo dummy", nullptr, nullptr, nullptr, nonElevatedToken.get()), (DWORD)0);
762 keepAlive.Set();
763
764 // Validate that our disk is still attached
765 disk = GetBlockDeviceInWsl();
766 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
767
768 // Validate that the mount succeeded
769 std::wstring trimmedDiskName(DiskDevice);
770 Trim(trimmedDiskName);
771
772 ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
773
774 // Unmount the disk
775 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
776 }
777
778 // Run a bare mount and then mount a partition
779 WSL2_TEST_METHOD(TestAttachThenMount)
780 {
781 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
782
783 TestAttachThenMountImpl(false);
784 }
785
786 // Validate that unmounting works when the UVM is not running
787 WSL2_TEST_METHOD(TestMountOnePartitionAfterTimeout)
788 {
789 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
790 WslKeepAlive keepAlive;
791
792 // Create a MBR disk with 1 ext4 partition
793 FormatDisk({L"ext4"}, false);
794
795 // Mount it
796 ValidateOffline(false);
797 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1"), (DWORD)0);
798 const auto disk = GetBlockDeviceInWsl();
799 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
800 ValidateOffline(true);
801
802 // Wait for vm timeout
803 WaitForVmTimeout(keepAlive);
804
805 // Unmount the disk
806 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
807
808 // The UVM shouldn't be running
809 VERIFY_IS_FALSE(GetVmmempPid().has_value());
810
811 // No state should be left in registry
812 const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
813 VERIFY_ARE_EQUAL(wsl::windows::common::registry::EnumKeys(key.get(), KEY_READ).size(), 0);
814 }
815
816 // Validate that the proper mount error is returned if the filesystem type is wrong
817 WSL2_TEST_METHOD(TestMountPartitionWithWrongFs)
818 {
819 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
820 WslKeepAlive keepAlive;
821
822 // Create a MBR disk with 1 ext4 partition
823 FormatDisk({L"ext4"}, false);
824
825 // Mount it
826 wsl::windows::common::SvcComm service;
827 VERIFY_ARE_EQUAL(service.AttachDisk(DiskDevice.c_str(), LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH), S_OK);
828
829 const auto result = service.MountDisk(DiskDevice.c_str(), LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH, 1, nullptr, L"vfat", nullptr);
830
831 VERIFY_ARE_EQUAL(result.Result, -22); //-EINVAL
832 VERIFY_ARE_EQUAL(result.Step, 3); // LxMiniInitMountStepMount
833
834 // Unmount the disk
835 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
836 }
837
838 // Validate that the proper mount error is returned if the partition can't be found
839 WSL2_TEST_METHOD(TestMountPartitionWithBadPartitionIndex)
840 {
841 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
842 WslKeepAlive keepAlive;
843
844 // Create a MBR disk with 1 fat partition
845 FormatDisk({L"vfat"}, false);
846
847 // Try to mount a partition that doesn't exist
848 wsl::windows::common::SvcComm service;
849 VERIFY_ARE_EQUAL(service.AttachDisk(DiskDevice.c_str(), LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH), S_OK);
850
851 const auto result = service.MountDisk(DiskDevice.c_str(), LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH, 2, nullptr, L"vfat", nullptr);
852
853 VERIFY_ARE_EQUAL(result.Result, -2); // -ENOENT
854 VERIFY_ARE_EQUAL(result.Step, 2); // LxMiniInitMountStepFindPartition
855
856 // Unmount the disk
857 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
858 }
859
860 // Validate that disk aren't detached if in use by other processes
861 WSL2_TEST_METHOD(TestDeviceCantBeMountedIfInUse)
862 {
863 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
864 {
865 // Format-Volume fails without automount enabled
866 SetAutoMountPolicy AutoMountPolicy{true};
867
868 // Reset the disk
869 LxsstuLaunchPowershellAndCaptureOutput(L"Clear-Disk -confirm:$false -RemoveData -Number " + std::to_wstring(DiskNumber));
870
871 LxsstuLaunchPowershellAndCaptureOutput(L"Initialize-Disk -confirm:$false -Number " + std::to_wstring(DiskNumber));
872
873 // Create one fat partition
874 LxsstuLaunchPowershellAndCaptureOutput(
875 L"New-Partition -DiskNumber " + std::to_wstring(DiskNumber) +
876 L" -UseMaximumSize \
877 | Format-Volume -FileSystem FAT");
878 }
879
880 // Mount it in Windows
881 auto [letter, _] = LxsstuLaunchPowershellAndCaptureOutput(
882 L"Set-Partition -DiskNumber " + std::to_wstring(DiskNumber) + L" -PartitionNumber 1" + L" -NewDriveLetter Y");
883
884 // Open a file under that partition
885 wil::unique_handle file(CreateFile(L"Y:\\foo.txt", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr));
886
887 const char* fileContent = "LOW!";
888 THROW_LAST_ERROR_IF(!WriteFile(file.get(), fileContent, static_cast<DWORD>(strlen(fileContent)), nullptr, nullptr));
889
890 // Validate that the disk can't be mounted (TODO: Find a way to validate the failure reason)
891 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --type vfat"), (DWORD)-1);
892
893 // Close the file and mount it
894 file.reset();
895 WaitForDiskReady();
896 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --type vfat"), (DWORD)0);
897
898 // Validate that the file content is correct
899 const auto disk = GetBlockDeviceInWsl();
900 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
901
902 // Validate that the mount succeeded
903 std::wstring trimmedDiskName(DiskDevice);
904 Trim(trimmedDiskName);
905
906 ValidateMountPoint(disk + L"1", {L"/mnt/wsl/" + trimmedDiskName + L"p1"}, {}, L"vfat");
907 auto [output, __] = LxsstuLaunchWslAndCaptureOutput(L"cat /mnt/wsl/" + trimmedDiskName + L"p1/foo.txt");
908
909 VERIFY_ARE_EQUAL(output, wsl::shared::string::MultiByteToWide(fileContent));
910 }
911
912 WSL2_TEST_METHOD(TestMountWithFlagOption)
913 {
914 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
915 WslKeepAlive keepAlive;
916
917 // Create a MBR disk with 1 ext4 partition
918 FormatDisk({L"ext4"}, false);
919
920 // Mount it
921 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --options sync"), (DWORD)0);
922 auto disk = GetBlockDeviceInWsl();
923 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
924
925 // Validate that the mount succeeded
926 std::wstring trimmedDiskName(DiskDevice);
927 Trim(trimmedDiskName);
928 auto mountTarget = L"/mnt/wsl/" + trimmedDiskName + L"p1";
929
930 ValidateMountPoint(disk + L"1", mountTarget, L"sync");
931 ValidateDiskState({DiskDevice, {{1, {}, L"sync"}}}, keepAlive);
932
933 // Unmount the disk
934 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
935 WaitForDiskReady();
936
937 // Mount the same partition, but with both a flag and a non-flag option
938 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + DiskDevice + L" --partition 1 --options data=ordered,sync"), (DWORD)0);
939
940 // Validate that the mount option was properly passed
941 disk = GetBlockDeviceInWsl();
942
943 ValidateMountPoint(disk + L"1", mountTarget, L"ync,relatime,data=ordered");
944
945 // Note: relatime is set by default
946 ValidateDiskState({DiskDevice, {{1, {}, L"data=ordered,sync"}}}, keepAlive);
947
948 // Unmount the disk
949 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + DiskDevice), (DWORD)0);
950 WaitForDiskReady();
951 }
952
953 WSL1_TEST_METHOD(TestAttachFailsWithoutWsl2Distro)
954 {
955 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
956 // Attempt to mount a disk with only a WSL1 distro
957 wsl::windows::common::SvcComm service;
958
959 if (std::ranges::any_of(service.EnumerateDistributions(), [](const auto& e) { return e.Version != 1; }))
960 {
961 LogSkipped("Skipping test because a WSL2 distro is present");
962 return;
963 }
964
965 VERIFY_ARE_EQUAL(service.AttachDisk(L"Dummy", LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH), WSL_E_WSL2_NEEDED);
966 }
967
968 WSL2_TEST_METHOD(VhdWithSpaces)
969 {
970 SKIP_UNSUPPORTED_ARM64_MOUNT_TEST();
971 LxsstuLaunchPowershellAndCaptureOutput(L"New-Vhd -Path 'vhd with spaces.vhdx' -SizeBytes 20MB");
972
973 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() {
974 WslShutdown();
975
976 if (!DeleteFile(L"vhd with spaces.vhdx"))
977 {
978 LogInfo("Failed to delete vhd, %i", GetLastError());
979 };
980 });
981
982 WslKeepAlive keepAlive;
983
984 // Validate that relative path mounting and unmounting works
985 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount \"vhd with spaces.vhdx\" --bare --vhd"), (DWORD)0);
986 auto disk = GetBlockDeviceInWsl();
987 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
988
989 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount \"vhd with spaces.vhdx\""), (DWORD)0);
990
991 // Validate that absolute path mounting and unmounting works
992 const std::wstring fullPath = wsl::windows::common::filesystem::GetFullPath(L"vhd with spaces.vhdx");
993 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount \"" + fullPath + L"\" --bare --vhd"), (DWORD)0);
994 disk = GetBlockDeviceInWsl();
995 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
996
997 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount \"" + fullPath + L"\""), (DWORD)0);
998 }
999
1000 void WaitForDiskReady() const
1001 {
1002 const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(30);
1003 while (timeout > std::chrono::steady_clock::now())
1004 {
1005 try
1006 {
1007 auto disk = wsl::windows::common::disk::OpenDevice(DiskDevice.c_str(), GENERIC_READ, c_diskOpenTimeoutMs);
1008 wsl::windows::common::disk::ValidateDiskVolumesAreReady(disk.get());
1009 return;
1010 }
1011 catch (...)
1012 {
1013 auto error = std::system_category().message(wil::ResultFromCaughtException());
1014 LogInfo("Caught '%S' while waiting for disk", error.c_str());
1015 std::this_thread::sleep_for(std::chrono::seconds(1));
1016 continue;
1017 }
1018 }
1019
1020 VERIFY_FAIL(L"Timeout waiting for disk");
1021 }
1022
1023 void ValidateOffline(bool offline) const
1024 {
1025 const auto disk = wsl::windows::common::disk::OpenDevice(DiskDevice.c_str(), FILE_READ_ATTRIBUTES, c_diskOpenTimeoutMs);
1026 VERIFY_ARE_EQUAL(!offline, wsl::windows::common::disk::IsDiskOnline(disk.get()));
1027 }
1028
1029 static std::wstring GetBlockDeviceInWsl()
1030 {
1031 // Wait for the disk to be attached
1032 const auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(30);
1033
1034 bool done = false;
1035 while (true)
1036 {
1037 for (wchar_t name = 'a'; name < 'z'; name++)
1038 {
1039 std::wstring cmd = L"-u root blockdev --getsize64 /dev/sd";
1040 cmd += name;
1041
1042 std::wstring out;
1043 try
1044 {
1045 out = LxsstuLaunchWslAndCaptureOutput(cmd.data()).first;
1046 }
1047 CATCH_LOG()
1048
1049 Trim(out);
1050
1051 // Disk size is 20MB, so 20 * 1024 * 1024 bytes
1052 if (out == L"20971520")
1053 {
1054 return std::wstring(L"/dev/sd") + name;
1055 }
1056 }
1057
1058 if (done)
1059 {
1060 break;
1061 }
1062
1063 done = std::chrono::steady_clock::now() > timeout;
1064 }
1065
1066 VERIFY_FAIL(L"Failed to find the block device in WSL");
1067
1068 // Unreachable.
1069 return {};
1070 }
1071
1072 static bool IsBlockDevicePresent(const std::wstring& Device)
1073 {
1074 const auto Cmd = L"test -e " + Device;
1075 return LxsstuLaunchWsl(Cmd.data()) == 0;
1076 }
1077
1078 static std::optional<std::vector<std::wstring>> GetBlockDeviceMount(const std::wstring& device)
1079 {
1080 const std::wstring cmd(L"cat /proc/mounts");
1081 auto [out, _] = LxsstuLaunchWslAndCaptureOutput(cmd.data());
1082
1083 LogInfo("/proc/mounts content: '%ls'", out.c_str());
1084 std::wistringstream output(out);
1085 std::wstring line;
1086
1087 while (std::getline(output, line))
1088 {
1089 if (wcsstr(line.data(), device.data()) == line.data())
1090 {
1091 return LxssSplitString(line);
1092 }
1093 }
1094
1095 return {};
1096 }
1097
1098 void ValidateDiskState(const ExpectedDiskState& State, WslKeepAlive& KeepAlive)
1099 {
1100 WaitForVmTimeout(KeepAlive);
1101 const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
1102 const auto subKeys = wsl::windows::common::registry::EnumKeys(key.get(), KEY_READ);
1103
1104 VERIFY_ARE_EQUAL(subKeys.size(), 1);
1105
1106 const auto& diskKey = subKeys.begin()->second;
1107
1108 auto read = [](const auto& Key, LPCWSTR Name) -> std::optional<std::wstring> {
1109 try
1110 {
1111 return wsl::windows::common::registry::ReadString(Key.get(), nullptr, Name);
1112 }
1113 catch (...)
1114 {
1115 return {};
1116 }
1117 };
1118
1119 VERIFY_ARE_EQUAL(read(diskKey, L"Disk").value(), State.Path);
1120 VERIFY_ARE_EQUAL(wsl::windows::common::registry::EnumKeys(diskKey.get(), KEY_READ).size(), State.Mounts.size());
1121
1122 for (const auto& e : State.Mounts)
1123 {
1124 auto keyName = std::to_wstring(e.PartitionIndex);
1125
1126 auto mountKey = wsl::windows::common::registry::OpenKey(diskKey.get(), keyName.c_str(), KEY_READ);
1127
1128 VERIFY_ARE_EQUAL(read(mountKey, L"Options"), e.Options);
1129 VERIFY_ARE_EQUAL(read(mountKey, L"Type"), e.Type);
1130 }
1131
1132 KeepAlive.Set();
1133 }
1134
1135 void WaitForVmTimeout(WslKeepAlive& KeepAlive)
1136 {
1137 const auto pid = GetVmmempPid();
1138 VERIFY_IS_TRUE(pid.has_value());
1139 KeepAlive.Reset();
1140 const std::wstring cmd = std::wstring(L"-t ") + std::wstring(LXSS_DISTRO_NAME_TEST_L);
1141
1142 // Terminate the distro to make the vm timeout faster
1143 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(cmd.c_str()), (DWORD)0);
1144
1145 const wil::unique_process_handle process(OpenProcess(SYNCHRONIZE, false, pid.value()));
1146 VERIFY_IS_NOT_NULL(process.get());
1147
1148 VERIFY_ARE_EQUAL((DWORD)WAIT_OBJECT_0, WaitForSingleObject(process.get(), INFINITE));
1149 }
1150
1151 static std::optional<DWORD> GetVmmempPid()
1152 {
1153 for (auto pid : wsl::windows::common::wslutil::ListRunningProcesses())
1154 {
1155 wil::unique_process_handle process(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid));
1156 if (!process)
1157 {
1158 continue;
1159 }
1160
1161 std::wstring imageName(MAX_PATH, '\0');
1162 const DWORD length = GetProcessImageFileName(process.get(), imageName.data(), (DWORD)imageName.size() + 1);
1163 if (length == 0)
1164 {
1165 continue;
1166 }
1167
1168 imageName.resize(length);
1169 if (imageName == L"vmmemWSL" || (!wsl::windows::common::helpers::IsWindows11OrAbove() && imageName == L"vmmem"))
1170 {
1171 return pid;
1172 }
1173 }
1174
1175 return {}; // Unreachable
1176 }
1177
1178 void FormatDisk(const std::vector<std::wstring>& Partitions, bool isVhdTest)
1179 {
1180 WaitForDiskReady();
1181 const auto deviceName = (isVhdTest) ? VhdDevice : DiskDevice;
1182 if (isVhdTest)
1183 {
1184 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + deviceName + L" --vhd --bare"), (DWORD)0);
1185 }
1186 else
1187 {
1188 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--mount " + deviceName + L" --bare"), (DWORD)0);
1189 }
1190
1191 const auto disk = GetBlockDeviceInWsl();
1192 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1193
1194 // Create a partition table
1195 std::wstringstream Cmd;
1196 Cmd << "bash -c \"(";
1197 Cmd << L"echo -e o\n"; // Create a new partition table
1198
1199 for (size_t i = 0; i < Partitions.size(); i++)
1200 {
1201 Cmd << L"echo -e n\n"; // Add a new partition
1202 Cmd << L"echo -e p\n"; // Primary partition
1203 Cmd << L"echo -e " << (i + 1) << L"\n"; // Partition number
1204 Cmd << L"echo -e\n"; // First sector (Accept default)
1205 Cmd << L"echo " << 2049 + (i + 1) * 4096 << L"\n"; // Last sector
1206 }
1207
1208 Cmd << L"echo -e w\n"; // Write changes
1209 Cmd << L") | fdisk " + disk + L"\"";
1210
1211 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(Cmd.str()), (DWORD)0);
1212
1213 for (size_t i = 1; i <= Partitions.size(); i++)
1214 {
1215 auto partition = disk + std::to_wstring(i);
1216
1217 // mkfs.ext4 interactively asks for confirmation, -F disables that behavior
1218 const auto forceFlag = Partitions[i - 1] == L"ext4" ? L" -F " : L"";
1219 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"mkfs." + Partitions[i - 1] + forceFlag + L" " + partition), (DWORD)0);
1220 }
1221 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1222
1223 if (!isVhdTest)
1224 {
1225 WaitForDiskReady();
1226 }
1227 }
1228
1229 void ValidateMountPoint(
1230 const std::wstring& BlockDevice,
1231 const std::optional<std::wstring>& Mountpoint,
1232 const std::optional<std::wstring>& ExpectedOption = {},
1233 const std::optional<std::wstring>& ExpectedType = {})
1234 {
1235 auto mount = GetBlockDeviceMount(BlockDevice);
1236 if (Mountpoint.has_value())
1237 {
1238 VERIFY_IS_TRUE(mount.has_value());
1239 }
1240 else
1241 {
1242 VERIFY_IS_FALSE(mount.has_value());
1243 return;
1244 }
1245
1246 VERIFY_ARE_EQUAL(mount.value()[1], Mountpoint.value());
1247 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -d " + Mountpoint.value()), (DWORD)0);
1248
1249 // If specified, validate that ExpectedOption is in the mount options
1250 // (We don't want to do a direct compare because the kernel might add some like rw, ...)
1251 if (ExpectedOption.has_value())
1252 {
1253 VERIFY_ARE_NOT_EQUAL(mount.value()[3].find(ExpectedOption.value()), std::string::npos);
1254 }
1255
1256 // If specified, validate the filesystem
1257 if (ExpectedType.has_value())
1258 {
1259 VERIFY_ARE_EQUAL(mount.value()[2], ExpectedType.value());
1260 }
1261 }
1262
1263 void TestBareMountImpl(bool isVhd)
1264 {
1265 WslKeepAlive keepAlive;
1266
1267 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1268 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1269
1270 if (isVhd)
1271 {
1272 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
1273 }
1274 else
1275 {
1276 ValidateOffline(false);
1277 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
1278 ValidateOffline(true);
1279 }
1280
1281 const auto disk = GetBlockDeviceInWsl();
1282 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1283
1284 VERIFY_IS_FALSE(GetBlockDeviceMount(disk).has_value());
1285
1286 ValidateDiskState({deviceName, {}}, keepAlive);
1287
1288 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1289
1290 if (!isVhd)
1291 {
1292 ValidateOffline(false);
1293 }
1294 }
1295
1296 void TestMountOnePartitionImpl(bool isVhd)
1297 {
1298 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1299 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1300
1301 WslKeepAlive keepAlive;
1302
1303 // Create a MBR disk with 1 ext4 partition
1304 FormatDisk({L"ext4"}, isVhd);
1305
1306 // Mount it
1307 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
1308 auto disk = GetBlockDeviceInWsl();
1309 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1310
1311 // Validate that the mount succeeded
1312 std::wstring trimmedDiskName(deviceName);
1313 Trim(trimmedDiskName);
1314 auto mountTarget = L"/mnt/wsl/" + trimmedDiskName + L"p1";
1315
1316 ValidateMountPoint(disk + L"1", mountTarget);
1317
1318 ValidateDiskState({deviceName, {{1, {}, {}}}}, keepAlive);
1319
1320 // Unmount the disk
1321 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1322 WaitForDiskReady();
1323
1324 if (!isVhd)
1325 {
1326 ValidateOffline(false);
1327 }
1328
1329 // Validate that the mount folder was deleted
1330 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -e " + mountTarget), (DWORD)1);
1331
1332 // Mount the same partition, but with a specific mount option
1333 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1 --options \"data=ordered\""), (DWORD)0);
1334
1335 // Validate that the mount option was properly passed
1336 disk = GetBlockDeviceInWsl();
1337 ValidateMountPoint(disk + L"1", mountTarget, L"data=ordered");
1338 ValidateDiskState({deviceName, {{1, {}, L"data=ordered"}}}, keepAlive);
1339
1340 // Unmount the disk
1341 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1342 WaitForDiskReady();
1343
1344 if (!isVhd)
1345 {
1346 ValidateOffline(false);
1347 }
1348 }
1349
1350 void TestMountTwoPartitionsImpl(bool isVhd)
1351 {
1352 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1353 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1354
1355 WslKeepAlive keepAlive;
1356
1357 // Create a MBR disk with 1 ext4 partition and one fat partitions
1358 FormatDisk({L"ext4", L"vfat"}, isVhd);
1359
1360 // Mount then both
1361 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
1362 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 2 --type vfat"), (DWORD)0);
1363 const auto disk = GetBlockDeviceInWsl();
1364 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1365
1366 // Validate that the mount succeeded
1367 std::wstring trimmedDiskName(deviceName);
1368 Trim(trimmedDiskName);
1369
1370 ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
1371 ValidateMountPoint(disk + L"2", L"/mnt/wsl/" + trimmedDiskName + L"p2", {}, L"vfat");
1372 ValidateDiskState({deviceName, {{1, {}, {}}, {2, {L"vfat"}, {}}}}, keepAlive);
1373
1374 // Unmount the disk
1375 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1376 WaitForDiskReady();
1377
1378 if (!isVhd)
1379 {
1380 ValidateOffline(false);
1381 }
1382 }
1383
1384 void TestAttachThenMountImpl(bool isVhd)
1385 {
1386 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1387 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1388
1389 WslKeepAlive keepAlive;
1390
1391 FormatDisk({L"ext4"}, isVhd);
1392
1393 // Mount then both
1394 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
1395 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
1396
1397 ValidateDiskState({deviceName, {{1, {}, {}}}}, keepAlive);
1398
1399 // Validate that our disk is still mounted
1400 const auto disk = GetBlockDeviceInWsl();
1401 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1402
1403 // Validate that the mount succeeded
1404 std::wstring trimmedDiskName(deviceName);
1405 Trim(trimmedDiskName);
1406
1407 ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, {});
1408
1409 // Unmount the disk
1410 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1411 }
1412
1413 void TestMountWholeDiskImpl(bool isVhd)
1414 {
1415 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1416 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1417
1418 WslKeepAlive keepAlive;
1419
1420 // Format the volume as ext4
1421 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
1422 const auto disk = GetBlockDeviceInWsl();
1423 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1424 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"mkfs.ext4 -F " + disk), (DWORD)0);
1425
1426 // Then mount it
1427 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --type ext4"), (DWORD)0);
1428
1429 // Validate that the mount succeeded
1430 std::wstring trimmedDiskName(deviceName);
1431 Trim(trimmedDiskName);
1432 auto mountTarget = L"/mnt/wsl/" + trimmedDiskName;
1433 ValidateMountPoint(disk, mountTarget, {}, L"ext4");
1434 ValidateDiskState({deviceName, {{0, {L"ext4"}, {}}}}, keepAlive);
1435
1436 // Unmount the disk
1437 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1438
1439 if (!isVhd)
1440 {
1441 WaitForDiskReady();
1442 ValidateOffline(false);
1443 }
1444 }
1445
1446 void TestMountStateIsDeletedOnShutdownImpl(bool isVhd)
1447 {
1448 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1449 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1450
1451 WslKeepAlive keepAlive;
1452
1453 // Create a MBR disk with 1 ext4 partition
1454 FormatDisk({L"ext4"}, isVhd);
1455
1456 // Mount it
1457 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1 --type ext4"), (DWORD)0);
1458 const auto disk = GetBlockDeviceInWsl();
1459 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1460
1461 ValidateDiskState({deviceName, {{1, {L"ext4"}, {}}}}, keepAlive);
1462 keepAlive.Reset();
1463
1464 // wsl --shutdown clears any disk state
1465 WslShutdown();
1466
1467 if (!isVhd)
1468 {
1469 ValidateOffline(false);
1470 }
1471
1472 // No state should be left in registry
1473 const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(User->User.Sid);
1474 VERIFY_ARE_EQUAL(wsl::windows::common::registry::EnumKeys(key.get(), KEY_READ).size(), 0);
1475 }
1476
1477 void TestFilesystemDetectionWholeDiskImpl(bool isVhd)
1478 {
1479 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1480 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1481
1482 WslKeepAlive keepAlive;
1483
1484 // Format the volume as fat
1485 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
1486 const auto disk = GetBlockDeviceInWsl();
1487 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1488 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"mkfs.fat --mbr=no -I " + disk), (DWORD)0);
1489
1490 // Then mount it. The filesystem should be autodetected
1491 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand), (DWORD)0);
1492
1493 // Validate that the mount succeeded
1494 std::wstring trimmedDiskName(deviceName);
1495 Trim(trimmedDiskName);
1496 auto mountTarget = L"/mnt/wsl/" + trimmedDiskName;
1497 ValidateMountPoint(disk, mountTarget, {}, L"vfat");
1498 ValidateDiskState({deviceName, {{0, {}, {}}}}, keepAlive);
1499
1500 // Unmount the disk
1501 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1502
1503 if (!isVhd)
1504 {
1505 WaitForDiskReady();
1506 ValidateOffline(false);
1507 }
1508 }
1509
1510 void TestMountTwoPartitionsWithDetectionImpl(bool isVhd)
1511 {
1512 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1513 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1514
1515 WslKeepAlive keepAlive;
1516
1517 // Create a MBR disk with 1 ext4 partition and one fat partitions
1518 FormatDisk({L"ext4", L"vfat"}, isVhd);
1519
1520 // Mount then both (filesystems should be detected).
1521 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 1"), (DWORD)0);
1522 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --partition 2"), (DWORD)0);
1523 const auto disk = GetBlockDeviceInWsl();
1524 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1525
1526 // Validate that the mount succeeded
1527 std::wstring trimmedDiskName(deviceName);
1528 Trim(trimmedDiskName);
1529
1530 ValidateMountPoint(disk + L"1", L"/mnt/wsl/" + trimmedDiskName + L"p1", {}, L"ext4");
1531 ValidateMountPoint(disk + L"2", L"/mnt/wsl/" + trimmedDiskName + L"p2", {}, L"vfat");
1532 ValidateDiskState({deviceName, {{1, {}, {}}, {2, {}, {}}}}, keepAlive);
1533
1534 // Unmount the disk
1535 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1536
1537 if (!isVhd)
1538 {
1539 WaitForDiskReady();
1540 ValidateOffline(false);
1541 }
1542 }
1543
1544 void TestFilesystemDetectionFailImpl(bool isVhd)
1545 {
1546 const auto deviceName = (isVhd) ? VhdDevice : DiskDevice;
1547 const auto mountCommand = (isVhd) ? (L"--mount " + deviceName + L" --vhd") : (L"--mount " + deviceName);
1548
1549 WslKeepAlive keepAlive;
1550
1551 // Write zeroes in the disk
1552 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(mountCommand + L" --bare"), (DWORD)0);
1553 const auto disk = GetBlockDeviceInWsl();
1554 VERIFY_IS_TRUE(IsBlockDevicePresent(disk));
1555 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"dd bs=4M count=1 if=/dev/zero of=" + disk), (DWORD)0);
1556
1557 // Then try to mount it
1558 wsl::windows::common::SvcComm service;
1559 const auto result = service.MountDisk(
1560 deviceName.c_str(), isVhd ? LXSS_ATTACH_MOUNT_FLAGS_VHD : LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH, 0, nullptr, nullptr, nullptr);
1561
1562 // Validate that the mount fail because the filesystem couldn't be detected
1563 VERIFY_ARE_EQUAL(result.Result, -1); //-EINVAL
1564 VERIFY_ARE_EQUAL(result.Step, 6); // LxMiniInitMountStepDetectFilesystem
1565
1566 // Unmount the disk
1567 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + deviceName), (DWORD)0);
1568
1569 if (!isVhd)
1570 {
1571 WaitForDiskReady();
1572 ValidateOffline(false);
1573 }
1574 }
1575 };
1576 } // namespace MountTests