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 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 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 80 EventEmitter* TtyNode::GetEventEmitter() { 81 return emitter_.get(); 82 } 83 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 // 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 110 Error 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 161 Error 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 174 Error 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 198 Error 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 262 Error 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 315 Error TtyNode::Tcgetattr(struct termios* termios_p) { 316 AUTO_LOCK(node_lock_); 317 *termios_p = termios_; 318 return 0; 319 } 320 321 Error 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