| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | LxssInstance.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains lxss instance definitions. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "LxssInstance.h" |
| 17 | #include "LxssSecurity.h" |
| 18 | #include "LxssUserSession.h" |
| 19 | |
| 20 | // Number of milliseconds to wait for the init process to respond. |
| 21 | #define LXSS_INIT_CONNECTION_TIMEOUT_MS (30 * 1000) |
| 22 | |
| 23 | // Registry keys related to integrity level checks. |
| 24 | #define LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK L"DisableMixedIntegrityLaunch" |
| 25 | #define LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK_DISABLED 0 |
| 26 | #define LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK_ENABLED 1 |
| 27 | |
| 28 | // Legacy folder mount information. |
| 29 | #define LXSS_CACHE_MOUNT_LXSS "/cache" |
| 30 | #define LXSS_CACHE_MOUNT_NT L"cache" |
| 31 | #define LXSS_CACHE_PERMISSIONS (0770) |
| 32 | #define LXSS_DATA_MOUNT_LXSS "/data" |
| 33 | #define LXSS_DATA_MOUNT_NT L"data" |
| 34 | #define LXSS_DATA_PERMISSIONS (0771) |
| 35 | #define LXSS_HOME_MOUNT_LXSS "/home" |
| 36 | #define LXSS_HOME_MOUNT_NT L"home" |
| 37 | #define LXSS_HOME_PERMISSIONS (0755) |
| 38 | #define LXSS_MNT_MOUNT_LXSS "/mnt" |
| 39 | #define LXSS_MNT_MOUNT_NT L"mnt" |
| 40 | #define LXSS_MNT_PERMISSIONS (0755) |
| 41 | #define LXSS_ROOT_HOME_MOUNT_LXSS "/root" |
| 42 | #define LXSS_ROOT_HOME_MOUNT_NT L"root" |
| 43 | #define LXSS_ROOT_PERMISSIONS (0700) |
| 44 | #define LXSS_ROOTFS_PERMISSIONS (0755) |
| 45 | |
| 46 | extern bool g_lxcoreInitialized; |
| 47 | |
| 48 | using namespace std::placeholders; |
| 49 | using namespace Microsoft::WRL; |
| 50 | using namespace wsl::windows::common::filesystem; |
| 51 | |
| 52 | LxssInstance::LxssInstance( |
| 53 | _In_ const GUID& InstanceId, |
| 54 | _In_ const LXSS_DISTRO_CONFIGURATION& Configuration, |
| 55 | _In_ ULONG DefaultUid, |
| 56 | _In_ ULONG64 ClientLifetimeId, |
| 57 | _In_ const std::function<void()>& TerminationCallback, |
| 58 | _In_ const std::function<void()>& UpdateInitCallback, |
| 59 | _In_ ULONG Flags, |
| 60 | _In_ int IdleTimeout) : |
| 61 | LxssRunningInstance(IdleTimeout), |
| 62 | m_instanceId(InstanceId), |
| 63 | m_instanceHandle(), |
| 64 | m_terminationCallback(TerminationCallback), |
| 65 | m_initialized(false), |
| 66 | m_running(false), |
| 67 | m_defaultUid(DefaultUid), |
| 68 | m_configuration(Configuration), |
| 69 | m_ntClientLifetimeId(ClientLifetimeId), |
| 70 | m_instanceBasicIntegrityLevelCheckEnabled(false), |
| 71 | m_redirectorConnectionTargets{m_configuration.Name} |
| 72 | { |
| 73 | // Running a WSL1 distro is not possible if the lxcore driver is not present. |
| 74 | THROW_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, !g_lxcoreInitialized); |
| 75 | |
| 76 | // Copy immutable distribution data into the info structure. |
| 77 | m_distributionInfo.Id = m_configuration.DistroId; |
| 78 | m_distributionInfo.Name = m_configuration.Name.c_str(); |
| 79 | m_distributionInfo.PackageFamilyName = m_configuration.PackageFamilyName.c_str(); |
| 80 | m_distributionInfo.InitPid = 1; |
| 81 | |
| 82 | m_ServerPort = std::make_shared<LxssServerPort>(); |
| 83 | { |
| 84 | // Create a job to hold pico processes from LXSS. |
| 85 | auto runAsSessionUser = wil::CoImpersonateClient(); |
| 86 | |
| 87 | m_instanceJob.reset(CreateJobObjectW(nullptr, nullptr)); |
| 88 | THROW_LAST_ERROR_IF(!m_instanceJob); |
| 89 | |
| 90 | Security::InitializeInstanceJob(m_instanceJob.get()); |
| 91 | |
| 92 | // Check if VPN detection is enabled. |
| 93 | const wil::unique_hkey LxssKey = wsl::windows::common::registry::OpenLxssUserKey(); |
| 94 | m_enableVpnDetection = (wsl::windows::common::registry::ReadDword(LxssKey.get(), nullptr, L"EnableVpnDetection", 1)) != 0; |
| 95 | } |
| 96 | |
| 97 | // Store the user token for access checks. |
| 98 | m_userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); |
| 99 | |
| 100 | // Create manual reset event that is signaled on instance termination |
| 101 | m_instanceTerminatedEvent.reset(CreateEventW(nullptr, TRUE, FALSE, nullptr)); |
| 102 | THROW_LAST_ERROR_IF(!m_instanceTerminatedEvent); |
| 103 | |
| 104 | // Check if integrity level check is enabled. |
| 105 | // |
| 106 | // N.B. The registry value is only writable from high-IL and above. |
| 107 | if (wsl::windows::common::registry::ReadDword( |
| 108 | HKEY_LOCAL_MACHINE, LXSS_SERVICE_REGISTRY_PATH, LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK, LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK_DISABLED) == |
| 109 | LXSS_SERVICE_REGISTRY_INTEGRITY_CHECK_ENABLED) |
| 110 | { |
| 111 | // Query the integrity level of the caller and store it in the instance. |
| 112 | m_instanceBasicIntegrityLevel = wsl::windows::common::security::GetUserBasicIntegrityLevel(m_userToken.get()); |
| 113 | m_instanceBasicIntegrityLevelCheckEnabled = true; |
| 114 | } |
| 115 | |
| 116 | // Initialize mount paths. |
| 117 | _ConfigureFilesystem(Flags); |
| 118 | |
| 119 | // Update the init binary if needed. |
| 120 | UpdateInitCallback(); |
| 121 | |
| 122 | // Create the LXSS instance |
| 123 | _StartInstance(Configuration.Flags); |
| 124 | |
| 125 | // Create a threadpool wait object to listen for instance termination |
| 126 | m_terminationWait.reset(CreateThreadpoolWait( |
| 127 | [](PTP_CALLBACK_INSTANCE, PVOID context, PTP_WAIT, TP_WAIT_RESULT) { |
| 128 | try |
| 129 | { |
| 130 | const auto instance = static_cast<LxssInstance*>(context); |
| 131 | instance->OnTerminated(); |
| 132 | } |
| 133 | CATCH_LOG() |
| 134 | }, |
| 135 | this, |
| 136 | nullptr)); |
| 137 | |
| 138 | THROW_LAST_ERROR_IF(!m_terminationWait); |
| 139 | |
| 140 | SetThreadpoolWait(m_terminationWait.get(), m_instanceTerminatedEvent.get(), nullptr); |
| 141 | |
| 142 | // Mark the instance as started. |
| 143 | m_running = true; |
| 144 | } |
| 145 | |
| 146 | LxssInstance::~LxssInstance() |
| 147 | { |
| 148 | Stop(); |
| 149 | } |
| 150 | bool CreateLxProcessIsValidStdHandle(_In_ PLXSS_HANDLE StdHandle) |
| 151 | { |
| 152 | bool IsValid = false; |
| 153 | switch (StdHandle->HandleType) |
| 154 | { |
| 155 | case LxssHandleConsole: |
| 156 | if (StdHandle->Handle == LXSS_HANDLE_USE_CONSOLE) |
| 157 | { |
| 158 | IsValid = true; |
| 159 | } |
| 160 | |
| 161 | break; |
| 162 | |
| 163 | case LxssHandleInput: |
| 164 | case LxssHandleOutput: |
| 165 | if (StdHandle->Handle != LXSS_HANDLE_USE_CONSOLE) |
| 166 | { |
| 167 | IsValid = true; |
| 168 | } |
| 169 | |
| 170 | break; |
| 171 | } |
| 172 | |
| 173 | return IsValid; |
| 174 | } |
| 175 | |
| 176 | void LxssInstance::CreateLxProcess( |
| 177 | _In_ const CreateLxProcessData& CreateProcessData, |
| 178 | _In_ const CreateLxProcessContext& CreateProcessContext, |
| 179 | _In_ const CreateLxProcessConsoleData& ConsoleData, |
| 180 | _In_ SHORT Columns, |
| 181 | _In_ SHORT Rows, |
| 182 | _In_ PLXSS_STD_HANDLES StdHandles, |
| 183 | _Out_ GUID* InstanceId, |
| 184 | _Out_ HANDLE* ProcessHandle, |
| 185 | _Out_ HANDLE* ServerHandle, |
| 186 | _Out_ HANDLE* StandardIn, |
| 187 | _Out_ HANDLE* StandardOut, |
| 188 | _Out_ HANDLE* StandardErr, |
| 189 | _Out_ HANDLE* CommunicationChannel, |
| 190 | _Out_ HANDLE* InteropSocket) |
| 191 | { |
| 192 | UNREFERENCED_PARAMETER(Columns); |
| 193 | UNREFERENCED_PARAMETER(Rows); |
| 194 | |
| 195 | THROW_HR_IF(E_INVALIDARG, !CreateLxProcessIsValidStdHandle(&StdHandles->StdIn)); |
| 196 | THROW_HR_IF(E_INVALIDARG, !CreateLxProcessIsValidStdHandle(&StdHandles->StdOut)); |
| 197 | THROW_HR_IF(E_INVALIDARG, !CreateLxProcessIsValidStdHandle(&StdHandles->StdErr)); |
| 198 | |
| 199 | // Check that the process is at the same basic integrity level if |
| 200 | // needed. |
| 201 | if (m_instanceBasicIntegrityLevelCheckEnabled != false) |
| 202 | { |
| 203 | const DWORD BasicIntegrityLevel = |
| 204 | wsl::windows::common::security::GetUserBasicIntegrityLevel(CreateProcessContext.UserToken.get()); |
| 205 | if (m_instanceBasicIntegrityLevel != BasicIntegrityLevel) |
| 206 | { |
| 207 | if (m_instanceBasicIntegrityLevel > BasicIntegrityLevel) |
| 208 | { |
| 209 | THROW_HR(WSL_E_LOWER_INTEGRITY); |
| 210 | } |
| 211 | |
| 212 | THROW_HR(WSL_E_HIGHER_INTEGRITY); |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | if (m_oobeCompleteEvent && !m_oobeCompleteEvent.is_signaled()) |
| 217 | { |
| 218 | EMIT_USER_WARNING(wsl::shared::Localization::MessageWaitingForOobe(m_configuration.Name.c_str())); |
| 219 | m_oobeCompleteEvent.wait(); |
| 220 | } |
| 221 | |
| 222 | // Duplicate the handles from the calling process into the current process. |
| 223 | std::vector<wil::unique_handle> StdHandlesLocal(3); |
| 224 | if (StdHandles->StdIn.Handle != LXSS_HANDLE_USE_CONSOLE) |
| 225 | { |
| 226 | StdHandlesLocal[0].reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(StdHandles->StdIn.Handle))); |
| 227 | } |
| 228 | |
| 229 | if (StdHandles->StdOut.Handle != LXSS_HANDLE_USE_CONSOLE) |
| 230 | { |
| 231 | StdHandlesLocal[1].reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(StdHandles->StdOut.Handle))); |
| 232 | } |
| 233 | |
| 234 | if (StdHandles->StdErr.Handle != LXSS_HANDLE_USE_CONSOLE) |
| 235 | { |
| 236 | StdHandlesLocal[2].reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(StdHandles->StdErr.Handle))); |
| 237 | } |
| 238 | |
| 239 | // Enable symlink creation privilege on the token, which is needed |
| 240 | // to allow DrvFs to create NT symlinks. |
| 241 | // |
| 242 | // N.B. This privilege is required to create NT symlinks only when |
| 243 | // developer mode is disabled. When developer mode is enabled, |
| 244 | // acquiring the privilege can fail, but creating the symlink |
| 245 | // will succeed even without the privilege. Therefore, it does |
| 246 | // not matter if this call fails. |
| 247 | const wil::unique_handle Token = wsl::windows::common::security::GetUserToken(TokenPrimary); |
| 248 | wsl::windows::common::security::EnableTokenPrivilege(Token.get(), SE_CREATE_SYMBOLIC_LINK_NAME); |
| 249 | |
| 250 | // Create an unnamed server port if the caller provided a buffer and |
| 251 | // interop is enabled. |
| 252 | wil::unique_handle ServerPort; |
| 253 | PHANDLE ServerPortPointer = nullptr; |
| 254 | if (LXSS_INTEROP_ENABLED(CreateProcessContext.Flags)) |
| 255 | { |
| 256 | ServerPortPointer = &ServerPort; |
| 257 | } |
| 258 | |
| 259 | // Send the create process request to the session leader. |
| 260 | bool CreatedSessionLeader; |
| 261 | const auto SessionLeader = std::static_pointer_cast<LxssMessagePort>( |
| 262 | m_consoleManager->GetSessionLeader(ConsoleData, CreateProcessContext.Elevated, &CreatedSessionLeader)); |
| 263 | |
| 264 | // If the session leader was just created, ensure the networking information |
| 265 | // is up-to-date. |
| 266 | if (CreatedSessionLeader) |
| 267 | { |
| 268 | _UpdateNetworkConfigurationFiles(true); |
| 269 | } |
| 270 | |
| 271 | wil::unique_handle ProcessHandleLocal = |
| 272 | _CreateLxProcess(SessionLeader, CreateProcessData, StdHandlesLocal, Token, m_defaultUid, ServerPortPointer); |
| 273 | |
| 274 | *InstanceId = m_instanceId; |
| 275 | *ProcessHandle = ProcessHandleLocal.release(); |
| 276 | *ServerHandle = ServerPort ? ServerPort.release() : nullptr; |
| 277 | *StandardIn = nullptr; |
| 278 | *StandardOut = nullptr; |
| 279 | *StandardErr = nullptr; |
| 280 | *CommunicationChannel = nullptr; |
| 281 | *InteropSocket = nullptr; |
| 282 | } |
| 283 | |
| 284 | ULONG LxssInstance::GetClientId() const |
| 285 | { |
| 286 | return LXSS_CLIENT_ID_INVALID; |
| 287 | } |
| 288 | |
| 289 | GUID LxssInstance::GetDistributionId() const |
| 290 | { |
| 291 | return m_configuration.DistroId; |
| 292 | } |
| 293 | |
| 294 | std::shared_ptr<LxssPort> LxssInstance::GetInitPort() |
| 295 | { |
| 296 | return m_InitMessagePort; |
| 297 | } |
| 298 | |
| 299 | void LxssInstance::UpdateTimezone() |
| 300 | { |
| 301 | const auto timezone = wsl::windows::common::helpers::GetLinuxTimezone(m_userToken.get()); |
| 302 | auto message = wsl::windows::common::helpers::GenerateTimezoneUpdateMessage(timezone); |
| 303 | auto lock = m_InitMessagePort->Lock(); |
| 304 | m_InitMessagePort->Send(message.data(), gsl::narrow_cast<ULONG>(message.size())); |
| 305 | } |
| 306 | |
| 307 | ULONG64 LxssInstance::GetLifetimeManagerId() const |
| 308 | { |
| 309 | return m_ntClientLifetimeId; |
| 310 | } |
| 311 | |
| 312 | void LxssInstance::Initialize() |
| 313 | { |
| 314 | std::lock_guard<std::mutex> lock(m_stateLock); |
| 315 | if (m_initialized) |
| 316 | { |
| 317 | return; |
| 318 | } |
| 319 | |
| 320 | const auto socketPath = m_configuration.BasePath / LXSS_PLAN9_UNIX_SOCKET; |
| 321 | |
| 322 | // Open the communication channel with init. |
| 323 | _InitiateConnectionToInitProcess(); |
| 324 | |
| 325 | // Send initial configuration information to the init daemon. |
| 326 | _InitializeConfiguration(socketPath); |
| 327 | |
| 328 | // Initialize networking for the instance, this will also register for network |
| 329 | // state change notifications and send the initial network information to the |
| 330 | // init process. |
| 331 | _InitializeNetworking(); |
| 332 | |
| 333 | // Initialization successful. |
| 334 | m_initialized = true; |
| 335 | } |
| 336 | |
| 337 | void LxssInstance::OnTerminated() const |
| 338 | { |
| 339 | m_terminationCallback(); |
| 340 | } |
| 341 | |
| 342 | bool LxssInstance::RequestStop(_In_ bool Force) |
| 343 | { |
| 344 | std::lock_guard<std::mutex> lock(m_stateLock); |
| 345 | |
| 346 | // Send the message to the init daemon to check if the instance can be terminated. |
| 347 | bool shutdown = true; |
| 348 | if (m_InitMessagePort) |
| 349 | { |
| 350 | try |
| 351 | { |
| 352 | auto lock = m_InitMessagePort->Lock(); |
| 353 | LX_INIT_TERMINATE_INSTANCE terminateMessage{}; |
| 354 | terminateMessage.Header.MessageType = LxInitMessageTerminateInstance; |
| 355 | terminateMessage.Header.MessageSize = sizeof(terminateMessage); |
| 356 | terminateMessage.Force = Force; |
| 357 | m_InitMessagePort->Send(&terminateMessage, sizeof(terminateMessage)); |
| 358 | LX_INIT_TERMINATE_INSTANCE::TResponse terminateResponse{}; |
| 359 | m_InitMessagePort->Receive(&terminateResponse, sizeof(terminateResponse)); |
| 360 | shutdown = terminateResponse.Result; |
| 361 | } |
| 362 | CATCH_LOG() |
| 363 | } |
| 364 | |
| 365 | return shutdown; |
| 366 | } |
| 367 | |
| 368 | void LxssInstance::Stop() |
| 369 | { |
| 370 | std::lock_guard<std::mutex> lock(m_stateLock); |
| 371 | |
| 372 | // Do nothing if the instance is already terminated. |
| 373 | if (!m_running) |
| 374 | { |
| 375 | return; |
| 376 | } |
| 377 | |
| 378 | // Logs when a distro is stopped |
| 379 | WSL_LOG_TELEMETRY( |
| 380 | "StopInstance", |
| 381 | PDT_ProductAndServiceUsage, |
| 382 | TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA), |
| 383 | TraceLoggingValue(m_configuration.Name.c_str(), "distroName"), |
| 384 | TraceLoggingValue(LXSS_WSL_VERSION_1, "version"), |
| 385 | TraceLoggingValue(m_instanceId, "instanceId")); |
| 386 | |
| 387 | // Unregister the instance termination TP wait. |
| 388 | if (m_terminationWait) |
| 389 | { |
| 390 | SetThreadpoolWait(m_terminationWait.get(), nullptr, nullptr); |
| 391 | } |
| 392 | |
| 393 | // Unregister from network change notifications. |
| 394 | m_networkNotificationHandle.reset(); |
| 395 | |
| 396 | // Unwind in the reverse order of creation. Stop the LXSS instance, delete it. |
| 397 | LOG_IF_NTSTATUS_FAILED(::LxssClientInstanceStop(m_instanceHandle.get())); |
| 398 | m_instanceTerminatedEvent.reset(); |
| 399 | |
| 400 | LOG_IF_NTSTATUS_FAILED(::LxssClientInstanceDestroy(m_instanceHandle.get())); |
| 401 | m_instanceHandle.reset(); |
| 402 | |
| 403 | // Wait on the oobe thread. |
| 404 | if (m_oobeThread.joinable()) |
| 405 | { |
| 406 | m_oobeThread.join(); |
| 407 | } |
| 408 | |
| 409 | // Remove the instance's Plan 9 Redirector connection targets. |
| 410 | m_redirectorConnectionTargets.RemoveAll(); |
| 411 | |
| 412 | // Attempt to clean up the instance's temp folder. |
| 413 | if (!m_tempPath.empty()) |
| 414 | { |
| 415 | auto runAsUser = wil::impersonate_token(m_userToken.get()); |
| 416 | LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(m_tempPath.c_str())); |
| 417 | } |
| 418 | |
| 419 | m_running = false; |
| 420 | m_tempPath.clear(); |
| 421 | m_rootDirectory.reset(); |
| 422 | m_tempDirectory.reset(); |
| 423 | return; |
| 424 | } |
| 425 | |
| 426 | void LxssInstance::RegisterPlan9ConnectionTarget(_In_ HANDLE userToken) |
| 427 | { |
| 428 | const auto path = m_configuration.BasePath / LXSS_PLAN9_UNIX_SOCKET; |
| 429 | using unique_unicode_string = wil::unique_struct<UNICODE_STRING, decltype(RtlFreeUnicodeString), RtlFreeUnicodeString>; |
| 430 | unique_unicode_string socketPath; |
| 431 | THROW_IF_NTSTATUS_FAILED(RtlDosPathNameToNtPathName_U_WithStatus(path.c_str(), &socketPath, nullptr, nullptr)); |
| 432 | |
| 433 | m_redirectorConnectionTargets.AddConnectionTarget( |
| 434 | userToken, {}, m_defaultUid, std::wstring_view{socketPath.Buffer, socketPath.Length / sizeof(WCHAR)}); |
| 435 | } |
| 436 | |
| 437 | const WSLDistributionInformation* LxssInstance::DistributionInformation() const noexcept |
| 438 | { |
| 439 | return &m_distributionInfo; |
| 440 | } |
| 441 | |
| 442 | void LxssInstance::_ConfigureFilesystem(_In_ ULONG Flags) |
| 443 | { |
| 444 | // Part of this process will try to upgrade existing LxFs folders to |
| 445 | // enable the per-directory case sensitivity flag. To allow easy detection |
| 446 | // of already processed folders, and resumption in case the process is |
| 447 | // interrupted, directories are only marked case-sensitive after their |
| 448 | // children are processed. |
| 449 | // |
| 450 | // Paths for LXSS instances look like so: |
| 451 | // |
| 452 | // <root> |
| 453 | // \rootfs <-- Where the file system is located |
| 454 | // \temp\{Instance GUID} <-- Where temporary files go |
| 455 | auto runAsUser = wil::CoImpersonateClient(); |
| 456 | |
| 457 | // Ensure the parent temp folder exists and is empty. |
| 458 | const auto tempFolder = m_configuration.BasePath / LXSS_TEMP_DIRECTORY; |
| 459 | EnsureDirectory(tempFolder.c_str()); |
| 460 | LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(tempFolder.c_str(), wil::RemoveDirectoryOptions::KeepRootDirectory)); |
| 461 | |
| 462 | // Create a subdirectory to be used as the temp folder for this instance. |
| 463 | const auto instanceIdString = wsl::shared::string::GuidToString<wchar_t>(m_instanceId); |
| 464 | m_tempPath = tempFolder / instanceIdString; |
| 465 | |
| 466 | // Make sure the directories of interest exist. Attributes are added |
| 467 | // separately because on upgrade directories without attributes may |
| 468 | // already exist. |
| 469 | EnsureDirectory(m_configuration.BasePath.c_str()); |
| 470 | |
| 471 | auto ensureDirectoryWithAttributes = [&](LPCWSTR directory, ULONG permissions) { |
| 472 | EnsureDirectoryWithAttributes( |
| 473 | (m_configuration.BasePath / directory).c_str(), permissions, LX_UID_ROOT, LX_GID_ROOT, Flags, m_configuration.Version); |
| 474 | }; |
| 475 | |
| 476 | ensureDirectoryWithAttributes(LXSS_ROOTFS_DIRECTORY, LXSS_ROOTFS_PERMISSIONS); |
| 477 | |
| 478 | // If this is the legacy distribution, ensure that the additional LxFs |
| 479 | // directories exist and have the correct attributes. Otherwise, ensure that |
| 480 | // the rootfs/mnt directory exists for DrvFs mounts. |
| 481 | switch (m_configuration.Version) |
| 482 | { |
| 483 | case LXSS_DISTRO_VERSION_LEGACY: |
| 484 | |
| 485 | WI_ASSERT(IsEqualGUID(LXSS_LEGACY_DISTRO_GUID, m_configuration.DistroId)); |
| 486 | |
| 487 | ensureDirectoryWithAttributes(LXSS_MNT_MOUNT_NT, LXSS_MNT_PERMISSIONS); |
| 488 | ensureDirectoryWithAttributes(LXSS_CACHE_MOUNT_NT, LXSS_CACHE_PERMISSIONS); |
| 489 | ensureDirectoryWithAttributes(LXSS_DATA_MOUNT_NT, LXSS_DATA_PERMISSIONS); |
| 490 | ensureDirectoryWithAttributes(LXSS_ROOT_HOME_MOUNT_NT, LXSS_ROOTFS_PERMISSIONS); |
| 491 | ensureDirectoryWithAttributes(LXSS_HOME_MOUNT_NT, LXSS_HOME_PERMISSIONS); |
| 492 | break; |
| 493 | |
| 494 | case LXSS_DISTRO_VERSION_1: |
| 495 | case LXSS_DISTRO_VERSION_CURRENT: |
| 496 | break; |
| 497 | |
| 498 | DEFAULT_UNREACHABLE; |
| 499 | } |
| 500 | |
| 501 | // Wipe out and then recreate the temporary directory. |
| 502 | m_tempDirectory = WipeAndOpenDirectory(m_tempPath.c_str()); |
| 503 | |
| 504 | // Open handle to rootfs directory for the instance. |
| 505 | m_rootDirectory = OpenDirectoryHandle(m_configuration.BasePath.c_str(), true); |
| 506 | } |
| 507 | |
| 508 | wil::unique_handle LxssInstance::_CreateLxProcess( |
| 509 | _In_ const std::shared_ptr<LxssMessagePort>& MessagePort, |
| 510 | _In_ const CreateLxProcessData& CreateProcessData, |
| 511 | _In_ const std::vector<wil::unique_handle>& StdHandles, |
| 512 | _In_ const wil::unique_handle& Token, |
| 513 | _In_ ULONG DefaultUid, |
| 514 | _Out_opt_ PHANDLE ServerPortHandle) |
| 515 | { |
| 516 | auto lock = MessagePort->Lock(); |
| 517 | |
| 518 | // Send a create process message for the session leader. |
| 519 | auto Message = _CreateLxProcessMarshalMessage(MessagePort, CreateProcessData, StdHandles, Token, DefaultUid); |
| 520 | |
| 521 | WI_ASSERT(Message.size() <= ULONG_MAX); |
| 522 | |
| 523 | const auto MessageLocal = (PLX_INIT_CREATE_PROCESS)Message.data(); |
| 524 | const bool AllowOOBE = WI_IsFlagSet(MessageLocal->Common.Flags, LxInitCreateProcessFlagAllowOOBE); |
| 525 | auto HandleEraser = |
| 526 | wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ReleaseHandlesFromLxProcessMarshalMessage(MessagePort, MessageLocal); }); |
| 527 | |
| 528 | std::unique_ptr<LxssServerPort> ServerPort; |
| 529 | if (ARGUMENT_PRESENT(ServerPortHandle) || AllowOOBE) |
| 530 | { |
| 531 | ServerPort = std::make_unique<LxssServerPort>(MessagePort->CreateUnnamedServer(&MessageLocal->IpcServerId)); |
| 532 | } |
| 533 | |
| 534 | MessagePort->Send(Message.data(), gsl::narrow_cast<ULONG>(Message.size())); |
| 535 | |
| 536 | if (AllowOOBE) |
| 537 | { |
| 538 | auto OobeMessagePort = ServerPort->WaitForConnection(); |
| 539 | |
| 540 | { |
| 541 | m_oobeCompleteEvent.create(wil::EventOptions::ManualReset); |
| 542 | |
| 543 | auto impersonate = wil::CoImpersonateClient(); |
| 544 | auto registration = wsl::windows::service::DistributionRegistration::Open( |
| 545 | wsl::windows::common::registry::OpenLxssUserKey().get(), m_configuration.DistroId); |
| 546 | |
| 547 | // Wait for a potential previous oobe thread to complete before creating a new one. |
| 548 | if (m_oobeThread.joinable()) |
| 549 | { |
| 550 | m_oobeThread.join(); |
| 551 | } |
| 552 | |
| 553 | m_oobeThread = std::thread([this, OobeMessagePort = std::move(OobeMessagePort), registration = std::move(registration)]() mutable { |
| 554 | try |
| 555 | { |
| 556 | // N.B. The LX_INIT_OOBE_RESULT message is only sent once the OOBE process completes, which might be waiting on user input. |
| 557 | // Do no set a timeout here otherwise the OOBE flow will fail if the OOBE process takes longer than expected. |
| 558 | auto Message = OobeMessagePort->Receive(INFINITE); |
| 559 | auto* OobeResult = gslhelpers::try_get_struct<LX_INIT_OOBE_RESULT>(gsl::make_span(Message)); |
| 560 | THROW_HR_IF(E_INVALIDARG, !OobeResult || (OobeResult->Header.MessageType != LxInitOobeResult)); |
| 561 | |
| 562 | WSL_LOG( |
| 563 | "OOBEResult", |
| 564 | TraceLoggingValue(OobeResult->Result, "Result"), |
| 565 | TraceLoggingValue(OobeResult->DefaultUid, "DefaultUid"), |
| 566 | TraceLoggingValue(m_configuration.Name.c_str(), "Name"), |
| 567 | TraceLoggingValue(1, "Version")); |
| 568 | |
| 569 | if (OobeResult->Result == 0) |
| 570 | { |
| 571 | // OOBE was successful, don't run it again. |
| 572 | m_configuration.RunOOBE = false; |
| 573 | registration.Write(wsl::windows::service::Property::RunOOBE, 0); |
| 574 | |
| 575 | if (OobeResult->DefaultUid != -1) |
| 576 | { |
| 577 | registration.Write(wsl::windows::service::Property::DefaultUid, static_cast<int>(OobeResult->DefaultUid)); |
| 578 | m_defaultUid = static_cast<int>(OobeResult->DefaultUid); |
| 579 | } |
| 580 | |
| 581 | m_redirectorConnectionTargets.UpdateUid(m_defaultUid); |
| 582 | } |
| 583 | } |
| 584 | CATCH_LOG() |
| 585 | |
| 586 | m_oobeCompleteEvent.SetEvent(); |
| 587 | }); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | // Wait for the session leader to send the process identifier and unmarshal |
| 592 | // the process handle. |
| 593 | LXBUS_IPC_PROCESS_ID ProcessId = {}; |
| 594 | MessagePort->Receive(&ProcessId, sizeof(ProcessId)); |
| 595 | HandleEraser.release(); |
| 596 | |
| 597 | auto ProcessEraser = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { |
| 598 | // Reply to the init process that the process is not unmarshaled. |
| 599 | ProcessId = 0; |
| 600 | MessagePort->Send(&ProcessId, sizeof(ProcessId)); |
| 601 | }); |
| 602 | |
| 603 | wil::unique_handle ProcessHandle = MessagePort->UnmarshalProcess(ProcessId); |
| 604 | |
| 605 | // Reply to the init process that the process is unmarshaled. |
| 606 | ProcessId = 1; |
| 607 | MessagePort->Send(&ProcessId, sizeof(ProcessId)); |
| 608 | |
| 609 | ProcessEraser.release(); |
| 610 | |
| 611 | if (ARGUMENT_PRESENT(ServerPortHandle)) |
| 612 | { |
| 613 | *ServerPortHandle = ServerPort->ReleaseServerPort(); |
| 614 | } |
| 615 | |
| 616 | return ProcessHandle; |
| 617 | } |
| 618 | |
| 619 | std::vector<gsl::byte> LxssInstance::_CreateLxProcessMarshalMessage( |
| 620 | _In_ const std::shared_ptr<LxssMessagePort>& MessagePort, |
| 621 | _In_ const CreateLxProcessData& CreateProcessData, |
| 622 | _In_ const std::vector<wil::unique_handle>& StdHandles, |
| 623 | _In_ const wil::unique_handle& Token, |
| 624 | _In_ ULONG DefaultUid) const |
| 625 | { |
| 626 | // Allocate a message and initialize the common parameters. |
| 627 | auto Message = LxssCreateProcess::CreateMessage(LxInitMessageCreateProcess, CreateProcessData, DefaultUid); |
| 628 | |
| 629 | const auto MessageLocal = (PLX_INIT_CREATE_PROCESS)Message.data(); |
| 630 | |
| 631 | { |
| 632 | static_assert(LX_INIT_CREATE_PROCESS_USE_CONSOLE == 0); |
| 633 | } |
| 634 | |
| 635 | { |
| 636 | static_assert(LX_INIT_CREATE_PROCESS_USE_CONSOLE == LXSS_HANDLE_USE_CONSOLE); |
| 637 | } |
| 638 | |
| 639 | // Marshal the standard handles. |
| 640 | |
| 641 | auto Eraser = |
| 642 | wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ReleaseHandlesFromLxProcessMarshalMessage(MessagePort, MessageLocal); }); |
| 643 | |
| 644 | for (size_t Index = 0; Index < StdHandles.size(); ++Index) |
| 645 | { |
| 646 | if (StdHandles[Index]) |
| 647 | { |
| 648 | LXBUS_IPC_MESSAGE_MARSHAL_HANDLE_DATA HandleData = {}; |
| 649 | HandleData.Handle = HandleToUlong(StdHandles[Index].get()); |
| 650 | if (Index == 0) |
| 651 | { |
| 652 | HandleData.HandleType = LxBusIpcMarshalHandleTypeInput; |
| 653 | } |
| 654 | else |
| 655 | { |
| 656 | HandleData.HandleType = LxBusIpcMarshalHandleTypeOutput; |
| 657 | } |
| 658 | |
| 659 | MessageLocal->StdFdIds[Index] = MessagePort->MarshalHandle(&HandleData); |
| 660 | } |
| 661 | } |
| 662 | |
| 663 | // Marshal the token. |
| 664 | |
| 665 | { |
| 666 | // Acquire assign primary token in order to pass the primary token for the new process. |
| 667 | auto revertPriv = wsl::windows::common::security::AcquirePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME); |
| 668 | MessageLocal->ForkTokenId = MessagePort->MarshalForkToken(Token.get()); |
| 669 | } |
| 670 | |
| 671 | if (m_configuration.RunOOBE && CreateProcessData.Filename.empty() && CreateProcessData.CommandLine.empty()) |
| 672 | { |
| 673 | WI_SetFlag(MessageLocal->Common.Flags, LxInitCreateProcessFlagAllowOOBE); |
| 674 | } |
| 675 | |
| 676 | Eraser.release(); |
| 677 | return Message; |
| 678 | } |
| 679 | |
| 680 | void LxssInstance::_ReleaseHandlesFromLxProcessMarshalMessage(_In_ const std::shared_ptr<LxssMessagePort>& MessagePort, _In_ PLX_INIT_CREATE_PROCESS Message) |
| 681 | { |
| 682 | if (Message->ForkTokenId != 0) |
| 683 | { |
| 684 | try |
| 685 | { |
| 686 | MessagePort->ReleaseForkToken(Message->ForkTokenId); |
| 687 | } |
| 688 | CATCH_LOG() |
| 689 | } |
| 690 | |
| 691 | for (ULONG Index = 0; Index < RTL_NUMBER_OF(Message->StdFdIds); ++Index) |
| 692 | { |
| 693 | if (Message->StdFdIds[Index] != LXBUS_IPC_CONSOLE_ID_INVALID) |
| 694 | { |
| 695 | try |
| 696 | { |
| 697 | MessagePort->ReleaseHandle(Message->StdFdIds[Index]); |
| 698 | } |
| 699 | CATCH_LOG() |
| 700 | } |
| 701 | } |
| 702 | } |
| 703 | |
| 704 | std::vector<unique_lxss_addmount> LxssInstance::_InitializeMounts() const |
| 705 | { |
| 706 | // Legacy distributions have more than one LxFs mount. If this is a legacy |
| 707 | // distribution add the /home, /root, /data, /cache, and /mnt LxFs mounts. |
| 708 | std::vector<unique_lxss_addmount> mounts; |
| 709 | auto addMount = [&](PCWSTR directory, PCWSTR source, LPCSTR target, ULONG mode) { |
| 710 | mounts.emplace_back(CreateMount((m_configuration.BasePath / directory).c_str(), source, target, LXSS_FS_TYPE_LXFS, mode)); |
| 711 | }; |
| 712 | |
| 713 | switch (m_configuration.Version) |
| 714 | { |
| 715 | case LXSS_DISTRO_VERSION_LEGACY: |
| 716 | |
| 717 | WI_ASSERT(IsEqualGUID(LXSS_LEGACY_DISTRO_GUID, m_configuration.DistroId)); |
| 718 | |
| 719 | addMount(LXSS_ROOT_HOME_MOUNT_NT, LXSS_ROOT_HOME_MOUNT_NT, LXSS_ROOT_HOME_MOUNT_LXSS, LXSS_ROOT_PERMISSIONS); |
| 720 | addMount(LXSS_HOME_MOUNT_NT, LXSS_HOME_MOUNT_NT, LXSS_HOME_MOUNT_LXSS, LXSS_HOME_PERMISSIONS); |
| 721 | addMount(LXSS_DATA_MOUNT_NT, LXSS_DATA_MOUNT_NT, LXSS_DATA_MOUNT_LXSS, LXSS_DATA_PERMISSIONS); |
| 722 | addMount(LXSS_CACHE_MOUNT_NT, LXSS_CACHE_MOUNT_NT, LXSS_CACHE_MOUNT_LXSS, LXSS_CACHE_PERMISSIONS); |
| 723 | addMount(LXSS_MNT_MOUNT_NT, LXSS_MNT_MOUNT_NT, LXSS_MNT_MOUNT_LXSS, LXSS_MNT_PERMISSIONS); |
| 724 | |
| 725 | default: |
| 726 | break; |
| 727 | } |
| 728 | |
| 729 | return mounts; |
| 730 | } |
| 731 | |
| 732 | void LxssInstance::_StartInstance(_In_ ULONG DistributionFlags) |
| 733 | { |
| 734 | std::vector<unique_lxss_addmount> mounts; |
| 735 | wil::unique_handle instanceToken; |
| 736 | { |
| 737 | // Be in the right session for creating the instance parameters. |
| 738 | const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); |
| 739 | auto runAsUser = wil::impersonate_token(userToken.get()); |
| 740 | |
| 741 | // Initialize mount points. |
| 742 | mounts = _InitializeMounts(); |
| 743 | |
| 744 | // Create token for the instance |
| 745 | instanceToken = wsl::windows::common::security::CreateRestrictedToken(userToken.get()); |
| 746 | } |
| 747 | |
| 748 | // Create a new instance. |
| 749 | LX_KINSTANCECREATESTART createParameters = {}; |
| 750 | createParameters.RootFsType = LXSS_DISTRO_USES_WSL_FS(m_configuration.Version) ? LXSS_FS_TYPE_WSLFS : LXSS_FS_TYPE_LXFS; |
| 751 | WI_SetFlagIf(createParameters.Flags, LX_KINSTANCECREATESTART_FLAG_DISABLE_DRIVE_MOUNTING, WI_IsFlagClear(DistributionFlags, LXSS_DISTRO_FLAGS_ENABLE_DRIVE_MOUNTING)); |
| 752 | createParameters.InstanceId = m_instanceId; |
| 753 | createParameters.RootDirectoryHandle = HandleToULong(m_rootDirectory.get()); |
| 754 | createParameters.TempDirectoryHandle = HandleToULong(m_tempDirectory.get()); |
| 755 | createParameters.JobHandle = HandleToULong(m_instanceJob.get()); |
| 756 | createParameters.TokenHandle = HandleToULong(instanceToken.get()); |
| 757 | createParameters.KernelCommandLine = LXSS_DISTRO_DEFAULT_KERNEL_COMMAND_LINE; |
| 758 | createParameters.NumPathsToMap = static_cast<ULONG>(mounts.size()); |
| 759 | createParameters.PathsToMap = static_cast<PLX_KMAPPATHS_ADDMOUNT>(mounts.data()); |
| 760 | createParameters.InstanceTerminatedEventHandle = HandleToULong(m_instanceTerminatedEvent.get()); |
| 761 | const wil::unique_hfile nulDevice{OpenNulDevice(GENERIC_READ | GENERIC_WRITE)}; |
| 762 | LX_KINIT_FILE_DESCRIPTOR initFileDescriptors[] = { |
| 763 | {nulDevice.get(), LX_O_RDONLY, 0}, {nulDevice.get(), LX_O_WRONLY, 0}, {nulDevice.get(), LX_O_WRONLY, 0}}; |
| 764 | |
| 765 | createParameters.NumInitFileDescriptors = RTL_NUMBER_OF(initFileDescriptors); |
| 766 | createParameters.InitFileDescriptors = initFileDescriptors; |
| 767 | |
| 768 | { |
| 769 | // Acquire assign primary token in order to pass the primary token for init process. |
| 770 | auto revertPriv = wsl::windows::common::security::AcquirePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME); |
| 771 | THROW_IF_NTSTATUS_FAILED(::LxssClientInstanceCreate(&createParameters, &m_instanceHandle)); |
| 772 | } |
| 773 | |
| 774 | auto deleter = wil::scope_exit([&] { LOG_IF_NTSTATUS_FAILED(::LxssClientInstanceDestroy(m_instanceHandle.get())); }); |
| 775 | |
| 776 | // Launch the instance. |
| 777 | const wil::unique_handle clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(PROCESS_CREATE_PROCESS | SYNCHRONIZE); |
| 778 | THROW_IF_NTSTATUS_FAILED(::LxssClientInstanceStart(m_instanceHandle.get(), clientProcess.get())); |
| 779 | |
| 780 | deleter.release(); |
| 781 | } |
| 782 | |
| 783 | void LxssInstance::_UpdateNetworkInformation() |
| 784 | try |
| 785 | { |
| 786 | // Impersonate the service. |
| 787 | auto runAsSelf = wil::run_as_self(); |
| 788 | |
| 789 | // Update the resolv.conf file if it has changed. |
| 790 | _UpdateNetworkConfigurationFiles(false); |
| 791 | return; |
| 792 | } |
| 793 | CATCH_LOG() |
| 794 | |
| 795 | void LxssInstance::_UpdateNetworkConfigurationFiles(_In_ bool UpdateAlways) |
| 796 | { |
| 797 | // Generate contents of /etc/resolv.conf file |
| 798 | wsl::core::networking::DnsSettingsFlags flags = wsl::core::networking::DnsSettingsFlags::IncludeIpv6Servers; |
| 799 | WI_SetFlagIf(flags, wsl::core::networking::DnsSettingsFlags::IncludeVpn, m_enableVpnDetection); |
| 800 | |
| 801 | const auto dnsSettings = wsl::core::networking::HostDnsInfo::GetDnsSettings(flags); |
| 802 | std::string fileContents = GenerateResolvConf(dnsSettings); |
| 803 | std::lock_guard<std::mutex> lock(m_resolvConfLock); |
| 804 | if (!UpdateAlways && (fileContents == m_lastResolvConfContents)) |
| 805 | { |
| 806 | return; |
| 807 | } |
| 808 | |
| 809 | // Construct the network information message. |
| 810 | wsl::shared::MessageWriter<LX_INIT_NETWORK_INFORMATION> message(LxInitMessageNetworkInformation); |
| 811 | message.WriteString(message->FileHeaderIndex, wsl::shared::string::WideToMultiByte(LX_INIT_RESOLVCONF_FULL_HEADER)); |
| 812 | message.WriteString(message->FileContentsIndex, fileContents); |
| 813 | auto messageSpan = message.Span(); |
| 814 | |
| 815 | // Send the message. |
| 816 | auto messagePortLock = m_InitMessagePort->Lock(); |
| 817 | m_InitMessagePort->Send(messageSpan.data(), gsl::narrow_cast<ULONG>(messageSpan.size())); |
| 818 | m_lastResolvConfContents = std::move(fileContents); |
| 819 | } |
| 820 | |
| 821 | void LxssInstance::_InitializeNetworking() |
| 822 | { |
| 823 | m_ipTables.EnableIpTablesSupport(m_instanceHandle); |
| 824 | |
| 825 | // Register for network connectivity change notifications to update network information. |
| 826 | LOG_IF_WIN32_ERROR(::NotifyNetworkConnectivityHintChange( |
| 827 | [](PVOID context, NL_NETWORK_CONNECTIVITY_HINT) { static_cast<LxssInstance*>(context)->_UpdateNetworkInformation(); }, this, TRUE, &m_networkNotificationHandle)); |
| 828 | } |
| 829 | |
| 830 | void LxssInstance::_InitiateConnectionToInitProcess() |
| 831 | { |
| 832 | // Register the server, wait for the init process to connect to the server, |
| 833 | // and create the message event. |
| 834 | m_ServerPort->RegisterLxBusServer(m_instanceHandle, LX_INIT_SERVER_NAME); |
| 835 | std::unique_ptr<LxssMessagePort> NewMessagePort = m_ServerPort->WaitForConnection(LXSS_INIT_CONNECTION_TIMEOUT_MS); |
| 836 | |
| 837 | // Associate the server port with the new message port and create a console |
| 838 | // manager that will be used to manage session leaders. |
| 839 | NewMessagePort->SetServerPort(m_ServerPort); |
| 840 | m_InitMessagePort = std::make_shared<LxssMessagePort>(std::move(NewMessagePort)); |
| 841 | m_consoleManager = ConsoleManager::CreateConsoleManager(m_InitMessagePort); |
| 842 | } |
| 843 | |
| 844 | void LxssInstance::_InitializeConfiguration(_In_ const std::filesystem::path& Plan9SocketPath) |
| 845 | { |
| 846 | // If DrvFs mounting is supported, initialize a bitmap of all fixed drives. |
| 847 | ULONG fixedDrives = 0; |
| 848 | if (WI_IsFlagSet(m_configuration.Flags, LXSS_DISTRO_FLAGS_ENABLE_DRIVE_MOUNTING)) |
| 849 | { |
| 850 | fixedDrives = EnumerateFixedDrives().first; |
| 851 | } |
| 852 | |
| 853 | const auto timezone = wsl::windows::common::helpers::GetLinuxTimezone(m_userToken.get()); |
| 854 | ULONG featureFlags{}; |
| 855 | WI_SetFlagIf(featureFlags, LxInitFeatureRootfsCompressed, WI_IsFlagSet(GetFileAttributesW(m_configuration.BasePath.c_str()), FILE_ATTRIBUTE_COMPRESSED)); |
| 856 | auto message = wsl::windows::common::helpers::GenerateConfigurationMessage( |
| 857 | m_configuration.Name, fixedDrives, m_defaultUid, timezone, Plan9SocketPath.wstring(), featureFlags); |
| 858 | |
| 859 | // Send the message to the init daemon. |
| 860 | auto lock = m_InitMessagePort->Lock(); |
| 861 | m_InitMessagePort->Send(message.data(), static_cast<ULONG>(message.size())); |
| 862 | |
| 863 | // Init replies with information about the distribution. |
| 864 | auto buffer = m_InitMessagePort->Receive(); |
| 865 | const auto span = gsl::make_span(buffer); |
| 866 | const auto* response = gslhelpers::try_get_struct<LX_INIT_CONFIGURATION_INFORMATION_RESPONSE>(span); |
| 867 | THROW_HR_IF(E_UNEXPECTED, !response || response->Header.MessageType != LxInitMessageInitializeResponse); |
| 868 | |
| 869 | m_defaultUid = response->DefaultUid; |
| 870 | if (response->VersionIndex > 0) |
| 871 | { |
| 872 | m_configuration.OsVersion = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(span, response->VersionIndex)); |
| 873 | m_distributionInfo.Version = m_configuration.OsVersion.c_str(); |
| 874 | } |
| 875 | |
| 876 | if (response->FlavorIndex > 0) |
| 877 | { |
| 878 | m_configuration.Flavor = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(span, response->FlavorIndex)); |
| 879 | m_distributionInfo.Flavor = m_configuration.Flavor.c_str(); |
| 880 | } |
| 881 | } |