master
cpp 1,391 lines 44.3 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCInit.cpp
8
9 Abstract:
10
11 Init implementation for WSLC.
12
13 --*/
14
15 #include "util.h"
16 #include "drvfs.h"
17 #include "SocketChannel.h"
18 #include "message.h"
19 #include "localhost.h"
20 #include "common.h"
21 #include <utmp.h>
22 #include <unistd.h>
23 #include <sys/wait.h>
24 #include <sys/mount.h>
25 #include <sys/syscall.h>
26 #include <sys/epoll.h>
27 #include <sys/prctl.h>
28 #include <sys/socket.h>
29 #include <sys/utsname.h>
30 #include <sys/signalfd.h>
31 #include <arpa/inet.h>
32
33 #include <pty.h>
34 #include <mutex>
35 #include "mountutilcpp.h"
36 #include <filesystem>
37 #include <iostream>
38 #include "JsonUtils.h"
39 #include "cdi_schema.h"
40 #include "lxfsshares.h"
41
42 extern int InitializeLogging(bool SetStderr, wil::LogFunction* ExceptionCallback) noexcept;
43
44 extern std::set<pid_t> ListInitChildProcesses();
45
46 extern std::vector<unsigned int> ListScsiDisks();
47
48 extern int DetachScsiDisk(unsigned int Lun);
49
50 extern std::string GetLunDeviceName(unsigned int Lun);
51
52 void ProcessMessages(wsl::shared::SocketChannel& Channel);
53 int MountInit(const char* Target);
54
55 extern int EnableInterface(int Socket, const char* Name);
56
57 extern int SetCloseOnExec(int Fd, bool Enable);
58
59 int Chroot(const char* Target);
60
61 extern int g_LogFd;
62
63 extern void WSLCEnableCrashDumpCollection();
64
65 struct WSLCState
66 {
67 std::optional<std::filesystem::path> ModulesMountPoint;
68 };
69
70 static WSLCState g_state;
71
72 constexpr auto c_kernelModulesVhdMountPoint = "/kernel_modules_vhd";
73
74 void WriteWslcCdiSpec()
75 try
76 {
77 wsl::shared::cdi::DeviceNode dxg{};
78 dxg.path = "/dev/dxg";
79 dxg.permissions = "rwm";
80
81 wsl::shared::cdi::Mount libs{};
82 libs.hostPath = LXSS_LIB_PATH;
83 libs.containerPath = LXSS_LIB_PATH;
84 libs.options = {"ro", "rbind"};
85
86 wsl::shared::cdi::Mount drivers{};
87 drivers.hostPath = LXSS_GPU_DRIVERS_PATH;
88 drivers.containerPath = LXSS_GPU_DRIVERS_PATH;
89 drivers.options = {"ro", "rbind"};
90
91 wsl::shared::cdi::Hook hook{};
92 hook.hookName = "createContainer";
93 hook.path = "/" LX_INIT_WSLC_GPU_HOOK;
94 hook.args = {LX_INIT_WSLC_GPU_HOOK};
95
96 wsl::shared::cdi::Device gpu{};
97 gpu.name = "gpu";
98 gpu.containerEdits.deviceNodes.push_back(std::move(dxg));
99 gpu.containerEdits.mounts.push_back(std::move(libs));
100 gpu.containerEdits.mounts.push_back(std::move(drivers));
101 gpu.containerEdits.hooks.push_back(std::move(hook));
102
103 wsl::shared::cdi::Spec spec{};
104 spec.cdiVersion = "0.6.0";
105 spec.kind = LX_WSLC_CDI_KIND;
106 spec.devices.push_back(std::move(gpu));
107
108 THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/cdi", 0755) < 0);
109 THROW_LAST_ERROR_IF(
110 WriteToFile("/etc/cdi/microsoft.com-wslc.json", nlohmann::json(spec).dump().c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0);
111 }
112 CATCH_LOG()
113
114 void WriteDockerDaemonConfig()
115 try
116 {
117 constexpr auto c_daemonConfigPath = "/etc/docker/daemon.json";
118
119 THROW_ERRNO_IF(EEXIST, std::filesystem::exists(c_daemonConfigPath));
120
121 nlohmann::json config = nlohmann::json::object();
122 config["features"]["cdi"] = true;
123
124 THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/docker", 0755) < 0);
125 THROW_LAST_ERROR_IF(WriteToFile(c_daemonConfigPath, config.dump().c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0);
126 }
127 CATCH_LOG()
128
129 int WslcGpuHookEntry()
130 try
131 {
132 // OCI runtime hooks receive the container state as JSON on stdin.
133 const auto state = nlohmann::json::parse(std::cin);
134 const std::filesystem::path bundle = state.at("bundle").get<std::string>();
135 THROW_ERRNO_IF(EINVAL, !bundle.is_absolute());
136
137 // Read the OCI spec's root.path from <bundle>/config.json. This is either an absolute path to
138 // the overlay-merged rootfs or a path relative to the bundle directory.
139 const auto spec = nlohmann::json::parse(UtilReadFileContent((bundle / "config.json").native()));
140 std::filesystem::path rootfsPath = spec.at("root").at("path").get<std::string>();
141 if (rootfsPath.is_relative())
142 {
143 rootfsPath = bundle / rootfsPath;
144 }
145
146 rootfsPath = std::filesystem::canonical(rootfsPath);
147 THROW_ERRNO_IF(EINVAL, rootfsPath == "/");
148
149 THROW_LAST_ERROR_IF(chroot(rootfsPath.c_str()) < 0);
150
151 THROW_LAST_ERROR_IF(UtilMkdirPath("/etc/ld.so.conf.d", 0755) < 0);
152 THROW_LAST_ERROR_IF(WriteToFile("/etc/ld.so.conf.d/ld.wsl.conf", LXSS_LIB_PATH "\n", O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC) < 0);
153
154 // Run the container's own ldconfig so it updates /etc/ld.so.cache.
155 const char* const ldArgv[] = {LDCONFIG_COMMAND, nullptr};
156 THROW_LAST_ERROR_IF(UtilCreateProcessAndWait(ldArgv[0], ldArgv) < 0);
157
158 constexpr auto c_binPath = "/usr/bin";
159 if (std::filesystem::is_directory(LXSS_LIB_PATH))
160 {
161 for (const auto& entry : std::filesystem::directory_iterator(LXSS_LIB_PATH))
162 {
163 const auto fileName = entry.path().filename().string();
164 if (fileName.find(".so") != std::string::npos || !entry.is_regular_file())
165 {
166 continue;
167 }
168
169 const auto target = std::format("{}/{}", c_binPath, fileName);
170 if (UtilMountFile(entry.path().c_str(), target.c_str()) < 0)
171 {
172 LOG_ERROR("UtilMountFile({}, {}) failed {}", entry.path().c_str(), target, errno);
173 }
174 }
175 }
176
177 return 0;
178 }
179 CATCH_RETURN_ERRNO()
180
181 void WSLCEnableCrashDumpCollection()
182 {
183 if (symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0 && errno != EEXIST)
184 {
185 LOG_ERROR("symlink(/init, /" LX_INIT_WSL_CAPTURE_CRASH ") failed {}", errno);
186 return;
187 }
188
189 // If the first character is a pipe, then the kernel will interpret this path as a command.
190 constexpr auto core_pattern = "|/" LX_INIT_WSL_CAPTURE_CRASH " %t %E %p %s";
191 WriteToFile("/proc/sys/kernel/core_pattern", core_pattern);
192 }
193
194 void HandleMessageImpl(
195 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_GET_DISK& Message, const gsl::span<gsl::byte>& Buffer)
196 {
197 wsl::shared::MessageWriter<WSLC_GET_DISK_RESULT> writer;
198
199 try
200 {
201 auto deviceName = GetLunDeviceName(Message.ScsiLun);
202
203 writer->Result = 0;
204 writer.WriteString("/dev/" + deviceName);
205 }
206 catch (...)
207 {
208 writer->Result = wil::ResultFromCaughtException();
209 }
210
211 Transaction.Send<WSLC_GET_DISK::TResponse>(writer.Span());
212 }
213
214 void HandleMessageImpl(
215 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_LISTDIR& Message, const gsl::span<gsl::byte>& Buffer)
216 {
217 wsl::shared::MessageWriter<WSLC_LISTDIR_RESULT> writer;
218
219 try
220 {
221 const auto* path = wsl::shared::string::FromMessageBuffer<WSLC_LISTDIR>(Buffer);
222 THROW_ERRNO_IF(EINVAL, path == nullptr);
223
224 wil::unique_dir dir{opendir(path)};
225 THROW_LAST_ERROR_IF(!dir);
226
227 std::vector<std::string> entries;
228 for (dirent64* entry = readdir64(dir.get()); entry != nullptr; entry = readdir64(dir.get()))
229 {
230 const std::string_view name{entry->d_name};
231 if (name == "." || name == "..")
232 {
233 continue;
234 }
235
236 entries.emplace_back(name);
237 }
238
239 auto pointers = wsl::shared::string::StringPointersFromArray(entries, false);
240 writer.WriteStringArray(writer->EntriesIndex, pointers.data(), pointers.size());
241 writer->Result = 0;
242 }
243 catch (...)
244 {
245 writer->Result = wil::ResultFromCaughtException();
246 }
247
248 Transaction.Send<WSLC_LISTDIR::TResponse>(writer.Span());
249 }
250
251 void HandleMessageImpl(
252 wsl::shared::SocketChannel& Channel,
253 wsl::shared::Transaction& Transaction,
254 const WSLC_GET_GUEST_CAPABILITIES& Message,
255 const gsl::span<gsl::byte>& Buffer)
256 {
257 WSLC_GET_GUEST_CAPABILITIES_RESULT response{};
258 response.Header.MessageType = WSLC_GET_GUEST_CAPABILITIES_RESULT::Type;
259 response.Header.MessageSize = sizeof(response);
260
261 auto pool = UtilReadHvPciSwiotlbPool();
262 response.HvPciSwiotlbBase = pool.Base;
263 response.HvPciSwiotlbSize = pool.Size;
264
265 Transaction.Send<WSLC_GET_GUEST_CAPABILITIES_RESULT>(response);
266 }
267
268 void HandleMessageImpl(
269 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_ACCEPT& Message, const gsl::span<gsl::byte>& Buffer)
270 {
271 sockaddr_vm SocketAddress{};
272 wil::unique_fd ListenSocket{UtilListenVsockAnyPort(&SocketAddress, 1, true)};
273 THROW_LAST_ERROR_IF(!ListenSocket);
274
275 Transaction.SendResultMessage<uint32_t>(SocketAddress.svm_port);
276
277 wil::unique_fd Socket{
278 UtilAcceptVsock(ListenSocket.get(), SocketAddress, SESSION_LEADER_ACCEPT_TIMEOUT_MS, Message.Fd != -1 ? SOCK_CLOEXEC : 0)};
279 THROW_LAST_ERROR_IF(!Socket);
280
281 if (Message.Fd != -1)
282 {
283 THROW_LAST_ERROR_IF(dup2(Socket.get(), Message.Fd) < 0);
284 }
285 else
286 {
287 Transaction.SendResultMessage<int32_t>(Socket.get());
288 Socket.release();
289 }
290 }
291
292 void HandleMessageImpl(
293 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_CONNECT& Message, const gsl::span<gsl::byte>& Buffer)
294 {
295 int32_t result = -EINVAL;
296 auto sendResult = wil::scope_exit([&]() { Transaction.SendResultMessage(result); });
297
298 auto fd = UtilConnectVsock(Message.HostPort, true);
299 if (!fd)
300 {
301 result = -errno;
302 }
303 else
304 {
305 result = fd.release();
306 }
307 }
308
309 void HandleMessageImpl(
310 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_UNIX_CONNECT& Message, const gsl::span<gsl::byte>& Buffer)
311 {
312 // Make sure to close the channel since no more messages can be processed after this.
313 auto closeChannel = wil::scope_exit([&]() { Channel.Close(); });
314
315 int result = -1;
316 auto sendResult = wil::scope_exit([&]() { Transaction.SendResultMessage(result); });
317
318 const auto* path = wsl::shared::string::FromSpan(Buffer, Message.PathOffset);
319 THROW_ERRNO_IF(EINVAL, path == nullptr);
320
321 wil::unique_fd socket;
322
323 try
324 {
325 socket = UtilConnectUnix(path);
326 result = 0;
327 }
328 catch (...)
329 {
330 result = wil::ResultFromCaughtException();
331 }
332
333 if (result != 0)
334 {
335 return;
336 }
337
338 sendResult.reset();
339
340 // Relay data between the two sockets.
341 pollfd pollDescriptors[2];
342 pollDescriptors[0].fd = socket.get();
343 pollDescriptors[0].events = POLLIN;
344 pollDescriptors[1].fd = Channel.Socket();
345 pollDescriptors[1].events = POLLIN;
346
347 std::vector<gsl::byte> relayBuffer;
348 while (true)
349 {
350 auto result = poll(pollDescriptors, COUNT_OF(pollDescriptors), -1);
351 THROW_LAST_ERROR_IF(result < 0);
352
353 if (pollDescriptors[0].revents & (POLLIN | POLLHUP | POLLERR))
354 {
355 auto bytesRead = UtilReadBuffer(pollDescriptors[0].fd, relayBuffer);
356 if (bytesRead < 0)
357 {
358 LOG_ERROR("read failed {}", errno);
359 break;
360 }
361 else if (bytesRead == 0)
362 {
363 // Unix socket has been closed. Gracefully half-close the
364 // hvsocket so the Windows side receives a clean EOF instead
365 // of ERROR_BROKEN_PIPE.
366 pollDescriptors[0].fd = -1;
367 if (shutdown(Channel.Socket(), SHUT_WR) < 0)
368 {
369 LOG_ERROR("shutdown({}, SHUT_WR) failed {}", Channel.Socket(), errno);
370 }
371
372 break;
373 }
374 else if (UtilWriteBuffer(Channel.Socket(), relayBuffer.data(), bytesRead) < 0)
375 {
376 LOG_ERROR("write failed {}", errno);
377 break;
378 }
379 }
380
381 if (pollDescriptors[1].revents & (POLLIN | POLLHUP | POLLERR))
382 {
383 auto bytesRead = UtilReadBuffer(pollDescriptors[1].fd, relayBuffer);
384 if (bytesRead < 0)
385 {
386 LOG_ERROR("read failed {}", errno);
387 break;
388 }
389 else if (bytesRead == 0)
390 {
391 // hvsocket has been closed.
392 pollDescriptors[1].fd = -1;
393
394 // Shutdown the write side of the socket. This is required so docker knows when stdin is in EOF for instance.
395 if (shutdown(socket.get(), SHUT_WR) < 0)
396 {
397 LOG_ERROR("shutdown({}, SHUT_WR) failed {}", socket.get(), errno);
398 }
399 }
400 else if (UtilWriteBuffer(socket.get(), relayBuffer.data(), bytesRead) < 0)
401 {
402 if (errno == ECONNRESET || errno == EPIPE)
403 {
404 // The other side of the socket has been closed. This isn't necessarily an error, so stop relaying this direction.
405 pollDescriptors[1].fd = -1;
406 continue;
407 }
408
409 LOG_ERROR("write failed {}", errno);
410 break;
411 }
412 }
413 }
414 }
415
416 void HandleMessageImpl(
417 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_TTY_RELAY& Message, const gsl::span<gsl::byte>&)
418 {
419 THROW_LAST_ERROR_IF(fcntl(Message.TtyMaster, F_SETFL, O_NONBLOCK) < 0);
420
421 wsl::shared::SocketChannel TerminalControlChannel({Message.TtyControl}, "TerminalControl");
422
423 pollfd pollDescriptors[3];
424
425 pollDescriptors[0].fd = Message.Socket;
426 pollDescriptors[0].events = POLLIN;
427 pollDescriptors[1].fd = Message.TtyMaster;
428 pollDescriptors[1].events = POLLIN;
429 pollDescriptors[2].fd = Message.TtyControl;
430 pollDescriptors[2].events = POLLIN;
431
432 std::vector<gsl::byte> pendingStdin;
433 std::vector<gsl::byte> buffer;
434
435 Channel.Close();
436
437 while (true)
438 {
439 ssize_t bytesWritten = 0;
440 auto result = poll(pollDescriptors, COUNT_OF(pollDescriptors), pendingStdin.empty() ? -1 : 100);
441 if (!pendingStdin.empty())
442 {
443 bytesWritten = write(Message.TtyMaster, pendingStdin.data(), pendingStdin.size());
444 if (bytesWritten < 0)
445 {
446 if (errno != EAGAIN && errno != EWOULDBLOCK)
447 {
448 LOG_ERROR("delayed stdin write failed {}", errno);
449 }
450 }
451 else
452 {
453 WI_ASSERT(static_cast<size_t>(bytesWritten) <= pendingStdin.size());
454
455 pendingStdin.erase(pendingStdin.begin(), pendingStdin.begin() + bytesWritten);
456 }
457 }
458
459 if (result < 0)
460 {
461 LOG_ERROR("poll failed {}", errno);
462 break;
463 }
464
465 // Relay stdin.
466 if (pollDescriptors[0].revents & (POLLIN | POLLHUP | POLLERR) && pendingStdin.empty())
467 {
468 auto bytesRead = UtilReadBuffer(pollDescriptors[0].fd, buffer);
469 if (bytesRead < 0)
470 {
471 LOG_ERROR("read failed {}", errno);
472 break;
473 }
474 else if (bytesRead == 0)
475 { // Stdin has been closed.
476 pollDescriptors[0].fd = -1;
477
478 CLOSE(Message.TtyMaster);
479 }
480 else
481 {
482 bytesWritten = write(Message.TtyMaster, buffer.data(), bytesRead);
483 if (bytesWritten < 0)
484 {
485 //
486 // If writing on stdin's pipe would block, mark the write as pending and continue.
487 // This is required because blocking on the write() could lead to a deadlock if the child process
488 // is blocking trying to write on stderr / stdout while the relay tries to write stdin.
489 //
490
491 if (errno == EWOULDBLOCK || errno == EAGAIN)
492 {
493 assert(pendingStdin.empty());
494 pendingStdin.assign(buffer.begin(), buffer.begin() + bytesRead);
495 }
496 else
497 {
498 LOG_ERROR("write failed {}", errno);
499 break;
500 }
501 }
502 else if (bytesWritten < bytesRead)
503 {
504 // Partial write — buffer the remaining bytes for the next iteration.
505 pendingStdin.assign(buffer.begin() + bytesWritten, buffer.begin() + bytesRead);
506 }
507 }
508 }
509
510 // Relay stdout & stderr
511 if (pollDescriptors[1].revents & (POLLIN | POLLHUP | POLLERR))
512 {
513 auto bytesRead = UtilReadBuffer(pollDescriptors[1].fd, buffer);
514 if (bytesRead <= 0)
515 {
516 if (bytesRead < 0 && errno != EIO)
517 {
518 LOG_ERROR("read failed {} {}", bytesRead, errno);
519 }
520
521 // The tty has been closed, stop relaying.
522 CLOSE(pollDescriptors[1].fd);
523 pollDescriptors[1].fd = -1;
524 break;
525 }
526
527 bytesWritten = UtilWriteBuffer(Message.Socket, buffer.data(), bytesRead);
528 if (bytesWritten < 0)
529 {
530 LOG_ERROR("write failed {}", errno);
531 CLOSE(pollDescriptors[1].fd);
532 pollDescriptors[1].fd = -1;
533 }
534 }
535
536 // Process message from the terminal control channel.
537 if (pollDescriptors[2].revents & (POLLIN | POLLHUP | POLLERR))
538 {
539 auto [ttyMessage, _] = TerminalControlChannel.ReceiveMessageOrClosed<WSLC_TERMINAL_CHANGED>();
540
541 //
542 // A zero-byte read means that the control channel has been closed
543 // and that the relay process should exit.
544 //
545
546 if (ttyMessage == nullptr)
547 {
548 break;
549 }
550
551 winsize terminal{};
552 terminal.ws_col = ttyMessage->Columns;
553 terminal.ws_row = ttyMessage->Rows;
554 if (ioctl(Message.TtyMaster, TIOCSWINSZ, &terminal))
555 {
556 LOG_ERROR("ioctl({}, TIOCSWINSZ) failed {}", Message.TtyMaster, errno);
557 }
558 }
559 }
560
561 // Shutdown sockets and tty
562 UtilSocketShutdown(Message.Socket, SHUT_WR);
563 }
564
565 void HandleMessageImpl(
566 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_FORK& Message, const gsl::span<gsl::byte>& Buffer)
567 {
568 sockaddr_vm SocketAddress{};
569 wil::unique_fd ListenSocket{UtilListenVsockAnyPort(&SocketAddress, 1, true)};
570 THROW_LAST_ERROR_IF(!ListenSocket);
571
572 WSLC_FORK_RESULT Response{};
573 Response.Header.MessageSize = sizeof(Response);
574 Response.Header.MessageType = WSLC_FORK_RESULT::Type;
575 Response.Port = SocketAddress.svm_port;
576
577 std::promise<pid_t> childPid;
578
579 {
580 auto childLogic = [ListenSocketFd = ListenSocket.get(), SocketAddress, &Channel, &Message, &childPid]() mutable {
581 bool futureSet = false;
582 try
583 {
584 wil::unique_fd ListenSocket;
585
586 // Close parent channel
587 if (Message.ForkType == WSLC_FORK::Process || Message.ForkType == WSLC_FORK::Pty)
588 {
589 Channel.Close();
590 }
591
592 if (Message.ForkType == WSLC_FORK::Thread)
593 {
594 // If this is a thread, detach from the process' fd table.
595 // This prevents other threads from creating child processes that could inherit fds that this thread could create.
596 // N.B. This needs to happen before childPid is signalled to ensure that ListenSocket() is not closed by the parent before getting duplicated in the child's fd table.
597 THROW_LAST_ERROR_IF(unshare(CLONE_FILES) < 0);
598 }
599
600 // ListenSocket should only be assigned after this thread is guaranteed to have its own fd table (either via unshare() or a child process).
601 ListenSocket.reset(ListenSocketFd);
602 childPid.set_value(getpid());
603 futureSet = true;
604
605 wil::unique_fd ProcessSocket{UtilAcceptVsock(ListenSocket.get(), SocketAddress, SESSION_LEADER_ACCEPT_TIMEOUT_MS)};
606 THROW_LAST_ERROR_IF(!ProcessSocket);
607
608 ListenSocket.reset();
609
610 auto subChannel = wsl::shared::SocketChannel{std::move(ProcessSocket), "ForkedChannel"};
611 ProcessMessages(subChannel);
612 }
613 catch (...)
614 {
615 LOG_CAUGHT_EXCEPTION();
616 if (!futureSet)
617 {
618 childPid.set_exception(std::current_exception());
619 }
620 }
621 };
622
623 if (Message.ForkType == WSLC_FORK::Thread)
624 {
625 std::thread thread{std::move(childLogic)};
626 thread.detach();
627
628 Response.Pid = childPid.get_future().get();
629 }
630 else if (Message.ForkType == WSLC_FORK::Process)
631 {
632 Response.Pid = UtilCreateChildProcess("CreateChildProcess", std::move(childLogic));
633 }
634 else if (Message.ForkType == WSLC_FORK::Pty)
635 {
636 THROW_LAST_ERROR_IF(prctl(PR_SET_CHILD_SUBREAPER, 1) < 0);
637
638 winsize ttySize{};
639 ttySize.ws_col = Message.TtyColumns;
640 ttySize.ws_row = Message.TtyRows;
641
642 wil::unique_fd ttyMaster;
643 auto result = forkpty(ttyMaster.addressof(), nullptr, nullptr, &ttySize);
644 THROW_ERRNO_IF(errno, result < 0);
645
646 if (result == 0) // Child
647 {
648 sigset_t SignalMask;
649 sigemptyset(&SignalMask);
650 THROW_LAST_ERROR_IF(sigprocmask(SIG_SETMASK, &SignalMask, nullptr) < 0);
651
652 try
653 {
654 childLogic();
655 }
656 CATCH_LOG();
657 exit(0);
658 }
659
660 Response.PtyMasterFd = ttyMaster.release();
661 Response.Pid = result;
662 }
663 else
664 {
665 LOG_ERROR("Unexpected fork type: {}", Message.Type);
666 THROW_ERRNO(EINVAL);
667 }
668 }
669
670 ListenSocket.reset();
671 Transaction.Send(Response);
672 }
673
674 template <typename TMessage>
675 void HandleMountMessage(
676 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const TMessage& Message, const gsl::span<gsl::byte>& Buffer)
677 {
678 WSLC_MOUNT_RESULT response{};
679 response.Header.MessageType = WSLC_MOUNT_RESULT::Type;
680 response.Header.MessageSize = sizeof(response);
681
682 try
683 {
684 auto readField = [&](unsigned int index) -> const char* {
685 if (index > 0)
686 {
687 return wsl::shared::string::FromSpan(Buffer, index);
688 }
689
690 return "";
691 };
692
693 const char* mountOptions = readField(Message.OptionsIndex);
694 mountutil::ParsedOptions options{};
695 if (Message.OptionsIndex > 0)
696 {
697 options = mountutil::MountParseFlags(mountOptions);
698 }
699
700 const char* source = readField(Message.SourceIndex);
701 const char* target = readField(Message.DestinationIndex);
702
703 // Chroot without OverlayFs is not supported — the chroot logic depends on the overlay target path.
704 THROW_ERRNO_IF(EINVAL, WI_IsFlagSet(Message.Flags, WSLC_MOUNT::Chroot) && !WI_IsFlagSet(Message.Flags, WSLC_MOUNT::OverlayFs));
705
706 auto type = readField(Message.TypeIndex);
707 if constexpr (std::is_same_v<TMessage, WSLC_MOUNT_VIRTIOFS>)
708 {
709 const char* childName = readField(Message.ChildNameIndex);
710 THROW_ERRNO_IF(EINVAL, !wsl::shared::string::IsEqual(type, VIRTIO_FS_TYPE));
711 THROW_LAST_ERROR_IF(MountVirtioFsChild(source, childName, target, mountOptions) < 0);
712 }
713 else
714 {
715 THROW_LAST_ERROR_IF(UtilMount(source, target, type, options.MountFlags, options.StringOptions.c_str(), c_defaultRetryTimeout) < 0);
716 }
717
718 // Workaround for a Linux bug where virtiofs permissions aren't properly propagated when an overlay is mounted on top of a virtiofs share before the permissions have been fetched.
719 // TODO: Remove once fixed upstream.
720 if (wsl::shared::string::IsEqual(type, VIRTIO_FS_TYPE))
721 {
722 struct stat targetStat{};
723 if (stat(target, &targetStat) < 0)
724 {
725 LOG_ERROR("stat({}) after virtiofs mount failed {}", target, errno);
726 }
727 }
728
729 std::optional<std::string> overlayTarget;
730 if (WI_IsFlagSet(Message.Flags, WSLC_MOUNT::OverlayFs))
731 {
732 overlayTarget.emplace(target + std::string("-rw"));
733 if (std::filesystem::exists(overlayTarget->c_str()))
734 {
735 LOG_ERROR("Overlay directory already exists: {}", overlayTarget.value());
736 THROW_ERRNO(EEXIST);
737 }
738
739 THROW_LAST_ERROR_IF(UtilMountOverlayFs(overlayTarget->c_str(), target));
740
741 if (WI_IsFlagSet(Message.Flags, WSLC_MOUNT::Chroot))
742 {
743 // If this is a chroot, simply mounts the overlay on top of the "-rw" folder.
744 // We'll chroot into it later, so moving the mountpoint isn't needed.
745 target = overlayTarget->c_str();
746
747 // Move standard filesystem mounts into the chroot.
748 // MS_MOVE moves the entire subtree, so /dev/pts comes with /dev and /sys/fs/cgroup comes with /sys.
749 for (const auto* mountPoint : {"/dev", "/proc", "/sys"})
750 {
751 auto chrootTarget = std::format("{}{}", target, mountPoint);
752 std::filesystem::create_directories(chrootTarget);
753
754 THROW_LAST_ERROR_IF(mount(mountPoint, chrootTarget.c_str(), "none", MS_MOVE, nullptr) < 0);
755 }
756
757 THROW_LAST_ERROR_IF(MountInit(std::format("{}/init", target).c_str()) < 0); // Required to call /gns later
758
759 // If it exists, mount /etc/resolv.conf
760 if (std::filesystem::exists("/etc/resolv.conf"))
761 {
762 THROW_LAST_ERROR_IF(UtilMountFile("/etc/resolv.conf", std::format("{}/etc/resolv.conf", target).c_str()) < 0);
763 }
764
765 // If the modules were previously mounted, move them to the chroot.
766 if (g_state.ModulesMountPoint.has_value())
767 {
768 auto chrootTarget = std::format("{}/{}", target, g_state.ModulesMountPoint->native());
769 std::filesystem::create_directories(chrootTarget);
770
771 THROW_LAST_ERROR_IF(mount(g_state.ModulesMountPoint->c_str(), chrootTarget.c_str(), "none", MS_MOVE, nullptr) < 0);
772 }
773 }
774 else
775 {
776 // Move the "-rw" mount to its final target.
777 THROW_LAST_ERROR_IF(mount(overlayTarget->c_str(), target, "none", MS_MOVE, nullptr) < 0);
778
779 // Clean up the underlying mount point
780 THROW_LAST_ERROR_IF(umount((overlayTarget.value() + "/rw").c_str()));
781
782 std::error_code error;
783 std::filesystem::remove_all(overlayTarget.value(), error);
784 if (error.value() != 0)
785 {
786 THROW_ERRNO(error.value());
787 }
788 }
789 }
790
791 if (WI_IsFlagSet(Message.Flags, WSLC_MOUNT::Chroot))
792 {
793 THROW_LAST_ERROR_IF(Chroot(target) < 0);
794
795 // Recreate the /init symlinks inside the new root.
796 THROW_LAST_ERROR_IF(symlink("/init", "/" LX_INIT_WSL_CAPTURE_CRASH) < 0 && errno != EEXIST);
797 THROW_LAST_ERROR_IF(symlink("/init", "/" LX_INIT_WSLC_GPU_HOOK) < 0 && errno != EEXIST);
798
799 WriteWslcCdiSpec();
800 WriteDockerDaemonConfig();
801
802 // Start the memory reduction thread now that procfs is in its final location.
803 static std::once_flag memoryReductionFlag;
804 std::call_once(memoryReductionFlag, [] { StartMemoryReductionThread(LxMiniInitMemoryReclaimModeDropCache); });
805 }
806
807 response.Result = 0;
808 }
809 catch (...)
810 {
811 LOG_CAUGHT_EXCEPTION();
812 response.Result = wil::ResultFromCaughtException();
813 }
814
815 Transaction.Send<WSLC_MOUNT_RESULT>(response);
816 }
817
818 void HandleMessageImpl(
819 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_MOUNT& Message, const gsl::span<gsl::byte>& Buffer)
820 {
821 HandleMountMessage(Channel, Transaction, Message, Buffer);
822 }
823
824 void HandleMessageImpl(
825 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_MOUNT_VIRTIOFS& Message, const gsl::span<gsl::byte>& Buffer)
826 {
827 HandleMountMessage(Channel, Transaction, Message, Buffer);
828 }
829
830 void HandleMessageImpl(
831 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_MOUNT_MODULES& Message, const gsl::span<gsl::byte>& Buffer)
832 {
833 WSLC_MOUNT_RESULT response{};
834 response.Header.MessageType = WSLC_MOUNT_RESULT::Type;
835 response.Header.MessageSize = sizeof(response);
836
837 try
838 {
839 assert(!g_state.ModulesMountPoint.has_value());
840
841 utsname unameBuffer{};
842 THROW_LAST_ERROR_IF(uname(&unameBuffer) < 0);
843
844 const char* source = wsl::shared::string::FromSpan(Buffer, Message.SourceIndex);
845 THROW_LAST_ERROR_IF(UtilMount(source, c_kernelModulesVhdMountPoint, "ext4", MS_RDONLY, nullptr, c_defaultRetryTimeout) < 0);
846
847 auto unmountVhd = wil::scope_exit([&]() {
848 if (umount(c_kernelModulesVhdMountPoint) < 0)
849 {
850 LOG_ERROR("umount({}) failed {}", c_kernelModulesVhdMountPoint, errno);
851 }
852 });
853
854 g_state.ModulesMountPoint = std::format("/lib/modules/{}", unameBuffer.release);
855 const std::string modulesSource = std::format("{}/{}/modules", c_kernelModulesVhdMountPoint, unameBuffer.release);
856 THROW_LAST_ERROR_IF(
857 UtilMount(modulesSource.c_str(), g_state.ModulesMountPoint->c_str(), nullptr, (MS_BIND | MS_REC), nullptr, c_defaultRetryTimeout) < 0);
858
859 response.Result = 0;
860 }
861 catch (...)
862 {
863 LOG_CAUGHT_EXCEPTION();
864 response.Result = wil::ResultFromCaughtException();
865 }
866
867 Transaction.Send<WSLC_MOUNT_RESULT>(response);
868 }
869
870 void HandleMessageImpl(
871 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_EXEC& Message, const gsl::span<gsl::byte>& Buffer)
872 {
873 auto Executable = wsl::shared::string::FromSpan(Buffer, Message.ExecutableIndex);
874 auto ArgumentArray = wsl::shared::string::ArrayFromSpan(Buffer, Message.CommandLineIndex);
875 auto ArgumentPointers = wsl::shared::string::StringPointersFromArray(ArgumentArray, true);
876
877 auto EnvironmentArray = wsl::shared::string::ArrayFromSpan(Buffer, Message.EnvironmentIndex);
878 auto EnvironmentPointers = wsl::shared::string::StringPointersFromArray(EnvironmentArray, true);
879
880 execvpe(Executable, (char* const*)(ArgumentPointers.data()), (char* const*)(EnvironmentPointers.data()));
881
882 // Only reached if exec() fails
883 Transaction.SendResultMessage<int32_t>(errno);
884 }
885
886 void HandleMessageImpl(
887 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_PORT_RELAY& Message, const gsl::span<gsl::byte>& Buffer)
888 {
889 sockaddr_vm SocketAddress{};
890 wil::unique_fd ListenSocket{UtilListenVsockAnyPort(&SocketAddress, 10, false)};
891 THROW_LAST_ERROR_IF(!ListenSocket);
892
893 Transaction.SendResultMessage<uint32_t>(SocketAddress.svm_port);
894 Channel.Close();
895 UtilSetThreadName("PortRelay");
896
897 // If the host end of a relay socket is reset, a write will raise SIGPIPE. Ignore it so
898 // the failure surfaces as an EPIPE return value (handled by the relay loop) instead of
899 // terminating this forked PortRelay process and tearing down the vsock accept listener.
900 THROW_LAST_ERROR_IF(signal(SIGPIPE, SIG_IGN) == SIG_ERR);
901
902 RunLocalHostRelay(SocketAddress, ListenSocket.get());
903 }
904
905 void HandleMessageImpl(
906 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_SIGNAL& Message, const gsl::span<gsl::byte>& Buffer)
907 {
908 auto result = kill(Message.Pid, Message.Signal);
909 Transaction.SendResultMessage(result < 0 ? errno : 0);
910 }
911
912 void HandleMessageImpl(wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_UNMOUNT&, const gsl::span<gsl::byte>& Buffer)
913 {
914 auto* path = wsl::shared::string::FromMessageBuffer<WSLC_UNMOUNT>(Buffer);
915 auto result = umount(path) < 0 ? errno : 0;
916 if (result == 0)
917 {
918 result = rmdir(path) < 0 ? errno : 0;
919 }
920
921 Transaction.SendResultMessage<int32_t>(result);
922 }
923
924 void HandleMessageImpl(
925 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_WRITE_FILE& Message, const gsl::span<gsl::byte>& Buffer)
926 {
927 if (Message.PathIndex >= Buffer.size() || Message.ContentIndex > Buffer.size() ||
928 Message.ContentLength > Buffer.size() - Message.ContentIndex)
929 {
930 Transaction.SendResultMessage<int32_t>(EINVAL);
931 return;
932 }
933
934 const auto* path = wsl::shared::string::FromSpan(Buffer, Message.PathIndex);
935 const auto content = Buffer.subspan(Message.ContentIndex, Message.ContentLength);
936
937 int result = 0;
938 if (UtilMkdirPath(path, 0755, true) < 0)
939 {
940 result = errno;
941 }
942 else
943 {
944 wil::unique_fd fd{open(path, Message.OpenFlags, Message.Permissions)};
945 if (!fd)
946 {
947 result = errno;
948 }
949 else if (UtilWriteBuffer(fd.get(), content) != static_cast<ssize_t>(content.size()))
950 {
951 result = errno;
952 }
953 }
954
955 Transaction.SendResultMessage<int32_t>(result);
956 }
957
958 void HandleMessageImpl(
959 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_DETACH& Message, const gsl::span<gsl::byte>& Buffer)
960 {
961 sync();
962
963 Transaction.SendResultMessage<int32_t>(DetachScsiDisk(Message.Lun));
964 }
965
966 template <typename TMessage, typename... Args>
967 void HandleMessage(wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, LX_MESSAGE_TYPE Type, const gsl::span<gsl::byte>& Buffer)
968 {
969 if (TMessage::Type == Type)
970 {
971 if (Buffer.size() < sizeof(TMessage))
972 {
973 LOG_ERROR("Received message {}, but size is too small: {}. Expected {}", Type, Buffer.size(), sizeof(TMessage));
974 THROW_ERRNO(EINVAL);
975 }
976
977 const auto Message = gslhelpers::try_get_struct<TMessage>(Buffer);
978 HandleMessageImpl(Channel, Transaction, *Message, Buffer);
979
980 return;
981 }
982 else
983 {
984 if constexpr (sizeof...(Args) > 0)
985 {
986 HandleMessage<Args...>(Channel, Transaction, Type, Buffer);
987 }
988 else
989 {
990 LOG_ERROR("Received unknown message type: {}", Type);
991 THROW_ERRNO(EINVAL);
992 }
993 }
994 }
995
996 void HandleMessageImpl(
997 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_WATCH_PROCESSES& Message, const gsl::span<gsl::byte>& Buffer)
998 {
999 // Create a signalfd to watch for SIGCHLD
1000 sigset_t mask{};
1001 sigemptyset(&mask);
1002 sigaddset(&mask, SIGCHLD);
1003 THROW_LAST_ERROR_IF(UtilSaveBlockedSignals(mask) < 0);
1004
1005 wil::unique_fd signalFd = signalfd(-1, &mask, SFD_CLOEXEC);
1006 THROW_LAST_ERROR_IF(signalFd.get() < 0);
1007
1008 Transaction.SendResultMessage<uint32_t>(0);
1009
1010 // Poll for either a received signal or a new message on the channel.
1011 pollfd polls[2]{};
1012 polls[0].fd = signalFd.get();
1013 polls[0].events = POLLIN;
1014 polls[1].fd = Channel.Socket();
1015 polls[1].events = POLLIN;
1016
1017 while (true)
1018 {
1019 auto result = poll(polls, COUNT_OF(polls), -1);
1020 THROW_LAST_ERROR_IF(result < 0);
1021
1022 // TODO: Check for poll errors
1023 if (polls[0].revents & POLLIN)
1024 {
1025 signalfd_siginfo sigInfo{};
1026 auto bytes = TEMP_FAILURE_RETRY(read(signalFd.get(), &sigInfo, sizeof(signalfd_siginfo)));
1027
1028 THROW_LAST_ERROR_IF(bytes < 0);
1029 if (bytes != sizeof(sigInfo))
1030 {
1031 LOG_ERROR("Unexpected read size: {} (expected {})", bytes, sizeof(sigInfo));
1032 THROW_ERRNO(EINVAL);
1033 }
1034
1035 if (sigInfo.ssi_signo != SIGCHLD)
1036 {
1037 LOG_ERROR("Received unexpected signal from signalfd: {}", sigInfo.ssi_signo);
1038 THROW_LAST_ERROR_IF(EINVAL);
1039 }
1040
1041 // We received a SIGCHLD. This means that one or more children processes have exited.
1042
1043 bool exitedProcess = false; // Sanity check
1044
1045 while (true)
1046 {
1047 int status{};
1048 result = waitpid(-1, &status, WNOHANG);
1049 if (result < 0 && errno != ECHILD)
1050 {
1051 THROW_LAST_ERROR();
1052 }
1053
1054 if (result <= 0)
1055 {
1056 break;
1057 }
1058
1059 exitedProcess = true;
1060
1061 WSLC_PROCESS_EXITED message{};
1062 message.Pid = result;
1063 if (WIFSIGNALED(status))
1064 {
1065 message.Signaled = true;
1066 message.Code = WTERMSIG(status);
1067 }
1068 else if (WIFEXITED(status))
1069 {
1070 message.Code = WEXITSTATUS(status);
1071 }
1072 else
1073 {
1074 LOG_ERROR("Received SIGCHLD for process that was neither signaled nor exited. Pid: {}, Status: {}", result, status);
1075 }
1076
1077 // Async notification - not a transaction reply
1078 Channel.SendMessage(message);
1079 }
1080
1081 if (!exitedProcess)
1082 {
1083 LOG_ERROR("Received SIGCHLD but no children have exited");
1084 }
1085 }
1086
1087 if (polls[1].revents & POLLIN)
1088 {
1089 auto [message, _] = Channel.ReceiveMessageOrClosed<MESSAGE_HEADER>();
1090 if (message == nullptr)
1091 {
1092 break;
1093 }
1094 else
1095 {
1096 LOG_ERROR("Received unexpected message: {}", message->MessageType);
1097 THROW_ERRNO(EINVAL);
1098 }
1099 }
1100 }
1101 }
1102
1103 void ProcessMessage(wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, LX_MESSAGE_TYPE Type, const gsl::span<gsl::byte>& Buffer)
1104 {
1105 try
1106 {
1107 HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_MOUNT_VIRTIOFS, WSLC_MOUNT_MODULES, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT, WSLC_GET_GUEST_CAPABILITIES, WSLC_LISTDIR, WSLC_WRITE_FILE>(
1108 Channel, Transaction, Type, Buffer);
1109 }
1110 catch (...)
1111 {
1112 LOG_CAUGHT_EXCEPTION();
1113
1114 // TODO: error message
1115 }
1116 }
1117
1118 void ProcessMessages(wsl::shared::SocketChannel& Channel)
1119 {
1120 while (Channel.Connected())
1121 {
1122 auto transaction = Channel.ReceiveTransaction();
1123 auto [Message, Range] = transaction.ReceiveOrClosed<MESSAGE_HEADER>();
1124 if (Message == nullptr)
1125 {
1126 break;
1127 }
1128
1129 ProcessMessage(Channel, transaction, Message->MessageType, Range);
1130 }
1131
1132 LOG_INFO("Process {} exiting", getpid());
1133 }
1134
1135 int WSLCEntryPoint(int Argc, char* Argv[])
1136 {
1137
1138 //
1139 // Perform initial mounts.
1140 //
1141
1142 if (UtilMount(nullptr, "/dev", "devtmpfs", MS_SHARED, nullptr) < 0)
1143 {
1144 return -1;
1145 }
1146
1147 if (UtilMount(nullptr, "/proc", "proc", MS_SHARED, nullptr) < 0)
1148 {
1149 return -1;
1150 }
1151
1152 if (UtilMount(nullptr, "/sys", "sysfs", MS_SHARED, nullptr) < 0)
1153 {
1154 return -1;
1155 }
1156
1157 if (UtilMount(nullptr, "/dev/pts", "devpts", MS_NOATIME | MS_NOSUID | MS_NOEXEC, "gid=5,mode=620") < 0)
1158 {
1159 return -1;
1160 }
1161
1162 if (UtilMount(nullptr, "/sys/fs/cgroup", "cgroup2", 0, nullptr) < 0)
1163 {
1164 return -1;
1165 }
1166
1167 //
1168 // Open kmesg for logging and ensure that the file descriptor is not set to one of the standard file descriptors.
1169 //
1170 // N.B. This is to work around a rare race condition where init is launched without /dev/console set as the controlling terminal.
1171 //
1172
1173 InitializeLogging(false);
1174 if (g_LogFd <= STDERR_FILENO)
1175 {
1176 LOG_ERROR("/init was started without /dev/console");
1177 if (dup2(g_LogFd, 3) < 0)
1178 {
1179 LOG_ERROR("dup2 failed {}", errno);
1180 }
1181
1182 close(g_LogFd);
1183 g_LogFd = 3;
1184 }
1185
1186 //
1187 // Increase the soft and hard limit for number of open file descriptors.
1188 // N.B. the soft limit shouldn't be too high. See https://github.com/microsoft/WSL/issues/12985 .
1189 //
1190
1191 rlimit Limit{};
1192 Limit.rlim_cur = 1024 * 10;
1193 Limit.rlim_max = 1024 * 1024;
1194 if (setrlimit(RLIMIT_NOFILE, &Limit) < 0)
1195 {
1196 LOG_ERROR("setrlimit(RLIMIT_NOFILE) failed {}", errno);
1197 return -1;
1198 }
1199
1200 Limit.rlim_cur = 0x4000000;
1201 Limit.rlim_max = 0x4000000;
1202 if (setrlimit(RLIMIT_MEMLOCK, &Limit) < 0)
1203 {
1204 LOG_ERROR("setrlimit(RLIMIT_MEMLOCK) failed {}", errno);
1205 return -1;
1206 }
1207
1208 //
1209 // Enable dump collection when processes crash.
1210 //
1211
1212 WSLCEnableCrashDumpCollection();
1213
1214 //
1215 // Enable logging when processes receive fatal signals.
1216 //
1217
1218 if (WriteToFile("/proc/sys/kernel/print-fatal-signals", "1\n") < 0)
1219 {
1220 return -1;
1221 }
1222
1223 //
1224 // Set the ephemeral port range
1225 //
1226
1227 if (WriteToFile(
1228 "/proc/sys/net/ipv4/ip_local_port_range",
1229 std::format("{} {}", c_ephemeralPortRange.first, c_ephemeralPortRange.second).c_str()) < 0)
1230 {
1231 return -1;
1232 }
1233
1234 THROW_LAST_ERROR_IF(UtilSetSignalHandlers(g_SavedSignalActions, false) < 0);
1235
1236 sigset_t mask{};
1237 sigemptyset(&mask);
1238 sigaddset(&mask, SIGCHLD);
1239 THROW_LAST_ERROR_IF(UtilSaveBlockedSignals(mask) < 0);
1240
1241 //
1242 // Ensure /dev/console is present and set as the controlling terminal.
1243 // If opening /dev/console times out, stdout and stderr to the logging file descriptor.
1244 //
1245
1246 wil::unique_fd ConsoleFd{};
1247
1248 try
1249 {
1250
1251 wsl::shared::retry::RetryWithTimeout<void>(
1252 [&]() {
1253 ConsoleFd = open("/dev/console", O_RDWR | O_CLOEXEC);
1254 THROW_LAST_ERROR_IF(!ConsoleFd);
1255 },
1256 c_defaultRetryPeriod,
1257 c_defaultRetryTimeout);
1258
1259 THROW_LAST_ERROR_IF(login_tty(ConsoleFd.get()) < 0);
1260 }
1261 catch (...)
1262 {
1263 if (dup3(g_LogFd, STDOUT_FILENO, O_CLOEXEC) < 0)
1264 {
1265 LOG_ERROR("dup2 failed {}", errno);
1266 }
1267
1268 if (dup3(g_LogFd, STDERR_FILENO, O_CLOEXEC) < 0)
1269 {
1270 LOG_ERROR("dup2 failed {}", errno);
1271 }
1272 }
1273
1274 //
1275 // Open /dev/null for stdin.
1276 //
1277
1278 {
1279 wil::unique_fd Fd{TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY))};
1280 if (!Fd)
1281 {
1282 LOG_ERROR("open({}) failed {}", "/dev/null", errno);
1283 return -1;
1284 }
1285
1286 if (Fd.get() == STDIN_FILENO)
1287 {
1288 Fd.release();
1289 }
1290 else
1291 {
1292 if (TEMP_FAILURE_RETRY(dup2(Fd.get(), STDIN_FILENO)) < 0)
1293 {
1294 LOG_ERROR("dup2 failed {}", errno);
1295 return -1;
1296 }
1297 }
1298 }
1299
1300 //
1301 // Enable the loopback interface.
1302 //
1303
1304 {
1305 wil::unique_fd Fd{socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)};
1306 if (!Fd)
1307 {
1308 LOG_ERROR("socket failed {}", errno);
1309 return -1;
1310 }
1311
1312 if (EnableInterface(Fd.get(), "lo") < 0)
1313 {
1314 return -1;
1315 }
1316 }
1317
1318 //
1319 // Make sure not to leak std fds to user processes.
1320 //
1321
1322 for (int fd : {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO})
1323 {
1324 SetCloseOnExec(fd, true);
1325 }
1326
1327 //
1328 // Establish the message channel with the service via hvsocket.
1329 //
1330
1331 wsl::shared::SocketChannel channel = {UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true), "mini_init"};
1332 if (channel.Socket() < 0)
1333 {
1334 FATAL_ERROR("Failed to connect to host hvsocket");
1335 }
1336 try
1337 {
1338 ProcessMessages(channel);
1339 }
1340 CATCH_LOG();
1341
1342 LOG_INFO("Init exiting");
1343
1344 try
1345 {
1346 auto children = ListInitChildProcesses();
1347
1348 while (!children.empty())
1349 {
1350
1351 // send SIGKILL to all running processes.
1352 for (auto pid : children)
1353 {
1354 if (kill(pid, SIGKILL) < 0)
1355 {
1356 LOG_ERROR("Failed to send SIGKILL to {}: {}", pid, errno);
1357 }
1358 }
1359
1360 // Wait for processes to actually exit.
1361 while (!children.empty())
1362 {
1363 auto Result = waitpid(-1, nullptr, 0);
1364 THROW_ERRNO_IF(errno, Result <= 0);
1365 LOG_INFO("Process {} exited", Result);
1366 children.erase(Result);
1367 }
1368
1369 children = ListInitChildProcesses();
1370 }
1371 }
1372 CATCH_LOG();
1373
1374 sync();
1375
1376 try
1377 {
1378 for (auto disk : ListScsiDisks())
1379 {
1380 if (DetachScsiDisk(disk) < 0)
1381 {
1382 LOG_ERROR("Failed to detach disk: {}", disk);
1383 }
1384 }
1385 }
1386 CATCH_LOG();
1387
1388 reboot(RB_POWER_OFF);
1389
1390 return 0;
1391 }