// 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 #include #include #include #include #include #include #include // returns an int to avoid sign errors from ASSERT_*() static int fd_signals(int fd) { uint32_t signals = 0; fdio_wait_fd(fd, 0, &signals, 0); return signals; } static int write_full(int fd) { char tmp[300]; memset(tmp, 0x33, sizeof(tmp)); int total = 0; for (;;) { int r = write(fd, tmp, sizeof(tmp)); if (r < 0) { if (errno == EAGAIN) { break; } return -errno; } if (r == 0) { break; } total += r; } return total; } static int read_all(int fd) { char tmp[700]; int total = 0; for (;;) { int r = read(fd, tmp, sizeof(tmp)); if (r < 0) { if (errno == EAGAIN) { break; } return -errno; } if (r == 0) { break; } for (int n = 0; n < r; n++) { if (tmp[n] != 0x33) { return -EFAULT; } } total += r; } return total; } static bool pty_test(void) { BEGIN_TEST; int ps = open("/dev/misc/ptmx", O_RDWR | O_NONBLOCK); ASSERT_GE(ps, 0, ""); int pc = openat(ps, "0", O_RDWR | O_NONBLOCK); ASSERT_GE(pc, 0, ""); char tmp[32]; ASSERT_EQ(fd_signals(ps), POLLOUT, ""); ASSERT_EQ(fd_signals(pc), POLLOUT, ""); // nothing to read ASSERT_EQ(read(ps, tmp, 32), -1, ""); ASSERT_EQ(errno, EAGAIN, ""); ASSERT_EQ(read(pc, tmp, 32), -1, ""); ASSERT_EQ(errno, EAGAIN, ""); // write server, read client ASSERT_EQ(write(ps, "xyzzy", 5), 5, ""); ASSERT_EQ(fd_signals(pc), POLLIN | POLLOUT, ""); memset(tmp, 0xee, 5); ASSERT_EQ(read(pc, tmp, 5), 5, ""); ASSERT_EQ(memcmp(tmp, "xyzzy", 5), 0, ""); ASSERT_EQ(fd_signals(pc), POLLOUT, ""); // write client, read server ASSERT_EQ(write(pc, "xyzzy", 5), 5, ""); ASSERT_EQ(fd_signals(ps), POLLIN | POLLOUT, ""); memset(tmp, 0xee, 5); ASSERT_EQ(read(ps, tmp, 5), 5, ""); ASSERT_EQ(memcmp(tmp, "xyzzy", 5), 0, ""); ASSERT_EQ(fd_signals(ps), POLLOUT, ""); // write server until full, then drain ASSERT_EQ(write_full(ps), 4096, ""); ASSERT_EQ(fd_signals(ps), 0, ""); ASSERT_EQ(read_all(pc), 4096, ""); ASSERT_EQ(fd_signals(ps), POLLOUT, ""); // write client until full, then drain ASSERT_EQ(write_full(pc), 4096, ""); ASSERT_EQ(fd_signals(pc), 0, ""); ASSERT_EQ(read_all(ps), 4096, ""); ASSERT_EQ(fd_signals(pc), POLLOUT, ""); // verify no events pending uint32_t events; ASSERT_EQ(ioctl_pty_read_events(pc, &events), (ssize_t)sizeof(events), ""); ASSERT_EQ(events, 0u, ""); // write a ctrl-c ASSERT_EQ(write(ps, "\x03", 1), 1, ""); // should be an event now ASSERT_EQ(ioctl_pty_read_events(pc, &events), (ssize_t)sizeof(events), ""); ASSERT_EQ(events, PTY_EVENT_INTERRUPT, ""); // should vanish once we read it ASSERT_EQ(ioctl_pty_read_events(pc, &events), (ssize_t)sizeof(events), ""); ASSERT_EQ(events, 0u, ""); // write something containing a special char // should write up to and including the special char // converting the special char to a signal ASSERT_EQ(write(ps, "hello\x03world", 11), 6, ""); ASSERT_EQ(read(pc, tmp, 6), 5, ""); ASSERT_EQ(memcmp(tmp, "hello", 5), 0, ""); ASSERT_EQ(ioctl_pty_read_events(pc, &events), (ssize_t)sizeof(events), ""); ASSERT_EQ(events, PTY_EVENT_INTERRUPT, ""); pty_window_size_t ws; ASSERT_EQ(ioctl_pty_get_window_size(pc, &ws), (ssize_t)sizeof(ws), ""); ASSERT_EQ(ws.width, 0u, ""); ASSERT_EQ(ws.height, 0u, ""); ws.width = 80; ws.height = 25; ASSERT_EQ(ioctl_pty_set_window_size(ps, &ws), 0, ""); ASSERT_EQ(ioctl_pty_get_window_size(pc, &ws), (ssize_t)sizeof(ws), ""); ASSERT_EQ(ws.width, 80u, ""); ASSERT_EQ(ws.height, 25u, ""); // verify that we don't get events for special chars in raw mode pty_clr_set_t cs = { .clr = 0, .set = PTY_FEATURE_RAW, }; ASSERT_EQ(ioctl_pty_clr_set_feature(pc, &cs), 0, ""); ASSERT_EQ(write(ps, "\x03", 1), 1, ""); ASSERT_EQ(read(pc, tmp, 1), 1, ""); ASSERT_EQ(tmp[0], '\x03', ""); ASSERT_EQ(ioctl_pty_read_events(pc, &events), (ssize_t)sizeof(events), ""); ASSERT_EQ(events, 0u, ""); // create a second client int pc1 = openat(pc, "1", O_RDWR | O_NONBLOCK); ASSERT_GE(pc1, 0, ""); // reads/writes to non-active client should block ASSERT_EQ(fd_signals(pc1), 0, ""); ASSERT_EQ(write(pc1, "test", 4), -1, ""); ASSERT_EQ(errno, EAGAIN, ""); ASSERT_EQ(read(pc1, tmp, 4), -1, ""); ASSERT_EQ(errno, EAGAIN, ""); uint32_t n = 2; ASSERT_EQ(ioctl_pty_make_active(pc, &n), ZX_ERR_NOT_FOUND, ""); // non-controlling client cannot change active client ASSERT_EQ(ioctl_pty_make_active(pc1, &n), ZX_ERR_ACCESS_DENIED, ""); // but controlling client can n = 1; ASSERT_EQ(ioctl_pty_make_active(pc, &n), ZX_OK, ""); ASSERT_EQ(fd_signals(pc), 0, ""); ASSERT_EQ(fd_signals(pc1), POLLOUT, ""); ASSERT_EQ(write(pc1, "test", 4), 4, ""); ASSERT_EQ(read(ps, tmp, 4), 4, ""); ASSERT_EQ(memcmp(tmp, "test", 4), 0, ""); // make sure controlling client observes departing active client close(pc1); ASSERT_EQ(fd_signals(pc), POLLHUP | POLLPRI, ""); ASSERT_EQ(ioctl_pty_read_events(pc, &events), (ssize_t)sizeof(events), ""); ASSERT_EQ(events, PTY_EVENT_HANGUP, ""); // verify that server observes depature of last client close(pc); ASSERT_EQ(fd_signals(ps), POLLHUP | POLLIN, ""); close(ps); END_TEST; } bool not_a_pty_test(void) { BEGIN_TEST; int root_dir = open("/", O_DIRECTORY | O_RDONLY); EXPECT_GE(root_dir, 0, ""); // Calling pty ioctls such as 'get window size' should fail // properly on things that are not ptys. pty_window_size_t ws; EXPECT_EQ(ioctl_pty_get_window_size(root_dir, &ws), ZX_ERR_NOT_SUPPORTED, ""); close(root_dir); END_TEST; } BEGIN_TEST_CASE(pty_tests) RUN_TEST(pty_test) RUN_TEST(not_a_pty_test) END_TEST_CASE(pty_tests) int main(int argc, char** argv) { return unittest_run_all_tests(argc, argv) ? 0 : -1; }