1 //
2 // Copyright 2023 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // process.cpp:
7 // Process manages a child process. This is largely copied from chrome.
8 //
9
10 #include "libANGLE/renderer/metal/process.h"
11
12 #include <crt_externs.h>
13 #include <fcntl.h>
14 #include <mach/mach.h>
15 #include <os/availability.h>
16 #include <spawn.h>
17 #include <string.h>
18 #include <sys/resource.h>
19 #include <sys/sysctl.h>
20 #include <sys/time.h>
21 #include <sys/wait.h>
22 #include <unistd.h>
23
24 #include "common/base/anglebase/logging.h"
25 #include "common/debug.h"
26
27 namespace rx
28 {
29 namespace mtl
30 {
31 namespace
32 {
33
34 // This code is copied from chrome's process launching code:
35 // (base/process/launch_mac.cc).
36
37 typedef pid_t ProcessId;
38 constexpr ProcessId kNullProcessId = 0;
39
40 // DPSXCHECK is a Debug Posix Spawn Check macro. The posix_spawn* family of
41 // functions return an errno value, as opposed to setting errno directly. This
42 // macro emulates a DPCHECK().
43 #define DPSXCHECK(expr) \
44 do \
45 { \
46 int rv = (expr); \
47 DCHECK(rv == 0); \
48 } while (0)
49
50 // DCHECK(rv == 0) << #expr << ": -" << rv << " " << strerror(rv);
51
52 class PosixSpawnAttr
53 {
54 public:
PosixSpawnAttr()55 PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_init(&attr_)); }
56
~PosixSpawnAttr()57 ~PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_destroy(&attr_)); }
58
get()59 posix_spawnattr_t *get() { return &attr_; }
60
61 private:
62 posix_spawnattr_t attr_;
63 };
64
65 class PosixSpawnFileActions
66 {
67 public:
PosixSpawnFileActions()68 PosixSpawnFileActions() { DPSXCHECK(posix_spawn_file_actions_init(&file_actions_)); }
69
70 PosixSpawnFileActions(const PosixSpawnFileActions &) = delete;
71 PosixSpawnFileActions &operator=(const PosixSpawnFileActions &) = delete;
72
~PosixSpawnFileActions()73 ~PosixSpawnFileActions() { DPSXCHECK(posix_spawn_file_actions_destroy(&file_actions_)); }
74
Open(int filedes,const char * path,int mode)75 void Open(int filedes, const char *path, int mode)
76 {
77 DPSXCHECK(posix_spawn_file_actions_addopen(&file_actions_, filedes, path, mode, 0));
78 }
79
Dup2(int filedes,int newfiledes)80 void Dup2(int filedes, int newfiledes)
81 {
82 DPSXCHECK(posix_spawn_file_actions_adddup2(&file_actions_, filedes, newfiledes));
83 }
84
Inherit(int filedes)85 void Inherit(int filedes)
86 {
87 DPSXCHECK(posix_spawn_file_actions_addinherit_np(&file_actions_, filedes));
88 }
89
90 #if TARGET_OS_OSX
Chdir(const char * path)91 void Chdir(const char *path) API_AVAILABLE(macos(10.15))
92 {
93 DPSXCHECK(posix_spawn_file_actions_addchdir_np(&file_actions_, path));
94 }
95 #endif
96
get() const97 const posix_spawn_file_actions_t *get() const { return &file_actions_; }
98
99 private:
100 posix_spawn_file_actions_t file_actions_;
101 };
102
103 // This is a slimmed down version of chrome's LaunchProcess().
LaunchProcess(const std::vector<std::string> & argv)104 ProcessId LaunchProcess(const std::vector<std::string> &argv)
105 {
106 PosixSpawnAttr attr;
107
108 DPSXCHECK(posix_spawnattr_setflags(attr.get(), POSIX_SPAWN_CLOEXEC_DEFAULT));
109
110 PosixSpawnFileActions file_actions;
111
112 // Process file descriptors for the child. By default, LaunchProcess will
113 // open stdin to /dev/null and inherit stdout and stderr.
114 bool inherit_stdout = true, inherit_stderr = true;
115 bool null_stdin = true;
116
117 if (null_stdin)
118 {
119 file_actions.Open(STDIN_FILENO, "/dev/null", O_RDONLY);
120 }
121 if (inherit_stdout)
122 {
123 file_actions.Inherit(STDOUT_FILENO);
124 }
125 if (inherit_stderr)
126 {
127 file_actions.Inherit(STDERR_FILENO);
128 }
129
130 std::vector<char *> argv_cstr;
131 argv_cstr.reserve(argv.size() + 1);
132 for (const auto &arg : argv)
133 {
134 argv_cstr.push_back(const_cast<char *>(arg.c_str()));
135 }
136 argv_cstr.push_back(nullptr);
137
138 const bool clear_environment = false;
139 char *empty_environ = nullptr;
140 char **new_environ = clear_environment ? &empty_environ : *_NSGetEnviron();
141
142 const char *executable_path = argv_cstr[0];
143
144 pid_t pid;
145 // Use posix_spawnp as some callers expect to have PATH consulted.
146 int rv = posix_spawnp(&pid, executable_path, file_actions.get(), attr.get(), &argv_cstr[0],
147 new_environ);
148
149 if (rv != 0)
150 {
151 FATAL() << "posix_spawnp failed";
152 return kNullProcessId;
153 }
154
155 return pid;
156 }
157
GetParentProcessId(ProcessId process)158 ProcessId GetParentProcessId(ProcessId process)
159 {
160 struct kinfo_proc info;
161 size_t length = sizeof(struct kinfo_proc);
162 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, process};
163 if (sysctl(mib, 4, &info, &length, NULL, 0) < 0)
164 {
165 FATAL() << "sysctl failed";
166 return -1;
167 }
168 if (length == 0)
169 return -1;
170 return info.kp_eproc.e_ppid;
171 }
172
173 #define HANDLE_EINTR(x) \
174 ({ \
175 decltype(x) eintr_wrapper_result; \
176 do \
177 { \
178 eintr_wrapper_result = (x); \
179 } while (eintr_wrapper_result == -1 && errno == EINTR); \
180 eintr_wrapper_result; \
181 })
182
WaitForExitImpl(ProcessId pid,int & exit_code)183 bool WaitForExitImpl(ProcessId pid, int &exit_code)
184 {
185 const ProcessId our_pid = getpid();
186 ASSERT(pid != our_pid);
187
188 const bool exited = (GetParentProcessId(pid) < 0);
189 int status;
190 const bool wait_result = HANDLE_EINTR(waitpid(pid, &status, 0)) > 0;
191 if (!wait_result)
192 {
193 return exited;
194 }
195 if (WIFSIGNALED(status))
196 {
197 exit_code = -1;
198 return true;
199 }
200 if (WIFEXITED(status))
201 {
202 exit_code = WEXITSTATUS(status);
203 return true;
204 }
205 return exited;
206 }
207
208 } // namespace
209
Process(const std::vector<std::string> & argv)210 Process::Process(const std::vector<std::string> &argv) : pid_(LaunchProcess(argv)) {}
211
~Process()212 Process::~Process()
213 {
214 // TODO: figure out if should terminate/wait/whatever.
215 }
216
WaitForExit(int & exit_code)217 bool Process::WaitForExit(int &exit_code)
218 {
219 ASSERT(pid_ != kNullProcessId);
220 return WaitForExitImpl(pid_, exit_code);
221 }
222
DidLaunch() const223 bool Process::DidLaunch() const
224 {
225 return pid_ != kNullProcessId;
226 }
227
228 } // namespace mtl
229 } // namespace rx
230