// Copyright (c) 2009 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 "chrome/browser/mach_broker_mac.h" #include "base/command_line.h" #include "base/logging.h" #include "base/mach_ipc_mac.h" #include "base/platform_thread.h" #include "base/string_util.h" #include "base/sys_string_conversions.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/renderer_host/render_process_host.h" #include "chrome/common/child_process_info.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/notification_service.h" namespace { // Prints a string representation of a Mach error code. std::string MachErrorCode(kern_return_t err) { return StringPrintf("0x%x %s", err, mach_error_string(err)); } } // namespace // Required because notifications happen on the UI thread. class RegisterNotificationTask : public Task { public: RegisterNotificationTask( MachBroker* broker) : broker_(broker) { } virtual void Run() { broker_->registrar_.Add(broker_, NotificationType::RENDERER_PROCESS_CLOSED, NotificationService::AllSources()); broker_->registrar_.Add(broker_, NotificationType::RENDERER_PROCESS_TERMINATED, NotificationService::AllSources()); broker_->registrar_.Add(broker_, NotificationType::CHILD_PROCESS_CRASHED, NotificationService::AllSources()); broker_->registrar_.Add(broker_, NotificationType::CHILD_PROCESS_HOST_DISCONNECTED, NotificationService::AllSources()); broker_->registrar_.Add(broker_, NotificationType::EXTENSION_PROCESS_TERMINATED, NotificationService::AllSources()); } private: MachBroker* broker_; DISALLOW_COPY_AND_ASSIGN(RegisterNotificationTask); }; class MachListenerThreadDelegate : public 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|. void ThreadMain() { 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. 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 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::instance() { return Singleton >::get(); } MachBroker::MachBroker() : listener_thread_started_(false) { } void MachBroker::PrepareForFork() { if (!listener_thread_started_) { listener_thread_started_ = true; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, new RegisterNotificationTask(this)); // Intentional leak. This thread is never joined or reaped. 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) { 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); } Lock& MachBroker::GetLock() { return lock_; } // Returns the mach task belonging to |pid|. mach_port_t MachBroker::TaskForPid(base::ProcessHandle pid) const { 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::Observe(NotificationType 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.value) { case NotificationType::RENDERER_PROCESS_CLOSED: case NotificationType::RENDERER_PROCESS_TERMINATED: handle = Source(source)->GetHandle(); break; case NotificationType::EXTENSION_PROCESS_TERMINATED: handle = Details(details)->render_process_host()->GetHandle(); break; case NotificationType::CHILD_PROCESS_CRASHED: case NotificationType::CHILD_PROCESS_HOST_DISCONNECTED: handle = Details(details)->handle(); break; default: NOTREACHED() << "Unexpected notification"; break; } InvalidatePid(handle); } // static std::string MachBroker::GetMachPortName() { static const char kFormatString[] = #if defined(GOOGLE_CHROME_BUILD) "com.google.Chrome" #else "org.chromium.Chromium" #endif ".rohitfork.%d"; 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 StringPrintf(kFormatString, pid); }