tty_test.cc revision 010d83a9304c5a91596085d917d248abff47903a
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <errno.h>
6#include <fcntl.h>
7#include <string.h>
8#include <sys/ioctl.h>
9#include <sys/select.h>
10#include <sys/stat.h>
11#include <sys/time.h>
12#include <string>
13
14#include "dev_fs_for_testing.h"
15#include "gtest/gtest.h"
16#include "nacl_io/devfs/dev_fs.h"
17#include "nacl_io/filesystem.h"
18#include "nacl_io/ioctl.h"
19#include "nacl_io/kernel_intercept.h"
20#include "nacl_io/kernel_proxy.h"
21#include "nacl_io/osdirent.h"
22
23using namespace nacl_io;
24
25namespace {
26
27class TtyNodeTest : public ::testing::Test {
28 public:
29  TtyNodeTest() : fs_(&pepper_) {}
30
31  void SetUp() {
32    ASSERT_EQ(0, fs_.Access(Path("/tty"), R_OK | W_OK));
33    ASSERT_EQ(EACCES, fs_.Access(Path("/tty"), X_OK));
34    ASSERT_EQ(0, fs_.Open(Path("/tty"), O_RDWR, &dev_tty_));
35    ASSERT_NE(NULL_NODE, dev_tty_.get());
36  }
37
38 protected:
39  FakePepperInterface pepper_;
40  DevFsForTesting fs_;
41  ScopedNode dev_tty_;
42};
43
44class TtyTest : public ::testing::Test {
45 public:
46  void SetUp() {
47    ASSERT_EQ(0, ki_push_state_for_testing());
48    ASSERT_EQ(0, ki_init(&kp_));
49  }
50
51  void TearDown() {
52    ki_uninit();
53  }
54
55 protected:
56  KernelProxy kp_;
57};
58
59TEST_F(TtyNodeTest, InvalidIoctl) {
60  // 123 is not a valid ioctl request.
61  EXPECT_EQ(EINVAL, dev_tty_->Ioctl(123));
62}
63
64TEST_F(TtyNodeTest, TtyInput) {
65  // Now let's try sending some data over.
66  // First we create the message.
67  std::string message("hello, how are you?\n");
68  struct tioc_nacl_input_string packaged_message;
69  packaged_message.length = message.size();
70  packaged_message.buffer = message.data();
71
72  // Now we make buffer we'll read into.
73  // We fill the buffer and a backup buffer with arbitrary data
74  // and compare them after reading to make sure read doesn't
75  // clobber parts of the buffer it shouldn't.
76  int bytes_read;
77  char buffer[100];
78  char backup_buffer[100];
79  memset(buffer, 'a', 100);
80  memset(backup_buffer, 'a', 100);
81
82  // Now we actually send the data
83  EXPECT_EQ(0, dev_tty_->Ioctl(TIOCNACLINPUT, &packaged_message));
84
85  // We read a small chunk first to ensure it doesn't give us
86  // more than we ask for.
87  HandleAttr attrs;
88  EXPECT_EQ(0, dev_tty_->Read(attrs, buffer, 5, &bytes_read));
89  EXPECT_EQ(5, bytes_read);
90  EXPECT_EQ(0, memcmp(message.data(), buffer, 5));
91  EXPECT_EQ(0, memcmp(buffer + 5, backup_buffer + 5, 95));
92
93  // Now we ask for more data than is left in the tty, to ensure
94  // it doesn't give us more than is there.
95  EXPECT_EQ(0, dev_tty_->Read(attrs, buffer + 5, 95, &bytes_read));
96  EXPECT_EQ(bytes_read, message.size() - 5);
97  EXPECT_EQ(0, memcmp(message.data(), buffer, message.size()));
98  EXPECT_EQ(0, memcmp(buffer + message.size(),
99                      backup_buffer + message.size(),
100                      100 - message.size()));
101}
102
103struct user_data_t {
104  const char* output_buf;
105  size_t output_count;
106};
107
108static ssize_t output_handler(const char* buf, size_t count, void* data) {
109  user_data_t* user_data = static_cast<user_data_t*>(data);
110  user_data->output_buf = buf;
111  user_data->output_count = count;
112  return count;
113}
114
115TEST_F(TtyNodeTest, TtyOutput) {
116  // When no handler is registered then all writes should return EIO
117  int bytes_written = 10;
118  const char* message = "hello\n";
119  int message_len = strlen(message);
120  HandleAttr attrs;
121  EXPECT_EQ(EIO, dev_tty_->Write(attrs, message, message_len, &bytes_written));
122
123  // Setup output handler with user_data to record calls.
124  user_data_t user_data;
125  user_data.output_buf = NULL;
126  user_data.output_count = 0;
127
128  tioc_nacl_output handler;
129  handler.handler = output_handler;
130  handler.user_data = &user_data;
131
132  EXPECT_EQ(0, dev_tty_->Ioctl(TIOCNACLOUTPUT, &handler));
133
134  EXPECT_EQ(0, dev_tty_->Write(attrs, message, message_len, &bytes_written));
135  EXPECT_EQ(message_len, bytes_written);
136  EXPECT_EQ(message_len, user_data.output_count);
137  EXPECT_EQ(0, strncmp(user_data.output_buf, message, message_len));
138}
139
140static int ki_ioctl_wrapper(int fd, int request, ...) {
141  va_list ap;
142  va_start(ap, request);
143  int rtn = ki_ioctl(fd, request, ap);
144  va_end(ap);
145  return rtn;
146}
147
148static int TtyWrite(int fd, const char* string) {
149  struct tioc_nacl_input_string input;
150  input.buffer = string;
151  input.length = strlen(input.buffer);
152  return ki_ioctl_wrapper(fd, TIOCNACLINPUT, &input);
153}
154
155// Returns:
156//   0 -> Not readable
157//   1 -> Readable
158//  -1 -> Error occured
159static int IsReadable(int fd) {
160  struct timeval timeout = {0, 0};
161  fd_set readfds;
162  fd_set errorfds;
163  FD_ZERO(&readfds);
164  FD_ZERO(&errorfds);
165  FD_SET(fd, &readfds);
166  FD_SET(fd, &errorfds);
167  int rtn = ki_select(fd + 1, &readfds, NULL, &errorfds, &timeout);
168  if (rtn == 0)
169    return 0;  // not readable
170  if (rtn != 1)
171    return -1;  // error
172  if (FD_ISSET(fd, &errorfds))
173    return -2;  // error
174  if (!FD_ISSET(fd, &readfds))
175    return -3;  // error
176  return 1;     // readable
177}
178
179TEST_F(TtyTest, TtySelect) {
180  struct timeval timeout;
181  fd_set readfds;
182  fd_set writefds;
183  fd_set errorfds;
184
185  int tty_fd = ki_open("/dev/tty", O_RDONLY);
186  ASSERT_GT(tty_fd, 0) << "tty open failed: " << errno;
187
188  FD_ZERO(&readfds);
189  FD_ZERO(&errorfds);
190  FD_SET(tty_fd, &readfds);
191  FD_SET(tty_fd, &errorfds);
192  // 10 millisecond timeout
193  timeout.tv_sec = 0;
194  timeout.tv_usec = 10 * 1000;
195  // Should timeout when no input is available.
196  int rtn = ki_select(tty_fd + 1, &readfds, NULL, &errorfds, &timeout);
197  ASSERT_EQ(0, rtn) << "select failed: " << rtn << " err=" << strerror(errno);
198  ASSERT_FALSE(FD_ISSET(tty_fd, &readfds));
199  ASSERT_FALSE(FD_ISSET(tty_fd, &errorfds));
200
201  FD_ZERO(&readfds);
202  FD_ZERO(&writefds);
203  FD_ZERO(&errorfds);
204  FD_SET(tty_fd, &readfds);
205  FD_SET(tty_fd, &writefds);
206  FD_SET(tty_fd, &errorfds);
207  // TTY should be writable on startup.
208  rtn = ki_select(tty_fd + 1, &readfds, &writefds, &errorfds, NULL);
209  ASSERT_EQ(1, rtn);
210  ASSERT_TRUE(FD_ISSET(tty_fd, &writefds));
211  ASSERT_FALSE(FD_ISSET(tty_fd, &readfds));
212  ASSERT_FALSE(FD_ISSET(tty_fd, &errorfds));
213
214  // Send 4 bytes to TTY input
215  ASSERT_EQ(0, TtyWrite(tty_fd, "input:test"));
216
217  // TTY should not be readable until newline in written
218  ASSERT_EQ(IsReadable(tty_fd), 0);
219  ASSERT_EQ(0, TtyWrite(tty_fd, "input:\n"));
220
221  // TTY should now be readable
222  ASSERT_EQ(1, IsReadable(tty_fd));
223
224  ki_close(tty_fd);
225}
226
227TEST_F(TtyTest, TtyICANON) {
228  int tty_fd = ki_open("/dev/tty", O_RDONLY);
229
230  ASSERT_EQ(0, IsReadable(tty_fd));
231
232  struct termios tattr;
233  ki_tcgetattr(tty_fd, &tattr);
234  tattr.c_lflag &= ~(ICANON | ECHO); /* Clear ICANON and ECHO. */
235  ki_tcsetattr(tty_fd, TCSAFLUSH, &tattr);
236
237  ASSERT_EQ(0, IsReadable(tty_fd));
238
239  // Set some bytes to the TTY, not including newline
240  ASSERT_EQ(0, TtyWrite(tty_fd, "a"));
241
242  // Since we are not in canonical mode the bytes should be
243  // immediately readable.
244  ASSERT_EQ(1, IsReadable(tty_fd));
245
246  // Read byte from tty.
247  char c;
248  ASSERT_EQ(1, ki_read(tty_fd, &c, 1));
249  ASSERT_EQ('a', c);
250
251  ASSERT_EQ(0, IsReadable(tty_fd));
252}
253
254static int g_received_signal;
255
256static void sighandler(int sig) { g_received_signal = sig; }
257
258TEST_F(TtyTest, WindowSize) {
259  // Get current window size
260  struct winsize old_winsize = {0};
261  int tty_fd = ki_open("/dev/tty", O_RDONLY);
262  ASSERT_EQ(0, ki_ioctl_wrapper(tty_fd, TIOCGWINSZ, &old_winsize));
263
264  // Install signal handler
265  sighandler_t new_handler = sighandler;
266  sighandler_t old_handler = ki_signal(SIGWINCH, new_handler);
267  ASSERT_NE(SIG_ERR, old_handler) << "signal return error: " << errno;
268
269  g_received_signal = 0;
270
271  // Set a new windows size
272  struct winsize winsize;
273  winsize.ws_col = 100;
274  winsize.ws_row = 200;
275  EXPECT_EQ(0, ki_ioctl_wrapper(tty_fd, TIOCSWINSZ, &winsize));
276  EXPECT_EQ(SIGWINCH, g_received_signal);
277
278  // Restore old signal handler
279  EXPECT_EQ(new_handler, ki_signal(SIGWINCH, old_handler));
280
281  // Verify new window size can be queried correctly.
282  winsize.ws_col = 0;
283  winsize.ws_row = 0;
284  EXPECT_EQ(0, ki_ioctl_wrapper(tty_fd, TIOCGWINSZ, &winsize));
285  EXPECT_EQ(100, winsize.ws_col);
286  EXPECT_EQ(200, winsize.ws_row);
287
288  // Restore original windows size.
289  EXPECT_EQ(0, ki_ioctl_wrapper(tty_fd, TIOCSWINSZ, &old_winsize));
290}
291
292/*
293 * Sleep for 50ms then send a resize event to /dev/tty.
294 */
295static void* resize_thread_main(void* arg) {
296  usleep(50 * 1000);
297
298  int* tty_fd = static_cast<int*>(arg);
299  struct winsize winsize;
300  winsize.ws_col = 100;
301  winsize.ws_row = 200;
302  ki_ioctl_wrapper(*tty_fd, TIOCSWINSZ, &winsize);
303  return NULL;
304}
305
306TEST_F(TtyTest, ResizeDuringSelect) {
307  // Test that a window resize during a call
308  // to select(3) will cause it to fail with EINTR.
309  int tty_fd = ki_open("/dev/tty", O_RDONLY);
310
311  fd_set readfds;
312  fd_set errorfds;
313  FD_ZERO(&readfds);
314  FD_ZERO(&errorfds);
315  FD_SET(tty_fd, &readfds);
316  FD_SET(tty_fd, &errorfds);
317
318  pthread_t resize_thread;
319  pthread_create(&resize_thread, NULL, resize_thread_main, &tty_fd);
320
321  struct timeval timeout;
322  timeout.tv_sec = 20;
323  timeout.tv_usec = 0;
324
325  // TTY should not be readable either before or after the
326  // call to select(3).
327  ASSERT_EQ(0, IsReadable(tty_fd));
328
329  int rtn = ki_select(tty_fd + 1, &readfds, NULL, &errorfds, &timeout);
330  pthread_join(resize_thread, NULL);
331  ASSERT_EQ(-1, rtn);
332  ASSERT_EQ(EINTR, errno);
333  ASSERT_EQ(0, IsReadable(tty_fd));
334}
335
336/*
337 * Sleep for 50ms then send some input to the /dev/tty.
338 */
339static void* input_thread_main(void* arg) {
340  usleep(50 * 1000);
341
342  int fd = ki_open("/dev/tty", O_RDONLY);
343  TtyWrite(fd, "test\n");
344  return NULL;
345}
346
347TEST_F(TtyTest, InputDuringSelect) {
348  // Test that input which occurs while in select causes
349  // select to return.
350  int tty_fd = ki_open("/dev/tty", O_RDONLY);
351
352  fd_set readfds;
353  fd_set errorfds;
354  FD_ZERO(&readfds);
355  FD_ZERO(&errorfds);
356  FD_SET(tty_fd, &readfds);
357  FD_SET(tty_fd, &errorfds);
358
359  pthread_t resize_thread;
360  pthread_create(&resize_thread, NULL, input_thread_main, NULL);
361
362  struct timeval timeout;
363  timeout.tv_sec = 20;
364  timeout.tv_usec = 0;
365
366  int rtn = ki_select(tty_fd + 1, &readfds, NULL, &errorfds, &timeout);
367  pthread_join(resize_thread, NULL);
368
369  ASSERT_EQ(1, rtn);
370}
371}
372