OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 "apps/moterm/moterm_driver.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <limits> |
| 9 |
| 10 #include "base/logging.h" |
| 11 #include "mojo/services/files/public/interfaces/types.mojom.h" |
| 12 |
| 13 const uint8_t kEOT = 4; |
| 14 const uint8_t kNL = 10; |
| 15 const uint8_t kCR = 13; |
| 16 const uint8_t kDEL = 127; |
| 17 |
| 18 MotermDriver::PendingRead::PendingRead(uint32_t num_bytes, |
| 19 const ReadCallback& callback) |
| 20 : num_bytes(num_bytes), callback(callback) { |
| 21 } |
| 22 |
| 23 MotermDriver::PendingRead::~PendingRead() { |
| 24 } |
| 25 |
| 26 // static |
| 27 base::WeakPtr<MotermDriver> MotermDriver::Create( |
| 28 Client* client, |
| 29 mojo::InterfaceRequest<mojo::files::File> request) { |
| 30 return (new MotermDriver(client, request.Pass()))->weak_factory_.GetWeakPtr(); |
| 31 } |
| 32 |
| 33 void MotermDriver::Detach() { |
| 34 client_ = nullptr; |
| 35 delete this; |
| 36 } |
| 37 |
| 38 void MotermDriver::SendData(const void* bytes, size_t num_bytes) { |
| 39 DCHECK(client_); |
| 40 DCHECK(!is_closed_); |
| 41 |
| 42 if (icanon_) { |
| 43 for (size_t i = 0; i < num_bytes; i++) |
| 44 HandleCanonicalModeInput(static_cast<const uint8_t*>(bytes)[i]); |
| 45 } else { |
| 46 // If not in canonical ("cooked") mode, just send data. |
| 47 const uint8_t* b = static_cast<const uint8_t*>(bytes); |
| 48 for (size_t i = 0; i < num_bytes; i++) |
| 49 send_data_queue_.push_back(b[i]); |
| 50 |
| 51 CompletePendingReads(); |
| 52 } |
| 53 } |
| 54 |
| 55 MotermDriver::MotermDriver(Client* client, |
| 56 mojo::InterfaceRequest<mojo::files::File> request) |
| 57 : client_(client), |
| 58 is_closed_(false), |
| 59 binding_(this, request.Pass()), |
| 60 icanon_(true), |
| 61 icrnl_(true), |
| 62 veof_(kEOT), |
| 63 verase_(kDEL), |
| 64 onlcr_(true), |
| 65 weak_factory_(this) { |
| 66 DCHECK(client_); |
| 67 } |
| 68 |
| 69 MotermDriver::~MotermDriver() { |
| 70 if (client_) { |
| 71 // Invalidate weak pointers now, to make sure the client doesn't call us. |
| 72 weak_factory_.InvalidateWeakPtrs(); |
| 73 client_->OnDestroyed(); |
| 74 } |
| 75 } |
| 76 |
| 77 void MotermDriver::HandleCanonicalModeInput(uint8_t ch) { |
| 78 DCHECK(icanon_); |
| 79 |
| 80 // Translate CR to NL, if appropriate. |
| 81 if (ch == kCR && icrnl_) |
| 82 ch = kNL; |
| 83 |
| 84 // Newline. |
| 85 // TODO(vtl): In addition to NL, we're supposed to handle settable EOL and |
| 86 // EOL2. |
| 87 if (ch == kNL) { // Newline. |
| 88 input_line_queue_.push_back(ch); |
| 89 HandleOutput(&ch, 1); |
| 90 FlushInputLine(); |
| 91 return; |
| 92 } |
| 93 |
| 94 // EOF at beginning of line. |
| 95 if (ch == veof_ && input_line_queue_.empty()) { |
| 96 // Don't add the character. Just flush and nuke ourselves. |
| 97 FlushInputLine(); |
| 98 delete this; |
| 99 return; |
| 100 } |
| 101 |
| 102 if (ch == verase_) { // Erase (backspace). |
| 103 if (!input_line_queue_.empty()) { |
| 104 // TODO(vtl): This is wrong for utf-8 (see Linux's IUTF8). |
| 105 input_line_queue_.pop_back(); |
| 106 HandleOutput(reinterpret_cast<const uint8_t*>("\x08 \x08"), 3); |
| 107 } // Else do nothing. |
| 108 return; |
| 109 } |
| 110 |
| 111 // Everything else. |
| 112 // TODO(vtl): Probably want to display control characters as ^X (e.g.). |
| 113 // TODO(vtl): Handle ^W. |
| 114 input_line_queue_.push_back(ch); |
| 115 HandleOutput(&ch, 1); |
| 116 } |
| 117 |
| 118 void MotermDriver::CompletePendingReads() { |
| 119 while (send_data_queue_.size() && pending_read_queue_.size()) { |
| 120 PendingRead pending_read = pending_read_queue_.front(); |
| 121 pending_read_queue_.pop_front(); |
| 122 |
| 123 size_t data_size = std::min(static_cast<size_t>(pending_read.num_bytes), |
| 124 send_data_queue_.size()); |
| 125 mojo::Array<uint8_t> data(data_size); |
| 126 for (size_t i = 0; i < data_size; i++) { |
| 127 data[i] = send_data_queue_[i]; |
| 128 // In canonical mode, each read only gets a single line. |
| 129 if (icanon_ && data[i] == kNL) { |
| 130 data_size = i + 1; |
| 131 data.resize(data_size); |
| 132 break; |
| 133 } |
| 134 } |
| 135 send_data_queue_.erase(send_data_queue_.begin(), |
| 136 send_data_queue_.begin() + data_size); |
| 137 |
| 138 pending_read.callback.Run(mojo::files::ERROR_OK, data.Pass()); |
| 139 } |
| 140 } |
| 141 |
| 142 void MotermDriver::FlushInputLine() { |
| 143 for (size_t i = 0; i < input_line_queue_.size(); i++) |
| 144 send_data_queue_.push_back(input_line_queue_[i]); |
| 145 input_line_queue_.clear(); |
| 146 CompletePendingReads(); |
| 147 } |
| 148 |
| 149 void MotermDriver::HandleOutput(const uint8_t* bytes, size_t num_bytes) { |
| 150 // Can we be smarter and not always copy? |
| 151 std::vector<uint8_t> translated_bytes; |
| 152 translated_bytes.reserve(num_bytes); |
| 153 |
| 154 for (size_t i = 0; i < num_bytes; i++) { |
| 155 uint8_t ch = bytes[i]; |
| 156 if (ch == kNL && onlcr_) { |
| 157 translated_bytes.push_back(kCR); |
| 158 translated_bytes.push_back(kNL); |
| 159 } else { |
| 160 translated_bytes.push_back(ch); |
| 161 } |
| 162 } |
| 163 |
| 164 // TODO(vtl): It seems extremely unlikely that we'd overlow a |uint32_t| here |
| 165 // (but it's theoretically possible). But perhaps we should handle it more |
| 166 // gracefully if it ever comes up. |
| 167 CHECK_LE(translated_bytes.size(), std::numeric_limits<uint32_t>::max()); |
| 168 client_->OnDataReceived(translated_bytes.data(), |
| 169 static_cast<uint32_t>(translated_bytes.size())); |
| 170 } |
| 171 |
| 172 void MotermDriver::Close(const CloseCallback& callback) { |
| 173 if (is_closed_) { |
| 174 callback.Run(mojo::files::ERROR_CLOSED); |
| 175 return; |
| 176 } |
| 177 |
| 178 callback.Run(mojo::files::ERROR_OK); |
| 179 |
| 180 // TODO(vtl): Call pending read callbacks? |
| 181 |
| 182 client_->OnClosed(); |
| 183 } |
| 184 |
| 185 void MotermDriver::Read(uint32_t num_bytes_to_read, |
| 186 int64_t offset, |
| 187 mojo::files::Whence whence, |
| 188 const ReadCallback& callback) { |
| 189 if (is_closed_) { |
| 190 callback.Run(mojo::files::ERROR_CLOSED, mojo::Array<uint8_t>()); |
| 191 return; |
| 192 } |
| 193 |
| 194 if (offset != 0 || whence != mojo::files::WHENCE_FROM_CURRENT) { |
| 195 // TODO(vtl): Is this the "right" behavior? |
| 196 callback.Run(mojo::files::ERROR_INVALID_ARGUMENT, mojo::Array<uint8_t>()); |
| 197 return; |
| 198 } |
| 199 |
| 200 if (!num_bytes_to_read) { |
| 201 callback.Run(mojo::files::ERROR_OK, mojo::Array<uint8_t>()); |
| 202 return; |
| 203 } |
| 204 |
| 205 pending_read_queue_.push_back(PendingRead(num_bytes_to_read, callback)); |
| 206 CompletePendingReads(); |
| 207 } |
| 208 |
| 209 void MotermDriver::Write(mojo::Array<uint8_t> bytes_to_write, |
| 210 int64_t offset, |
| 211 mojo::files::Whence whence, |
| 212 const WriteCallback& callback) { |
| 213 DCHECK(!bytes_to_write.is_null()); |
| 214 |
| 215 if (is_closed_) { |
| 216 callback.Run(mojo::files::ERROR_CLOSED, 0); |
| 217 return; |
| 218 } |
| 219 |
| 220 if (offset != 0 || whence != mojo::files::WHENCE_FROM_CURRENT) { |
| 221 // TODO(vtl): Is this the "right" behavior? |
| 222 callback.Run(mojo::files::ERROR_INVALID_ARGUMENT, 0); |
| 223 return; |
| 224 } |
| 225 |
| 226 if (!bytes_to_write.size()) { |
| 227 callback.Run(mojo::files::ERROR_OK, 0); |
| 228 return; |
| 229 } |
| 230 |
| 231 HandleOutput(static_cast<const uint8_t*>(&bytes_to_write.front()), |
| 232 bytes_to_write.size()); |
| 233 |
| 234 // TODO(vtl): Is this OK if the client detached (and we're destroyed?). |
| 235 callback.Run(mojo::files::ERROR_OK, |
| 236 static_cast<uint32_t>(bytes_to_write.size())); |
| 237 } |
| 238 |
| 239 void MotermDriver::ReadToStream(mojo::ScopedDataPipeProducerHandle source, |
| 240 int64_t offset, |
| 241 mojo::files::Whence whence, |
| 242 int64_t num_bytes_to_read, |
| 243 const ReadToStreamCallback& callback) { |
| 244 if (is_closed_) { |
| 245 callback.Run(mojo::files::ERROR_CLOSED); |
| 246 return; |
| 247 } |
| 248 |
| 249 // TODO(vtl) |
| 250 NOTIMPLEMENTED(); |
| 251 callback.Run(mojo::files::ERROR_UNIMPLEMENTED); |
| 252 } |
| 253 |
| 254 void MotermDriver::WriteFromStream(mojo::ScopedDataPipeConsumerHandle sink, |
| 255 int64_t offset, |
| 256 mojo::files::Whence whence, |
| 257 const WriteFromStreamCallback& callback) { |
| 258 if (is_closed_) { |
| 259 callback.Run(mojo::files::ERROR_CLOSED); |
| 260 return; |
| 261 } |
| 262 |
| 263 // TODO(vtl) |
| 264 NOTIMPLEMENTED(); |
| 265 callback.Run(mojo::files::ERROR_UNIMPLEMENTED); |
| 266 } |
| 267 |
| 268 void MotermDriver::Tell(const TellCallback& callback) { |
| 269 if (is_closed_) { |
| 270 callback.Run(mojo::files::ERROR_CLOSED, 0); |
| 271 return; |
| 272 } |
| 273 |
| 274 // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
| 275 // unsupported/EINVAL is better.) |
| 276 callback.Run(mojo::files::ERROR_UNAVAILABLE, 0); |
| 277 } |
| 278 |
| 279 void MotermDriver::Seek(int64_t offset, |
| 280 mojo::files::Whence whence, |
| 281 const SeekCallback& callback) { |
| 282 if (is_closed_) { |
| 283 callback.Run(mojo::files::ERROR_CLOSED, 0); |
| 284 return; |
| 285 } |
| 286 |
| 287 // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
| 288 // unsupported/EINVAL is better.) |
| 289 callback.Run(mojo::files::ERROR_UNAVAILABLE, 0); |
| 290 } |
| 291 |
| 292 void MotermDriver::Stat(const StatCallback& callback) { |
| 293 if (is_closed_) { |
| 294 callback.Run(mojo::files::ERROR_CLOSED, nullptr); |
| 295 return; |
| 296 } |
| 297 |
| 298 // TODO(vtl) |
| 299 NOTIMPLEMENTED(); |
| 300 callback.Run(mojo::files::ERROR_UNIMPLEMENTED, nullptr); |
| 301 } |
| 302 |
| 303 void MotermDriver::Truncate(int64_t size, const TruncateCallback& callback) { |
| 304 if (is_closed_) { |
| 305 callback.Run(mojo::files::ERROR_CLOSED); |
| 306 return; |
| 307 } |
| 308 |
| 309 // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
| 310 // unsupported/EINVAL is better.) |
| 311 callback.Run(mojo::files::ERROR_UNAVAILABLE); |
| 312 } |
| 313 |
| 314 void MotermDriver::Touch(mojo::files::TimespecOrNowPtr atime, |
| 315 mojo::files::TimespecOrNowPtr mtime, |
| 316 const TouchCallback& callback) { |
| 317 if (is_closed_) { |
| 318 callback.Run(mojo::files::ERROR_CLOSED); |
| 319 return; |
| 320 } |
| 321 |
| 322 // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
| 323 // unsupported/EINVAL is better.) |
| 324 callback.Run(mojo::files::ERROR_UNAVAILABLE); |
| 325 } |
| 326 |
| 327 void MotermDriver::Dup(mojo::InterfaceRequest<mojo::files::File> file, |
| 328 const DupCallback& callback) { |
| 329 if (is_closed_) { |
| 330 callback.Run(mojo::files::ERROR_CLOSED); |
| 331 return; |
| 332 } |
| 333 |
| 334 // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
| 335 // unsupported/EINVAL is better.) |
| 336 callback.Run(mojo::files::ERROR_UNAVAILABLE); |
| 337 } |
| 338 |
| 339 void MotermDriver::Reopen(mojo::InterfaceRequest<mojo::files::File> file, |
| 340 uint32_t open_flags, |
| 341 const ReopenCallback& callback) { |
| 342 if (is_closed_) { |
| 343 callback.Run(mojo::files::ERROR_CLOSED); |
| 344 return; |
| 345 } |
| 346 |
| 347 // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
| 348 // unsupported/EINVAL is better.) |
| 349 callback.Run(mojo::files::ERROR_UNAVAILABLE); |
| 350 } |
| 351 |
| 352 void MotermDriver::AsBuffer(const AsBufferCallback& callback) { |
| 353 if (is_closed_) { |
| 354 callback.Run(mojo::files::ERROR_CLOSED, mojo::ScopedSharedBufferHandle()); |
| 355 return; |
| 356 } |
| 357 |
| 358 // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
| 359 // unsupported/EINVAL is better.) |
| 360 callback.Run(mojo::files::ERROR_UNAVAILABLE, |
| 361 mojo::ScopedSharedBufferHandle()); |
| 362 } |
| 363 |
| 364 void MotermDriver::Ioctl(uint32_t request, |
| 365 mojo::Array<uint32_t> in_values, |
| 366 const IoctlCallback& callback) { |
| 367 if (is_closed_) { |
| 368 callback.Run(mojo::files::ERROR_CLOSED, mojo::Array<uint32_t>()); |
| 369 return; |
| 370 } |
| 371 |
| 372 // TODO(vtl) |
| 373 callback.Run(mojo::files::ERROR_UNIMPLEMENTED, mojo::Array<uint32_t>()); |
| 374 } |
OLD | NEW |