Serenity Operating System
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}