From 9d8a2b420d8b9825530fd560aa92548ef4f7a65c Mon Sep 17 00:00:00 2001 From: Jianfeng Jiang Date: Thu, 16 Nov 2023 17:28:10 +0800 Subject: [PATCH] Refactor tty&pty code --- regression/syscall_test/blocklists/pty_test | 16 +- services/libs/jinux-std/src/device/pty/mod.rs | 4 +- services/libs/jinux-std/src/device/pty/pty.rs | 190 +++++++++++++----- .../libs/jinux-std/src/device/tty/device.rs | 47 +++++ .../src/device/tty/line_discipline.rs | 143 ++++++------- services/libs/jinux-std/src/device/tty/mod.rs | 121 +++++++---- 6 files changed, 337 insertions(+), 184 deletions(-) create mode 100644 services/libs/jinux-std/src/device/tty/device.rs diff --git a/regression/syscall_test/blocklists/pty_test b/regression/syscall_test/blocklists/pty_test index 3d712cd4f..88a3265ee 100644 --- a/regression/syscall_test/blocklists/pty_test +++ b/regression/syscall_test/blocklists/pty_test @@ -21,22 +21,8 @@ PtyTest.SwitchNoncanonToCanonNoNewlineBig PtyTest.NoncanonBigWrite PtyTest.SwitchNoncanonToCanonMultiline PtyTest.SwitchTwiceMultiline -JobControlTest.SetTTYMaster -JobControlTest.SetTTY -JobControlTest.SetTTYNonLeader JobControlTest.SetTTYBadArg JobControlTest.SetTTYDifferentSession -JobControlTest.ReleaseTTY -JobControlTest.ReleaseUnsetTTY -JobControlTest.ReleaseWrongTTY -JobControlTest.ReleaseTTYNonLeader -JobControlTest.ReleaseTTYDifferentSession JobControlTest.ReleaseTTYSignals -JobControlTest.GetForegroundProcessGroup -JobControlTest.GetForegroundProcessGroupNonControlling JobControlTest.SetForegroundProcessGroup -JobControlTest.SetForegroundProcessGroupWrongTTY -JobControlTest.SetForegroundProcessGroupNegPgid -JobControlTest.SetForegroundProcessGroupEmptyProcessGroup -JobControlTest.SetForegroundProcessGroupDifferentSession -JobControlTest.OrphanRegression \ No newline at end of file +JobControlTest.SetForegroundProcessGroupEmptyProcessGroup \ No newline at end of file diff --git a/services/libs/jinux-std/src/device/pty/mod.rs b/services/libs/jinux-std/src/device/pty/mod.rs index a6a817372..8ee323a67 100644 --- a/services/libs/jinux-std/src/device/pty/mod.rs +++ b/services/libs/jinux-std/src/device/pty/mod.rs @@ -33,7 +33,7 @@ pub fn init() -> Result<()> { pub fn new_pty_pair(index: u32, ptmx: Arc) -> Result<(Arc, Arc)> { debug!("pty index = {}", index); - let master = Arc::new(PtyMaster::new(ptmx, index)); - let slave = Arc::new(PtySlave::new(master.clone())); + let master = PtyMaster::new(ptmx, index); + let slave = PtySlave::new(&master); Ok((master, slave)) } diff --git a/services/libs/jinux-std/src/device/pty/pty.rs b/services/libs/jinux-std/src/device/pty/pty.rs index 34ca3044a..7c7d5780d 100644 --- a/services/libs/jinux-std/src/device/pty/pty.rs +++ b/services/libs/jinux-std/src/device/pty/pty.rs @@ -4,14 +4,16 @@ use ringbuf::{ring_buffer::RbBase, HeapRb, Rb}; use crate::device::tty::line_discipline::LineDiscipline; use crate::events::IoEvents; use crate::fs::device::{Device, DeviceId, DeviceType}; -use crate::fs::file_handle::FileLike; +use crate::fs::devpts::DevPts; use crate::fs::fs_resolver::FsPath; +use crate::fs::inode_handle::FileIo; use crate::fs::utils::{AccessMode, Inode, InodeMode, IoctlCmd}; use crate::prelude::*; +use crate::process::signal::signals::kernel::KernelSignal; use crate::process::signal::{Pollee, Poller}; +use crate::process::{JobControl, Terminal}; use crate::util::{read_val_from_user, write_val_to_user}; -const PTS_DIR: &str = "/dev/pts"; const BUFFER_CAPACITY: usize = 4096; /// Pesudo terminal master. @@ -23,19 +25,23 @@ pub struct PtyMaster { index: u32, output: Arc, input: SpinLock>, + job_control: JobControl, /// The state of input buffer pollee: Pollee, + weak_self: Weak, } impl PtyMaster { - pub fn new(ptmx: Arc, index: u32) -> Self { - Self { + pub fn new(ptmx: Arc, index: u32) -> Arc { + Arc::new_cyclic(|weak_ref| PtyMaster { ptmx, index, output: LineDiscipline::new(), input: SpinLock::new(HeapRb::new(BUFFER_CAPACITY)), + job_control: JobControl::new(), pollee: Pollee::new(IoEvents::OUT), - } + weak_self: weak_ref.clone(), + }) } pub fn index(&self) -> u32 { @@ -46,16 +52,12 @@ impl PtyMaster { &self.ptmx } - pub(super) fn slave_push_byte(&self, byte: u8) { + pub(super) fn slave_push_char(&self, ch: u8) { let mut input = self.input.lock_irq_disabled(); - input.push_overwrite(byte); + input.push_overwrite(ch); self.update_state(&input); } - pub(super) fn slave_read(&self, buf: &mut [u8]) -> Result { - self.output.read(buf) - } - pub(super) fn slave_poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { let mut poll_status = IoEvents::empty(); @@ -87,7 +89,7 @@ impl PtyMaster { } } -impl FileLike for PtyMaster { +impl FileIo for PtyMaster { fn read(&self, buf: &mut [u8]) -> Result { // TODO: deal with nonblocking read if buf.is_empty() { @@ -121,14 +123,26 @@ impl FileLike for PtyMaster { } fn write(&self, buf: &[u8]) -> Result { - let mut input = self.input.lock(); + let may_send_signal = || { + let Some(foreground) = self.foreground() else { + return None; + }; + let send_signal = move |signal: KernelSignal| { + foreground.kernel_signal(signal); + }; + + Some(Arc::new(send_signal) as Arc) + }; + + let mut input = self.input.lock(); for character in buf { - self.output.push_char(*character, |content| { - for byte in content.as_bytes() { - input.push_overwrite(*byte); - } - }); + self.output + .push_char(*character, may_send_signal, |content| { + for byte in content.as_bytes() { + input.push_overwrite(*byte); + } + }); } self.update_state(&input); @@ -193,29 +207,35 @@ impl FileLike for PtyMaster { self.output.set_window_size(winsize); Ok(0) } - IoctlCmd::TIOCSCTTY => { - // TODO: reimplement when adding session. - let foreground = { - let current = current!(); - let process_group = current.process_group().unwrap(); - Arc::downgrade(&process_group) - }; - self.output.set_fg(foreground); - Ok(0) - } IoctlCmd::TIOCGPGRP => { - let Some(fg_pgid) = self.output.fg_pgid() else { + let Some(foreground) = self.foreground() else { return_errno_with_message!( Errno::ESRCH, "the foreground process group does not exist" ); }; + let fg_pgid = foreground.pgid(); write_val_to_user(arg, &fg_pgid)?; Ok(0) } + IoctlCmd::TIOCSPGRP => { + let pgid = { + let pgid: i32 = read_val_from_user(arg)?; + if pgid < 0 { + return_errno_with_message!(Errno::EINVAL, "negative pgid"); + } + pgid as u32 + }; + + self.set_foreground(&pgid)?; + Ok(0) + } + IoctlCmd::TIOCSCTTY => { + self.set_current_session()?; + Ok(0) + } IoctlCmd::TIOCNOTTY => { - // TODO: reimplement when adding session. - self.output.set_fg(Weak::new()); + self.release_current_session()?; Ok(0) } IoctlCmd::FIONREAD => { @@ -246,15 +266,47 @@ impl FileLike for PtyMaster { } } -pub struct PtySlave(Arc); +impl Terminal for PtyMaster { + fn arc_self(&self) -> Arc { + self.weak_self.upgrade().unwrap() as _ + } + + fn job_control(&self) -> &JobControl { + &self.job_control + } +} + +impl Drop for PtyMaster { + fn drop(&mut self) { + let fs = self.ptmx.fs(); + let devpts = fs.downcast_ref::().unwrap(); + + let index = self.index; + devpts.remove_slave(index); + } +} + +pub struct PtySlave { + master: Weak, + job_control: JobControl, + weak_self: Weak, +} impl PtySlave { - pub fn new(master: Arc) -> Self { - PtySlave(master) + pub fn new(master: &Arc) -> Arc { + Arc::new_cyclic(|weak_ref| PtySlave { + master: Arc::downgrade(master), + job_control: JobControl::new(), + weak_self: weak_ref.clone(), + }) } pub fn index(&self) -> u32 { - self.0.index() + self.master().index() + } + + fn master(&self) -> Arc { + self.master.upgrade().unwrap() } } @@ -266,50 +318,92 @@ impl Device for PtySlave { fn id(&self) -> crate::fs::device::DeviceId { DeviceId::new(88, self.index()) } +} +impl Terminal for PtySlave { + fn arc_self(&self) -> Arc { + self.weak_self.upgrade().unwrap() as _ + } + + fn job_control(&self) -> &JobControl { + &self.job_control + } +} + +impl FileIo for PtySlave { fn read(&self, buf: &mut [u8]) -> Result { - self.0.slave_read(buf) + self.master() + .output + .read(buf, || self.job_control.current_belongs_to_foreground()) } fn write(&self, buf: &[u8]) -> Result { + let master = self.master(); for ch in buf { // do we need to add '\r' here? if *ch == b'\n' { - self.0.slave_push_byte(b'\r'); - self.0.slave_push_byte(b'\n'); + master.slave_push_char(b'\r'); + master.slave_push_char(b'\n'); } else { - self.0.slave_push_byte(*ch); + master.slave_push_char(*ch); } } Ok(buf.len()) } + fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { + self.master().slave_poll(mask, poller) + } + fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { match cmd { IoctlCmd::TCGETS | IoctlCmd::TCSETS - | IoctlCmd::TIOCGPGRP | IoctlCmd::TIOCGPTN | IoctlCmd::TIOCGWINSZ - | IoctlCmd::TIOCSWINSZ => self.0.ioctl(cmd, arg), + | IoctlCmd::TIOCSWINSZ => self.master().ioctl(cmd, arg), + IoctlCmd::TIOCGPGRP => { + if !self.is_controlling_terminal() { + return_errno_with_message!(Errno::ENOTTY, "slave is not controlling terminal"); + } + + let Some(foreground) = self.foreground() else { + return_errno_with_message!( + Errno::ESRCH, + "the foreground process group does not exist" + ); + }; + + let fg_pgid = foreground.pgid(); + write_val_to_user(arg, &fg_pgid)?; + Ok(0) + } + IoctlCmd::TIOCSPGRP => { + let pgid = { + let pgid: i32 = read_val_from_user(arg)?; + if pgid < 0 { + return_errno_with_message!(Errno::EINVAL, "negative pgid"); + } + pgid as u32 + }; + + self.set_foreground(&pgid)?; + Ok(0) + } IoctlCmd::TIOCSCTTY => { - // TODO: + self.set_current_session()?; Ok(0) } IoctlCmd::TIOCNOTTY => { - // TODO: + self.release_current_session()?; Ok(0) } IoctlCmd::FIONREAD => { - let buffer_len = self.0.slave_buf_len() as i32; + let buffer_len = self.master().slave_buf_len() as i32; write_val_to_user(arg, &buffer_len)?; Ok(0) } _ => Ok(0), } } - - fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { - self.0.slave_poll(mask, poller) - } } diff --git a/services/libs/jinux-std/src/device/tty/device.rs b/services/libs/jinux-std/src/device/tty/device.rs new file mode 100644 index 000000000..661ac3e3f --- /dev/null +++ b/services/libs/jinux-std/src/device/tty/device.rs @@ -0,0 +1,47 @@ +use crate::events::IoEvents; +use crate::fs::device::{Device, DeviceId, DeviceType}; +use crate::fs::inode_handle::FileIo; +use crate::prelude::*; +use crate::process::signal::Poller; + +/// Corresponds to `/dev/tty` in the file system. This device represents the controlling terminal +/// of the session of current process. +pub struct TtyDevice; + +impl Device for TtyDevice { + fn open(&self) -> Result>> { + let current = current!(); + let session = current.session().unwrap(); + + let Some(terminal) = session.terminal() else { + return_errno_with_message!( + Errno::ENOTTY, + "the session does not have controlling terminal" + ); + }; + + Ok(Some(terminal as Arc)) + } + + fn type_(&self) -> DeviceType { + DeviceType::CharDevice + } + + fn id(&self) -> DeviceId { + DeviceId::new(5, 0) + } +} + +impl FileIo for TtyDevice { + fn read(&self, buf: &mut [u8]) -> Result { + unreachable!() + } + + fn write(&self, buf: &[u8]) -> Result { + unreachable!() + } + + fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { + unreachable!() + } +} diff --git a/services/libs/jinux-std/src/device/tty/line_discipline.rs b/services/libs/jinux-std/src/device/tty/line_discipline.rs index c8defa6b2..c127befd4 100644 --- a/services/libs/jinux-std/src/device/tty/line_discipline.rs +++ b/services/libs/jinux-std/src/device/tty/line_discipline.rs @@ -1,13 +1,10 @@ use crate::events::IoEvents; +use crate::prelude::*; use crate::process::signal::constants::{SIGINT, SIGQUIT}; +use crate::process::signal::signals::kernel::KernelSignal; use crate::process::signal::{Pollee, Poller}; -use crate::process::ProcessGroup; use crate::thread::work_queue::work_item::WorkItem; use crate::thread::work_queue::{submit_work_item, WorkPriority}; -use crate::{ - prelude::*, - process::{signal::signals::kernel::KernelSignal, Pgid}, -}; use alloc::format; use jinux_frame::trap::disable_local; use ringbuf::{ring_buffer::RbBase, Rb, StaticRb}; @@ -24,8 +21,6 @@ pub struct LineDiscipline { current_line: SpinLock, /// The read buffer read_buffer: SpinLock>, - /// The foreground process group - foreground: SpinLock>, /// termios termios: SpinLock, /// Windows size, @@ -87,7 +82,6 @@ impl LineDiscipline { Self { current_line: SpinLock::new(CurrentLine::new()), read_buffer: SpinLock::new(StaticRb::default()), - foreground: SpinLock::new(Weak::new()), termios: SpinLock::new(KernelTermios::default()), winsize: SpinLock::new(WinSize::default()), pollee: Pollee::new(IoEvents::empty()), @@ -98,7 +92,15 @@ impl LineDiscipline { } /// Push char to line discipline. - pub fn push_char(&self, ch: u8, echo_callback: F) { + pub fn push_char< + F1: Fn() -> Option>, + F2: FnMut(&str), + >( + &self, + ch: u8, + may_send_signal: F1, + echo_callback: F2, + ) { let termios = self.termios.lock_irq_disabled(); let ch = if termios.contains_icrnl() && ch == b'\r' { @@ -107,7 +109,7 @@ impl LineDiscipline { ch }; - if self.may_send_signal_to_foreground(&termios, ch) { + if self.may_send_signal(&termios, ch, may_send_signal) { // The char is already dealt with, so just return return; } @@ -158,31 +160,32 @@ impl LineDiscipline { self.update_readable_state_deferred(); } - fn may_send_signal_to_foreground(&self, termios: &KernelTermios, ch: u8) -> bool { - if !termios.contains_isig() { + fn may_send_signal Option>>( + &self, + termios: &KernelTermios, + ch: u8, + may_send_signal: F, + ) -> bool { + if !termios.is_canonical_mode() || !termios.contains_isig() { return false; } - let Some(foreground) = self.foreground.lock().upgrade() else { + let Some(send_signal) = may_send_signal() else { return false; }; let signal = match ch { - item if item == *termios.get_special_char(CC_C_CHAR::VINTR) => { - KernelSignal::new(SIGINT) - } - item if item == *termios.get_special_char(CC_C_CHAR::VQUIT) => { - KernelSignal::new(SIGQUIT) - } + ch if ch == *termios.get_special_char(CC_C_CHAR::VINTR) => KernelSignal::new(SIGINT), + ch if ch == *termios.get_special_char(CC_C_CHAR::VQUIT) => KernelSignal::new(SIGQUIT), _ => return false, }; // `kernel_signal()` may cause sleep, so only construct parameters here. - self.work_item_para.lock_irq_disabled().kernel_signal = Some(signal); + self.work_item_para.lock_irq_disabled().kernel_signal = Some((send_signal, signal)); true } - fn update_readable_state(&self) { + pub fn update_readable_state(&self) { let buffer = self.read_buffer.lock_irq_disabled(); if !buffer.is_empty() { self.pollee.add_events(IoEvents::IN); @@ -193,7 +196,6 @@ impl LineDiscipline { fn update_readable_state_deferred(&self) { let buffer = self.read_buffer.lock_irq_disabled(); - let pollee = self.pollee.clone(); // add/del events may sleep, so only construct parameters here. if !buffer.is_empty() { self.work_item_para.lock_irq_disabled().pollee_type = Some(PolleeType::Add); @@ -205,12 +207,10 @@ impl LineDiscipline { /// include all operations that may cause sleep, and processes by a work queue. fn update_readable_state_after(&self) { - if let Some(signal) = self.work_item_para.lock_irq_disabled().kernel_signal.take() { - self.foreground - .lock() - .upgrade() - .unwrap() - .kernel_signal(signal) + if let Some((send_signal, signal)) = + self.work_item_para.lock_irq_disabled().kernel_signal.take() + { + send_signal(signal); }; if let Some(pollee_type) = self.work_item_para.lock_irq_disabled().pollee_type.take() { match pollee_type { @@ -243,42 +243,33 @@ impl LineDiscipline { } } - /// read all bytes buffered to dst, return the actual read length. - pub fn read(&self, dst: &mut [u8]) -> Result { + pub fn read bool>(&self, buf: &mut [u8], belongs_to_foreground: F) -> Result { let mut poller = None; loop { - let res = self.try_read(dst); - match res { - Ok(read_len) => { - return Ok(read_len); - } - Err(e) => { - if e.error() != Errno::EAGAIN { - return Err(e); - } + if !belongs_to_foreground() { + init_poller(&mut poller); + if self.poll(IoEvents::IN, poller.as_ref()).is_empty() { + poller.as_ref().unwrap().wait()? } + continue; } - // Wait for read event - let need_poller = if poller.is_none() { - poller = Some(Poller::new()); - poller.as_ref() - } else { - None - }; - let revents = self.pollee.poll(IoEvents::IN, need_poller); - if revents.is_empty() { - // FIXME: deal with ldisc read timeout - poller.as_ref().unwrap().wait()?; + let res = self.try_read(buf); + match res { + Ok(len) => return Ok(len), + Err(e) if e.error() != Errno::EAGAIN => return Err(e), + Err(_) => { + init_poller(&mut poller); + if self.poll(IoEvents::IN, poller.as_ref()).is_empty() { + poller.as_ref().unwrap().wait()? + } + } } } } - pub fn try_read(&self, dst: &mut [u8]) -> Result { - if !self.current_can_read() { - return_errno!(Errno::EAGAIN); - } - + /// read all bytes buffered to dst, return the actual read length. + fn try_read(&self, dst: &mut [u8]) -> Result { let (vmin, vtime) = { let termios = self.termios.lock_irq_disabled(); let vmin = *termios.get_special_char(CC_C_CHAR::VMIN); @@ -326,7 +317,8 @@ impl LineDiscipline { if termios.is_canonical_mode() { // canonical mode, read until meet new line if is_line_terminator(next_char, &termios) { - if !should_not_be_read(next_char, &termios) { + // The eof should not be read + if !is_eof(next_char, &termios) { *dst_i = next_char; read_len += 1; } @@ -368,31 +360,6 @@ impl LineDiscipline { todo!() } - /// Determine whether current process can read the line discipline. If current belongs to the foreground process group. - /// or the foreground process group is None, returns true. - fn current_can_read(&self) -> bool { - let current = current!(); - let Some(foreground) = self.foreground.lock_irq_disabled().upgrade() else { - return true; - }; - foreground.contains_process(current.pid()) - } - - /// set foreground process group - pub fn set_fg(&self, foreground: Weak) { - *self.foreground.lock_irq_disabled() = foreground; - // Some background processes may be waiting on the wait queue, when set_fg, the background processes may be able to read. - self.update_readable_state(); - } - - /// get foreground process group id - pub fn fg_pgid(&self) -> Option { - self.foreground - .lock_irq_disabled() - .upgrade() - .map(|foreground| foreground.pgid()) - } - /// whether there is buffered data pub fn is_empty(&self) -> bool { self.read_buffer.lock_irq_disabled().len() == 0 @@ -439,8 +406,7 @@ fn is_line_terminator(item: u8, termios: &KernelTermios) -> bool { false } -/// The special char should not be read by reading process -fn should_not_be_read(ch: u8, termios: &KernelTermios) -> bool { +fn is_eof(ch: u8, termios: &KernelTermios) -> bool { ch == *termios.get_special_char(CC_C_CHAR::VEOF) } @@ -461,13 +427,22 @@ fn get_printable_char(ctrl_char: u8) -> u8 { ctrl_char + b'A' - 1 } +fn init_poller(poller: &mut Option) { + if poller.is_some() { + return; + } + + *poller = Some(Poller::new()); +} + enum PolleeType { Add, Del, } struct LineDisciplineWorkPara { - kernel_signal: Option, + #[allow(clippy::type_complexity)] + kernel_signal: Option<(Arc, KernelSignal)>, pollee_type: Option, } diff --git a/services/libs/jinux-std/src/device/tty/mod.rs b/services/libs/jinux-std/src/device/tty/mod.rs index 395ca0f75..9bbf0448c 100644 --- a/services/libs/jinux-std/src/device/tty/mod.rs +++ b/services/libs/jinux-std/src/device/tty/mod.rs @@ -2,23 +2,28 @@ use spin::Once; use self::driver::TtyDriver; use self::line_discipline::LineDiscipline; -use super::*; use crate::events::IoEvents; +use crate::fs::device::{Device, DeviceId, DeviceType}; +use crate::fs::inode_handle::FileIo; use crate::fs::utils::IoctlCmd; use crate::prelude::*; +use crate::process::signal::signals::kernel::KernelSignal; use crate::process::signal::Poller; -use crate::process::{process_table, ProcessGroup}; +use crate::process::{JobControl, Process, Terminal}; use crate::util::{read_val_from_user, write_val_to_user}; +mod device; pub mod driver; pub mod line_discipline; pub mod termio; +pub use device::TtyDevice; + static N_TTY: Once> = Once::new(); pub(super) fn init() { let name = CString::new("console").unwrap(); - let tty = Arc::new(Tty::new(name)); + let tty = Tty::new(name); N_TTY.call_once(|| tty); driver::init(); } @@ -28,45 +33,49 @@ pub struct Tty { name: CString, /// line discipline ldisc: Arc, + job_control: JobControl, /// driver driver: SpinLock>, + weak_self: Weak, } impl Tty { - pub fn new(name: CString) -> Self { - Tty { + pub fn new(name: CString) -> Arc { + Arc::new_cyclic(|weak_ref| Tty { name, ldisc: LineDiscipline::new(), + job_control: JobControl::new(), driver: SpinLock::new(Weak::new()), - } - } - - /// Set foreground process group - pub fn set_fg(&self, process_group: Weak) { - self.ldisc.set_fg(process_group); + weak_self: weak_ref.clone(), + }) } pub fn set_driver(&self, driver: Weak) { *self.driver.lock_irq_disabled() = driver; } - pub fn receive_char(&self, item: u8) { - self.ldisc.push_char(item, |content| print!("{}", content)); + pub fn receive_char(&self, ch: u8) { + let may_send_signal = || { + let Some(foreground) = self.foreground() else { + return None; + }; + + let send_signal = move |signal: KernelSignal| { + foreground.kernel_signal(signal); + }; + + Some(Arc::new(send_signal) as Arc) + }; + + self.ldisc + .push_char(ch, may_send_signal, |content| print!("{}", content)); } } -impl Device for Tty { - fn type_(&self) -> DeviceType { - DeviceType::CharDevice - } - - fn id(&self) -> DeviceId { - // Same value with Linux - DeviceId::new(5, 0) - } - +impl FileIo for Tty { fn read(&self, buf: &mut [u8]) -> Result { - self.ldisc.read(buf) + self.ldisc + .read(buf, || self.job_control.current_belongs_to_foreground()) } fn write(&self, buf: &[u8]) -> Result { @@ -92,23 +101,28 @@ impl Device for Tty { Ok(0) } IoctlCmd::TIOCGPGRP => { - let Some(fg_pgid) = self.ldisc.fg_pgid() else { - return_errno_with_message!(Errno::ENOENT, "No fg process group") + let Some(foreground) = self.foreground() else { + return_errno_with_message!(Errno::ESRCH, "No fg process group") }; + let fg_pgid = foreground.pgid(); debug!("fg_pgid = {}", fg_pgid); write_val_to_user(arg, &fg_pgid)?; Ok(0) } IoctlCmd::TIOCSPGRP => { // Set the process group id of fg progress group - let pgid = read_val_from_user::(arg)?; - if pgid < 0 { - return_errno_with_message!(Errno::EINVAL, "invalid pgid"); - } - match process_table::pgid_to_process_group(pgid as u32) { - None => self.ldisc.set_fg(Weak::new()), - Some(process_group) => self.ldisc.set_fg(Arc::downgrade(&process_group)), - } + let pgid = { + let pgid: i32 = read_val_from_user(arg)?; + if pgid < 0 { + return_errno_with_message!(Errno::EINVAL, "negative pgid"); + } + pgid as u32 + }; + + self.set_foreground(&pgid)?; + // Some background processes may be waiting on the wait queue, + // when set_fg, the background processes may be able to read. + self.ldisc.update_readable_state(); Ok(0) } IoctlCmd::TCSETS => { @@ -143,12 +157,49 @@ impl Device for Tty { self.ldisc.set_window_size(winsize); Ok(0) } + IoctlCmd::TIOCSCTTY => { + self.set_current_session()?; + Ok(0) + } _ => todo!(), } } } -/// FIXME: should we maintain a static console? +impl Terminal for Tty { + fn arc_self(&self) -> Arc { + self.weak_self.upgrade().unwrap() as _ + } + + fn job_control(&self) -> &JobControl { + &self.job_control + } +} + +impl Device for Tty { + fn type_(&self) -> DeviceType { + DeviceType::CharDevice + } + + fn id(&self) -> DeviceId { + // The same value as /dev/console in linux. + DeviceId::new(88, 0) + } +} + pub fn get_n_tty() -> &'static Arc { N_TTY.get().unwrap() } + +/// Open `N_TTY` as the controlling terminal for the process. This method should +/// only be called when creating the init process. +pub fn open_ntty_as_controlling_terminal(process: &Process) -> Result<()> { + let tty = get_n_tty(); + + let session = &process.session().unwrap(); + let process_group = process.process_group().unwrap(); + tty.job_control.set_session(session); + tty.job_control.set_foreground(Some(&process_group))?; + + session.set_terminal(|| Ok(tty.clone())) +}