| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "js_file.h" |
| |
| #include <algorithm> |
| |
| #include <assert.h> |
| #include <string.h> |
| |
| #include "ppapi/cpp/module.h" |
| |
| #include "file_system.h" |
| #include "proxy_stream.h" |
| |
| termios JsFile::tio_ = {}; |
| |
| JsFileHandler::JsFileHandler(OutputInterface* out) |
| : ref_(1), factory_(this), out_(out) { |
| } |
| |
| JsFileHandler::~JsFileHandler() { |
| assert(!ref_); |
| } |
| |
| void JsFileHandler::addref() { |
| ++ref_; |
| } |
| |
| void JsFileHandler::release() { |
| if (!--ref_) |
| delete this; |
| } |
| |
| void JsFileHandler::Open(int32_t result, JsFile* stream, const char* pathname) { |
| out_->OpenFile(stream->fd(), pathname, stream->oflag(), stream); |
| } |
| |
| FileStream* JsFileHandler::open(int fd, const char* pathname, int oflag, |
| int* err) { |
| JsFile* stream = new JsFile(fd, (oflag & ~O_NONBLOCK), out_); |
| pp::Module::Get()->core()->CallOnMainThread(0, |
| factory_.NewCallback(&JsFileHandler::Open, stream, pathname)); |
| |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| while (!stream->is_open()) |
| sys->cond().wait(sys->mutex()); |
| |
| if (stream->fd() == -1) { |
| stream->release(); |
| return NULL; |
| } |
| |
| return stream; |
| } |
| |
| int JsFileHandler::stat(const char* pathname, nacl_abi_stat* out) { |
| memset(out, 0, sizeof(nacl_abi_stat)); |
| return 0; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| JsFile::JsFile(int fd, int oflag, OutputInterface* out) |
| : ref_(1), fd_(fd), oflag_(oflag), out_(out), |
| factory_(this), out_task_sent_(false), is_open_(false), |
| is_atty_(false), is_read_ready_(false), |
| write_sent_(0), write_acknowledged_(0), |
| on_read_call_count_(0) { |
| } |
| |
| JsFile::~JsFile() { |
| assert(!ref_); |
| } |
| |
| void JsFile::OnOpen(bool success, bool is_atty) { |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| Mutex::Lock lock(sys->mutex()); |
| is_open_ = true; |
| is_atty_ = is_atty; |
| if (!success) |
| fd_ = -1; |
| sys->cond().broadcast(); |
| } |
| |
| void JsFile::OnRead(const char* buf, size_t size) { |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| Mutex::Lock lock(sys->mutex()); |
| if (isatty()) { |
| for (size_t i = 0; i < size; i++) { |
| char c = buf[i]; |
| // Transform characters according to input flags. |
| if (c == '\r') { |
| if (tio_.c_iflag & IGNCR) |
| continue; |
| if (tio_.c_iflag & ICRNL) |
| c = '\n'; |
| } else if (c == '\n') { |
| if (tio_.c_iflag & INLCR) |
| c = '\r'; |
| } |
| if (tio_.c_lflag & ICANON) { |
| if ((tio_.c_lflag & ECHOE) && c == tio_.c_cc[VERASE]) { |
| // Remove previous character in the line if any. |
| // TODO(davidben): This should be IUTF8-aware. |
| if (!in_buf_.empty() && in_buf_.back() != '\n') { |
| in_buf_.pop_back(); |
| if (tio_.c_lflag & ECHO) |
| ::write(fd_, "\b \b", 3); |
| } |
| continue; |
| } else if ((tio_.c_lflag & ECHO) || |
| ((tio_.c_lflag & ECHONL) && c == '\n')) { |
| ::write(fd_, &c, 1); |
| } |
| } else if (tio_.c_lflag & ECHO) { |
| ::write(fd_, &c, 1); |
| } |
| in_buf_.push_back(c); |
| } |
| } else { |
| in_buf_.insert(in_buf_.end(), buf, buf + size); |
| } |
| on_read_call_count_++; |
| sys->cond().broadcast(); |
| } |
| |
| void JsFile::OnWriteAcknowledge(uint64_t count) { |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| Mutex::Lock lock(sys->mutex()); |
| assert(write_acknowledged_ <= write_sent_); |
| write_acknowledged_ = count; |
| PostWriteTask(false); |
| sys->cond().broadcast(); |
| } |
| |
| void JsFile::OnClose() { |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| Mutex::Lock lock(sys->mutex()); |
| is_open_ = false; |
| sys->cond().broadcast(); |
| } |
| |
| void JsFile::OnReadReady(bool is_read_ready) { |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| Mutex::Lock lock(sys->mutex()); |
| is_read_ready_ = is_read_ready; |
| sys->cond().broadcast(); |
| } |
| |
| void JsFile::addref() { |
| ++ref_; |
| } |
| |
| void JsFile::release() { |
| if (!--ref_) |
| delete this; |
| } |
| |
| FileStream* JsFile::dup(int fd) { |
| return new ProxyStream(fd, oflag_, this); |
| } |
| |
| void JsFile::close() { |
| if (is_open()) { |
| assert(fd_ >= 3); |
| pp::Module::Get()->core()->CallOnMainThread(0, |
| factory_.NewCallback(&JsFile::Close)); |
| |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| while (out_task_sent_) |
| sys->cond().wait(sys->mutex()); |
| while (is_open_) |
| sys->cond().wait(sys->mutex()); |
| |
| fd_ = -1; |
| } |
| } |
| |
| int JsFile::read(char* buf, size_t count, size_t* nread) { |
| if (is_open() && in_buf_.empty()) { |
| pp::Module::Get()->core()->CallOnMainThread(0, |
| factory_.NewCallback(&JsFile::Read, count)); |
| } |
| |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| if (is_block()) { |
| while (is_open() && in_buf_.empty()) |
| sys->cond().wait(sys->mutex()); |
| } else if (is_read_ready_) { |
| // We will still "block" waiting for data from JavaScript if we believe |
| // that the data is readily available and just needs to be sent over. If |
| // is_read_ready_ becomes false while we're waiting (another reader gets |
| // the data first), we'll exit the loop with whatever data is available in |
| // in_buf_. If in_buf_ is still empty, the loop below will be exited and |
| // return -1, EAGAIN. |
| while (is_open() && in_buf_.empty() && is_read_ready_) |
| sys->cond().wait(sys->mutex()); |
| } |
| |
| if (isatty() && (tio_.c_lflag & ICANON)) { |
| // Need to wait for the whole line. This code doesn't introduce performance |
| // issue because ICANON is used only during local ssh prompts, ssh session |
| // use raw TTY mode. |
| // TODO(dpolukhin): find better way to detect whole line. |
| while (true) { |
| if (!is_open()) |
| break; |
| |
| if (std::find(in_buf_.begin(), in_buf_.end(), '\n') != in_buf_.end()) { |
| break; |
| } |
| |
| while (is_open() && !is_read_ready_) |
| sys->cond().wait(sys->mutex()); |
| |
| uint64_t old_on_read_call_count = on_read_call_count_; |
| pp::Module::Get()->core()->CallOnMainThread( |
| 0, factory_.NewCallback(&JsFile::Read, 1)); |
| |
| while (is_open() && on_read_call_count_ == old_on_read_call_count) |
| sys->cond().wait(sys->mutex()); |
| } |
| } |
| |
| *nread = 0; |
| while (*nread < count) { |
| if (in_buf_.empty()) |
| break; |
| |
| buf[(*nread)++] = in_buf_.front(); |
| in_buf_.pop_front(); |
| } |
| |
| if (*nread == 0 && !is_block() && is_open()) { |
| *nread = -1; |
| return EAGAIN; |
| } |
| |
| return 0; |
| } |
| |
| int JsFile::write(const char* buf, size_t count, size_t* nwrote) { |
| if (!is_open()) |
| return EIO; |
| |
| out_buf_.insert(out_buf_.end(), buf, buf + count); |
| |
| if (isatty() && (tio_.c_oflag & OPOST) && (tio_.c_oflag & ONLCR)) { |
| // It could be performance issue to do this conversion in-place but |
| // fortunately it's used only for first few lines like password prompt. |
| for (size_t i = out_buf_.size() - count; i < out_buf_.size(); i++) { |
| if (out_buf_[i] == '\n') { |
| out_buf_.insert(out_buf_.begin() + i++, '\r'); |
| } |
| } |
| } |
| |
| *nwrote = count; |
| PostWriteTask(true); |
| return 0; |
| } |
| |
| int JsFile::fstat(nacl_abi_stat* out) { |
| memset(out, 0, sizeof(nacl_abi_stat)); |
| // openssl uses st_ino and st_dev to distinguish random sources and doesn't |
| // expect 0 there. |
| out->nacl_abi_st_ino = fd_; |
| out->nacl_abi_st_dev = fd_; |
| return 0; |
| } |
| |
| int JsFile::isatty() { |
| return is_atty_; |
| } |
| |
| void JsFile::InitTerminal() { |
| // Some sane values that produce good result. |
| tio_.c_iflag = ICRNL | IXON | IXOFF | IUTF8; |
| tio_.c_oflag = OPOST | ONLCR; |
| tio_.c_cflag = CREAD | 077; |
| tio_.c_lflag = |
| ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; |
| tio_.c_ispeed = B38400; |
| tio_.c_ospeed = B38400; |
| tio_.c_cc[VINTR] = 3; |
| tio_.c_cc[VQUIT] = 28; |
| tio_.c_cc[VERASE] = 127; |
| tio_.c_cc[VKILL] = 21; |
| tio_.c_cc[VEOF] = 4; |
| tio_.c_cc[VTIME] = 0; |
| tio_.c_cc[VMIN] = 1; |
| tio_.c_cc[VSWTC] = 0; |
| tio_.c_cc[VSTART] = 17; |
| tio_.c_cc[VSTOP] = 19; |
| tio_.c_cc[VSUSP] = 26; |
| tio_.c_cc[VEOL] = 0; |
| tio_.c_cc[VREPRINT] = 18; |
| tio_.c_cc[VDISCARD] = 15; |
| tio_.c_cc[VWERASE] = 23; |
| tio_.c_cc[VLNEXT] = 22; |
| tio_.c_cc[VEOL2] = 0; |
| } |
| |
| int JsFile::tcgetattr(termios* termios_p) { |
| *termios_p = tio_; |
| return 0; |
| } |
| |
| int JsFile::tcsetattr(int optional_actions, const termios* termios_p) { |
| tio_ = *termios_p; |
| return 0; |
| } |
| |
| int JsFile::fcntl(int cmd, va_list ap) { |
| if (cmd == F_GETFL) { |
| return oflag_; |
| } else if (cmd == F_SETFL) { |
| oflag_ = va_arg(ap, long); |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| int JsFile::ioctl(int request, va_list ap) { |
| if (request == TIOCGWINSZ) { |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| winsize* argp = va_arg(ap, winsize*); |
| if (sys->GetTerminalSize(&argp->ws_col, &argp->ws_row)) { |
| argp->ws_xpixel = 0; |
| argp->ws_ypixel = 0; |
| return 0; |
| } |
| } |
| return -1; |
| } |
| |
| bool JsFile::is_read_ready() { |
| return is_read_ready_ || !in_buf_.empty(); |
| } |
| |
| bool JsFile::is_write_ready() { |
| size_t not_acknowledged = write_sent_ - write_acknowledged_; |
| return (not_acknowledged + out_buf_.size()) < out_->GetWriteWindow(); |
| } |
| |
| void JsFile::PostWriteTask(bool always_post) { |
| if (!out_task_sent_ && !out_buf_.empty() && |
| (write_sent_ - write_acknowledged_) < out_->GetWriteWindow()) { |
| if (always_post || !pp::Module::Get()->core()->IsMainThread()) { |
| pp::Module::Get()->core()->CallOnMainThread( |
| 0, factory_.NewCallback(&JsFile::Write)); |
| out_task_sent_ = true; |
| } else { |
| // If on main Pepper thread and delay is not required call it directly. |
| Write(PP_OK); |
| } |
| } |
| } |
| |
| void JsFile::Read(int32_t result, size_t size) { |
| out_->Read(fd_, size); |
| } |
| |
| void JsFile::Write(int32_t result) { |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| Mutex::Lock lock(sys->mutex()); |
| out_task_sent_ = false; |
| |
| size_t count = std::min( |
| size_t(write_acknowledged_ + out_->GetWriteWindow() - write_sent_), |
| out_buf_.size()); |
| if (count == 0) { |
| LOG("JsFile::Write: %d is not ready for write, cached %d\n", |
| fd_, out_buf_.size()); |
| return; |
| } |
| |
| // Use temporary buffer in vector because std::deque doesn't use continuous |
| // memory inside. Alternative solution is to use vector as out_buf_ but erase |
| // below will require copy for the whole remaining bytes (will be slow with |
| // small writeWindow). |
| std::vector<char> buf(out_buf_.begin(), out_buf_.begin() + count); |
| if (out_->Write(fd_, &buf[0], count)) { |
| write_sent_ += count; |
| out_buf_.erase(out_buf_.begin(), out_buf_.begin() + count); |
| sys->cond().broadcast(); |
| } else { |
| assert(0); |
| PostWriteTask(true); |
| } |
| } |
| |
| void JsFile::Close(int32_t result) { |
| out_->Close(fd_); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| JsSocket::JsSocket(int fd, int oflag, OutputInterface* out) |
| : JsFile(fd, oflag, out), factory_(this) { |
| } |
| |
| JsSocket::~JsSocket() { |
| } |
| |
| bool JsSocket::connect(const char* host, uint16_t port) { |
| pp::Module::Get()->core()->CallOnMainThread(0, |
| factory_.NewCallback(&JsSocket::Connect, host, port)); |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| while (!is_open()) |
| sys->cond().wait(sys->mutex()); |
| |
| if (fd() == -1) |
| return false; |
| |
| return true; |
| } |
| |
| int JsSocket::isatty() { |
| return false; |
| } |
| |
| bool JsSocket::is_read_ready() { |
| return !in_buf_.empty(); |
| } |
| |
| void JsSocket::Connect(int32_t result, const char* host, uint16_t port) { |
| FileSystem* sys = FileSystem::GetFileSystem(); |
| Mutex::Lock lock(sys->mutex()); |
| out_->OpenSocket(fd_, host, port, this); |
| } |