From f802ff40c520cd2d3734bdcaa1cf841b1f70fe07 Mon Sep 17 00:00:00 2001 From: Jianfeng Jiang Date: Tue, 1 Aug 2023 14:35:35 +0800 Subject: [PATCH] Implement pseudo terminal --- services/libs/jinux-std/src/device/mod.rs | 1 + .../libs/jinux-std/src/device/pty/master.rs | 261 ++++++++++++++++++ .../src/device/{pty.rs => pty/mod.rs} | 6 + .../libs/jinux-std/src/device/pty/slave.rs | 65 +++++ .../src/device/tty/line_discipline.rs | 6 +- services/libs/jinux-std/src/device/tty/mod.rs | 2 +- .../libs/jinux-std/src/fs/devpts/master.rs | 58 +--- services/libs/jinux-std/src/fs/devpts/mod.rs | 12 +- .../libs/jinux-std/src/fs/devpts/slave.rs | 43 +-- services/libs/jinux-std/src/fs/utils/ioctl.rs | 22 +- 10 files changed, 369 insertions(+), 107 deletions(-) create mode 100644 services/libs/jinux-std/src/device/pty/master.rs rename services/libs/jinux-std/src/device/{pty.rs => pty/mod.rs} (89%) create mode 100644 services/libs/jinux-std/src/device/pty/slave.rs diff --git a/services/libs/jinux-std/src/device/mod.rs b/services/libs/jinux-std/src/device/mod.rs index 899206d62..0264c45d7 100644 --- a/services/libs/jinux-std/src/device/mod.rs +++ b/services/libs/jinux-std/src/device/mod.rs @@ -7,6 +7,7 @@ mod zero; use crate::fs::device::{add_node, Device, DeviceId, DeviceType}; use crate::prelude::*; +pub use pty::{PtyMaster, PtySlave}; pub use random::Random; pub use urandom::Urandom; diff --git a/services/libs/jinux-std/src/device/pty/master.rs b/services/libs/jinux-std/src/device/pty/master.rs new file mode 100644 index 000000000..f7f97f202 --- /dev/null +++ b/services/libs/jinux-std/src/device/pty/master.rs @@ -0,0 +1,261 @@ +use crate::{ + fs::{ + file_handle::FileLike, + fs_resolver::FsPath, + utils::{AccessMode, Inode, InodeMode, IoEvents, IoctlCmd, Poller}, + }, + prelude::*, + util::{read_val_from_user, write_val_to_user}, +}; +use alloc::format; +use jinux_frame::sync::SpinLock; +use ringbuf::{ring_buffer::RbBase, HeapRb, Rb}; + +use crate::{device::tty::line_discipline::LineDiscipline, fs::utils::Pollee}; + +use super::slave::PtySlave; + +const PTS_DIR: &str = "/dev/pts"; +const BUFFER_CAPACITY: usize = 4096; + +/// Pesudo terminal master. +/// Internally, it has two buffers. +/// One is inside ldisc, which is written by master and read by slave, +/// the other is a ring buffer, which is written by slave and read by master. +pub struct PtyMaster { + ptmx: Arc, + index: usize, + ldisc: LineDiscipline, + master_buffer: SpinLock>, + /// The state of master buffer + pollee: Pollee, +} + +impl PtyMaster { + pub fn new_pair(index: u32, ptmx: Arc) -> Result<(Arc, Arc)> { + debug!("allocate pty index = {}", index); + let master = Arc::new(PtyMaster { + ptmx, + index: index as usize, + master_buffer: SpinLock::new(HeapRb::new(BUFFER_CAPACITY)), + pollee: Pollee::new(IoEvents::OUT), + ldisc: LineDiscipline::new(), + }); + let slave = Arc::new(PtySlave::new(master.clone())); + Ok((master, slave)) + } + + pub fn index(&self) -> usize { + self.index + } + + pub fn ptmx(&self) -> &Arc { + &self.ptmx + } + + pub(super) fn slave_push_char(&self, item: u8) -> Result<()> { + let mut buf = self.master_buffer.lock_irq_disabled(); + if buf.is_full() { + return_errno_with_message!(Errno::EIO, "the buffer is full"); + } + // Unwrap safety: the buf is not full, so push will always succeed. + buf.push(item).unwrap(); + self.update_state(&buf); + Ok(()) + } + + pub(super) fn slave_read(&self, buf: &mut [u8]) -> Result { + self.ldisc.read(buf) + } + + pub(super) fn slave_poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { + let poll_out_mask = mask & IoEvents::OUT; + let poll_in_mask = mask & IoEvents::IN; + + loop { + let mut poll_status = IoEvents::empty(); + + if !poll_in_mask.is_empty() { + let poll_in_status = self.ldisc.poll(poll_in_mask, poller); + poll_status |= poll_in_status; + } + + if !poll_out_mask.is_empty() { + let poll_out_status = self.pollee.poll(poll_out_mask, poller); + poll_status |= poll_out_status; + } + + if !poll_status.is_empty() || poller.is_none() { + return poll_status; + } + + poller.unwrap().wait(); + } + } + + fn update_state(&self, buf: &HeapRb) { + if buf.is_full() { + self.pollee.del_events(IoEvents::OUT); + } else { + self.pollee.add_events(IoEvents::OUT); + } + + if buf.is_empty() { + self.pollee.del_events(IoEvents::IN) + } else { + self.pollee.add_events(IoEvents::IN); + } + } +} + +impl FileLike for PtyMaster { + fn read(&self, buf: &mut [u8]) -> Result { + // TODO: deal with nonblocking read + if buf.len() == 0 { + return Ok(0); + } + + let poller = Poller::new(); + loop { + let mut master_buf = self.master_buffer.lock_irq_disabled(); + + if master_buf.is_empty() { + self.update_state(&master_buf); + let events = self.pollee.poll(IoEvents::IN, Some(&poller)); + if !events.contains(IoEvents::IN) { + drop(master_buf); + poller.wait(); + } + continue; + } + + let read_len = master_buf.len().min(buf.len()); + master_buf.pop_slice(&mut buf[..read_len]); + self.update_state(&master_buf); + return Ok(read_len); + } + } + + fn write(&self, buf: &[u8]) -> Result { + let mut master_buf = self.master_buffer.lock(); + + if self.ldisc.termios().contain_echo() && master_buf.len() + buf.len() > BUFFER_CAPACITY { + return_errno_with_message!( + Errno::EIO, + "the written bytes exceeds the master buf capacity" + ); + } + + for item in buf { + self.ldisc.push_char(*item, |content| { + for byte in content.as_bytes() { + // Unwrap safety: the master buf is ensured to have enough space. + master_buf.push(*byte).unwrap(); + } + }); + } + + self.update_state(&master_buf); + Ok(buf.len()) + } + + fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { + match cmd { + IoctlCmd::TCGETS => { + let termios = self.ldisc.termios(); + write_val_to_user(arg, &termios)?; + Ok(0) + } + IoctlCmd::TCSETS => { + let termios = read_val_from_user(arg)?; + self.ldisc.set_termios(termios); + Ok(0) + } + IoctlCmd::TIOCSPTLCK => { + // TODO: lock/unlock pty + Ok(0) + } + IoctlCmd::TIOCGPTN => { + let idx = self.index() as u32; + write_val_to_user(arg, &idx)?; + Ok(0) + } + IoctlCmd::TIOCGPTPEER => { + let current = current!(); + + // TODO: deal with open options + let slave = { + let slave_name = format!("{}/{}", PTS_DIR, self.index()); + let fs_path = FsPath::try_from(slave_name.as_str())?; + + let inode_handle = { + let fs = current.fs().read(); + let flags = AccessMode::O_RDWR as u32; + let mode = (InodeMode::S_IRUSR | InodeMode::S_IWUSR).bits(); + fs.open(&fs_path, flags, mode)? + }; + Arc::new(inode_handle) + }; + + let fd = { + let mut file_table = current.file_table().lock(); + file_table.insert(slave) + }; + Ok(fd) + } + IoctlCmd::TIOCGWINSZ => Ok(0), + IoctlCmd::TIOCSCTTY => { + // TODO + let foreground = { + let current = current!(); + let process_group = current.process_group().lock(); + process_group.clone() + }; + self.ldisc.set_fg(foreground); + Ok(0) + } + IoctlCmd::TIOCGPGRP => { + let Some(fg_pgid) = self.ldisc.fg_pgid() else { + return_errno_with_message!( + Errno::ESRCH, + "the foreground process group does not exist" + ); + }; + write_val_to_user(arg, &fg_pgid)?; + Ok(0) + } + IoctlCmd::TIOCNOTTY => { + self.ldisc.set_fg(Weak::new()); + Ok(0) + } + _ => Ok(0), + } + } + + fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { + let poll_out_mask = mask & IoEvents::OUT; + let poll_in_mask = mask & IoEvents::IN; + + loop { + let _master_buf = self.master_buffer.lock_irq_disabled(); + + let mut poll_status = IoEvents::empty(); + + if !poll_in_mask.is_empty() { + let poll_in_status = self.pollee.poll(poll_in_mask, poller); + poll_status |= poll_in_status; + } + + if !poll_out_mask.is_empty() { + let poll_out_status = self.ldisc.poll(poll_out_mask, poller); + poll_status |= poll_out_status; + } + + if !poll_status.is_empty() || poller.is_none() { + return poll_status; + } + + poller.unwrap().wait(); + } + } +} diff --git a/services/libs/jinux-std/src/device/pty.rs b/services/libs/jinux-std/src/device/pty/mod.rs similarity index 89% rename from services/libs/jinux-std/src/device/pty.rs rename to services/libs/jinux-std/src/device/pty/mod.rs index 5aba6e7b7..d0fc149d2 100644 --- a/services/libs/jinux-std/src/device/pty.rs +++ b/services/libs/jinux-std/src/device/pty/mod.rs @@ -1,3 +1,9 @@ +mod master; +mod slave; + +pub use master::PtyMaster; +pub use slave::PtySlave; + use crate::fs::{ devpts::DevPts, fs_resolver::{FsPath, FsResolver}, diff --git a/services/libs/jinux-std/src/device/pty/slave.rs b/services/libs/jinux-std/src/device/pty/slave.rs new file mode 100644 index 000000000..dbb6d7e94 --- /dev/null +++ b/services/libs/jinux-std/src/device/pty/slave.rs @@ -0,0 +1,65 @@ +use crate::fs::device::{Device, DeviceId, DeviceType}; +use crate::fs::file_handle::FileLike; +use crate::fs::utils::{IoEvents, IoctlCmd, Poller}; +use crate::prelude::*; + +use super::master::PtyMaster; + +pub struct PtySlave(Arc); + +impl PtySlave { + pub fn new(master: Arc) -> Self { + PtySlave(master) + } + + pub fn index(&self) -> usize { + self.0.index() + } +} + +impl Device for PtySlave { + fn type_(&self) -> DeviceType { + DeviceType::CharDevice + } + + fn id(&self) -> crate::fs::device::DeviceId { + DeviceId::new(88, self.index() as u32) + } + + fn read(&self, buf: &mut [u8]) -> Result { + self.0.slave_read(buf) + } + + fn write(&self, buf: &[u8]) -> Result { + for ch in buf { + // do we need to add '\r' here? + if *ch == b'\n' { + self.0.slave_push_char(b'\r')?; + self.0.slave_push_char(b'\n')?; + } else { + self.0.slave_push_char(*ch)?; + } + } + Ok(buf.len()) + } + + fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { + match cmd { + IoctlCmd::TCGETS | IoctlCmd::TCSETS | IoctlCmd::TIOCGPGRP => self.0.ioctl(cmd, arg), + IoctlCmd::TIOCGWINSZ => Ok(0), + IoctlCmd::TIOCSCTTY => { + // TODO: + Ok(0) + } + IoctlCmd::TIOCNOTTY => { + // TODO: + 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/line_discipline.rs b/services/libs/jinux-std/src/device/tty/line_discipline.rs index 2a55f8a96..66a53f120 100644 --- a/services/libs/jinux-std/src/device/tty/line_discipline.rs +++ b/services/libs/jinux-std/src/device/tty/line_discipline.rs @@ -3,7 +3,7 @@ use crate::process::process_group::ProcessGroup; use crate::process::signal::constants::{SIGINT, SIGQUIT}; use crate::{ prelude::*, - process::{process_table, signal::signals::kernel::KernelSignal, Pgid}, + process::{signal::signals::kernel::KernelSignal, Pgid}, }; use alloc::format; use jinux_frame::trap::disable_local; @@ -77,7 +77,7 @@ impl LineDiscipline { } /// Push char to line discipline. - pub fn push_char(&self, mut item: u8, echo_callback: fn(&str)) { + pub fn push_char(&self, mut item: u8, echo_callback: F) { let termios = self.termios.lock_irq_disabled(); if termios.contains_icrnl() && item == b'\r' { item = b'\n' @@ -162,7 +162,7 @@ impl LineDiscipline { } // TODO: respect output flags - fn output_char(&self, item: u8, termios: &KernelTermios, echo_callback: fn(&str)) { + fn output_char(&self, item: u8, termios: &KernelTermios, mut echo_callback: F) { match item { b'\n' => echo_callback("\n"), b'\r' => echo_callback("\r\n"), diff --git a/services/libs/jinux-std/src/device/tty/mod.rs b/services/libs/jinux-std/src/device/tty/mod.rs index 5d5aff5ee..1505ac3e4 100644 --- a/services/libs/jinux-std/src/device/tty/mod.rs +++ b/services/libs/jinux-std/src/device/tty/mod.rs @@ -6,7 +6,7 @@ use super::*; use crate::fs::utils::{IoEvents, IoctlCmd, Poller}; use crate::prelude::*; use crate::process::process_group::ProcessGroup; -use crate::process::{process_table, Pgid}; +use crate::process::process_table; use crate::util::{read_val_from_user, write_val_to_user}; pub mod driver; diff --git a/services/libs/jinux-std/src/fs/devpts/master.rs b/services/libs/jinux-std/src/fs/devpts/master.rs index 911f074c0..e4d6c61e0 100644 --- a/services/libs/jinux-std/src/fs/devpts/master.rs +++ b/services/libs/jinux-std/src/fs/devpts/master.rs @@ -1,7 +1,9 @@ -use crate::prelude::*; +use crate::{fs::file_handle::FileLike, prelude::*}; use super::*; +use crate::device::PtyMaster; + /// Pty master inode for the master device. pub struct PtyMasterInode(Arc); @@ -14,8 +16,10 @@ impl PtyMasterInode { impl Drop for PtyMasterInode { fn drop(&mut self) { // Remove the slave from fs. - let index = self.0.slave_index(); - let _ = self.0.ptmx().devpts().remove_slave(index); + let index = self.0.index(); + let fs = self.0.ptmx().fs(); + let devpts = fs.downcast_ref::().unwrap(); + devpts.remove_slave(index); } } @@ -77,52 +81,6 @@ impl Inode for PtyMasterInode { } fn fs(&self) -> Arc { - self.0.ptmx().devpts() - } -} - -// TODO: implement real pty master. -pub struct PtyMaster { - slave_index: u32, - ptmx: Arc, -} - -impl PtyMaster { - pub fn new(slave_index: u32, ptmx: Arc) -> Arc { - Arc::new(Self { slave_index, ptmx }) - } - - pub fn slave_index(&self) -> u32 { - self.slave_index - } - - fn ptmx(&self) -> &Ptmx { - &self.ptmx - } -} - -impl Device for PtyMaster { - fn type_(&self) -> DeviceType { - self.ptmx.device_type() - } - - fn id(&self) -> DeviceId { - self.ptmx.device_id() - } - - fn read(&self, buf: &mut [u8]) -> Result { - todo!(); - } - - fn write(&self, buf: &[u8]) -> Result { - todo!(); - } - - fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { - todo!(); - } - - fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { - todo!(); + self.0.ptmx().fs() } } diff --git a/services/libs/jinux-std/src/fs/devpts/mod.rs b/services/libs/jinux-std/src/fs/devpts/mod.rs index a661274b8..58093bf1c 100644 --- a/services/libs/jinux-std/src/fs/devpts/mod.rs +++ b/services/libs/jinux-std/src/fs/devpts/mod.rs @@ -9,9 +9,10 @@ use core::time::Duration; use jinux_frame::vm::VmFrame; use jinux_util::{id_allocator::IdAlloc, slot_vec::SlotVec}; -use self::master::{PtyMaster, PtyMasterInode}; +use self::master::PtyMasterInode; use self::ptmx::Ptmx; -use self::slave::{PtySlave, PtySlaveInode}; +use self::slave::PtySlaveInode; +use crate::device::PtyMaster; mod master; mod ptmx; @@ -60,8 +61,7 @@ impl DevPts { .alloc() .ok_or_else(|| Error::with_message(Errno::EIO, "cannot alloc index"))?; - let master = PtyMaster::new(index as u32, self.root.ptmx.clone()); - let slave = PtySlave::new(master.clone()); + let (master, slave) = PtyMaster::new_pair(index as u32, self.root.ptmx.clone())?; let master_inode = PtyMasterInode::new(master); let slave_inode = PtySlaveInode::new(slave, self.this.clone()); @@ -73,10 +73,10 @@ impl DevPts { /// Remove the slave from fs. /// /// This is called when the master is being dropped. - fn remove_slave(&self, index: u32) -> Option> { + fn remove_slave(&self, index: usize) -> Option> { let removed_slave = self.root.remove_slave(&index.to_string()); if removed_slave.is_some() { - self.index_alloc.lock().free(index as usize); + self.index_alloc.lock().free(index); } removed_slave } diff --git a/services/libs/jinux-std/src/fs/devpts/slave.rs b/services/libs/jinux-std/src/fs/devpts/slave.rs index 2297c3364..027e243eb 100644 --- a/services/libs/jinux-std/src/fs/devpts/slave.rs +++ b/services/libs/jinux-std/src/fs/devpts/slave.rs @@ -2,6 +2,8 @@ use crate::prelude::*; use super::*; +use crate::device::PtySlave; + /// Same major number with Linux, the minor number is the index of slave. const SLAVE_MAJOR_NUM: u32 = 3; @@ -88,44 +90,3 @@ impl Inode for PtySlaveInode { self.fs.upgrade().unwrap() } } - -// TODO: implement real pty slave. -pub struct PtySlave { - master: Arc, -} - -impl PtySlave { - pub fn new(master: Arc) -> Arc { - Arc::new(Self { master }) - } - - pub fn index(&self) -> u32 { - self.master.slave_index() - } -} - -impl Device for PtySlave { - fn type_(&self) -> DeviceType { - DeviceType::CharDevice - } - - fn id(&self) -> DeviceId { - DeviceId::new(SLAVE_MAJOR_NUM, self.index()) - } - - fn read(&self, buf: &mut [u8]) -> Result { - todo!(); - } - - fn write(&self, buf: &[u8]) -> Result { - todo!(); - } - - fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { - todo!(); - } - - fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { - todo!(); - } -} diff --git a/services/libs/jinux-std/src/fs/utils/ioctl.rs b/services/libs/jinux-std/src/fs/utils/ioctl.rs index 58c0c4d81..bd97aac11 100644 --- a/services/libs/jinux-std/src/fs/utils/ioctl.rs +++ b/services/libs/jinux-std/src/fs/utils/ioctl.rs @@ -3,18 +3,28 @@ use crate::prelude::*; #[repr(u32)] #[derive(Debug, Clone, Copy, TryFromInt)] pub enum IoctlCmd { - // Get terminal attributes + /// Get terminal attributes TCGETS = 0x5401, TCSETS = 0x5402, - // Drain the output buffer and set attributes + /// Drain the output buffer and set attributes TCSETSW = 0x5403, - // Drain the output buffer, and discard pending input, and set attributes + /// Drain the output buffer, and discard pending input, and set attributes TCSETSF = 0x5404, - // Get the process group ID of the foreground process group on this terminal + /// Make the given terminal the controlling terminal of the calling process. + TIOCSCTTY = 0x540e, + /// Get the process group ID of the foreground process group on this terminal TIOCGPGRP = 0x540f, - // Set the foreground process group ID of this terminal. + /// Set the foreground process group ID of this terminal. TIOCSPGRP = 0x5410, - // Set window size + /// Set window size TIOCGWINSZ = 0x5413, TIOCSWINSZ = 0x5414, + /// the calling process gives up this controlling terminal + TIOCNOTTY = 0x5422, + /// Get Pty Number + TIOCGPTN = 0x80045430, + /// Lock/unlock Pty + TIOCSPTLCK = 0x40045431, + /// Safely open the slave + TIOCGPTPEER = 0x40045441, }