1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 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 "chrome/browser/mach_broker_mac.h" 6 7 #include "base/command_line.h" 8 #include "base/logging.h" 9 #include "base/mach_ipc_mac.h" 10 #include "base/string_util.h" 11 #include "base/sys_string_conversions.h" 12 #include "base/threading/platform_thread.h" 13 #include "chrome/browser/extensions/extension_host.h" 14 #include "chrome/common/chrome_switches.h" 15 #include "content/browser/browser_thread.h" 16 #include "content/browser/renderer_host/render_process_host.h" 17 #include "content/common/child_process_info.h" 18 #include "content/common/notification_service.h" 19 20 namespace { 21 // Prints a string representation of a Mach error code. 22 std::string MachErrorCode(kern_return_t err) { 23 return StringPrintf("0x%x %s", err, mach_error_string(err)); 24 } 25 } // namespace 26 27 // Required because notifications happen on the UI thread. 28 class RegisterNotificationTask : public Task { 29 public: 30 RegisterNotificationTask( 31 MachBroker* broker) 32 : broker_(broker) { } 33 34 virtual void Run() { 35 broker_->registrar_.Add(broker_, 36 NotificationType::RENDERER_PROCESS_CLOSED, 37 NotificationService::AllSources()); 38 broker_->registrar_.Add(broker_, 39 NotificationType::RENDERER_PROCESS_TERMINATED, 40 NotificationService::AllSources()); 41 broker_->registrar_.Add(broker_, 42 NotificationType::CHILD_PROCESS_CRASHED, 43 NotificationService::AllSources()); 44 broker_->registrar_.Add(broker_, 45 NotificationType::CHILD_PROCESS_HOST_DISCONNECTED, 46 NotificationService::AllSources()); 47 broker_->registrar_.Add(broker_, 48 NotificationType::EXTENSION_PROCESS_TERMINATED, 49 NotificationService::AllSources()); 50 } 51 52 private: 53 MachBroker* broker_; 54 DISALLOW_COPY_AND_ASSIGN(RegisterNotificationTask); 55 }; 56 57 class MachListenerThreadDelegate : public base::PlatformThread::Delegate { 58 public: 59 MachListenerThreadDelegate(MachBroker* broker) : broker_(broker) { 60 DCHECK(broker_); 61 std::string port_name = MachBroker::GetMachPortName(); 62 63 // Create the receive port in the constructor, not in ThreadMain(). It is 64 // important to create and register the receive port before starting the 65 // thread so that child processes will always have someone who's listening. 66 receive_port_.reset(new base::ReceivePort(port_name.c_str())); 67 } 68 69 // Implement |PlatformThread::Delegate|. 70 void ThreadMain() { 71 base::MachReceiveMessage message; 72 kern_return_t err; 73 while ((err = receive_port_->WaitForMessage(&message, 74 MACH_MSG_TIMEOUT_NONE)) == 75 KERN_SUCCESS) { 76 // 0 was the secret message id. Reject any messages that don't have it. 77 if (message.GetMessageID() != 0) { 78 LOG(ERROR) << "Received message with incorrect id: " 79 << message.GetMessageID(); 80 continue; 81 } 82 83 const task_t child_task = message.GetTranslatedPort(0); 84 if (child_task == MACH_PORT_NULL) { 85 LOG(ERROR) << "parent GetTranslatedPort(0) failed."; 86 continue; 87 } 88 89 // It is possible for the child process to die after the call to 90 // |pid_for_task()| but before the call to |FinalizePid()|. To prevent 91 // leaking MachBroker map entries in this case, lock around both these 92 // calls. If the child dies, the death notification will be processed 93 // after the call to FinalizePid(), ensuring proper cleanup. 94 base::AutoLock lock(broker_->GetLock()); 95 96 int pid; 97 err = pid_for_task(child_task, &pid); 98 if (err == KERN_SUCCESS) { 99 broker_->FinalizePid(pid, 100 MachBroker::MachInfo().SetTask(child_task)); 101 } else { 102 LOG(ERROR) << "Error getting pid for task " << child_task 103 << ": " << MachErrorCode(err); 104 } 105 } 106 107 LOG(ERROR) << "Mach listener thread exiting; " 108 << "parent WaitForMessage() likely failed: " 109 << MachErrorCode(err); 110 } 111 112 private: 113 // The Mach port to listen on. Created on thread startup. 114 scoped_ptr<base::ReceivePort> receive_port_; 115 116 // The MachBroker to use when new child task rights are received. Can be 117 // NULL. 118 MachBroker* broker_; // weak 119 120 DISALLOW_COPY_AND_ASSIGN(MachListenerThreadDelegate); 121 }; 122 123 // Returns the global MachBroker. 124 MachBroker* MachBroker::GetInstance() { 125 return Singleton<MachBroker, LeakySingletonTraits<MachBroker> >::get(); 126 } 127 128 MachBroker::MachBroker() : listener_thread_started_(false) { 129 } 130 131 MachBroker::~MachBroker() {} 132 133 void MachBroker::PrepareForFork() { 134 if (!listener_thread_started_) { 135 listener_thread_started_ = true; 136 137 BrowserThread::PostTask( 138 BrowserThread::UI, FROM_HERE, new RegisterNotificationTask(this)); 139 140 // Intentional leak. This thread is never joined or reaped. 141 base::PlatformThread::CreateNonJoinable( 142 0, new MachListenerThreadDelegate(this)); 143 } 144 } 145 146 // Adds a placeholder to the map for the given pid with MACH_PORT_NULL. 147 void MachBroker::AddPlaceholderForPid(base::ProcessHandle pid) { 148 lock_.AssertAcquired(); 149 150 MachInfo mach_info; 151 DCHECK_EQ(0u, mach_map_.count(pid)); 152 mach_map_[pid] = mach_info; 153 } 154 155 // Updates the mapping for |pid| to include the given |mach_info|. 156 void MachBroker::FinalizePid(base::ProcessHandle pid, 157 const MachInfo& mach_info) { 158 lock_.AssertAcquired(); 159 160 const int count = mach_map_.count(pid); 161 if (count == 0) { 162 // Do nothing for unknown pids. 163 LOG(ERROR) << "Unknown process " << pid << " is sending Mach IPC messages!"; 164 return; 165 } 166 167 DCHECK_EQ(1, count); 168 DCHECK(mach_map_[pid].mach_task_ == MACH_PORT_NULL); 169 if (mach_map_[pid].mach_task_ == MACH_PORT_NULL) 170 mach_map_[pid] = mach_info; 171 } 172 173 // Removes all mappings belonging to |pid| from the broker. 174 void MachBroker::InvalidatePid(base::ProcessHandle pid) { 175 base::AutoLock lock(lock_); 176 MachBroker::MachMap::iterator it = mach_map_.find(pid); 177 if (it == mach_map_.end()) 178 return; 179 180 kern_return_t kr = mach_port_deallocate(mach_task_self(), 181 it->second.mach_task_); 182 LOG_IF(WARNING, kr != KERN_SUCCESS) 183 << "Failed to mach_port_deallocate mach task " << it->second.mach_task_ 184 << ", error " << MachErrorCode(kr); 185 mach_map_.erase(it); 186 } 187 188 base::Lock& MachBroker::GetLock() { 189 return lock_; 190 } 191 192 // Returns the mach task belonging to |pid|. 193 mach_port_t MachBroker::TaskForPid(base::ProcessHandle pid) const { 194 base::AutoLock lock(lock_); 195 MachBroker::MachMap::const_iterator it = mach_map_.find(pid); 196 if (it == mach_map_.end()) 197 return MACH_PORT_NULL; 198 return it->second.mach_task_; 199 } 200 201 void MachBroker::Observe(NotificationType type, 202 const NotificationSource& source, 203 const NotificationDetails& details) { 204 // TODO(rohitrao): These notifications do not always carry the proper PIDs, 205 // especially when the renderer is already gone or has crashed. Find a better 206 // way to listen for child process deaths. http://crbug.com/55734 207 base::ProcessHandle handle = 0; 208 switch (type.value) { 209 case NotificationType::RENDERER_PROCESS_CLOSED: 210 case NotificationType::RENDERER_PROCESS_TERMINATED: 211 handle = Source<RenderProcessHost>(source)->GetHandle(); 212 break; 213 case NotificationType::EXTENSION_PROCESS_TERMINATED: 214 handle = 215 Details<ExtensionHost>(details)->render_process_host()->GetHandle(); 216 break; 217 case NotificationType::CHILD_PROCESS_CRASHED: 218 case NotificationType::CHILD_PROCESS_HOST_DISCONNECTED: 219 handle = Details<ChildProcessInfo>(details)->handle(); 220 break; 221 default: 222 NOTREACHED() << "Unexpected notification"; 223 break; 224 } 225 InvalidatePid(handle); 226 } 227 228 // static 229 std::string MachBroker::GetMachPortName() { 230 static const char kFormatString[] = 231 #if defined(GOOGLE_CHROME_BUILD) 232 "com.google.Chrome" 233 #else 234 "org.chromium.Chromium" 235 #endif 236 ".rohitfork.%d"; 237 238 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); 239 const bool is_child = command_line.HasSwitch(switches::kProcessType); 240 241 // In non-browser (child) processes, use the parent's pid. 242 const pid_t pid = is_child ? getppid() : getpid(); 243 return StringPrintf(kFormatString, pid); 244 } 245