blob: 0b3809d65a9bc07b694249467a05bafebba3f34c [file] [log] [blame]
// Copyright 2016 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 <inttypes.h>
#include <object/job_dispatcher.h>
#include <err.h>
#include <zircon/rights.h>
#include <zircon/syscalls/policy.h>
#include <fbl/alloc_checker.h>
#include <fbl/array.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <lib/counters.h>
#include <object/process_dispatcher.h>
#include <platform.h>
KCOUNTER(dispatcher_job_create_count, "dispatcher.job.create")
KCOUNTER(dispatcher_job_destroy_count, "dispatcher.job.destroy")
// The starting max_height value of the root job.
static constexpr uint32_t kRootJobMaxHeight = 32;
static constexpr char kRootJobName[] = "<superroot>";
template <>
uint32_t JobDispatcher::ChildCountLocked<JobDispatcher>() const {
return job_count_;
}
template <>
uint32_t JobDispatcher::ChildCountLocked<ProcessDispatcher>() const {
return process_count_;
}
// Calls the provided |zx_status_t func(fbl::RefPtr<DISPATCHER_TYPE>)|
// function on all live elements of |children|, which must be one of |jobs_|
// or |procs_|. Stops iterating early if |func| returns a value other than
// ZX_OK, returning that value from this method. |lock_| must be held when
// calling this method, and it will still be held while the callback is
// called.
//
// The returned |LiveRefsArray| needs to be destructed when |lock_| is not
// held anymore. The recommended pattern is:
//
// LiveRefsArray refs;
// {
// Guard<fbl::Mutex> guard{get_lock()};
// refs = ForEachChildInLocked(...);
// }
//
template <typename T, typename Fn>
JobDispatcher::LiveRefsArray JobDispatcher::ForEachChildInLocked(
T& children, zx_status_t* result, Fn func) {
// Convert child raw pointers into RefPtrs. This is tricky and requires
// special logic on the RefPtr class to handle a ref count that can be
// zero.
//
// The main requirement is that |lock_| is both controlling child
// list lookup and also making sure that the child destructor cannot
// make progress when doing so. In other words, when inspecting the
// |children| list we can be sure that a given child process or child
// job is either
// - alive, with refcount > 0
// - in destruction process but blocked, refcount == 0
const uint32_t count = ChildCountLocked<typename T::ValueType>();
if (!count) {
*result = ZX_OK;
return LiveRefsArray();
}
fbl::AllocChecker ac;
LiveRefsArray refs(new (&ac) fbl::RefPtr<Dispatcher>[count], count);
if (!ac.check()) {
*result = ZX_ERR_NO_MEMORY;
return LiveRefsArray();
}
size_t ix = 0;
for (auto& craw : children) {
auto cref = ::fbl::MakeRefPtrUpgradeFromRaw(&craw, get_lock());
if (!cref)
continue;
*result = func(cref);
// |cref| might be the last reference at this point. If so,
// when we drop it in the next iteration the object dtor
// would be called here with the |get_lock()| held. To avoid that
// we keep the reference alive in the |refs| array and pass
// the responsibility of releasing them outside the lock to
// the caller.
refs[ix++] = ktl::move(cref);
if (*result != ZX_OK)
break;
}
return refs;
}
fbl::RefPtr<JobDispatcher> JobDispatcher::CreateRootJob() {
fbl::AllocChecker ac;
auto job = fbl::AdoptRef(new (&ac) JobDispatcher(0u, nullptr, JobPolicy()));
if (!ac.check())
return nullptr;
job->set_name(kRootJobName, sizeof(kRootJobName));
return job;
}
zx_status_t JobDispatcher::Create(uint32_t flags,
fbl::RefPtr<JobDispatcher> parent,
KernelHandle<JobDispatcher>* handle,
zx_rights_t* rights) {
if (parent != nullptr && parent->max_height() == 0) {
// The parent job cannot have children.
return ZX_ERR_OUT_OF_RANGE;
}
fbl::AllocChecker ac;
KernelHandle new_handle(fbl::AdoptRef(new (&ac) JobDispatcher(flags, parent,
parent->GetPolicy())));
if (!ac.check())
return ZX_ERR_NO_MEMORY;
if (!parent->AddChildJob(new_handle.dispatcher())) {
return ZX_ERR_BAD_STATE;
}
*rights = default_rights();
*handle = ktl::move(new_handle);
return ZX_OK;
}
JobDispatcher::JobDispatcher(uint32_t /*flags*/,
fbl::RefPtr<JobDispatcher> parent,
JobPolicy policy)
: SoloDispatcher(ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS),
parent_(ktl::move(parent)),
max_height_(parent_ ? parent_->max_height() - 1 : kRootJobMaxHeight),
state_(State::READY),
process_count_(0u),
job_count_(0u),
return_code_(0),
kill_on_oom_(false),
policy_(policy),
exceptionate_(ExceptionPort::Type::JOB),
debug_exceptionate_(ExceptionPort::Type::JOB_DEBUGGER) {
kcounter_add(dispatcher_job_create_count, 1);
if (parent_ == nullptr) {
Guard<Mutex> guard{AllJobsLock::Get()};
all_jobs_list_.push_back(this);
}
}
JobDispatcher::~JobDispatcher() {
kcounter_add(dispatcher_job_destroy_count, 1);
RemoveFromJobTreesUnlocked();
}
zx_koid_t JobDispatcher::get_related_koid() const {
return parent_ ? parent_->get_koid() : 0u;
}
bool JobDispatcher::AddChildProcess(const fbl::RefPtr<ProcessDispatcher>& process) {
canary_.Assert();
Guard<fbl::Mutex> guard{get_lock()};
if (state_ != State::READY)
return false;
procs_.push_back(process.get());
++process_count_;
UpdateSignalsIncrementLocked();
return true;
}
bool JobDispatcher::AddChildJob(const fbl::RefPtr<JobDispatcher>& job) {
canary_.Assert();
// Maintain consistent lock ordering by grabbing the all-jobs lock before
// any individual JobDispatcher lock.
Guard<fbl::Mutex> all_jobs_guard{AllJobsLock::Get()};
Guard<fbl::Mutex> guard{get_lock()};
if (state_ != State::READY)
return false;
// Put the new job after our next-youngest child, or us if we have none.
//
// We try to make older jobs closer to the root (both hierarchically and
// temporally) show up earlier in enumeration.
JobDispatcher* neighbor = (jobs_.is_empty() ? this : &jobs_.back());
// This can only be called once, the job should not already be part
// of any job tree.
DEBUG_ASSERT(!job->dll_job_raw_.InContainer());
DEBUG_ASSERT(neighbor != job.get());
all_jobs_list_.insert(all_jobs_list_.make_iterator(*neighbor), job.get());
jobs_.push_back(job.get());
++job_count_;
UpdateSignalsIncrementLocked();
return true;
}
void JobDispatcher::RemoveChildProcess(ProcessDispatcher* process) {
canary_.Assert();
bool should_die = false;
{
Guard<fbl::Mutex> guard{get_lock()};
// The process dispatcher can call us in its destructor, Kill(),
// or RemoveThread().
if (!ProcessDispatcher::JobListTraitsRaw::node_state(*process).InContainer())
return;
procs_.erase(*process);
--process_count_;
UpdateSignalsDecrementLocked();
should_die = IsReadyForDeadTransitionLocked();
}
if (should_die)
FinishDeadTransitionUnlocked();
}
void JobDispatcher::RemoveChildJob(JobDispatcher* job) {
canary_.Assert();
bool should_die = false;
{
Guard<fbl::Mutex> guard{get_lock()};
if (!JobDispatcher::ListTraitsRaw::node_state(*job).InContainer())
return;
jobs_.erase(*job);
--job_count_;
UpdateSignalsDecrementLocked();
should_die = IsReadyForDeadTransitionLocked();
}
if (should_die)
FinishDeadTransitionUnlocked();
}
JobDispatcher::State JobDispatcher::GetState() const {
Guard<fbl::Mutex> guard{get_lock()};
return state_;
}
void JobDispatcher::RemoveFromJobTreesUnlocked() {
canary_.Assert();
if (parent_)
parent_->RemoveChildJob(this);
{
Guard<Mutex> guard{AllJobsLock::Get()};
if (dll_all_jobs_.InContainer())
all_jobs_list_.erase(*this);
}
}
bool JobDispatcher::IsReadyForDeadTransitionLocked() {
canary_.Assert();
return state_ == State::KILLING && job_count_ == 0 && process_count_ == 0;
}
void JobDispatcher::FinishDeadTransitionUnlocked() {
canary_.Assert();
// Make sure we're killing from the bottom of the tree up or else parent
// jobs could die before their children.
//
// In particular, this means we have to finish dying before leaving the job
// trees, since the last child leaving the tree can trigger its parent to
// finish dying.
DEBUG_ASSERT(!parent_ || (parent_->GetState() != State::DEAD));
{
Guard<fbl::Mutex> guard{get_lock()};
state_ = State::DEAD;
exceptionate_.Shutdown();
debug_exceptionate_.Shutdown();
UpdateStateLocked(0u, ZX_TASK_TERMINATED);
}
RemoveFromJobTreesUnlocked();
}
void JobDispatcher::UpdateSignalsDecrementLocked() {
canary_.Assert();
DEBUG_ASSERT(get_lock()->lock().IsHeld());
// removing jobs or processes.
zx_signals_t set = 0u;
if (process_count_ == 0u) {
DEBUG_ASSERT(procs_.is_empty());
set |= ZX_JOB_NO_PROCESSES;
}
if (job_count_ == 0u) {
DEBUG_ASSERT(jobs_.is_empty());
set |= ZX_JOB_NO_JOBS;
}
if (!parent_ && (job_count_ == 0) && (process_count_ == 0)) {
// There are no userspace process left. From here, there's
// no particular context as to whether this was
// intentional, or if a core devhost crashed due to a
// bug. Either way, shut down the kernel.
platform_halt(HALT_ACTION_HALT, HALT_REASON_SW_RESET);
}
UpdateStateLocked(0u, set);
}
void JobDispatcher::UpdateSignalsIncrementLocked() {
canary_.Assert();
DEBUG_ASSERT(get_lock()->lock().IsHeld());
// Adding jobs or processes.
zx_signals_t clear = 0u;
if (process_count_ == 1u) {
DEBUG_ASSERT(!procs_.is_empty());
clear |= ZX_JOB_NO_PROCESSES;
}
if (job_count_ == 1u) {
DEBUG_ASSERT(!jobs_.is_empty());
clear |= ZX_JOB_NO_JOBS;
}
UpdateStateLocked(clear, 0u);
}
JobPolicy JobDispatcher::GetPolicy() const {
Guard<fbl::Mutex> guard{get_lock()};
return policy_;
}
bool JobDispatcher::Kill(int64_t return_code) {
canary_.Assert();
JobList jobs_to_kill;
ProcessList procs_to_kill;
LiveRefsArray jobs_refs;
LiveRefsArray proc_refs;
bool should_die = false;
{
Guard<fbl::Mutex> guard{get_lock()};
if (state_ != State::READY)
return false;
return_code_ = return_code;
state_ = State::KILLING;
zx_status_t result;
// Safely gather refs to the children.
jobs_refs = ForEachChildInLocked(jobs_, &result, [&](fbl::RefPtr<JobDispatcher> job) {
jobs_to_kill.push_front(ktl::move(job));
return ZX_OK;
});
proc_refs = ForEachChildInLocked(procs_, &result, [&](fbl::RefPtr<ProcessDispatcher> proc) {
procs_to_kill.push_front(ktl::move(proc));
return ZX_OK;
});
should_die = IsReadyForDeadTransitionLocked();
}
if (should_die)
FinishDeadTransitionUnlocked();
// Since we kill the child jobs first we have a depth-first massacre.
while (!jobs_to_kill.is_empty()) {
// TODO(cpu): This recursive call can overflow the stack.
jobs_to_kill.pop_front()->Kill(return_code);
}
while (!procs_to_kill.is_empty()) {
procs_to_kill.pop_front()->Kill(return_code);
}
return true;
}
bool JobDispatcher::CanSetPolicy() TA_REQ(get_lock()) {
// Can't set policy when there are active processes or jobs. This constraint ensures that a
// process's policy cannot change over its lifetime. Because a process's policy cannot change,
// the risk of TOCTOU bugs is reduced and we are free to apply policy at the ProcessDispatcher
// without having to walk up the tree to its containing job.
if (!procs_.is_empty() || !jobs_.is_empty()) {
return false;
}
return true;
}
zx_status_t JobDispatcher::SetBasicPolicy(
uint32_t mode, const zx_policy_basic* in_policy, size_t policy_count) {
Guard<fbl::Mutex> guard{get_lock()};
if (!CanSetPolicy()) {
return ZX_ERR_BAD_STATE;
}
auto status = policy_.AddBasicPolicy(mode, in_policy, policy_count);
if (status != ZX_OK)
return status;
return ZX_OK;
}
zx_status_t JobDispatcher::SetTimerSlackPolicy(const zx_policy_timer_slack& policy) {
Guard<fbl::Mutex> guard{get_lock()};
if (!CanSetPolicy()) {
return ZX_ERR_BAD_STATE;
}
// Is the policy valid?
if (policy.min_slack < 0) {
return ZX_ERR_INVALID_ARGS;
}
slack_mode new_mode;
switch (policy.default_mode) {
case ZX_TIMER_SLACK_CENTER:
new_mode = TIMER_SLACK_CENTER;
break;
case ZX_TIMER_SLACK_EARLY:
new_mode = TIMER_SLACK_EARLY;
break;
case ZX_TIMER_SLACK_LATE:
new_mode = TIMER_SLACK_LATE;
break;
default:
return ZX_ERR_INVALID_ARGS;
};
const TimerSlack old_slack = policy_.GetTimerSlack();
const zx_duration_t new_amount = fbl::max(old_slack.amount(), policy.min_slack);
const TimerSlack new_slack(new_amount, new_mode);
policy_.SetTimerSlack(new_slack);
return ZX_OK;
}
bool JobDispatcher::EnumerateChildren(JobEnumerator* je, bool recurse) {
canary_.Assert();
LiveRefsArray jobs_refs;
LiveRefsArray proc_refs;
zx_status_t result = ZX_OK;
{
Guard<fbl::Mutex> guard{get_lock()};
proc_refs = ForEachChildInLocked(
procs_, &result, [&](fbl::RefPtr<ProcessDispatcher> proc) {
return je->OnProcess(proc.get()) ? ZX_OK : ZX_ERR_STOP;
});
if (result != ZX_OK) {
return false;
}
jobs_refs = ForEachChildInLocked(jobs_, &result, [&](fbl::RefPtr<JobDispatcher> job) {
if (!je->OnJob(job.get())) {
return ZX_ERR_STOP;
}
if (recurse) {
// TODO(kulakowski): This recursive call can overflow the stack.
return job->EnumerateChildren(je, /* recurse */ true)
? ZX_OK
: ZX_ERR_STOP;
}
return ZX_OK;
});
}
return result == ZX_OK;
}
fbl::RefPtr<ProcessDispatcher>
JobDispatcher::LookupProcessById(zx_koid_t koid) {
canary_.Assert();
LiveRefsArray proc_refs;
fbl::RefPtr<ProcessDispatcher> found_proc;
{
Guard<fbl::Mutex> guard{get_lock()};
zx_status_t result;
proc_refs = ForEachChildInLocked(procs_, &result, [&](fbl::RefPtr<ProcessDispatcher> proc) {
if (proc->get_koid() == koid) {
found_proc = ktl::move(proc);
return ZX_ERR_STOP;
}
return ZX_OK;
});
}
return found_proc; // Null if not found.
}
fbl::RefPtr<JobDispatcher>
JobDispatcher::LookupJobById(zx_koid_t koid) {
canary_.Assert();
LiveRefsArray jobs_refs;
fbl::RefPtr<JobDispatcher> found_job;
{
Guard<fbl::Mutex> guard{get_lock()};
zx_status_t result;
jobs_refs = ForEachChildInLocked(jobs_, &result, [&](fbl::RefPtr<JobDispatcher> job) {
if (job->get_koid() == koid) {
found_job = ktl::move(job);
return ZX_ERR_STOP;
}
return ZX_OK;
});
}
return found_job; // Null if not found.
}
void JobDispatcher::get_name(char out_name[ZX_MAX_NAME_LEN]) const {
canary_.Assert();
name_.get(ZX_MAX_NAME_LEN, out_name);
}
zx_status_t JobDispatcher::set_name(const char* name, size_t len) {
canary_.Assert();
return name_.set(name, len);
}
// Global list of all jobs.
JobDispatcher::AllJobsList JobDispatcher::all_jobs_list_;
zx_status_t JobDispatcher::SetExceptionPort(fbl::RefPtr<ExceptionPort> eport) {
canary_.Assert();
bool debugger = false;
switch (eport->type()) {
case ExceptionPort::Type::JOB_DEBUGGER:
debugger = true;
break;
case ExceptionPort::Type::JOB:
break;
default:
DEBUG_ASSERT_MSG(false, "unexpected port type: %d",
static_cast<int>(eport->type()));
break;
}
Guard<fbl::Mutex> guard{get_lock()};
if (debugger) {
if (debugger_exception_port_)
return ZX_ERR_ALREADY_BOUND;
debugger_exception_port_ = ktl::move(eport);
} else {
if (exception_port_)
return ZX_ERR_ALREADY_BOUND;
exception_port_ = ktl::move(eport);
}
return ZX_OK;
}
class OnExceptionPortRemovalEnumerator final : public JobEnumerator {
public:
OnExceptionPortRemovalEnumerator(fbl::RefPtr<ExceptionPort> eport)
: eport_(ktl::move(eport)) {}
OnExceptionPortRemovalEnumerator(const OnExceptionPortRemovalEnumerator&) = delete;
private:
bool OnProcess(ProcessDispatcher* process) override {
process->OnExceptionPortRemoval(eport_);
// Keep looking.
return true;
}
fbl::RefPtr<ExceptionPort> eport_;
};
bool JobDispatcher::ResetExceptionPort(bool debugger) {
canary_.Assert();
fbl::RefPtr<ExceptionPort> eport;
{
Guard<fbl::Mutex> lock{get_lock()};
if (debugger) {
debugger_exception_port_.swap(eport);
} else {
exception_port_.swap(eport);
}
if (eport == nullptr) {
// Attempted to unbind when no exception port is bound.
return false;
}
// This method must guarantee that no caller will return until
// OnTargetUnbind has been called on the port-to-unbind.
// This becomes important when a manual unbind races with a
// PortDispatcher::on_zero_handles auto-unbind.
//
// If OnTargetUnbind were called outside of the lock, it would lead to
// a race (for threads A and B):
//
// A: Calls ResetExceptionPort; acquires the lock
// A: Sees a non-null exception_port_, swaps it into the eport local.
// exception_port_ is now null.
// A: Releases the lock
//
// B: Calls ResetExceptionPort; acquires the lock
// B: Sees a null exception_port_ and returns. But OnTargetUnbind()
// hasn't yet been called for the port.
//
// So, call it before releasing the lock.
eport->OnTargetUnbind();
}
OnExceptionPortRemovalEnumerator remover(eport);
if (!EnumerateChildren(&remover, true)) {
DEBUG_ASSERT(false);
}
return true;
}
fbl::RefPtr<ExceptionPort> JobDispatcher::exception_port() {
Guard<fbl::Mutex> lock{get_lock()};
return exception_port_;
}
fbl::RefPtr<ExceptionPort> JobDispatcher::debugger_exception_port() {
Guard<fbl::Mutex> guard{get_lock()};
return debugger_exception_port_;
}
Exceptionate* JobDispatcher::exceptionate(Exceptionate::Type type) {
canary_.Assert();
return type == Exceptionate::Type::kDebug ? &debug_exceptionate_ : &exceptionate_;
}
void JobDispatcher::set_kill_on_oom(bool value) {
Guard<fbl::Mutex> guard{get_lock()};
kill_on_oom_ = value;
}
bool JobDispatcher::get_kill_on_oom() const {
Guard<fbl::Mutex> guard{get_lock()};
return kill_on_oom_;
}
void JobDispatcher::GetInfo(zx_info_job_t* info) const {
canary_.Assert();
Guard<fbl::Mutex> guard{get_lock()};
info->return_code = return_code_;
info->exited = (state_ == State::DEAD);
info->kill_on_oom = kill_on_oom_;
info->debugger_attached = debugger_exception_port_ != nullptr;
}