master
cpp 4,366 lines 120 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 main.cpp
8
9 Abstract:
10
11 This file contains the entrypoint of the WSL init implementation.
12
13 --*/
14
15 #include <sys/mount.h>
16 #include <sys/signalfd.h>
17 #include <sys/socket.h>
18 #include <sys/stat.h>
19 #include <sys/sysinfo.h>
20 #include <sys/sysmacros.h>
21 #include <sys/reboot.h>
22 #include <sys/resource.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <net/if.h>
26 #include <net/route.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 #include <linux/audit.h> /* Definition of AUDIT_* constants */
30 #include <linux/if_tun.h>
31 #include <linux/loop.h>
32 #include <linux/net.h>
33 #include <linux/random.h>
34 #include <linux/vm_sockets.h>
35 #include <linux/filter.h>
36 #include <linux/seccomp.h>
37 #include <linux/sock_diag.h>
38 #include <sys/utsname.h>
39 #include <linux/netlink.h>
40 #include <dirent.h>
41 #include <errno.h>
42 #include <limits.h>
43 #include <poll.h>
44 #include <pthread.h>
45 #include <sched.h>
46 #include <signal.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stddef.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <time.h>
53 #include <unistd.h>
54 #include <utmp.h>
55 #include <assert.h>
56 #include "configfile.h"
57 #include "lxfsshares.h"
58 #include "common.h"
59 #include "util.h"
60 #include "seccomp_defs.h"
61 #include "mountutilcpp.h"
62 #include "message.h"
63 #include "binfmt.h"
64 #include "address.h"
65 #include "SocketChannel.h"
66
67 #define BSDTAR_PATH "/usr/bin/bsdtar"
68 #define BINFMT_REGISTER_STRING BINFMT_INTEROP_REGISTRATION_STRING_VM(LX_INIT_BINFMT_NAME) "\n"
69 #define BINFMT_PATH PROCFS_PATH "/sys/fs/binfmt_misc"
70 #define CHRONY_CONF_PATH ETC_PATH "/chrony.conf"
71 #define CHRONYD_PATH "/sbin/chronyd"
72 #define CROSS_DISTRO_SHARE_PATH "/mnt/wsl"
73 #define DEVFS_PATH "/dev"
74 #define DEVNULL_PATH DEVFS_PATH "/null"
75 #define DHCPCD_CONF_PATH "/dhcpcd.conf"
76 #define DHCPCD_PATH "/usr/sbin/dhcpcd"
77
78 #define DISTRO_PATH "/distro"
79 #define ETC_PATH "/etc"
80 #define GPU_SHARE_PREFIX "/gpu_"
81 #define GPU_SHARE_DRIVERS GPU_SHARE_PREFIX LXSS_GPU_DRIVERS_SHARE
82 #define GPU_SHARE_LIB GPU_SHARE_PREFIX LXSS_GPU_LIB_SHARE
83 #define GPU_SHARE_LIB_INBOX GPU_SHARE_LIB "_inbox"
84 #define GPU_SHARE_LIB_PACKAGED GPU_SHARE_LIB "_packaged"
85 #define KERNEL_MODULES_PATH "/lib/modules"
86 #define KERNEL_MODULES_VHD_PATH "/modules"
87 #define KERNEL_MODULES_OVERLAY "/modules_overlay"
88 #define MODPROBE_PATH "/sbin/modprobe"
89 #define PROCFS_PATH "/proc"
90 #define RESOLV_CONF_FILE "resolv.conf"
91 #define RESOLV_CONF_PATH ETC_PATH "/" RESOLV_CONF_FILE
92 #define SCSI_DEVICE_PATH "/sys/bus/scsi/devices"
93 #define SCSI_DEVICE_NAME_PREFIX "0:0:0:"
94 #define SCSI_DEVICE_PREFIX SCSI_DEVICE_PATH "/" SCSI_DEVICE_NAME_PREFIX
95 #define SYSFS_PATH "/sys"
96 #define SYSTEM_DISTRO_PATH "/system"
97 #define SYSTEM_DISTRO_VHD_PATH "/systemvhd"
98 #define WSLG_PATH "/wslg"
99
100 #define syscall_arg(_n) (offsetof(struct seccomp_data, args[_n]))
101 #define syscall_nr (offsetof(struct seccomp_data, nr))
102 #define syscall_arch (offsetof(struct seccomp_data, arch))
103
104 constexpr auto c_trueString = "1";
105 constexpr size_t c_systemReservedMemory = 32 * 1024 * 1024; // 32MiB reserved for WSL system processes
106 constexpr long c_cpuPeriodMicros = 100000;
107 constexpr long c_systemReservedCpuMicros = 1000; // 0.01 Logical core reserved for WSL system processes
108
109 struct VmConfiguration
110 {
111 bool EnableGpuSupport = false;
112 bool EnableGuiApps = false;
113 bool EnableInboxGpuLibs = false;
114 bool EnableSafeMode = false;
115 bool EnableSystemDistro = false;
116 bool EnableCrashDumpCollection = false;
117 std::string KernelModulesPath;
118 LX_MINI_INIT_NETWORKING_MODE NetworkingMode = LxMiniInitNetworkingModeNone;
119 };
120
121 int g_LogFd = STDERR_FILENO;
122 int g_TelemetryFd = -1;
123 std::optional<bool> g_EnableSocketLogging;
124
125 int Chroot(const char* Target);
126
127 void CreateSwap(unsigned int Lun);
128
129 int CreateTempDirectory(const char* ParentPath, std::string& Path);
130
131 int DetachScsiDisk(unsigned int Lun);
132
133 int EjectScsi(unsigned int Lun);
134
135 int EnableInterface(int Socket, const char* Name);
136
137 int ExportToSocket(const char* Source, int Socket, int ErrorSocket, unsigned int flags);
138
139 int FormatDevice(unsigned int Lun);
140
141 std::string GetLunDeviceName(unsigned int Lun);
142
143 std::string GetLunDevicePath(unsigned int Lun);
144
145 int GetDiskPartitionIndex(const char* DiskPath, const char* PartitionName);
146
147 std::string GetMountTarget(const char* Name);
148
149 int ImportFromSocket(const char* Destination, int Socket, int ErrorSocket, unsigned int Flags);
150
151 int Initialize(const char* Hostname);
152
153 void InjectEntropy(gsl::span<gsl::byte> EntropyBuffer);
154
155 void LaunchInit(
156 int SocketFd,
157 const char* Target,
158 bool EnableGuiApps,
159 const VmConfiguration& Config,
160 const char* VmId = nullptr,
161 const char* DistributionName = nullptr,
162 const char* SharedMemoryRoot = nullptr,
163 const char* InstallPath = nullptr,
164 const char* UserProfile = nullptr,
165 std::optional<pid_t> DistroInitPid = {},
166 const char* DistroCgroupPath = nullptr);
167
168 void LaunchSystemDistro(
169 int SocketFd,
170 const char* Target,
171 const VmConfiguration& Config,
172 const char* VmId,
173 const char* DistributionName,
174 const char* SharedMemoryRoot,
175 const char* InstallPath,
176 const char* UserProfile,
177 pid_t DistroInitPid,
178 const char* DistroCgroupPath);
179
180 std::map<unsigned long, std::string> ListDiskPartitions(const std::string& DeviceName, std::optional<unsigned long> WaitForIndex = {});
181
182 std::vector<unsigned int> ListScsiDisks();
183
184 void LogException(const char* Message, const char* Description) noexcept;
185
186 int MountDevice(LX_MINI_INIT_MOUNT_DEVICE_TYPE DeviceType, unsigned int DeviceId, const char* Target, const char* FsType, unsigned int Flags, const char* Options);
187
188 int MountSystemDistro(LX_MINI_INIT_MOUNT_DEVICE_TYPE DeviceType, unsigned int DeviceId);
189
190 int MountInit(const char* Target);
191
192 int MountPlan9(const char* Name, const char* Target, bool ReadOnly, std::optional<int> BufferSize = {});
193
194 int ProcessMessage(wsl::shared::Transaction& Transaction, LX_MESSAGE_TYPE Type, gsl::span<gsl::byte> Buffer, VmConfiguration& Config);
195
196 wil::unique_fd RegisterSeccompHook();
197
198 int ReportMountStatus(wsl::shared::SocketChannel& Channel, int Result, LX_MINI_MOUNT_STEP Step);
199
200 int SendCapabilities(wsl::shared::SocketChannel& Channel);
201
202 int SetCloseOnExec(int Fd, bool Enable);
203
204 int SetEphemeralPortRange(uint16_t Start, uint16_t End);
205
206 void StartDebugShell();
207
208 int StartDhcpClient(int DhcpTimeout);
209
210 int StartGuestNetworkService(int GnsFd, wil::unique_fd&& DnsTunnelingFd, uint32_t DnsTunnelingIpAddress);
211
212 void StartPortTracker(LX_MINI_INIT_PORT_TRACKER_TYPE Type, LX_MINI_INIT_NETWORKING_MODE NetworkingMode);
213
214 void StartTimeSyncAgent(void);
215
216 void WaitForBlockDevice(const char* Path);
217
218 int WaitForChild(pid_t Pid, const char* Name);
219
220 void SetupWslUserCgroup();
221
222 int Chroot(const char* Target)
223
224 /*++
225
226 Routine Description:
227
228 This routine changes the root directory of the calling process to the specified
229 path.
230
231 Arguments:
232
233 Target - Supplies the path to chroot to.
234
235 Return Value:
236
237 0 on success, -1 on failure.
238
239 --*/
240
241 {
242 //
243 // Set the current working directory to the distro mount point, move the
244 // mount to the root, and chroot.
245 //
246
247 if (chdir(Target) < 0)
248 {
249 LOG_ERROR("chdir({}) failed {}", Target, errno);
250 return -1;
251 }
252
253 if (mount(".", "/", nullptr, MS_MOVE, nullptr) < 0)
254 {
255 LOG_ERROR("mount(MS_MOVE) failed {}", errno);
256 return -1;
257 }
258
259 if (chroot(".") < 0)
260 {
261 LOG_ERROR("chroot failed {}", errno);
262 return -1;
263 }
264
265 return 0;
266 }
267
268 wil::unique_fd CreateNetlinkSocket(void)
269
270 /*++
271
272 Routine Description:
273
274 Create and bind a netlink socket.
275
276 Arguments:
277
278 None.
279
280 Return Value:
281
282 The socket file descriptor or < 0 on failure.
283
284 --*/
285
286 {
287 wil::unique_fd Fd{socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG)};
288 if (!Fd)
289 {
290 LOG_ERROR("socket failed {}", errno);
291 return {};
292 }
293
294 struct sockaddr_nl Address{};
295 Address.nl_family = AF_NETLINK;
296 if (bind(Fd.get(), (struct sockaddr*)&Address, sizeof(Address)) < 0)
297 {
298 LOG_ERROR("bind failed {}", errno);
299 return {};
300 }
301
302 return Fd;
303 }
304
305 void CreateSwap(unsigned int Lun)
306
307 /*++
308
309 Routine Description:
310
311 This routine sets up a swap area on the specified SCSI device.
312
313 Arguments:
314
315 Lun - Supplies the LUN number of the SCSI device.
316
317 Return Value:
318
319 None.
320
321 --*/
322
323 {
324 //
325 // Create the swap file asynchronously using the mkswap and swapon utilities in the system distro.
326 //
327 // N.B. This is done because creating the swap file can take some time and
328 // the swap file does not need to be available immediately.
329 //
330
331 UtilCreateChildProcess("CreateSwap", [Lun]() {
332 std::string DevicePath = GetLunDevicePath(Lun);
333
334 WaitForBlockDevice(DevicePath.c_str());
335
336 std::string CommandLine = std::format("/usr/sbin/mkswap '{}'", DevicePath);
337 THROW_LAST_ERROR_IF(UtilExecCommandLine(CommandLine.c_str(), nullptr) < 0);
338
339 CommandLine = std::format("/usr/sbin/swapon '{}'", DevicePath);
340 UtilExecCommandLine(CommandLine.c_str(), nullptr);
341 });
342 }
343
344 int CreateTempDirectory(const char* ParentPath, std::string& Path)
345
346 /*++
347
348 Routine Description:
349
350 This routine creates a unique directory under the specified parent path.
351
352 Arguments:
353
354 ParentPath - Supplies the path of the parent directory.
355
356 Path - Supplies a buffer to receive the path of the child directory that was
357 created.
358
359 Return Value:
360
361 0 on success, -1 on failure.
362
363 --*/
364
365 {
366 if (ParentPath)
367 {
368 Path = ParentPath;
369 }
370
371 //
372 // Generate a random name for the directory.
373 //
374 // N.B. mkdtemp requires a template string that ends in "XXXXXX".
375 //
376
377 Path += "/wslXXXXXX";
378
379 if (mkdtemp(Path.data()) == NULL)
380 {
381 LOG_ERROR("mkdtemp({}) failed {}", Path.c_str(), errno);
382 return -1;
383 }
384
385 return 0;
386 }
387
388 dev_t GetBlockDeviceNumber(const std::string& BlockDeviceName)
389
390 /*++
391
392 Routine Description:
393
394 This method return the device number of a given block device.
395
396 Arguments:
397
398 BlockDeviceName - Supplies the name of the block device.
399
400 Return Value:
401
402 The device block number. Throws on error.
403
404 --*/
405
406 {
407 std::string content = wsl::shared::string::ReadFile<char, char>(std::format("/sys/block/{}/dev", BlockDeviceName).c_str());
408 auto separator = content.find(':');
409
410 if (separator == std::string::npos || separator == 0 || separator + 1 == content.size())
411 {
412 LOG_ERROR("Failed to parse device number '{}' for device '{}'", content.c_str(), BlockDeviceName.c_str());
413 THROW_ERRNO(EINVAL);
414 }
415
416 try
417 {
418 return makedev(std::strtoul(content.c_str(), nullptr, 10), std::strtoul(content.substr(separator + 1).c_str(), nullptr, 10));
419 }
420 catch (...)
421 {
422 LOG_ERROR("Failed to parse device number '{}' for device '{}'", content.c_str(), BlockDeviceName.c_str());
423 THROW_ERRNO(EINVAL);
424 }
425 }
426
427 int DetachScsiDisk(unsigned int Lun)
428
429 /*++
430
431 Routine Description:
432
433 This routine detaches a SCSI disk.
434
435 Arguments:
436
437 Lun - Supplies the LUN of the disk to detach.
438
439 Return Value:
440
441 0 on success, -1 on failure.
442
443 --*/
444
445 {
446 auto deviceName = GetLunDeviceName(Lun);
447
448 try
449 {
450 auto deviceNumbers = std::set<dev_t>{GetBlockDeviceNumber(deviceName)};
451 for (const auto& e : ListDiskPartitions(deviceName.c_str()))
452 {
453 deviceNumbers.insert(GetBlockDeviceNumber(std::format("{}/{}", deviceName, e.second)));
454 }
455
456 mountutil::MountEnum mounts;
457 while (mounts.Next())
458 {
459 if (deviceNumbers.find(mounts.Current().Device) != deviceNumbers.end())
460 {
461 if (umount(mounts.Current().MountPoint) < 0)
462 {
463 LOG_ERROR("Failed to unmount '{}', {}", mounts.Current().MountPoint, errno);
464 }
465 }
466 }
467 }
468 CATCH_LOG();
469
470 // Flush the block device.
471 std::string DevicePath = DEVFS_PATH + std::string("/") + deviceName;
472 wil::unique_fd BlockDevice{open(DevicePath.c_str(), O_RDONLY)};
473 int Result = ioctl(BlockDevice.get(), BLKFLSBUF);
474 if (Result < 0)
475 {
476 LOG_ERROR("Failed to flush block device: '{}', {}", DevicePath.c_str(), errno);
477 return Result;
478 }
479
480 // Close the device before trying to delete it.
481 BlockDevice.reset();
482
483 // Remove the block device.
484 return WriteToFile(std::format("/sys/block/{}/device/delete", deviceName).c_str(), "1");
485 }
486
487 int DetectFilesystem(const char* BlockDevice, std::string& Output)
488
489 /*++
490
491 Routine Description:
492
493 This routine performs file system detect on a block device.
494
495 Arguments:
496
497 BlockDevice - Path to the block device.
498
499 Output - Detected filesystem, if any.
500
501 Return Value:
502
503 0 on success, < 0 on failure.
504
505 --*/
506
507 try
508 {
509 //
510 // Wait for the block device to be available.
511 //
512
513 wsl::shared::retry::RetryWithTimeout<void>(
514 [&]() { THROW_LAST_ERROR_IF(!wil::unique_fd{open(BlockDevice, O_RDONLY)}); },
515 c_defaultRetryPeriod,
516 c_defaultRetryTimeout,
517 []() {
518 auto err = wil::ResultFromCaughtException();
519 return err == ENOENT || err == ENXIO;
520 });
521
522 auto CommandLine = std::format("/usr/sbin/blkid '{}' -p -s TYPE -o value -u filesystem", BlockDevice);
523 if (UtilExecCommandLine(CommandLine.c_str(), &Output) < 0)
524 {
525 return -1;
526 }
527
528 while (!Output.empty() && Output.back() == '\n')
529 {
530 Output.pop_back();
531 }
532
533 LOG_INFO("Detected {} filesystem for device: {}", Output, BlockDevice);
534 return 0;
535 }
536 CATCH_RETURN_ERRNO()
537
538 int EjectScsi(unsigned int Lun)
539
540 /*++
541
542 Routine Description:
543
544 This routine ejects the specified SCSI device.
545
546 Arguments:
547
548 Lun - Supplies the LUN of the SCSI device to eject.
549
550 Return Value:
551
552 0 on success, -1 on failure.
553
554 --*/
555
556 try
557 {
558 //
559 // Perform a sync to ensure all writes are flushed.
560 //
561
562 sync();
563
564 //
565 // Write "1" to /sys/bus/scsi/devices/0:0:<controller>:<lun>/delete to eject the SCSI device.
566 //
567
568 std::string Path = std::format("{}{}/delete", SCSI_DEVICE_PREFIX, Lun);
569 if (WriteToFile(Path.c_str(), c_trueString) < 0)
570 {
571 return -1;
572 }
573
574 return 0;
575 }
576 CATCH_RETURN_ERRNO()
577
578 void EnableCrashDumpCollection()
579 {
580 if (symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0)
581 {
582 LOG_ERROR("symlink({}, {}) failed {}", "/init", "/" LX_INIT_WSL_CAPTURE_CRASH, errno);
583 return;
584 }
585
586 // If the first character is a pipe, then the kernel will interpret this path as a command.
587 constexpr auto core_pattern = "|/" LX_INIT_WSL_CAPTURE_CRASH " %t %E %p %s";
588 WriteToFile("/proc/sys/kernel/core_pattern", core_pattern);
589 }
590
591 int EnableInterface(int Socket, const char* Name)
592
593 /*++
594
595 Routine Description:
596
597 This routine marks the specified interface as up / running.
598
599 Arguments:
600
601 Socket - Supplies a socket file descriptor.
602
603 Name - Supplies the name of an interface.
604
605 Return Value:
606
607 0 on success, -1 on failure.
608
609 --*/
610
611 {
612 ifreq InterfaceRequest{};
613 strncpy(InterfaceRequest.ifr_name, Name, IFNAMSIZ - 1);
614 if (ioctl(Socket, SIOCGIFFLAGS, &InterfaceRequest) < 0)
615 {
616 LOG_ERROR("SIOCGIFFLAGS failed {}", errno);
617 return -1;
618 }
619
620 InterfaceRequest.ifr_flags |= (IFF_UP | IFF_RUNNING);
621 if (ioctl(Socket, SIOCSIFFLAGS, &InterfaceRequest) < 0)
622 {
623 LOG_ERROR("SIOCSIFFLAGS failed {}", errno);
624 return -1;
625 }
626
627 return 0;
628 }
629
630 int ExportToSocket(const char* Source, int Socket, int ErrorSocket, unsigned int Flags)
631
632 /*++
633
634 Routine Description:
635
636 This routine uses bsdtar to export a source directory in tar format via a
637 socket.
638
639 Arguments:
640
641 Source - Supplies the path to export.
642
643 Socket - Supplies the socket to write to.
644
645 Flags - Additional compression flags.
646
647 Return Value:
648
649 0 on success, -1 on failure.
650
651 --*/
652
653 {
654 //
655 // Create a child process running bsdtar with the socket set to stdout.
656 //
657
658 int ChildPid = UtilCreateChildProcess("ExportDistro", [Source, TarFd = Socket, ErrorSocket = ErrorSocket, Flags = Flags]() {
659 THROW_LAST_ERROR_IF(TEMP_FAILURE_RETRY(dup2(TarFd, STDOUT_FILENO)) < 0);
660 THROW_LAST_ERROR_IF(TEMP_FAILURE_RETRY(dup2(ErrorSocket, STDERR_FILENO)) < 0);
661
662 std::string compressionArguments;
663
664 if (WI_IsFlagSet(Flags, LxMiniInitMessageFlagExportCompressGzip))
665 {
666 assert(!WI_IsFlagSet(Flags, LxMiniInitMessageFlagExportCompressXzip));
667
668 compressionArguments = "-cz";
669 }
670 else if (WI_IsFlagSet(Flags, LxMiniInitMessageFlagExportCompressXzip))
671 {
672 compressionArguments = "-cJ";
673 }
674 else
675 {
676 compressionArguments = "-c";
677 }
678
679 if (WI_IsFlagSet(Flags, LxMiniInitMessageFlagVerbose))
680 {
681 compressionArguments += "vv";
682 }
683
684 std::vector<const char*> arguments{
685 BSDTAR_PATH,
686 "-C",
687 Source,
688 compressionArguments.c_str(),
689 "--one-file-system",
690 "--xattrs",
691 "--numeric-owner",
692 "-f",
693 "-",
694 ".",
695 nullptr};
696
697 if (WI_IsFlagSet(Flags, LxMiniInitMessageFlagVerbose))
698 {
699 arguments.emplace(arguments.begin() + 3, "--totals");
700 }
701
702 execv(BSDTAR_PATH, const_cast<char**>(arguments.data()));
703 LOG_ERROR("execl failed, {}", errno);
704 });
705
706 if (ChildPid < 0)
707 {
708 return -1;
709 }
710
711 //
712 // Wait for the child to exit and shut down the socket.
713 //
714
715 const int Result = WaitForChild(ChildPid, BSDTAR_PATH);
716 if (shutdown(Socket, SHUT_WR) < 0)
717 {
718 LOG_ERROR("shutdown failed {}", errno);
719 }
720
721 return Result;
722 }
723
724 int FormatDevice(unsigned int Lun)
725
726 /*++
727
728 Routine Description:
729
730 This routine formats the specified SCSI device with the ext4 file system.
731 N.B. The group size was chosen based on the best practices for Linux VHDs:
732 https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/best-practices-for-running-linux-on-hyper-v
733
734 Arguments:
735
736 Lun - Supplies the LUN number of the SCSI device.
737
738 Return Value:
739
740 0 on success, < 0 on failure.
741
742 --*/
743
744 try
745 {
746 std::string DevicePath = GetLunDevicePath(Lun);
747
748 WaitForBlockDevice(DevicePath.c_str());
749
750 std::string CommandLine = std::format("/usr/sbin/mkfs.ext4 -G 4096 '{}'", DevicePath);
751 if (UtilExecCommandLine(CommandLine.c_str(), nullptr) < 0)
752 {
753 return -1;
754 }
755
756 return 0;
757 }
758 CATCH_RETURN_ERRNO()
759
760 std::string GetLunDeviceName(unsigned int Lun)
761
762 /*++
763
764 Routine Description:
765
766 This routine returns the device name(sdX) for the specified SCSI device.
767
768 Arguments:
769
770 Lun - Supplies a SCSI LUN.
771
772 Return Value:
773
774 The device name (throws on error).
775
776 --*/
777
778 {
779 //
780 // Construct a path to the block directory which contains a single directory
781 // entry with the name of the device where the vhd is attached, for example: sda.
782 //
783 // N.B. A retry loop is needed because there is a delay between when the vhd
784 // is hot-added from the host, and when the sysfs directory is
785 // available in the guest.
786 //
787
788 std::string Path = std::format("{}{}/block", SCSI_DEVICE_PREFIX, Lun);
789 return wsl::shared::retry::RetryWithTimeout<std::string>(
790 [&]() {
791 wil::unique_dir Dir{opendir(Path.c_str())};
792 THROW_LAST_ERROR_IF(!Dir);
793
794 //
795 // Find the first directory entry that does not begin with a dot.
796 //
797
798 dirent64* Entry{};
799 while ((Entry = readdir64(Dir.get())) != nullptr)
800 {
801 if (Entry->d_name[0] != '.')
802 {
803 return std::string(Entry->d_name);
804 }
805 }
806
807 THROW_ERRNO(ENXIO);
808 },
809 c_defaultRetryPeriod,
810 c_defaultRetryTimeout);
811 }
812
813 std::string GetLunDevicePath(unsigned int Lun)
814
815 /*++
816
817 Routine Description:
818
819 This routine returns the device path for the specified SCSI device.
820
821 Arguments:
822
823 Lun - Supplies a SCSI LUN.
824
825 Return Value:
826
827 The device path;
828
829 --*/
830
831 {
832 auto DeviceName = GetLunDeviceName(Lun);
833
834 return std::format("{}/{}", DEVFS_PATH, DeviceName.c_str());
835 }
836
837 int GetDiskPartitionIndex(const char* DiskPath, const char* PartitionName)
838
839 /*++
840
841 Routine Description:
842
843 Finds the partition number of a specified partition path.
844
845 Arguments:
846
847 DiskPath - Supplies the path to the Disk (ex: /sys/block/sda).
848
849 PartitionName - Supplies the partition name (ex: sda1).
850
851 Return Value:
852
853 > 0 (the partition number), < 0 on failure.
854
855 --*/
856
857 try
858 {
859 std::string FilePath = std::format("{}/{}/partition", DiskPath, PartitionName);
860 wil::unique_fd Fd{open(FilePath.c_str(), O_RDONLY)};
861 if (!Fd)
862 {
863 LOG_ERROR("open({}) failed {}", FilePath, errno);
864 return -errno;
865 }
866
867 char Buffer[64];
868 int Result = TEMP_FAILURE_RETRY(read(Fd.get(), Buffer, (sizeof(Buffer) - 1)));
869 if (Result < 0)
870 {
871 LOG_ERROR("read failed {}", errno);
872 return -errno;
873 }
874
875 Buffer[Result] = '\0';
876 return atol(Buffer);
877 }
878 CATCH_RETURN_ERRNO()
879
880 int ImportFromSocket(const char* Destination, int Socket, int ErrorSocket, unsigned int Flags)
881
882 /*++
883
884 Routine Description:
885
886 This routine uses bsdtar to extract a tar file via a socket.
887
888 Arguments:
889
890 Destination - Supplies the path to extract the tar.
891
892 Socket - Supplies the socket to read from.
893
894 Flags - Import flags.
895
896 Return Value:
897
898 0 on success, -1 on failure.
899
900 --*/
901
902 {
903 //
904 // Create a child process running bsdtar with the socket set to stdin.
905 //
906
907 int ChildPid = UtilCreateChildProcess("ImportDistro", [Destination, TarFd = Socket, ErrorSocket = ErrorSocket, Flags]() {
908 THROW_LAST_ERROR_IF(TEMP_FAILURE_RETRY(dup2(TarFd, STDIN_FILENO)) < 0);
909 THROW_LAST_ERROR_IF(TEMP_FAILURE_RETRY(dup2(ErrorSocket, STDERR_FILENO)) < 0);
910
911 execl(
912 BSDTAR_PATH,
913 BSDTAR_PATH,
914 "-C",
915 Destination,
916 "-x",
917 WI_IsFlagSet(Flags, LxMiniInitMessageFlagVerbose) ? "-vvp" : "-p",
918 "--xattrs",
919 "--numeric-owner",
920 "-f",
921 "-",
922 NULL);
923 LOG_ERROR("execl failed, {}", errno);
924 });
925
926 if (ChildPid < 0)
927 {
928 return -1;
929 }
930
931 return WaitForChild(ChildPid, BSDTAR_PATH);
932 }
933
934 void StartDebugShell()
935
936 /*++
937
938 Routine Description:
939
940 This routine starts the debug shell.
941
942 Arguments:
943
944 None.
945
946 Return Value:
947
948 None.
949
950 --*/
951
952 {
953 // Spawn a child process to handle relaunching the debug shell if it exits.
954 UtilCreateChildProcess("DebugShell", []() {
955 for (;;)
956 {
957 const auto Pid = UtilCreateChildProcess("agetty", []() {
958 execl("/usr/bin/setsid", "/usr/bin/setsid", "/sbin/agetty", "-w", "-L", LX_INIT_HVC_DEBUG_SHELL, "-a", "root", NULL);
959 LOG_ERROR("execl failed, {}", errno);
960 });
961
962 if (Pid < 0)
963 {
964 _exit(1);
965 }
966
967 int Status = -1;
968 if (TEMP_FAILURE_RETRY(waitpid(Pid, &Status, 0)) < 0)
969 {
970 LOG_ERROR("waitpid failed {}", errno);
971 _exit(1);
972 }
973 }
974 });
975 }
976
977 int StartDhcpClient(int DhcpTimeout)
978
979 /*++
980
981 Routine Description:
982
983 Starts the dhcp client daemon. Blocks until the initial DHCP lease is acquired,
984 then the daemon continues running in the background to handle renewals.
985
986 Arguments:
987
988 DhcpTimeout - Supplies the timeout in seconds for the DHCP request.
989
990 Return Value:
991
992 0 on success, < 0 on failure.
993
994 --*/
995
996 {
997 int ChildPid = UtilCreateChildProcess("dhcpcd", [DhcpTimeout]() {
998 //
999 // Write the dhcpcd.conf config file.
1000 //
1001
1002 std::string Config = std::format(
1003 "option subnet_mask, routers, broadcast, domain_name, domain_name_servers, domain_search, host_name, interface_mtu\n"
1004 "noarp\n"
1005 "timeout {}\n",
1006 DhcpTimeout);
1007
1008 THROW_LAST_ERROR_IF(WriteToFile(DHCPCD_CONF_PATH, Config.c_str()) < 0);
1009
1010 execl(DHCPCD_PATH, DHCPCD_PATH, "-w", "-4", "-f", DHCPCD_CONF_PATH, "eth0", NULL);
1011 LOG_ERROR("execl({}) failed, {}", DHCPCD_PATH, errno);
1012 });
1013
1014 if (ChildPid < 0)
1015 {
1016 return -1;
1017 }
1018
1019 return WaitForChild(ChildPid, DHCPCD_PATH);
1020 }
1021
1022 int StartGuestNetworkService(int GnsFd, wil::unique_fd&& DnsTunnelingFd, uint32_t DnsTunnelingIpAddress)
1023
1024 /*++
1025
1026 Routine Description:
1027
1028 Start the guest network service.
1029
1030 Arguments:
1031
1032 GnsFd - Supplies the socket file descriptor to use for the guest network service.
1033
1034 DnsTunnelingFd - Supplies an optional file descriptor to be used for DNS tunneling.
1035
1036 DnsTunnelingIpAddress - IP address to be used by the DNS tunneling listener.
1037
1038 Return Value:
1039
1040 0 on success, -1 on failure.
1041
1042 --*/
1043
1044 {
1045 const auto ChildPid =
1046 UtilCreateChildProcess("GuestNetworkService", [GnsFd, DnsTunnelingFd = std::move(DnsTunnelingFd), DnsTunnelingIpAddress]() {
1047 std::string GnsSocketArg = std::to_string(GnsFd);
1048 THROW_LAST_ERROR_IF(SetCloseOnExec(GnsFd, false) < 0);
1049
1050 if (DnsTunnelingFd)
1051 {
1052 std::string DnsSocketArg = std::to_string(DnsTunnelingFd.get());
1053 THROW_LAST_ERROR_IF(SetCloseOnExec(DnsTunnelingFd.get(), false) < 0);
1054
1055 in_addr address{.s_addr = DnsTunnelingIpAddress};
1056 Address dnsIp = Address::FromBinary(AF_INET, 32, &address);
1057 execl(
1058 LX_INIT_PATH,
1059 LX_INIT_GNS,
1060 LX_INIT_GNS_SOCKET_ARG,
1061 GnsSocketArg.c_str(),
1062 LX_INIT_GNS_DNS_SOCKET_ARG,
1063 DnsSocketArg.c_str(),
1064 LX_INIT_GNS_DNS_TUNNELING_IP,
1065 dnsIp.Addr().c_str(),
1066 nullptr);
1067 }
1068 else
1069 {
1070 execl(LX_INIT_PATH, LX_INIT_GNS, LX_INIT_GNS_SOCKET_ARG, GnsSocketArg.c_str(), nullptr);
1071 }
1072
1073 LOG_ERROR("execl failed, {}", errno);
1074 });
1075
1076 return (ChildPid < 0) ? -1 : 0;
1077 }
1078
1079 void StartPortTracker(LX_MINI_INIT_PORT_TRACKER_TYPE Type, LX_MINI_INIT_NETWORKING_MODE NetworkingMode)
1080
1081 /*++
1082
1083 Routine Description:
1084
1085 Start a port tracker daemon.
1086
1087 Arguments:
1088
1089 Type - specifies the type of port tracker (localhost relay or mirrored).
1090
1091 NetworkingMode - specifies the networking mode (mirrored, virtio, etc.).
1092
1093 Return Value:
1094
1095 None.
1096
1097 --*/
1098
1099 {
1100 auto PortTrackerFd = UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, false);
1101 if (!PortTrackerFd)
1102 {
1103 return;
1104 }
1105
1106 wil::unique_fd NetlinkSocket{};
1107 wil::unique_fd BpfFd{};
1108 wil::unique_fd GuestRelayFd{};
1109 switch (Type)
1110 {
1111 case LxMiniInitPortTrackerTypeMirrored:
1112 {
1113
1114 //
1115 // Create a netlink socket before registering the bpf filter so creation of the socket
1116 // does not trigger the filter.
1117 //
1118
1119 NetlinkSocket = CreateNetlinkSocket();
1120 if (!NetlinkSocket)
1121 {
1122 return;
1123 }
1124
1125 BpfFd = RegisterSeccompHook();
1126 if (!BpfFd)
1127 {
1128 return;
1129 }
1130
1131 break;
1132 }
1133 case LxMiniInitPortTrackerTypeRelay:
1134 {
1135 sockaddr_vm HvSocketAddress = {};
1136 GuestRelayFd.reset(UtilListenVsockAnyPort(&HvSocketAddress, -1, false));
1137 if (!GuestRelayFd)
1138 {
1139 return;
1140 }
1141
1142 break;
1143 }
1144 default:
1145 assert(false);
1146 return;
1147 }
1148
1149 UtilCreateChildProcess(
1150 "PortTracker",
1151 [PortTrackerFd = std::move(PortTrackerFd),
1152 NetlinkSocket = std::move(NetlinkSocket),
1153 BpfFd = std::move(BpfFd),
1154 GuestRelayFd = std::move(GuestRelayFd),
1155 NetworkingMode]() {
1156 execl(
1157 LX_INIT_PATH,
1158 LX_INIT_LOCALHOST_RELAY,
1159 INIT_PORT_TRACKER_FD_ARG,
1160 std::format("{}", PortTrackerFd.get()).c_str(),
1161 INIT_BPF_FD_ARG,
1162 std::format("{}", BpfFd.get()).c_str(),
1163 INIT_NETLINK_FD_ARG,
1164 std::format("{}", NetlinkSocket.get()).c_str(),
1165 INIT_PORT_TRACKER_LOCALHOST_RELAY,
1166 std::format("{}", GuestRelayFd.get()).c_str(),
1167 INIT_PORT_TRACKER_NETWORKING_MODE_ARG,
1168 std::format("{}", static_cast<int>(NetworkingMode)).c_str(),
1169 NULL);
1170
1171 LOG_ERROR("execl failed {}", errno);
1172 });
1173 }
1174
1175 int Initialize(const char* Hostname)
1176
1177 /*++
1178
1179 Routine Description:
1180
1181 This routine performs initialization required for mini_init functionality.
1182
1183 Arguments:
1184
1185 Hostname - Supplies a string specifying the hostname.
1186
1187 Return Value:
1188
1189 0 on success, < 0 on failure.
1190
1191 --*/
1192
1193 {
1194 //
1195 // Allow unprivileged users to view the kernel log.
1196 //
1197
1198 if (WriteToFile(PROCFS_PATH "/sys/kernel/dmesg_restrict", "0\n") < 0)
1199 {
1200 return -1;
1201 }
1202
1203 //
1204 // Set max inotify watches to the value suggested by Visual Studio Code Remote.
1205 //
1206
1207 if (WriteToFile(PROCFS_PATH "/sys/fs/inotify/max_user_watches", "524288\n") < 0)
1208 {
1209 return -1;
1210 }
1211
1212 //
1213 // Increase the soft and hard limit for number of open file descriptors.
1214 // N.B. the soft limit shouldn't be too high. See https://github.com/microsoft/WSL/issues/12985 .
1215 //
1216
1217 rlimit Limit{};
1218 Limit.rlim_cur = 1024 * 10;
1219 Limit.rlim_max = 1024 * 1024;
1220 if (setrlimit(RLIMIT_NOFILE, &Limit) < 0)
1221 {
1222 LOG_ERROR("setrlimit(RLIMIT_NOFILE) failed {}", errno);
1223 return -1;
1224 }
1225
1226 //
1227 // Increase the maximum number of bytes of memory that may be locked into RAM.
1228 //
1229
1230 Limit.rlim_cur = 0x4000000;
1231 Limit.rlim_max = 0x4000000;
1232 if (setrlimit(RLIMIT_MEMLOCK, &Limit) < 0)
1233 {
1234 LOG_ERROR("setrlimit(RLIMIT_MEMLOCK) failed {}", errno);
1235 return -1;
1236 }
1237
1238 //
1239 // Enable the loopback interface.
1240 //
1241
1242 wil::unique_fd Fd{socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)};
1243 if (!Fd)
1244 {
1245 LOG_ERROR("socket failed {}", errno);
1246 return -1;
1247 }
1248
1249 if (EnableInterface(Fd.get(), "lo") < 0)
1250 {
1251 return -1;
1252 }
1253
1254 //
1255 // Enable logging when processes receive fatal signals.
1256 //
1257
1258 if (WriteToFile("/proc/sys/kernel/print-fatal-signals", "1\n") < 0)
1259 {
1260 return -1;
1261 }
1262
1263 //
1264 // Set the hostname.
1265 //
1266
1267 if (sethostname(Hostname, strlen(Hostname)) < 0)
1268 {
1269 LOG_ERROR("sethostname({}) failed {}", Hostname, errno);
1270 }
1271
1272 //
1273 // Create a tmpfs mount for the cross-distro shared mount.
1274 //
1275
1276 if (UtilMount(nullptr, CROSS_DISTRO_SHARE_PATH, "tmpfs", MS_SHARED, nullptr) < 0)
1277 {
1278 return -1;
1279 }
1280
1281 //
1282 // Create the resolv.conf symlink in the cross-distro share (gns writes to /etc/resolv.conf).
1283 //
1284
1285 remove(RESOLV_CONF_PATH);
1286 if (symlink(CROSS_DISTRO_SHARE_PATH "/" RESOLV_CONF_FILE, RESOLV_CONF_PATH) < 0)
1287 {
1288 LOG_ERROR("symlink({}, {}) failed {}", CROSS_DISTRO_SHARE_PATH "/" RESOLV_CONF_FILE, RESOLV_CONF_PATH, errno);
1289 return -1;
1290 }
1291
1292 //
1293 // Mount the binfmt_misc filesystem.
1294 //
1295
1296 if (UtilMount(nullptr, BINFMT_PATH, "binfmt_misc", MS_RELATIME, nullptr) < 0)
1297 {
1298 return -1;
1299 }
1300
1301 //
1302 // Register the Windows interop interpreter using the 'F' flag which makes
1303 // it available in other mount namespaces and chroot environments.
1304 //
1305
1306 if (WriteToFile(BINFMT_PATH "/register", BINFMT_REGISTER_STRING) < 0)
1307 {
1308 return -1;
1309 }
1310
1311 return 0;
1312 }
1313
1314 int InitializeLogging(bool SetStderr, wil::LogFunction* ExceptionCallback) noexcept
1315
1316 /*++
1317
1318 Routine Description:
1319
1320 This routine opens /dev/kmsg for logging and optionally sets it as stderr.
1321
1322 Arguments:
1323
1324 SetStderr - Supplies a boolean specifying if kmsg should be set as stderr.
1325
1326 ExceptionCallback - Supplies an optional callback to log exceptions.
1327 If not callback is specified, the default callback is used.
1328
1329 Return Value:
1330
1331 0 on success, < 0 on failure.
1332
1333 --*/
1334
1335 {
1336 wil::g_LogExceptionCallback = ExceptionCallback ? ExceptionCallback : LogException;
1337 auto devicePath = DEVFS_PATH "/kmsg";
1338 g_LogFd = TEMP_FAILURE_RETRY(open(devicePath, (O_WRONLY | O_CLOEXEC)));
1339 if (g_LogFd < 0)
1340 {
1341 g_LogFd = STDERR_FILENO;
1342 LOG_ERROR("open({}) failed {}", devicePath, errno);
1343 return -1;
1344 }
1345 else if (SetStderr)
1346 {
1347 if (g_LogFd != STDERR_FILENO)
1348 {
1349 if (dup2(g_LogFd, STDERR_FILENO) < 0)
1350 {
1351 LOG_ERROR("dup2({}, {}) failed {}", g_LogFd, STDERR_FILENO, errno);
1352 return -1;
1353 }
1354
1355 close(g_LogFd);
1356 g_LogFd = STDERR_FILENO;
1357 }
1358
1359 if (SetCloseOnExec(g_LogFd, false) < 0)
1360 {
1361 return -1;
1362 }
1363 }
1364
1365 // Initialize logging to the hvc console device responsible for logging telemetry.
1366 // If the device is not present, error messages will be logged to kmesg.
1367 if (UtilIsUtilityVm())
1368 {
1369 devicePath = DEVFS_PATH "/" LX_INIT_HVC_TELEMETRY;
1370 g_TelemetryFd = TEMP_FAILURE_RETRY(open(devicePath, (O_WRONLY | O_CLOEXEC)));
1371 if (g_TelemetryFd < 0 && errno != ENODEV)
1372 {
1373 LOG_ERROR("open({}) failed {}", devicePath, errno);
1374 }
1375 }
1376
1377 return 0;
1378 }
1379
1380 void InjectEntropy(gsl::span<gsl::byte> EntropyBuffer)
1381
1382 /*++
1383
1384 Routine Description:
1385
1386 This routine injects boot-time entropy from the provided source.
1387
1388 Arguments:
1389
1390 EntropyBuffer - Supplies a buffer of bytes to use as entropy.
1391
1392 Return Value:
1393
1394 None.
1395
1396 --*/
1397
1398 {
1399 wil::unique_fd Fd{open(DEVFS_PATH "/random", O_RDWR)};
1400 if (!Fd)
1401 {
1402 LOG_ERROR("open failed {}", errno);
1403 return;
1404 }
1405
1406 std::vector<gsl::byte> Buffer(sizeof(rand_pool_info) + EntropyBuffer.size());
1407 auto* PoolInfo = gslhelpers::get_struct<rand_pool_info>(gsl::make_span(Buffer));
1408 PoolInfo->entropy_count = EntropyBuffer.size() * 8;
1409 PoolInfo->buf_size = EntropyBuffer.size();
1410 gsl::copy(EntropyBuffer, gsl::as_writable_bytes(gsl::make_span(PoolInfo->buf, PoolInfo->buf_size)));
1411 if (ioctl(Fd.get(), RNDADDENTROPY, PoolInfo) < 0)
1412 {
1413 LOG_ERROR("ioctl(RNDADDENTROPY) failed {}", errno);
1414 }
1415
1416 return;
1417 }
1418
1419 void LaunchInit(
1420 int SocketFd,
1421 const char* Target,
1422 bool EnableGuiApps,
1423 const VmConfiguration& Config,
1424 const char* VmId,
1425 const char* DistributionName,
1426 const char* SharedMemoryRoot,
1427 const char* InstallPath,
1428 const char* UserProfile,
1429 std::optional<pid_t> DistroInitPid,
1430 const char* DistroCgroupPath)
1431
1432 /*++
1433
1434 Routine Description:
1435
1436 This routine launches the init daemon for the specified distro.
1437
1438 Arguments:
1439
1440 SocketFd - Supplies a file descriptor to communicate with the init daemon.
1441 This routine takes ownership of this file descriptor.
1442
1443 Target - Supplies the location where the distro filesystem is mounted.
1444
1445 EnableGuiApps - True if GUI apps should be enabled.
1446
1447 VmConfiguration - Supplies the VM configuration.
1448
1449 VmId - Supplies the GUID of the VM. If this value is a non-empty string it
1450 is passed to init as an environment variable.
1451
1452 DistributionName - Supplies the name of the distribution. If this value is a
1453 non-empty string it is passed to init as an environment variable.
1454
1455 SharedMemoryRoot - Supplies the Windows OB path for virtiofs shared memory.
1456 If this value is a non-empty string, it is passed to init as an
1457 environment variable.
1458
1459 InstallPath - Supplies the Windows path for the location where the lifted
1460 WSL package is installed. If this value is a non-empty string, it is
1461 passed to init as an environment variable.
1462
1463 UserProfile - Supplies the Windows path for user profile of the VM owner.
1464 If this value is a non-empty string, it is passed to init as an
1465 environment variable.
1466
1467 DistroInitPid - Supplies the pid of the user distribution's init process.
1468
1469 DistroCgroupPath - Supplies the cgroup path of this distribution.
1470
1471 Return Value:
1472
1473 None. This method does not return.
1474
1475 --*/
1476
1477 {
1478 std::vector<std::string> Variables;
1479 auto AddEnvironmentVariable = [&Variables](const char* Name, const char* Value) {
1480 if ((Value) && (*Value != '\0'))
1481 {
1482 Variables.emplace_back(std::format("{}={}", Name, Value));
1483 }
1484 };
1485
1486 size_t TargetPathLength = strlen(Target);
1487 auto AddTemporaryMount = [&](const char* Name, const char* Source, unsigned long MountFlags) {
1488 std::string Path;
1489 THROW_LAST_ERROR_IF(CreateTempDirectory(Target, Path) < 0);
1490 THROW_LAST_ERROR_IF(mount(Source, Path.c_str(), nullptr, MountFlags, nullptr) < 0);
1491 AddEnvironmentVariable(Name, Path.substr(TargetPathLength).data());
1492 };
1493
1494 //
1495 // Set the communication channel to expected file descriptor value.
1496 //
1497
1498 if (SocketFd != LX_INIT_UTILITY_VM_INIT_SOCKET_FD)
1499 {
1500 THROW_LAST_ERROR_IF(TEMP_FAILURE_RETRY(dup2(SocketFd, LX_INIT_UTILITY_VM_INIT_SOCKET_FD)) < 0);
1501
1502 THROW_LAST_ERROR_IF(SetCloseOnExec(SocketFd, true));
1503 SocketFd = LX_INIT_UTILITY_VM_INIT_SOCKET_FD;
1504 }
1505 else
1506 {
1507
1508 //
1509 // Remove the CLOEXEC flag since this fd is to be passed down to init.
1510 //
1511
1512 THROW_LAST_ERROR_IF(SetCloseOnExec(SocketFd, false));
1513 }
1514
1515 //
1516 // Move the cross-distro shared mount to a temporary location. This mount
1517 // will be moved by the distro init.
1518 //
1519
1520 bool readOnly = false;
1521 try
1522 {
1523 AddTemporaryMount(LX_WSL2_CROSS_DISTRO_ENV, CROSS_DISTRO_SHARE_PATH, (MS_MOVE | MS_REC));
1524 }
1525 catch (...)
1526 {
1527 //
1528 // Creating the temporary mount can fail if:
1529 // - The distro VHD was mounted read-only (because a fsck is needed)
1530 // - The distro VHD is full
1531 //
1532 // Mount a writable overlay if that's the case so the distro can start.
1533 //
1534
1535 LOG_WARNING("Detected read-only or full filesystem. Adding a tmpfs overlay");
1536
1537 const std::string tmpfsTarget = std::format("{}-rw", Target);
1538 THROW_LAST_ERROR_IF(UtilMkdir(Target, 0755) < 0);
1539
1540 THROW_LAST_ERROR_IF(UtilMountOverlayFs(tmpfsTarget.c_str(), Target) < 0);
1541 THROW_LAST_ERROR_IF(mount(tmpfsTarget.c_str(), Target, NULL, MS_BIND, NULL) < 0);
1542
1543 AddTemporaryMount(LX_WSL2_CROSS_DISTRO_ENV, CROSS_DISTRO_SHARE_PATH, (MS_MOVE | MS_REC));
1544 readOnly = true;
1545 AddEnvironmentVariable(LX_WSL2_DISTRO_READ_ONLY_ENV, "1");
1546 }
1547
1548 //
1549 // If GUI support is enabled, move the WSLg shared mount to a temporary
1550 // location. This mount will be moved by the distro init.
1551 //
1552
1553 if (EnableGuiApps)
1554 {
1555 AddTemporaryMount(LX_WSL2_SYSTEM_DISTRO_SHARE_ENV, WSLG_PATH, (MS_MOVE | MS_REC));
1556 }
1557
1558 //
1559 // Add other environment variables.
1560 //
1561
1562 //
1563 // Init needs to know its pid relative to the root pid namespace.
1564 // Since the root namespace /proc is still mounted, it can be recovered by /proc/self.
1565 //
1566
1567 auto pid = std::filesystem::read_symlink(PROCFS_PATH "/self");
1568
1569 AddEnvironmentVariable(LX_WSL_PID_ENV, pid.c_str());
1570 AddEnvironmentVariable(LX_WSL2_VM_ID_ENV, VmId);
1571 AddEnvironmentVariable(LX_WSL2_DISTRO_NAME_ENV, DistributionName);
1572 AddEnvironmentVariable(LX_WSL2_SHARED_MEMORY_OB_DIRECTORY, SharedMemoryRoot);
1573 AddEnvironmentVariable(LX_WSL2_INSTALL_PATH, InstallPath);
1574 AddEnvironmentVariable(LX_WSL2_USER_PROFILE, UserProfile);
1575 AddEnvironmentVariable(LX_WSL2_NETWORKING_MODE_ENV, std::to_string(static_cast<int>(Config.NetworkingMode)).c_str());
1576 AddEnvironmentVariable(LX_WSL2_DISTRO_CGROUP_PATH, DistroCgroupPath);
1577
1578 if (DistroInitPid.has_value())
1579 {
1580 AddEnvironmentVariable(LX_WSL2_DISTRO_INIT_PID, std::to_string(static_cast<int>(DistroInitPid.value())).c_str());
1581 }
1582
1583 if (Config.EnableSafeMode)
1584 {
1585 AddEnvironmentVariable(LX_WSL2_SAFE_MODE, c_trueString);
1586 }
1587
1588 //
1589 // If GPU support is enabled, move the GPU share mounts to temporary
1590 // mount points inside the distro. These will be moved by the distro init
1591 // process, or unmounted if GPU support is disabled via /etc/wsl.conf.
1592 //
1593
1594 if (Config.EnableGpuSupport)
1595 {
1596 std::string Lower = GPU_SHARE_LIB_PACKAGED;
1597 if (Config.EnableInboxGpuLibs)
1598 {
1599 Lower += std::format(":{}", GPU_SHARE_LIB_INBOX);
1600 }
1601
1602 THROW_LAST_ERROR_IF(UtilMountOverlayFs(GPU_SHARE_LIB, Lower.c_str(), (MS_NOATIME | MS_NOSUID | MS_NODEV), c_defaultRetryTimeout) < 0);
1603
1604 for (int ShareIndex = 0; ShareIndex < COUNT_OF(g_gpuShares); ShareIndex += 1)
1605 {
1606 auto SharePath = std::format("{}{}", GPU_SHARE_PREFIX, g_gpuShares[ShareIndex].Name);
1607 auto ShareVariable = std::format("{}{}", LX_WSL2_GPU_SHARE_ENV, g_gpuShares[ShareIndex].Name);
1608 AddTemporaryMount(ShareVariable.c_str(), SharePath.c_str(), MS_MOVE);
1609 }
1610 }
1611
1612 //
1613 // If kernel modules are supported, move the mount to a temporary location.
1614 // This mount will be moved by the distro init.
1615 //
1616
1617 if (!Config.KernelModulesPath.empty())
1618 {
1619 AddTemporaryMount(LX_WSL2_KERNEL_MODULES_MOUNT_ENV, Config.KernelModulesPath.c_str(), (MS_MOVE | MS_REC));
1620 AddEnvironmentVariable(LX_WSL2_KERNEL_MODULES_PATH_ENV, Config.KernelModulesPath.c_str());
1621 }
1622
1623 //
1624 // Bind mount the init daemon into the distro namespace.
1625 //
1626
1627 auto Path = std::format("{}{}", Target, LX_INIT_PATH);
1628 THROW_LAST_ERROR_IF(MountInit(Path.c_str()) < 0);
1629
1630 if (readOnly)
1631 {
1632 //
1633 // If a rw overlay was added, mark it as read-only.
1634 //
1635
1636 THROW_LAST_ERROR_IF(mount(nullptr, Target, nullptr, MS_REMOUNT | MS_RDONLY, nullptr) < 0);
1637 }
1638
1639 //
1640 // Change the root of the calling process to the distro mountpoint.
1641 //
1642
1643 THROW_LAST_ERROR_IF(Chroot(Target) < 0);
1644
1645 //
1646 // Exec the init daemon.
1647 //
1648
1649 std::vector<char*> Environment;
1650 for (auto& e : Variables)
1651 {
1652 Environment.emplace_back(e.data());
1653 }
1654
1655 assert(Environment.size() == Variables.size());
1656
1657 Environment.push_back(nullptr);
1658
1659 execle(LX_INIT_PATH, LX_INIT_PATH, nullptr, Environment.data());
1660 LOG_ERROR("execle({}) failed {}", LX_INIT_PATH, errno);
1661 _exit(1);
1662 }
1663
1664 void LaunchSystemDistro(
1665 int SocketFd,
1666 const char* Target,
1667 const VmConfiguration& Config,
1668 const char* VmId,
1669 const char* DistributionName,
1670 const char* SharedMemoryRoot,
1671 const char* InstallPath,
1672 const char* UserProfile,
1673 pid_t DistroInitPid,
1674 const char* DistroCgroupPath)
1675
1676 /*++
1677
1678 Routine Description:
1679
1680 This routine launches the system distro.
1681
1682 Arguments:
1683
1684 SocketFd - Supplies a file descriptor to communicate with the init daemon.
1685 This routine takes ownership of this file descriptor.
1686
1687 Target - Supplies the location where the distro filesystem is mounted.
1688
1689 VmConfiguration - Supplies the VM configuration.
1690
1691 VmId - Supplies the GUID of the VM. If this value is a non-empty string it
1692 is passed to init as an environment variable.
1693
1694 DistributionName - Supplies the name of the distribution. If this value is a
1695 non-empty string it is passed to init as an environment variable.
1696
1697 SharedMemoryRoot - Supplies the Windows OB path for virtiofs shared memory.
1698 If this value is a non-empty string, it is passed to init as an
1699 environment variable.
1700
1701 InstallPath - Supplies the Windows path for the location where the lifted
1702 WSL package is installed. If this value is a non-empty string, it is
1703 passed to init as an environment variable.
1704
1705 UserProfile - Supplies the Windows path for user profile of the VM owner.
1706 If this value is a non-empty string, it is passed to init as an
1707 environment variable.
1708
1709 DistroInitPid - Supplies the pid of the user distribution's init process.
1710
1711 DistroCgroupPath - Supplies the cgroup path of this distribution.
1712
1713 Return Value:
1714
1715 None. This method does not return.
1716
1717 --*/
1718
1719 try
1720 {
1721 //
1722 // Create a writable layer on top of the read-only vhd.
1723 //
1724
1725 THROW_LAST_ERROR_IF(UtilMountOverlayFs(Target, SYSTEM_DISTRO_VHD_PATH) < 0);
1726
1727 //
1728 // Launch the init daemon, this method does not return.
1729 //
1730
1731 LaunchInit(SocketFd, Target, true, Config, VmId, DistributionName, SharedMemoryRoot, InstallPath, UserProfile, DistroInitPid, DistroCgroupPath);
1732 _exit(1);
1733 }
1734 catch (...)
1735 {
1736 LOG_CAUGHT_EXCEPTION();
1737 _exit(1);
1738 }
1739
1740 std::set<pid_t> ListInitChildProcesses()
1741 {
1742 std::set<pid_t> children;
1743
1744 auto content = wsl::shared::string::ReadFile<char>("/proc/self/task/1/children");
1745
1746 for (const auto& e : wsl::shared::string::Split<char>(content, ' '))
1747 {
1748 children.insert(std::stoul(e, nullptr, 10));
1749 }
1750
1751 return children;
1752 }
1753
1754 std::vector<unsigned int> ListScsiDisks()
1755 {
1756 std::vector<unsigned int> disks;
1757
1758 for (const auto& e : std::filesystem::directory_iterator(SCSI_DEVICE_PATH))
1759 {
1760 auto filename = e.path().filename().string();
1761 if (filename.find(SCSI_DEVICE_NAME_PREFIX) == 0)
1762 {
1763 try
1764 {
1765 disks.emplace_back(std::stoul(filename.substr(strlen(SCSI_DEVICE_NAME_PREFIX))));
1766 }
1767 CATCH_LOG();
1768 }
1769 }
1770
1771 return disks;
1772 }
1773
1774 void LogException(const char* Message, const char* Description) noexcept
1775
1776 /*++
1777
1778 Routine Description:
1779
1780 Callback to log exception information.
1781
1782 Arguments:
1783
1784 Message - Supplies the message to log.
1785
1786 Description - Supplies the exception description.
1787
1788 Return Value:
1789
1790 None.
1791
1792 --*/
1793
1794 {
1795 if (Message)
1796 {
1797 dprintf(g_LogFd, "<3>WSL (%d) ERROR: %s %s", getpid(), Message, Description);
1798 }
1799 else
1800 {
1801 dprintf(g_LogFd, "<3>WSL (%d) ERROR: %s", getpid(), Description);
1802 }
1803 }
1804
1805 int MountDevice(LX_MINI_INIT_MOUNT_DEVICE_TYPE DeviceType, unsigned int DeviceId, const char* Target, const char* FsType, unsigned int Flags, const char* Options)
1806
1807 /*++
1808
1809 Routine Description:
1810
1811 This routine mounts the specified device.
1812
1813 Arguments:
1814
1815 DeviceType - Supplies the type of device to mount.
1816
1817 DeviceId - Supplies identifier for the SCSI or pmem device to mount.
1818
1819 Target - Supplies the target of the mount.
1820
1821 FsType - Supplies the filesystem type.
1822
1823 Flags - Supplies flags for the operation.
1824
1825 Options - Supplies mount options.
1826
1827 Return Value:
1828
1829 0 on success, < 0 on failure.
1830
1831 --*/
1832
1833 try
1834 {
1835 //
1836 // Build the /dev path of the device.
1837 //
1838
1839 std::string DevicePath;
1840 switch (DeviceType)
1841 {
1842 case LxMiniInitMountDeviceTypeLun:
1843 DevicePath = GetLunDevicePath(DeviceId);
1844 break;
1845
1846 case LxMiniInitMountDeviceTypePmem:
1847 DevicePath = std::format("{}/pmem{}", DEVFS_PATH, DeviceId);
1848 break;
1849
1850 default:
1851 LOG_ERROR("Unexpected DeviceType {}", DeviceType);
1852 return -EINVAL;
1853 }
1854
1855 //
1856 // Mount to a temporary location if overlayfs was requested; otherwise, mount
1857 // the device directly on the target.
1858 //
1859
1860 std::string MountPoint;
1861 if (Flags & LxMiniInitMessageFlagCreateOverlayFs)
1862 {
1863 if (CreateTempDirectory(Target, MountPoint) < 0)
1864 {
1865 return -1;
1866 }
1867 }
1868 else
1869 {
1870 MountPoint += Target;
1871 }
1872
1873 //
1874 // Perform the mount.
1875 //
1876
1877 const unsigned long MountFlags = (Flags & LxMiniInitMessageFlagMountReadOnly) ? MS_RDONLY : 0;
1878 if (UtilMount(DevicePath.c_str(), MountPoint.c_str(), FsType, MountFlags, Options, c_defaultRetryTimeout) < 0)
1879 {
1880 return -1;
1881 }
1882
1883 //
1884 // Create an overlayfs mount for a read/write layer if requested.
1885 //
1886
1887 if (Flags & LxMiniInitMessageFlagCreateOverlayFs)
1888 {
1889 if (UtilMountOverlayFs(Target, MountPoint.c_str()) < 0)
1890 {
1891 return -1;
1892 }
1893 }
1894
1895 return 0;
1896 }
1897 CATCH_RETURN_ERRNO()
1898
1899 int MountPlan9(const char* Name, const char* Target, bool ReadOnly, std::optional<int> BufferSize)
1900
1901 /*++
1902
1903 Routine Description:
1904
1905 This routine will mount a 9p share.
1906
1907 Arguments:
1908
1909 Name - Supplies the aname of the 9p share to mount.
1910
1911 Target - Supplies the mount target.
1912
1913 ReadOnly - Supplies a boolean specifying if the share should be mounted as read-only.
1914
1915 BufferSize - Optionally supplies a buffer size to use for the hvsocket send / receive buffers and 9p msize.
1916
1917 Return Value:
1918
1919 0 on success, -1 on failure.
1920
1921 --*/
1922
1923 try
1924 {
1925 int Size = BufferSize.value_or(LX_INIT_UTILITY_VM_PLAN9_BUFFER_SIZE);
1926 wil::unique_fd Fd{UtilConnectVsock(LX_INIT_UTILITY_VM_PLAN9_PORT, true, Size)};
1927 if (!Fd)
1928 {
1929 return -1;
1930 }
1931
1932 unsigned long Flags = MS_NOATIME | MS_NOSUID | MS_NODEV;
1933 auto Options = std::format("msize={},trans=fd,rfdno={},wfdno={},cache=mmap,aname={}", Size, Fd.get(), Fd.get(), Name);
1934 if (ReadOnly)
1935 {
1936 WI_SetFlag(Flags, MS_RDONLY);
1937 Options += ";fmask=222;dmask=222";
1938 }
1939
1940 return UtilMount(Name, Target, PLAN9_FS_TYPE, Flags, Options.c_str(), c_defaultRetryTimeout);
1941 }
1942 CATCH_RETURN_ERRNO()
1943
1944 int MountSystemDistro(LX_MINI_INIT_MOUNT_DEVICE_TYPE DeviceType, unsigned int DeviceId)
1945
1946 /*++
1947
1948 Routine Description:
1949
1950 This routine mounts the system distro as read-only, creates a writable
1951 tmpfs layer using overlayfs, and chroots to the mount point.
1952
1953 Arguments:
1954
1955 DeviceType - Supplies the type of device to mount.
1956
1957 DeviceId - Supplies identifier for the SCSI or pmem device to mount.
1958
1959 Return Value:
1960
1961 0 on success, < 0 on failure.
1962
1963 --*/
1964
1965 {
1966 //
1967 // Mount the system distro device as read-only.
1968 //
1969
1970 const unsigned int Flags = LxMiniInitMessageFlagMountReadOnly;
1971 auto* Options = (DeviceType == LxMiniInitMountDeviceTypePmem) ? "dax" : nullptr;
1972 if (MountDevice(DeviceType, DeviceId, SYSTEM_DISTRO_VHD_PATH, "ext4", Flags, Options) < 0)
1973 {
1974 return -1;
1975 }
1976
1977 //
1978 // Create a read / write overlay layer.
1979 //
1980
1981 if (UtilMountOverlayFs(SYSTEM_DISTRO_PATH, SYSTEM_DISTRO_VHD_PATH) < 0)
1982 {
1983 return -1;
1984 }
1985
1986 //
1987 // Move the devtmpfs, procfs, sysfs and system distro vhd mounts before chrooting.
1988 //
1989
1990 for (const auto* Source : {DEVFS_PATH, PROCFS_PATH, SYSFS_PATH, SYSTEM_DISTRO_VHD_PATH})
1991 {
1992 auto Target = std::format("{}{}", SYSTEM_DISTRO_PATH, Source);
1993 if (UtilMount(Source, Target.c_str(), nullptr, (MS_MOVE | MS_REC), nullptr) < 0)
1994 {
1995 return -1;
1996 }
1997 }
1998
1999 //
2000 // Create a bind mount of WSL init.
2001 //
2002
2003 if (MountInit(SYSTEM_DISTRO_PATH LX_INIT_PATH) < 0)
2004 {
2005 return -1;
2006 }
2007
2008 //
2009 // Chroot to system distro mount point.
2010 //
2011 // N.B. This allows running binaries present in the system distro without having to chroot.
2012 //
2013
2014 return Chroot(SYSTEM_DISTRO_PATH);
2015 }
2016
2017 std::map<unsigned long, std::string> ListDiskPartitions(const std::string& DeviceName, std::optional<unsigned long> SearchForIndex)
2018
2019 /*++
2020
2021 Routine Description:
2022
2023 This routine returns the list of partitions in a block device.
2024
2025 Arguments:
2026
2027 DeviceName - Supplies the block device name.
2028
2029 SearchForIndex - Supplies a partition index to search for.
2030
2031 Return Value:
2032
2033 A map from partition index to device name.
2034
2035 --*/
2036
2037 {
2038 std::string DevicePath = std::format("/sys/block/{}", DeviceName);
2039
2040 return wsl::shared::retry::RetryWithTimeout<std::map<unsigned long, std::string>>(
2041 [&]() {
2042 wil::unique_dir Dir{opendir(DevicePath.c_str())};
2043 THROW_LAST_ERROR_IF(!Dir);
2044
2045 std::map<unsigned long, std::string> partitions;
2046
2047 for (auto Entry = readdir64(Dir.get()); Entry != nullptr; Entry = readdir64(Dir.get()))
2048 {
2049 if ((Entry->d_type != DT_DIR) || (strstr(Entry->d_name, DeviceName.c_str()) != Entry->d_name))
2050 {
2051 continue;
2052 }
2053
2054 partitions.emplace(GetDiskPartitionIndex(DevicePath.c_str(), Entry->d_name), Entry->d_name);
2055 }
2056
2057 THROW_ERRNO_IF(ENOENT, SearchForIndex.has_value() && partitions.find(SearchForIndex.value()) == partitions.end());
2058
2059 return partitions;
2060 },
2061 c_defaultRetryPeriod,
2062 c_defaultRetryTimeout,
2063 []() {
2064 auto err = wil::ResultFromCaughtException();
2065 return err == ENOENT || err == ENXIO;
2066 });
2067 }
2068
2069 int MountDiskPartition(const char* DevicePath, const char* Type, const char* Target, unsigned long Flags, const char* Options, size_t PartitionIndex, PLX_MINI_MOUNT_STEP Step)
2070
2071 /*++
2072
2073 Routine Description:
2074
2075 Mount a disk partition with a timeout.
2076
2077 Arguments:
2078
2079 DevicePath - Path of the device to mount.
2080
2081 Type - The filesystem to use.
2082
2083 Target - The mount target.
2084
2085 Flags - The mount flags.
2086
2087 Options - The mount options.
2088
2089 PartitionIndex - The partition to mount.
2090
2091 Step - Pointer to update the current mount step.
2092
2093 Return Value:
2094
2095 0 on success, < 0 on failure.
2096
2097 --*/
2098
2099 try
2100 {
2101 *Step = LxMiniInitMountStepFindPartition;
2102 if (!wsl::shared::string::StartsWith(DevicePath, DEVFS_PATH "/"))
2103 {
2104 LOG_ERROR("unexpected device path {}", DevicePath);
2105 return -1;
2106 }
2107
2108 auto* DeviceName = &DevicePath[sizeof(DEVFS_PATH)];
2109
2110 //
2111 // Find the partition on the specified device.
2112 //
2113 // N.B. A retry is needed because there is a delay between when a device is
2114 // hot-added, and when the device is available in the guest.
2115 //
2116
2117 auto partitions = ListDiskPartitions(DeviceName, PartitionIndex);
2118
2119 auto partition = partitions.find(PartitionIndex);
2120
2121 THROW_ERRNO_IF(ENOENT, partition == partitions.end());
2122
2123 std::string partitionPath = std::format("/dev/{}", partition->second);
2124 LOG_INFO("Mapped partition {} from device {} to {}", PartitionIndex, DeviceName, partitionPath);
2125
2126 //
2127 // Detect the filesystem type.
2128 //
2129
2130 *Step = LxMiniInitMountStepDetectFilesystem;
2131 std::string DetectedFilesystem;
2132 if (Type == nullptr)
2133 {
2134 if (DetectFilesystem(partitionPath.c_str(), DetectedFilesystem) < 0)
2135 {
2136 return -1;
2137 }
2138
2139 Type = DetectedFilesystem.c_str();
2140 }
2141
2142 *Step = LxMiniInitMountStepMount;
2143 return UtilMount(partitionPath.c_str(), Target, Type, Flags, Options, c_defaultRetryTimeout);
2144 }
2145 CATCH_RETURN()
2146
2147 int MountInit(const char* Target)
2148
2149 /*++
2150
2151 Routine Description:
2152
2153 This routine create a read-only bind mount of the init daemon at the specified target.
2154
2155 Arguments:
2156
2157 Target - Supplies the target for the mount.
2158
2159 Return Value:
2160
2161 0 on success, < 0 on failure.
2162
2163 --*/
2164
2165 try
2166 {
2167 THROW_LAST_ERROR_IF(unlink(Target) < 0 && errno != ENOENT);
2168
2169 wil::unique_fd InitFd{open(Target, (O_CREAT | O_EXCL | O_WRONLY), 0755)};
2170 THROW_LAST_ERROR_IF(!InitFd);
2171
2172 THROW_LAST_ERROR_IF(mount(LX_INIT_PATH, Target, nullptr, (MS_RDONLY | MS_BIND), nullptr) < 0);
2173
2174 THROW_LAST_ERROR_IF(mount(nullptr, Target, nullptr, (MS_RDONLY | MS_REMOUNT | MS_BIND), nullptr) < 0);
2175
2176 return 0;
2177 }
2178 CATCH_RETURN_ERRNO()
2179
2180 std::string GetMountTarget(const char* Name)
2181
2182 /*++
2183
2184 Routine Description:
2185
2186 Generate the path to a mount target.
2187
2188 Arguments:
2189
2190 Name - Supplies the mount name.
2191
2192 Target - The buffer receiving the mountpoint target.
2193
2194 Return Value:
2195
2196 0 on success, < 0 on failure.
2197
2198 --*/
2199
2200 {
2201 return std::format("{}/{}", CROSS_DISTRO_SHARE_PATH, Name);
2202 }
2203
2204 void ProcessLaunchInitMessage(
2205 const LX_MINI_INIT_MESSAGE* Message,
2206 gsl::span<gsl::byte> Buffer,
2207 wsl::shared::SocketChannel&& Channel,
2208 wil::unique_fd&& SystemDistroSocketFd,
2209 const VmConfiguration& Config)
2210 {
2211 //
2212 // Send a message back to the service that contains the pid of the child process.
2213 // If the distribution terminates unexpectedly, this pid will be sent to the service so it knows that the instance
2214 // has terminated.
2215 //
2216
2217 LX_MINI_CREATE_INSTANCE_STEP Step = LxInitCreateInstanceStepMountDisk;
2218
2219 auto ReportStatus = [&Channel, &Step](auto result) {
2220 LX_MINI_INIT_CREATE_INSTANCE_RESULT message{};
2221 message.Header.MessageType = LxMiniInitMessageCreateInstanceResult;
2222 message.Header.MessageSize = sizeof(message);
2223 message.FailureStep = Step;
2224 message.Result = result;
2225
2226 Channel.SendMessage(message);
2227 };
2228
2229 try
2230 {
2231 auto* FsType = wsl::shared::string::FromSpan(Buffer, Message->FsTypeOffset);
2232 auto* MountOptions = wsl::shared::string::FromSpan(Buffer, Message->MountOptionsOffset);
2233
2234 //
2235 // Mount the device.
2236 //
2237
2238 THROW_LAST_ERROR_IF(MountDevice(Message->MountDeviceType, Message->DeviceId, DISTRO_PATH, FsType, Message->Flags, MountOptions) < 0);
2239
2240 auto MiniInitDirectChildPidPath = std::filesystem::read_symlink(PROCFS_PATH "/self");
2241 pid_t MiniInitDirectChildPid = std::stoul(MiniInitDirectChildPidPath.string());
2242
2243 bool bootInit = false;
2244 bool enableGuiApps = Config.EnableGuiApps;
2245 {
2246 wil::unique_file File{fopen(DISTRO_PATH ETC_PATH "/wsl.conf", "r")};
2247 if (File)
2248 {
2249 std::vector<ConfigKey> ConfigKeys = {ConfigKey("boot.systemd", bootInit), ConfigKey("general.guiApplications", enableGuiApps)};
2250 ParseConfigFile(ConfigKeys, File.get(), CFG_SKIP_UNKNOWN_VALUES, STRING_TO_WSTRING(CONFIG_FILE));
2251 }
2252 }
2253
2254 //
2255 // Set up the per-distro cgroup before potentially forking into two inits.
2256 //
2257
2258 std::string DistroCgroupPath{};
2259 if (access(WSL_USER_CGROUP_PATH, F_OK) == 0)
2260 {
2261 DistroCgroupPath = UtilGetDistroCgroupPath(MiniInitDirectChildPid);
2262
2263 auto cleanup = wil::scope_exit([&]() {
2264 rmdir((DistroCgroupPath + WSL_USER_NON_SYSTEMD_CGROUP_DIR).c_str());
2265 rmdir((DistroCgroupPath + WSL_USER_SYSTEMD_CGROUP_DIR).c_str());
2266 rmdir(DistroCgroupPath.c_str());
2267 DistroCgroupPath.clear();
2268 });
2269
2270 try
2271 {
2272 THROW_LAST_ERROR_IF(UtilMkdir(DistroCgroupPath.c_str(), 0755) < 0);
2273
2274 if (bootInit)
2275 {
2276 THROW_LAST_ERROR_IF(UtilEnableAllCgroupControllers(DistroCgroupPath) < 0);
2277 THROW_LAST_ERROR_IF(UtilMkdir((DistroCgroupPath + WSL_USER_SYSTEMD_CGROUP_DIR).c_str(), 0755) < 0);
2278 THROW_LAST_ERROR_IF(UtilMkdir((DistroCgroupPath + WSL_USER_NON_SYSTEMD_CGROUP_DIR).c_str(), 0755) < 0);
2279 }
2280
2281 cleanup.release();
2282 }
2283 CATCH_LOG();
2284 }
2285
2286 //
2287 // Allow /etc/wsl.conf in the user distro to opt-out of GUI support.
2288 //
2289 // N.B. A connection for the system distro must established even if the distro opts out
2290 // of GUI app support because WslService is waiting to accept a connection.
2291 //
2292
2293 if (Message->Flags & LxMiniInitMessageFlagLaunchSystemDistro && Config.EnableGuiApps)
2294 {
2295 Step = LxInitCreateInstanceStepLaunchSystemDistro;
2296
2297 //
2298 // If the distro did not opt-out of GUI applications, continue launching the system distro.
2299 //
2300
2301 if (enableGuiApps)
2302 {
2303 //
2304 // Create a tmpfs mount for a shared folder between user and system distro.
2305 //
2306
2307 THROW_LAST_ERROR_IF(UtilMount(nullptr, WSLG_PATH, "tmpfs", MS_SHARED, nullptr) < 0);
2308
2309 //
2310 // Create a directory to store x11 sockets.
2311 //
2312 // N.B. This needs to be created early so a bind mount into the shared WSLg location
2313 // can be created on top of the hard-coded location expected by x11 clients.
2314 //
2315
2316 THROW_LAST_ERROR_IF(UtilMkdir(WSLG_PATH "/" X11_SOCKET_NAME, 0777) < 0);
2317
2318 //
2319 // Create a read-only bind mount of the user distro into the shared WSLg folder so fonts and icons can be accessed.
2320 //
2321
2322 THROW_LAST_ERROR_IF(UtilMount(DISTRO_PATH, WSLG_PATH DISTRO_PATH, nullptr, (MS_BIND | MS_RDONLY), nullptr) < 0);
2323
2324 THROW_LAST_ERROR_IF(UtilMount(nullptr, WSLG_PATH DISTRO_PATH, nullptr, (MS_RDONLY | MS_REMOUNT | MS_BIND), nullptr) < 0);
2325
2326 //
2327 // Create a child process in a new mount, pid, and UTS namespace (with a shared IPC namespace).
2328 // This child process will become the user distro init daemon.
2329 //
2330
2331 auto ChildPid = CLONE(CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | SIGCHLD);
2332 THROW_LAST_ERROR_IF(ChildPid < 0);
2333
2334 if (ChildPid > 0)
2335 {
2336 //
2337 // Close the socket for the user distro and launch the system
2338 // distro. This method does not return.
2339 //
2340
2341 Channel.Close();
2342
2343 LaunchSystemDistro(
2344 SystemDistroSocketFd.get(),
2345 SYSTEM_DISTRO_PATH,
2346 Config,
2347 wsl::shared::string::FromSpan(Buffer, Message->VmIdOffset),
2348 wsl::shared::string::FromSpan(Buffer, Message->DistributionNameOffset),
2349 wsl::shared::string::FromSpan(Buffer, Message->SharedMemoryRootOffset),
2350 wsl::shared::string::FromSpan(Buffer, Message->InstallPathOffset),
2351 wsl::shared::string::FromSpan(Buffer, Message->UserProfileOffset),
2352 ChildPid,
2353 DistroCgroupPath.empty() ? nullptr : DistroCgroupPath.c_str());
2354 }
2355 }
2356
2357 SystemDistroSocketFd.reset();
2358 }
2359
2360 //
2361 // Launch the distro init daemon, this method does not return.
2362 //
2363
2364 Step = LxInitCreateInstanceStepLaunchInit;
2365 LaunchInit(
2366 Channel.Socket(),
2367 DISTRO_PATH,
2368 enableGuiApps,
2369 Config,
2370 wsl::shared::string::FromSpan(Buffer, Message->VmIdOffset),
2371 wsl::shared::string::FromSpan(Buffer, Message->DistributionNameOffset),
2372 nullptr,
2373 wsl::shared::string::FromSpan(Buffer, Message->InstallPathOffset),
2374 wsl::shared::string::FromSpan(Buffer, Message->UserProfileOffset),
2375 std::nullopt,
2376 DistroCgroupPath.empty() ? nullptr : DistroCgroupPath.c_str());
2377 }
2378 catch (...)
2379 {
2380 LOG_CAUGHT_EXCEPTION();
2381 ReportStatus(wil::ResultFromCaughtException());
2382 _exit(1);
2383 }
2384 }
2385
2386 void PostProcessImportedDistribution(wsl::shared::MessageWriter<LX_MINI_INIT_IMPORT_RESULT>& Message, const char* ExtractedPath)
2387 {
2388 //
2389 // Save the current working directory as a file descriptor so it can be restored.
2390 //
2391
2392 wil::unique_fd cwdFd{open(".", O_RDONLY | O_DIRECTORY)};
2393 THROW_LAST_ERROR_IF(!cwdFd);
2394
2395 auto restoreCwd = wil::scope_exit([&cwdFd]() {
2396 THROW_LAST_ERROR_IF(fchdir(cwdFd.get()) < 0);
2397 THROW_LAST_ERROR_IF(chroot(".") < 0);
2398 });
2399
2400 //
2401 // Chroot to the extracted path to validate distro contents.
2402 //
2403 // N.B. The chroot is needed because the distro may contain absolute symlinks (for example, /bin/sh may symlink to /bin/toolbox).
2404 //
2405
2406 THROW_LAST_ERROR_IF(chdir(ExtractedPath) < 0);
2407 THROW_LAST_ERROR_IF(chroot(".") < 0);
2408
2409 Message->ValidDistribution = false;
2410
2411 for (auto* path : {"/etc", "/bin/sh"})
2412 {
2413 if (access(path, F_OK) >= 0)
2414 {
2415 Message->ValidDistribution = true;
2416 }
2417 }
2418
2419 if (!Message->ValidDistribution)
2420 {
2421 return;
2422 }
2423
2424 auto [flavor, version] = UtilReadFlavorAndVersion("/etc/os-release");
2425
2426 if (flavor.has_value())
2427 {
2428 Message.WriteString(Message->FlavorIndex, flavor.value());
2429 }
2430
2431 if (version.has_value())
2432 {
2433 Message.WriteString(Message->VersionIndex, version.value());
2434 }
2435
2436 std::string defaultName{};
2437 std::string shortcutIconPath;
2438 std::string terminalProfileTemplatePath;
2439 Message->GenerateTerminalProfile = true;
2440 Message->GenerateShortcut = true;
2441
2442 std::vector<ConfigKey> keys = {
2443 ConfigKey("shortcut.icon", shortcutIconPath),
2444 ConfigKey("shortcut.enabled", Message->GenerateShortcut),
2445 ConfigKey("oobe.defaultName", defaultName),
2446 ConfigKey("windowsterminal.profileTemplate", terminalProfileTemplatePath),
2447 ConfigKey("windowsterminal.enabled", Message->GenerateTerminalProfile)};
2448
2449 {
2450 wil::unique_file File{fopen(WSL_DISTRIBUTION_CONF, "r")};
2451 ParseConfigFile(keys, File.get(), CFG_SKIP_UNKNOWN_VALUES, STRING_TO_WSTRING(WSL_DISTRIBUTION_CONF));
2452 }
2453
2454 if (!defaultName.empty())
2455 {
2456 Message.WriteString(Message->DefaultNameIndex, defaultName);
2457 }
2458
2459 try
2460 {
2461 if (!shortcutIconPath.empty())
2462 {
2463 // Prevent escaping the distribution install path.
2464 if (shortcutIconPath.find("..") != std::string::npos)
2465 {
2466 LOG_ERROR("Invalid format for shortcut.icon: {}", shortcutIconPath.c_str());
2467 THROW_ERRNO(EINVAL);
2468 }
2469
2470 auto iconBuffer = UtilReadFileRaw(shortcutIconPath.c_str(), 1024 * 1024);
2471 gsl::copy(
2472 gsl::as_writable_bytes(gsl::make_span(iconBuffer)),
2473 Message.InsertBuffer(Message->ShortcutIconIndex, iconBuffer.size(), Message->ShortcutIconSize));
2474 }
2475 }
2476 CATCH_LOG();
2477
2478 try
2479 {
2480 if (Message->GenerateTerminalProfile && !terminalProfileTemplatePath.empty())
2481 {
2482 // Prevent escaping the distribution install path.
2483 if (terminalProfileTemplatePath.find("..") != std::string::npos)
2484 {
2485 LOG_ERROR("Invalid format for windows-terminal.profile_template: {}", terminalProfileTemplatePath.c_str());
2486 THROW_ERRNO(EINVAL);
2487 }
2488
2489 auto content = UtilReadFileRaw(terminalProfileTemplatePath.c_str(), 1024 * 1024);
2490 gsl::copy(
2491 gsl::as_writable_bytes(gsl::make_span(content)),
2492 Message.InsertBuffer(Message->TerminalProfileIndex, content.size(), Message->TerminalProfileSize));
2493 }
2494 }
2495 CATCH_LOG();
2496 }
2497
2498 void ProcessImportExportMessage(gsl::span<gsl::byte> Buffer, wsl::shared::SocketChannel&& Channel)
2499 {
2500 const LX_MINI_INIT_MESSAGE* Message{};
2501 sockaddr_vm ListenAddress{};
2502 wil::unique_fd ListenSocket;
2503 int Result = -1;
2504
2505 {
2506 auto ReportStatus = wil::scope_exit([&Channel, &Result, &ListenAddress]() {
2507 LX_MINI_INIT_CREATE_INSTANCE_RESULT message{};
2508 message.Header.MessageType = LxMiniInitMessageCreateInstanceResult;
2509 message.Header.MessageSize = sizeof(message);
2510 message.FailureStep = LxInitCreateInstanceStepMountDisk;
2511 message.Result = Result;
2512 message.ConnectPort = ListenAddress.svm_port;
2513 Channel.SendMessage(message);
2514 });
2515
2516 try
2517 {
2518 Message = gslhelpers::try_get_struct<LX_MINI_INIT_MESSAGE>(Buffer);
2519 THROW_ERRNO_IF(EINVAL, !Message);
2520
2521 ListenSocket = UtilListenVsockAnyPort(&ListenAddress, 2, true);
2522 THROW_LAST_ERROR_IF(!ListenSocket);
2523
2524 if (Message->Header.MessageType == LxMiniInitMessageImport)
2525 {
2526 THROW_LAST_ERROR_IF(FormatDevice(Message->DeviceId) < 0);
2527 }
2528
2529 auto* FsType = wsl::shared::string::FromSpan(Buffer, Message->FsTypeOffset);
2530 auto* MountOptions = wsl::shared::string::FromSpan(Buffer, Message->MountOptionsOffset);
2531 THROW_LAST_ERROR_IF(MountDevice(Message->MountDeviceType, Message->DeviceId, DISTRO_PATH, FsType, Message->Flags, MountOptions) < 0);
2532
2533 Result = 0;
2534 }
2535 catch (...)
2536 {
2537 Result = wil::ResultFromCaughtException();
2538 }
2539 }
2540
2541 if (Result < 0)
2542 {
2543 LOG_ERROR("ProcessImportExportMessage failed, {}", errno);
2544 return;
2545 }
2546
2547 Result = -1;
2548 auto ReportStatus = wil::scope_exit([&Channel, &Result, MessageType = Message->Header.MessageType]() {
2549 wsl::shared::MessageWriter<LX_MINI_INIT_IMPORT_RESULT> message;
2550
2551 if (MessageType != LxMiniInitMessageExport && Result == 0)
2552 {
2553 PostProcessImportedDistribution(message, DISTRO_PATH);
2554 }
2555
2556 sync();
2557
2558 if (umount(DISTRO_PATH) < 0)
2559 {
2560 LOG_ERROR("umount({}) failed, {}", DISTRO_PATH, errno);
2561 Result = -1;
2562 }
2563
2564 if (MessageType == LxMiniInitMessageExport)
2565 {
2566 if (UtilWriteBuffer(Channel.Socket(), &Result, sizeof(Result)) < 0)
2567 {
2568 LOG_ERROR("response write failed {}", errno);
2569 }
2570 }
2571 else
2572 {
2573 message->Result = Result;
2574 Channel.SendMessage<LX_MINI_INIT_IMPORT_RESULT>(message.Span());
2575 }
2576 });
2577
2578 wil::unique_fd DataSocket{UtilAcceptVsock(ListenSocket.get(), ListenAddress, SESSION_LEADER_ACCEPT_TIMEOUT_MS)};
2579 THROW_LAST_ERROR_IF(!DataSocket);
2580
2581 wil::unique_fd ErrorSocket{UtilAcceptVsock(ListenSocket.get(), ListenAddress, SESSION_LEADER_ACCEPT_TIMEOUT_MS)};
2582 THROW_LAST_ERROR_IF(!ErrorSocket);
2583
2584 switch (Message->Header.MessageType)
2585 {
2586 case LxMiniInitMessageImport:
2587 Result = ImportFromSocket(DISTRO_PATH, DataSocket.get(), ErrorSocket.get(), Message->Flags);
2588 break;
2589
2590 case LxMiniInitMessageExport:
2591 Result = ExportToSocket(DISTRO_PATH, DataSocket.get(), ErrorSocket.get(), Message->Flags);
2592 break;
2593
2594 case LxMiniInitMessageImportInplace:
2595 Result = 0;
2596 break;
2597
2598 default:
2599 LOG_ERROR("Unexpected message type {}", Message->Header.MessageType);
2600 }
2601 }
2602
2603 int ProcessMountFolderMessage(wsl::shared::Transaction& Transaction, gsl::span<gsl::byte> Buffer)
2604
2605 /*++
2606
2607 Routine Description:
2608
2609 Mount a filesystem as requested by the mount message
2610
2611 Arguments:
2612
2613 Buffer - Supplies the mount message.
2614
2615 Return Value:
2616
2617 0 on success, < 0 on failure.
2618
2619 --*/
2620
2621 {
2622 auto* Message = gslhelpers::try_get_struct<LX_MINI_INIT_MOUNT_FOLDER_MESSAGE>(Buffer);
2623 if (!Message)
2624 {
2625 LOG_ERROR("Unexpected message size {}", Buffer.size());
2626 return -1;
2627 }
2628
2629 const auto* Target = wsl::shared::string::FromSpan(Buffer, Message->PathIndex);
2630 const auto* Name = wsl::shared::string::FromSpan(Buffer, Message->NameIndex);
2631
2632 if (Target == nullptr || Name == nullptr)
2633 {
2634 LOG_ERROR("Invalid name or path index in LX_MINI_INIT_MOUNT_FOLDER_MESSAGE");
2635 return -1;
2636 }
2637
2638 int Result = MountPlan9(Name, Target, Message->ReadOnly);
2639 Transaction.SendResultMessage<int32_t>(Result);
2640 return 0;
2641 }
2642
2643 int ProcessMountMessage(gsl::span<gsl::byte> Buffer)
2644
2645 /*++
2646
2647 Routine Description:
2648
2649 Mount a filesystem as requested by the mount message
2650
2651 Arguments:
2652
2653 Buffer - Supplies the mount message.
2654
2655 Return Value:
2656
2657 0 on success, < 0 on failure.
2658
2659 --*/
2660
2661 {
2662 wil::unique_fd SocketFd{UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true)};
2663 if (!SocketFd)
2664 {
2665 return -1;
2666 }
2667
2668 const int ChildPid = UtilCreateChildProcess(
2669 "DiskMount", [Buffer, Channel = wsl::shared::SocketChannel{std::move(SocketFd), "MountResult"}]() mutable {
2670 // Set up a scope exit variable to report mount status.
2671 int Result = -1;
2672 LX_MINI_MOUNT_STEP Step = LxMiniInitMountStepFindDevice;
2673 auto ReportStatus = wil::scope_exit([&Channel, &Result, &Step]() { ReportMountStatus(Channel, Result, Step); });
2674
2675 auto* Header = gslhelpers::try_get_struct<MESSAGE_HEADER>(Buffer);
2676 if (!Header)
2677 {
2678 LOG_ERROR("Unexpected message size {}", Buffer.size());
2679 return;
2680 }
2681
2682 std::string Device;
2683 std::string DetectedFilesystem;
2684 std::string Target;
2685 if (Header->MessageType == LxMiniInitMessageMount)
2686 {
2687 auto* Message = gslhelpers::try_get_struct<LX_MINI_INIT_MOUNT_MESSAGE>(Buffer);
2688 if (!Message)
2689 {
2690 LOG_ERROR("Unexpected message size {}", Buffer.size());
2691 return;
2692 }
2693
2694 Device = GetLunDevicePath(Message->ScsiLun);
2695
2696 //
2697 // Construct the target of the mount.
2698 //
2699
2700 Target = GetMountTarget(wsl::shared::string::FromSpan(Buffer, Message->TargetNameOffset));
2701
2702 //
2703 // Determine the type of mount. If no type was specified, detect it with blkid.
2704 //
2705
2706 const auto* Type = wsl::shared::string::FromSpan(Buffer, Message->TypeOffset);
2707 if (*Type == '\0')
2708 {
2709 Type = nullptr;
2710 }
2711
2712 //
2713 // Parse the mount flags.
2714 //
2715
2716 auto* MountOptions = wsl::shared::string::FromSpan(Buffer, Message->OptionsOffset);
2717 auto ParsedOptions = mountutil::MountParseFlags(MountOptions == nullptr ? "" : MountOptions);
2718
2719 //
2720 // Perform the mount.
2721 //
2722
2723 if (Message->PartitionIndex == 0)
2724 {
2725 Step = LxMiniInitMountStepDetectFilesystem;
2726 if (Type == nullptr)
2727 {
2728 Result = DetectFilesystem(Device.c_str(), DetectedFilesystem);
2729 if (Result < 0)
2730 {
2731 return;
2732 }
2733
2734 Type = DetectedFilesystem.c_str();
2735 }
2736
2737 Step = LxMiniInitMountStepMount;
2738 Result = UtilMount(
2739 Device.c_str(), Target.c_str(), Type, ParsedOptions.MountFlags, ParsedOptions.StringOptions.c_str(), c_defaultRetryTimeout);
2740 }
2741 else
2742 {
2743 Result = MountDiskPartition(
2744 Device.c_str(),
2745 Type,
2746 Target.c_str(),
2747 ParsedOptions.MountFlags,
2748 ParsedOptions.StringOptions.c_str(),
2749 Message->PartitionIndex,
2750 &Step);
2751 }
2752 }
2753 else if (Header->MessageType == LxMiniInitMessageUnmount)
2754 {
2755 auto* Message = gslhelpers::try_get_struct<LX_MINI_INIT_UNMOUNT_MESSAGE>(Buffer);
2756 if (!Message)
2757 {
2758 LOG_ERROR("Unexpected message size {}", Buffer.size());
2759 return;
2760 }
2761
2762 Target = GetMountTarget(wsl::shared::string::FromMessageBuffer<LX_MINI_INIT_UNMOUNT_MESSAGE>(Buffer));
2763
2764 Step = LxMiniInitMountStepUnmount;
2765 Result = umount(Target.c_str());
2766 if (Result < 0)
2767 {
2768 Result = -errno;
2769 LOG_ERROR("umount({}) failed, {}", Target.c_str(), errno);
2770 return;
2771 }
2772
2773 Step = LxMiniInitMountStepRmDir;
2774 Result = rmdir(Target.c_str());
2775 if (Result < 0)
2776 {
2777 Result = -errno;
2778 LOG_ERROR("rmdir({}) failed, {}", Target.c_str(), errno);
2779 }
2780 }
2781 else
2782 {
2783 assert(Header->MessageType == LxMiniInitMessageDetach);
2784 auto* Message = gslhelpers::try_get_struct<LX_MINI_INIT_DETACH_MESSAGE>(Buffer);
2785 if (!Message)
2786 {
2787 LOG_ERROR("Unexpected message size {}", Buffer.size());
2788 return;
2789 }
2790
2791 Result = DetachScsiDisk(Message->ScsiLun);
2792 }
2793 });
2794
2795 return (ChildPid < 0) ? -1 : 0;
2796 }
2797
2798 int ReportMountStatus(wsl::shared::SocketChannel& Channel, int Result, LX_MINI_MOUNT_STEP Step)
2799
2800 /*++
2801
2802 Routine Description:
2803
2804 Report the result of a mount / unmount operation to via an hvsocket.
2805
2806 Arguments:
2807
2808 Channel - Supplies the socket channel.
2809
2810 Result - Supplies the operation result code.
2811
2812 Step - Supplies the step at which the mount operation failed, if any.
2813
2814 Return Value:
2815
2816 0 on success, < 0 on failure.
2817
2818 --*/
2819 try
2820 {
2821 LX_MINI_INIT_MOUNT_RESULT_MESSAGE Message{};
2822 Message.Header.MessageSize = sizeof(Message);
2823 Message.Header.MessageType = LxMiniInitMessageMountStatus;
2824 Message.Result = Result;
2825 Message.FailureStep = Step;
2826
2827 Channel.SendMessage(Message);
2828
2829 return 0;
2830 }
2831 CATCH_RETURN_ERRNO();
2832
2833 int ProcessWaitForPmemDeviceMessage(PLX_MINI_INIT_WAIT_FOR_PMEM_DEVICE_MESSAGE Message)
2834
2835 /*++
2836
2837 Routine Description:
2838
2839 This routine processes a message that waits for a pmem device to appear under /dev.
2840 The actual waiting is performed asynchronously.
2841
2842 Arguments:
2843
2844 Message - The wait for pmem device message
2845
2846 Return Value:
2847
2848 0 on success, < 0 on failure.
2849
2850 --*/
2851
2852 {
2853 wsl::shared::SocketChannel Channel{wil::unique_fd{UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true)}, "WaitForPmem"};
2854 if (Channel.Socket() < 0)
2855 {
2856 return -1;
2857 }
2858
2859 const int ChildPid = UtilCreateChildProcess("PMemDeviceWait", [&Channel, PmemId = Message->PmemId]() {
2860 int Result = -1;
2861 auto ReportStatus = wil::scope_exit([&Channel, &Result]() { Channel.SendResultMessage<int32_t>(Result); });
2862
2863 //
2864 // Construct the device path.
2865 //
2866
2867 std::string DevicePath = std::format("{}/pmem{}", DEVFS_PATH, PmemId);
2868
2869 //
2870 // Poll for the device to appear. Ideally we'd replace this with something
2871 // like libudev so we can be notified when devices appear.
2872 //
2873
2874 struct stat Buffer;
2875 wsl::shared::retry::RetryWithTimeout<void>(
2876 [&]() { THROW_LAST_ERROR_IF(stat(DevicePath.c_str(), &Buffer) < 0); },
2877 c_defaultRetryPeriod,
2878 c_defaultRetryTimeout,
2879 [&]() {
2880 Result = -wil::ResultFromCaughtException();
2881 return Result == -ENOENT;
2882 });
2883
2884 Result = 0;
2885 });
2886
2887 if (ChildPid < 0)
2888 {
2889 Channel.SendResultMessage<int32_t>(errno);
2890 return -1;
2891 }
2892
2893 return 0;
2894 }
2895
2896 int ProcessResizeDistributionMessage(gsl::span<gsl::byte> Buffer)
2897 try
2898 {
2899 auto* Message = gslhelpers::try_get_struct<LX_MINI_INIT_RESIZE_DISTRIBUTION_MESSAGE>(Buffer);
2900
2901 if (!Message)
2902 {
2903 LOG_ERROR("Unexpected message size {}", Buffer.size());
2904 return -1;
2905 }
2906
2907 wil::unique_fd SocketFd{UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true)};
2908 if (!SocketFd)
2909 {
2910 return -1;
2911 }
2912
2913 wil::unique_fd OutputSocketFd{UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true)};
2914 if (!OutputSocketFd)
2915 {
2916 return -1;
2917 }
2918
2919 const int ChildPid = UtilCreateChildProcess(
2920 "ResizeDistribution",
2921 [Message, Channel = wsl::shared::SocketChannel{std::move(SocketFd), "ResizeDistribution"}, OutputSocket = std::move(OutputSocketFd)]() mutable {
2922 int ResponseCode = -1;
2923 auto ReportStatus = wil::scope_exit([&]() {
2924 LX_MINI_INIT_RESIZE_DISTRIBUTION_RESPONSE ResponseMessage{};
2925 ResponseMessage.ResponseCode = ResponseCode;
2926 ResponseMessage.Header.MessageType = LxMiniInitMessageResizeDistributionResponse;
2927 ResponseMessage.Header.MessageSize = sizeof(ResponseMessage);
2928
2929 Channel.SendMessage(ResponseMessage);
2930 });
2931
2932 THROW_LAST_ERROR_IF(TEMP_FAILURE_RETRY(dup2(OutputSocket.get(), STDOUT_FILENO)) < 0);
2933 THROW_LAST_ERROR_IF(TEMP_FAILURE_RETRY(dup2(OutputSocket.get(), STDERR_FILENO)) < 0);
2934
2935 auto DevicePath = GetLunDevicePath(Message->ScsiLun);
2936
2937 auto CommandLine = std::format("/usr/sbin/e2fsck -f -y '{}'", DevicePath);
2938 THROW_LAST_ERROR_IF(UtilExecCommandLine(CommandLine.c_str()) < 0);
2939
2940 if (Message->NewSize == 0)
2941 {
2942 CommandLine = std::format("/usr/sbin/resize2fs '{}'", DevicePath);
2943 }
2944 else
2945 {
2946 CommandLine = std::format("/usr/sbin/resize2fs '{}' '{}K'", DevicePath, ((Message->NewSize + 1024) - 1) / 1024);
2947 }
2948
2949 THROW_LAST_ERROR_IF(UtilExecCommandLine(CommandLine.c_str()) < 0);
2950
2951 ResponseCode = 0;
2952 });
2953
2954 return (ChildPid < 0) ? -1 : 0;
2955 }
2956 CATCH_RETURN_ERRNO();
2957
2958 int ProcessTrimDistributionMessage(gsl::span<gsl::byte> Buffer)
2959 try
2960 {
2961 auto* Message = gslhelpers::try_get_struct<LX_MINI_INIT_TRIM_DISTRIBUTION_MESSAGE>(Buffer);
2962
2963 if (!Message)
2964 {
2965 LOG_ERROR("Unexpected message size {}", Buffer.size());
2966 return -1;
2967 }
2968
2969 wil::unique_fd SocketFd{UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true)};
2970 if (!SocketFd)
2971 {
2972 return -1;
2973 }
2974
2975 const int ChildPid = UtilCreateChildProcess(
2976 "TrimDistribution", [Message, Channel = wsl::shared::SocketChannel{std::move(SocketFd), "TrimDistribution"}]() mutable {
2977 int ResponseCode = -1;
2978 auto ReportStatus = wil::scope_exit([&]() {
2979 LX_MINI_INIT_TRIM_DISTRIBUTION_RESPONSE ResponseMessage{};
2980 ResponseMessage.ResponseCode = ResponseCode;
2981 ResponseMessage.Header.MessageType = LxMiniInitMessageTrimDistributionResponse;
2982 ResponseMessage.Header.MessageSize = sizeof(ResponseMessage);
2983
2984 Channel.SendMessage(ResponseMessage);
2985 });
2986
2987 const auto DevicePath = GetLunDevicePath(Message->ScsiLun);
2988
2989 //
2990 // Run a full offline filesystem check and discard the free blocks so the host can reclaim
2991 // them when the VHD is compacted. This mirrors the offline e2fsck used by
2992 // ResizeDistribution: it runs on the detached device without mounting it, and '-E discard'
2993 // issues the same block-discard requests that 'fstrim' would on a mounted filesystem.
2994 //
2995 // This is best-effort: a failure here must not prevent compaction, so the child logs the
2996 // error but still reports success.
2997 //
2998 const auto CommandLine = std::format("/usr/sbin/e2fsck -f -y -E discard '{}'", DevicePath);
2999 if (UtilExecCommandLine(CommandLine.c_str(), nullptr) < 0)
3000 {
3001 LOG_WARNING("Failed to trim {}", DevicePath.c_str());
3002 }
3003
3004 ResponseCode = 0;
3005 });
3006
3007 return (ChildPid < 0) ? -1 : 0;
3008 }
3009 CATCH_RETURN_ERRNO();
3010
3011 int ProcessMessage(wsl::shared::Transaction& Transaction, LX_MESSAGE_TYPE Type, gsl::span<gsl::byte> Buffer, VmConfiguration& Config)
3012
3013 /*++
3014
3015 Routine Description:
3016
3017 This routine processes messages from the service.
3018
3019 Arguments:
3020
3021 Transaction - Supplies the transaction for replying to the message.
3022
3023 Buffer - Supplies the message.
3024
3025 Config - Supplies the VM configuration.
3026
3027 Return Value:
3028
3029 0 on success, -1 on failure.
3030
3031 --*/
3032 try
3033 {
3034
3035 //
3036 // Validate the message and handle operations that do not require creating a child process.
3037 //
3038
3039 switch (Type)
3040 {
3041 case LxMiniInitMessageLaunchInit:
3042 case LxMiniInitMessageImport:
3043 case LxMiniInitMessageImportInplace:
3044 case LxMiniInitMessageExport:
3045 try
3046 {
3047 const auto Message = gslhelpers::try_get_struct<LX_MINI_INIT_MESSAGE>(Buffer);
3048 THROW_ERRNO_IF(EINVAL, !Message);
3049
3050 wsl::shared::SocketChannel Channel{UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, false), "Init"};
3051 if (Channel.Socket() < 0)
3052 {
3053 return -1;
3054 }
3055
3056 wil::unique_fd SystemDistroSocketFd{};
3057 if (Message->Flags & LxMiniInitMessageFlagLaunchSystemDistro && Config.EnableGuiApps)
3058 {
3059 SystemDistroSocketFd = UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, false);
3060 if (!SystemDistroSocketFd)
3061 {
3062 return -1;
3063 }
3064 }
3065
3066 auto ChildPid = UtilCreateChildProcess(
3067 "LaunchDistro",
3068 [Type, Message, Buffer, Channel = std::move(Channel), SystemDistroSocketFd = std::move(SystemDistroSocketFd), &Config]() mutable {
3069 //
3070 // Restore the default signal flags so anything blocked by mini_init doesn't get
3071 // inherited by init and session leaders.
3072 //
3073
3074 THROW_LAST_ERROR_IF(UtilRestoreBlockedSignals() < 0);
3075
3076 if (Type == LxMiniInitMessageLaunchInit)
3077 {
3078 ProcessLaunchInitMessage(Message, Buffer, std::move(Channel), std::move(SystemDistroSocketFd), Config);
3079 FATAL_ERROR("Unexpected return from ProcessLaunchInitMessage");
3080 }
3081 else
3082 {
3083 ProcessImportExportMessage(Buffer, std::move(Channel));
3084 }
3085 },
3086 (CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | SIGCHLD));
3087
3088 return (ChildPid < 0) ? -1 : 0;
3089 }
3090 CATCH_RETURN_ERRNO()
3091
3092 case LxMiniInitMessageEjectVhd:
3093 {
3094 //
3095 // Eject the scsi device and inform the service that the operation is complete.
3096 //
3097
3098 const auto* EjectMessage = gslhelpers::try_get_struct<EJECT_VHD_MESSAGE>(Buffer);
3099 if (!EjectMessage)
3100 {
3101 LOG_ERROR("Unexpected message size {}", Buffer.size());
3102 return -1;
3103 }
3104
3105 Transaction.SendResultMessage(EjectScsi(EjectMessage->Lun));
3106 return 0;
3107 }
3108
3109 case LxMiniInitMessageEarlyConfig:
3110 {
3111 const auto EarlyConfig = gslhelpers::try_get_struct<LX_MINI_INIT_EARLY_CONFIG_MESSAGE>(Buffer);
3112 if (!EarlyConfig)
3113 {
3114 LOG_ERROR("Unexpected message size {}", Buffer.size());
3115 return -1;
3116 }
3117
3118 if (EarlyConfig->EnableSafeMode)
3119 {
3120 LOG_WARNING("{} - many features will be disabled", WSL_SAFE_MODE_WARNING);
3121 Config.EnableSafeMode = true;
3122 }
3123
3124 if (EarlyConfig->IsolateDistroCgroup && access(CGROUP_MOUNTPOINT "/cgroup.controllers", F_OK) == 0)
3125 {
3126 SetupWslUserCgroup();
3127 }
3128
3129 //
3130 // Establish the connection for the guest network service.
3131 //
3132
3133 auto SocketFd = UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true);
3134 if (!SocketFd)
3135 {
3136 return -1;
3137 }
3138
3139 //
3140 // If DNS tunneling is enabled, open a separate hvsocket connection for it.
3141 //
3142
3143 wil::unique_fd DnsTunnelingSocketFd{};
3144 if (EarlyConfig->EnableDnsTunneling)
3145 {
3146 DnsTunnelingSocketFd = UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true);
3147 if (!DnsTunnelingSocketFd)
3148 {
3149 return -1;
3150 }
3151 }
3152
3153 //
3154 // Configure memory reclamation.
3155 //
3156
3157 StartMemoryReductionThread(EarlyConfig->MemoryReclaimMode);
3158
3159 //
3160 // Initialize system distro if supported.
3161 //
3162
3163 if (EarlyConfig->SystemDistroDeviceId != UINT_MAX)
3164 {
3165 if (MountSystemDistro(EarlyConfig->SystemDistroDeviceType, EarlyConfig->SystemDistroDeviceId) < 0)
3166 {
3167 return -1;
3168 }
3169
3170 //
3171 // Crash dump collection needs to be reconfigured here, because we called chroot.
3172 //
3173
3174 if (Config.EnableCrashDumpCollection)
3175 {
3176 EnableCrashDumpCollection();
3177 }
3178
3179 Config.EnableSystemDistro = true;
3180
3181 //
3182 // Set the $LANG environment variable.
3183 //
3184 // N.B. This is needed by bsdtar for path conversions (to support .xz file format).
3185 //
3186
3187 if (setenv("LANG", "en_US.UTF-8", 1) < 0)
3188 {
3189 LOG_ERROR("setenv(LANG, en_US.UTF-8) failed {}", errno);
3190 }
3191
3192 //
3193 // Start the debug shell if enabled.
3194 //
3195
3196 if (EarlyConfig->EnableDebugShell)
3197 {
3198 StartDebugShell();
3199 }
3200
3201 //
3202 // Configure swap space.
3203 //
3204
3205 if (EarlyConfig->SwapLun != UINT_MAX)
3206 {
3207 CreateSwap(EarlyConfig->SwapLun);
3208 }
3209
3210 //
3211 // Start the time sync agent (chronyd) to keep guest clock in sync with the host.
3212 //
3213
3214 StartTimeSyncAgent();
3215 }
3216
3217 //
3218 // Mount kernel modules if supported.
3219 //
3220 // N.B. The VHD is mounted as read-only but with a writable overlayfs layer. The modules
3221 // directory must be writable for tools like depmod to work.
3222 //
3223 // N.B. The artifacts VHD nests the modules under <release>/modules.
3224 // Older module-only VHDs place the modules tree at the filesystem root; fall back to that
3225 // layout when the nested modules directory is not present.
3226 //
3227 if (EarlyConfig->KernelModulesDeviceId != UINT_MAX)
3228 {
3229 THROW_LAST_ERROR_IF(
3230 MountDevice(LxMiniInitMountDeviceTypeLun, EarlyConfig->KernelModulesDeviceId, KERNEL_MODULES_VHD_PATH, "ext4", LxMiniInitMessageFlagMountReadOnly, nullptr) <
3231 0);
3232
3233 utsname UnameBuffer{};
3234 THROW_LAST_ERROR_IF(uname(&UnameBuffer) < 0);
3235 const std::string Release{UnameBuffer.release};
3236
3237 const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
3238 const std::string NestedModules = ArtifactsBase + "/modules";
3239
3240 std::error_code Error{};
3241 const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
3242 const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
3243 const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
3244
3245 //
3246 // A valid artifacts VHD nests the tree under <release>/modules; a legacy module-only VHD
3247 // places it at the root.
3248 //
3249 if (LegacyLayout)
3250 {
3251 LOG_WARNING(
3252 "kernel modules VHD uses the legacy flat layout; support for the legacy modules VHD format will be "
3253 "removed in a future version");
3254 }
3255 else if (!NestedLayout)
3256 {
3257 LOG_WARNING("kernel modules VHD does not contain modules for {}", Release);
3258 }
3259
3260 std::string Target = std::format("{}/{}", KERNEL_MODULES_PATH, Release);
3261 THROW_LAST_ERROR_IF(UtilMountOverlayFs(Target.c_str(), ModulesLower.c_str(), (MS_NOATIME | MS_NOSUID | MS_NODEV)) < 0);
3262
3263 const std::string KernelModulesList = wsl::shared::string::FromSpan(Buffer, EarlyConfig->KernelModulesListOffset);
3264 for (const auto& Module : wsl::shared::string::Split(KernelModulesList, ','))
3265 {
3266 const char* Argv[] = {MODPROBE_PATH, Module.c_str(), nullptr};
3267 int Status = -1;
3268 auto result = UtilCreateProcessAndWait(MODPROBE_PATH, Argv, &Status);
3269 if (result < 0)
3270 {
3271 LOG_ERROR("Failed to load module '{}', {}", Module, Status);
3272 }
3273 }
3274
3275 Config.KernelModulesPath = std::move(Target);
3276 }
3277
3278 //
3279 // Initialization required by mini_init.
3280 //
3281
3282 if (Initialize(wsl::shared::string::FromSpan(Buffer, EarlyConfig->HostnameOffset)) < 0)
3283 {
3284 return -1;
3285 }
3286
3287 //
3288 // Start the guest network service.
3289 //
3290
3291 if (StartGuestNetworkService(SocketFd.get(), std::move(DnsTunnelingSocketFd), EarlyConfig->DnsTunnelingIpAddress) < 0)
3292 {
3293 return -1;
3294 }
3295
3296 return 0;
3297 }
3298
3299 case LxMiniInitMessageInitialConfig:
3300 {
3301 const auto ConfigMessage = gslhelpers::try_get_struct<LX_MINI_INIT_CONFIG_MESSAGE>(Buffer);
3302 if (!ConfigMessage)
3303 {
3304 LOG_ERROR("Unexpected message size {}", Buffer.size());
3305 return -1;
3306 }
3307
3308 auto NetworkingConfiguration = &ConfigMessage->NetworkingConfiguration;
3309 Config.NetworkingMode = NetworkingConfiguration->NetworkingMode;
3310 if (NetworkingConfiguration->PortTrackerType != LxMiniInitPortTrackerTypeNone)
3311 {
3312 StartPortTracker(NetworkingConfiguration->PortTrackerType, NetworkingConfiguration->NetworkingMode);
3313 }
3314
3315 if (NetworkingConfiguration->DisableIpv6)
3316 {
3317 WriteToFile("/proc/sys/net/ipv6/conf/all/disable_ipv6", c_trueString);
3318 }
3319
3320 if (NetworkingConfiguration->EnableDhcpClient)
3321 {
3322 StartDhcpClient(NetworkingConfiguration->DhcpTimeout);
3323 }
3324
3325 if (SetEphemeralPortRange(NetworkingConfiguration->EphemeralPortRangeStart, NetworkingConfiguration->EphemeralPortRangeEnd) < 0)
3326 {
3327 return -1;
3328 }
3329
3330 if (ConfigMessage->EntropySize > 0)
3331 {
3332 InjectEntropy(Buffer.subspan(ConfigMessage->EntropyOffset, ConfigMessage->EntropySize));
3333 }
3334
3335 if (ConfigMessage->MountGpuShares)
3336 {
3337 if (MountPlan9(LXSS_GPU_DRIVERS_SHARE, GPU_SHARE_DRIVERS, true) < 0)
3338 {
3339 return -1;
3340 }
3341
3342 if (MountPlan9(LXSS_GPU_PACKAGED_LIB_SHARE, GPU_SHARE_LIB_PACKAGED, true) < 0)
3343 {
3344 return -1;
3345 }
3346
3347 if (ConfigMessage->EnableInboxGpuLibs)
3348 {
3349 if (MountPlan9(LXSS_GPU_INBOX_LIB_SHARE, GPU_SHARE_LIB_INBOX, true) < 0)
3350 {
3351 return -1;
3352 }
3353 }
3354 }
3355
3356 Config.EnableInboxGpuLibs = ConfigMessage->EnableInboxGpuLibs;
3357 Config.EnableGpuSupport = ConfigMessage->MountGpuShares;
3358 Config.EnableGuiApps = ConfigMessage->EnableGuiApps;
3359 return 0;
3360 }
3361 case LxMiniInitMessageMount:
3362 case LxMiniInitMessageUnmount:
3363 case LxMiniInitMessageDetach:
3364 ProcessMountMessage(Buffer);
3365
3366 //
3367 // Ignore the return code from ProcessMountMessage so that we don't exit on error.
3368 //
3369
3370 return 0;
3371
3372 case LxMiniInitMountFolder:
3373 return ProcessMountFolderMessage(Transaction, Buffer);
3374
3375 case LxInitCreateProcess:
3376 if (access(WSL_USER_NON_DISTRO_CGROUP_PATH, F_OK) == 0)
3377 {
3378 return ProcessCreateProcessMessage(Transaction, Buffer, WSL_USER_NON_DISTRO_CGROUP_PATH);
3379 }
3380 else
3381 {
3382 return ProcessCreateProcessMessage(Transaction, Buffer, std::nullopt);
3383 }
3384
3385 case LxMiniInitMessageWaitForPmemDevice:
3386 {
3387 const auto PmemMessage = gslhelpers::try_get_struct<LX_MINI_INIT_WAIT_FOR_PMEM_DEVICE_MESSAGE>(Buffer);
3388 if (!PmemMessage)
3389 {
3390 LOG_ERROR("Unexpected message size {}", Buffer.size());
3391 return -1;
3392 }
3393
3394 ProcessWaitForPmemDeviceMessage(PmemMessage);
3395
3396 //
3397 // Ignore the return code from ProcessWaitForPmemDeviceMessage so that we don't exit on error.
3398 //
3399
3400 return 0;
3401 }
3402
3403 case LxMiniInitMessageResizeDistribution:
3404 {
3405
3406 ProcessResizeDistributionMessage(Buffer);
3407 return 0;
3408 }
3409
3410 case LxMiniInitMessageTrimDistribution:
3411 {
3412
3413 ProcessTrimDistributionMessage(Buffer);
3414 return 0;
3415 }
3416
3417 default:
3418 LOG_ERROR("Unexpected message type {}", Type);
3419 return -1;
3420 }
3421
3422 _exit(1);
3423 }
3424 CATCH_RETURN_ERRNO();
3425
3426 wil::unique_fd RegisterSeccompHook()
3427
3428 /*++
3429
3430 Routine Description:
3431
3432 Register a seccomp notification for bind() & listen() calls (both the native and 32-bit
3433 compat ABIs), plus ioctl(*, SIOCSIFFLAGS, *) calls on the native 64-bit ABI only.
3434
3435 listen() is intercepted in addition to bind() because it can perform an implicit
3436 autobind (assigning an ephemeral port) on a socket that was never explicitly bind()'d;
3437 that autobind would otherwise be invisible to the port tracker.
3438
3439 Arguments:
3440
3441 None.
3442
3443 Return Value:
3444
3445 The notification file descriptor or < 0 on failure.
3446
3447 --*/
3448
3449 {
3450 struct sock_filter Filter[] = {
3451 // Structure of this program:
3452 // For each architecture, there is a block of instructions to match specific calls.
3453 // The first two instructions check for the arch and skip to the next one if it doesn't match.
3454 // Each block contains a return SECCOMP_RET_USER_NOTIF/SECCOMP_RET_ALLOW so that
3455 // offset within a block don't change as other blocks change.
3456
3457 // 64bit:
3458 // If syscall_arch & __AUDIT_ARCH_64BIT then continue else goto :32bit
3459 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_arch),
3460 // For now, notify on all non-native arch
3461 BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, __AUDIT_ARCH_64BIT, 0, 8),
3462 // If syscall_nr == __NR_bind then goto user_notify: else continue
3463 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
3464 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_bind, 4, 0),
3465 // if (syscall_nr == __NR_listen) then goto user_notify: else continue
3466 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_listen, 3, 0),
3467 // if (syscall_nr == __NR_ioctl) then continue else goto allow:
3468 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_ioctl, 0, 3),
3469 // if (syscall arg1 == SIOCSIFFLAGS) goto user_notify else goto allow:
3470 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_arg(1)),
3471 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SIOCSIFFLAGS, 0, 1),
3472 // user_notify:
3473 // return SECCOMP_RET_USER_NOTIF;
3474 BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_USER_NOTIF),
3475 // allow:
3476 // return SECCOMP_RET_ALLOW;
3477 BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
3478
3479 // Note: 32bit on x86_64 uses the __NR_socketcall with the first argument
3480 // set to SYS_BIND/SYS_LISTEN to make bind()/listen() system calls.
3481 #ifdef __x86_64__
3482 // 32bit:
3483 // If syscall_nr == __NR_socketcall then continue else goto allow:
3484 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
3485 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, I386_NR_socketcall, 0, 4),
3486 // if syscall arg0 == SYS_BIND then goto user_notify: else continue
3487 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_arg(0)),
3488 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SYS_BIND, 1, 0),
3489 // if syscall arg0 == SYS_LISTEN then continue else goto allow:
3490 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SYS_LISTEN, 0, 1),
3491 // user_notify:
3492 // return SECCOMP_RET_USER_NOTIF;
3493 BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_USER_NOTIF),
3494 // allow:
3495 // return SECCOMP_RET_ALLOW;
3496 BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
3497 #else
3498 // 32bit:
3499 // If syscall_nr == __NR_bind then goto user_notify: else continue
3500 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
3501 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARMV7_NR_bind, 1, 0),
3502 // if (syscall_nr == __NR_listen) then goto user_notify: else goto allow:
3503 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARMV7_NR_listen, 0, 1),
3504 // user_notify:
3505 // return SECCOMP_RET_USER_NOTIF;
3506 BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_USER_NOTIF),
3507 // allow:
3508 // return SECCOMP_RET_ALLOW;
3509 BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
3510 #endif
3511 };
3512
3513 struct sock_fprog Prog = {
3514 .len = sizeof(Filter) / sizeof(Filter[0]),
3515 .filter = Filter,
3516 };
3517
3518 wil::unique_fd Fd{syscall(
3519 __NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER | SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV, &Prog)};
3520 if (!Fd && errno == EINVAL)
3521 {
3522 LOG_INFO("seccomp failed with EINVAL with SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV, retrying without it.");
3523 Fd = syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &Prog);
3524 }
3525 if (!Fd)
3526 {
3527 LOG_ERROR("Failed to register bpf syscall hook, {}", errno);
3528 return {};
3529 }
3530
3531 if (SetCloseOnExec(Fd.get(), false) < 0)
3532 {
3533 return {};
3534 }
3535
3536 return Fd;
3537 }
3538
3539 int SendCapabilities(wsl::shared::SocketChannel& Channel)
3540
3541 /*++
3542
3543 Routine Description:
3544
3545 Send the kernel capabilities on the specified channel.
3546
3547 Arguments:
3548
3549 Channel - The channel to send the message on.
3550
3551 Return Value:
3552
3553 0 on success or < 0 on failure.
3554
3555 --*/
3556
3557 try
3558 {
3559 utsname Version;
3560 THROW_LAST_ERROR_IF(uname(&Version) < 0);
3561
3562 wsl::shared::MessageWriter<LX_INIT_GUEST_CAPABILITIES> Message(LxMiniInitMessageGuestCapabilities);
3563 Message.WriteString(Version.release);
3564
3565 // SECCOMP_USER_NOTIF_FLAG_CONTINUE is the latest flag that flow steering needs
3566 // but there's no way to test for its presence. The assumption is that if seccomp is available
3567 // and the kernel version is >= 5.10, then SECCOMP_USER_NOTIF_FLAG_CONTINUE is available.
3568 uint32_t SeccompFlag = SECCOMP_RET_USER_NOTIF;
3569 Message->SeccompAvailable = syscall(__NR_seccomp, SECCOMP_GET_ACTION_AVAIL, 0, &SeccompFlag) == 0;
3570
3571 auto pool = UtilReadHvPciSwiotlbPool();
3572 Message->HvPciSwiotlbBase = pool.Base;
3573 Message->HvPciSwiotlbSize = pool.Size;
3574
3575 Channel.SendMessage<LX_INIT_GUEST_CAPABILITIES>(Message.Span());
3576 return 0;
3577 }
3578 CATCH_RETURN_ERRNO();
3579
3580 int SetCloseOnExec(int Fd, bool Enable)
3581
3582 /*++
3583
3584 Routine Description:
3585
3586 Sets or clears the FD_CLOEXEC flag on the file descriptor.
3587
3588 Arguments:
3589
3590 Fd - Supplies the file descriptor to modify.
3591
3592 Enable - true to set the flag, false to clear.
3593
3594 Return Value:
3595
3596 0 on success or -1 on failure.
3597
3598 --*/
3599
3600 {
3601 int Result = fcntl(Fd, F_GETFD, 0);
3602 if (Result < 0)
3603 {
3604 LOG_ERROR("fcntl(F_GETFD) failed {}", errno);
3605 return -1;
3606 }
3607
3608 int Flags = Enable ? (Result | FD_CLOEXEC) : (Result & ~FD_CLOEXEC);
3609 Result = fcntl(Fd, F_SETFD, Flags);
3610 if (Result < 0)
3611 {
3612 LOG_ERROR("fcntl(F_SETFD, {}) failed {}", Flags, errno);
3613 return -1;
3614 }
3615
3616 return 0;
3617 }
3618
3619 int SetEphemeralPortRange(uint16_t Start, uint16_t End)
3620
3621 /*++
3622
3623 Routine Description:
3624
3625 This routine sets the ephemeral port range.
3626
3627 Arguments:
3628
3629 Start - Supplies the first port of the range (inclusive)
3630
3631 End - Supplies the last port of the range (inclusive).
3632
3633 Return Value:
3634
3635 0 on success, -1 on failure.
3636
3637 --*/
3638
3639 try
3640 {
3641 if (Start == 0 && End == 0)
3642 {
3643 return 0;
3644 }
3645
3646 std::string Content = std::format("{} {}", Start, End);
3647
3648 //
3649 // N.B. IPv6 reads from /proc/sys/net/ipv4/ip_local_port_range as well according to
3650 // https://tldp.org/HOWTO/Linux+IPv6-HOWTO/ch11s03.html.
3651 //
3652
3653 return WriteToFile("/proc/sys/net/ipv4/ip_local_port_range", Content.c_str());
3654 }
3655 CATCH_RETURN_ERRNO()
3656
3657 void StartTimeSyncAgent()
3658
3659 /*++
3660
3661 Routine Description:
3662
3663 This routine configures and launches chronyd.
3664
3665 Arguments:
3666
3667 None.
3668
3669 Return Value:
3670
3671 None.
3672
3673 --*/
3674
3675 {
3676 //
3677 // Check if the /dev/ptp0 device is present.
3678 //
3679
3680 if (access("/dev/ptp0", F_OK) < 0)
3681 {
3682 LOG_ERROR("/dev/ptp0 not found - kernel must be built with CONFIG_PTP_1588_CLOCK");
3683 return;
3684 }
3685
3686 //
3687 // Create a child process to run chronyd.
3688 //
3689
3690 UtilCreateChildProcess("chrony", []() {
3691 const auto FileContents =
3692 "driftfile /var/lib/chrony/drift\n" // Record the rate at which the system clock gains/losses time.
3693 "makestep 1.0 3\n" // Allow the system clock to be stepped in the first three updates if its offset is larger than 1 second.
3694 "rtcsync\n" // Enable kernel synchronization of the real-time clock (RTC).
3695 "leapsectz right/UTC\n" // Get TAI-UTC offset and leap seconds from the system tz database.
3696 "logdir /var/log/chrony\n" // Specify directory for log files.
3697 "refclock PHC /dev/ptp0 poll 3 dpoll -2 offset 0\n"; // Use the /dev/ptp0 device as a clock source.
3698
3699 remove(CHRONY_CONF_PATH);
3700 THROW_LAST_ERROR_IF(WriteToFile(CHRONY_CONF_PATH, FileContents) < 0);
3701
3702 execl(CHRONYD_PATH, CHRONYD_PATH, NULL);
3703 LOG_ERROR("execl failed {}", errno);
3704 });
3705 }
3706
3707 void WaitForBlockDevice(const char* Path)
3708
3709 /*++
3710
3711 Routine Description:
3712
3713 Wait for a block device to be available.
3714
3715 Arguments:
3716
3717 Path - Supplies the path to the block device.
3718
3719 Return Value:
3720
3721 None.
3722
3723 --*/
3724
3725 {
3726 wsl::shared::retry::RetryWithTimeout<void>(
3727 [&]() {
3728 wil::unique_fd device{open(Path, O_RDONLY)};
3729 THROW_LAST_ERROR_IF(!device);
3730 },
3731 c_defaultRetryPeriod,
3732 c_defaultRetryTimeout,
3733 [&]() {
3734 errno = wil::ResultFromCaughtException();
3735 return errno == ENOENT || errno == ENXIO || errno == EIO;
3736 });
3737 }
3738
3739 int WaitForChild(pid_t Pid, const char* Name)
3740
3741 /*++
3742
3743 Routine Description:
3744
3745 Wait for a child process to exit and check that it exited successfully.
3746
3747 Arguments:
3748
3749 Pid - Supplies the pid to wait for.
3750
3751 Name - Supplies the process image name, for logging.
3752
3753 Return Value:
3754
3755 0 on success, -1 on failure.
3756
3757 --*/
3758
3759 {
3760 int Status = -1;
3761 if (TEMP_FAILURE_RETRY(waitpid(Pid, &Status, 0)) < 0)
3762 {
3763 LOG_ERROR("Waiting for child '{}' failed, waitpid failed {}", Name, errno);
3764 return -1;
3765 }
3766
3767 return UtilProcessChildExitCode(Status, Name);
3768 }
3769
3770 int WslEntryPoint(int Argc, char* Argv[]);
3771
3772 extern int WSLCEntryPoint(int Argc, char* Argv[]);
3773
3774 void EnableDebugMode(const std::string& Mode)
3775 {
3776 if (Mode == "hvsocket")
3777 {
3778 // Mount the debugfs.
3779 THROW_LAST_ERROR_IF(UtilMount("none", "/sys/kernel/debug", "debugfs", 0, nullptr) < 0);
3780
3781 // Enable hvsocket events.
3782 std::vector<const char*> files{
3783 "/sys/kernel/debug/tracing/events/hyperv/vmbus_on_msg_dpc/enable",
3784 "/sys/kernel/debug/tracing/events/hyperv/vmbus_on_message/enable",
3785 "/sys/kernel/debug/tracing/events/hyperv/vmbus_onoffer/enable",
3786 "/sys/kernel/debug/tracing/events/hyperv/vmbus_onoffer_rescind/enable",
3787 "/sys/kernel/debug/tracing/events/hyperv/vmbus_onopen_result/enable",
3788 "/sys/kernel/debug/tracing/events/hyperv/vmbus_ongpadl_created/enable",
3789 "/sys/kernel/debug/tracing/events/hyperv/vmbus_ongpadl_torndown/enable",
3790 "/sys/kernel/debug/tracing/events/hyperv/vmbus_open/enable",
3791 "/sys/kernel/debug/tracing/events/hyperv/vmbus_close_internal/enable",
3792 "/sys/kernel/debug/tracing/events/hyperv/vmbus_establish_gpadl_header/enable",
3793 "/sys/kernel/debug/tracing/events/hyperv/vmbus_establish_gpadl_body/enable",
3794 "/sys/kernel/debug/tracing/events/hyperv/vmbus_teardown_gpadl/enable",
3795 "/sys/kernel/debug/tracing/events/hyperv/vmbus_release_relid/enable",
3796 "/sys/kernel/debug/tracing/events/hyperv/vmbus_send_tl_connect_request/enable"};
3797
3798 for (auto* e : files)
3799 {
3800 WriteToFile(e, "1");
3801 }
3802
3803 // Relay logs to the host.
3804 std::thread relayThread{[]() {
3805 constexpr auto path = "/sys/kernel/debug/tracing/trace_pipe";
3806 std::ifstream file(path);
3807
3808 if (!file)
3809 {
3810 LOG_ERROR("Failed to open {}, {}", path, errno);
3811 return;
3812 }
3813
3814 std::string line;
3815 while (std::getline(file, line))
3816 {
3817 LOG_INFO("{}", line);
3818 }
3819
3820 LOG_ERROR("{}: closed", path);
3821 }};
3822
3823 relayThread.detach();
3824 }
3825 else
3826 {
3827 LOG_ERROR("Unknown debugging mode: '{}'", Mode);
3828 }
3829 }
3830
3831 void SetupWslUserCgroup()
3832
3833 /*++
3834
3835 Routine Description:
3836
3837 This routine creates a memory-limited cgroup for user processes. All user workloads
3838 (systemd, session leaders, boot commands) are placed into this cgroup so that they
3839 cannot exhaust all VM memory. This reserves a fixed amount of memory for critical
3840 WSL system processes (mini_init, GNS, Plan9, WSL init) that remain in the root cgroup.
3841
3842 The memory.max limit is set to totalram - c_systemReservedMemory, which provides a
3843 hard cap. When this limit is reached, the cgroup-local OOM killer activates and only
3844 kills processes within wsl-user, leaving system processes unaffected.
3845
3846 The cpu.max limit is set to (nproc * c_cpuPeriodMicros - c_systemReservedCpuMicros) per
3847 c_cpuPeriodMicros period, reserving a small portion of the CPU for WSL system processes so they remain
3848 schedulable even when user workloads saturate every CPU.
3849
3850 Arguments:
3851
3852 None.
3853
3854 Return Value:
3855
3856 None.
3857
3858 --*/
3859
3860 {
3861 struct sysinfo info = {};
3862 if (sysinfo(&info) < 0)
3863 {
3864 LOG_ERROR("sysinfo failed {}", errno);
3865 return;
3866 }
3867
3868 uint64_t totalRam = static_cast<uint64_t>(info.totalram) * info.mem_unit;
3869
3870 if (totalRam <= c_systemReservedMemory)
3871 {
3872 LOG_WARNING("Total RAM ({}) is too small to reserve {} for system processes", totalRam, c_systemReservedMemory);
3873 return;
3874 }
3875
3876 if (UtilEnableAllCgroupControllers(CGROUP_MOUNTPOINT) < 0)
3877 {
3878 LOG_ERROR("Failed to enable cgroup controllers for root {}", errno);
3879 return;
3880 }
3881
3882 if (UtilMkdir(WSL_USER_CGROUP_PATH, 0755) < 0)
3883 {
3884 LOG_ERROR("Failed to create wsl-user cgroup directory {}", errno);
3885 return;
3886 }
3887
3888 if (UtilEnableAllCgroupControllers(WSL_USER_CGROUP_PATH) < 0)
3889 {
3890 LOG_ERROR("Failed to enable cgroup controllers for wsl-user {}", errno);
3891 return;
3892 }
3893
3894 if (UtilMkdir(WSL_USER_NON_DISTRO_CGROUP_PATH, 0755) < 0)
3895 {
3896 LOG_ERROR("Failed to create wsl-user non-distro cgroup directory {}", errno);
3897 return;
3898 }
3899
3900 auto userMemoryMax = std::to_string(totalRam - c_systemReservedMemory);
3901 if (WriteToFile(WSL_USER_CGROUP_PATH "/memory.max", userMemoryMax.c_str()) < 0)
3902 {
3903 LOG_ERROR("Failed to set memory.max for wsl-user cgroup {}", errno);
3904 return;
3905 }
3906
3907 LOG_INFO("WSL user cgroup created with memory.max={} (totalram={}, reserved={})", userMemoryMax, totalRam, c_systemReservedMemory);
3908
3909 const long nproc = get_nprocs();
3910 if (nproc <= 0)
3911 {
3912 LOG_WARNING("get_nprocs returned {}, skipping cpu.max", nproc);
3913 return;
3914 }
3915
3916 const long cpuQuota = (nproc * c_cpuPeriodMicros) - c_systemReservedCpuMicros;
3917 auto userCpuMax = std::format("{} {}", cpuQuota, c_cpuPeriodMicros);
3918 if (WriteToFile(WSL_USER_CGROUP_PATH "/cpu.max", userCpuMax.c_str()) < 0)
3919 {
3920 LOG_ERROR("Failed to set cpu.max for wsl-user cgroup {}", errno);
3921 return;
3922 }
3923
3924 LOG_INFO("WSL user cgroup cpu.max={} (nproc={}, reserved={}us)", userCpuMax, nproc, c_systemReservedCpuMicros);
3925 }
3926
3927 int main(int Argc, char* Argv[])
3928 {
3929 std::vector<gsl::byte> Buffer;
3930 ssize_t BytesRead;
3931 VmConfiguration Config{};
3932 wil::unique_fd ConsoleFd{};
3933 wsl::shared::SocketChannel channel;
3934 wil::unique_fd NotifyFd{};
3935 struct pollfd PollDescriptors[2];
3936 wil::unique_fd SignalFd{};
3937 struct signalfd_siginfo SignalInfo;
3938 sigset_t SignalMask;
3939 int Status;
3940
3941 //
3942 // Determine which entrypoint should be used.
3943 //
3944
3945 if (getenv(WSLC_ROOT_INIT_ENV))
3946 {
3947 if (unsetenv(WSLC_ROOT_INIT_ENV))
3948 {
3949 LOG_ERROR("unsetenv failed {}", errno);
3950 }
3951
3952 return WSLCEntryPoint(Argc, Argv);
3953 }
3954
3955 if (getpid() != 1 || !getenv(WSL_ROOT_INIT_ENV))
3956 {
3957 return WslEntryPoint(Argc, Argv);
3958 }
3959
3960 if (unsetenv(WSL_ROOT_INIT_ENV))
3961 {
3962 LOG_ERROR("unsetenv failed {}", errno);
3963 }
3964
3965 // Use an env variable to determine whether socket logging is enabled since /proc isn't mounted yet
3966 // so SocketChannel can't look at the kernel command line.
3967 wsl::shared::SocketChannel::EnableSocketLogging(getenv(WSL_SOCKET_LOG_ENV) != nullptr);
3968
3969 if (unsetenv(WSL_SOCKET_LOG_ENV))
3970 {
3971 LOG_ERROR("unsetenv failed {}", errno);
3972 }
3973
3974 //
3975 // Mount devtmpfs.
3976 //
3977
3978 int Result = UtilMount(nullptr, DEVFS_PATH, "devtmpfs", 0, nullptr);
3979 if (Result < 0)
3980 {
3981 goto ErrorExit;
3982 }
3983
3984 //
3985 // Open kmsg for logging and ensure that the file descriptor is not set to one of the standard file descriptors.
3986 //
3987 // N.B. This is to work around a rare race condition where init is launched without /dev/console set as the controlling terminal.
3988 //
3989
3990 InitializeLogging(false);
3991 if (g_LogFd <= STDERR_FILENO)
3992 {
3993 LOG_ERROR("/init was started without /dev/console");
3994 if (dup2(g_LogFd, 3) < 0)
3995 {
3996 LOG_ERROR("dup2 failed {}", errno);
3997 }
3998
3999 close(g_LogFd);
4000 g_LogFd = 3;
4001 }
4002
4003 //
4004 // Log the WSL version to kmesg.
4005 //
4006
4007 LOG_INFO("WSL version {}", WSL_PACKAGE_VERSION);
4008
4009 //
4010 // Ensure /dev/console is present and set as the controlling terminal.
4011 // If opening /dev/console times out, stdout and stderr to the logging file descriptor.
4012 //
4013
4014 try
4015 {
4016 wsl::shared::retry::RetryWithTimeout<void>(
4017 [&]() {
4018 ConsoleFd = open("/dev/console", O_RDWR);
4019 THROW_LAST_ERROR_IF(!ConsoleFd);
4020 },
4021 c_defaultRetryPeriod,
4022 c_defaultRetryTimeout);
4023
4024 THROW_LAST_ERROR_IF(login_tty(ConsoleFd.get()) < 0);
4025 }
4026 catch (...)
4027 {
4028 if (dup2(g_LogFd, STDOUT_FILENO) < 0)
4029 {
4030 LOG_ERROR("dup2 failed {}", errno);
4031 }
4032
4033 if (dup2(g_LogFd, STDERR_FILENO) < 0)
4034 {
4035 LOG_ERROR("dup2 failed {}", errno);
4036 }
4037 }
4038
4039 //
4040 // Open /dev/null for stdin.
4041 //
4042
4043 {
4044 wil::unique_fd Fd{TEMP_FAILURE_RETRY(open(DEVNULL_PATH, O_RDONLY))};
4045 if (!Fd)
4046 {
4047 LOG_ERROR("open({}) failed {}", DEVNULL_PATH, errno);
4048 return -1;
4049 }
4050
4051 if (Fd.get() == STDIN_FILENO)
4052 {
4053 Fd.release();
4054 }
4055 else
4056 {
4057 if (TEMP_FAILURE_RETRY(dup2(Fd.get(), STDIN_FILENO)) < 0)
4058 {
4059 LOG_ERROR("dup2 failed {}", errno);
4060 return -1;
4061 }
4062 }
4063 }
4064
4065 //
4066 // Create the etc directory and mount procfs and sysfs.
4067 //
4068
4069 if (UtilMkdir(ETC_PATH, 0755) < 0)
4070 {
4071 return -1;
4072 }
4073
4074 if (UtilMount(nullptr, PROCFS_PATH, "proc", 0, nullptr) < 0)
4075 {
4076 return -1;
4077 }
4078
4079 if (UtilMount(nullptr, SYSFS_PATH, "sysfs", 0, nullptr) < 0)
4080 {
4081 return -1;
4082 }
4083
4084 //
4085 // Enable debug mode, if specified.
4086 //
4087
4088 if (const auto* debugMode = getenv(WSL_DEBUG_ENV))
4089 {
4090 LOG_ERROR("Running in debug mode: '{}'", debugMode);
4091 EnableDebugMode(debugMode);
4092
4093 unsetenv(WSL_DEBUG_ENV);
4094 }
4095
4096 //
4097 // Establish the message channel with the service via hvsocket.
4098 //
4099
4100 channel = {UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true), "mini_init"};
4101 if (channel.Socket() < 0)
4102 {
4103 Result = -1;
4104 goto ErrorExit;
4105 }
4106
4107 if (SendCapabilities(channel) < 0)
4108 {
4109 goto ErrorExit;
4110 }
4111 //
4112 // Create another channel for guest-driven communication, for example, to
4113 // notify the service when a distribution terminates unexpectedly.
4114 //
4115
4116 NotifyFd = UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true);
4117 if (!NotifyFd)
4118 {
4119 Result = -1;
4120 goto ErrorExit;
4121 }
4122
4123 if (getenv(WSL_ENABLE_CRASH_DUMP_ENV))
4124 {
4125 Config.EnableCrashDumpCollection = true;
4126
4127 EnableCrashDumpCollection();
4128 if (unsetenv(WSL_ENABLE_CRASH_DUMP_ENV) < 0)
4129 {
4130 LOG_ERROR("unsetenv failed {}", errno);
4131 }
4132 }
4133
4134 if (UtilMount(nullptr, CGROUP_MOUNTPOINT, CGROUP2_DEVICE, 0, nullptr) < 0)
4135 {
4136 Result = -1;
4137 LOG_ERROR("Failed to mount cgroup2: {}", errno);
4138 goto ErrorExit;
4139 }
4140
4141 UtilSetThreadName("mini_init");
4142
4143 //
4144 // Create a signalfd to detect when the child process exits.
4145 //
4146
4147 sigemptyset(&SignalMask);
4148 sigaddset(&SignalMask, SIGCHLD);
4149 Result = UtilSaveBlockedSignals(SignalMask);
4150 if (Result < 0)
4151 {
4152 LOG_ERROR("sigprocmask failed {}", errno);
4153 goto ErrorExit;
4154 }
4155
4156 SignalFd = signalfd(-1, &SignalMask, SFD_CLOEXEC);
4157 if (!SignalFd)
4158 {
4159 Result = -1;
4160 LOG_ERROR("signalfd failed {}", errno);
4161 goto ErrorExit;
4162 }
4163
4164 //
4165 // Fill the poll descriptors and begin worker loop.
4166 //
4167
4168 PollDescriptors[0].fd = channel.Socket();
4169 PollDescriptors[0].events = POLLIN;
4170 PollDescriptors[1].fd = SignalFd.get();
4171 PollDescriptors[1].events = POLLIN;
4172 for (;;)
4173 {
4174 Result = poll(PollDescriptors, COUNT_OF(PollDescriptors), -1);
4175 if (Result < 0)
4176 {
4177 LOG_ERROR("poll failed {}", errno);
4178 break;
4179 }
4180
4181 //
4182 // Process messages from the service. Break out of the loop if the socket is closed.
4183 //
4184
4185 assert((PollDescriptors[0].revents & POLLNVAL) == 0);
4186 if (PollDescriptors[0].revents & (POLLHUP | POLLERR))
4187 {
4188 break;
4189 }
4190 else if (PollDescriptors[0].revents & POLLIN)
4191 {
4192 auto transaction = channel.ReceiveTransaction();
4193 auto [Message, Range] = transaction.ReceiveOrClosed<MESSAGE_HEADER>();
4194 if (Message == nullptr)
4195 {
4196 break; // Socket was closed, exit
4197 }
4198
4199 Result = ProcessMessage(transaction, Message->MessageType, Range, Config);
4200 if (Result < 0)
4201 {
4202 goto ErrorExit;
4203 }
4204 }
4205
4206 //
4207 // Handle signalfd.
4208 //
4209
4210 assert((PollDescriptors[1].revents & (POLLHUP | POLLERR | POLLNVAL)) == 0);
4211 if (PollDescriptors[1].revents & POLLIN)
4212 {
4213 BytesRead = TEMP_FAILURE_RETRY(read(PollDescriptors[1].fd, &SignalInfo, sizeof(SignalInfo)));
4214 if (BytesRead != sizeof(SignalInfo))
4215 {
4216 Result = -1;
4217 LOG_ERROR("read failed {} {}", BytesRead, errno);
4218 goto ErrorExit;
4219 }
4220
4221 if (SignalInfo.ssi_signo != SIGCHLD)
4222 {
4223 LOG_ERROR("Unexpected signal {}", SignalInfo.ssi_signo);
4224 goto ErrorExit;
4225 }
4226
4227 //
4228 // Reap zombies and notify the service when child processes exit.
4229 //
4230
4231 for (;;)
4232 {
4233 Result = waitpid(-1, &Status, WNOHANG);
4234 if (Result == 0)
4235 {
4236 break;
4237 }
4238 else if (Result > 0)
4239 {
4240 //
4241 // Perform a sync to flush all writes.
4242 //
4243
4244 sync();
4245
4246 //
4247 // Clear the distro cgroup
4248 //
4249
4250 auto CgroupDir = UtilGetDistroCgroupPath(Result);
4251 if (access(CgroupDir.c_str(), F_OK) == 0)
4252 {
4253 LOG_INFO("Process {} exited, removing cgroup {}", Result, CgroupDir);
4254
4255 //
4256 // Recursively rmdir the cgroup subtree.
4257 //
4258
4259 try
4260 {
4261 std::vector<std::string> dirs;
4262 for (const auto& entry : std::filesystem::recursive_directory_iterator(
4263 CgroupDir, std::filesystem::directory_options::skip_permission_denied))
4264 {
4265 if (entry.is_directory())
4266 {
4267 dirs.emplace_back(entry.path().string());
4268 }
4269 }
4270
4271 for (auto it = dirs.rbegin(); it != dirs.rend(); ++it)
4272 {
4273 if (rmdir(it->c_str()) < 0 && errno != ENOENT)
4274 {
4275 LOG_ERROR("rmdir({}) failed {}", *it, errno);
4276 }
4277 }
4278
4279 if (rmdir(CgroupDir.c_str()) < 0 && errno != ENOENT)
4280 {
4281 LOG_ERROR("rmdir({}) failed {}", CgroupDir, errno);
4282 }
4283 }
4284 CATCH_LOG();
4285 }
4286
4287 //
4288 // Send a message with the child's pid to the service.
4289 //
4290
4291 LX_MINI_INIT_CHILD_EXIT_MESSAGE Message{};
4292 Message.Header.MessageType = LxMiniInitMessageChildExit;
4293 Message.Header.MessageSize = sizeof(Message);
4294 Message.ChildPid = Result;
4295 Result = UtilWriteBuffer(NotifyFd.get(), gslhelpers::struct_as_bytes(Message));
4296 if (Result < 0)
4297 {
4298 LOG_ERROR("write failed {}", errno);
4299 }
4300 }
4301 else
4302 {
4303 //
4304 // No more children exist.
4305 //
4306
4307 if (errno != ECHILD)
4308 {
4309 LOG_ERROR("waitpid failed {}", errno);
4310 }
4311
4312 break;
4313 }
4314 }
4315 }
4316 }
4317
4318 ErrorExit:
4319 try
4320 {
4321 auto children = ListInitChildProcesses();
4322
4323 while (!children.empty())
4324 {
4325
4326 // send SIGKILL to all running processes.
4327 for (auto pid : children)
4328 {
4329 if (kill(pid, SIGKILL) < 0)
4330 {
4331 LOG_ERROR("Failed to send SIGKILL to {}: {}", pid, errno);
4332 }
4333 }
4334
4335 // Wait for processes to actually exit.
4336 while (!children.empty())
4337 {
4338 auto Result = waitpid(-1, nullptr, 0);
4339 THROW_ERRNO_IF(errno, Result <= 0);
4340 LOG_INFO("Process {} exited", Result);
4341 children.erase(Result);
4342 }
4343
4344 children = ListInitChildProcesses();
4345 }
4346 }
4347 CATCH_LOG();
4348
4349 sync();
4350
4351 try
4352 {
4353 for (auto disk : ListScsiDisks())
4354 {
4355 if (DetachScsiDisk(disk) < 0)
4356 {
4357 LOG_ERROR("Failed to detach disk: {}", disk);
4358 }
4359 }
4360 }
4361 CATCH_LOG();
4362
4363 reboot(RB_POWER_OFF);
4364
4365 return Result;
4366 }