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
36 namespace nacl_io {
37
TtyNode(Filesystem * filesystem)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();
45
46 // Output will never block
47 emitter_->RaiseEvents_Locked(POLLOUT);
48 }
49
InitTermios()50 void 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
GetEventEmitter()80 EventEmitter* TtyNode::GetEventEmitter() {
81 return emitter_.get();
82 }
83
Write(const HandleAttr & attr,const void * buf,size_t count,int * out_bytes)84 Error 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 return EIO;
94
95 int rtn = output_handler_.handler(
96 static_cast<const char*>(buf), count, output_handler_.user_data);
97
98 // Negative return value means an error occured and the return
99 // value is a negated errno value.
100 if (rtn < 0)
101 return -rtn;
102
103 *out_bytes = rtn;
104 return 0;
105 }
106
Read(const HandleAttr & attr,void * buf,size_t count,int * out_bytes)107 Error TtyNode::Read(const HandleAttr& attr,
108 void* buf,
109 size_t count,
110 int* out_bytes) {
111 EventListenerLock wait(GetEventEmitter());
112 *out_bytes = 0;
113
114 // If interrupted, return
115 Error err = wait.WaitOnEvent(POLLIN, -1);
116 if (err == ETIMEDOUT)
117 err = EWOULDBLOCK;
118 if (err != 0)
119 return err;
120
121 size_t bytes_to_copy = std::min(count, input_buffer_.size());
122 if (IS_ICANON) {
123 // Only read up to (and including) the first newline
124 std::deque<char>::iterator nl =
125 std::find(input_buffer_.begin(), input_buffer_.end(), '\n');
126
127 if (nl != input_buffer_.end()) {
128 // We found a newline in the buffer, adjust bytes_to_copy accordingly
129 size_t line_len = static_cast<size_t>(nl - input_buffer_.begin()) + 1;
130 bytes_to_copy = std::min(bytes_to_copy, line_len);
131 }
132 }
133
134 // Copies data from the input buffer into buf.
135 std::copy(input_buffer_.begin(),
136 input_buffer_.begin() + bytes_to_copy,
137 static_cast<char*>(buf));
138 *out_bytes = bytes_to_copy;
139 input_buffer_.erase(input_buffer_.begin(),
140 input_buffer_.begin() + bytes_to_copy);
141
142 // mark input as no longer readable if we consumed
143 // the entire buffer or, in the case of buffered input,
144 // we consumed the final \n char.
145 bool avail;
146 if (IS_ICANON)
147 avail = std::find(input_buffer_.begin(), input_buffer_.end(), '\n') !=
148 input_buffer_.end();
149 else
150 avail = input_buffer_.size() > 0;
151
152 if (!avail)
153 emitter_->ClearEvents_Locked(POLLIN);
154
155 return 0;
156 }
157
Echo(const char * string,int count)158 Error TtyNode::Echo(const char* string, int count) {
159 int wrote;
160 HandleAttr data;
161 Error error = Write(data, string, count, &wrote);
162 if (error != 0 || wrote != count) {
163 // TOOD(sbc): Do something more useful in response to a
164 // failure to echo.
165 return error;
166 }
167
168 return 0;
169 }
170
ProcessInput(PP_Var message)171 Error TtyNode::ProcessInput(PP_Var message) {
172 if (message.type != PP_VARTYPE_STRING) {
173 LOG_ERROR("ProcessInput: expected VarString but got %d.", message.type);
174 return EINVAL;
175 }
176
177 PepperInterface* ppapi = filesystem_->ppapi();
178 if (!ppapi) {
179 LOG_ERROR("ProcessInput: ppapi is NULL.");
180 return EINVAL;
181 }
182
183 VarInterface* var_iface = ppapi->GetVarInterface();
184 if (!var_iface) {
185 LOG_ERROR("ProcessInput: Var interface pointer is NULL.");
186 return EINVAL;
187 }
188
189 uint32_t num_bytes;
190 const char* buffer = var_iface->VarToUtf8(message, &num_bytes);
191 Error error = ProcessInput(buffer, num_bytes);
192 return error;
193 }
194
ProcessInput(const char * buffer,size_t num_bytes)195 Error TtyNode::ProcessInput(const char* buffer, size_t num_bytes) {
196 AUTO_LOCK(emitter_->GetLock())
197
198 for (size_t i = 0; i < num_bytes; i++) {
199 char c = buffer[i];
200 // Transform characters according to input flags.
201 if (c == '\r') {
202 if (termios_.c_iflag & IGNCR)
203 continue;
204 if (termios_.c_iflag & ICRNL)
205 c = '\n';
206 } else if (c == '\n') {
207 if (termios_.c_iflag & INLCR)
208 c = '\r';
209 }
210
211 bool skip = false;
212
213 // ICANON mode means we wait for a newline before making the
214 // file readable.
215 if (IS_ICANON) {
216 if (IS_ECHOE && c == termios_.c_cc[VERASE]) {
217 // Remove previous character in the line if any.
218 if (!input_buffer_.empty()) {
219 char char_to_delete = input_buffer_.back();
220 if (char_to_delete != '\n') {
221 input_buffer_.pop_back();
222 if (IS_ECHO)
223 Echo("\b \b", 3);
224
225 // When ECHOCTL is set the echo buffer contains an extra
226 // char for each control char.
227 if (IS_ECHOCTL && iscntrl(char_to_delete))
228 Echo("\b \b", 3);
229 }
230 }
231 continue;
232 } else if (IS_ECHO || (IS_ECHONL && c == '\n')) {
233 if (c == termios_.c_cc[VEOF]) {
234 // VEOF sequence is not echoed, nor is it sent as
235 // input.
236 skip = true;
237 } else if (c != '\n' && iscntrl(c) && IS_ECHOCTL) {
238 // In ECHOCTL mode a control char C is echoed as '^'
239 // followed by the ascii char which at C + 0x40.
240 char visible_char = c + 0x40;
241 Echo("^", 1);
242 Echo(&visible_char, 1);
243 } else {
244 Echo(&c, 1);
245 }
246 }
247 }
248
249 if (!skip)
250 input_buffer_.push_back(c);
251
252 if (c == '\n' || c == termios_.c_cc[VEOF] || !IS_ICANON)
253 emitter_->RaiseEvents_Locked(POLLIN);
254 }
255
256 return 0;
257 }
258
VIoctl(int request,va_list args)259 Error TtyNode::VIoctl(int request, va_list args) {
260 switch (request) {
261 case TIOCNACLOUTPUT: {
262 struct tioc_nacl_output* arg = va_arg(args, struct tioc_nacl_output*);
263 AUTO_LOCK(output_lock_);
264 if (arg == NULL) {
265 output_handler_.handler = NULL;
266 return 0;
267 }
268 if (output_handler_.handler != NULL)
269 return EALREADY;
270 output_handler_ = *arg;
271 return 0;
272 }
273 case NACL_IOC_HANDLEMESSAGE: {
274 struct PP_Var* message = va_arg(args, struct PP_Var*);
275 return ProcessInput(*message);
276 }
277 case TIOCSWINSZ: {
278 struct winsize* size = va_arg(args, struct winsize*);
279 {
280 AUTO_LOCK(node_lock_);
281 if (rows_ == size->ws_row && cols_ == size->ws_col)
282 return 0;
283 rows_ = size->ws_row;
284 cols_ = size->ws_col;
285 }
286 ki_kill(getpid(), SIGWINCH);
287 {
288 // Wake up any thread waiting on Read with POLLERR then immediate
289 // clear it to signal EINTR.
290 AUTO_LOCK(emitter_->GetLock())
291 emitter_->RaiseEvents_Locked(POLLERR);
292 emitter_->ClearEvents_Locked(POLLERR);
293 }
294 return 0;
295 }
296 case TIOCGWINSZ: {
297 struct winsize* size = va_arg(args, struct winsize*);
298 size->ws_row = rows_;
299 size->ws_col = cols_;
300 return 0;
301 }
302 default: {
303 LOG_ERROR("TtyNode:VIoctl: Unknown request: %#x", request);
304 }
305 }
306
307 return EINVAL;
308 }
309
Tcgetattr(struct termios * termios_p)310 Error TtyNode::Tcgetattr(struct termios* termios_p) {
311 AUTO_LOCK(node_lock_);
312 *termios_p = termios_;
313 return 0;
314 }
315
Tcsetattr(int optional_actions,const struct termios * termios_p)316 Error TtyNode::Tcsetattr(int optional_actions,
317 const struct termios* termios_p) {
318 AUTO_LOCK(node_lock_);
319 termios_ = *termios_p;
320 return 0;
321 }
322
323 } // namespace nacl_io
324