| // 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)); |
| } |