Home | History | Annotate | Download | only in devfs
      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