Serenity Operating System
at hosted 369 lines 10 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "Process.h" 28#include <Kernel/TTY/TTY.h> 29#include <LibC/errno_numbers.h> 30#include <LibC/signal_numbers.h> 31#include <LibC/sys/ioctl_numbers.h> 32 33//#define TTY_DEBUG 34 35namespace Kernel { 36 37TTY::TTY(unsigned major, unsigned minor) 38 : CharacterDevice(major, minor) 39{ 40 set_default_termios(); 41} 42 43TTY::~TTY() 44{ 45} 46 47void TTY::set_default_termios() 48{ 49 memset(&m_termios, 0, sizeof(m_termios)); 50 m_termios.c_lflag |= ISIG | ECHO | ICANON; 51 static const char default_cc[32] = "\003\034\010\025\004\0\1\0\021\023\032\0\022\017\027\026\0"; 52 memcpy(m_termios.c_cc, default_cc, sizeof(default_cc)); 53} 54 55ssize_t TTY::read(FileDescription&, u8* buffer, ssize_t size) 56{ 57 ASSERT(size >= 0); 58 59 if (m_input_buffer.size() < static_cast<size_t>(size)) 60 size = m_input_buffer.size(); 61 62 if (in_canonical_mode()) { 63 int i = 0; 64 for (; i < size; i++) { 65 u8 ch = m_input_buffer.dequeue(); 66 if (ch == '\0') { 67 //Here we handle a ^D line, so we don't add the 68 //character to the output. 69 m_available_lines--; 70 break; 71 } else if (ch == '\n' || is_eol(ch)) { 72 buffer[i] = ch; 73 i++; 74 m_available_lines--; 75 break; 76 } 77 buffer[i] = ch; 78 } 79 return i; 80 } 81 82 for (int i = 0; i < size; i++) 83 buffer[i] = m_input_buffer.dequeue(); 84 85 return size; 86} 87 88ssize_t TTY::write(FileDescription&, const u8* buffer, ssize_t size) 89{ 90#ifdef TTY_DEBUG 91 dbg() << "TTY::write {" << String::format("%u", size) << "} "; 92 for (size_t i = 0; i < size; ++i) { 93 dbg() << String::format("%b ", buffer[i]); 94 } 95 dbg() << ""; 96#endif 97 on_tty_write(buffer, size); 98 return size; 99} 100 101bool TTY::can_read(const FileDescription&) const 102{ 103 if (in_canonical_mode()) { 104 return m_available_lines > 0; 105 } 106 return !m_input_buffer.is_empty(); 107} 108 109bool TTY::can_write(const FileDescription&) const 110{ 111 return true; 112} 113 114bool TTY::is_eol(u8 ch) const 115{ 116 return ch == m_termios.c_cc[VEOL]; 117} 118 119bool TTY::is_eof(u8 ch) const 120{ 121 return ch == m_termios.c_cc[VEOF]; 122} 123 124bool TTY::is_kill(u8 ch) const 125{ 126 return ch == m_termios.c_cc[VKILL]; 127} 128 129bool TTY::is_erase(u8 ch) const 130{ 131 return ch == m_termios.c_cc[VERASE]; 132} 133 134bool TTY::is_werase(u8 ch) const 135{ 136 return ch == m_termios.c_cc[VWERASE]; 137} 138 139void TTY::emit(u8 ch) 140{ 141 if (should_generate_signals()) { 142 if (ch == m_termios.c_cc[VINTR]) { 143 dbg() << tty_name() << ": VINTR pressed!"; 144 generate_signal(SIGINT); 145 return; 146 } 147 if (ch == m_termios.c_cc[VQUIT]) { 148 dbg() << tty_name() << ": VQUIT pressed!"; 149 generate_signal(SIGQUIT); 150 return; 151 } 152 if (ch == m_termios.c_cc[VSUSP]) { 153 dbg() << tty_name() << ": VSUSP pressed!"; 154 generate_signal(SIGTSTP); 155 return; 156 } 157 } 158 159 if (in_canonical_mode()) { 160 if (is_eof(ch)) { 161 m_available_lines++; 162 //We use '\0' to delimit the end 163 //of a line. 164 m_input_buffer.enqueue('\0'); 165 return; 166 } 167 if (is_kill(ch)) { 168 kill_line(); 169 return; 170 } 171 if (is_erase(ch)) { 172 do_backspace(); 173 return; 174 } 175 if (is_werase(ch)) { 176 erase_word(); 177 return; 178 } 179 if (ch == '\n' || is_eol(ch)) { 180 m_available_lines++; 181 } 182 } 183 m_input_buffer.enqueue(ch); 184 echo(ch); 185} 186 187bool TTY::can_do_backspace() const 188{ 189 //can't do back space if we're empty. Plus, we don't want to 190 //removing any lines "commited" by newlines or ^D. 191 if (!m_input_buffer.is_empty() && !is_eol(m_input_buffer.last()) && m_input_buffer.last() != '\0') { 192 return true; 193 } 194 return false; 195} 196 197void TTY::do_backspace() 198{ 199 if (can_do_backspace()) { 200 m_input_buffer.dequeue_end(); 201 echo(8); 202 echo(' '); 203 echo(8); 204 } 205} 206 207// TODO: Currently, both erase_word() and kill_line work by sending 208// a lot of VERASE characters; this is done because Terminal.cpp 209// doesn't currently support VWERASE and VKILL. When these are 210// implemented we could just send a VKILL or VWERASE. 211 212void TTY::erase_word() 213{ 214 //Note: if we have leading whitespace before the word 215 //we want to delete we have to also delete that. 216 bool first_char = false; 217 while (can_do_backspace()) { 218 u8 ch = m_input_buffer.last(); 219 if (ch == ' ' && first_char) 220 break; 221 if (ch != ' ') 222 first_char = true; 223 m_input_buffer.dequeue_end(); 224 erase_character(); 225 } 226} 227 228void TTY::kill_line() 229{ 230 while (can_do_backspace()) { 231 m_input_buffer.dequeue_end(); 232 erase_character(); 233 } 234} 235 236void TTY::erase_character() 237{ 238 echo(m_termios.c_cc[VERASE]); 239 echo(' '); 240 echo(m_termios.c_cc[VERASE]); 241} 242 243void TTY::generate_signal(int signal) 244{ 245 if (!pgid()) 246 return; 247 if (should_flush_on_signal()) 248 flush_input(); 249 dbg() << tty_name() << ": Send signal " << signal << " to everyone in pgrp " << pgid(); 250 InterruptDisabler disabler; // FIXME: Iterate over a set of process handles instead? 251 Process::for_each_in_pgrp(pgid(), [&](auto& process) { 252 dbg() << tty_name() << ": Send signal " << signal << " to " << process; 253 process.send_signal(signal, nullptr); 254 return IterationDecision::Continue; 255 }); 256} 257 258void TTY::flush_input() 259{ 260 m_available_lines = 0; 261 m_input_buffer.clear(); 262} 263 264void TTY::set_termios(const termios& t) 265{ 266 m_termios = t; 267#ifdef TTY_DEBUG 268 dbg() << tty_name() << " set_termios: " 269 << "ECHO=" << should_echo_input() 270 << ", ISIG=" << should_generate_signals() 271 << ", ICANON=" << in_canonical_mode() 272 << ", ECHOE=" << ((m_termios.c_lflag & ECHOE) != 0) 273 << ", ECHOK=" << ((m_termios.c_lflag & ECHOK) != 0) 274 << ", ECHONL=" << ((m_termios.c_lflag & ECHONL) != 0) 275 << ", ISTRIP=" << ((m_termios.c_iflag & ISTRIP) != 0) 276 << ", ICRNL=" << ((m_termios.c_iflag & ICRNL) != 0) 277 << ", INLCR=" << ((m_termios.c_iflag & INLCR) != 0) 278 << ", IGNCR=" << ((m_termios.c_iflag & IGNCR) != 0); 279#endif 280} 281 282int TTY::ioctl(FileDescription&, unsigned request, unsigned arg) 283{ 284 REQUIRE_PROMISE(tty); 285 auto& process = *Process::current; 286 pid_t pgid; 287 termios* tp; 288 winsize* ws; 289 290#if 0 291 // FIXME: When should we block things? 292 // How do we make this work together with MasterPTY forwarding to us? 293 if (process.tty() && process.tty() != this) { 294 return -ENOTTY; 295 } 296#endif 297 switch (request) { 298 case TIOCGPGRP: 299 return m_pgid; 300 case TIOCSPGRP: 301 pgid = static_cast<pid_t>(arg); 302 if (pgid <= 0) 303 return -EINVAL; 304 { 305 InterruptDisabler disabler; 306 auto* process = Process::from_pid(pgid); 307 if (!process) 308 return -EPERM; 309 if (pgid != process->pgid()) 310 return -EPERM; 311 if (Process::current->sid() != process->sid()) 312 return -EPERM; 313 } 314 m_pgid = pgid; 315 return 0; 316 case TCGETS: 317 tp = reinterpret_cast<termios*>(arg); 318 if (!process.validate_write(tp, sizeof(termios))) 319 return -EFAULT; 320 *tp = m_termios; 321 return 0; 322 case TCSETS: 323 case TCSETSF: 324 case TCSETSW: 325 tp = reinterpret_cast<termios*>(arg); 326 if (!process.validate_read(tp, sizeof(termios))) 327 return -EFAULT; 328 set_termios(*tp); 329 return 0; 330 case TIOCGWINSZ: 331 ws = reinterpret_cast<winsize*>(arg); 332 if (!process.validate_write(ws, sizeof(winsize))) 333 return -EFAULT; 334 ws->ws_row = m_rows; 335 ws->ws_col = m_columns; 336 return 0; 337 case TIOCSWINSZ: 338 ws = reinterpret_cast<winsize*>(arg); 339 if (!process.validate_read(ws, sizeof(winsize))) 340 return -EFAULT; 341 if (ws->ws_col == m_columns && ws->ws_row == m_rows) 342 return 0; 343 m_rows = ws->ws_row; 344 m_columns = ws->ws_col; 345 generate_signal(SIGWINCH); 346 return 0; 347 case TIOCSCTTY: 348 process.set_tty(this); 349 return 0; 350 case TIOCNOTTY: 351 process.set_tty(nullptr); 352 return 0; 353 } 354 ASSERT_NOT_REACHED(); 355 return -EINVAL; 356} 357 358void TTY::set_size(unsigned short columns, unsigned short rows) 359{ 360 m_rows = rows; 361 m_columns = columns; 362} 363 364void TTY::hang_up() 365{ 366 generate_signal(SIGHUP); 367} 368 369}