use alloc::{ collections::vec_deque::VecDeque, sync::{Arc, Weak}, }; use bindings::{EINTR, ENOTTY, EPERM}; use bitflags::bitflags; use crate::{ io::Buffer, prelude::*, sync::{AsRefPosition as _, CondVar}, }; use super::{ task::{ProcessList, Session, Signal, Thread}, user::{UserPointer, UserPointerMut}, }; const BUFFER_SIZE: usize = 4096; const NCCS: usize = 32; // taken from linux kernel code /* c_cc characters */ const VINTR: usize = 0; const VQUIT: usize = 1; const VERASE: usize = 2; const VKILL: usize = 3; const VEOF: usize = 4; const VTIME: usize = 5; const VMIN: usize = 6; const VSWTC: usize = 7; const VSTART: usize = 8; const VSTOP: usize = 9; const VSUSP: usize = 10; const VEOL: usize = 11; const VREPRINT: usize = 12; const VDISCARD: usize = 13; const VWERASE: usize = 14; const VLNEXT: usize = 15; const VEOL2: usize = 16; /* c_iflag bits */ bitflags! { #[derive(Debug)] pub struct TermioIFlags: u16 { /// Ignore break condition const IGNBRK = 0x0001; /// Signal interrupt on break const BRKINT = 0x0002; /// Ignore characters with parity errors const IGNPAR = 0x0004; /// Mark parity and framing errors const PARMRK = 0x0008; /// Enable input parity check const INPCK = 0x0010; /// Strip 8th bit off characters const ISTRIP = 0x0020; /// Map NL to CR on input const INLCR = 0x0040; /// Ignore CR const IGNCR = 0x0080; /// Map CR to NL on input const ICRNL = 0x0100; const IUCLC = 0x0200; const IXON = 0x0400; /// Any character will restart after stop const IXANY = 0x0800; const IXOFF = 0x1000; const IMAXBEL = 0x2000; const IUTF8 = 0x4000; } #[derive(Debug)] pub struct TermioOFlags: u16 { /// Perform output processing const OPOST = 0x0001; const OLCUC = 0x0002; const ONLCR = 0x0004; const OCRNL = 0x0008; const ONOCR = 0x0010; const ONLRET = 0x0020; const OFILL = 0x0040; const OFDEL = 0x0080; } } bitflags! { #[derive(Debug)] pub struct TermioLFlags: u16 { const ISIG = 0x0001; const ICANON = 0x0002; const XCASE = 0x0004; const ECHO = 0x0008; const ECHOE = 0x0010; const ECHOK = 0x0020; const ECHONL = 0x0040; const NOFLSH = 0x0080; const TOSTOP = 0x0100; const ECHOCTL = 0x0200; const ECHOPRT = 0x0400; const ECHOKE = 0x0800; const FLUSHO = 0x1000; const PENDIN = 0x4000; const IEXTEN = 0x8000; } } /* c_cflag bit meaning */ const B38400: u32 = 0x0000000f; const CS8: u32 = 0x00000030; const CREAD: u32 = 0x00000080; const HUPCL: u32 = 0x00000400; // line disciplines const N_TTY: u8 = 0; #[derive(Debug)] pub struct Termios { iflag: TermioIFlags, oflag: TermioOFlags, cflag: u32, lflag: TermioLFlags, line: u8, cc: [u8; NCCS], } macro_rules! CTRL { ('A') => { 0x01 }; ('B') => { 0x02 }; ('C') => { 0x03 }; ('D') => { 0x04 }; ('E') => { 0x05 }; ('F') => { 0x06 }; ('G') => { 0x07 }; ('H') => { 0x08 }; ('I') => { 0x09 }; ('J') => { 0x0A }; ('K') => { 0x0B }; ('L') => { 0x0C }; ('M') => { 0x0D }; ('N') => { 0x0E }; ('O') => { 0x0F }; ('P') => { 0x10 }; ('Q') => { 0x11 }; ('R') => { 0x12 }; ('S') => { 0x13 }; ('T') => { 0x14 }; ('U') => { 0x15 }; ('V') => { 0x16 }; ('W') => { 0x17 }; ('X') => { 0x18 }; ('Y') => { 0x19 }; ('Z') => { 0x1A }; ('\\') => { 0x1c }; } impl Termios { pub fn veof(&self) -> u8 { self.cc[VEOF] } pub fn veol(&self) -> u8 { self.cc[VEOL] } pub fn veol2(&self) -> u8 { self.cc[VEOL2] } pub fn vintr(&self) -> u8 { self.cc[VINTR] } pub fn vquit(&self) -> u8 { self.cc[VQUIT] } pub fn vsusp(&self) -> u8 { self.cc[VSUSP] } pub fn verase(&self) -> u8 { self.cc[VERASE] } pub fn vkill(&self) -> u8 { self.cc[VKILL] } pub fn echo(&self) -> bool { self.lflag.contains(TermioLFlags::ECHO) } pub fn echoe(&self) -> bool { self.lflag.contains(TermioLFlags::ECHOE) } pub fn echoctl(&self) -> bool { self.lflag.contains(TermioLFlags::ECHOCTL) } pub fn echoke(&self) -> bool { self.lflag.contains(TermioLFlags::ECHOKE) } pub fn echok(&self) -> bool { self.lflag.contains(TermioLFlags::ECHOK) } pub fn echonl(&self) -> bool { self.lflag.contains(TermioLFlags::ECHONL) } pub fn isig(&self) -> bool { self.lflag.contains(TermioLFlags::ISIG) } pub fn icanon(&self) -> bool { self.lflag.contains(TermioLFlags::ICANON) } pub fn iexten(&self) -> bool { self.lflag.contains(TermioLFlags::IEXTEN) } pub fn igncr(&self) -> bool { self.iflag.contains(TermioIFlags::IGNCR) } pub fn icrnl(&self) -> bool { self.iflag.contains(TermioIFlags::ICRNL) } pub fn inlcr(&self) -> bool { self.iflag.contains(TermioIFlags::INLCR) } pub fn noflsh(&self) -> bool { self.lflag.contains(TermioLFlags::NOFLSH) } pub fn new_standard() -> Self { let cc = core::array::from_fn(|idx| match idx { VINTR => CTRL!('C'), VQUIT => CTRL!('\\'), VERASE => 0x7f, VKILL => CTRL!('U'), VEOF => CTRL!('D'), VSUSP => CTRL!('Z'), VMIN => 1, _ => 0, }); Self { iflag: TermioIFlags::ICRNL | TermioIFlags::IXOFF, oflag: TermioOFlags::OPOST | TermioOFlags::ONLCR, cflag: B38400 | CS8 | CREAD | HUPCL, lflag: TermioLFlags::ISIG | TermioLFlags::ICANON | TermioLFlags::ECHO | TermioLFlags::ECHOE | TermioLFlags::ECHOK | TermioLFlags::ECHOCTL | TermioLFlags::ECHOKE | TermioLFlags::IEXTEN, line: N_TTY, cc, } } fn get_user(&self) -> UserTermios { UserTermios { iflag: self.iflag.bits() as u32, oflag: self.oflag.bits() as u32, cflag: self.cflag, lflag: self.lflag.bits() as u32, line: self.line, cc: self.cc, } } } pub trait TerminalDevice: Send + Sync { fn putchar(&self, ch: u8); } struct TerminalInner { termio: Termios, session: Weak, buffer: VecDeque, } pub struct Terminal { /// Lock with IRQ disabled. We might use this in IRQ context. inner: Spin, device: Arc, cv: CondVar, } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct UserWindowSize { row: u16, col: u16, xpixel: u16, ypixel: u16, } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct UserTermios { iflag: u32, oflag: u32, cflag: u32, lflag: u32, line: u8, cc: [u8; NCCS], } pub enum TerminalIORequest<'a> { GetProcessGroup(UserPointerMut<'a, u32>), SetProcessGroup(UserPointer<'a, u32>), GetWindowSize(UserPointerMut<'a, UserWindowSize>), GetTermios(UserPointerMut<'a, UserTermios>), SetTermios(UserPointer<'a, UserTermios>), } impl core::fmt::Debug for Terminal { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Terminal").finish() } } impl Terminal { pub fn new(device: Arc) -> Arc { Arc::new(Self { inner: Spin::new(TerminalInner { termio: Termios::new_standard(), session: Weak::new(), buffer: VecDeque::with_capacity(BUFFER_SIZE), }), cv: CondVar::new(), device, }) } /// Clear the read buffer. fn clear_read_buffer(&self, inner: &mut TerminalInner) { inner.buffer.clear(); } // TODO: Buffer terminal writes. pub fn show_char(&self, ch: u8) { self.device.putchar(ch) } fn erase(&self, inner: &mut TerminalInner, echo: bool) -> Option { let back = inner.buffer.back().copied(); match back { None => return None, Some(b'\n') => return None, Some(back) if back == inner.termio.veof() => return None, Some(back) if back == inner.termio.veol() => return None, Some(back) if back == inner.termio.veol2() => return None, _ => {} } let back = inner.buffer.pop_back(); if echo && inner.termio.echo() && inner.termio.echoe() { self.show_char(CTRL!('H')); // Backspace self.show_char(b' '); // Space self.show_char(CTRL!('H')); // Backspace } return back; } fn echo_char(&self, inner: &mut TerminalInner, ch: u8) { match ch { b'\t' | b'\n' | CTRL!('Q') | CTRL!('S') => self.show_char(ch), c if c >= 32 => self.show_char(ch), _ if !inner.termio.echo() => self.show_char(ch), _ if !inner.termio.echoctl() => self.show_char(ch), _ if !inner.termio.iexten() => self.show_char(ch), _ => { self.show_char(b'^'); self.show_char(ch + 0x40); } } } fn signal(&self, inner: &mut TerminalInner, signal: Signal) { if let Some(session) = inner.session.upgrade() { session.raise_foreground(signal); } if !inner.termio.noflsh() { self.clear_read_buffer(inner); } } fn echo_and_signal(&self, inner: &mut TerminalInner, ch: u8, signal: Signal) { self.echo_char(inner, ch); self.signal(inner, signal); } fn do_commit_char(&self, inner: &mut TerminalInner, ch: u8) { inner.buffer.push_back(ch); if inner.termio.echo() || (ch == b'\n' && inner.termio.echonl()) { self.echo_char(inner, ch); } // If ICANON is not set, we notify all waiting processes. // If ICANON is set but we have a new line, there are data ready, we notify as well. if ch == b'\n' || !inner.termio.icanon() { self.cv.notify_all(); } } // TODO: Find a better way to handle this. pub fn commit_char(&self, ch: u8) { let mut inner = self.inner.lock_irq(); if inner.termio.isig() { match ch { 0xff => {} ch if ch == inner.termio.vintr() => { return self.echo_and_signal(&mut inner, ch, Signal::SIGINT) } ch if ch == inner.termio.vquit() => { return self.echo_and_signal(&mut inner, ch, Signal::SIGQUIT) } ch if ch == inner.termio.vsusp() => { return self.echo_and_signal(&mut inner, ch, Signal::SIGTSTP) } _ => {} } } // If handled, the character is discarded. if inner.termio.icanon() { match ch { 0xff => {} ch if ch == inner.termio.veof() => return self.cv.notify_all(), ch if ch == inner.termio.verase() => { self.erase(&mut inner, true); return; } ch if ch == inner.termio.vkill() => { if inner.termio.echok() { while self.erase(&mut inner, false).is_some() {} self.show_char(b'\n'); } else if inner.termio.echoke() && inner.termio.iexten() { while self.erase(&mut inner, true).is_some() {} } return; } _ => {} } } match ch { b'\r' if inner.termio.igncr() => {} b'\r' if inner.termio.icrnl() => return self.do_commit_char(&mut inner, b'\n'), b'\n' if inner.termio.inlcr() => return self.do_commit_char(&mut inner, b'\r'), _ => self.do_commit_char(&mut inner, ch), } } pub fn poll_in(&self) -> KResult<()> { let mut inner = self.inner.lock_irq(); if inner.buffer.is_empty() { self.cv.wait(&mut inner); if Thread::current().signal_list.has_pending_signal() { return Err(EINTR); } } Ok(()) } pub fn read(&self, buffer: &mut dyn Buffer) -> KResult { let mut tmp_buffer = [0u8; 32]; let data = 'block: { if buffer.available() == 0 { break 'block &tmp_buffer[..0]; } let mut inner = self.inner.lock_irq(); if inner.buffer.is_empty() { self.cv.wait(&mut inner); if Thread::current().signal_list.has_pending_signal() { return Err(EINTR); } } if inner.buffer.is_empty() { break 'block &tmp_buffer[..0]; } let length = if inner.termio.icanon() { // Canonical mode, return data until we see a newline. // TODO!!: We should wait if we don't see one. inner .buffer .iter() .position(|&ch| ch == b'\n') .map(|pos| pos + 1) .unwrap_or(inner.buffer.len()) } else { buffer.available().min(inner.buffer.len()) }; // TODO!!!!!!: Change this. We need to loop until we've got enough data. let length = length.min(tmp_buffer.len()); for (ch, r) in inner .buffer .drain(..length) .zip(tmp_buffer.iter_mut().take(length)) { *r = ch; } &tmp_buffer[..length] }; buffer.fill(data).map(|result| result.allow_partial()) } pub fn ioctl(&self, request: TerminalIORequest) -> KResult<()> { match request { TerminalIORequest::GetProcessGroup(pgid_pointer) => { let session = self.inner.lock().session.upgrade(); let pgroup = session.map(|session| session.foreground()).flatten(); if let Some(pgroup) = pgroup { pgid_pointer.write(pgroup.pgid) } else { Err(ENOTTY) } } TerminalIORequest::SetProcessGroup(pgid) => { let pgid = pgid.read()?; let procs = ProcessList::get().lock_shared(); let inner = self.inner.lock(); let session = inner.session.upgrade(); if let Some(session) = session { session.set_foreground_pgid(pgid, procs.as_pos()) } else { Err(ENOTTY) } } TerminalIORequest::GetWindowSize(ptr) => { // TODO: Get the actual window size let window_size = UserWindowSize { row: 40, col: 80, xpixel: 0, ypixel: 0, }; ptr.write(window_size) } TerminalIORequest::GetTermios(ptr) => { let termios = self.inner.lock().termio.get_user(); ptr.write(termios) } TerminalIORequest::SetTermios(ptr) => { let user_termios = ptr.read()?; let mut inner = self.inner.lock(); // TODO: We ignore unknown bits for now. inner.termio.iflag = TermioIFlags::from_bits_truncate(user_termios.iflag as u16); inner.termio.oflag = TermioOFlags::from_bits_truncate(user_termios.oflag as u16); inner.termio.lflag = TermioLFlags::from_bits_truncate(user_termios.lflag as u16); inner.termio.cflag = user_termios.cflag; inner.termio.line = user_termios.line; inner.termio.cc = user_termios.cc; Ok(()) } } } /// Assign the `session` to this terminal. Drop the previous session if `forced` is true. pub fn set_session(&self, session: &Arc, forced: bool) -> KResult<()> { let mut inner = self.inner.lock(); if let Some(session) = inner.session.upgrade() { if !forced { Err(EPERM) } else { session.drop_control_terminal(); inner.session = Arc::downgrade(&session); Ok(()) } } else { // Sessions should set their `control_terminal` field. inner.session = Arc::downgrade(&session); Ok(()) } } pub fn drop_session(&self) { self.inner.lock().session = Weak::new(); } pub fn session(&self) -> Option> { self.inner.lock().session.upgrade() } }