blob: 3a2cf12215d0bee0a5e8002f1dd4235fbef3879c [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <task-utils/walker.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fuchsia/sysinfo/c/fidl.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <lib/zx/channel.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
// Immutable state of a specific call to walk_job_tree, passed along
// to most helper functions.
typedef struct {
task_callback_t* job_callback;
task_callback_t* process_callback;
task_callback_t* thread_callback;
void* callback_context;
} walk_ctx_t;
// A dynamically-managed array of koids.
// TODO(dbort): Turn into a class now that this is a .cpp file.
typedef struct {
zx_koid_t* entries;
size_t num_entries;
size_t capacity; // allocation size
} koid_table_t;
// best first guess at number of children
static const size_t kNumInitialKoids = 128;
// when reallocating koid buffer because we were too small add this much extra
// on top of what the kernel says is currently needed
static const size_t kNumExtraKoids = 10;
static zx_status_t walk_job_tree_internal(
const walk_ctx_t* ctx, zx_handle_t job, zx_koid_t job_koid, int depth);
static size_t koid_table_byte_capacity(koid_table_t* table) {
return table->capacity * sizeof(table->entries[0]);
}
static void realloc_koid_table(koid_table_t* table, size_t new_capacity) {
table->entries = reinterpret_cast<zx_koid_t*>(
realloc(table->entries, new_capacity * sizeof(table->entries[0])));
table->capacity = new_capacity;
}
static koid_table_t* make_koid_table(void) {
koid_table_t* table =
reinterpret_cast<koid_table_t*>(malloc(sizeof(*table)));
table->num_entries = 0;
table->entries = nullptr;
realloc_koid_table(table, kNumInitialKoids);
return table;
}
static void free_koid_table(koid_table_t* table) {
free(table->entries);
free(table);
}
static zx_status_t fetch_children(zx_handle_t parent, zx_koid_t parent_koid,
int children_kind, const char* kind_name,
koid_table_t* koids) {
size_t actual = 0;
size_t avail = 0;
zx_status_t status;
// this is inherently racy, but we retry once with a bit of slop to try to
// get a complete list
for (int pass = 0; pass < 2; ++pass) {
if (actual < avail) {
realloc_koid_table(koids, avail + kNumExtraKoids);
}
status = zx_object_get_info(parent, children_kind,
koids->entries,
koid_table_byte_capacity(koids),
&actual, &avail);
if (status != ZX_OK) {
fprintf(stderr,
"ERROR: zx_object_get_info(%" PRIu64 ", %s, ...) "
"failed: %s (%d)\n",
parent_koid, kind_name,
zx_status_get_string(status), status);
return status;
}
if (actual == avail) {
break;
}
}
// if we're still too small at least warn the user
if (actual < avail) {
fprintf(stderr,
"WARNING: zx_object_get_info(%" PRIu64 ", %s, ...) "
"truncated %zu/%zu results\n",
parent_koid, kind_name, avail - actual, avail);
}
koids->num_entries = actual;
return ZX_OK;
}
static zx_status_t do_threads_worker(
const walk_ctx_t* ctx, koid_table_t* koids,
zx_handle_t process, zx_koid_t process_koid, int depth) {
zx_status_t status;
// get the list of processes under this job
status = fetch_children(process, process_koid, ZX_INFO_PROCESS_THREADS,
"ZX_INFO_PROCESS_THREADS", koids);
if (status != ZX_OK) {
return status;
}
for (size_t n = 0; n < koids->num_entries; n++) {
zx_handle_t child;
status = zx_object_get_child(process, koids->entries[n],
ZX_RIGHT_SAME_RIGHTS, &child);
if (status == ZX_OK) {
// call the thread_callback if supplied
if (ctx->thread_callback) {
status = (ctx->thread_callback)(
ctx->callback_context,
depth, child, koids->entries[n], process_koid);
// abort on failure
if (status != ZX_OK) {
return status;
}
}
zx_handle_close(child);
} else {
fprintf(stderr,
"WARNING: zx_object_get_child(%" PRIu64 ", "
"(proc)%" PRIu64 ", ...) failed: %s (%d)\n",
process_koid, koids->entries[n],
zx_status_get_string(status), status);
}
}
return ZX_OK;
}
static zx_status_t do_threads(
const walk_ctx_t* ctx, zx_handle_t job, zx_koid_t job_koid, int depth) {
koid_table_t* koids = make_koid_table();
zx_status_t status = do_threads_worker(ctx, koids, job, job_koid, depth);
free_koid_table(koids);
return status;
}
static zx_status_t do_processes_worker(
const walk_ctx_t* ctx, koid_table_t* koids,
zx_handle_t job, zx_koid_t job_koid, int depth) {
zx_status_t status;
// get the list of processes under this job
status = fetch_children(job, job_koid, ZX_INFO_JOB_PROCESSES,
"ZX_INFO_JOB_PROCESSES", koids);
if (status != ZX_OK) {
return status;
}
for (size_t n = 0; n < koids->num_entries; n++) {
zx_handle_t child;
status = zx_object_get_child(job, koids->entries[n],
ZX_RIGHT_SAME_RIGHTS, &child);
if (status == ZX_OK) {
// call the process_callback if supplied
if (ctx->process_callback) {
status = (ctx->process_callback)(ctx->callback_context,
depth, child,
koids->entries[n], job_koid);
// abort on failure
if (status != ZX_OK) {
return status;
}
}
if (ctx->thread_callback) {
status = do_threads(ctx, child, koids->entries[n], depth + 1);
// abort on failure
if (status != ZX_OK) {
return status;
}
}
zx_handle_close(child);
} else {
fprintf(stderr,
"WARNING: zx_object_get_child(%" PRIu64 ", "
"(proc)%" PRIu64 ", ...) failed: %s (%d)\n",
job_koid, koids->entries[n],
zx_status_get_string(status), status);
}
}
return ZX_OK;
}
static zx_status_t do_processes(
const walk_ctx_t* ctx, zx_handle_t job, zx_koid_t job_koid, int depth) {
koid_table_t* koids = make_koid_table();
zx_status_t status = do_processes_worker(ctx, koids, job, job_koid, depth);
free_koid_table(koids);
return status;
}
static zx_status_t do_jobs_worker(
const walk_ctx_t* ctx, koid_table_t* koids,
zx_handle_t job, zx_koid_t job_koid, int depth) {
zx_status_t status;
// get a list of child jobs for this job
status = fetch_children(job, job_koid, ZX_INFO_JOB_CHILDREN,
"ZX_INFO_JOB_CHILDREN", koids);
if (status != ZX_OK) {
return status;
}
// drill down into the job tree
for (size_t n = 0; n < koids->num_entries; n++) {
zx_handle_t child;
status = zx_object_get_child(job, koids->entries[n],
ZX_RIGHT_SAME_RIGHTS, &child);
if (status == ZX_OK) {
// call the job_callback if supplied
if (ctx->job_callback) {
status = (ctx->job_callback)(ctx->callback_context,
depth, child,
koids->entries[n], job_koid);
// abort on failure
if (status != ZX_OK) {
return status;
}
}
// recurse to its children
status = walk_job_tree_internal(
ctx, child, koids->entries[n], depth + 1);
// abort on failure
if (status != ZX_OK) {
return status;
}
zx_handle_close(child);
} else {
fprintf(stderr,
"WARNING: zx_object_get_child(%" PRIu64 ", (job)%" PRIu64
", ...) failed: %s (%d)\n",
job_koid, koids->entries[n],
zx_status_get_string(status), status);
}
}
return ZX_OK;
}
static zx_status_t do_jobs(
const walk_ctx_t* ctx, zx_handle_t job, zx_koid_t job_koid, int depth) {
koid_table_t* koids = make_koid_table();
zx_status_t status = do_jobs_worker(ctx, koids, job, job_koid, depth);
free_koid_table(koids);
return status;
}
static zx_status_t walk_job_tree_internal(
const walk_ctx_t* ctx, zx_handle_t job, zx_koid_t job_koid, int depth) {
if (ctx->process_callback != nullptr || ctx->thread_callback != nullptr) {
zx_status_t status = do_processes(ctx, job, job_koid, depth);
if (status != ZX_OK) {
return status;
}
}
return do_jobs(ctx, job, job_koid, depth);
}
zx_status_t walk_job_tree(zx_handle_t root_job,
task_callback_t job_callback,
task_callback_t process_callback,
task_callback_t thread_callback,
void* context) {
zx_koid_t root_job_koid = 0;
zx_info_handle_basic_t info;
zx_status_t status = zx_object_get_info(
root_job, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
if (status == ZX_OK) {
root_job_koid = info.koid;
}
// Else keep going with a koid of zero.
if (job_callback) {
status =
(job_callback)(context, /* depth */ 0, root_job, root_job_koid, 0);
if (status != ZX_OK) {
return status;
}
}
walk_ctx_t ctx = {
.job_callback = job_callback,
.process_callback = process_callback,
.thread_callback = thread_callback,
.callback_context = context,
};
return walk_job_tree_internal(
&ctx, root_job, root_job_koid, /* depth */ 1);
}
zx_status_t walk_root_job_tree(task_callback_t job_callback,
task_callback_t process_callback,
task_callback_t thread_callback,
void* context) {
int fd = open("/dev/misc/sysinfo", O_RDWR);
if (fd < 0) {
fprintf(stderr, "task-utils/walker: cannot open sysinfo: %d\n", errno);
return ZX_ERR_NOT_FOUND;
}
zx::channel channel;
zx_status_t status = fdio_get_service_handle(fd, channel.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
zx_handle_t root_job;
zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetRootJob(channel.get(), &status, &root_job);
if (fidl_status != ZX_OK || status != ZX_OK) {
fprintf(stderr, "task-utils/walker: cannot obtain root job\n");
return ZX_ERR_NOT_FOUND;
}
zx_status_t s = walk_job_tree(
root_job, job_callback, process_callback, thread_callback, context);
zx_handle_close(root_job);
return s;
}
// C++ interface
namespace {
static zx_status_t job_cpp_cb(void* ctx, int depth, zx_handle_t handle,
zx_koid_t koid, zx_koid_t parent_koid) {
return reinterpret_cast<TaskEnumerator*>(ctx)->OnJob(
depth, handle, koid, parent_koid);
}
static zx_status_t process_cpp_cb(void* ctx, int depth, zx_handle_t handle,
zx_koid_t koid, zx_koid_t parent_koid) {
return reinterpret_cast<TaskEnumerator*>(ctx)->OnProcess(
depth, handle, koid, parent_koid);
}
static zx_status_t thread_cpp_cb(void* ctx, int depth, zx_handle_t handle,
zx_koid_t koid, zx_koid_t parent_koid) {
return reinterpret_cast<TaskEnumerator*>(ctx)->OnThread(
depth, handle, koid, parent_koid);
}
} // namespace
zx_status_t TaskEnumerator::WalkJobTree(zx_handle_t root_job) {
return walk_job_tree(root_job,
has_on_job() ? job_cpp_cb : nullptr,
has_on_process() ? process_cpp_cb : nullptr,
has_on_thread() ? thread_cpp_cb : nullptr,
reinterpret_cast<void*>(this));
}
zx_status_t TaskEnumerator::WalkRootJobTree() {
return walk_root_job_tree(has_on_job() ? job_cpp_cb : nullptr,
has_on_process() ? process_cpp_cb : nullptr,
has_on_thread() ? thread_cpp_cb : nullptr,
reinterpret_cast<void*>(this));
}