1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/process/launch.h"
6
7 #include <crt_externs.h>
8 #include <mach/mach.h>
9 #include <os/availability.h>
10 #include <spawn.h>
11 #include <string.h>
12 #include <sys/wait.h>
13
14 #include "base/command_line.h"
15 #include "base/files/scoped_file.h"
16 #include "base/logging.h"
17 #include "base/mac/mach_port_rendezvous.h"
18 #include "base/memory/raw_ptr.h"
19 #include "base/posix/eintr_wrapper.h"
20 #include "base/process/environment_internal.h"
21 #include "base/threading/scoped_blocking_call.h"
22 #include "base/threading/thread_restrictions.h"
23 #include "base/trace_event/base_tracing.h"
24
25 extern "C" {
26 // Changes the current thread's directory to a path or directory file
27 // descriptor.
28 int pthread_chdir_np(const char* dir);
29
30 int pthread_fchdir_np(int fd);
31
32 int responsibility_spawnattrs_setdisclaim(posix_spawnattr_t attrs,
33 int disclaim);
34 } // extern "C"
35
36 namespace base {
37
38 namespace {
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 int rv = (expr); \
46 DCHECK_EQ(rv, 0) << #expr << ": -" << rv << " " << strerror(rv); \
47 } while (0)
48
49 class PosixSpawnAttr {
50 public:
PosixSpawnAttr()51 PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_init(&attr_)); }
52
~PosixSpawnAttr()53 ~PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_destroy(&attr_)); }
54
get()55 posix_spawnattr_t* get() { return &attr_; }
56
57 private:
58 posix_spawnattr_t attr_;
59 };
60
61 class PosixSpawnFileActions {
62 public:
PosixSpawnFileActions()63 PosixSpawnFileActions() {
64 DPSXCHECK(posix_spawn_file_actions_init(&file_actions_));
65 }
66
67 PosixSpawnFileActions(const PosixSpawnFileActions&) = delete;
68 PosixSpawnFileActions& operator=(const PosixSpawnFileActions&) = delete;
69
~PosixSpawnFileActions()70 ~PosixSpawnFileActions() {
71 DPSXCHECK(posix_spawn_file_actions_destroy(&file_actions_));
72 }
73
Open(int filedes,const char * path,int mode)74 void Open(int filedes, const char* path, int mode) {
75 DPSXCHECK(posix_spawn_file_actions_addopen(&file_actions_, filedes, path,
76 mode, 0));
77 }
78
Dup2(int filedes,int newfiledes)79 void Dup2(int filedes, int newfiledes) {
80 DPSXCHECK(
81 posix_spawn_file_actions_adddup2(&file_actions_, filedes, newfiledes));
82 }
83
Inherit(int filedes)84 void Inherit(int filedes) {
85 DPSXCHECK(posix_spawn_file_actions_addinherit_np(&file_actions_, filedes));
86 }
87
88 #if BUILDFLAG(IS_MAC)
Chdir(const char * path)89 void Chdir(const char* path) {
90 DPSXCHECK(posix_spawn_file_actions_addchdir_np(&file_actions_, path));
91 }
92 #endif
93
get() const94 const posix_spawn_file_actions_t* get() const { return &file_actions_; }
95
96 private:
97 posix_spawn_file_actions_t file_actions_;
98 };
99
100 #if !BUILDFLAG(IS_MAC)
ChangeCurrentThreadDirectory(const char * path)101 int ChangeCurrentThreadDirectory(const char* path) {
102 return pthread_chdir_np(path);
103 }
104
105 // The recommended way to unset a per-thread cwd is to set a new value to an
106 // invalid file descriptor, per libpthread-218.1.3/private/private.h.
ResetCurrentThreadDirectory()107 int ResetCurrentThreadDirectory() {
108 return pthread_fchdir_np(-1);
109 }
110 #endif
111
112 struct GetAppOutputOptions {
113 // Whether to pipe stderr to stdout in |output|.
114 bool include_stderr = false;
115 // Caller-supplied string poiter for the output.
116 raw_ptr<std::string> output = nullptr;
117 // Result exit code of Process::Wait().
118 int exit_code = 0;
119 };
120
GetAppOutputInternal(const std::vector<std::string> & argv,GetAppOutputOptions * gao_options)121 bool GetAppOutputInternal(const std::vector<std::string>& argv,
122 GetAppOutputOptions* gao_options) {
123 TRACE_EVENT0("base", "GetAppOutput");
124
125 ScopedFD read_fd, write_fd;
126 {
127 int pipefds[2];
128 if (pipe(pipefds) != 0) {
129 DPLOG(ERROR) << "pipe";
130 return false;
131 }
132 read_fd.reset(pipefds[0]);
133 write_fd.reset(pipefds[1]);
134 }
135
136 LaunchOptions launch_options;
137 launch_options.fds_to_remap.emplace_back(write_fd.get(), STDOUT_FILENO);
138 if (gao_options->include_stderr) {
139 launch_options.fds_to_remap.emplace_back(write_fd.get(), STDERR_FILENO);
140 }
141
142 Process process = LaunchProcess(argv, launch_options);
143
144 // Close the parent process' write descriptor, so that EOF is generated in
145 // read loop below.
146 write_fd.reset();
147
148 // Read the child's output before waiting for its exit, otherwise the pipe
149 // buffer may fill up if the process is producing a lot of output.
150 std::string* output = gao_options->output;
151 output->clear();
152
153 const size_t kBufferSize = 1024;
154 size_t total_bytes_read = 0;
155 ssize_t read_this_pass = 0;
156 do {
157 output->resize(output->size() + kBufferSize);
158 read_this_pass = HANDLE_EINTR(
159 read(read_fd.get(), &(*output)[total_bytes_read], kBufferSize));
160 if (read_this_pass >= 0) {
161 total_bytes_read += static_cast<size_t>(read_this_pass);
162 output->resize(total_bytes_read);
163 }
164 } while (read_this_pass > 0);
165
166 // It is okay to allow this process to wait on the launched process as a
167 // process launched with GetAppOutput*() shouldn't wait back on the process
168 // that launched it.
169 internal::GetAppOutputScopedAllowBaseSyncPrimitives allow_wait;
170 if (!process.WaitForExit(&gao_options->exit_code)) {
171 return false;
172 }
173
174 return read_this_pass == 0;
175 }
176
177 } // namespace
178
LaunchProcess(const CommandLine & cmdline,const LaunchOptions & options)179 Process LaunchProcess(const CommandLine& cmdline,
180 const LaunchOptions& options) {
181 return LaunchProcess(cmdline.argv(), options);
182 }
183
LaunchProcess(const std::vector<std::string> & argv,const LaunchOptions & options)184 Process LaunchProcess(const std::vector<std::string>& argv,
185 const LaunchOptions& options) {
186 TRACE_EVENT0("base", "LaunchProcess");
187
188 PosixSpawnAttr attr;
189
190 short flags = POSIX_SPAWN_CLOEXEC_DEFAULT;
191 if (options.new_process_group) {
192 flags |= POSIX_SPAWN_SETPGROUP;
193 DPSXCHECK(posix_spawnattr_setpgroup(attr.get(), 0));
194 }
195 DPSXCHECK(posix_spawnattr_setflags(attr.get(), flags));
196
197 PosixSpawnFileActions file_actions;
198
199 // Process file descriptors for the child. By default, LaunchProcess will
200 // open stdin to /dev/null and inherit stdout and stderr.
201 bool inherit_stdout = true, inherit_stderr = true;
202 bool null_stdin = true;
203 for (const auto& dup2_pair : options.fds_to_remap) {
204 if (dup2_pair.second == STDIN_FILENO) {
205 null_stdin = false;
206 } else if (dup2_pair.second == STDOUT_FILENO) {
207 inherit_stdout = false;
208 } else if (dup2_pair.second == STDERR_FILENO) {
209 inherit_stderr = false;
210 }
211
212 if (dup2_pair.first == dup2_pair.second) {
213 file_actions.Inherit(dup2_pair.second);
214 } else {
215 file_actions.Dup2(dup2_pair.first, dup2_pair.second);
216 }
217 }
218
219 if (null_stdin) {
220 file_actions.Open(STDIN_FILENO, "/dev/null", O_RDONLY);
221 }
222 if (inherit_stdout) {
223 file_actions.Inherit(STDOUT_FILENO);
224 }
225 if (inherit_stderr) {
226 file_actions.Inherit(STDERR_FILENO);
227 }
228
229 #if BUILDFLAG(IS_MAC)
230 if (options.disclaim_responsibility) {
231 DPSXCHECK(responsibility_spawnattrs_setdisclaim(attr.get(), 1));
232 }
233 #endif
234
235 std::vector<char*> argv_cstr;
236 argv_cstr.reserve(argv.size() + 1);
237 for (const auto& arg : argv)
238 argv_cstr.push_back(const_cast<char*>(arg.c_str()));
239 argv_cstr.push_back(nullptr);
240
241 std::unique_ptr<char*[]> owned_environ;
242 char* empty_environ = nullptr;
243 char** new_environ =
244 options.clear_environment ? &empty_environ : *_NSGetEnviron();
245 if (!options.environment.empty()) {
246 owned_environ =
247 internal::AlterEnvironment(new_environ, options.environment);
248 new_environ = owned_environ.get();
249 }
250
251 const char* executable_path = !options.real_path.empty()
252 ? options.real_path.value().c_str()
253 : argv_cstr[0];
254
255 if (__builtin_available(macOS 11.0, *)) {
256 if (options.enable_cpu_security_mitigations) {
257 DPSXCHECK(posix_spawnattr_set_csm_np(attr.get(), POSIX_SPAWN_NP_CSM_ALL));
258 }
259 }
260
261 if (!options.current_directory.empty()) {
262 const char* chdir_str = options.current_directory.value().c_str();
263 #if BUILDFLAG(IS_MAC)
264 file_actions.Chdir(chdir_str);
265 #else
266 // If the chdir posix_spawn_file_actions extension is not available,
267 // change the thread-specific working directory. The new process will
268 // inherit it during posix_spawnp().
269 int rv = ChangeCurrentThreadDirectory(chdir_str);
270 if (rv != 0) {
271 DPLOG(ERROR) << "pthread_chdir_np";
272 return Process();
273 }
274 #endif
275 }
276
277 int rv;
278 pid_t pid;
279 {
280 const bool has_mach_ports_for_rendezvous =
281 !options.mach_ports_for_rendezvous.empty();
282 #if BUILDFLAG(IS_IOS)
283 // This code is only used for the iOS simulator to launch tests. We do not
284 // support setting MachPorts on launch. You should look at
285 // content::ChildProcessLauncherHelper (for iOS) if you are trying to spawn
286 // a non-test and need ports.
287 CHECK(!has_mach_ports_for_rendezvous);
288 #else
289 // If |options.mach_ports_for_rendezvous| is specified : the server's lock
290 // must be held for the duration of posix_spawnp() so that new child's PID
291 // can be recorded with the set of ports.
292 AutoLockMaybe rendezvous_lock(
293 has_mach_ports_for_rendezvous
294 ? &MachPortRendezvousServer::GetInstance()->GetLock()
295 : nullptr);
296 #endif
297 // Use posix_spawnp as some callers expect to have PATH consulted.
298 rv = posix_spawnp(&pid, executable_path, file_actions.get(), attr.get(),
299 &argv_cstr[0], new_environ);
300
301 #if !BUILDFLAG(IS_IOS)
302 if (has_mach_ports_for_rendezvous) {
303 if (rv == 0) {
304 MachPortRendezvousServer::GetInstance()->GetLock().AssertAcquired();
305 MachPortRendezvousServer::GetInstance()->RegisterPortsForPid(
306 pid, options.mach_ports_for_rendezvous);
307 } else {
308 // Because |options| is const-ref, the collection has to be copied here.
309 // The caller expects to relinquish ownership of any strong rights if
310 // LaunchProcess() were to succeed, so these rights should be manually
311 // destroyed on failure.
312 MachPortsForRendezvous ports = options.mach_ports_for_rendezvous;
313 for (auto& port : ports) {
314 port.second.Destroy();
315 }
316 }
317 }
318 #endif
319 }
320
321 #if !BUILDFLAG(IS_MAC)
322 // Restore the thread's working directory if it was changed.
323 if (!options.current_directory.empty()) {
324 ResetCurrentThreadDirectory();
325 }
326 #endif
327
328 if (rv != 0) {
329 DLOG(ERROR) << "posix_spawnp(" << executable_path << "): -" << rv << " "
330 << strerror(rv);
331 return Process();
332 }
333
334 if (options.wait) {
335 // While this isn't strictly disk IO, waiting for another process to
336 // finish is the sort of thing ThreadRestrictions is trying to prevent.
337 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
338 pid_t ret = HANDLE_EINTR(waitpid(pid, nullptr, 0));
339 DPCHECK(ret > 0);
340 }
341
342 return Process(pid);
343 }
344
GetAppOutput(const CommandLine & cl,std::string * output)345 bool GetAppOutput(const CommandLine& cl, std::string* output) {
346 return GetAppOutput(cl.argv(), output);
347 }
348
GetAppOutputAndError(const CommandLine & cl,std::string * output)349 bool GetAppOutputAndError(const CommandLine& cl, std::string* output) {
350 return GetAppOutputAndError(cl.argv(), output);
351 }
352
GetAppOutputWithExitCode(const CommandLine & cl,std::string * output,int * exit_code)353 bool GetAppOutputWithExitCode(const CommandLine& cl,
354 std::string* output,
355 int* exit_code) {
356 GetAppOutputOptions options;
357 options.output = output;
358 bool rv = GetAppOutputInternal(cl.argv(), &options);
359 *exit_code = options.exit_code;
360 return rv;
361 }
362
GetAppOutput(const std::vector<std::string> & argv,std::string * output)363 bool GetAppOutput(const std::vector<std::string>& argv, std::string* output) {
364 GetAppOutputOptions options;
365 options.output = output;
366 return GetAppOutputInternal(argv, &options) &&
367 options.exit_code == EXIT_SUCCESS;
368 }
369
GetAppOutputAndError(const std::vector<std::string> & argv,std::string * output)370 bool GetAppOutputAndError(const std::vector<std::string>& argv,
371 std::string* output) {
372 GetAppOutputOptions options;
373 options.include_stderr = true;
374 options.output = output;
375 return GetAppOutputInternal(argv, &options) &&
376 options.exit_code == EXIT_SUCCESS;
377 }
378
RaiseProcessToHighPriority()379 void RaiseProcessToHighPriority() {
380 // Historically this has not been implemented on POSIX and macOS. This could
381 // influence the Mach task policy in the future.
382 }
383
384 } // namespace base
385