// 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/logging.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/notification_service.h"

// 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_;
};

MachBroker::MachBroker() {
  ChromeThread::PostTask(
      ChromeThread::UI, FROM_HERE, new RegisterNotificationTask(this));
}

// Returns the global MachBroker.
MachBroker* MachBroker::instance() {
  return Singleton<MachBroker>::get();
}

// Adds mach info for a given pid.
void MachBroker::RegisterPid(
    base::ProcessHandle pid, const MachInfo& mach_info) {
  AutoLock lock(lock_);
  DCHECK_EQ(0u, mach_map_.count(pid));
  mach_map_[pid] = mach_info;
}

// Removes all mappings belonging to |pid| from the broker.
void MachBroker::Invalidate(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 " << kr;
  mach_map_.erase(it);
}

// 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) {
  base::ProcessHandle handle = 0;
  switch (type.value) {
    case NotificationType::RENDERER_PROCESS_CLOSED:
    case NotificationType::RENDERER_PROCESS_TERMINATED:
      handle = Source<RenderProcessHost>(source)->GetHandle();
      break;
    case NotificationType::EXTENSION_PROCESS_TERMINATED:
      handle =
          Details<ExtensionHost>(details)->render_process_host()->GetHandle();
      break;
    case NotificationType::CHILD_PROCESS_CRASHED:
    case NotificationType::CHILD_PROCESS_HOST_DISCONNECTED:
      handle = Details<ChildProcessInfo>(details)->handle();
      break;
    default:
      NOTREACHED() << "Unexpected notification";
      break;
  }
  Invalidate(handle);
}