summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/process_util.h58
-rw-r--r--base/process_util_posix.cc125
-rw-r--r--base/process_util_unittest.cc4
-rw-r--r--content/browser/child_process_launcher.cc50
-rw-r--r--content/browser/mach_broker_mac.cc4
-rw-r--r--content/browser/mach_broker_mac.h8
6 files changed, 204 insertions, 45 deletions
diff --git a/base/process_util.h b/base/process_util.h
index 66c63dd..34e9e91 100644
--- a/base/process_util.h
+++ b/base/process_util.h
@@ -200,6 +200,13 @@ BASE_EXPORT void CloseSuperfluousFds(const InjectiveMultimap& saved_map);
typedef std::vector<std::pair<std::string, std::string> > environment_vector;
typedef std::vector<std::pair<int, int> > file_handle_mapping_vector;
+#if defined(OS_MACOSX)
+// Used with LaunchOptions::synchronize and LaunchSynchronize, a
+// LaunchSynchronizationHandle is an opaque value that LaunchProcess will
+// create and set, and that LaunchSynchronize will consume and destroy.
+typedef int* LaunchSynchronizationHandle;
+#endif // defined(OS_MACOSX)
+
// Options for launching a subprocess that are passed to LaunchProcess().
// The default constructor constructs the object with default options.
struct LaunchOptions {
@@ -208,8 +215,13 @@ struct LaunchOptions {
start_hidden(false), inherit_handles(false), as_user(NULL),
empty_desktop_name(false)
#else
- environ(NULL), fds_to_remap(NULL), new_process_group(false),
- clone_flags(0)
+ environ(NULL), fds_to_remap(NULL), new_process_group(false)
+#if defined(OS_LINUX)
+ , clone_flags(0)
+#endif // OS_LINUX
+#if defined(OS_MACOSX)
+ , synchronize(NULL)
+#endif // defined(OS_MACOSX)
#endif // !defined(OS_WIN)
{}
@@ -251,9 +263,33 @@ struct LaunchOptions {
// will be the same as its pid.
bool new_process_group;
+#if defined(OS_LINUX)
// If non-zero, start the process using clone(), using flags as provided.
int clone_flags;
-#endif // !defined(OS_WIN)
+#endif // defined(OS_LINUX)
+
+#if defined(OS_MACOSX)
+ // When non-NULL, a new LaunchSynchronizationHandle will be created and
+ // stored in *synchronize whenever LaunchProcess returns true in the parent
+ // process. The child process will have been created (with fork) but will
+ // be waiting (before exec) for the parent to call LaunchSynchronize with
+ // this handle. Only when LaunchSynchronize is called will the child be
+ // permitted to continue execution and call exec. LaunchSynchronize
+ // destroys the handle created by LaunchProcess.
+ //
+ // When synchronize is non-NULL, the parent must call LaunchSynchronize
+ // whenever LaunchProcess returns true. No exceptions.
+ //
+ // Synchronization is useful when the parent process needs to guarantee that
+ // it can take some action (such as recording the newly-forked child's
+ // process ID) before the child does something (such as using its process ID
+ // to communicate with its parent).
+ //
+ // |synchronize| and |wait| must not both be set simultaneously.
+ LaunchSynchronizationHandle* synchronize;
+#endif // defined(OS_MACOSX)
+
+#endif // !defined(OS_WIN)
};
// Launch a process via the command line |cmdline|.
@@ -265,8 +301,11 @@ struct LaunchOptions {
// Otherwise, the process handle will be implicitly closed.
//
// Unix-specific notes:
-// - Before launching, all FDs open in the parent process will be marked as
-// close-on-exec.
+// - All file descriptors open in the parent process will be closed in the
+// child process except for any preserved by options::fds_to_remap, and
+// stdin, stdout, and stderr. If not remapped by options::fds_to_remap,
+// stdin is reopened as /dev/null, and the child is allowed to inherit its
+// parent's stdout and stderr.
// - If the first argument on the command line does not contain a slash,
// PATH will be searched. (See man execvp.)
BASE_EXPORT bool LaunchProcess(const CommandLine& cmdline,
@@ -319,6 +358,15 @@ BASE_EXPORT bool LaunchProcess(const std::vector<std::string>& argv,
// The returned array is allocated using new[] and must be freed by the caller.
BASE_EXPORT char** AlterEnvironment(const environment_vector& changes,
const char* const* const env);
+
+#if defined(OS_MACOSX)
+
+// After a successful call to LaunchProcess with LaunchOptions::synchronize
+// set, the parent process must call LaunchSynchronize to allow the child
+// process to proceed, and to destroy the LaunchSynchronizationHandle.
+BASE_EXPORT void LaunchSynchronize(LaunchSynchronizationHandle handle);
+
+#endif // defined(OS_MACOSX)
#endif // defined(OS_POSIX)
// Executes the application specified by |cl| and wait for it to exit. Stores
diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc
index 059b7d7..06fa779 100644
--- a/base/process_util_posix.cc
+++ b/base/process_util_posix.cc
@@ -531,32 +531,72 @@ char** AlterEnvironment(const environment_vector& changes,
bool LaunchProcess(const std::vector<std::string>& argv,
const LaunchOptions& options,
ProcessHandle* process_handle) {
- pid_t pid;
- InjectiveMultimap fd_shuffle1, fd_shuffle2;
+ size_t fd_shuffle_size = 0;
if (options.fds_to_remap) {
- fd_shuffle1.reserve(options.fds_to_remap->size());
- fd_shuffle2.reserve(options.fds_to_remap->size());
+ fd_shuffle_size = options.fds_to_remap->size();
}
+
+#if defined(OS_MACOSX)
+ if (options.synchronize) {
+ // When synchronizing, the "read" end of the synchronization pipe needs
+ // to make it to the child process. This is handled by mapping it back to
+ // itself.
+ ++fd_shuffle_size;
+ }
+#endif // defined(OS_MACOSX)
+
+ InjectiveMultimap fd_shuffle1;
+ InjectiveMultimap fd_shuffle2;
+ fd_shuffle1.reserve(fd_shuffle_size);
+ fd_shuffle2.reserve(fd_shuffle_size);
+
scoped_array<char*> argv_cstr(new char*[argv.size() + 1]);
scoped_array<char*> new_environ;
if (options.environ)
new_environ.reset(AlterEnvironment(*options.environ, GetEnvironment()));
- if (options.clone_flags) {
+#if defined(OS_MACOSX)
+ int synchronization_pipe_fds[2];
+ file_util::ScopedFD synchronization_read_fd;
+ file_util::ScopedFD synchronization_write_fd;
+
+ if (options.synchronize) {
+ // wait means "don't return from LaunchProcess until the child exits", and
+ // synchronize means "return from LaunchProcess but don't let the child
+ // run until LaunchSynchronize is called". These two options are highly
+ // incompatible.
+ CHECK(!options.wait);
+
+ // Create the pipe used for synchronization.
+ if (HANDLE_EINTR(pipe(synchronization_pipe_fds)) != 0) {
+ PLOG(ERROR) << "pipe";
+ return false;
+ }
+
+ // The parent process will only use synchronization_write_fd as the write
+ // side of the pipe. It can close the read side as soon as the child
+ // process has forked off. The child process will only use
+ // synchronization_read_fd as the read side of the pipe. In that process,
+ // the write side can be closed as soon as it has forked.
+ synchronization_read_fd.reset(&synchronization_pipe_fds[0]);
+ synchronization_write_fd.reset(&synchronization_pipe_fds[1]);
+ }
+#endif // OS_MACOSX
+
+ pid_t pid;
#if defined(OS_LINUX)
+ if (options.clone_flags) {
pid = syscall(__NR_clone, options.clone_flags, 0, 0, 0);
-#else
- pid = -1; // hygiene; prevent clang warnings
- NOTREACHED() << "Tried to use clone() on non-Linux system";
+ } else
#endif
- } else {
+ {
pid = fork();
}
+
if (pid < 0) {
PLOG(ERROR) << "fork";
return false;
- }
- if (pid == 0) {
+ } else if (pid == 0) {
// Child process
// DANGER: fork() rule: in the child, if you don't end up doing exec*(),
@@ -589,12 +629,20 @@ bool LaunchProcess(const std::vector<std::string>& argv,
_exit(127);
}
}
+
#if defined(OS_MACOSX)
RestoreDefaultExceptionHandler();
-#endif
+#endif // defined(OS_MACOSX)
ResetChildSignalHandlersToDefaults();
+#if defined(OS_MACOSX)
+ if (options.synchronize) {
+ // The "write" side of the synchronization pipe belongs to the parent.
+ synchronization_write_fd.reset(); // closes synchronization_pipe_fds[1]
+ }
+#endif // defined(OS_MACOSX)
+
#if 0
// When debugging it can be helpful to check that we really aren't making
// any hidden calls to malloc.
@@ -602,7 +650,7 @@ bool LaunchProcess(const std::vector<std::string>& argv,
reinterpret_cast<void*>(reinterpret_cast<intptr_t>(malloc) & ~4095);
mprotect(malloc_thunk, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
memset(reinterpret_cast<void*>(malloc), 0xff, 8);
-#endif
+#endif // 0
// DANGER: no calls to malloc are allowed from now on:
// http://crbug.com/36678
@@ -616,6 +664,16 @@ bool LaunchProcess(const std::vector<std::string>& argv,
}
}
+#if defined(OS_MACOSX)
+ if (options.synchronize) {
+ // Remap the read side of the synchronization pipe back onto itself,
+ // ensuring that it won't be closed by CloseSuperfluousFds.
+ int keep_fd = *synchronization_read_fd.get();
+ fd_shuffle1.push_back(InjectionArc(keep_fd, keep_fd, false));
+ fd_shuffle2.push_back(InjectionArc(keep_fd, keep_fd, false));
+ }
+#endif // defined(OS_MACOSX)
+
if (options.environ)
SetEnvironment(new_environ.get());
@@ -625,10 +683,29 @@ bool LaunchProcess(const std::vector<std::string>& argv,
CloseSuperfluousFds(fd_shuffle2);
+#if defined(OS_MACOSX)
+ if (options.synchronize) {
+ // Do a blocking read to wait until the parent says it's OK to proceed.
+ // The byte that's read here is written by LaunchSynchronize.
+ char read_char;
+ int read_result =
+ HANDLE_EINTR(read(*synchronization_read_fd.get(), &read_char, 1));
+ if (read_result != 1) {
+ RAW_LOG(ERROR, "LaunchProcess: synchronization read: error");
+ _exit(127);
+ }
+
+ // The pipe is no longer useful. Don't let it live on in the new process
+ // after exec.
+ synchronization_read_fd.reset(); // closes synchronization_pipe_fds[0]
+ }
+#endif // defined(OS_MACOSX)
+
for (size_t i = 0; i < argv.size(); i++)
argv_cstr[i] = const_cast<char*>(argv[i].c_str());
argv_cstr[argv.size()] = NULL;
execvp(argv_cstr[0], argv_cstr.get());
+
RAW_LOG(ERROR, "LaunchProcess: failed to execvp:");
RAW_LOG(ERROR, argv_cstr[0]);
_exit(127);
@@ -644,17 +721,39 @@ bool LaunchProcess(const std::vector<std::string>& argv,
if (process_handle)
*process_handle = pid;
+
+#if defined(OS_MACOSX)
+ if (options.synchronize) {
+ // The "read" side of the synchronization pipe belongs to the child.
+ synchronization_read_fd.reset(); // closes synchronization_pipe_fds[0]
+ *options.synchronize = new int(*synchronization_write_fd.release());
+ }
+#endif // defined(OS_MACOSX)
}
return true;
}
+
bool LaunchProcess(const CommandLine& cmdline,
const LaunchOptions& options,
ProcessHandle* process_handle) {
return LaunchProcess(cmdline.argv(), options, process_handle);
}
+#if defined(OS_MACOSX)
+void LaunchSynchronize(LaunchSynchronizationHandle handle) {
+ int synchronization_fd = *handle;
+ file_util::ScopedFD synchronization_fd_closer(&synchronization_fd);
+ delete handle;
+
+ // Write a '\0' character to the pipe.
+ if (HANDLE_EINTR(write(synchronization_fd, "", 1)) != 1) {
+ PLOG(ERROR) << "write";
+ }
+}
+#endif // defined(OS_MACOSX)
+
ProcessMetrics::~ProcessMetrics() { }
bool EnableInProcessStackDumping() {
diff --git a/base/process_util_unittest.cc b/base/process_util_unittest.cc
index bcc1308..0403d7a 100644
--- a/base/process_util_unittest.cc
+++ b/base/process_util_unittest.cc
@@ -538,7 +538,11 @@ std::string TestLaunchProcess(const base::environment_vector& env_changes,
options.wait = true;
options.environ = &env_changes;
options.fds_to_remap = &fds_to_remap;
+#if defined(OS_LINUX)
options.clone_flags = clone_flags;
+#else
+ CHECK_EQ(0, clone_flags);
+#endif // OS_LINUX
EXPECT_TRUE(base::LaunchProcess(args, options, NULL));
PCHECK(HANDLE_EINTR(close(fds[1])) == 0);
diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc
index 6150965..0892b66 100644
--- a/content/browser/child_process_launcher.cc
+++ b/content/browser/child_process_launcher.cc
@@ -156,34 +156,38 @@ class ChildProcessLauncher::Context
}
#endif // defined(OS_POSIX) && !defined(OS_MACOSX)
- bool launched = false;
-#if defined(OS_MACOSX)
- // It is possible for the child process to die immediately after
- // launching. To prevent leaking MachBroker map entries in this case,
- // lock around all of LaunchProcess(). If the child dies, the death
- // notification will be processed by the MachBroker after the call to
- // AddPlaceholderForPid(), enabling proper cleanup.
- { // begin scope for AutoLock
- MachBroker* broker = MachBroker::GetInstance();
- base::AutoLock lock(broker->GetLock());
+ // Actually launch the app.
+ base::LaunchOptions options;
+ options.environ = &env;
+ options.fds_to_remap = &fds_to_map;
- // This call to |PrepareForFork()| will start the MachBroker listener
- // thread, if it is not already running. Therefore the browser process
- // will be listening for Mach IPC before LaunchProcess() is called.
- broker->PrepareForFork();
-#endif
+#if defined(OS_MACOSX)
+ // Use synchronization to make sure that the MachBroker is ready to
+ // receive a check-in from the new process before the new process
+ // actually tries to check in.
+ base::LaunchSynchronizationHandle synchronization_handle;
+ options.synchronize = &synchronization_handle;
+#endif // defined(OS_MACOSX)
- // Actually launch the app.
- base::LaunchOptions options;
- options.environ = &env;
- options.fds_to_remap = &fds_to_map;
- launched = base::LaunchProcess(*cmd_line, options, &handle);
+ bool launched = base::LaunchProcess(*cmd_line, options, &handle);
#if defined(OS_MACOSX)
- if (launched)
+ if (launched) {
+ MachBroker* broker = MachBroker::GetInstance();
+ {
+ base::AutoLock lock(broker->GetLock());
+
+ // Make sure the MachBroker is running, and inform it to expect a
+ // check-in from the new process.
+ broker->EnsureRunning();
broker->AddPlaceholderForPid(handle);
- } // end scope for AutoLock
-#endif
+ }
+
+ // Now that the MachBroker is ready, the child may continue.
+ base::LaunchSynchronize(synchronization_handle);
+ }
+#endif // defined(OS_MACOSX)
+
if (!launched)
handle = base::kNullProcessHandle;
}
diff --git a/content/browser/mach_broker_mac.cc b/content/browser/mach_broker_mac.cc
index 86d1fcf..ad92a5a 100644
--- a/content/browser/mach_broker_mac.cc
+++ b/content/browser/mach_broker_mac.cc
@@ -128,7 +128,9 @@ MachBroker::MachBroker() : listener_thread_started_(false) {
MachBroker::~MachBroker() {}
-void MachBroker::PrepareForFork() {
+void MachBroker::EnsureRunning() {
+ lock_.AssertAcquired();
+
if (!listener_thread_started_) {
listener_thread_started_ = true;
diff --git a/content/browser/mach_broker_mac.h b/content/browser/mach_broker_mac.h
index 6efeab4..d78b0d7 100644
--- a/content/browser/mach_broker_mac.h
+++ b/content/browser/mach_broker_mac.h
@@ -39,8 +39,9 @@ class MachBroker : public base::ProcessMetrics::PortProvider,
static MachBroker* GetInstance();
// Performs any necessary setup that cannot happen in the constructor.
- // Clients MUST call this method before fork()ing any children.
- void PrepareForFork();
+ // Callers MUST acquire the lock given by GetLock() before calling this
+ // method (and release the lock afterwards).
+ void EnsureRunning();
struct MachInfo {
MachInfo() : mach_task_(MACH_PORT_NULL) {}
@@ -69,7 +70,8 @@ class MachBroker : public base::ProcessMetrics::PortProvider,
void InvalidatePid(base::ProcessHandle pid);
// The lock that protects this MachBroker object. Clients MUST acquire and
- // release this lock around calls to PlaceholderForPid() and FinalizePid().
+ // release this lock around calls to EnsureRunning(), PlaceholderForPid(),
+ // and FinalizePid().
base::Lock& GetLock();
// Returns the Mach port name to use when sending or receiving messages.