Serenity Operating System
at master 593 lines 18 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/ScopeGuard.h> 9#include <AK/StringView.h> 10#include <Kernel/API/Ioctl.h> 11#include <Kernel/API/POSIX/errno.h> 12#include <Kernel/API/POSIX/signal_numbers.h> 13#include <Kernel/API/ttydefaults.h> 14#include <Kernel/API/ttydefaultschars.h> 15#include <Kernel/Debug.h> 16#include <Kernel/InterruptDisabler.h> 17#include <Kernel/Process.h> 18#include <Kernel/TTY/TTY.h> 19#include <Kernel/UnixTypes.h> 20 21namespace Kernel { 22 23TTY::TTY(MajorNumber major, MinorNumber minor) 24 : CharacterDevice(major, minor) 25{ 26 set_default_termios(); 27} 28 29TTY::~TTY() = default; 30 31void TTY::set_default_termios() 32{ 33 memset(&m_termios, 0, sizeof(m_termios)); 34 m_termios.c_iflag = TTYDEF_IFLAG; 35 m_termios.c_oflag = TTYDEF_OFLAG; 36 m_termios.c_cflag = TTYDEF_CFLAG; 37 m_termios.c_lflag = TTYDEF_LFLAG; 38 m_termios.c_ispeed = TTYDEF_SPEED; 39 m_termios.c_ospeed = TTYDEF_SPEED; 40 memcpy(m_termios.c_cc, ttydefchars, sizeof(ttydefchars)); 41} 42 43ErrorOr<size_t> TTY::read(OpenFileDescription&, u64, UserOrKernelBuffer& buffer, size_t size) 44{ 45 if (Process::current().pgid() != pgid()) { 46 // FIXME: Should we propagate this error path somehow? 47 [[maybe_unused]] auto rc = Process::current().send_signal(SIGTTIN, nullptr); 48 return EINTR; 49 } 50 if (m_input_buffer.size() < static_cast<size_t>(size)) 51 size = m_input_buffer.size(); 52 53 bool need_evaluate_block_conditions = false; 54 auto result = buffer.write_buffered<512>(size, [&](Bytes data) { 55 size_t bytes_written = 0; 56 for (; bytes_written < data.size(); ++bytes_written) { 57 auto bit_index = m_input_buffer.head_index(); 58 bool is_special_character = m_special_character_bitmask[bit_index / 8] & (1 << (bit_index % 8)); 59 if (in_canonical_mode() && is_special_character) { 60 u8 ch = m_input_buffer.dequeue(); 61 if (ch == '\0') { 62 // EOF 63 m_available_lines--; 64 need_evaluate_block_conditions = true; 65 break; 66 } else { 67 // '\n' or EOL 68 data[bytes_written++] = ch; 69 m_available_lines--; 70 break; 71 } 72 } 73 data[bytes_written] = m_input_buffer.dequeue(); 74 } 75 return bytes_written; 76 }); 77 if ((!result.is_error() && result.value() > 0) || need_evaluate_block_conditions) 78 evaluate_block_conditions(); 79 return result; 80} 81 82ErrorOr<size_t> TTY::write(OpenFileDescription&, u64, UserOrKernelBuffer const& buffer, size_t size) 83{ 84 if (m_termios.c_lflag & TOSTOP && Process::current().pgid() != pgid()) { 85 [[maybe_unused]] auto rc = Process::current().send_signal(SIGTTOU, nullptr); 86 return EINTR; 87 } 88 89 constexpr size_t num_chars = 256; 90 return buffer.read_buffered<num_chars>(size, [&](ReadonlyBytes bytes) -> ErrorOr<size_t> { 91 u8 modified_data[num_chars * 2]; 92 size_t modified_data_size = 0; 93 for (const auto& byte : bytes) { 94 process_output(byte, [&modified_data, &modified_data_size](u8 out_ch) { 95 modified_data[modified_data_size++] = out_ch; 96 }); 97 } 98 auto bytes_written_or_error = on_tty_write(UserOrKernelBuffer::for_kernel_buffer(modified_data), modified_data_size); 99 if (bytes_written_or_error.is_error() || !(m_termios.c_oflag & OPOST) || !(m_termios.c_oflag & ONLCR)) 100 return bytes_written_or_error; 101 auto bytes_written = bytes_written_or_error.value(); 102 if (bytes_written == modified_data_size) 103 return bytes.size(); 104 105 // Degenerate case where we converted some newlines and encountered a partial write 106 107 // Calculate where in the input buffer the last character would have been 108 size_t pos_data = 0; 109 for (size_t pos_modified_data = 0; pos_modified_data < bytes_written; ++pos_data) { 110 if (bytes[pos_data] == '\n') 111 pos_modified_data += 2; 112 else 113 pos_modified_data += 1; 114 115 // Handle case where the '\r' got written but not the '\n' 116 // FIXME: Our strategy is to retry writing both. We should really be queuing a write for the corresponding '\n' 117 if (pos_modified_data > bytes_written) 118 --pos_data; 119 } 120 return pos_data; 121 }); 122} 123 124void TTY::echo_with_processing(u8 ch) 125{ 126 process_output(ch, [this](u8 out_ch) { echo(out_ch); }); 127} 128 129template<typename Functor> 130void TTY::process_output(u8 ch, Functor put_char) 131{ 132 if (m_termios.c_oflag & OPOST) { 133 if (ch == '\n' && (m_termios.c_oflag & ONLCR)) 134 put_char('\r'); 135 put_char(ch); 136 } else { 137 put_char(ch); 138 } 139} 140 141bool TTY::can_read(OpenFileDescription const&, u64) const 142{ 143 if (in_canonical_mode()) { 144 return m_available_lines > 0; 145 } 146 return !m_input_buffer.is_empty(); 147} 148 149bool TTY::can_write(OpenFileDescription const&, u64) const 150{ 151 return true; 152} 153 154bool TTY::is_eol(u8 ch) const 155{ 156 return ch == m_termios.c_cc[VEOL]; 157} 158 159bool TTY::is_eof(u8 ch) const 160{ 161 return ch == m_termios.c_cc[VEOF]; 162} 163 164bool TTY::is_kill(u8 ch) const 165{ 166 return ch == m_termios.c_cc[VKILL]; 167} 168 169bool TTY::is_erase(u8 ch) const 170{ 171 return ch == m_termios.c_cc[VERASE]; 172} 173 174bool TTY::is_werase(u8 ch) const 175{ 176 return ch == m_termios.c_cc[VWERASE]; 177} 178 179void TTY::emit(u8 ch, bool do_evaluate_block_conditions) 180{ 181 if (m_termios.c_iflag & ISTRIP) 182 ch &= 0x7F; 183 184 if (should_generate_signals()) { 185 if (ch == m_termios.c_cc[VINFO]) { 186 generate_signal(SIGINFO); 187 return; 188 } 189 if (ch == m_termios.c_cc[VINTR]) { 190 generate_signal(SIGINT); 191 return; 192 } 193 if (ch == m_termios.c_cc[VQUIT]) { 194 generate_signal(SIGQUIT); 195 return; 196 } 197 if (ch == m_termios.c_cc[VSUSP]) { 198 generate_signal(SIGTSTP); 199 if (auto original_process_parent = m_original_process_parent.strong_ref()) { 200 [[maybe_unused]] auto rc = original_process_parent->send_signal(SIGCHLD, nullptr); 201 } 202 // TODO: Else send it to the session leader maybe? 203 return; 204 } 205 } 206 207 ScopeGuard guard([&]() { 208 if (do_evaluate_block_conditions) 209 evaluate_block_conditions(); 210 }); 211 212 if (ch == '\r' && (m_termios.c_iflag & ICRNL)) 213 ch = '\n'; 214 else if (ch == '\n' && (m_termios.c_iflag & INLCR)) 215 ch = '\r'; 216 217 auto current_char_head_index = (m_input_buffer.head_index() + m_input_buffer.size()) % TTY_BUFFER_SIZE; 218 m_special_character_bitmask[current_char_head_index / 8] &= ~(1u << (current_char_head_index % 8)); 219 220 auto set_special_bit = [&] { 221 m_special_character_bitmask[current_char_head_index / 8] |= (1u << (current_char_head_index % 8)); 222 }; 223 224 if (in_canonical_mode()) { 225 if (is_eof(ch)) { 226 // Since EOF might change between when the data came in and when it is read, 227 // we use '\0' along with the bitmask to signal EOF. Any non-zero byte with 228 // the special bit set signals an end-of-line. 229 set_special_bit(); 230 m_available_lines++; 231 m_input_buffer.enqueue('\0'); 232 return; 233 } 234 if (is_kill(ch) && m_termios.c_lflag & ECHOK) { 235 kill_line(); 236 return; 237 } 238 if (is_erase(ch) && m_termios.c_lflag & ECHOE) { 239 do_backspace(); 240 return; 241 } 242 if (is_werase(ch)) { 243 erase_word(); 244 return; 245 } 246 247 if (ch == '\n') { 248 if (m_termios.c_lflag & ECHO || m_termios.c_lflag & ECHONL) 249 echo_with_processing('\n'); 250 251 set_special_bit(); 252 m_input_buffer.enqueue('\n'); 253 m_available_lines++; 254 return; 255 } 256 257 if (is_eol(ch)) { 258 set_special_bit(); 259 m_available_lines++; 260 } 261 } 262 263 m_input_buffer.enqueue(ch); 264 if (m_termios.c_lflag & ECHO) 265 echo_with_processing(ch); 266} 267 268bool TTY::can_do_backspace() const 269{ 270 // can't do back space if we're empty. Plus, we don't want to 271 // remove any lines "committed" by newlines or ^D. 272 if (!m_input_buffer.is_empty() && !is_eol(m_input_buffer.last()) && m_input_buffer.last() != '\0') { 273 return true; 274 } 275 return false; 276} 277 278static size_t length_with_tabs(CircularDeque<u8, TTY_BUFFER_SIZE> const& line) 279{ 280 size_t length = 0; 281 for (auto& ch : line) { 282 length += (ch == '\t') ? 8 - (length % 8) : 1; 283 } 284 return length; 285} 286 287void TTY::do_backspace() 288{ 289 if (can_do_backspace()) { 290 auto ch = m_input_buffer.dequeue_end(); 291 size_t to_delete = 1; 292 293 if (ch == '\t') { 294 auto length = length_with_tabs(m_input_buffer); 295 to_delete = 8 - (length % 8); 296 } 297 298 for (size_t i = 0; i < to_delete; ++i) { 299 // We deliberately don't process the output here. 300 echo('\b'); 301 echo(' '); 302 echo('\b'); 303 } 304 305 evaluate_block_conditions(); 306 } 307} 308 309// TODO: Currently, both erase_word() and kill_line work by sending 310// a lot of VERASE characters; this is done because Terminal.cpp 311// doesn't currently support VWERASE and VKILL. When these are 312// implemented we could just send a VKILL or VWERASE. 313 314void TTY::erase_word() 315{ 316 // Note: if we have leading whitespace before the word 317 // we want to delete we have to also delete that. 318 bool first_char = false; 319 bool did_dequeue = false; 320 while (can_do_backspace()) { 321 u8 ch = m_input_buffer.last(); 322 if (ch == ' ' && first_char) 323 break; 324 if (ch != ' ') 325 first_char = true; 326 m_input_buffer.dequeue_end(); 327 did_dequeue = true; 328 erase_character(); 329 } 330 if (did_dequeue) 331 evaluate_block_conditions(); 332} 333 334void TTY::kill_line() 335{ 336 bool did_dequeue = false; 337 while (can_do_backspace()) { 338 m_input_buffer.dequeue_end(); 339 did_dequeue = true; 340 erase_character(); 341 } 342 if (did_dequeue) 343 evaluate_block_conditions(); 344} 345 346void TTY::erase_character() 347{ 348 // We deliberately don't process the output here. 349 echo(m_termios.c_cc[VERASE]); 350 echo(' '); 351 echo(m_termios.c_cc[VERASE]); 352} 353 354void TTY::generate_signal(int signal) 355{ 356 if (!pgid()) 357 return; 358 if (should_flush_on_signal()) 359 flush_input(); 360 dbgln_if(TTY_DEBUG, "Send signal {} to everyone in pgrp {}", signal, pgid().value()); 361 InterruptDisabler disabler; // FIXME: Iterate over a set of process handles instead? 362 MUST(Process::current().for_each_in_pgrp_in_same_jail(pgid(), [&](auto& process) -> ErrorOr<void> { 363 dbgln_if(TTY_DEBUG, "Send signal {} to {}", signal, process); 364 // FIXME: Should this error be propagated somehow? 365 [[maybe_unused]] auto rc = process.send_signal(signal, nullptr); 366 return {}; 367 })); 368} 369 370void TTY::flush_input() 371{ 372 m_available_lines = 0; 373 m_input_buffer.clear(); 374 evaluate_block_conditions(); 375} 376 377ErrorOr<void> TTY::set_termios(termios const& t) 378{ 379 ErrorOr<void> rc; 380 m_termios = t; 381 382 dbgln_if(TTY_DEBUG, "set_termios: ECHO={}, ISIG={}, ICANON={}, ECHOE={}, ECHOK={}, ECHONL={}, ISTRIP={}, ICRNL={}, INLCR={}, IGNCR={}, OPOST={}, ONLCR={}", 383 should_echo_input(), 384 should_generate_signals(), 385 in_canonical_mode(), 386 ((m_termios.c_lflag & ECHOE) != 0), 387 ((m_termios.c_lflag & ECHOK) != 0), 388 ((m_termios.c_lflag & ECHONL) != 0), 389 ((m_termios.c_iflag & ISTRIP) != 0), 390 ((m_termios.c_iflag & ICRNL) != 0), 391 ((m_termios.c_iflag & INLCR) != 0), 392 ((m_termios.c_iflag & IGNCR) != 0), 393 ((m_termios.c_oflag & OPOST) != 0), 394 ((m_termios.c_oflag & ONLCR) != 0)); 395 396 struct FlagDescription { 397 tcflag_t value; 398 StringView name; 399 }; 400 401 constexpr FlagDescription unimplemented_iflags[] = { 402 { IGNBRK, "IGNBRK"sv }, 403 { BRKINT, "BRKINT"sv }, 404 { IGNPAR, "IGNPAR"sv }, 405 { PARMRK, "PARMRK"sv }, 406 { INPCK, "INPCK"sv }, 407 { IGNCR, "IGNCR"sv }, 408 { IUCLC, "IUCLC"sv }, 409 { IXON, "IXON"sv }, 410 { IXANY, "IXANY"sv }, 411 { IXOFF, "IXOFF"sv }, 412 { IMAXBEL, "IMAXBEL"sv }, 413 { IUTF8, "IUTF8"sv } 414 }; 415 for (auto flag : unimplemented_iflags) { 416 if (m_termios.c_iflag & flag.value) { 417 dbgln("FIXME: iflag {} unimplemented", flag.name); 418 rc = ENOTIMPL; 419 } 420 } 421 422 constexpr FlagDescription unimplemented_oflags[] = { 423 { OLCUC, "OLCUC"sv }, 424 { ONOCR, "ONOCR"sv }, 425 { ONLRET, "ONLRET"sv }, 426 { OFILL, "OFILL"sv }, 427 { OFDEL, "OFDEL"sv } 428 }; 429 for (auto flag : unimplemented_oflags) { 430 if (m_termios.c_oflag & flag.value) { 431 dbgln("FIXME: oflag {} unimplemented", flag.name); 432 rc = ENOTIMPL; 433 } 434 } 435 436 if ((m_termios.c_cflag & CSIZE) != CS8) { 437 dbgln("FIXME: Character sizes other than 8 bits are not supported"); 438 rc = ENOTIMPL; 439 } 440 441 constexpr FlagDescription unimplemented_cflags[] = { 442 { CSTOPB, "CSTOPB"sv }, 443 { CREAD, "CREAD"sv }, 444 { PARENB, "PARENB"sv }, 445 { PARODD, "PARODD"sv }, 446 { HUPCL, "HUPCL"sv }, 447 { CLOCAL, "CLOCAL"sv } 448 }; 449 for (auto flag : unimplemented_cflags) { 450 if (m_termios.c_cflag & flag.value) { 451 dbgln("FIXME: cflag {} unimplemented", flag.name); 452 rc = ENOTIMPL; 453 } 454 } 455 456 constexpr FlagDescription unimplemented_lflags[] = { 457 { TOSTOP, "TOSTOP"sv }, 458 { IEXTEN, "IEXTEN"sv } 459 }; 460 for (auto flag : unimplemented_lflags) { 461 if (m_termios.c_lflag & flag.value) { 462 dbgln("FIXME: lflag {} unimplemented", flag.name); 463 rc = ENOTIMPL; 464 } 465 } 466 467 return rc; 468} 469 470ErrorOr<void> TTY::ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg) 471{ 472 auto& current_process = Process::current(); 473 TRY(current_process.require_promise(Pledge::tty)); 474#if 0 475 // FIXME: When should we block things? 476 // How do we make this work together with MasterPTY forwarding to us? 477 if (current_process.tty() && current_process.tty() != this) { 478 return ENOTTY; 479 } 480#endif 481 switch (request) { 482 case TIOCGPGRP: { 483 auto user_pgid = static_ptr_cast<pid_t*>(arg); 484 auto pgid = this->pgid().value(); 485 return copy_to_user(user_pgid, &pgid); 486 } 487 case TIOCSPGRP: { 488 ProcessGroupID pgid = static_cast<pid_t>(arg.ptr()); 489 if (pgid <= 0) 490 return EINVAL; 491 InterruptDisabler disabler; 492 auto process_group = ProcessGroup::from_pgid(pgid); 493 // Disallow setting a nonexistent PGID. 494 if (!process_group) 495 return EINVAL; 496 497 auto process = Process::from_pid_in_same_jail(ProcessID(pgid.value())); 498 SessionID new_sid = process ? process->sid() : Process::get_sid_from_pgid(pgid); 499 if (!new_sid || new_sid != current_process.sid()) 500 return EPERM; 501 if (process && pgid != process->pgid()) 502 return EPERM; 503 m_pg = process_group; 504 505 if (process) { 506 if (auto parent = Process::from_pid_ignoring_jails(process->ppid())) { 507 m_original_process_parent = *parent; 508 return {}; 509 } 510 } 511 512 m_original_process_parent = nullptr; 513 return {}; 514 } 515 case TCGETS: { 516 auto user_termios = static_ptr_cast<termios*>(arg); 517 return copy_to_user(user_termios, &m_termios); 518 } 519 case TCSETS: 520 case TCSETSF: 521 case TCSETSW: { 522 auto user_termios = static_ptr_cast<termios const*>(arg); 523 auto termios = TRY(copy_typed_from_user(user_termios)); 524 auto rc = set_termios(termios); 525 if (request == TCSETSF) 526 flush_input(); 527 return rc; 528 } 529 case TCFLSH: { 530 // Serenity's TTY implementation does not use an output buffer, so ignore TCOFLUSH. 531 auto operation = static_cast<u8>(arg.ptr()); 532 if (operation == TCIFLUSH || operation == TCIOFLUSH) { 533 flush_input(); 534 } else if (operation != TCOFLUSH) { 535 return EINVAL; 536 } 537 return {}; 538 } 539 case TIOCGWINSZ: { 540 auto user_winsize = static_ptr_cast<winsize*>(arg); 541 winsize ws {}; 542 ws.ws_row = m_rows; 543 ws.ws_col = m_columns; 544 ws.ws_xpixel = 0; 545 ws.ws_ypixel = 0; 546 return copy_to_user(user_winsize, &ws); 547 } 548 case TIOCSWINSZ: { 549 auto user_winsize = static_ptr_cast<winsize const*>(arg); 550 auto ws = TRY(copy_typed_from_user(user_winsize)); 551 if (ws.ws_col == m_columns && ws.ws_row == m_rows) 552 return {}; 553 m_rows = ws.ws_row; 554 m_columns = ws.ws_col; 555 generate_signal(SIGWINCH); 556 return {}; 557 } 558 case TIOCSCTTY: 559 current_process.set_tty(this); 560 return {}; 561 case TIOCSTI: 562 return EIO; 563 case TIOCNOTTY: 564 current_process.set_tty(nullptr); 565 return {}; 566 case KDSETMODE: { 567 auto mode = static_cast<unsigned int>(arg.ptr()); 568 if (mode != KD_TEXT && mode != KD_GRAPHICS) 569 return EINVAL; 570 571 set_graphical(mode == KD_GRAPHICS); 572 return {}; 573 } 574 case KDGETMODE: { 575 auto mode_ptr = static_ptr_cast<int*>(arg); 576 int mode = (is_graphical()) ? KD_GRAPHICS : KD_TEXT; 577 return copy_to_user(mode_ptr, &mode); 578 } 579 } 580 return EINVAL; 581} 582 583void TTY::set_size(unsigned short columns, unsigned short rows) 584{ 585 m_rows = rows; 586 m_columns = columns; 587} 588 589void TTY::hang_up() 590{ 591 generate_signal(SIGHUP); 592} 593}