master
cpp 2,817 lines 76.3 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 config.c
8
9 Abstract:
10
11 This file contains methods for configuring a running instance.
12
13 --*/
14
15 #include <bitset>
16 #include <sys/mount.h>
17 #include <sys/utsname.h>
18 #include <sys/socket.h>
19 #include <sys/sysmacros.h>
20 #include <pwd.h>
21 #include <future>
22 #include <signal.h>
23 #include <pty.h>
24 #include <lxbusapi.h>
25 #include "common.h"
26 #include "mountutilcpp.h"
27 #include "config.h"
28 #include "util.h"
29 #include "configfile.h"
30 #include "binfmt.h"
31 #include "wslpath.h"
32 #include "wslinfo.h"
33 #include "drvfs.h"
34 #include "timezone.h"
35 #include "message.h"
36 #include "WslDistributionConfig.h"
37 #include "lxfsshares.h"
38 #include "plan9.h"
39
40 #define AUTO_MOUNT_PARENT_MODE 0755
41 #define CGROUP_DEVICE "cgroup"
42 #define CGROUPS_FILE "/proc/cgroups"
43 #define CGROUPS_NO_V1 "cgroup_no_v1="
44 #define DEFAULT_CWD "/"
45 #define DRVFS_MOUNT_OPTIONS (MS_NOATIME)
46 #define DRVFS_SOURCE " :\\"
47 #define DRVFS_TARGET_MODE 0777
48 #define DRVFS_OPTIONS_BUFFER_LENGTH 38
49 #define ETC_DEFAULT_FOLDER ETC_FOLDER "default/"
50 #define HOSTNAME_FILE_PATH ETC_FOLDER "hostname"
51 #define HOSTNAME_FILE_MODE 0644
52 #define HOSTS_FILE_MODE 0644
53 #define HOSTS_FILE_PATH ETC_FOLDER "hosts"
54 #define LANG_ENV "LANG"
55 #define LOCALE_FILE_PATH ETC_DEFAULT_FOLDER "locale"
56 #define LOCALE_CONF_FILE_PATH ETC_FOLDER "locale.conf"
57 #define PATH_ENV "PATH"
58 #define RESOLV_CONF_DIRECTORY_MODE 0755
59 #define RESOLV_CONF_FILE_MODE 0644
60 #define RESOLV_CONF_FILE_NAME "resolv.conf"
61 #define RESOLV_CONF_FILE_PATH ETC_FOLDER RESOLV_CONF_FILE_NAME
62 #define RESOLV_CONF_FOLDER RUN_FOLDER "/resolvconf"
63 #define RESOLV_CONF_SYMLINK_TARGET ".." RESOLV_CONF_FOLDER "/" RESOLV_CONF_FILE_NAME
64 #define RESOLV_CONF_SYMLINK_WSL_MOUNT_SUFFIX SHARED_MOUNT_FOLDER "/" RESOLV_CONF_FILE_NAME
65 #define RUN_FOLDER "/run"
66 #define SHARED_MOUNT_FOLDER "wsl"
67 #define USER_MOUNT_FOLDER "user"
68 #define WINDOWS_LD_CONF_FILE "/etc/ld.so.conf.d/ld.wsl.conf"
69 #define WINDOWS_LD_CONF_FILE_MODE 0644
70
71 #define MOUNTS_FILE "/proc/self/mounts"
72 #define MOUNTS_FIELD_SEPARATOR ' '
73 #define MOUNTS_LINE_SEPARATOR '\n'
74 #define MOUNTS_DEVICE_FIELD 0
75 #define MOUNTS_FSTYPE_FIELD 2
76
77 using wsl::linux::WslDistributionConfig;
78
79 static void ConfigApplyWindowsLibPath(const wsl::linux::WslDistributionConfig& Config);
80
81 static bool CreateLoginSession(const wsl::linux::WslDistributionConfig& Config, const char* Username, uid_t Uid);
82
83 class RemoveMountAndEnvironmentOnScopeExit
84 {
85 public:
86 RemoveMountAndEnvironmentOnScopeExit() = default;
87
88 RemoveMountAndEnvironmentOnScopeExit(const char* EnvironmentName) : m_environmentName(EnvironmentName)
89 {
90 m_mountPath = getenv(m_environmentName);
91 }
92
93 RemoveMountAndEnvironmentOnScopeExit& operator=(const RemoveMountAndEnvironmentOnScopeExit&) = delete;
94 RemoveMountAndEnvironmentOnScopeExit(const RemoveMountAndEnvironmentOnScopeExit&) = delete;
95
96 RemoveMountAndEnvironmentOnScopeExit(RemoveMountAndEnvironmentOnScopeExit&& Other)
97 {
98 *this = std::move(Other);
99 }
100
101 RemoveMountAndEnvironmentOnScopeExit& operator=(RemoveMountAndEnvironmentOnScopeExit&& Other)
102 {
103 m_environmentName = Other.m_environmentName;
104 Other.m_environmentName = nullptr;
105
106 m_mountPath = Other.m_mountPath;
107 Other.m_mountPath = nullptr;
108
109 return *this;
110 }
111
112 ~RemoveMountAndEnvironmentOnScopeExit()
113 {
114 if (m_environmentName != nullptr)
115 {
116 if (unsetenv(m_environmentName) < 0)
117 {
118 LOG_ERROR("unsetenv({}) failed {}", m_environmentName, errno);
119 }
120 }
121
122 if (m_mountPath != nullptr)
123 {
124 if (umount2(m_mountPath, MNT_DETACH) < 0)
125 {
126 LOG_ERROR("umount2({}, MNT_DETACH) failed {}", m_mountPath, errno);
127 return;
128 }
129
130 if (rmdir(m_mountPath) < 0)
131 {
132 LOG_ERROR("rmdir({}) failed {}", m_mountPath, errno);
133 }
134 }
135 }
136
137 operator bool() const
138 {
139 return m_mountPath;
140 }
141
142 const char* MountPath() const
143 {
144 return m_mountPath;
145 }
146
147 bool MoveMount(const char* Target)
148 {
149 if (m_mountPath == nullptr)
150 {
151 return false;
152 }
153
154 if (UtilMount(m_mountPath, Target, nullptr, (MS_MOVE | MS_REC), nullptr) < 0)
155 {
156 return false;
157 }
158
159 if (rmdir(m_mountPath) < 0)
160 {
161 LOG_ERROR("rmdir({}) failed {}", m_mountPath, errno);
162 }
163
164 m_mountPath = nullptr;
165 return true;
166 }
167
168 private:
169 const char* m_environmentName = nullptr;
170 const char* m_mountPath = nullptr;
171 };
172
173 constexpr auto HostsFileFormatString = LX_INIT_AUTO_GENERATED_FILE_HEADER
174 "# [network]\n"
175 "# generateHosts = false\n"
176 "127.0.0.1\tlocalhost\n"
177 "127.0.1.1\t{}.{}\t{}\n"
178 "{}\n"
179 "# The following lines are desirable for IPv6 capable hosts\n"
180 "::1 ip6-localhost ip6-loopback\n"
181 "fe00::0 ip6-localnet\n"
182 "ff00::0 ip6-mcastprefix\n"
183 "ff02::1 ip6-allnodes\n"
184 "ff02::2 ip6-allrouters\n";
185
186 constexpr auto WindowsLibSearchFileHeaderString = LX_INIT_AUTO_GENERATED_FILE_HEADER
187 "# [automount]\n"
188 "# ldconfig = false\n";
189
190 const INIT_STARTUP_ANY LxssStartupCommon[] = {
191 INIT_ANY_DIRECTORY("/sys", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
192 INIT_ANY_MOUNT_DEVICE("/sys", "sysfs", "sysfs", (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_SHARED)),
193 INIT_ANY_DIRECTORY("/proc", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
194 INIT_ANY_MOUNT_DEVICE("/proc", "proc", "proc", (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_SHARED)),
195 INIT_ANY_DIRECTORY("/dev/block", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
196 INIT_ANY_SYMLINK("/dev/fd", "/proc/self/fd"),
197 INIT_ANY_SYMLINK("/dev/stdin", "/proc/self/fd/0"),
198 INIT_ANY_SYMLINK("/dev/stdout", "/proc/self/fd/1"),
199 INIT_ANY_SYMLINK("/dev/stderr", "/proc/self/fd/2"),
200 INIT_ANY_DIRECTORY("/dev/pts", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
201 INIT_ANY_MOUNT_DEVICE_OPTION("/dev/pts", "devpts", "devpts", "gid=5,mode=620", MS_NOATIME | MS_NOSUID | MS_NOEXEC),
202 INIT_ANY_DIRECTORY("/run", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
203 INIT_ANY_MOUNT_OPTION("/run", "tmpfs", "mode=755", (MS_NODEV | MS_STRICTATIME | MS_NOSUID | MS_SHARED)),
204 INIT_ANY_DIRECTORY("/run/lock", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
205 INIT_ANY_MOUNT("/run/lock", "tmpfs", MS_NOATIME | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_SHARED),
206 INIT_ANY_DIRECTORY("/run/shm", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
207 INIT_ANY_MOUNT("/run/shm", "tmpfs", MS_NOATIME | MS_NOSUID | MS_NODEV | MS_SHARED),
208 INIT_ANY_DIRECTORY("/dev/shm", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
209 INIT_ANY_MOUNT_DEVICE("/dev/shm", nullptr, "/run/shm", MS_BIND),
210 INIT_ANY_DIRECTORY("/run/user", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
211 INIT_ANY_MOUNT_OPTION("/run/user", "tmpfs", "mode=755", MS_NOATIME | MS_NOSUID | MS_NOEXEC | MS_NODEV),
212 INIT_ANY_DIRECTORY("/bin", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
213 INIT_ANY_SYMLINK("/bin/" WSLINFO_NAME, "/init"),
214 INIT_ANY_SYMLINK("/bin/" WSLPATH_NAME, "/init"),
215 INIT_ANY_DIRECTORY("/sbin", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
216 INIT_ANY_SYMLINK("/sbin/" MOUNT_DRVFS_NAME, "/init"),
217 INIT_ANY_MOUNT_DEVICE(BINFMT_MISC_MOUNT_TARGET, "binfmt_misc", "binfmt_misc", MS_RELATIME),
218 INIT_ANY_DIRECTORY("/tmp", ROOT_UID, ROOT_GID, S_IFDIR | S_ISVTX | 0777)};
219
220 const INIT_STARTUP_ANY LxssStartupLoggingVmMode[] = {
221 INIT_ANY_DIRECTORY("/dev", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
222 INIT_ANY_MOUNT_OPTION("/dev", "devtmpfs", "mode=755", (MS_NOSUID | MS_RELATIME | MS_SHARED))};
223
224 const INIT_STARTUP_ANY LxssStartupLoggingWsl[] = {
225 INIT_ANY_DIRECTORY("/dev", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
226 INIT_ANY_MOUNT_OPTION("/dev", "tmpfs", "mode=755", MS_NOATIME | MS_SHARED),
227 INIT_ANY_NODE("/dev/kmsg", ROOT_UID, ROOT_GID, S_IFCHR | 0644, INIT_DEV_LOG_KMSG_MAJOR_NUMBER, INIT_DEV_LOG_KMSG_MINOR_NUMBER)};
228
229 const INIT_STARTUP_ANY LxssStartupWsl[] = {
230 INIT_ANY_NODE("/dev/ptmx", ROOT_UID, TTY_GID, S_IFCHR | 0666, INIT_DEV_PTM_MAJOR_NUMBER, INIT_DEV_PTM_MINOR_NUMBER),
231 INIT_ANY_NODE("/dev/random", ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_RANDOM_MAJOR_NUMBER, INIT_DEV_RANDOM_MINOR_NUMBER),
232 INIT_ANY_NODE("/dev/urandom", ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_URANDOM_MAJOR_NUMBER, INIT_DEV_URANDOM_MINOR_NUMBER),
233 INIT_ANY_NODE("/dev/null", ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_NULL_MAJOR_NUMBER, INIT_DEV_NULL_MINOR_NUMBER),
234 INIT_ANY_NODE("/dev/tty", ROOT_UID, TTY_GID, S_IFCHR | 0666, INIT_DEV_TTYCT_MAJOR_NUMBER, INIT_DEV_TTYCT_MINOR_NUMBER),
235 INIT_ANY_NODE("/dev/tty0", ROOT_UID, TTY_GID, S_IFCHR | 0620, INIT_DEV_TTY_MAJOR_NUMBER, INIT_DEV_TTY0_MINOR_NUMBER),
236 INIT_ANY_NODE("/dev/zero", ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_ZERO_MAJOR_NUMBER, INIT_DEV_ZERO_MINOR_NUMBER),
237 INIT_ANY_NODE(LXBUS_DEVICE_NAME, ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_LXBUS_MAJOR_NUMBER, INIT_DEV_LXBUS_MINOR_NUMBER)};
238
239 //
240 // Mount namespace file descriptors for VM mode.
241 //
242
243 int g_ElevatedMountNamespace = -1;
244 int g_NonElevatedMountNamespace = -1;
245
246 //
247 // Boot state bookkeeping.
248 //
249
250 extern wsl::shared::SocketChannel g_plan9ControlChannel;
251
252 void ConfigAppendNtPath(EnvironmentBlock& Environment, char* NtPath)
253
254 /*++
255
256 Routine Description:
257
258 This routine updates the $PATH variable of the provided environment block.
259
260 Arguments:
261
262 Environment - Supplies the environment block to update.
263
264 NtPath - Supplies a semicolon-separated list of NT paths to translate and
265 append to the $PATH variable. If no $PATH variable exists, one is
266 created.
267
268 Return Value:
269
270 None.
271
272 --*/
273
274 try
275 {
276 auto TranslatedPath = UtilTranslatePathList(NtPath, true);
277 if (!TranslatedPath.has_value())
278 {
279 return;
280 }
281
282 ConfigAppendToPath(Environment, TranslatedPath.value());
283 return;
284 }
285 CATCH_LOG()
286
287 void ConfigAppendToPath(EnvironmentBlock& Environment, std::string_view PathElement)
288
289 /*++
290
291 Routine Description:
292
293 This routine adds the specified path element to the $PATH variable of the
294 supplied environment block.
295
296 Arguments:
297
298 Environment - Supplies the environment block to update.
299
300 PathElement - Supplies a path element to add to the $PATH variable. If no
301 $PATH variable exists, one is created.
302
303 Return Value:
304
305 None.
306
307 --*/
308
309 try
310 {
311 //
312 // If no PATH variable is present, create a new variable. If a PATH is
313 // present, add the path element onto the end of the existing value.
314 //
315
316 auto Path = Environment.GetVariable(PATH_ENV);
317 if (Path.empty())
318 {
319 Environment.AddVariable(PATH_ENV, PathElement);
320 }
321 else
322 {
323 std::string NewPath{Path};
324 if (NewPath.back() != ':')
325 {
326 NewPath += ':';
327 }
328
329 NewPath += PathElement;
330 Environment.AddVariable(PATH_ENV, NewPath);
331 }
332
333 return;
334 }
335 CATCH_LOG()
336
337 void ConfigHandleInteropMessage(
338 wsl::shared::Transaction& Transaction,
339 wsl::shared::SocketChannel& InteropChannel,
340 bool Elevated,
341 gsl::span<gsl::byte> Message,
342 const MESSAGE_HEADER* Header,
343 const wsl::linux::WslDistributionConfig& Config)
344
345 /*++
346
347 Routine Description:
348
349 This routine handles a message received from a Linux client using init's
350 interop socket.
351
352 Arguments:
353
354 Transaction - Supplies transaction used to send responses.
355
356 InteropChannel - Supplies a channel to the host to be used for create
357 process requests.
358
359 Elevated - Supplies a boolean specifying if the elevated DrvFs share should be used.
360
361 Message - Supplies the message buffer.
362
363 Header- Supplies the message Header.
364
365 Return Value:
366
367 None.
368
369 --*/
370
371 try
372 {
373 switch (Header->MessageType)
374 {
375 case LxInitMessageCreateProcessUtilityVm:
376 if (InteropChannel.Socket() > 0)
377 {
378 InteropChannel.SendMessage<LX_INIT_CREATE_NT_PROCESS_UTILITY_VM>(Message);
379 }
380
381 break;
382
383 case LxInitMessageQueryDrvfsElevated:
384 {
385 Transaction.SendResultMessage<bool>(Elevated);
386 break;
387 }
388
389 case LxInitMessageQueryEnvironmentVariable:
390 {
391 auto* Query = gslhelpers::try_get_struct<LX_INIT_QUERY_ENVIRONMENT_VARIABLE>(Message);
392 if (!Query)
393 {
394 LOG_ERROR("Unexpected MessageSize {}", Message.size());
395 return;
396 }
397
398 auto Value = UtilGetEnvironmentVariable(wsl::shared::string::FromMessageBuffer<LX_INIT_QUERY_ENVIRONMENT_VARIABLE>(Message));
399 wsl::shared::MessageWriter<LX_INIT_QUERY_ENVIRONMENT_VARIABLE> Response(LxInitMessageQueryEnvironmentVariable);
400 Response.WriteString(Value);
401 Transaction.Send<LX_INIT_QUERY_ENVIRONMENT_VARIABLE>(Response.Span());
402 }
403
404 break;
405
406 case LxInitMessageQueryFeatureFlags:
407 {
408 assert(Config.FeatureFlags.has_value());
409 Transaction.SendResultMessage<int32_t>(Config.FeatureFlags.value());
410 break;
411 }
412
413 case LxInitMessageCreateLoginSession:
414 {
415 auto* CreateSession = gslhelpers::try_get_struct<LX_INIT_CREATE_LOGIN_SESSION>(Message);
416 if (!CreateSession)
417 {
418 LOG_ERROR("Unexpected MessageSize {}", Message.size());
419 return;
420 }
421
422 bool success = false;
423 auto sendResponse = wil::scope_exit([&]() { Transaction.SendResultMessage<bool>(success); });
424
425 if (!Config.BootInit || Config.InitPid.value_or(0) != getpid())
426 {
427 LOG_ERROR("Unexpected LxInitMessageCreateLoginSession message");
428 }
429 else
430 {
431 success = CreateLoginSession(
432 Config, wsl::shared::string::FromMessageBuffer<LX_INIT_CREATE_LOGIN_SESSION>(Message), CreateSession->Uid);
433 }
434
435 break;
436 }
437
438 case LxInitMessageQueryNetworkingMode:
439 assert(Config.NetworkingMode.has_value());
440 Transaction.SendResultMessage<uint8_t>(static_cast<uint8_t>(Config.NetworkingMode.value()));
441 break;
442
443 case LxInitMessageQueryVmId:
444 {
445 wsl::shared::MessageWriter<LX_INIT_QUERY_VM_ID> Response(LxInitMessageQueryVmId);
446 if (Config.VmId.has_value())
447 {
448 Response.WriteString(Config.VmId.value());
449 }
450
451 Transaction.Send<LX_INIT_QUERY_VM_ID>(Response.Span());
452 break;
453 }
454
455 default:
456 LOG_ERROR("unexpected message {}", Header->MessageType);
457 break;
458 }
459 }
460 CATCH_LOG()
461
462 wsl::linux::WslDistributionConfig ConfigInitializeCommon(struct sigaction* SavedSignalActions)
463
464 /*++
465
466 Routine Description:
467
468 This routine sets up common devices and mounts.
469
470 Arguments:
471
472 SavedSignalActions - Supplies an array to save default signal actions.
473
474 Return Value:
475
476 0 on success, -1 on failure.
477
478 --*/
479
480 {
481 wil::unique_fd DevNullFd;
482 unsigned int Index;
483
484 //
485 // Set the umask to 0 to ensure that devices and files that init creates
486 // have the correct mode.
487 //
488
489 umask(0);
490
491 //
492 // Perform initialization required for logging to kmsg.
493 //
494
495 if (!UtilIsUtilityVm())
496 {
497 for (Index = 0; Index < COUNT_OF(LxssStartupLoggingWsl); Index += 1)
498 {
499 THROW_LAST_ERROR_IF(ConfigInitializeEntry(&LxssStartupLoggingWsl[Index]) < 0);
500 }
501 }
502 else
503 {
504 for (Index = 0; Index < COUNT_OF(LxssStartupLoggingVmMode); Index += 1)
505 {
506 THROW_LAST_ERROR_IF(ConfigInitializeEntry(&LxssStartupLoggingVmMode[Index]) < 0);
507 }
508 }
509
510 //
511 // Open /dev/kmsg for logging.
512 //
513
514 THROW_LAST_ERROR_IF(InitializeLogging(true) < 0);
515
516 //
517 // Ignore all signals except SIGHUP and signals that cannot be ignored.
518 //
519 // N.B. Ignoring SIGCHLD automatically reaps zombie processes.
520 //
521 // N.B. Child processes reset signals to default before calling execv.
522 //
523
524 THROW_LAST_ERROR_IF(UtilSaveSignalHandlers(SavedSignalActions) < 0);
525
526 THROW_LAST_ERROR_IF(UtilSetSignalHandlers(SavedSignalActions, true) < 0);
527
528 //
529 // Load the configuration file.
530 //
531
532 wsl::linux::WslDistributionConfig Config{CONFIG_FILE};
533
534 if (getenv(LX_WSL2_SYSTEM_DISTRO_SHARE_ENV) != nullptr)
535 {
536 Config.GuiAppsEnabled = true;
537 }
538
539 //
540 // Initialize the static entries.
541 //
542
543 for (Index = 0; Index < COUNT_OF(LxssStartupCommon); Index += 1)
544 {
545 THROW_LAST_ERROR_IF(ConfigInitializeEntry(&LxssStartupCommon[Index]) < 0);
546 }
547
548 //
549 // Initialize WSL1 and WSL2 specific environment.
550 //
551
552 if (!UtilIsUtilityVm())
553 {
554 THROW_LAST_ERROR_IF(ConfigInitializeWsl() < 0);
555 }
556
557 //
558 // Open /dev/null for the stdin and stdout in case libraries try to use
559 // them (but keep stderr open for kmsg logging).
560 //
561
562 DevNullFd = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR));
563 THROW_LAST_ERROR_IF(!DevNullFd);
564
565 for (const auto& Fd : {STDIN_FILENO, STDOUT_FILENO})
566 {
567 THROW_LAST_ERROR_IF(dup2(DevNullFd.get(), Fd) < 0);
568 }
569
570 //
571 // Initialize cgroups based on what the kernel supports.
572 //
573
574 ConfigInitializeCgroups(Config);
575
576 //
577 // Attempt to register the NT interop binfmt extension.
578 //
579 // N.B. Registration for VM mode is done by mini_init.
580 //
581
582 if ((!UtilIsUtilityVm()) && (Config.InteropEnabled))
583 {
584 ConfigRegisterBinfmtInterpreter();
585 }
586
587 //
588 // Ensure the target for automounts exists.
589 //
590
591 if ((Config.AutoMount) || ((UtilIsUtilityVm())))
592 {
593 UtilMkdirPath(Config.DrvFsPrefix.c_str(), AUTO_MOUNT_PARENT_MODE, false);
594 }
595
596 //
597 // Initialization successful.
598 //
599
600 return Config;
601 }
602
603 void ConfigInitializeX11(const wsl::linux::WslDistributionConfig& Config)
604 try
605 {
606 auto socketPath = "/tmp/" X11_SOCKET_NAME;
607 THROW_LAST_ERROR_IF(UtilMkdir(socketPath, 0775) < 0);
608
609 std::string source{Config.DrvFsPrefix};
610 source += WSLG_SHARED_FOLDER;
611 source += "/" X11_SOCKET_NAME;
612 THROW_LAST_ERROR_IF(mount(source.c_str(), socketPath, NULL, (MS_BIND | MS_REC), NULL) < 0);
613
614 // The .X11-unix folder is mounted read-only so the socket file can't be removed.
615 // It's left writable in the system distro since wslg is supposed to write to that folder to create it.
616 if (WI_IsFlagClear(Config.FeatureFlags.value(), LxInitFeatureSystemDistro))
617 {
618 THROW_LAST_ERROR_IF(mount("none", socketPath, NULL, (MS_RDONLY | MS_REMOUNT | MS_BIND), NULL) < 0);
619
620 // Override the distro-provided x11.conf so systemd-tmpfiles does not try to modify the read-only mount.
621 THROW_LAST_ERROR_IF(UtilMkdirPath("/run/tmpfiles.d", 0755) < 0);
622 const std::string tmpFilesConfig =
623 "# Note: This file is generated by WSL to prevent systemd-tmpfiles from modifying /tmp/.X11-unix.\n";
624
625 THROW_LAST_ERROR_IF(WriteToFile("/run/tmpfiles.d/x11.conf", tmpFilesConfig.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0);
626 }
627 }
628 CATCH_LOG()
629
630 int ConfigInitializeInstance(const std::function<void(const gsl::span<gsl::byte>&)>& SendResponse, gsl::span<gsl::byte> Buffer, wsl::linux::WslDistributionConfig& Config)
631
632 /*++
633
634 Routine Description:
635
636 This routine initializes the instance's externally controlled state, which
637 is received from the service.
638
639 N.B. When setting these values errors are treated as non-fatal to account
640 for unexpected distro state.
641
642 Arguments:
643
644 SendResponse - Supplies a function to send the response message.
645
646 Buffer - Supplies the message buffer.
647
648 Return Value:
649
650 0 on success, -1 on failure.
651
652 --*/
653
654 try
655 {
656 //
657 // Validate input parameters.
658 //
659
660 const auto* Message = gslhelpers::try_get_struct<const LX_INIT_CONFIGURATION_INFORMATION>(Buffer);
661 if (!Message)
662 {
663 FATAL_ERROR("Unexpected configuration size {}", Buffer.size());
664 }
665
666 //
667 // Set the host name and domain name buffers.
668 //
669
670 std::string Hostname = wsl::shared::string::FromSpan(Buffer, Message->HostnameOffset);
671 auto* Domainname = wsl::shared::string::FromSpan(Buffer, Message->DomainnameOffset);
672 auto* WindowsHosts = wsl::shared::string::FromSpan(Buffer, Message->WindowsHostsOffset);
673 auto* DistributionName = wsl::shared::string::FromSpan(Buffer, Message->DistributionNameOffset);
674 auto* Plan9SocketPath = wsl::shared::string::FromSpan(Buffer, Message->Plan9SocketOffset);
675 auto* Timezone = wsl::shared::string::FromSpan(Buffer, Message->TimezoneOffset);
676 bool Elevated = Message->DrvfsMount == LxInitDrvfsMountElevated;
677
678 const std::string ThreadName = std::format("{}({})", (Config.BootInit ? "init-systemd" : "init"), DistributionName);
679 UtilSetThreadName(ThreadName.c_str());
680
681 //
682 // Store feature flags for future use.
683 //
684 // N.B. This is also stored in an environment variable so that mount.drvfs, when launched
685 // through fstab mounting below, can use that. This is needed because mount.drvfs won't
686 // be able to connect to init during this call. This environment variable is not present
687 // for user-launched processes.
688 //
689
690 Config.FeatureFlags = Message->FeatureFlags;
691 UtilSetFeatureFlags(Config.FeatureFlags.value());
692
693 //
694 // Determine the default UID which can be specified in /etc/wsl.conf.
695 //
696
697 uid_t DefaultUid = Message->DrvFsDefaultOwner;
698 if (Config.DefaultUser.has_value())
699 {
700 passwd* PasswordEntry = getpwnam(Config.DefaultUser->c_str());
701 if (PasswordEntry == nullptr)
702 {
703 LOG_ERROR("getpwnam({}) failed {}", Config.DefaultUser->c_str(), errno);
704 }
705 else
706 {
707 DefaultUid = PasswordEntry->pw_uid;
708 }
709 }
710
711 //
712 // Process the /etc/fstab file.
713 //
714 // N.B. This must happen before mounting DrvFs volumes because the user may
715 // have specified DrvFs mounts in /etc/fstab and they should overwrite defaults.
716 //
717
718 if (Config.MountFsTab)
719 {
720 ConfigMountFsTab(Elevated);
721 }
722
723 //
724 // Perform additional WSL2-specific mounts.
725 //
726
727 if (UtilIsUtilityVm())
728 {
729 if (ConfigInitializeVmMode(Elevated, Config) < 0)
730 {
731 FATAL_ERROR("ConfigInitializeVmMode");
732 }
733 }
734
735 if (Config.AutoMount && (Message->DrvfsMount != LxInitDrvfsMountNone))
736 {
737 ConfigMountDrvFsVolumes(Message->DrvFsVolumesBitmap, DefaultUid, Elevated, Config);
738 }
739
740 //
741 // If a hostname was specified in /etc/wsl.conf, use it.
742 //
743
744 if (Config.HostName.has_value())
745 {
746 Hostname = Config.HostName.value();
747 LOG_WARNING("hostname set to {} in {}", Hostname.c_str(), CONFIG_FILE);
748 }
749
750 //
751 // Sanitize the hostname.
752 //
753 // N.B. If systemd is enabled, systemd-hostnamed will cleanup the
754 // hostname, which can lead to a disconnect if that doesn't match
755 // what we write in /etc/hostname & /etc/hosts, so to hostname needs
756 // to be cleaned up before being passed to systemd.
757 //
758 // N.B. While the Windows UI doesn't let the user set an invalid hostname
759 // (from systemd-hostnamed's perspective), it's possible to override that
760 // via Rename-Computer.
761
762 Hostname = wsl::shared::string::CleanHostname(Hostname);
763
764 //
765 // Update the host and domain name.
766 //
767
768 if (sethostname(Hostname.c_str(), Hostname.size()) < 0)
769 {
770 LOG_ERROR("sethostname({}) failed {}", Hostname.c_str(), errno);
771 Hostname = wsl::shared::string::c_defaultHostName;
772 if (sethostname(Hostname.c_str(), Hostname.size()) < 0)
773 {
774 LOG_ERROR("sethostname({}) failed {}", Hostname.c_str(), errno);
775 }
776 }
777
778 if (setenv(NAME_ENV, Hostname.c_str(), 1) < 0)
779 {
780 LOG_ERROR("setenv({}, {}) failed {}", NAME_ENV, Hostname.c_str(), errno);
781 }
782
783 //
784 // Update the domain name.
785 //
786
787 if (setdomainname(Domainname, strlen(Domainname)) < 0)
788 {
789 LOG_ERROR("setdomainname({}) failed {}", Domainname, errno);
790 }
791
792 //
793 // Generate and write /etc/hostname.
794 //
795
796 wil::unique_fd HostnameFd{TEMP_FAILURE_RETRY(creat(HOSTNAME_FILE_PATH, HOSTNAME_FILE_MODE))};
797 if (!HostnameFd)
798 {
799 LOG_ERROR("creat {} failed: {}", HOSTNAME_FILE_PATH, errno);
800 }
801 else
802 {
803 try
804 {
805 auto FileContents = std::format("{}\n", Hostname);
806 if (UtilWriteStringView(HostnameFd.get(), FileContents) < 0)
807 {
808 LOG_ERROR("write failed {}", errno);
809 }
810 }
811 CATCH_LOG()
812 }
813
814 HostnameFd.reset();
815
816 //
817 // Generate and write /etc/hosts.
818 //
819
820 if (Config.GenerateHosts)
821 {
822 wil::unique_fd HostsFd{TEMP_FAILURE_RETRY(creat(HOSTS_FILE_PATH, HOSTS_FILE_MODE))};
823 if (!HostsFd)
824 {
825 LOG_ERROR("creat {} failed {}", HOSTS_FILE_PATH, errno);
826 }
827 else
828 {
829 try
830 {
831 auto FileContents = std::format(HostsFileFormatString, Hostname.c_str(), Domainname, Hostname.c_str(), WindowsHosts);
832 if (UtilWriteStringView(HostsFd.get(), FileContents) < 0)
833 {
834 LOG_ERROR("write failed {}", errno);
835 }
836 }
837 CATCH_LOG()
838 }
839 }
840 else
841 {
842 LOG_WARNING("{} updating disabled in {}", HOSTS_FILE_PATH, CONFIG_FILE);
843 }
844
845 //
846 // Store the distribution name.
847 //
848
849 if (setenv(WSL_DISTRO_NAME_ENV, DistributionName, 1) < 0)
850 {
851 LOG_ERROR("setenv({}, {}, 1) failed {}", WSL_DISTRO_NAME_ENV, DistributionName, errno);
852 }
853
854 //
855 // Run the Plan 9 server. On WSL1 this requires a DrvFs mount for the socket
856 // file, so either fstab or automount must be enabled to have a chance for
857 // the mount to be available. WSL2 serves over an hvsocket and has no such
858 // dependency.
859 //
860 // N.B. Failure to start the server is non-fatal.
861 //
862 unsigned int Plan9Port = LX_INIT_UTILITY_VM_INVALID_PORT;
863 if ((WI_IsFlagClear(Config.FeatureFlags.value(), LxInitFeatureDisable9pServer)) && (Config.Plan9Enabled) &&
864 (UtilIsUtilityVm() || Config.AutoMount || Config.MountFsTab))
865 {
866 std::tie(Plan9Port, Config.Plan9ControlChannel) = StartPlan9Server(Plan9SocketPath, Config);
867 }
868
869 //
870 // If the root filesystem is compressed, log a warning.
871 //
872
873 if (WI_IsFlagSet(Config.FeatureFlags.value(), LxInitFeatureRootfsCompressed))
874 {
875 LOG_WARNING("{} root file system is compressed, performance may be severely impacted.", DistributionName);
876 }
877
878 //
879 // Update the timezone.
880 //
881
882 UpdateTimezone(Timezone, Config);
883
884 if (Config.BootInit)
885 {
886 try
887 {
888 // Create the /run/user bind mount.
889 // This mount is required because systemd will mount a tmpfs on each /run/user/<uid> folder
890 // so /run/user need to be in the global mount namespace so both elevated and non elevated processes see it.
891 const auto UserMountTarget = Config.DrvFsPrefix + WSLG_SHARED_FOLDER "/run/user";
892 THROW_LAST_ERROR_IF(UtilMkdirPath(UserMountTarget.c_str(), 0755) < 0);
893 THROW_LAST_ERROR_IF(UtilMount(UserMountTarget.c_str(), RUN_FOLDER "/" USER_MOUNT_FOLDER, nullptr, MS_BIND, nullptr) < 0)
894 }
895 CATCH_LOG();
896 }
897
898 //
899 // Create a listening hvsocket for interop if the feature is enabled.
900 //
901
902 wil::unique_fd ListenSocket{};
903 sockaddr_vm SocketAddress{};
904 if (UtilIsUtilityVm() && Config.InteropEnabled)
905 {
906 ListenSocket = UtilListenVsockAnyPort(&SocketAddress, 1);
907 }
908
909 //
910 // Send the config response to the service.
911 //
912
913 wsl::shared::MessageWriter<LX_INIT_CONFIGURATION_INFORMATION_RESPONSE> Response(LxInitMessageInitializeResponse);
914 Response->Plan9Port = Plan9Port;
915 Response->DefaultUid = DefaultUid;
916 Response->InteropPort = ListenSocket ? SocketAddress.svm_port : LX_INIT_UTILITY_VM_INVALID_PORT;
917 Response->SystemdEnabled = Config.BootInit;
918
919 struct stat PidNamespaceInfo = {};
920 THROW_LAST_ERROR_IF(stat("/proc/self/ns/pid", &PidNamespaceInfo));
921 Response->PidNamespace = PidNamespaceInfo.st_ino;
922 static_assert(sizeof(Response->PidNamespace) == sizeof(PidNamespaceInfo.st_ino));
923
924 auto [Flavor, Version] = UtilReadFlavorAndVersion("/etc/os-release");
925 if (Flavor.has_value())
926 {
927 Response.WriteString(Response->FlavorIndex, Flavor->c_str());
928 }
929
930 if (Version.has_value())
931 {
932 Response.WriteString(Response->VersionIndex, Version->c_str());
933 }
934
935 SendResponse(Response.Span());
936
937 //
938 // Accept the interop connection.
939 //
940
941 wsl::shared::SocketChannel InteropChannel;
942 if (ListenSocket)
943 {
944 InteropChannel = {UtilAcceptVsock(ListenSocket.get(), SocketAddress, INTEROP_TIMEOUT_MS), "Interop"};
945 }
946
947 //
948 // Create a thread to handle interop requests.
949 //
950
951 InteropServer InteropServer;
952 if (InteropServer.Create() < 0)
953 {
954 FATAL_ERROR("Could not create init interop server");
955 }
956
957 //
958 // If init is not running as pid 1, create a symlink to the interop server that was created.
959 //
960
961 if (Config.InitPid.has_value())
962 {
963 try
964 {
965 std::string LinkPath = std::format(WSL_INTEROP_SOCKET_FORMAT, WSL_TEMP_FOLDER, 1, WSL_INTEROP_SOCKET);
966 if (symlink(InteropServer.Path(), LinkPath.c_str()) < 0)
967 {
968 LOG_ERROR("symlink({}, {}) failed {}", InteropServer.Path(), LinkPath.c_str(), errno);
969 }
970 }
971 CATCH_LOG()
972 }
973
974 UtilCreateWorkerThread(
975 "Interop", [InteropChannel = std::move(InteropChannel), InteropServer = std::move(InteropServer), Elevated, &Config]() mutable {
976 std::vector<gsl::byte> Buffer;
977 for (;;)
978 {
979 wsl::shared::SocketChannel ClientChannel{InteropServer.Accept(), "InteropServer"};
980 if (ClientChannel.Socket() < 0)
981 {
982 continue;
983 }
984
985 auto transaction = ClientChannel.ReceiveTransaction();
986 auto [Message, Span] = transaction.ReceiveOrClosed<MESSAGE_HEADER>();
987 if (Message == nullptr)
988 {
989 continue;
990 }
991
992 ConfigHandleInteropMessage(transaction, InteropChannel, Elevated, Span, Message, Config);
993 }
994 });
995
996 //
997 // If there was a command specified in /etc/wsl.conf, run it in a child process.
998 //
999
1000 if (Config.BootCommand.has_value())
1001 {
1002 UtilCreateChildProcess(
1003 "BootCommand",
1004 [Command = Config.BootCommand.value(), SavedSignals = g_SavedSignalActions]() {
1005 //
1006 // Restore default signal dispositions for the child process.
1007 //
1008
1009 THROW_LAST_ERROR_IF(UtilSetSignalHandlers(SavedSignals, false) < 0);
1010 THROW_LAST_ERROR_IF(UtilRestoreBlockedSignals() < 0);
1011
1012 execl("/bin/sh", "sh", "-c", Command.c_str(), nullptr);
1013 LOG_ERROR("execl() failed, {}", errno);
1014 },
1015 {},
1016 Config.CgroupPath);
1017 }
1018
1019 return 0;
1020 }
1021 CATCH_RETURN_ERRNO()
1022
1023 int ConfigInitializeVmMode(bool Elevated, wsl::linux::WslDistributionConfig& Config)
1024
1025 /*++
1026
1027 Routine Description:
1028
1029 This routine sets up VM Mode specific devices and mounts.
1030
1031 Arguments:
1032
1033 None.
1034
1035 Return Value:
1036
1037 0 on success, -1 on failure.
1038
1039 --*/
1040
1041 {
1042 //
1043 // Move temporary mounts created by mini_init to their final locations.
1044 //
1045 // N.B. Failure to mount these is not fatal.
1046 //
1047
1048 for (auto& share : g_gpuShares)
1049 {
1050 try
1051 {
1052 auto variable = LX_WSL2_GPU_SHARE_ENV + std::string{share.Name};
1053 auto tempMount = RemoveMountAndEnvironmentOnScopeExit(variable.c_str());
1054 if (tempMount && Config.GpuEnabled)
1055 {
1056 tempMount.MoveMount(share.MountPoint);
1057 }
1058 }
1059 CATCH_LOG()
1060 }
1061
1062 if (Config.GpuEnabled)
1063 {
1064 ConfigApplyWindowsLibPath(Config);
1065 }
1066
1067 try
1068 {
1069 auto tempMount = RemoveMountAndEnvironmentOnScopeExit(LX_WSL2_CROSS_DISTRO_ENV);
1070 if (tempMount)
1071 {
1072 const auto target = Config.DrvFsPrefix + SHARED_MOUNT_FOLDER;
1073 if (tempMount.MoveMount(target.c_str()))
1074 {
1075 ConfigCreateResolvConfSymlink(Config);
1076 }
1077 }
1078 }
1079 CATCH_LOG()
1080
1081 try
1082 {
1083 auto tempMount = RemoveMountAndEnvironmentOnScopeExit(LX_WSL2_SYSTEM_DISTRO_SHARE_ENV);
1084 if (tempMount)
1085 {
1086 const auto target = Config.DrvFsPrefix + WSLG_SHARED_FOLDER;
1087 if (!tempMount.MoveMount(target.c_str()))
1088 {
1089 Config.GuiAppsEnabled = false;
1090 }
1091 else
1092 {
1093 Config.GuiAppsEnabled = true;
1094
1095 //
1096 // Create a bind mount of the shared WSLg path at the expected location for x11 clients.
1097 //
1098 // N.B. If using distro init, this is done after waiting for the distro init to finish booting
1099 // since that will typically clear the /tmp directory.
1100 //
1101
1102 ConfigInitializeX11(Config);
1103
1104 //
1105 // Add environment variables to support GUI applications.
1106 //
1107
1108 for (const auto& var : ConfigGetWslgEnvironmentVariables(Config))
1109 {
1110 if (setenv(var.first.c_str(), var.second.c_str(), 1) < 0)
1111 {
1112 LOG_ERROR("setenv({}, {}) failed {}", var.first.c_str(), var.second.c_str(), errno);
1113 }
1114 }
1115 }
1116 }
1117 }
1118 CATCH_LOG()
1119
1120 try
1121 {
1122 auto tempMount = RemoveMountAndEnvironmentOnScopeExit(LX_WSL2_KERNEL_MODULES_MOUNT_ENV);
1123 if (tempMount)
1124 {
1125 auto target = getenv(LX_WSL2_KERNEL_MODULES_PATH_ENV);
1126 if (target)
1127 {
1128 unsetenv(LX_WSL2_KERNEL_MODULES_PATH_ENV);
1129 tempMount.MoveMount(target);
1130 }
1131 }
1132 }
1133 CATCH_LOG()
1134
1135 //
1136 // Change the permission of some devtmpfs devices to be more permissive.
1137 //
1138 // N.B. These devices may not be present with a custom kernel config.
1139 //
1140
1141 for (const auto* Device : {"/dev/fuse", "/dev/net/tun"})
1142 {
1143 if ((chmod(Device, 0666) < 0) && (errno != ENOENT))
1144 {
1145 LOG_ERROR("chmod({}, 0666) failed {}", Device, errno);
1146 return -1;
1147 }
1148 }
1149
1150 //
1151 // Open a file descriptor to the current mount namespace.
1152 //
1153
1154 wil::unique_fd Namespace{UtilOpenMountNamespace()};
1155 if (!Namespace)
1156 {
1157 return -1;
1158 }
1159
1160 if (Elevated)
1161 {
1162 g_ElevatedMountNamespace = Namespace.release();
1163 }
1164 else
1165 {
1166 g_NonElevatedMountNamespace = Namespace.release();
1167 }
1168
1169 return 0;
1170 }
1171
1172 int ConfigInitializeWsl(void)
1173
1174 /*++
1175
1176 Routine Description:
1177
1178 This routine sets up WSL-specific devices and mounts.
1179
1180 Arguments:
1181
1182 None.
1183
1184 Return Value:
1185
1186 0 on success, -1 on failure.
1187
1188 --*/
1189
1190 {
1191 unsigned int Index;
1192 int Result;
1193
1194 for (Index = 0; Index < COUNT_OF(LxssStartupWsl); Index += 1)
1195 {
1196 Result = ConfigInitializeEntry(&LxssStartupWsl[Index]);
1197 if (Result < 0)
1198 {
1199 goto InitializeWslExit;
1200 }
1201 }
1202
1203 //
1204 // Initialize the serial device entries.
1205 //
1206
1207 for (Index = INIT_DEV_TTY_MINOR_NUMBER_FIRST_SERIAL; Index < INIT_DEV_TTY_MINOR_NUMBER_MAX_SERIAL; Index += 1)
1208 {
1209 auto TtySPath = std::format(INIT_DEV_TTY_SERIAL_FORMAT, (Index - INIT_DEV_TTY_MINOR_NUMBER_FIRST_SERIAL));
1210
1211 Result = mknod(TtySPath.c_str(), INIT_DEV_TTY_SERIAL_MODE, makedev(INIT_DEV_TTY_MAJOR_NUMBER, Index));
1212 if (Result < 0)
1213 {
1214 FATAL_ERROR("mknod({}) failed {}", TtySPath, errno);
1215 }
1216
1217 Result = chown(TtySPath.c_str(), INIT_DEV_TTY_SERIAL_UID, INIT_DEV_TTY_SERIAL_GID);
1218 if (Result < 0)
1219 {
1220 FATAL_ERROR("chown({}) failed {}", TtySPath, errno);
1221 }
1222 }
1223
1224 Result = 0;
1225
1226 InitializeWslExit:
1227 return Result;
1228 }
1229
1230 int ConfigInitializeEntry(PCINIT_STARTUP_ANY AnyEntry)
1231
1232 /*++
1233
1234 Routine Description:
1235
1236 This routine creates an init startup entry.
1237
1238 Arguments:
1239
1240 AnyEntry - Supplies the startup entry to create.
1241
1242 Return Value:
1243
1244 0 on success, -1 on failure.
1245
1246 --*/
1247
1248 {
1249 PCINIT_STARTUP_DIRECTORY Directory;
1250 PCINIT_STARTUP_FILE File;
1251 PCINIT_STARTUP_MOUNT Mount;
1252 PCINIT_STARTUP_NODE Node;
1253 int Result;
1254 PCINIT_STARTUP_SYMBOLIC_LINK Symlink;
1255
1256 switch (AnyEntry->Type)
1257 {
1258 case InitStartupTypeDirectory:
1259 Directory = &AnyEntry->u.Directory;
1260 Result = UtilMkdir(Directory->Path, Directory->Security.Mode);
1261 if (Result < 0)
1262 {
1263 FATAL_ERROR("Failed to create {} {}", Directory->Path, errno);
1264 }
1265
1266 if (errno != EEXIST)
1267 {
1268 Result = chown(Directory->Path, Directory->Security.Uid, Directory->Security.Gid);
1269 if (Result < 0)
1270 {
1271 FATAL_ERROR("Failed to chown {} {}", Directory->Path, errno);
1272 }
1273 }
1274
1275 break;
1276
1277 case InitStartupTypeMount:
1278 Mount = &AnyEntry->u.Mount;
1279 Result = mount(Mount->DeviceName, Mount->MountLocation, Mount->FileSystemType, Mount->Flags & ~MS_SHARED, Mount->MountOptions);
1280 if (Result < 0 && !Mount->IgnoreFailure)
1281 {
1282 FATAL_ERROR("Failed to mount {} at {} as {} {}", Mount->DeviceName, Mount->MountLocation, Mount->FileSystemType, errno);
1283 }
1284
1285 // N.B. The shared flag must be done in a followup mount() call
1286 if (WI_IsFlagSet(Mount->Flags, MS_SHARED))
1287 {
1288 Result = mount(nullptr, Mount->MountLocation, nullptr, MS_SHARED, nullptr);
1289 if (Result < 0 && !Mount->IgnoreFailure)
1290 {
1291 FATAL_ERROR("Failed to make shared mount {} {}", Mount->MountLocation, errno);
1292 }
1293 }
1294
1295 break;
1296
1297 case InitStartupTypeNode:
1298 Node = &AnyEntry->u.Node;
1299 Result = mknod(Node->Path, Node->Security.Mode, makedev(Node->MajorNumber, Node->MinorNumber));
1300 if (Result < 0)
1301 {
1302 FATAL_ERROR("Failed to create {} {}", Node->Path, errno);
1303 }
1304
1305 Result = chown(Node->Path, Node->Security.Uid, Node->Security.Gid);
1306 if (Result < 0)
1307 {
1308 FATAL_ERROR("Failed to chown {} {}", Node->Path, errno);
1309 }
1310
1311 break;
1312
1313 case InitStartupTypeSymlink:
1314 Symlink = &AnyEntry->u.Symlink;
1315 Result = symlink(Symlink->Target, Symlink->Source);
1316 if ((Result < 0) && (errno != EEXIST))
1317 {
1318 FATAL_ERROR("Failed to create {} -> {} {}", Symlink->Source, Symlink->Target, errno);
1319 }
1320
1321 break;
1322
1323 case InitStartupTypeFile:
1324 File = &AnyEntry->u.File;
1325 Result = TEMP_FAILURE_RETRY(creat(File->FileName, File->Mode));
1326 if ((Result < 0) && (errno != EEXIST))
1327 {
1328 FATAL_ERROR("Failed to create {} {}", File->FileName, errno);
1329 goto InitializeEntryExit;
1330 }
1331
1332 break;
1333
1334 default:
1335 FATAL_ERROR("Unsupported Type {}", AnyEntry->Type);
1336 }
1337
1338 Result = 0;
1339
1340 InitializeEntryExit:
1341 return Result;
1342 }
1343
1344 void ConfigCreateResolvConfSymlink(const wsl::linux::WslDistributionConfig& Config)
1345
1346 /*++
1347
1348 Routine Description:
1349
1350 This routine ensures the /etc/resolv.conf symlink exists for WSL2.
1351
1352 Arguments:
1353
1354 Config - Supplies the distribution configuration.
1355
1356 Return Value:
1357
1358 0 on success, -1 on failure.
1359
1360 --*/
1361
1362 {
1363 if (!UtilIsUtilityVm())
1364 {
1365 return;
1366 }
1367
1368 if (!Config.GenerateResolvConf)
1369 {
1370 LOG_WARNING("{} updating disabled in {}", RESOLV_CONF_FILE_PATH, CONFIG_FILE);
1371
1372 //
1373 // Ensure that the symlink between /etc/resolv.conf -> /mnt/wsl/resolv.conf is removed
1374 //
1375
1376 ConfigReconfigureResolvConfSymlink(Config);
1377
1378 return;
1379 }
1380
1381 //
1382 // Create a /etc/resolv.conf symlink to the file that is automatically
1383 // generated by WSL Core.
1384 //
1385
1386 try
1387 {
1388 std::string Target = std::format("{}{}/{}", Config.DrvFsPrefix, SHARED_MOUNT_FOLDER, RESOLV_CONF_FILE_NAME);
1389
1390 remove(RESOLV_CONF_FILE_PATH);
1391 if (symlink(Target.c_str(), RESOLV_CONF_FILE_PATH) < 0)
1392 {
1393 LOG_ERROR("symlink({}, {}) failed {}", Target, RESOLV_CONF_FILE_PATH, errno);
1394 }
1395 }
1396 CATCH_LOG()
1397 }
1398
1399 int ConfigCreateResolvConfSymlinkTarget(void)
1400
1401 /*++
1402
1403 Routine Description:
1404
1405 If the /etc/resolv.conf file is a symlink, this routine will recursively
1406 create the directory structure and target of the symlink.
1407
1408 Arguments:
1409
1410 None.
1411
1412 Return Value:
1413
1414 0 on success, -1 on failure.
1415
1416 --*/
1417
1418 {
1419 bool RestoreCwd = false;
1420 int Result;
1421 char SymlinkBuffer[PATH_MAX + 1];
1422 int SymlinkFd = -1;
1423
1424 //
1425 // If /etc/resolv.conf is a symlink, recursively create the directory
1426 // structure. If the file is not a symlink, return success. If the symlink
1427 // does not exist, recreate it.
1428 // TODO: move to std::filesystem
1429 //
1430
1431 Result = readlink(RESOLV_CONF_FILE_PATH, SymlinkBuffer, (sizeof(SymlinkBuffer) - 1));
1432
1433 if (Result < 0)
1434 {
1435 if (errno == EINVAL)
1436 {
1437 Result = 0;
1438 goto CreateResolvConfSymlinkTargetExit;
1439 }
1440 else if (errno == ENOENT)
1441 {
1442 Result = symlink(RESOLV_CONF_SYMLINK_TARGET, RESOLV_CONF_FILE_PATH);
1443 if (Result < 0)
1444 {
1445 LOG_ERROR("symlink({}, {}) failed {}", RESOLV_CONF_SYMLINK_TARGET, RESOLV_CONF_FILE_PATH, errno);
1446
1447 goto CreateResolvConfSymlinkTargetExit;
1448 }
1449
1450 Result = readlink(RESOLV_CONF_FILE_PATH, SymlinkBuffer, (sizeof(SymlinkBuffer) - 1));
1451 }
1452
1453 if (Result < 0)
1454 {
1455 LOG_ERROR("readlink({}) failed {}", RESOLV_CONF_FILE_PATH, errno);
1456 goto CreateResolvConfSymlinkTargetExit;
1457 }
1458 }
1459
1460 //
1461 // Null-terminate the symlink buffer string.
1462 //
1463
1464 SymlinkBuffer[Result] = '\0';
1465
1466 //
1467 // Set current working directory to the folder that contains the resolv.conf
1468 // symlink.
1469 //
1470 // N.B. This is so creating the target of the symlinks will work since they
1471 // may use relative symlinks.
1472 //
1473
1474 Result = chdir(ETC_FOLDER);
1475 if (Result < 0)
1476 {
1477 LOG_ERROR("chdir {} failed {}", ETC_FOLDER, errno);
1478 goto CreateResolvConfSymlinkTargetExit;
1479 }
1480
1481 RestoreCwd = true;
1482
1483 //
1484 // Check if the symlink target exists, if the file exists return success. If
1485 // it does not exist, recursively create the directory structure.
1486 //
1487
1488 Result = access(SymlinkBuffer, W_OK);
1489 if (Result == 0)
1490 {
1491 goto CreateResolvConfSymlinkTargetExit;
1492 }
1493 else if (errno != ENOENT)
1494 {
1495 Result = -1;
1496 LOG_ERROR("access {} W_OK failed {}", SymlinkBuffer, errno);
1497 goto CreateResolvConfSymlinkTargetExit;
1498 }
1499
1500 //
1501 // Recursively create the directory structure.
1502 //
1503
1504 Result = UtilMkdirPath(SymlinkBuffer, RESOLV_CONF_DIRECTORY_MODE, true);
1505
1506 //
1507 // The symlink target itself does not need to be created here, as it will
1508 // be created when the symlink is opened later.
1509 //
1510
1511 Result = 0;
1512
1513 CreateResolvConfSymlinkTargetExit:
1514 if (RestoreCwd != false)
1515 {
1516 if (chdir(DEFAULT_CWD) < 0)
1517 {
1518 LOG_ERROR("chdir({}) failed {}", DEFAULT_CWD, errno);
1519 }
1520 }
1521
1522 if (SymlinkFd != -1)
1523 {
1524 CLOSE(SymlinkFd);
1525 }
1526
1527 return Result;
1528 }
1529
1530 int ConfigReconfigureResolvConfSymlink(const wsl::linux::WslDistributionConfig& Config)
1531
1532 /*++
1533
1534 Routine Description:
1535
1536 Checks the value of the Config.GenerateResolvConf and removes the symlink (if one has been set previously)
1537
1538 Arguments:
1539
1540 Config - Supplies the Distribution Configuration.
1541
1542 Return Value:
1543
1544 0 on success, -1 on failure.
1545
1546 --*/
1547
1548 {
1549 int Result;
1550 char SymLinkBuffer[PATH_MAX + 1];
1551
1552 // check if /etc/resolv.conf is symlink
1553 // TODO: move to std::filesystem.
1554 Result = readlink(RESOLV_CONF_FILE_PATH, SymLinkBuffer, (sizeof(SymLinkBuffer) - 1));
1555 if (Result < 0)
1556 {
1557 if (errno == EINVAL || errno == ENOENT)
1558 {
1559 Result = 0;
1560 }
1561 else
1562 {
1563 LOG_ERROR("readlink({}) failed {}", RESOLV_CONF_FILE_PATH, errno);
1564 }
1565
1566 return Result;
1567 }
1568
1569 // null-terminate the symlink buffer string
1570 SymLinkBuffer[Result] = '\0';
1571
1572 // recreate the location of [automount root]/wsl/resolv.conf
1573 auto target = Config.DrvFsPrefix + std::string{RESOLV_CONF_SYMLINK_WSL_MOUNT_SUFFIX};
1574
1575 // check if the symlink is pointing to /mnt/wsl/resolv.conf created by wslcore
1576 // do not interfere with symlinks set by other networking management processes (ie. resolvconf, NetworkManager, etc.)
1577 if (std::string_view{SymLinkBuffer} == target)
1578 {
1579 // generateResolveConf setting has changed, remove symlink and restore if specified
1580 Result = remove(RESOLV_CONF_FILE_PATH);
1581 if (Result < 0)
1582 {
1583 LOG_ERROR("remove({}) failed {}", RESOLV_CONF_FILE_PATH, errno);
1584 }
1585 }
1586
1587 return Result;
1588 }
1589
1590 EnvironmentBlock ConfigCreateEnvironmentBlock(const PLX_INIT_CREATE_PROCESS_COMMON Common, const wsl::linux::WslDistributionConfig& Config)
1591
1592 /*++
1593
1594 Routine Description:
1595
1596 This routine creates the environment block to be used when launching a new
1597 process.
1598
1599 Arguments:
1600
1601 Common - Supplies a pointer to the common create process message data.
1602
1603 Return Value:
1604
1605 An environment block.
1606
1607 --*/
1608
1609 {
1610 //
1611 // Initialize the environment block.
1612 //
1613
1614 auto Buffer = (char*)Common + Common->EnvironmentOffset;
1615 EnvironmentBlock Environment(Buffer, Common->EnvironmentCount);
1616
1617 //
1618 // Add environment variables to support GUI applications.
1619 //
1620 // N.B. This must be done before processing WSLENV so the user can override
1621 // these values if desired.
1622 //
1623
1624 if (Config.GuiAppsEnabled)
1625 {
1626 for (const auto& Var : ConfigGetWslgEnvironmentVariables(Config))
1627 {
1628 Environment.AddVariable(Var.first, Var.second);
1629 }
1630 }
1631
1632 //
1633 // Add each Windows environment variable from WSLENV to the environment block.
1634 //
1635 // N.B. Failure to parse WSLENV is non-fatal.
1636 //
1637
1638 Buffer = (char*)Common + Common->NtEnvironmentOffset;
1639 auto NtEnvironment = UtilParseWslEnv(Buffer);
1640 if (!NtEnvironment.empty())
1641 {
1642 for (size_t Index = 0;;)
1643 {
1644 Buffer = NtEnvironment.data() + Index;
1645 auto Length = strnlen(Buffer, NtEnvironment.size() - Index);
1646 if (Length == 0)
1647 {
1648 break;
1649 }
1650
1651 auto Value = strchr(Buffer, '=');
1652 if (Value != NULL)
1653 {
1654 *Value = '\0';
1655 Value += 1;
1656 Environment.AddVariable(Buffer, Value);
1657 }
1658
1659 Index += Length + 1;
1660 }
1661 }
1662
1663 //
1664 // Add the GPU library to the $PATH variable. This is done because some GPU
1665 // vendors ship small utilities along with their usermode drivers.
1666 //
1667
1668 if (UtilIsUtilityVm())
1669 {
1670 if (Config.AppendGpuLibPath && Config.GpuEnabled)
1671 {
1672 ConfigAppendToPath(Environment, LXSS_LIB_PATH);
1673 }
1674 }
1675
1676 //
1677 // Translate the NT path into a list of Linux paths and add it to the PATH
1678 // environment variable. Individual path elements that fail to translate
1679 // are skipped.
1680 //
1681 // N.B. Failure to append the NT path is non-fatal.
1682 //
1683
1684 Buffer = reinterpret_cast<char*>(Common) + Common->NtPathOffset;
1685 if ((Config.InteropAppendWindowsPath) && (*Buffer != '\0'))
1686 {
1687 ConfigAppendNtPath(Environment, Buffer);
1688 }
1689
1690 return Environment;
1691 }
1692
1693 std::set<std::pair<unsigned int, std::string>> ConfigGetMountedDrvFsVolumes(void)
1694
1695 /*++
1696
1697 Routine Description:
1698
1699 This routine returns a bitmap indicating which Windows drive letters are
1700 already mounted.
1701
1702 N.B. If this function fails, it just returns 0, since failure is not
1703 considered fatal here.
1704
1705 Arguments:
1706
1707 None.
1708
1709 Return Value:
1710
1711 The bitmap of mounted drives.
1712
1713 --*/
1714
1715 {
1716 std::set<std::pair<unsigned int, std::string>> MountPoints;
1717 mountutil::MountEnum MountEnum;
1718 while (MountEnum.Next())
1719 {
1720 //
1721 // Do not consider bind mounts.
1722 //
1723
1724 if (strcmp(MountEnum.Current().Root, "/") != 0 &&
1725 !ParseAggregateVirtioFsMountRoot(MountEnum.Current().Source, MountEnum.Current().Root))
1726 {
1727 continue;
1728 }
1729
1730 //
1731 // Extract the correct mount source depending on whether this is 9p
1732 // (WSL2) or DrvFs (WSL1). For virtio-9p, the entry's mount source
1733 // will just be "drvfs" or "drvfsa", so it must be extracted from the
1734 // aname (this works for hvsocket-9p too).
1735 // N.B. UtilParsePlan9MountSource always returns a canonicalized path.
1736 //
1737
1738 std::string MountSource;
1739 if (strcmp(MountEnum.Current().FileSystemType, PLAN9_FS_TYPE) == 0)
1740 {
1741 MountSource = UtilParsePlan9MountSource(MountEnum.Current().SuperOptions);
1742 }
1743 else if (strcmp(MountEnum.Current().FileSystemType, DRVFS_FS_TYPE) == 0)
1744 {
1745 MountSource = MountEnum.Current().Source;
1746 UtilCanonicalisePathSeparator(MountSource, PATH_SEP_NT);
1747 }
1748 else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
1749 {
1750 MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source, MountEnum.Current().Root);
1751 }
1752 else
1753 {
1754 continue;
1755 }
1756
1757 if (MountSource.empty())
1758 {
1759 continue;
1760 }
1761
1762 auto letter = ConfigGetDriveLetter(MountSource);
1763 if (letter.has_value())
1764 {
1765 MountPoints.emplace(letter.value(), MountEnum.Current().MountPoint);
1766 }
1767 }
1768
1769 return MountPoints;
1770 }
1771
1772 std::vector<std::pair<std::string, std::string>> ConfigGetWslgEnvironmentVariables(const wsl::linux::WslDistributionConfig& Config)
1773
1774 /*++
1775
1776 Routine Description:
1777
1778 This routine returns the environment variables needed by WSLg.
1779
1780 Arguments:
1781
1782 None.
1783
1784 Return Value:
1785
1786 A list of environment variables.
1787
1788 --*/
1789
1790 {
1791 std::string WaylandPath = std::format("{}{}/{}", Config.DrvFsPrefix, WSLG_SHARED_FOLDER, WAYLAND_RUNTIME_DIR);
1792 std::string PulsePath = std::format("unix:{}{}/{}", Config.DrvFsPrefix, WSLG_SHARED_FOLDER, PULSE_SERVER_NAME);
1793 return std::vector<std::pair<std::string, std::string>>{
1794 {XDG_RUNTIME_DIR_ENV, std::move(WaylandPath)},
1795 {X11_DISPLAY_ENV, X11_DISPLAY_VALUE},
1796 {WAYLAND_DISPLAY_ENV, WAYLAND_DISPLAY_VALUE},
1797 {PULSE_SERVER_ENV, std::move(PulsePath)},
1798 {LX_WSL2_GUI_APP_SUPPORT_ENV, "1"}};
1799 }
1800
1801 void ConfigInitializeCgroups(wsl::linux::WslDistributionConfig& Config)
1802
1803 /*++
1804
1805 Routine Description:
1806
1807 This parses the /proc/cgroups file and mounts enabled cgroups.
1808
1809 N.B. This routine was modeled after the cgroupfs-mount script.
1810
1811 Arguments:
1812
1813 Config - Supplies the distribution configuration.
1814
1815 Return Value:
1816
1817 None.
1818
1819 --*/
1820
1821 try
1822 {
1823 std::vector<std::string> DisabledControllers;
1824
1825 if (UtilIsUtilityVm())
1826 {
1827 if (Config.CGroup == WslDistributionConfig::CGroupVersion::v1)
1828 {
1829 auto commandLine = UtilReadFileContent("/proc/cmdline");
1830 auto position = commandLine.find(CGROUPS_NO_V1);
1831 if (position != std::string::npos)
1832 {
1833 auto list = commandLine.substr(position + sizeof(CGROUPS_NO_V1) - 1);
1834 auto end = list.find_first_of(" \n");
1835 if (end != std::string::npos)
1836 {
1837 list = list.substr(0, end);
1838 }
1839
1840 if (list == "all")
1841 {
1842 LOG_WARNING("Distribution has cgroupv1 enabled, but kernel command line has {}all. Falling back to cgroupv2", CGROUPS_NO_V1);
1843 Config.CGroup = WslDistributionConfig::CGroupVersion::v2;
1844 }
1845 else
1846 {
1847 DisabledControllers = wsl::shared::string::Split(list, ',');
1848 }
1849 }
1850 }
1851
1852 if (Config.CGroup == WslDistributionConfig::CGroupVersion::v1 && getenv(LX_WSL2_DISTRO_CGROUP_PATH) != nullptr)
1853 {
1854 EMIT_USER_WARNING(wsl::shared::Localization::MessageCgroupV1IncompatibleWithDistroIsolation());
1855 }
1856
1857 if (Config.CGroup == WslDistributionConfig::CGroupVersion::v1)
1858 {
1859 THROW_LAST_ERROR_IF(mount("tmpfs", CGROUP_MOUNTPOINT, "tmpfs", (MS_NOSUID | MS_NODEV | MS_NOEXEC), "mode=755") < 0);
1860 }
1861
1862 const auto Target = Config.CGroup == WslDistributionConfig::CGroupVersion::v1 ? CGROUP_MOUNTPOINT "/unified" : CGROUP_MOUNTPOINT;
1863 THROW_LAST_ERROR_IF(
1864 UtilMount(CGROUP2_DEVICE, Target, CGROUP2_DEVICE, (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME), "nsdelegate") < 0);
1865
1866 if (Config.CGroup == WslDistributionConfig::CGroupVersion::v2)
1867 {
1868 return;
1869 }
1870 }
1871 else
1872 {
1873 THROW_LAST_ERROR_IF(mount("tmpfs", CGROUP_MOUNTPOINT, "tmpfs", (MS_NOSUID | MS_NODEV | MS_NOEXEC), "mode=755") < 0);
1874 }
1875
1876 //
1877 // Mount cgroup v1 when running in WSL1 mode or when a WSL2 distro has automount.cgroups=v1 specified.
1878 //
1879 // Open the /proc/cgroups file and parse each line, ignoring malformed
1880 // lines and disabled controllers.
1881 //
1882
1883 wil::unique_file Cgroups{fopen(CGROUPS_FILE, "r")};
1884 THROW_LAST_ERROR_IF(!Cgroups);
1885
1886 ssize_t BytesRead;
1887 char* Line = nullptr;
1888 auto LineCleanup = wil::scope_exit([&]() { free(Line); });
1889 size_t LineLength = 0;
1890 while ((BytesRead = getline(&Line, &LineLength, Cgroups.get())) != -1)
1891 {
1892 char* Subsystem = nullptr;
1893 bool Enabled = false;
1894 if ((UtilParseCgroupsLine(Line, &Subsystem, &Enabled) < 0) || (Enabled == false) ||
1895 std::find(DisabledControllers.begin(), DisabledControllers.end(), Subsystem) != DisabledControllers.end())
1896
1897 {
1898 continue;
1899 }
1900
1901 auto Target = std::format("{}/{}", CGROUP_MOUNTPOINT, Subsystem);
1902 THROW_LAST_ERROR_IF(
1903 UtilMount(CGROUP_DEVICE, Target.c_str(), CGROUP_DEVICE, (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME), Subsystem) < 0);
1904 }
1905 }
1906 CATCH_LOG()
1907
1908 std::optional<unsigned int> ConfigGetDriveLetter(std::string_view MountSource)
1909
1910 /*++
1911
1912 Routine Description:
1913
1914 This routine extracts the drive letter from a mount source.
1915
1916 N.B. Recognized formats are "X:", "X:\134" (the escape sequence for a
1917 backslash used in /proc/self/mounts) and "X:/", where X may be
1918 lowercase or uppercase.
1919
1920 Arguments:
1921
1922 MountSource - Supplies the mount source.
1923
1924 Return Value:
1925
1926 The drive letter index as an integer (0 is 'A', 1 is 'B' and so on).
1927
1928 --*/
1929
1930 {
1931 //
1932 // The length must be 2 or 3 and the second character must always be ':'.
1933 //
1934
1935 if ((MountSource.length() < 2) || (MountSource.length() > 3) || (MountSource[1] != ':'))
1936 {
1937 return {};
1938 }
1939
1940 //
1941 // If there are three characters, the third one must be a path separator.
1942 //
1943
1944 if (MountSource.length() == 3 && MountSource[2] != '/' && MountSource[2] != '\\')
1945 {
1946 return {};
1947 }
1948
1949 //
1950 // Extract the drive letter from the first character.
1951 //
1952
1953 if ((MountSource[0] >= 'a') && (MountSource[0] <= 'z'))
1954 {
1955 return MountSource[0] - 'a';
1956 }
1957 else if ((MountSource[0] >= 'A') && (MountSource[0] <= 'Z'))
1958 {
1959 return MountSource[0] - 'A';
1960 }
1961
1962 return {};
1963 }
1964
1965 void ConfigMountDrvFsVolumes(unsigned int DrvFsVolumes, uid_t OwnerUid, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config)
1966
1967 /*++
1968
1969 Routine Description:
1970
1971 This routine mounts the specified DrvFs volumes.
1972
1973 Arguments:
1974
1975 DrvFsVolumes - Supplies a bitmap that contains the indices of DrvFs volumes
1976 to mount.
1977
1978 OwnerUid - Supplies the owner uid to use.
1979
1980 Admin - Supplies an optional boolean to specify if the admin or non-admin
1981 server should be used.
1982
1983 Return Value:
1984
1985 None.
1986
1987 --*/
1988
1989 try
1990 {
1991 if (DrvFsVolumes == 0)
1992 {
1993 return;
1994 }
1995
1996 //
1997 // If fstab was processed, exclude already mounted volumes.
1998 //
1999
2000 std::set<std::pair<unsigned int, std::string>> MountedVolumes;
2001 if (Config.MountFsTab)
2002 {
2003 MountedVolumes = ConfigGetMountedDrvFsVolumes();
2004 }
2005
2006 //
2007 // Attempt to determine the owner gid to use.
2008 //
2009 // N.B. If no entry is found, root is used as the owner gid.
2010 //
2011
2012 auto OwnerGid = ROOT_GID;
2013 auto Password = getpwuid(OwnerUid);
2014 if (Password != nullptr)
2015 {
2016 OwnerGid = Password->pw_gid;
2017 }
2018
2019 //
2020 // Initialize the mount options.
2021 //
2022 // N.B. If the options weren't specified, ConfigDrvFsOptions will be an
2023 // empty string. Since DrvFs ignores empty mount options, the extra
2024 // comma on the end in that case is not a problem.
2025 //
2026
2027 std::string Options =
2028 std::format("noatime,uid={},gid={},{}", OwnerUid, OwnerGid, Config.DrvFsOptions.has_value() ? Config.DrvFsOptions->c_str() : "");
2029
2030 //
2031 // Iterate over the bitmap and attempt to create a DrvFs mount for each
2032 // drive letter.
2033 //
2034 // N.B. __builtin_ffsll returns a one-based index.
2035 //
2036
2037 char Source[] = DRVFS_SOURCE;
2038 for (int Index = __builtin_ffsll(DrvFsVolumes); Index != 0; Index = __builtin_ffsll(DrvFsVolumes))
2039 {
2040 //
2041 // Mask out the current Index.
2042 //
2043
2044 Index -= 1;
2045 DrvFsVolumes ^= (1 << Index);
2046
2047 //
2048 // If this drive is already mounted on the same mountpoint, skip.
2049 //
2050
2051 auto Target = std::format("{}{:c}", Config.DrvFsPrefix, 'a' + Index);
2052 if (MountedVolumes.contains(std::make_pair(Index, Target.c_str())))
2053 {
2054 LOG_WARNING("{} already mounted, skipping...", Target);
2055 continue;
2056 }
2057
2058 //
2059 // Create the target directory and attempt the mount.
2060 //
2061
2062 if (UtilMkdir(Target.c_str(), DRVFS_TARGET_MODE) < 0)
2063 {
2064 continue;
2065 }
2066
2067 Source[0] = 'A' + Index;
2068 if (MountDrvfs(Source, Target.c_str(), Options.c_str(), Admin, Config) < 0)
2069 {
2070 EMIT_USER_WARNING(wsl::shared::Localization::MessageDrvfsMountFailed(Source));
2071 }
2072 }
2073 }
2074 CATCH_LOG()
2075
2076 static void ConfigApplyWindowsLibPath(const wsl::linux::WslDistributionConfig& Config)
2077
2078 /*++
2079
2080 Routine Description:
2081
2082 This routine creates a file for the GNU loader to include in its
2083 library search paths, located under /etc/ld.so.conf.d/.
2084
2085 After writing this file, /sbin/ldconfig will be invoked to update the cache.
2086
2087 N.B. Failures during this function are not fatal to instance start.
2088
2089 Arguments:
2090
2091 Config - Supplies the distribution configuration.
2092
2093 Return Value:
2094
2095 None.
2096
2097 --*/
2098
2099 {
2100 const char* const LdConfigArgv[] = {LDCONFIG_COMMAND, nullptr};
2101
2102 if (!Config.LinkOsLibs)
2103 {
2104 return;
2105 }
2106
2107 wil::unique_fd Fd{TEMP_FAILURE_RETRY(open(WINDOWS_LD_CONF_FILE, (O_CREAT | O_RDWR | O_TRUNC), WINDOWS_LD_CONF_FILE_MODE))};
2108 if (!Fd)
2109 {
2110 LOG_ERROR("open {} failed {}", WINDOWS_LD_CONF_FILE, errno);
2111 return;
2112 }
2113
2114 std::string_view Buffer{WindowsLibSearchFileHeaderString};
2115 if (UtilWriteStringView(Fd.get(), Buffer) < 0)
2116 {
2117 LOG_ERROR("write failed {}", errno);
2118 return;
2119 }
2120
2121 Buffer = LXSS_LIB_PATH;
2122 if (UtilWriteStringView(Fd.get(), Buffer) < 0)
2123 {
2124 LOG_ERROR("write failed {}", errno);
2125 return;
2126 }
2127
2128 if (UtilCreateProcessAndWait(LdConfigArgv[0], LdConfigArgv) < 0)
2129 {
2130 LOG_ERROR("Processing ldconfig failed");
2131 }
2132 }
2133
2134 void ConfigMountFsTab(bool Elevated)
2135
2136 /*++
2137
2138 Routine Description:
2139
2140 This routine runs mount -a to process the /etc/fstab file.
2141
2142 N.B. Failures during this function are not fatal to instance start.
2143
2144 Arguments:
2145
2146 Elevated - True if the plan9 drvfs entries should use the elevated plan9 server.
2147
2148 Return Value:
2149
2150 None.
2151
2152 --*/
2153
2154 {
2155 //
2156 // Note: The WSL_DRVFS_ELEVATED_ENV variable is used because the interop server isn't running yet.
2157 //
2158
2159 const char* const Argv[] = {MOUNT_COMMAND, MOUNT_FSTAB_ARG, nullptr};
2160 if (UtilCreateProcessAndWait(Argv[0], Argv, nullptr, {{WSL_DRVFS_ELEVATED_ENV, Elevated ? "1" : "0"}}, true) < 0)
2161 {
2162 auto message = wsl::shared::Localization::MessageFstabMountFailed();
2163 LOG_ERROR("{}", message.c_str());
2164
2165 EMIT_USER_WARNING(std::move(message));
2166 }
2167 }
2168
2169 int ConfigRegisterBinfmtInterpreter(void)
2170
2171 /*++
2172
2173 Routine Description:
2174
2175 This routine registers the binfmt extension for interop.
2176
2177 Arguments:
2178
2179 None.
2180
2181 Return Value:
2182
2183 0 on success, -1 on failure.
2184
2185 --*/
2186
2187 {
2188 //
2189 // Register the interop binfmt extension.
2190 //
2191
2192 wil::unique_fd Fd{TEMP_FAILURE_RETRY(open(BINFMT_MISC_REGISTER_FILE, O_WRONLY))};
2193 if (!Fd)
2194 {
2195 LOG_ERROR("open " BINFMT_MISC_REGISTER_FILE " failed {}", errno);
2196 return -1;
2197 }
2198
2199 std::string_view Buffer{BINFMT_INTEROP_REGISTRATION_STRING(LX_INIT_BINFMT_NAME) "\n"};
2200 int Result = UtilWriteStringView(Fd.get(), Buffer);
2201 if (Result < 0)
2202 {
2203 LOG_ERROR("binfmt registration failed {}", errno);
2204 }
2205
2206 return Result;
2207 }
2208
2209 int ConfigRemountDrvFs(gsl::span<gsl::byte> Buffer, wsl::shared::Transaction& Transaction, const wsl::linux::WslDistributionConfig& Config)
2210
2211 /*++
2212
2213 Routine Description:
2214
2215 This remounts DrvFs volumes in the appropriate mount namespace
2216 and the result on the channel.
2217
2218 Arguments:
2219
2220 Buffer - Supplies a buffer to the LX_INIT_MOUNT_DRVFS message.
2221
2222 ResultChannel - Supplies the file descriptor to write the result to.
2223
2224 Return Value:
2225
2226 0 on success, -1 on failure.
2227
2228 --*/
2229 {
2230 Transaction.SendResultMessage<int32_t>(ConfigRemountDrvFsImpl(Buffer, Config));
2231
2232 return 0;
2233 }
2234
2235 int ConfigRemountDrvFsImpl(gsl::span<gsl::byte> Buffer, const wsl::linux::WslDistributionConfig& Config)
2236
2237 /*++
2238
2239 Routine Description:
2240
2241 This remounts DrvFs volumes in the appropriate mount namespace.
2242
2243 Arguments:
2244
2245 Buffer - Supplies a buffer to the LX_INIT_MOUNT_DRVFS message.
2246
2247 Config - Supplies the distribution configuration.
2248
2249 Return Value:
2250
2251 0 on success, -1 on failure.
2252
2253 --*/
2254
2255 try
2256 {
2257 //
2258 // This method is only valid for VM mode.
2259 //
2260
2261 if (!UtilIsUtilityVm())
2262 {
2263 return -1;
2264 }
2265
2266 const auto* Message = gslhelpers::try_get_struct<LX_INIT_MOUNT_DRVFS>(Buffer);
2267 if (!Message)
2268 {
2269 LOG_ERROR("Unexpected sizeof for LX_INIT_MOUNT_DRVFS: {}", Buffer.size());
2270 return -1;
2271 }
2272
2273 if (Message->Admin ? (g_ElevatedMountNamespace != -1) : (g_NonElevatedMountNamespace != -1))
2274 {
2275 LOG_ERROR("{} namespace already initialized", Message->Admin ? "Admin" : "Non-Admin");
2276 return -1;
2277 }
2278
2279 //
2280 // Read the mountinfo file for the namespace that is already configured.
2281 // This contains all mounts from /etc/fstab as well as the initial drives
2282 // that were mounted when the instance was created.
2283 //
2284
2285 wil::unique_file MountInfo{fopen(MOUNT_INFO_FILE, "r")};
2286 if (!MountInfo)
2287 {
2288 LOG_ERROR("fopen failed {}", errno);
2289 return -1;
2290 }
2291
2292 std::string FileContents = UtilReadFile(MountInfo.get());
2293
2294 wil::unique_fd OriginalNamespace{UtilOpenMountNamespace()};
2295 if (!OriginalNamespace)
2296 {
2297 return -1;
2298 }
2299
2300 auto RestoreNamespace = wil::scope_exit([&]() {
2301 if (setns(OriginalNamespace.get(), CLONE_NEWNS) < 0)
2302 {
2303 LOG_ERROR("restoring mount namespace failed {}", errno);
2304 }
2305 });
2306
2307 //
2308 // Configure the new mount namespace.
2309 //
2310
2311 if (unshare(CLONE_NEWNS) < 0)
2312 {
2313 LOG_ERROR("unshare failed {}", errno);
2314 return -1;
2315 }
2316
2317 wil::unique_fd NewNamespace{UtilOpenMountNamespace()};
2318 if (!NewNamespace)
2319 {
2320 return -1;
2321 }
2322
2323 if (Message->Admin)
2324 {
2325 g_ElevatedMountNamespace = NewNamespace.release();
2326 }
2327 else
2328 {
2329 g_NonElevatedMountNamespace = NewNamespace.release();
2330 }
2331
2332 //
2333 // Parse the mountinfo file get a list of all the drvfs mounts.
2334 //
2335
2336 std::vector<MOUNT_ENTRY> DrvfsMounts;
2337 for (char *Sp1, *Info = strtok_r(FileContents.data(), "\n", &Sp1); Info != nullptr; Info = strtok_r(NULL, "\n", &Sp1))
2338 {
2339 MOUNT_ENTRY MountEntry;
2340 if (MountParseMountInfoLine(Info, &MountEntry) < 0)
2341 {
2342 return -1;
2343 }
2344
2345 //
2346 // Bind mounts which have a root other than / are currently not supported, except for aggregate virtio-fs shares.
2347 //
2348 // TODO_LX: Support bind mounts.
2349 //
2350
2351 if (strcmp(MountEntry.Root, "/") != 0 && !ParseAggregateVirtioFsMountRoot(MountEntry.Source, MountEntry.Root))
2352 {
2353 continue;
2354 }
2355
2356 if (strcmp(MountEntry.FileSystemType, PLAN9_FS_TYPE) == 0)
2357 {
2358
2359 //
2360 // Ensure that only drvfs mounts are re-mounted. This avoids unmounting sharefs mounts (used for mounting gpu libs and drivers).
2361 //
2362
2363 auto Plan9Source = UtilParsePlan9MountSource(MountEntry.SuperOptions);
2364 if (Plan9Source.empty() || !ConfigGetDriveLetter(Plan9Source).has_value())
2365 {
2366 continue;
2367 }
2368 }
2369 else if (strcmp(MountEntry.FileSystemType, VIRTIO_FS_TYPE) != 0)
2370 {
2371 continue;
2372 }
2373 else if (wsl::shared::string::StartsWith(std::string_view{MountEntry.MountPoint}, VIRTIOFS_MOUNT_DIR))
2374 {
2375 // Hidden aggregate mounts are inherited by the new namespace and must not be replaced by a child bind mount.
2376 continue;
2377 }
2378
2379 DrvfsMounts.emplace_back(std::move(MountEntry));
2380 }
2381
2382 //
2383 // Unmount the existing drvfs mounts in reverse order, then remount the new version.
2384 //
2385
2386 for (auto ReverseIterator = DrvfsMounts.rbegin(); ReverseIterator != DrvfsMounts.rend(); ReverseIterator++)
2387 {
2388 const auto* MountPoint = (*ReverseIterator).MountPoint;
2389 if (umount2(MountPoint, MNT_DETACH) < 0)
2390 {
2391 LOG_ERROR("umount2({}) failed {}", MountPoint, errno);
2392 }
2393 }
2394
2395 std::bitset<32> volumesToMount(Message->VolumesToMount);
2396 std::bitset<32> unreadableVolumes(Message->UnreadableVolumes);
2397
2398 std::string NewMountOptions;
2399 for (const auto& MountEntry : DrvfsMounts)
2400 {
2401 if (strcmp(MountEntry.FileSystemType, PLAN9_FS_TYPE) == 0)
2402 {
2403 const char* NewSource = MountEntry.Source;
2404 auto Plan9Source = UtilParsePlan9MountSource(MountEntry.SuperOptions);
2405 if (Plan9Source.empty())
2406 {
2407 continue;
2408 }
2409
2410 auto driveIndex = ConfigGetDriveLetter(Plan9Source);
2411 if (driveIndex.has_value())
2412 {
2413 //
2414 // This is drive mount. Remount only if the drive is actually readable.
2415 //
2416
2417 if (unreadableVolumes[driveIndex.value()])
2418 {
2419 //
2420 // This drive is not readable, don't try to mount it.
2421 //
2422
2423 LOG_WARNING("Drvfs mount '{}' is not readable, skipping mount", Plan9Source);
2424 continue;
2425 }
2426
2427 volumesToMount[driveIndex.value()] = false;
2428 }
2429
2430 //
2431 // Construct new Plan9 mount options based on the existing mount.
2432 //
2433
2434 NewMountOptions = MountEntry.MountOptions;
2435 NewMountOptions += ',';
2436 if (WSL_USE_VIRTIO_9P())
2437 {
2438 //
2439 // Check if the existing mount is a drvfs mount that needs to be remounted.
2440 //
2441
2442 auto Tag = Message->Admin ? LX_INIT_DRVFS_VIRTIO_TAG : LX_INIT_DRVFS_ADMIN_VIRTIO_TAG;
2443 if (strcmp(MountEntry.Source, Tag) != 0)
2444 {
2445 continue;
2446 }
2447
2448 NewSource = Message->Admin ? LX_INIT_DRVFS_ADMIN_VIRTIO_TAG : LX_INIT_DRVFS_VIRTIO_TAG;
2449 }
2450
2451 //
2452 // Remove the transport-related mount options.
2453 //
2454
2455 std::string_view SuperOptions = MountEntry.SuperOptions;
2456 while (!SuperOptions.empty())
2457 {
2458 auto Option = UtilStringNextToken(SuperOptions, ",");
2459 if (wsl::shared::string::StartsWith(Option, "trans=") || wsl::shared::string::StartsWith(Option, "rfd=") ||
2460 wsl::shared::string::StartsWith(Option, "wfd=") || wsl::shared::string::StartsWith(Option, "msize="))
2461 {
2462 continue;
2463 }
2464
2465 NewMountOptions += Option;
2466 NewMountOptions += ',';
2467 }
2468
2469 MountPlan9Share(NewSource, MountEntry.MountPoint, NewMountOptions.c_str(), Message->Admin);
2470 }
2471 else if (strcmp(MountEntry.FileSystemType, VIRTIO_FS_TYPE) == 0)
2472 {
2473 if (const auto aggregateRoot = ParseAggregateVirtioFsMountRoot(MountEntry.Source, MountEntry.Root))
2474 {
2475 const std::string childName{aggregateRoot->ChildName};
2476 RemountVirtioFs(childName.c_str(), MountEntry.MountPoint, MountEntry.MountOptions, Message->Admin, aggregateRoot->SubPath);
2477 }
2478 else
2479 {
2480 RemountVirtioFs(MountEntry.Source, MountEntry.MountPoint, MountEntry.MountOptions, Message->Admin);
2481 }
2482 }
2483 else
2484 {
2485 LOG_ERROR("Unexpected fstype {}", MountEntry.FileSystemType);
2486 }
2487 }
2488
2489 // It's possible that some drives are only visible to one namespace and not the other
2490 // (for instance if only an elevated token has read access to a drive).
2491 // If that's the case, those drives might not have been mounted previously, so
2492 // mount any drive that hasn't been found in MountInfo.
2493 if (Config.AutoMount)
2494 {
2495 ConfigMountDrvFsVolumes(volumesToMount.to_ulong(), Message->DefaultOwnerUid, Message->Admin, Config);
2496 }
2497
2498 return 0;
2499 }
2500 CATCH_RETURN_ERRNO()
2501
2502 int ConfigSetMountNamespace(bool Elevated)
2503
2504 /*++
2505
2506 Routine Description:
2507
2508 This routine sets the mount namespace of the caller.
2509
2510 Arguments:
2511
2512 Elevated - Supplies true if the client represents an elevated Windows process, false otherwise.
2513
2514 Return Value:
2515
2516 The file descriptor representing the mount namespace on success, -1 on failure.
2517
2518 --*/
2519
2520 {
2521 if (!UtilIsUtilityVm())
2522 {
2523 return -1;
2524 }
2525
2526 auto Namespace = Elevated ? g_ElevatedMountNamespace : g_NonElevatedMountNamespace;
2527 if (Namespace == -1)
2528 {
2529 LOG_ERROR("{} namespace has not been initialized", Elevated ? "Admin" : "Non-Admin");
2530 return -1;
2531 }
2532
2533 if (setns(Namespace, CLONE_NEWNS) < 0)
2534 {
2535 LOG_ERROR("setns failed {}", errno);
2536 return -1;
2537 }
2538
2539 return Namespace;
2540 }
2541
2542 void ConfigUpdateLanguage(EnvironmentBlock& Environment)
2543
2544 /*++
2545
2546 Routine Description:
2547
2548 This routine queries the contents of the locale configuration file and
2549 if present updates the $LANG environment variable in the environment block.
2550
2551 Different distributions store this file in different locations:
2552 - /etc/default/locale
2553 - /etc/locale.conf
2554 Both share the same "LANG=" line format, so the first file that exists is used.
2555
2556 Arguments:
2557
2558 Environment - Supplies the environment block pointer to update.
2559
2560 Return Value:
2561
2562 None.
2563
2564 --*/
2565
2566 try
2567 {
2568 //
2569 // Attempt to open the locale configuration file, trying each known path in turn.
2570 // If none of the files exist then the $LANG environment variable will not be updated.
2571 //
2572 // N.B. These files are being opened by root. The only user-visible content
2573 // will be the contents of the last line of the file that contains
2574 // "LANG=".
2575 //
2576
2577 constexpr const char* LocaleFilePaths[] = {LOCALE_FILE_PATH, LOCALE_CONF_FILE_PATH};
2578
2579 wil::unique_file LocaleFile;
2580 for (const auto* Path : LocaleFilePaths)
2581 {
2582 LocaleFile.reset(fopen(Path, "r"));
2583 if (LocaleFile)
2584 {
2585 break;
2586 }
2587
2588 if (errno != ENOENT)
2589 {
2590 LOG_ERROR("fopen({}) failed {}", Path, errno);
2591 }
2592 }
2593
2594 if (!LocaleFile)
2595 {
2596 return;
2597 }
2598
2599 // TODO: Move to std::regex
2600
2601 //
2602 // Parse the file line-by-line looking for the "LANG=" string.
2603 //
2604
2605 char* Line = nullptr;
2606 auto freeLine = wil::scope_exit([&Line]() { free(Line); });
2607
2608 size_t LineLength = 0;
2609 while (getline(&Line, &LineLength, LocaleFile.get()) != -1)
2610 {
2611 //
2612 // Handle comments by replacing the first comment character with a null
2613 // terminator, thus ending the string.
2614 //
2615
2616 auto SpecialCharacter = strchr(Line, '#');
2617 if (SpecialCharacter != nullptr)
2618 {
2619 *SpecialCharacter = '\0';
2620 }
2621
2622 //
2623 // If the current line contains the "LANG=" string, update the
2624 // environment block with the remainder of the line.
2625 //
2626 // N.B. If the file contains multiple lines containing "LANG=" the last
2627 // will be used.
2628 //
2629
2630 auto Content = strstr(Line, LANG_ENV "=");
2631 if (Content != nullptr)
2632 {
2633 Content += sizeof(LANG_ENV);
2634
2635 //
2636 // Replace newline character with a null terminator.
2637 //
2638
2639 SpecialCharacter = strchr(Content, '\n');
2640 if (SpecialCharacter != nullptr)
2641 {
2642 *SpecialCharacter = '\0';
2643 }
2644
2645 const auto Value = wsl::shared::string::UnescapeShell(wsl::shared::string::Trim(std::string{Content}));
2646 Environment.AddVariable(LANG_ENV, Value);
2647 }
2648 }
2649
2650 return;
2651 }
2652 CATCH_LOG()
2653
2654 void ConfigUpdateNetworkInformation(gsl::span<gsl::byte> Buffer, const wsl::linux::WslDistributionConfig& Config)
2655
2656 /*++
2657
2658 Routine Description:
2659
2660 This routine updates the instance's network information by writing to
2661 /etc/resolv.conf.
2662
2663 Arguments:
2664
2665 Buffer - Supplies the message buffer.
2666
2667 Config - Supplies the WSL distribution configuration.
2668
2669 Return Value:
2670
2671 0 on success, -1 on failure.
2672
2673 --*/
2674
2675 try
2676 {
2677 if (!Config.GenerateResolvConf)
2678 {
2679 LOG_WARNING("{} updating disabled in {}", RESOLV_CONF_FILE_PATH, CONFIG_FILE);
2680 return;
2681 }
2682
2683 //
2684 // Validate input parameters.
2685 //
2686
2687 auto* Message = gslhelpers::try_get_struct<LX_INIT_NETWORK_INFORMATION>(Buffer);
2688 if (!Message)
2689 {
2690 LOG_ERROR("Unexpected network information size {}", Buffer.size());
2691 return;
2692 }
2693
2694 //
2695 // Write the contents to /etc/resolv.conf.
2696 //
2697
2698 if (ConfigCreateResolvConfSymlinkTarget() < 0)
2699 {
2700 return;
2701 }
2702
2703 THROW_LAST_ERROR_IF(UtilMkdir(RESOLV_CONF_FOLDER, RESOLV_CONF_DIRECTORY_MODE) < 0);
2704
2705 wil::unique_fd Fd{TEMP_FAILURE_RETRY(open(RESOLV_CONF_FILE_PATH, (O_CREAT | O_RDWR | O_TRUNC), RESOLV_CONF_FILE_MODE))};
2706 THROW_LAST_ERROR_IF(!Fd);
2707
2708 const char* Header = wsl::shared::string::FromSpan(Buffer, Message->FileHeaderIndex);
2709 if (Header)
2710 {
2711 THROW_LAST_ERROR_IF(UtilWriteStringView(Fd.get(), Header) < 0);
2712 }
2713
2714 const char* Content = wsl::shared::string::FromSpan(Buffer, Message->FileContentsIndex);
2715 if (Content)
2716 {
2717 THROW_LAST_ERROR_IF(UtilWriteStringView(Fd.get(), Content) < 0);
2718 }
2719 else
2720 {
2721 LOG_ERROR("/etc/resolv.conf unexpectedly empty");
2722 }
2723 }
2724 CATCH_LOG()
2725
2726 bool CreateLoginSession(const wsl::linux::WslDistributionConfig& Config, const char* Username, uid_t Uid)
2727 /*++
2728
2729 Routine Description:
2730
2731 Create a systemd login session for the given user.
2732
2733 Arguments:
2734
2735 Config - Supplies the WSL distribution configuration.
2736
2737 Username - Supplies session username.
2738
2739 Uid - Supplies the session UID.
2740
2741 Return Value:
2742
2743 true on success, false on failure.
2744
2745 --*/
2746 try
2747 {
2748 static std::mutex LoginSessionsLock;
2749 static std::map<uid_t, int> LoginSessions;
2750
2751 // Keep track of login sessions that have been created.
2752 LoginSessionsLock.lock();
2753 auto Unlock = wil::scope_exit([&]() { LoginSessionsLock.unlock(); });
2754 if (LoginSessions.contains(Uid))
2755 {
2756 return true;
2757 }
2758
2759 int LoginLeader;
2760 const int Result = forkpty(&LoginLeader, nullptr, nullptr, nullptr);
2761 if (Result < 0)
2762 {
2763 LOG_ERROR("forkpty failed {}", errno);
2764 return false;
2765 }
2766 else if (Result == 0)
2767 {
2768 Unlock.reset();
2769 _exit(execl("/bin/login", "/bin/login", "-f", Username, nullptr));
2770 }
2771
2772 LoginSessions.emplace(Uid, LoginLeader);
2773
2774 //
2775 // N.B. Init needs to not ignore SIGCHLD so it can wait for the child process.
2776 //
2777 signal(SIGCHLD, SIG_DFL);
2778 auto restoreDisposition = wil::scope_exit([]() { signal(SIGCHLD, SIG_IGN); });
2779
2780 if (Config.BootInitTimeout > 0)
2781 {
2782 auto cmd = std::format("systemctl is-active user@{}.service", Uid);
2783 try
2784 {
2785 return wsl::shared::retry::RetryWithTimeout<bool>(
2786 [&]() {
2787 std::string Output;
2788 auto exitCode = UtilExecCommandLine(cmd.c_str(), &Output, 0, false);
2789 if (exitCode == 0) // is-active returns 0 if the unit is active.
2790 {
2791 return true;
2792 }
2793 else if (Output == "failed\n")
2794 {
2795 LOG_ERROR("{} returned: {}", cmd, Output);
2796 return false;
2797 }
2798
2799 THROW_ERRNO(EAGAIN);
2800 },
2801 std::chrono::milliseconds{250},
2802 std::chrono::milliseconds{Config.BootInitTimeout});
2803 }
2804 catch (...)
2805 {
2806 LOG_ERROR("Timed out waiting for user session for uid={}", Uid);
2807 return false;
2808 }
2809 }
2810
2811 return true;
2812 }
2813 catch (...)
2814 {
2815 LOG_CAUGHT_EXCEPTION();
2816 return false;
2817 }