blob: 060a728a28cf34474897e865bef7b87ee09b1f59 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "object/exceptionate.h"
#include <zircon/errors.h>
#include <zircon/syscalls/exception.h>
#include <object/exception_dispatcher.h>
#include <object/handle.h>
#include <object/message_packet.h>
#include <object/process_dispatcher.h>
#include <object/thread_dispatcher.h>
Exceptionate::Exceptionate(uint32_t type) : type_(type) {}
Exceptionate::~Exceptionate() { Shutdown(); }
zx_status_t Exceptionate::SetChannel(KernelHandle<ChannelDispatcher> channel_handle,
zx_rights_t thread_rights, zx_rights_t process_rights) {
if (!channel_handle.dispatcher()) {
return ZX_ERR_INVALID_ARGS;
}
Guard<Mutex> guard{&lock_};
if (is_shutdown_) {
return ZX_ERR_BAD_STATE;
}
if (HasValidChannelLocked()) {
return ZX_ERR_ALREADY_BOUND;
}
// At this point we're certain that either there is no channel or it's a
// dead channel with no peer (since channel endpoints can't re-open) so we
// can overwrite it.
channel_handle_ = ktl::move(channel_handle);
thread_rights_ = thread_rights;
process_rights_ = process_rights;
return ZX_OK;
}
void Exceptionate::Shutdown() {
Guard<Mutex> guard{&lock_};
channel_handle_.reset();
is_shutdown_ = true;
}
bool Exceptionate::HasValidChannel() const {
Guard<Mutex> guard{&lock_};
return HasValidChannelLocked();
}
bool Exceptionate::HasValidChannelLocked() const {
return channel_handle_.dispatcher() && !channel_handle_.dispatcher()->PeerHasClosed();
}
zx_status_t Exceptionate::SendException(const fbl::RefPtr<ExceptionDispatcher>& exception) {
DEBUG_ASSERT(exception);
Guard<Mutex> guard{&lock_};
if (!channel_handle_.dispatcher()) {
return ZX_ERR_NEXT;
}
zx_exception_info_t info{};
// Since info will be copied to a usermode process make sure it's safe to to be copied (no
// internal padding, trivially copyable, etc.).
static_assert(internal::is_copy_allowed<decltype(info)>::value);
info.tid = exception->thread()->get_koid();
info.pid = exception->thread()->process()->get_koid();
info.type = exception->exception_type();
MessagePacketPtr message;
zx_status_t status =
MessagePacket::Create(reinterpret_cast<const char*>(&info), sizeof(info), 1, &message);
if (status != ZX_OK) {
return status;
}
// It's OK if the function fails after this point, all exception sending
// funnels through here so the task rights will get overwritten next time
// we try to send it.
//
// This is safe to do because we know that an ExceptionDispatcher only goes
// to one handler at a time, so we'll never change the task rights while
// the exception is out in userspace.
exception->SetTaskRights(thread_rights_, process_rights_);
HandleOwner exception_handle(Handle::Make(exception, ExceptionDispatcher::default_rights()));
if (!exception_handle) {
return ZX_ERR_NO_MEMORY;
}
message->mutable_handles()[0] = exception_handle.release();
message->set_owns_handles(true);
status = channel_handle_.dispatcher()->Write(ZX_KOID_INVALID, ktl::move(message));
// If sending failed for any reason, the exception handle never made it to
// userspace and has now gone out of scope, triggering on_zero_handles(),
// so we need to reset the exception.
if (status != ZX_OK) {
exception->DiscardHandleClose();
}
// ZX_ERR_PEER_CLOSED just indicates that there's no longer an endpoint
// to receive exceptions, simplify things for callers by collapsing this
// into the ZX_ERR_NEXT case since it means the same thing.
if (status == ZX_ERR_PEER_CLOSED) {
channel_handle_.reset();
return ZX_ERR_NEXT;
}
return status;
}