master
cpp 1,734 lines 56.5 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 wslutil.cpp
8
9 Abstract:
10
11 This file contains helper function definitions.
12
13 --*/
14
15 #include "precomp.h"
16 #include "wslutil.h"
17 #include "WslPluginApi.h"
18 #include <wincrypt.h>
19 #include "wslinstallerservice.h"
20 #include "wslc.h"
21
22 #include "ConsoleProgressBar.h"
23 #include "ExecutionContext.h"
24 #include "HandleIO.h"
25 #include "MsiQuery.h"
26 #include "WslInstall.h"
27
28 using winrt::Windows::Foundation::Uri;
29 using winrt::Windows::Management::Deployment::DeploymentOptions;
30 using wsl::shared::Localization;
31 using wsl::windows::common::Context;
32 using namespace wsl::windows::common::registry;
33 using namespace wsl::windows::common::wslutil;
34
35 constexpr auto c_latestReleaseUrl = L"https://api.github.com/repos/Microsoft/WSL/releases/latest";
36 constexpr auto c_releaseListUrl = L"https://api.github.com/repos/Microsoft/WSL/releases";
37
38 wsl::windows::common::io::HandleWrapper COMOutputHandle::Release()
39 {
40 const auto type = Type;
41 const auto handle = Handle.File;
42 Handle.File = nullptr;
43 Type = WSLCHandleTypeUnknown;
44
45 if (type == WSLCHandleTypeSocket)
46 {
47 return wsl::windows::common::io::HandleWrapper{wil::unique_socket{reinterpret_cast<SOCKET>(handle)}};
48 }
49
50 return wsl::windows::common::io::HandleWrapper{wil::unique_handle{handle}};
51 }
52 constexpr auto c_specificReleaseListUrl = L"https://api.github.com/repos/Microsoft/WSL/releases/tags/";
53 constexpr auto c_userAgent = L"wsl-install"; // required to use the GitHub API
54 constexpr auto c_pipePrefix = L"\\\\.\\pipe\\";
55
56 namespace {
57
58 #define X(Error) {(Error), L## #Error}
59 #define X_WIN32(Error) {HRESULT_FROM_WIN32(Error), L## #Error}
60 #define X_NT(Error) {HRESULT_FROM_NT(Error), L## #Error}
61
62 static const std::map<HRESULT, LPCWSTR> g_commonErrors{
63 X(WSL_E_DEFAULT_DISTRO_NOT_FOUND),
64 X(WSL_E_DISTRO_NOT_FOUND),
65 X(WSL_E_WSL1_NOT_SUPPORTED),
66 X(WSL_E_VM_MODE_NOT_SUPPORTED),
67 X(WSL_E_TOO_MANY_DISKS_ATTACHED),
68 X(WSL_E_CONSOLE),
69 X(WSL_E_CUSTOM_KERNEL_NOT_FOUND),
70 X(WSL_E_USER_NOT_FOUND),
71 X(WSL_E_INVALID_USAGE),
72 X(WSL_E_EXPORT_FAILED),
73 X(WSL_E_IMPORT_FAILED),
74 X(WSL_E_TTY_LIMIT),
75 X(WSL_E_CUSTOM_SYSTEM_DISTRO_ERROR),
76 X(WSL_E_LOWER_INTEGRITY),
77 X(WSL_E_HIGHER_INTEGRITY),
78 X(WSL_E_FS_UPGRADE_NEEDED),
79 X(WSL_E_USER_VHD_ALREADY_ATTACHED),
80 X(WSL_E_VM_MODE_INVALID_STATE),
81 X(WSL_E_VM_MODE_MOUNT_NAME_ALREADY_EXISTS),
82 X(WSL_E_ELEVATION_NEEDED_TO_MOUNT_DISK),
83 X(WSL_E_DISK_ALREADY_ATTACHED),
84 X(WSL_E_DISK_ALREADY_MOUNTED),
85 X(WSL_E_DISK_MOUNT_FAILED),
86 X(WSL_E_DISK_UNMOUNT_FAILED),
87 X(WSL_E_WSL2_NEEDED),
88 X(WSL_E_VM_MODE_INVALID_MOUNT_NAME),
89 X(WSL_E_GUI_APPLICATIONS_DISABLED),
90 X(WSL_E_DISTRO_ONLY_AVAILABLE_FROM_STORE),
91 X(WSL_E_WSL_MOUNT_NOT_SUPPORTED),
92 X(WSL_E_WSL_OPTIONAL_COMPONENT_REQUIRED),
93 X(WSL_E_VMSWITCH_NOT_FOUND),
94 X(WSL_E_WSL_MOUNT_NOT_SUPPORTED),
95 X(WSL_E_VMSWITCH_NOT_SET),
96 X(WSL_E_INSTALL_PROCESS_FAILED),
97 X(WSL_E_OS_NOT_SUPPORTED),
98 X(WSL_E_INSTALL_COMPONENT_FAILED),
99 X(WSL_E_PLUGIN_REQUIRES_UPDATE),
100 X(WSL_E_DISK_MOUNT_DISABLED),
101 X(WSL_E_WSL1_DISABLED),
102 X(WSL_E_VIRTUAL_MACHINE_PLATFORM_REQUIRED),
103 X(WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED),
104 X(WSL_E_DISK_CORRUPTED),
105 X(WSL_E_DISTRIBUTION_NAME_NEEDED),
106 X(WSL_E_INVALID_JSON),
107 X(WSL_E_VM_CRASHED),
108 X(WSL_E_NOT_A_LINUX_DISTRO),
109 X(WSLC_E_CONTAINER_DISABLED),
110 X(WSLC_E_REGISTRY_BLOCKED_BY_POLICY),
111 X(WSLC_E_CONTAINER_PREFIX_AMBIGUOUS),
112 X(E_ACCESSDENIED),
113 X_WIN32(ERROR_NOT_FOUND),
114 X_WIN32(ERROR_VERSION_PARSE_ERROR),
115 X(E_INVALIDARG),
116 X_WIN32(ERROR_FILE_NOT_FOUND),
117 X(WININET_E_CANNOT_CONNECT),
118 X(WININET_E_NAME_NOT_RESOLVED),
119 X(HTTP_E_STATUS_NOT_FOUND),
120 X(HCS_E_SERVICE_NOT_AVAILABLE),
121 X_WIN32(ERROR_PATH_NOT_FOUND),
122 X(HCS_E_CONNECTION_TIMEOUT),
123 X(E_FAIL),
124 X(E_UNEXPECTED),
125 X(HCN_E_ADDR_INVALID_OR_RESERVED),
126 X_WIN32(RPC_S_CALL_FAILED),
127 X(RPC_E_DISCONNECTED),
128 X_WIN32(ERROR_PIPE_NOT_CONNECTED),
129 X_WIN32(ERROR_PIPE_BUSY),
130 X_WIN32(ERROR_UNSUPPORTED_TYPE),
131 X_WIN32(ERROR_CANCELLED),
132 X_WIN32(ERROR_ACCESS_DISABLED_BY_POLICY),
133 X_WIN32(HCS_E_HYPERV_NOT_INSTALLED),
134 X(E_NOINTERFACE),
135 X(REGDB_E_CLASSNOTREG),
136 X(CERT_E_UNTRUSTEDROOT),
137 X(E_ABORT),
138 X_WIN32(ERROR_SERVICE_NOT_ACTIVE),
139 X_WIN32(ERROR_SHARING_VIOLATION),
140 X_WIN32(ERROR_DISK_REPAIR_DISABLED),
141 X(WSL_E_DISTRO_NOT_STOPPED),
142 X_WIN32(ERROR_UNHANDLED_EXCEPTION),
143 X(TRUST_E_NOSIGNATURE),
144 X(TRUST_E_BAD_DIGEST),
145 X(E_INVALID_PROTOCOL_FORMAT),
146 X_WIN32(ERROR_MOD_NOT_FOUND),
147 X_WIN32(ERROR_INSTALL_USEREXIT),
148 X_WIN32(ERROR_INSTALL_FAILURE),
149 X_WIN32(ERROR_SERVICE_DOES_NOT_EXIST),
150 X_WIN32(WSAENOTCONN),
151 X_WIN32(ERROR_FILE_EXISTS),
152 X_WIN32(ERROR_ALREADY_EXISTS),
153 X_WIN32(ERROR_INVALID_NAME),
154 X_WIN32(ERROR_NOT_SUPPORTED),
155 X_WIN32(ERROR_INVALID_HANDLE),
156 X_WIN32(ERROR_INVALID_DATA),
157 X(HCS_E_INVALID_JSON),
158 X_WIN32(ERROR_INVALID_SECURITY_DESCR),
159 X(VM_E_INVALID_STATE),
160 X_NT(STATUS_SHUTDOWN_IN_PROGRESS),
161 X(WININET_E_TIMEOUT),
162 X(WSAEADDRNOTAVAIL),
163 X_WIN32(ERROR_BAD_IMPERSONATION_LEVEL),
164 X_WIN32(ERROR_NO_DATA),
165 X_WIN32(WSAETIMEDOUT),
166 X_WIN32(ERROR_OPERATION_ABORTED),
167 X_WIN32(WSAECONNREFUSED),
168 X_WIN32(ERROR_BAD_PATHNAME),
169 X(WININET_E_TIMEOUT),
170 X_WIN32(ERROR_INVALID_SID),
171 X_WIN32(ERROR_INVALID_STATE),
172 X(WSLC_E_IMAGE_NOT_FOUND),
173 X(WSLC_E_CONTAINER_NOT_FOUND),
174 X(WSLC_E_VOLUME_NOT_FOUND),
175 X(WSLC_E_VOLUME_NOT_AVAILABLE),
176 X(WSLC_E_CONTAINER_NOT_RUNNING),
177 X(WSLC_E_CONTAINER_IS_RUNNING),
178 X(WSLC_E_SESSION_RESERVED),
179 X(WSLC_E_INVALID_SESSION_NAME),
180 X(WSLC_E_NETWORK_NOT_FOUND),
181 X(WSLC_E_SESSION_NOT_FOUND),
182 X(WSLC_E_VM_NOT_RUNNING),
183 X(WSLC_E_CONTAINER_DELETED),
184 X(WSLC_E_EVENTS_LOST),
185 X(WSLC_E_EVENT_STREAM_FINISHED),
186 X(WSLC_E_WU_SEARCH_FAILED),
187 X_WIN32(RPC_S_SERVER_UNAVAILABLE),
188 X_WIN32(ERROR_ELEVATION_REQUIRED),
189 X_WIN32(WSAEACCES),
190 X_WIN32(WSAEADDRINUSE),
191 X(CO_E_SERVER_EXEC_FAILURE),
192 X(E_ILLEGAL_STATE_CHANGE),
193 X(E_NOTIMPL),
194 X(E_OUTOFMEMORY),
195 X(FVE_E_LOCKED_VOLUME),
196 X(HCS_E_SYSTEM_ALREADY_STOPPED),
197 X(HCS_E_TERMINATED),
198 X(HTTP_E_STATUS_BAD_GATEWAY),
199 X(HTTP_E_STATUS_FORBIDDEN),
200 X(HTTP_E_STATUS_SERVICE_UNAVAIL),
201 X(RPC_E_CALL_CANCELED),
202 X(WININET_E_CONNECTION_ABORTED),
203 X(WININET_E_CONNECTION_RESET),
204 X(WININET_E_INVALID_CA),
205 X(WININET_E_INVALID_SERVER_RESPONSE),
206 X(WININET_E_SECURITY_CHANNEL_ERROR),
207 X_WIN32(ERROR_BAD_EXE_FORMAT),
208 X_WIN32(ERROR_BAD_INHERITANCE_ACL),
209 X_WIN32(ERROR_BAD_NET_NAME),
210 X_WIN32(ERROR_COMMITMENT_LIMIT),
211 X_WIN32(ERROR_CONNECTION_ABORTED),
212 X_WIN32(ERROR_DEVICE_REMOVED),
213 X_WIN32(ERROR_DISK_FULL),
214 X_WIN32(ERROR_DRIVE_LOCKED),
215 X_WIN32(ERROR_FILE_CORRUPT),
216 X_WIN32(ERROR_FILE_SYSTEM_LIMITATION),
217 X_WIN32(ERROR_GEN_FAILURE),
218 X_WIN32(ERROR_INSTALL_ALREADY_RUNNING),
219 X_WIN32(ERROR_INSTALL_LOG_FAILURE),
220 X_WIN32(ERROR_INSTALL_PACKAGE_OPEN_FAILED),
221 X_WIN32(ERROR_INVALID_DRIVE),
222 X_WIN32(ERROR_INVALID_FUNCTION),
223 X_WIN32(ERROR_LOGON_TYPE_NOT_GRANTED),
224 X_WIN32(ERROR_NETNAME_DELETED),
225 X_WIN32(ERROR_NO_SUCH_DEVICE),
226 X_WIN32(ERROR_NO_SYSTEM_RESOURCES),
227 X_WIN32(ERROR_NOT_READY),
228 X_WIN32(ERROR_NOT_SAFEBOOT_SERVICE),
229 X_WIN32(ERROR_SEM_TIMEOUT),
230 X_WIN32(ERROR_SERVICE_DISABLED),
231 X_WIN32(ERROR_SHUTDOWN_IN_PROGRESS),
232 X_WIN32(ERROR_SUCCESS_REBOOT_INITIATED),
233 X_WIN32(ERROR_TIMEOUT),
234 X_WIN32(ERROR_UNRECOGNIZED_VOLUME),
235 X_WIN32(ERROR_VIRTDISK_PROVIDER_NOT_FOUND),
236 X_WIN32(ERROR_VIRTUAL_DISK_LIMITATION),
237 X_WIN32(RPC_S_CALL_CANCELLED),
238 X_WIN32(RPC_S_CALL_FAILED_DNE),
239 X_WIN32(RPC_S_SYSTEM_HANDLE_TYPE_MISMATCH),
240 X_WIN32(RPC_S_UNKNOWN_IF),
241 X_WIN32(WSAECONNABORTED),
242 X_WIN32(WSAECONNRESET),
243 X_WIN32(WSAENOBUFS),
244 X_WIN32(WSAEOPNOTSUPP),
245 X_NT(STATUS_ACCESS_DENIED),
246 X_NT(STATUS_CONNECTION_DISCONNECTED),
247 X_NT(STATUS_INVALID_PARAMETER),
248 X_NT(STATUS_RETRY)};
249
250 #undef X_NT
251 #undef X_WIN32
252 #undef X
253
254 #define X(Ctx) {Context::Ctx, L## #Ctx}
255
256 static const std::map<Context, LPCWSTR> g_contextStrings{
257 X(Empty),
258 X(Wsl),
259 X(Wslg),
260 X(Bash),
261 X(WslConfig),
262 X(InstallDistro),
263 X(Service),
264 X(RegisterDistro),
265 X(CreateInstance),
266 X(AttachDisk),
267 X(DetachDisk),
268 X(CreateVm),
269 X(ParseConfig),
270 X(ConfigureNetworking),
271 X(ConfigureGpu),
272 X(LaunchProcess),
273 X(UpdatePackage),
274 X(ConfigureDistro),
275 X(CreateLxProcess),
276 X(EnumerateDistros),
277 X(ExportDistro),
278 X(GetDefaultDistro),
279 X(GetDistroConfiguration),
280 X(GetDistroId),
281 X(SetDefaultDistro),
282 X(SetVersion),
283 X(TerminateDistro),
284 X(UnregisterDistro),
285 X(RegisterLxBus),
286 X(MountDisk),
287 X(QueryLatestGitHubRelease),
288 X(DebugShell),
289 X(Plugin),
290 X(CallMsi),
291 X(Install),
292 X(HCS),
293 X(HNS),
294 X(ReadDistroConfig),
295 X(MoveDistro),
296 X(VerifyChecksum),
297 X(WslC)};
298
299 #undef X
300
301 wil::unique_hlocal_string GetWinInetErrorString(HRESULT error)
302 {
303 const wil::unique_hmodule library{LoadLibrary(L"WinInet.dll")};
304 if (!library)
305 {
306 return {};
307 }
308
309 wil::unique_hlocal_string message{};
310 LOG_HR_IF(
311 E_UNEXPECTED,
312 FormatMessageW(
313 (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK),
314 library.get(),
315 error - 0x80070000, // Mandatory to correctly resolve the error string
316 0,
317 wil::out_param_ptr<LPWSTR>(message),
318 0,
319 nullptr) == 0);
320
321 return message;
322 }
323
324 bool IsWinInetError(HRESULT error)
325 {
326 const DWORD code = error - 0x80070000;
327
328 return code >= INTERNET_ERROR_BASE && code <= INTERNET_ERROR_LAST;
329 }
330
331 constexpr uint16_t EndianSwap(uint16_t value)
332 {
333 return (value & 0xFF00) >> 8 | (value & 0x00FF) << 8;
334 }
335
336 constexpr uint32_t EndianSwap(uint32_t value)
337 {
338 return (value & 0xFF000000) >> 24 | (value & 0x00FF0000) >> 8 | (value & 0x0000FF00) << 8 | (value & 0x000000FF) << 24;
339 }
340
341 constexpr unsigned long EndianSwap(unsigned long value)
342 {
343 return gsl::narrow_cast<unsigned long>(EndianSwap(gsl::narrow_cast<uint32_t>(value)));
344 }
345
346 constexpr GUID EndianSwap(GUID value)
347 {
348 value.Data1 = EndianSwap(value.Data1);
349 value.Data2 = EndianSwap(value.Data2);
350 value.Data3 = EndianSwap(value.Data3);
351 return value;
352 }
353
354 std::regex BuildImageReferenceRegex()
355 {
356 // See: https://github.com/containers/image/blob/main/docker/reference/regexp.go
357
358 std::string alphaNum = "[a-z0-9]+";
359 std::string separator = "(?:[._]|__|[-]*)";
360 std::string domainComponent = "(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])";
361 std::string tag = "[\\w][\\w.-]{0,127}";
362 std::string digest = "[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}";
363
364 auto group = [](const auto& exp) { return std::format("(?:{})", exp); };
365 auto optional = [&group](const auto& exp) { return group(exp) + "?"; };
366 auto repeated = [&group](const auto& exp) { return group(exp) + "+"; };
367 auto capture = [](const auto& exp) { return std::format("({})", exp); };
368
369 auto nameComponent = alphaNum + optional(repeated(separator + alphaNum));
370 auto domain = domainComponent + optional(repeated("\\." + domainComponent)) + optional(":[0-9]+");
371 auto namePat = optional(domain + "\\/") + nameComponent + optional(repeated("\\/" + nameComponent));
372
373 return std::regex("^" + capture(namePat) + optional(":" + capture(tag)) + optional("@" + capture(digest)) + "$");
374 }
375
376 } // namespace
377
378 template <typename TInterface>
379 wil::com_ptr<TInterface> wsl::windows::common::wslutil::CoGetCallContext()
380 {
381 wil::com_ptr<TInterface> context;
382 const HRESULT hr = ::CoGetCallContext(IID_PPV_ARGS(&context));
383 THROW_HR_IF(hr, FAILED(hr) && (hr != RPC_E_CALL_COMPLETE));
384
385 return context;
386 }
387
388 void wsl::windows::common::wslutil::CoInitializeSecurity()
389 {
390 THROW_IF_FAILED(CoInitializeSecurity(
391 nullptr, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING, 0));
392 }
393
394 void wsl::windows::common::wslutil::ConfigureCrt()
395 {
396 // _CALL_REPORTFAULT will cause the process to actually crash instead of just exiting.
397 _set_abort_behavior(_CALL_REPORTFAULT, _CALL_REPORTFAULT);
398 }
399
400 // Copied from the terminal repository:
401 // https://github.com/microsoft/terminal/blob/52262b05fa0a97d2d3a0fce0990840ffc0fa53f1/src/types/utils.cpp#L926
402 GUID wsl::windows::common::wslutil::CreateV5Uuid(const GUID& namespaceGuid, const std::span<const std::byte> name)
403 {
404 // v5 uuid generation happens over values in network byte order, so let's enforce that
405 auto correctEndianNamespaceGuid{EndianSwap(namespaceGuid)};
406
407 wil::unique_bcrypt_hash hash;
408 THROW_IF_NTSTATUS_FAILED(BCryptCreateHash(BCRYPT_SHA1_ALG_HANDLE, &hash, nullptr, 0, nullptr, 0, 0));
409
410 // According to N4713 8.2.1.11 [basic.lval], accessing the bytes underlying an object
411 // through unsigned char or char pointer *is defined*.
412 THROW_IF_NTSTATUS_FAILED(BCryptHashData(hash.get(), reinterpret_cast<PUCHAR>(&correctEndianNamespaceGuid), sizeof(GUID), 0));
413 // BCryptHashData is ill-specified in that it leaves off "const" qualification for pbInput
414 THROW_IF_NTSTATUS_FAILED(
415 BCryptHashData(hash.get(), reinterpret_cast<PUCHAR>(const_cast<std::byte*>(name.data())), gsl::narrow<ULONG>(name.size()), 0));
416
417 std::array<uint8_t, 20> buffer;
418 THROW_IF_NTSTATUS_FAILED(BCryptFinishHash(hash.get(), buffer.data(), gsl::narrow<ULONG>(buffer.size()), 0));
419
420 buffer.at(6) = (buffer.at(6) & 0x0F) | 0x50; // set the uuid version to 5
421 buffer.at(8) = (buffer.at(8) & 0x3F) | 0x80; // set the variant to 2 (RFC4122)
422
423 // We're using memcpy here pursuant to N4713 6.7.2/3 [basic.types],
424 // "...the underlying bytes making up the object can be copied into an array
425 // of char or unsigned char...array is copied back into the object..."
426 // std::copy may compile down to ::memcpy for these types, but using it might
427 // contravene the standard and nobody's got time for that.
428 GUID newGuid{0};
429 ::memcpy_s(&newGuid, sizeof(GUID), buffer.data(), sizeof(GUID));
430 return EndianSwap(newGuid);
431 }
432
433 std::wstring wsl::windows::common::wslutil::DownloadFile(std::wstring_view Url, std::wstring Filename, bool reportProgress)
434 {
435 wsl::windows::common::ConsoleProgressBar progressBar;
436 auto progress = [&](auto current, auto total) {
437 if (reportProgress)
438 {
439 progressBar.Print(current, total);
440 }
441 return true;
442 };
443
444 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
445 if (reportProgress)
446 {
447 progressBar.Clear();
448 }
449 });
450
451 return DownloadFileImpl(Url, Filename, progress);
452 }
453
454 std::wstring wsl::windows::common::wslutil::DownloadFileImpl(
455 std::wstring_view Url, std::wstring Filename, const std::function<void(uint64_t, uint64_t)>& Progress)
456 {
457 const auto lastSlash = Url.find_last_of('/');
458 THROW_HR_IF(E_INVALIDARG, lastSlash == std::wstring::npos);
459
460 if (Filename.empty())
461 {
462 Filename = Url.substr(lastSlash + 1);
463 }
464
465 // GetFolderFromPathAsync won't work if the folder is hidden or system.
466 auto downloadFolderPath = std::filesystem::temp_directory_path();
467 auto filenameStem = std::filesystem::path(Filename).stem().wstring();
468 auto filenameExtension = std::filesystem::path(Filename).extension().wstring();
469 std::wstring filePath{};
470 winrt::Windows::Storage::Streams::IRandomAccessStream outputStream{};
471 for (int suffix = 1; outputStream == nullptr; suffix++)
472 {
473 if (suffix == 1)
474 {
475 filePath = (downloadFolderPath / Filename).wstring();
476 }
477 else
478 {
479 filePath = (downloadFolderPath / std::format(L"{} ({}){}", filenameStem, suffix, filenameExtension)).wstring();
480 }
481 try
482 {
483 outputStream = winrt::Windows::Storage::Streams::FileRandomAccessStream::OpenAsync(
484 filePath,
485 winrt::Windows::Storage::FileAccessMode::ReadWrite,
486 winrt::Windows::Storage::StorageOpenOptions::None,
487 winrt::Windows::Storage::Streams::FileOpenDisposition::CreateNew)
488 .get();
489 }
490 catch (...)
491 {
492 if (wil::ResultFromCaughtException() != HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
493 {
494 throw;
495 }
496 }
497 }
498
499 auto deleteFileOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
500 outputStream.Close();
501 std::error_code ec;
502 std::filesystem::remove(filePath, ec);
503 });
504
505 // By default downloaded files are cached in %appdata%/local/packages/{package-family}/AC/InetCache .
506 // Disable caching since there's no reason to keep local copies of .msixbundle files.
507 const winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter;
508 filter.CacheControl().WriteBehavior(winrt::Windows::Web::Http::Filters::HttpCacheWriteBehavior::NoCache);
509
510 const winrt::Windows::Web::Http::HttpClient client(filter);
511 client.DefaultRequestHeaders().Append(L"Accept", L"application/octet-stream");
512 client.DefaultRequestHeaders().Append(L"User-Agent", c_userAgent);
513 const auto asyncResponse = client.GetInputStreamAsync(winrt::Windows::Foundation::Uri(Url));
514
515 std::atomic<uint64_t> totalBytes;
516 asyncResponse.Progress(
517 [&](const winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Windows::Storage::Streams::IInputStream, winrt::Windows::Web::Http::HttpProgress>&,
518 const winrt::Windows::Web::Http::HttpProgress& progress) {
519 if (progress.TotalBytesToReceive)
520 {
521 totalBytes = progress.TotalBytesToReceive.GetUInt64();
522 }
523 });
524
525 auto download = winrt::Windows::Storage::Streams::RandomAccessStream::CopyAsync(asyncResponse.get(), outputStream);
526
527 download.Progress([&](const auto& _, uint64_t progress) {
528 if (totalBytes != 0)
529 {
530 Progress(progress, totalBytes);
531 }
532 });
533
534 download.get();
535 deleteFileOnFailure.release();
536
537 return filePath;
538 }
539
540 [[nodiscard]] HANDLE wsl::windows::common::wslutil::DuplicateHandle(_In_ HANDLE Handle, _In_ std::optional<DWORD> DesiredAccess, _In_ BOOL InheritHandle)
541 {
542 HANDLE newHandle;
543 THROW_IF_WIN32_BOOL_FALSE(::DuplicateHandle(
544 GetCurrentProcess(), Handle, GetCurrentProcess(), &newHandle, DesiredAccess.value_or(0), InheritHandle, DesiredAccess.has_value() ? 0 : DUPLICATE_SAME_ACCESS));
545
546 return newHandle;
547 }
548
549 [[nodiscard]] HANDLE wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(_In_ HANDLE Handle, _In_ std::optional<DWORD> DesiredAccess)
550 {
551 const wil::unique_handle caller = OpenCallingProcess(PROCESS_DUP_HANDLE);
552 THROW_LAST_ERROR_IF(!caller);
553
554 HANDLE newHandle;
555 THROW_IF_WIN32_BOOL_FALSE(::DuplicateHandle(
556 caller.get(), Handle, GetCurrentProcess(), &newHandle, DesiredAccess.value_or(0), FALSE, DesiredAccess.has_value() ? 0 : DUPLICATE_SAME_ACCESS));
557
558 return newHandle;
559 }
560
561 [[nodiscard]] HANDLE wsl::windows::common::wslutil::DuplicateHandleToCallingProcess(_In_ HANDLE Handle, _In_ std::optional<DWORD> DesiredAccess)
562 {
563 const wil::unique_handle caller = OpenCallingProcess(PROCESS_DUP_HANDLE);
564 THROW_LAST_ERROR_IF(!caller);
565
566 HANDLE newHandle;
567 THROW_IF_WIN32_BOOL_FALSE(::DuplicateHandle(
568 GetCurrentProcess(), Handle, caller.get(), &newHandle, DesiredAccess.value_or(0), FALSE, DesiredAccess.has_value() ? 0 : DUPLICATE_SAME_ACCESS));
569
570 return newHandle;
571 }
572
573 void wsl::windows::common::wslutil::EnforceFileLimit(LPCWSTR Path, size_t Limit, const std::function<bool(const std::filesystem::directory_entry&)>& pred)
574 {
575 if (Limit <= 0)
576 {
577 return;
578 }
579
580 std::map<std::filesystem::file_time_type, std::filesystem::path> files;
581 for (auto const& e : std::filesystem::directory_iterator{Path})
582 {
583 if (pred(e))
584 {
585 files.emplace(e.last_write_time(), e.path());
586 }
587 }
588
589 if (files.size() < Limit)
590 {
591 return;
592 }
593
594 auto fileToRemove = files.begin()->second.c_str();
595
596 WSL_LOG(
597 "File limit exceeded, deleting oldest file", TraceLoggingValue(Path, "Folder"), TraceLoggingValue(fileToRemove, "File"));
598
599 LOG_IF_WIN32_BOOL_FALSE(DeleteFile(fileToRemove));
600 }
601
602 std::wstring wsl::windows::common::wslutil::ErrorCodeToString(HRESULT Error)
603 {
604 std::wstringstream output;
605 const auto resultString = g_commonErrors.find(Error);
606 if (resultString != g_commonErrors.end())
607 {
608 output << resultString->second;
609 }
610 else
611 {
612 output << L"0x" << std::hex << Error;
613 }
614
615 return output.str();
616 }
617
618 wsl::windows::common::ErrorStrings wsl::windows::common::wslutil::ErrorToString(const Error& error)
619 {
620 ErrorStrings errorStrings{.Source = error.Source};
621
622 if (error.Message.has_value())
623 {
624 errorStrings.Message = error.Message.value();
625 }
626 else
627 {
628 errorStrings.Message = GetErrorString(error.Code);
629 }
630
631 std::wstringstream errorCode;
632 bool first = true;
633 const std::bitset<64> bits(error.Context);
634 for (auto i = 0; i < bits.size(); i++)
635 {
636 if (bits[i])
637 {
638 auto context = static_cast<Context>(1ull << i);
639 auto it = g_contextStrings.find(context);
640 if (first)
641 {
642 first = false;
643 }
644 else
645 {
646 errorCode << L"/";
647 }
648
649 if (it == g_contextStrings.end())
650 {
651 errorCode << L"?(" << context << L")";
652 }
653 else
654 {
655 errorCode << it->second;
656 }
657 }
658 }
659
660 errorCode << "/" << ErrorCodeToString(error.Code);
661
662 errorStrings.Code = errorCode.str();
663
664 return errorStrings;
665 }
666
667 [[nodiscard]] HANDLE wsl::windows::common::wslutil::FromCOMInputHandle(WSLCHandle Handle)
668 {
669 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE), Handle.Handle.File == nullptr || Handle.Handle.File == INVALID_HANDLE_VALUE);
670
671 switch (Handle.Type)
672 {
673 case WSLCHandleTypeFile:
674 return Handle.Handle.File;
675 case WSLCHandleTypePipe:
676 return Handle.Handle.Pipe;
677 case WSLCHandleTypeSocket:
678 return Handle.Handle.Socket;
679 default:
680 THROW_HR_MSG(E_UNEXPECTED, "Unsupported handle type: %d", Handle.Type);
681 }
682 }
683
684 std::wstring wsl::windows::common::wslutil::ConstructPipePath(std::wstring_view PipeName)
685 {
686 return c_pipePrefix + std::wstring(PipeName);
687 }
688
689 std::filesystem::path wsl::windows::common::wslutil::GetBasePath()
690 {
691 auto path = wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle());
692 THROW_IF_FAILED(::PathCchRemoveFileSpec(path.data(), path.size()));
693
694 path.resize(std::wcslen(path.c_str()));
695 return std::filesystem::path(std::move(path));
696 }
697
698 std::optional<COMErrorInfo> wsl::windows::common::wslutil::GetCOMErrorInfo()
699 {
700 wil::com_ptr<IErrorInfo> errorInfo;
701 THROW_IF_FAILED(GetErrorInfo(0, &errorInfo));
702
703 if (!errorInfo)
704 {
705 return {};
706 }
707
708 COMErrorInfo error{};
709
710 THROW_IF_FAILED(errorInfo->GetDescription(&error.Message));
711 THROW_IF_FAILED(errorInfo->GetSource(&error.Source));
712
713 return error;
714 }
715
716 std::wstring wsl::windows::common::wslutil::GetDebugShellPipeName(_In_ PSID Sid)
717 {
718 return ConstructPipePath(std::wstring(L"wsl_debugshell_") + SidToString(Sid).get());
719 }
720
721 DWORD
722 wsl::windows::common::wslutil::GetDefaultVersion(void)
723 {
724 DWORD version = LXSS_WSL_VERSION_2;
725 const auto hr = [&version] {
726 wil::unique_hkey userKey{};
727 RETURN_IF_WIN32_ERROR(RegOpenCurrentUser(KEY_READ, &userKey));
728
729 wil::unique_hkey lxssKey{};
730 RETURN_IF_WIN32_ERROR(RegOpenKeyEx(userKey.get(), LXSS_REGISTRY_PATH, 0, KEY_READ, &lxssKey));
731
732 DWORD size = sizeof(version);
733 RETURN_IF_WIN32_ERROR(RegGetValueW(lxssKey.get(), nullptr, LXSS_WSL_DEFAULT_VERSION, RRF_RT_REG_DWORD, nullptr, &version, &size));
734
735 return S_OK;
736 }();
737
738 if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) && (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)))
739 {
740 THROW_IF_FAILED(hr);
741 }
742
743 return version;
744 }
745
746 namespace {
747
748 // Returns true if Windows Admin Protection (shadow admin) is enabled on
749 // this system. The message is shown for both elevated and non-elevated
750 // callers because either side may be missing the other's distributions.
751 // Caches the DLL lookup on first call.
752 bool IsAdminProtectionEnabled()
753 {
754 using ShadowAdminEnabledFn = BOOL(WINAPI)();
755 static std::optional<LxssDynamicFunction<ShadowAdminEnabledFn>> s_fn;
756 static std::once_flag s_initFlag;
757
758 std::call_once(s_initFlag, []() {
759 LxssDynamicFunction<ShadowAdminEnabledFn> fn{DynamicFunctionErrorLogs::None};
760 if (SUCCEEDED(fn.load(L"SecurityHealthUdk.dll", "Shield_LUAIsShadowAdminEnabled")))
761 {
762 s_fn.emplace(std::move(fn));
763 }
764 });
765
766 return s_fn.has_value() && (*s_fn)();
767 }
768
769 } // anonymous namespace
770
771 std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result)
772 {
773 ULONG buildNumber = 0;
774 std::wstring kbUrl;
775 std::wstring errorString;
776
777 switch (result)
778 {
779 case E_ILLEGAL_STATE_CHANGE:
780 return Localization::MessageInvalidState();
781
782 case WSL_E_USER_NOT_FOUND:
783 return Localization::MessageUserNotFound();
784
785 case WSL_E_CONSOLE:
786 return Localization::MessageInvalidConsole();
787
788 case WSL_E_LOWER_INTEGRITY:
789 return Localization::MessageLowerIntegrity();
790
791 case WSL_E_HIGHER_INTEGRITY:
792 return Localization::MessageHigherIntegrity();
793
794 case WSL_E_DEFAULT_DISTRO_NOT_FOUND:
795 errorString = Localization::MessageNoDefaultDistro();
796 break;
797
798 case WSL_E_DISTRO_NOT_FOUND:
799 errorString = Localization::MessageDistroNotFound();
800 break;
801
802 case WSL_E_DISTRIBUTION_NAME_NEEDED:
803 return Localization::MessageDistributionNameNeeded();
804
805 case WSL_E_TOO_MANY_DISKS_ATTACHED:
806 return Localization::MessageTooManyDisks();
807
808 case WSL_E_USER_VHD_ALREADY_ATTACHED:
809 return Localization::MessageUserVhdAlreadyAttached();
810
811 case WSL_E_VM_MODE_NOT_SUPPORTED:
812 return Localization::MessageVmModeNotSupported();
813
814 case HCS_E_HYPERV_NOT_INSTALLED:
815 return Localization::MessageEnableVirtualization();
816
817 case WSL_E_VM_MODE_INVALID_STATE:
818 return Localization::MessageAlreadyRequestedVersion();
819
820 case WSL_E_WSL2_NEEDED:
821 return Localization::MessageWsl2Needed();
822
823 case WSL_E_WSL1_NOT_SUPPORTED:
824 return Localization::MessageWsl1NotSupported();
825
826 case WSL_E_DISTRO_ONLY_AVAILABLE_FROM_STORE:
827 return Localization::MessageDistroOnlyAvailableFromStore();
828
829 case WSL_E_WSL_MOUNT_NOT_SUPPORTED:
830 return Localization::MessageWslMountNotSupportedOnArm();
831
832 case WSL_E_WSL_OPTIONAL_COMPONENT_REQUIRED:
833 return Localization::MessageWslOptionalComponentRequired();
834
835 case WSL_E_EXPORT_FAILED:
836 return Localization::MessageExportFailed();
837
838 case WSL_E_IMPORT_FAILED:
839 return Localization::MessageImportFailed();
840
841 case WSL_E_DISTRO_NOT_STOPPED:
842 return Localization::MessageVhdInUse();
843
844 case WSL_E_OS_NOT_SUPPORTED:
845 buildNumber = helpers::GetWindowsVersion().BuildNumber;
846 if (buildNumber >= helpers::WindowsBuildNumbers::Cobalt)
847 {
848 kbUrl = L"https://aka.ms/store-wsl-kb-win11";
849 }
850 else if (buildNumber >= helpers::WindowsBuildNumbers::Iron)
851 {
852 kbUrl = L"https://aka.ms/store-wsl-kb-winserver2022";
853 }
854 else if (buildNumber >= helpers::WindowsBuildNumbers::Vibranium)
855 {
856 kbUrl = L"https://aka.ms/store-wsl-kb-win10";
857 }
858 else
859 {
860 // Don't throw from here, the caller might be in a catch block.
861 kbUrl = std::format(L"[Unexpected build number: {}]", buildNumber);
862 }
863
864 return Localization::MessageOsNotSupported(helpers::GetWindowsVersionString().c_str(), kbUrl.c_str());
865
866 // All the errors below this comment are not supposed to be reachable here (since there's meant to be emitted from the
867 // service). But if we somehow hit them here, it's better show something useful to the user.
868 case WSL_E_VM_MODE_MOUNT_NAME_ALREADY_EXISTS:
869 return Localization::MessageDiskMountNameAlreadyExists();
870
871 case WSL_E_VM_MODE_INVALID_MOUNT_NAME:
872 return Localization::MessageDiskMountNameInvalid();
873
874 case WSL_E_ELEVATION_NEEDED_TO_MOUNT_DISK:
875 return Localization::MessageElevationNeededToMountDisk();
876
877 case WSL_E_DISK_ALREADY_ATTACHED:
878 return Localization::MessageDiskAlreadyAttached(L"");
879
880 case WSL_E_DISK_ALREADY_MOUNTED:
881 return Localization::MessageDiskAlreadyMounted();
882
883 case WSL_E_CUSTOM_KERNEL_NOT_FOUND:
884 return Localization::MessageCustomKernelNotFound(helpers::GetWslConfigPath().c_str(), L"");
885
886 case WSL_E_CUSTOM_SYSTEM_DISTRO_ERROR:
887 return Localization::MessageCustomSystemDistroError(helpers::GetWslConfigPath().c_str());
888
889 case WSL_E_GUI_APPLICATIONS_DISABLED:
890 return Localization::GuiApplicationsDisabled(helpers::GetWslConfigPath().c_str());
891
892 case WSL_E_VMSWITCH_NOT_FOUND:
893 return Localization::MessageVmSwitchNotFound(L"", L"");
894
895 case WSL_E_VMSWITCH_NOT_SET:
896 return Localization::MessageVmSwitchNotSet();
897
898 case WSL_E_DISK_MOUNT_DISABLED:
899 return Localization::MessageWSLMountDisabled();
900
901 case WSL_E_VIRTUAL_MACHINE_PLATFORM_REQUIRED:
902 return Localization::MessageVirtualMachinePlatformNotInstalled();
903
904 case WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED:
905 return Localization::MessageLocalSystemNotSupported();
906
907 case WSL_E_DISK_CORRUPTED:
908 return Localization::MessageDiskCorrupted();
909
910 case WSL_E_NOT_A_LINUX_DISTRO:
911 return Localization::MessageInvalidDistributionTar();
912
913 case WSLC_E_CONTAINER_DISABLED:
914 return Localization::MessageWSLContainerDisabled();
915
916 case WSL_E_INVALID_USAGE:
917 {
918 const auto* context = wsl::windows::common::ExecutionContext::Current();
919 if (context == nullptr)
920 {
921 // Should be unreachable, but better fallback on something.
922 break;
923 }
924
925 if (WI_IsFlagSet(context->CurrentContext(), Context::Wsl))
926 {
927 return wsl::shared::Localization::MessageWslUsage();
928 }
929 else if (WI_IsFlagSet(context->CurrentContext(), Context::Wslg))
930 {
931 return wsl::shared::Localization::MessageWslgUsage();
932 }
933 else if (WI_IsFlagSet(context->CurrentContext(), Context::WslConfig))
934 {
935 return wsl::shared::Localization::MessageWslconfigUsage();
936 }
937 }
938 }
939
940 if (errorString.empty())
941 {
942 return GetSystemErrorString(result);
943 }
944
945 // If Admin Protection is enabled, prepend an informational message for
946 // errors that may be caused by the shadow admin's separate registry hive.
947 try
948 {
949 if (IsAdminProtectionEnabled())
950 {
951 auto message = Localization::MessageAdminProtectionEnabled();
952 message += L"\n\n";
953 message += errorString;
954 return message;
955 }
956 }
957 CATCH_LOG()
958
959 return errorString;
960 }
961
962 std::optional<std::pair<std::wstring, GitHubReleaseAsset>> wsl::windows::common::wslutil::GetGitHubAssetFromRelease(const GitHubRelease& Release)
963 {
964 auto findAsset = [&Release](LPCWSTR Suffix) {
965 for (const auto& asset : Release.assets)
966 {
967 std::wstring filename(asset.name.size(), '\0');
968 std::transform(asset.name.begin(), asset.name.end(), filename.begin(), towlower);
969
970 if (wsl::shared::string::EndsWith<wchar_t>(filename, Suffix))
971 {
972 return std::make_optional(std::make_pair(Release.name, asset));
973 }
974 }
975
976 return std::optional<std::pair<std::wstring, GitHubReleaseAsset>>();
977 };
978
979 // Look for an MSI package first
980 auto asset = findAsset(wsl::shared::Arm64 ? L".arm64.msi" : L".x64.msi");
981 if (asset.has_value())
982 {
983 return asset.value();
984 }
985
986 // If none was found, look for an msixbundle
987 asset = findAsset(L".msixbundle");
988
989 return asset.value();
990 }
991
992 std::pair<std::wstring, GitHubReleaseAsset> wsl::windows::common::wslutil::GetLatestGitHubRelease(bool preRelease)
993 {
994 ExecutionContext context(Context::QueryLatestGitHubRelease);
995
996 auto registryKey = registry::OpenLxssMachineKey();
997 const auto url =
998 registry::ReadString(registryKey.get(), nullptr, c_githubUrlOverrideRegistryValue, preRelease ? c_releaseListUrl : c_latestReleaseUrl);
999 WSL_LOG("PollLatestGitHubRelease", TraceLoggingValue(url.c_str(), "url"));
1000
1001 winrt::Windows::Web::Http::HttpClient client;
1002 client.DefaultRequestHeaders().Append(L"User-Agent", c_userAgent);
1003 const auto response = client.GetAsync(winrt::Windows::Foundation::Uri(url)).get();
1004 response.EnsureSuccessStatusCode();
1005
1006 return GetLatestGitHubRelease(preRelease, response.Content().ReadAsStringAsync().get().c_str());
1007 }
1008
1009 std::pair<std::wstring, GitHubReleaseAsset> wsl::windows::common::wslutil::GetLatestGitHubRelease(bool preRelease, LPCWSTR releases)
1010 {
1011 std::optional<GitHubRelease> parsed{};
1012
1013 if (preRelease)
1014 {
1015 std::optional<std::tuple<uint32_t, uint32_t, uint32_t>> highestVersion;
1016 for (const auto& e : wsl::shared::FromJson<std::vector<GitHubRelease>>(releases))
1017 {
1018 auto version = ParseWslPackageVersion(e.name);
1019 if (!highestVersion.has_value() || version > highestVersion)
1020 {
1021 parsed.emplace(std::move(e));
1022 highestVersion = version;
1023 }
1024 }
1025 }
1026 else
1027 {
1028 parsed = wsl::shared::FromJson<GitHubRelease>(releases);
1029 }
1030
1031 THROW_HR_IF(E_UNEXPECTED, !parsed.has_value());
1032
1033 // Find the latest release with an msix package asset
1034 auto asset = GetGitHubAssetFromRelease(parsed.value());
1035 THROW_HR_IF_MSG(E_UNEXPECTED, !asset.has_value(), "No suitable WSL release found on github");
1036
1037 return asset.value();
1038 }
1039
1040 GitHubRelease wsl::windows::common::wslutil::GetGitHubReleaseByTag(_In_ const std::wstring& inTag)
1041 {
1042 ExecutionContext context(Context::QueryLatestGitHubRelease);
1043
1044 const winrt::Windows::Web::Http::HttpClient client;
1045 client.DefaultRequestHeaders().Append(L"User-Agent", c_userAgent);
1046 const auto url = c_specificReleaseListUrl + inTag;
1047 const auto response = client.GetAsync(winrt::Windows::Foundation::Uri(url)).get();
1048 response.EnsureSuccessStatusCode();
1049
1050 const auto content = response.Content().ReadAsStringAsync().get();
1051
1052 return wsl::shared::FromJson<GitHubRelease>(content.c_str());
1053 }
1054
1055 int wsl::windows::common::wslutil::GetLogicalProcessorCount()
1056 {
1057 std::vector<gsl::byte> buffer;
1058 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX information = nullptr;
1059 DWORD length = 0;
1060 while (!GetLogicalProcessorInformationEx(RelationProcessorCore, information, &length))
1061 {
1062 const DWORD error = GetLastError();
1063 if (error == ERROR_INSUFFICIENT_BUFFER)
1064 {
1065 WI_ASSERT(buffer.size() < length);
1066 buffer.resize(length);
1067 information = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(buffer.data());
1068 }
1069 else
1070 {
1071 THROW_WIN32_MSG(error, "GetLogicalProcessorInformationEx");
1072 }
1073 }
1074
1075 int processorCount = 0;
1076 for (size_t offset = 0; offset < buffer.size(); offset += information->Size)
1077 {
1078 information = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(&buffer[offset]);
1079 for (WORD group = 0; group < information->Processor.GroupCount; group += 1)
1080 {
1081 processorCount += std::popcount(information->Processor.GroupMask[group].Mask);
1082 }
1083 }
1084
1085 return processorCount;
1086 }
1087
1088 std::optional<std::wstring> wsl::windows::common::wslutil::GetMsiPackagePath()
1089 {
1090 const auto key = OpenLxssMachineKey(KEY_READ);
1091
1092 try
1093 {
1094 return ReadString(key.get(), L"Msi", L"InstallLocation", nullptr);
1095 }
1096 catch (...)
1097 {
1098 return {};
1099 }
1100 }
1101
1102 std::wstring wsl::windows::common::wslutil::GetPackageFamilyName(_In_ HANDLE process)
1103 {
1104 std::wstring packageFamilyName;
1105 UINT32 length = 0;
1106 switch (::GetPackageFamilyName(process, &length, nullptr))
1107 {
1108 case APPMODEL_ERROR_NO_PACKAGE:
1109 break;
1110
1111 case ERROR_INSUFFICIENT_BUFFER:
1112 packageFamilyName.resize(length);
1113 THROW_IF_WIN32_ERROR(::GetPackageFamilyName(process, &length, packageFamilyName.data()));
1114
1115 break;
1116
1117 default:
1118 THROW_LAST_ERROR_MSG("GetPackageFamilyName");
1119 }
1120
1121 return packageFamilyName;
1122 }
1123
1124 std::wstring wsl::windows::common::wslutil::GetSystemErrorString(_In_ HRESULT result)
1125 {
1126 wil::unique_hlocal_string message{};
1127
1128 // Special treatment for wininet errors
1129 if (IsWinInetError(result))
1130 {
1131 message = GetWinInetErrorString(result);
1132 }
1133
1134 if (!message)
1135 {
1136 LOG_HR_IF(
1137 E_UNEXPECTED,
1138 FormatMessageW(
1139 (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK),
1140 nullptr,
1141 result,
1142 0,
1143 wil::out_param_ptr<LPWSTR>(message),
1144 0,
1145 nullptr) == 0);
1146 }
1147
1148 if (message)
1149 {
1150 return std::wstring{message.get()};
1151 }
1152
1153 std::wstringstream stream;
1154 stream << std::hex << result;
1155 return std::wstring(L"Error: 0x" + stream.str());
1156 }
1157
1158 std::vector<BYTE> wsl::windows::common::wslutil::HashFile(HANDLE file, DWORD Algorithm)
1159 {
1160 wil::unique_hcryptprov provider;
1161 THROW_IF_WIN32_BOOL_FALSE(CryptAcquireContext(&provider, nullptr, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT | CRYPT_SILENT));
1162
1163 wil::unique_hcrypthash hash;
1164 THROW_IF_WIN32_BOOL_FALSE(CryptCreateHash(provider.get(), Algorithm, 0, 0, &hash));
1165
1166 constexpr auto bufferSize = 10 * 1024 * 1024; // 10 MB.
1167 std::vector<char> buffer(bufferSize);
1168
1169 DWORD readBytes{};
1170 while (true)
1171 {
1172 THROW_IF_WIN32_BOOL_FALSE(ReadFile(file, buffer.data(), bufferSize, &readBytes, nullptr));
1173 if (readBytes == 0)
1174 {
1175 break;
1176 }
1177
1178 THROW_IF_WIN32_BOOL_FALSE(CryptHashData(hash.get(), reinterpret_cast<const BYTE*>(buffer.data()), readBytes, 0));
1179 }
1180
1181 std::vector<BYTE> fileHash(32);
1182 DWORD hashSize = static_cast<DWORD>(fileHash.size());
1183
1184 THROW_IF_WIN32_BOOL_FALSE(CryptGetHashParam(hash.get(), HP_HASHVAL, fileHash.data(), &hashSize, 0));
1185 THROW_HR_IF(E_UNEXPECTED, hashSize != fileHash.size());
1186
1187 return fileHash;
1188 }
1189
1190 std::optional<std::tuple<uint32_t, uint32_t, uint32_t>> wsl::windows::common::wslutil::GetInstalledPackageVersion()
1191 {
1192 std::wstring packageVersion;
1193 auto result = wil::ResultFromException([&]() {
1194 auto msiKey = wsl::windows::common::registry::OpenLxssMachineKey(KEY_READ);
1195
1196 packageVersion = wsl::windows::common::registry::ReadString(msiKey.get(), L"Msi", L"Version");
1197 });
1198
1199 if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
1200 {
1201 return {};
1202 }
1203
1204 THROW_IF_FAILED(result);
1205
1206 return ParseWslPackageVersion(packageVersion);
1207 }
1208
1209 void wsl::windows::common::wslutil::InitializeWil()
1210 {
1211 wil::WilInitialize_CppWinRT();
1212
1213 if constexpr (!wsl::shared::Debug)
1214 {
1215 wil::g_fResultFailFastUnknownExceptions = false;
1216 }
1217 }
1218
1219 bool wsl::windows::common::wslutil::IsConsoleHandle(HANDLE Handle)
1220 {
1221 DWORD Mode;
1222 return GetFileType(Handle) == FILE_TYPE_CHAR && GetConsoleMode(Handle, &Mode);
1223 }
1224
1225 bool wsl::windows::common::wslutil::IsInteractiveConsole()
1226 {
1227 return IsConsoleHandle(GetStdHandle(STD_INPUT_HANDLE));
1228 }
1229
1230 bool wsl::windows::common::wslutil::IsRunningInMsix()
1231 {
1232 UINT32 dummy{};
1233 const auto result = GetCurrentPackageId(&dummy, nullptr);
1234
1235 if (result == NOERROR || result == ERROR_INSUFFICIENT_BUFFER)
1236 {
1237 return true;
1238 }
1239 else
1240 {
1241 // It's safer to return false by default since returning true incorrectly could create an infinity of wsl.exe.
1242 LOG_HR_IF_MSG(E_UNEXPECTED, result != APPMODEL_ERROR_NO_PACKAGE, "Unexpected error from: %ld", result);
1243 return false;
1244 }
1245 }
1246
1247 bool wsl::windows::common::wslutil::IsVhdFile(_In_ const std::filesystem::path& path)
1248 {
1249 return wsl::windows::common::string::IsPathComponentEqual(path.extension().native(), c_vhdFileExtension) ||
1250 wsl::windows::common::string::IsPathComponentEqual(path.extension().native(), c_vhdxFileExtension);
1251 }
1252
1253 std::vector<DWORD> wsl::windows::common::wslutil::ListRunningProcesses()
1254 {
1255 std::vector<DWORD> pids(1024);
1256 DWORD bytesReturned = 0;
1257 while (!EnumProcesses(pids.data(), (DWORD)pids.size() * sizeof(DWORD), &bytesReturned))
1258 {
1259 THROW_LAST_ERROR_IF(GetLastError() != ERROR_MORE_DATA);
1260
1261 pids.resize(pids.size() * 2);
1262 }
1263
1264 pids.resize(bytesReturned / sizeof(DWORD));
1265
1266 return pids;
1267 }
1268
1269 wsl::windows::common::wslutil::RepositoryReference wsl::windows::common::wslutil::RepositoryReference::Parse(const std::string& input)
1270 {
1271 // See: https://github.com/distribution/reference/blob/ff14fafe2236e51c2894ac07d4bdfc778e96d682/normalize.go#L126
1272
1273 constexpr auto defaultDomain = "docker.io";
1274 constexpr auto officialPrefix = "library/";
1275 constexpr auto legacyDomain = "index.docker.io";
1276 constexpr auto localhost = "localhost";
1277
1278 auto slash = input.find('/');
1279 if (slash == std::string::npos)
1280 {
1281 return RepositoryReference{input, defaultDomain, officialPrefix + input};
1282 }
1283
1284 auto domain = input.substr(0, slash);
1285 auto path = input.substr(slash + 1);
1286
1287 if (domain == legacyDomain)
1288 {
1289 domain = defaultDomain;
1290 }
1291 else if (domain != localhost && domain.find_first_of(".:") == std::string::npos && !std::ranges::any_of(domain, [](unsigned char e) {
1292 return std::isupper(e);
1293 }))
1294 {
1295 domain = defaultDomain;
1296 path = input;
1297 }
1298
1299 if (domain == defaultDomain && path.find('/') == std::string::npos)
1300 {
1301 path = "library/" + path;
1302 }
1303
1304 return RepositoryReference{input, std::move(domain), std::move(path)};
1305 }
1306
1307 std::string wsl::windows::common::wslutil::RepositoryReference::GetCanonical() const
1308 {
1309 return std::format("{}/{}", Server, Path);
1310 }
1311
1312 std::pair<wil::unique_hfile, wil::unique_hfile> wsl::windows::common::wslutil::OpenAnonymousPipe(DWORD Size, bool ReadPipeOverlapped, bool WritePipeOverlapped)
1313 {
1314 // Default to 4096 byte buffer, just like CreatePipe().
1315 if (Size == 0)
1316 {
1317 Size = 4096;
1318 }
1319
1320 // Open the pipe device. Performing a relative open against this will
1321 // create an anonymous pipe.
1322 const wil::unique_hfile pipeDevice{
1323 CreateFileW(L"\\\\.\\pipe\\", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr)};
1324
1325 THROW_LAST_ERROR_IF(!pipeDevice);
1326
1327 LARGE_INTEGER timeout{};
1328 timeout.QuadPart = -10 * 1000 * 1000 * 120; // 120 seconds (doesn't actually matter)
1329
1330 UNICODE_STRING empty{};
1331 OBJECT_ATTRIBUTES objectAttributes;
1332 InitializeObjectAttributes(&objectAttributes, &empty, 0, pipeDevice.get(), nullptr);
1333
1334 IO_STATUS_BLOCK ioStatusBlock{};
1335
1336 wil::unique_hfile readPipe;
1337 THROW_IF_NTSTATUS_FAILED(NtCreateNamedPipeFile(
1338 &readPipe,
1339 GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
1340 &objectAttributes,
1341 &ioStatusBlock,
1342 FILE_SHARE_READ | FILE_SHARE_WRITE,
1343 FILE_CREATE,
1344 ReadPipeOverlapped ? 0 : FILE_SYNCHRONOUS_IO_NONALERT,
1345 0,
1346 0,
1347 0,
1348 1,
1349 Size,
1350 Size,
1351 &timeout));
1352
1353 InitializeObjectAttributes(&objectAttributes, &empty, 0, readPipe.get(), nullptr);
1354
1355 wil::unique_hfile writePipe;
1356 THROW_IF_NTSTATUS_FAILED(NtOpenFile(
1357 &writePipe,
1358 GENERIC_WRITE | SYNCHRONIZE | FILE_READ_ATTRIBUTES,
1359 &objectAttributes,
1360 &ioStatusBlock,
1361 FILE_SHARE_READ | FILE_SHARE_WRITE,
1362 (WritePipeOverlapped ? 0 : FILE_SYNCHRONOUS_IO_NONALERT) | FILE_NON_DIRECTORY_FILE));
1363
1364 return {std::move(readPipe), std::move(writePipe)};
1365 }
1366
1367 bool wsl::windows::common::wslutil::IsVirtualMachinePlatformInstalled()
1368 {
1369 // Note for Windows 11 22H2 and above builds: If hyper-v is installed but VMP platform isn't, HNS and vmcompute are
1370 // available but calls to HNS will fail if vfpext isn't installed.
1371 if (!wsl::windows::common::helpers::IsServicePresent(L"HNS") || !wsl::windows::common::helpers::IsServicePresent(L"vmcompute") ||
1372 (helpers::GetWindowsVersion().BuildNumber >= helpers::WindowsBuildNumbers::Nickel &&
1373 !wsl::windows::common::helpers::IsServicePresent(L"vfpext")))
1374 {
1375 return false;
1376 }
1377
1378 try
1379 {
1380 return WslInstall::IsOptionalComponentInstalled(WslInstall::c_optionalFeatureNameVmp);
1381 }
1382 CATCH_LOG()
1383
1384 return true;
1385 }
1386
1387 wil::unique_handle wsl::windows::common::wslutil::OpenCallingProcess(_In_ DWORD access)
1388 {
1389 wil::unique_handle caller{};
1390 const auto context = wsl::windows::common::wslutil::CoGetCallContext<ICallingProcessInfo>();
1391 if (context)
1392 {
1393 THROW_IF_FAILED(context->OpenCallerProcessHandle(access, &caller));
1394 }
1395
1396 return caller;
1397 }
1398
1399 void wsl::windows::common::wslutil::ParseIpv4Address(const char* Address, in_addr& Result)
1400 {
1401 if (inet_pton(AF_INET, Address, &Result) != 1)
1402 {
1403 THROW_HR_WITH_USER_ERROR(E_INVALIDARG, wsl::shared::Localization::MessageInvalidIp(Address));
1404 }
1405 }
1406
1407 void wsl::windows::common::wslutil::ParseIpv6Address(const char* Address, in_addr6& Result)
1408 {
1409 if (inet_pton(AF_INET6, Address, &Result) != 1)
1410 {
1411 THROW_HR_WITH_USER_ERROR(E_INVALIDARG, wsl::shared::Localization::MessageInvalidIp(Address));
1412 }
1413 }
1414
1415 std::tuple<uint32_t, uint32_t, uint32_t> wsl::windows::common::wslutil::ParseWslPackageVersion(_In_ const std::wstring& Version)
1416 {
1417 const std::wregex pattern(L"(\\d+)\\.(\\d+)\\.(\\d+).*");
1418 std::wsmatch match;
1419 if (!std::regex_match(Version, match, pattern) || match.size() != 4)
1420 {
1421 THROW_HR_MSG(E_UNEXPECTED, "Failed to parse WSL package version: '%ls'", Version.c_str());
1422 }
1423
1424 auto get = [&](int position) { return std::stoul(match.str(position)); };
1425
1426 try
1427 {
1428 return std::make_tuple(get(1), get(2), get(3));
1429 }
1430 catch (const std::exception& e)
1431 {
1432 THROW_HR_MSG(E_UNEXPECTED, "Failed to parse WSL package version: '%ls', %hs", Version.c_str(), e.what());
1433 }
1434 }
1435
1436 wsl::windows::common::wslutil::ImageReference wsl::windows::common::wslutil::ImageReference::Parse(const std::string& input)
1437 {
1438 static const auto regex = BuildImageReferenceRegex();
1439 std::smatch match;
1440 if (!std::regex_match(input, match, regex))
1441 {
1442 THROW_HR_WITH_USER_ERROR(E_INVALIDARG, wsl::shared::Localization::MessageWslcInvalidImage(input.c_str()));
1443 }
1444
1445 const auto& repo = match[1];
1446 const auto& tag = match[2];
1447 const auto& digest = match[3];
1448
1449 THROW_HR_IF_MSG(E_UNEXPECTED, !repo.matched, "Unexpected regex match. Input: %hs", input.c_str());
1450
1451 std::optional<std::string> tagValue;
1452 if (tag.matched)
1453 {
1454 tagValue = tag.str();
1455 }
1456
1457 std::optional<std::string> digestValue;
1458 if (digest.matched)
1459 {
1460 digestValue = digest.str();
1461 }
1462
1463 // Classify the reference the way the Docker CLI does, where a digest takes precedence over a tag.
1464 EnumReferenceFormat format = EnumReferenceFormatNone;
1465 if (digestValue.has_value())
1466 {
1467 format = EnumReferenceFormatDigest;
1468 }
1469 else if (tagValue.has_value())
1470 {
1471 format = EnumReferenceFormatTag;
1472 }
1473
1474 return ImageReference{RepositoryReference::Parse(repo.str()), std::move(tagValue), std::move(digestValue), format};
1475 }
1476
1477 std::string wsl::windows::common::wslutil::ImageReference::GetCanonical() const
1478 {
1479 // Mirror the Docker CLI's client-side reference normalization so the result matches `docker pull` exactly.
1480 // See github.com/distribution/reference (normalize.go, reference.go) and github.com/docker/cli
1481 // (cli/command/image/pull.go). Docker's canonical string keeps both a tag and a digest when both are present.
1482
1483 // A tag joins with ':' and a digest with '@'. A name-only reference (no tag and no digest) defaults to ":latest";
1484 // a digest-only reference is not name-only, so it keeps no tag (matching Docker's TagNameOnly).
1485 const std::string tag = Tag ? std::format(":{}", *Tag) : (Digest ? "" : ":latest");
1486 const std::string digest = Digest ? std::format("@{}", *Digest) : "";
1487 return std::format("{}{}{}", Repository.GetCanonical(), tag, digest);
1488 }
1489
1490 void wsl::windows::common::wslutil::PrintSystemError(_In_ HRESULT result, _Inout_ FILE* const stream)
1491 {
1492 fwprintf(stream, L"%ls\n", GetSystemErrorString(result).c_str());
1493 }
1494
1495 void wsl::windows::common::wslutil::PrintMessageImpl(_In_ const std::wstring& message, _In_ va_list& args, _Inout_ FILE* const stream)
1496 {
1497 vfwprintf(stream, message.c_str(), args);
1498 fputws(L"\n", stream);
1499 }
1500
1501 void wsl::windows::common::wslutil::PrintMessageImpl(_In_ const std::wstring& message, _Inout_ FILE* const stream, ...)
1502 {
1503 va_list arguments{};
1504 va_start(arguments, stream);
1505 auto vaEnd = wil::scope_exit([&arguments] { va_end(arguments); });
1506 PrintMessageImpl(message, arguments, stream);
1507 }
1508
1509 void wsl::windows::common::wslutil::PrintMessage(_In_ const std::wstring& message, _Inout_ FILE* const stream)
1510 {
1511 fwprintf(stream, L"%ls\n", message.c_str());
1512 }
1513
1514 std::optional<std::wstring> wsl::windows::common::wslutil::ReadEnvironmentVariable(_In_ LPCWSTR Name)
1515 {
1516 std::wstring value;
1517 const HRESULT hr = wil::GetEnvironmentVariableW(Name, value);
1518 if (hr == HRESULT_FROM_WIN32(ERROR_ENVVAR_NOT_FOUND))
1519 {
1520 return std::nullopt;
1521 }
1522
1523 THROW_IF_FAILED(hr);
1524 return value;
1525 }
1526
1527 void wsl::windows::common::wslutil::SetCrtEncoding(int Mode)
1528 {
1529 // Configure the CRT to manipulate text as the specified mode.
1530 auto setMode = [](FILE* stream, int Mode) {
1531 const auto fileNumber = _fileno(stream);
1532 if (fileNumber >= 0)
1533 {
1534 WI_VERIFY(_setmode(fileNumber, Mode) != -1);
1535 }
1536 };
1537
1538 setMode(stdin, Mode);
1539 setMode(stdout, Mode);
1540 setMode(stderr, Mode);
1541
1542 // Set the locale to the current environment's default locale for regional
1543 // formatting (numeric, time, collation), then override LC_CTYPE to UTF-8
1544 // so that narrow-to-wide conversions (e.g. %hs in wprintf) correctly decode
1545 // UTF-8 multi-byte sequences from Linux/container processes.
1546 WI_VERIFY(_wsetlocale(LC_ALL, L"") != NULL);
1547 if (Mode == _O_U8TEXT)
1548 {
1549 WI_VERIFY(_wsetlocale(LC_CTYPE, L".UTF-8") != NULL);
1550 }
1551 }
1552
1553 void wsl::windows::common::wslutil::SetThreadDescription(LPCWSTR Name)
1554 {
1555 LOG_IF_FAILED(::SetThreadDescription(GetCurrentThread(), Name));
1556 }
1557
1558 wil::unique_hlocal_string wsl::windows::common::wslutil::SidToString(_In_ PSID UserSid)
1559 {
1560 wil::unique_hlocal_string sid;
1561 THROW_LAST_ERROR_IF(!ConvertSidToStringSid(UserSid, &sid));
1562
1563 return sid;
1564 }
1565
1566 WSLCHandle wsl::windows::common::wslutil::ToCOMOutputHandle(HANDLE Handle, DWORD Access)
1567 {
1568 wil::unique_handle duplicatedHandle{DuplicateHandle(Handle, Access)};
1569
1570 // N.B. COM closes the handle when returning an out parameter.
1571 // The return value of this method should always be passed to a COM out parameter.
1572 auto comHandle = ToCOMInputHandle(duplicatedHandle.release());
1573
1574 return comHandle;
1575 }
1576
1577 WSLCHandle wsl::windows::common::wslutil::ToCOMOutputHandle(HANDLE Handle, DWORD Access, WSLCHandleType Type)
1578 {
1579 wil::unique_handle duplicatedHandle{DuplicateHandle(Handle, Access)};
1580
1581 // N.B. COM closes the handle when returning an out parameter.
1582 // The return value of this method should always be passed to a COM out parameter.
1583 switch (Type)
1584 {
1585 case WSLCHandleTypeFile:
1586 return WSLCHandle{.Type = WSLCHandleTypeFile, .Handle = {.File = duplicatedHandle.release()}};
1587 case WSLCHandleTypePipe:
1588 return WSLCHandle{.Type = WSLCHandleTypePipe, .Handle = {.Pipe = duplicatedHandle.release()}};
1589 case WSLCHandleTypeSocket:
1590 return WSLCHandle{.Type = WSLCHandleTypeSocket, .Handle = {.Socket = duplicatedHandle.release()}};
1591 default:
1592 THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), "Unsupported handle type: %d", static_cast<int>(Type));
1593 }
1594 }
1595
1596 WSLCHandle wsl::windows::common::wslutil::ToCOMInputHandle(HANDLE Handle)
1597 {
1598 auto type = GetFileType(Handle);
1599 if (type == FILE_TYPE_PIPE)
1600 {
1601 int socketType{};
1602 int len = sizeof(socketType);
1603
1604 // N.B. FILE_TYPE_PIPE can describe a pipe, a named pipe, or a socket.
1605 // Check for a named pipe first, since getsockopt() can return success for a named pipe.
1606
1607 if (GetNamedPipeInfo(Handle, nullptr, nullptr, nullptr, nullptr))
1608 {
1609 return WSLCHandle{.Type = WSLCHandleTypePipe, .Handle = {.Pipe = Handle}};
1610 }
1611 else if (getsockopt(reinterpret_cast<SOCKET>(Handle), SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&socketType), &len) == 0)
1612 {
1613 return WSLCHandle{.Type = WSLCHandleTypeSocket, .Handle = {.Socket = Handle}};
1614 }
1615 else
1616 {
1617 return WSLCHandle{.Type = WSLCHandleTypePipe, .Handle = {.Pipe = Handle}};
1618 }
1619 }
1620 else if (type == FILE_TYPE_DISK)
1621 {
1622 return WSLCHandle{.Type = WSLCHandleTypeFile, .Handle = {.File = Handle}};
1623 }
1624 else
1625 {
1626 THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), "Unsupported handle type: %d", type);
1627 }
1628 }
1629
1630 winrt::Windows::Management::Deployment::PackageVolume wsl::windows::common::wslutil::GetSystemVolume()
1631 try
1632 {
1633 const auto packageManager = winrt::Windows::Management::Deployment::PackageManager();
1634 const auto volumes = packageManager.FindPackageVolumes();
1635 for (auto volume : volumes)
1636 {
1637 if (volume.IsSystemVolume())
1638 {
1639 return volume;
1640 }
1641 }
1642
1643 WSL_LOG("GetSystemVolumeNotFound");
1644 return nullptr;
1645 }
1646 catch (...)
1647 {
1648 LOG_CAUGHT_EXCEPTION();
1649 return nullptr;
1650 }
1651
1652 std::string wsl::windows::common::wslutil::Base64Encode(const std::string& input)
1653 {
1654 DWORD base64Size = 0;
1655 THROW_IF_WIN32_BOOL_FALSE(CryptBinaryToStringA(
1656 reinterpret_cast<const BYTE*>(input.c_str()), static_cast<DWORD>(input.size()), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Size));
1657
1658 auto buffer = std::make_unique<char[]>(base64Size);
1659 THROW_IF_WIN32_BOOL_FALSE(CryptBinaryToStringA(
1660 reinterpret_cast<const BYTE*>(input.c_str()),
1661 static_cast<DWORD>(input.size()),
1662 CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
1663 buffer.get(),
1664 &base64Size));
1665
1666 return std::string(buffer.get());
1667 }
1668
1669 std::string wsl::windows::common::wslutil::Base64Decode(const std::string& encoded)
1670 {
1671 DWORD size = 0;
1672 THROW_IF_WIN32_BOOL_FALSE(CryptStringToBinaryA(
1673 encoded.c_str(), static_cast<DWORD>(encoded.size()), CRYPT_STRING_BASE64, nullptr, &size, nullptr, nullptr));
1674
1675 std::string result(size, '\0');
1676 THROW_IF_WIN32_BOOL_FALSE(CryptStringToBinaryA(
1677 encoded.c_str(), static_cast<DWORD>(encoded.size()), CRYPT_STRING_BASE64, reinterpret_cast<BYTE*>(result.data()), &size, nullptr, nullptr));
1678
1679 result.resize(size);
1680 return result;
1681 }
1682
1683 std::string wsl::windows::common::wslutil::BuildRegistryAuthHeader(const std::string& username, const std::string& password)
1684 {
1685 nlohmann::json authJson = {{"username", username}, {"password", password}};
1686 return Base64Encode(authJson.dump());
1687 }
1688
1689 std::string wsl::windows::common::wslutil::BuildRegistryAuthHeader(const std::string& identityToken)
1690 {
1691 nlohmann::json authJson = {{"identitytoken", identityToken}};
1692 return Base64Encode(authJson.dump());
1693 }
1694
1695 std::map<std::string, std::string> wsl::windows::common::wslutil::ParseKeyValuePairs(const KeyValuePair* pairs, ULONG count, LPCSTR reservedKey)
1696 {
1697 THROW_HR_IF(E_POINTER, count > 0 && pairs == nullptr);
1698
1699 std::map<std::string, std::string> result;
1700
1701 for (ULONG i = 0; i < count; i++)
1702 {
1703 THROW_HR_IF_NULL_MSG(E_INVALIDARG, pairs[i].Key, "Key at index %lu is null", i);
1704 THROW_HR_IF_NULL_MSG(E_INVALIDARG, pairs[i].Value, "Value at index %lu is null", i);
1705
1706 if (reservedKey != nullptr)
1707 {
1708 THROW_HR_IF_MSG(E_INVALIDARG, strcmp(pairs[i].Key, reservedKey) == 0, "Key '%hs' is reserved", reservedKey);
1709 }
1710
1711 THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), result.contains(pairs[i].Key), "Duplicate key: '%hs'", pairs[i].Key);
1712
1713 result[pairs[i].Key] = pairs[i].Value;
1714 }
1715
1716 return result;
1717 }
1718
1719 std::map<std::string, std::vector<std::string>> wsl::windows::common::wslutil::ParseKeyMultiValuePairs(const KeyValuePair* pairs, ULONG count)
1720 {
1721 THROW_HR_IF(E_POINTER, count > 0 && pairs == nullptr);
1722
1723 std::map<std::string, std::vector<std::string>> result;
1724
1725 for (ULONG i = 0; i < count; i++)
1726 {
1727 THROW_HR_IF_NULL_MSG(E_POINTER, pairs[i].Key, "Key at index %lu is null", i);
1728 THROW_HR_IF_NULL_MSG(E_POINTER, pairs[i].Value, "Value at index %lu is null", i);
1729
1730 result[pairs[i].Key].emplace_back(pairs[i].Value);
1731 }
1732
1733 return result;
1734 }