Refactor tty&pty code

This commit is contained in:
Jianfeng Jiang 2023-11-16 17:28:10 +08:00 committed by Tate, Hongliang Tian
parent 001326110e
commit 9d8a2b420d
6 changed files with 337 additions and 184 deletions

View File

@ -21,22 +21,8 @@ PtyTest.SwitchNoncanonToCanonNoNewlineBig
PtyTest.NoncanonBigWrite PtyTest.NoncanonBigWrite
PtyTest.SwitchNoncanonToCanonMultiline PtyTest.SwitchNoncanonToCanonMultiline
PtyTest.SwitchTwiceMultiline PtyTest.SwitchTwiceMultiline
JobControlTest.SetTTYMaster
JobControlTest.SetTTY
JobControlTest.SetTTYNonLeader
JobControlTest.SetTTYBadArg JobControlTest.SetTTYBadArg
JobControlTest.SetTTYDifferentSession JobControlTest.SetTTYDifferentSession
JobControlTest.ReleaseTTY
JobControlTest.ReleaseUnsetTTY
JobControlTest.ReleaseWrongTTY
JobControlTest.ReleaseTTYNonLeader
JobControlTest.ReleaseTTYDifferentSession
JobControlTest.ReleaseTTYSignals JobControlTest.ReleaseTTYSignals
JobControlTest.GetForegroundProcessGroup
JobControlTest.GetForegroundProcessGroupNonControlling
JobControlTest.SetForegroundProcessGroup JobControlTest.SetForegroundProcessGroup
JobControlTest.SetForegroundProcessGroupWrongTTY
JobControlTest.SetForegroundProcessGroupNegPgid
JobControlTest.SetForegroundProcessGroupEmptyProcessGroup JobControlTest.SetForegroundProcessGroupEmptyProcessGroup
JobControlTest.SetForegroundProcessGroupDifferentSession
JobControlTest.OrphanRegression

View File

