Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / native_client_sdk / src / libraries / nacl_io / devfs / tty_node.cc
blob4d9676a012562fcfd1509d2a91f94b6f2ee0ea24
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.
5 #include "nacl_io/devfs/tty_node.h"
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>
15 #include <algorithm>
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"
25 #define CHECK_LFLAG(TERMIOS, FLAG) (TERMIOS.c_lflag& FLAG)
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)
33 #define DEFAULT_TTY_COLS 80
34 #define DEFAULT_TTY_ROWS 30
36 namespace nacl_io {
38 TtyNode::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();
46 // Output will never block
47 emitter_->RaiseEvents_Locked(POLLOUT);
50 void TtyNode::InitTermios() {
51 // Some sane values that produce good result.
52 termios_.c_iflag = ICRNL | IXON | IXOFF;
53 #ifdef IUTF8
54 termios_.c_iflag |= IUTF8;
55 #endif
56 termios_.c_oflag = OPOST | ONLCR;
57 termios_.c_cflag = CREAD | 077;
58 termios_.c_lflag =
59 ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
60 #if !defined(__BIONIC__) && !(defined(__GLIBC__) && defined(__arm__))
61 termios_.c_ispeed = B38400;
62 termios_.c_ospeed = B38400;
63 #endif
64 termios_.c_cc[VINTR] = 3;
65 termios_.c_cc[VQUIT] = 28;
66 termios_.c_cc[VERASE] = 127;
67 termios_.c_cc[VKILL] = 21;
68 termios_.c_cc[VEOF] = 4;
69 termios_.c_cc[VTIME] = 0;
70 termios_.c_cc[VMIN] = 1;
71 #if defined(VSWTC) /* Not defined on on Mac */
72 termios_.c_cc[VSWTC] = 0;
73 #endif
74 termios_.c_cc[VSTART] = 17;
75 termios_.c_cc[VSTOP] = 19;
76 termios_.c_cc[VSUSP] = 26;
77 termios_.c_cc[VEOL] = 0;
78 termios_.c_cc[VREPRINT] = 18;
79 termios_.c_cc[VDISCARD] = 15;
80 termios_.c_cc[VWERASE] = 23;
81 termios_.c_cc[VLNEXT] = 22;
82 termios_.c_cc[VEOL2] = 0;
85 EventEmitter* TtyNode::GetEventEmitter() {
86 return emitter_.get();
89 Error TtyNode::Write(const HandleAttr& attr,
90 const void* buf,
91 size_t count,
92 int* out_bytes) {
93 AUTO_LOCK(output_lock_);
94 *out_bytes = 0;
96 // No handler registered.
97 if (output_handler_.handler == NULL) {
98 // No error here; many of the tests trigger this message.
99 LOG_TRACE("No output handler registered.");
100 return EIO;
103 int rtn = output_handler_.handler(
104 static_cast<const char*>(buf), count, output_handler_.user_data);
106 // Negative return value means an error occured and the return
107 // value is a negated errno value.
108 if (rtn < 0)
109 return -rtn;
111 *out_bytes = rtn;
112 return 0;
115 Error TtyNode::Read(const HandleAttr& attr,
116 void* buf,
117 size_t count,
118 int* out_bytes) {
119 EventListenerLock wait(GetEventEmitter());
120 *out_bytes = 0;
122 // If interrupted, return
123 int ms = attr.IsBlocking() ? -1 : 0;
124 Error err = wait.WaitOnEvent(POLLIN, ms);
125 if (err == ETIMEDOUT)
126 err = EWOULDBLOCK;
127 if (err != 0)
128 return err;
130 size_t bytes_to_copy = std::min(count, input_buffer_.size());
131 if (IS_ICANON) {
132 // Only read up to (and including) the first newline
133 std::deque<char>::iterator nl =
134 std::find(input_buffer_.begin(), input_buffer_.end(), '\n');
136 if (nl != input_buffer_.end()) {
137 // We found a newline in the buffer, adjust bytes_to_copy accordingly
138 size_t line_len = static_cast<size_t>(nl - input_buffer_.begin()) + 1;
139 bytes_to_copy = std::min(bytes_to_copy, line_len);
143 // Copies data from the input buffer into buf.
144 std::copy(input_buffer_.begin(),
145 input_buffer_.begin() + bytes_to_copy,
146 static_cast<char*>(buf));
147 *out_bytes = bytes_to_copy;
148 input_buffer_.erase(input_buffer_.begin(),
149 input_buffer_.begin() + bytes_to_copy);
151 // mark input as no longer readable if we consumed
152 // the entire buffer or, in the case of buffered input,
153 // we consumed the final \n char.
154 bool avail;
155 if (IS_ICANON)
156 avail = std::find(input_buffer_.begin(), input_buffer_.end(), '\n') !=
157 input_buffer_.end();
158 else
159 avail = input_buffer_.size() > 0;
161 if (!avail)
162 emitter_->ClearEvents_Locked(POLLIN);
164 return 0;
167 Error TtyNode::Echo(const char* string, int count) {
168 int wrote;
169 HandleAttr data;
170 Error error = Write(data, string, count, &wrote);
171 if (error != 0 || wrote != count) {
172 // TOOD(sbc): Do something more useful in response to a
173 // failure to echo.
174 return error;
177 return 0;
180 Error TtyNode::ProcessInput(PP_Var message) {
181 if (message.type != PP_VARTYPE_STRING) {
182 LOG_ERROR("Expected VarString but got %d.", message.type);
183 return EINVAL;
186 PepperInterface* ppapi = filesystem_->ppapi();
187 if (!ppapi) {
188 LOG_ERROR("ppapi is NULL.");
189 return EINVAL;
192 VarInterface* var_iface = ppapi->GetVarInterface();
193 if (!var_iface) {
194 LOG_ERROR("Got NULL interface: Var");
195 return EINVAL;
198 uint32_t num_bytes;
199 const char* buffer = var_iface->VarToUtf8(message, &num_bytes);
200 Error error = ProcessInput(buffer, num_bytes);
201 return error;
204 Error TtyNode::ProcessInput(const char* buffer, size_t num_bytes) {
205 AUTO_LOCK(emitter_->GetLock())
207 for (size_t i = 0; i < num_bytes; i++) {
208 char c = buffer[i];
209 // Transform characters according to input flags.
210 if (c == '\r') {
211 if (termios_.c_iflag & IGNCR)
212 continue;
213 if (termios_.c_iflag & ICRNL)
214 c = '\n';
215 } else if (c == '\n') {
216 if (termios_.c_iflag & INLCR)
217 c = '\r';
220 bool skip = false;
222 // ICANON mode means we wait for a newline before making the
223 // file readable.
224 if (IS_ICANON) {
225 if (IS_ECHOE && c == termios_.c_cc[VERASE]) {
226 // Remove previous character in the line if any.
227 if (!input_buffer_.empty()) {
228 char char_to_delete = input_buffer_.back();
229 if (char_to_delete != '\n') {
230 input_buffer_.pop_back();
231 if (IS_ECHO)
232 Echo("\b \b", 3);
234 // When ECHOCTL is set the echo buffer contains an extra
235 // char for each control char.
236 if (IS_ECHOCTL && iscntrl(char_to_delete))
237 Echo("\b \b", 3);
240 continue;
241 } else if (IS_ECHO || (IS_ECHONL && c == '\n')) {
242 if (c == termios_.c_cc[VEOF]) {
243 // VEOF sequence is not echoed, nor is it sent as
244 // input.
245 skip = true;
246 } else if (c != '\n' && iscntrl(c) && IS_ECHOCTL) {
247 // In ECHOCTL mode a control char C is echoed as '^'
248 // followed by the ascii char which at C + 0x40.
249 char visible_char = c + 0x40;
250 Echo("^", 1);
251 Echo(&visible_char, 1);
252 } else {
253 Echo(&c, 1);
258 if (!skip)
259 input_buffer_.push_back(c);
261 if (c == '\n' || c == termios_.c_cc[VEOF] || !IS_ICANON)
262 emitter_->RaiseEvents_Locked(POLLIN);
265 return 0;
268 Error TtyNode::VIoctl(int request, va_list args) {
270 * Casts required for some of these case statements in order to silence
271 * compiler warning when built with darwin headers.
273 switch (request) {
274 case TIOCNACLOUTPUT: {
275 struct tioc_nacl_output* arg = va_arg(args, struct tioc_nacl_output*);
276 AUTO_LOCK(output_lock_);
277 if (arg == NULL) {
278 output_handler_.handler = NULL;
279 return 0;
281 if (output_handler_.handler != NULL) {
282 LOG_ERROR("Output handler already set.");
283 return EALREADY;
285 output_handler_ = *arg;
286 return 0;
288 case NACL_IOC_HANDLEMESSAGE: {
289 struct PP_Var* message = va_arg(args, struct PP_Var*);
290 return ProcessInput(*message);
292 case (unsigned int)TIOCSWINSZ: {
293 struct winsize* size = va_arg(args, struct winsize*);
295 AUTO_LOCK(node_lock_);
296 if (rows_ == size->ws_row && cols_ == size->ws_col)
297 return 0;
298 rows_ = size->ws_row;
299 cols_ = size->ws_col;
301 ki_kill(getpid(), SIGWINCH);
303 // Wake up any thread waiting on Read with POLLERR then immediate
304 // clear it to signal EINTR.
305 AUTO_LOCK(emitter_->GetLock())
306 emitter_->RaiseEvents_Locked(POLLERR);
307 emitter_->ClearEvents_Locked(POLLERR);
309 return 0;
311 case (unsigned int)TIOCGWINSZ: {
312 struct winsize* size = va_arg(args, struct winsize*);
313 size->ws_row = rows_;
314 size->ws_col = cols_;
315 return 0;
317 default: {
318 LOG_ERROR("TtyNode:VIoctl: Unknown request: %#x", request);
322 return EINVAL;
325 Error TtyNode::Tcgetattr(struct termios* termios_p) {
326 AUTO_LOCK(node_lock_);
327 *termios_p = termios_;
328 return 0;
331 Error TtyNode::Tcsetattr(int optional_actions,
332 const struct termios* termios_p) {
333 AUTO_LOCK(node_lock_);
334 termios_ = *termios_p;
335 return 0;
338 } // namespace nacl_io