master
cpp 1,161 lines 37.5 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 filesystem.cpp
8
9 Abstract:
10
11 This file contains file system function definitions.
12
13 --*/
14
15 #include "precomp.h"
16 #include "filesystem.hpp"
17
18 #define FULL_PATH_PREFIX L"\\\\?\\"
19 #define LXSS_DOMAIN_NAME_DEFAULT "localdomain"
20 #define LXSS_EA_BUFFER_INCREMENT_SIZE 4096
21
22 namespace {
23
24 enum CaseSensitivity
25 {
26 Invalid,
27 Disabled,
28 Enabled
29 };
30
31 constexpr const wchar_t* c_fileSystemKeyName = L"System\\CurrentControlSet\\Control\\FileSystem";
32 constexpr const wchar_t* c_enableDirCaseSensitivityValue = L"NtfsEnableDirCaseSensitivity";
33 constexpr DWORD c_enableDirCaseSensitivity = 0x1;
34 constexpr DWORD c_enableDirCaseSensitivityEmptyDirOnly = 0x2;
35
36 std::vector<char> CreateMetaDataEaBuffer(LX_UID_T Uid, LX_GID_T Gid, LX_MODE_T Mode)
37 {
38 constexpr auto c_nameSize = (RTL_NUMBER_OF(LX_FILE_METADATA_UID_EA_NAME) - 1);
39
40 static_assert(RTL_NUMBER_OF(LX_FILE_METADATA_UID_EA_NAME) == RTL_NUMBER_OF(LX_FILE_METADATA_GID_EA_NAME));
41 static_assert(RTL_NUMBER_OF(LX_FILE_METADATA_UID_EA_NAME) == RTL_NUMBER_OF(LX_FILE_METADATA_MODE_EA_NAME));
42
43 // Simplified version of FILE_FULL_EA_INFORMATION since the names have constant sizes.
44 // See: https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_full_ea_information
45
46 #pragma pack(push, 1)
47 struct EA_ENTRY
48 {
49 ULONG NextEntryOffset;
50 UCHAR Flags;
51 UCHAR EaNameLength;
52 USHORT EaValueLength;
53 CHAR EaName[c_nameSize];
54 char padding[1];
55 ULONG EaValue{};
56 char padding2[1];
57 };
58 #pragma pack(pop)
59
60 static_assert(sizeof(EA_ENTRY) == 20);
61
62 std::vector<char> buffer;
63 __unaligned EA_ENTRY* currentEntry = nullptr;
64
65 auto writeEntry = [&currentEntry, &buffer](const std::string_view& Name, ULONG Value) {
66 WI_ASSERT(Name.size() == c_nameSize);
67
68 const auto currentOffset = buffer.size();
69 buffer.resize(currentOffset + sizeof(EA_ENTRY));
70
71 currentEntry = reinterpret_cast<EA_ENTRY*>(buffer.data() + currentOffset);
72 currentEntry->NextEntryOffset = sizeof(EA_ENTRY);
73 currentEntry->EaNameLength = c_nameSize; // Does not include null terminator.
74 currentEntry->EaValueLength = sizeof(ULONG);
75 currentEntry->EaValue = Value;
76 std::copy(Name.begin(), Name.end(), &currentEntry->EaName[0]);
77 };
78
79 if (Uid != LX_UID_INVALID)
80 {
81 writeEntry(LX_FILE_METADATA_UID_EA_NAME, Uid);
82 }
83
84 if (Gid != LX_GID_INVALID)
85 {
86 writeEntry(LX_FILE_METADATA_GID_EA_NAME, Gid);
87 }
88
89 if (Mode != LX_MODE_INVALID)
90 {
91 writeEntry(LX_FILE_METADATA_MODE_EA_NAME, Mode);
92 }
93
94 if (currentEntry != nullptr)
95 {
96 currentEntry->NextEntryOffset = 0;
97 }
98
99 WI_ASSERT((buffer.size() % sizeof(EA_ENTRY)) == 0);
100
101 return buffer;
102 }
103
104 void CopyFileWithMetadata(_In_ PCWSTR Source, _In_ PCWSTR Destination, _In_ ULONG Mode, _In_ ULONG DistroVersion)
105 {
106 //
107 // Impersonate the client, copy the file, and write the extended attributes.
108 //
109
110 {
111 auto runAsUser = wil::CoImpersonateClient();
112 THROW_IF_WIN32_BOOL_FALSE(CopyFileW(Source, Destination, FALSE));
113
114 //
115 // Apply DrvFs-style attributes for instances using WslFs; otherwise,
116 // use the old LxFs-style attributes.
117 //
118
119 const wil::unique_hfile file{CreateFileW(
120 Destination, GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
121
122 IO_STATUS_BLOCK ioStatus{};
123
124 //
125 // Write the extended attributes.
126 //
127
128 if (LXSS_DISTRO_USES_WSL_FS(DistroVersion) != FALSE)
129 {
130 auto buffer = CreateMetaDataEaBuffer(LX_UID_ROOT, LX_GID_ROOT, Mode);
131 THROW_IF_NTSTATUS_FAILED(ZwSetEaFile(file.get(), &ioStatus, buffer.data(), static_cast<ULONG>(buffer.size())));
132 }
133 else
134 {
135 LX_FILE_ATTRIBUTES_EA LxFs{};
136 LX_FILE_ATTRIBUTES_EA_INITIALIZE(&LxFs);
137 LxFs.Attributes.Mode = Mode;
138
139 THROW_IF_NTSTATUS_FAILED(ZwSetEaFile(file.get(), &ioStatus, &LxFs, sizeof(LxFs)));
140 }
141 }
142 }
143
144 DWORD GetNtfsDirCaseSensitivityFlags()
145 {
146 return wsl::windows::common::registry::ReadDword(HKEY_LOCAL_MACHINE, c_fileSystemKeyName, c_enableDirCaseSensitivityValue, 0);
147 }
148
149 void SetNtfsDirCaseSensitivityFlags(_In_ ULONG Flags)
150 {
151 //
152 // The service is already impersonating when this is used, and the user
153 // likely doesn't have permission to set this key, so temporarily revert
154 // impersonation.
155 //
156
157 auto RunAsSelf = wil::run_as_self();
158 wsl::windows::common::registry::WriteDword(HKEY_LOCAL_MACHINE, c_fileSystemKeyName, c_enableDirCaseSensitivityValue, Flags);
159 }
160
161 CaseSensitivity GetCaseSensitivity()
162 {
163 ULONG caseSensitiveRaw;
164 THROW_IF_NTSTATUS_FAILED(::NtQueryInformationThread(
165 GetCurrentThread(), ThreadExplicitCaseSensitivity, &caseSensitiveRaw, sizeof(caseSensitiveRaw), nullptr));
166
167 return (caseSensitiveRaw == 0) ? Disabled : Enabled;
168 }
169
170 NTSTATUS SetCaseSensitivity(_In_ CaseSensitivity value)
171 {
172 ULONG caseSensitiveRaw;
173 switch (value)
174 {
175 case Disabled:
176 caseSensitiveRaw = 0;
177 break;
178
179 case Enabled:
180 caseSensitiveRaw = 1;
181 break;
182
183 default:
184 return STATUS_INVALID_PARAMETER;
185 }
186
187 return NtSetInformationThread(GetCurrentThread(), ThreadExplicitCaseSensitivity, &caseSensitiveRaw, sizeof(caseSensitiveRaw));
188 }
189
190 using revert_dir_case_sensitivity =
191 wil::unique_any<DWORD, decltype(&SetNtfsDirCaseSensitivityFlags), SetNtfsDirCaseSensitivityFlags, wil::details::pointer_access_none, DWORD, DWORD, DWORD_MAX, DWORD>;
192
193 using revert_case_sensitivity =
194 wil::unique_any<CaseSensitivity, decltype(&SetCaseSensitivity), SetCaseSensitivity, wil::details::pointer_access_none, CaseSensitivity, CaseSensitivity, Invalid, CaseSensitivity>;
195
196 revert_dir_case_sensitivity EnableNtfsDirCaseSensitivity()
197 {
198 auto Flags = GetNtfsDirCaseSensitivityFlags();
199 auto NewFlags = Flags;
200 WI_SetFlag(NewFlags, c_enableDirCaseSensitivity);
201 WI_ClearFlag(NewFlags, c_enableDirCaseSensitivityEmptyDirOnly);
202
203 //
204 // Check if a change needs to be made.
205 //
206
207 if (Flags == NewFlags)
208 {
209 return {};
210 }
211
212 SetNtfsDirCaseSensitivityFlags(NewFlags);
213
214 //
215 // Just in case, make sure at least the main enable flag is set after
216 // reverting; otherwise, WSL will break.
217 //
218
219 WI_SetFlag(Flags, c_enableDirCaseSensitivity);
220 return revert_dir_case_sensitivity{Flags};
221 }
222
223 revert_case_sensitivity EnableCaseSensitivity()
224 {
225 CaseSensitivity oldCaseSensitive = GetCaseSensitivity();
226 THROW_IF_NTSTATUS_FAILED(SetCaseSensitivity(CaseSensitivity::Enabled));
227 return revert_case_sensitivity(oldCaseSensitive);
228 }
229
230 bool HasReadAccessToDrive(wchar_t drive)
231 {
232 // Note: Using GetFileSecurity / AccessCheck doesn't work if the user doesn't have access
233 // to a drive (for instance the EFI partition), since the ACL returned by GetFileSecurity
234 // allows read access to Everyone.
235 // Using FindFirstFile guarantees that the user actually has read access to that drive.
236
237 const wchar_t path[] = {drive, ':', '\\', '*', '\0'};
238
239 WIN32_FIND_DATAW findData{};
240 const wil::unique_hfind find{FindFirstFileW(path, &findData)};
241
242 return !!find;
243 }
244
245 void EnsureCaseSensitiveDirectoryRecursive(_In_ HANDLE Directory)
246 {
247 FILE_CASE_SENSITIVE_INFORMATION CaseInfo{};
248 IO_STATUS_BLOCK IoStatus{};
249 std::vector<std::byte> buffer{sizeof(FILE_ID_BOTH_DIR_INFORMATION) + MAX_PATH};
250 bool restart = true;
251
252 while (true)
253 {
254 const auto result = NtQueryDirectoryFile(
255 Directory,
256 nullptr,
257 nullptr,
258 nullptr,
259 &IoStatus,
260 buffer.data(),
261 static_cast<DWORD>(buffer.size()),
262 static_cast<FILE_INFORMATION_CLASS>(FileIdBothDirectoryInformation),
263 true,
264 nullptr,
265 restart);
266
267 WI_ASSERT(result != STATUS_PENDING);
268
269 if (result == STATUS_NO_MORE_FILES || result == STATUS_NO_SUCH_FILE)
270 {
271 break;
272 }
273 else if (result == STATUS_BUFFER_OVERFLOW)
274 {
275 buffer.resize(buffer.size() * 2);
276 continue;
277 }
278
279 THROW_IF_NTSTATUS_FAILED(result);
280
281 restart = false;
282
283 const auto* information = reinterpret_cast<const FILE_ID_BOTH_DIR_INFORMATION*>(buffer.data());
284
285 //
286 // Only process non-reparse point directories.
287 //
288 // N.B. Nothing needs to be done for files.
289 //
290
291 if ((WI_IsFlagSet(information->FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) &&
292 (WI_IsFlagClear(information->FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT)))
293 {
294
295 //
296 // Skip the . and .. entries.
297 //
298
299 const auto name = std::wstring_view(&information->FileName[0], information->FileNameLength / sizeof(wchar_t));
300 if (name == L"." || name == L"..")
301 {
302 continue;
303 }
304
305 UNICODE_STRING Name{};
306 RtlInitUnicodeString(&Name, information->FileName);
307
308 auto Child = wsl::windows::common::filesystem::OpenRelativeFile(
309 Directory,
310 &Name,
311 (FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE),
312 FILE_OPEN,
313 (FILE_OPEN_REPARSE_POINT | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT));
314
315 THROW_IF_NTSTATUS_FAILED(NtQueryInformationFile(Child.get(), &IoStatus, &CaseInfo, sizeof(CaseInfo), FileCaseSensitiveInformation));
316
317 //
318 // Skip if the directory already has the flag.
319 //
320
321 if (WI_IsFlagClear(CaseInfo.Flags, FILE_CS_FLAG_CASE_SENSITIVE_DIR))
322 {
323 EnsureCaseSensitiveDirectoryRecursive(Child.get());
324 }
325 }
326 }
327
328 //
329 // After all children are processed, mark the directory case-sensitive.
330 //
331 // N.B. This is done with a retry because if the NtfsEnableDirCaseSensitivity
332 // flag was just changed from 3 to 1, NTFS may not have updated its
333 // behavior yet in which case it will fail with STATUS_DIRECTORY_NOT_EMPTY.
334 //
335
336 CaseInfo.Flags = FILE_CS_FLAG_CASE_SENSITIVE_DIR;
337 wsl::shared::retry::RetryWithTimeout<void>(
338 [&]() {
339 THROW_IF_NTSTATUS_FAILED(NtSetInformationFile(Directory, &IoStatus, &CaseInfo, sizeof(CaseInfo), FileCaseSensitiveInformation));
340 },
341 std::chrono::milliseconds{100},
342 std::chrono::seconds{1},
343 []() { return wil::ResultFromCaughtException() == HRESULT_FROM_NT(STATUS_DIRECTORY_NOT_EMPTY); });
344 }
345
346 void SetDirectoryCaseSensitive(_In_ PCWSTR Path)
347 {
348 const wil::unique_hfile Directory{CreateFileW(
349 Path,
350 FILE_WRITE_ATTRIBUTES,
351 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
352 nullptr,
353 OPEN_EXISTING,
354 (FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT),
355 nullptr)};
356
357 IO_STATUS_BLOCK IoStatus;
358 FILE_CASE_SENSITIVE_INFORMATION CaseInfo;
359 CaseInfo.Flags = FILE_CS_FLAG_CASE_SENSITIVE_DIR;
360 THROW_IF_NTSTATUS_FAILED(NtSetInformationFile(Directory.get(), &IoStatus, &CaseInfo, sizeof(CaseInfo), FileCaseSensitiveInformation));
361 }
362
363 void SetExtendedAttributesLxFs(_In_ PCWSTR Path, _In_ ULONG Mode, _In_ ULONG Uid, _In_ ULONG Gid)
364 {
365 const wil::unique_hfile FileHandle(::CreateFileW(
366 Path,
367 FILE_GENERIC_READ | FILE_GENERIC_WRITE,
368 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
369 nullptr,
370 OPEN_EXISTING,
371 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
372 nullptr));
373
374 THROW_LAST_ERROR_IF(!FileHandle);
375
376 LX_FILE_ATTRIBUTES_EA AttributesEa;
377 IO_STATUS_BLOCK IoStatusBlock;
378 const NTSTATUS Status = wsl::windows::common::filesystem::QuerySingleEaFileNoThrow(
379 FileHandle.get(), &IoStatusBlock, LX_FILE_ATTRIBUTES_NAME, &AttributesEa, sizeof(AttributesEa));
380
381 //
382 // If the attributes exist and are valid, leave them alone. Users can
383 // change the attributes on a root inode (e.g. with chmod/chown) and those
384 // changes should not be overwritten.
385 //
386
387 if ((NT_SUCCESS(Status)) && (IoStatusBlock.Information == sizeof(AttributesEa)) &&
388 (AttributesEa.u.EaInformation.EaValueLength == sizeof(AttributesEa.Attributes)) &&
389 (AttributesEa.Attributes.u.Flags.Version == LX_FILE_ATTRIBUTES_CURRENT_VERSION))
390 {
391 return;
392 }
393
394 LX_FILE_ATTRIBUTES_EA_INITIALIZE(&AttributesEa);
395 AttributesEa.Attributes.Uid = Uid;
396 AttributesEa.Attributes.Gid = Gid;
397 AttributesEa.Attributes.Mode = Mode;
398 THROW_IF_NTSTATUS_FAILED(ZwSetEaFile(FileHandle.get(), &IoStatusBlock, &AttributesEa, sizeof(AttributesEa)));
399 }
400
401 void SetExtendedAttributesDrvFs(_In_ PCWSTR Path, _In_ ULONG Mode, _In_ ULONG Uid, _In_ ULONG Gid)
402 {
403 const wil::unique_hfile FileHandle{::CreateFileW(
404 Path,
405 FILE_GENERIC_READ | FILE_GENERIC_WRITE,
406 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
407 nullptr,
408 OPEN_EXISTING,
409 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
410 nullptr)};
411
412 THROW_LAST_ERROR_IF(!FileHandle);
413
414 //
415 // Use FILE_STAT_LX_INFORMATION as an easy way to determine what attributes
416 // the file already has.
417 //
418
419 FILE_STAT_LX_INFORMATION Info;
420 IO_STATUS_BLOCK IoStatus;
421 THROW_IF_NTSTATUS_FAILED(NtQueryInformationFile(FileHandle.get(), &IoStatus, &Info, sizeof(Info), FileStatLxInformation));
422
423 LX_UID_T UidToSet = LX_UID_INVALID;
424 LX_GID_T GidToSet = LX_GID_INVALID;
425 LX_MODE_T ModeToSet = LX_MODE_INVALID;
426 bool NeedUpdate = false;
427 if (WI_IsFlagClear(Info.LxFlags, LX_FILE_METADATA_HAS_UID))
428 {
429 UidToSet = Uid;
430 NeedUpdate = true;
431 }
432
433 if (WI_IsFlagClear(Info.LxFlags, LX_FILE_METADATA_HAS_GID))
434 {
435 GidToSet = Gid;
436 NeedUpdate = true;
437 }
438
439 if (WI_IsFlagClear(Info.LxFlags, LX_FILE_METADATA_HAS_MODE))
440 {
441 ModeToSet = Mode;
442 NeedUpdate = true;
443 }
444
445 if (NeedUpdate != false)
446 {
447 auto buffer = CreateMetaDataEaBuffer(UidToSet, GidToSet, ModeToSet);
448 IO_STATUS_BLOCK IoStatus{};
449
450 THROW_IF_NTSTATUS_FAILED_MSG(
451 ZwSetEaFile(FileHandle.get(), &IoStatus, buffer.data(), static_cast<DWORD>(buffer.size())), "%ls", Path);
452 }
453 }
454
455 void SetExtendedAttributes(_In_ PCWSTR Path, _In_ ULONG Mode, _In_ ULONG Uid, _In_ ULONG Gid, _In_ ULONG DistroVersion)
456 {
457 //
458 // Apply DrvFs-style attributes for instances using WslFs; otherwise, use
459 // the old LxFs-style attributes.
460 //
461
462 if (LXSS_DISTRO_USES_WSL_FS(DistroVersion) != FALSE)
463 {
464 SetExtendedAttributesDrvFs(Path, Mode, Uid, Gid);
465 }
466 else
467 {
468 SetExtendedAttributesLxFs(Path, Mode, Uid, Gid);
469 }
470 }
471
472 } // namespace
473
474 wsl::windows::common::filesystem::TempFile::TempFile(
475 _In_ DWORD DesiredAccess, _In_ DWORD ShareMode, _In_ DWORD CreationDisposition, _In_ TempFileFlags Flags, _In_opt_ std::wstring_view Extension) :
476 Flags(Flags)
477 {
478 Path = GetTempFilename();
479 if (!Extension.empty())
480 {
481 Path.replace_extension(Extension);
482 }
483
484 LPSECURITY_ATTRIBUTES SecurityAttributes{};
485 SECURITY_ATTRIBUTES Attributes = {sizeof(SECURITY_ATTRIBUTES), nullptr, true};
486 if (WI_IsFlagSet(Flags, TempFileFlags::InheritHandle))
487 {
488 SecurityAttributes = &Attributes;
489 }
490
491 DWORD FlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY;
492 WI_SetFlagIf(FlagsAndAttributes, FILE_FLAG_DELETE_ON_CLOSE, WI_IsFlagSet(Flags, TempFileFlags::DeleteOnClose));
493 Handle.reset(CreateFileW(Path.c_str(), DesiredAccess, ShareMode, SecurityAttributes, CreationDisposition, FlagsAndAttributes, nullptr));
494 THROW_LAST_ERROR_IF(!Handle);
495 }
496
497 wsl::windows::common::filesystem::TempFile::~TempFile()
498 {
499 // If the delete on close flag is not set, close the handle and delete the file.
500 if (!Path.empty() && WI_IsFlagClear(Flags, TempFileFlags::DeleteOnClose))
501 {
502 Handle.reset();
503 LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(Path.c_str()));
504 }
505 }
506
507 wsl::windows::common::filesystem::unique_lxss_addmount wsl::windows::common::filesystem::CreateMount(
508 _In_ PCWSTR NtPath, _In_ PCWSTR Source, _In_opt_ LPCSTR Target, _In_ LPCSTR FsType, _In_ ULONG Mode, _In_ bool forWrite)
509 {
510 unique_lxss_addmount mount = {};
511 mount.WindowsDataRoot = OpenDirectoryHandle(NtPath, forWrite).release();
512 mount.Source =
513 wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(wsl::shared::string::WideToMultiByte(Source).c_str()).release();
514 if (ARGUMENT_PRESENT(Target))
515 {
516 mount.Target = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(Target).release();
517 }
518
519 mount.FsType = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(FsType).release();
520 mount.MountFlags = LX_MS_NOATIME;
521 WI_SetFlagIf(mount.MountFlags, LX_MS_RDONLY, !forWrite);
522 mount.Mode = Mode;
523 mount.Uid = LX_UID_ROOT;
524 mount.Gid = LX_GID_ROOT;
525 return mount;
526 }
527
528 void wsl::windows::common::filesystem::CreateRootFs(_In_ PCWSTR Path, _In_ ULONG Version)
529 {
530 //
531 // Declare a scope exit variable to clean up on failure.
532 //
533
534 bool deleteRootFs = false;
535 const wil::unique_handle userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
536 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
537 if (deleteRootFs)
538 {
539 auto runAsUser = wil::impersonate_token(userToken.get());
540 LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(Path));
541 }
542 });
543
544 //
545 // Create the rootfs directory while impersonating the user, fail if the
546 // directory already exists.
547 //
548 // N.B. Throw ERROR_FILE_EXISTS instead of ERROR_ALREADY_EXISTS for consistent
549 // error messages with WSL2.
550 //
551
552 {
553 auto runAsUser = wil::impersonate_token(userToken.get());
554 if (!CreateDirectoryW(Path, nullptr))
555 {
556 const auto lastError = GetLastError();
557 THROW_WIN32_MSG(lastError == ERROR_ALREADY_EXISTS ? ERROR_FILE_EXISTS : lastError, "CreateDirectoryW");
558 }
559
560 deleteRootFs = true;
561 SetExtendedAttributes(Path, (LX_S_IFDIR | 0755), LX_UID_ROOT, LX_GID_ROOT, Version);
562 }
563
564 //
565 // Make sure the directory is marked case-sensitive.
566 //
567 // N.B. This is done without impersonating the client because setting this
568 // attribute requires the "delete subfolders and files" permission on
569 // the parent directory.
570 //
571
572 SetDirectoryCaseSensitive(Path);
573 cleanup.release();
574 }
575
576 // Sends an ioctl to a device, and waits for the result.
577 void wsl::windows::common::filesystem::DeviceIoControl(_In_ HANDLE handle, _In_ ULONG code, _In_ gsl::span<const gsl::byte> input)
578 {
579 THROW_IF_NTSTATUS_FAILED(DeviceIoControlNoThrow(handle, code, input));
580 }
581
582 // Sends an ioctl to a device, and waits for the result.
583 NTSTATUS
584 wsl::windows::common::filesystem::DeviceIoControlNoThrow(_In_ HANDLE handle, _In_ ULONG code, _In_ gsl::span<const gsl::byte> input)
585 {
586 PVOID inputBuffer{};
587 if (input.size() > 0)
588 {
589 inputBuffer = const_cast<gsl::byte*>(input.data());
590 }
591
592 IO_STATUS_BLOCK ioStatus;
593 wil::unique_event event;
594 event.create();
595 NTSTATUS status = NtDeviceIoControlFile(
596 handle, event.get(), nullptr, nullptr, &ioStatus, code, inputBuffer, gsl::narrow_cast<ULONG>(input.size()), nullptr, 0);
597
598 if (status == STATUS_PENDING)
599 {
600 event.wait();
601 status = ioStatus.Status;
602 }
603
604 return status;
605 }
606
607 std::pair<ULONG, ULONG> wsl::windows::common::filesystem::EnumerateFixedDrives(HANDLE Token)
608 {
609 std::variant<wil::unique_coreverttoself_call, wil::unique_token_reverter> runAsUser;
610
611 if (Token == nullptr)
612 {
613 runAsUser = wil::CoImpersonateClient();
614 }
615 else
616 {
617 runAsUser = wil::impersonate_token(Token);
618 }
619
620 ULONG fixedDriveBitmap = GetLogicalDrives();
621 ULONG driveBitmap = fixedDriveBitmap;
622 ULONG index = 0;
623 ULONG nonReadableDrives = 0;
624 wchar_t drivePath[] = L"A:\\";
625 while (driveBitmap != 0)
626 {
627 WI_VERIFY(_BitScanForward(&index, driveBitmap) != FALSE);
628
629 const ULONG driveMask = (1 << index);
630 driveBitmap ^= driveMask;
631 const auto driveName = static_cast<wchar_t>(L'A' + index);
632 drivePath[0] = driveName;
633 if (GetDriveTypeW(drivePath) != DRIVE_FIXED)
634 {
635 // Don't try to check if the user has read access to non-fixed drives.
636 // This can cause a hang for network devices. See https://github.com/microsoft/WSL/issues/11460 .
637 fixedDriveBitmap ^= driveMask;
638 continue;
639 }
640
641 if (!HasReadAccessToDrive(driveName))
642 {
643 nonReadableDrives |= driveMask;
644 }
645 }
646
647 return {fixedDriveBitmap & ~nonReadableDrives, nonReadableDrives};
648 }
649
650 void wsl::windows::common::filesystem::EnsureCaseSensitiveDirectory(_In_ PCWSTR Path, _In_ ULONG Flags)
651 {
652 // N.B. Passing SYNCHRONIZE and FILE_SYNCHRONOUS_IO_NONALERT is required; otherwise, NtQueryDirectoryFile
653 // might return STATUS_PENDING, which would break our folder enumeration logic.
654
655 const wil::unique_hfile Directory{CreateFileW(
656 Path,
657 (FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE),
658 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
659 nullptr,
660 OPEN_EXISTING,
661 (FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT),
662 nullptr)};
663
664 FILE_CASE_SENSITIVE_INFORMATION CaseInfo;
665 QueryInformationFile(Directory.get(), CaseInfo, FileCaseSensitiveInformation);
666
667 //
668 // Because upgrading is done depth-first, if the directory already has the
669 // flag all its children must too; this allows checking for upgrade at
670 // every start with low cost, and resuming of interrupted upgrades.
671 //
672
673 if (WI_IsFlagSet(CaseInfo.Flags, FILE_CS_FLAG_CASE_SENSITIVE_DIR))
674 {
675 return;
676 }
677
678 //
679 // Abort if upgrading is not allowed.
680 //
681
682 if (WI_IsFlagClear(Flags, LXSS_CREATE_INSTANCE_FLAGS_ALLOW_FS_UPGRADE))
683 {
684 THROW_HR(WSL_E_FS_UPGRADE_NEEDED);
685 }
686
687 //
688 // Enable per-thread case sensitivity on the thread.
689 //
690 // N.B. This requires the service is running as PPL. The lifted service will
691 // return an error in this case but this is a legacy upgrade path for
692 // WSL distributions that have not been launched since RS3. This logic
693 // should be refactored in the lifted service to not require per-thread
694 // case sensitivity
695 //
696
697 revert_case_sensitivity revertCase;
698 if (WI_IsFlagClear(Flags, c_case_sensitive_folders_only))
699 {
700 auto runAsSelf = wil::run_as_self();
701 auto revertPrivilege = wsl::windows::common::security::AcquirePrivilege(SE_DEBUG_NAME);
702 revertCase = EnableCaseSensitivity();
703 }
704
705 //
706 // Upgrading requires that setting the per-directory case sensitivity flag
707 // is allowed on non-empty directories, which requires changing the
708 // registry.
709 //
710 // N.B. This change is reverted after the operation is complete.
711 //
712
713 auto dirCaseSensitivity = EnableNtfsDirCaseSensitivity();
714 EnsureCaseSensitiveDirectoryRecursive(Directory.get());
715 }
716
717 bool wsl::windows::common::filesystem::EnsureDirectory(_In_ LPCWSTR pPath)
718 {
719 //
720 // Return true if a new directory is created.
721 //
722
723 if (CreateDirectoryW(pPath, nullptr))
724 {
725 return true;
726 }
727
728 //
729 // Return false if the directory existed.
730 //
731
732 const auto lastError = GetLastError();
733 if (lastError == ERROR_ALREADY_EXISTS)
734 {
735 return false;
736 }
737 else if (lastError == ERROR_PATH_NOT_FOUND)
738 {
739 wil::CreateDirectoryDeep(pPath);
740 }
741 else
742 {
743 THROW_WIN32_MSG(lastError, "CreateDirectoryW(%ls)", pPath);
744 }
745
746 return true;
747 }
748
749 void wsl::windows::common::filesystem::EnsureDirectoryWithAttributes(
750 _In_ PCWSTR Path, _In_ ULONG Mode, _In_ ULONG Uid, _In_ ULONG Gid, _In_ ULONG Flags, _In_ ULONG DistroVersion)
751 {
752 const bool newDirectory = EnsureDirectory(Path);
753 SetExtendedAttributes(Path, LX_S_IFDIR | Mode, Uid, Gid, DistroVersion);
754
755 //
756 // Mark a new directory case-sensitive, or upgrade the entire tree if it
757 // exists. If the root is already case-sensitive, it's assumed the entire
758 // tree is.
759 //
760
761 if (newDirectory)
762 {
763 SetDirectoryCaseSensitive(Path);
764 }
765 else
766 {
767 EnsureCaseSensitiveDirectory(Path, Flags);
768 }
769 }
770
771 bool wsl::windows::common::filesystem::FileExists(_In_ LPCWSTR Path)
772 {
773 const DWORD Attributes = GetFileAttributesW(Path);
774 return (Attributes != INVALID_FILE_ATTRIBUTES);
775 }
776
777 std::filesystem::path wsl::windows::common::filesystem::GetCanonicalPath(const std::filesystem::path& Path)
778 {
779 std::error_code error;
780 auto canonicalPath = GetCanonicalPath(Path, error);
781 THROW_HR_IF_MSG(HRESULT_FROM_WIN32(error.value()), !!error, "GetCanonicalPath(%ls)", Path.c_str());
782
783 return canonicalPath;
784 }
785
786 std::filesystem::path wsl::windows::common::filesystem::GetCanonicalPath(const std::filesystem::path& Path, std::error_code& Error)
787 {
788 // absolute() is applied first because weakly_canonical() does not resolve a relative path
789 // against the current directory on its own. Its result is checked before canonicalizing because
790 // weakly_canonical() clears Error on success, which would otherwise mask an absolute() failure.
791 const auto absolutePath = std::filesystem::absolute(Path, Error);
792 if (Error)
793 {
794 return {};
795 }
796
797 auto canonicalPath = std::filesystem::weakly_canonical(absolutePath, Error);
798 if (Error)
799 {
800 return {};
801 }
802
803 return canonicalPath;
804 }
805
806 std::filesystem::path wsl::windows::common::filesystem::GetFullPath(_In_ LPCWSTR Path)
807 {
808 DWORD Attributes = GetFileAttributesW(Path);
809 THROW_LAST_ERROR_IF(Attributes == INVALID_FILE_ATTRIBUTES);
810
811 const wil::unique_hfile Handle(CreateFileW(
812 Path,
813 GENERIC_READ,
814 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
815 nullptr,
816 OPEN_EXISTING,
817 (WI_IsFlagSet(Attributes, FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : FILE_ATTRIBUTE_NORMAL),
818 nullptr));
819
820 THROW_LAST_ERROR_IF(!Handle);
821
822 std::wstring FullPath;
823 THROW_IF_FAILED(wil::GetFinalPathNameByHandleW(Handle.get(), FullPath));
824
825 return std::filesystem::path(std::move(FullPath));
826 }
827
828 std::pair<std::string, std::string> wsl::windows::common::filesystem::GetHostAndDomainNames()
829 {
830 std::string hostName = GetLinuxHostName();
831
832 DWORD size = 0;
833 WI_VERIFY(GetComputerNameExA(ComputerNameDnsDomain, nullptr, &size) == FALSE);
834
835 // If there is no domain name, initialize with a default. Truncate the
836 // domain name to the max size that the driver allows.
837 // N.B. If the buffer is too small, GetComputerNameEx() sets 'size' to the string size,
838 // ** including ** the null terminator. On success it returns the string size,
839 // See: https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexa
840
841 std::string domainName{};
842 if (size <= 1)
843 {
844 domainName = LXSS_DOMAIN_NAME_DEFAULT;
845 }
846 else
847 {
848 domainName.resize(size - 1, '\0');
849 THROW_LAST_ERROR_IF(!GetComputerNameExA(ComputerNameDnsDomain, domainName.data(), &size));
850 WI_ASSERT(domainName.size() == size);
851
852 if (domainName.size() > LX_DOMAIN_NAME_MAX)
853 {
854 domainName.resize(LX_DOMAIN_NAME_MAX);
855 }
856 }
857
858 return {std::move(hostName), std::move(domainName)};
859 }
860
861 std::filesystem::path wsl::windows::common::filesystem::GetLegacyBasePath(_In_ HANDLE UserToken)
862 {
863 return GetLocalAppDataPath(UserToken) / L"lxss";
864 }
865
866 std::string wsl::windows::common::filesystem::GetLinuxHostName()
867 {
868 DWORD size = 0;
869 WI_VERIFY(GetComputerNameExA(ComputerNamePhysicalDnsHostname, nullptr, &size) == FALSE);
870 std::string hostName(size - 1, '\0');
871 THROW_LAST_ERROR_IF(!GetComputerNameExA(ComputerNamePhysicalDnsHostname, hostName.data(), &size));
872
873 WI_ASSERT((size <= LX_HOST_NAME_MAX) && (hostName.size() == size));
874
875 return wsl::shared::string::CleanHostname(hostName);
876 }
877
878 std::filesystem::path wsl::windows::common::filesystem::GetLocalAppDataPath(_In_ HANDLE userToken)
879 {
880 return GetKnownFolderPath(FOLDERID_LocalAppData, (KF_FLAG_CREATE | KF_FLAG_NO_APPCONTAINER_REDIRECTION), userToken);
881 }
882
883 std::filesystem::path wsl::windows::common::filesystem::GetKnownFolderPath(const KNOWNFOLDERID& id, DWORD flags, HANDLE token)
884 {
885 wil::unique_cotaskmem_string path;
886 THROW_IF_FAILED(::SHGetKnownFolderPath(id, flags, token, &path));
887
888 return std::filesystem::path(path.get());
889 }
890
891 std::filesystem::path wsl::windows::common::filesystem::GetTempFilename()
892 {
893 WCHAR Path[MAX_PATH + 1];
894 std::wstring File(MAX_PATH + 1, L'\0');
895 THROW_LAST_ERROR_IF(GetTempPathW(ARRAYSIZE(Path), Path) == 0);
896 THROW_LAST_ERROR_IF(GetTempFileNameW(Path, L"lx", 0, File.data()) == 0);
897 File.resize(wcsnlen(File.c_str(), File.size()));
898 return std::filesystem::path(std::move(File));
899 }
900
901 std::filesystem::path wsl::windows::common::filesystem::GetTempFolderPath(_In_ HANDLE userToken)
902 {
903 return GetLocalAppDataPath(userToken) / L"temp";
904 }
905
906 std::string wsl::windows::common::filesystem::GetWindowsHosts(const std::filesystem::path& Path)
907 {
908 std::ifstream Stream(Path.c_str());
909 THROW_HR_IF_MSG(E_FAIL, (Stream.bad() || !Stream.is_open()), "errno = %d", errno);
910
911 // Discard any BOM header.
912 int potentialHeader[] = {Stream.get(), Stream.get(), Stream.get()};
913 if (potentialHeader[0] != 0xEF || potentialHeader[1] != 0xBB || potentialHeader[2] != 0xBF)
914 {
915 Stream.seekg(0); // Reset the position to beginning of the file if no BOM header is found.
916 }
917
918 std::string WindowsHosts;
919 std::string Line;
920 while (std::getline(Stream, Line))
921 {
922 // Ignore all text after comment characters.
923
924 const size_t Comment = Line.find_first_of('#');
925 if (Comment != std::string::npos)
926 {
927 Line.resize(Comment);
928 }
929
930 if (Line.size() == 0)
931 {
932 continue;
933 }
934
935 // Create a copy of the line since the string tokenizing API is
936 // destructive.
937
938 std::string LineCopy = Line;
939
940 // Each line is in the following format:
941 // <host-address> <host-alias1> <host-alias2> ...
942 //
943 // N.B. There must be at least one host aliases for each host address.
944
945 std::string CurrentEntry;
946 PCHAR ElementContext = nullptr;
947 PCHAR Element = strtok_s(&LineCopy[0], " \t\r\n", &ElementContext);
948 while (Element != nullptr)
949 {
950 CurrentEntry.append(Element);
951 Element = strtok_s(nullptr, " \t\r\n", &ElementContext);
952 if (Element != nullptr)
953 {
954 CurrentEntry.append("\t");
955 }
956 else
957 {
958 CurrentEntry.append("\n");
959 WindowsHosts.append(CurrentEntry);
960 }
961 }
962
963 if (WindowsHosts.size() > 8 * _1MB)
964 {
965 EMIT_USER_WARNING(wsl::shared::Localization::MessageHostsFileTooLarge());
966 return {};
967 }
968 }
969
970 WI_ASSERT(Stream.eof());
971
972 return WindowsHosts;
973 }
974
975 wil::unique_hfile wsl::windows::common::filesystem::OpenDirectoryHandle(_In_ LPCWSTR pPath, _In_ bool forWrite)
976 {
977 wil::unique_hfile handle(OpenDirectoryHandleNoThrow(pPath, forWrite));
978 THROW_LAST_ERROR_IF(!handle);
979
980 return handle;
981 }
982
983 wil::unique_hfile wsl::windows::common::filesystem::OpenDirectoryHandleNoThrow(_In_ LPCWSTR pPath, _In_ bool forWrite)
984 {
985 DWORD AccessMask = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE;
986 if (forWrite)
987 {
988 WI_SetAllFlags(AccessMask, FILE_GENERIC_WRITE);
989 }
990
991 wil::unique_hfile handle(CreateFileW(
992 pPath, AccessMask, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
993
994 return handle;
995 }
996
997 wil::unique_hfile wsl::windows::common::filesystem::OpenNulDevice(_In_ DWORD DesiredAccess)
998 {
999 wil::unique_hfile nulDevice{CreateFileW(
1000 L"nul", DesiredAccess, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
1001
1002 THROW_LAST_ERROR_IF(!nulDevice);
1003
1004 return nulDevice;
1005 }
1006
1007 wil::unique_hfile wsl::windows::common::filesystem::OpenRelativeFile(
1008 _In_opt_ HANDLE Parent,
1009 _In_ PUNICODE_STRING RelativePath,
1010 _In_ ACCESS_MASK DesiredAccess,
1011 _In_ ULONG Disposition,
1012 _In_ ULONG CreateOptions,
1013 _In_opt_ PVOID EaBuffer,
1014 _In_ ULONG EaSize)
1015
1016 {
1017 auto [Status, File] = OpenRelativeFileNoThrow(Parent, RelativePath, DesiredAccess, Disposition, CreateOptions, EaBuffer, EaSize);
1018 THROW_IF_NTSTATUS_FAILED_MSG(Status, "Path: %.*ls", RelativePath->Length, RelativePath->Buffer);
1019
1020 return std::move(File);
1021 }
1022
1023 std::pair<NTSTATUS, wil::unique_hfile> wsl::windows::common::filesystem::OpenRelativeFileNoThrow(
1024 _In_opt_ HANDLE Parent,
1025 _In_ PUNICODE_STRING RelativePath,
1026 _In_ ACCESS_MASK DesiredAccess,
1027 _In_ ULONG Disposition,
1028 _In_ ULONG CreateOptions,
1029 _In_opt_ PVOID EaBuffer,
1030 _In_ ULONG EaSize)
1031
1032 {
1033 OBJECT_ATTRIBUTES Attributes;
1034 InitializeObjectAttributes(&Attributes, RelativePath, 0, Parent, nullptr);
1035 wil::unique_hfile File;
1036 IO_STATUS_BLOCK IoStatus;
1037 NTSTATUS Status = NtCreateFile(
1038 &File, DesiredAccess, &Attributes, &IoStatus, nullptr, 0, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), Disposition, CreateOptions, EaBuffer, EaSize);
1039
1040 return std::make_pair(Status, std::move(File));
1041 }
1042
1043 wil::unique_hfile wsl::windows::common::filesystem::ReopenFile(_In_ HANDLE Handle, _In_ ACCESS_MASK DesiredAccess, _In_ ULONG CreateOptions)
1044 {
1045 UNICODE_STRING Empty;
1046 RtlInitUnicodeString(&Empty, L"");
1047 return OpenRelativeFile(Handle, &Empty, DesiredAccess, FILE_OPEN, CreateOptions);
1048 }
1049
1050 void wsl::windows::common::filesystem::QueryInformationFile(
1051 _In_ HANDLE Handle, _Out_writes_bytes_(Length) PVOID Buffer, _In_ ULONG Length, _In_ FILE_INFORMATION_CLASS FileInformationClass)
1052 {
1053 IO_STATUS_BLOCK IoStatus;
1054 THROW_IF_NTSTATUS_FAILED(NtQueryInformationFile(Handle, &IoStatus, Buffer, Length, FileInformationClass));
1055 }
1056
1057 VOID wsl::windows::common::filesystem::QuerySingleEaFile(
1058 _In_ HANDLE Handle, _Out_ PIO_STATUS_BLOCK IoStatus, _In_ std::string_view EaName, _Out_writes_bytes_(Length) PVOID Buffer, _In_ ULONG Length)
1059 {
1060 THROW_IF_NTSTATUS_FAILED(QuerySingleEaFileNoThrow(Handle, IoStatus, EaName, Buffer, Length));
1061 }
1062
1063 std::vector<CHAR> wsl::windows::common::filesystem::QuerySingleEaFile(_In_ HANDLE Handle, _In_ std::string_view EaName)
1064 {
1065 std::vector<CHAR> Buffer;
1066 NTSTATUS Status;
1067 IO_STATUS_BLOCK IoStatus;
1068 ULONG Size = 0;
1069 do
1070 {
1071 Size += LXSS_EA_BUFFER_INCREMENT_SIZE;
1072 Buffer.resize(Size);
1073 Status = QuerySingleEaFileNoThrow(Handle, &IoStatus, EaName, &Buffer[0], Size);
1074 } while ((Status == STATUS_BUFFER_OVERFLOW) && (Size <= USHORT_MAX));
1075
1076 THROW_IF_NTSTATUS_FAILED(Status);
1077
1078 //
1079 // Resize to the actual size of the attribute.
1080 //
1081
1082 Buffer.resize(IoStatus.Information);
1083 return Buffer;
1084 }
1085
1086 NTSTATUS
1087 wsl::windows::common::filesystem::QuerySingleEaFileNoThrow(
1088 _In_ HANDLE Handle, _Out_ PIO_STATUS_BLOCK IoStatus, _In_ std::string_view EaName, _Out_writes_bytes_(Length) PVOID Buffer, _In_ ULONG Length)
1089 {
1090 union
1091 {
1092 FILE_GET_EA_INFORMATION List;
1093 CHAR Buffer[offsetof(FILE_GET_EA_INFORMATION, EaName) + UCHAR_MAX];
1094 } EaList;
1095
1096 RtlZeroMemory(&EaList, sizeof(EaList));
1097
1098 WI_ASSERT(EaName.size() < UCHAR_MAX);
1099
1100 EaList.List.EaNameLength = static_cast<UCHAR>(EaName.size());
1101 RtlCopyMemory(EaList.List.EaName, EaName.data(), EaName.size());
1102 return ZwQueryEaFile(Handle, IoStatus, Buffer, Length, TRUE, &EaList, sizeof(EaList), nullptr, TRUE);
1103 }
1104
1105 void wsl::windows::common::filesystem::SetInformationFile(
1106 _In_ HANDLE Handle, _In_reads_bytes_(Length) PVOID Buffer, _In_ ULONG Length, _In_ FILE_INFORMATION_CLASS FileInformationClass)
1107 {
1108 IO_STATUS_BLOCK IoStatus;
1109 THROW_IF_NTSTATUS_FAILED(NtSetInformationFile(Handle, &IoStatus, Buffer, Length, FileInformationClass));
1110 }
1111
1112 std::optional<std::filesystem::path> wsl::windows::common::filesystem::TryGetPathFromFileUrl(const std::wstring& Url)
1113 {
1114 constexpr auto filePrefix = L"file://";
1115
1116 if (!Url.starts_with(filePrefix))
1117 {
1118 return {};
1119 }
1120
1121 // Skip third '/', if any
1122 auto startIndex = wcslen(filePrefix);
1123 if (Url.size() > startIndex && Url[startIndex] == L'/')
1124 {
1125 startIndex++;
1126 }
1127
1128 // Replace '/' with '\', for convenience.
1129 auto path = Url.substr(startIndex);
1130 std::replace(path.begin(), path.end(), '/', '\\');
1131
1132 return path;
1133 }
1134
1135 std::wstring wsl::windows::common::filesystem::UnquotePath(_In_ LPCWSTR Path)
1136 {
1137 std::wstring UnquotedPath{Path};
1138
1139 // N.B. PathUnquoteSpaces() returns false if no quotes were found. No error handling is needed.
1140 PathUnquoteSpaces(UnquotedPath.data());
1141 UnquotedPath.resize(wcslen(UnquotedPath.c_str()));
1142
1143 return UnquotedPath;
1144 }
1145
1146 void wsl::windows::common::filesystem::UpdateInit(_In_ PCWSTR BasePath, _In_ ULONG DistroVersion)
1147 {
1148 const auto source = wsl::windows::common::wslutil::GetBasePath() / L"tools" / L"init";
1149 const auto dest = std::filesystem::path(BasePath) / LXSS_ROOTFS_DIRECTORY / L"init";
1150 CopyFileWithMetadata(source.c_str(), dest.c_str(), (LX_S_IFREG | 0755), DistroVersion);
1151 }
1152
1153 wil::unique_hfile wsl::windows::common::filesystem::WipeAndOpenDirectory(_In_ LPCWSTR pPath)
1154 {
1155 const auto result = wil::RemoveDirectoryRecursiveNoThrow(pPath);
1156 THROW_HR_IF(result, (result != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) && (result != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)));
1157
1158 EnsureDirectory(pPath);
1159
1160 return OpenDirectoryHandle(pPath, true);
1161 }