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