master
cpp 4,340 lines 168 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 LxssUserSession.cpp
8
9 Abstract:
10
11 This file contains session function definitions.
12
13 --*/
14
15 #include "precomp.h"
16 #include "Localization.h"
17 #include "LxssUserSession.h"
18 #include "LxssInstance.h"
19 #include "LxssSecurity.h"
20 #include "notifications.h"
21 #include "WslInstall.h"
22 #include "WslCoreInstance.h"
23 #include "resource.h"
24 #include <winrt\Windows.ApplicationModel.Background.h>
25 #include <nlohmann\json.hpp>
26
27 // Registry keys for migrating legacy distro user config.
28 #define LXSS_LEGACY_APPEND_NT_PATH L"AppendNtPath"
29 #define LXSS_LEGACY_INTEROP_ENABLED L"InteropEnabled"
30
31 #define LXSS_BSDTAR_PATH LXSS_TOOLS_MOUNT "/bsdtar"
32 #define LXSS_BSDTAR_CREATE_ARGS " -c --one-file-system --xattrs -f - ."
33 #define LXSS_BSDTAR_CREATE_ARGS_GZIP " -cz --one-file-system --xattrs -f - ."
34 #define LXSS_BSDTAR_CREATE_ARGS_XZIP " -cJ --one-file-system --xattrs -f - ."
35 #define LXSS_BSDTAR_EXTRACT_ARGS " -x -p --xattrs --no-acls -f -"
36 #define LXSS_ROOTFS_MOUNT "/rootfs"
37 #define LXSS_TOOLS_MOUNT "/tools"
38
39 constexpr auto c_shortIconName = L"shortcut.ico";
40
41 // 16 MB buffer used for relaying tar contents via hvsocket.
42 #define LXSS_RELAY_BUFFER_SIZE (0x1000000)
43
44 extern bool g_lxcoreInitialized;
45
46 using namespace std::placeholders;
47 using namespace Microsoft::WRL;
48 using namespace wsl::windows::service;
49 using wsl::windows::common::Context;
50 using wsl::windows::common::ExecutionContext;
51 using wsl::windows::common::ServiceExecutionContext;
52
53 LxssUserSession::LxssUserSession(_In_ const std::weak_ptr<LxssUserSessionImpl>& Session) : m_session(Session)
54 {
55 return;
56 }
57
58 HRESULT STDMETHODCALLTYPE LxssUserSession::ConfigureDistribution(_In_opt_ LPCGUID DistroGuid, _In_ ULONG DefaultUid, _In_ ULONG Flags, _Out_ LXSS_ERROR_INFO* Error)
59 try
60 {
61 ServiceExecutionContext context(Error);
62
63 const auto session = m_session.lock();
64 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
65
66 return session->ConfigureDistribution(DistroGuid, DefaultUid, Flags);
67 }
68 CATCH_RETURN()
69
70 HRESULT STDMETHODCALLTYPE LxssUserSession::AttachDisk(_In_ LPCWSTR Disk, _In_ ULONG Flags, _Out_ LXSS_ERROR_INFO* Error)
71 try
72 {
73 ServiceExecutionContext context(Error);
74
75 if constexpr (wsl::shared::Arm64)
76 {
77 // Pass-through disk support for ARM64 was added to Windows version 27653.
78 if (wsl::windows::common::helpers::GetWindowsVersion().BuildNumber < 27653)
79 {
80 return WSL_E_WSL_MOUNT_NOT_SUPPORTED;
81 }
82 }
83
84 RETURN_HR_IF(
85 WSL_E_DISK_MOUNT_DISABLED,
86 !wsl::windows::policies::IsFeatureAllowed(wsl::windows::policies::OpenPoliciesKey().get(), wsl::windows::policies::c_allowDiskMount));
87
88 RETURN_HR_IF(
89 E_INVALIDARG,
90 ((WI_IsFlagSet(Flags, LXSS_ATTACH_MOUNT_FLAGS_VHD) && WI_IsFlagSet(Flags, LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH)) ||
91 (WI_IsAnyFlagSet(Flags, ~(LXSS_ATTACH_MOUNT_FLAGS_VHD | LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH)))));
92
93 const auto session = m_session.lock();
94 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
95
96 return session->AttachDisk(Disk, Flags);
97 }
98 CATCH_RETURN()
99
100 HRESULT STDMETHODCALLTYPE LxssUserSession::CreateInstance(_In_opt_ LPCGUID DistroGuid, _In_ ULONG Flags, _Out_ LXSS_ERROR_INFO* Error)
101 try
102 {
103 ServiceExecutionContext context(Error);
104
105 const auto session = m_session.lock();
106 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
107
108 return session->CreateInstance(DistroGuid, Flags);
109 }
110 CATCH_RETURN()
111
112 HRESULT STDMETHODCALLTYPE LxssUserSession::CreateInstance(_In_ LPCWSTR DistributionName, _In_ ULONG Flags)
113 try
114 {
115 RETURN_HR_IF(E_INVALIDARG, WI_IsAnyFlagSet(Flags, ~WslSupportCreateInstanceFlags::IgnoreClient));
116
117 GUID distroGuid{};
118 RETURN_IF_FAILED(GetDistributionId(DistributionName, 0, nullptr, &distroGuid));
119
120 ULONG internalFlags = 0;
121 WI_SetFlagIf(internalFlags, LXSS_CREATE_INSTANCE_FLAGS_IGNORE_CLIENT, WI_IsFlagSet(Flags, WslSupportCreateInstanceFlags::IgnoreClient));
122
123 return CreateInstance(&distroGuid, internalFlags, nullptr);
124 }
125 CATCH_RETURN()
126
127 HRESULT STDMETHODCALLTYPE LxssUserSession::CreateLxProcess(
128 _In_opt_ LPCGUID DistroGuid,
129 _In_opt_ LPCSTR Filename,
130 _In_ ULONG CommandLineCount,
131 _In_reads_opt_(CommandLineCount) LPCSTR* CommandLine,
132 _In_opt_ LPCWSTR CurrentWorkingDirectory,
133 _In_opt_ LPCWSTR NtPath,
134 _In_reads_opt_(NtEnvironmentLength) PWCHAR NtEnvironment,
135 _In_ ULONG NtEnvironmentLength,
136 _In_opt_ LPCWSTR Username,
137 _In_ SHORT Columns,
138 _In_ SHORT Rows,
139 _In_ ULONG ConsoleHandle,
140 _In_ PLXSS_STD_HANDLES StdHandles,
141 _In_ ULONG Flags,
142 _Out_ GUID* DistributionId,
143 _Out_ GUID* InstanceId,
144 _Out_ HANDLE* ProcessHandle,
145 _Out_ HANDLE* ServerHandle,
146 _Out_ HANDLE* StandardIn,
147 _Out_ HANDLE* StandardOut,
148 _Out_ HANDLE* StandardErr,
149 _Out_ HANDLE* CommunicationChannel,
150 _Out_ HANDLE* InteropSocket,
151 _Out_ LXSS_ERROR_INFO* Error)
152 try
153 {
154 ServiceExecutionContext context(Error);
155
156 const auto session = m_session.lock();
157 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
158
159 return session->CreateLxProcess(
160 DistroGuid,
161 Filename,
162 CommandLineCount,
163 CommandLine,
164 CurrentWorkingDirectory,
165 NtPath,
166 NtEnvironment,
167 NtEnvironmentLength,
168 Username,
169 Columns,
170 Rows,
171 ULongToHandle(ConsoleHandle),
172 StdHandles,
173 Flags,
174 DistributionId,
175 InstanceId,
176 ProcessHandle,
177 ServerHandle,
178 StandardIn,
179 StandardOut,
180 StandardErr,
181 CommunicationChannel,
182 InteropSocket);
183 }
184 CATCH_RETURN()
185
186 HRESULT STDMETHODCALLTYPE LxssUserSession::DetachDisk(_In_ LPCWSTR Disk, _Out_ int* Result, _Out_ int* Step, _Out_ LXSS_ERROR_INFO* Error)
187 try
188 {
189 ServiceExecutionContext context(Error);
190
191 const auto session = m_session.lock();
192 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
193
194 return session->DetachDisk(Disk, Result, Step);
195 }
196 CATCH_RETURN()
197
198 HRESULT STDMETHODCALLTYPE LxssUserSession::EnumerateDistributions(
199 _Out_ PULONG DistributionCount, _Out_ LXSS_ENUMERATE_INFO** Distributions, _Out_ LXSS_ERROR_INFO* Error)
200 try
201 {
202 ServiceExecutionContext context(Error);
203
204 const auto session = m_session.lock();
205 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
206
207 return session->EnumerateDistributions(DistributionCount, Distributions);
208 }
209 CATCH_RETURN()
210
211 HRESULT STDMETHODCALLTYPE LxssUserSession::ExportDistribution(
212 _In_opt_ LPCGUID DistroGuid, _In_ HANDLE FileHandle, _In_ HANDLE ErrorHandle, _In_ ULONG Flags, _Out_ LXSS_ERROR_INFO* Error)
213 try
214 {
215 ServiceExecutionContext context(Error);
216
217 const auto session = m_session.lock();
218 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
219
220 return session->ExportDistribution(DistroGuid, FileHandle, ErrorHandle, Flags);
221 }
222 CATCH_RETURN()
223
224 HRESULT STDMETHODCALLTYPE LxssUserSession::ExportDistributionPipe(
225 _In_opt_ LPCGUID DistroGuid, _In_ HANDLE PipeHandle, _In_ HANDLE ErrorHandle, _In_ ULONG Flags, _Out_ LXSS_ERROR_INFO* Error)
226 try
227 {
228 ServiceExecutionContext context(Error);
229
230 const auto session = m_session.lock();
231 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
232
233 return session->ExportDistribution(DistroGuid, PipeHandle, ErrorHandle, Flags);
234 }
235 CATCH_RETURN()
236
237 HRESULT STDMETHODCALLTYPE LxssUserSession::GetDefaultDistribution(_Out_ LXSS_ERROR_INFO* Error, _Out_ LPGUID DefaultDistribution)
238 try
239 {
240 ServiceExecutionContext context(Error);
241
242 const auto session = m_session.lock();
243 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
244
245 return session->GetDefaultDistribution(DefaultDistribution);
246 }
247 CATCH_RETURN()
248
249 HRESULT STDMETHODCALLTYPE LxssUserSession::GetDistributionConfiguration(
250 _In_opt_ LPCGUID DistroGuid,
251 _Out_ LPWSTR* DistributionName,
252 _Out_ ULONG* Version,
253 _Out_ ULONG* DefaultUid,
254 _Out_ ULONG* DefaultEnvironmentCount,
255 _Out_ LPSTR** DefaultEnvironment,
256 _Out_ ULONG* Flags,
257 _Out_ LXSS_ERROR_INFO* Error)
258 try
259 {
260 ServiceExecutionContext context(Error);
261
262 const auto session = m_session.lock();
263 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
264
265 return session->GetDistributionConfiguration(
266 DistroGuid, DistributionName, Version, DefaultUid, DefaultEnvironmentCount, DefaultEnvironment, Flags);
267 }
268 CATCH_RETURN()
269
270 HRESULT STDMETHODCALLTYPE LxssUserSession::GetDistributionConfiguration(
271 _In_ LPCWSTR DistributionName,
272 _Out_ ULONG* Version,
273 _Out_ ULONG* DefaultUid,
274 _Out_ ULONG* DefaultEnvironmentCount,
275 _Out_ LPSTR** DefaultEnvironment,
276 _Out_ ULONG* WslFlags)
277 try
278 {
279 GUID distroGuid{};
280 RETURN_IF_FAILED(GetDistributionId(DistributionName, 0, nullptr, &distroGuid));
281
282 wil::unique_cotaskmem_string distroNameLocal;
283 const auto result = GetDistributionConfiguration(
284 &distroGuid, &distroNameLocal, Version, DefaultUid, DefaultEnvironmentCount, DefaultEnvironment, WslFlags, nullptr);
285
286 WI_ASSERT(FAILED(result) || wsl::shared::string::IsEqual(DistributionName, distroNameLocal.get(), true));
287
288 return result;
289 }
290 CATCH_RETURN()
291
292 HRESULT STDMETHODCALLTYPE LxssUserSession::GetDistributionId(_In_ LPCWSTR DistributionName, _In_ ULONG Flags, _Out_ LXSS_ERROR_INFO* Error, _Out_ GUID* pDistroGuid)
293 try
294 {
295 ServiceExecutionContext context(Error);
296
297 const auto session = m_session.lock();
298 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
299
300 return session->GetDistributionId(DistributionName, Flags, pDistroGuid);
301 }
302 CATCH_RETURN()
303
304 HRESULT STDMETHODCALLTYPE LxssUserSession::ImportDistributionInplace(
305 _In_ LPCWSTR DistributionName, _In_ LPCWSTR VhdPath, _Out_ LXSS_ERROR_INFO* Error, _Out_ GUID* pDistroGuid)
306 try
307 {
308 ServiceExecutionContext context(Error);
309
310 const auto session = m_session.lock();
311 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
312
313 return session->ImportDistributionInplace(DistributionName, VhdPath, pDistroGuid);
314 }
315 CATCH_RETURN()
316
317 HRESULT STDMETHODCALLTYPE LxssUserSession::ListDistributions(_Out_ ULONG* Count, _Out_ LPWSTR** Distributions)
318 try
319 {
320
321 wil::unique_cotaskmem_array_ptr<LXSS_ENUMERATE_INFO> distributions;
322 RETURN_IF_FAILED(EnumerateDistributions(distributions.size_address<ULONG>(), &distributions, nullptr));
323
324 // Filter out distributions that are not in the installed or running state.
325 std::vector<wil::unique_cotaskmem_string> installedDistros{};
326 for (size_t index = 0; index < distributions.size(); index += 1)
327 {
328 if ((distributions[index].State == LxssDistributionStateInstalled) || (distributions[index].State == LxssDistributionStateRunning))
329 {
330 installedDistros.emplace_back(wil::make_unique_string<wil::unique_cotaskmem_string>(distributions[index].DistroName));
331 }
332 }
333
334 auto userDistributions(wil::make_unique_cotaskmem<LPWSTR[]>(installedDistros.size()));
335 for (size_t index = 0; index < installedDistros.size(); index += 1)
336 {
337 userDistributions.get()[index] = installedDistros[index].release();
338 }
339
340 *Count = gsl::narrow_cast<ULONG>(installedDistros.size());
341 *Distributions = userDistributions.release();
342 return S_OK;
343 }
344 CATCH_RETURN()
345
346 HRESULT STDMETHODCALLTYPE LxssUserSession::MountDisk(
347 _In_ LPCWSTR Disk,
348 _In_ ULONG Flags,
349 _In_ ULONG PartitionIndex,
350 _In_opt_ LPCWSTR Name,
351 _In_opt_ LPCWSTR Type,
352 _In_opt_ LPCWSTR Options,
353 _Out_ int* Result,
354 _Out_ int* Step,
355 _Out_ LPWSTR* MountName,
356 _Out_ LXSS_ERROR_INFO* Error)
357 try
358 {
359 ServiceExecutionContext context(Error);
360
361 const auto session = m_session.lock();
362 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
363
364 return session->MountDisk(Disk, Flags, PartitionIndex, Name, Type, Options, Result, Step, MountName);
365 }
366 CATCH_RETURN()
367
368 HRESULT STDMETHODCALLTYPE LxssUserSession::MoveDistribution(_In_ LPCGUID DistroGuid, _In_ LPCWSTR Location, _Out_ LXSS_ERROR_INFO* Error)
369 try
370 {
371 ServiceExecutionContext context(Error);
372
373 const auto session = m_session.lock();
374 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
375
376 return session->MoveDistribution(DistroGuid, Location);
377 }
378 CATCH_RETURN()
379
380 HRESULT STDMETHODCALLTYPE LxssUserSession::RegisterDistribution(
381 _In_ LPCWSTR DistributionName,
382 _In_ ULONG Version,
383 _In_ HANDLE FileHandle,
384 _In_ HANDLE ErrorHandle,
385 _In_ LPCWSTR TargetDirectory,
386 _In_ ULONG Flags,
387 _In_ ULONG64 VhdSize,
388 _In_opt_ LPCWSTR PackageFamilyName,
389 _Out_ LPWSTR* InstalledDistributionName,
390 _Out_ LXSS_ERROR_INFO* Error,
391 _Out_ GUID* pDistroGuid)
392 try
393 {
394 ServiceExecutionContext context(Error);
395
396 const auto session = m_session.lock();
397 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
398
399 return session->RegisterDistribution(
400 DistributionName, Version, FileHandle, ErrorHandle, TargetDirectory, Flags, VhdSize, PackageFamilyName, InstalledDistributionName, pDistroGuid);
401 }
402 CATCH_RETURN()
403
404 HRESULT LxssUserSession::RegisterDistribution(
405 _In_ LPCWSTR DistributionName, _In_ ULONG Version, _In_opt_ HANDLE TarGzFile, _In_opt_ HANDLE TarGzPipe, _In_ LPCWSTR TargetDirectory)
406 try
407 {
408 const auto clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(PROCESS_QUERY_LIMITED_INFORMATION);
409 const auto packageFamilyName = wsl::windows::common::wslutil::GetPackageFamilyName(clientProcess.get());
410 GUID distroGuid{};
411
412 return RegisterDistribution(
413 DistributionName,
414 Version,
415 TarGzFile,
416 nullptr,
417 TargetDirectory,
418 0,
419 0,
420 packageFamilyName.empty() ? nullptr : packageFamilyName.c_str(),
421 nullptr,
422 nullptr,
423 &distroGuid);
424 }
425 CATCH_RETURN();
426
427 HRESULT STDMETHODCALLTYPE LxssUserSession::RegisterDistributionPipe(
428 _In_ LPCWSTR DistributionName,
429 _In_ ULONG Version,
430 _In_ HANDLE PipeHandle,
431 _In_ HANDLE ErrorHandle,
432 _In_ LPCWSTR TargetDirectory,
433 _In_ ULONG Flags,
434 _In_ ULONG64 VhdSize,
435 _In_opt_ LPCWSTR PackageFamilyName,
436 _Out_ LPWSTR* InstalledDistributionName,
437 _Out_ LXSS_ERROR_INFO* Error,
438 _Out_ GUID* pDistroGuid)
439 try
440 {
441 ServiceExecutionContext context(Error);
442
443 const auto session = m_session.lock();
444 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
445
446 return session->RegisterDistribution(
447 DistributionName, Version, PipeHandle, ErrorHandle, TargetDirectory, Flags, VhdSize, PackageFamilyName, InstalledDistributionName, pDistroGuid);
448 }
449 CATCH_RETURN()
450
451 HRESULT STDMETHODCALLTYPE LxssUserSession::SetDefaultDistribution(_In_ LPCGUID DistroGuid, _Out_ LXSS_ERROR_INFO* Error)
452 try
453 {
454 ServiceExecutionContext context(Error);
455
456 const auto session = m_session.lock();
457 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
458
459 return session->SetDefaultDistribution(DistroGuid);
460 }
461 CATCH_RETURN()
462
463 HRESULT STDMETHODCALLTYPE LxssUserSession::SetDistributionConfiguration(_In_ LPCWSTR DistributionName, _In_ ULONG DefaultUid, _In_ ULONG WslFlags)
464 try
465 {
466 GUID distroGuid{};
467 RETURN_IF_FAILED(GetDistributionId(DistributionName, 0, nullptr, &distroGuid));
468
469 return ConfigureDistribution(&distroGuid, DefaultUid, WslFlags, nullptr);
470 }
471 CATCH_RETURN()
472
473 HRESULT STDMETHODCALLTYPE LxssUserSession::SetSparse(_In_ LPCGUID DistroGuid, _In_ BOOLEAN Sparse, _In_ BOOLEAN AllowUnsafe, _Out_ LXSS_ERROR_INFO* Error)
474 try
475 {
476 ServiceExecutionContext context(Error);
477
478 const auto session = m_session.lock();
479 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
480
481 return session->SetSparse(DistroGuid, Sparse, AllowUnsafe);
482 }
483 CATCH_RETURN()
484
485 HRESULT STDMETHODCALLTYPE LxssUserSession::ResizeDistribution(_In_ LPCGUID DistroGuid, _In_ HANDLE OutputHandle, _In_ ULONG64 NewSize, _Out_ LXSS_ERROR_INFO* Error)
486 try
487 {
488 ServiceExecutionContext context(Error);
489
490 const auto session = m_session.lock();
491 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
492
493 return session->ResizeDistribution(DistroGuid, OutputHandle, NewSize);
494 }
495 CATCH_RETURN()
496
497 HRESULT STDMETHODCALLTYPE LxssUserSession::CompactDistribution(_In_ LPCGUID DistroGuid, _Out_ LXSS_ERROR_INFO* Error)
498 try
499 {
500 ServiceExecutionContext context(Error);
501
502 const auto session = m_session.lock();
503 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
504
505 return session->CompactDistribution(DistroGuid);
506 }
507 CATCH_RETURN()
508
509 HRESULT STDMETHODCALLTYPE LxssUserSession::SetVersion(_In_ LPCGUID DistroGuid, _In_ ULONG Version, _In_ HANDLE StdErrHandle, _Out_ LXSS_ERROR_INFO* Error)
510 try
511 {
512 ServiceExecutionContext context(Error);
513
514 const auto session = m_session.lock();
515 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
516
517 return session->SetVersion(DistroGuid, Version, StdErrHandle);
518 }
519 CATCH_RETURN()
520
521 HRESULT STDMETHODCALLTYPE LxssUserSession::Shutdown(BOOL Force)
522 try
523 {
524 const auto session = m_session.lock();
525 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
526
527 return session->Shutdown(false, Force ? ShutdownBehavior::Force : ShutdownBehavior::Wait);
528 }
529 CATCH_RETURN()
530
531 HRESULT STDMETHODCALLTYPE LxssUserSession::Shutdown()
532 try
533 {
534 return Shutdown(false);
535 }
536 CATCH_RETURN()
537
538 HRESULT STDMETHODCALLTYPE LxssUserSession::TerminateDistribution(_In_opt_ LPCGUID DistroGuid, _Out_ LXSS_ERROR_INFO* Error)
539 try
540 {
541 ServiceExecutionContext context(Error);
542
543 const auto session = m_session.lock();
544 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
545
546 return session->TerminateDistribution(DistroGuid);
547 }
548 CATCH_RETURN()
549
550 HRESULT STDMETHODCALLTYPE LxssUserSession::UnregisterDistribution(_In_ LPCGUID DistroGuid, _Out_ LXSS_ERROR_INFO* Error)
551 try
552 {
553 ServiceExecutionContext context(Error);
554
555 const auto session = m_session.lock();
556 RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
557
558 return session->UnregisterDistribution(DistroGuid);
559 }
560 CATCH_RETURN()
561
562 HRESULT STDMETHODCALLTYPE LxssUserSession::UnregisterDistribution(_In_ LPCWSTR DistributionName)
563 try
564 {
565 GUID distroGuid;
566 RETURN_IF_FAILED(GetDistributionId(DistributionName, 0, nullptr, &distroGuid));
567
568 return UnregisterDistribution(&distroGuid, nullptr);
569 }
570 CATCH_RETURN()
571
572 LxssUserSessionImpl::LxssUserSessionImpl(_In_ PSID userSid, _In_ DWORD sessionId, _Inout_ wsl::windows::service::PluginManager& pluginManager) :
573 m_sessionId(sessionId), m_pluginManager(pluginManager)
574 {
575 THROW_IF_WIN32_BOOL_FALSE(::CopySid(sizeof(m_userSid), &m_userSid.Sid, userSid));
576
577 try
578 {
579 wil::unique_hkey lxssKey;
580 wil::unique_handle userToken;
581 {
582 userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
583 lxssKey = s_OpenLxssUserKey(userToken.get());
584 }
585
586 static std::atomic<DWORD> sessionCookie;
587
588 m_session = {sessionCookie++, nullptr, &m_userSid.Sid};
589
590 // Detect existing legacy installs and convert them to the new format.
591 const DWORD state =
592 wsl::windows::common::registry::ReadDword(lxssKey.get(), nullptr, LXSS_LEGACY_INSTALL_VALUE, LxssDistributionStateInvalid);
593
594 if (state == LxssDistributionStateInstalled)
595 {
596 // Create a registration for legacy installs and delete legacy
597 // installed state.
598 std::lock_guard lock(m_instanceLock);
599 _CreateLegacyRegistration(lxssKey.get(), userToken.get());
600 wsl::windows::common::registry::DeleteKeyValue(lxssKey.get(), LXSS_LEGACY_INSTALL_VALUE);
601 }
602
603 // Create a threadpool timer to terminate a Linux utility VM that is idle.
604 m_vmTerminationTimer.reset(CreateThreadpoolTimer(s_VmIdleTerminate, this, nullptr));
605 THROW_IF_NULL_ALLOC(m_vmTerminationTimer);
606
607 // Register for timezone update notifications.
608
609 auto listenForTimeZoneChanges = [this] {
610 try
611 {
612 WNDCLASSEX windowClass{};
613 windowClass.cbSize = sizeof(windowClass);
614 windowClass.lpfnWndProc = s_TimezoneWindowProc;
615 windowClass.hInstance = nullptr;
616 windowClass.lpszClassName = L"wslservice-timezone-notifications";
617 THROW_LAST_ERROR_IF(RegisterClassExW(&windowClass) == 0);
618
619 // Note: HWND_MESSAGE cannot be used here because such windows don't receive broadcast messages like WM_TIMECHANGE
620 const wil::unique_hwnd windowHandle{CreateWindowExW(
621 0,
622 windowClass.lpszClassName,
623 nullptr,
624 WS_OVERLAPPEDWINDOW,
625 CW_USEDEFAULT,
626 CW_USEDEFAULT,
627 CW_USEDEFAULT,
628 CW_USEDEFAULT,
629 nullptr,
630 nullptr,
631 windowClass.hInstance,
632 nullptr)};
633
634 THROW_LAST_ERROR_IF(!windowHandle);
635 SetWindowLongPtr(windowHandle.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
636
637 MSG windowMessage{};
638 while (GetMessageW(&windowMessage, nullptr, 0, 0))
639 {
640 TranslateMessage(&windowMessage);
641 DispatchMessage(&windowMessage);
642 }
643 }
644 CATCH_LOG();
645 };
646
647 m_timezoneThread = std::thread{listenForTimeZoneChanges};
648
649 // Shutdown the inbox session for the current user if needed, this is only required once after the
650 // lifted package is installed to ensure that the inbox service has released per-user resources.
651 LOG_IF_FAILED(wil::ResultFromException(WI_DIAGNOSTICS_INFO, [&] {
652 // Open a handle to the service control manager and check if the inbox service is registered.
653 const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE)};
654 THROW_LAST_ERROR_IF(!manager);
655
656 const wil::unique_schandle service{OpenServiceW(manager.get(), LXSS_INBOX_SERVICE_NAME, SERVICE_QUERY_STATUS)};
657 if (!service)
658 {
659 return;
660 }
661
662 // Check if the service is already stopped.
663 SERVICE_STATUS status;
664 THROW_IF_WIN32_BOOL_FALSE(QueryServiceStatus(service.get(), &status));
665
666 if (status.dwCurrentState == SERVICE_STOPPED)
667 {
668 return;
669 }
670
671 // Shutdown the user's session.
672 auto runAsUser = wil::impersonate_token(userToken.get());
673 const auto wslSupport = wil::CoCreateInstance<LxssUserSessionInBox, IWslSupport>(CLSCTX_LOCAL_SERVER | CLSCTX_ENABLE_CLOAKING);
674 THROW_IF_FAILED(wslSupport->Shutdown());
675 }));
676 }
677 CATCH_LOG()
678 }
679
680 LxssUserSessionImpl::~LxssUserSessionImpl()
681 {
682 if (m_timezoneThread.joinable())
683 {
684 LOG_IF_WIN32_BOOL_FALSE(PostThreadMessage(GetThreadId(m_timezoneThread.native_handle()), WM_QUIT, 0, 0));
685 m_timezoneThread.join();
686 }
687
688 m_lifetimeManager.ClearCallbacks();
689
690 // Ensure that if there are no running instances.
691 WI_ASSERT(m_runningInstances.empty());
692 }
693
694 HRESULT LxssUserSessionImpl::AttachDisk(_In_ LPCWSTR Disk, _In_ ULONG Flags)
695 {
696 ExecutionContext context(Context::AttachDisk);
697
698 std::lock_guard lock(m_instanceLock);
699 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
700 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
701
702 // Validate that at least one WSL2 distro is installed
703 auto pred = [&](const auto& e) { return WI_IsFlagSet(e.Read(Property::Flags), LXSS_DISTRO_FLAGS_VM_MODE); };
704
705 auto distributions = _EnumerateDistributions(lxssKey.get(), true);
706 RETURN_HR_IF(WSL_E_WSL2_NEEDED, !std::any_of(distributions.begin(), distributions.end(), pred));
707
708 return wil::ResultFromException([&]() {
709 _CreateVm();
710 const auto diskType = WI_IsFlagSet(Flags, LXSS_ATTACH_MOUNT_FLAGS_VHD) ? WslCoreVm::DiskType::VHD : WslCoreVm::DiskType::PassThrough;
711 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
712 m_utilityVm->AttachDisk(Disk, diskType, {}, true, userToken.get());
713 });
714 }
715
716 HRESULT LxssUserSessionImpl::ConfigureDistribution(_In_opt_ LPCGUID DistroGuid, _In_ ULONG DefaultUid, _In_ ULONG Flags)
717 try
718 {
719 ExecutionContext context(Context::ConfigureDistro);
720
721 WSL_LOG("ConfigureDistribution", TraceLoggingValue(DefaultUid, "DefaultUid"), TraceLoggingValue(Flags, "Flags"));
722
723 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
724 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
725 std::lock_guard lock(m_instanceLock);
726
727 // Ensure the distribution exists.
728 auto distribution = DistributionRegistration::OpenOrDefault(lxssKey.get(), DistroGuid);
729
730 auto configuration = s_GetDistributionConfiguration(distribution);
731
732 // Validate parameters.
733 RETURN_HR_IF(
734 E_INVALIDARG,
735 ((DefaultUid == LX_UID_INVALID) || (Flags != LXSS_DISTRO_FLAGS_UNCHANGED && WI_IsAnyFlagSet(Flags, ~LXSS_DISTRO_FLAGS_ALL))));
736
737 // If the configuration is changed, terminate the distribution so the new settings will take effect.
738 bool modified = false;
739 if (DefaultUid != distribution.Read(Property::DefaultUid))
740 {
741 distribution.Write(Property::DefaultUid, DefaultUid);
742 modified = true;
743 }
744
745 if (Flags != LXSS_DISTRO_FLAGS_UNCHANGED)
746 {
747 // The VM Mode flag is not configurable via this API.
748 if (WI_IsFlagSet(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE))
749 {
750 WI_SetFlag(Flags, LXSS_DISTRO_FLAGS_VM_MODE);
751 }
752 else
753 {
754 WI_ClearFlag(Flags, LXSS_DISTRO_FLAGS_VM_MODE);
755 }
756
757 if (Flags != configuration.Flags)
758 {
759 distribution.Write(Property::Flags, Flags);
760 modified = true;
761 }
762 }
763
764 if (modified)
765 {
766 _TerminateInstanceInternal(&distribution.Id(), false);
767 }
768
769 return S_OK;
770 }
771 CATCH_RETURN()
772
773 HRESULT LxssUserSessionImpl::CreateInstance(_In_opt_ LPCGUID DistroGuid, _In_ ULONG Flags)
774 try
775 {
776 // Register the client process with the lifetime manager so when the last
777 // client goes away the instance is terminated (after a timeout).
778 _CreateInstance(DistroGuid, Flags);
779 return S_OK;
780 }
781 CATCH_RETURN()
782
783 HRESULT LxssUserSessionImpl::CreateLxProcess(
784 _In_opt_ LPCGUID DistroGuid,
785 _In_opt_ LPCSTR Filename,
786 _In_ ULONG CommandLineCount,
787 _In_reads_opt_(CommandLineCount) LPCSTR* CommandLine,
788 _In_opt_ LPCWSTR CurrentWorkingDirectory,
789 _In_opt_ LPCWSTR NtPath,
790 _In_reads_opt_(NtEnvironmentLength) PWCHAR NtEnvironment,
791 _In_ ULONG NtEnvironmentLength,
792 _In_opt_ LPCWSTR Username,
793 _In_ SHORT Columns,
794 _In_ SHORT Rows,
795 _In_ HANDLE ConsoleHandle,
796 _In_ PLXSS_STD_HANDLES StdHandles,
797 _In_ ULONG Flags,
798 _Out_ GUID* DistributionId,
799 _Out_ GUID* InstanceId,
800 _Out_ HANDLE* ProcessHandle,
801 _Out_ HANDLE* ServerHandle,
802 _Out_ HANDLE* StandardIn,
803 _Out_ HANDLE* StandardOut,
804 _Out_ HANDLE* StandardErr,
805 _Out_ HANDLE* CommunicationChannel,
806 _Out_ HANDLE* InteropSocket)
807 try
808 {
809 // This API handles launching processes three ways:
810 // 1. If Filename and CommandLine are both NULL, the user's default shell
811 // is launched. The default shell is stored in /etc/passwd.
812 // 2. If Filename is NULL but CommandLine is not, the user's default shell
813 // is used to invoke the specified command. For example:
814 // /bin/bash -c "command"
815 // 3. If Filename and CommandLine are both non-NULL, they are passed along
816 // as-is to the exec system call by the init daemon.
817
818 // Create an instance to run the process.
819 auto instance = _CreateInstance(DistroGuid, Flags);
820
821 // Query process creation context.
822 auto distributionId = instance->GetDistributionId();
823 auto context = s_GetCreateProcessContext(distributionId, WI_IsFlagSet(Flags, LXSS_CREATE_INSTANCE_FLAGS_USE_SYSTEM_DISTRO));
824
825 _SetHttpProxyInfo(context.DefaultEnvironment);
826
827 // Parse the create process params.
828 auto parsed = LxssCreateProcess::ParseArguments(
829 Filename,
830 CommandLineCount,
831 CommandLine,
832 CurrentWorkingDirectory,
833 NtPath,
834 NtEnvironment,
835 NtEnvironmentLength,
836 Username,
837 context.DefaultEnvironment,
838 context.Flags);
839
840 if (WI_IsFlagSet(Flags, LXSS_CREATE_INSTANCE_FLAGS_SHELL_LOGIN))
841 {
842 THROW_HR_IF(E_INVALIDARG, ARGUMENT_PRESENT(Filename));
843 parsed.ShellOptions = ShellOptionsLogin;
844 }
845
846 // Initialize console data and launch the process.
847 CreateLxProcessConsoleData consoleData;
848 if (ConsoleHandle)
849 {
850 consoleData.ConsoleHandle.reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ConsoleHandle));
851 }
852
853 consoleData.ClientProcess = wsl::windows::common::wslutil::OpenCallingProcess(PROCESS_VM_READ | GENERIC_READ | SYNCHRONIZE);
854 instance->CreateLxProcess(
855 parsed, context, consoleData, Columns, Rows, StdHandles, InstanceId, ProcessHandle, ServerHandle, StandardIn, StandardOut, StandardErr, CommunicationChannel, InteropSocket);
856
857 *DistributionId = distributionId;
858 return S_OK;
859 }
860 CATCH_RETURN()
861
862 void LxssUserSessionImpl::ClearDiskStateInRegistry(_In_ const LPCWSTR Disk)
863 {
864 bool deleted = !ARGUMENT_PRESENT(Disk);
865
866 const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(&m_userSid.Sid);
867 for (const auto& e : wsl::windows::common::registry::EnumKeys(key.get(), KEY_READ))
868 {
869 if (Disk == nullptr || wsl::windows::common::registry::ReadString(e.second.get(), nullptr, c_diskValueName) == Disk)
870 {
871 wsl::windows::common::registry::DeleteKey(key.get(), e.first.c_str());
872 deleted = true;
873 }
874 }
875
876 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !deleted);
877 }
878
879 HRESULT LxssUserSessionImpl::DetachDisk(_In_ LPCWSTR Disk, _Out_ int* Result, _Out_ int* Step)
880 {
881 ExecutionContext context(Context::DetachDisk);
882
883 std::lock_guard lock(m_instanceLock);
884
885 // If the UVM isn't running, simply clear the disk state in the registry, if any
886 if (!m_utilityVm)
887 {
888 return wil::ResultFromException([&]() {
889 ClearDiskStateInRegistry(Disk);
890 *Result = 0;
891 *Step = LxMiniInitMountStepNone;
892 });
893 }
894
895 return wil::ResultFromException([&]() { std::tie(*Result, *Step) = m_utilityVm->DetachDisk(Disk); });
896 }
897
898 HRESULT LxssUserSessionImpl::MountDisk(
899 _In_ LPCWSTR Disk,
900 _In_ ULONG Flags,
901 _In_ ULONG PartitionIndex,
902 _In_opt_ LPCWSTR Name,
903 _In_opt_ LPCWSTR Type,
904 _In_opt_ LPCWSTR Options,
905 _Out_ int* Result,
906 _Out_ int* Step,
907 _Out_ LPWSTR* MountName)
908 {
909 std::lock_guard lock(m_instanceLock);
910 return wil::ResultFromException([&]() {
911 _CreateVm();
912 ExecutionContext context(Context::MountDisk);
913 const auto MountDiskType = WI_IsFlagSet(Flags, LXSS_ATTACH_MOUNT_FLAGS_VHD) ? WslCoreVm::DiskType::VHD : WslCoreVm::DiskType::PassThrough;
914 const auto MountResult = m_utilityVm->MountDisk(Disk, MountDiskType, PartitionIndex, Name, Type, Options);
915 const auto MountNameWide = wsl::shared::string::MultiByteToWide(MountResult.MountPointName);
916 *Result = MountResult.Result;
917 *Step = MountResult.Step;
918 *MountName = wil::make_unique_string<wil::unique_cotaskmem_string>(MountNameWide.c_str()).release();
919 });
920 }
921
922 HRESULT LxssUserSessionImpl::MoveDistribution(_In_ LPCGUID DistroGuid, _In_ LPCWSTR Location)
923 {
924 ExecutionContext context(Context::MoveDistro);
925
926 std::lock_guard lock(m_instanceLock);
927
928 // Fail if the distribution is running.
929 RETURN_HR_IF(WSL_E_DISTRO_NOT_STOPPED, m_runningInstances.contains(*DistroGuid));
930
931 // Fail if a conversion or export is in progress for this distribution. Those operations release
932 // m_instanceLock while running but keep the distribution in m_lockedDistributions, so mutating
933 // the VHD here would race with them.
934 _EnsureNotLocked(DistroGuid);
935
936 // Lookup the distribution configuration
937 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
938 const auto lxssKey = s_OpenLxssUserKey(userToken.get());
939 _ValidateDistributionNameAndPathNotInUse(lxssKey.get(), Location, nullptr);
940
941 auto registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid);
942 auto distro = s_GetDistributionConfiguration(registration);
943
944 RETURN_HR_IF(E_NOTIMPL, WI_IsFlagClear(distro.Flags, LXSS_DISTRO_FLAGS_VM_MODE));
945
946 std::filesystem::path destDir(Location);
947 RETURN_HR_IF(E_INVALIDARG, destDir.empty());
948
949 const std::filesystem::path destPath = destDir / distro.VhdFilePath.filename();
950
951 // Cross-volume MoveFileEx creates a new file using the impersonation token's
952 // default owner. Normalize that owner to the caller's user SID so elevated moves
953 // do not produce a VHD owned by BUILTIN\Administrators.
954 PSID originalVhdOwner = nullptr;
955 wil::unique_hlocal originalSecurityDescriptor;
956 {
957 auto impersonate = wil::impersonate_token(userToken.get());
958 THROW_IF_WIN32_ERROR(GetNamedSecurityInfoW(
959 distro.VhdFilePath.c_str(), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &originalVhdOwner, nullptr, nullptr, nullptr, &originalSecurityDescriptor));
960 }
961
962 auto tokenUser = wil::get_token_information<TOKEN_USER>(userToken.get());
963 TOKEN_OWNER tokenOwner{tokenUser->User.Sid};
964 THROW_IF_WIN32_BOOL_FALSE(SetTokenInformation(userToken.get(), TokenOwner, &tokenOwner, sizeof(tokenOwner)));
965
966 {
967 auto impersonate = wil::impersonate_token(userToken.get());
968
969 std::error_code error;
970 std::filesystem::create_directories(destDir, error);
971 if (error.value())
972 {
973 THROW_WIN32(error.value());
974 }
975
976 THROW_IF_WIN32_BOOL_FALSE(MoveFileExW(distro.VhdFilePath.c_str(), destPath.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH));
977 }
978
979 auto revert = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
980 TOKEN_OWNER originalOwner{originalVhdOwner};
981 LOG_IF_WIN32_BOOL_FALSE(SetTokenInformation(userToken.get(), TokenOwner, &originalOwner, sizeof(originalOwner)));
982
983 auto impersonate = wil::impersonate_token(userToken.get());
984 if (!MoveFileExW(destPath.c_str(), distro.VhdFilePath.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
985 {
986 LOG_LAST_ERROR();
987 return;
988 }
989
990 LOG_IF_FAILED(wil::ResultFromException(
991 WI_DIAGNOSTICS_INFO, [&]() { registration.Write(Property::BasePath, distro.BasePath.c_str()); }));
992 });
993
994 registration.Write(Property::BasePath, Location);
995 registration.Write(Property::VhdFileName, destPath.filename().c_str());
996
997 revert.release();
998 return S_OK;
999 }
1000
1001 HRESULT LxssUserSessionImpl::EnumerateDistributions(_Out_ PULONG DistributionCount, _Out_ LXSS_ENUMERATE_INFO** Distributions)
1002 {
1003 // Get a list of all registered distributions.
1004 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1005 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1006 std::lock_guard lock(m_instanceLock);
1007 const auto distributions = _EnumerateDistributions(lxssKey.get(), true);
1008
1009 // Get the default distribution.
1010 //
1011 // N.B. It is possible the default to not exist, for example if there is
1012 // a single distribution that is being installed.
1013 GUID defaultGuid = GUID_NULL;
1014 try
1015 {
1016 defaultGuid = _GetDefaultDistro(lxssKey.get());
1017 }
1018 CATCH_LOG()
1019
1020 const ULONG numberOfDistributions = gsl::narrow_cast<ULONG>(distributions.size());
1021 auto userDistributions(wil::make_unique_cotaskmem<LXSS_ENUMERATE_INFO[]>(numberOfDistributions));
1022
1023 // Fill in information about each distribution.
1024 for (ULONG index = 0; index < numberOfDistributions; index += 1)
1025 {
1026
1027 auto configuration = s_GetDistributionConfiguration(distributions[index]);
1028 auto state = static_cast<LxssDistributionState>(configuration.State);
1029
1030 if (m_runningInstances.contains(distributions[index].Id()))
1031 {
1032 state = LxssDistributionStateRunning;
1033 }
1034 else if (state == LxssDistributionStateInstalled)
1035 {
1036 auto distro = std::find_if(m_lockedDistributions.begin(), m_lockedDistributions.end(), [&](const auto& pair) {
1037 return IsEqualGUID(distributions[index].Id(), pair.first);
1038 });
1039
1040 if (distro != m_lockedDistributions.end())
1041 {
1042 state = distro->second;
1043 }
1044 }
1045
1046 const auto current = &userDistributions.get()[index];
1047 current->DistroGuid = distributions[index].Id();
1048 current->State = state;
1049 current->Version = WI_IsFlagSet(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE) ? LXSS_WSL_VERSION_2 : LXSS_WSL_VERSION_1;
1050 current->Flags = 0;
1051 WI_SetFlagIf(current->Flags, LXSS_ENUMERATE_FLAGS_DEFAULT, IsEqualGUID(distributions[index].Id(), defaultGuid));
1052
1053 static_assert((RTL_NUMBER_OF(current->DistroName) - 1) == LX_INIT_DISTRO_NAME_MAX);
1054
1055 memset(current->DistroName, 0, sizeof(current->DistroName));
1056 wcscpy_s(current->DistroName, RTL_NUMBER_OF(current->DistroName), configuration.Name.c_str());
1057 }
1058
1059 *DistributionCount = numberOfDistributions;
1060 *Distributions = userDistributions.release();
1061 return S_OK;
1062 }
1063
1064 HRESULT LxssUserSessionImpl::ExportDistribution(_In_opt_ LPCGUID DistroGuid, _In_ HANDLE FileHandle, _In_ HANDLE ErrorHandle, _In_ ULONG Flags)
1065 {
1066 RETURN_HR_IF(E_INVALIDARG, (WI_IsAnyFlagSet(Flags, ~LXSS_EXPORT_DISTRO_FLAGS_ALL)));
1067
1068 LXSS_DISTRO_CONFIGURATION configuration;
1069 wil::unique_hkey distroKey;
1070 try
1071 {
1072 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1073 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1074 std::lock_guard lock(m_instanceLock);
1075
1076 const auto registration = DistributionRegistration::OpenOrDefault(lxssKey.get(), DistroGuid);
1077
1078 // Ensure the distribution is installed.
1079 configuration = s_GetDistributionConfiguration(registration);
1080 RETURN_HR_IF(E_ILLEGAL_STATE_CHANGE, (configuration.State != LxssDistributionStateInstalled));
1081
1082 // Exporting a WSL1 distro is not possible if the VHD flag is specified.
1083 RETURN_HR_IF(WSL_E_WSL2_NEEDED, WI_IsFlagSet(Flags, LXSS_EXPORT_DISTRO_FLAGS_VHD) && WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE));
1084
1085 // Exporting a WSL1 distro is not possible if the lxcore driver is not present.
1086 RETURN_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE) && !g_lxcoreInitialized);
1087
1088 // Add the distribution to the list of converting distributions.
1089 _ConversionBegin(configuration.DistroId, LxssDistributionStateExporting);
1090 }
1091 CATCH_RETURN()
1092
1093 // Set up a scope exit member to remove the distribution from the converting list.
1094 auto exportComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); });
1095
1096 // Log telemetry to track how long exporting the distribution takes.
1097 WSL_LOG_TELEMETRY(
1098 "ExportDistributionBegin",
1099 PDT_ProductAndServicePerformance,
1100 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
1101 TraceLoggingValue(Flags, "flags"));
1102
1103 HRESULT result;
1104 auto enableExit = wil::scope_exit([&] {
1105 WSL_LOG_TELEMETRY(
1106 "ExportDistributionEnd",
1107 PDT_ProductAndServicePerformance,
1108 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
1109 TraceLoggingValue(result, "result"),
1110 TraceLoggingValue(Flags, "flags"));
1111 });
1112
1113 // Export the distribution.
1114 try
1115 {
1116 const wil::unique_handle clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(GENERIC_READ | SYNCHRONIZE);
1117 if (WI_IsFlagSet(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE))
1118 {
1119 if (WI_IsFlagSet(Flags, LXSS_EXPORT_DISTRO_FLAGS_VHD))
1120 {
1121 const wil::unique_handle userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1122 auto runAsUser = wil::impersonate_token(userToken.get());
1123
1124 // Ensure the target file has the correct file extension.
1125 if (GetFileType(FileHandle) == FILE_TYPE_DISK)
1126 {
1127 std::wstring exportPath;
1128 THROW_IF_FAILED(wil::GetFinalPathNameByHandleW(FileHandle, exportPath));
1129
1130 const auto sourceFileExtension = configuration.VhdFilePath.extension().native();
1131 const auto targetFileExtension = std::filesystem::path(exportPath).extension().native();
1132 if (!wsl::windows::common::string::IsPathComponentEqual(sourceFileExtension, targetFileExtension))
1133 {
1134 THROW_HR_WITH_USER_ERROR(
1135 WSL_E_EXPORT_FAILED, wsl::shared::Localization::MessageRequiresFileExtension(sourceFileExtension.c_str()));
1136 }
1137 }
1138
1139 const wil::unique_hfile vhdFile(CreateFileW(
1140 configuration.VhdFilePath.c_str(), GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
1141
1142 RETURN_LAST_ERROR_IF(!vhdFile);
1143
1144 wsl::windows::common::relay::InterruptableRelay(vhdFile.get(), FileHandle, clientProcess.get(), LXSS_RELAY_BUFFER_SIZE);
1145 }
1146 else
1147 {
1148 auto vmContext = _RunUtilityVmSetup(configuration, LxMiniInitMessageExport, Flags);
1149
1150 wsl::windows::common::relay::ScopedRelay stdErrRelay(
1151 wil::unique_handle{reinterpret_cast<HANDLE>(vmContext.errorSocket.release())}, ErrorHandle);
1152
1153 // Relay the filesystem file contents to the tar.gz handle.
1154 wsl::windows::common::relay::InterruptableRelay(
1155 reinterpret_cast<HANDLE>(vmContext.tarSocket.get()), FileHandle, clientProcess.get(), LXSS_RELAY_BUFFER_SIZE);
1156
1157 // Wait for the utility VM to finish expanding the tar and ensure that
1158 // the operation was successful.
1159 ULONG exitCode = 1;
1160 vmContext.instance->GetInitPort()->Receive(&exitCode, sizeof(exitCode), clientProcess.get());
1161
1162 // Flush any pending IO on the error relay before exiting.
1163 stdErrRelay.Sync();
1164
1165 THROW_HR_IF(WSL_E_EXPORT_FAILED, (exitCode != 0));
1166 }
1167 }
1168 else
1169 {
1170 auto mounts = _CreateSetupMounts(configuration);
1171
1172 const char* formatArgs = nullptr;
1173
1174 if (WI_IsFlagSet(Flags, LXSS_EXPORT_DISTRO_FLAGS_GZIP))
1175 {
1176 THROW_HR_IF(E_INVALIDARG, WI_IsFlagSet(Flags, LXSS_EXPORT_DISTRO_FLAGS_XZIP));
1177
1178 formatArgs = LXSS_BSDTAR_CREATE_ARGS_GZIP;
1179 }
1180 else if (WI_IsFlagSet(Flags, LXSS_EXPORT_DISTRO_FLAGS_XZIP))
1181 {
1182 formatArgs = LXSS_BSDTAR_CREATE_ARGS_XZIP;
1183 }
1184 else
1185 {
1186 formatArgs = LXSS_BSDTAR_CREATE_ARGS;
1187 }
1188
1189 const auto commandLine = std::format("{} -C {}{}", LXSS_BSDTAR_PATH, LXSS_ROOTFS_MOUNT, formatArgs);
1190
1191 const auto elfContext = _RunElfBinary(
1192 commandLine.c_str(),
1193 configuration.BasePath.c_str(),
1194 clientProcess.get(),
1195 nullptr,
1196 FileHandle,
1197 ErrorHandle,
1198 mounts.data(),
1199 static_cast<ULONG>(mounts.size()));
1200
1201 const auto exitStatus = _GetElfExitStatus(elfContext);
1202 THROW_HR_IF(WSL_E_EXPORT_FAILED, exitStatus != 0);
1203 }
1204
1205 result = S_OK;
1206 }
1207 catch (...)
1208 {
1209 result = wil::ResultFromCaughtException();
1210 }
1211
1212 return result;
1213 }
1214
1215 HRESULT LxssUserSessionImpl::GetDefaultDistribution(_Out_ LPGUID DefaultDistribution)
1216 try
1217 {
1218 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1219 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1220 std::lock_guard lock(m_instanceLock);
1221 *DefaultDistribution = _GetDefaultDistro(lxssKey.get());
1222 return S_OK;
1223 }
1224 CATCH_RETURN()
1225
1226 HRESULT LxssUserSessionImpl::GetDistributionConfiguration(
1227 _In_opt_ LPCGUID DistroGuid,
1228 _Out_ LPWSTR* DistributionName,
1229 _Out_ ULONG* Version,
1230 _Out_ ULONG* DefaultUid,
1231 _Out_ ULONG* DefaultEnvironmentCount,
1232 _Out_ LPSTR** DefaultEnvironment,
1233 _Out_ ULONG* Flags)
1234 try
1235 {
1236 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1237 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1238 std::lock_guard lock(m_instanceLock);
1239
1240 const auto registration = DistributionRegistration::OpenOrDefault(lxssKey.get(), DistroGuid);
1241 const auto configuration = s_GetDistributionConfiguration(registration);
1242
1243 // Write configuration information back to the calling process.
1244 *DistributionName = wil::make_cotaskmem_string(configuration.Name.c_str()).release();
1245 *Version = configuration.Version;
1246 *DefaultUid = registration.Read(Property::DefaultUid);
1247 *Flags = configuration.Flags;
1248 const auto defaultEnvironment = registration.Read(Property::DefaultEnvironment);
1249 *DefaultEnvironmentCount = gsl::narrow_cast<ULONG>(defaultEnvironment.size());
1250 auto environment(wil::make_unique_cotaskmem<LPSTR[]>(defaultEnvironment.size()));
1251 for (size_t index = 0; index < defaultEnvironment.size(); index += 1)
1252 {
1253 environment.get()[index] =
1254 wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(defaultEnvironment[index].c_str()).release();
1255 }
1256
1257 *DefaultEnvironment = environment.release();
1258 return S_OK;
1259 }
1260 CATCH_RETURN()
1261
1262 HRESULT LxssUserSessionImpl::GetDistributionId(_In_ LPCWSTR DistributionName, _In_ ULONG Flags, _Out_ GUID* pDistroGuid)
1263 try
1264 {
1265 // The client must provide a non-empty string.
1266 //
1267 // N.B. COM insures that the name buffer is non-NULL.
1268 RETURN_HR_IF(E_INVALIDARG, (wcslen(DistributionName) == 0));
1269
1270 // Validate flags.
1271 RETURN_HR_IF(E_INVALIDARG, (WI_IsAnyFlagSet(Flags, ~LXSS_GET_DISTRO_ID_LIST_ALL)));
1272
1273 // Open the user's lxss registry key.
1274 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1275 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1276 const bool listAll = WI_IsFlagSet(Flags, LXSS_GET_DISTRO_ID_LIST_ALL);
1277 bool distroFound = false;
1278
1279 // Lock the session and search for a distribution that has a matching name.
1280 std::lock_guard lock(m_instanceLock);
1281 const auto distros = _EnumerateDistributions(lxssKey.get(), listAll);
1282 for (const auto& registration : distros)
1283 {
1284 if (wsl::shared::string::IsEqual(DistributionName, registration.Read(Property::Name), true))
1285 {
1286 distroFound = true;
1287 *pDistroGuid = registration.Id();
1288 break;
1289 }
1290 }
1291
1292 // Return an error if no distribution was found with a matching name.
1293 RETURN_HR_IF(WSL_E_DISTRO_NOT_FOUND, !distroFound);
1294
1295 return S_OK;
1296 }
1297 CATCH_RETURN()
1298
1299 DWORD LxssUserSessionImpl::GetSessionCookie() const
1300 {
1301 return m_session.SessionId;
1302 }
1303
1304 DWORD LxssUserSessionImpl::GetSessionId() const
1305 {
1306 return m_sessionId;
1307 }
1308
1309 PSID LxssUserSessionImpl::GetUserSid()
1310 {
1311 return &m_userSid.Sid;
1312 }
1313
1314 HRESULT
1315 LxssUserSessionImpl::ImportDistributionInplace(_In_ LPCWSTR DistributionName, _In_ LPCWSTR VhdPath, _Out_ GUID* pDistroGuid)
1316 {
1317 ExecutionContext context(Context::RegisterDistro);
1318
1319 s_ValidateDistroName(DistributionName);
1320
1321 // Return an error if the path is not absolute or does not have a valid VHD file extension.
1322 const std::filesystem::path path{VhdPath};
1323 RETURN_HR_IF(E_INVALIDARG, !path.is_absolute() || !wsl::windows::common::wslutil::IsVhdFile(path));
1324
1325 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1326 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1327 std::lock_guard lock(m_instanceLock);
1328
1329 // Create a registration for the distribution.
1330 //
1331 // N.B. Import inplace is always WSL2.
1332 _ValidateDistributionNameAndPathNotInUse(lxssKey.get(), path.parent_path().c_str(), DistributionName);
1333
1334 constexpr ULONG flags = LXSS_DISTRO_FLAGS_DEFAULT | LXSS_DISTRO_FLAGS_VM_MODE;
1335 auto registration = DistributionRegistration::Create(
1336 lxssKey.get(),
1337 {},
1338 DistributionName,
1339 LXSS_DISTRO_VERSION_CURRENT,
1340 path.parent_path().c_str(),
1341 flags,
1342 LX_UID_ROOT,
1343 nullptr,
1344 path.filename().c_str(),
1345 false);
1346
1347 auto configuration = s_GetDistributionConfiguration(registration);
1348
1349 // Declare a scope exit variable to clean up on failure.
1350 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
1351 {
1352 auto runAsUser = wil::impersonate_token(userToken.get());
1353 _DeleteDistribution(configuration, LXSS_DELETE_DISTRO_FLAGS_UNMOUNT);
1354 }
1355
1356 registration.Delete(lxssKey.get());
1357 });
1358
1359 const auto vmContext = _RunUtilityVmSetup(configuration, LxMiniInitMessageImportInplace);
1360 auto* channel = dynamic_cast<WslCoreInstance::WslCorePort*>(vmContext.instance->GetInitPort().get());
1361
1362 gsl::span<gsl::byte> span;
1363 const auto& message = channel->GetChannel().ReceiveMessage<LX_MINI_INIT_IMPORT_RESULT>(&span);
1364
1365 // Process the import result message.
1366 THROW_HR_IF(WSL_E_IMPORT_FAILED, (message.Result != 0));
1367
1368 _ProcessImportResultMessage(message, span, lxssKey.get(), configuration, registration);
1369
1370 // Set the distribution as installed.
1371 _SetDistributionInstalled(lxssKey.get(), registration.Id());
1372 cleanup.release();
1373
1374 _SendDistributionRegisteredEvent(configuration);
1375
1376 _LaunchOOBEIfNeeded();
1377
1378 // Log when a distro is imported in place
1379 WSL_LOG_TELEMETRY(
1380 "ImportDistributionInplace",
1381 PDT_ProductAndServiceUsage,
1382 TraceLoggingValue(DistributionName, "distroName"),
1383 TraceLoggingValue(path.filename().c_str(), "fileName"));
1384
1385 *pDistroGuid = registration.Id();
1386 return S_OK;
1387 }
1388
1389 HRESULT LxssUserSessionImpl::RegisterDistribution(
1390 _In_ LPCWSTR DistributionName,
1391 _In_ ULONG Version,
1392 _In_ HANDLE FileHandle,
1393 _In_ HANDLE ErrorHandle,
1394 _In_ LPCWSTR TargetDirectory,
1395 _In_ ULONG Flags,
1396 _In_ ULONG64 VhdSize,
1397 _In_opt_ LPCWSTR PackageFamilyName,
1398 _Out_opt_ LPWSTR* InstalledDistributionName,
1399 _Out_ GUID* pDistroGuid)
1400 {
1401 ExecutionContext context(Context::RegisterDistro);
1402
1403 RETURN_HR_IF(E_INVALIDARG, (WI_IsAnyFlagSet(Flags, ~LXSS_IMPORT_DISTRO_FLAGS_ALL)));
1404
1405 // Set up a scope exit member to log registration status.
1406 HRESULT result = E_FAIL;
1407 auto registerExit = wil::scope_exit([&] {
1408 // Log when a distribution registration ends and its result
1409 WSL_LOG_TELEMETRY(
1410 "RegisterDistributionEnd",
1411 PDT_ProductAndServiceUsage,
1412 TraceLoggingValue(DistributionName, "name"),
1413 TraceLoggingHexUInt32(result, "result"),
1414 TraceLoggingValue(Version, "version"),
1415 TraceLoggingValue(Flags, "flags"));
1416 });
1417
1418 try
1419 {
1420 // Log when a distribution is being registered in WSL
1421 WSL_LOG_TELEMETRY(
1422 "RegisterDistributionBegin",
1423 PDT_ProductAndServiceUsage,
1424 TraceLoggingValue(DistributionName, "name"),
1425 TraceLoggingValue(Version, "version"),
1426 TraceLoggingValue(Flags, "flags"));
1427
1428 if (DistributionName != nullptr)
1429 {
1430 s_ValidateDistroName(DistributionName);
1431 }
1432
1433 // Impersonate the user and open their lxss registry key.
1434 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1435 wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1436
1437 // Determine the filesystem version. If WslFs is not enabled, downgrade
1438 // the version.
1439 ULONG FilesystemVersion = LXSS_DISTRO_VERSION_CURRENT;
1440 if (wsl::windows::common::registry::ReadDword(lxssKey.get(), nullptr, WSL_NEW_DISTRO_LXFS, 0) != 0)
1441 {
1442 if (LXSS_DISTRO_USES_WSL_FS(FilesystemVersion) != FALSE)
1443 {
1444 FilesystemVersion = LXSS_DISTRO_VERSION_1;
1445 }
1446 }
1447
1448 // Validate the version number.
1449 if (Version == LXSS_WSL_VERSION_DEFAULT)
1450 {
1451 Version = wsl::windows::common::registry::ReadDword(lxssKey.get(), nullptr, LXSS_WSL_DEFAULT_VERSION, LXSS_WSL_VERSION_2);
1452 }
1453
1454 RETURN_HR_IF(E_INVALIDARG, ((Version != LXSS_WSL_VERSION_1) && (Version != LXSS_WSL_VERSION_2)));
1455
1456 // Registering a WSL1 distro is not possible if any VHD flags are specified.
1457 RETURN_HR_IF(
1458 WSL_E_WSL2_NEEDED,
1459 WI_IsAnyFlagSet(Flags, LXSS_IMPORT_DISTRO_FLAGS_VHD | LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD) && (Version == LXSS_WSL_VERSION_1));
1460
1461 // Registering a vhd with the fixed vhd flag is not allowed.
1462 if (WI_IsFlagSet(Flags, LXSS_IMPORT_DISTRO_FLAGS_VHD))
1463 {
1464 RETURN_HR_IF(E_INVALIDARG, WI_IsFlagSet(Flags, LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD));
1465 }
1466
1467 // Registering a distro with a fixed VHD is only allowed if a size is specified.
1468 RETURN_HR_IF(E_INVALIDARG, VhdSize == 0 && WI_IsFlagSet(Flags, LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD));
1469
1470 // Registering a WSL1 distro is not possible if the lxcore driver is not present.
1471 RETURN_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, (Version == LXSS_WSL_VERSION_1) && !g_lxcoreInitialized);
1472
1473 DistributionRegistration registration;
1474 LXSS_DISTRO_CONFIGURATION configuration;
1475 std::filesystem::path distributionPath;
1476 auto config = _GetResultantConfig(userToken.get());
1477
1478 {
1479 std::lock_guard lock(m_instanceLock);
1480
1481 // Create a registration for the distribution and determine which version should be used.
1482 ULONG flags = LXSS_DISTRO_FLAGS_DEFAULT;
1483 WI_SetFlagIf(flags, LXSS_DISTRO_FLAGS_VM_MODE, (Version == LXSS_WSL_VERSION_2));
1484
1485 GUID DistributionId{};
1486 THROW_IF_FAILED(CoCreateGuid(&DistributionId));
1487
1488 if (TargetDirectory == nullptr)
1489 {
1490 distributionPath = config.DefaultDistributionLocation / wsl::shared::string::GuidToString<wchar_t>(DistributionId);
1491 }
1492 else
1493 {
1494 distributionPath = TargetDirectory;
1495 }
1496
1497 TargetDirectory = nullptr; // Make sure this isn't reused later.
1498
1499 _ValidateDistributionNameAndPathNotInUse(lxssKey.get(), distributionPath.c_str(), DistributionName);
1500
1501 if (!std::filesystem::exists(distributionPath))
1502 {
1503 auto impersonate = wil::CoImpersonateClient();
1504 wil::CreateDirectoryDeep(distributionPath.c_str());
1505 }
1506
1507 // If importing a vhd, determine if it is a .vhd or .vhdx.
1508 std::wstring vhdName{LXSS_VM_MODE_VHD_NAME};
1509 if ((WI_IsFlagSet(Flags, LXSS_IMPORT_DISTRO_FLAGS_VHD)) && (GetFileType(FileHandle) == FILE_TYPE_DISK))
1510 {
1511 std::wstring pathBuffer;
1512 THROW_IF_FAILED(wil::GetFinalPathNameByHandleW(FileHandle, pathBuffer));
1513
1514 std::filesystem::path vhdPath{std::move(pathBuffer)};
1515 if (!wsl::windows::common::wslutil::IsVhdFile(vhdPath))
1516 {
1517 using namespace wsl::windows::common::wslutil;
1518 THROW_HR_WITH_USER_ERROR(
1519 WSL_E_IMPORT_FAILED, wsl::shared::Localization::MessageRequiresFileExtensions(c_vhdFileExtension, c_vhdxFileExtension));
1520 }
1521
1522 vhdName = vhdPath.filename();
1523 }
1524
1525 registration = DistributionRegistration::Create(
1526 lxssKey.get(),
1527 DistributionId,
1528 DistributionName,
1529 FilesystemVersion,
1530 distributionPath.c_str(),
1531 flags,
1532 LX_UID_ROOT,
1533 PackageFamilyName,
1534 vhdName.c_str(),
1535 WI_IsFlagClear(Flags, LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE));
1536
1537 configuration = s_GetDistributionConfiguration(registration, DistributionName == nullptr);
1538
1539 // Add the distribution to the list of converting distributions.
1540 _ConversionBegin(configuration.DistroId, LxssDistributionStateInstalling);
1541 }
1542
1543 // Set up a scope exit member to remove the distribution from the converting list.
1544 auto installComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); });
1545
1546 // Declare a scope exit variable to clean up on failure.
1547 ULONG deleteFlags = 0;
1548 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
1549 {
1550 auto runAsUser = wil::impersonate_token(userToken.get());
1551 _DeleteDistribution(configuration, deleteFlags);
1552 }
1553
1554 registration.Delete(lxssKey.get());
1555 });
1556
1557 // Initialize the filesystem.
1558 wil::unique_handle clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(GENERIC_READ | SYNCHRONIZE);
1559 if (Version == LXSS_WSL_VERSION_2)
1560 {
1561 if (WI_IsFlagSet(Flags, LXSS_IMPORT_DISTRO_FLAGS_VHD))
1562 {
1563 auto runAsUser = wil::impersonate_token(userToken.get());
1564 auto vhdFile = wsl::core::filesystem::CreateFile(
1565 configuration.VhdFilePath.c_str(),
1566 GENERIC_WRITE,
1567 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
1568 CREATE_NEW,
1569 FILE_ATTRIBUTE_NORMAL,
1570 GetUserSid());
1571
1572 deleteFlags = LXSS_DELETE_DISTRO_FLAGS_VHD;
1573 wsl::windows::common::relay::InterruptableRelay(FileHandle, vhdFile.get(), clientProcess.get(), LXSS_RELAY_BUFFER_SIZE);
1574 }
1575 else
1576 {
1577 // Create a vhd to store the root filesystem.
1578 {
1579 auto runAsUser = wil::impersonate_token(userToken.get());
1580 if (VhdSize == 0)
1581 {
1582 VhdSize = config.VhdSizeBytes;
1583 }
1584
1585 wsl::core::filesystem::CreateVhd(
1586 configuration.VhdFilePath.c_str(), VhdSize, GetUserSid(), config.EnableSparseVhd, WI_IsFlagSet(Flags, LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD));
1587
1588 deleteFlags = LXSS_DELETE_DISTRO_FLAGS_VHD;
1589 }
1590
1591 // Create a process in the utility VM to expand the tar file from a socket.
1592 auto vmContext = _RunUtilityVmSetup(configuration, LxMiniInitMessageImport);
1593
1594 std::optional<wsl::windows::common::relay::ScopedRelay> errorRelay;
1595 if (ErrorHandle != nullptr)
1596 {
1597 errorRelay.emplace(std::move(vmContext.errorSocket), ErrorHandle);
1598 }
1599
1600 // Relay the filesystem file contents to the tar.gz handle.
1601 // Note: This is done in a separate thread because we can sometimes get stuck while writing the socket if tar exited without reading anything.
1602 // Note: because the tarsSocket is moved, the relay owns it, meaning it will automatically close it when the relaying thread exits.
1603 wsl::windows::common::relay::ScopedRelay dataRelay(FileHandle, std::move(vmContext.tarSocket));
1604
1605 // Wait for the utility VM to finish expanding the tar and ensure that
1606 // the operation was successful.
1607 auto* channel = dynamic_cast<WslCoreInstance::WslCorePort*>(vmContext.instance->GetInitPort().get());
1608
1609 gsl::span<gsl::byte> span;
1610 const auto& message = channel->GetChannel().ReceiveMessage<LX_MINI_INIT_IMPORT_RESULT>(&span);
1611
1612 // Flush any pending IO on the error relay before exiting.
1613 if (errorRelay.has_value())
1614 {
1615 errorRelay->Sync();
1616 }
1617
1618 // Process the import result message.
1619 THROW_HR_IF(WSL_E_IMPORT_FAILED, (message.Result != 0));
1620
1621 _ProcessImportResultMessage(message, span, lxssKey.get(), configuration, registration);
1622 }
1623 }
1624 else
1625 {
1626 // Create the directory to store the root filesystem.
1627 const auto rootFsPath = configuration.BasePath / LXSS_ROOTFS_DIRECTORY;
1628 wsl::windows::common::filesystem::CreateRootFs(rootFsPath.c_str(), configuration.Version);
1629 deleteFlags = LXSS_DELETE_DISTRO_FLAGS_ROOTFS;
1630
1631 // Use bsdtar to extract the tar.gz file.
1632 auto mounts = _CreateSetupMounts(configuration);
1633 {
1634 auto elfContext = _RunElfBinary(
1635 LXSS_BSDTAR_PATH " -C " LXSS_ROOTFS_MOUNT LXSS_BSDTAR_EXTRACT_ARGS,
1636 configuration.BasePath.c_str(),
1637 clientProcess.get(),
1638 FileHandle,
1639 nullptr,
1640 ErrorHandle,
1641 mounts.data(),
1642 static_cast<ULONG>(mounts.size()));
1643
1644 auto exitStatus = _GetElfExitStatus(elfContext);
1645 THROW_HR_IF(WSL_E_IMPORT_FAILED, exitStatus != 0);
1646 }
1647
1648 // Invoke the init binary with the option to export the distribution information via stdout.
1649 {
1650 std::pair<wil::unique_handle, wil::unique_handle> input;
1651 THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&input.first, &input.second, nullptr, 0));
1652
1653 std::pair<wil::unique_handle, wil::unique_handle> output;
1654 THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&output.first, &output.second, nullptr, 0));
1655
1656 auto elfContext = _RunElfBinary(
1657 LXSS_TOOLS_MOUNT "/init " LX_INIT_IMPORT_MESSAGE_ARG " " LXSS_ROOTFS_MOUNT,
1658 configuration.BasePath.c_str(),
1659 clientProcess.get(),
1660 input.first.get(),
1661 output.second.get(),
1662 ErrorHandle,
1663 mounts.data(),
1664 static_cast<ULONG>(mounts.size()));
1665
1666 // Close handles that were marshalled to WSL1.
1667 input.first.reset();
1668 output.second.reset();
1669
1670 // Read the import result message from stdout.
1671 wil::unique_handle clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(GENERIC_READ | SYNCHRONIZE);
1672 MESSAGE_HEADER header{};
1673 const auto headerSpan = gslhelpers::struct_as_writeable_bytes(header);
1674 auto bytesRead = wsl::windows::common::relay::InterruptableRead(
1675 output.first.get(), gslhelpers::struct_as_writeable_bytes(header), {clientProcess.get()});
1676
1677 THROW_HR_IF(WSL_E_IMPORT_FAILED, bytesRead != headerSpan.size() || header.MessageSize <= headerSpan.size() || header.MessageType != LxMiniInitMessageImportResult);
1678
1679 std::vector<gsl::byte> buffer(header.MessageSize);
1680 const auto span = gsl::make_span(buffer);
1681 gsl::copy(headerSpan, span);
1682
1683 auto offset = headerSpan.size();
1684 while (offset < span.size())
1685 {
1686 bytesRead =
1687 wsl::windows::common::relay::InterruptableRead(output.first.get(), span.subspan(offset), {clientProcess.get()});
1688 if (bytesRead <= 0)
1689 {
1690 break;
1691 }
1692
1693 offset += bytesRead;
1694 }
1695
1696 THROW_HR_IF(WSL_E_IMPORT_FAILED, offset != buffer.size());
1697
1698 // Close the stdin write handle to let init exit and process the import result message.
1699 input.second.reset();
1700 auto exitStatus = _GetElfExitStatus(elfContext);
1701 THROW_HR_IF(WSL_E_IMPORT_FAILED, exitStatus != 0);
1702
1703 const auto message = gslhelpers::try_get_struct<LX_MINI_INIT_IMPORT_RESULT>(span);
1704 THROW_HR_IF(WSL_E_IMPORT_FAILED, !message);
1705
1706 _ProcessImportResultMessage(*message, span, lxssKey.get(), configuration, registration);
1707 }
1708 }
1709
1710 // Mark the distribution as installed and delete the scope exit variable
1711 // so the registration is persisted.
1712 {
1713 std::lock_guard lock(m_instanceLock);
1714 _SetDistributionInstalled(lxssKey.get(), registration.Id());
1715 cleanup.release();
1716 }
1717
1718 _SendDistributionRegisteredEvent(configuration);
1719
1720 _LaunchOOBEIfNeeded();
1721
1722 *pDistroGuid = registration.Id();
1723 if (InstalledDistributionName != nullptr)
1724 {
1725 *InstalledDistributionName = wil::make_cotaskmem_string(configuration.Name.c_str()).release();
1726 }
1727
1728 result = S_OK;
1729 }
1730 catch (...)
1731 {
1732 result = wil::ResultFromCaughtException();
1733 }
1734
1735 return result;
1736 }
1737
1738 HRESULT LxssUserSessionImpl::SetDefaultDistribution(_In_ LPCGUID DistroGuid)
1739 try
1740 {
1741 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1742 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1743
1744 // Ensure the distribution is in the installed state.
1745 std::lock_guard lock(m_instanceLock);
1746
1747 const auto registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid);
1748 const DWORD state = registration.Read(Property::State);
1749
1750 RETURN_HR_IF(WSL_E_DISTRO_NOT_FOUND, (state != LxssDistributionStateInstalled));
1751
1752 // Set the distribution to the default.
1753 DistributionRegistration::SetDefault(lxssKey.get(), registration);
1754
1755 return S_OK;
1756 }
1757 CATCH_RETURN()
1758
1759 HRESULT LxssUserSessionImpl::SetSparse(_In_ LPCGUID DistroGuid, _In_ BOOLEAN Sparse, _In_ BOOLEAN AllowUnsafe)
1760 try
1761 {
1762 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1763 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1764 auto runAsUser = wil::impersonate_token(userToken.get());
1765 std::lock_guard lock(m_instanceLock);
1766
1767 const auto registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid);
1768 LXSS_DISTRO_CONFIGURATION configuration = s_GetDistributionConfiguration(registration);
1769
1770 // Don't attempt on V1
1771 if (WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE))
1772 {
1773 THROW_HR_WITH_USER_ERROR(WSL_E_VM_MODE_INVALID_STATE, wsl::shared::Localization::MessageSparseVhdWsl2Only());
1774 }
1775
1776 // Allow disabling sparse mode but not enabling until the data corruption issue has been resolved.
1777 if (Sparse && !AllowUnsafe)
1778 {
1779 THROW_HR_WITH_USER_ERROR(E_INVALIDARG, wsl::shared::Localization::MessageSparseVhdDisabled());
1780 }
1781
1782 // Don't attempt if running
1783 RETURN_HR_IF(WSL_E_DISTRO_NOT_STOPPED, m_runningInstances.contains(*DistroGuid));
1784
1785 // Don't attempt while a conversion or export holds this distribution; those operations release
1786 // m_instanceLock while running but keep the entry in m_lockedDistributions.
1787 _EnsureNotLocked(DistroGuid);
1788
1789 const wil::unique_hfile vhd{::CreateFileW(configuration.VhdFilePath.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr)};
1790 if (!vhd)
1791 {
1792 const DWORD err = GetLastError();
1793 if (err == ERROR_SHARING_VIOLATION)
1794 {
1795 THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(err), wsl::shared::Localization::MessageVhdInUse());
1796 }
1797 THROW_WIN32(err);
1798 }
1799
1800 FILE_SET_SPARSE_BUFFER buffer{
1801 .SetSparse = Sparse,
1802 };
1803 THROW_IF_WIN32_BOOL_FALSE(::DeviceIoControl(vhd.get(), FSCTL_SET_SPARSE, &buffer, sizeof(buffer), nullptr, 0, nullptr, nullptr));
1804
1805 return S_OK;
1806 }
1807 CATCH_RETURN()
1808
1809 HRESULT LxssUserSessionImpl::ResizeDistribution(_In_ LPCGUID DistroGuid, _In_ HANDLE OutputHandle, _In_ ULONG64 NewSize)
1810 try
1811 {
1812 std::lock_guard lock(m_instanceLock);
1813 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1814 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1815 const auto registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid);
1816 const auto configuration = s_GetDistributionConfiguration(registration);
1817 RETURN_HR_IF(WSL_E_WSL2_NEEDED, WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE));
1818
1819 // Fail if a conversion or export is in progress; those operations release m_instanceLock while
1820 // running but keep this distribution in m_lockedDistributions, so resizing its VHD now would
1821 // race with them.
1822 _EnsureNotLocked(DistroGuid);
1823
1824 const auto& vhdPath = configuration.VhdFilePath;
1825 if (m_utilityVm && m_utilityVm->IsVhdAttached(vhdPath.c_str()))
1826 {
1827 THROW_HR_WITH_USER_ERROR(WSL_E_DISTRO_NOT_STOPPED, wsl::shared::Localization::MessageVhdInUse());
1828 }
1829
1830 // If growing the VHD, resize the underlying VHD file before resizing the filesystem.
1831 bool resizingLarger;
1832 {
1833 auto runAsUser = wil::CoImpersonateClient();
1834 auto diskHandle = wsl::core::filesystem::OpenVhd(vhdPath.c_str(), VIRTUAL_DISK_ACCESS_GET_INFO | VIRTUAL_DISK_ACCESS_METAOPS);
1835 resizingLarger = NewSize > wsl::core::filesystem::GetDiskSize(diskHandle.get());
1836
1837 if (resizingLarger)
1838 {
1839 wsl::core::filesystem::ResizeExistingVhd(diskHandle.get(), NewSize, RESIZE_VIRTUAL_DISK_FLAG_NONE);
1840 }
1841 }
1842
1843 // Ensure VM exists and attach the VHD.
1844 _CreateVm();
1845 const auto lun = m_utilityVm->AttachDisk(vhdPath.c_str(), WslCoreVm::DiskType::VHD, {}, true, userToken.get());
1846
1847 // Resize the underlying filesystem.
1848 //
1849 // N.B. Passing zero as the size causes the resize to consume all available space on the block device.
1850 {
1851 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { m_utilityVm->EjectVhd(vhdPath.c_str()); });
1852 m_utilityVm->ResizeDistribution(lun, OutputHandle, resizingLarger ? 0 : NewSize);
1853 }
1854
1855 // If shrinking the VHD, resize the underlying VHD file. This is only supported for .vhdx files.
1856 //
1857 // N.B. RESIZE_VIRTUAL_DISK_FLAG_ALLOW_UNSAFE_VIRTUAL_SIZE is required because vhdmp can't validate that the minimum safe ext4 size.
1858 if (!resizingLarger && wsl::shared::string::IsEqual(vhdPath.extension().c_str(), wsl::windows::common::wslutil::c_vhdxFileExtension, true))
1859 {
1860 auto runAsUser = wil::CoImpersonateClient();
1861 const auto diskHandle = wsl::core::filesystem::OpenVhd(vhdPath.c_str(), VIRTUAL_DISK_ACCESS_GET_INFO | VIRTUAL_DISK_ACCESS_METAOPS);
1862 wsl::core::filesystem::ResizeExistingVhd(diskHandle.get(), NewSize, RESIZE_VIRTUAL_DISK_FLAG_ALLOW_UNSAFE_VIRTUAL_SIZE);
1863 }
1864
1865 return S_OK;
1866 }
1867 CATCH_RETURN()
1868
1869 HRESULT LxssUserSessionImpl::CompactDistribution(_In_ LPCGUID DistroGuid)
1870 try
1871 {
1872 auto runAsUser = wil::CoImpersonateClient();
1873 std::filesystem::path vhdPath;
1874 LXSS_DISTRO_CONFIGURATION configuration{};
1875
1876 {
1877 std::lock_guard lock(m_instanceLock);
1878 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1879 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1880 const auto registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid);
1881 configuration = s_GetDistributionConfiguration(registration);
1882 RETURN_HR_IF(WSL_E_WSL2_NEEDED, WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE));
1883
1884 vhdPath = configuration.VhdFilePath;
1885 if (wsl::shared::string::IsEqual(vhdPath.extension().c_str(), wsl::windows::common::wslutil::c_vhdFileExtension, true))
1886 {
1887 THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), wsl::shared::Localization::MessageCompactVhdNotSupported());
1888 }
1889
1890 _ConversionBegin(configuration.DistroId, LxssDistributionStateCompacting);
1891
1892 // Trim the filesystem before compaction so the host can reclaim the freed blocks.
1893 //
1894 // WSL2 does not mount ext4 with 'discard' and does not run fsck at boot, so blocks freed
1895 // inside the guest are still marked as allocated in the VHD and a bare compaction reclaims
1896 // little space. Attaching the (now stopped) distribution's VHD to the utility VM and running
1897 // an offline fsck with block discard releases those blocks, then ejecting flushes the change
1898 // back to the VHD before it is compacted below.
1899 //
1900 // This is best-effort: any failure here must not prevent compaction.
1901 try
1902 {
1903 _CreateVm();
1904 const auto lun = m_utilityVm->AttachDisk(vhdPath.c_str(), WslCoreVm::DiskType::VHD, {}, true, userToken.get());
1905 auto ejectVhd = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { m_utilityVm->EjectVhd(vhdPath.c_str()); });
1906 m_utilityVm->TrimDistribution(lun);
1907 }
1908 CATCH_LOG();
1909 }
1910
1911 auto compactionComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); });
1912
1913 THROW_IF_FAILED_MSG(
1914 wil::ResultFromException([&] { wsl::core::filesystem::CompactVhd(vhdPath.c_str()); }),
1915 "Failed to compact VHD: %ls",
1916 vhdPath.c_str());
1917 return S_OK;
1918 }
1919 CATCH_RETURN()
1920
1921 HRESULT LxssUserSessionImpl::SetVersion(_In_ LPCGUID DistroGuid, _In_ ULONG Version, _In_ HANDLE StderrHandle)
1922 {
1923 RETURN_HR_IF(E_INVALIDARG, ((Version != LXSS_WSL_VERSION_1) && (Version != LXSS_WSL_VERSION_2)));
1924
1925 DistributionRegistration registration;
1926 LXSS_DISTRO_CONFIGURATION configuration;
1927 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1928 wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
1929 try
1930 {
1931 // Ensure the distribution exists.
1932 std::lock_guard lock(m_instanceLock);
1933 registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid);
1934 configuration = s_GetDistributionConfiguration(registration);
1935
1936 // The distro must be in the installed state.
1937 RETURN_HR_IF(E_ILLEGAL_STATE_CHANGE, (configuration.State != LxssDistributionStateInstalled));
1938
1939 // Ensure distro is not already in the requested state.
1940 if (Version == LXSS_WSL_VERSION_1)
1941 {
1942 RETURN_HR_IF(WSL_E_VM_MODE_INVALID_STATE, WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE));
1943 }
1944 else
1945 {
1946 // The legacy distribution does not support VM mode.
1947 RETURN_HR_IF(WSL_E_VM_MODE_NOT_SUPPORTED, (configuration.Version == LXSS_DISTRO_VERSION_LEGACY));
1948
1949 RETURN_HR_IF(WSL_E_VM_MODE_INVALID_STATE, WI_IsFlagSet(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE));
1950 }
1951
1952 // Conversion is not possible if the lxcore driver is not present.
1953 RETURN_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, !g_lxcoreInitialized);
1954
1955 // Add the distribution to the list of converting distributions.
1956 _ConversionBegin(configuration.DistroId, LxssDistributionStateConverting);
1957
1958 // Remove the distribution ID from m_updatedInitDistros so init is updated on the next launch (in the case of a conversion to WSL1).
1959 m_updatedInitDistros.erase(
1960 std::remove(m_updatedInitDistros.begin(), m_updatedInitDistros.end(), configuration.DistroId), m_updatedInitDistros.end());
1961 }
1962 CATCH_RETURN()
1963
1964 // Set up a scope exit member to remove the distribution from the converting list.
1965 auto conversionComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); });
1966
1967 // Log telemetry to track how long enabling VM mode takes.
1968 WSL_LOG_TELEMETRY(
1969 "SetVersionBegin",
1970 PDT_ProductAndServicePerformance,
1971 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
1972 TraceLoggingValue(Version, "version"));
1973
1974 HRESULT result;
1975 auto setVersionComplete = wil::scope_exit([&] {
1976 WSL_LOG_TELEMETRY(
1977 "SetVersionEnd",
1978 PDT_ProductAndServicePerformance,
1979 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
1980 TraceLoggingValue(Version, "version"),
1981 TraceLoggingValue(result, "result"));
1982 });
1983
1984 try
1985 {
1986 ULONG deleteFlags = 0;
1987 wil::unique_handle userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
1988 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
1989 auto runAsUser = wil::impersonate_token(userToken.get());
1990 _DeleteDistribution(configuration, deleteFlags);
1991 });
1992
1993 bool wroteLf = false;
1994 size_t lastIndex = -1;
1995 auto onTarOutput = [&StderrHandle, &wroteLf, &lastIndex](size_t Index, const gsl::span<gsl::byte>& Content) {
1996 WI_ASSERT(Index == 0 || Index == 1);
1997 auto it = Content.begin();
1998
1999 while (it != Content.end())
2000 {
2001 if (wroteLf || lastIndex != Index)
2002 {
2003 if (*it == static_cast<std::byte>('\n') && lastIndex != Index)
2004 {
2005 it++;
2006 continue;
2007 }
2008
2009 // Add an extra newline if the input index changed to avoid mixing lines.
2010 if (lastIndex != Index && !wroteLf)
2011 {
2012 THROW_IF_WIN32_BOOL_FALSE(WriteFile(StderrHandle, "\n", 1, nullptr, nullptr));
2013 }
2014
2015 THROW_IF_WIN32_BOOL_FALSE(WriteFile(StderrHandle, Index == 0 ? "wsl1: " : "wsl2: ", 6, nullptr, nullptr));
2016 wroteLf = false;
2017 lastIndex = Index;
2018 }
2019
2020 auto lf = std::find(it, Content.end(), static_cast<std::byte>('\n'));
2021 if (lf != Content.end())
2022 {
2023 lf++;
2024 wroteLf = true;
2025 }
2026
2027 THROW_IF_WIN32_BOOL_FALSE(WriteFile(StderrHandle, &*it, static_cast<DWORD>(lf - it), nullptr, nullptr));
2028
2029 it = lf;
2030 }
2031 };
2032
2033 wil::unique_handle clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(GENERIC_READ | SYNCHRONIZE);
2034 std::string commandLine{LXSS_BSDTAR_PATH};
2035 ULONG newFlags = configuration.Flags;
2036 if (Version == LXSS_WSL_VERSION_1)
2037 {
2038 auto policiesKey = wsl::windows::policies::OpenPoliciesKey();
2039 if (!wsl::windows::policies::IsFeatureAllowed(policiesKey.get(), wsl::windows::policies::c_allowWSL1))
2040 {
2041 THROW_HR_WITH_USER_ERROR(WSL_E_WSL1_DISABLED, wsl::shared::Localization::MessageWSL1Disabled());
2042 }
2043
2044 auto rootfsPath = configuration.BasePath / LXSS_ROOTFS_DIRECTORY;
2045
2046 // Ensure the target directory is empty and create the root filesystem.
2047 {
2048 std::lock_guard lock(m_instanceLock);
2049 {
2050 auto runAsUser = wil::impersonate_token(userToken.get());
2051 _DeleteDistributionLockHeld(configuration, LXSS_DELETE_DISTRO_FLAGS_ROOTFS);
2052 }
2053
2054 wsl::windows::common::filesystem::CreateRootFs(rootfsPath.c_str(), configuration.Version);
2055 deleteFlags = LXSS_DELETE_DISTRO_FLAGS_ROOTFS;
2056 }
2057
2058 // Create a utility VM to create the tar file and output it via a
2059 // socket.
2060 auto vmContext = _RunUtilityVmSetup(configuration, LxMiniInitMessageExport, 0, true);
2061
2062 auto wsl1Pipe = wsl::windows::common::wslutil::OpenAnonymousPipe(LX_RELAY_BUFFER_SIZE, true, true);
2063
2064 wsl::windows::common::relay::ScopedMultiRelay stdErrRelay(
2065 std::vector<HANDLE>{wsl1Pipe.first.get(), reinterpret_cast<HANDLE>(vmContext.errorSocket.get())}, onTarOutput);
2066
2067 // Add mounts for the rootfs and tools.
2068 auto mounts = _CreateSetupMounts(configuration);
2069
2070 if (m_utilityVm->GetConfig().SetVersionDebug)
2071 {
2072 commandLine += " -vv --totals";
2073 }
2074
2075 // Run the bsdtar elf binary expand the tar file using the socket as stdin.
2076 commandLine += " -C " LXSS_ROOTFS_MOUNT LXSS_BSDTAR_EXTRACT_ARGS;
2077 auto elfContext = _RunElfBinary(
2078 commandLine.c_str(),
2079 configuration.BasePath.c_str(),
2080 clientProcess.get(),
2081 reinterpret_cast<HANDLE>(vmContext.tarSocket.get()),
2082 nullptr,
2083 wsl1Pipe.second.get(),
2084 mounts.data(),
2085 static_cast<ULONG>(mounts.size()));
2086
2087 wsl1Pipe.second.reset();
2088
2089 // Wait for the utility VM to finish creating the tar and ensure that
2090 // the export was successful.
2091 LONG exitStatus = 1;
2092 vmContext.instance->GetInitPort()->Receive(&exitStatus, sizeof(exitStatus), clientProcess.get());
2093 THROW_HR_IF(WSL_E_EXPORT_FAILED, (exitStatus != 0));
2094
2095 // Wait for the elf binary to finish expanding the tar and ensure
2096 // that it was successful.
2097 exitStatus = _GetElfExitStatus(elfContext);
2098 THROW_HR_IF(WSL_E_IMPORT_FAILED, exitStatus != 0);
2099
2100 // Import from the vhd was successful.
2101 deleteFlags = LXSS_DELETE_DISTRO_FLAGS_VHD | LXSS_DELETE_DISTRO_FLAGS_WSLG_SHORTCUTS;
2102 WI_ClearFlag(newFlags, LXSS_DISTRO_FLAGS_VM_MODE);
2103 }
2104 else
2105 {
2106 {
2107 std::lock_guard lock(m_instanceLock);
2108 _CreateVm();
2109 }
2110
2111 // Create a vhd to store the root filesystem.
2112 {
2113 auto runAsUser = wil::impersonate_token(userToken.get());
2114 wsl::core::filesystem::CreateVhd(
2115 configuration.VhdFilePath.c_str(),
2116 m_utilityVm->GetConfig().VhdSizeBytes,
2117 GetUserSid(),
2118 m_utilityVm->GetConfig().EnableSparseVhd,
2119 false);
2120
2121 deleteFlags = LXSS_DELETE_DISTRO_FLAGS_VHD;
2122 }
2123
2124 // Create a process in the utility VM to expand the tar file from a socket.
2125 auto vmContext = _RunUtilityVmSetup(configuration, LxMiniInitMessageImport, 0, true);
2126
2127 auto wsl1Pipe = wsl::windows::common::wslutil::OpenAnonymousPipe(LX_RELAY_BUFFER_SIZE, true, true);
2128
2129 wsl::windows::common::relay::ScopedMultiRelay stdErrRelay(
2130 std::vector<HANDLE>{wsl1Pipe.first.get(), reinterpret_cast<HANDLE>(vmContext.errorSocket.get())}, onTarOutput);
2131
2132 // Add mounts for the rootfs and tools.
2133 auto mounts = _CreateSetupMounts(configuration);
2134
2135 if (m_utilityVm->GetConfig().SetVersionDebug)
2136 {
2137 commandLine += " -vv --totals";
2138 }
2139
2140 // Run the bsdtar elf binary to create the tar file using the socket as stdout.
2141 commandLine += " -C " LXSS_ROOTFS_MOUNT LXSS_BSDTAR_CREATE_ARGS;
2142 auto elfContext = _RunElfBinary(
2143 commandLine.c_str(),
2144 configuration.BasePath.c_str(),
2145 clientProcess.get(),
2146 nullptr,
2147 reinterpret_cast<HANDLE>(vmContext.tarSocket.get()),
2148 wsl1Pipe.second.get(),
2149 mounts.data(),
2150 static_cast<ULONG>(mounts.size()));
2151
2152 wsl1Pipe.second.reset();
2153
2154 LONG exitStatus = _GetElfExitStatus(elfContext);
2155 THROW_HR_IF(WSL_E_IMPORT_FAILED, exitStatus != 0);
2156
2157 // Close the socket now that all data has been written.
2158 vmContext.tarSocket.reset();
2159
2160 // Wait for the utility VM to finish expanding the tar and ensure that
2161 // the export was successful.
2162 auto* channel = dynamic_cast<WslCoreInstance::WslCorePort*>(vmContext.instance->GetInitPort().get());
2163
2164 gsl::span<gsl::byte> span;
2165 const auto& message = channel->GetChannel().ReceiveMessage<LX_MINI_INIT_IMPORT_RESULT>(&span);
2166 THROW_HR_IF(E_FAIL, (message.Result != 0));
2167
2168 if (message.FlavorIndex > 0)
2169 {
2170 configuration.Flavor = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(span, message.FlavorIndex));
2171 registration.Write(Property::Flavor, configuration.Flavor.c_str());
2172 }
2173
2174 if (message.VersionIndex > 0)
2175 {
2176 configuration.OsVersion = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(span, message.VersionIndex));
2177 registration.Write(Property::OsVersion, configuration.OsVersion.c_str());
2178 }
2179
2180 // Operation was successful.
2181 deleteFlags = LXSS_DELETE_DISTRO_FLAGS_ROOTFS;
2182 WI_SetFlag(newFlags, LXSS_DISTRO_FLAGS_VM_MODE);
2183 }
2184
2185 // Record the new distribution state.
2186 registration.Write(Property::Flags, newFlags);
2187
2188 result = S_OK;
2189 }
2190 catch (...)
2191 {
2192 result = wil::ResultFromCaughtException();
2193 }
2194
2195 return result;
2196 }
2197
2198 HRESULT LxssUserSessionImpl::Shutdown(_In_ bool PreventNewInstances, ShutdownBehavior Behavior)
2199 {
2200 try
2201 {
2202 auto resetVmTerminationCallback = wil::scope_exit([&]() { m_suppressVmTerminationCallback.store(false); });
2203
2204 auto forceTerminate = [this]() {
2205 auto vmId = m_vmId.load();
2206 if (!IsEqualGUID(vmId, GUID_NULL))
2207 {
2208 auto vmIdStr = wsl::shared::string::GuidToString<wchar_t>(vmId, wsl::shared::string::GuidToStringFlags::Uppercase);
2209
2210 m_suppressVmTerminationCallback.store(true);
2211
2212 auto result = wil::ResultFromException([&]() {
2213 auto computeSystem = wsl::windows::common::hcs::OpenComputeSystem(vmIdStr.c_str(), GENERIC_ALL);
2214 wsl::windows::common::hcs::TerminateComputeSystem(computeSystem.get());
2215 });
2216
2217 WSL_LOG("ForceTerminateVm", TraceLoggingValue(result, "Result"));
2218 }
2219 };
2220
2221 // If the user asks for a forced termination, kill the VM
2222 if (Behavior == ShutdownBehavior::Force)
2223 {
2224 forceTerminate();
2225 }
2226
2227 {
2228 bool locked = false;
2229 auto unlock = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this, &locked]() {
2230 if (locked)
2231 {
2232 m_instanceLock.unlock();
2233 }
2234 });
2235
2236 if (Behavior == ShutdownBehavior::ForceAfter30Seconds)
2237 {
2238 if (m_instanceLock.try_lock_for(std::chrono::seconds(30)))
2239 {
2240 locked = true;
2241 }
2242 else
2243 {
2244 WSL_LOG("VmShutdownLockTimedOut");
2245 forceTerminate();
2246 }
2247 }
2248
2249 if (!locked)
2250 {
2251 m_instanceLock.lock();
2252 locked = true;
2253 }
2254
2255 // Stop each instance with the lock held.
2256 while (!m_runningInstances.empty())
2257 {
2258 _TerminateInstanceInternal(&m_runningInstances.begin()->first, false);
2259 }
2260
2261 // Terminate the utility VM.
2262 _VmTerminate();
2263 resetVmTerminationCallback.reset();
2264
2265 // Reset the proxy state.
2266 // We don't clear it in _VMTerminate because we want to cache results if possible.
2267 m_httpProxyStateTracker.reset();
2268
2269 // Clear any attached disk state.
2270 // This is needed because wsl --shutdown might be called after the vm
2271 // has timed out (and so the disks states would have been written in the registry)
2272 const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(&m_userSid.Sid);
2273 wsl::windows::common::registry::ClearSubkeys(key.get());
2274
2275 WI_ASSERT(!PreventNewInstances || !m_disableNewInstanceCreation);
2276
2277 // This is used when the session is being deleted.
2278 // This is in place to prevent a CreateInstance() call from succeeding
2279 // after the session is shut down since this would mean that the destructor,
2280 // which could run on that thread (if the session already dropped its LxssUserSessionImpl reference)
2281 // would have to do all the cleanup work.
2282 m_disableNewInstanceCreation = PreventNewInstances;
2283 }
2284
2285 auto lock = m_terminatedInstanceLock.lock_exclusive();
2286 m_terminatedInstances.clear();
2287 }
2288 CATCH_LOG()
2289
2290 return S_OK;
2291 }
2292
2293 void LxssUserSessionImpl::TelemetryWorker(_In_ wil::unique_socket&& socket) const
2294 try
2295 {
2296 wsl::windows::common::wslutil::SetThreadDescription(L"Telemetry");
2297
2298 wsl::shared::SocketChannel channel(std::move(socket), "Telemetry", {m_vmTerminating.get()});
2299
2300 // Check if drvfs notifications are enabled for the user.
2301 bool drvFsNotifications{};
2302 {
2303 auto impersonate = wil::impersonate_token(m_userToken.get());
2304 const auto lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
2305 drvFsNotifications =
2306 wsl::windows::common::registry::ReadDword(lxssKey.get(), LXSS_NOTIFICATIONS_KEY, LXSS_NOTIFICATION_DRVFS_PERF_DISABLED, 0) == 0;
2307 }
2308
2309 // Aggregate information about what is running inside the VM. This is logged
2310 // periodically because logging each event individually would be too noisy.
2311 for (;;)
2312 {
2313 auto [Message, Span] = channel.ReceiveMessageOrClosed<LX_MINI_INIT_TELEMETRY_MESSAGE>();
2314 if (Message == nullptr)
2315 {
2316 break;
2317 }
2318
2319 std::map<std::string, size_t> events{};
2320
2321 std::string content = wsl::shared::string::FromSpan(Span, offsetof(LX_MINI_INIT_TELEMETRY_MESSAGE, Buffer));
2322 auto values = wsl::shared::string::Split<char>(content, '/');
2323
2324 THROW_HR_IF(E_UNEXPECTED, values.size() % 2 != 0);
2325
2326 // Periodically log an event to track active WSL usage. This event must be marked as
2327 // 'MICROSOFT_KEYWORD_CRITICAL_DATA' and not MICROSOFT_KEYWORD_MEASURES.
2328 //
2329 // N.B. The count and imageName values are unused but required because they were present in the approved critical event.
2330 WSL_LOG(
2331 "ExecCritical",
2332 TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage),
2333 TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA),
2334 TraceLoggingValue(0, "count"),
2335 TraceLoggingValue("", "imageName"),
2336 TraceLoggingLevel(WINEVENT_LEVEL_INFO));
2337
2338 for (size_t i = 0; i < values.size(); i += 2)
2339 {
2340 // Log an aggregated account of the binary names run in WSL and their counts, used to determine popular use cases and prioritize support for issues
2341 WSL_LOG_TELEMETRY(
2342 "Exec",
2343 PDT_ProductAndServiceUsage,
2344 TraceLoggingValue(std::stoull(values[i + 1]), "count"),
2345 TraceLoggingValue(values[i].c_str(), "imageName"),
2346 TraceLoggingLevel(WINEVENT_LEVEL_INFO));
2347 }
2348
2349 if (drvFsNotifications && Message->ShowDrvFsNotification && !values.empty())
2350 {
2351 // If a drvfs notification is requested, the first entry is the executable that triggered it.
2352 LOG_IF_FAILED(wsl::windows::common::notifications::DisplayFilesystemNotification(values[0].c_str()));
2353 drvFsNotifications = false;
2354 }
2355 }
2356 }
2357 CATCH_LOG()
2358
2359 _Requires_lock_not_held_(m_instanceLock)
2360 void LxssUserSessionImpl::TerminateByClientId(_In_ ULONG ClientId)
2361 {
2362 if (ClientId == LXSS_CLIENT_ID_INVALID)
2363 {
2364 return;
2365 }
2366
2367 std::lock_guard lock(m_instanceLock);
2368 TerminateByClientIdLockHeld(ClientId);
2369 }
2370
2371 _Requires_lock_held_(m_instanceLock)
2372 void LxssUserSessionImpl::TerminateByClientIdLockHeld(_In_ ULONG ClientId)
2373 {
2374 // Terminate any instances with a matching client ID.
2375 std::vector<GUID> instances;
2376 std::for_each(m_runningInstances.begin(), m_runningInstances.end(), [&](auto& pair) {
2377 auto id = pair.second->GetClientId();
2378 if ((id == ClientId) || ((ClientId == LXSS_CLIENT_ID_WILDCARD) && (id != LXSS_CLIENT_ID_INVALID)))
2379 {
2380 instances.push_back(pair.first);
2381 }
2382 });
2383
2384 std::for_each(instances.begin(), instances.end(), [&](auto& guid) { _TerminateInstanceInternal(&guid, false); });
2385
2386 // If the wildcard client ID was specified, the utility VM unexpectedly exited.
2387 if (ClientId == LXSS_CLIENT_ID_WILDCARD)
2388 {
2389 _VmTerminate();
2390 }
2391 }
2392
2393 HRESULT LxssUserSessionImpl::TerminateDistribution(_In_opt_ LPCGUID DistroGuid)
2394 try
2395 {
2396 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
2397 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
2398 GUID defaultDistro;
2399 {
2400 std::lock_guard lock(m_instanceLock);
2401
2402 // If no distribution GUID was supplied, use the default.
2403 if (ARGUMENT_PRESENT(DistroGuid) == FALSE)
2404 {
2405 defaultDistro = _GetDefaultDistro(lxssKey.get());
2406 DistroGuid = &defaultDistro;
2407 }
2408
2409 _TerminateInstanceInternal(DistroGuid);
2410 }
2411
2412 return S_OK;
2413 }
2414 CATCH_RETURN()
2415
2416 HRESULT LxssUserSessionImpl::UnregisterDistribution(_In_ LPCGUID DistroGuid)
2417 {
2418 ExecutionContext context(Context::UnregisterDistro);
2419
2420 // Set up a scope exit member to log unregistration status.
2421 DistributionRegistration registration;
2422 LXSS_DISTRO_CONFIGURATION configuration{};
2423 HRESULT result = E_FAIL;
2424 auto unregisterExit = wil::scope_exit([&] {
2425 // Only log the end event if a distro was found.
2426 if (configuration.Name.size() > 0)
2427 {
2428 WSL_LOG(
2429 "UnregisterDistributionEnd",
2430 TraceLoggingValue(configuration.Name.c_str(), "name"),
2431 TraceLoggingHexUInt32(result, "result"));
2432 }
2433 });
2434
2435 try
2436 {
2437 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
2438 wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
2439
2440 // Set up a scope exit lambda to delete the distribution registry key
2441 // when the function exits.
2442 auto removedDistroString = wsl::shared::string::GuidToString<wchar_t>(*DistroGuid);
2443 bool removeDistro = false;
2444 auto deleteDistroKey = wil::scope_exit([&] {
2445 if (removeDistro)
2446 {
2447 wsl::windows::common::registry::DeleteKey(lxssKey.get(), removedDistroString.c_str());
2448 }
2449 });
2450
2451 {
2452 std::lock_guard lock(m_instanceLock);
2453
2454 // Get the configuration information about the distribution.
2455 registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid);
2456 configuration = s_GetDistributionConfiguration(registration);
2457
2458 // Log telemetry about the distribution being removed.
2459 WSL_LOG_TELEMETRY(
2460 "UnregisterDistributionBegin", PDT_ProductAndServiceUsage, TraceLoggingValue(configuration.Name.c_str(), "name"));
2461
2462 // Ensure that a filesystem export is not in progress.
2463 _EnsureNotLocked(DistroGuid);
2464
2465 // After this point the distribution registry key should be deleted.
2466 removeDistro = true;
2467
2468 // Terminate the distribution and mark it as uninstalling.
2469 _TerminateInstanceInternal(DistroGuid);
2470 registration.Write(Property::State, LxssDistributionStateUninstalling);
2471
2472 // If the default distribution has been unregistered, search for another
2473 // distribution to set as the new default.
2474
2475 auto defaultDistribution = DistributionRegistration::OpenDefault(lxssKey.get());
2476 if (defaultDistribution.has_value() && IsEqualGUID(defaultDistribution->Id(), registration.Id()))
2477 {
2478 // Remove the old default.
2479 DistributionRegistration::DeleteDefault(lxssKey.get());
2480
2481 // If there are any other registered distributions, set the first
2482 // one found to the new default.
2483 auto distributions = _EnumerateDistributions(lxssKey.get());
2484 if (distributions.size() > 0)
2485 {
2486 DistributionRegistration::SetDefault(lxssKey.get(), distributions[0]);
2487 }
2488 }
2489
2490 {
2491 auto runAsUser = wil::CoImpersonateClient();
2492 _DeleteDistributionLockHeld(configuration);
2493 }
2494
2495 WslOfflineDistributionInformation distributionInfo;
2496 distributionInfo.Id = configuration.DistroId;
2497 distributionInfo.Name = configuration.Name.c_str();
2498 distributionInfo.PackageFamilyName = configuration.PackageFamilyName.c_str();
2499 distributionInfo.Flavor = configuration.Flavor.empty() ? nullptr : configuration.Flavor.c_str();
2500 distributionInfo.Version = configuration.OsVersion.empty() ? nullptr : configuration.OsVersion.c_str();
2501
2502 m_pluginManager.OnDistributionUnregistered(&m_session, &distributionInfo);
2503 }
2504
2505 result = S_OK;
2506 }
2507 catch (...)
2508 {
2509 result = wil::ResultFromCaughtException();
2510 }
2511
2512 return result;
2513 }
2514
2515 _Requires_lock_held_(m_instanceLock)
2516 void LxssUserSessionImpl::_ConversionBegin(_In_ GUID DistroGuid, _In_ LxssDistributionState State)
2517 {
2518 _EnsureNotLocked(&DistroGuid);
2519 _TerminateInstanceInternal(&DistroGuid);
2520 m_lockedDistributions.emplace_back(DistroGuid, State);
2521 }
2522
2523 _Requires_lock_not_held_(m_instanceLock)
2524 void LxssUserSessionImpl::_ConversionComplete(_In_ GUID DistroGuid)
2525 {
2526 std::lock_guard lock(m_instanceLock);
2527 std::erase_if(m_lockedDistributions, [&](const auto& pair) { return (IsEqualGUID(pair.first, DistroGuid)); });
2528
2529 _VmCheckIdle();
2530 }
2531
2532 _Requires_exclusive_lock_held_(m_instanceLock)
2533 void LxssUserSessionImpl::_CreateLegacyRegistration(_In_ HKEY LxssKey, _In_ HANDLE UserToken)
2534 {
2535 // Delete any existing legacy registration.
2536 const auto distroGuidString = wsl::shared::string::GuidToString<wchar_t>(LXSS_LEGACY_DISTRO_GUID);
2537 wsl::windows::common::registry::DeleteKey(LxssKey, distroGuidString.c_str());
2538
2539 // Migrate legacy default user configuration.
2540 const ULONG defaultUid = wsl::windows::common::registry::ReadDword(LxssKey, nullptr, WSL_DISTRO_CONFIG_DEFAULT_UID, LX_UID_ROOT);
2541 DWORD configFlags = LXSS_DISTRO_FLAGS_DEFAULT;
2542 DWORD enabled = wsl::windows::common::registry::ReadDword(LxssKey, nullptr, LXSS_LEGACY_APPEND_NT_PATH, 1);
2543 WI_ClearFlagIf(configFlags, LXSS_DISTRO_FLAGS_APPEND_NT_PATH, (enabled == 0));
2544 enabled = wsl::windows::common::registry::ReadDword(LxssKey, nullptr, LXSS_LEGACY_INTEROP_ENABLED, 1);
2545 WI_ClearFlagIf(configFlags, LXSS_DISTRO_FLAGS_ENABLE_INTEROP, (enabled == 0));
2546
2547 // Create a new registration for the legacy distro.
2548 const auto basePath = wsl::windows::common::filesystem::GetLegacyBasePath(UserToken);
2549
2550 DistributionRegistration::Create(
2551 LxssKey, LXSS_LEGACY_DISTRO_GUID, LXSS_LEGACY_INSTALL_NAME, LXSS_DISTRO_VERSION_LEGACY, basePath.c_str(), configFlags, defaultUid, nullptr, LXSS_VM_MODE_VHD_NAME, false);
2552
2553 _SetDistributionInstalled(LxssKey, LXSS_LEGACY_DISTRO_GUID);
2554 }
2555
2556 std::vector<wsl::windows::common::filesystem::unique_lxss_addmount> LxssUserSessionImpl::_CreateSetupMounts(_In_ const LXSS_DISTRO_CONFIGURATION& Configuration)
2557 {
2558 // Add a rootfs mount.
2559 auto runAsUser = wil::CoImpersonateClient();
2560 const auto rootFsPath = Configuration.BasePath / LXSS_ROOTFS_DIRECTORY;
2561 std::vector<wsl::windows::common::filesystem::unique_lxss_addmount> mounts;
2562 mounts.emplace_back(wsl::windows::common::filesystem::CreateMount(
2563 rootFsPath.c_str(), LXSS_ROOTFS_DIRECTORY, LXSS_ROOTFS_MOUNT, LXSS_DISTRO_USES_WSL_FS(Configuration.Version) ? LXSS_FS_TYPE_WSLFS : LXSS_FS_TYPE_LXFS, 0755));
2564
2565 // Add a read only sharefs mount to the inbox tools directory which contains the bsdtar binary.
2566 std::wstring systemDirectory;
2567 THROW_IF_FAILED(wil::GetSystemDirectoryW(systemDirectory));
2568
2569 // Add a read only sharefs mount to the packaged tools directory which contains the init binary.
2570 const auto initPath = wsl::windows::common::wslutil::GetBasePath() / L"tools";
2571 mounts.emplace_back(wsl::windows::common::filesystem::CreateMount(
2572 initPath.c_str(), initPath.c_str(), LXSS_TOOLS_MOUNT, LXSS_FS_TYPE_SHAREFS, 0755, false));
2573
2574 return mounts;
2575 }
2576
2577 _Requires_lock_not_held_(m_instanceLock)
2578 std::shared_ptr<LxssRunningInstance> LxssUserSessionImpl::_CreateInstance(_In_opt_ LPCGUID DistroGuid, _In_ ULONG Flags)
2579 {
2580 ExecutionContext context(Context::CreateInstance);
2581
2582 // Validate flags.
2583 THROW_HR_IF(E_INVALIDARG, (WI_IsAnyFlagSet(Flags, ~LXSS_CREATE_INSTANCE_FLAGS_ALL)));
2584
2585 // Clear the list of terminated instances before acquiring the instance
2586 // list lock.
2587 {
2588 auto lock = m_terminatedInstanceLock.lock_exclusive();
2589 m_terminatedInstances.clear();
2590 }
2591
2592 wil::unique_handle userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
2593 wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
2594 DistributionRegistration registration;
2595
2596 std::shared_ptr<LxssRunningInstance> instance;
2597 {
2598 std::lock_guard lock(m_instanceLock);
2599
2600 // m_disableNewInstanceCreation is set when the session is being deleted.
2601 // In that code path, don't create a new session.
2602 THROW_HR_IF(RPC_E_DISCONNECTED, m_disableNewInstanceCreation);
2603
2604 registration = DistributionRegistration::OpenOrDefault(lxssKey.get(), DistroGuid);
2605
2606 // Check if an instance is already running for this distribution, if
2607 // not create one.
2608 instance = _RunningInstance(&registration.Id());
2609 if (!instance)
2610 {
2611 THROW_HR_IF(E_NOT_SET, WI_IsFlagSet(Flags, LXSS_CREATE_INSTANCE_FLAGS_OPEN_EXISTING));
2612
2613 // Query information about the distribution.
2614 auto configuration = s_GetDistributionConfiguration(registration);
2615 auto defaultUid = registration.Read(Property::DefaultUid);
2616
2617 THROW_HR_IF(E_ILLEGAL_STATE_CHANGE, (configuration.State != LxssDistributionStateInstalled));
2618
2619 // Determine the distribution version.
2620 ULONG version = WI_IsFlagSet(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE) ? LXSS_WSL_VERSION_2 : LXSS_WSL_VERSION_1;
2621
2622 // Create a GUID for the instance.
2623 GUID instanceId;
2624 THROW_IF_FAILED(CoCreateGuid(&instanceId));
2625
2626 // Log telemetry to determine how long instance creation takes.
2627 WSL_LOG_TELEMETRY(
2628 "CreateInstanceBegin",
2629 PDT_ProductAndServicePerformance,
2630 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
2631 TraceLoggingValue(version, "version"),
2632 TraceLoggingValue(instanceId, "instanceId"));
2633
2634 HRESULT result = E_UNEXPECTED;
2635 wsl::windows::common::wslutil::StopWatch stopWatch;
2636 auto createEnd = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
2637 const auto& reportedError = context.ReportedError();
2638 WSL_LOG_TELEMETRY(
2639 "CreateInstanceEnd",
2640 PDT_ProductAndServicePerformance,
2641 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
2642 TraceLoggingValue(version, "version"),
2643 TraceLoggingValue(instanceId, "instanceId"),
2644 TraceLoggingValue(SUCCEEDED(result), "success"),
2645 TraceLoggingValue(result, "error"),
2646 TraceLoggingValue(reportedError ? reportedError->Context : 0ULL, "errorContext"),
2647 TraceLoggingValue(stopWatch.ElapsedMilliseconds(), "CreationTimeMs"));
2648 });
2649
2650 try
2651 {
2652 auto clientKey = m_lifetimeManager.GetRegistrationId();
2653 if (version == LXSS_WSL_VERSION_1)
2654 {
2655 auto key = wsl::windows::policies::OpenPoliciesKey();
2656 if (!wsl::windows::policies::IsFeatureAllowed(key.get(), wsl::windows::policies::c_allowWSL1))
2657 {
2658 THROW_HR_WITH_USER_ERROR(
2659 WSL_E_WSL1_DISABLED,
2660 wsl::shared::Localization::MessageWSL1Disabled() + L"\n" +
2661 wsl::shared::Localization::MessageUpgradeToWSL2(configuration.Name));
2662 }
2663
2664 instance = std::make_shared<LxssInstance>(
2665 instanceId,
2666 configuration,
2667 defaultUid,
2668 clientKey,
2669 std::bind(s_TerminateInstance, this, registration.Id(), false),
2670 std::bind(s_UpdateInit, this, configuration),
2671 Flags,
2672 _GetResultantConfig(userToken.get()).InstanceIdleTimeout);
2673 }
2674 else
2675 {
2676 // Ensure the VM has been created.
2677 _CreateVm();
2678 instance = m_utilityVm->CreateInstance(
2679 instanceId, configuration, LxMiniInitMessageLaunchInit, m_utilityVm->GetConfig().KernelBootTimeout, defaultUid, clientKey);
2680 }
2681
2682 // Log telemetry to determine how long initialization takes.
2683 WSL_LOG(
2684 "InitializeInstanceBegin",
2685 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
2686 TraceLoggingValue(version, "version"),
2687 TraceLoggingValue(instanceId, "instanceId"));
2688
2689 auto initializeEnd = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
2690 WSL_LOG(
2691 "InitializeInstanceEnd",
2692 TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA),
2693 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
2694 TraceLoggingValue(version, "version"),
2695 TraceLoggingValue(instanceId, "instanceId"));
2696 });
2697
2698 // Initialize the instance and add it to the list of running instances.
2699 instance->Initialize();
2700
2701 const auto* distributionInfo = instance->DistributionInformation();
2702 if (distributionInfo->Flavor != nullptr && distributionInfo->Flavor != configuration.Flavor)
2703 {
2704 WSL_LOG(
2705 "DistributionFlavorChange",
2706 TraceLoggingValue(distributionInfo->Flavor, "NewFlavor"),
2707 TraceLoggingValue(configuration.Flavor.c_str(), "OldFlavor"),
2708 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
2709 TraceLoggingValue(instanceId, "instanceId"));
2710
2711 registration.Write(Property::Flavor, distributionInfo->Flavor);
2712 }
2713
2714 if (distributionInfo->Version != nullptr && distributionInfo->Version != configuration.OsVersion)
2715 {
2716 WSL_LOG(
2717 "DistributionVersionChange",
2718 TraceLoggingValue(distributionInfo->Version, "NewVersion"),
2719 TraceLoggingValue(configuration.OsVersion.c_str(), "OldVersion"),
2720 TraceLoggingValue(configuration.Name.c_str(), "distroName"),
2721 TraceLoggingValue(instanceId, "instanceId"));
2722
2723 registration.Write(Property::OsVersion, distributionInfo->Version);
2724 }
2725
2726 // This needs to be done before plugins are notifed because they might try to run a command inside the distribution.
2727 m_runningInstances[registration.Id()] = instance;
2728
2729 if (version == LXSS_WSL_VERSION_2)
2730 {
2731 auto cleanupOnFailure =
2732 wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { m_runningInstances.erase(registration.Id()); });
2733 m_pluginManager.OnDistributionStarted(&m_session, instance->DistributionInformation());
2734 cleanupOnFailure.release();
2735 }
2736
2737 result = S_OK;
2738 }
2739 catch (...)
2740 {
2741 result = wil::ResultFromCaughtException();
2742
2743 if (version == LXSS_WSL_VERSION_2)
2744 {
2745 try
2746 {
2747 if (!WslInstall::IsOptionalComponentInstalled(WslInstall::c_optionalFeatureNameVmp))
2748 {
2749 wsl::windows::common::notifications::DisplayOptionalComponentsNotification();
2750 EMIT_USER_WARNING(wsl::shared::Localization::MessageVirtualMachinePlatformNotInstalled());
2751 }
2752 }
2753 CATCH_LOG()
2754 }
2755
2756 throw;
2757 }
2758 }
2759 }
2760
2761 // Register the Plan 9 Redirector connection targets for the calling user if necessary.
2762 // N.B. Normally this is only necessary when creating the instance, and every subsequent time
2763 // it's skipped because the user is already registered. However, in rare cases the
2764 // instance is created by the same user but under a different context, with a different
2765 // authentication ID, than the user's interactive session. For example, if the instance
2766 // was created by a scheduled task. For this reason, ensure that the calling user is
2767 // registered even for already running instances.
2768 instance->RegisterPlan9ConnectionTarget(userToken.get());
2769
2770 // Determine the idle timeout for the instance. A value of less than zero indicates that the instance
2771 // should never be idle-terminated.
2772 if (instance->GetIdleTimeout() >= 0)
2773 {
2774 // Register a client termination callback with the lifetime manager. If the
2775 // ignore client callback flag is specified and there are no other clients,
2776 // the timer is immediately queued.
2777 wil::unique_handle currentProcess{};
2778 if (WI_IsFlagClear(Flags, LXSS_CREATE_INSTANCE_FLAGS_IGNORE_CLIENT))
2779 {
2780 currentProcess = wsl::windows::common::wslutil::OpenCallingProcess(GENERIC_READ | SYNCHRONIZE);
2781 }
2782
2783 m_lifetimeManager.RegisterCallback(
2784 instance->GetLifetimeManagerId(),
2785 std::bind(s_TerminateInstance, this, registration.Id(), true),
2786 currentProcess.get(),
2787 instance->GetIdleTimeout());
2788 }
2789
2790 // If the system distro flag was specified, return the system distro for the instance.
2791 //
2792 // N.B. The system distro is only supported for WSL2.
2793 if (WI_IsFlagSet(Flags, LXSS_CREATE_INSTANCE_FLAGS_USE_SYSTEM_DISTRO))
2794 {
2795 auto wslCoreInstance = std::dynamic_pointer_cast<WslCoreInstance>(instance);
2796 THROW_HR_IF(WSL_E_WSL2_NEEDED, !wslCoreInstance);
2797
2798 instance = wslCoreInstance->GetSystemDistro();
2799 THROW_HR_IF(WSL_E_GUI_APPLICATIONS_DISABLED, !instance);
2800 }
2801
2802 return instance;
2803 }
2804
2805 // N.B. This methods expects the caller to impersonate the user.
2806 void LxssUserSessionImpl::_CreateDistributionShortcut(_In_ LPCWSTR DistributionName, LPCWSTR ShortcutIcon, LPCWSTR ExecutablePath, DistributionRegistration& registration)
2807 try
2808 {
2809 const auto shellLink = wil::CoCreateInstance<IShellLink>(CLSID_ShellLink);
2810
2811 auto shortcutPath = wsl::windows::common::filesystem::GetKnownFolderPath(FOLDERID_StartMenu, KF_FLAG_CREATE);
2812 shortcutPath /= DistributionName + std::wstring(L".lnk");
2813
2814 THROW_IF_FAILED(shellLink->SetPath(ExecutablePath));
2815
2816 // Construct the command line to set the working directory to the user's home directory.
2817 const auto commandLine = std::format(
2818 L"{} {} {} {}", WSL_DISTRIBUTION_ID_ARG, wsl::shared::string::GuidToString<wchar_t>(registration.Id()), WSL_CHANGE_DIRECTORY_ARG, WSL_CWD_HOME);
2819
2820 THROW_IF_FAILED(shellLink->SetArguments(commandLine.c_str()));
2821 THROW_IF_FAILED(shellLink->SetIconLocation(ShortcutIcon, 0));
2822
2823 auto storage = shellLink.query<IPersistFile>();
2824 THROW_IF_FAILED(storage->Save(shortcutPath.c_str(), true));
2825
2826 registration.Write(Property::ShortcutPath, shortcutPath.c_str());
2827 }
2828 CATCH_LOG();
2829
2830 // N.B. This methods expects the caller to impersonate the user.
2831 void LxssUserSessionImpl::_CreateTerminalProfile(
2832 _In_ const std::string_view& Template,
2833 _In_ _In_ const std::filesystem::path& IconPath,
2834 _In_ const LXSS_DISTRO_CONFIGURATION& Configuration,
2835 wsl::windows::service::DistributionRegistration& Registration)
2836 try
2837 {
2838 using namespace wsl::windows::common::string;
2839 using namespace wsl::windows::common::wslutil;
2840 using wsl::shared::string::WideToMultiByte;
2841
2842 nlohmann::basic_json json;
2843 nlohmann::json::iterator profiles;
2844
2845 try
2846 {
2847 json = nlohmann::json::parse(Template, nullptr, true, false);
2848 THROW_HR_IF(E_UNEXPECTED, !json.is_object());
2849
2850 profiles = json.find("profiles");
2851 THROW_HR_IF(E_UNEXPECTED, profiles == json.end() || !profiles->is_array());
2852 }
2853 catch (const nlohmann::json::parse_error& e)
2854 {
2855 EMIT_USER_WARNING(wsl::shared::Localization::MessageFailedToParseTerminalProfile(e.what()));
2856 return;
2857 }
2858 catch (...)
2859 {
2860 auto error = WideToMultiByte(wsl::windows::common::wslutil::ErrorCodeToString(wil::ResultFromCaughtException()));
2861 EMIT_USER_WARNING(wsl::shared::Localization::MessageFailedToParseTerminalProfile(error));
2862 return;
2863 }
2864
2865 auto distributionIdString = wsl::shared::string::GuidToString<wchar_t>(Registration.Id());
2866 auto distributionProfileId =
2867 wsl::shared::string::GuidToString<wchar_t>(CreateV5Uuid(WslTerminalNamespace, std::as_bytes(std::span{distributionIdString})));
2868
2869 auto hideGeneratedProfileGuid = WideToMultiByte(wsl::shared::string::GuidToString<wchar_t>(
2870 CreateV5Uuid(GeneratedProfilesTerminalNamespace, std::as_bytes(std::span{Configuration.Name}))));
2871
2872 bool foundHideProfile = false;
2873
2874 for (auto& e : *profiles)
2875 {
2876 auto updates = e.find("updates");
2877 if (updates != e.end() && (*updates) == hideGeneratedProfileGuid)
2878 {
2879 foundHideProfile = true;
2880 continue;
2881 }
2882
2883 std::wstring systemDirectory;
2884 THROW_IF_FAILED(wil::GetSystemDirectory(systemDirectory));
2885
2886 e["commandline"] =
2887 WideToMultiByte(std::format(L"{}\\{} {} {}", systemDirectory, WSL_BINARY_NAME, WSL_DISTRIBUTION_ID_ARG, distributionIdString));
2888
2889 e["name"] = WideToMultiByte(Configuration.Name);
2890 e["guid"] = WideToMultiByte(distributionProfileId);
2891 e["icon"] = WideToMultiByte(IconPath.native());
2892
2893 // Set default starting directory to home directory if not already specified
2894 // This allows Windows Terminal to override with startingDirectory setting
2895 if (e.find("startingDirectory") == e.end())
2896 {
2897 e["startingDirectory"] = "~";
2898 }
2899
2900 // See https://github.com/microsoft/terminal/pull/18195. Supported in terminal >= 1.23
2901 e["pathTranslationStyle"] = "wsl";
2902
2903 if (!Configuration.Flavor.empty())
2904 {
2905 e["wsl.distribution-type"] = WideToMultiByte(Configuration.Flavor);
2906 }
2907
2908 if (!Configuration.OsVersion.empty())
2909 {
2910 e["wsl.distribution-version"] = WideToMultiByte(Configuration.OsVersion);
2911 }
2912 }
2913
2914 // Add an entry to hide the autogenerated terminal profile, if not provided by the distribution.
2915 if (!foundHideProfile)
2916 {
2917 nlohmann::json hideProfile{{"updates", hideGeneratedProfileGuid}, {"hidden", true}};
2918
2919 profiles->insert(profiles->begin(), hideProfile);
2920 }
2921
2922 auto targetFolder = wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"Microsoft" / L"Windows Terminal" /
2923 L"Fragments" / L"Microsoft.WSL";
2924
2925 wil::CreateDirectoryDeep(targetFolder.c_str());
2926
2927 auto tempFilePath = wsl::windows::common::filesystem::GetTempFilename();
2928 auto targetPath = targetFolder / (distributionProfileId + L".json");
2929
2930 // Unfortunately creating & writing the file isn't atomic.
2931 // Creating the file somewhere else and then moving it to 'targetPath' isn't an option either, because MoveFile
2932 // will set its ownership to the Administrators group, which breaks terminal.
2933 wil::unique_handle file{CreateFile(targetPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr)};
2934 THROW_LAST_ERROR_IF(!file);
2935
2936 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
2937 file.reset();
2938 DeleteFile(targetPath.c_str());
2939 });
2940
2941 auto content = json.dump(2);
2942 THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), content.c_str(), gsl::narrow_cast<DWORD>(content.size()), nullptr, nullptr));
2943 cleanup.release();
2944
2945 Registration.Write(Property::TerminalProfilePath, targetPath.c_str());
2946 }
2947 CATCH_LOG();
2948
2949 _Requires_exclusive_lock_held_(m_instanceLock)
2950 void LxssUserSessionImpl::_CreateVm()
2951 {
2952 ExecutionContext context(Context::CreateVm);
2953
2954 if (!m_utilityVm)
2955 {
2956
2957 // Return an error if a plugin failed to initialize or needs a newer WSL version.
2958 // Note: It's better to do this here instead of CreateInstanceForCurrentUser() because we
2959 // can return a proper error message with the plugin name since we have an execution context here.
2960 m_pluginManager.ThrowIfFatalPluginError();
2961
2962 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
2963 auto config = _GetResultantConfig(userToken.get());
2964
2965 // Initialize policies for the plugin interface.
2966 WSLVmCreationSettings userSettings{};
2967 WI_SetFlagIf(userSettings.CustomConfigurationFlags, WSLUserConfigurationCustomKernel, !config.KernelPath.empty());
2968 WI_SetFlagIf(userSettings.CustomConfigurationFlags, WSLUserConfigurationCustomKernelCommandLine, !config.KernelCommandLine.empty());
2969
2970 // Duplicate the passed-in user token and pass it down to plugins.
2971 THROW_IF_WIN32_BOOL_FALSE(
2972 ::DuplicateTokenEx(userToken.get(), MAXIMUM_ALLOWED, nullptr, SecurityImpersonation, TokenImpersonation, &m_userToken));
2973
2974 m_session.UserToken = m_userToken.get();
2975
2976 GUID vmId{};
2977 THROW_IF_FAILED(CoCreateGuid(&vmId));
2978
2979 m_vmId.store(vmId);
2980
2981 const auto weakSession = weak_from_this();
2982 auto initializeDrvFs = [weakSession, vmId](HANDLE userToken) noexcept {
2983 return s_InitializeDrvFs(weakSession, vmId, userToken);
2984 };
2985
2986 // Create the utility VM and register for callbacks.
2987 m_utilityVm = WslCoreVm::Create(m_userToken, std::move(config), vmId, std::move(initializeDrvFs));
2988
2989 if (m_httpProxyStateTracker)
2990 {
2991 // this needs to be done after the VM has finished in case we fell back to NAT mode
2992 m_httpProxyStateTracker->ConfigureNetworkingMode(m_utilityVm->GetConfig().NetworkingMode);
2993 }
2994
2995 try
2996 {
2997 // Mount disks after the system distro vhd is mounted in case filesystem detection is needed.
2998 _LoadDiskMounts();
2999
3000 // Save the networking settings so they can be reused on the next instantiation.
3001 m_utilityVm->GetConfig().SaveNetworkingSettings(m_userToken.get());
3002
3003 // If the telemetry is enabled, launch the telemetry agent inside the VM.
3004 if (m_utilityVm->GetConfig().EnableTelemetry && TraceLoggingProviderEnabled(g_hTraceLoggingProvider, WINEVENT_LEVEL_INFO, 0))
3005 {
3006 LPCSTR Arguments[] = {LX_INIT_TELEMETRY_AGENT, nullptr};
3007 auto socket = m_utilityVm->CreateRootNamespaceProcess(LX_INIT_PATH, Arguments);
3008 m_telemetryThread = std::thread(&LxssUserSessionImpl::TelemetryWorker, this, std::move(socket));
3009 }
3010
3011 m_pluginManager.OnVmStarted(&m_session, &userSettings);
3012 }
3013 catch (...)
3014 {
3015 LOG_CAUGHT_EXCEPTION_MSG("VM failed to start, shutting down.");
3016
3017 _VmTerminate();
3018 throw;
3019 }
3020
3021 auto callback = [this](auto Pid) {
3022 // If the vm is currently being destroyed, the instance lock might be held
3023 // while WslCoreVm's destructor is waiting on this thread.
3024 // Cancel the call if the vm destruction is signaled.
3025 // Note: This is safe because m_instanceLock is always initialized
3026 // and because WslCoreVm's destructor waits for this thread, the session can't be gone
3027 // until this callback completes.
3028
3029 auto lock = m_instanceLock.try_lock();
3030 while (!lock)
3031 {
3032 if (m_vmTerminating.wait(100))
3033 {
3034 return;
3035 }
3036 lock = m_instanceLock.try_lock();
3037 }
3038
3039 auto unlock = wil::scope_exit([&]() { m_instanceLock.unlock(); });
3040 TerminateByClientIdLockHeld(Pid);
3041 };
3042
3043 // N.B. The callbacks must be registered outside of the above try/catch.
3044 // Otherwise if an exception is thrown, calling _VmTerminate() will trigger the 's_VmTerminated' termination callback
3045 // Which can deadlock since this thread holds the instance lock and HCS can block until the VM termination callback returns before deleting the VM.
3046
3047 m_utilityVm->RegisterCallbacks(std::bind(callback, _1), std::bind(s_VmTerminated, this, _1));
3048 }
3049
3050 _VmCheckIdle();
3051 }
3052
3053 _Requires_exclusive_lock_held_(m_instanceLock)
3054 void LxssUserSessionImpl::_DeleteDistribution(_In_ const LXSS_DISTRO_CONFIGURATION& Configuration, _In_ ULONG Flags)
3055 {
3056 std::lock_guard lock(m_instanceLock);
3057 _DeleteDistributionLockHeld(Configuration, Flags);
3058 }
3059
3060 // Function signature of the API to remove WSLg start menu shortcuts.
3061 HRESULT RemoveAppProvider(LPCWSTR);
3062
3063 _Requires_exclusive_lock_held_(m_instanceLock)
3064 void LxssUserSessionImpl::_DeleteDistributionLockHeld(_In_ const LXSS_DISTRO_CONFIGURATION& Configuration, _In_ ULONG Flags) const
3065 {
3066 THROW_HR_IF(E_UNEXPECTED, (WI_IsAnyFlagSet(Flags, ~LXSS_DELETE_DISTRO_FLAGS_ALL)));
3067
3068 // For WSL1 distributions delete rootfs, temp, and the 9p socket.
3069 std::filesystem::path deletePath{};
3070 if (WI_IsFlagSet(Flags, LXSS_DELETE_DISTRO_FLAGS_ROOTFS))
3071 {
3072 deletePath = Configuration.BasePath / LXSS_ROOTFS_DIRECTORY;
3073 if (PathFileExistsW(deletePath.c_str()))
3074 {
3075 LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(deletePath.c_str()));
3076 }
3077
3078 deletePath = Configuration.BasePath / LXSS_TEMP_DIRECTORY;
3079 if (PathFileExistsW(deletePath.c_str()))
3080 {
3081 LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(deletePath.c_str()));
3082 }
3083
3084 deletePath = Configuration.BasePath / LXSS_PLAN9_UNIX_SOCKET;
3085 if (PathFileExistsW(deletePath.c_str()))
3086 {
3087 LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(deletePath.c_str()));
3088 }
3089 }
3090
3091 auto deleteWithRetry = [&](const std::filesystem::path& path) {
3092 try
3093 {
3094 wsl::shared::retry::RetryWithTimeout<void>(
3095 [&]() { THROW_IF_WIN32_BOOL_FALSE(DeleteFileW(path.c_str())); },
3096 std::chrono::milliseconds(100),
3097 std::chrono::seconds(10),
3098 {HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION), HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)});
3099 }
3100 CATCH_LOG_MSG("Failed to delete %ls", path.c_str())
3101 };
3102
3103 // For WSL2 distributions, unmount and delete the VHD.
3104 if (WI_IsFlagSet(Flags, LXSS_DELETE_DISTRO_FLAGS_VHD) || WI_IsFlagSet(Flags, LXSS_DELETE_DISTRO_FLAGS_UNMOUNT))
3105 {
3106 if (PathFileExistsW(Configuration.VhdFilePath.c_str()))
3107 {
3108 if (m_utilityVm)
3109 {
3110 try
3111 {
3112 m_utilityVm->EjectVhd(Configuration.VhdFilePath.c_str());
3113 }
3114 CATCH_LOG()
3115 }
3116
3117 if (WI_IsFlagSet(Flags, LXSS_DELETE_DISTRO_FLAGS_VHD))
3118 {
3119 // The VHD might be in use so try to delete it for up to 10 seconds.
3120 deleteWithRetry(Configuration.VhdFilePath);
3121 }
3122 }
3123 }
3124
3125 if (WI_IsFlagSet(Flags, LXSS_DELETE_DISTRO_FLAGS_SHORTCUTS))
3126 {
3127 // Delete the shortcut icon, if any
3128 const auto shortcutIconPath = Configuration.BasePath / c_shortIconName;
3129 if (std::filesystem::exists(shortcutIconPath))
3130 {
3131 deleteWithRetry(shortcutIconPath);
3132 }
3133
3134 // Remove start menu entry for the distribution, if any.
3135 if (Configuration.ShortcutPath.has_value())
3136 {
3137 // The shortcut file may be in use. Try to delete it for up to 10 seconds, and then give up.
3138 deleteWithRetry(Configuration.ShortcutPath.value());
3139 }
3140
3141 // Remove the terminal profile, if any.
3142 try
3143 {
3144 const auto lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
3145 const auto profile = DistributionRegistration::Open(lxssKey.get(), Configuration.DistroId).Read(Property::TerminalProfilePath);
3146
3147 if (profile.has_value())
3148 {
3149 deleteWithRetry(profile.value());
3150 }
3151 }
3152 CATCH_LOG()
3153 }
3154
3155 // Remove start menu shortcuts for WSLg applications.
3156 if (WI_IsFlagSet(Flags, LXSS_DELETE_DISTRO_FLAGS_WSLG_SHORTCUTS))
3157 {
3158 try
3159 {
3160 const auto dllPath = wsl::windows::common::wslutil::GetBasePath() / WSLG_TS_PLUGIN_DLL;
3161 static LxssDynamicFunction<decltype(RemoveAppProvider)> removeAppProvider(dllPath.c_str(), "RemoveAppProvider");
3162 LOG_IF_FAILED(removeAppProvider(Configuration.Name.c_str()));
3163 }
3164 CATCH_LOG()
3165 }
3166
3167 // If the basepath is empty, delete it.
3168 try
3169 {
3170 if (std::filesystem::is_empty(Configuration.BasePath))
3171 {
3172 LOG_IF_WIN32_BOOL_FALSE_MSG(
3173 RemoveDirectory(Configuration.BasePath.c_str()), "Failed to delete %ls", Configuration.BasePath.c_str());
3174 }
3175 }
3176 CATCH_LOG();
3177 }
3178
3179 _Requires_exclusive_lock_held_(m_instanceLock)
3180 std::vector<DistributionRegistration> LxssUserSessionImpl::_EnumerateDistributions(
3181 _In_ HKEY LxssKey, _In_ bool ListAll, _In_ const std::optional<GUID>& Exclude)
3182 {
3183 // Iterate through all subkeys looking for distributions.
3184 std::vector<DistributionRegistration> distributions;
3185 std::vector<GUID> orphanedDistributions;
3186 for (const auto& distro : wsl::windows::common::registry::EnumGuidKeys(LxssKey))
3187 {
3188 if (Exclude.has_value() && IsEqualGUID(Exclude.value(), distro.first))
3189 {
3190 continue;
3191 }
3192
3193 // Validate that the distribution's package is still installed.
3194 if (!_ValidateDistro(LxssKey, &distro.first))
3195 {
3196 orphanedDistributions.push_back(distro.first);
3197 continue;
3198 }
3199
3200 auto registration = DistributionRegistration::Open(LxssKey, distro.first);
3201
3202 // Add the distribution to the list if the caller requested all, or if
3203 // it is installed or upgrading.
3204 const DWORD state = registration.Read(Property::State);
3205 if ((ListAll) || (state == LxssDistributionStateInstalled))
3206 {
3207 distributions.push_back(std::move(registration));
3208 }
3209 }
3210
3211 // Unregister each orphaned distribution.
3212 for (GUID Distro : orphanedDistributions)
3213 {
3214 // TODO: This can fail if the registration is broken.
3215 auto configuration = s_GetDistributionConfiguration(DistributionRegistration::Open(LxssKey, Distro));
3216 _UnregisterDistributionLockHeld(LxssKey, configuration);
3217 }
3218
3219 // Ensure that the default distribution is still valid.
3220 if (!orphanedDistributions.empty())
3221 {
3222 try
3223 {
3224 _GetDefaultDistro(LxssKey);
3225 }
3226 CATCH_LOG()
3227 }
3228
3229 return distributions;
3230 }
3231
3232 _Requires_lock_held_(m_instanceLock)
3233 void LxssUserSessionImpl::_EnsureNotLocked(_In_ LPCGUID DistroGuid, const std::source_location& location)
3234 {
3235 const auto found = std::find_if(m_lockedDistributions.begin(), m_lockedDistributions.end(), [&DistroGuid](const auto& entry) {
3236 return IsEqualGUID(entry.first, *DistroGuid);
3237 });
3238
3239 THROW_HR_IF_MSG(
3240 E_ILLEGAL_STATE_CHANGE,
3241 (found != m_lockedDistributions.end()),
3242 "%hs, %hs:%u",
3243 location.function_name(),
3244 location.file_name(),
3245 location.line());
3246 }
3247
3248 _Requires_exclusive_lock_held_(m_instanceLock)
3249 GUID LxssUserSessionImpl::_GetDefaultDistro(_In_ HKEY LxssKey)
3250 {
3251 ExecutionContext context(Context::GetDefaultDistro);
3252
3253 GUID defaultDistroId = {};
3254 HRESULT result;
3255 try
3256 {
3257 const auto defaultDistro = DistributionRegistration::OpenDefault(LxssKey);
3258
3259 THROW_HR_IF(WSL_E_DEFAULT_DISTRO_NOT_FOUND, !defaultDistro.has_value());
3260
3261 // Ensure that the default distribution is valid.
3262 if (!_ValidateDistro(LxssKey, &defaultDistro->Id()))
3263 {
3264 // Delete the old default distribution.
3265 DistributionRegistration::DeleteDefault(LxssKey);
3266
3267 const auto configuration = s_GetDistributionConfiguration(defaultDistro.value());
3268 _UnregisterDistributionLockHeld(LxssKey, configuration);
3269
3270 // Validate remaining WSL distributions, if there are any remaining
3271 // set the first one found to the new default.
3272 const auto distros = _EnumerateDistributions(LxssKey);
3273 THROW_HR_IF(WSL_E_DEFAULT_DISTRO_NOT_FOUND, (distros.size() == 0));
3274
3275 DistributionRegistration::SetDefault(LxssKey, distros[0]);
3276 defaultDistroId = distros[0].Id();
3277 }
3278 else
3279 {
3280 defaultDistroId = defaultDistro->Id();
3281 }
3282
3283 result = S_OK;
3284 }
3285 catch (...)
3286 {
3287 result = WSL_E_DEFAULT_DISTRO_NOT_FOUND;
3288 }
3289
3290 THROW_IF_FAILED(result);
3291
3292 return defaultDistroId;
3293 }
3294
3295 LONG LxssUserSessionImpl::_GetElfExitStatus(_In_ const LXSS_RUN_ELF_CONTEXT& Context)
3296 {
3297 // Wait for the instance to terminate or the client process to exit.
3298 const wil::unique_handle clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(GENERIC_READ | SYNCHRONIZE);
3299 THROW_HR_IF(E_ABORT, !wsl::windows::common::relay::InterruptableWait(Context.instanceTerminatedEvent.get(), {clientProcess.get()}));
3300
3301 // Ensure that the process exited successfully. If the process encountered
3302 // an error, wait for the stderr worker thread and log the error message.
3303 LONG exitStatus;
3304 THROW_IF_NTSTATUS_FAILED(LxssClientInstanceGetExitStatus(Context.instanceHandle.get(), &exitStatus));
3305
3306 return exitStatus;
3307 }
3308
3309 wsl::core::Config LxssUserSessionImpl::_GetResultantConfig(_In_ const HANDLE userToken)
3310 {
3311 const auto configFilePath = wsl::windows::common::helpers::GetWslConfigPath(userToken);
3312 // Open the config file (%userprofile%\.wslconfig).
3313 wsl::core::Config config(configFilePath.c_str(), userToken);
3314
3315 _LoadNetworkingSettings(config, userToken);
3316 return config;
3317 }
3318
3319 void LxssUserSessionImpl::_LoadDiskMount(_In_ HKEY Key, _In_ const std::wstring& LunStr) const
3320 try
3321 {
3322 // Get the disk path
3323 const auto path = wsl::windows::common::registry::ReadString(Key, nullptr, c_diskValueName);
3324
3325 // Get the disk type; throw if unexpected type
3326 const auto diskType = static_cast<WslCoreVm::DiskType>(wsl::windows::common::registry::ReadDword(
3327 Key, nullptr, c_disktypeValueName, static_cast<DWORD>(WslCoreVm::DiskType::PassThrough)));
3328
3329 THROW_HR_IF(E_UNEXPECTED, (diskType != WslCoreVm::DiskType::VHD && diskType != WslCoreVm::DiskType::PassThrough));
3330
3331 // Attach the disk to the VM, reusing the same LUN if possible.
3332 //
3333 // N.B. The disk-mount state is stored under the user's SID in a volatile (per-boot)
3334 // registry key, so the disk being restored here was mounted earlier in this same boot
3335 // by this same user. For a VHD we therefore pass the user token so the access grant and
3336 // the path resolution run under the mounting user's identity: a privileged operation can
3337 // only ever touch a file that user can already reach, which closes the restore-time
3338 // junction/symlink swap (TOCTOU) without re-resolving the path as SYSTEM.
3339 //
3340 // A pass-through (raw block device) attach is elevation-gated and the reconnecting user
3341 // may no longer be elevated, so it is restored as SYSTEM (no token). Block-device paths
3342 // (\\.\PhysicalDriveN) have no reparse-point surface, so there is no swap to defend
3343 // against.
3344 auto lun = std::stoul(LunStr);
3345 const HANDLE userToken = (diskType == WslCoreVm::DiskType::VHD) ? m_userToken.get() : nullptr;
3346 m_utilityVm->AttachDisk(path.c_str(), diskType, lun, true, userToken);
3347
3348 // Restore each mount point.
3349 for (const auto& e : wsl::windows::common::registry::EnumKeys(Key, KEY_READ))
3350 {
3351 auto optionalValue = [&](std::wstring& storage, LPCWSTR name) -> LPCWSTR {
3352 try
3353 {
3354 storage = wsl::windows::common::registry::ReadString(e.second.get(), nullptr, name);
3355 return storage.c_str();
3356 }
3357 catch (...)
3358 {
3359 LOG_CAUGHT_EXCEPTION();
3360 return nullptr;
3361 }
3362 };
3363
3364 std::wstring options;
3365 std::wstring type;
3366
3367 // Get the mount name
3368 auto diskName = wsl::windows::common::registry::ReadString(e.second.get(), nullptr, c_mountNameValueName, L"");
3369
3370 // If there was not a disk name stored, set it to the default generated name when mounting
3371 const auto result = m_utilityVm->MountDisk(
3372 path.c_str(),
3373 diskType,
3374 std::stoul(e.first),
3375 diskName.empty() ? nullptr : diskName.c_str(),
3376 optionalValue(type, c_typeValueName),
3377 optionalValue(options, c_optionsValueName));
3378
3379 LOG_HR_IF_MSG(
3380 E_UNEXPECTED,
3381 result.Result != 0,
3382 "Failed to restore disk mount. Device: '%ls', Partition: '%ls', error: %i, step: %i",
3383 path.c_str(),
3384 e.first.c_str(),
3385 result.Result,
3386 result.Step);
3387 }
3388
3389 return;
3390 }
3391 CATCH_LOG()
3392
3393 void LxssUserSessionImpl::_LoadNetworkingSettings(_Inout_ wsl::core::Config& config, _In_ HANDLE userToken)
3394 try
3395 {
3396 const auto autoProxyRequested = config.EnableAutoProxy;
3397 if (config.EnableAutoProxy)
3398 {
3399 if (SUCCEEDED(HttpProxyStateTracker::s_LoadWinHttpProxyMethods()))
3400 {
3401 if (!m_httpProxyStateTracker)
3402 {
3403 try
3404 {
3405 m_httpProxyStateTracker =
3406 std::make_shared<HttpProxyStateTracker>(config.InitialAutoProxyTimeout, userToken, config.NetworkingMode);
3407 }
3408 catch (...)
3409 {
3410 LOG_CAUGHT_EXCEPTION_MSG("autoProxy failed to start");
3411 config.EnableAutoProxy = false;
3412 }
3413 }
3414 }
3415 else
3416 {
3417 config.EnableAutoProxy = false;
3418 }
3419 }
3420
3421 WSL_LOG(
3422 "AutoProxyEnabled",
3423 TraceLoggingValue(autoProxyRequested, "autoProxyRequested"),
3424 TraceLoggingValue(config.EnableAutoProxy, "autoProxyEnabled"));
3425 }
3426 CATCH_LOG();
3427
3428 void LxssUserSessionImpl::_LoadDiskMounts()
3429 try
3430 {
3431 const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(&m_userSid.Sid);
3432 for (const auto& e : wsl::windows::common::registry::EnumKeys(key.get(), KEY_READ))
3433 {
3434 _LoadDiskMount(e.second.get(), e.first);
3435 }
3436
3437 // Clear the state from the registry now that the mounts have been loaded
3438 wsl::windows::common::registry::ClearSubkeys(key.get());
3439 return;
3440 }
3441 CATCH_LOG()
3442
3443 void LxssUserSessionImpl::_ProcessImportResultMessage(
3444 const LX_MINI_INIT_IMPORT_RESULT& Message,
3445 const gsl::span<gsl::byte> Span,
3446 HKEY LxssKey,
3447 LXSS_DISTRO_CONFIGURATION& Configuration,
3448 wsl::windows::service::DistributionRegistration& Registration)
3449 {
3450 THROW_HR_IF(WSL_E_NOT_A_LINUX_DISTRO, !Message.ValidDistribution);
3451
3452 if (Configuration.Name.empty())
3453 {
3454 THROW_HR_IF(WSL_E_DISTRIBUTION_NAME_NEEDED, Message.DefaultNameIndex <= 0);
3455
3456 auto distributionName = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(Span, Message.DefaultNameIndex));
3457
3458 // Validate that name is valid, and doesn't conflict with existing distributions.
3459 s_ValidateDistroName(distributionName.c_str());
3460 _ValidateDistributionNameAndPathNotInUse(LxssKey, nullptr, distributionName.c_str(), Registration.Id());
3461
3462 Configuration.Name = std::move(distributionName);
3463 Registration.Write(Property::Name, Configuration.Name.c_str());
3464 }
3465
3466 if (Message.FlavorIndex > 0)
3467 {
3468 Configuration.Flavor = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(Span, Message.FlavorIndex));
3469 Registration.Write(Property::Flavor, Configuration.Flavor.c_str());
3470 }
3471
3472 if (Message.VersionIndex != 0)
3473 {
3474 Configuration.OsVersion = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(Span, Message.VersionIndex));
3475 Registration.Write(Property::OsVersion, Configuration.OsVersion.c_str());
3476 }
3477
3478 // Do not create start menu shortcut or terminal profiles for appx based distributions.
3479 if (Configuration.PackageFamilyName.empty())
3480 {
3481 auto impersonate = wil::CoImpersonateClient();
3482
3483 Registration.Write(Property::Modern, 1);
3484
3485 std::filesystem::path iconPath;
3486 const auto basePath = wsl::windows::common::wslutil::GetBasePath();
3487
3488 if (Message.ShortcutIconIndex != 0)
3489 {
3490 iconPath = Configuration.BasePath / c_shortIconName;
3491 const wil::unique_handle icon{CreateFileW(iconPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
3492 THROW_LAST_ERROR_IF(!icon);
3493
3494 const auto iconData = Span.subspan(Message.ShortcutIconIndex, Message.ShortcutIconSize);
3495 THROW_IF_WIN32_BOOL_FALSE(WriteFile(icon.get(), iconData.data(), static_cast<DWORD>(iconData.size_bytes()), nullptr, nullptr));
3496 }
3497 else
3498 {
3499 iconPath = basePath / L"wsl.exe";
3500 }
3501
3502 if (Message.GenerateShortcut)
3503 {
3504 _CreateDistributionShortcut(Configuration.Name.c_str(), iconPath.c_str(), (basePath / L"wsl.exe").c_str(), Registration);
3505 }
3506
3507 // Generate a Windows Terminal profile, as long as the distribution didn't opt-out of it.
3508 if (Message.GenerateTerminalProfile)
3509 {
3510 if (Message.TerminalProfileIndex != 0)
3511 {
3512 const auto terminalProfileSpan = Span.subspan(Message.TerminalProfileIndex, Message.TerminalProfileSize);
3513 const std::string_view terminalProfile(
3514 reinterpret_cast<const char*>(terminalProfileSpan.data()), terminalProfileSpan.size());
3515 _CreateTerminalProfile(terminalProfile, iconPath, Configuration, Registration);
3516 }
3517 else
3518 {
3519 constexpr auto defaultProfile = R"(
3520 {
3521 "profiles": [{
3522 "startingDirectory": "~"
3523 }]
3524 })";
3525
3526 _CreateTerminalProfile(defaultProfile, iconPath, Configuration, Registration);
3527 }
3528 }
3529 }
3530 }
3531
3532 LXSS_RUN_ELF_CONTEXT LxssUserSessionImpl::_RunElfBinary(
3533 _In_ LPCSTR CommandLine,
3534 _In_ LPCWSTR TargetDirectory,
3535 _In_ HANDLE ClientProcess,
3536 _In_opt_ HANDLE StdIn,
3537 _In_opt_ HANDLE StdOut,
3538 _In_opt_ HANDLE StdErr,
3539 _In_opt_count_(NumMounts) PLX_KMAPPATHS_ADDMOUNT Mounts,
3540 _In_opt_ ULONG NumMounts)
3541 {
3542 GUID instanceId;
3543 THROW_IF_FAILED(CoCreateGuid(&instanceId));
3544
3545 // If the caller did not provide stdin, stdout, or stderr handles use the nul device.
3546 wil::unique_hfile stdInLocal;
3547 if (!ARGUMENT_PRESENT(StdIn))
3548 {
3549 stdInLocal = wsl::windows::common::filesystem::OpenNulDevice(GENERIC_READ);
3550 StdIn = stdInLocal.get();
3551 }
3552
3553 wil::unique_hfile stdOutLocal;
3554 if (!ARGUMENT_PRESENT(StdOut))
3555 {
3556 stdOutLocal = wsl::windows::common::filesystem::OpenNulDevice(GENERIC_WRITE);
3557 StdOut = stdOutLocal.get();
3558 }
3559
3560 wil::unique_hfile stdErrLocal;
3561 if (!ARGUMENT_PRESENT(StdErr))
3562 {
3563 stdErrLocal = wsl::windows::common::filesystem::OpenNulDevice(GENERIC_WRITE);
3564 StdErr = stdErrLocal.get();
3565 }
3566
3567 // Get the user and instance tokens.
3568 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
3569 const wil::unique_handle instanceToken(wsl::windows::common::security::CreateRestrictedToken(userToken.get()));
3570
3571 // Open handles to the root directory and temp directory while impersonating
3572 // the client.
3573 wil::unique_hfile rootDirectory;
3574 wil::unique_hfile tempDirectory;
3575 {
3576 auto runAsUser = wil::impersonate_token(userToken.get());
3577 rootDirectory = wsl::windows::common::filesystem::OpenDirectoryHandle(TargetDirectory, true);
3578 const auto tempFolder = std::filesystem::path(TargetDirectory) / LXSS_TEMP_DIRECTORY;
3579 wsl::windows::common::filesystem::EnsureDirectory(tempFolder.c_str());
3580 const auto instanceIdString = wsl::shared::string::GuidToString<wchar_t>(instanceId);
3581 const auto tempPath = tempFolder / instanceIdString;
3582 tempDirectory = wsl::windows::common::filesystem::WipeAndOpenDirectory(tempPath.c_str());
3583 }
3584
3585 // Create manual reset event that is signaled on instance termination
3586 LXSS_RUN_ELF_CONTEXT elfContext;
3587 elfContext.instanceTerminatedEvent.create(wil::EventOptions::ManualReset);
3588 THROW_LAST_ERROR_IF(!elfContext.instanceTerminatedEvent);
3589
3590 // Create and initialize a job object for the instance.
3591 const wil::unique_handle instanceJob(CreateJobObjectW(nullptr, nullptr));
3592 THROW_LAST_ERROR_IF(!instanceJob);
3593
3594 Security::InitializeInstanceJob(instanceJob.get());
3595
3596 // Create a new instance with bsdtar as the init process to perform the extraction.
3597 LX_KINSTANCECREATESTART createParameters = {};
3598 createParameters.InstanceId = instanceId;
3599 createParameters.RootFsType = LXSS_FS_TYPE_TMPFS;
3600 createParameters.RootDirectoryHandle = HandleToULong(rootDirectory.get());
3601 createParameters.TempDirectoryHandle = HandleToULong(tempDirectory.get());
3602 createParameters.JobHandle = HandleToULong(instanceJob.get());
3603 createParameters.TokenHandle = HandleToULong(instanceToken.get());
3604 createParameters.InstanceTerminatedEventHandle = HandleToULong(elfContext.instanceTerminatedEvent.get());
3605 createParameters.NumPathsToMap = NumMounts;
3606 createParameters.PathsToMap = Mounts;
3607
3608 // Format the kernel command line.
3609 std::string kernelCommandLine("init=");
3610 kernelCommandLine += CommandLine;
3611 createParameters.KernelCommandLine = kernelCommandLine.c_str();
3612
3613 // Set up the file descriptors that will be passed to the init process.
3614 LX_KINIT_FILE_DESCRIPTOR initFileDescriptors[] = {
3615 {StdIn, LX_O_RDONLY, LX_FD_CLOEXEC}, {StdOut, LX_O_WRONLY, LX_FD_CLOEXEC}, {StdErr, LX_O_WRONLY, LX_FD_CLOEXEC}};
3616
3617 createParameters.NumInitFileDescriptors = RTL_NUMBER_OF(initFileDescriptors);
3618 createParameters.InitFileDescriptors = initFileDescriptors;
3619
3620 {
3621 // Acquire assign primary token privilege in order to pass the primary token for init process.
3622 auto revertPriv = wsl::windows::common::security::AcquirePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME);
3623 THROW_IF_NTSTATUS_FAILED(LxssClientInstanceCreate(&createParameters, &elfContext.instanceHandle));
3624 }
3625
3626 // Start the instance.
3627 THROW_IF_NTSTATUS_FAILED(LxssClientInstanceStart(elfContext.instanceHandle.get(), ClientProcess));
3628
3629 return elfContext;
3630 }
3631
3632 _Requires_lock_held_(m_instanceLock)
3633 std::shared_ptr<LxssRunningInstance> LxssUserSessionImpl::_RunningInstance(_In_ LPCGUID DistroGuid)
3634 {
3635 _EnsureNotLocked(DistroGuid);
3636 const auto instance = m_runningInstances.find(*DistroGuid);
3637 if (instance != m_runningInstances.end())
3638 {
3639 return instance->second;
3640 }
3641
3642 return nullptr;
3643 }
3644
3645 LXSS_VM_MODE_SETUP_CONTEXT
3646 LxssUserSessionImpl::_RunUtilityVmSetup(_In_ const LXSS_DISTRO_CONFIGURATION& Configuration, _In_ LX_MESSAGE_TYPE MessageType, ULONG ExportFlags, bool SetVersion)
3647 {
3648 THROW_HR_IF(E_INVALIDARG, ((MessageType != LxMiniInitMessageImport) && (MessageType != LxMiniInitMessageExport) && (MessageType != LxMiniInitMessageImportInplace)));
3649
3650 // Open the client process so the operation can be aborted if client exits.
3651 wil::unique_handle clientProcess = wsl::windows::common::wslutil::OpenCallingProcess(GENERIC_READ | SYNCHRONIZE);
3652
3653 // Ensure that the Linux utility VM has been created.
3654 std::lock_guard lock(m_instanceLock);
3655 _CreateVm();
3656
3657 WI_SetFlagIf(ExportFlags, LXSS_EXPORT_DISTRO_FLAGS_VERBOSE, SetVersion && m_utilityVm->GetConfig().SetVersionDebug);
3658
3659 // Generate a GUID for the instance.
3660 GUID instanceId;
3661 THROW_IF_FAILED(CoCreateGuid(&instanceId));
3662
3663 LXSS_VM_MODE_SETUP_CONTEXT context{};
3664 ULONG connectPort{};
3665 context.instance = m_utilityVm->CreateInstance(instanceId, Configuration, MessageType, 0, 0, 0, ExportFlags, &connectPort);
3666
3667 // Establish the socket that will be used to transfer the tar file contents.
3668 context.tarSocket = wsl::windows::common::hvsocket::Connect(m_utilityVm->GetRuntimeId(), connectPort);
3669 context.errorSocket = wsl::windows::common::hvsocket::Connect(m_utilityVm->GetRuntimeId(), connectPort);
3670 WI_ASSERT(context.tarSocket.is_valid());
3671
3672 return context;
3673 }
3674
3675 void LxssUserSessionImpl::_SendDistributionRegisteredEvent(_In_ const LXSS_DISTRO_CONFIGURATION& Configuration) const
3676 {
3677 WslOfflineDistributionInformation distributionInfo{};
3678 distributionInfo.Id = Configuration.DistroId;
3679 distributionInfo.Name = Configuration.Name.c_str();
3680 distributionInfo.PackageFamilyName = Configuration.PackageFamilyName.c_str();
3681 distributionInfo.Flavor = Configuration.Flavor.c_str();
3682 distributionInfo.Version = Configuration.OsVersion.c_str();
3683 m_pluginManager.OnDistributionRegistered(&m_session, &distributionInfo);
3684 }
3685
3686 _Requires_lock_held_(m_instanceLock)
3687 void LxssUserSessionImpl::_SetDistributionInstalled(_In_ HKEY LxssKey, _In_ const GUID& DistroGuid)
3688 {
3689 // Mark the distribution as installed.
3690 auto registration = DistributionRegistration::Open(LxssKey, DistroGuid);
3691 registration.Write(Property::State, LxssDistributionStateInstalled);
3692
3693 // Set this distribution as the default if there is not already a default
3694 // distribution.
3695 const auto defaultDistro = DistributionRegistration::OpenDefault(LxssKey);
3696 if (!defaultDistro.has_value())
3697 {
3698 DistributionRegistration::SetDefault(LxssKey, registration);
3699 }
3700 }
3701
3702 _Requires_lock_not_held_(m_instanceLock)
3703 bool LxssUserSessionImpl::_TerminateInstance(_In_ LPCGUID DistroGuid, _In_ bool CheckForClients)
3704 {
3705 std::lock_guard lock(m_instanceLock);
3706 return _TerminateInstanceInternal(DistroGuid, CheckForClients);
3707 }
3708
3709 _Requires_exclusive_lock_held_(m_instanceLock)
3710 bool LxssUserSessionImpl::_TerminateInstanceInternal(_In_ LPCGUID DistroGuid, _In_ bool CheckForClients)
3711 {
3712 ExecutionContext context(Context::TerminateDistro);
3713
3714 // Look up an instance with the matching distro identifier. If the are no
3715 // more active clients, it is stopped and removed from the list.
3716 bool success = true;
3717 const auto instance = m_runningInstances.find(*DistroGuid);
3718
3719 if (instance != m_runningInstances.end())
3720 {
3721 const auto clientKey = instance->second->GetLifetimeManagerId();
3722 if ((CheckForClients == false) || (!m_lifetimeManager.IsAnyProcessRegistered(clientKey)))
3723 {
3724 // Stop the instance and move it to a list of terminated instances.
3725 // This allows the instance destructor to run without the instance
3726 // lock held, and allows in-flight termination callbacks to complete.
3727 const bool force = !CheckForClients;
3728 try
3729 {
3730 success = instance->second->RequestStop(force);
3731 }
3732 CATCH_LOG()
3733
3734 success = (success || force);
3735 if (success)
3736 {
3737 if (const auto* wslcoreInstance = dynamic_cast<WslCoreInstance*>(instance->second.get()); wslcoreInstance != nullptr)
3738 {
3739 m_pluginManager.OnDistributionStopping(&m_session, wslcoreInstance->DistributionInformation());
3740 }
3741
3742 instance->second->Stop();
3743
3744 const auto clientId = instance->second->GetClientId();
3745 {
3746 auto lock = m_terminatedInstanceLock.lock_exclusive();
3747 m_terminatedInstances.push_back(std::move(instance->second));
3748 }
3749
3750 m_lifetimeManager.RemoveCallback(clientKey);
3751
3752 m_runningInstances.erase(instance);
3753
3754 // If the instance that was terminated was a WSL2 instance,
3755 // check if the VM is now idle.
3756 if (clientId != LXSS_CLIENT_ID_INVALID)
3757 {
3758 _VmCheckIdle();
3759 }
3760 }
3761 }
3762 }
3763
3764 return success;
3765 }
3766
3767 void LxssUserSessionImpl::_UpdateInit(_In_ const LXSS_DISTRO_CONFIGURATION& Configuration)
3768 {
3769 // Only update the init binary once per-distro, per-session.
3770 auto lock = m_initUpdateLock.lock_exclusive();
3771 if (std::find(m_updatedInitDistros.begin(), m_updatedInitDistros.end(), Configuration.DistroId) == m_updatedInitDistros.end())
3772 {
3773 wsl::windows::common::filesystem::UpdateInit(Configuration.BasePath.c_str(), Configuration.Version);
3774 m_updatedInitDistros.emplace_back(Configuration.DistroId);
3775 }
3776 }
3777
3778 HRESULT LxssUserSessionImpl::MountRootNamespaceFolder(_In_ LPCWSTR HostPath, _In_ LPCWSTR GuestPath, _In_ bool ReadOnly, _In_ LPCWSTR Name)
3779 {
3780 std::lock_guard lock(m_instanceLock);
3781 RETURN_HR_IF(E_NOT_VALID_STATE, !m_utilityVm);
3782
3783 m_utilityVm->MountRootNamespaceFolder(HostPath, GuestPath, ReadOnly, Name);
3784 return S_OK;
3785 }
3786
3787 HRESULT LxssUserSessionImpl::CreateLinuxProcess(_In_opt_ const GUID* Distro, _In_ LPCSTR Path, _In_ LPCSTR* Arguments, _Out_ SOCKET* Socket)
3788 {
3789 std::lock_guard lock(m_instanceLock);
3790 RETURN_HR_IF(E_NOT_VALID_STATE, !m_utilityVm);
3791
3792 if (Distro == nullptr)
3793 {
3794 *Socket = m_utilityVm->CreateRootNamespaceProcess(Path, Arguments).release();
3795 }
3796 else
3797 {
3798 const auto distro = _RunningInstance(Distro);
3799 THROW_HR_IF(WSL_E_VM_MODE_INVALID_STATE, !distro);
3800
3801 const auto wsl2Distro = dynamic_cast<WslCoreInstance*>(distro.get());
3802 THROW_HR_IF(WSL_E_WSL2_NEEDED, !wsl2Distro);
3803
3804 *Socket = wsl2Distro->CreateLinuxProcess(Path, Arguments).release();
3805 }
3806 return S_OK;
3807 }
3808
3809 _Requires_exclusive_lock_held_(m_instanceLock)
3810 void LxssUserSessionImpl::_UnregisterDistributionLockHeld(_In_ HKEY LxssKey, _In_ const LXSS_DISTRO_CONFIGURATION& Configuration)
3811 {
3812 ExecutionContext context(Context::UnregisterDistro);
3813
3814 try
3815 {
3816 const auto removedDistroString = wsl::shared::string::GuidToString<wchar_t>(Configuration.DistroId);
3817
3818 // Terminate any running instance of the distro and delete the distro.
3819 _TerminateInstanceInternal(&Configuration.DistroId);
3820
3821 // Impersonate the user and delete the distro filesystem.
3822 {
3823 auto runAsUser = wil::CoImpersonateClient();
3824 _DeleteDistributionLockHeld(Configuration);
3825 }
3826
3827 // Delete the distro registry key.
3828 wsl::windows::common::registry::DeleteKey(LxssKey, removedDistroString.c_str());
3829 }
3830 CATCH_LOG()
3831 }
3832
3833 void LxssUserSessionImpl::_TimezoneUpdated()
3834 try
3835 {
3836 WSL_LOG("Received timezone change notification");
3837
3838 // Update the timezone information for each running instance.
3839 std::lock_guard lock(m_instanceLock);
3840 std::for_each(m_runningInstances.begin(), m_runningInstances.end(), [&](auto& pair) { pair.second->UpdateTimezone(); });
3841
3842 return;
3843 }
3844 CATCH_LOG()
3845
3846 _Requires_exclusive_lock_held_(m_instanceLock)
3847 bool LxssUserSessionImpl::_ValidateDistro(_In_ HKEY LxssKey, _In_ LPCGUID DistroGuid)
3848 {
3849 bool isValid = false;
3850 std::wstring packageFamilyName;
3851 try
3852 {
3853 // Ensure a subkey exists for the distribution.
3854 auto configuration = s_GetDistributionConfiguration(DistributionRegistration::Open(LxssKey, *DistroGuid));
3855 packageFamilyName = configuration.PackageFamilyName;
3856
3857 // If there is no package family name associated with the distribution,
3858 // the user is responsible for unregistering the distribution.
3859 // Otherwise, ensure that the package is still installed. If the
3860 // package is installed ensure that the root file system is present.
3861 //
3862 // N.B. This covers the case where a package was uninstalled and
3863 // reinstalled without the service being invoked.
3864 isValid = true;
3865
3866 // TODO: Below block needs test coverage
3867 if (!packageFamilyName.empty())
3868 {
3869 std::filesystem::path localPath;
3870 PCWSTR path;
3871 if (WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE))
3872 {
3873 localPath = configuration.BasePath / LXSS_ROOTFS_DIRECTORY;
3874 path = localPath.c_str();
3875 }
3876 else
3877 {
3878 path = configuration.VhdFilePath.c_str();
3879 }
3880
3881 auto runAsUser = wil::CoImpersonateClient();
3882
3883 // If the path is not found and the package is removed, then the distro can be considered to be uninstalled.
3884 // Only do this if the path is actually missing to prevent any accidental distro deletion if the store API
3885 // can't find the package for transient reasons.
3886 if (!PathFileExistsW(path) && !wsl::windows::common::helpers::IsPackageInstalled(packageFamilyName.c_str()))
3887 {
3888 isValid = false;
3889 }
3890 }
3891 }
3892 CATCH_LOG()
3893
3894 if (!isValid)
3895 {
3896 WSL_LOG("ValidateDistributionFailed", TraceLoggingValue(packageFamilyName.c_str(), "packageFamilyName"));
3897 }
3898
3899 return isValid;
3900 }
3901
3902 void LxssUserSessionImpl::_ValidateDistributionNameAndPathNotInUse(
3903 _In_ HKEY LxssKey, _In_opt_ LPCWSTR Path, _In_opt_ LPCWSTR Name, const std::optional<GUID>& Exclude)
3904 {
3905 // Use the canonical path to compare distribution registration paths.
3906 // The canonical path allows us to compare paths regardless of symlinks.
3907 //
3908 // Even with this, it's theoretically possible to use different drive mounts to have two paths
3909 // that will point to the same underlying folder. To catch this, we'd need to use BY_HANDLE_FILE_INFORMATION and compare file & volume indexes.
3910 // Unfortunately this is tricky because this doesn't work of the folder doesn't exist yet (or if a registered distribution's folder has been deleted).
3911 // For the sake of simplicity, this isn't implemented given that trying to double register a distribution will fail at the VHD creation step regardless.
3912
3913 std::error_code error;
3914 std::filesystem::path canonicalPath;
3915
3916 if (Path != nullptr)
3917 {
3918 canonicalPath = wsl::windows::common::filesystem::GetCanonicalPath(Path, error);
3919 if (error)
3920 {
3921 LOG_WIN32(error.value());
3922 }
3923 else
3924 {
3925 Path = canonicalPath.c_str();
3926 }
3927 }
3928
3929 // Ensure no existing distributions have the same name or install path.
3930 for (const auto& distro : _EnumerateDistributions(LxssKey, true, Exclude))
3931 {
3932 // Return an appropriate failure code for the two possible
3933 // conditions here:
3934 //
3935 // 1. The distribution is already registered successfully.
3936 // 2. The distribution is currently being registered or unregistered by another thread.
3937
3938 LXSS_DISTRO_CONFIGURATION configuration{};
3939 try
3940 {
3941 configuration = s_GetDistributionConfiguration(distro);
3942 }
3943 catch (...)
3944 {
3945 // Don't break registration of new distro if one registration is invalid.
3946 LOG_CAUGHT_EXCEPTION();
3947 continue;
3948 }
3949
3950 if (Name != nullptr && wsl::shared::string::IsEqual(Name, configuration.Name, true))
3951 {
3952 THROW_HR_WITH_USER_ERROR_IF(
3953 HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS),
3954 wsl::shared::Localization::MessageDistroNameAlreadyExists(),
3955 configuration.State == LxssDistributionStateInstalled);
3956
3957 THROW_HR_MSG(E_ILLEGAL_STATE_CHANGE, "%ls already registered (state = %d)", Name, configuration.State);
3958 }
3959
3960 if (Path != nullptr)
3961 {
3962 auto canonicalDistroPath = wsl::windows::common::filesystem::GetCanonicalPath(configuration.BasePath, error);
3963 if (error)
3964 {
3965 LOG_WIN32(error.value());
3966 }
3967
3968 // Ensure another distribution by a different name is not already registered to the same location.
3969 THROW_HR_WITH_USER_ERROR_IF(
3970 HRESULT_FROM_WIN32(ERROR_FILE_EXISTS),
3971 wsl::shared::Localization::MessageDistroInstallPathAlreadyExists(),
3972 wsl::windows::common::string::IsPathComponentEqual(error ? configuration.BasePath.native() : canonicalDistroPath.native(), Path));
3973 }
3974 }
3975 }
3976
3977 _Requires_exclusive_lock_held_(m_instanceLock)
3978 void LxssUserSessionImpl::_VmCheckIdle()
3979 {
3980 // If the VM is idle, queue a timer to terminate the VM.
3981 // Otherwise, cancel any pending termination timers.
3982 //
3983 // N.B. A negative timeout means that the VM will continue running until it
3984 // is terminated via wsl.exe --shutdown, or the service is stopped.
3985 if (_VmIsIdle())
3986 {
3987 const auto timeout = m_utilityVm->GetVmIdleTimeout();
3988 if (timeout >= 0)
3989 {
3990 auto dueTime = wil::filetime::from_int64(-wil::filetime_duration::one_millisecond * timeout);
3991 SetThreadpoolTimer(m_vmTerminationTimer.get(), &dueTime, 0, 0);
3992 }
3993 }
3994 else
3995 {
3996 SetThreadpoolTimer(m_vmTerminationTimer.get(), nullptr, 0, 0);
3997 }
3998 }
3999
4000 void LxssUserSessionImpl::_VmIdleTerminate()
4001 {
4002 std::lock_guard lock(m_instanceLock);
4003 if (_VmIsIdle())
4004 {
4005 WSL_LOG("StopVm");
4006 m_utilityVm->SaveAttachedDisksState();
4007 _VmTerminate();
4008 }
4009 }
4010
4011 _Requires_exclusive_lock_held_(m_instanceLock)
4012 bool LxssUserSessionImpl::_VmIsIdle()
4013 {
4014 const auto found = std::find_if(m_runningInstances.begin(), m_runningInstances.end(), [&](auto& pair) {
4015 return (pair.second->GetClientId() != LXSS_CLIENT_ID_INVALID);
4016 });
4017
4018 return (m_utilityVm && m_lockedDistributions.empty() && (found == m_runningInstances.end()));
4019 }
4020
4021 _Requires_exclusive_lock_held_(m_instanceLock)
4022 void LxssUserSessionImpl::_VmTerminate()
4023 {
4024 // Cancel any pending termination timers and terminate the system distro and VM.
4025 SetThreadpoolTimer(m_vmTerminationTimer.get(), nullptr, 0, 0);
4026
4027 if (m_utilityVm != nullptr)
4028 {
4029 m_pluginManager.OnVmStopping(&m_session);
4030 }
4031
4032 m_vmTerminating.SetEvent();
4033 if (m_telemetryThread.joinable())
4034 {
4035 m_telemetryThread.join();
4036 }
4037
4038 m_utilityVm.reset();
4039 m_vmId.store(GUID_NULL);
4040
4041 // Reset the user's token since its lifetime is tied to the VM.
4042 m_userToken.reset();
4043 m_session.UserToken = nullptr;
4044
4045 // Reset the event since the VM can be recreated.
4046 // This can done safely because WslCoreVm's destructor waits until
4047 // its distro exit callback is done before returning, so at this point
4048 // it's guaranteed that no one is waiting (or about to wait) on the event.
4049 // Note: Using an auto-reset event wouldn't work since the callback can be invoked
4050 // more than once while the vm is being destroyed.
4051 m_vmTerminating.ResetEvent();
4052 }
4053
4054 void LxssUserSessionImpl::_SetHttpProxyInfo(std::vector<std::string>& environment) const noexcept
4055 try
4056 {
4057 // Copy guarantees a ref is held on the original instance, or it's null.
4058 const auto localTracker = m_httpProxyStateTracker;
4059 if (localTracker)
4060 {
4061 WSL_LOG("_SetHttpProxyInfo: Attempting to set proxy info");
4062 const std::optional<HttpProxySettings> proxySettings = localTracker->WaitForInitialProxySettings();
4063
4064 if (proxySettings.has_value())
4065 {
4066 if (proxySettings->UnsupportedProxyDropReason != UnsupportedProxyReason::Supported)
4067 {
4068 switch (proxySettings->UnsupportedProxyDropReason)
4069 {
4070 case UnsupportedProxyReason::LoopbackNotMirrored:
4071 EMIT_USER_WARNING(wsl::shared::Localization::MessageProxyLocalhostSettingsDropped());
4072 break;
4073 case UnsupportedProxyReason::Ipv6NotMirrored:
4074 EMIT_USER_WARNING(wsl::shared::Localization::MessageProxyV6SettingsDropped());
4075 break;
4076 case UnsupportedProxyReason::LoopbackV6:
4077 EMIT_USER_WARNING(wsl::shared::Localization::MessageProxyLoopbackV6SettingsDropped());
4078 break;
4079 case UnsupportedProxyReason::UnsupportedError:
4080 EMIT_USER_WARNING(wsl::shared::Localization::MessageProxyUnexpectedSettingsDropped());
4081 break;
4082 case UnsupportedProxyReason::Supported:
4083 default:
4084 WSL_LOG("_SetHttpProxyInfo: Unexpected UnsupportedProxyReason");
4085 }
4086 }
4087 if (proxySettings->HasSettingsConfigured())
4088 {
4089 s_AddHttpProxyToEnvironment(proxySettings.value(), environment);
4090
4091 WSL_LOG(
4092 "AutoProxyConfiguration",
4093 TraceLoggingValue(!proxySettings->Proxy.empty(), "ProxySet"),
4094 TraceLoggingValue(!proxySettings->SecureProxy.empty(), "SecureProxySet"),
4095 TraceLoggingValue(proxySettings->ProxyBypasses.size(), "ProxyBypassesCount"),
4096 TraceLoggingValue(!proxySettings->PacUrl.empty(), "PacUrlSet"));
4097 }
4098 else
4099 {
4100 WSL_LOG("_SetHttpProxyInfo: No HttpProxy settings detected so not configuring env vars.");
4101 }
4102 }
4103 else
4104 {
4105 // User will get a notification to restart WSL if proxy query completes later.
4106 WSL_LOG("_SetHttpProxyInfo: Initial HttpProxy query timeout, start WSL process anyway.");
4107 }
4108 }
4109 }
4110 CATCH_LOG()
4111
4112 void LxssUserSessionImpl::_LaunchOOBEIfNeeded() noexcept
4113 try
4114 {
4115 // Impersonate the user and open their lxss registry key.
4116 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
4117 const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get());
4118
4119 // OOBE hasn't run if the value is not present or set to 0.
4120 if (wsl::windows::common::registry::ReadDword(lxssKey.get(), nullptr, LXSS_OOBE_COMPLETE_NAME, false) != false)
4121 {
4122 return;
4123 }
4124
4125 // Don't run OOBE for existing users who already have a distro.
4126 wil::unique_cotaskmem_array_ptr<LXSS_ENUMERATE_INFO> distributions;
4127 THROW_IF_FAILED(EnumerateDistributions(distributions.size_address<ULONG>(), &distributions));
4128 if (distributions.size() > 1)
4129 {
4130 wsl::windows::common::registry::WriteDword(lxssKey.get(), nullptr, LXSS_OOBE_COMPLETE_NAME, true);
4131 return;
4132 }
4133
4134 // This is needed to launch the OOBE process as the user.
4135 wil::unique_handle userTokenCreateProcess;
4136 THROW_IF_WIN32_BOOL_FALSE(::DuplicateTokenEx(
4137 userToken.get(), MAXIMUM_ALLOWED, nullptr, SecurityImpersonation, TokenImpersonation, &userTokenCreateProcess));
4138 wsl::windows::common::helpers::LaunchWslSettingsOOBE(userTokenCreateProcess.get());
4139 wsl::windows::common::registry::WriteDword(lxssKey.get(), nullptr, LXSS_OOBE_COMPLETE_NAME, true);
4140 }
4141 CATCH_LOG()
4142
4143 LXSS_DISTRO_CONFIGURATION
4144 LxssUserSessionImpl::s_GetDistributionConfiguration(const DistributionRegistration& Distro, bool skipName)
4145 {
4146 ExecutionContext context(Context::ReadDistroConfig);
4147
4148 // Read information about the distribution from the distro key.
4149 LXSS_DISTRO_CONFIGURATION configuration;
4150 configuration.DistroId = Distro.Id();
4151 configuration.State = Distro.Read(Property::State);
4152 configuration.Version = Distro.Read(Property::Version);
4153 configuration.BasePath = Distro.Read(Property::BasePath);
4154 configuration.PackageFamilyName = Distro.Read(Property::PackageFamilyName);
4155
4156 // Read the vhd file name and append to the base path.
4157 configuration.VhdFilePath = configuration.BasePath / Distro.Read(Property::VhdFileName);
4158 configuration.Flags = Distro.Read(Property::Flags);
4159
4160 configuration.OsVersion = Distro.Read(Property::OsVersion).value_or(L"");
4161 configuration.Flavor = Distro.Read(Property::Flavor).value_or(L"");
4162 configuration.RunOOBE = Distro.Read(Property::RunOOBE);
4163 configuration.ShortcutPath = Distro.Read(Property::ShortcutPath);
4164
4165 if (!skipName)
4166 {
4167 configuration.Name = Distro.Read(Property::Name);
4168 }
4169
4170 return configuration;
4171 }
4172
4173 CreateLxProcessContext LxssUserSessionImpl::s_GetCreateProcessContext(_In_ const GUID& DistroGuid, _In_ bool SystemDistro)
4174 {
4175 CreateLxProcessContext context{};
4176 std::vector<std::wstring> environment{};
4177 if (!SystemDistro)
4178 {
4179 auto runAsUser = wil::CoImpersonateClient();
4180 const auto lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
4181
4182 const auto registration = DistributionRegistration::Open(lxssKey.get(), DistroGuid);
4183
4184 context.Flags = registration.Read(Property::Flags);
4185 context.DefaultEnvironment = registration.Read(Property::DefaultEnvironment);
4186 }
4187 else
4188 {
4189 context.Flags = DistributionRegistration::ApplyGlobalFlagsOverride(LXSS_DISTRO_FLAGS_DEFAULT | LXSS_DISTRO_FLAGS_VM_MODE);
4190 context.DefaultEnvironment = Property::DefaultEnvironment.DefaultValue;
4191 }
4192
4193 context.UserToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
4194 context.Elevated = wsl::windows::common::security::IsTokenElevated(context.UserToken.get());
4195 return context;
4196 }
4197
4198 // Note that if the user defines proxy variables via WSLENV, these values will be overwritten by those when init spawns process
4199 void LxssUserSessionImpl::s_AddHttpProxyToEnvironment(_In_ const HttpProxySettings& proxySettings, _Inout_ std::vector<std::string>& environment) noexcept
4200 try
4201 {
4202 if (!proxySettings.Proxy.empty())
4203 {
4204 // Note that we add both lower and uppercase as some Linux apps use upper, others lower.
4205 environment.emplace_back(std::format("{}={}", c_httpProxyLower, proxySettings.Proxy));
4206 environment.emplace_back(std::format("{}={}", c_httpProxyUpper, proxySettings.Proxy));
4207 }
4208
4209 if (!proxySettings.SecureProxy.empty())
4210 {
4211 environment.emplace_back(std::format("{}={}", c_httpsProxyLower, proxySettings.SecureProxy));
4212 environment.emplace_back(std::format("{}={}", c_httpsProxyUpper, proxySettings.SecureProxy));
4213 }
4214
4215 if (!proxySettings.ProxyBypassesComma.empty())
4216 {
4217 environment.emplace_back(std::format("{}={}", c_proxyBypassLower, proxySettings.ProxyBypassesComma));
4218 environment.emplace_back(std::format("{}={}", c_proxyBypassUpper, proxySettings.ProxyBypassesComma));
4219 }
4220
4221 if (!proxySettings.PacUrl.empty())
4222 {
4223 // We only add uppercase as there is no standard environment variable for PAC proxies.
4224 // This at least makes the PAC url available to the user in case they wish to use it.
4225 environment.emplace_back(std::format("{}={}", c_pacProxy, proxySettings.PacUrl));
4226
4227 // When PAC is used, the reply only populates the proxy field.
4228 // Set both envs to this value as best effort since PAC is not functional in headless Linux.
4229 if (proxySettings.SecureProxy.empty() && !proxySettings.Proxy.empty())
4230 {
4231 environment.emplace_back(std::format("{}={}", c_httpsProxyLower, proxySettings.Proxy));
4232 environment.emplace_back(std::format("{}={}", c_httpsProxyUpper, proxySettings.Proxy));
4233 }
4234 }
4235 }
4236 CATCH_LOG()
4237
4238 wil::unique_hkey LxssUserSessionImpl::s_OpenLxssUserKey(_In_ HANDLE UserToken)
4239 {
4240 auto runAsUser = wil::impersonate_token(UserToken);
4241 return wsl::windows::common::registry::OpenLxssUserKey();
4242 }
4243
4244 LX_INIT_DRVFS_MOUNT LxssUserSessionImpl::s_InitializeDrvFs(_In_ const std::weak_ptr<LxssUserSessionImpl>& Session, _In_ const GUID& VmId, _In_ HANDLE UserToken) noexcept
4245 {
4246 try
4247 {
4248 const auto session = Session.lock();
4249 if (!session)
4250 {
4251 return LxInitDrvfsMountNone;
4252 }
4253
4254 std::lock_guard lock(session->m_instanceLock);
4255 if (!session->m_utilityVm || !IsEqualGUID(session->m_utilityVm->GetRuntimeId(), VmId))
4256 {
4257 return LxInitDrvfsMountNone;
4258 }
4259
4260 return session->m_utilityVm->InitializeDrvFs(UserToken) ? LxInitDrvfsMountElevated : LxInitDrvfsMountNonElevated;
4261 }
4262 catch (...)
4263 {
4264 LOG_CAUGHT_EXCEPTION();
4265 return LxInitDrvfsMountNone;
4266 }
4267 }
4268
4269 bool LxssUserSessionImpl::s_TerminateInstance(_Inout_ LxssUserSessionImpl* UserSession, _In_ GUID DistroGuid, _In_ bool CheckForClients)
4270 {
4271 bool success = true;
4272 try
4273 {
4274 success = UserSession->_TerminateInstance(&DistroGuid, CheckForClients);
4275 }
4276 CATCH_LOG()
4277
4278 return success;
4279 }
4280
4281 void LxssUserSessionImpl::s_UpdateInit(_Inout_ LxssUserSessionImpl* UserSession, _In_ const LXSS_DISTRO_CONFIGURATION& Configuration)
4282 try
4283 {
4284 UserSession->_UpdateInit(Configuration);
4285 }
4286 CATCH_LOG()
4287
4288 LRESULT
4289 CALLBACK
4290 LxssUserSessionImpl::s_TimezoneWindowProc(HWND windowHandle, UINT messageCode, WPARAM wParameter, LPARAM lParameter)
4291 {
4292 if (messageCode == WM_TIMECHANGE)
4293 {
4294 auto* session = reinterpret_cast<LxssUserSessionImpl*>(GetWindowLongPtr(windowHandle, GWLP_USERDATA));
4295 if (session != nullptr)
4296 {
4297 session->_TimezoneUpdated();
4298 }
4299 }
4300
4301 return DefWindowProc(windowHandle, messageCode, wParameter, lParameter);
4302 }
4303
4304 void LxssUserSessionImpl::s_ValidateDistroName(_In_ LPCWSTR Name)
4305 {
4306 // Validate the name string. The name must match the regular expression
4307 // and cannot be the reserved legacy name.
4308 std::wstring regex{L"^[a-zA-Z0-9._-]{1,"};
4309 regex += std::to_wstring(LX_INIT_DISTRO_NAME_MAX);
4310 regex += L"}$";
4311 if ((!std::regex_match(Name, std::wregex(regex.c_str()))) || wsl::shared::string::IsEqual(Name, LXSS_LEGACY_INSTALL_NAME, true))
4312 {
4313 THROW_HR_WITH_USER_ERROR(E_INVALIDARG, wsl::shared::Localization::MessageInvalidInstallDistributionName(Name));
4314 }
4315 }
4316
4317 VOID CALLBACK LxssUserSessionImpl::s_VmIdleTerminate(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_opt_ PVOID Context, _Inout_ PTP_TIMER)
4318 {
4319 try
4320 {
4321 const auto userSession = reinterpret_cast<LxssUserSessionImpl*>(Context);
4322 userSession->_VmIdleTerminate();
4323 }
4324 CATCH_LOG()
4325 }
4326
4327 void LxssUserSessionImpl::s_VmTerminated(_Inout_ LxssUserSessionImpl* UserSession, _In_ const GUID& VmId)
4328 try
4329 {
4330 UNREFERENCED_PARAMETER(VmId);
4331
4332 if (UserSession->m_suppressVmTerminationCallback.load())
4333 {
4334 return;
4335 }
4336
4337 UserSession->TerminateByClientId(LXSS_CLIENT_ID_WILDCARD);
4338 return;
4339 }
4340 CATCH_LOG()