| 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 | } |