228 lines
6.7 KiB
Rust
228 lines
6.7 KiB
Rust
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
use alloc::format;
|
|
|
|
use ostd::task::Task;
|
|
|
|
use super::{driver::PtyDriver, PtySlave};
|
|
use crate::{
|
|
current_userspace,
|
|
device::tty::TtyFlags,
|
|
events::IoEvents,
|
|
fs::{
|
|
devpts::Ptmx,
|
|
file_table::FdFlags,
|
|
fs_resolver::FsPath,
|
|
inode_handle::FileIo,
|
|
utils::{mkmod, AccessMode, InodeIo, IoctlCmd, OpenArgs, StatusFlags},
|
|
},
|
|
prelude::*,
|
|
process::{
|
|
posix_thread::AsThreadLocal,
|
|
signal::{PollHandle, Pollable},
|
|
Terminal,
|
|
},
|
|
};
|
|
|
|
const IO_CAPACITY: usize = 4096;
|
|
|
|
/// A pseudoterminal master.
|
|
///
|
|
/// A pseudoterminal contains two buffers:
|
|
/// * The input buffer is written by the master and read by the slave, which is maintained in the
|
|
/// line discipline (part of [`PtySlave`], which is a [`Tty`]).
|
|
/// * The output buffer is written by the slave and read by the master, which is maintained in the
|
|
/// driver (i.e., [`PtyDriver`]).
|
|
///
|
|
/// [`Tty`]: crate::device::tty::Tty
|
|
pub struct PtyMaster {
|
|
ptmx: Arc<Ptmx>,
|
|
slave: Arc<PtySlave>,
|
|
}
|
|
|
|
impl PtyMaster {
|
|
pub(super) fn new(ptmx: Arc<Ptmx>, index: u32) -> Box<Self> {
|
|
let slave = PtySlave::new(index, PtyDriver::new());
|
|
|
|
Box::new(Self { ptmx, slave })
|
|
}
|
|
|
|
pub(super) fn slave(&self) -> &Arc<PtySlave> {
|
|
&self.slave
|
|
}
|
|
|
|
fn master_flags(&self) -> &TtyFlags {
|
|
self.slave.driver().tty_flags()
|
|
}
|
|
|
|
fn slave_flags(&self) -> &TtyFlags {
|
|
self.slave.tty_flags()
|
|
}
|
|
|
|
fn check_io_events(&self) -> IoEvents {
|
|
let mut events = IoEvents::empty();
|
|
|
|
if self.slave().driver().buffer_len() > 0 {
|
|
events |= IoEvents::IN;
|
|
}
|
|
|
|
if self.slave().can_push() {
|
|
events |= IoEvents::OUT;
|
|
}
|
|
|
|
if self.master_flags().is_other_closed() {
|
|
events |= IoEvents::HUP;
|
|
}
|
|
|
|
events
|
|
}
|
|
}
|
|
|
|
impl Pollable for PtyMaster {
|
|
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
|
|
self.slave
|
|
.driver()
|
|
.pollee()
|
|
.poll_with(mask, poller, || self.check_io_events())
|
|
}
|
|
}
|
|
|
|
impl InodeIo for PtyMaster {
|
|
fn read_at(
|
|
&self,
|
|
_offset: usize,
|
|
writer: &mut VmWriter,
|
|
status_flags: StatusFlags,
|
|
) -> Result<usize> {
|
|
// TODO: Add support for timeout.
|
|
let mut buf = vec![0u8; writer.avail().min(IO_CAPACITY)];
|
|
let is_nonblocking = status_flags.contains(StatusFlags::O_NONBLOCK);
|
|
let read_len = if is_nonblocking {
|
|
self.slave.driver().try_read(&mut buf)?
|
|
} else {
|
|
self.wait_events(IoEvents::IN, None, || {
|
|
self.slave.driver().try_read(&mut buf)
|
|
})?
|
|
};
|
|
self.slave.driver().pollee().invalidate();
|
|
self.slave.notify_output();
|
|
|
|
// TODO: Confirm what we should do if `write_fallible` fails in the middle.
|
|
writer.write_fallible(&mut buf[..read_len].into())?;
|
|
Ok(read_len)
|
|
}
|
|
|
|
fn write_at(
|
|
&self,
|
|
_offset: usize,
|
|
reader: &mut VmReader,
|
|
status_flags: StatusFlags,
|
|
) -> Result<usize> {
|
|
let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)];
|
|
let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?;
|
|
|
|
// TODO: Add support for timeout.
|
|
let is_nonblocking = status_flags.contains(StatusFlags::O_NONBLOCK);
|
|
let len = if is_nonblocking {
|
|
self.slave.push_input(&buf[..write_len])?
|
|
} else {
|
|
self.wait_events(IoEvents::OUT, None, || {
|
|
self.slave.push_input(&buf[..write_len])
|
|
})?
|
|
};
|
|
self.slave.driver().pollee().invalidate();
|
|
Ok(len)
|
|
}
|
|
}
|
|
|
|
impl FileIo for PtyMaster {
|
|
fn check_seekable(&self) -> Result<()> {
|
|
return_errno_with_message!(Errno::ESPIPE, "the inode is a pty");
|
|
}
|
|
|
|
fn is_offset_aware(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
|
|
match cmd {
|
|
IoctlCmd::TCGETS
|
|
| IoctlCmd::TCSETS
|
|
| IoctlCmd::TCSETSW
|
|
| IoctlCmd::TCSETSF
|
|
| IoctlCmd::TIOCGWINSZ
|
|
| IoctlCmd::TIOCSWINSZ
|
|
| IoctlCmd::TIOCGPTN => return self.slave.ioctl(cmd, arg),
|
|
IoctlCmd::TIOCSPTLCK => {
|
|
let val = current_userspace!().read_val::<i32>(arg)?;
|
|
let flags = self.master_flags();
|
|
if val == 0 {
|
|
flags.clear_pty_locked();
|
|
} else {
|
|
flags.set_pty_locked();
|
|
}
|
|
}
|
|
IoctlCmd::TIOCGPTLCK => {
|
|
let val = if self.master_flags().is_pty_locked() {
|
|
1
|
|
} else {
|
|
0
|
|
};
|
|
current_userspace!().write_val(arg, &val)?;
|
|
}
|
|
IoctlCmd::TIOCGPTPEER => {
|
|
let current_task = Task::current().unwrap();
|
|
let thread_local = current_task.as_thread_local().unwrap();
|
|
|
|
// TODO: Deal with `open()` flags.
|
|
let slave = {
|
|
let slave_name = {
|
|
let devpts_path = super::DEV_PTS.get().unwrap().abs_path();
|
|
format!("{}/{}", devpts_path, self.slave.index())
|
|
};
|
|
|
|
let fs_path = FsPath::try_from(slave_name.as_str())?;
|
|
|
|
let inode_handle = {
|
|
let open_args = OpenArgs::from_modes(AccessMode::O_RDWR, mkmod!(u+rw));
|
|
thread_local
|
|
.borrow_fs()
|
|
.resolver()
|
|
.read()
|
|
.lookup(&fs_path)?
|
|
.open(open_args)?
|
|
};
|
|
Arc::new(inode_handle)
|
|
};
|
|
|
|
let fd = {
|
|
let file_table = thread_local.borrow_file_table();
|
|
let mut file_table_locked = file_table.unwrap().write();
|
|
// TODO: Deal with the `O_CLOEXEC` flag.
|
|
file_table_locked.insert(slave, FdFlags::empty())
|
|
};
|
|
return Ok(fd);
|
|
}
|
|
IoctlCmd::FIONREAD => {
|
|
let len = self.slave.driver().buffer_len() as i32;
|
|
current_userspace!().write_val(arg, &len)?;
|
|
}
|
|
_ => (self.slave.clone() as Arc<dyn Terminal>).job_ioctl(cmd, arg, true)?,
|
|
}
|
|
|
|
Ok(0)
|
|
}
|
|
}
|
|
|
|
impl Drop for PtyMaster {
|
|
fn drop(&mut self) {
|
|
if let Some(devpts) = self.ptmx.devpts() {
|
|
let index = self.slave.index();
|
|
devpts.remove_slave(index);
|
|
}
|
|
|
|
self.slave_flags().set_other_closed();
|
|
self.slave.notify_hup();
|
|
}
|
|
}
|