// 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.

#ifndef CRASHPAD_UTIL_MACH_EXCEPTION_PORTS_H_
#define CRASHPAD_UTIL_MACH_EXCEPTION_PORTS_H_

#include <mach/mach.h>

#include <vector>

#include "base/macros.h"

namespace crashpad {

//! \brief A better interface to `*_get_exception_ports()` and
//!     `*_set_exception_ports()`.
//!
//! The same generic interface can be used to operate on host, task, and thread
//! exception ports. The “get” interface is superior to the system’s native
//! interface because it keeps related data about a single exception handler
//! together in one struct, rather than separating it into four parallel arrays.
class ExceptionPorts {
 public:
  //! \brief Various entities which can have their own exception ports set.
  enum TargetType {
    //! \brief The host exception target.
    //!
    //! `host_get_exception_ports()` and `host_set_exception_ports()` will be
    //! used. If no target port is explicitly provided, `mach_host_self()` will
    //! be used as the target port. `mach_host_self()` is the only target port
    //! for this type that is expected to function properly.
    //!
    //! \note Operations on this target type are not expected to succeed as
    //!     non-root, because `mach_host_self()` doesn’t return the privileged
    //!     `host_priv` port to non-root users, and this is the target port
    //!     that’s required for `host_get_exception_ports()` and
    //!     `host_set_exception_ports()`.
    kTargetTypeHost = 0,

    //! \brief A task exception target.
    //!
    //! `task_get_exception_ports()` and `task_set_exception_ports()` will be
    //! used. If no target port is explicitly provided, `mach_task_self()` will
    //! be used as the target port.
    kTargetTypeTask,

    //! \brief A thread exception target.
    //!
    //! `thread_get_exception_ports()` and `thread_set_exception_ports()` will
    //! be used. If no target port is explicitly provided, `mach_thread_self()`
    //! will be used as the target port.
    kTargetTypeThread,
  };

  //! \brief Information about a registered exception handler.
  struct ExceptionHandler {
    //! \brief A mask specifying the exception types to direct to \a port,
    //!     containing `EXC_MASK_*` values.
    exception_mask_t mask;

    //! \brief A send right to a Mach port that will handle exceptions of the
    //!     types indicated in \a mask.
    exception_handler_t port;

    //! \brief The “behavior” that the exception handler at \a port implements:
    //!     `EXCEPTION_DEFAULT`, `EXCEPTION_STATE`, or
    //!     `EXCEPTION_STATE_IDENTITY`, possibly combined with
    //!     `MACH_EXCEPTION_CODES`.
    exception_behavior_t behavior;

    //! \brief The thread state flavor that the exception handler at \a port
    //!     will receive and possibly modify. This member has no effect for \a
    //!     \a behavior values that indicate a “default” behavior.
    thread_state_flavor_t flavor;
  };

  //! \brief Wraps `std::vector<ExceptionHandler>`, providing proper cleanup of
  //!     the send rights contained in each element’s ExceptionHandler::port.
  //!
  //! Upon destruction or clear(), an object of this class will deallocate all
  //! send rights it contains. Otherwise, it is an interface-compatible drop-in
  //! replacement for `std::vector<ExceptionHandler>`. Note that non-`const`
  //! mutators are not provided to avoid accidental Mach right leaks.
  class ExceptionHandlerVector {
   public:
    using VectorType = std::vector<ExceptionHandler>;

    ExceptionHandlerVector();
    ~ExceptionHandlerVector();

    VectorType::const_iterator begin() const { return vector_.begin(); }
    VectorType::const_iterator end() const { return vector_.end(); }
    VectorType::size_type size() const { return vector_.size(); }
    bool empty() const { return vector_.empty(); }
    VectorType::const_reference operator[](VectorType::size_type index) const {
      return vector_[index];
    }
    void push_back(VectorType::value_type& value) { vector_.push_back(value); }
    void clear();

   private:
    void Deallocate();

    VectorType vector_;

    DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerVector);
  };

  //! \brief Constructs an interface object to get or set exception ports on a
  //!     host, task, or thread port.
  //!
  //! \param[in] target_type The type of target on which the exception ports are
  //!     to be get or set: #kTargetTypeHost, #kTargetTypeTask, or or
  //!     #kTargetTypeThread. The correct functions for
  //!     `*_get_exception_ports()` and `*_set_exception_ports()` will be used.
  //! \param[in] target_port The target on which to call
  //!     `*_get_exception_ports()` or `*_set_exception_ports()`. The target
  //!     port must be a send right to a port of the type specified in \a
  //!     target_type. In this case, ownership of \a target_port is not given to
  //!     the new ExceptionPorts object. \a target_port may also be
  //!     `HOST_NULL`, `TASK_NULL`, or `THREAD_NULL`, in which case
  //!     `mach_host_self()`, `mach_task_self()`, or `mach_thread_self()` will
  //!     be used as the target port depending on the value of \a target_type.
  //!     In this case, ownership of the target port will be managed
  //!     appropriately for \a target_type.
  ExceptionPorts(TargetType target_type, mach_port_t target_port);

  ~ExceptionPorts();

