xref: /aosp_15_r20/external/cronet/base/process/launch_mac.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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