Implement pseudo terminal

This commit is contained in:
Jianfeng Jiang
2023-08-01 14:35:35 +08:00
committed by Tate, Hongliang Tian
parent a042da1847
commit f802ff40c5
10 changed files with 369 additions and 107 deletions

View File

@ -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;

View File

@ -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<dyn Inode>,
index: usize,
ldisc: LineDiscipline,
master_buffer: SpinLock<HeapRb<u8>>,
/// The state of master buffer
pollee: Pollee,
}
impl PtyMaster {
pub fn new_pair(index: u32, ptmx: Arc<dyn Inode>) -> Result<(Arc<PtyMaster>, Arc<PtySlave>)> {
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<dyn Inode> {
&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<usize> {
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<u8>) {
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<usize> {
// 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<usize> {
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<i32> {
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();
}
}
}

View File

@ -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},

View File

@ -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<PtyMaster>);
impl PtySlave {
pub fn new(master: Arc<PtyMaster>) -> 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<usize> {
self.0.slave_read(buf)
}
fn write(&self, buf: &[u8]) -> Result<usize> {
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<i32> {
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)
}
}

View File

@ -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<F: FnMut(&str)>(&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<F: FnMut(&str)>(&self, item: u8, termios: &KernelTermios, mut echo_callback: F) {
match item {
b'\n' => echo_callback("\n"),
b'\r' => echo_callback("\r\n"),

View File

@ -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;

View File

@ -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<PtyMaster>);
@ -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::<DevPts>().unwrap();
devpts.remove_slave(index);
}
}
@ -77,52 +81,6 @@ impl Inode for PtyMasterInode {
}
fn fs(&self) -> Arc<dyn FileSystem> {
self.0.ptmx().devpts()
}
}
// TODO: implement real pty master.
pub struct PtyMaster {
slave_index: u32,
ptmx: Arc<Ptmx>,
}
impl PtyMaster {
pub fn new(slave_index: u32, ptmx: Arc<Ptmx>) -> Arc<Self> {
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<usize> {
todo!();
}
fn write(&self, buf: &[u8]) -> Result<usize> {
todo!();
}
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
todo!();
}
fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents {
todo!();
self.0.ptmx().fs()
}
}

View File

@ -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<Arc<PtySlaveInode>> {
fn remove_slave(&self, index: usize) -> Option<Arc<PtySlaveInode>> {
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
}

View File

@ -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<PtyMaster>,
}
impl PtySlave {
pub fn new(master: Arc<PtyMaster>) -> Arc<Self> {
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<usize> {
todo!();
}
fn write(&self, buf: &[u8]) -> Result<usize> {
todo!();
}
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
todo!();
}
fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents {
todo!();
}
}

View File

@ -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,
}