// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "util/mach/exception_ports.h"

#include "base/logging.h"
#include "base/mac/mach_logging.h"

namespace crashpad {

ExceptionPorts::ExceptionPorts(TargetType target_type, mach_port_t target_port)
    : target_port_(target_port), dealloc_target_port_(false) {
  switch (target_type) {
    case kTargetTypeHost:
      get_exception_ports_ = host_get_exception_ports;
      set_exception_ports_ = host_set_exception_ports;
      target_name_ = "host";
      if (target_port_ == HOST_NULL) {
        target_port_ = mach_host_self();
        dealloc_target_port_ = true;
      }
      break;

    case kTargetTypeTask:
      get_exception_ports_ = task_get_exception_ports;
      set_exception_ports_ = task_set_exception_ports;
      target_name_ = "task";
      if (target_port_ == TASK_NULL) {
        target_port_ = mach_task_self();
        // Don’t deallocate mach_task_self().
      }
      break;

    case kTargetTypeThread:
      get_exception_ports_ = thread_get_exception_ports;
      set_exception_ports_ = thread_set_exception_ports;
      target_name_ = "thread";
      if (target_port_ == THREAD_NULL) {
        target_port_ = mach_thread_self();
        dealloc_target_port_ = true;
      }
      break;

    default:
      NOTREACHED();
      get_exception_ports_ = nullptr;
      set_exception_ports_ = nullptr;
      target_name_ = nullptr;
      target_port_ = MACH_PORT_NULL;
      break;
  }
}

ExceptionPorts::~ExceptionPorts() {
  if (dealloc_target_port_) {
    kern_return_t kr = mach_port_deallocate(mach_task_self(), target_port_);
    MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "mach_port_deallocate";
  }
}

bool ExceptionPorts::GetExceptionPorts(
    exception_mask_t mask,
    std::vector<ExceptionHandler>* handlers) const {
  // <mach/mach_types.defs> says that these arrays have room for 32 elements,
  // despite EXC_TYPES_COUNT only being as low as 11 (in the 10.6 SDK), and
  // later operating system versions have defined more exception types. The
  // generated task_get_exception_ports() in taskUser.c expects there to be room
  // for 32.
  const int kMaxPorts = 32;

  // task_get_exception_ports() doesn’t actually use the initial value of
  // handler_count, but 10.9.4
  // xnu-2422.110.17/osfmk/man/task_get_exception_ports.html says it does. Humor
  // the documentation.
  mach_msg_type_number_t handler_count = kMaxPorts;

  exception_mask_t masks[kMaxPorts];
  exception_handler_t ports[kMaxPorts];
  exception_behavior_t behaviors[kMaxPorts];
  thread_state_flavor_t flavors[kMaxPorts];

  kern_return_t kr = get_exception_ports_(
      target_port_, mask, masks, &handler_count, ports, behaviors, flavors);
  if (kr != KERN_SUCCESS) {
    MACH_LOG(ERROR, kr) << TargetTypeName() << "_get_exception_ports";
    return false;
  }

  handlers->clear();
  for (mach_msg_type_number_t index = 0; index < handler_count; ++index) {
    if (ports[index] != MACH_PORT_NULL) {
      ExceptionHandler handler;
      handler.mask = masks[index];
      handler.port = ports[index];
      handler.behavior = behaviors[index];
      handler.flavor = flavors[index];
      handlers->push_back(handler);
    }
  }

  return true;
}

bool ExceptionPorts::SetExceptionPort(exception_mask_t mask,
                                      exception_handler_t port,
                                      exception_behavior_t behavior,
                                      thread_state_flavor_t flavor) const {
  kern_return_t kr =
      set_exception_ports_(target_port_, mask, port, behavior, flavor);
  if (kr != KERN_SUCCESS) {
    MACH_LOG(ERROR, kr) << TargetTypeName() << "_set_exception_ports";
    return false;
  }

  return true;
}

const char* ExceptionPorts::TargetTypeName() const {
  return target_name_;
}

}  // namespace crashpad