diff --git a/kernel/src/device/mod.rs b/kernel/src/device/mod.rs index dcbde6fd1..5550a9b1f 100644 --- a/kernel/src/device/mod.rs +++ b/kernel/src/device/mod.rs @@ -14,7 +14,7 @@ pub mod tdxguest; use alloc::format; -pub use pty::{new_pty_pair, PtyMaster, PtySlave}; +pub use pty::{new_pty_pair, PtyMaster, PtySlave, PtySlaveFile}; pub use random::Random; pub use urandom::Urandom; diff --git a/kernel/src/device/pty/driver.rs b/kernel/src/device/pty/driver.rs index 8ea66e5e1..c103494a4 100644 --- a/kernel/src/device/pty/driver.rs +++ b/kernel/src/device/pty/driver.rs @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MPL-2.0 +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + use aster_console::AnyConsoleDevice; use ostd::sync::SpinLock; use crate::{ - device::tty::{PushCharError, Tty, TtyDriver}, + device::tty::{Tty, TtyDriver}, events::IoEvents, prelude::{return_errno_with_message, Errno, Result}, process::signal::Pollee, @@ -15,12 +17,14 @@ const BUFFER_CAPACITY: usize = 8192; /// A pseudoterminal driver. /// -/// This is contained in the PTY slave, but it maintains the output buffer and the pollee of the +/// This is contained in the pty slave, but it maintains the output buffer and the pollee of the /// master. The pollee of the slave is part of the [`Tty`] structure (see the definition of /// [`PtySlave`]). pub struct PtyDriver { output: SpinLock>, pollee: Pollee, + is_master_closed: AtomicBool, + opened_slaves: AtomicUsize, } /// A pseudoterminal slave. @@ -31,6 +35,8 @@ impl PtyDriver { Self { output: SpinLock::new(RingBuffer::new(BUFFER_CAPACITY)), pollee: Pollee::new(), + is_master_closed: AtomicBool::new(false), + opened_slaves: AtomicUsize::new(0), } } @@ -41,6 +47,12 @@ impl PtyDriver { let mut output = self.output.lock(); if output.is_empty() { + if self.opened_slaves.load(Ordering::Relaxed) == 0 { + return_errno_with_message!( + Errno::EIO, + "the pty master does not have opened slaves" + ); + } return_errno_with_message!(Errno::EAGAIN, "the buffer is empty"); } @@ -57,10 +69,22 @@ impl PtyDriver { pub(super) fn buffer_len(&self) -> usize { self.output.lock().len() } + + pub(super) fn set_master_closed(&self) { + self.is_master_closed.store(true, Ordering::Relaxed); + } + + pub(super) fn opened_slaves(&self) -> &AtomicUsize { + &self.opened_slaves + } } impl TtyDriver for PtyDriver { - fn push_output(&self, chs: &[u8]) -> core::result::Result { + fn push_output(&self, chs: &[u8]) -> Result { + if self.is_closed() { + return_errno_with_message!(Errno::EIO, "the pty master has been closed"); + } + let mut output = self.output.lock(); let mut len = 0; @@ -73,7 +97,7 @@ impl TtyDriver for PtyDriver { } else if *ch != b'\n' && !output.is_full() { output.push(*ch).unwrap(); } else if len == 0 { - return Err(PushCharError); + return_errno_with_message!(Errno::EAGAIN, "the output buffer is full"); } else { break; } @@ -110,6 +134,10 @@ impl TtyDriver for PtyDriver { output.capacity() - output.len() >= 2 } + fn is_closed(&self) -> bool { + self.is_master_closed.load(Ordering::Relaxed) + } + fn notify_input(&self) { self.pollee.notify(IoEvents::OUT); } diff --git a/kernel/src/device/pty/file.rs b/kernel/src/device/pty/file.rs new file mode 100644 index 000000000..58713b4bf --- /dev/null +++ b/kernel/src/device/pty/file.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::sync::atomic::Ordering; + +use inherit_methods_macro::inherit_methods; + +use crate::{ + device::PtySlave, + events::IoEvents, + fs::{ + inode_handle::FileIo, + utils::{IoctlCmd, StatusFlags}, + }, + prelude::*, + process::signal::{PollHandle, Pollable}, +}; + +/// The file for a pseudoterminal slave. +pub struct PtySlaveFile(Arc); + +impl PtySlaveFile { + pub fn new(slave: Arc) -> PtySlaveFile { + slave + .driver() + .opened_slaves() + .fetch_add(1, Ordering::Relaxed); + slave.driver().pollee().invalidate(); + PtySlaveFile(slave) + } +} + +impl Drop for PtySlaveFile { + fn drop(&mut self) { + let driver = self.0.driver(); + driver.opened_slaves().fetch_sub(1, Ordering::Relaxed); + driver.pollee().notify(IoEvents::HUP); + } +} + +#[inherit_methods(from = "self.0")] +impl FileIo for PtySlaveFile { + fn read(&self, writer: &mut VmWriter, status_flags: StatusFlags) -> Result; + fn write(&self, reader: &mut VmReader, status_flags: StatusFlags) -> Result; + fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result; +} + +#[inherit_methods(from = "self.0")] +impl Pollable for PtySlaveFile { + fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents; +} diff --git a/kernel/src/device/pty/master.rs b/kernel/src/device/pty/master.rs index a95aef4f3..3680cc793 100644 --- a/kernel/src/device/pty/master.rs +++ b/kernel/src/device/pty/master.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 use alloc::format; +use core::sync::atomic::Ordering; use ostd::task::Task; @@ -61,6 +62,10 @@ impl PtyMaster { events |= IoEvents::OUT; } + if self.slave.driver().opened_slaves().load(Ordering::Relaxed) == 0 { + events |= IoEvents::HUP; + } + events } } @@ -95,7 +100,7 @@ impl FileIo for PtyMaster { // TODO: Add support for non-blocking mode and timeout let len = self.wait_events(IoEvents::OUT, None, || { - Ok(self.slave.push_input(&buf[..write_len])?) + self.slave.push_input(&buf[..write_len]) })?; self.slave.driver().pollee().invalidate(); Ok(len) @@ -111,7 +116,7 @@ impl FileIo for PtyMaster { | IoctlCmd::TIOCSWINSZ | IoctlCmd::TIOCGPTN => return self.slave.ioctl(cmd, arg), IoctlCmd::TIOCSPTLCK => { - // TODO: Lock or unlock the PTY. + // TODO: Lock or unlock the pty. } IoctlCmd::TIOCGPTPEER => { let current_task = Task::current().unwrap(); @@ -164,5 +169,8 @@ impl Drop for PtyMaster { let index = self.slave.index(); devpts.remove_slave(index); + + self.slave.driver().set_master_closed(); + self.slave.notify_hup(); } } diff --git a/kernel/src/device/pty/mod.rs b/kernel/src/device/pty/mod.rs index add30160c..873f806cc 100644 --- a/kernel/src/device/pty/mod.rs +++ b/kernel/src/device/pty/mod.rs @@ -11,9 +11,11 @@ use crate::{ }; mod driver; +mod file; mod master; pub use driver::PtySlave; +pub use file::PtySlaveFile; pub use master::PtyMaster; use spin::Once; diff --git a/kernel/src/device/tty/driver.rs b/kernel/src/device/tty/driver.rs index 2ecd36309..544412fab 100644 --- a/kernel/src/device/tty/driver.rs +++ b/kernel/src/device/tty/driver.rs @@ -2,17 +2,7 @@ use aster_console::AnyConsoleDevice; -use crate::prelude::{Errno, Error}; - -/// An error indicating that no characters can be pushed because the buffer is full. -#[derive(Debug, Clone, Copy)] -pub struct PushCharError; - -impl From for Error { - fn from(_value: PushCharError) -> Self { - Error::with_message(Errno::EAGAIN, "the buffer is full") - } -} +use crate::prelude::*; /// A TTY driver. /// @@ -26,8 +16,8 @@ pub trait TtyDriver: Send + Sync + 'static { /// Pushes characters into the output buffer. /// /// This method returns the number of bytes pushed or fails with an error if no bytes can be - /// pushed because the buffer is full. - fn push_output(&self, chs: &[u8]) -> core::result::Result; + /// pushed. + fn push_output(&self, chs: &[u8]) -> Result; /// Drains the output buffer. fn drain_output(&self); @@ -43,6 +33,12 @@ pub trait TtyDriver: Send + Sync + 'static { /// This method should return `false` if the output buffer is full. fn can_push(&self) -> bool; + /// Returns whether the TTY is closed. + /// + /// For a pty slave, this method returns `true` only if its associated master has been closed. + /// For other TTY types, this method returns `false`. + fn is_closed(&self) -> bool; + /// Notifies that the input buffer now has room for new characters. /// /// This method should be called when the state of [`Tty::can_push`] changes from `false` to diff --git a/kernel/src/device/tty/line_discipline.rs b/kernel/src/device/tty/line_discipline.rs index 2bac32ff3..f88923b1b 100644 --- a/kernel/src/device/tty/line_discipline.rs +++ b/kernel/src/device/tty/line_discipline.rs @@ -2,10 +2,7 @@ use ostd::const_assert; -use super::{ - termio::{CCtrlCharId, CTermios, CWinSize}, - PushCharError, -}; +use super::termio::{CCtrlCharId, CTermios, CWinSize}; use crate::{ prelude::*, process::signal::{ @@ -100,7 +97,7 @@ impl LineDiscipline { ch: u8, mut signal_callback: F1, echo_callback: F2, - ) -> core::result::Result<(), PushCharError> { + ) -> Result<()> { let ch = if self.termios.contains_icrnl() && ch == b'\r' { b'\n' } else { @@ -122,7 +119,7 @@ impl LineDiscipline { // If the buffer is full, we should not push the character into the buffer. The caller // can silently ignore the error (if the input comes from the keyboard) or block the // user space (if the input comes from the pseduoterminal master). - return Err(PushCharError); + return_errno_with_message!(Errno::EAGAIN, "the buffer is full"); } // Raw mode diff --git a/kernel/src/device/tty/mod.rs b/kernel/src/device/tty/mod.rs index 048eeec3f..fdb169b8c 100644 --- a/kernel/src/device/tty/mod.rs +++ b/kernel/src/device/tty/mod.rs @@ -31,7 +31,7 @@ mod n_tty; mod termio; pub use device::TtyDevice; -pub use driver::{PushCharError, TtyDriver}; +pub use driver::TtyDriver; pub(super) use n_tty::init; pub use n_tty::{iter_n_tty, system_console}; @@ -100,6 +100,11 @@ impl Tty { pub fn notify_output(&self) { self.pollee.notify(IoEvents::OUT); } + + /// Notifies that the other end has been closed. + pub fn notify_hup(&self) { + self.pollee.notify(IoEvents::HUP); + } } impl Tty { @@ -107,7 +112,7 @@ impl Tty { /// /// This method returns the number of bytes pushed or fails with an error if no bytes can be /// pushed because the buffer is full. - pub fn push_input(&self, chs: &[u8]) -> core::result::Result { + pub fn push_input(&self, chs: &[u8]) -> Result { let mut ldisc = self.ldisc.lock(); let mut echo = self.driver.echo_callback(); @@ -123,7 +128,7 @@ impl Tty { &mut echo, ); if res.is_err() && len == 0 { - return Err(PushCharError); + return_errno_with_message!(Errno::EAGAIN, "the line discipline is full"); } else if res.is_err() { break; } else { @@ -146,6 +151,10 @@ impl Tty { events |= IoEvents::OUT; } + if self.driver.is_closed() { + events |= IoEvents::HUP; + } + events } } @@ -233,6 +242,10 @@ impl Pollable for Tty { impl FileIo for Tty { fn read(&self, writer: &mut VmWriter, _status_flags: StatusFlags) -> Result { + if self.driver.is_closed() { + return Ok(0); + } + self.job_control.wait_until_in_foreground()?; // TODO: Add support for non-blocking mode and timeout @@ -253,7 +266,7 @@ impl FileIo for Tty { // TODO: Add support for non-blocking mode and timeout let len = self.wait_events(IoEvents::OUT, None, || { - Ok(self.driver.push_output(&buf[..write_len])?) + self.driver.push_output(&buf[..write_len]) })?; self.pollee.invalidate(); Ok(len) @@ -304,6 +317,10 @@ impl FileIo for Tty { current_userspace!().write_val(arg, &idx)?; } IoctlCmd::FIONREAD => { + if self.driver().is_closed() { + return_errno_with_message!(Errno::EIO, "the TTY is closed"); + } + let buffer_len = self.ldisc.lock().buffer_len() as u32; current_userspace!().write_val(arg, &buffer_len)?; diff --git a/kernel/src/device/tty/n_tty.rs b/kernel/src/device/tty/n_tty.rs index 377860b2a..d5eb520f3 100644 --- a/kernel/src/device/tty/n_tty.rs +++ b/kernel/src/device/tty/n_tty.rs @@ -1,19 +1,18 @@ // SPDX-License-Identifier: MPL-2.0 -use alloc::{boxed::Box, sync::Arc, vec}; - use aster_console::AnyConsoleDevice; use ostd::mm::{Infallible, VmReader, VmWriter}; use spin::Once; -use super::{PushCharError, Tty, TtyDriver}; +use super::{Tty, TtyDriver}; +use crate::prelude::*; pub struct ConsoleDriver { console: Arc, } impl TtyDriver for ConsoleDriver { - fn push_output(&self, chs: &[u8]) -> core::result::Result { + fn push_output(&self, chs: &[u8]) -> Result { self.console.send(chs); Ok(chs.len()) } @@ -28,6 +27,10 @@ impl TtyDriver for ConsoleDriver { true } + fn is_closed(&self) -> bool { + false + } + fn notify_input(&self) {} fn console(&self) -> Option<&dyn AnyConsoleDevice> { diff --git a/kernel/src/fs/devpts/slave.rs b/kernel/src/fs/devpts/slave.rs index eb3b87dc6..35cbb4acb 100644 --- a/kernel/src/fs/devpts/slave.rs +++ b/kernel/src/fs/devpts/slave.rs @@ -5,7 +5,7 @@ use super::*; use crate::{ - device::PtySlave, + device::{PtySlave, PtySlaveFile}, events::IoEvents, fs::{ inode_handle::FileIo, @@ -152,6 +152,6 @@ impl Inode for PtySlaveInode { access_mode: AccessMode, status_flags: StatusFlags, ) -> Option>> { - self.device.open() + Some(Ok(Arc::new(PtySlaveFile::new(self.device.clone())))) } } diff --git a/kernel/src/process/process/job_control.rs b/kernel/src/process/process/job_control.rs index 26d2647e8..9991b6050 100644 --- a/kernel/src/process/process/job_control.rs +++ b/kernel/src/process/process/job_control.rs @@ -5,7 +5,7 @@ use ostd::sync::{LocalIrqDisabled, WaitQueue}; use super::{ProcessGroup, Session}; use crate::prelude::*; -/// The job control for terminals like TTY and PTY. +/// The job control for terminals like TTY and pty. /// /// This structure is used to support the shell job control, allowing users to /// run commands in the foreground or in the background. To achieve this, this diff --git a/kernel/src/process/process/terminal.rs b/kernel/src/process/process/terminal.rs index e53cb6bf1..6ab5a6ccb 100644 --- a/kernel/src/process/process/terminal.rs +++ b/kernel/src/process/process/terminal.rs @@ -12,7 +12,7 @@ use crate::{ /// A terminal. /// -/// We currently support two kinds of terminal, the TTY and PTY. They're associated with a +/// We currently support two kinds of terminal, the TTY and pty. They're associated with a /// `JobControl` to track the session and the foreground process group. pub trait Terminal: FileIo + Device { /// Returns the job control of the terminal.