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 "nacl_io/devfs/tty_node.h"
6
7#include <assert.h>
8#include <errno.h>
9#include <signal.h>
10#include <stdio.h>
11#include <string.h>
12#include <sys/ioctl.h>
13#include <unistd.h>
14
15#include <algorithm>
16
17#include "nacl_io/filesystem.h"
18#include "nacl_io/ioctl.h"
19#include "nacl_io/kernel_handle.h"
20#include "nacl_io/kernel_intercept.h"
21#include "nacl_io/log.h"
22#include "nacl_io/pepper_interface.h"
23#include "sdk_util/auto_lock.h"
24
25#define CHECK_LFLAG(TERMIOS, FLAG) (TERMIOS.c_lflag& FLAG)
26
27#define IS_ECHO CHECK_LFLAG(termios_, ECHO)
28#define IS_ECHOE CHECK_LFLAG(termios_, ECHOE)
29#define IS_ECHONL CHECK_LFLAG(termios_, ECHONL)
30#define IS_ECHOCTL CHECK_LFLAG(termios_, ECHOCTL)
31#define IS_ICANON CHECK_LFLAG(termios_, ICANON)
32
33#define DEFAULT_TTY_COLS 80
34#define DEFAULT_TTY_ROWS 30
35
36namespace nacl_io {
37
38TtyNode::TtyNode(Filesystem* filesystem)
39    : CharNode(filesystem),
40      emitter_(new EventEmitter),
41      rows_(DEFAULT_TTY_ROWS),
42      cols_(DEFAULT_TTY_COLS) {
43  output_handler_.handler = NULL;
44  InitTermios();
45
46  // Output will never block
47  emitter_->RaiseEvents_Locked(POLLOUT);
48}
49
50void TtyNode::InitTermios() {
51  // Some sane values that produce good result.
52  termios_.c_iflag = ICRNL | IXON | IXOFF | IUTF8;
53  termios_.c_oflag = OPOST | ONLCR;
54  termios_.c_cflag = CREAD | 077;
55  termios_.c_lflag =
56      ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
57#if !defined(__BIONIC__)
58  termios_.c_ispeed = B38400;
59  termios_.c_ospeed = B38400;
60#endif
61  termios_.c_cc[VINTR] = 3;
62  termios_.c_cc[VQUIT] = 28;
63  termios_.c_cc[VERASE] = 127;
64  termios_.c_cc[VKILL] = 21;
65  termios_.c_cc[VEOF] = 4;
66  termios_.c_cc[VTIME] = 0;
67  termios_.c_cc[VMIN] = 1;
68  termios_.c_cc[VSWTC] = 0;
69  termios_.c_cc[VSTART] = 17;
70  termios_.c_cc[VSTOP] = 19;
71  termios_.c_cc[VSUSP] = 26;
72  termios_.c_cc[VEOL] = 0;
73  termios_.c_cc[VREPRINT] = 18;
74  termios_.c_cc[VDISCARD] = 15;
75  termios_.c_cc[VWERASE] = 23;
76  termios_.c_cc[VLNEXT] = 22;
77  termios_.c_cc[VEOL2] = 0;
78}
79
80EventEmitter* TtyNode::GetEventEmitter() {
81  return emitter_.get();
82}
83
84Error TtyNode::Write(const HandleAttr& attr,
85                     const void* buf,
86                     size_t count,
87                     int* out_bytes) {
88  AUTO_LOCK(output_lock_);
89  *out_bytes = 0;
90
91  // No handler registered.
92  if (output_handler_.handler == NULL) {
93    // No error here; many of the tests trigger this message.
94    LOG_TRACE("No output handler registered.");
95    return EIO;
96  }
97
98  int rtn = output_handler_.handler(
99      static_cast<const char*>(buf), count, output_handler_.user_data);
100
101  // Negative return value means an error occured and the return
102  // value is a negated errno value.
103  if (rtn < 0)
104    return -rtn;
105
106  *out_bytes = rtn;
107  return 0;
108}
109
110Error TtyNode::Read(const HandleAttr& attr,
111                    void* buf,
112                    size_t count,
113                    int* out_bytes) {
114  EventListenerLock wait(GetEventEmitter());
115  *out_bytes = 0;
116
117  // If interrupted, return
118  Error err = wait.WaitOnEvent(POLLIN, -1);
119  if (err == ETIMEDOUT)
120    err = EWOULDBLOCK;
121  if (err != 0)
122    return err;
123
124  size_t bytes_to_copy = std::min(count, input_buffer_.size());
125  if (IS_ICANON) {
126    // Only read up to (and including) the first newline
127    std::deque<char>::iterator nl =
128        std::find(input_buffer_.begin(), input_buffer_.end(), '\n');
129
130    if (nl != input_buffer_.end()) {
131      // We found a newline in the buffer, adjust bytes_to_copy accordingly
132      size_t line_len = static_cast<size_t>(nl - input_buffer_.begin()) + 1;
133      bytes_to_copy = std::min(bytes_to_copy, line_len);
134    }
135  }
136
137  // Copies data from the input buffer into buf.
138  std::copy(input_buffer_.begin(),
139            input_buffer_.begin() + bytes_to_copy,
140            static_cast<char*>(buf));
141  *out_bytes = bytes_to_copy;
142  input_buffer_.erase(input_buffer_.begin(),
143                      input_buffer_.begin() + bytes_to_copy);
144
145  // mark input as no longer readable if we consumed
146  // the entire buffer or, in the case of buffered input,
147  // we consumed the final \n char.
148  bool avail;
149  if (IS_ICANON)
150    avail = std::find(input_buffer_.begin(), input_buffer_.end(), '\n') !=
151            input_buffer_.end();
152  else
153    avail = input_buffer_.size() > 0;
154
155  if (!avail)
156    emitter_->ClearEvents_Locked(POLLIN);
157
158  return 0;
159}
160
161Error TtyNode::Echo(const char* string, int count) {
162  int wrote;
163  HandleAttr data;
164  Error error = Write(data, string, count, &wrote);
165  if (error != 0 || wrote != count) {
166    // TOOD(sbc): Do something more useful in response to a
167    // failure to echo.
168    return error;
169  }
170
171  return 0;
172}
173
174Error TtyNode::ProcessInput(PP_Var message) {
175  if (message.type != PP_VARTYPE_STRING) {
176    LOG_ERROR("Expected VarString but got %d.", message.type);
177    return EINVAL;
178  }
179
180  PepperInterface* ppapi = filesystem_->ppapi();
181  if (!ppapi) {
182    LOG_ERROR("ppapi is NULL.");
183    return EINVAL;
184  }
185
186  VarInterface* var_iface = ppapi->GetVarInterface();
187  if (!var_iface) {
188    LOG_ERROR("Got NULL interface: Var");
189    return EINVAL;
190  }
191
192  uint32_t num_bytes;
193  const char* buffer = var_iface->VarToUtf8(message, &num_bytes);
194  Error error = ProcessInput(buffer, num_bytes);
195  return error;
196}
197
198Error TtyNode::ProcessInput(const char* buffer, size_t num_bytes) {
199  AUTO_LOCK(emitter_->GetLock())
200
201  for (size_t i = 0; i < num_bytes; i++) {
202    char c = buffer[i];
203    // Transform characters according to input flags.
204    if (c == '\r') {
205      if (termios_.c_iflag & IGNCR)
206        continue;
207      if (termios_.c_iflag & ICRNL)
208        c = '\n';
209    } else if (c == '\n') {
210      if (termios_.c_iflag & INLCR)
211        c = '\r';
212    }
213
214    bool skip = false;
215
216    // ICANON mode means we wait for a newline before making the
217    // file readable.
218    if (IS_ICANON) {
219      if (IS_ECHOE && c == termios_.c_cc[VERASE]) {
220        // Remove previous character in the line if any.
221        if (!input_buffer_.empty()) {
222          char char_to_delete = input_buffer_.back();
223          if (char_to_delete != '\n') {
224            input_buffer_.pop_back();
225            if (IS_ECHO)
226              Echo("\b \b", 3);
227
228            // When ECHOCTL is set the echo buffer contains an extra
229            // char for each control char.
230            if (IS_ECHOCTL && iscntrl(char_to_delete))
231              Echo("\b \b", 3);
232          }
233        }
234        continue;
235      } else if (IS_ECHO || (IS_ECHONL && c == '\n')) {
236        if (c == termios_.c_cc[VEOF]) {
237          // VEOF sequence is not echoed, nor is it sent as
238          // input.
239          skip = true;
240        } else if (c != '\n' && iscntrl(c) && IS_ECHOCTL) {
241          // In ECHOCTL mode a control char C is echoed  as '^'
242          // followed by the ascii char which at C + 0x40.
243          char visible_char = c + 0x40;
244          Echo("^", 1);
245          Echo(&visible_char, 1);
246        } else {
247          Echo(&c, 1);
248        }
249      }
250    }
251
252    if (!skip)
253      input_buffer_.push_back(c);
254
255    if (c == '\n' || c == termios_.c_cc[VEOF] || !IS_ICANON)
256      emitter_->RaiseEvents_Locked(POLLIN);
257  }
258
259  return 0;
260}
261
262Error TtyNode::VIoctl(int request, va_list args) {
263  switch (request) {
264    case TIOCNACLOUTPUT: {
265      struct tioc_nacl_output* arg = va_arg(args, struct tioc_nacl_output*);
266      AUTO_LOCK(output_lock_);
267      if (arg == NULL) {
268        output_handler_.handler = NULL;
269        return 0;
270      }
271      if (output_handler_.handler != NULL) {
272        LOG_ERROR("Output handler already set.");
273        return EALREADY;
274      }
275      output_handler_ = *arg;
276      return 0;
277    }
278    case NACL_IOC_HANDLEMESSAGE: {
279      struct PP_Var* message = va_arg(args, struct PP_Var*);
280      return ProcessInput(*message);
281    }
282    case TIOCSWINSZ: {
283      struct winsize* size = va_arg(args, struct winsize*);
284      {
285        AUTO_LOCK(node_lock_);
286        if (rows_ == size->ws_row && cols_ == size->ws_col)
287          return 0;
288        rows_ = size->ws_row;
289        cols_ = size->ws_col;
290      }
291      ki_kill(getpid(), SIGWINCH);
292      {
293        // Wake up any thread waiting on Read with POLLERR then immediate
294        // clear it to signal EINTR.
295        AUTO_LOCK(emitter_->GetLock())
296        emitter_->RaiseEvents_Locked(POLLERR);
297        emitter_->ClearEvents_Locked(POLLERR);
298      }
299      return 0;
300    }
301    case TIOCGWINSZ: {
302      struct winsize* size = va_arg(args, struct winsize*);
303      size->ws_row = rows_;
304      size->ws_col = cols_;
305      return 0;
306    }
307    default: {
308      LOG_ERROR("TtyNode:VIoctl: Unknown request: %#x", request);
309    }
310  }
311
312  return EINVAL;
313}
314
315Error TtyNode::Tcgetattr(struct termios* termios_p) {
316  AUTO_LOCK(node_lock_);
317  *termios_p = termios_;
318  return 0;
319}
320
321Error TtyNode::Tcsetattr(int optional_actions,
322                         const struct termios* termios_p) {
323  AUTO_LOCK(node_lock_);
324  termios_ = *termios_p;
325  return 0;
326}
327
328}  // namespace nacl_io
329