123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- 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<Session>,
- buffer: VecDeque<u8>,
- }
- pub struct Terminal {
- /// Lock with IRQ disabled. We might use this in IRQ context.
- inner: Spin<TerminalInner>,
- device: Arc<dyn TerminalDevice>,
- 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<dyn TerminalDevice>) -> Arc<Self> {
- 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<u8> {
- 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<usize> {
- 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<Session>, 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<Arc<Session>> {
- self.inner.lock().session.upgrade()
- }
- }
|