blob: 5e8fca78858efa4893e7f6d7814a1ef5e4500f8d [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 <inttypes.h>
#include <zircon/compiler.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/object.h>
#include <zircon/types.h>
#include <zx/handle.h>
#include <zx/job.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include "vector.h"
/*
threads creating and removing children (by closing final handles)
thread walking children, getting handles, calling INFO on them, closing them
handle to deep leaf job, none in between, let the whole thing collapse
hard part is seeing if we actually hit any corner cases
*/
#define RETURN_IF_ERROR(x) \
do { \
zx_status_t TRY_status__ = (x); \
if (TRY_status__ != ZX_OK) { \
fprintf(stderr, "%s:%d: %s failed: %s (%d)\n", \
__func__, __LINE__, #x, \
zx_status_get_string(TRY_status__), TRY_status__); \
return TRY_status__; \
} \
} while (0)
typedef Vector<zx::handle> HandleVector;
bool is_good_handle(zx_handle_t h) {
zx_info_handle_basic_t info;
zx_status_t s = zx_object_get_info(h, ZX_INFO_HANDLE_BASIC,
&info, sizeof(info), nullptr, nullptr);
return s == ZX_OK;
}
void tvtest() {
static constexpr size_t kNumHandles = 16;
zx_handle_t raw_handles[kNumHandles];
{
Vector<zx::handle> handles;
for (size_t i = 0; i < kNumHandles; i++) {
zx::handle h;
zx_status_t s = zx_event_create(0u, h.reset_and_get_address());
if (s != ZX_OK) {
fprintf(stderr, "Can't create event %zu: %d\n", i, s);
return;
}
raw_handles[i] = h.get();
handles.push_back(fbl::move(h));
ZX_DEBUG_ASSERT(!h.is_valid());
ZX_DEBUG_ASSERT(handles[handles.size() - 1].get() == raw_handles[i]);
}
for (const auto& h : handles) {
ZX_DEBUG_ASSERT(is_good_handle(h.get()));
printf("Good: %" PRIu32 "\n", h.get());
}
}
for (size_t i = 0; i < kNumHandles; i++) {
ZX_DEBUG_ASSERT(!is_good_handle(raw_handles[i]));
printf("Bad: %" PRIu32 "\n", raw_handles[i]);
}
printf("*** ok ***\n");
}
// Creates child jobs until it hits the bottom, closing intermediate handles
// along the way.
zx_status_t create_max_height_job(zx_handle_t parent_job,
zx::handle* leaf_job) {
bool first = true;
zx_handle_t prev_job = parent_job;
while (true) {
zx_handle_t child_job;
zx_status_t s = zx_job_create(prev_job, 0u, &child_job);
if (s == ZX_ERR_OUT_OF_RANGE) {
// Hit the max job height.
leaf_job->reset(prev_job);
return ZX_OK;
}
if (!first) {
zx_handle_close(prev_job);
} else {
first = false;
}
if (s != ZX_OK) {
leaf_job->reset();
return s;
}
//xxx give it a unique name; supply prefix
zx_object_set_property(child_job, ZX_PROP_NAME, "tg-job", 7);
prev_job = child_job;
}
}
// Creates some number of jobs under a parent job, pushing their handles
// onto the output vector.
zx_status_t create_child_jobs(zx_handle_t parent_job, size_t n,
HandleVector* out_handles) {
for (size_t i = 0; i < n; i++) {
zx::handle child;
RETURN_IF_ERROR(zx_job_create(parent_job, 0u,
child.reset_and_get_address()));
//xxx give it a unique name; supply prefix
child.set_property(ZX_PROP_NAME, "tg-job", 7);
out_handles->push_back(fbl::move(child));
}
return ZX_OK;
}
// Creates some number of processes under a parent job, pushing their handles
// onto the output vector.
zx_status_t create_child_processes(zx_handle_t parent_job, size_t n,
HandleVector* out_handles) {
for (size_t i = 0; i < n; i++) {
zx::handle child;
zx::handle vmar;
//xxx give it a unique name; supply prefix
RETURN_IF_ERROR(zx_process_create(parent_job, "tg-proc", 8, 0u,
child.reset_and_get_address(),
vmar.reset_and_get_address()));
out_handles->push_back(fbl::move(child));
// Let the VMAR handle close.
}
return ZX_OK;
}
// Creates some number of threads under a parent process, pushing their handles
// onto the output vector.
zx_status_t create_child_threads(zx_handle_t parent_process, size_t n,
HandleVector* out_handles) {
for (size_t i = 0; i < n; i++) {
zx::handle child;
//xxx give it a unique name; supply prefix
RETURN_IF_ERROR(zx_thread_create(parent_process, "tg-thread", 10,
0u, child.reset_and_get_address()));
out_handles->push_back(fbl::move(child));
}
return ZX_OK;
}
//xxx something that keeps creating children, writing handles to a pool?
//xxx another thing that reads handles out of the pool and closes them?
//xxx watch out for synchronization on that pool serializing things
//xxx could have a thread grab a bunch of handles and then operate on them
//xxx on its own
//xxx child-walker function: take this process or job, walk its children;
// maybe recurse
class HandleRegistry {
public:
void AddJobs(HandleVector* jobs) {
if (!jobs->empty()) {
fbl::AutoLock al(&jobs_lock_);
Merge(jobs, &jobs_, &num_jobs_);
}
}
void AddProcesses(HandleVector* processes) {
if (!processes->empty()) {
fbl::AutoLock al(&processes_lock_);
Merge(processes, &processes_, &num_processes_);
}
}
void AddThreads(HandleVector* threads) {
if (!threads->empty()) {
fbl::AutoLock al(&threads_lock_);
Merge(threads, &threads_, &num_threads_);
}
}
__attribute__((warn_unused_result)) zx_handle_t ReleaseRandomJob() {
fbl::AutoLock al(&jobs_lock_);
return ReleaseRandomHandle(&jobs_, &num_jobs_);
}
__attribute__((warn_unused_result)) zx_handle_t ReleaseRandomProcess() {
fbl::AutoLock al(&processes_lock_);
return ReleaseRandomHandle(&processes_, &num_processes_);
}
__attribute__((warn_unused_result)) zx_handle_t ReleaseRandomThread() {
fbl::AutoLock al(&threads_lock_);
return ReleaseRandomHandle(&threads_, &num_threads_);
}
__attribute__((warn_unused_result)) zx_handle_t ReleaseRandomTask() {
size_t total = num_jobs_ + num_processes_ + num_threads_;
const size_t r = rand() % total;
if (r < num_jobs_) {
return ReleaseRandomJob();
} else if (r < num_jobs_ + num_processes_) {
return ReleaseRandomProcess();
} else {
return ReleaseRandomThread();
}
//xxx try another if we picked a list with no handles
}
//xxx use atomics
size_t num_jobs() const { return num_jobs_; }
size_t num_processes() const { return num_processes_; }
size_t num_threads() const { return num_threads_; }
size_t num_tasks() const {
return num_jobs_ + num_processes_ + num_threads_;
}
private:
static void Merge(HandleVector* src, HandleVector* dst, size_t* count) {
const size_t dst_size = dst->size(); // No holes after this index.
//xxx use an iterator
size_t di = 0; // Destination index
for (auto& sit : *src) {
if (!sit.is_valid()) {
continue;
}
// Look for a hole in the destination.
while (di < dst_size && (*dst)[di].is_valid()) {
di++;
}
if (di < dst_size) {
(*dst)[di] = fbl::move(sit);
} else {
dst->push_back(fbl::move(sit));
}
}
*count += src->size();
}
static __attribute__((warn_unused_result))
zx_handle_t
ReleaseRandomHandle(HandleVector* hv, size_t* count) {
const size_t size = hv->size();
if (size == 0) {
return ZX_HANDLE_INVALID;
}
const size_t start = rand() % size;
//xxx use an iterator
for (size_t i = start; i < size; i++) {
if ((*hv)[i].is_valid()) {
(*count)--;
return (*hv)[i].release();
}
}
for (size_t i = 0; i < start; i++) {
if ((*hv)[i].is_valid()) {
(*count)--;
return (*hv)[i].release();
}
}
return ZX_HANDLE_INVALID;
}
mutable fbl::Mutex jobs_lock_;
HandleVector jobs_; // TA_GUARDED(jobs_lock_);
size_t num_jobs_; // TA_GUARDED(jobs_lock_);
mutable fbl::Mutex processes_lock_;
HandleVector processes_; // TA_GUARDED(processes_lock_);
size_t num_processes_; // TA_GUARDED(processes_lock_);
mutable fbl::Mutex threads_lock_;
HandleVector threads_; // TA_GUARDED(threads_lock_);
size_t num_threads_; // TA_GUARDED(threads_lock_);
};
//xxx Pass in as a param
static constexpr size_t kMaxTasks = 1000;
#define MTRACE(args...) printf(args)
zx_status_t mutate(HandleRegistry* registry) {
size_t total = registry->num_tasks();
enum {
OP_ADD,
OP_DELETE,
} op_class;
// Randomly pick between add, mutate, and delete.
if (total < kMaxTasks / 10) {
op_class = OP_ADD;
} else if (total > (9 * kMaxTasks) / 10) {
op_class = OP_DELETE;
} else {
op_class = (rand() % 32) < 16 ? OP_ADD : OP_DELETE;
}
enum {
TARGET_JOB,
TARGET_PROCESS,
TARGET_THREAD,
} op_target;
const size_t r = rand() % 48;
if (r < 16) {
op_target = TARGET_JOB;
} else if (r < 32) {
op_target = TARGET_PROCESS;
} else {
op_target = TARGET_THREAD;
}
// Handles that should go into the registry before we return.
HandleVector jobs;
HandleVector processes;
HandleVector threads;
switch (op_class) {
case OP_ADD: {
const size_t num_children = rand() % 5 + 1;
switch (op_target) {
case TARGET_JOB: {
zx_handle_t parent = registry->ReleaseRandomJob();
if (parent != ZX_HANDLE_INVALID) {
MTRACE("Create %zu jobs\n", num_children);
jobs.push_back(zx::handle(parent));
create_child_jobs(parent, num_children, &jobs);
//xxx if creation fails with BAD_STATE, the parent
//xxx is probably dead; don't put it back in the list
}
//xxx chance to create super-deep job
} break;
case TARGET_PROCESS: {
zx_handle_t parent = registry->ReleaseRandomJob();
if (parent != ZX_HANDLE_INVALID) {
MTRACE("Create %zu processes\n", num_children);
jobs.push_back(zx::handle(parent));
create_child_processes(parent, num_children, &processes);
}
} break;
case TARGET_THREAD: {
zx_handle_t parent = registry->ReleaseRandomProcess();
if (parent != ZX_HANDLE_INVALID) {
MTRACE("Create %zu threads\n", num_children);
processes.push_back(zx::handle(parent));
create_child_threads(parent, num_children, &threads);
}
} break;
}
} break;
case OP_DELETE: {
const bool kill = rand() % 32 < 16;
const bool close = rand() % 32 < 16;
if (kill || close) {
zx_handle_t task = registry->ReleaseRandomTask();
if (task != ZX_HANDLE_INVALID) {
if (kill) {
MTRACE("Kill one\n");
zx_task_kill(task);
}
if (close) {
MTRACE("Close one\n");
zx_handle_close(task);
} else {
MTRACE("(Close one)\n");
//xxx need to figure out the type so we can put it back.
zx_handle_close(task);
}
}
}
} break;
}
registry->AddJobs(&jobs);
registry->AddProcesses(&processes);
registry->AddThreads(&threads);
return ZX_OK;
}
zx_status_t buildup(const zx::handle& root_job) {
HandleRegistry registry;
{
HandleVector jobs;
jobs.push_back(zx::handle(root_job.get())); //xxx can't let them delete this
registry.AddJobs(&jobs);
}
for (int i = 0; i < 1000; i++) {
mutate(&registry);
if (i > 0 && i % 100 == 0) {
printf("%d mutations. Press a key:\n", i);
fgetc(stdin);
}
}
printf("Mutations done. Press a key:\n");
fgetc(stdin);
printf("Done.\n");
return ZX_OK;
}
int main(int argc, char** argv) {
//tvtest();
zx::handle test_root_job;
zx_status_t s = zx_job_create(zx_job_default(), 0u,
test_root_job.reset_and_get_address());
if (s != ZX_OK) {
return s;
}
test_root_job.set_property(ZX_PROP_NAME, "tg-root", 8);
return buildup(test_root_job);
}