diff options
-rw-r--r-- | base/mac/scoped_mach_port.cc | 5 | ||||
-rw-r--r-- | base/mac/scoped_mach_port.h | 2 | ||||
-rw-r--r-- | base/process_util.h | 40 | ||||
-rw-r--r-- | base/process_util_posix.cc | 93 | ||||
-rw-r--r-- | content/app/content_main_runner.cc | 28 | ||||
-rw-r--r-- | content/browser/child_process_launcher.cc | 36 | ||||
-rw-r--r-- | content/browser/mach_broker_mac.cc | 232 | ||||
-rw-r--r-- | content/browser/mach_broker_mac.h | 55 | ||||
-rw-r--r-- | content/browser/mach_broker_mac.mm | 309 | ||||
-rw-r--r-- | content/browser/mach_broker_mac_unittest.cc | 16 | ||||
-rw-r--r-- | content/content_browser.gypi | 7 |
11 files changed, 447 insertions, 376 deletions
diff --git a/base/mac/scoped_mach_port.cc b/base/mac/scoped_mach_port.cc index 9e45a85..652e3f4 100644 --- a/base/mac/scoped_mach_port.cc +++ b/base/mac/scoped_mach_port.cc @@ -11,14 +11,9 @@ ScopedMachPort::ScopedMachPort(mach_port_t port) : port_(port) { } ScopedMachPort::~ScopedMachPort() { - reset(); -} - -void ScopedMachPort::reset(mach_port_t port) { if (port_ != MACH_PORT_NULL) { mach_port_deallocate(mach_task_self(), port_); } - port_ = port; } } // namespace mac diff --git a/base/mac/scoped_mach_port.h b/base/mac/scoped_mach_port.h index cc2ef20..d2aa2f7 100644 --- a/base/mac/scoped_mach_port.h +++ b/base/mac/scoped_mach_port.h @@ -22,8 +22,6 @@ class BASE_EXPORT ScopedMachPort { ~ScopedMachPort(); - void reset(mach_port_t port = MACH_PORT_NULL); - operator mach_port_t() const { return port_; } diff --git a/base/process_util.h b/base/process_util.h index a06f689..54d8533 100644 --- a/base/process_util.h +++ b/base/process_util.h @@ -232,6 +232,13 @@ BASE_EXPORT void CloseSuperfluousFds(const InjectiveMultimap& saved_map); typedef std::vector<std::pair<std::string, std::string> > EnvironmentVector; typedef std::vector<std::pair<int, int> > FileHandleMappingVector; +#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 { @@ -259,6 +266,9 @@ struct LaunchOptions { #if defined(OS_CHROMEOS) , ctrl_terminal_fd(-1) #endif // OS_CHROMEOS +#if defined(OS_MACOSX) + , synchronize(NULL) +#endif // defined(OS_MACOSX) #endif // !defined(OS_WIN) {} @@ -340,6 +350,27 @@ struct LaunchOptions { int ctrl_terminal_fd; #endif // defined(OS_CHROMEOS) +#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) }; @@ -411,6 +442,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 EnvironmentVector& 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) #if defined(OS_WIN) diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc index 98ec183..52e964b 100644 --- a/base/process_util_posix.cc +++ b/base/process_util_posix.cc @@ -604,6 +604,15 @@ bool LaunchProcess(const std::vector<std::string>& argv, 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); @@ -614,6 +623,34 @@ bool LaunchProcess(const std::vector<std::string>& argv, if (options.environ) new_environ.reset(AlterEnvironment(*options.environ, GetEnvironment())); +#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. + DCHECK(!options.wait); + + // Create the pipe used for synchronization. + if (HANDLE_EINTR(pipe(synchronization_pipe_fds)) != 0) { + DPLOG(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) { @@ -706,6 +743,13 @@ bool LaunchProcess(const std::vector<std::string>& argv, RAW_LOG(INFO, "Right after signal/exception handler restoration."); } +#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. @@ -745,6 +789,16 @@ bool LaunchProcess(const std::vector<std::string>& argv, RAW_LOG(INFO, "Right after fd_shuffle push_backs."); } +#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()); @@ -762,6 +816,24 @@ bool LaunchProcess(const std::vector<std::string>& argv, RAW_LOG(INFO, "Right after CloseSuperfluousFds"); } +#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) + if (options.debug) { RAW_LOG(INFO, "Right before execvp"); } @@ -786,6 +858,14 @@ 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; @@ -798,6 +878,19 @@ bool LaunchProcess(const CommandLine& cmdline, 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) { + DPLOG(ERROR) << "write"; + } +} +#endif // defined(OS_MACOSX) + ProcessMetrics::~ProcessMetrics() { } void RaiseProcessToHighPriority() { diff --git a/content/app/content_main_runner.cc b/content/app/content_main_runner.cc index 257f701..256f78f 100644 --- a/content/app/content_main_runner.cc +++ b/content/app/content_main_runner.cc @@ -180,6 +180,32 @@ base::LazyInstance<ContentUtilityClient> static CAppModule _Module; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + +// Completes the Mach IPC handshake by sending this process' task port to the +// parent process. The parent is listening on the Mach port given by +// |GetMachPortName()|. The task port is used by the parent to get CPU/memory +// stats to display in the task manager. +void SendTaskPortToParentProcess() { + const mach_msg_timeout_t kTimeoutMs = 100; + const int32_t kMessageId = 0; + std::string mach_port_name = MachBroker::GetMachPortName(); + + base::MachSendMessage child_message(kMessageId); + if (!child_message.AddDescriptor(mach_task_self())) { + LOG(ERROR) << "child AddDescriptor(mach_task_self()) failed."; + return; + } + + base::MachPortSender child_sender(mach_port_name.c_str()); + kern_return_t err = child_sender.SendMessage(child_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + LOG(ERROR) << + base::StringPrintf("child SendMessage() failed: 0x%x %s", err, + mach_error_string(err)); + } +} + #endif // defined(OS_WIN) #if defined(OS_POSIX) && !defined(OS_IOS) @@ -640,7 +666,7 @@ class ContentMainRunnerImpl : public ContentMainRunner { if (!process_type.empty() && (!delegate || delegate->ShouldSendMachPort(process_type))) { - MachBroker::ChildSendTaskPortToParent(); + SendTaskPortToParentProcess(); } #elif defined(OS_WIN) // This must be done early enough since some helper functions like diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc index bde7f93..66e7c83 100644 --- a/content/browser/child_process_launcher.cc +++ b/content/browser/child_process_launcher.cc @@ -266,30 +266,30 @@ class ChildProcessLauncher::Context options.fds_to_remap = &fds_to_map; #if defined(OS_MACOSX) - // Hold the MachBroker lock for the duration of LaunchProcess. The child - // will send its task port to the parent almost immediately after startup. - // The Mach message will be delivered to the parent, but updating the - // record of the launch will wait until after the placeholder PID is - // inserted below. This ensures that while the child process may send its - // port to the parent prior to the parent leaving LaunchProcess, the - // order in which the record in MachBroker is updated is correct. - MachBroker* broker = MachBroker::GetInstance(); - broker->GetLock().Acquire(); - - // Make sure the MachBroker is running, and inform it to expect a - // check-in from the new process. - broker->EnsureRunning(); + // 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) bool launched = base::LaunchProcess(*cmd_line, options, &handle); #if defined(OS_MACOSX) - if (launched) - broker->AddPlaceholderForPid(handle); + 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); + } - // After updating the broker, release the lock and let the child's - // messasge be processed on the broker's thread. - broker->GetLock().Release(); + // Now that the MachBroker is ready, the child may continue. + base::LaunchSynchronize(synchronization_handle); + } #endif // defined(OS_MACOSX) if (!launched) diff --git a/content/browser/mach_broker_mac.cc b/content/browser/mach_broker_mac.cc new file mode 100644 index 0000000..b255635 --- /dev/null +++ b/content/browser/mach_broker_mac.cc @@ -0,0 +1,232 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/mach_broker_mac.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mach_ipc_mac.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/threading/platform_thread.h" +#include "content/browser/renderer_host/render_process_host_impl.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/common/content_switches.h" + +namespace content { + +namespace { +// Prints a string representation of a Mach error code. +std::string MachErrorCode(kern_return_t err) { + return base::StringPrintf("0x%x %s", err, mach_error_string(err)); +} +} // namespace + +class MachListenerThreadDelegate : public base::PlatformThread::Delegate { + public: + MachListenerThreadDelegate(MachBroker* broker) : broker_(broker) { + DCHECK(broker_); + std::string port_name = MachBroker::GetMachPortName(); + + // Create the receive port in the constructor, not in ThreadMain(). It is + // important to create and register the receive port before starting the + // thread so that child processes will always have someone who's listening. + receive_port_.reset(new base::ReceivePort(port_name.c_str())); + } + + // Implement |PlatformThread::Delegate|. + virtual void ThreadMain() OVERRIDE { + base::MachReceiveMessage message; + kern_return_t err; + while ((err = receive_port_->WaitForMessage(&message, + MACH_MSG_TIMEOUT_NONE)) == + KERN_SUCCESS) { + // 0 was the secret message id. Reject any messages that don't have it. + if (message.GetMessageID() != 0) { + LOG(ERROR) << "Received message with incorrect id: " + << message.GetMessageID(); + continue; + } + + const task_t child_task = message.GetTranslatedPort(0); + if (child_task == MACH_PORT_NULL) { + LOG(ERROR) << "parent GetTranslatedPort(0) failed."; + continue; + } + + // It is possible for the child process to die after the call to + // |pid_for_task()| but before the call to |FinalizePid()|. To prevent + // leaking MachBroker map entries in this case, lock around both these + // calls. If the child dies, the death notification will be processed + // after the call to FinalizePid(), ensuring proper cleanup. + base::AutoLock lock(broker_->GetLock()); + + int pid; + err = pid_for_task(child_task, &pid); + if (err == KERN_SUCCESS) { + broker_->FinalizePid(pid, + MachBroker::MachInfo().SetTask(child_task)); + } else { + LOG(ERROR) << "Error getting pid for task " << child_task + << ": " << MachErrorCode(err); + } + } + + LOG(ERROR) << "Mach listener thread exiting; " + << "parent WaitForMessage() likely failed: " + << MachErrorCode(err); + } + + private: + // The Mach port to listen on. Created on thread startup. + scoped_ptr<base::ReceivePort> receive_port_; + + // The MachBroker to use when new child task rights are received. Can be + // NULL. + MachBroker* broker_; // weak + + DISALLOW_COPY_AND_ASSIGN(MachListenerThreadDelegate); +}; + +// Returns the global MachBroker. +MachBroker* MachBroker::GetInstance() { + return Singleton<MachBroker, LeakySingletonTraits<MachBroker> >::get(); +} + +void MachBroker::EnsureRunning() { + lock_.AssertAcquired(); + + if (!listener_thread_started_) { + listener_thread_started_ = true; + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&MachBroker::RegisterNotifications, base::Unretained(this))); + + // Intentional leak. This thread is never joined or reaped. + base::PlatformThread::CreateNonJoinable( + 0, new MachListenerThreadDelegate(this)); + } +} + +// Adds a placeholder to the map for the given pid with MACH_PORT_NULL. +void MachBroker::AddPlaceholderForPid(base::ProcessHandle pid) { + lock_.AssertAcquired(); + + MachInfo mach_info; + DCHECK_EQ(0u, mach_map_.count(pid)); + mach_map_[pid] = mach_info; +} + +// Updates the mapping for |pid| to include the given |mach_info|. +void MachBroker::FinalizePid(base::ProcessHandle pid, + const MachInfo& mach_info) { + lock_.AssertAcquired(); + + const int count = mach_map_.count(pid); + if (count == 0) { + // Do nothing for unknown pids. + LOG(ERROR) << "Unknown process " << pid << " is sending Mach IPC messages!"; + return; + } + + DCHECK_EQ(1, count); + DCHECK(mach_map_[pid].mach_task_ == MACH_PORT_NULL); + if (mach_map_[pid].mach_task_ == MACH_PORT_NULL) + mach_map_[pid] = mach_info; +} + +// Removes all mappings belonging to |pid| from the broker. +void MachBroker::InvalidatePid(base::ProcessHandle pid) { + base::AutoLock lock(lock_); + MachBroker::MachMap::iterator it = mach_map_.find(pid); + if (it == mach_map_.end()) + return; + + kern_return_t kr = mach_port_deallocate(mach_task_self(), + it->second.mach_task_); + LOG_IF(WARNING, kr != KERN_SUCCESS) + << "Failed to mach_port_deallocate mach task " << it->second.mach_task_ + << ", error " << MachErrorCode(kr); + mach_map_.erase(it); +} + +base::Lock& MachBroker::GetLock() { + return lock_; +} + +// Returns the mach task belonging to |pid|. +mach_port_t MachBroker::TaskForPid(base::ProcessHandle pid) const { + base::AutoLock lock(lock_); + MachBroker::MachMap::const_iterator it = mach_map_.find(pid); + if (it == mach_map_.end()) + return MACH_PORT_NULL; + return it->second.mach_task_; +} + +void MachBroker::BrowserChildProcessHostDisconnected( + const ChildProcessData& data) { + InvalidatePid(data.handle); +} + +void MachBroker::BrowserChildProcessCrashed(const ChildProcessData& data) { + InvalidatePid(data.handle); +} + +void MachBroker::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + // TODO(rohitrao): These notifications do not always carry the proper PIDs, + // especially when the renderer is already gone or has crashed. Find a better + // way to listen for child process deaths. http://crbug.com/55734 + base::ProcessHandle handle = 0; + switch (type) { + case NOTIFICATION_RENDERER_PROCESS_CLOSED: + handle = Details<RenderProcessHost::RendererClosedDetails>( + details)->handle; + break; + case NOTIFICATION_RENDERER_PROCESS_TERMINATED: + handle = Source<RenderProcessHost>(source)->GetHandle(); + break; + default: + NOTREACHED() << "Unexpected notification"; + break; + } + InvalidatePid(handle); +} + +// static +std::string MachBroker::GetMachPortName() { + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + const bool is_child = command_line->HasSwitch(switches::kProcessType); + + // In non-browser (child) processes, use the parent's pid. + const pid_t pid = is_child ? getppid() : getpid(); + return base::StringPrintf("%s.rohitfork.%d", base::mac::BaseBundleID(), pid); +} + +MachBroker::MachBroker() : listener_thread_started_(false) { +} + +MachBroker::~MachBroker() {} + +void MachBroker::RegisterNotifications() { + registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED, + NotificationService::AllBrowserContextsAndSources()); + registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED, + NotificationService::AllBrowserContextsAndSources()); + + // No corresponding StopObservingBrowserChildProcesses, + // we leak this singleton. + BrowserChildProcessObserver::Add(this); +} + +} // namespace content diff --git a/content/browser/mach_broker_mac.h b/content/browser/mach_broker_mac.h index e38e86f..011df04 100644 --- a/content/browser/mach_broker_mac.h +++ b/content/browser/mach_broker_mac.h @@ -38,31 +38,49 @@ class CONTENT_EXPORT MachBroker : public base::ProcessMetrics::PortProvider, public BrowserChildProcessObserver, public NotificationObserver { public: - // For use in child processes. This will send the task port of the current - // process over Mach IPC to the port registered by name (via this class) in - // the parent process. Returns true if the message was sent successfully - // and false if otherwise. - static bool ChildSendTaskPortToParent(); - // Returns the global MachBroker. static MachBroker* GetInstance(); - // The lock that protects this MachBroker object. Clients MUST acquire and - // release this lock around calls to EnsureRunning(), PlaceholderForPid(), - // and FinalizePid(). - base::Lock& GetLock(); - // Performs any necessary setup that cannot happen in the constructor. // 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) {} + + MachInfo& SetTask(mach_port_t task) { + mach_task_ = task; + return *this; + } + + mach_port_t mach_task_; + }; + // Adds a placeholder to the map for the given pid with MACH_PORT_NULL. // Callers are expected to later update the port with FinalizePid(). Callers // MUST acquire the lock given by GetLock() before calling this method (and // release the lock afterwards). void AddPlaceholderForPid(base::ProcessHandle pid); + // Updates the mapping for |pid| to include the given |mach_info|. Does + // nothing if PlaceholderForPid() has not already been called for the given + // |pid|. Callers MUST acquire the lock given by GetLock() before calling + // this method (and release the lock afterwards). + void FinalizePid(base::ProcessHandle pid, const MachInfo& mach_info); + + // Removes all mappings belonging to |pid| from the broker. + void InvalidatePid(base::ProcessHandle pid); + + // The lock that protects this MachBroker object. Clients MUST acquire and + // 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. + // Does the Right Thing in the browser and in child processes. + static std::string GetMachPortName(); + // Implement |ProcessMetrics::PortProvider|. virtual mach_port_t TaskForPid(base::ProcessHandle process) const OVERRIDE; @@ -78,24 +96,11 @@ class CONTENT_EXPORT MachBroker : public base::ProcessMetrics::PortProvider, const NotificationDetails& details) OVERRIDE; private: friend class MachBrokerTest; - friend class MachListenerThreadDelegate; friend struct DefaultSingletonTraits<MachBroker>; MachBroker(); virtual ~MachBroker(); - // Updates the mapping for |pid| to include the given |mach_info|. Does - // nothing if PlaceholderForPid() has not already been called for the given - // |pid|. Callers MUST acquire the lock given by GetLock() before calling - // this method (and release the lock afterwards). - void FinalizePid(base::ProcessHandle pid, mach_port_t task_port); - - // Removes all mappings belonging to |pid| from the broker. - void InvalidatePid(base::ProcessHandle pid); - - // Returns the Mach port name to use when sending or receiving messages. - // Does the Right Thing in the browser and in child processes. - static std::string GetMachPortName(); // Callback used to register notifications on the UI thread. void RegisterNotifications(); @@ -107,7 +112,7 @@ class CONTENT_EXPORT MachBroker : public base::ProcessMetrics::PortProvider, NotificationRegistrar registrar_; // Stores mach info for every process in the broker. - typedef std::map<base::ProcessHandle, mach_port_t> MachMap; + typedef std::map<base::ProcessHandle, MachInfo> MachMap; MachMap mach_map_; // Mutex that guards |mach_map_|. diff --git a/content/browser/mach_broker_mac.mm b/content/browser/mach_broker_mac.mm deleted file mode 100644 index d765cf5..0000000 --- a/content/browser/mach_broker_mac.mm +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/browser/mach_broker_mac.h" - -#include <bsm/libbsm.h> - -#include "base/bind.h" -#include "base/bind_helpers.h" -#include "base/command_line.h" -#include "base/logging.h" -#include "base/mac/foundation_util.h" -#include "base/mac/scoped_mach_port.h" -#include "base/mach_ipc_mac.h" -#include "base/string_util.h" -#include "base/stringprintf.h" -#include "base/strings/sys_string_conversions.h" -#include "base/threading/platform_thread.h" -#include "content/browser/renderer_host/render_process_host_impl.h" -#include "content/public/browser/child_process_data.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/notification_service.h" -#include "content/public/browser/notification_types.h" -#include "content/public/common/content_switches.h" - -namespace content { - -namespace { - -// Prints a string representation of a Mach error code. -std::string MachErrorCode(kern_return_t err) { - return base::StringPrintf("0x%x %s", err, mach_error_string(err)); -} - -// Mach message structure used in the child as a sending message. -struct MachBroker_ChildSendMsg { - mach_msg_header_t header; - mach_msg_body_t body; - mach_msg_port_descriptor_t child_task_port; -}; - -// Complement to the ChildSendMsg, this is used in the parent for receiving -// a message. Contains a message trailer with audit information. -struct MachBroker_ParentRecvMsg : public MachBroker_ChildSendMsg { - mach_msg_audit_trailer_t trailer; -}; - -} // namespace - -class MachListenerThreadDelegate : public base::PlatformThread::Delegate { - public: - explicit MachListenerThreadDelegate(MachBroker* broker) - : broker_(broker), - server_port_(MACH_PORT_NULL) { - DCHECK(broker_); - } - - bool Init() { - DCHECK(server_port_ == MACH_PORT_NULL); - - mach_port_t port; - kern_return_t kr = mach_port_allocate(mach_task_self(), - MACH_PORT_RIGHT_RECEIVE, - &port); - if (kr != KERN_SUCCESS) { - LOG(ERROR) << "Failed to allocate MachBroker server port: " - << MachErrorCode(kr); - return false; - } - - // Allocate a send right for the server port. - kr = mach_port_insert_right( - mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); - if (kr != KERN_SUCCESS) { - LOG(ERROR) << "Failed to insert send right for MachBroker server port: " - << MachErrorCode(kr); - return false; - } - - server_port_.reset(port); - - // Register the port with the bootstrap server. Because bootstrap_register - // is deprecated, this has to be wraped in an ObjC interface. - NSPort* ns_port = [NSMachPort portWithMachPort:port - options:NSMachPortDeallocateNone]; - NSString* name = base::SysUTF8ToNSString(broker_->GetMachPortName()); - return [[NSMachBootstrapServer sharedInstance] registerPort:ns_port - name:name]; - } - - // Implement |PlatformThread::Delegate|. - virtual void ThreadMain() OVERRIDE { - MachBroker_ParentRecvMsg msg; - bzero(&msg, sizeof(msg)); - msg.header.msgh_size = sizeof(msg); - msg.header.msgh_local_port = server_port_.get(); - - kern_return_t kr; - do { - // Use the kernel audit information to make sure this message is from - // a task that this process spawned. The kernel audit token contains the - // unspoofable pid of the task that sent the message. - mach_msg_option_t options = MACH_RCV_MSG | - MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | - MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); - - kr = mach_msg(&msg.header, options, 0, sizeof(msg), server_port_, - MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); - if (kr == KERN_SUCCESS) { - // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). - pid_t child_pid; - audit_token_to_au32(msg.trailer.msgh_audit, - NULL, NULL, NULL, NULL, NULL, &child_pid, NULL, NULL); - - mach_port_t child_task_port = msg.child_task_port.name; - - // Take the lock and update the broker information. - base::AutoLock lock(broker_->GetLock()); - broker_->FinalizePid(child_pid, child_task_port); - } - } while (kr == KERN_SUCCESS); - - LOG(ERROR) << "MachBroker thread exiting; mach_msg() likely failed: " - << MachErrorCode(kr); - } - - private: - // The MachBroker to use when new child task rights are received. Can be - // NULL. - MachBroker* broker_; // weak - - base::mac::ScopedMachPort server_port_; - - DISALLOW_COPY_AND_ASSIGN(MachListenerThreadDelegate); -}; - -bool MachBroker::ChildSendTaskPortToParent() { - // Look up the named MachBroker port that's been registered with the - // bootstrap server. - mach_port_t bootstrap_port; - kern_return_t kr = task_get_bootstrap_port(mach_task_self(), &bootstrap_port); - if (kr != KERN_SUCCESS) { - LOG(ERROR) << "Failed to look up bootstrap port: " << MachErrorCode(kr); - return false; - } - - mach_port_t parent_port; - kr = bootstrap_look_up(bootstrap_port, - const_cast<char*>(GetMachPortName().c_str()), &parent_port); - if (kr != KERN_SUCCESS) { - LOG(ERROR) << "Failed to look up named parent port: " << MachErrorCode(kr); - return false; - } - - // Create the check in message. This will copy a send right on this process' - // (the child's) task port and send it to the parent. - MachBroker_ChildSendMsg msg; - bzero(&msg, sizeof(msg)); - msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND) | - MACH_MSGH_BITS_COMPLEX; - msg.header.msgh_remote_port = parent_port; - msg.header.msgh_size = sizeof(msg); - msg.body.msgh_descriptor_count = 1; - msg.child_task_port.name = mach_task_self(); - msg.child_task_port.disposition = MACH_MSG_TYPE_PORT_SEND; - msg.child_task_port.type = MACH_MSG_PORT_DESCRIPTOR; - - kr = mach_msg(&msg.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, sizeof(msg), - 0, MACH_PORT_NULL, 100 /*milliseconds*/, MACH_PORT_NULL); - if (kr != KERN_SUCCESS) { - LOG(ERROR) << "Failed to send task port to parent: " << MachErrorCode(kr); - return false; - } - - return true; -} - -MachBroker* MachBroker::GetInstance() { - return Singleton<MachBroker, LeakySingletonTraits<MachBroker> >::get(); -} - -base::Lock& MachBroker::GetLock() { - return lock_; -} - -void MachBroker::EnsureRunning() { - lock_.AssertAcquired(); - - if (!listener_thread_started_) { - listener_thread_started_ = true; - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&MachBroker::RegisterNotifications, base::Unretained(this))); - - // Intentional leak. This thread is never joined or reaped. - MachListenerThreadDelegate* thread = new MachListenerThreadDelegate(this); - if (thread->Init()) { - base::PlatformThread::CreateNonJoinable(0, thread); - } else { - LOG(ERROR) << "Failed to initialize the MachListenerThreadDelegate"; - } - } -} - -void MachBroker::AddPlaceholderForPid(base::ProcessHandle pid) { - lock_.AssertAcquired(); - - DCHECK_EQ(0u, mach_map_.count(pid)); - mach_map_[pid] = MACH_PORT_NULL; -} - -mach_port_t MachBroker::TaskForPid(base::ProcessHandle pid) const { - base::AutoLock lock(lock_); - MachBroker::MachMap::const_iterator it = mach_map_.find(pid); - if (it == mach_map_.end()) - return MACH_PORT_NULL; - return it->second; -} - -void MachBroker::BrowserChildProcessHostDisconnected( - const ChildProcessData& data) { - InvalidatePid(data.handle); -} - -void MachBroker::BrowserChildProcessCrashed(const ChildProcessData& data) { - InvalidatePid(data.handle); -} - -void MachBroker::Observe(int type, - const NotificationSource& source, - const NotificationDetails& details) { - // TODO(rohitrao): These notifications do not always carry the proper PIDs, - // especially when the renderer is already gone or has crashed. Find a better - // way to listen for child process deaths. http://crbug.com/55734 - base::ProcessHandle handle = 0; - switch (type) { - case NOTIFICATION_RENDERER_PROCESS_CLOSED: - handle = Details<RenderProcessHost::RendererClosedDetails>( - details)->handle; - break; - case NOTIFICATION_RENDERER_PROCESS_TERMINATED: - handle = Source<RenderProcessHost>(source)->GetHandle(); - break; - default: - NOTREACHED() << "Unexpected notification"; - break; - } - InvalidatePid(handle); -} - -MachBroker::MachBroker() : listener_thread_started_(false) { -} - -MachBroker::~MachBroker() {} - -void MachBroker::FinalizePid(base::ProcessHandle pid, - mach_port_t task_port) { - lock_.AssertAcquired(); - - MachMap::iterator it = mach_map_.find(pid); - if (it == mach_map_.end()) { - // Do nothing for unknown pids. - LOG(ERROR) << "Unknown process " << pid << " is sending Mach IPC messages!"; - return; - } - - DCHECK(it->second == MACH_PORT_NULL); - if (it->second == MACH_PORT_NULL) - it->second = task_port; -} - -void MachBroker::InvalidatePid(base::ProcessHandle pid) { - base::AutoLock lock(lock_); - MachBroker::MachMap::iterator it = mach_map_.find(pid); - if (it == mach_map_.end()) - return; - - kern_return_t kr = mach_port_deallocate(mach_task_self(), - it->second); - LOG_IF(WARNING, kr != KERN_SUCCESS) - << "Failed to mach_port_deallocate mach task " << it->second - << ", error " << MachErrorCode(kr); - mach_map_.erase(it); -} - -// static -std::string MachBroker::GetMachPortName() { - const CommandLine* command_line = CommandLine::ForCurrentProcess(); - const bool is_child = command_line->HasSwitch(switches::kProcessType); - - // In non-browser (child) processes, use the parent's pid. - const pid_t pid = is_child ? getppid() : getpid(); - return base::StringPrintf("%s.rohitfork.%d", base::mac::BaseBundleID(), pid); -} - -void MachBroker::RegisterNotifications() { - registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED, - NotificationService::AllBrowserContextsAndSources()); - registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED, - NotificationService::AllBrowserContextsAndSources()); - - // No corresponding StopObservingBrowserChildProcesses, - // we leak this singleton. - BrowserChildProcessObserver::Add(this); -} - -} // namespace content diff --git a/content/browser/mach_broker_mac_unittest.cc b/content/browser/mach_broker_mac_unittest.cc index a7eca4f..7bd88a5 100644 --- a/content/browser/mach_broker_mac_unittest.cc +++ b/content/browser/mach_broker_mac_unittest.cc @@ -17,15 +17,11 @@ class MachBrokerTest : public testing::Test { broker_.AddPlaceholderForPid(pid); } - void InvalidatePid(base::ProcessHandle pid) { - broker_.InvalidatePid(pid); - } - // Helper function to acquire/release locks and call |FinalizePid()|. void FinalizePid(base::ProcessHandle pid, - mach_port_t task_port) { + const MachBroker::MachInfo& mach_info) { base::AutoLock lock(broker_.GetLock()); - broker_.FinalizePid(pid, task_port); + broker_.FinalizePid(pid, mach_info); } protected: @@ -43,7 +39,7 @@ TEST_F(MachBrokerTest, AddPlaceholderAndFinalize) { EXPECT_EQ(0u, broker_.TaskForPid(1)); // Finalize PID 1. - FinalizePid(1, 100u); + FinalizePid(1, MachBroker::MachInfo().SetTask(100u)); EXPECT_EQ(100u, broker_.TaskForPid(1)); // Should be no entry for PID 2. @@ -52,16 +48,16 @@ TEST_F(MachBrokerTest, AddPlaceholderAndFinalize) { TEST_F(MachBrokerTest, Invalidate) { AddPlaceholderForPid(1); - FinalizePid(1, 100u); + FinalizePid(1, MachBroker::MachInfo().SetTask(100u)); EXPECT_EQ(100u, broker_.TaskForPid(1)); - InvalidatePid(1u); + broker_.InvalidatePid(1u); EXPECT_EQ(0u, broker_.TaskForPid(1)); } TEST_F(MachBrokerTest, FinalizeUnknownPid) { // Finalizing an entry for an unknown pid should not add it to the map. - FinalizePid(1u, 100u); + FinalizePid(1u, MachBroker::MachInfo().SetTask(100u)); EXPECT_EQ(0u, broker_.TaskForPid(1u)); } diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 7174603..017d291 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -587,7 +587,7 @@ 'browser/loader/throttling_resource_handler.h', 'browser/loader/transfer_navigation_resource_throttle.cc', 'browser/loader/transfer_navigation_resource_throttle.h', - 'browser/mach_broker_mac.mm', + 'browser/mach_broker_mac.cc', 'browser/mach_broker_mac.h', 'browser/media_devices_monitor.cc', 'browser/media/media_internals.cc', @@ -1201,11 +1201,6 @@ 'dependencies': [ '../third_party/sudden_motion_sensor/sudden_motion_sensor.gyp:sudden_motion_sensor', ], - 'link_settings': { - 'libraries': [ - '-lbsm', - ], - }, }], ['chromeos==1', { 'dependencies': [ |