master
cpp 823 lines 28.3 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 helpers.cpp
8
9 Abstract:
10
11 This file contains helper function definitions.
12
13 --*/
14
15 #include "precomp.h"
16 #include "helpers.hpp"
17 #include "Stringify.h"
18 #include "svccomm.hpp"
19 #include "socket.hpp"
20 #include "hvsocket.hpp"
21 #include "relay.hpp"
22 #include "LxssMessagePort.h"
23 #include <gsl/algorithm>
24 #include <gslhelpers.h>
25 #include "registry.hpp"
26 #include "versionhelpers.h"
27 #include <regstr.h>
28
29 // Version numbers for various functionality that was backported.
30
31 #define VIRTIO_SERIAL_CONSOLE_COBALT_RELEASE_UBR 40
32 #define NICKEL_BUILD_FLOOR 22350
33 #define VMEMM_SUFFIX_COBALT_REFRESH_BUILD_NUMBER 22138
34 #define VMMEM_SUFFIX_COBALT_RELEASE_UBR 71
35 #define VMMEM_SUFFIX_NICKEL_BUILD_NUMBER 22420
36
37 using wsl::windows::common::helpers::LaunchWslRelayFlags;
38
39 constexpr auto c_WslSupportInterfaceKey = L"Software\\Classes\\Interface\\{46f3c96d-ffa3-42f0-b052-52f5e7ecbb08}";
40 constexpr auto c_WslSupportInterfaceName = L"IWslSupport";
41 constexpr auto c_DcatRegistryVersionValueName = L"Version";
42
43 constexpr ULONG c_MaxStorageHwQueues = 4;
44
45 namespace {
46
47 class ProcessLauncher
48 {
49 public:
50 ProcessLauncher(LPCWSTR executable) : m_executable(executable)
51 {
52 }
53
54 ProcessLauncher(LPCWSTR executable, LPCWSTR commandLine) : m_executable(executable), m_commandLine(commandLine)
55 {
56 }
57
58 ProcessLauncher(const ProcessLauncher&) = delete;
59 ProcessLauncher& operator=(const ProcessLauncher&) = delete;
60
61 ProcessLauncher(ProcessLauncher&& other) noexcept
62 {
63 *this = std::move(other);
64 }
65
66 ProcessLauncher& operator=(ProcessLauncher&& other) noexcept
67 {
68 std::swap(m_executable, other.m_executable);
69 std::swap(m_commandLine, other.m_commandLine);
70 std::swap(m_handles, other.m_handles);
71 return *this;
72 }
73
74 void AddOption(LPCWSTR OptionName, LPCWSTR OptionValue = nullptr)
75 {
76 m_commandLine += L' ';
77 m_commandLine += OptionName;
78 if (OptionValue)
79 {
80 m_commandLine += L" ";
81 m_commandLine += OptionValue;
82 }
83 };
84
85 void AddGuidOption(LPCWSTR OptionName, LPCGUID Guid)
86 {
87 if (ARGUMENT_PRESENT(Guid))
88 {
89 AddOption(OptionName, wsl::shared::string::GuidToString<wchar_t>(*Guid).c_str());
90 }
91 };
92
93 void AddHandleOption(LPCWSTR OptionName, HANDLE Handle)
94 {
95 if (ARGUMENT_PRESENT(Handle))
96 {
97 AddOption(OptionName, std::to_wstring(HandleToUlong(Handle)).c_str());
98 m_handles.push_back(Handle);
99 wsl::windows::common::helpers::SetHandleInheritable(Handle);
100 }
101 };
102
103 [[nodiscard]] wil::unique_handle Launch(
104 _In_opt_ HANDLE UserToken, _In_ bool HideWindow, _In_ bool CreateNoWindow = false, _In_opt_ HANDLE JobObject = nullptr) const
105 {
106 // If a user token was provided, create an environment block from the token.
107 wsl::windows::common::helpers::unique_environment_block environmentBlock{nullptr};
108 if (ARGUMENT_PRESENT(UserToken))
109 {
110 THROW_LAST_ERROR_IF(!CreateEnvironmentBlock(&environmentBlock, UserToken, false));
111 }
112
113 wsl::windows::common::SubProcess process(m_executable.data(), m_commandLine.data(), CREATE_UNICODE_ENVIRONMENT);
114
115 for (const auto e : m_handles)
116 {
117 process.InheritHandle(e);
118 }
119
120 if (HideWindow)
121 {
122 process.SetShowWindow(SW_HIDE);
123 }
124
125 if (CreateNoWindow)
126 {
127 process.SetFlags(CREATE_NO_WINDOW);
128 }
129
130 process.SetEnvironment(environmentBlock.get());
131 process.SetToken(UserToken);
132 process.SetJobObject(JobObject);
133
134 // Launch the process.
135 return process.Start();
136 }
137
138 private:
139 std::wstring m_executable;
140 std::wstring m_commandLine;
141 std::vector<HANDLE> m_handles;
142 };
143
144 [[nodiscard]] wil::unique_handle LaunchWslHost(
145 _In_opt_ LPCGUID DistroId,
146 _In_opt_ HANDLE InteropHandle,
147 _In_opt_ HANDLE EventHandle,
148 _In_opt_ HANDLE ParentHandle,
149 _In_opt_ LPCGUID VmId,
150 _In_opt_ HANDLE UserToken,
151 _In_opt_ HANDLE JobObject = nullptr)
152 {
153 // Construct the command line.
154 //
155 // N.B. The two places that launch wslhost.exe are the wsl.exe the service.
156 const auto path = wsl::windows::common::wslutil::GetBasePath();
157
158 // Format the command line.
159 ProcessLauncher launcher((path / L"wslhost.exe").c_str());
160 launcher.AddGuidOption(wslhost::distro_id_option, DistroId);
161 launcher.AddGuidOption(wslhost::vm_id_option, VmId);
162 launcher.AddHandleOption(wslhost::handle_option, InteropHandle);
163 launcher.AddHandleOption(wslhost::event_option, EventHandle);
164 launcher.AddHandleOption(wslhost::parent_option, ParentHandle);
165 return launcher.Launch(UserToken, true, false, JobObject);
166 }
167
168 [[nodiscard]] wil::unique_handle LaunchWslRelay(
169 _In_ wslrelay::RelayMode Mode,
170 _In_opt_ HANDLE InteropHandle,
171 _In_opt_ LPCGUID VmId,
172 _In_opt_ HANDLE PipeHandle,
173 _In_opt_ std::optional<int> Port,
174 _In_opt_ HANDLE ExitEvent,
175 _In_opt_ HANDLE UserToken,
176 _In_ LaunchWslRelayFlags Flags,
177 _In_opt_ HANDLE JobObject = nullptr)
178 {
179 // Construct the command line.
180 //
181 // N.B. The two places that launch wslrelay.exe are the wsl.exe the service.
182 const auto path = wsl::windows::common::wslutil::GetBasePath();
183
184 // Format the command line.
185 ProcessLauncher launcher((path / L"wslrelay.exe").c_str());
186 launcher.AddOption(wslrelay::mode_option, std::to_wstring(Mode).c_str());
187 launcher.AddGuidOption(wslrelay::vm_id_option, VmId);
188 launcher.AddHandleOption(wslrelay::handle_option, InteropHandle);
189 launcher.AddHandleOption(wslrelay::pipe_option, PipeHandle);
190 launcher.AddHandleOption(wslrelay::exit_event_option, ExitEvent);
191 if (Port)
192 {
193 launcher.AddOption(wslrelay::port_option, std::to_wstring(Port.value()).c_str());
194 }
195
196 if (WI_IsFlagSet(Flags, LaunchWslRelayFlags::DisableTelemetry))
197 {
198 launcher.AddOption(wslrelay::disable_telemetry_option);
199 }
200
201 if (WI_IsFlagSet(Flags, LaunchWslRelayFlags::ConnectPipe))
202 {
203 launcher.AddOption(wslrelay::connect_pipe_option);
204 }
205
206 return launcher.Launch(UserToken, WI_IsFlagSet(Flags, LaunchWslRelayFlags::HideWindow), false, JobObject);
207 }
208 } // namespace
209
210 void wsl::windows::common::helpers::ConnectPipe(_In_ HANDLE Pipe, _In_ DWORD Timeout, _In_ const std::vector<HANDLE>& ExitEvents)
211 {
212 const wil::unique_event OverlappedEvent(wil::EventOptions::ManualReset);
213 OVERLAPPED Overlapped = {0};
214 Overlapped.hEvent = OverlappedEvent.get();
215 if (!ConnectNamedPipe(Pipe, &Overlapped))
216 {
217 switch (GetLastError())
218 {
219 case ERROR_PIPE_CONNECTED:
220 break;
221
222 case ERROR_IO_PENDING:
223 {
224 DWORD Bytes;
225 auto Cancel = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
226 CancelIoEx(Pipe, &Overlapped);
227 GetOverlappedResult(Pipe, &Overlapped, &Bytes, TRUE);
228 });
229
230 std::vector<HANDLE> WaitHandles;
231 WaitHandles.push_back(Overlapped.hEvent);
232 for (auto ExitEvent : ExitEvents)
233 {
234 WaitHandles.push_back(ExitEvent);
235 }
236
237 const auto Result = WaitForMultipleObjects(gsl::narrow_cast<DWORD>(WaitHandles.size()), WaitHandles.data(), FALSE, Timeout);
238 if (!ExitEvents.empty() && Result > WAIT_OBJECT_0 && Result < WAIT_OBJECT_0 + WaitHandles.size())
239 {
240 THROW_HR(E_ABORT);
241 }
242
243 THROW_LAST_ERROR_IF(Result != WAIT_OBJECT_0);
244
245 Cancel.release();
246 THROW_IF_WIN32_BOOL_FALSE(GetOverlappedResult(Pipe, &Overlapped, &Bytes, FALSE));
247 }
248
249 break;
250
251 default:
252 THROW_LAST_ERROR();
253 }
254 }
255 }
256
257 std::wstring_view wsl::windows::common::helpers::ConsumeArgument(_In_ std::wstring_view CommandLine, _In_ std::wstring_view Argument)
258 {
259 WI_ASSERT((CommandLine.size() >= Argument.size()) && (wcsncmp(CommandLine.data(), Argument.data(), Argument.size()) == 0));
260
261 CommandLine.remove_prefix(Argument.size());
262 return string::StripLeadingWhitespace(CommandLine);
263 }
264
265 void wsl::windows::common::helpers::CreateConsole(_In_ LPCWSTR ConsoleTitle)
266 {
267 THROW_IF_WIN32_BOOL_FALSE(AllocConsole());
268 WI_VERIFY(wsl::windows::common::helpers::ReopenStdHandles());
269 if (ConsoleTitle != nullptr)
270 {
271 LOG_IF_WIN32_BOOL_FALSE(SetConsoleTitleW(ConsoleTitle));
272 }
273 }
274
275 wsl::windows::common::helpers::unique_proc_attribute_list wsl::windows::common::helpers::CreateProcThreadAttributeList(_In_ DWORD AttributeCount)
276 {
277 SIZE_T Size = 0;
278 if (!InitializeProcThreadAttributeList(nullptr, AttributeCount, 0, &Size))
279 {
280 THROW_LAST_ERROR_IF(GetLastError() != ERROR_INSUFFICIENT_BUFFER);
281 }
282
283 unique_proc_attribute_list List(reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(CoTaskMemAlloc(Size)));
284 THROW_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(List.get(), AttributeCount, 0, &Size));
285
286 return List;
287 }
288
289 wil::unique_handle wsl::windows::common::helpers::CreateKillOnCloseJob()
290 {
291 wil::unique_handle job{CreateJobObjectW(nullptr, nullptr)};
292 THROW_LAST_ERROR_IF(!job);
293
294 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo{};
295 jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
296 THROW_IF_WIN32_BOOL_FALSE(SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo)));
297
298 return job;
299 }
300
301 std::vector<gsl::byte> wsl::windows::common::helpers::GenerateConfigurationMessage(
302 _In_ const std::wstring& DistributionName,
303 _In_ ULONG FixedDrivesBitmap,
304 _In_ ULONG DefaultUid,
305 _In_ const std::string& Timezone,
306 _In_ const std::wstring& Plan9SocketPath,
307 _In_ ULONG FeatureFlags,
308 _In_ LX_INIT_DRVFS_MOUNT DrvfsMount)
309 {
310 auto [hostName, domainName] = filesystem::GetHostAndDomainNames();
311
312 std::string windowsHosts;
313
314 // If DNS tunneling is enabled, we don't need to reflect the Windows hosts file in Linux, as the
315 // Windows DNS client will use the Windows hosts file for tunneled DNS requests
316 if (!WI_IsFlagSet(FeatureFlags, LxInitFeatureDnsTunneling))
317 {
318 // Parse the Windows hosts file.
319 //
320 // N.B. failures generating the hosts string are non-fatal.
321 try
322 {
323
324 // Parse the Windows hosts file.
325 std::wstring SystemDirectory;
326 THROW_IF_FAILED(wil::GetSystemDirectoryW(SystemDirectory));
327
328 windowsHosts =
329 filesystem::GetWindowsHosts(std::filesystem::path(std::move(SystemDirectory)) / L"drivers" / L"etc" / L"hosts");
330 }
331 CATCH_LOG()
332 }
333
334 shared::MessageWriter<LX_INIT_CONFIGURATION_INFORMATION> message(LxInitMessageInitialize);
335 message->DrvFsVolumesBitmap = FixedDrivesBitmap;
336 message->DrvFsDefaultOwner = DefaultUid;
337 message->FeatureFlags = FeatureFlags;
338 message->DrvfsMount = DrvfsMount;
339 message.WriteString(message->HostnameOffset, hostName);
340 message.WriteString(message->DomainnameOffset, domainName);
341 message.WriteString(message->WindowsHostsOffset, windowsHosts);
342 message.WriteString(message->DistributionNameOffset, DistributionName);
343 message.WriteString(message->Plan9SocketOffset, Plan9SocketPath);
344 message.WriteString(message->TimezoneOffset, Timezone);
345
346 return message.MoveBuffer();
347 }
348
349 std::vector<gsl::byte> wsl::windows::common::helpers::GenerateTimezoneUpdateMessage(_In_ std::string_view Timezone)
350 {
351 // Construct the timezone update message.
352 shared::MessageWriter<LX_INIT_TIMEZONE_INFORMATION> message(LxInitMessageTimezoneInformation);
353 message.WriteString(message->TimezoneOffset, Timezone);
354
355 return message.MoveBuffer();
356 }
357
358 std::string wsl::windows::common::helpers::GetLinuxTimezone(_In_opt_ HANDLE UserToken)
359 {
360 std::string timezone{};
361 try
362 {
363 // If a user token was specified, impersonate to get the per-user region settings.
364 auto runAsSelf = UserToken ? wil::impersonate_token(UserToken) : wil::run_as_self();
365
366 // Query the system region.
367 std::vector<WCHAR> geoName;
368 int length;
369 do
370 {
371 length = GetUserDefaultGeoName(nullptr, 0);
372 THROW_LAST_ERROR_IF(length == 0);
373
374 geoName.resize(length + 1);
375 length = GetUserDefaultGeoName(geoName.data(), length);
376 } while ((length == 0) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER));
377
378 THROW_LAST_ERROR_IF(length == 0);
379
380 const auto region = wsl::shared::string::WideToMultiByte(geoName.data());
381
382 // Query the Windows timezone.
383 DYNAMIC_TIME_ZONE_INFORMATION zoneInfo;
384 THROW_LAST_ERROR_IF(GetDynamicTimeZoneInformation(&zoneInfo) == TIME_ZONE_ID_INVALID);
385
386 UErrorCode status = U_ZERO_ERROR;
387 auto windowsId = reinterpret_cast<const UChar*>(zoneInfo.TimeZoneKeyName);
388 const auto size = ucal_getTimeZoneIDForWindowsID(windowsId, -1, region.c_str(), nullptr, 0, &status);
389
390 // If no mapping exists, return an empty string.
391 THROW_HR_IF_MSG(
392 E_UNEXPECTED,
393 size == 0,
394 "GetTimeZoneIDForWindowsID(%ls, -1, %hs, nullptr, 0, &status) returned %d",
395 zoneInfo.TimeZoneKeyName,
396 region.c_str(),
397 status);
398
399 WI_ASSERT(status == UErrorCode::U_BUFFER_OVERFLOW_ERROR);
400
401 std::vector<UChar> buffer(size + 1);
402 status = U_ZERO_ERROR;
403 WI_VERIFY(ucal_getTimeZoneIDForWindowsID(windowsId, -1, region.c_str(), buffer.data(), size, &status) == size);
404
405 THROW_HR_IF_MSG(E_FAIL, (U_FAILURE(status) != false), "%hs", u_errorName(status));
406
407 timezone.resize(size);
408 u_UCharsToChars(buffer.data(), timezone.data(), static_cast<int32_t>(timezone.size()));
409 }
410 CATCH_LOG()
411
412 return timezone;
413 }
414
415 wsl::windows::common::helpers::WindowsVersion wsl::windows::common::helpers::GetWindowsVersion()
416 {
417 static WindowsVersion version;
418 static std::once_flag flag;
419 std::call_once(flag, [&]() {
420 const auto regKey = registry::OpenKey(HKEY_LOCAL_MACHINE, REGSTR_PATH_NT_CURRENTVERSION, KEY_READ);
421 const auto majorVersion = registry::ReadDword(regKey.get(), nullptr, L"CurrentMajorVersionNumber", 0);
422 const auto minorVersion = registry::ReadDword(regKey.get(), nullptr, L"CurrentMinorVersionNumber", 0);
423 const auto buildNumberString = registry::ReadString(regKey.get(), nullptr, REGSTR_VAL_CURRENT_BUILD, L"0");
424 const auto buildNumber = wcstoul(buildNumberString.c_str(), nullptr, 10);
425 const auto revision = registry::ReadDword(regKey.get(), nullptr, L"UBR", 0);
426 version = {majorVersion, minorVersion, buildNumber, revision};
427 });
428
429 return version;
430 }
431
432 std::wstring wsl::windows::common::helpers::GetUniquePipeName()
433 {
434 GUID pipeId;
435 THROW_IF_FAILED(CoCreateGuid(&pipeId));
436 return wslutil::ConstructPipePath(wsl::shared::string::GuidToString<wchar_t>(pipeId));
437 }
438
439 std::filesystem::path wsl::windows::common::helpers::GetUserProfilePath(_In_opt_ HANDLE userToken)
440 {
441 if (userToken != nullptr)
442 {
443 // N.B. stringSize includes the null terminator.
444 DWORD stringSize = 0;
445 ::GetUserProfileDirectoryW(userToken, nullptr, &stringSize);
446 WI_ASSERT(stringSize > 0);
447
448 std::wstring path(stringSize - 1, L'\0');
449 THROW_IF_WIN32_BOOL_FALSE(::GetUserProfileDirectoryW(userToken, path.data(), &stringSize));
450
451 return std::filesystem::path(std::move(path));
452 }
453 else
454 {
455 wil::unique_cotaskmem_string profileDir;
456 THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &profileDir));
457
458 return std::filesystem::path(profileDir.get());
459 }
460 }
461
462 std::string wsl::windows::common::helpers::GetWindowsVersionString()
463 {
464 std::string versionString{};
465 try
466 {
467 const auto version = GetWindowsVersion();
468 std::stringstream stream;
469 stream << version.MajorVersion << "." << version.MinorVersion << "." << version.BuildNumber << "." << version.UpdateBuildRevision;
470 versionString = stream.str();
471 }
472 CATCH_LOG()
473
474 return versionString;
475 }
476
477 std::filesystem::path wsl::windows::common::helpers::GetWslConfigPath(_In_opt_ HANDLE userToken)
478 {
479 return wsl::windows::common::helpers::GetUserProfilePath(userToken) / L".wslconfig";
480 }
481
482 bool wsl::windows::common::helpers::IsPackageInstalled(_In_ LPCWSTR PackageFamilyName)
483 {
484 UINT32 packageCount = 0;
485 UINT32 bufferSize = 0;
486 const auto result = GetPackagesByPackageFamily(PackageFamilyName, &packageCount, nullptr, &bufferSize, nullptr);
487
488 THROW_HR_IF(HRESULT_FROM_WIN32(result), result != ERROR_INSUFFICIENT_BUFFER && result != STATUS_SUCCESS && result != STATUS_NOT_FOUND);
489
490 return result != STATUS_NOT_FOUND && packageCount > 0;
491 }
492
493 bool wsl::windows::common::helpers::IsServicePresent(_In_ LPCWSTR ServiceName)
494 {
495 const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)};
496 THROW_LAST_ERROR_IF(!manager);
497
498 const wil::unique_schandle service{OpenService(manager.get(), ServiceName, SERVICE_QUERY_CONFIG)};
499 return !!service;
500 }
501
502 bool wsl::windows::common::helpers::IsServiceRunning(_In_ LPCWSTR ServiceName)
503 {
504 const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)};
505 if (!manager)
506 {
507 return false;
508 }
509
510 const wil::unique_schandle service{OpenServiceW(manager.get(), ServiceName, SERVICE_QUERY_STATUS)};
511 if (!service)
512 {
513 return false;
514 }
515
516 SERVICE_STATUS status;
517 if (!QueryServiceStatus(service.get(), &status))
518 {
519 return false;
520 }
521
522 return status.dwCurrentState != SERVICE_STOPPED;
523 }
524
525 bool wsl::windows::common::helpers::IsVirtioSerialConsoleSupported()
526 {
527 // See if the Windows version has the required platform change.
528 //
529 // N.B. If the package is running on a vibranium or iron build, then it means that lifted
530 // support is available, so virtio serial is available as well (since it was done in the same LCU).
531
532 auto windowsVersion = GetWindowsVersion();
533 return windowsVersion.BuildNumber != WindowsBuildNumbers::Cobalt ||
534 windowsVersion.UpdateBuildRevision >= VIRTIO_SERIAL_CONSOLE_COBALT_RELEASE_UBR;
535 }
536
537 bool wsl::windows::common::helpers::IsVmemmSuffixSupported()
538 {
539 auto windowsVersion = GetWindowsVersion();
540
541 // See if the Windows version has the required platform change.
542 return (
543 (windowsVersion.BuildNumber >= VMMEM_SUFFIX_NICKEL_BUILD_NUMBER) ||
544 ((windowsVersion.BuildNumber < NICKEL_BUILD_FLOOR) && (windowsVersion.BuildNumber >= VMEMM_SUFFIX_COBALT_REFRESH_BUILD_NUMBER)) ||
545 ((windowsVersion.BuildNumber == WindowsBuildNumbers::Cobalt) && (windowsVersion.UpdateBuildRevision >= VMMEM_SUFFIX_COBALT_RELEASE_UBR)));
546 }
547
548 bool wsl::windows::common::helpers::IsWindows11OrAbove()
549 {
550 return GetWindowsVersion().BuildNumber >= WindowsBuildNumbers::Cobalt;
551 }
552
553 bool wsl::windows::common::helpers::IsWslOptionalComponentPresent()
554 {
555 // Query if the lxss service (the lxss.sys driver) is present.
556 return IsServicePresent(L"lxss");
557 }
558
559 bool wsl::windows::common::helpers::IsWslSupportInterfacePresent()
560 {
561 // Check if the IWslSupport interface is registered. This interface is present on all Windows builds
562 // that support the lifted WSL package.
563 wil::unique_hkey key;
564 try
565 {
566 key = windows::common::registry::OpenKey(HKEY_LOCAL_MACHINE, c_WslSupportInterfaceKey, KEY_READ);
567 WI_ASSERT(windows::common::registry::ReadString(key.get(), nullptr, nullptr, nullptr) == c_WslSupportInterfaceName);
568 }
569 CATCH_LOG()
570
571 return !!key;
572 }
573
574 void wsl::windows::common::helpers::LaunchDebugConsole(
575 _In_ LPCWSTR PipeName, _In_ bool ConnectExistingPipe, _In_ HANDLE UserToken, _In_opt_ HANDLE LogFile, _In_ bool DisableTelemetry, _In_opt_ HANDLE JobObject)
576 {
577 LaunchWslRelayFlags flags{};
578 wil::unique_hfile pipe;
579 if (ConnectExistingPipe)
580 {
581 // Connect to an existing pipe. The connection should be:
582 // Asynchronous (FILE_FLAG_OVERLAPPED)
583 // Anonymous (SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS)
584 // - Don't allow the pipe server to impersonate the connecting client.
585 pipe.reset(CreateFileW(
586 PipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, nullptr));
587 }
588 else
589 {
590 // Create a new pipe server the child process will connect to. The pipe should be:
591 // Bi-directional: PIPE_ACCESS_DUPLEX
592 // Asynchronous: FILE_FLAG_OVERLAPPED
593 // Raw: PIPE_TYPE_BYTE | PIPE_READMODE_BYTE
594 // Blocking: PIPE_WAIT
595 WI_SetFlag(flags, LaunchWslRelayFlags::ConnectPipe);
596 pipe.reset(CreateNamedPipeW(
597 PipeName, (PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED), (PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT), 1, LX_RELAY_BUFFER_SIZE, LX_RELAY_BUFFER_SIZE, 0, nullptr));
598 }
599
600 THROW_LAST_ERROR_IF(!pipe);
601
602 WI_SetFlagIf(flags, LaunchWslRelayFlags::DisableTelemetry, DisableTelemetry);
603 wil::unique_handle info{
604 LaunchWslRelay(wslrelay::RelayMode::DebugConsole, LogFile, nullptr, pipe.get(), {}, nullptr, UserToken, flags, JobObject)};
605 }
606
607 [[nodiscard]] wil::unique_handle wsl::windows::common::helpers::LaunchInteropServer(
608 _In_opt_ LPCGUID DistroId,
609 _In_ HANDLE InteropHandle,
610 _In_opt_ HANDLE EventHandle,
611 _In_opt_ HANDLE ParentHandle,
612 _In_opt_ LPCGUID VmId,
613 _In_opt_ HANDLE UserToken,
614 _In_opt_ HANDLE JobObject)
615 {
616 return LaunchWslHost(DistroId, InteropHandle, EventHandle, ParentHandle, VmId, UserToken, JobObject);
617 }
618
619 void wsl::windows::common::helpers::LaunchKdRelay(
620 _In_ LPCWSTR PipeName, _In_ HANDLE UserToken, _In_ int Port, _In_ HANDLE ExitEvent, _In_ bool DisableTelemetry, _In_opt_ HANDLE JobObject)
621 {
622 // Create a new pipe server. The pipe should be:
623 // Bi-directional: PIPE_ACCESS_DUPLEX
624 // Asynchronous: FILE_FLAG_OVERLAPPED
625 // Raw: PIPE_TYPE_BYTE | PIPE_READMODE_BYTE
626 // Blocking: PIPE_WAIT
627 const wil::unique_hfile pipe{CreateNamedPipeW(
628 PipeName, (PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED), (PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT), 1, LX_RELAY_BUFFER_SIZE, LX_RELAY_BUFFER_SIZE, 0, nullptr)};
629
630 THROW_LAST_ERROR_IF(!pipe);
631
632 LaunchWslRelayFlags flags = LaunchWslRelayFlags::ConnectPipe;
633 WI_SetFlagIf(flags, LaunchWslRelayFlags::DisableTelemetry, DisableTelemetry);
634 wil::unique_handle info{
635 LaunchWslRelay(wslrelay::RelayMode::KdRelay, nullptr, nullptr, pipe.get(), Port, ExitEvent, UserToken, flags, JobObject)};
636 }
637
638 void wsl::windows::common::helpers::LaunchPortRelay(
639 _In_ SOCKET Socket, _In_ const GUID& VmId, _In_ HANDLE UserToken, _In_ bool DisableTelemetry, _In_opt_ HANDLE JobObject)
640 {
641 LaunchWslRelayFlags flags{};
642 WI_SetFlagIf(flags, LaunchWslRelayFlags::DisableTelemetry, DisableTelemetry);
643 wil::unique_handle info{LaunchWslRelay(
644 wslrelay::RelayMode::PortRelay, reinterpret_cast<HANDLE>(Socket), &VmId, nullptr, {}, nullptr, UserToken, flags, JobObject)};
645 }
646
647 void wsl::windows::common::helpers::LaunchWslSettingsOOBE(_In_ HANDLE UserToken)
648 {
649 const auto wslSettingsExePath = wsl::windows::common::wslutil::GetBasePath() / L"wslsettings" / L"wslsettings.exe";
650 static constexpr auto commandLine = L" ----ms-protocol:wsl-settings://oobe";
651
652 wsl::windows::common::SubProcess process(wslSettingsExePath.c_str(), commandLine);
653 process.SetToken(UserToken);
654 process.SetShowWindow(SW_SHOW);
655
656 wsl::windows::common::helpers::unique_environment_block environmentBlock{nullptr};
657 THROW_LAST_ERROR_IF(!CreateEnvironmentBlock(&environmentBlock, UserToken, false));
658
659 process.SetEnvironment(environmentBlock.get());
660
661 process.Start();
662 }
663
664 std::wstring_view wsl::windows::common::helpers::ParseArgument(_In_ std::wstring_view CommandLine, _In_ bool HandleQuotes)
665 {
666 std::wstring_view Argument = CommandLine;
667 const size_t Index = Argument.find_first_of(L" \t");
668 if (Index != std::wstring_view::npos)
669 {
670 Argument = Argument.substr(0, Index);
671 }
672
673 if (HandleQuotes && CommandLine.find_first_of(L"\"") == 0)
674 {
675 const auto QuoteIndex = CommandLine.find_first_of(L"\"", 1);
676 if (QuoteIndex != std::wstring_view::npos)
677 {
678 Argument = CommandLine.substr(0, QuoteIndex + 1);
679 }
680 }
681
682 return Argument;
683 }
684
685 bool wsl::windows::common::helpers::ReopenStdHandles()
686 {
687 // Reopen the standard streams to make sure *printf* methods will write to the correct place.
688 if (_wfreopen(L"CONIN$", L"r", stdin) == nullptr || _wfreopen(L"CONOUT$", L"w", stdout) == nullptr ||
689 _wfreopen(L"CONOUT$", L"w", stderr) == nullptr)
690 {
691 return false;
692 }
693
694 // Configure std::cout, std::cerr and std::cin to use the reopened FILE*.
695 std::ios::sync_with_stdio();
696
697 return true;
698 }
699
700 #ifdef _WIN64
701 INT64
702 wsl::windows::common::helpers::RoundUpToNearestPowerOfTwo(_In_ INT64 Num)
703 #else
704 INT32
705 wsl::windows::common::helpers::RoundUpToNearestPowerOfTwo(_In_ INT32 Num)
706 #endif
707 {
708 // Don't round the number up further if it's zero or already a power of two.
709 if (Num == 0 || (Num & (Num - 1)) == 0)
710 {
711 return Num;
712 }
713
714 // Round the number up to the nearest power of two.
715 #ifdef _WIN64
716 ULONG index = 0;
717 WI_VERIFY(_BitScanReverse64(&index, Num));
718
719 return 1i64 << (index + 1);
720 #else
721 ULONG index = 0;
722 WI_VERIFY(_BitScanReverse(&index, Num));
723
724 return 1i32 << (index + 1);
725 #endif
726 }
727
728 DWORD
729 wsl::windows::common::helpers::RunProcess(_Inout_ std::wstring& CommandLine)
730 {
731 SubProcess process(nullptr, CommandLine.c_str());
732 return process.Run();
733 }
734
735 void wsl::windows::common::helpers::SetHandleInheritable(_In_ HANDLE Handle, _In_ bool Inheritable)
736 {
737 THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(Handle, HANDLE_FLAG_INHERIT, Inheritable ? HANDLE_FLAG_INHERIT : 0));
738 }
739
740 bool wsl::windows::common::helpers::TryAttachConsole()
741 {
742 if (!AttachConsole(GetCurrentProcessId()) && !AttachConsole(ATTACH_PARENT_PROCESS))
743 {
744 return false;
745 }
746
747 return ReopenStdHandles();
748 }
749
750 std::optional<std::wstring> wsl::windows::common::helpers::VersionRegisteredWithDcat()
751 {
752 try
753 {
754 auto [dcatKey, result] = wsl::windows::common::registry::OpenKeyNoThrow(HKEY_LOCAL_MACHINE, TEXT(DCAT_REGISTRATION_KEY), KEY_READ);
755 if (SUCCEEDED(result))
756 {
757 return wsl::windows::common::registry::ReadOptionalString(dcatKey.get(), nullptr, c_DcatRegistryVersionValueName);
758 }
759 }
760 CATCH_LOG()
761
762 return {};
763 }
764
765 void wsl::windows::common::helpers::RegisterWithDcat(_In_ bool IncludeVersionNumber)
766 try
767 {
768 std::wstring registeredVersion;
769 if (IncludeVersionNumber)
770 {
771 registeredVersion.assign(TEXT(WSL_PACKAGE_VERSION));
772 }
773 else
774 {
775 registeredVersion.assign(L"0.0.0.0");
776 }
777
778 wil::unique_hkey dcatKey = wsl::windows::common::registry::CreateKey(HKEY_LOCAL_MACHINE, TEXT(DCAT_REGISTRATION_KEY), KEY_SET_VALUE);
779 wsl::windows::common::registry::WriteString(dcatKey.get(), nullptr, c_DcatRegistryVersionValueName, registeredVersion.c_str());
780 }
781 CATCH_LOG()
782
783 void wsl::windows::common::helpers::AppendCommonKernelCommandLine(
784 _Inout_ std::wstring& kernelCmdLine, _In_ int pageReportingOrder, _In_ ULONG64 swiotlbSizeBytes, _In_ ULONG cpuCount)
785 {
786 // Set number of processors.
787 kernelCmdLine += std::format(L" nr_cpus={}", cpuCount);
788
789 // Enable timesync workaround to sync on resume from sleep in modern standby.
790 kernelCmdLine += L" hv_utils.timesync_implicit=1";
791
792 // Disable rate limiting of user writes to dmesg.
793 kernelCmdLine += L" printk.devkmsg=on";
794
795 // Configure page reporting order - minimum order of pages reported as free to the hypervisor.
796 kernelCmdLine += std::format(L" page_reporting.page_reporting_order={}", pageReportingOrder);
797
798 // Reserve a swiotlb bounce buffer for virtio devices.
799 if (swiotlbSizeBytes != 0)
800 {
801 kernelCmdLine += std::format(L" swiotlb=force hv_pci_swiotlb={}", swiotlbSizeBytes);
802 }
803
804 // Cap the storage hw queue count. Default is CPU count.
805 if (cpuCount > c_MaxStorageHwQueues)
806 {
807 kernelCmdLine += std::format(L" hv_storvsc.storvsc_max_hw_queues={}", c_MaxStorageHwQueues);
808 }
809 }
810
811 UINT64 wsl::windows::common::helpers::ComputeDefaultSwiotlbConfig(_In_ UINT64 memoryBytes)
812 {
813 constexpr UINT64 c_swiotlbSize = 64 * _1MB;
814
815 // Skip swiotlb on VMs that cannot fit the reserved buffer. Users can still opt in explicitly
816 // via experimental.swiotlb in .wslconfig.
817 if (memoryBytes < _1GB + c_swiotlbSize)
818 {
819 return {};
820 }
821
822 return c_swiotlbSize;
823 }