master
cpp 4,064 lines 96.4 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 util.c
8
9 Abstract:
10
11 This file utility function definitions.
12
13 --*/
14
15 #include <sys/mount.h>
16 #include <sys/wait.h>
17 #include <sys/epoll.h>
18 #include <sys/utsname.h>
19 #include <sys/types.h>
20 #include <sys/sysinfo.h>
21 #include <grp.h>
22 #include <unistd.h>
23 #include <sys/prctl.h>
24 #include <ctype.h>
25 #include <optional>
26 #include <fstream>
27 #include <iostream>
28 #include <sstream>
29 #include <algorithm>
30 #include <regex>
31 #include <thread>
32 #include <chrono>
33 #include <climits>
34 #include <pthread.h>
35 #include "common.h"
36 #include "wslpath.h"
37 #include "util.h"
38 #include "drvfs.h"
39 #include "escape.h"
40 #include "config.h"
41 #include "mountutilcpp.h"
42 #include "message.h"
43 #include "RuntimeErrorWithSourceLocation.h"
44 #include "SocketChannel.h"
45 #include "Localization.h"
46
47 #define INITIAL_MESSAGE_BUFFER_SIZE (0x1000)
48
49 #define PLAN9_RDR_PREFIX "\\\\wsl.localhost\\"
50 #define PLAN9_RDR_COMPAT_PREFIX "\\\\wsl$\\"
51
52 #define WSLENV_ENV "WSLENV"
53
54 #define WSL_CGROUPS_FIELD_ENABLED (3)
55 #define WSL_CGROUPS_FIELD_MAX WSL_CGROUPS_FIELD_ENABLED
56 #define WSL_CGROUPS_FIELD_SEP '\t'
57 #define WSL_CGROUPS_FIELD_SUBSYSTEM (0)
58
59 #define WSL_MOUNT_OPTION_SEP ','
60
61 int g_IsVmMode = -1;
62 static std::optional<int> g_CachedFeatureFlags;
63 static sigset_t g_originalSignals;
64 thread_local std::string g_threadName;
65
66 namespace wil {
67
68 thread_local std::optional<std::stringstream> ScopedWarningsCollector::g_collectedWarnings;
69
70 }
71
72 int InteropServer::Create()
73
74 /*++
75
76 Routine Description:
77
78 This routine creates an interop server unix socket and starts listening on it.
79
80 Arguments:
81
82 None.
83
84 Return Value:
85
86 0 on success, -1 on failure.
87
88 --*/
89
90 {
91 if (!m_InteropSocketPath.empty())
92 {
93 LOG_ERROR("Interop server already created");
94 return -1;
95 }
96
97 //
98 // Generate a unique name to be used for the interop socket path.
99 //
100
101 m_InteropSocketPath = std::format(WSL_INTEROP_SOCKET_FORMAT, WSL_TEMP_FOLDER, getpid(), WSL_INTEROP_SOCKET);
102
103 //
104 // Ensure the WSL temp folder exists and has the correct mode.
105 //
106
107 if (UtilMkdir(WSL_TEMP_FOLDER, WSL_TEMP_FOLDER_MODE) < 0)
108 {
109 return -1;
110 }
111
112 //
113 // Create a unix socket to handle interop requests.
114 //
115 // N.B. This is done before the child process is created to ensure that
116 // the socket is ready for connections.
117 //
118
119 m_InteropSocket.reset(socket(AF_UNIX, (SOCK_STREAM | SOCK_CLOEXEC), 0));
120 if (!m_InteropSocket)
121 {
122 LOG_ERROR("socket failed {}", errno);
123 return -1;
124 }
125
126 sockaddr_un InteropSocketAddress{};
127 InteropSocketAddress.sun_family = AF_UNIX;
128 strncpy(InteropSocketAddress.sun_path, m_InteropSocketPath.c_str(), (sizeof(InteropSocketAddress.sun_path) - 1));
129
130 auto Result = bind(m_InteropSocket.get(), reinterpret_cast<sockaddr*>(&InteropSocketAddress), sizeof(InteropSocketAddress));
131 if (Result < 0)
132 {
133 LOG_ERROR("bind failed {}", errno);
134 return -1;
135 }
136
137 Result = listen(m_InteropSocket.get(), -1);
138 if (Result < 0)
139 {
140 LOG_ERROR("listen failed {}", errno);
141 return -1;
142 }
143
144 //
145 // Ensure that any users can connect to the interop socket.
146 //
147
148 Result = chmod(m_InteropSocketPath.c_str(), 0777);
149 if (Result < 0)
150 {
151 LOG_ERROR("chmod failed {}", errno);
152 return -1;
153 }
154
155 return 0;
156 }
157
158 wil::unique_fd InteropServer::Accept() const
159
160 /*++
161
162 Routine Description:
163
164 This routine accepts a connection on the interop server.
165
166 Arguments:
167
168 None.
169
170 Return Value:
171
172 The socket.
173
174 --*/
175
176 {
177 wil::unique_fd InteropConnection{accept4(m_InteropSocket.get(), nullptr, nullptr, SOCK_CLOEXEC)};
178 if (!InteropConnection)
179 {
180 LOG_ERROR("accept4 failed {}", errno);
181 return {};
182 }
183
184 timeval Timeout{};
185 Timeout.tv_sec = INTEROP_TIMEOUT_SEC;
186 if (setsockopt(InteropConnection.get(), SOL_SOCKET, SO_RCVTIMEO, &Timeout, sizeof(Timeout)) < 0)
187 {
188 LOG_ERROR("setsockopt(SO_RCVTIMEO) failed {}", errno);
189 }
190
191 return InteropConnection;
192 }
193
194 void InteropServer::Reset()
195 {
196 if (!m_InteropSocketPath.empty())
197 {
198 unlink(m_InteropSocketPath.c_str());
199 m_InteropSocketPath = {};
200 }
201 }
202
203 InteropServer::~InteropServer()
204 {
205 Reset();
206 }
207
208 int UtilAcceptVsock(int SocketFd, sockaddr_vm SocketAddress, int Timeout, int SocketFlags)
209
210 /*++
211
212 Routine Description:
213
214 This routine accepts a socket connection.
215
216 Arguments:
217
218 SocketFd - Supplies a socket file descriptor.
219
220 SocketAddress - Supplies the socket address. This is passed by value instead
221 of by reference because accept4 modifies the structure to contain the
222 address of the peer socket.
223
224 Timeout - Supplies a timeout.
225
226 SocketFlags - Supplies the socket flags.
227
228 Return Value:
229
230 A file descriptor representing the socket, -1 on failure.
231
232 --*/
233
234 {
235 //
236 // If a timeout was specified, use a pollfd to wait for the accept.
237 //
238
239 int Result = 0;
240 if (Timeout == -1)
241 {
242 pollfd PollDescriptor{SocketFd, POLLIN, 0};
243
244 while (true)
245 {
246 Result = poll(&PollDescriptor, 1, 60 * 1000);
247 if (Result < 0)
248 {
249 LOG_ERROR("poll({}) failed, {}", SocketFd, errno);
250 return Result;
251 }
252 else if ((Result == 0) || ((PollDescriptor.revents & POLLIN) == 0))
253 {
254 LOG_ERROR("Waiting for abnormally long accept({})", SocketFd);
255 }
256 else
257 {
258 break;
259 }
260 }
261 }
262 else
263 {
264 pollfd PollDescriptor{SocketFd, POLLIN, 0};
265 Result = poll(&PollDescriptor, 1, Timeout);
266 if ((Result <= 0) || ((PollDescriptor.revents & POLLIN) == 0))
267 {
268 errno = ETIMEDOUT;
269 Result = -1;
270 }
271 }
272
273 if (Result != -1)
274 {
275 socklen_t SocketAddressSize = sizeof(SocketAddress);
276 Result = accept4(SocketFd, reinterpret_cast<sockaddr*>(&SocketAddress), &SocketAddressSize, SocketFlags);
277 }
278
279 if (Result < 0)
280 {
281 LOG_ERROR("accept4 failed {}", errno);
282 }
283
284 return Result;
285 }
286
287 int UtilBindVsockAnyPort(struct sockaddr_vm* SocketAddress, int Type)
288
289 /*++
290
291 Routine Description:
292
293 This routine creates a bound vsock socket an available port.
294
295 Arguments:
296
297 SocketAddress - Supplies a buffer to receive the socket address of the
298 socket.
299
300 Type - Supplies the socket type.
301
302 Return Value:
303
304 A file descriptor representing the bound socket, -1 on failure.
305
306 --*/
307
308 {
309 int Result;
310 socklen_t SocketAddressSize;
311 int SocketFd;
312
313 SocketFd = socket(AF_VSOCK, Type, 0);
314 if (SocketFd < 0)
315 {
316 Result = -1;
317 LOG_ERROR("socket failed {}", errno);
318 goto BindVsockAnyPortExit;
319 }
320
321 memset(SocketAddress, 0, sizeof(*SocketAddress));
322 SocketAddress->svm_family = AF_VSOCK;
323 SocketAddress->svm_cid = VMADDR_CID_ANY;
324 SocketAddress->svm_port = VMADDR_PORT_ANY;
325 SocketAddressSize = sizeof(*SocketAddress);
326 Result = bind(SocketFd, (const struct sockaddr*)SocketAddress, SocketAddressSize);
327
328 if (Result < 0)
329 {
330 LOG_ERROR("bind failed {}", errno);
331 goto BindVsockAnyPortExit;
332 }
333
334 //
335 // Query the socket name to get the assigned port.
336 //
337
338 Result = getsockname(SocketFd, (struct sockaddr*)SocketAddress, &SocketAddressSize);
339
340 if (Result < 0)
341 {
342 LOG_ERROR("getsockname failed {}", errno);
343 goto BindVsockAnyPortExit;
344 }
345
346 Result = SocketFd;
347 SocketFd = -1;
348
349 BindVsockAnyPortExit:
350 if (SocketFd != -1)
351 {
352 CLOSE(SocketFd);
353 }
354
355 return Result;
356 }
357
358 size_t UtilCanonicalisePathSeparator(char* Path, char Separator)
359
360 /*++
361
362 Routine Description:
363
364 This routine ensures all separators in Path use the specified separator.
365
366 Arguments:
367
368 Path - Supplies the path to canonicalise.
369
370 Separator - Supplies the separator character to be used.
371
372 Return Value:
373
374 The size of the new string.
375
376 --*/
377
378 {
379 size_t DestIndex;
380 size_t PathLength;
381 size_t SourceIndex;
382
383 DestIndex = 0;
384 SourceIndex = 0;
385 PathLength = strlen(Path);
386
387 //
388 // Iterate through the path, replacing all separators.
389 //
390
391 for (; SourceIndex < PathLength; SourceIndex++)
392 {
393 if (Path[SourceIndex] == PATH_SEP || Path[SourceIndex] == PATH_SEP_NT)
394 {
395 //
396 // Don't add a separator if previous char already is a separator.
397 // Also handle the special case where 'Path' is a UNC path (\\X or //X)
398 // where both separators should be kept.
399 //
400
401 if (DestIndex > 1 && Path[DestIndex - 1] == Separator)
402 {
403 continue;
404 }
405
406 Path[DestIndex] = Separator;
407 }
408 else
409 {
410 Path[DestIndex] = Path[SourceIndex];
411 }
412
413 DestIndex++;
414 }
415
416 Path[DestIndex] = '\0';
417 return DestIndex;
418 }
419
420 void UtilCanonicalisePathSeparator(std::string& Path, char Separator)
421
422 /*++
423
424 Routine Description:
425
426 This routine ensures all separators in Path use the specified separator.
427
428 Arguments:
429
430 Path - Supplies the path to canonicalise.
431
432 Separator - Supplies the separator character to be used.
433
434 Return Value:
435
436 None.
437
438 --*/
439
440 {
441 Path.resize(UtilCanonicalisePathSeparator(Path.data(), Separator));
442 }
443
444 wil::unique_fd UtilConnectToInteropServer(std::optional<pid_t> Pid)
445
446 /*++
447
448 Routine Description:
449
450 This routine connects to the interop server of the current client process.
451
452 Arguments:
453
454 Pid - Supplies an optional process ID to connect to.
455
456 Return Value:
457
458 A file descriptor representing the connected socket, -1 on failure.
459
460 --*/
461
462 try
463 {
464 char* InteropSocketPath;
465 std::string Path;
466 if (Pid.has_value())
467 {
468 Path = std::format(WSL_INTEROP_SOCKET_FORMAT, WSL_TEMP_FOLDER, Pid.value(), WSL_INTEROP_SOCKET);
469 InteropSocketPath = Path.data();
470 }
471 else
472 {
473 //
474 // Query the interop server environment variable. If the process does not
475 // have the environment variable, or if the socket does not exists, search through parent process tree for an
476 // interop server.
477 //
478
479 InteropSocketPath = getenv(WSL_INTEROP_ENV);
480 if (InteropSocketPath == nullptr || (access(InteropSocketPath, F_OK) < 0 && errno == ENOENT))
481 {
482 pid_t Parent = getppid();
483 while (Parent > 0)
484 {
485 Path = std::format(WSL_INTEROP_SOCKET_FORMAT, WSL_TEMP_FOLDER, Parent, WSL_INTEROP_SOCKET);
486 if (access(Path.c_str(), F_OK) == 0)
487 {
488 InteropSocketPath = Path.data();
489 break;
490 }
491
492 Parent = UtilGetPpid(Parent);
493 }
494
495 if (InteropSocketPath == nullptr)
496 {
497 return {};
498 }
499
500 setenv(WSL_INTEROP_ENV, InteropSocketPath, 1);
501 }
502 }
503
504 //
505 // Connect to the server and return the connected socket to the caller.
506 //
507
508 return UtilConnectUnix(InteropSocketPath);
509 }
510 CATCH_RETURN_ERRNO()
511
512 wil::unique_fd UtilConnectUnix(const char* Path)
513
514 /*++
515
516 Routine Description:
517
518 This routine connects to the specified unix socket path.
519
520 Arguments:
521
522 Path - Supplies the path of the unix socket.
523
524 Return Value:
525
526 The connected socket, or a default-initialized value on failure.
527
528 --*/
529
530 {
531 wil::unique_fd Socket{socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)};
532 if (!Socket)
533 {
534 LOG_ERROR("socket failed {}", errno);
535 return {};
536 }
537
538 sockaddr_un SocketAddress{};
539 SocketAddress.sun_family = AF_UNIX;
540 strncpy(SocketAddress.sun_path, Path, sizeof(SocketAddress.sun_path) - 1);
541 if (connect(Socket.get(), reinterpret_cast<sockaddr*>(&SocketAddress), sizeof(SocketAddress)) < 0)
542 {
543 LOG_ERROR("connect failed {}", errno);
544 return {};
545 }
546
547 return Socket;
548 }
549
550 wil::unique_fd UtilConnectVsock(unsigned int Port, bool CloseOnExec, std::optional<int> SocketBuffer, const std::source_location& Source) noexcept
551
552 /*++
553
554 Routine Description:
555
556 This routine connects to a vsock with the specified port.
557
558 Arguments:
559
560 Port - Supplies the port to connect to.
561
562 CloseOnExec - Supplies a boolean specifying if the socket file descriptor should be closed on exec.
563
564 SocketBuffer - Optionally supplies the size to use for the socket send and receive buffers.
565
566 Source - Supplies the caller location.
567
568 Return Value:
569
570 A file descriptor representing the connected socket, -1 on failure.
571
572 --*/
573
574 {
575 int Type = SOCK_STREAM;
576 WI_SetFlagIf(Type, SOCK_CLOEXEC, CloseOnExec);
577 wil::unique_fd SocketFd{socket(AF_VSOCK, Type, 0)};
578 if (!SocketFd)
579 {
580 LOG_ERROR("socket failed {} (from: {})", errno, Source);
581 return {};
582 }
583
584 //
585 // Set the socket connect timeout.
586 //
587
588 timeval Timeout{};
589 Timeout.tv_sec = LX_INIT_HVSOCKET_TIMEOUT_SECONDS;
590 if (setsockopt(SocketFd.get(), AF_VSOCK, SO_VM_SOCKETS_CONNECT_TIMEOUT, &Timeout, sizeof(Timeout)) < 0)
591 {
592 LOG_ERROR("setsockopt SO_VM_SOCKETS_CONNECT_TIMEOUT failed {}, (from: {})", errno, Source);
593 return {};
594 }
595
596 if (SocketBuffer)
597 {
598 int BufferSize = *SocketBuffer;
599 if (setsockopt(SocketFd.get(), SOL_SOCKET, SO_SNDBUF, &BufferSize, sizeof(BufferSize)) < 0)
600 {
601 LOG_ERROR("setsockopt(SO_SNDBUF, {}) failed {}, (from: {})", BufferSize, errno, Source);
602 return {};
603 }
604
605 if (setsockopt(SocketFd.get(), SOL_SOCKET, SO_RCVBUF, &BufferSize, sizeof(BufferSize)) < 0)
606 {
607 LOG_ERROR("setsockopt(SO_RCVBUF, {}) failed {}, (from: {})", BufferSize, errno, Source);
608 return {};
609 }
610 }
611
612 sockaddr_vm SocketAddress{};
613 SocketAddress.svm_family = AF_VSOCK;
614 SocketAddress.svm_cid = VMADDR_CID_HOST;
615 SocketAddress.svm_port = Port;
616 if (connect(SocketFd.get(), (const struct sockaddr*)&SocketAddress, sizeof(SocketAddress)) < 0)
617 {
618 LOG_ERROR("connect port {} failed {} (from: {})", Port, errno, Source);
619 return {};
620 }
621
622 return SocketFd;
623 }
624
625 int UtilCreateProcessAndWait(const char* const File, const char* const Argv[], int* Status, const std::map<std::string, std::string>& Env, bool DetachTerminal)
626
627 /*++
628
629 Routine Description:
630
631 This routine creates a helper process from init and waits for it to exit.
632
633 Arguments:
634
635 File - Supplies the file name to execute.
636
637 Argv - Supplies the arguments for the command.
638
639 Status - Supplies an optional pointer that receives the exit status of the
640 process.
641
642 DetachTerminal - Supplies a boolean that, when true, calls setsid() in the
643 child process to detach it from the controlling terminal.
644
645 Return Value:
646
647 0 on success, -1 on failure.
648
649 --*/
650 {
651 pid_t ChildPid;
652 int Result;
653 int LocalStatus;
654 pid_t WaitResult;
655
656 Result = -1;
657
658 //
659 // Init needs to not ignore SIGCHLD so it can wait for this child.
660 //
661
662 auto restore = signal(SIGCHLD, SIG_DFL);
663
664 ChildPid = fork();
665 if (ChildPid < 0)
666 {
667 LOG_ERROR("Forking child process for {} failed with {}", File, errno);
668 goto CreateProcessAndWaitEnd;
669 }
670
671 if (ChildPid == 0)
672 {
673 //
674 // Restore default signal dispositions for the child process.
675 //
676
677 if (UtilSetSignalHandlers(g_SavedSignalActions, false) < 0 || UtilRestoreBlockedSignals() < 0)
678 {
679 _exit(-1);
680 }
681
682 //
683 // Set environment variables.
684 //
685
686 for (const auto& e : Env)
687 {
688 setenv(e.first.c_str(), e.second.c_str(), 1);
689 }
690
691 //
692 // Detach from the controlling terminal if requested.
693 //
694
695 if (DetachTerminal)
696 {
697 if (setsid() == -1)
698 {
699 LOG_ERROR("setsid failed {}", errno);
700 _exit(-1);
701 }
702 }
703
704 //
705 // Invoke the executable.
706 //
707
708 // This explicit cast is okay for now because:
709 // 1. execv function is guaranteed to not alter the arguments
710 // 2. In sometime we probably will replace most of these string constants
711 // with std::string anyway.
712 execv(File, const_cast<char* const*>(Argv));
713 LOG_ERROR("execv({}) failed with {}", File, errno);
714 _exit(-1);
715 }
716
717 if (Status == nullptr)
718 {
719 Status = &LocalStatus;
720 }
721
722 //
723 // TODO_LX: Do we need a timeout when waiting for the process?
724 //
725
726 WaitResult = waitpid(ChildPid, Status, 0);
727 if (WaitResult < 0)
728 {
729 LOG_ERROR("Waiting for {} failed with {}", File, errno);
730 goto CreateProcessAndWaitEnd;
731 }
732
733 if (*Status != 0)
734 {
735 LOG_ERROR("{} failed with status {:#x}", File, *Status);
736 goto CreateProcessAndWaitEnd;
737 }
738
739 Result = 0;
740
741 CreateProcessAndWaitEnd:
742
743 //
744 // Restore the disposition of SIGCHLD.
745 //
746
747 signal(SIGCHLD, restore);
748
749 return Result;
750 }
751
752 int UtilExecCommandLine(const char* CommandLine, std::string* Output, int ExpectedStatus, bool PrintError)
753
754 /*++
755
756 Routine Description:
757
758 This routine runs the command and optionally returns the output.
759
760 Arguments:
761
762 CommandLine - Supplies the command line of the process to launch.
763
764 Output - Supplies an optional pointer to a std::string to receive the output of the command.
765 If no buffer is provided the output will appear in stdout.
766
767 ExpectedStatus - Supplies the expected return status of the command.
768
769 PrintError - Supplies a boolean that specifies if an error should be printed if the process does not return the expected status.
770
771 Return Value:
772
773 0 on success, -1 on failure.
774
775 --*/
776
777 {
778 //
779 // Exec the command and read the output.
780 //
781
782 wil::unique_file Pipe{popen(CommandLine, "re")};
783 if (!Pipe)
784 {
785 LOG_ERROR("popen({}) failed {}", CommandLine, errno);
786 return -1;
787 }
788
789 std::vector<char> Buffer(1024);
790 int Result = -1;
791 while (fgets(Buffer.data(), Buffer.size(), Pipe.get()) != nullptr)
792 {
793 if (Output)
794 {
795 (*Output) += Buffer.data();
796 }
797 else
798 {
799 fputs(Buffer.data(), stdout);
800 }
801 }
802
803 if (ferror(Pipe.get()))
804 {
805 Result = -1;
806 LOG_ERROR("fgets failed {}", errno);
807 goto ErrorExit;
808 }
809
810 Result = 0;
811
812 ErrorExit:
813 if (Pipe)
814 {
815 Result = pclose(Pipe.release());
816 if (Result == -1)
817 {
818 LOG_ERROR("pclose failed {}", errno);
819 }
820 else
821 {
822 Result = UtilProcessChildExitCode(Result, CommandLine, ExpectedStatus, PrintError);
823 }
824 }
825
826 return Result;
827 }
828
829 std::string UtilFindMount(const char* MountInfoFile, const char* Path, bool WinPath, size_t* PrefixLength)
830
831 /*++
832
833 Routine Description:
834
835 This routine parses the /proc/self/mountinfo file to find a mount that
836 matches the specified path.
837
838 N.B. The caller is responsible for freeing the returned replacement prefix
839 buffer.
840
841 Arguments:
842
843 MountInfoFile - Supplies the path to the mountinfo file.
844
845 Path - Supplies the path.
846
847 WinPath - Supplies a value that indicates whether the path is a Windows
848 path.
849
850 PrefixLength - Supplies a pointer which receives the length of the prefix
851 that should be stripped from the path.
852
853 Return Value:
854
855 The replacement prefix on success, or an empty string on failure.
856
857 --*/
858
859 try
860 {
861 char** MatchField;
862 char** ReplacementField;
863
864 mountutil::MountEnum MountEnum{MountInfoFile};
865 if (WinPath != false)
866 {
867 MatchField = &MountEnum.Current().Source;
868 ReplacementField = &MountEnum.Current().MountPoint;
869 }
870 else
871 {
872 MatchField = &MountEnum.Current().MountPoint;
873 ReplacementField = &MountEnum.Current().Source;
874 }
875
876 std::string FoundReplacement;
877 size_t FoundPrefixLength = 0;
878 while (MountEnum.Next())
879 {
880 //
881 // When translating Windows paths to Linux, skip internal virtiofs
882 // device mounts. The aggregate virtiofs root and its per-share child
883 // binds live under VIRTIOFS_MOUNT_DIR and carry the same Windows source
884 // as the user-facing /mnt/<drive> bind mounts. If they were considered,
885 // translation could return an internal plumbing path (for example
886 // /run/wsl/virtiofs-mounts/drvfsa/<guid>) instead of the real mount
887 // point such as /mnt/c.
888 //
889
890 if (WinPath && UtilIsPathPrefix(MountEnum.Current().MountPoint, VIRTIOFS_MOUNT_DIR, false) > 0)
891 {
892 continue;
893 }
894
895 //
896 // If a mount point was previously found, and this mount point is a
897 // prefix of the path (or the previously found mount point, for Windows
898 // to Linux translation), it means that the path is not actually on
899 // the previously found mount, so discard that result.
900 //
901 // For example:
902 // - When translating /mnt/c/foo/bar, first /mnt/c is found, but a
903 // later entry indicates /mnt/c/foo is also a mount point (e.g. using
904 // tmpfs). This means /mnt/c/foo/bar is not on the /mnt/c mount.
905 // - When translating C:\foo, first /mnt/c is found. A later entry
906 // indicates /mnt itself is a mount point, making the earlier /mnt/c
907 // mount unreachable.
908 //
909 // TODO_LX: This doesn't catch the case when translating C:\foo\bar and
910 // /mnt/c/foo is a mount point. Handling that is more complicated.
911 //
912
913 if (!FoundReplacement.empty())
914 {
915 const char* LinuxPath = WinPath ? FoundReplacement.c_str() : Path;
916 size_t LinuxPrefixLength = UtilIsPathPrefix(LinuxPath, MountEnum.Current().MountPoint, false);
917 if (LinuxPrefixLength > 0)
918 {
919 FoundReplacement.resize(0);
920 }
921 }
922
923 //
924 // For Plan 9, parse the actual mount source from the superblock options.
925 // For virtiofs, parse the mount source from source (for example drvfsC or drvfsaC).
926 // If the file system isn't Plan 9, virtiofs, or DrvFs, skip this mount.
927 //
928
929 std::string MountSource;
930 std::string_view MountRoot{MountEnum.Current().Root};
931 if (strcmp(MountEnum.Current().FileSystemType, PLAN9_FS_TYPE) == 0)
932 {
933 MountSource = UtilParsePlan9MountSource(MountEnum.Current().SuperOptions);
934 if (MountSource.empty())
935 {
936 continue;
937 }
938
939 MountEnum.Current().Source = MountSource.data();
940 }
941 else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
942 {
943 const auto aggregateRoot = ParseAggregateVirtioFsMountRoot(MountEnum.Current().Source, MountRoot);
944 MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source, MountEnum.Current().Root);
945 if (MountSource.empty())
946 {
947 continue;
948 }
949
950 MountEnum.Current().Source = MountSource.data();
951 if (aggregateRoot)
952 {
953 MountRoot = aggregateRoot->SubPath;
954 }
955 }
956 else if (strcmp(MountEnum.Current().FileSystemType, DRVFS_FS_TYPE) == 0)
957 {
958 //
959 // The mount source is a Windows path and may use forward slashes;
960 // flip them to backslashes.
961 //
962
963 UtilCanonicalisePathSeparator(MountEnum.Current().Source, PATH_SEP_NT);
964 }
965 else
966 {
967 continue;
968 }
969
970 //
971 // Strip the trailing backslash if present.
972 //
973
974 size_t Length = strlen(MountEnum.Current().Source);
975 if ((Length > 0) && (MountEnum.Current().Source[Length - 1] == PATH_SEP_NT))
976 {
977 MountEnum.Current().Source[Length - 1] = '\0';
978 }
979
980 //
981 // For bind mounts, use the concatenation of the mount source and root
982 // of the mount as the mount source string.
983 //
984
985 std::string CombinedMountSource;
986 if (MountRoot != "/")
987 {
988 CombinedMountSource += MountEnum.Current().Source;
989 CombinedMountSource += MountRoot;
990 UtilCanonicalisePathSeparator(CombinedMountSource, PATH_SEP_NT);
991 MountEnum.Current().Source = CombinedMountSource.data();
992 }
993
994 //
995 // Check if the match field is a prefix of the path.
996 //
997 // N.B. For Windows paths, only matches longer than the existing match
998 // are considered. This is because Windows mounts aren't
999 // guaranteed to be in order and NTFS directory mounts should be
1000 // preferred over plain drive letter mounts if they match.
1001 //
1002
1003 Length = UtilIsPathPrefix(Path, *MatchField, WinPath);
1004 if ((Length == 0) || ((WinPath != false) && (Length < FoundPrefixLength)))
1005 {
1006 continue;
1007 }
1008
1009 //
1010 // Store the length of the prefix so the caller can strip it from the
1011 // string.
1012 //
1013
1014 FoundPrefixLength = Length;
1015
1016 //
1017 // Store the replacement.
1018 //
1019
1020 FoundReplacement = *ReplacementField;
1021
1022 //
1023 // Continue searching the file even if a mount has been found, since
1024 // newer mounts could shadow this one or be a nested mount.
1025 //
1026 }
1027
1028 if (!FoundReplacement.empty() && PrefixLength != nullptr)
1029 {
1030 *PrefixLength = FoundPrefixLength;
1031 }
1032
1033 return FoundReplacement;
1034 }
1035 catch (...)
1036 {
1037 LOG_CAUGHT_EXCEPTION();
1038 return {};
1039 }
1040
1041 std::optional<std::string> UtilGetEnv(const char* Name, char* Environment)
1042
1043 /*++
1044
1045 Routine Description:
1046
1047 This queries the specified environment variable.
1048
1049 Arguments:
1050
1051 Name - Supplies the name to query.
1052
1053 Environment - Supplies an environment block to search. If NULL is provided
1054 the environment of the calling process is used.
1055
1056 Return Value:
1057
1058 The value of the specified environment variable, NULL if there is no match.
1059
1060 --*/
1061
1062 {
1063 char* Current;
1064 size_t Length;
1065 size_t NameLength;
1066 std::optional<std::string> Value;
1067
1068 if (Environment == nullptr)
1069 {
1070 const auto* EnvValue = getenv(Name);
1071 if (EnvValue != nullptr)
1072 {
1073 Value = std::string{EnvValue};
1074 }
1075 }
1076 else
1077 {
1078 NameLength = strlen(Name);
1079 for (size_t Index = 0;;)
1080 {
1081 Current = Environment + Index;
1082 Length = strlen(Current);
1083 if (Length == 0)
1084 {
1085 break;
1086 }
1087
1088 if ((strncmp(Current, Name, NameLength) == 0) && (Current[NameLength] == '='))
1089 {
1090 Value = std::string{&Current[NameLength + 1]};
1091 break;
1092 }
1093
1094 Index += Length + 1;
1095 }
1096 }
1097
1098 return Value;
1099 }
1100
1101 std::string UtilGetEnvironmentVariable(const char* Name)
1102
1103 /*++
1104
1105 Routine Description:
1106
1107 This queries the specified environment variable. If the value does not exist it gets the value from
1108 the WSL interop server.
1109
1110 Arguments:
1111
1112 Name - Supplies the name to query.
1113
1114 Return Value:
1115
1116 The value of the specified environment variable if there is a match.
1117
1118 --*/
1119
1120 try
1121 {
1122 //
1123 // Try to get the environment variable value. If it is not set, query the interop server for value.
1124 //
1125
1126 std::vector<gsl::byte> Buffer;
1127 auto Value = getenv(Name);
1128 if (Value == nullptr)
1129 {
1130 wsl::shared::SocketChannel channel{UtilConnectToInteropServer(), "InteropClient"};
1131 if (channel.Socket() < 0)
1132 {
1133 return {};
1134 }
1135
1136 wsl::shared::MessageWriter<LX_INIT_QUERY_ENVIRONMENT_VARIABLE> Message(LxInitMessageQueryEnvironmentVariable);
1137 Message.WriteString(Name);
1138
1139 auto transaction = channel.StartTransaction();
1140 transaction.Send<LX_INIT_QUERY_ENVIRONMENT_VARIABLE>(Message.Span());
1141
1142 //
1143 // Read a response, this will contain the environment variable value if it exists.
1144 //
1145
1146 Value = transaction.Receive<LX_INIT_QUERY_ENVIRONMENT_VARIABLE>().Buffer;
1147
1148 //
1149 // Set the environment variable for future queries.
1150 //
1151
1152 if (setenv(Name, Value, 1) < 0)
1153 {
1154 LOG_ERROR("setenv({}, {}, 1) failed {}", Name, Value, errno);
1155 }
1156 }
1157
1158 return Value;
1159 }
1160 catch (...)
1161 {
1162 LOG_CAUGHT_EXCEPTION();
1163 return {};
1164 }
1165
1166 int UtilGetFeatureFlags()
1167
1168 /*++
1169
1170 Routine Description:
1171
1172 This routine gets the feature flags, either directly, from an environment
1173 variable, or by querying it from the init process.
1174
1175 Arguments:
1176
1177 None.
1178
1179 Return Value:
1180
1181 The feature flags.
1182
1183 --*/
1184
1185 {
1186 //
1187 // If feature flags are already known, return them.
1188 //
1189
1190 if (g_CachedFeatureFlags)
1191 {
1192 return *g_CachedFeatureFlags;
1193 }
1194
1195 //
1196 // Check if the environment variable is present.
1197 //
1198 // N.B. This is used for processes launched directly from init (e.g.
1199 // mount.drvfs during initial configuration), because they may not be
1200 // able to connect to init.
1201 //
1202
1203 int FeatureFlags = LxInitFeatureNone;
1204 const char* FeatureFlagEnv = getenv(WSL_FEATURE_FLAGS_ENV);
1205 if (FeatureFlagEnv != nullptr)
1206 {
1207 FeatureFlags = strtol(FeatureFlagEnv, nullptr, 16);
1208 }
1209 else
1210 {
1211 //
1212 // Query init for the value. If an error occurs, just return no features.
1213 //
1214
1215 wsl::shared::SocketChannel channel{UtilConnectUnix(WSL_INIT_INTEROP_SOCKET), "wslinfo"};
1216 if (channel.Socket() < 0)
1217 {
1218 return FeatureFlags;
1219 }
1220
1221 MESSAGE_HEADER Message{};
1222 Message.MessageType = LxInitMessageQueryFeatureFlags;
1223 Message.MessageSize = sizeof(Message);
1224
1225 auto transaction = channel.StartTransaction();
1226 transaction.Send(Message);
1227 FeatureFlags = transaction.Receive<RESULT_MESSAGE<int32_t>>().Result;
1228 }
1229
1230 UtilSetFeatureFlags(FeatureFlags, FeatureFlagEnv == nullptr);
1231 return FeatureFlags;
1232 }
1233
1234 void UtilSetFeatureFlags(int FeatureFlags, bool UpdateEnv)
1235
1236 /*++
1237
1238 Routine Description:
1239
1240 This routine sets the feature flags and updates the cached value and environment variable.
1241
1242 Arguments:
1243
1244 FeatureFlags - Supplies the feature flags to set.
1245
1246 UpdateEnv - Supplies a boolean that indicates whether the environment variable should be updated.
1247
1248 Return Value:
1249
1250 None.
1251
1252 --*/
1253
1254 try
1255 {
1256 g_CachedFeatureFlags = FeatureFlags;
1257 if (UpdateEnv)
1258 {
1259 auto FeatureFlagsString = std::format("{:x}", FeatureFlags);
1260 if (setenv(WSL_FEATURE_FLAGS_ENV, FeatureFlagsString.c_str(), 1) < 0)
1261 {
1262 LOG_ERROR("setenv({}, {}, 1) failed {}", WSL_FEATURE_FLAGS_ENV, FeatureFlagsString, errno);
1263 }
1264 }
1265 }
1266 CATCH_LOG()
1267
1268 std::optional<LX_MINI_INIT_NETWORKING_MODE> UtilGetNetworkingMode(void)
1269
1270 /*++
1271
1272 Routine Description:
1273
1274 This routine queries the networking mode from the init process.
1275
1276 Arguments:
1277
1278 None.
1279
1280 Return Value:
1281
1282 The networking mode if successful, std::nullopt otherwise.
1283
1284 --*/
1285
1286 try
1287 {
1288 wsl::shared::SocketChannel channel{UtilConnectUnix(WSL_INIT_INTEROP_SOCKET), "wslinfo"};
1289 THROW_LAST_ERROR_IF(channel.Socket() < 0);
1290
1291 MESSAGE_HEADER Message{};
1292 Message.MessageType = LxInitMessageQueryNetworkingMode;
1293 Message.MessageSize = sizeof(Message);
1294
1295 auto transaction = channel.StartTransaction();
1296 transaction.Send(Message);
1297
1298 const auto& response = transaction.Receive<RESULT_MESSAGE<uint8_t>>();
1299 auto NetworkingMode = static_cast<LX_MINI_INIT_NETWORKING_MODE>(response.Result);
1300
1301 THROW_ERRNO_IF(EINVAL, NetworkingMode < LxMiniInitNetworkingModeNone || NetworkingMode > LxMiniInitNetworkingModeConsomme);
1302
1303 return NetworkingMode;
1304 }
1305 catch (...)
1306 {
1307 LOG_CAUGHT_EXCEPTION();
1308 return {};
1309 }
1310
1311 pid_t UtilGetPpid(pid_t Pid)
1312
1313 /*++
1314
1315 Routine Description:
1316
1317 This routine returns the parent process id of the specified process.
1318
1319 Arguments:
1320
1321 Pid - Supplies the process id to get the parent of.
1322
1323 Return Value:
1324
1325 The parent process id if successful, -1 otherwise.
1326
1327 --*/
1328
1329 {
1330 //
1331 // Open the /proc/[pid]/stat file.
1332 //
1333
1334 const auto FilePath = std::format("/proc/{}/stat", Pid);
1335 std::ifstream File(FilePath);
1336
1337 std::string Line;
1338 if (!File || !std::getline(File, Line))
1339 {
1340 return -1;
1341 }
1342
1343 //
1344 // Parse the file. Sample format: "86 (bash) S 9".
1345 // N.B. The second entry can contain a space so we can't just use strtok.
1346 //
1347
1348 const std::regex Pattern("^[0-9]+ \\(.*\\) \\w ([0-9]+).*");
1349 std::smatch Match;
1350 if (!std::regex_match(Line, Match, Pattern) || Match.size() != 2)
1351 {
1352 LOG_ERROR("Failed to parse: {}, content: {}", FilePath, Line);
1353 return -1;
1354 }
1355
1356 auto Result = strtol(Match.str(1).c_str(), nullptr, 10);
1357 if (Result == 0)
1358 {
1359 LOG_ERROR("Failed to parse: {}, content: {}", FilePath, Line);
1360 return -1;
1361 }
1362
1363 return Result;
1364 }
1365
1366 std::string UtilGetVmId(void)
1367
1368 /*++
1369
1370 Routine Description:
1371
1372 This routine queries the VM ID from the init process.
1373
1374 Arguments:
1375
1376 None.
1377
1378 Return Value:
1379
1380 The VM ID if successful, an empty string otherwise.
1381
1382 --*/
1383
1384 try
1385 {
1386 wsl::shared::SocketChannel channel{UtilConnectUnix(WSL_INIT_INTEROP_SOCKET), "wslinfo"};
1387 THROW_LAST_ERROR_IF(channel.Socket() < 0);
1388
1389 wsl::shared::MessageWriter<LX_INIT_QUERY_VM_ID> Message(LxInitMessageQueryVmId);
1390 auto transaction = channel.StartTransaction();
1391 transaction.Send<LX_INIT_QUERY_VM_ID>(Message.Span());
1392
1393 return transaction.Receive<LX_INIT_QUERY_VM_ID>().Buffer;
1394 }
1395 catch (...)
1396 {
1397 LOG_CAUGHT_EXCEPTION();
1398 return {};
1399 }
1400
1401 void UtilInitGroups(const char* User, gid_t Gid)
1402
1403 /*++
1404
1405 Routine Description:
1406
1407 This routine initializes the groups for the current process.
1408 N.B. This is needed because the musl version of initgroups has a hard-coded 32 group max.
1409
1410 Arguments:
1411
1412 User - Supplies the user name.
1413
1414 Gid - Supplies the group id.
1415
1416 Return Value:
1417
1418 None.
1419
1420 --*/
1421
1422 {
1423 if (initgroups(User, Gid) < 0)
1424 {
1425 int Count{};
1426 getgrouplist(User, Gid, nullptr, &Count);
1427 std::vector<gid_t> Groups(Count);
1428 THROW_LAST_ERROR_IF(getgrouplist(User, Gid, Groups.data(), &Count) < 0);
1429
1430 THROW_LAST_ERROR_IF(setgroups(Count, Groups.data()) < 0);
1431 }
1432 }
1433
1434 void UtilInitializeMessageBuffer(std::vector<gsl::byte>& Buffer)
1435
1436 /*++
1437
1438 Routine Description:
1439
1440 This routine ensures the supplied buffer is initialized.
1441
1442 Arguments:
1443
1444 Buffer - Supplies the buffer to be initialized.
1445
1446 Return Value:
1447
1448 None.
1449
1450 --*/
1451
1452 {
1453 if (Buffer.size() < INITIAL_MESSAGE_BUFFER_SIZE)
1454 {
1455 Buffer.resize(INITIAL_MESSAGE_BUFFER_SIZE);
1456 }
1457 }
1458
1459 bool UtilIsAbsoluteWindowsPath(const char* Path)
1460
1461 /*++
1462
1463 Routine Description:
1464
1465 This routine determines if the supplied path is an absolute Windows path.
1466
1467 Arguments:
1468
1469 Path - Supplies the path to check.
1470
1471 Return Value:
1472
1473 true if the supplied path is an absolute Windows path, false otherwise.
1474
1475 --*/
1476
1477 {
1478 if ((strlen(Path) < 3) || (!(((Path[0] == PATH_SEP_NT || Path[0] == PATH_SEP) && (Path[1] == PATH_SEP_NT || Path[1] == PATH_SEP)) ||
1479 (isalpha(Path[0]) && Path[1] == DRIVE_SEP_NT))))
1480 {
1481 return false;
1482 }
1483
1484 return true;
1485 }
1486
1487 size_t UtilIsPathPrefix(const char* Path, const char* Prefix, bool WinPath)
1488
1489 /*++
1490
1491 Routine Description:
1492
1493 This routine checks if one path is a prefix of another.
1494
1495 Arguments:
1496
1497 Path - Supplies the path to check for a prefix.
1498
1499 Prefix - Supplies the prefix to check for.
1500
1501 WinPath - Supplies a value that indicates whether Path is a Windows path.
1502
1503 Return Value:
1504
1505 The length of the prefix, or 0 if there is no match.
1506
1507 --*/
1508
1509 {
1510 size_t PathLength;
1511 size_t PrefixLength;
1512 char Separator;
1513
1514 if (WinPath != false)
1515 {
1516 Separator = PATH_SEP_NT;
1517 }
1518 else
1519 {
1520 Separator = PATH_SEP;
1521 }
1522
1523 //
1524 // Check the lengths and make sure the next character is a separator.
1525 //
1526
1527 PathLength = strlen(Path);
1528 PrefixLength = strlen(Prefix);
1529 if ((PathLength < PrefixLength) || ((PathLength > PrefixLength) && (Path[PrefixLength] != Separator)))
1530 {
1531 return 0;
1532 }
1533
1534 //
1535 // Check if the prefix matches.
1536 //
1537 // N.B. For Windows paths, this is done case-insensitive.
1538 //
1539
1540 if (!wsl::shared::string::StartsWith(Path, Prefix, WinPath))
1541 {
1542 return 0;
1543 }
1544
1545 return PrefixLength;
1546 }
1547
1548 bool UtilIsUtilityVm(void)
1549
1550 /*++
1551
1552 Routine Description:
1553
1554 This routine determines if the current process is running in a Utility VM or
1555 an WSL1 based instance.
1556
1557 Arguments:
1558
1559 None.
1560
1561 Return Value:
1562
1563 true if this instance is VM Mode, false otherwise.
1564
1565 --*/
1566
1567 {
1568 //
1569 // If this process has not yet checked, inspect the Linux kernel release
1570 // string.
1571 //
1572
1573 if (g_IsVmMode == -1)
1574 {
1575 //
1576 // The VM Mode kernel release contains "microsoft", the lxcore kernel
1577 // contains "Microsoft".
1578 //
1579 // TODO: Come up with a different way to detect if we are running under
1580 // lxcore versus a Utility VM.
1581 //
1582
1583 struct utsname UnameBuffer;
1584 memset(&UnameBuffer, 0, sizeof(UnameBuffer));
1585 if (uname(&UnameBuffer) < 0)
1586 {
1587 FATAL_ERROR("uname failed {}", errno);
1588 }
1589
1590 g_IsVmMode = (strstr(UnameBuffer.release, "Microsoft") == NULL);
1591 }
1592
1593 return g_IsVmMode;
1594 }
1595
1596 int UtilListenVsockAnyPort(struct sockaddr_vm* Address, int Backlog, bool CloseOnExec)
1597
1598 /*++
1599
1600 Routine Description:
1601
1602 This routine creates a bound and listening vsock socket an available port.
1603
1604 Arguments:
1605
1606 Address - Supplies a buffer to receive the socket address of the socket.
1607
1608 Backlog - Supplies the length of the backlog.
1609
1610 Return Value:
1611
1612 A file descriptor representing the listening socket, -1 on failure.
1613
1614 --*/
1615
1616 {
1617 int Result;
1618 int SocketFd;
1619
1620 int flags = SOCK_STREAM;
1621 WI_SetFlagIf(flags, SOCK_CLOEXEC, CloseOnExec);
1622
1623 SocketFd = UtilBindVsockAnyPort(Address, flags);
1624 if (SocketFd < 0)
1625 {
1626 Result = -1;
1627 goto ListenVsockAnyPortExit;
1628 }
1629
1630 Result = listen(SocketFd, Backlog);
1631 if (Result < 0)
1632 {
1633 LOG_ERROR("listen failed {}", errno);
1634 goto ListenVsockAnyPortExit;
1635 }
1636
1637 Result = SocketFd;
1638 SocketFd = -1;
1639
1640 ListenVsockAnyPortExit:
1641 if (SocketFd != -1)
1642 {
1643 CLOSE(SocketFd);
1644 }
1645
1646 return Result;
1647 }
1648
1649 int UtilMkdir(const char* Path, mode_t Mode)
1650
1651 /*++
1652
1653 Routine Description:
1654
1655 This routine ensures the directory exists.
1656
1657 Arguments:
1658
1659 Path - Supplies the path of the directory to create.
1660
1661 Mode - Supplies the mode.
1662
1663 Return Value:
1664
1665 0 on success, -1 on failure.
1666
1667 --*/
1668
1669 {
1670 if ((mkdir(Path, Mode) < 0) && (errno != EEXIST))
1671 {
1672 LOG_ERROR("mkdir({}, {:o}) failed {}", Path, Mode, errno);
1673 return -1;
1674 }
1675
1676 return 0;
1677 }
1678
1679 int UtilMkdirPath(const char* Path, mode_t Mode, bool SkipLast)
1680
1681 /*++
1682
1683 Routine Description:
1684
1685 This routine ensures the directory exists. If necessary, all its parents
1686 are created as well.
1687
1688 Arguments:
1689
1690 Path - Supplies the path of the directory to create.
1691
1692 Mode - Supplies the mode.
1693
1694 SkipLast - Indicates whether to skip creating the final entry in the path.
1695
1696 Return Value:
1697
1698 0 on success, -1 on failure.
1699
1700 --*/
1701
1702 {
1703 std::string LocalPath{Path};
1704 std::string::size_type Index = 0;
1705
1706 //
1707 // Because the search is always from index + 1, the first leading / is skipped.
1708 //
1709
1710 for (;;)
1711 {
1712 Index = LocalPath.find_first_of(PATH_SEP, Index + 1);
1713 if (Index != std::string::npos)
1714 {
1715 LocalPath[Index] = '\0';
1716 }
1717 else if (SkipLast)
1718 {
1719 break;
1720 }
1721
1722 if (UtilMkdir(LocalPath.c_str(), Mode) < 0)
1723 {
1724 return -1;
1725 }
1726
1727 if (Index == std::string::npos)
1728 {
1729 break;
1730 }
1731
1732 LocalPath[Index] = PATH_SEP;
1733 }
1734
1735 return 0;
1736 }
1737
1738 int UtilMountFile(const char* Source, const char* Destination)
1739 try
1740 {
1741 // Is the file is a symlink, delete it since that would break the mount.
1742 if (std::filesystem::is_symlink(Destination))
1743 {
1744 std::filesystem::remove(Destination);
1745 }
1746
1747 wil::unique_fd Fd{open(Destination, (O_CREAT | O_WRONLY), 0755)};
1748 THROW_LAST_ERROR_IF(!Fd);
1749
1750 THROW_LAST_ERROR_IF(mount(Source, Destination, nullptr, (MS_RDONLY | MS_BIND), nullptr) < 0);
1751 THROW_LAST_ERROR_IF(mount(nullptr, Destination, nullptr, (MS_RDONLY | MS_REMOUNT | MS_BIND), nullptr) < 0);
1752
1753 return 0;
1754 }
1755 CATCH_RETURN_ERRNO();
1756
1757 int UtilMount(const char* Source, const char* Target, const char* Type, unsigned long MountFlags, const char* Options, std::optional<std::chrono::seconds> TimeoutSeconds)
1758
1759 /*++
1760
1761 Routine Description:
1762
1763 This routine performs a mount with a retry and timeout.
1764
1765 Arguments:
1766
1767 Source - Supplies the source of the mount.
1768
1769 Target - Supplies the target of the mount.
1770
1771 Type - Supplies the filesystem type.
1772
1773 MountFlags - Supplies mount flags.
1774
1775 Options - Supplies the mount options.
1776
1777 TimeoutSeconds - Supplies an optional retry timeout in seconds.
1778
1779 Return Value:
1780
1781 0 on success, < 0 on failure.
1782
1783 --*/
1784
1785 {
1786 //
1787 // Ensure the mount point exists.
1788 //
1789
1790 if (UtilMkdirPath(Target, 0755) < 0)
1791 {
1792 return -1;
1793 }
1794
1795 //
1796 // Mount the device to the mount point.
1797 //
1798 // N.B. The mount operation is retried if:
1799 // - The mount source does not yet exist (hot-added devices)
1800 // - For Plan9 (9p): device is busy or not found
1801 // - For VirtioFS: invalid tag (device not ready)
1802 //
1803 // N.B. MS_SHARED must be applied in a separate mount() call, so it is
1804 // stripped from the initial mount flags and applied after the mount.
1805 //
1806
1807 const unsigned long initialFlags = MountFlags & ~MS_SHARED;
1808
1809 try
1810 {
1811 if (TimeoutSeconds.has_value())
1812 {
1813 wsl::shared::retry::RetryWithTimeout<void>(
1814 [&]() { THROW_LAST_ERROR_IF(mount(Source, Target, Type, initialFlags, Options) < 0); },
1815 c_defaultRetryPeriod,
1816 TimeoutSeconds.value(),
1817 [&]() {
1818 errno = wil::ResultFromCaughtException();
1819
1820 // Generic device not ready errors
1821 if (errno == ENXIO || errno == EIO || errno == ENOENT)
1822 {
1823 return true;
1824 }
1825
1826 // Filesystem-specific device readiness errors
1827 if (Type != nullptr)
1828 {
1829 if ((strcmp(Type, PLAN9_FS_TYPE) == 0 && errno == EBUSY) || (strcmp(Type, VIRTIO_FS_TYPE) == 0 && errno == EINVAL))
1830 {
1831 return true;
1832 }
1833 }
1834
1835 return false;
1836 });
1837 }
1838 else
1839 {
1840 THROW_LAST_ERROR_IF(mount(Source, Target, Type, initialFlags, Options) < 0);
1841 }
1842 }
1843 catch (...)
1844 {
1845 errno = wil::ResultFromCaughtException();
1846 LOG_ERROR("mount({}, {}, {}, {:#x}, {}) failed {}", Source, Target, Type, MountFlags, Options, errno);
1847 return -errno;
1848 }
1849
1850 // N.B. The shared flag must be applied in a separate mount() call.
1851 if (WI_IsFlagSet(MountFlags, MS_SHARED))
1852 {
1853 if (mount(nullptr, Target, nullptr, MS_SHARED, nullptr) < 0)
1854 {
1855 LOG_ERROR("Failed to make shared mount {} {}", Target, errno);
1856 return -errno;
1857 }
1858 }
1859
1860 return 0;
1861 }
1862
1863 int UtilMountOverlayFs(const char* Target, const char* Lower, unsigned long MountFlags, std::optional<std::chrono::seconds> TimeoutSeconds)
1864
1865 /*++
1866
1867 Routine Description:
1868
1869 This routine mounts an overlayfs at the specified location.
1870
1871 Arguments:
1872
1873 Target - Supplies target for the overlayfs mount.
1874
1875 Lower - Supplies the lower layer for the overlayfs mount. Multiple lower
1876 layers can be specified by a colon-separated list.
1877
1878 MountFlags - Supplies mount flags for the operation.
1879
1880 TimeoutSeconds - Supplies an optional timeout if the mount should be retried.
1881
1882 Return Value:
1883
1884 0 on success, < 0 on failure.
1885
1886 --*/
1887
1888 try
1889 {
1890 //
1891 // Set up the state required for overlayfs mount:
1892 //
1893 // <Target> - mount point for read/write overlayfs (this happens last)
1894 // <Target>/rw - tmpfs mount for upper and work dirs
1895 // <Target>/rw/upper - upper dir
1896 // <Target>/rw/work - work dir
1897 //
1898
1899 if (UtilMkdirPath(Target, 0755) < 0)
1900 {
1901 return -1;
1902 }
1903
1904 auto Path = std::format("{}/rw", Target);
1905
1906 //
1907 // Create a tmpfs mount for the read/write layer
1908 //
1909
1910 if (UtilMount(nullptr, Path.c_str(), "tmpfs", 0, nullptr) < 0)
1911 {
1912 return -1;
1913 }
1914
1915 //
1916 // Create upper and work directories.
1917 //
1918
1919 Path = std::format("{}/rw/upper", Target);
1920 if (UtilMkdir(Path.c_str(), 0755) < 0)
1921 {
1922 return -1;
1923 }
1924
1925 auto MountOptions = std::format("lowerdir={},upperdir={},", Lower, Path);
1926 Path = std::format("{}/rw/work", Target);
1927 if (UtilMkdir(Path.c_str(), 0755) < 0)
1928 {
1929 return -1;
1930 }
1931
1932 MountOptions += std::format("workdir={}", Path);
1933 if (UtilMount(nullptr, Target, "overlay", MountFlags, MountOptions.c_str(), TimeoutSeconds) < 0)
1934 {
1935 return -1;
1936 }
1937
1938 return 0;
1939 }
1940 CATCH_RETURN_ERRNO()
1941
1942 int UtilOpenMountNamespace(void)
1943
1944 /*++
1945
1946 Routine Description:
1947
1948 This routine opens a file descriptor to the current mount namespace.
1949
1950 Arguments:
1951
1952 None.
1953
1954 Return Value:
1955
1956 A file descriptor representing the current mount namespace, -1 on failure.
1957
1958 --*/
1959
1960 {
1961 int Fd;
1962
1963 Fd = open("/proc/self/ns/mnt", (O_RDONLY | O_CLOEXEC));
1964 if (Fd < 0)
1965 {
1966 LOG_ERROR("open failed {}", errno);
1967 }
1968
1969 return Fd;
1970 }
1971
1972 int UtilParseCgroupsLine(char* Line, char** SubsystemName, bool* Enabled)
1973
1974 /*++
1975
1976 Routine Description:
1977
1978 This routine parses a line from the /proc/cgroups file. The output
1979 buffers will be pointers into the provided line buffer and does not need to
1980 be freed.
1981
1982 N.B. The Line buffer will be modified to insert NULL terminators.
1983
1984 Arguments:
1985
1986 Line - Supplies the line to parse.
1987
1988 SubsystemName - Supplies a buffer to receive the subsystem name.
1989
1990 Enabled - Supplies a buffer to receive true if the subsystem is enabled,
1991 false otherwise.
1992
1993 Return Value:
1994
1995 0 on success, -1 on failure.
1996
1997 --*/
1998
1999 {
2000 char* Current;
2001 int Field;
2002 int Result;
2003
2004 //
2005 // Ignore comments.
2006 //
2007
2008 Current = strchr(Line, '#');
2009 if (Current != nullptr)
2010 {
2011 *Current = '\0';
2012 }
2013
2014 for (Field = 0, Current = Line; ((Current != nullptr) && (Field <= WSL_CGROUPS_FIELD_MAX));
2015 Field += 1, Current = strchr(Current, WSL_CGROUPS_FIELD_SEP))
2016 {
2017 //
2018 // Replace field separators with NULL characters and skip past them.
2019 //
2020
2021 if (Field > 0)
2022 {
2023 *Current = '\0';
2024 Current += 1;
2025 }
2026
2027 switch (Field)
2028 {
2029 case WSL_CGROUPS_FIELD_SUBSYSTEM:
2030 *SubsystemName = Current;
2031 break;
2032
2033 case WSL_CGROUPS_FIELD_ENABLED:
2034 *Enabled = (*Current == '1');
2035 break;
2036 }
2037 }
2038
2039 //
2040 // Check if all the fields were found. If not, this is a malformed line.
2041 //
2042
2043 if (Field < WSL_CGROUPS_FIELD_MAX)
2044 {
2045 Result = -1;
2046 goto ParseCgroupsLineEnd;
2047 }
2048
2049 Result = 0;
2050
2051 ParseCgroupsLineEnd:
2052 return Result;
2053 }
2054
2055 std::string UtilParsePlan9MountSource(std::string_view MountOptions)
2056
2057 /*++
2058
2059 Routine Description:
2060
2061 This routine parses the mount options to determine the actual source of a
2062 a Plan 9 mount.
2063
2064 Arguments:
2065
2066 MountOptions - Supplies the mount options.
2067
2068 Return Value:
2069
2070 The mount source, or NULL if no valid option could be found.
2071
2072 --*/
2073
2074 {
2075 //
2076 // Search each option.
2077 //
2078 // N.B. The first option is always "ro" or "rw" so doesn't need to be
2079 // considered.
2080 //
2081
2082 while (!MountOptions.empty())
2083 {
2084 auto Current = UtilStringNextToken(MountOptions, WSL_MOUNT_OPTION_SEP);
2085 if (wsl::shared::string::StartsWith(Current, PLAN9_ANAME_DRVFS))
2086 {
2087 //
2088 // Search for the sub path.
2089 //
2090
2091 auto MountSource = Current.substr(PLAN9_ANAME_DRVFS_LENGTH);
2092 auto Index = Current.find(PLAN9_ANAME_PATH_OPTION);
2093 if (Index == std::string_view::npos)
2094 {
2095 break;
2096 }
2097
2098 MountSource = Current.substr(Index + PLAN9_ANAME_PATH_OPTION_LENGTH);
2099 MountSource = UtilStringNextToken(MountSource, PLAN9_ANAME_OPTION_SEP);
2100
2101 //
2102 // The value can only be used if it starts with a drive letter or
2103 // the UNC prefix "UNC\"
2104 //
2105
2106 std::string Plan9Source;
2107 if (MountSource.length() > 1 && isalpha(MountSource[0]) && MountSource[1] == DRIVE_SEP_NT)
2108 {
2109 Plan9Source = MountSource;
2110 }
2111 else if (wsl::shared::string::StartsWith(MountSource, PLAN9_UNC_TRANSLATED_PREFIX))
2112 {
2113 Plan9Source = PLAN9_UNC_PREFIX;
2114 Plan9Source += MountSource.substr(PLAN9_UNC_TRANSLATED_PREFIX_LENGTH);
2115 }
2116 else
2117 {
2118 break;
2119 }
2120
2121 //
2122 // Ensure the returned path uses Windows path separators.
2123 //
2124
2125 UtilCanonicalisePathSeparator(Plan9Source, PATH_SEP_NT);
2126 return Plan9Source;
2127 }
2128 }
2129
2130 return {};
2131 }
2132
2133 std::vector<char> UtilParseWslEnv(char* NtEnvironment)
2134
2135 /*++
2136
2137 Routine Description:
2138
2139 This routine parses the WSLENV environment variable and constructs an
2140 environment block with the resulting values.
2141
2142 Arguments:
2143
2144 NtEnvironment - Supplies an NT environment block. If NULL is provided,
2145 the current processes's environment block is used.
2146
2147 Return Value:
2148
2149 The constructed env block.
2150
2151 --*/
2152
2153 {
2154 std::optional<std::string> EnvList;
2155 bool Reverse = false;
2156
2157 std::vector<char> Output;
2158
2159 auto Append = [&Output](const std::string_view& Content) {
2160 for (auto e : Content)
2161 {
2162 Output.push_back(e);
2163 }
2164 };
2165
2166 Reverse = (NtEnvironment != nullptr);
2167
2168 //
2169 // Always add WSLENV to the block.
2170 //
2171
2172 Append(WSLENV_ENV "=");
2173
2174 EnvList = UtilGetEnv(WSLENV_ENV, NtEnvironment);
2175 if (EnvList.has_value())
2176 {
2177 Append(EnvList.value());
2178 }
2179
2180 Output.push_back('\0');
2181 if (EnvList.has_value())
2182 {
2183 //
2184 // Trim any whitespace from the end of the list.
2185 //
2186
2187 while (!EnvList->empty() && isspace(EnvList->back()))
2188 {
2189 EnvList->pop_back();
2190 }
2191
2192 for (char *Sp, *EnvName = strtok_r(EnvList->data(), ":", &Sp); EnvName != nullptr; EnvName = strtok_r(NULL, ":", &Sp))
2193 {
2194 char Mode = 0;
2195 bool SkipTranslation = false;
2196 char* Slash = strchr(EnvName, '/');
2197 if (Slash != nullptr)
2198 {
2199 *Slash = '\0';
2200 for (char* Flags = Slash + 1; *Flags != '\0'; Flags++)
2201 {
2202 switch (*Flags)
2203 {
2204 case 'p': // path
2205 case 'l': // path list
2206 if (Mode != 0 && Mode != 'p' && Mode != 'l')
2207 {
2208 SkipTranslation = true;
2209 }
2210 else
2211 {
2212 Mode = *Flags;
2213 }
2214 break;
2215
2216 case 'u': // Win32 -> WSL translation only
2217 if (Reverse == false)
2218 {
2219 SkipTranslation = true;
2220 }
2221
2222 break;
2223
2224 case 'w': // WSL -> Win32 translation only
2225 if (Reverse != false)
2226 {
2227 SkipTranslation = true;
2228 }
2229
2230 break;
2231
2232 default:
2233
2234 //
2235 // Ignore entries with an unknown flag to support future
2236 // extensibility.
2237 //
2238
2239 SkipTranslation = true;
2240 break;
2241 }
2242 }
2243 }
2244
2245 auto EnvVal = UtilGetEnv(EnvName, NtEnvironment);
2246 if (!SkipTranslation && EnvVal.has_value())
2247 {
2248 switch (Mode)
2249 {
2250 case 'p':
2251 case 'l':
2252 {
2253
2254 //
2255 // Translate the path or path list.
2256 //
2257
2258 auto Result = UtilTranslatePathList(EnvVal->data(), Reverse);
2259 if (Result.has_value())
2260 {
2261 EnvVal = std::move(Result.value());
2262 }
2263 else
2264 {
2265 SkipTranslation = true;
2266 }
2267
2268 break;
2269 }
2270
2271 default:
2272 break;
2273 }
2274 }
2275
2276 if (!SkipTranslation)
2277 {
2278 Append(std::format("{}={}", EnvName, EnvVal.value_or("")));
2279 Output.push_back('\0');
2280 }
2281 }
2282 }
2283
2284 Output.push_back('\0');
2285
2286 return Output;
2287 }
2288
2289 int UtilProcessChildExitCode(int Status, const char* Name, int ExpectedStatus, bool PrintError)
2290
2291 /*++
2292
2293 Routine Description:
2294
2295 Handles the exit status of a child process.
2296
2297 Arguments:
2298
2299 Status - Supplies the exit status.
2300
2301 Name - Supplies the process image name, for logging.
2302
2303 ExpectedStatus - Supplies the expected exit status.
2304
2305 PrintError - Supplies a boolean that specifies if an error should be printed if the process does not return the expected status.
2306
2307 Return Value:
2308
2309 0 on success, -1 on failure.
2310
2311 --*/
2312
2313 {
2314 if (WIFEXITED(Status))
2315 {
2316 Status = WEXITSTATUS(Status);
2317 if (Status == ExpectedStatus)
2318 {
2319 return 0;
2320 }
2321 }
2322 else if (WIFSIGNALED(Status))
2323 {
2324 LOG_ERROR("{} killed by signal {}", Name, WTERMSIG(Status));
2325 return -1;
2326 }
2327
2328 if (PrintError)
2329 {
2330 LOG_ERROR("{} returned {}", Name, Status);
2331 }
2332
2333 return -1;
2334 }
2335
2336 ssize_t UtilRead(int Fd, void* Buffer, size_t BufferSize, int Timeout)
2337
2338 /*++
2339
2340 Routine Description:
2341
2342 This routine reads a message from the given file descriptor with an optional
2343 timeout.
2344
2345 Arguments:
2346
2347 Fd - Supplies a file descriptor.
2348
2349 Buffer - Supplies a buffer.
2350
2351 BufferSize - Supplies the size of the buffer in bytes.
2352
2353 Timeout - Supplies a timeout in milliseconds.
2354
2355 Return Value:
2356
2357 The number of bytes read, -1 on failure.
2358
2359 --*/
2360
2361 {
2362 //
2363 // If a timeout was specified, use a pollfd.
2364 //
2365
2366 ssize_t Result = 0;
2367 if (Timeout != -1)
2368 {
2369 pollfd PollDescriptor{Fd, POLLIN, 0};
2370 Result = poll(&PollDescriptor, 1, Timeout);
2371 if ((Result <= 0) || ((PollDescriptor.revents & POLLIN) == 0))
2372 {
2373 errno = ETIMEDOUT;
2374 Result = -1;
2375 }
2376 }
2377
2378 if (Result != -1)
2379 {
2380 Result = TEMP_FAILURE_RETRY(read(Fd, Buffer, BufferSize));
2381 }
2382
2383 return Result;
2384 }
2385
2386 ssize_t UtilReadBuffer(int Fd, std::vector<gsl::byte>& Buffer, int Timeout)
2387
2388 /*++
2389
2390 Routine Description:
2391
2392 This routine reads a message from the given file descriptor and
2393 automatically grows the buffer when needed.
2394
2395 Arguments:
2396
2397 Fd - Supplies a file descriptor.
2398
2399 Buffer - Supplies a buffer; this buffer will be resized if needed.
2400
2401 Timeout - Supplies a timeout in milliseconds.
2402
2403 Return Value:
2404
2405 The number of bytes read, -1 on failure.
2406
2407 --*/
2408
2409 try
2410 {
2411 ssize_t Result;
2412
2413 UtilInitializeMessageBuffer(Buffer);
2414 for (;;)
2415 {
2416 Result = UtilRead(Fd, Buffer.data(), Buffer.size(), Timeout);
2417 if (Result < 0)
2418 {
2419 //
2420 // When the message buffer is too small, EOVERFLOW is returned and
2421 // the buffer size is doubled.
2422 //
2423
2424 if (errno == EOVERFLOW)
2425 {
2426 Buffer.resize(Buffer.size() * 2);
2427 continue;
2428 }
2429 }
2430
2431 break;
2432 }
2433
2434 return Result;
2435 }
2436 CATCH_RETURN_ERRNO()
2437
2438 std::string UtilReadFile(FILE* File)
2439
2440 /*++
2441
2442 Routine Description:
2443
2444 This routine reads an entire file into a buffer.
2445
2446 Arguments:
2447
2448 File - Supplies a open file stream.
2449
2450 Return Value:
2451
2452 A std::string that contains the contents of the file.
2453
2454 --*/
2455
2456 {
2457 char* Line = nullptr;
2458 size_t LineLength;
2459 std::string output;
2460
2461 //
2462 // Ensure the file is at the beginning of the stream.
2463 //
2464
2465 rewind(File);
2466
2467 //
2468 // Read the entire file into a buffer.
2469 //
2470
2471 LineLength = 0;
2472 while (getline(&Line, &LineLength, File) != -1)
2473 {
2474 output += Line;
2475 output += '\n';
2476 }
2477
2478 return output;
2479 }
2480
2481 std::vector<gsl::byte> UtilReadFileRaw(const char* Path, size_t MaxSize)
2482 {
2483 wil::unique_fd file{open(Path, O_RDONLY)};
2484 THROW_LAST_ERROR_IF(!file);
2485
2486 constexpr auto bufferSize = 4096;
2487
2488 size_t offset = 0;
2489 std::vector<gsl::byte> buffer;
2490 while (true)
2491 {
2492 buffer.resize(offset + bufferSize);
2493
2494 int result = read(file.get(), buffer.data() + offset, bufferSize);
2495 THROW_LAST_ERROR_IF(result < 0);
2496
2497 if (result == 0)
2498 {
2499 break;
2500 }
2501
2502 offset += result;
2503
2504 if (offset > MaxSize)
2505 {
2506 LOG_ERROR("File \"{}\" is too big. Maximum size: {}", Path, MaxSize);
2507 THROW_ERRNO(E2BIG);
2508 }
2509 }
2510
2511 buffer.resize(offset);
2512
2513 return buffer;
2514 }
2515
2516 std::pair<std::optional<std::string>, std::optional<std::string>> UtilReadFlavorAndVersion(const char* Path)
2517 try
2518 {
2519 // See reference format: https://www.freedesktop.org/software/systemd/man/latest/os-release.html
2520 std::ifstream file;
2521 file.open(Path);
2522
2523 std::optional<std::string> version;
2524 std::optional<std::string> flavor;
2525 std::regex versionPattern("^VERSION_ID=\"?([a-zA-Z0-9\\-_\\.,]*)\"?$");
2526 std::regex flavorPattern("^ID=\"?([a-zA-Z0-9\\-_\\.,]*)\"?$");
2527
2528 std::string line;
2529 while (file && std::getline(file, line) && (!version.has_value() || !flavor.has_value()))
2530 {
2531 std::smatch match;
2532 if (std::regex_search(line, match, versionPattern))
2533 {
2534 version = match.str(1);
2535 }
2536 else if (std::regex_search(line, match, flavorPattern))
2537 {
2538 flavor = match.str(1);
2539 }
2540 }
2541
2542 return std::make_pair(std::move(flavor), std::move(version));
2543 }
2544 catch (...)
2545 {
2546 LOG_CAUGHT_EXCEPTION();
2547
2548 return {};
2549 }
2550
2551 ssize_t UtilReadMessageLxBus(int MessageFd, std::vector<gsl::byte>& Buffer, bool ShutdownOnDisconnect)
2552
2553 /*++
2554
2555 Routine Description:
2556
2557 This routine reads a message from the server.
2558
2559 Arguments:
2560
2561 MessageFd - Supplies a message port file descriptor.
2562
2563 Buffer - Supplies a buffer; this buffer will be resized if needed.
2564
2565 ShutdownOnDisconnect - Supplies true for shutdown on disconnect, false for
2566 _exit.
2567
2568 Return Value:
2569
2570 The number of bytes read, -1 on failure.
2571
2572 --*/
2573
2574 try
2575 {
2576 UtilInitializeMessageBuffer(Buffer);
2577 wil::unique_fd Epoll{epoll_create1(EPOLL_CLOEXEC)};
2578 if (!Epoll)
2579 {
2580 FATAL_ERROR("Failed to create epoll {}", errno);
2581 }
2582
2583 epoll_event EpollEvent{};
2584 EpollEvent.events = EPOLLIN | EPOLLHUP;
2585 EpollEvent.data.fd = MessageFd;
2586 if (epoll_ctl(Epoll.get(), EPOLL_CTL_ADD, MessageFd, &EpollEvent) < 0)
2587 {
2588 FATAL_ERROR("Failed epoll_ctl {}", errno);
2589 }
2590
2591 ssize_t BytesRead;
2592 for (;;)
2593 {
2594 //
2595 // Message port read/write operations are blocking, so use an epoll to
2596 // allow any incoming signals to be processed while waiting for the
2597 // message.
2598 //
2599
2600 if (TEMP_FAILURE_RETRY(epoll_wait(Epoll.get(), &EpollEvent, 1, -1)) != 1)
2601 {
2602 FATAL_ERROR("Failed epoll_wait {}", errno);
2603 }
2604
2605 BytesRead = TEMP_FAILURE_RETRY(read(MessageFd, Buffer.data(), Buffer.size()));
2606 if (BytesRead >= static_cast<ssize_t>(sizeof(MESSAGE_HEADER)))
2607 {
2608 break;
2609 }
2610
2611 if (BytesRead < 0)
2612 {
2613 //
2614 // When the message buffer is too small, EOVERFLOW is returned and
2615 // the buffer is increased. When the Windows server disconnects
2616 // EPIPE is returned and the process handles the disconnect.
2617 //
2618
2619 if (errno == EOVERFLOW)
2620 {
2621 auto BufferSizeNew = *(reinterpret_cast<size_t*>(Buffer.data()));
2622 if (BufferSizeNew <= Buffer.size())
2623 {
2624 BufferSizeNew = Buffer.size() * 2;
2625 }
2626
2627 Buffer.resize(BufferSizeNew);
2628 continue;
2629 }
2630
2631 if (errno == EPIPE)
2632 {
2633 if (ShutdownOnDisconnect == false)
2634 {
2635 _exit(0);
2636 }
2637 }
2638
2639 FATAL_ERROR("Failed to read message {}", errno);
2640 break;
2641 }
2642
2643 FATAL_ERROR("Unexpected message size {}", BytesRead);
2644 break;
2645 }
2646
2647 return BytesRead;
2648 }
2649 CATCH_RETURN_ERRNO()
2650
2651 int UtilRestoreBlockedSignals()
2652 {
2653 return sigprocmask(SIG_SETMASK, &g_originalSignals, nullptr);
2654 }
2655
2656 int UtilSaveBlockedSignals(const sigset_t& SignalMask)
2657 {
2658 return sigprocmask(SIG_BLOCK, &SignalMask, &g_originalSignals);
2659 }
2660
2661 // Returns true for signals that should not be saved/restored:
2662 // SIGKILL/SIGSTOP — not settable per POSIX.
2663 // SIGCONT — left at default to allow process resumption.
2664 // SIGHUP — handled separately by the caller.
2665 // 32-34 — internal NPTL signals (__SIGRTMIN through __SIGRTMIN+2) reserved
2666 // by glibc for thread cancellation and other runtime use.
2667 static bool SkipSignal(unsigned int Signal)
2668 {
2669 switch (Signal)
2670 {
2671 case SIGKILL:
2672 case SIGSTOP:
2673 case SIGCONT:
2674 case SIGHUP:
2675 case 32:
2676 case 33:
2677 case 34:
2678 return true;
2679
2680 default:
2681 return false;
2682 }
2683 }
2684
2685 int UtilSaveSignalHandlers(struct sigaction* SavedSignalActions)
2686
2687 /*++
2688
2689 Routine Description:
2690
2691 This routine saves all settable signal handlers, skipping signals
2692 listed in SkipSignal() (non-settable, SIGHUP, and internal NPTL signals).
2693
2694 Arguments:
2695
2696 SavedSignalActions - Supplies an array to save default signal actions.
2697
2698 Return Value:
2699
2700 0 on success, -1 on failure.
2701
2702 --*/
2703
2704 {
2705 for (unsigned int Index = 1; Index < _NSIG; Index += 1)
2706 {
2707 if (SkipSignal(Index))
2708 {
2709 continue;
2710 }
2711
2712 if (sigaction(Index, NULL, &SavedSignalActions[Index]) < 0)
2713 {
2714 FATAL_ERROR("sigaction ({}) query failed {}", Index, errno);
2715 }
2716 }
2717
2718 return 0;
2719 }
2720
2721 int UtilSetSignalHandlers(struct sigaction* SavedSignalActions, bool Ignore)
2722
2723 /*++
2724
2725 Routine Description:
2726
2727 This routine sets all settable signal handlers to the given handler,
2728 skipping signals listed in SkipSignal().
2729
2730 Arguments:
2731
2732 SavedSignalActions - Supplies an array of signal handlers to set.
2733
2734 Ignore - Supplies a boolean specifying if signals should be ignored.
2735
2736 Return Value:
2737
2738 0 on success, -1 on failure.
2739
2740 --*/
2741
2742 {
2743 struct sigaction SignalAction;
2744
2745 for (unsigned int Index = 1; Index < _NSIG; Index += 1)
2746 {
2747 if (SkipSignal(Index))
2748 {
2749 continue;
2750 }
2751
2752 memcpy(&SignalAction, &SavedSignalActions[Index], sizeof(SignalAction));
2753 if (Ignore != false)
2754 {
2755 SignalAction.sa_handler = SIG_IGN;
2756 }
2757
2758 if (sigaction(Index, &SignalAction, NULL) < 0)
2759 {
2760 FATAL_ERROR("sigaction ({}) set failed {}", Index, errno);
2761 }
2762 }
2763
2764 return 0;
2765 }
2766
2767 void UtilSetThreadName(const char* Name)
2768 {
2769 g_threadName = Name;
2770
2771 if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(Name), 0, 0, 0) < 0)
2772 {
2773 LOG_ERROR("prctl failed {}", errno);
2774 }
2775 }
2776
2777 void UtilSocketShutdown(int Fd, int How)
2778
2779 /*++
2780
2781 Routine Description:
2782
2783 This routine cleanly shuts down a socket.
2784
2785 Arguments:
2786
2787 Fd - Supplies a socket file descriptor.
2788
2789 How - Supplies the type of shutdown.
2790
2791 Return Value:
2792
2793 None.
2794
2795 --*/
2796
2797 {
2798 if (shutdown(Fd, How) < 0)
2799 {
2800 LOG_ERROR("shutdown({}) failed {}", How, errno);
2801 }
2802 }
2803
2804 bool UtilSizeTAdd(size_t Left, size_t Right, size_t* Out)
2805
2806 /*++
2807
2808 Routine Description:
2809
2810 This routine checks if overflow will occur when adding two size_t values and
2811 if overflow is not possible, adds the values.
2812
2813 Arguments:
2814
2815 Left - Supplies the first value.
2816
2817 Right - Supplies the second value.
2818
2819 Out - Supplies a pointer to receive sum on success.
2820
2821 Return Value:
2822
2823 true if addition was done without overflow, false otherwise.
2824
2825 --*/
2826
2827 {
2828 bool Success = false;
2829 if (Right > (Right + Left))
2830 {
2831 goto SizeTAddExit;
2832 }
2833
2834 *Out = Left + Right;
2835 Success = true;
2836
2837 SizeTAddExit:
2838 return Success;
2839 }
2840
2841 std::string_view UtilStringNextToken(std::string_view& View, std::string_view Separators)
2842
2843 /*++
2844
2845 Routine Description:
2846
2847 This routine extracts the next token identified by one of the specified
2848 separators.
2849
2850 Arguments:
2851
2852 View - Supplies the string view to tokenize. On return, this parameter is
2853 modified to contain the remaining portion of the string after the
2854 separator, or an empty string if no separator was found.
2855
2856 Separators - Supplies the separators that appear between the tokens.
2857
2858 Return Value:
2859
2860 The contents of the string up to the next separator, or the entire string
2861 if no separator was found.
2862
2863 --*/
2864
2865 {
2866 std::string_view Result;
2867 auto Pos = View.find_first_of(Separators);
2868 if (Pos == std::string_view::npos)
2869 {
2870 Result = View;
2871 View = {};
2872 }
2873 else
2874 {
2875 Result = View.substr(0, Pos);
2876 View = View.substr(Pos + 1);
2877 }
2878
2879 return Result;
2880 }
2881
2882 std::string_view UtilStringNextToken(std::string_view& View, char Separator)
2883
2884 /*++
2885
2886 Routine Description:
2887
2888 This routine extracts the next token identified by the specified separator.
2889
2890 Arguments:
2891
2892 View - Supplies the string view to tokenize. On return, this parameter is
2893 modified to contain the remaining portion of the string after the
2894 separator, or an empty string if no separator was found.
2895
2896 Separators - Supplies the separator that appears between the tokens.
2897
2898 Return Value:
2899
2900 The contents of the string up to the next separator, or the entire string
2901 if no separator was found.
2902
2903 --*/
2904
2905 {
2906 std::string_view Result;
2907 auto Pos = View.find_first_of(Separator);
2908 if (Pos == std::string_view::npos)
2909 {
2910 Result = View;
2911 View = {};
2912 }
2913 else
2914 {
2915 Result = View.substr(0, Pos);
2916 View = View.substr(Pos + 1);
2917 }
2918
2919 return Result;
2920 }
2921
2922 std::optional<std::string> UtilTranslatePathList(char* PathList, bool IsNtPathList)
2923
2924 /*++
2925
2926 Routine Description:
2927
2928 This routine translates a semicolon-separated list of NT paths into a
2929 colon-separated list of Linux paths.
2930
2931 Arguments:
2932
2933 PathList - Supplies the semicolon-separated list of paths to translate.
2934
2935 IsNtPathList - Supplies a boolean specifying if the list contains NT-style
2936 paths.
2937
2938 LxPath - Supplies a buffer to receive an allocated string with the
2939 translated path list.
2940
2941 Return Value:
2942
2943 0 on success, -error for failure.
2944
2945 --*/
2946
2947 {
2948 char Mode;
2949 const char* SourceSeparator;
2950 char TargetSeparator;
2951 std::string TranslatedList;
2952
2953 if (IsNtPathList != false)
2954 {
2955 Mode = TRANSLATE_MODE_UNIX;
2956 SourceSeparator = ";";
2957 TargetSeparator = ':';
2958 }
2959 else
2960 {
2961 Mode = TRANSLATE_MODE_WINDOWS;
2962 SourceSeparator = ":";
2963 TargetSeparator = ';';
2964 }
2965
2966 //
2967 // Translate each element in the list. If an element in the list fails to
2968 // translate, ignore it.
2969 //
2970
2971 for (char *Sp, *Path = strtok_r(PathList, SourceSeparator, &Sp); Path != nullptr; Path = strtok_r(NULL, SourceSeparator, &Sp))
2972 {
2973 //
2974 // Skip relative Windows paths.
2975 //
2976
2977 if ((Mode == TRANSLATE_MODE_UNIX) && (!UtilIsAbsoluteWindowsPath(Path)))
2978 {
2979 continue;
2980 }
2981
2982 std::string TranslatedPath = WslPathTranslate(Path, 0, Mode);
2983 if (TranslatedPath.empty())
2984 {
2985 auto WarningMessage = wsl::shared::Localization::MessageFailedToTranslate(Path);
2986 if (wil::ScopedWarningsCollector::CanCollectWarning())
2987 {
2988 EMIT_USER_WARNING(std::move(WarningMessage));
2989 }
2990 else
2991 {
2992 LOG_WARNING("{}", WarningMessage);
2993 }
2994
2995 continue;
2996 }
2997
2998 if (!TranslatedList.empty())
2999 {
3000 TranslatedList += TargetSeparator;
3001 }
3002
3003 TranslatedList += TranslatedPath;
3004 }
3005
3006 if (TranslatedList.empty())
3007 {
3008 return {};
3009 }
3010
3011 return TranslatedList;
3012 }
3013
3014 std::string UtilWinPathTranslate(const char* Path, bool Reverse)
3015
3016 /*++
3017
3018 Routine Description:
3019
3020 This routine translates an absolute Linux path or an absolute
3021 Windows path into the other.
3022
3023 Arguments:
3024
3025 Path - Supplies the path to translate.
3026
3027 Reverse - Supplies a bool, if set perform translation from Windows->Linux
3028 path; otherwise, translation from Linux->Windows path.
3029
3030 Return Value:
3031
3032 0 on success, -error for general failure.
3033
3034 --*/
3035
3036 try
3037 {
3038 size_t PathLength;
3039 size_t PrefixLength;
3040 char* Suffix;
3041 size_t SuffixLength;
3042 size_t TranslatedLength;
3043 size_t TranslatedSuffixLength;
3044
3045 //
3046 // Find if there is a DrvFs or Plan 9 mount for the specified path.
3047 //
3048
3049 auto PrefixReplacement = UtilFindMount(MOUNT_INFO_FILE, Path, Reverse, &PrefixLength);
3050 if (PrefixReplacement.empty())
3051 {
3052 //
3053 // The path is not part of a DrvFs or Plan 9 mount, so the path should
3054 // be translated to use the plan9 redirector.
3055 //
3056
3057 return UtilWinPathTranslateInternal(Path, Reverse);
3058 }
3059
3060 //
3061 // If translating Linux to Windows, see if any characters need to be
3062 // escaped.
3063 //
3064
3065 PathLength = strlen(Path);
3066 SuffixLength = PathLength - PrefixLength;
3067 if (Reverse == false)
3068 {
3069 //
3070 // If the suffix is empty and the replacement prefix is just a drive
3071 // letter, make space to append a backslash.
3072 //
3073
3074 if ((SuffixLength == 0) && (PrefixReplacement.length() == 2) && (PrefixReplacement[1] == DRIVE_SEP_NT))
3075 {
3076 TranslatedSuffixLength = 1;
3077 }
3078 else
3079 {
3080 TranslatedSuffixLength = EscapePathForNtLength(&Path[PrefixLength]);
3081 }
3082 }
3083 else
3084 {
3085 TranslatedSuffixLength = SuffixLength;
3086 }
3087
3088 //
3089 // Construct the new path out of the replacement prefix and the remainder
3090 // of the path, escaping it if necessary.
3091 //
3092
3093 std::string TranslatedPath{PrefixReplacement};
3094 TranslatedLength = PrefixReplacement.length() + TranslatedSuffixLength;
3095 TranslatedPath.resize(TranslatedLength, '\0');
3096 Suffix = &TranslatedPath[PrefixReplacement.length()];
3097 if (TranslatedSuffixLength != SuffixLength)
3098 {
3099 //
3100 // If the suffix is empty and the replacement prefix is just a drive
3101 // letter, append a backslash. Otherwise, escape and append the
3102 // suffix.
3103 //
3104
3105 if ((SuffixLength == 0) && (TranslatedSuffixLength == 1))
3106 {
3107 *Suffix = PATH_SEP_NT;
3108 }
3109 else
3110 {
3111 EscapePathForNt(&Path[PrefixLength], Suffix);
3112 }
3113 }
3114 else
3115 {
3116 memcpy(Suffix, &Path[PrefixLength], SuffixLength);
3117
3118 //
3119 // Make sure the translated path uses the correct separators.
3120 //
3121 // N.B. This is done by the escape method if escaping is necessary.
3122 //
3123
3124 UtilCanonicalisePathSeparator(Suffix, Reverse ? PATH_SEP : PATH_SEP_NT);
3125 }
3126
3127 return TranslatedPath;
3128 }
3129 catch (...)
3130 {
3131 LOG_CAUGHT_EXCEPTION();
3132 return {};
3133 }
3134
3135 std::string UtilWinPathTranslateInternal(const char* Path, bool Reverse)
3136
3137 /*++
3138
3139 Routine Description:
3140
3141 This routine translates an absolute Linux path or an absolute
3142 Windows path into the other.
3143
3144 Arguments:
3145
3146 Path - Supplies the path to translate.
3147
3148 Reverse - Supplies a bool, if set perform translation from Windows->Linux
3149 path; otherwise, translation from Linux->Windows path.
3150
3151 Return Value:
3152
3153 0 on success, -1 on failure.
3154
3155 --*/
3156
3157 try
3158 {
3159 //
3160 // Get the distribution name from the environment variable.
3161 //
3162
3163 const auto DistributionName = UtilGetEnvironmentVariable(WSL_DISTRO_NAME_ENV);
3164 if (DistributionName.empty())
3165 {
3166 return {};
3167 }
3168
3169 //
3170 // Construct a prefix (\\wsl.localhost\DistributionName).
3171 //
3172
3173 std::string Prefix{PLAN9_RDR_PREFIX};
3174 Prefix += DistributionName;
3175
3176 //
3177 // For Windows to Linux translation, concatenate the prefix and the escaped
3178 // version of the path. For Linux to Windows translation, ensure the path
3179 // begins with the prefix, remove the prefix, and unescape the path.
3180 //
3181
3182 std::string TranslatedPath{};
3183 if (Reverse == false)
3184 {
3185 TranslatedPath += Prefix;
3186 const size_t EscapedPathLength = EscapePathForNtLength(Path);
3187 std::string EscapedPath(EscapedPathLength, '\0');
3188 EscapePathForNt(Path, EscapedPath.data());
3189 TranslatedPath += EscapedPath;
3190 }
3191 else
3192 {
3193 auto matchesPrefix = [Path](const std::string_view& prefix) {
3194 if (!wsl::shared::string::StartsWith(Path, prefix, true))
3195 {
3196 return false;
3197 }
3198
3199 // Validate that the next character is a path separator or the end of the string to prevent matching other distribution paths like:
3200 // \\wsl.localhost\<distro-name>-<suffix>
3201
3202 auto nextChar = Path[prefix.size()];
3203 return nextChar == '\0' || nextChar == PATH_SEP || nextChar == PATH_SEP_NT;
3204 };
3205
3206 auto PrefixLength = Prefix.length();
3207 if (!matchesPrefix(Prefix))
3208 {
3209 //
3210 // Check the old \\wsl$ prefix if it's not \\wsl.localhost.
3211 //
3212
3213 std::string CompatPrefix{PLAN9_RDR_COMPAT_PREFIX};
3214 CompatPrefix += DistributionName;
3215 if (!matchesPrefix(CompatPrefix))
3216 {
3217 return {};
3218 }
3219
3220 PrefixLength = CompatPrefix.length();
3221 }
3222
3223 Path += PrefixLength;
3224 if (strlen(Path) == 0)
3225 {
3226 TranslatedPath += PATH_SEP;
3227 }
3228 else
3229 {
3230 TranslatedPath += Path;
3231
3232 //
3233 // Canonicalize the path separators and unescape the string.
3234 //
3235
3236 UtilCanonicalisePathSeparator(TranslatedPath.data(), PATH_SEP);
3237 UnescapePathInplace(TranslatedPath.data());
3238 }
3239 }
3240
3241 return TranslatedPath;
3242 }
3243 catch (...)
3244 {
3245 LOG_CAUGHT_EXCEPTION();
3246 return {};
3247 }
3248
3249 ssize_t UtilWriteBuffer(int Fd, gsl::span<const gsl::byte> Buffer)
3250
3251 /*++
3252
3253 Routine Description:
3254
3255 This routine writes an entire buffer to the given file descriptor.
3256
3257 Arguments:
3258
3259 Fd - Supplies a file descriptor.
3260
3261 Buffer - Supplies the buffer to write.
3262
3263 Return Value:
3264
3265 The total number of bytes written, -1 on failure.
3266
3267 --*/
3268
3269 {
3270 return UtilWriteBuffer(Fd, Buffer.data(), Buffer.size());
3271 }
3272
3273 ssize_t UtilWriteBuffer(int Fd, const void* Buffer, size_t BufferSize)
3274
3275 /*++
3276
3277 Routine Description:
3278
3279 This routine writes an entire buffer to the given file descriptor.
3280
3281 Arguments:
3282
3283 Fd - Supplies a file descriptor.
3284
3285 Buffer - Supplies a buffer pointer.
3286
3287 BufferSize - Supplies the buffer size.
3288
3289 Return Value:
3290
3291 The total number of bytes written, -1 on failure.
3292
3293 --*/
3294
3295 {
3296 ssize_t BytesWritten;
3297 ssize_t Result;
3298 ssize_t TotalBytesWritten;
3299 auto* Offset = static_cast<const char*>(Buffer);
3300
3301 Result = -1;
3302 TotalBytesWritten = 0;
3303 do
3304 {
3305 BytesWritten = TEMP_FAILURE_RETRY(write(Fd, Offset, BufferSize));
3306 if (BytesWritten < 0)
3307 {
3308 goto WriteBufferExit;
3309 }
3310
3311 BufferSize -= BytesWritten;
3312 Offset += BytesWritten;
3313 TotalBytesWritten += BytesWritten;
3314 } while (BufferSize > 0);
3315
3316 Result = TotalBytesWritten;
3317
3318 WriteBufferExit:
3319 return Result;
3320 }
3321
3322 ssize_t UtilWriteStringView(int Fd, std::string_view StringView)
3323
3324 /*++
3325
3326 Routine Description:
3327
3328 This routine writes a string view to the given file descriptor.
3329
3330 Arguments:
3331
3332 Fd - Supplies a file descriptor.
3333
3334 StringView - Supplies the string view to write.
3335
3336 Return Value:
3337
3338 The total number of bytes written, -1 on failure.
3339
3340 --*/
3341
3342 {
3343 return UtilWriteBuffer(Fd, StringView.data(), StringView.size());
3344 }
3345
3346 std::wstring UtilReadFileContentW(std::string_view path)
3347 {
3348 std::wifstream file;
3349 file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
3350 file.open(path);
3351
3352 return {std::istreambuf_iterator<wchar_t>(file), {}};
3353 }
3354
3355 std::string UtilReadFileContent(std::string_view path)
3356 {
3357 std::ifstream file;
3358 file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
3359 file.open(path);
3360
3361 return {std::istreambuf_iterator<char>(file), {}};
3362 }
3363
3364 HvPciSwiotlbPool UtilReadHvPciSwiotlbPool()
3365 {
3366 HvPciSwiotlbPool pool{};
3367 try
3368 {
3369 pool.Base = std::stoull(UtilReadFileContent("/sys/bus/vmbus/drivers/hv_pci/swiotlb_base"), nullptr, 0);
3370 pool.Size = std::stoull(UtilReadFileContent("/sys/bus/vmbus/drivers/hv_pci/swiotlb_size"), nullptr, 0);
3371 }
3372 catch (...)
3373 {
3374 pool = {};
3375 }
3376
3377 return pool;
3378 }
3379
3380 uint16_t UtilWinAfToLinuxAf(uint16_t WinAddressFamily)
3381 {
3382 uint16_t LinuxAddressFamily = AF_UNSPEC;
3383
3384 switch (WinAddressFamily)
3385 {
3386 case 2:
3387 LinuxAddressFamily = AF_INET;
3388 break;
3389 case 23:
3390 LinuxAddressFamily = AF_INET6;
3391 break;
3392 }
3393
3394 return LinuxAddressFamily;
3395 }
3396
3397 int WriteToFile(const char* Path, const char* Content, int OpenFlags, int Permissions)
3398
3399 /*++
3400
3401 Routine Description:
3402
3403 Write content to the specified file.
3404
3405 Arguments:
3406
3407 Path - Supplies the path to the file to write.
3408
3409 Content - Supplies the content to be written to the file.
3410
3411 OpenFlags - Supplies the flags passed to open().
3412
3413 Permissions - Supplies the file mode used when O_CREAT causes the file to be created.
3414
3415 Return Value:
3416
3417 0 on success, -1 on failure.
3418
3419 --*/
3420
3421 {
3422 wil::unique_fd Fd{open(Path, OpenFlags, Permissions)};
3423 if (!Fd)
3424 {
3425 int errnoPrev = errno;
3426 LOG_ERROR("open({}) failed {}", Path, errno);
3427 errno = errnoPrev;
3428 return -1;
3429 }
3430
3431 std::string_view Buffer{Content};
3432 auto Result = UtilWriteStringView(Fd.get(), Buffer);
3433 if (Result != Buffer.size())
3434 {
3435 int errnoPrev = errno;
3436 LOG_ERROR("write({}, {}) failed {} {}", Path, Content, Result, errno);
3437 errno = errnoPrev;
3438 return -1;
3439 }
3440
3441 return 0;
3442 }
3443
3444 int ProcessCreateProcessMessage(wsl::shared::Transaction& Transaction, gsl::span<gsl::byte> Buffer, const std::optional<std::string>& DistroCgroupPath)
3445 {
3446 auto* Message = gslhelpers::try_get_struct<CREATE_PROCESS_MESSAGE>(Buffer);
3447 if (!Message)
3448 {
3449 LOG_ERROR("Unexpected message size {}", Buffer.size());
3450 return -1;
3451 }
3452
3453 auto sendResult = [&](unsigned long Result) { Transaction.SendResultMessage<int32_t>(Result); };
3454
3455 sockaddr_vm SocketAddress{};
3456 wil::unique_fd ListenSocket{UtilListenVsockAnyPort(&SocketAddress, 1, false)};
3457 THROW_LAST_ERROR_IF(!ListenSocket);
3458
3459 sendResult(SocketAddress.svm_port);
3460
3461 // Always return the execution result, since the service expects it
3462 int execResult = -1;
3463 auto sendExecResult = wil::scope_exit([&]() { sendResult(execResult); });
3464
3465 const char* Path = wsl::shared::string::FromSpan(Buffer, Message->PathIndex);
3466 const char* Arguments = wsl::shared::string::FromSpan(Buffer, Message->CommandLineIndex);
3467
3468 // Note: this makes the assumption that no empty arguments are in the message
3469 std::vector<const char*> ArgumentArray;
3470 while (*Arguments != '\0')
3471 {
3472 ArgumentArray.emplace_back(Arguments);
3473 Arguments += strlen(Arguments) + 1;
3474 }
3475 ArgumentArray.emplace_back(nullptr);
3476
3477 auto ControlPipe = wil::unique_pipe::create(O_CLOEXEC);
3478
3479 const int ChildPid = UtilCreateChildProcess(
3480 "CreateChildProcess",
3481 [&]() {
3482 try
3483 {
3484 wil::unique_fd ProcessSocket{UtilAcceptVsock(ListenSocket.get(), SocketAddress, SESSION_LEADER_ACCEPT_TIMEOUT_MS)};
3485 THROW_LAST_ERROR_IF(!ProcessSocket);
3486
3487 THROW_LAST_ERROR_IF(dup2(ProcessSocket.get(), STDIN_FILENO) < 0);
3488 THROW_LAST_ERROR_IF(dup2(ProcessSocket.get(), STDOUT_FILENO) < 0);
3489 execv(Path, (char* const*)(ArgumentArray.data()));
3490
3491 // If this point is reached, an error needs to be reported back since execv() failed.
3492 THROW_LAST_ERROR();
3493 }
3494 catch (...)
3495 {
3496 auto error = wil::ResultFromCaughtException();
3497 LOG_ERROR("Command execution failed: {}", errno);
3498
3499 if (write(ControlPipe.write().get(), &error, sizeof(error)) != sizeof(error))
3500 {
3501 LOG_ERROR("Failed to write command execution status: {}", errno);
3502 }
3503 }
3504 },
3505 {},
3506 DistroCgroupPath);
3507
3508 THROW_LAST_ERROR_IF(ChildPid < 0);
3509 ControlPipe.write().reset();
3510
3511 int ReadResult = TEMP_FAILURE_RETRY(read(ControlPipe.read().get(), &execResult, sizeof(execResult)));
3512 THROW_LAST_ERROR_IF(ReadResult < 0);
3513
3514 // If the pipe closed without data, then exec() was successful
3515 if (ReadResult == 0)
3516 {
3517 execResult = 0;
3518 }
3519 else if (ReadResult == sizeof(execResult))
3520 {
3521 // Otherwise, return the error code to the service
3522 execResult = abs(execResult);
3523 }
3524 else
3525 {
3526 execResult = EINVAL;
3527 }
3528
3529 return 0;
3530 }
3531
3532 #define RECLAIM_PATH CGROUP_MOUNTPOINT "/memory.reclaim"
3533
3534 namespace {
3535
3536 class CpuIdleTracker
3537 {
3538 public:
3539 struct State
3540 {
3541 bool IntervalIdle;
3542 bool WindowIdle;
3543 };
3544
3545 State AddSample(unsigned long long Busy, unsigned long long Total)
3546 {
3547 m_windowBusy -= m_busyWindow[m_windowIndex];
3548 m_windowTotal -= m_totalWindow[m_windowIndex];
3549 m_busyWindow[m_windowIndex] = Busy;
3550 m_totalWindow[m_windowIndex] = Total;
3551 m_windowBusy += Busy;
3552 m_windowTotal += Total;
3553 m_windowIndex = (m_windowIndex + 1) % c_windowIntervals;
3554 if (m_windowSamples < c_windowIntervals)
3555 {
3556 m_windowSamples += 1;
3557 }
3558
3559 return {
3560 .IntervalIdle = IsIdle(Busy, Total),
3561 .WindowIdle = m_windowSamples == c_windowIntervals && IsIdle(m_windowBusy, m_windowTotal),
3562 };
3563 }
3564
3565 void Reset()
3566 {
3567 m_busyWindow.fill(0);
3568 m_totalWindow.fill(0);
3569 m_windowBusy = 0;
3570 m_windowTotal = 0;
3571 m_windowIndex = 0;
3572 m_windowSamples = 0;
3573 }
3574
3575 private:
3576 static bool IsIdle(unsigned long long Busy, unsigned long long Total)
3577 {
3578 return Total == 0 || Busy * 1000 <= Total * c_busyThresholdPerMille;
3579 }
3580
3581 static constexpr size_t c_windowIntervals = 12; // 2 minutes
3582 static constexpr unsigned long long c_busyThresholdPerMille = 5; // 0.5%
3583
3584 std::array<unsigned long long, c_windowIntervals> m_busyWindow{};
3585 std::array<unsigned long long, c_windowIntervals> m_totalWindow{};
3586 unsigned long long m_windowBusy = 0;
3587 unsigned long long m_windowTotal = 0;
3588 size_t m_windowIndex = 0;
3589 size_t m_windowSamples = 0;
3590 };
3591
3592 } // namespace
3593
3594 static bool ReadCpuBusyIdle(unsigned long long& Busy, unsigned long long& Idle)
3595
3596 /*++
3597
3598 Routine Description:
3599
3600 This routine parses the aggregate "cpu" line of /proc/stat and splits the cumulative jiffies into
3601 busy and idle buckets. Idle time is idle + iowait; everything else (user, nice, system, irq,
3602 softirq, steal) counts as busy, so kernel-bound work keeps the VM out of the idle state rather than
3603 looking at user time alone.
3604
3605 Arguments:
3606
3607 Busy - Receives the cumulative busy jiffies across all cores.
3608
3609 Idle - Receives the cumulative idle jiffies (idle + iowait) across all cores.
3610
3611 Return Value:
3612
3613 true on success, false on failure.
3614
3615 --*/
3616
3617 {
3618 wil::unique_fd fd{TEMP_FAILURE_RETRY(open("/proc/stat", O_RDONLY | O_CLOEXEC))};
3619 if (!fd)
3620 {
3621 LOG_ERROR("open(/proc/stat) failed {}", errno);
3622 return false;
3623 }
3624
3625 char buffer[256];
3626 const ssize_t result = TEMP_FAILURE_RETRY(read(fd.get(), buffer, sizeof(buffer) - 1));
3627 if (result <= 0)
3628 {
3629 LOG_ERROR("read(/proc/stat) failed {}", errno);
3630 return false;
3631 }
3632
3633 buffer[result] = '\0';
3634
3635 //
3636 // Format: "cpu user nice system idle iowait irq softirq steal ...". The user, nice, system, idle,
3637 // and iowait fields are required; irq, softirq, and steal are optional and any fields after steal
3638 // are ignored.
3639 //
3640
3641 static const std::regex cpuLine{R"(^cpu[ \t]+(\d+)[ \t]+(\d+)[ \t]+(\d+)[ \t]+(\d+)[ \t]+(\d+)(?:[ \t]+(\d+))?(?:[ \t]+(\d+))?(?:[ \t]+(\d+))?)"};
3642 std::cmatch match;
3643 if (!std::regex_search(buffer, match, cpuLine))
3644 {
3645 LOG_ERROR("failed to parse /proc/stat cpu line");
3646 return false;
3647 }
3648
3649 unsigned long long fields[8] = {};
3650 for (size_t index = 0; index < COUNT_OF(fields); index += 1)
3651 {
3652 if (match[index + 1].matched)
3653 {
3654 fields[index] = strtoull(match[index + 1].first, nullptr, 10);
3655 }
3656 }
3657
3658 Idle = fields[3] + fields[4];
3659 Busy = fields[0] + fields[1] + fields[2] + fields[5] + fields[6] + fields[7];
3660
3661 return true;
3662 }
3663
3664 static long long GetReclaimableCacheBytes()
3665
3666 /*++
3667
3668 Routine Description:
3669
3670 This routine returns the amount of reclaimable file-backed page cache (in bytes) by parsing
3671 /proc/meminfo. It counts only memory that cache reclaim can actually return to the host:
3672 Active(file) + Inactive(file) + SReclaimable. Anonymous memory is excluded because reclaim of clean
3673 cache cannot free it.
3674
3675 Arguments:
3676
3677 None.
3678
3679 Return Value:
3680
3681 Reclaimable cache in bytes, or -1 on failure.
3682
3683 --*/
3684
3685 {
3686 std::ifstream memInfo("/proc/meminfo");
3687 if (!memInfo)
3688 {
3689 LOG_ERROR("failed to open /proc/meminfo");
3690 return -1;
3691 }
3692
3693 // /proc/meminfo values are in kB.
3694 long long activeFileKb = 0;
3695 long long inactiveFileKb = 0;
3696 long long reclaimableSlabKb = 0;
3697 bool foundActiveFile = false;
3698 bool foundInactiveFile = false;
3699 bool foundReclaimableSlab = false;
3700 std::string line;
3701 while (std::getline(memInfo, line))
3702 {
3703 std::istringstream stream(line);
3704 std::string name;
3705 long long value = 0;
3706 if (!(stream >> name >> value))
3707 {
3708 continue;
3709 }
3710
3711 if (name == "Active(file):")
3712 {
3713 activeFileKb = value;
3714 foundActiveFile = true;
3715 }
3716 else if (name == "Inactive(file):")
3717 {
3718 inactiveFileKb = value;
3719 foundInactiveFile = true;
3720 }
3721 else if (name == "SReclaimable:")
3722 {
3723 reclaimableSlabKb = value;
3724 foundReclaimableSlab = true;
3725 }
3726 }
3727
3728 if (memInfo.bad())
3729 {
3730 LOG_ERROR("failed to read /proc/meminfo");
3731 return -1;
3732 }
3733
3734 if (!foundActiveFile || !foundInactiveFile || !foundReclaimableSlab)
3735 {
3736 LOG_ERROR("failed to find reclaimable cache counters in /proc/meminfo");
3737 return -1;
3738 }
3739
3740 return (activeFileKb + inactiveFileKb + reclaimableSlabKb) * 1024;
3741 }
3742
3743 static bool RequestReclaim(long long Bytes)
3744
3745 /*++
3746
3747 Routine Description:
3748
3749 Best-effort write of a byte count to the cgroup memory.reclaim knob. EAGAIN is an expected outcome
3750 (the kernel freed some, but not all, of the requested pages) and is treated as success without
3751 logging, so the long-lived reduction thread does not error out every interval. A transient failure
3752 never throws.
3753
3754 Arguments:
3755
3756 Bytes - Supplies the number of bytes to request the kernel reclaim.
3757
3758 Return Value:
3759
3760 true if pages were reclaimed (full success or EAGAIN), false otherwise.
3761
3762 --*/
3763
3764 {
3765 wil::unique_fd fd{TEMP_FAILURE_RETRY(open(RECLAIM_PATH, O_WRONLY | O_CLOEXEC))};
3766 if (!fd)
3767 {
3768 LOG_ERROR("open({}) failed {}", RECLAIM_PATH, errno);
3769 return false;
3770 }
3771
3772 const std::string request = std::to_string(Bytes) + " swappiness=0";
3773 const ssize_t result = UtilWriteStringView(fd.get(), request);
3774 if (result == static_cast<ssize_t>(request.size()) || (result < 0 && errno == EAGAIN))
3775 {
3776 return true;
3777 }
3778
3779 LOG_ERROR("write({}, {}) failed {}", RECLAIM_PATH, request, errno);
3780 return false;
3781 }
3782
3783 void StartMemoryReductionThread(LX_MINI_INIT_MEMORY_RECLAIM_MODE Mode)
3784
3785 /*++
3786
3787 Routine Description:
3788
3789 This routine starts a background thread that reclaims cold page cache and compacts free pages while
3790 the VM is idle, so the maximum number of pages can be discarded back to the host.
3791
3792 Reclaim is gated on CPU idle using a rolling window of all non-idle CPU time, not just user time.
3793 Gradual mode reclaims cold file-backed page cache above a small floor via the cgroup memory.reclaim
3794 knob, falling back to drop_caches when the knob is unavailable. DropCache mode uses drop_caches
3795 directly. Freed pages are compacted so free-page reporting can hand back large blocks.
3796
3797 Arguments:
3798
3799 Mode - Supplies the memory reclaim mode.
3800
3801 Return Value:
3802
3803 None.
3804
3805 --*/
3806
3807 try
3808 {
3809 if (Mode == LxMiniInitMemoryReclaimModeDisabled)
3810 {
3811 return;
3812 }
3813
3814 std::thread([Mode]() {
3815 try
3816 {
3817 //
3818 // Run at idle scheduling priority so reclaim never competes with real work.
3819 //
3820
3821 sched_param parameter{};
3822 parameter.sched_priority = 0;
3823 const int result = pthread_setschedparam(pthread_self(), SCHED_IDLE, &parameter);
3824 THROW_ERRNO_IF(result, result != 0);
3825
3826 //
3827 // Gradual mode uses cgroup memory.reclaim and falls back to drop_caches when unavailable.
3828 //
3829
3830 bool useReclaim = Mode != LxMiniInitMemoryReclaimModeDropCache;
3831 if (useReclaim && access(RECLAIM_PATH, W_OK) < 0)
3832 {
3833 LOG_WARNING("access({}, W_OK) failed {}, falling back to drop_caches", RECLAIM_PATH, errno);
3834 useReclaim = false;
3835 }
3836
3837 constexpr auto c_pollInterval = std::chrono::seconds(10);
3838
3839 // Reclaimable cache below this floor is always retained to protect a minimal working set.
3840 constexpr long long c_floorBytes = 128ll * 1024 * 1024;
3841
3842 // Scale reclaim requests with the VM size while keeping individual operations bounded.
3843 constexpr long long c_minReclaimBytes = 256ll * 1024 * 1024;
3844 constexpr long long c_maxReclaimBytes = 1024ll * 1024 * 1024;
3845
3846 struct sysinfo info = {};
3847 THROW_LAST_ERROR_IF(sysinfo(&info) < 0);
3848
3849 long long reclaimStepBytes = (static_cast<long long>(info.totalram) * info.mem_unit) / 32;
3850 if (reclaimStepBytes < c_minReclaimBytes)
3851 {
3852 reclaimStepBytes = c_minReclaimBytes;
3853 }
3854 else if (reclaimStepBytes > c_maxReclaimBytes)
3855 {
3856 reclaimStepBytes = c_maxReclaimBytes;
3857 }
3858
3859 unsigned long long previousBusy = 0;
3860 unsigned long long previousIdle = 0;
3861 bool havePreviousSample = false;
3862
3863 CpuIdleTracker idleTracker;
3864
3865 bool droppedThisIdlePeriod = false;
3866 bool compactedThisIdlePeriod = false;
3867
3868 for (;;)
3869 {
3870 std::this_thread::sleep_for(c_pollInterval);
3871
3872 unsigned long long busy = 0;
3873 unsigned long long idle = 0;
3874 if (!ReadCpuBusyIdle(busy, idle))
3875 {
3876 continue;
3877 }
3878
3879 if (!havePreviousSample)
3880 {
3881 previousBusy = busy;
3882 previousIdle = idle;
3883 havePreviousSample = true;
3884 continue;
3885 }
3886
3887 //
3888 // Guard against non-monotonic counters (should not happen, but resample if it does).
3889 //
3890
3891 if (busy < previousBusy || idle < previousIdle)
3892 {
3893 previousBusy = busy;
3894 previousIdle = idle;
3895 idleTracker.Reset();
3896 droppedThisIdlePeriod = false;
3897 compactedThisIdlePeriod = false;
3898 continue;
3899 }
3900
3901 const unsigned long long busyDelta = busy - previousBusy;
3902 const unsigned long long totalDelta = busyDelta + (idle - previousIdle);
3903 previousBusy = busy;
3904 previousIdle = idle;
3905
3906 const auto idleState = idleTracker.AddSample(busyDelta, totalDelta);
3907 if (!idleState.WindowIdle)
3908 {
3909 droppedThisIdlePeriod = false;
3910 compactedThisIdlePeriod = false;
3911 continue;
3912 }
3913
3914 //
3915 // A short burst blocks this tick but does not discard the preceding idle history.
3916 //
3917
3918 if (!idleState.IntervalIdle)
3919 {
3920 continue;
3921 }
3922
3923 //
3924 // The VM is idle: reclaim cold cache and compact.
3925 //
3926
3927 bool reclaimed = false;
3928 if (useReclaim)
3929 {
3930 const long long cache = GetReclaimableCacheBytes();
3931 if (cache > c_floorBytes)
3932 {
3933 long long bytes = cache - c_floorBytes;
3934 if (bytes > reclaimStepBytes)
3935 {
3936 bytes = reclaimStepBytes;
3937 }
3938
3939 reclaimed = RequestReclaim(bytes);
3940 }
3941 }
3942 else if (!droppedThisIdlePeriod)
3943 {
3944 //
3945 // drop_caches=3 frees the page cache along with reclaimable slab (dentries and
3946 // inodes), matching the SReclaimable slab counted by GetReclaimableCacheBytes.
3947 //
3948
3949 if (WriteToFile("/proc/sys/vm/drop_caches", "3\n") == 0)
3950 {
3951 droppedThisIdlePeriod = true;
3952 reclaimed = true;
3953 }
3954 }
3955
3956 //
3957 // Coalesce freed pages into larger blocks for efficient page reporting.
3958 //
3959
3960 bool memoryOperation = reclaimed;
3961 if (!compactedThisIdlePeriod || reclaimed)
3962 {
3963 if (WriteToFile("/proc/sys/vm/compact_memory", "1\n") == 0)
3964 {
3965 compactedThisIdlePeriod = true;
3966 memoryOperation = true;
3967 }
3968 }
3969
3970 //
3971 // Exclude the reclaim/compaction work from the next utilization interval so it does not
3972 // restart the grace period itself.
3973 //
3974
3975 if (memoryOperation)
3976 {
3977 if (!ReadCpuBusyIdle(previousBusy, previousIdle))
3978 {
3979 havePreviousSample = false;
3980 idleTracker.Reset();
3981 droppedThisIdlePeriod = false;
3982 compactedThisIdlePeriod = false;
3983 }
3984 }
3985 }
3986 }
3987 CATCH_LOG()
3988 }).detach();
3989 }
3990 CATCH_LOG()
3991
3992 std::string UtilGetDistroCgroupPath(pid_t DistroInitPid)
3993 {
3994 return std::format("{}/distro-{}", WSL_USER_CGROUP_PATH, DistroInitPid);
3995 }
3996
3997 int UtilEnableAllCgroupControllers(const std::string& CgroupPath)
3998 {
3999 // Only cpu and memory are required for wsl's resource limit; every other controller cgroup.controllers
4000 // reports is enabled on a best-effort basis.
4001 constexpr std::string_view RequiredControllers[] = {"cpu", "memory"};
4002 std::string RequiredEntries;
4003 for (const auto Controller : RequiredControllers)
4004 {
4005 RequiredEntries += std::format("+{} ", Controller);
4006 }
4007
4008 if (WriteToFile((CgroupPath + "/cgroup.subtree_control").c_str(), RequiredEntries.c_str()) < 0)
4009 {
4010 LOG_ERROR("Failed to enable cgroup controllers for {}: {}", CgroupPath, errno);
4011 return -1;
4012 }
4013
4014 std::string AvailableControllers;
4015 try
4016 {
4017 AvailableControllers = UtilReadFileContent(CgroupPath + "/cgroup.controllers");
4018 }
4019 CATCH_LOG();
4020
4021 std::string_view Remaining{AvailableControllers};
4022 while (!Remaining.empty())
4023 {
4024 auto Controller = UtilStringNextToken(Remaining, " \n");
4025 if (Controller.empty() ||
4026 std::find(std::begin(RequiredControllers), std::end(RequiredControllers), Controller) != std::end(RequiredControllers))
4027 {
4028 continue;
4029 }
4030
4031 // WriteToFile() already logs a failure; these controllers are best-effort so no extra handling is needed.
4032 WriteToFile((CgroupPath + "/cgroup.subtree_control").c_str(), std::format("+{}", Controller).c_str());
4033 }
4034 return 0;
4035 }
4036
4037 void UtilTryMoveSelfToDistroCgroup(const std::string& CgroupPath, bool IsSystemd, const std::string& LogSubject)
4038 try
4039 {
4040 std::string ProcsFile{};
4041 if (IsSystemd)
4042 {
4043 ProcsFile = CgroupPath + WSL_USER_SYSTEMD_CGROUP_DIR + "/cgroup.procs";
4044 }
4045 else
4046 {
4047 auto NonSystemdCgroupPath = CgroupPath + WSL_USER_NON_SYSTEMD_CGROUP_DIR;
4048 auto NonSystemdCgroupExists = access(NonSystemdCgroupPath.c_str(), F_OK) == 0;
4049 if (NonSystemdCgroupExists)
4050 {
4051 ProcsFile = CgroupPath + WSL_USER_NON_SYSTEMD_CGROUP_DIR "/cgroup.procs";
4052 }
4053 else
4054 {
4055 ProcsFile = CgroupPath + "/cgroup.procs";
4056 }
4057 }
4058
4059 if (WriteToFile(ProcsFile.c_str(), "0") < 0)
4060 {
4061 LOG_WARNING("Failed to move process to cgroup {} for {}: {}", CgroupPath, LogSubject, errno);
4062 }
4063 }
4064 CATCH_LOG();