blob: ceabc672fe12495bdc948a00a9fa0a93df289da9 [file] [log] [blame]
// Copyright 2018 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 <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fbl/algorithm.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/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include <zircon/threads.h>
#include <test-utils/test-utils.h>
#include <unittest/unittest.h>
static int thread_func(void* arg);
// argv[0]
static char* program_path;
// We have to poll a thread's state as there is no way to wait for it to
// transition states. Wait this amount of time. Generally the thread won't
// take very long so this is a compromise between polling too frequently and
// waiting too long.
constexpr zx_duration_t THREAD_BLOCKED_WAIT_DURATION = ZX_MSEC(1);
static const char test_child_name[] = "test-child";
// The maximum number of handles we send with send_msg_with_handles.
constexpr uint32_t MAX_NUM_MSG_HANDLES = 2;
// The number of handles used in the wait-many test.
constexpr uint32_t NUM_WAIT_MANY_HANDLES = MAX_NUM_MSG_HANDLES;
const zx_port_packet_t port_test_packet = {
.key = 42u,
.type = ZX_PKT_TYPE_USER,
.status = -42,
{ .user = { .u64 = { 1, 2, 3, 4 } } }
};
const zx_time_t interrupt_signaled_timestamp = 12345;
enum MessageType : uint32_t {
MSG_DONE,
MSG_PASS,
MSG_FAIL,
MSG_PROCEED,
MSG_THREAD_HANDLE_REQUEST,
MSG_THREAD_HANDLE_RESPONSE,
MSG_SLEEP_TEST,
MSG_FUTEX_TEST,
MSG_PORT_TEST,
MSG_CHANNEL_TEST,
MSG_WAIT_ONE_TEST,
MSG_WAIT_MANY_TEST,
MSG_INTERRUPT_TEST,
};
struct Message {
MessageType type;
uint32_t num_handles;
zx_handle_t handles[MAX_NUM_MSG_HANDLES];
};
static zx_status_t get_root_resource(zx_handle_t* root_resource) {
int fd = open("/dev/misc/sysinfo", O_RDWR);
if (fd < 0) {
unittest_printf("ERROR: Cannot open sysinfo: %d/%s\n",
errno, strerror(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) {
fprintf(stderr, "ERROR: Cannot obtain sysinfo channel: %s (%d)\n",
zx_status_get_string(status), status);
return status;
}
zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetRootResource(channel.get(), &status,
root_resource);
if (fidl_status != ZX_OK) {
fprintf(stderr, "ERROR: Cannot obtain root resource: %s (%d)\n",
zx_status_get_string(fidl_status), fidl_status);
return fidl_status;
} else if (status != ZX_OK) {
fprintf(stderr, "ERROR: Cannot obtain root resource: %s (%d)\n",
zx_status_get_string(status), status);
return status;
}
return ZX_OK;
}
static void send_msg_with_handles(zx_handle_t channel, MessageType type,
zx_handle_t* optional_handles, uint32_t num_handles) {
uint32_t data = type;
unittest_printf("sending message %d on handle %u, with %u handles\n",
type, channel, num_handles);
tu_channel_write(channel, 0, &data, sizeof(data), optional_handles, num_handles);
}
static void send_msg(zx_handle_t channel, MessageType type) {
send_msg_with_handles(channel, type, nullptr, 0);
}
static bool recv_msg(zx_handle_t channel, Message* msg) {
unittest_printf("waiting for message on handle %u\n", channel);
if (!tu_channel_wait_readable(channel)) {
unittest_printf("peer closed while trying to read message\n");
return false;
}
uint32_t data;
uint32_t num_bytes = sizeof(data);
tu_channel_read(channel, 0, &data, &num_bytes, &msg->handles[0], &msg->num_handles);
if (num_bytes != sizeof(data)) {
unittest_printf("ERROR: unexpected message size, %u != %zu\n",
num_bytes, sizeof(data));
return false;
}
msg->type = static_cast<MessageType>(data);
unittest_printf("received message %u\n", msg->type);
return true;
}
static bool recv_specific_msg(zx_handle_t channel, MessageType expected_type) {
Message msg;
ASSERT_TRUE(recv_msg(channel, &msg));
ASSERT_EQ(msg.type, expected_type);
return true;
}
static void do_msg_thread_handle_request(zx_handle_t channel, const Message* msg) {
if (msg->num_handles != 0) {
unittest_printf("ERROR: wrong number handles\n");
send_msg(channel, MSG_FAIL);
return;
}
auto self = zx_thread_self();
send_msg_with_handles(channel, MSG_THREAD_HANDLE_RESPONSE, &self, 1);
}
static void do_msg_sleep_test(zx_handle_t channel, const Message* msg) {
if (msg->num_handles != 0) {
unittest_printf("ERROR: wrong number handles\n");
// There's no point in sending MSG_FAIL here as the test can never
// receive MSG_PASS.
return;
}
zx_nanosleep(ZX_TIME_INFINITE);
/* NOTREACHED */
}
static void do_msg_futex_test(zx_handle_t channel, const Message* msg) {
if (msg->num_handles != 0) {
unittest_printf("ERROR: wrong number handles\n");
// There's no point in sending MSG_FAIL here as the test can never
// receive MSG_PASS.
return;
}
int futex_value = 0;
zx_status_t __UNUSED status = zx_futex_wait(&futex_value, 0,
ZX_HANDLE_INVALID,
ZX_TIME_INFINITE);
/* NOTREACHED*/
}
static void do_msg_port_test(zx_handle_t channel, const Message* msg) {
if (msg->num_handles != 1) {
unittest_printf("ERROR: wrong number handles\n");
send_msg(channel, MSG_FAIL);
return;
}
auto port = msg->handles[0];
zx_port_packet_t packet;
auto status = zx_port_wait(port, ZX_TIME_INFINITE, &packet);
tu_handle_close(port);
if (status != ZX_OK) {
unittest_printf("ERROR: port_wait failed: %d/%s\n", status,
zx_status_get_string(status));
send_msg(channel, MSG_FAIL);
return;
}
if (packet.key != port_test_packet.key ||
packet.type != port_test_packet.type ||
packet.status != port_test_packet.status ||
memcmp(&packet.user, &port_test_packet.user, sizeof(zx_port_packet_t::user)) != 0) {
unittest_printf("ERROR: bad data in packet\n");
send_msg(channel, MSG_FAIL);
return;
}
send_msg(channel, MSG_PASS);
}
static void do_msg_channel_test(zx_handle_t channel, const Message* msg) {
if (msg->num_handles != 1) {
unittest_printf_critical("ERROR: wrong number handles\n");
send_msg(channel, MSG_FAIL);
return;
}
auto test_channel = msg->handles[0];
uint32_t write_data = 0xdeadbeef;
uint32_t read_data;
zx_channel_call_args_t args = {
.wr_bytes = &write_data,
.wr_handles = nullptr,
.rd_bytes = &read_data,
.rd_handles = nullptr,
.wr_num_bytes = sizeof(write_data),
.wr_num_handles = 0,
.rd_num_bytes = sizeof(read_data),
.rd_num_handles = 0,
};
uint32_t actual_num_bytes = 0;
uint32_t actual_num_handles = 0;
auto status = zx_channel_call(test_channel, 0, ZX_TIME_INFINITE, &args,
&actual_num_bytes, &actual_num_handles);
tu_handle_close(test_channel);
if (status == ZX_ERR_PEER_CLOSED) {
// ok
} else {
unittest_printf_critical("ERROR: channel_call didn't get PEER_CLOSED: %d/%s\n",
status, zx_status_get_string(status));
send_msg(channel, MSG_FAIL);
return;
}
send_msg(channel, MSG_PASS);
}
static void do_msg_wait_one_test(zx_handle_t channel, const Message* msg) {
if (msg->num_handles != 1) {
unittest_printf("ERROR: wrong number handles\n");
send_msg(channel, MSG_FAIL);
return;
}
// The test waits for this to make sure it doesn't see us blocked waiting
// for a Message. This is sent for wait_one and wait_many so that we don't
// have to know which one is used to wait for messages.
send_msg(channel, MSG_PROCEED);
zx_signals_t observed = 0u;
auto status = zx_object_wait_one(msg->handles[0], ZX_EVENTPAIR_PEER_CLOSED,
ZX_TIME_INFINITE, &observed);
tu_handle_close(msg->handles[0]);
if (status != ZX_OK) {
unittest_printf("ERROR: wait_one failed: %d/%s\n", status,
zx_status_get_string(status));
send_msg(channel, MSG_FAIL);
return;
}
if (!(observed & ZX_EVENTPAIR_PEER_CLOSED)) {
unittest_printf("ERROR: ZX_EVENTPAIR_PEER_CLOSED not observed\n");
send_msg(channel, MSG_FAIL);
return;
}
send_msg(channel, MSG_PASS);
}
static void do_msg_wait_many_test(zx_handle_t channel, const Message* msg) {
if (msg->num_handles > NUM_WAIT_MANY_HANDLES) {
unittest_printf("ERROR: too many handles\n");
send_msg(channel, MSG_FAIL);
return;
}
// The test waits for this to make sure it doesn't see us blocked waiting
// for a Message. This is sent for wait_one and wait_many so that we don't
// have to know which one is used to wait for messages.
send_msg(channel, MSG_PROCEED);
uint32_t num_handles = msg->num_handles;
zx_wait_item_t items[num_handles];
for (uint32_t i = 0; i < num_handles; ++i) {
items[i].handle = msg->handles[i];
items[i].waitfor = ZX_EVENTPAIR_PEER_CLOSED;
}
auto status = zx_object_wait_many(&items[0], num_handles, ZX_TIME_INFINITE);
for (uint32_t i = 0; i < num_handles; ++i) {
tu_handle_close(msg->handles[i]);
}
if (status != ZX_OK) {
unittest_printf("ERROR: wait_many failed: %d/%s\n", status,
zx_status_get_string(status));
send_msg(channel, MSG_FAIL);
return;
}
// At least one of the handles should have gotten PEER_CLOSED.
bool got_peer_closed = false;
for (uint32_t i = 0; i < num_handles; ++i) {
if (items[i].pending & ZX_EVENTPAIR_PEER_CLOSED) {
got_peer_closed = true;
break;
}
}
if (!got_peer_closed) {
unittest_printf("ERROR: ZX_EVENTPAIR_PEER_CLOSED not observed\n");
send_msg(channel, MSG_FAIL);
return;
}
send_msg(channel, MSG_PASS);
}
static void do_msg_interrupt_test(zx_handle_t channel, const Message* msg) {
if (msg->num_handles != 1) {
unittest_printf("ERROR: wrong number handles\n");
send_msg(channel, MSG_FAIL);
return;
}
auto interrupt = msg->handles[0];
zx_time_t timestamp;
auto status = zx_interrupt_wait(interrupt, &timestamp);
tu_handle_close(interrupt);
if (status != ZX_OK) {
unittest_printf("ERROR: interrupt_wait failed: %d/%s\n", status,
zx_status_get_string(status));
send_msg(channel, MSG_FAIL);
return;
}
if (timestamp != interrupt_signaled_timestamp) {
unittest_printf("ERROR: interrupt timestamp mismatch\n");
send_msg(channel, MSG_FAIL);
return;
}
send_msg(channel, MSG_PASS);
}
static void msg_loop(zx_handle_t channel) {
bool my_done_tests = false;
while (!my_done_tests) {
Message msg;
msg.num_handles = static_cast<uint32_t>(fbl::count_of(msg.handles));
if (!recv_msg(channel, &msg)) {
unittest_printf("ERROR: while receiving msg\n");
return;
}
switch (msg.type) {
case MSG_DONE:
my_done_tests = true;
break;
case MSG_THREAD_HANDLE_REQUEST:
do_msg_thread_handle_request(channel, &msg);
break;
case MSG_SLEEP_TEST:
do_msg_sleep_test(channel, &msg);
break;
case MSG_FUTEX_TEST:
do_msg_futex_test(channel, &msg);
break;
case MSG_PORT_TEST:
do_msg_port_test(channel, &msg);
break;
case MSG_CHANNEL_TEST:
do_msg_channel_test(channel, &msg);
break;
case MSG_WAIT_ONE_TEST:
do_msg_wait_one_test(channel, &msg);
break;
case MSG_WAIT_MANY_TEST:
do_msg_wait_many_test(channel, &msg);
break;
case MSG_INTERRUPT_TEST:
do_msg_interrupt_test(channel, &msg);
break;
default:
unittest_printf("ERROR: unknown message received: %u\n", msg.type);
break;
}
}
}
static int thread_func(void* arg) {
unittest_printf("test thread starting\n");
zx_handle_t msg_channel = (zx_handle_t) (uintptr_t) arg;
msg_loop(msg_channel);
unittest_printf("test thread exiting\n");
tu_handle_close(msg_channel);
return 0;
}
static void __NO_RETURN test_child(void) {
unittest_printf("Test child starting.\n");
zx_handle_t channel = zx_take_startup_handle(PA_USER0);
if (channel == ZX_HANDLE_INVALID)
tu_fatal("zx_take_startup_handle", ZX_ERR_BAD_HANDLE - 1000);
msg_loop(channel);
unittest_printf("Test child exiting.\n");
exit(0);
}
static launchpad_t* setup_test_child(zx_handle_t job, const char* arg,
zx_handle_t* out_channel) {
unittest_printf("Starting test child %s.\n", arg);
zx_handle_t our_channel, their_channel;
tu_channel_create(&our_channel, &their_channel);
const char* test_child_path = program_path;
const char verbosity_string[] =
{ 'v', '=', static_cast<char>(utest_verbosity_level + '0'), '\0' };
const char* const argv[] = {
test_child_path,
arg,
verbosity_string,
};
int argc = fbl::count_of(argv);
zx_handle_t handles[1] = { their_channel };
uint32_t handle_ids[1] = { PA_USER0 };
*out_channel = our_channel;
launchpad_t* lp = tu_launch_fdio_init(job, test_child_name, argc, argv,
NULL, 1, handles, handle_ids);
unittest_printf("Test child setup.\n");
return lp;
}
static void start_test_child(zx_handle_t job, const char* arg,
zx_handle_t* out_child, zx_handle_t* out_channel) {
launchpad_t* lp = setup_test_child(job, arg, out_channel);
*out_child = tu_launch_fdio_fini(lp);
unittest_printf("Test child started.\n");
}
static bool get_child_thread(zx_handle_t channel, zx_handle_t* thread) {
send_msg(channel, MSG_THREAD_HANDLE_REQUEST);
Message msg;
ASSERT_TRUE(recv_msg(channel, &msg));
EXPECT_EQ(msg.type, MSG_THREAD_HANDLE_RESPONSE);
EXPECT_EQ(msg.num_handles, 1u);
*thread = msg.handles[0];
return true;
}
// Wait for |thread| to enter blocked state |reason|.
// We wait forever and let Unittest's watchdog handle errors.
static void wait_thread_blocked(zx_handle_t thread, uint32_t reason) {
while (true) {
auto state = tu_thread_get_state(thread);
if (state == reason)
break;
zx_nanosleep(zx_deadline_after(THREAD_BLOCKED_WAIT_DURATION));
}
}
// Terminate |process| by killing it and wait for it to exit.
static void terminate_process(zx_handle_t process) {
tu_task_kill(process);
tu_process_wait_signaled(process);
}
static bool sleeping_test() {
BEGIN_TEST;
// Start a new process.
zx_handle_t child, channel;
start_test_child(zx_job_default(), test_child_name, &child, &channel);
zx_handle_t thread;
ASSERT_TRUE(get_child_thread(channel, &thread));
send_msg(channel, MSG_SLEEP_TEST);
// There is no good way to do this test without having the child
// sleep forever and then kill it: There's no way to interrupt the sleep,
// and there's no good value for the amount of time to sleep.
wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_SLEEPING);
terminate_process(child);
END_TEST;
}
static bool futex_test() {
BEGIN_TEST;
// Start a new process.
zx_handle_t child, channel;
start_test_child(zx_job_default(), test_child_name, &child, &channel);
zx_handle_t thread;
ASSERT_TRUE(get_child_thread(channel, &thread));
send_msg_with_handles(channel, MSG_FUTEX_TEST, nullptr, 0);
wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_FUTEX);
terminate_process(child);
END_TEST;
}
static bool port_test() {
BEGIN_TEST;
// Start a new process.
zx_handle_t child, channel;
start_test_child(zx_job_default(), test_child_name, &child, &channel);
zx_handle_t thread;
ASSERT_TRUE(get_child_thread(channel, &thread));
zx_handle_t port;
ASSERT_EQ(zx_port_create(0, &port), ZX_OK);
zx_handle_t port_dupe = tu_handle_duplicate(port);
send_msg_with_handles(channel, MSG_PORT_TEST, &port_dupe, 1);
wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_PORT);
// Wake the child up.
EXPECT_EQ(zx_port_queue(port, &port_test_packet), ZX_OK);
// The child sends a pass/fail message back as extra verification that
// things went correctly on that side.
ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS));
tu_handle_close(port);
terminate_process(child);
END_TEST;
}
static bool channel_test() {
BEGIN_TEST;
// Start a new process.
zx_handle_t child, channel;
start_test_child(zx_job_default(), test_child_name, &child, &channel);
zx_handle_t thread;
ASSERT_TRUE(get_child_thread(channel, &thread));
zx_handle_t our_channel, their_channel;
ASSERT_EQ(zx_channel_create(0, &our_channel, &their_channel), ZX_OK);
send_msg_with_handles(channel, MSG_CHANNEL_TEST, &their_channel, 1);
wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_CHANNEL);
// Wake the child up.
tu_handle_close(our_channel);
// The child sends a pass/fail message back as extra verification that
// things went correctly on that side.
ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS));
terminate_process(child);
END_TEST;
}
static bool wait_one_test() {
BEGIN_TEST;
// Start a new process.
zx_handle_t child, channel;
start_test_child(zx_job_default(), test_child_name, &child, &channel);
zx_handle_t thread;
ASSERT_TRUE(get_child_thread(channel, &thread));
zx_handle_t h[2];
ASSERT_EQ(zx_eventpair_create(0, &h[0], &h[1]), ZX_OK);
send_msg_with_handles(channel, MSG_WAIT_ONE_TEST, &h[1], 1);
// Don't continue until we see MSG_PROCEED, that tells us the child has
// received the message and isn't in a wait_one/wait_many syscall.
ASSERT_TRUE(recv_specific_msg(channel, MSG_PROCEED));
wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_WAIT_ONE);
// Wake the child up.
tu_handle_close(h[0]);
// The child sends a pass/fail message back as extra verification that
// things went correctly on that side.
ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS));
terminate_process(child);
END_TEST;
}
static bool wait_many_test() {
BEGIN_TEST;
// Start a new process.
zx_handle_t child, channel;
start_test_child(zx_job_default(), test_child_name, &child, &channel);
zx_handle_t thread;
ASSERT_TRUE(get_child_thread(channel, &thread));
uint32_t num_handles = NUM_WAIT_MANY_HANDLES;
zx_handle_t h[2][num_handles];
for (uint32_t i = 0; i < num_handles; ++i) {
ASSERT_EQ(zx_eventpair_create(0, &h[0][i], &h[1][i]), ZX_OK);
}
send_msg_with_handles(channel, MSG_WAIT_MANY_TEST, &h[1][0], num_handles);
// Don't continue until we see MSG_PROCEED, that tells us the child has
// received the message and isn't in a wait_one/wait_many syscall.
ASSERT_TRUE(recv_specific_msg(channel, MSG_PROCEED));
wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_WAIT_MANY);
// Wake the child up.
for (uint32_t i = 0; i < num_handles; ++i) {
tu_handle_close(h[0][i]);
}
// The child sends a pass/fail message back as extra verification that
// things went correctly on that side.
ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS));
terminate_process(child);
END_TEST;
}
// Just like wait_many_test except the child doesn't wait on any objects, just
// the (infinite) timeout.
static bool wait_many_no_objects_test() {
BEGIN_TEST;
// Start a new process.
zx_handle_t child, channel;
start_test_child(zx_job_default(), test_child_name, &child, &channel);
zx_handle_t thread;
ASSERT_TRUE(get_child_thread(channel, &thread));
send_msg(channel, MSG_WAIT_MANY_TEST);
// Don't continue until we see MSG_PROCEED, that tells us the child has
// received the message and isn't in a wait_one/wait_many syscall.
ASSERT_TRUE(recv_specific_msg(channel, MSG_PROCEED));
wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_WAIT_MANY);
// The child won't be sending a pass/fail message back because it's stuck in
// object_wait_many so just kill it.
terminate_process(child);
END_TEST;
}
static bool interrupt_test() {
BEGIN_TEST;
// Start a new process.
zx_handle_t child, channel;
start_test_child(zx_job_default(), test_child_name, &child, &channel);
zx_handle_t thread;
ASSERT_TRUE(get_child_thread(channel, &thread));
zx_handle_t resource;
ASSERT_EQ(get_root_resource(&resource), ZX_OK);
zx_handle_t interrupt;
ASSERT_EQ(zx_interrupt_create(resource, 0, ZX_INTERRUPT_VIRTUAL, &interrupt),
ZX_OK);
zx_handle_t interrupt_dupe = tu_handle_duplicate(interrupt);
send_msg_with_handles(channel, MSG_INTERRUPT_TEST, &interrupt_dupe, 1);
wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_INTERRUPT);
// Wake the child up.
EXPECT_EQ(zx_interrupt_trigger(interrupt, 0, interrupt_signaled_timestamp), ZX_OK);
// The child sends a pass/fail message back as extra verification that
// things went correctly on that side.
ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS));
tu_handle_close(interrupt);
terminate_process(child);
END_TEST;
}
BEGIN_TEST_CASE(thread_state_tests)
// ZX_THREAD_STATE_BLOCKED_EXCEPTION is tested in utest/exception.
// There's a lot of support logic and there's no reason to duplicate it here.
RUN_TEST(sleeping_test);
RUN_TEST(futex_test);
RUN_TEST(port_test);
RUN_TEST(channel_test);
RUN_TEST(wait_one_test);
RUN_TEST(wait_many_test);
RUN_TEST(wait_many_no_objects_test);
RUN_TEST(interrupt_test);
END_TEST_CASE(thread_state_tests)
static void scan_argv(int argc, char** argv) {
for (int i = 1; i < argc; ++i) {
if (strncmp(argv[i], "v=", 2) == 0) {
int verbosity = atoi(argv[i] + 2);
unittest_set_verbosity_level(verbosity);
}
}
}
int main(int argc, char **argv) {
program_path = argv[0];
scan_argv(argc, argv);
if (argc >= 2) {
if (strcmp(argv[1], test_child_name) == 0) {
test_child();
return 0;
}
}
bool success = unittest_run_all_tests(argc, argv);
return success ? 0 : -1;
}