mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-09 05:16:47 +00:00
Refactor tty&pty code
This commit is contained in:
parent
001326110e
commit
9d8a2b420d
@ -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
|
@ -33,7 +33,7 @@ pub fn init() -> Result<()> {
|
||||
|
||||
pub fn new_pty_pair(index: u32, ptmx: Arc<dyn Inode>) -> Result<(Arc<PtyMaster>, Arc<PtySlave>)> {
|
||||
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))
|
||||
}
|
||||
|
@ -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<LineDiscipline>,
|
||||
input: SpinLock<HeapRb<u8>>,
|
||||
job_control: JobControl,
|
||||
/// The state of input buffer
|
||||
pollee: Pollee,
|
||||
weak_self: Weak<Self>,
|
||||
}
|
||||
|
||||
impl PtyMaster {
|
||||
pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Self {
|
||||
Self {
|
||||
pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Arc<Self> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
// TODO: deal with nonblocking read
|
||||
if buf.is_empty() {
|
||||
@ -121,10 +123,22 @@ impl FileLike for PtyMaster {
|
||||
}
|
||||
|
||||
fn write(&self, buf: &[u8]) -> Result<usize> {
|
||||
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<dyn Fn(KernelSignal) + Send + Sync>)
|
||||
};
|
||||
|
||||
let mut input = self.input.lock();
|
||||
for character in buf {
|
||||
self.output.push_char(*character, |content| {
|
||||
self.output
|
||||
.push_char(*character, may_send_signal, |content| {
|
||||
for byte in content.as_bytes() {
|
||||
input.push_overwrite(*byte);
|
||||
}
|
||||
@ -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<PtyMaster>);
|
||||
impl Terminal for PtyMaster {
|
||||
fn arc_self(&self) -> Arc<dyn Terminal> {
|
||||
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::<DevPts>().unwrap();
|
||||
|
||||
let index = self.index;
|
||||
devpts.remove_slave(index);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PtySlave {
|
||||
master: Weak<PtyMaster>,
|
||||
job_control: JobControl,
|
||||
weak_self: Weak<Self>,
|
||||
}
|
||||
|
||||
impl PtySlave {
|
||||
pub fn new(master: Arc<PtyMaster>) -> Self {
|
||||
PtySlave(master)
|
||||
pub fn new(master: &Arc<PtyMaster>) -> Arc<Self> {
|
||||
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<PtyMaster> {
|
||||
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<dyn Terminal> {
|
||||
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<usize> {
|
||||
self.0.slave_read(buf)
|
||||
self.master()
|
||||
.output
|
||||
.read(buf, || self.job_control.current_belongs_to_foreground())
|
||||
}
|
||||
|
||||
fn write(&self, buf: &[u8]) -> Result<usize> {
|
||||
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<i32> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
47
services/libs/jinux-std/src/device/tty/device.rs
Normal file
47
services/libs/jinux-std/src/device/tty/device.rs
Normal file
@ -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<Option<Arc<dyn FileIo>>> {
|
||||
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<dyn FileIo>))
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn write(&self, buf: &[u8]) -> Result<usize> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
@ -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<CurrentLine>,
|
||||
/// The read buffer
|
||||
read_buffer: SpinLock<StaticRb<u8, BUFFER_CAPACITY>>,
|
||||
/// The foreground process group
|
||||
foreground: SpinLock<Weak<ProcessGroup>>,
|
||||
/// termios
|
||||
termios: SpinLock<KernelTermios>,
|
||||
/// 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<F: FnMut(&str)>(&self, ch: u8, echo_callback: F) {
|
||||
pub fn push_char<
|
||||
F1: Fn() -> Option<Arc<dyn Fn(KernelSignal) + Send + Sync>>,
|
||||
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<F: Fn() -> Option<Arc<dyn Fn(KernelSignal) + Send + Sync>>>(
|
||||
&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<usize> {
|
||||
pub fn read<F: Fn() -> bool>(&self, buf: &mut [u8], belongs_to_foreground: F) -> Result<usize> {
|
||||
let mut poller = None;
|
||||
loop {
|
||||
let res = self.try_read(dst);
|
||||
if !belongs_to_foreground() {
|
||||
init_poller(&mut poller);
|
||||
if self.poll(IoEvents::IN, poller.as_ref()).is_empty() {
|
||||
poller.as_ref().unwrap().wait()?
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let res = self.try_read(buf);
|
||||
match res {
|
||||
Ok(read_len) => {
|
||||
return Ok(read_len);
|
||||
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()?
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e.error() != Errno::EAGAIN {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_read(&self, dst: &mut [u8]) -> Result<usize> {
|
||||
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<usize> {
|
||||
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<ProcessGroup>) {
|
||||
*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<Pgid> {
|
||||
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<Poller>) {
|
||||
if poller.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
*poller = Some(Poller::new());
|
||||
}
|
||||
|
||||
enum PolleeType {
|
||||
Add,
|
||||
Del,
|
||||
}
|
||||
|
||||
struct LineDisciplineWorkPara {
|
||||
kernel_signal: Option<KernelSignal>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
kernel_signal: Option<(Arc<dyn Fn(KernelSignal) + Send + Sync>, KernelSignal)>,
|
||||
pollee_type: Option<PolleeType>,
|
||||
}
|
||||
|
||||
|
@ -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<Arc<Tty>> = 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<LineDiscipline>,
|
||||
job_control: JobControl,
|
||||
/// driver
|
||||
driver: SpinLock<Weak<TtyDriver>>,
|
||||
weak_self: Weak<Self>,
|
||||
}
|
||||
|
||||
impl Tty {
|
||||
pub fn new(name: CString) -> Self {
|
||||
Tty {
|
||||
pub fn new(name: CString) -> Arc<Self> {
|
||||
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<ProcessGroup>) {
|
||||
self.ldisc.set_fg(process_group);
|
||||
weak_self: weak_ref.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_driver(&self, driver: Weak<TtyDriver>) {
|
||||
*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<dyn Fn(KernelSignal) + Send + Sync>)
|
||||
};
|
||||
|
||||
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<usize> {
|
||||
self.ldisc.read(buf)
|
||||
self.ldisc
|
||||
.read(buf, || self.job_control.current_belongs_to_foreground())
|
||||
}
|
||||
|
||||
fn write(&self, buf: &[u8]) -> Result<usize> {
|
||||
@ -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::<i32>(arg)?;
|
||||
let pgid = {
|
||||
let pgid: i32 = 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)),
|
||||
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<dyn Terminal> {
|
||||
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<Tty> {
|
||||
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()))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user