  //! \brief Calls `*_get_exception_ports()` on the target.
  //!
  //! \param[in] mask The exception mask, containing the `EXC_MASK_*` values to
  //!     be looked up and returned in \a handlers.
  //! \param[out] handlers The exception handlers registered for \a target_port
  //!     to handle exceptions indicated in \a mask. If no execption port is
  //!     registered for a bit in \a mask, \a handlers will not contain an entry
  //!     corresponding to that bit. This is a departure from the
  //!     `*_get_exception_ports()` functions, which may return a handler whose
  //!     port is set to `EXCEPTION_PORT_NULL` in this case. On failure, this
  //!     argument is untouched.
  //!
  //! \return `true` if `*_get_exception_ports()` returned `KERN_SUCCESS`, with
  //!     \a handlers set appropriately. `false` otherwise, with an appropriate
  //!     message logged.
  bool GetExceptionPorts(exception_mask_t mask,
                         ExceptionHandlerVector* handlers) const;

  //! \brief Calls `*_set_exception_ports()` on the target.
  //!
  //! \param[in] mask A mask specifying the exception types to direct to \a
  //!     port, containing `EXC_MASK_*` values.
  //! \param[in] port A send right to a Mach port that will handle exceptions
  //!     sustained by \a target_port of the types indicated in \a mask. The
  //!     send right is copied, not consumed, by this call.
  //! \param[in] behavior The “behavior” that the exception handler at \a port
  //!     implements: `EXCEPTION_DEFAULT`, `EXCEPTION_STATE`, or
  //!     `EXCEPTION_STATE_IDENTITY`, possibly combined with
  //!     `MACH_EXCEPTION_CODES`.
  //! \param[in] flavor The thread state flavor that the exception handler at \a
  //!     port expects to receive and possibly modify. This argument has no
  //!     effect for \a behavior values that indicate a “default” behavior.
  //!
  //! \return `true` if `*_set_exception_ports()` returned `KERN_SUCCESS`.
  //!     `false` otherwise, with an appropriate message logged.
  bool SetExceptionPort(exception_mask_t mask,
                        exception_handler_t port,
                        exception_behavior_t behavior,
                        thread_state_flavor_t flavor) const;

  //! \brief Calls `*_swap_exception_ports()` on the target.
  //!
  //! \param[in] mask A mask specifying the exception types to direct to \a
  //!     port, containing `EXC_MASK_*` values.
  //! \param[in] new_port A send right to a Mach port that will handle
  //!     exceptions sustained by \a target_port of the types indicated in \a
  //!     mask. The send right is copied, not consumed, by this call.
  //! \param[in] new_behavior The “behavior” that the exception handler at \a
  //!     port implements: `EXCEPTION_DEFAULT`, `EXCEPTION_STATE`, or
  //!     `EXCEPTION_STATE_IDENTITY`, possibly combined with
  //!     `MACH_EXCEPTION_CODES`.
  //! \param[in] new_flavor The thread state flavor that the exception handler
  //!     at \a port expects to receive and possibly modify. This argument has
  //!     no effect for \a new_behavior values that indicate a “default”
  //!     behavior.
  //! \param[out] old_handlers The exception handlers registered for \a
  //!     target_port to handle exceptions indicated in \a mask. If no execption
  //!     port is registered for a bit in \a mask, \a old_handlers will not
  //!     contain an entry corresponding to that bit. This is a departure from
  //!     the `*_get_exception_ports()` functions, which may return a handler
  //!     whose port is set to `EXCEPTION_PORT_NULL` in this case. On failure,
  //!     this argument is untouched.
  //!
  //! \return `true` if `*_swap_exception_ports()` returned `KERN_SUCCESS`, with
  //!     \a old_handlers set appropriately. . `false` otherwise, with an
  //!     appropriate message logged.
  bool SwapExceptionPorts(exception_mask_t mask,
                          exception_handler_t new_port,
                          exception_behavior_t new_behavior,
                          thread_state_flavor_t new_flavor,
                          ExceptionHandlerVector* old_handlers) const;

  //! \brief Returns a string identifying the target type.
  //!
  //! \return `"host"`, `"task"`, or `"thread"`, as appropriate.
  const char* TargetTypeName() const;

 private:
  using GetExceptionPortsType = kern_return_t(*)(mach_port_t,
                                                 exception_mask_t,
                                                 exception_mask_array_t,
                                                 mach_msg_type_number_t*,
                                                 exception_handler_array_t,
                                                 exception_behavior_array_t,
                                                 exception_flavor_array_t);

  using SetExceptionPortsType = kern_return_t(*)(mach_port_t,
                                                 exception_mask_t,
                                                 exception_handler_t,
                                                 exception_behavior_t,
                                                 thread_state_flavor_t);

  using SwapExceptionPortsType = kern_return_t(*)(mach_port_t,
                                                  exception_mask_t,
                                                  exception_handler_t,
                                                  exception_behavior_t,
                                                  thread_state_flavor_t,
                                                  exception_mask_array_t,
                                                  mach_msg_type_number_t*,
                                                  exception_handler_array_t,
                                                  exception_behavior_array_t,
                                                  exception_flavor_array_t);

  GetExceptionPortsType get_exception_ports_;
  SetExceptionPortsType set_exception_ports_;
  SwapExceptionPortsType swap_exception_ports_;
  const char* target_name_;
  mach_port_t target_port_;

  // If true, target_port_ will be deallocated in the destructor. This will
  // always be false when the user provides a non-null target_port to the
  // constructor. It will also be false when target_type is kTargetTypeTask,
  // even with a TASK_NULL target_port, because it is incorrect to deallocate
  // the result of mach_task_self().
  bool dealloc_target_port_;

  DISALLOW_COPY_AND_ASSIGN(ExceptionPorts);
};

}  // namespace crashpad

#endif  // CRASHPAD_UTIL_MACH_EXCEPTION_PORTS_H_
