blob: d1c01c7c61bc97bf9fce99c472c3108936e87942 [file] [log] [blame]
// Copyright 2016 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 <assert.h>
#include <lib/zx/event.h>
#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#include <stdio.h>
#include <stdlib.h>
#include <zircon/syscalls/policy.h>
#include <iterator>
#include <fbl/algorithm.h>
#include <mini-process/mini-process.h>
#include <zxtest/zxtest.h>
extern zx_handle_t root_job;
// Job signal that is active when a job has no children (i.e., no child jobs and no child
// processes).
//
// TODO(fxbug.dev/53986): This is a temporary signal that we don't want userspace using (yet?).
// The kernel doesn't export it, but we declare it here to allow it to be tested.
#define ZX_JOB_NO_CHILDREN __ZX_OBJECT_SIGNAL_6
namespace {
constexpr char kProcessName[] = "job-test-p";
TEST(JobTest, BasicTest) {
// Never close the launchpad job.
zx_handle_t job_parent = zx_job_default();
ASSERT_NE(job_parent, ZX_HANDLE_INVALID);
// If the parent job is valid, one should be able to create a child job
// and a child job of the child job.
zx_handle_t job_child, job_grandchild;
ASSERT_OK(zx_job_create(job_parent, 0u, &job_child));
ASSERT_OK(zx_job_create(job_child, 0u, &job_grandchild));
zx_info_job_t job_info;
ASSERT_OK(
zx_object_get_info(job_child, ZX_INFO_JOB, &job_info, sizeof(job_info), nullptr, nullptr));
EXPECT_FALSE(job_info.exited);
EXPECT_EQ(job_info.return_code, 0);
ASSERT_OK(zx_handle_close(job_child));
ASSERT_OK(zx_handle_close(job_grandchild));
// If the parent job is not valid it should fail.
zx_handle_t job_fail;
ASSERT_STATUS(zx_job_create(ZX_HANDLE_INVALID, 0u, &job_fail), ZX_ERR_BAD_HANDLE);
}
TEST(JobTest, CreateTest) {
zx_handle_t job_parent = zx_job_default();
ASSERT_NE(job_parent, ZX_HANDLE_INVALID);
zx_handle_t job_child;
ASSERT_OK(zx_job_create(job_parent, 0u, &job_child));
// Make sure we can create process object with both the parent job and a child job.
zx_handle_t process1, vmar1;
ASSERT_OK(
zx_process_create(job_parent, kProcessName, sizeof(kProcessName), 0u, &process1, &vmar1));
zx_handle_t process2, vmar2;
ASSERT_OK(
zx_process_create(job_child, kProcessName, sizeof(kProcessName), 0u, &process2, &vmar2));
ASSERT_OK(zx_handle_close(job_child));
ASSERT_OK(zx_handle_close(process1));
ASSERT_OK(zx_handle_close(process2));
ASSERT_OK(zx_handle_close(vmar1));
ASSERT_OK(zx_handle_close(vmar2));
}
zx_signals_t GetActiveSignals(zx_handle_t object) {
zx_signals_t observed = 0;
EXPECT_EQ(zx_object_wait_one(object, 0x0, ZX_TIME_INFINITE_PAST, &observed), ZX_ERR_TIMED_OUT);
return observed;
}
TEST(JobTest, JobSignals) {
zx_handle_t job;
ASSERT_OK(zx_job_create(zx_job_default(), 0u, &job));
// A new job should have the NO_{JOBS,PROCESSES,CHILDREN} signals set.
EXPECT_EQ(GetActiveSignals(job), ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
// Create a child process.
zx_handle_t child_process, vmar;
ASSERT_OK(zx_process_create(job, kProcessName, sizeof(kProcessName), 0u, &child_process, &vmar));
// Expect only the NO_JOBS signal now.
EXPECT_EQ(GetActiveSignals(job), ZX_JOB_NO_JOBS);
// Create a child job.
zx_handle_t child_job;
ASSERT_OK(zx_job_create(job, 0u, &child_job));
// Expect no signals.
EXPECT_EQ(GetActiveSignals(job), 0);
// Kill the process. We expect the NO_PROCESSES signal to activate.
ASSERT_OK(zx_handle_close(child_process));
EXPECT_EQ(GetActiveSignals(job), ZX_JOB_NO_PROCESSES);
// Kill the job. We expect the NO_JOBS and NO_CHILDREN signal to also become active.
ASSERT_OK(zx_handle_close(child_job));
EXPECT_EQ(GetActiveSignals(job), ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
ASSERT_OK(zx_handle_close(vmar));
ASSERT_OK(zx_handle_close(job));
}
TEST(JobTest, CreateMissingRightsTest) {
zx_rights_t rights = ZX_DEFAULT_JOB_RIGHTS & ~ZX_RIGHT_WRITE & ~ZX_RIGHT_MANAGE_JOB;
zx_handle_t job_parent;
zx_status_t status = zx_handle_duplicate(zx_job_default(), rights, &job_parent);
ASSERT_OK(status);
zx_handle_t job_child;
ASSERT_STATUS(zx_job_create(job_parent, 0u, &job_child), ZX_ERR_ACCESS_DENIED);
zx_handle_close(job_parent);
}
TEST(JobTest, PolicyInvalidTopicTest) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
const uint32_t invalid_topic = 2u;
const uint32_t some_policy = 0;
ASSERT_STATUS(job_child.set_policy(ZX_JOB_POL_RELATIVE, invalid_topic, &some_policy, 1),
ZX_ERR_INVALID_ARGS);
}
TEST(JobTest, PolicyBasicOverrideDenyTest) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
zx_policy_basic_v2_t policy[] = {
{ZX_POL_BAD_HANDLE, ZX_POL_ACTION_KILL, ZX_POL_OVERRIDE_DENY},
{ZX_POL_NEW_CHANNEL, ZX_POL_ACTION_ALLOW_EXCEPTION, ZX_POL_OVERRIDE_DENY},
{ZX_POL_NEW_FIFO, ZX_POL_ACTION_DENY, ZX_POL_OVERRIDE_DENY},
};
// Set policy that does not allow overrides. Setting the exact same policy succeeds.
ASSERT_OK(
job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC_V2, policy, std::size(policy)));
ASSERT_OK(
job_child.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC_V2, policy, std::size(policy)));
// Changing a set policy should fail.
policy[0].action = ZX_POL_ACTION_ALLOW;
ASSERT_STATUS(
job_child.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC_V2, policy, std::size(policy)),
ZX_ERR_ALREADY_EXISTS);
}
TEST(JobTest, PolicyBasicOverrideAllowTest) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
zx_policy_basic_v2_t policy[] = {
{ZX_POL_BAD_HANDLE, ZX_POL_ACTION_KILL, ZX_POL_OVERRIDE_ALLOW},
{ZX_POL_NEW_CHANNEL, ZX_POL_ACTION_ALLOW_EXCEPTION, ZX_POL_OVERRIDE_ALLOW},
{ZX_POL_NEW_FIFO, ZX_POL_ACTION_DENY, ZX_POL_OVERRIDE_ALLOW},
};
// Set policy that does not allow overrides. Setting the exact same policy succeeds.
ASSERT_OK(
job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC_V2, policy, std::size(policy)));
// Changing a set policy should succeed.
policy[0].action = ZX_POL_ACTION_ALLOW;
ASSERT_OK(
job_child.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC_V2, policy, std::size(policy)));
}
TEST(JobTest, PolicyTimerSlackInvalidOptionsTest) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
zx_policy_timer_slack policy = {ZX_MSEC(10), ZX_TIMER_SLACK_LATE, {}};
// Invalid.
uint32_t options = ZX_JOB_POL_ABSOLUTE;
ASSERT_STATUS(job_child.set_policy(options, ZX_JOB_POL_TIMER_SLACK, &policy, 1),
ZX_ERR_INVALID_ARGS);
// Valid.
options = ZX_JOB_POL_RELATIVE;
ASSERT_OK(job_child.set_policy(options, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
}
TEST(JobTest, PolicyTimerSlackInvalidCountTest) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
zx_policy_timer_slack policy[2] = {{ZX_MSEC(10), ZX_TIMER_SLACK_LATE, {}},
{ZX_MSEC(10), ZX_TIMER_SLACK_LATE, {}}};
// Too few.
ASSERT_STATUS(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 0),
ZX_ERR_INVALID_ARGS);
// Too many.
ASSERT_STATUS(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 2),
ZX_ERR_INVALID_ARGS);
// Just right.
ASSERT_OK(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
}
TEST(JobTest, PolicyTimerSlackInvalidPolicyTest) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
// Null.
ASSERT_STATUS(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, nullptr, 1),
ZX_ERR_INVALID_ARGS);
// Negative amount.
zx_policy_timer_slack policy = {-ZX_MSEC(10), ZX_TIMER_SLACK_LATE, {}};
ASSERT_STATUS(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1),
ZX_ERR_INVALID_ARGS);
// Invalid mode.
policy = {ZX_MSEC(10), 3, {}};
ASSERT_STATUS(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1),
ZX_ERR_INVALID_ARGS);
// OK.
policy = {ZX_MSEC(10), ZX_TIMER_SLACK_LATE, {}};
ASSERT_OK(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
}
TEST(JobTest, PolicyTimerSlackNonEmptyTest) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
zx::job job_grandchild;
ASSERT_OK(zx::job::create(job_child, 0u, &job_grandchild));
zx_policy_timer_slack policy = {ZX_MSEC(10), ZX_TIMER_SLACK_LATE, {}};
// The job isn't empty.
ASSERT_STATUS(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1),
ZX_ERR_BAD_STATE);
job_grandchild.reset();
// Job is now empty.
ASSERT_OK(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
}
// For verifying timer slack correctness, see |timer_diag()| in kernel/tests/timer_tests.cpp or run
// "k timer_diag".
TEST(JobTest, PolicyTimerSlackValid) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
// All modes.
zx_policy_timer_slack policy = {ZX_MSEC(10), ZX_TIMER_SLACK_CENTER, {}};
ASSERT_OK(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
policy = {ZX_MSEC(10), ZX_TIMER_SLACK_EARLY, {}};
ASSERT_OK(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
policy = {ZX_MSEC(10), ZX_TIMER_SLACK_LATE, {}};
ASSERT_OK(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
// Raise the minimum.
policy = {ZX_SEC(10), ZX_TIMER_SLACK_LATE, {}};
ASSERT_OK(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
// Try to lower the minimum, no error.
policy = {ZX_USEC(5), ZX_TIMER_SLACK_CENTER, {}};
ASSERT_OK(job_child.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, &policy, 1));
}
TEST(JobTest, KillTest) {
zx_handle_t job_parent = zx_job_default();
ASSERT_NE(job_parent, ZX_HANDLE_INVALID);
zx_handle_t job_child;
ASSERT_OK(zx_job_create(job_parent, 0u, &job_child));
zx_handle_t job_grandchild;
ASSERT_OK(zx_job_create(job_child, 0u, &job_grandchild));
zx_handle_t event;
ASSERT_OK(zx_event_create(0u, &event));
zx_handle_t process, thread;
ASSERT_OK(start_mini_process(job_child, event, &process, &thread));
ASSERT_OK(zx_task_kill(job_child));
zx_signals_t signals;
ASSERT_OK(zx_object_wait_one(process, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, &signals));
ASSERT_EQ(signals, ZX_TASK_TERMINATED);
ASSERT_OK(zx_object_wait_one(job_child, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, &signals));
ASSERT_EQ(signals,
ZX_TASK_TERMINATED | ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
// Process should be in the dead state here.
zx_info_job_t job_info;
ASSERT_OK(
zx_object_get_info(job_child, ZX_INFO_JOB, &job_info, sizeof(job_info), nullptr, nullptr));
EXPECT_TRUE(job_info.exited);
EXPECT_EQ(job_info.return_code, ZX_TASK_RETCODE_SYSCALL_KILL);
ASSERT_OK(zx_object_get_info(job_grandchild, ZX_INFO_JOB, &job_info, sizeof(job_info), nullptr,
nullptr));
EXPECT_TRUE(job_info.exited);
EXPECT_EQ(job_info.return_code, ZX_TASK_RETCODE_SYSCALL_KILL);
zx_info_process_t proc_info;
ASSERT_OK(zx_object_get_info(process, ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), nullptr,
nullptr));
EXPECT_TRUE(proc_info.flags & ZX_INFO_PROCESS_FLAG_EXITED);
EXPECT_EQ(proc_info.return_code, ZX_TASK_RETCODE_SYSCALL_KILL);
// Can't create more processes or jobs.
zx_handle_t job_grandchild_2;
ASSERT_STATUS(zx_job_create(job_child, 0u, &job_grandchild_2), ZX_ERR_BAD_STATE);
ASSERT_OK(zx_handle_close(thread));
ASSERT_OK(zx_handle_close(process));
ASSERT_STATUS(start_mini_process(job_child, event, &process, &thread), ZX_ERR_BAD_STATE);
ASSERT_OK(zx_handle_close(job_child));
ASSERT_OK(zx_handle_close(job_grandchild));
}
TEST(JobTest, KillJobNoChildTest) {
zx_handle_t job_parent = zx_job_default();
ASSERT_NE(job_parent, ZX_HANDLE_INVALID);
zx_handle_t job_child;
ASSERT_OK(zx_job_create(job_parent, 0u, &job_child));
ASSERT_OK(zx_task_kill(job_child));
zx_handle_t job_grandchild;
ASSERT_STATUS(zx_job_create(job_child, 0u, &job_grandchild), ZX_ERR_BAD_STATE);
zx_handle_t event;
ASSERT_OK(zx_event_create(0u, &event));
zx_handle_t process, thread;
ASSERT_STATUS(start_mini_process(job_child, event, &process, &thread), ZX_ERR_BAD_STATE);
ASSERT_OK(zx_handle_close(job_child));
}
TEST(JobTest, KillJobRemovesFromTree) {
zx_handle_t job_parent, job_child;
ASSERT_OK(zx_job_create(zx_job_default(), 0u, &job_parent));
ASSERT_OK(zx_job_create(job_parent, 0u, &job_child));
zx_info_handle_basic_t job_child_info;
ASSERT_OK(zx_object_get_info(job_child, ZX_INFO_HANDLE_BASIC, &job_child_info,
sizeof(job_child_info), nullptr, nullptr));
zx_koid_t children_koids[1] = {ZX_KOID_INVALID};
size_t num_children = 0;
ASSERT_OK(zx_object_get_info(job_parent, ZX_INFO_JOB_CHILDREN, children_koids,
sizeof(children_koids), nullptr, &num_children));
ASSERT_EQ(num_children, 1);
ASSERT_EQ(children_koids[0], job_child_info.koid);
ASSERT_OK(zx_task_kill(job_child));
ASSERT_OK(zx_object_wait_one(job_child, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, nullptr));
ASSERT_OK(zx_object_get_info(job_parent, ZX_INFO_JOB_CHILDREN, children_koids,
sizeof(children_koids), nullptr, &num_children));
ASSERT_EQ(num_children, 0);
ASSERT_OK(zx_handle_close(job_parent));
ASSERT_OK(zx_handle_close(job_child));
}
// Jobs aren't always killed, it should also be removed from the tree if the
// last handle is closed when it has no children.
TEST(JobTest, CloseJobRemovesFromTree) {
zx_handle_t job_parent, job_child;
ASSERT_OK(zx_job_create(zx_job_default(), 0u, &job_parent));
ASSERT_OK(zx_job_create(job_parent, 0u, &job_child));
zx_info_handle_basic_t job_child_info;
ASSERT_OK(zx_object_get_info(job_child, ZX_INFO_HANDLE_BASIC, &job_child_info,
sizeof(job_child_info), nullptr, nullptr));
zx_koid_t children_koids[1] = {ZX_KOID_INVALID};
size_t num_children = 0;
ASSERT_OK(zx_object_get_info(job_parent, ZX_INFO_JOB_CHILDREN, children_koids,
sizeof(children_koids), nullptr, &num_children));
ASSERT_EQ(num_children, 1);
ASSERT_EQ(children_koids[0], job_child_info.koid);
ASSERT_OK(zx_handle_close(job_child));
ASSERT_OK(zx_object_get_info(job_parent, ZX_INFO_JOB_CHILDREN, children_koids,
sizeof(children_koids), nullptr, &num_children));
ASSERT_EQ(num_children, 0);
ASSERT_OK(zx_handle_close(job_parent));
}
// Make sure a chain of jobs killed from the top cascades properly.
TEST(JobTest, KillJobChain) {
zx_handle_t jobs[5];
zx_handle_t parent = zx_job_default();
for (zx_handle_t& job : jobs) {
ASSERT_OK(zx_job_create(parent, 0u, &job));
parent = job;
zx_handle_t event, process, thread;
ASSERT_OK(zx_event_create(0u, &event));
ASSERT_OK(start_mini_process(job, event, &process, &thread));
ASSERT_OK(zx_handle_close(process));
ASSERT_OK(zx_handle_close(thread));
}
ASSERT_OK(zx_task_kill(jobs[0]));
// Jobs should terminate bottom-up, so grab the signals right when the top
// job terminates and all other jobs should have terminated as well.
zx_wait_item_t wait_items[5] = {{jobs[0], ZX_TASK_TERMINATED, 0},
{jobs[1], 0, 0},
{jobs[2], 0, 0},
{jobs[3], 0, 0},
{jobs[4], 0, 0}};
ASSERT_OK(zx_object_wait_many(wait_items, std::size(wait_items), ZX_TIME_INFINITE));
for (const zx_wait_item_t& wait_item : wait_items) {
EXPECT_EQ(wait_item.pending,
ZX_TASK_TERMINATED | ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
}
ASSERT_OK(zx_handle_close_many(jobs, std::size(jobs)));
}
TEST(JobTest, OneCriticalProcessKillsOneJob) {
// 1 job, |job|.
// 1 process, |process|.
// |process| is a child of |job|.
zx::job job;
zx::process process;
zx::thread thread;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job));
ASSERT_OK(start_mini_process(job.get(), ZX_HANDLE_INVALID, process.reset_and_get_address(),
thread.reset_and_get_address()));
ASSERT_OK(job.set_critical(0u, process));
ASSERT_OK(process.kill());
zx_signals_t observed;
ASSERT_OK(job.wait_one(ZX_JOB_TERMINATED, zx::time::infinite(), &observed));
EXPECT_EQ(observed,
ZX_JOB_TERMINATED | ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
zx_info_job_t job_info;
ASSERT_OK(job.get_info(ZX_INFO_JOB, &job_info, sizeof(job_info), nullptr, nullptr));
EXPECT_EQ(job_info.return_code, ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL);
}
TEST(JobTest, ManyCriticalProcessesKillOneJob) {
// 1 job, |job|.
// 2 processes, |process1| and |process2|.
// |process1| and |process2| are children of |job|.
zx::job job;
zx::process process1, process2;
zx::thread thread1, thread2;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job));
ASSERT_OK(start_mini_process(job.get(), ZX_HANDLE_INVALID, process1.reset_and_get_address(),
thread1.reset_and_get_address()));
ASSERT_OK(start_mini_process(job.get(), ZX_HANDLE_INVALID, process2.reset_and_get_address(),
thread2.reset_and_get_address()));
ASSERT_OK(job.set_critical(0u, process1));
ASSERT_OK(job.set_critical(0u, process2));
ASSERT_OK(process1.kill());
zx_signals_t observed;
ASSERT_OK(job.wait_one(ZX_JOB_TERMINATED, zx::time::infinite(), &observed));
EXPECT_EQ(observed,
ZX_JOB_TERMINATED | ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
zx_info_job_t job_info;
ASSERT_OK(job.get_info(ZX_INFO_JOB, &job_info, sizeof(job_info), nullptr, nullptr));
EXPECT_EQ(job_info.return_code, ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL);
zx_info_process_t process_info;
ASSERT_OK(
process2.get_info(ZX_INFO_PROCESS, &process_info, sizeof(process_info), nullptr, nullptr));
EXPECT_EQ(process_info.return_code, ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL);
}
TEST(JobTest, OneCriticalProcessKillsJobTree) {
// 2 jobs, |job1| and |job2|.
// 2 processes, |process1| and |process2|.
// |job2| is a child of |job1|.
// |process1| is a child of |job1|, and |process2| is a child of |job2|.
zx::job job1, job2;
zx::process process1, process2;
zx::thread thread1, thread2;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job1));
ASSERT_OK(zx::job::create(job1, 0u, &job2));
ASSERT_OK(start_mini_process(job1.get(), ZX_HANDLE_INVALID, process1.reset_and_get_address(),
thread1.reset_and_get_address()));
ASSERT_OK(start_mini_process(job2.get(), ZX_HANDLE_INVALID, process2.reset_and_get_address(),
thread2.reset_and_get_address()));
ASSERT_OK(job1.set_critical(0u, process2));
ASSERT_STATUS(job2.set_critical(0u, process1), ZX_ERR_INVALID_ARGS);
ASSERT_OK(process2.kill());
zx_signals_t observed1, observed2;
ASSERT_OK(job1.wait_one(ZX_JOB_TERMINATED, zx::time::infinite(), &observed1));
ASSERT_OK(job2.wait_one(ZX_JOB_TERMINATED, zx::time::infinite(), &observed2));
EXPECT_EQ(observed1,
ZX_JOB_TERMINATED | ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
EXPECT_EQ(observed2,
ZX_JOB_TERMINATED | ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
zx_info_job_t job_info1, job_info2;
ASSERT_OK(job1.get_info(ZX_INFO_JOB, &job_info1, sizeof(job_info1), nullptr, nullptr));
ASSERT_OK(job2.get_info(ZX_INFO_JOB, &job_info2, sizeof(job_info2), nullptr, nullptr));
EXPECT_EQ(job_info1.return_code, ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL);
EXPECT_EQ(job_info2.return_code, ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL);
}
TEST(JobTest, OneCriticalProcessKillsOneJobIfRetcodeNonzero) {
// 1 job, |job|.
// 1 process, |process|.
// |process| is a child of |job|.
zx::job job;
zx::process process;
zx::thread thread;
zx::vmar vmar;
zx::channel channel;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job));
ASSERT_OK(zx::process::create(job, "", 0u, 0u, &process, &vmar));
ASSERT_OK(zx::thread::create(process, "", 0u, 0u, &thread));
ASSERT_OK(start_mini_process_etc(process.get(), thread.get(), vmar.get(), ZX_HANDLE_INVALID, true,
channel.reset_and_get_address()));
ASSERT_OK(job.set_critical(ZX_JOB_CRITICAL_PROCESS_RETCODE_NONZERO, process));
ASSERT_OK(mini_process_cmd_send(channel.get(), MINIP_CMD_EXIT_NORMAL));
zx_signals_t observed;
ASSERT_OK(job.wait_one(ZX_JOB_NO_PROCESSES, zx::time::infinite(), &observed));
EXPECT_EQ(observed, ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
}
TEST(JobTest, CriticalProcessNotInAncestor) {
// 1 job, |job|.
// 1 process, |process|.
// |process| is not a child of |job|.
zx::job job;
zx::process process;
zx::thread thread;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job));
ASSERT_OK(start_mini_process(zx_job_default(), ZX_HANDLE_INVALID, process.reset_and_get_address(),
thread.reset_and_get_address()));
ASSERT_STATUS(job.set_critical(0u, process), ZX_ERR_INVALID_ARGS);
ASSERT_OK(process.kill());
}
TEST(JobTest, CriticalProcessAlreadySet) {
// 1 job, |job|.
// 1 process, |process|.
// |process| is a child of |job|.
zx::job job;
zx::process process;
zx::thread thread;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job));
ASSERT_OK(start_mini_process(job.get(), ZX_HANDLE_INVALID, process.reset_and_get_address(),
thread.reset_and_get_address()));
ASSERT_OK(job.set_critical(0u, process));
ASSERT_STATUS(job.set_critical(0u, process), ZX_ERR_ALREADY_BOUND);
ASSERT_OK(process.kill());
zx_signals_t observed;
ASSERT_OK(job.wait_one(ZX_JOB_TERMINATED, zx::time::infinite(), &observed));
EXPECT_EQ(observed,
ZX_JOB_TERMINATED | ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
zx_info_job_t job_info;
ASSERT_OK(job.get_info(ZX_INFO_JOB, &job_info, sizeof(job_info), nullptr, nullptr));
EXPECT_EQ(job_info.return_code, ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL);
}
TEST(JobTest, SetJobOomKillBit) {
// TODO(cpu): Other than trivial set/reset of the property this can't be
// fully tested without de-establizing the system under test. The current
// best way to test this is to boot the full stack and issue in a console
// $k oom lowmem
// And watch the kernel log output.
size_t oom = 1u;
ASSERT_OK(
zx_object_set_property(zx_job_default(), ZX_PROP_JOB_KILL_ON_OOM, &oom, sizeof(size_t)));
oom = 0u;
ASSERT_OK(
zx_object_set_property(zx_job_default(), ZX_PROP_JOB_KILL_ON_OOM, &oom, sizeof(size_t)));
oom = 2u;
ASSERT_STATUS(
zx_object_set_property(zx_job_default(), ZX_PROP_JOB_KILL_ON_OOM, &oom, sizeof(size_t)),
ZX_ERR_INVALID_ARGS);
}
TEST(JobTest, WaitTest) {
zx_handle_t job_parent = zx_job_default();
ASSERT_NE(job_parent, ZX_HANDLE_INVALID);
zx_handle_t job_child;
ASSERT_OK(zx_job_create(job_parent, 0u, &job_child));
zx_handle_t event;
ASSERT_OK(zx_event_create(0u, &event));
zx_handle_t process, thread;
ASSERT_OK(start_mini_process(job_child, event, &process, &thread));
zx_signals_t signals;
ASSERT_OK(zx_object_wait_one(job_child, ZX_JOB_NO_JOBS, ZX_TIME_INFINITE, &signals));
ASSERT_EQ(signals, ZX_JOB_NO_JOBS);
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
ASSERT_OK(zx_task_kill(process));
ASSERT_OK(zx_object_wait_one(job_child, ZX_JOB_NO_PROCESSES, ZX_TIME_INFINITE, &signals));
ASSERT_EQ(signals, ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS | ZX_JOB_NO_CHILDREN);
ASSERT_OK(zx_handle_close(thread));
ASSERT_OK(zx_handle_close(process));
ASSERT_OK(zx_handle_close(job_child));
}
TEST(JobTest, InfoTaskStatsFails) {
zx_info_task_stats_t info;
ASSERT_NOT_OK(zx_object_get_info(zx_job_default(), ZX_INFO_TASK_STATS, &info, sizeof(info),
nullptr, nullptr),
"Just added job support to info_task_status?");
// If so, replace this with a real test; see example in process.cpp.
}
// Show that there is a max job height.
TEST(JobTest, MaxHeightSmoke) {
// Get our parent job.
zx_handle_t parent_job = zx_job_default();
// Stack of handles that we need to close.
static constexpr int kNumJobs = 128;
zx_handle_t* handles = static_cast<zx_handle_t*>(calloc(kNumJobs, sizeof(*handles)));
ASSERT_TRUE(handles);
zx_handle_t* htop = handles;
// Eat up our max height.
while (true) {
zx_handle_t child_job;
zx_status_t s = zx_job_create(parent_job, 0u, &child_job);
if (s != ZX_OK) {
break;
}
// We should hit the max before running out of entries;
// this is the core check of this test.
ASSERT_LT(htop - handles, kNumJobs, "Should have seen the max job height");
*htop++ = child_job;
parent_job = child_job;
}
// We've hit the bottom. Creating a child under this job should fail.
zx_handle_t child_job;
EXPECT_STATUS(zx_job_create(parent_job, 0u, &child_job), ZX_ERR_OUT_OF_RANGE);
// Creating a process should succeed, though.
zx_handle_t child_proc;
zx_handle_t vmar;
ASSERT_OK(zx_process_create(parent_job, "test", sizeof("test"), 0u, &child_proc, &vmar));
zx_handle_close(vmar);
zx_handle_close(child_proc);
// Clean up.
while (htop > handles) {
EXPECT_OK(zx_handle_close(*--htop));
}
free(handles);
}
template <typename InfoT>
static void TestJobGetInfoRuntime(const uint32_t topic) {
zx::job job_child;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0u, &job_child));
InfoT info;
size_t actual = 0;
size_t avail = 0;
ASSERT_OK(job_child.get_info(topic, &info, sizeof(info), &actual, &avail));
EXPECT_EQ(info.cpu_time, 0);
EXPECT_EQ(info.queue_time, 0);
EXPECT_EQ(actual, 1);
EXPECT_EQ(avail, 1);
zx::event event;
ASSERT_OK(zx::event::create(0, &event));
zx::process process;
zx::thread thread;
ASSERT_OK(start_mini_process(job_child.get(), event.get(), process.reset_and_get_address(),
thread.reset_and_get_address()));
ASSERT_OK(thread.wait_one(ZX_THREAD_RUNNING, zx::time::infinite(), nullptr));
ASSERT_OK(job_child.get_info(topic, &info, sizeof(info), nullptr, nullptr));
EXPECT_GT(info.cpu_time, 0);
EXPECT_GT(info.queue_time, 0);
// Check we can still read the task runtimes after the job is terminates, and that they don't
// change.
ASSERT_OK(job_child.kill());
ASSERT_OK(job_child.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), nullptr));
ASSERT_OK(job_child.get_info(topic, &info, sizeof(info), nullptr, nullptr));
EXPECT_GT(info.cpu_time, 0);
EXPECT_GT(info.queue_time, 0);
zx_info_task_runtime_t info2;
ASSERT_OK(job_child.get_info(topic, &info2, sizeof(info2), nullptr, nullptr));
EXPECT_EQ(info.cpu_time, info2.cpu_time);
EXPECT_EQ(info.queue_time, info2.queue_time);
// Check that we cannot get info anymore if we remove ZX_RIGHT_INSPECT
zx_info_handle_basic_t basic;
ASSERT_OK(job_child.get_info(ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, nullptr));
zx::job job_child_dup;
ASSERT_OK(job_child.duplicate(basic.rights & ~ZX_RIGHT_INSPECT, &job_child_dup));
EXPECT_EQ(job_child_dup.get_info(topic, &info, sizeof(info), nullptr, nullptr),
ZX_ERR_ACCESS_DENIED);
}
TEST(JobTest, GetInfoRuntime) {
TestJobGetInfoRuntime<zx_info_task_runtime_t>(ZX_INFO_TASK_RUNTIME);
}
TEST(JobTest, GetInfoRuntimeV1) {
TestJobGetInfoRuntime<zx_info_task_runtime_v1_t>(ZX_INFO_TASK_RUNTIME_V1);
}
} // namespace