@ -33,7 +33,7 @@ pub fn init() -> Result<()> {
pub fn new_pty_pair(index: u32, ptmx: Arc<dyn Inode>) -> Result<(Arc<PtyMaster>, Arc<PtySlave>)> { pub fn new_pty_pair(index: u32, ptmx: Arc<dyn Inode>) -> Result<(Arc<PtyMaster>, Arc<PtySlave>)> {
debug!("pty index = {}", index); debug!("pty index = {}", index);
let master = Arc::new(PtyMaster::new(ptmx, index)); let master = PtyMaster::new(ptmx, index);
let slave = Arc::new(PtySlave::new(master.clone())); let slave = PtySlave::new(&master);
Ok((master, slave)) Ok((master, slave))
} }

View File

@ -4,14 +4,16 @@ use ringbuf::{ring_buffer::RbBase, HeapRb, Rb};
use crate::device::tty::line_discipline::LineDiscipline; use crate::device::tty::line_discipline::LineDiscipline;
use crate::events::IoEvents; use crate::events::IoEvents;
use crate::fs::device::{Device, DeviceId, DeviceType}; 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::fs_resolver::FsPath;
use crate::fs::inode_handle::FileIo;
use crate::fs::utils::{AccessMode, Inode, InodeMode, IoctlCmd}; use crate::fs::utils::{AccessMode, Inode, InodeMode, IoctlCmd};
use crate::prelude::*; use crate::prelude::*;
use crate::process::signal::signals::kernel::KernelSignal;
use crate::process::signal::{Pollee, Poller}; use crate::process::signal::{Pollee, Poller};
use crate::process::{JobControl, Terminal};
use crate::util::{read_val_from_user, write_val_to_user}; use crate::util::{read_val_from_user, write_val_to_user};
const PTS_DIR: &str = "/dev/pts";
const BUFFER_CAPACITY: usize = 4096; const BUFFER_CAPACITY: usize = 4096;
/// Pesudo terminal master. /// Pesudo terminal master.
@ -23,19 +25,23 @@ pub struct PtyMaster {
index: u32, index: u32,
output: Arc<LineDiscipline>, output: Arc<LineDiscipline>,
input: SpinLock<HeapRb<u8>>, input: SpinLock<HeapRb<u8>>,
job_control: JobControl,
/// The state of input buffer /// The state of input buffer
pollee: Pollee, pollee: Pollee,
weak_self: Weak<Self>,
} }
impl PtyMaster { impl PtyMaster {
pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Self { pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Arc<Self> {
Self { Arc::new_cyclic(|weak_ref| PtyMaster {
ptmx, ptmx,
index, index,
output: LineDiscipline::new(), output: LineDiscipline::new(),
input: SpinLock::new(HeapRb::new(BUFFER_CAPACITY)), input: SpinLock::new(HeapRb::new(BUFFER_CAPACITY)),
job_control: JobControl::new(),
pollee: Pollee::new(IoEvents::OUT), pollee: Pollee::new(IoEvents::OUT),
} weak_self: weak_ref.clone(),
})
} }
pub fn index(&self) -> u32 { pub fn index(&self) -> u32 {
@ -46,16 +52,12 @@ impl PtyMaster {
&self.ptmx &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(); let mut input = self.input.lock_irq_disabled();
input.push_overwrite(byte); input.push_overwrite(ch);
self.update_state(&input); 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 { pub(super) fn slave_poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents {
let mut poll_status = IoEvents::empty(); 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> { fn read(&self, buf: &mut [u8]) -> Result<usize> {
// TODO: deal with nonblocking read // TODO: deal with nonblocking read
if buf.is_empty() { if buf.is_empty() {
@ -121,10 +123,22 @@ impl FileLike for PtyMaster {
} }
fn write(&self, buf: &[u8]) -> Result<usize> { 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 { 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() { for byte in content.as_bytes() {
input.push_overwrite(*byte); input.push_overwrite(*byte);
} }
@ -193,29 +207,35 @@ impl FileLike for PtyMaster {
self.output.set_window_size(winsize); self.output.set_window_size(winsize);
Ok(0) 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 => { IoctlCmd::TIOCGPGRP => {
let Some(fg_pgid) = self.output.fg_pgid() else { let Some(foreground) = self.foreground() else {
return_errno_with_message!( return_errno_with_message!(
Errno::ESRCH, Errno::ESRCH,
"the foreground process group does not exist" "the foreground process group does not exist"
); );
}; };
let fg_pgid = foreground.pgid();
write_val_to_user(arg, &fg_pgid)?; write_val_to_user(arg, &fg_pgid)?;
Ok(0) 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 => { IoctlCmd::TIOCNOTTY => {
// TODO: reimplement when adding session. self.release_current_session()?;
self.output.set_fg(Weak::new());
Ok(0) Ok(0)
} }
IoctlCmd::FIONREAD => { 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 { impl PtySlave {
pub fn new(master: Arc<PtyMaster>) -> Self { pub fn new(master: &Arc<PtyMaster>) -> Arc<Self> {
PtySlave(master) Arc::new_cyclic(|weak_ref| PtySlave {
master: Arc::downgrade(master),
job_control: JobControl::new(),
weak_self: weak_ref.clone(),
})
} }
pub fn index(&self) -> u32 { 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 { fn id(&self) -> crate::fs::device::DeviceId {
DeviceId::new(88, self.index()) 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> { 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> { fn write(&self, buf: &[u8]) -> Result<usize> {
let master = self.master();
for ch in buf { for ch in buf {
// do we need to add '\r' here? // do we need to add '\r' here?
if *ch == b'\n' { if *ch == b'\n' {
self.0.slave_push_byte(b'\r'); master.slave_push_char(b'\r');
self.0.slave_push_byte(b'\n'); master.slave_push_char(b'\n');
} else { } else {
self.0.slave_push_byte(*ch); master.slave_push_char(*ch);
} }
} }
Ok(buf.len()) 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> { fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
match cmd { match cmd {
IoctlCmd::TCGETS IoctlCmd::TCGETS
| IoctlCmd::TCSETS | IoctlCmd::TCSETS
| IoctlCmd::TIOCGPGRP
| IoctlCmd::TIOCGPTN | IoctlCmd::TIOCGPTN
| IoctlCmd::TIOCGWINSZ | 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 => { IoctlCmd::TIOCSCTTY => {
// TODO: self.set_current_session()?;
Ok(0) Ok(0)
} }
IoctlCmd::TIOCNOTTY => { IoctlCmd::TIOCNOTTY => {
// TODO: self.release_current_session()?;
Ok(0) Ok(0)
} }
IoctlCmd::FIONREAD => { 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)?; write_val_to_user(arg, &buffer_len)?;
Ok(0) Ok(0)
} }
_ => Ok(0), _ => Ok(0),
} }
} }
fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents {
self.0.slave_poll(mask, poller)
}
} }

View 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!()
}
}

View File

@ -1,13 +1,10 @@
use crate::events::IoEvents; use crate::events::IoEvents;
use crate::prelude::*;
use crate::process::signal::constants::{SIGINT, SIGQUIT}; use crate::process::signal::constants::{SIGINT, SIGQUIT};
use crate::process::signal::signals::kernel::KernelSignal;
use crate::process::signal::{Pollee, Poller}; use crate::process::signal::{Pollee, Poller};
use crate::process::ProcessGroup;
use crate::thread::work_queue::work_item::WorkItem; use crate::thread::work_queue::work_item::WorkItem;
use crate::thread::work_queue::{submit_work_item, WorkPriority}; use crate::thread::work_queue::{submit_work_item, WorkPriority};
use crate::{
prelude::*,
process::{signal::signals::kernel::KernelSignal, Pgid},
};
use alloc::format; use alloc::format;
use jinux_frame::trap::disable_local; use jinux_frame::trap::disable_local;
use ringbuf::{ring_buffer::RbBase, Rb, StaticRb}; use ringbuf::{ring_buffer::RbBase, Rb, StaticRb};
@ -24,8 +21,6 @@ pub struct LineDiscipline {
current_line: SpinLock<CurrentLine>, current_line: SpinLock<CurrentLine>,
/// The read buffer /// The read buffer
read_buffer: SpinLock<StaticRb<u8, BUFFER_CAPACITY>>, read_buffer: SpinLock<StaticRb<u8, BUFFER_CAPACITY>>,
/// The foreground process group
foreground: SpinLock<Weak<ProcessGroup>>,
/// termios /// termios
termios: SpinLock<KernelTermios>, termios: SpinLock<KernelTermios>,
/// Windows size, /// Windows size,
@ -87,7 +82,6 @@ impl LineDiscipline {
Self { Self {
current_line: SpinLock::new(CurrentLine::new()), current_line: SpinLock::new(CurrentLine::new()),
read_buffer: SpinLock::new(StaticRb::default()), read_buffer: SpinLock::new(StaticRb::default()),
foreground: SpinLock::new(Weak::new()),
termios: SpinLock::new(KernelTermios::default()), termios: SpinLock::new(KernelTermios::default()),
winsize: SpinLock::new(WinSize::default()), winsize: SpinLock::new(WinSize::default()),
pollee: Pollee::new(IoEvents::empty()), pollee: Pollee::new(IoEvents::empty()),
@ -98,7 +92,15 @@ impl LineDiscipline {
} }
/// Push char to line discipline. /// 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 termios = self.termios.lock_irq_disabled();
let ch = if termios.contains_icrnl() && ch == b'\r' { let ch = if termios.contains_icrnl() && ch == b'\r' {
@ -107,7 +109,7 @@ impl LineDiscipline {
ch 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 // The char is already dealt with, so just return
return; return;
} }
@ -158,31 +160,32 @@ impl LineDiscipline {
self.update_readable_state_deferred(); self.update_readable_state_deferred();
} }
fn may_send_signal_to_foreground(&self, termios: &KernelTermios, ch: u8) -> bool { fn may_send_signal<F: Fn() -> Option<Arc<dyn Fn(KernelSignal) + Send + Sync>>>(
if !termios.contains_isig() { &self,
termios: &KernelTermios,
ch: u8,
may_send_signal: F,
) -> bool {
if !termios.is_canonical_mode() || !termios.contains_isig() {
return false; return false;
} }
let Some(foreground) = self.foreground.lock().upgrade() else { let Some(send_signal) = may_send_signal() else {
return false; return false;
}; };
let signal = match ch { let signal = match ch {
item if item == *termios.get_special_char(CC_C_CHAR::VINTR) => { ch if ch == *termios.get_special_char(CC_C_CHAR::VINTR) => KernelSignal::new(SIGINT),
KernelSignal::new(SIGINT) ch if ch == *termios.get_special_char(CC_C_CHAR::VQUIT) => KernelSignal::new(SIGQUIT),
}
item if item == *termios.get_special_char(CC_C_CHAR::VQUIT) => {
KernelSignal::new(SIGQUIT)
}
_ => return false, _ => return false,
}; };
// `kernel_signal()` may cause sleep, so only construct parameters here. // `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 true
} }
fn update_readable_state(&self) { pub fn update_readable_state(&self) {
let buffer = self.read_buffer.lock_irq_disabled(); let buffer = self.read_buffer.lock_irq_disabled();
if !buffer.is_empty() { if !buffer.is_empty() {
self.pollee.add_events(IoEvents::IN); self.pollee.add_events(IoEvents::IN);
@ -193,7 +196,6 @@ impl LineDiscipline {
fn update_readable_state_deferred(&self) { fn update_readable_state_deferred(&self) {
let buffer = self.read_buffer.lock_irq_disabled(); let buffer = self.read_buffer.lock_irq_disabled();
let pollee = self.pollee.clone();
// add/del events may sleep, so only construct parameters here. // add/del events may sleep, so only construct parameters here.
if !buffer.is_empty() { if !buffer.is_empty() {
self.work_item_para.lock_irq_disabled().pollee_type = Some(PolleeType::Add); 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. /// include all operations that may cause sleep, and processes by a work queue.
fn update_readable_state_after(&self) { fn update_readable_state_after(&self) {
if let Some(signal) = self.work_item_para.lock_irq_disabled().kernel_signal.take() { if let Some((send_signal, signal)) =
self.foreground self.work_item_para.lock_irq_disabled().kernel_signal.take()
.lock() {
.upgrade() send_signal(signal);
.unwrap()
.kernel_signal(signal)
}; };
if let Some(pollee_type) = self.work_item_para.lock_irq_disabled().pollee_type.take() { if let Some(pollee_type) = self.work_item_para.lock_irq_disabled().pollee_type.take() {
match pollee_type { match pollee_type {
@ -243,42 +243,33 @@ impl LineDiscipline {
} }
} }
/// read all bytes buffered to dst, return the actual read length. pub fn read<F: Fn() -> bool>(&self, buf: &mut [u8], belongs_to_foreground: F) -> Result<usize> {
pub fn read(&self, dst: &mut [u8]) -> Result<usize> {
let mut poller = None; let mut poller = None;
loop { 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 { match res {
Ok(read_len) => { Ok(len) => return Ok(len),
return Ok(read_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 /// read all bytes buffered to dst, return the actual read length.
let need_poller = if poller.is_none() { fn try_read(&self, dst: &mut [u8]) -> Result<usize> {
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);
}
let (vmin, vtime) = { let (vmin, vtime) = {
let termios = self.termios.lock_irq_disabled(); let termios = self.termios.lock_irq_disabled();
let vmin = *termios.get_special_char(CC_C_CHAR::VMIN); let vmin = *termios.get_special_char(CC_C_CHAR::VMIN);
@ -326,7 +317,8 @@ impl LineDiscipline {
if termios.is_canonical_mode() { if termios.is_canonical_mode() {
// canonical mode, read until meet new line // canonical mode, read until meet new line
if is_line_terminator(next_char, &termios) { 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; *dst_i = next_char;
read_len += 1; read_len += 1;
} }
@ -368,31 +360,6 @@ impl LineDiscipline {
todo!() 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 /// whether there is buffered data
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.read_buffer.lock_irq_disabled().len() == 0 self.read_buffer.lock_irq_disabled().len() == 0
@ -439,8 +406,7 @@ fn is_line_terminator(item: u8, termios: &KernelTermios) -> bool {
false false
} }
/// The special char should not be read by reading process fn is_eof(ch: u8, termios: &KernelTermios) -> bool {
fn should_not_be_read(ch: u8, termios: &KernelTermios) -> bool {
ch == *termios.get_special_char(CC_C_CHAR::VEOF) 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 ctrl_char + b'A' - 1
} }
fn init_poller(poller: &mut Option<Poller>) {
if poller.is_some() {
return;
}
*poller = Some(Poller::new());
}
enum PolleeType { enum PolleeType {
Add, Add,
Del, Del,
} }
struct LineDisciplineWorkPara { struct LineDisciplineWorkPara {
kernel_signal: Option<KernelSignal>, #[allow(clippy::type_complexity)]
kernel_signal: Option<(Arc<dyn Fn(KernelSignal) + Send + Sync>, KernelSignal)>,
pollee_type: Option<PolleeType>, pollee_type: Option<PolleeType>,
} }

View File

@ -2,23 +2,28 @@ use spin::Once;
use self::driver::TtyDriver; use self::driver::TtyDriver;
use self::line_discipline::LineDiscipline; use self::line_discipline::LineDiscipline;
use super::*;
use crate::events::IoEvents; use crate::events::IoEvents;
use crate::fs::device::{Device, DeviceId, DeviceType};
use crate::fs::inode_handle::FileIo;
use crate::fs::utils::IoctlCmd; use crate::fs::utils::IoctlCmd;
use crate::prelude::*; use crate::prelude::*;
use crate::process::signal::signals::kernel::KernelSignal;
use crate::process::signal::Poller; 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}; use crate::util::{read_val_from_user, write_val_to_user};
mod device;
pub mod driver; pub mod driver;
pub mod line_discipline; pub mod line_discipline;
pub mod termio; pub mod termio;
pub use device::TtyDevice;
static N_TTY: Once<Arc<Tty>> = Once::new(); static N_TTY: Once<Arc<Tty>> = Once::new();
pub(super) fn init() { pub(super) fn init() {
let name = CString::new("console").unwrap(); let name = CString::new("console").unwrap();
let tty = Arc::new(Tty::new(name)); let tty = Tty::new(name);
N_TTY.call_once(|| tty); N_TTY.call_once(|| tty);
driver::init(); driver::init();
} }
@ -28,45 +33,49 @@ pub struct Tty {
name: CString, name: CString,
/// line discipline /// line discipline
ldisc: Arc<LineDiscipline>, ldisc: Arc<LineDiscipline>,
job_control: JobControl,
/// driver /// driver
driver: SpinLock<Weak<TtyDriver>>, driver: SpinLock<Weak<TtyDriver>>,
weak_self: Weak<Self>,
} }
impl Tty { impl Tty {
pub fn new(name: CString) -> Self { pub fn new(name: CString) -> Arc<Self> {
Tty { Arc::new_cyclic(|weak_ref| Tty {
name, name,
ldisc: LineDiscipline::new(), ldisc: LineDiscipline::new(),
job_control: JobControl::new(),
driver: SpinLock::new(Weak::new()), driver: SpinLock::new(Weak::new()),
} weak_self: weak_ref.clone(),
} })
/// Set foreground process group
pub fn set_fg(&self, process_group: Weak<ProcessGroup>) {
self.ldisc.set_fg(process_group);
} }
pub fn set_driver(&self, driver: Weak<TtyDriver>) { pub fn set_driver(&self, driver: Weak<TtyDriver>) {
*self.driver.lock_irq_disabled() = driver; *self.driver.lock_irq_disabled() = driver;
} }
pub fn receive_char(&self, item: u8) { pub fn receive_char(&self, ch: u8) {
self.ldisc.push_char(item, |content| print!("{}", content)); 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 { impl FileIo for Tty {
fn type_(&self) -> DeviceType {
DeviceType::CharDevice
}
fn id(&self) -> DeviceId {
// Same value with Linux
DeviceId::new(5, 0)
}
fn read(&self, buf: &mut [u8]) -> Result<usize> { 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> { fn write(&self, buf: &[u8]) -> Result<usize> {
@ -92,23 +101,28 @@ impl Device for Tty {
Ok(0) Ok(0)
} }
IoctlCmd::TIOCGPGRP => { IoctlCmd::TIOCGPGRP => {
let Some(fg_pgid) = self.ldisc.fg_pgid() else { let Some(foreground) = self.foreground() else {
return_errno_with_message!(Errno::ENOENT, "No fg process group") return_errno_with_message!(Errno::ESRCH, "No fg process group")
}; };
let fg_pgid = foreground.pgid();
debug!("fg_pgid = {}", fg_pgid); debug!("fg_pgid = {}", fg_pgid);
write_val_to_user(arg, &fg_pgid)?; write_val_to_user(arg, &fg_pgid)?;
Ok(0) Ok(0)
} }
IoctlCmd::TIOCSPGRP => { IoctlCmd::TIOCSPGRP => {
// Set the process group id of fg progress group // 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 { if pgid < 0 {
return_errno_with_message!(Errno::EINVAL, "invalid pgid"); return_errno_with_message!(Errno::EINVAL, "negative 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)),
} }
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) Ok(0)
} }
IoctlCmd::TCSETS => { IoctlCmd::TCSETS => {
@ -143,12 +157,49 @@ impl Device for Tty {
self.ldisc.set_window_size(winsize); self.ldisc.set_window_size(winsize);
Ok(0) Ok(0)
} }
IoctlCmd::TIOCSCTTY => {
self.set_current_session()?;
Ok(0)
}
_ => todo!(), _ => 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> { pub fn get_n_tty() -> &'static Arc<Tty> {
N_TTY.get().unwrap() 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()))
}