diff --git a/kernel/src/arch/x86_64/ipc/signal.rs b/kernel/src/arch/x86_64/ipc/signal.rs index 17a54dd77f..90d54ec980 100644 --- a/kernel/src/arch/x86_64/ipc/signal.rs +++ b/kernel/src/arch/x86_64/ipc/signal.rs @@ -28,7 +28,7 @@ use crate::{ }, }, mm::MemoryManagementArch, - process::ProcessManager, + process::{ProcessFlags, ProcessManager}, syscall::user_access::{UserBufferReader, UserBufferWriter}, }; @@ -714,6 +714,11 @@ unsafe fn do_signal(frame: &mut TrapFrame, got_signal: &mut bool) { let mut info: Option; let mut sigaction: Option; let sig_block: SigSet = *siginfo_read_guard.sig_blocked(); + let frame_oldset = if pcb.flags().contains(ProcessFlags::RESTORE_SIG_MASK) { + *siginfo_read_guard.saved_sigmask() + } else { + sig_block + }; drop(siginfo_read_guard); loop { @@ -783,8 +788,8 @@ unsafe fn do_signal(frame: &mut TrapFrame, got_signal: &mut bool) { } } - let oldset = sig_block; // no sig_struct guard to drop + pcb.flags().remove(ProcessFlags::RESTORE_SIG_MASK); drop(pcb); // 做完上面的检查后,开中断 CurrentIrqArch::interrupt_enable(); @@ -813,7 +818,8 @@ unsafe fn do_signal(frame: &mut TrapFrame, got_signal: &mut bool) { } *got_signal = true; - let mut blocked = oldset | sigaction.mask(); + let oldset = frame_oldset; + let mut blocked = sig_block | sigaction.mask(); if !sigaction.flags().contains(SigFlags::SA_NODEFER) { blocked.insert(sig_number.into()); } diff --git a/kernel/src/driver/char/virtio_console.rs b/kernel/src/driver/char/virtio_console.rs index 755270ba7c..3d3b70a0ed 100644 --- a/kernel/src/driver/char/virtio_console.rs +++ b/kernel/src/driver/char/virtio_console.rs @@ -40,6 +40,7 @@ use crate::{ }, mm::page::PAGE_4K_SIZE, }; +use alloc::boxed::Box; use alloc::string::String; use alloc::string::ToString; use alloc::sync::{Arc, Weak}; @@ -49,15 +50,28 @@ use core::fmt::Debug; use core::fmt::Formatter; use core::{ any::Any, + ptr::NonNull, sync::atomic::{compiler_fence, Ordering}, }; use system_error::SystemError; use unified_init::macros::unified_init; -use virtio_drivers::device::console::VirtIOConsole; +use virtio_drivers::{ + queue::VirtQueue, + transport::{DeviceStatus, Transport}, + Error as VirtioError, +}; const VIRTIO_CONSOLE_BASENAME: &str = "virtio_console"; const HVC_MINOR: u32 = 0; const VIRTIO_CONSOLE_RX_IRQ_LIMIT: usize = PAGE_4K_SIZE; +const VIRTIO_CONSOLE_RECEIVEQ_PORT_0: u16 = 0; +const VIRTIO_CONSOLE_TRANSMITQ_PORT_0: u16 = 1; +const VIRTIO_CONSOLE_QUEUE_SIZE: usize = 2; +const VIRTIO_CONSOLE_F_RING_EVENT_IDX: u64 = 1 << 29; +const VIRTIO_CONSOLE_OUTBUF_SIZE: usize = PAGE_4K_SIZE; +const VIRTIO_CONSOLE_TX_CHUNK: usize = 256; +const VIRTIO_CONSOLE_TX_FLUSH_BUDGET: usize = PAGE_4K_SIZE; +const VIRTIO_CONSOLE_IRQ_TX_FLUSH_BUDGET: usize = VIRTIO_CONSOLE_TX_CHUNK; static mut VIRTIO_CONSOLE_DRIVER: Option> = None; static mut TTY_HVC_DRIVER: Option> = None; @@ -67,6 +81,202 @@ fn tty_hvc_driver() -> &'static Arc { unsafe { TTY_HVC_DRIVER.as_ref().unwrap() } } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct VirtIOConsoleInfo { + rows: u16, + columns: u16, +} + +#[repr(C)] +struct VirtIOConsoleConfig { + cols: u16, + rows: u16, + max_nr_ports: u32, + emerg_wr: u32, +} + +struct DragonVirtIOConsole { + transport: VirtIOTransport, + config_space: NonNull, + receiveq: VirtQueue, + transmitq: VirtQueue, + queue_buf_rx: Box<[u8; PAGE_4K_SIZE]>, + tx_buf: Box<[u8; VIRTIO_CONSOLE_TX_CHUNK]>, + tx_len: usize, + tx_token: Option, + cursor: usize, + pending_len: usize, + receive_token: Option, +} + +impl DragonVirtIOConsole { + fn new(mut transport: VirtIOTransport) -> Result { + transport.set_status(DeviceStatus::empty()); + transport.set_status(DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER); + + let device_features = transport.read_device_features(); + let negotiated_features = device_features & VIRTIO_CONSOLE_F_RING_EVENT_IDX; + transport.write_driver_features(negotiated_features); + transport.set_status( + DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER | DeviceStatus::FEATURES_OK, + ); + transport.set_guest_page_size(PAGE_4K_SIZE as u32); + + let event_idx = negotiated_features & VIRTIO_CONSOLE_F_RING_EVENT_IDX != 0; + let config_space = transport.config_space::()?; + let receiveq = VirtQueue::new( + &mut transport, + VIRTIO_CONSOLE_RECEIVEQ_PORT_0, + false, + event_idx, + )?; + let transmitq = VirtQueue::new( + &mut transport, + VIRTIO_CONSOLE_TRANSMITQ_PORT_0, + false, + event_idx, + )?; + + let queue_buf_rx = Box::new([0; PAGE_4K_SIZE]); + let tx_buf = Box::new([0; VIRTIO_CONSOLE_TX_CHUNK]); + transport.finish_init(); + + let mut console = Self { + transport, + config_space, + receiveq, + transmitq, + queue_buf_rx, + tx_buf, + tx_len: 0, + tx_token: None, + cursor: 0, + pending_len: 0, + receive_token: None, + }; + console.poll_retrieve()?; + Ok(console) + } + + fn info(&self) -> VirtIOConsoleInfo { + let config = self.config_space.as_ptr(); + // SAFETY: config_space is provided by the virtio transport for this console device. + unsafe { + VirtIOConsoleInfo { + columns: core::ptr::read_volatile(core::ptr::addr_of!((*config).cols)), + rows: core::ptr::read_volatile(core::ptr::addr_of!((*config).rows)), + } + } + } + + fn poll_retrieve(&mut self) -> Result<(), VirtioError> { + if self.receive_token.is_none() && self.cursor == self.pending_len { + // SAFETY: queue_buf_rx remains alive until the matching pop_used completes. + self.receive_token = Some(unsafe { + self.receiveq + .add(&[], &mut [self.queue_buf_rx.as_mut_slice()]) + }?); + if self.receiveq.should_notify() { + self.transport.notify(VIRTIO_CONSOLE_RECEIVEQ_PORT_0); + } + } + Ok(()) + } + + fn finish_receive(&mut self) -> Result { + let mut has_new_data = false; + if let Some(receive_token) = self.receive_token { + if self.receiveq.peek_used() == Some(receive_token) { + // SAFETY: this pops the same RX buffer registered in poll_retrieve(). + let len = unsafe { + self.receiveq.pop_used( + receive_token, + &[], + &mut [self.queue_buf_rx.as_mut_slice()], + )? + }; + has_new_data = true; + self.cursor = 0; + self.pending_len = len as usize; + self.receive_token.take(); + } + } + Ok(has_new_data) + } + + fn recv(&mut self, pop: bool) -> Result, VirtioError> { + self.finish_receive()?; + if self.cursor == self.pending_len { + return Ok(None); + } + let ch = self.queue_buf_rx[self.cursor]; + if pop { + self.cursor += 1; + self.poll_retrieve()?; + } + Ok(Some(ch)) + } + + fn pending_tx_len(&self) -> usize { + if self.tx_token.is_some() { + self.tx_len + } else { + 0 + } + } + + fn complete_tx(&mut self) -> Result, VirtioError> { + let Some(token) = self.tx_token else { + return Ok(None); + }; + match self.transmitq.peek_used() { + None => return Ok(None), + Some(used) if used == token => {} + Some(_) => return Err(VirtioError::WrongToken), + } + + let tx_len = self.tx_len; + let tx_buf = &self.tx_buf[..tx_len]; + // SAFETY: tx_buf is the same stable DMA buffer submitted by submit_tx(). + unsafe { + self.transmitq.pop_used(token, &[tx_buf], &mut [])?; + } + self.tx_token = None; + self.tx_len = 0; + Ok(Some(tx_len)) + } + + fn submit_tx(&mut self, buf: &[u8]) -> Result<(), VirtioError> { + if buf.is_empty() || self.tx_token.is_some() { + return Ok(()); + } + let len = buf.len().min(self.tx_buf.len()); + self.tx_buf[..len].copy_from_slice(&buf[..len]); + let tx_buf = &self.tx_buf[..len]; + // SAFETY: tx_buf belongs to this device and is not modified again until complete_tx() + // observes and pops the matching used descriptor. + let token = unsafe { self.transmitq.add(&[tx_buf], &mut [])? }; + self.tx_token = Some(token); + self.tx_len = len; + if self.transmitq.should_notify() { + self.transport.notify(VIRTIO_CONSOLE_TRANSMITQ_PORT_0); + } + Ok(()) + } + + fn enable_interrupts(&mut self) { + self.receiveq.set_dev_notify(true); + self.transmitq.set_dev_notify(true); + } +} + +impl Drop for DragonVirtIOConsole { + fn drop(&mut self) { + self.transport.queue_unset(VIRTIO_CONSOLE_RECEIVEQ_PORT_0); + self.transport.queue_unset(VIRTIO_CONSOLE_TRANSMITQ_PORT_0); + } +} + pub fn virtio_console( transport: VirtIOTransport, dev_id: Arc, @@ -134,13 +344,13 @@ impl VirtIOConsoleDevice { } let irq = Some(transport.irq()); - let device_inner = VirtIOConsole::::new(transport); + let device_inner = DragonVirtIOConsole::new(transport); if let Err(e) = device_inner { log::error!("VirtIOConsoleDevice '{dev_id:?}' create failed: {:?}", e); return None; } - let mut device_inner: VirtIOConsole = device_inner.unwrap(); + let mut device_inner = device_inner.unwrap(); device_inner.enable_interrupts(); let dev = Arc::new_cyclic(|self_ref| Self { @@ -156,6 +366,10 @@ impl VirtIOConsoleDevice { kobject_common: KObjectCommonData::default(), irq, input_vc_index: None, + output_tty: Weak::new(), + outbuf: Vec::with_capacity(VIRTIO_CONSOLE_OUTBUF_SIZE), + outbuf_size: VIRTIO_CONSOLE_OUTBUF_SIZE, + flush_pending: false, }), }); @@ -165,16 +379,50 @@ impl VirtIOConsoleDevice { fn inner(&self) -> SpinLockGuard<'_, InnerVirtIOConsoleDevice> { self.inner.lock_irqsave() } + + fn flush_output_budget( + &self, + budget: usize, + ) -> Result<(usize, Option>), SystemError> { + let mut inner = self.inner(); + let was_full = inner.write_room() == 0; + let flushed = match inner.flush_output_locked(budget) { + Ok(flushed) => flushed, + Err(err) => { + let tty = inner.output_tty.upgrade(); + drop(inner); + Self::wake_output_tty(tty); + return Err(err); + } + }; + let should_wakeup = flushed > 0 && (was_full || inner.write_room() > 0); + let tty = if should_wakeup { + inner.output_tty.upgrade() + } else { + None + }; + Ok((flushed, tty)) + } + + fn wake_output_tty(tty: Option>) { + if let Some(tty) = tty { + tty.tty_wakeup(); + } + } } struct InnerVirtIOConsoleDevice { - device_inner: VirtIOConsole, + device_inner: DragonVirtIOConsole, virtio_index: Option, name: Option, device_common: DeviceCommonData, kobject_common: KObjectCommonData, irq: Option, input_vc_index: Option, + output_tty: Weak, + outbuf: Vec, + outbuf_size: usize, + flush_pending: bool, } impl Debug for InnerVirtIOConsoleDevice { @@ -186,10 +434,79 @@ impl Debug for InnerVirtIOConsoleDevice { .field("kobject_common", &self.kobject_common) .field("irq", &self.irq) .field("input_vc_index", &self.input_vc_index) + .field("outbuf_len", &self.outbuf.len()) + .field("outbuf_size", &self.outbuf_size) + .field("flush_pending", &self.flush_pending) .finish() } } +impl InnerVirtIOConsoleDevice { + fn write_room(&self) -> usize { + self.outbuf_size.saturating_sub(self.outbuf.len()) + } + + fn chars_in_buffer(&self) -> usize { + self.outbuf.len() + } + + fn flush_output_locked(&mut self, max_bytes: usize) -> Result { + if self.outbuf.is_empty() && self.device_inner.pending_tx_len() == 0 { + self.flush_pending = false; + return Ok(0); + } + + let mut total = 0; + while total < max_bytes { + let Some(done) = self + .device_inner + .complete_tx() + .map_err(virtio_drivers_error_to_system_error)? + else { + break; + }; + let drain_len = done.min(self.outbuf.len()); + if drain_len != 0 { + self.outbuf.drain(0..drain_len); + total += drain_len; + } + } + + if self.device_inner.pending_tx_len() == 0 && !self.outbuf.is_empty() && max_bytes != 0 { + let send_len = self + .outbuf + .len() + .min(VIRTIO_CONSOLE_TX_CHUNK) + .min(max_bytes.saturating_sub(total).max(1)); + match self.device_inner.submit_tx(&self.outbuf[..send_len]) { + Ok(()) => {} + Err(VirtioError::QueueFull) | Err(VirtioError::NotReady) => { + self.flush_pending = true; + } + Err(err) => { + self.outbuf.clear(); + self.flush_pending = false; + if total > 0 { + return Ok(total); + } + return Err(virtio_drivers_error_to_system_error(err)); + } + } + } + + self.flush_pending = !self.outbuf.is_empty() || self.device_inner.pending_tx_len() != 0; + Ok(total) + } + + fn discard_unsubmitted_output_locked(&mut self) { + let keep_inflight = self.device_inner.pending_tx_len().min(self.outbuf.len()); + if self.outbuf.len() > keep_inflight { + self.outbuf.truncate(keep_inflight); + } + self.flush_pending = keep_inflight != 0; + } +} + impl KObject for VirtIOConsoleDevice { fn as_any_ref(&self) -> &dyn Any { self @@ -350,6 +667,9 @@ impl VirtIODevice for VirtIOConsoleDevice { enqueue_tty_rx_to_vc_from_irq(vc_index, &buf[0..index]); } } + if let Ok((_, tty)) = self.flush_output_budget(VIRTIO_CONSOLE_IRQ_TX_FLUSH_BUDGET) { + Self::wake_output_tty(tty); + } Ok(IrqReturn::Handled) } @@ -481,6 +801,18 @@ impl TtyOperation for VirtIOConsoleDriver { Ok(()) } + fn write_room(&self, tty: &TtyCoreData) -> usize { + let index = tty.index(); + if index >= VirtIOConsoleDriver::MAX_DEVICES { + return 0; + } + + self.devices.read()[index] + .as_ref() + .map(|dev| dev.inner().write_room()) + .unwrap_or(0) + } + fn write(&self, tty: &TtyCoreData, buf: &[u8], nr: usize) -> Result { if nr > buf.len() { return Err(SystemError::EINVAL); @@ -493,31 +825,126 @@ impl TtyOperation for VirtIOConsoleDriver { let dev = self.devices.read()[index] .clone() .ok_or(SystemError::ENODEV)?; - let mut cnt = 0; + let mut accepted = 0; + let mut wake_tty = None; let mut inner = dev.inner(); - for c in buf[0..nr].iter() { - if let Err(e) = inner.device_inner.send(*c) { - if cnt > 0 { - return Ok(cnt); + + while accepted < nr { + if inner.write_room() == 0 { + match inner.flush_output_locked(VIRTIO_CONSOLE_TX_FLUSH_BUDGET) { + Ok(flushed) => { + if flushed > 0 && inner.write_room() > 0 { + wake_tty = inner.output_tty.upgrade(); + } + } + Err(err) => { + wake_tty = inner.output_tty.upgrade(); + drop(inner); + VirtIOConsoleDevice::wake_output_tty(wake_tty); + if accepted > 0 { + return Ok(accepted); + } + return Err(err); + } + } + + if inner.write_room() == 0 { + break; + } + } + + let copy_len = (nr - accepted).min(inner.write_room()); + if copy_len == 0 { + break; + } + inner + .outbuf + .extend_from_slice(&buf[accepted..accepted + copy_len]); + accepted += copy_len; + + let was_full = inner.write_room() == 0; + match inner.flush_output_locked(VIRTIO_CONSOLE_TX_FLUSH_BUDGET) { + Ok(flushed) => { + if flushed > 0 && (was_full || inner.write_room() > 0) { + wake_tty = inner.output_tty.upgrade(); + } + if flushed == 0 && inner.write_room() == 0 { + break; + } + } + Err(err) => { + wake_tty = inner.output_tty.upgrade(); + drop(inner); + VirtIOConsoleDevice::wake_output_tty(wake_tty); + if accepted > 0 { + return Ok(accepted); + } + return Err(err); } - return Err(virtio_drivers_error_to_system_error(e)); - } else { - cnt += 1; } } - Ok(cnt) + drop(inner); + VirtIOConsoleDevice::wake_output_tty(wake_tty); + + Ok(accepted) } - fn flush_chars(&self, _tty: &TtyCoreData) { - // do nothing + fn flush_chars(&self, tty: &TtyCoreData) { + let index = tty.index(); + if index >= VirtIOConsoleDriver::MAX_DEVICES { + return; + } + + if let Some(dev) = self.devices.read()[index].clone() { + if let Ok((_, wake_tty)) = dev.flush_output_budget(VIRTIO_CONSOLE_TX_FLUSH_BUDGET) { + VirtIOConsoleDevice::wake_output_tty(wake_tty); + } + } + } + + fn chars_in_buffer(&self, tty: &TtyCoreData) -> usize { + let index = tty.index(); + if index >= VirtIOConsoleDriver::MAX_DEVICES { + return 0; + } + + self.devices.read()[index] + .as_ref() + .map(|dev| dev.inner().chars_in_buffer()) + .unwrap_or(0) } fn ioctl(&self, _tty: Arc, _cmd: u32, _arg: usize) -> Result<(), SystemError> { Err(SystemError::ENOIOCTLCMD) } - fn close(&self, _tty: Arc) -> Result<(), SystemError> { + fn close(&self, tty: Arc) -> Result<(), SystemError> { + let index = tty.core().index(); + if index >= VirtIOConsoleDriver::MAX_DEVICES { + return Ok(()); + } + + if let Some(dev) = self.devices.read()[index].clone() { + match dev.flush_output_budget(VIRTIO_CONSOLE_TX_FLUSH_BUDGET) { + Ok((_, wake_tty)) => { + let close_wake = { + let mut inner = dev.inner(); + inner.discard_unsubmitted_output_locked(); + inner.output_tty.upgrade() + }; + VirtIOConsoleDevice::wake_output_tty(wake_tty.or(close_wake)); + } + Err(_) => { + let mut inner = dev.inner(); + inner.discard_unsubmitted_output_locked(); + let wake_tty = inner.output_tty.upgrade(); + drop(inner); + VirtIOConsoleDevice::wake_output_tty(wake_tty); + } + } + } + Ok(()) } @@ -550,10 +977,16 @@ impl TtyOperation for VirtIOConsoleDriver { let vc = VirtConsole::new(Some(vc_data)); let vc_index = vc_manager().alloc(vc.clone()).ok_or(SystemError::EBUSY)?; - dev.inner().input_vc_index = Some(vc_index); + { + let mut inner = dev.inner(); + inner.input_vc_index = Some(vc_index); + inner.output_tty = Arc::downgrade(&tty); + } self.do_install(driver, tty, vc.clone()).inspect_err(|_| { vc_manager().free(vc_index); - dev.inner().input_vc_index = None; + let mut inner = dev.inner(); + inner.input_vc_index = None; + inner.output_tty = Weak::new(); })?; Ok(()) diff --git a/kernel/src/driver/tty/pty/mod.rs b/kernel/src/driver/tty/pty/mod.rs index 8e366189e9..e736916590 100644 --- a/kernel/src/driver/tty/pty/mod.rs +++ b/kernel/src/driver/tty/pty/mod.rs @@ -208,10 +208,7 @@ pub fn pty_init() -> Result<(), SystemError> { ); pts_driver.set_subtype(TtyDriverSubType::PtySlave); let term = pts_driver.init_termios_mut(); - term.input_mode = InputMode::empty(); - term.output_mode = OutputMode::empty(); term.control_mode = ControlMode::B38400 | ControlMode::CS8 | ControlMode::CREAD; - term.local_mode = LocalMode::empty(); term.input_speed = 38400; term.output_speed = 38400; PTS_DRIVER.init(TtyDriverManager::tty_register_driver(pts_driver).unwrap()); diff --git a/kernel/src/driver/tty/pty/unix98pty.rs b/kernel/src/driver/tty/pty/unix98pty.rs index f2872e882a..8f98725425 100644 --- a/kernel/src/driver/tty/pty/unix98pty.rs +++ b/kernel/src/driver/tty/pty/unix98pty.rs @@ -1,6 +1,12 @@ use alloc::{ + boxed::Box, string::ToString, sync::{Arc, Weak}, + vec, +}; +use core::{ + hint::spin_loop, + sync::atomic::{AtomicBool, Ordering}, }; use system_error::SystemError; @@ -21,6 +27,7 @@ use crate::{ libs::{ casting::DowncastArc, mutex::{Mutex, MutexGuard}, + spinlock::SpinLock, }, mm::VirtAddr, process::ProcessManager, @@ -30,6 +37,8 @@ use crate::{ use super::{ptm_driver, pts_driver, PtyCommon}; pub const NR_UNIX98_PTY_MAX: u32 = 128; +const PTY_BUFFER_LIMIT: usize = 16 * 1024; +const PTY_DRAIN_CHUNK: usize = 256; fn current_devpts() -> Result, SystemError> { let fs = ProcessManager::current_mntns() @@ -53,8 +62,21 @@ struct PtyDevPtsLink { pts_root: Weak, /// devpts 文件系统本体,用于精确回收索引(避免再去 downcast/全局路径查找) devpts: Weak, + /// slave 端 inode。TIOCGPTPEER 必须从 master 关联对象打开 peer, + /// 不能重新按 /dev/pts/N 路径查找,否则目录项被 unlink 后会偏离 Linux 语义。 + slave_inode: Arc, index: usize, state: Mutex, + master_to_slave: SpinLock, + slave_to_master: SpinLock, + master_to_slave_draining: AtomicBool, + slave_to_master_draining: AtomicBool, + master_to_slave_drain_requested: AtomicBool, + slave_to_master_drain_requested: AtomicBool, + master_to_slave_discarding: AtomicBool, + slave_to_master_discarding: AtomicBool, + master_to_slave_flushing: AtomicBool, + slave_to_master_flushing: AtomicBool, } #[derive(Debug, Default)] @@ -63,14 +85,102 @@ struct PtyDevPtsState { master_closed: bool, /// slave open 已经进入 driver open,但尚未提交为 active fd。 slave_opening: usize, - /// 是否存在已成功打开的 userspace slave fd。 - slave_active: bool, + /// 已成功打开的 userspace slave open file description 数量。 + slave_active: usize, /// 目录项是否已经 unlink(通常在 master close 时执行)。 unlinked: bool, /// 索引是否已经归还(仅在 master close 且无 opening/active slave 后允许归还)。 index_freed: bool, } +#[derive(Debug, Default)] +struct DrainResult { + delivered: usize, + freed_backlog: usize, + still_pending: bool, +} + +#[derive(Debug)] +struct PtyByteQueue { + buf: Box<[u8; PTY_BUFFER_LIMIT]>, + head: usize, + len: usize, + discard_len: usize, +} + +impl PtyByteQueue { + fn new() -> Self { + Self { + buf: vec![0; PTY_BUFFER_LIMIT] + .into_boxed_slice() + .try_into() + .unwrap(), + head: 0, + len: 0, + discard_len: 0, + } + } + + fn is_empty(&self) -> bool { + self.len == 0 + } + + fn room(&self) -> usize { + PTY_BUFFER_LIMIT - self.len + } + + fn clear_count(&mut self) -> usize { + let cleared = self.len; + self.head = 0; + self.len = 0; + self.discard_len = 0; + cleared + } + + fn clear(&mut self) { + self.clear_count(); + } + + fn push_slice(&mut self, buf: &[u8]) -> usize { + let accepted = buf.len().min(self.room()); + for (i, c) in buf[..accepted].iter().enumerate() { + let idx = (self.head + self.len + i) % PTY_BUFFER_LIMIT; + self.buf[idx] = *c; + } + self.len += accepted; + accepted + } + + fn copy_front(&self, out: &mut [u8]) -> usize { + let copied = out.len().min(self.len); + for (i, slot) in out[..copied].iter_mut().enumerate() { + *slot = self.buf[(self.head + i) % PTY_BUFFER_LIMIT]; + } + copied + } + + fn advance_front(&mut self, count: usize) { + let count = count.min(self.len); + self.head = (self.head + count) % PTY_BUFFER_LIMIT; + self.len -= count; + self.discard_len = self.discard_len.saturating_sub(count); + if self.len == 0 { + self.head = 0; + self.discard_len = 0; + } + } + + fn request_discard_prefix(&mut self) { + self.discard_len = self.len; + } + + fn discard_requested_prefix(&mut self) -> usize { + let discard_len = self.discard_len.min(self.len); + self.advance_front(discard_len); + discard_len + } +} + impl crate::driver::tty::tty_driver::TtyCorePrivateField for PtyDevPtsLink { fn as_any(&self) -> &dyn core::any::Any { self @@ -78,13 +188,313 @@ impl crate::driver::tty::tty_driver::TtyCorePrivateField for PtyDevPtsLink { } impl PtyDevPtsLink { - fn new(pts_root: Weak, devpts: Weak, index: usize) -> Self { + fn new( + pts_root: Weak, + devpts: Weak, + slave_inode: Arc, + index: usize, + ) -> Self { Self { pts_root, devpts, + slave_inode, index, state: Mutex::new(PtyDevPtsState::default()), + master_to_slave: SpinLock::new(PtyByteQueue::new()), + slave_to_master: SpinLock::new(PtyByteQueue::new()), + master_to_slave_draining: AtomicBool::new(false), + slave_to_master_draining: AtomicBool::new(false), + master_to_slave_drain_requested: AtomicBool::new(false), + slave_to_master_drain_requested: AtomicBool::new(false), + master_to_slave_discarding: AtomicBool::new(false), + slave_to_master_discarding: AtomicBool::new(false), + master_to_slave_flushing: AtomicBool::new(false), + slave_to_master_flushing: AtomicBool::new(false), + } + } + + fn queue_for_source(&self, subtype: TtyDriverSubType) -> Option<&SpinLock> { + match subtype { + TtyDriverSubType::PtyMaster => Some(&self.master_to_slave), + TtyDriverSubType::PtySlave => Some(&self.slave_to_master), + _ => None, + } + } + + fn state_flags_for_source( + &self, + subtype: TtyDriverSubType, + ) -> Option<(&AtomicBool, &AtomicBool, &AtomicBool, &AtomicBool)> { + match subtype { + TtyDriverSubType::PtyMaster => Some(( + &self.master_to_slave_draining, + &self.master_to_slave_drain_requested, + &self.master_to_slave_discarding, + &self.master_to_slave_flushing, + )), + TtyDriverSubType::PtySlave => Some(( + &self.slave_to_master_draining, + &self.slave_to_master_drain_requested, + &self.slave_to_master_discarding, + &self.slave_to_master_flushing, + )), + _ => None, + } + } + + fn pending_write_room(&self, subtype: TtyDriverSubType) -> usize { + self.queue_for_source(subtype) + .map(|queue| queue.lock_irqsave().room()) + .unwrap_or(0) + } + + fn clear_pending_from(&self, subtype: TtyDriverSubType) -> Result<(), SystemError> { + let Some(queue) = self.queue_for_source(subtype) else { + return Err(SystemError::ENODEV); + }; + let Some((draining, drain_requested, discarding, flushing)) = + self.state_flags_for_source(subtype) + else { + return Err(SystemError::ENODEV); + }; + + while flushing + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + spin_loop(); } + + while draining + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + spin_loop(); + } + + drain_requested.store(false, Ordering::Release); + discarding.store(false, Ordering::Release); + queue.lock_irqsave().clear(); + draining.store(false, Ordering::Release); + flushing.store(false, Ordering::Release); + Ok(()) + } + + fn discard_requested_prefix(&self, queue: &SpinLock) -> usize { + queue.lock_irqsave().discard_requested_prefix() + } + + fn request_discard_pending_from( + &self, + subtype: TtyDriverSubType, + ) -> Result { + let Some(queue) = self.queue_for_source(subtype) else { + return Err(SystemError::ENODEV); + }; + let Some((draining, drain_requested, discarding, flushing)) = + self.state_flags_for_source(subtype) + else { + return Err(SystemError::ENODEV); + }; + + if flushing.load(Ordering::Acquire) { + return Ok(0); + } + + if discarding + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + return Ok(0); + } + + queue.lock_irqsave().request_discard_prefix(); + + if draining + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + discarding.store(false, Ordering::Release); + return Ok(0); + } + + let discarded = self.discard_requested_prefix(queue); + drain_requested.store(false, Ordering::Release); + draining.store(false, Ordering::Release); + discarding.store(false, Ordering::Release); + + Ok(discarded) + } + + fn write_to_peer( + &self, + subtype: TtyDriverSubType, + to: Arc, + buf: &[u8], + nr: usize, + ) -> Result { + let Some(queue) = self.queue_for_source(subtype) else { + return Err(SystemError::ENODEV); + }; + let Some((_, _, discarding, flushing)) = self.state_flags_for_source(subtype) else { + return Err(SystemError::ENODEV); + }; + + let mut accepted = loop { + while flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { + spin_loop(); + } + + let mut queue = queue.lock_irqsave(); + if flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { + drop(queue); + spin_loop(); + continue; + } + break queue.push_slice(&buf[..nr]); + }; + + if accepted == 0 { + self.drain_to_peer(subtype, to.clone())?; + accepted = loop { + while flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { + spin_loop(); + } + + let mut queue = queue.lock_irqsave(); + if flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { + drop(queue); + spin_loop(); + continue; + } + break queue.push_slice(&buf[..nr]); + }; + } + + if accepted != 0 { + self.drain_to_peer(subtype, to)?; + } + + Ok(accepted) + } + + fn drain_to_peer( + &self, + subtype: TtyDriverSubType, + to: Arc, + ) -> Result { + let Some(queue) = self.queue_for_source(subtype) else { + return Err(SystemError::ENODEV); + }; + let Some((draining, drain_requested, discarding, flushing)) = + self.state_flags_for_source(subtype) + else { + return Err(SystemError::ENODEV); + }; + + let mut result = DrainResult::default(); + if flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { + result.still_pending = !queue.lock_irqsave().is_empty(); + return Ok(result); + } + if draining + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + drain_requested.store(true, Ordering::Release); + result.still_pending = !queue.lock_irqsave().is_empty(); + return Ok(result); + } + + loop { + if flushing.load(Ordering::Acquire) { + result.still_pending = !queue.lock_irqsave().is_empty(); + draining.store(false, Ordering::Release); + break; + } + if discarding.load(Ordering::Acquire) { + result.still_pending = !queue.lock_irqsave().is_empty(); + draining.store(false, Ordering::Release); + break; + } + + let discarded = self.discard_requested_prefix(queue); + if discarded != 0 { + result.freed_backlog += discarded; + if !queue.lock_irqsave().is_empty() { + continue; + } + draining.store(false, Ordering::Release); + if drain_requested.swap(false, Ordering::AcqRel) + && draining + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + continue; + } + break; + } + + let mut chunk = [0u8; PTY_DRAIN_CHUNK]; + let chunk_len = queue.lock_irqsave().copy_front(&mut chunk); + + if chunk_len == 0 { + draining.store(false, Ordering::Release); + if drain_requested.swap(false, Ordering::AcqRel) + && draining + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + continue; + } + break; + } + + let delivered = + match to + .core() + .port() + .unwrap() + .receive_buf(&chunk[..chunk_len], &[], chunk_len) + { + Ok(delivered) => delivered, + Err(err) => { + let _ = self.discard_requested_prefix(queue); + draining.store(false, Ordering::Release); + return Err(err); + } + }; + queue.lock_irqsave().advance_front(delivered); + result.delivered += delivered; + result.freed_backlog += delivered; + let discarded = self.discard_requested_prefix(queue); + if discarded != 0 { + result.freed_backlog += discarded; + if !queue.lock_irqsave().is_empty() { + continue; + } + draining.store(false, Ordering::Release); + if drain_requested.swap(false, Ordering::AcqRel) + && draining + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + continue; + } + break; + } + if delivered < chunk_len { + result.still_pending = true; + draining.store(false, Ordering::Release); + break; + } + } + + if !result.still_pending { + result.still_pending = !queue.lock_irqsave().is_empty(); + } + + Ok(result) } fn on_close(&self, subtype: TtyDriverSubType) { @@ -96,7 +506,8 @@ impl PtyDevPtsLink { self.try_unlink_once(); } TtyDriverSubType::PtySlave => { - self.state.lock().slave_active = false; + // Slave file close is tracked by on_slave_file_close(), because driver close is + // only reached when the final tty reference is released. } _ => {} } @@ -124,7 +535,22 @@ impl PtyDevPtsLink { return; } state.slave_opening -= 1; - state.slave_active = true; + state.slave_active += 1; + } + self.try_free_index_when_fully_closed(); + } + + fn on_slave_file_close(&self) { + { + let mut state = self.state.lock(); + if state.slave_active == 0 { + log::warn!( + "PtyDevPtsLink: slave file close without active open, index={}", + self.index + ); + } else { + state.slave_active -= 1; + } } self.try_free_index_when_fully_closed(); } @@ -167,7 +593,7 @@ impl PtyDevPtsLink { let mut state = self.state.lock(); if !state.master_closed || state.slave_opening != 0 - || state.slave_active + || state.slave_active != 0 || state.index_freed { (false, false) @@ -193,6 +619,10 @@ impl PtyDevPtsLink { devpts.free_index(self.index); } } + + fn slave_inode(&self) -> Result, SystemError> { + Ok(self.slave_inode.clone()) + } } #[derive(Debug)] @@ -242,16 +672,27 @@ impl TtyOperation for Unix98PtyDriverInner { return Ok(0); } + if let Some(hook_arc) = tty.private_fields() { + if let Some(hook) = hook_arc.as_any().downcast_ref::() { + return hook.write_to_peer(tty.driver().tty_driver_sub_type(), to, buf, nr); + } + } + to.core().port().unwrap().receive_buf(buf, &[], nr) } fn write_room(&self, tty: &TtyCoreData) -> usize { - // TODO 暂时 if tty.flow_irqsave().stopped { return 0; } - 8192 + if let Some(hook_arc) = tty.private_fields() { + if let Some(hook) = hook_arc.as_any().downcast_ref::() { + return hook.pending_write_room(tty.driver().tty_driver_sub_type()); + } + } + + PTY_BUFFER_LIMIT } fn flush_buffer(&self, tty: &TtyCoreData) -> Result<(), SystemError> { @@ -259,6 +700,12 @@ impl TtyOperation for Unix98PtyDriverInner { return Ok(()); }; + if let Some(hook_arc) = tty.private_fields() { + if let Some(hook) = hook_arc.as_any().downcast_ref::() { + hook.clear_pending_from(tty.driver().tty_driver_sub_type())?; + } + } + if to.core().contorl_info_irqsave().packet { tty.contorl_info_irqsave() .pktstatus @@ -526,17 +973,20 @@ pub fn ptmx_open( let core = tty.core(); core.flags_write().insert(TtyFlag::PTY_LOCK); - if let Err(err) = pts_root_inode.create( + let slave_inode = match pts_root_inode.create( &index.to_string(), FileType::CharDevice, InodeMode::from_bits_truncate(0x666), ) { - ptm_driver().ttys().remove(&index); - pts_driver().ttys().remove(&index); - fsinfo.free_index(index); - *data = FilePrivateData::Unused; - return Err(err); - } + Ok(slave_inode) => slave_inode, + Err(err) => { + ptm_driver().ttys().remove(&index); + pts_driver().ttys().remove(&index); + fsinfo.free_index(index); + *data = FilePrivateData::Unused; + return Err(err); + } + }; // 在 master/slave 两端记录 devpts 根目录与 fs,用于精确清理: // - master close: unlink /dev/pts/N @@ -544,6 +994,7 @@ pub fn ptmx_open( let hook = Arc::new(PtyDevPtsLink::new( Arc::downgrade(&pts_root_inode), Arc::downgrade(&fsinfo), + slave_inode, index, )); tty.set_private_fields(hook.clone()); @@ -562,3 +1013,83 @@ pub fn ptmx_open( Ok(()) } + +pub fn ptm_peer_inode(master: Arc) -> Result, SystemError> { + let core = master.core(); + if core.driver().tty_driver_sub_type() != TtyDriverSubType::PtyMaster { + return Err(SystemError::EIO); + } + + let hook_arc = master.private_fields().ok_or(SystemError::EIO)?; + let hook = hook_arc + .as_any() + .downcast_ref::() + .ok_or(SystemError::EIO)?; + hook.slave_inode() +} + +pub fn pty_file_close(tty: &TtyCore) { + let core = tty.core(); + if core.driver().tty_driver_sub_type() != TtyDriverSubType::PtySlave { + return; + } + + if let Some(hook_arc) = tty.private_fields() { + if let Some(hook) = hook_arc.as_any().downcast_ref::() { + hook.on_slave_file_close(); + } + } +} + +pub fn pty_drain_pending_to(tty: Arc) -> Result<(), SystemError> { + let Some(peer) = tty.core().link() else { + return Ok(()); + }; + let Some(hook_arc) = tty.private_fields() else { + return Ok(()); + }; + let Some(hook) = hook_arc.as_any().downcast_ref::() else { + return Ok(()); + }; + + let result = hook.drain_to_peer(peer.core().driver().tty_driver_sub_type(), tty)?; + if result.freed_backlog != 0 { + peer.tty_wakeup(); + } + Ok(()) +} + +pub fn pty_discard_pending_to(tty: Arc) -> Result<(), SystemError> { + let Some(peer) = tty.core().link() else { + return Ok(()); + }; + let Some(hook_arc) = tty.private_fields() else { + return Ok(()); + }; + let Some(hook) = hook_arc.as_any().downcast_ref::() else { + return Ok(()); + }; + + hook.clear_pending_from(peer.core().driver().tty_driver_sub_type())?; + peer.tty_wakeup(); + Ok(()) +} + +pub fn pty_request_discard_pending_to(tty: Arc) -> Result<(), SystemError> { + let Some(peer) = tty.core().link() else { + return Ok(()); + }; + let Some(hook_arc) = tty.private_fields() else { + return Ok(()); + }; + let Some(hook) = hook_arc.as_any().downcast_ref::() else { + return Ok(()); + }; + + let discarded = + hook.request_discard_pending_from(peer.core().driver().tty_driver_sub_type())?; + if discarded != 0 { + peer.tty_wakeup(); + } + Ok(()) +} diff --git a/kernel/src/driver/tty/termios.rs b/kernel/src/driver/tty/termios.rs index 919ec84c32..4ae3e78809 100644 --- a/kernel/src/driver/tty/termios.rs +++ b/kernel/src/driver/tty/termios.rs @@ -75,9 +75,7 @@ impl PosixTermios { } fn output_speed(&self) -> Option { - let flag = ControlMode::from_bits_truncate( - self.c_cflag & ControlMode::CBAUD.intersection(ControlMode::CBAUDEX).bits(), - ); // CBAUD + CBAUDEX + let flag = ControlMode::from_bits_truncate(self.c_cflag & ControlMode::CBAUD.bits()); flag.baud_rate() } @@ -91,27 +89,23 @@ impl PosixTermios { } } -pub const INIT_CONTORL_CHARACTERS: [u8; CONTORL_CHARACTER_NUM] = [ - b'C' - 0x40, // VINTR - b'\\' - 0x40, // VQUIT - 0o177, // VERASE - b'U' - 0x40, // VKILL - b'D' - 0x40, // VEOF - 1, // VMIN - 0, // VEOL - 0, // VTIME - 0, // VEOL2 - 0, // VSWTC - b'W' - 0x40, // VWERASE - b'R' - 0x40, // VREPRINT - b'Z' - 0x40, // VSUSP - b'Q' - 0x40, // VSTART - b'S' - 0x40, // VSTOP - b'V' - 0x40, // VLNEXT - b'O' - 0x40, // VDISCARD - 0, - 0, -]; +pub const INIT_CONTORL_CHARACTERS: [u8; CONTORL_CHARACTER_NUM] = { + let mut chs = [0; CONTORL_CHARACTER_NUM]; + chs[ControlCharIndex::VINTR] = b'C' - 0x40; + chs[ControlCharIndex::VQUIT] = b'\\' - 0x40; + chs[ControlCharIndex::VERASE] = 0o177; + chs[ControlCharIndex::VKILL] = b'U' - 0x40; + chs[ControlCharIndex::VEOF] = b'D' - 0x40; + chs[ControlCharIndex::VSTART] = b'Q' - 0x40; + chs[ControlCharIndex::VSTOP] = b'S' - 0x40; + chs[ControlCharIndex::VSUSP] = b'Z' - 0x40; + chs[ControlCharIndex::VREPRINT] = b'R' - 0x40; + chs[ControlCharIndex::VDISCARD] = b'O' - 0x40; + chs[ControlCharIndex::VWERASE] = b'W' - 0x40; + chs[ControlCharIndex::VLNEXT] = b'V' - 0x40; + chs[ControlCharIndex::VMIN] = 1; + chs +}; // pub const INIT_CONTORL_CHARACTERS: [u8; CONTORL_CHARACTER_NUM] = { // let mut chs: [u8; CONTORL_CHARACTER_NUM] = Default::default(); @@ -420,28 +414,28 @@ impl ControlCharIndex { pub const VKILL: usize = 3; /// 文件结束信号 \0? pub const VEOF: usize = 4; - /// 指定非规范模式下的最小字符数 - pub const VMIN: usize = 5; - /// 换行符 - pub const VEOL: usize = 6; /// 指定非规范模式下的超时时间 - pub const VTIME: usize = 7; - /// 换行符 - pub const VEOL2: usize = 8; + pub const VTIME: usize = 5; + /// 指定非规范模式下的最小字符数 + pub const VMIN: usize = 6; /// 未使用,保留 - pub const VSWTC: usize = 9; - /// 擦除前一个单词 - pub const VWERASE: usize = 10; - /// 重新打印整行 - pub const VREPRINT: usize = 11; - /// 挂起信号 - pub const VSUSP: usize = 12; + pub const VSWTC: usize = 7; /// 启动输出信号 - pub const VSTART: usize = 13; + pub const VSTART: usize = 8; /// 停止输出信号 - pub const VSTOP: usize = 14; + pub const VSTOP: usize = 9; + /// 挂起信号 + pub const VSUSP: usize = 10; + /// 换行符 + pub const VEOL: usize = 11; + /// 重新打印整行 + pub const VREPRINT: usize = 12; + /// 对应于字符丢弃信号,用于丢弃当前输入的行 + pub const VDISCARD: usize = 13; + /// 擦除前一个单词 + pub const VWERASE: usize = 14; /// 将下一个字符视为字面值,而不是特殊字符 pub const VLNEXT: usize = 15; - /// 对应于字符丢弃信号,用于丢弃当前输入的行 - pub const VDISCARD: usize = 16; + /// 换行符 + pub const VEOL2: usize = 16; } diff --git a/kernel/src/driver/tty/tty_core.rs b/kernel/src/driver/tty/tty_core.rs index ae70937145..244be3ef97 100644 --- a/kernel/src/driver/tty/tty_core.rs +++ b/kernel/src/driver/tty/tty_core.rs @@ -1,5 +1,5 @@ use core::{ - fmt::Debug, + fmt::{self, Debug}, sync::atomic::{AtomicBool, AtomicUsize, Ordering}, }; @@ -16,7 +16,7 @@ use crate::{ libs::{ rwlock::{RwLock, RwLockReadGuard, RwLockUpgradableGuard, RwLockWriteGuard}, spinlock::{SpinLock, SpinLockGuard}, - wait_queue::EventWaitQueue, + wait_queue::{EventWaitQueue, WaitQueue}, }, mm::VirtAddr, process::{pid::Pid, ProcessControlBlock}, @@ -42,6 +42,81 @@ pub struct TtyCore { line_discipline: Arc, } +pub struct TtySleepLock { + locked: AtomicBool, + wait_queue: WaitQueue, +} + +impl Debug for TtySleepLock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TtySleepLock") + .field("locked", &self.locked.load(Ordering::Relaxed)) + .finish() + } +} + +pub struct TtySleepLockGuard<'a> { + lock: &'a TtySleepLock, +} + +impl !Send for TtySleepLockGuard<'_> {} + +impl TtySleepLock { + pub const fn new() -> Self { + Self { + locked: AtomicBool::new(false), + wait_queue: WaitQueue::default(), + } + } + + pub fn lock(&self) -> TtySleepLockGuard<'_> { + if let Some(guard) = self.try_lock() { + return guard; + } + + self.wait_queue.wait_until(|| self.try_lock()) + } + + pub fn lock_interruptible(&self, nonblock: bool) -> Result, SystemError> { + if let Some(guard) = self.try_lock() { + return Ok(guard); + } + + if nonblock { + return Err(SystemError::EAGAIN_OR_EWOULDBLOCK); + } + + self.wait_queue.wait_until_interruptible(|| self.try_lock()) + } + + pub fn is_locked(&self) -> bool { + self.locked.load(Ordering::Acquire) + } + + pub fn try_lock(&self) -> Option> { + if self + .locked + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + Some(TtySleepLockGuard { lock: self }) + } else { + None + } + } + + fn unlock(&self) { + self.locked.store(false, Ordering::Release); + self.wait_queue.wake_one(); + } +} + +impl Drop for TtySleepLockGuard<'_> { + fn drop(&mut self) { + self.lock.unlock(); + } +} + impl TtyCore { #[inline(never)] pub fn new(driver: Arc, index: usize) -> Arc { @@ -59,6 +134,7 @@ impl TtyCore { window_size: RwLock::new(WindowSize::default()), read_wq: EventWaitQueue::new(), write_wq: EventWaitQueue::new(), + write_lock: TtySleepLock::new(), port: RwLock::new(None), index, vc_index: AtomicUsize::new(usize::MAX), @@ -74,6 +150,7 @@ impl TtyCore { core, line_discipline: Arc::new(NTtyLinediscipline { data: SpinLock::new(NTtyData::new()), + output_lock: TtySleepLock::new(), }), }); } @@ -195,6 +272,15 @@ impl TtyCore { TtySetTermiosOpt::TERMIOS_WAIT | TtySetTermiosOpt::TERMIOS_OLD, ); } + TtyIoctlCmd::TCSETSF => { + return TtyCore::core_set_termios( + real_tty, + VirtAddr::new(arg), + TtySetTermiosOpt::TERMIOS_FLUSH + | TtySetTermiosOpt::TERMIOS_WAIT + | TtySetTermiosOpt::TERMIOS_OLD, + ); + } _ => { return Err(SystemError::ENOIOCTLCMD); } @@ -210,16 +296,20 @@ impl TtyCore { } TtyFlushArg::TCIOFLUSH => { tty.ldisc().flush_buffer(tty.clone())?; + tty.ldisc().flush_output(tty.clone())?; let ret = tty.core().driver().driver_funcs().flush_buffer(tty.core()); if ret != Err(SystemError::ENOSYS) { ret?; } + tty.core().write_wq().wakeup_all(); } TtyFlushArg::TCOFLUSH => { + tty.ldisc().flush_output(tty.clone())?; let ret = tty.core().driver().driver_funcs().flush_buffer(tty.core()); if ret != Err(SystemError::ENOSYS) { ret?; } + tty.core().write_wq().wakeup_all(); } _ => { return Err(SystemError::EINVAL); @@ -356,6 +446,8 @@ pub struct TtyCoreData { read_wq: EventWaitQueue, /// 写等待队列 write_wq: EventWaitQueue, + /// 串行化整个 tty write 调用,等价于 Linux tty->atomic_write_lock。 + write_lock: TtySleepLock, /// 端口 port: RwLock>>, /// 前台进程 @@ -466,6 +558,10 @@ impl TtyCoreData { &self.write_wq } + pub fn write_lock(&self) -> &TtySleepLock { + &self.write_lock + } + #[inline] pub fn contorl_info_irqsave(&self) -> SpinLockGuard<'_, TtyControlInfo> { self.ctrl.lock_irqsave() @@ -660,8 +756,8 @@ impl TtyOperation for TtyCore { } #[inline] - fn chars_in_buffer(&self) -> usize { - return self.core().tty_driver.driver_funcs().chars_in_buffer(); + fn chars_in_buffer(&self, tty: &TtyCoreData) -> usize { + return tty.tty_driver.driver_funcs().chars_in_buffer(tty); } #[inline] @@ -869,6 +965,8 @@ impl TtyIoctlCmd { pub const TIOCGPKT: u32 = 0x80045438; /// 获取pts index pub const TIOCGPTN: u32 = 0x80045430; + /// Open the slave side of a Unix98 PTY from the master fd. + pub const TIOCGPTPEER: u32 = 0x5441; } pub struct TtyFlushArg; diff --git a/kernel/src/driver/tty/tty_device.rs b/kernel/src/driver/tty/tty_device.rs index a286df6be8..2642acb9dc 100644 --- a/kernel/src/driver/tty/tty_device.rs +++ b/kernel/src/driver/tty/tty_device.rs @@ -28,11 +28,12 @@ use crate::{ filesystem::{ devfs::{devfs_register, DevFS, DeviceINode, LockedDevFSInode}, devpts::{DevPtsFs, LockedDevPtsFSInode}, - epoll::EPollItem, + epoll::{EPollEventType, EPollItem}, kernfs::KernFSInode, vfs::{ - file::FileFlags, utils::DName, FilePrivateData, FileType, IndexNode, InodeMode, - Metadata, PollableInode, + file::{File, FileFlags}, + utils::DName, + FilePrivateData, FileType, IndexNode, InodeMode, Metadata, PollableInode, }, }, init::initcall::INITCALL_DEVICE, @@ -44,7 +45,7 @@ use crate::{ use super::{ kthread::tty_flush_thread_init, - pty::unix98pty::ptmx_open, + pty::unix98pty::{ptm_peer_inode, ptmx_open, pty_file_close}, sysfs::sys_class_tty_instance, termios::WindowSize, tty_core::{TtyCore, TtyFlag, TtyIoctlCmd}, @@ -362,6 +363,9 @@ impl IndexNode for TtyDevice { drop(data); let ld = tty.ldisc(); let core = tty.core(); + let write_guard = core + .write_lock() + .lock_interruptible(flags.contains(FileFlags::O_NONBLOCK))?; let mut chunk = 2048; if core.flags().contains(TtyFlag::NO_WRITE_SPLIT) { chunk = 65536; @@ -376,7 +380,22 @@ impl IndexNode for TtyDevice { // 将数据从buf拷贝到writebuf - let ret = ld.write(tty.clone(), &buf[written..], size, flags)?; + let ret = match ld.write(tty.clone(), &buf[written..], size, flags) { + Ok(ret) => ret, + Err(err) => { + if written != 0 { + break; + } + drop(write_guard); + core.write_wq() + .wakeup_any(EPollEventType::EPOLLOUT.bits() as u64); + return Err(err); + } + }; + + if ret == 0 { + break; + } written += ret; count -= ret; @@ -386,6 +405,12 @@ impl IndexNode for TtyDevice { } if pcb.has_pending_signal_fast() { + if written != 0 { + break; + } + drop(write_guard); + core.write_wq() + .wakeup_any(EPollEventType::EPOLLOUT.bits() as u64); return Err(SystemError::ERESTARTSYS); } } @@ -394,6 +419,9 @@ impl IndexNode for TtyDevice { // todo: 更新时间 } + drop(write_guard); + core.write_wq() + .wakeup_any(EPollEventType::EPOLLOUT.bits() as u64); Ok(written) } @@ -443,7 +471,9 @@ impl IndexNode for TtyDevice { }; drop(data); - tty.close(tty.clone()) + let ret = tty.close(tty.clone()); + pty_file_close(&tty); + ret } fn resize(&self, _len: usize) -> Result<(), SystemError> { @@ -480,6 +510,27 @@ impl IndexNode for TtyDevice { } match cmd { + TtyIoctlCmd::TIOCGPTPEER => { + let flags = FileFlags::from_bits(arg as u32).ok_or(SystemError::EINVAL)?; + let cloexec = flags.contains(FileFlags::O_CLOEXEC); + let slave_inode = ptm_peer_inode(tty)?; + let fd_table = ProcessManager::current_pcb().fd_table(); + let reserved_fd = fd_table.write().reserve_fd(cloexec)?; + let file = match File::new(slave_inode, flags) { + Ok(file) => file, + Err(err) => { + fd_table.write().release_reserved_fd(reserved_fd); + return Err(err); + } + }; + return match fd_table.write().install_reserved_fd(reserved_fd, file) { + Ok(fd) => Ok(fd as usize), + Err(err) => { + fd_table.write().release_reserved_fd(reserved_fd); + Err(err) + } + }; + } TtyIoctlCmd::TIOCGWINSZ => { let core = tty.core(); let winsize = *core.window_size(); diff --git a/kernel/src/driver/tty/tty_driver.rs b/kernel/src/driver/tty/tty_driver.rs index 5fda58d300..cf10558654 100644 --- a/kernel/src/driver/tty/tty_driver.rs +++ b/kernel/src/driver/tty/tty_driver.rs @@ -520,7 +520,7 @@ pub trait TtyOperation: Sync + Send + Debug { fn ioctl(&self, tty: Arc, cmd: u32, arg: usize) -> Result<(), SystemError>; - fn chars_in_buffer(&self) -> usize { + fn chars_in_buffer(&self, _tty: &TtyCoreData) -> usize { 0 } diff --git a/kernel/src/driver/tty/tty_job_control.rs b/kernel/src/driver/tty/tty_job_control.rs index 6e2aefe9ef..54db83821c 100644 --- a/kernel/src/driver/tty/tty_job_control.rs +++ b/kernel/src/driver/tty/tty_job_control.rs @@ -244,6 +244,9 @@ impl TtyJobCtrlManager { )?; let pgrp_nr = *user_reader.read_one_from_user::(0)?; + if pgrp_nr < 0 { + return Err(SystemError::EINVAL); + } let current = ProcessManager::current_pcb(); @@ -312,21 +315,44 @@ impl TtyJobCtrlManager { /// Detach controlling tty from current process if it matches `real_tty`. fn tiocnotty(real_tty: Arc) -> Result { let pcb = ProcessManager::current_pcb(); - let mut siginfo = pcb.sig_info_mut(); - if let Some(cur) = siginfo.tty() { - if Arc::ptr_eq(&cur, &real_tty) { - Self::__proc_clear_tty(&mut siginfo); - drop(siginfo); - let mut ctrl = real_tty.core().contorl_info_irqsave(); - ctrl.session = None; - ctrl.pgid = None; - return Ok(0); - } + let (current_tty, is_session_leader) = { + let siginfo = pcb.sig_info_irqsave(); + (siginfo.tty(), siginfo.is_session_leader) + }; + + if current_tty.is_none() || !Arc::ptr_eq(¤t_tty.unwrap(), &real_tty) { + return Err(SystemError::ENOTTY); } - Err(SystemError::ENOTTY) + + if !is_session_leader { + Self::proc_clear_tty(&pcb); + return Ok(0); + } + + let sid = pcb.task_session(); + let tty_pgrp = real_tty.core().contorl_info_irqsave().pgid.clone(); + + if let Some(pgrp) = tty_pgrp { + let _ = crate::ipc::kill::send_signal_to_pgid(&pgrp, Signal::SIGHUP); + let _ = crate::ipc::kill::send_signal_to_pgid(&pgrp, Signal::SIGCONT); + } + + { + let mut ctrl = real_tty.core().contorl_info_irqsave(); + ctrl.session = None; + ctrl.pgid = None; + } + + if let Some(sid) = sid { + Self::session_clear_tty(sid); + } else { + Self::proc_clear_tty(&pcb); + } + + Ok(0) } - pub(super) fn session_clear_tty(sid: Arc) { + pub fn session_clear_tty(sid: Arc) { // 清除会话的tty for task in sid.tasks_iter(PidType::SID) { TtyJobCtrlManager::proc_clear_tty(&task); diff --git a/kernel/src/driver/tty/tty_ldisc/mod.rs b/kernel/src/driver/tty/tty_ldisc/mod.rs index 14ebe6c866..bce6701662 100644 --- a/kernel/src/driver/tty/tty_ldisc/mod.rs +++ b/kernel/src/driver/tty/tty_ldisc/mod.rs @@ -16,6 +16,9 @@ pub trait TtyLineDiscipline: Sync + Send + Debug { fn open(&self, tty: Arc) -> Result<(), SystemError>; fn close(&self, tty: Arc) -> Result<(), SystemError>; fn flush_buffer(&self, tty: Arc) -> Result<(), SystemError>; + fn flush_output(&self, _tty: Arc) -> Result<(), SystemError> { + Ok(()) + } /// ## tty行规程循环读取函数 /// diff --git a/kernel/src/driver/tty/tty_ldisc/ntty.rs b/kernel/src/driver/tty/tty_ldisc/ntty.rs index a1483f1019..675838e150 100644 --- a/kernel/src/driver/tty/tty_ldisc/ntty.rs +++ b/kernel/src/driver/tty/tty_ldisc/ntty.rs @@ -1,4 +1,4 @@ -use alloc::boxed::Box; +use alloc::{boxed::Box, vec::Vec}; use core::intrinsics::likely; use core::ops::BitXor; @@ -10,8 +10,14 @@ use system_error::SystemError; use crate::{ arch::ipc::signal::Signal, driver::tty::{ + pty::unix98pty::{ + pty_discard_pending_to, pty_drain_pending_to, pty_request_discard_pending_to, + }, termios::{ControlCharIndex, InputMode, LocalMode, OutputMode, Termios}, - tty_core::{EchoOperation, TtyCore, TtyCoreData, TtyFlag, TtyIoctlCmd, TtyPacketStatus}, + tty_core::{ + EchoOperation, TtyCore, TtyCoreData, TtyFlag, TtyIoctlCmd, TtyPacketStatus, + TtySleepLock, + }, tty_driver::{TtyDriverFlag, TtyDriverSubType, TtyOperation}, tty_job_control::TtyJobCtrlManager, }, @@ -35,9 +41,26 @@ fn ntty_buf_mask(idx: usize) -> usize { return idx & (NTTY_BUFSIZE - 1); } +fn is_ascii_control(c: u8) -> bool { + c < b' ' || c == 0x7f +} + +fn output_mode_has_xtabs(termios: &Termios) -> bool { + OutputMode::from_bits_truncate(termios.output_mode.bits() & OutputMode::TABDLY.bits()) + == OutputMode::XTABS +} + #[derive(Debug)] pub struct NTtyLinediscipline { pub data: SpinLock, + pub(crate) output_lock: TtySleepLock, +} + +struct EchoStep { + bytes: Vec, + tail: usize, + cursor_column: u32, + canon_cursor_column: u32, } impl NTtyLinediscipline { @@ -62,6 +85,46 @@ impl NTtyLinediscipline { } } } + + fn drain_echoes(&self, tty: Arc) -> Result<(), SystemError> { + let core = tty.core(); + loop { + let termios = *core.termios(); + let space = tty.write_room(core); + let step = { + let guard = self.disc_data(); + guard.next_echo_step(&termios, space) + }; + + let Some(step) = step else { + break; + }; + + if !step.bytes.is_empty() { + let mut sent = 0; + while sent < step.bytes.len() { + let written = tty.write(core, &step.bytes[sent..], step.bytes.len() - sent)?; + if written == 0 { + if sent == 0 { + return Ok(()); + } + let _ = core.write_wq().wait_event_interruptible( + EPollEventType::EPOLLOUT.bits() as u64, + || tty.write_room(core) > 0, + ); + continue; + } + sent += written; + } + tty.flush_chars(core); + } + + let mut guard = self.disc_data(); + guard.apply_echo_step(step); + } + + Ok(()) + } } #[derive(Debug)] @@ -110,6 +173,9 @@ pub struct NTtyData { cursor_column: u32, /// 规范模式下光标所在列 canon_cursor_column: u32, + /// OPOST 处理后尚未被底层 driver 接收的输出字节。 + opost_pending: Vec, + opost_pending_offset: usize, /// 回显缓冲区的尾指针 echo_tail: usize, @@ -145,6 +211,8 @@ impl NTtyData { echo: false, cursor_column: 0, canon_cursor_column: 0, + opost_pending: Vec::with_capacity(NTTY_BUFSIZE), + opost_pending_offset: 0, echo_tail: 0, read_buf: vec![0; NTTY_BUFSIZE].into_boxed_slice().try_into().unwrap(), echo_buf: vec![0; NTTY_BUFSIZE].into_boxed_slice().try_into().unwrap(), @@ -155,6 +223,54 @@ impl NTtyData { } } + fn opost_pending_bytes(&self) -> &[u8] { + if self.opost_pending_offset >= self.opost_pending.len() { + &[] + } else { + &self.opost_pending[self.opost_pending_offset..] + } + } + + fn advance_opost_pending(&mut self, count: usize) { + self.opost_pending_offset += count; + if self.opost_pending_offset >= self.opost_pending.len() { + self.opost_pending.clear(); + self.opost_pending_offset = 0; + } + } + + fn opost_char_space(&self, termios: &Termios, c: u8) -> usize { + if c as usize == 8 { + return 1; + } + + match c as char { + '\n' if termios.output_mode.contains(OutputMode::ONLCR) => 2, + '\r' if termios.output_mode.contains(OutputMode::ONOCR) && self.cursor_column == 0 => 0, + '\t' if output_mode_has_xtabs(termios) => (8 - (self.cursor_column & 7)) as usize, + _ => 1, + } + } + + fn opost_progress_possible( + &self, + termios: &Termios, + next_input: Option, + write_room: usize, + ) -> bool { + if !self.opost_pending_bytes().is_empty() { + return write_room > 0; + } + + let Some(c) = next_input else { + return false; + }; + if write_room == 0 { + return false; + } + self.opost_char_space(termios, c) <= write_room + } + #[inline] pub fn read_cnt(&self) -> usize { self.read_head - self.read_tail @@ -177,42 +293,37 @@ impl NTtyData { ) -> Result { // 获取termios读锁 let termios = tty.core().termios(); - let mut overflow; let mut n; let mut offset = 0; let mut recved = 0; loop { let tail = self.read_tail; - let mut room = NTTY_BUFSIZE - (self.read_head - tail); + let mut room = NTTY_BUFSIZE as isize - (self.read_head - tail) as isize; if termios.input_mode.contains(InputMode::PARMRK) { - room = room.div_ceil(3); + room = if room > 0 { (room + 2) / 3 } else { room }; } room -= 1; - if room == 0 || room > NTTY_BUFSIZE { + if room <= 0 { // 可能溢出 - overflow = self.icanon && self.canon_head == tail; - if room > NTTY_BUFSIZE && overflow { + let overflow = self.icanon && self.canon_head == tail; + if overflow && room < 0 { self.read_head -= 1; } self.no_room = flow && !overflow; - room = if overflow { !0 } else { 0 } - } else { - overflow = false; + room = if overflow { 1 } else { 0 }; } - n = count.min(room); + n = count.min(room as usize); if n == 0 { break; } - if !overflow { - if let Some(flags) = flags { - self.receive_buf(tty.clone(), &buf[offset..], Some(&flags[offset..]), n); - } else { - self.receive_buf(tty.clone(), &buf[offset..], flags, n); - } + if let Some(flags) = flags { + self.receive_buf(tty.clone(), &buf[offset..], Some(&flags[offset..]), n); + } else { + self.receive_buf(tty.clone(), &buf[offset..], flags, n); } offset += n; @@ -330,8 +441,6 @@ impl NTtyData { } self.echo_commit = self.echo_head; - drop(termios); - let _ = self.echoes(tty); } pub fn receive_buf_standard( @@ -609,6 +718,7 @@ impl NTtyData { } } + let mut seen_alnums = false; let mut head; let mut cnt; while ntty_buf_mask(self.read_head) != ntty_buf_mask(self.canon_head) { @@ -631,7 +741,11 @@ impl NTtyData { } if werase { - todo!() + if c.is_ascii_alphanumeric() || c == b'_' { + seen_alnums = true; + } else if seen_alnums { + break; + } } cnt = self.read_head - head; @@ -667,7 +781,7 @@ impl NTtyData { if c == b'\t' { after_tab = true; break; - } else if (c as char).is_control() { + } else if is_ascii_control(c) { if termios.local_mode.contains(LocalMode::ECHOCTL) { num_chars += 2; } @@ -678,15 +792,14 @@ impl NTtyData { self.echo_erase_tab(num_chars, after_tab); } else { - if (c as char).is_control() && termios.local_mode.contains(LocalMode::ECHOCTL) { + if is_ascii_control(c) && termios.local_mode.contains(LocalMode::ECHOCTL) { // 8 => '\b' self.echo_char_raw(8); self.echo_char_raw(b' '); self.echo_char_raw(8); } - if !(c as char).is_control() || termios.local_mode.contains(LocalMode::ECHOCTL) - { + if !is_ascii_control(c) || termios.local_mode.contains(LocalMode::ECHOCTL) { // 8 => '\b' self.echo_char_raw(8); self.echo_char_raw(b' '); @@ -810,6 +923,8 @@ impl NTtyData { self.pushing = false; self.lookahead_count = 0; + let _ = pty_request_discard_pending_to(tty.clone()); + if tty.core().link().is_some() { self.packet_mode_flush(tty.core()); } @@ -845,9 +960,7 @@ impl NTtyData { self.add_echo_byte(EchoOperation::Start.to_u8()); self.add_echo_byte(EchoOperation::Start.to_u8()); } else { - if termios.local_mode.contains(LocalMode::ECHOCTL) - && (c as char).is_control() - && c != b'\t' + if termios.local_mode.contains(LocalMode::ECHOCTL) && is_ascii_control(c) && c != b'\t' { self.add_echo_byte(EchoOperation::Start.to_u8()); } @@ -865,7 +978,7 @@ impl NTtyData { } /// ## 提交echobuf里的数据显示 - pub fn commit_echoes(&mut self, tty: Arc) { + pub fn commit_echoes(&mut self, _tty: Arc) { let head = self.echo_head; self.echo_mark = head; let old = self.echo_commit - self.echo_tail; @@ -878,11 +991,6 @@ impl NTtyData { } self.echo_commit = head; - let echoed = self.echoes(tty.clone()); - - if echoed.is_ok() && echoed.unwrap() > 0 { - tty.flush_chars(tty.core()); - } } pub fn add_echo_byte(&mut self, c: u8) { @@ -1165,326 +1273,258 @@ impl NTtyData { Ok(false) } - /// ## 用于处理带有 OPOST(Output Post-processing)标志的输出块的函数 - /// OPOST 是 POSIX 终端驱动器标志之一,用于指定在写入终端设备之前对输出数据进行一些后期处理。 - pub fn process_output_block( - &mut self, - core: &TtyCoreData, - termios: RwLockReadGuard, - buf: &[u8], - nr: usize, - ) -> Result { - let mut nr = nr; - let tty = self.tty.upgrade().unwrap(); - let space = tty.write_room(tty.core()); + /// ## 处理回显 + pub fn process_echoes(&mut self, _tty: Arc) { + if self.echo_mark != self.echo_tail { + self.echo_commit = self.echo_mark; + } + } - // 如果读取数量大于了可用空间,则取最小的为真正的写入数量 - if nr > space { - nr = space + fn next_echo_step(&self, termios: &Termios, space: usize) -> Option { + let mut tail = self.echo_tail; + if ntty_buf_mask(self.echo_commit) == ntty_buf_mask(tail) { + return None; } - let mut cnt = 0; - for (i, c) in buf.iter().enumerate().take(nr) { - cnt = i; - let c = *c; - if c as usize == 8 { - // 表示退格 - if self.cursor_column > 0 { - self.cursor_column -= 1; - } - continue; + let mut cursor_column = self.cursor_column; + let mut canon_cursor_column = self.canon_cursor_column; + let mut bytes = Vec::with_capacity(8); + let c = self.echo_buf[ntty_buf_mask(tail)]; + + if EchoOperation::from_u8(c) == EchoOperation::Start { + if ntty_buf_mask(self.echo_commit) == ntty_buf_mask(tail + 1) { + return None; } - match c as char { - '\n' => { - if termios.output_mode.contains(OutputMode::ONLRET) { - // 将回车映射为\n,即将\n换为回车 - self.cursor_column = 0; - } - if termios.output_mode.contains(OutputMode::ONLCR) { - // 输出时将\n换为\r\n - break; - } - self.canon_cursor_column = self.cursor_column; + match EchoOperation::from_u8(self.echo_buf[ntty_buf_mask(tail + 1)]) { + EchoOperation::Start => { + if space < 1 { + return None; + } + bytes.push(EchoOperation::Start.to_u8()); + cursor_column += 1; + tail += 2; } - '\r' => { - if termios.output_mode.contains(OutputMode::ONOCR) && self.cursor_column == 0 { - // 光标已经在第0列,则不输出回车符 - break; + EchoOperation::MoveBackCol => { + cursor_column = cursor_column.saturating_sub(1); + tail += 2; + } + EchoOperation::SetCanonCol => { + canon_cursor_column = cursor_column; + tail += 2; + } + EchoOperation::EraseTab => { + if ntty_buf_mask(self.echo_commit) == ntty_buf_mask(tail + 2) { + return None; } - - if termios.output_mode.contains(OutputMode::OCRNL) { - break; + let mut char_num = self.echo_buf[ntty_buf_mask(tail + 2)] as usize; + if char_num & 0x80 == 0 { + char_num += canon_cursor_column as usize; } - self.cursor_column = 0; - self.canon_cursor_column = 0; - } - '\t' => { - break; + let num_bs = 8 - (char_num & 7); + if num_bs > space { + return None; + } + bytes.resize(num_bs, 8); + cursor_column = cursor_column.saturating_sub(num_bs as u32); + tail += 3; } - _ => { - // 判断是否为控制字符 - if !(c as char).is_control() { - if termios.output_mode.contains(OutputMode::OLCUC) { - break; + EchoOperation::Undefined(ch) => match ch { + 8 => { + if space < 2 { + return None; } - - // 判断是否为utf8模式下的连续字符 - if !(termios.input_mode.contains(InputMode::IUTF8) - && (c as usize) & 0xc0 == 0x80) - { - self.cursor_column += 1; + bytes.extend_from_slice(&[8, b' ']); + cursor_column = cursor_column.saturating_sub(1); + tail += 1; + } + _ => { + if space < 2 { + return None; } + bytes.extend_from_slice(&[b'^', ch ^ 0o100]); + cursor_column += 2; + tail += 2; } - } + }, + } + } else if termios.output_mode.contains(OutputMode::OPOST) { + if Self::format_output_char( + termios, + c, + &mut bytes, + space, + &mut cursor_column, + &mut canon_cursor_column, + ) + .is_err() + { + return None; } + tail += 1; + } else { + if space < 1 { + return None; + } + bytes.push(c); + tail += 1; } - drop(termios); - return tty.write(core, buf, cnt); + Some(EchoStep { + bytes, + tail: self.echo_discard_tail(tail), + cursor_column, + canon_cursor_column, + }) } - /// ## 处理回显 - pub fn process_echoes(&mut self, tty: Arc) { - if self.echo_mark == self.echo_tail { - return; + fn echo_discard_tail(&self, mut tail: usize) -> usize { + while self.echo_commit > tail && self.echo_commit - tail >= ECHO_DISCARD_WATERMARK { + if self.echo_buf[ntty_buf_mask(tail)] == EchoOperation::Start.to_u8() { + if self.echo_buf[ntty_buf_mask(tail + 1)] == EchoOperation::EraseTab.to_u8() { + tail += 3; + } else { + tail += 2; + } + } else { + tail += 1; + } } - self.echo_commit = self.echo_mark; - let echoed = self.echoes(tty.clone()); + tail + } - if echoed.is_ok() && echoed.unwrap() > 0 { - tty.flush_chars(tty.core()); - } + fn apply_echo_step(&mut self, step: EchoStep) { + self.echo_tail = step.tail; + self.cursor_column = step.cursor_column; + self.canon_cursor_column = step.canon_cursor_column; } - #[inline(never)] - pub fn echoes(&mut self, tty: Arc) -> Result { - let mut space = tty.write_room(tty.core()); - let ospace = space; - let termios = tty.core().termios(); - let core = tty.core(); - let mut tail = self.echo_tail; + fn format_output_char( + termios: &Termios, + mut c: u8, + out: &mut Vec, + space: usize, + cursor_column: &mut u32, + canon_cursor_column: &mut u32, + ) -> Result { + let used = out.len(); + if used >= space { + return Err(SystemError::ENOBUFS); + } - while ntty_buf_mask(self.echo_commit) != ntty_buf_mask(tail) { - let c = self.echo_buf[ntty_buf_mask(tail)]; + if c as usize == 8 { + *cursor_column = cursor_column.saturating_sub(1); + out.push(c); + return Ok(1); + } - if EchoOperation::from_u8(c) == EchoOperation::Start { - if ntty_buf_mask(self.echo_commit) == ntty_buf_mask(tail + 1) { - self.echo_tail = tail; - return Ok(ospace - space); + match c as char { + '\n' => { + if termios.output_mode.contains(OutputMode::ONLRET) { + *cursor_column = 0; } - - // 获取到start,之后取第一个作为op - let op = EchoOperation::from_u8(self.echo_buf[ntty_buf_mask(tail + 1)]); - - match op { - EchoOperation::Start => { - if space == 0 { - break; - } - - if tty - .put_char(tty.core(), EchoOperation::Start.to_u8()) - .is_err() - { - tty.write(core, &[EchoOperation::Start.to_u8()], 1)?; - } - - self.cursor_column += 1; - space -= 1; - tail += 2; - } - EchoOperation::MoveBackCol => { - if self.cursor_column > 0 { - self.cursor_column -= 1; - } - tail += 2; - } - EchoOperation::SetCanonCol => { - self.canon_cursor_column = self.cursor_column; - tail += 2; - } - EchoOperation::EraseTab => { - if ntty_buf_mask(self.echo_commit) == ntty_buf_mask(tail + 2) { - self.echo_tail = tail; - return Ok(ospace - space); - } - - // 要擦除的制表符所占用的列数 - let mut char_num = self.echo_buf[ntty_buf_mask(tail + 2)] as usize; - - /* - 如果 num_chars 的最高位(0x80)未设置, - 表示这是从输入的起始位置而不是从先前的制表符开始计算的列数。 - 在这种情况下,将 num_chars 与 ldata->canon_column 相加,否则,列数就是正常的制表符列数。 - */ - if char_num & 0x80 == 0 { - char_num += self.canon_cursor_column as usize; - } - - // 计算要回退的列数,即制表符宽度减去实际占用的列数 - let mut num_bs = 8 - (char_num & 7); - if num_bs > space { - // 表示左边没有足够空间回退 - break; - } - - space -= num_bs; - while num_bs != 0 { - num_bs -= 1; - // 8 => '\b' - if tty.put_char(tty.core(), 8).is_err() { - tty.write(core, &[8], 1)?; - } - - if self.cursor_column > 0 { - self.cursor_column -= 1; - } - } - - // 已经读取了 tail tail+1 tail+2,所以这里偏移加3 - tail += 3; - } - EchoOperation::Undefined(ch) => { - match ch { - 8 => { - if tty.put_char(tty.core(), 8).is_err() { - tty.write(core, &[8], 1)?; - } - if tty.put_char(tty.core(), b' ').is_err() { - tty.write(core, b" ", 1)?; - } - self.cursor_column -= 1; - space -= 1; - tail += 1; - } - _ => { - // 不是特殊字节码,则表示控制字符 例如 ^C - if space < 2 { - break; - } - - if tty.put_char(tty.core(), b'^').is_err() { - tty.write(core, b"^", 1)?; - } - - if tty.put_char(tty.core(), ch ^ 0o100).is_err() { - tty.write(core, &[ch ^ 0o100], 1)?; - } - - self.cursor_column += 2; - space -= 2; - tail += 2; - } - } + if termios.output_mode.contains(OutputMode::ONLCR) { + if used + 2 > space { + return Err(SystemError::ENOBUFS); } + *cursor_column = 0; + *canon_cursor_column = 0; + out.extend_from_slice(b"\r\n"); + return Ok(2); + } + *canon_cursor_column = *cursor_column; + } + '\r' => { + if termios.output_mode.contains(OutputMode::ONOCR) && *cursor_column == 0 { + return Ok(0); } - } else { - if termios.output_mode.contains(OutputMode::OPOST) { - let ret = self.do_output_char(tty.clone(), c, space); - if ret.is_err() { - break; + if termios.output_mode.contains(OutputMode::OCRNL) { + c = b'\n'; + if termios.output_mode.contains(OutputMode::ONLRET) { + *cursor_column = 0; + *canon_cursor_column = 0; } - space -= ret.unwrap(); } else { - if space == 0 { - break; - } - - if tty.put_char(tty.core(), c).is_err() { - tty.write(core, &[c], 1)?; + *cursor_column = 0; + *canon_cursor_column = 0; + } + } + '\t' => { + let spaces = 8 - (*cursor_column & 7) as usize; + if output_mode_has_xtabs(termios) { + if used + spaces > space { + return Err(SystemError::ENOBUFS); } - space -= 1; + *cursor_column += spaces as u32; + out.extend_from_slice(&b" "[..spaces]); + return Ok(spaces); } - tail += 1; + *cursor_column += spaces as u32; } - } + _ => { + if !is_ascii_control(c) { + if termios.output_mode.contains(OutputMode::OLCUC) { + c = c.to_ascii_uppercase(); + } - // 如果回显缓冲区接近满(在下一次提交之前可能会发生回显溢出的情况),则丢弃足够的尾部数据以防止随后的溢出。 - while self.echo_commit > tail && self.echo_commit - tail >= ECHO_DISCARD_WATERMARK { - if self.echo_buf[ntty_buf_mask(tail)] == EchoOperation::Start.to_u8() { - if self.echo_buf[ntty_buf_mask(tail + 1)] == EchoOperation::EraseTab.to_u8() { - tail += 3; - } else { - tail += 2; + if !(termios.input_mode.contains(InputMode::IUTF8) + && (c as usize) & 0xc0 == 0x80) + { + *cursor_column += 1; + } } - } else { - tail += 1; } } - self.echo_tail = tail; - return Ok(ospace - space); - } - - /// ## 处理输出字符(带有 OPOST 处理) - pub fn process_output(&mut self, tty: Arc, c: u8) -> bool { - let space = tty.write_room(tty.core()); - - if self.do_output_char(tty, c, space).is_err() { - return false; - } - - true + out.push(c); + Ok(1) } - // ## 设置带有 OPOST 处理的tty输出一个字符 - pub fn do_output_char( + fn process_output_char_to_buf( &mut self, - tty: Arc, - c: u8, + termios: &Termios, + mut c: u8, + out: &mut Vec, space: usize, ) -> Result { - if space == 0 { + let used = out.len(); + if used >= space { return Err(SystemError::ENOBUFS); } - let termios = tty.core().termios(); - let core = tty.core(); - let mut c = c; if c as usize == 8 { - // 表示退格 if self.cursor_column > 0 { self.cursor_column -= 1; } - if tty.put_char(tty.core(), c).is_err() { - tty.write(core, &[c], 1)?; - } + out.push(c); return Ok(1); } + match c as char { '\n' => { if termios.output_mode.contains(OutputMode::ONLRET) { - // 回车符 self.cursor_column = 0; } if termios.output_mode.contains(OutputMode::ONLCR) { - // 映射为“\r\n” - if space < 2 { + if used + 2 > space { return Err(SystemError::ENOBUFS); } self.cursor_column = 0; self.canon_cursor_column = 0; - - // 通过驱动写入 - tty.write(core, "\r\n".as_bytes(), 2)?; + out.extend_from_slice(b"\r\n"); return Ok(2); } - self.canon_cursor_column = self.cursor_column; } '\r' => { if termios.output_mode.contains(OutputMode::ONOCR) && self.cursor_column == 0 { - // 光标已经在第0列,则不输出回车符 return Ok(0); } if termios.output_mode.contains(OutputMode::OCRNL) { - // 输出的\r映射为\n c = b'\n'; if termios.output_mode.contains(OutputMode::ONLRET) { - // \r映射为\n,但是保留\r特性 self.cursor_column = 0; self.canon_cursor_column = 0; } @@ -1494,31 +1534,23 @@ impl NTtyData { } } '\t' => { - // 计算输出一个\t需要的空间 let spaces = 8 - (self.cursor_column & 7) as usize; - if termios.output_mode.contains(OutputMode::TABDLY) - && OutputMode::TABDLY.bits() == OutputMode::XTABS.bits() - { - // 配置的tab选项是真正输出空格到驱动 - if space < spaces { - // 空间不够 + if output_mode_has_xtabs(termios) { + if used + spaces > space { return Err(SystemError::ENOBUFS); } self.cursor_column += spaces as u32; - // 写入sapces个空格 - tty.write(core, " ".as_bytes(), spaces)?; + out.extend_from_slice(&b" "[..spaces]); return Ok(spaces); } self.cursor_column += spaces as u32; } _ => { - // 判断是否为控制字符 - if !(c as char).is_control() { + if !is_ascii_control(c) { if termios.output_mode.contains(OutputMode::OLCUC) { c = c.to_ascii_uppercase(); } - // 判断是否为utf8模式下的连续字符 if !(termios.input_mode.contains(InputMode::IUTF8) && (c as usize) & 0xc0 == 0x80) { @@ -1528,12 +1560,42 @@ impl NTtyData { } } - if tty.put_char(tty.core(), c).is_err() { - tty.write(core, &[c], 1)?; - } + out.push(c); Ok(1) } + fn simple_output_block_len(&self, termios: &Termios, buf: &[u8], limit: usize) -> usize { + let mut len = 0; + let limit = limit.min(buf.len()); + while len < limit { + let c = buf[len]; + match c as char { + '\n' | '\r' | '\t' => break, + _ => { + if !is_ascii_control(c) && termios.output_mode.contains(OutputMode::OLCUC) { + break; + } + } + } + len += 1; + } + len + } + + fn apply_simple_output_columns(&mut self, termios: &Termios, buf: &[u8]) { + for &c in buf { + if c as usize == 8 { + if self.cursor_column > 0 { + self.cursor_column -= 1; + } + } else if !(is_ascii_control(c) + || termios.input_mode.contains(InputMode::IUTF8) && (c as usize) & 0xc0 == 0x80) + { + self.cursor_column += 1; + } + } + } + fn packet_mode_flush(&self, tty: &TtyCoreData) { let link = tty.link().unwrap(); if link.core().contorl_info_irqsave().packet { @@ -1554,8 +1616,9 @@ impl TtyLineDiscipline for NTtyLinediscipline { return self.set_termios(tty, None); } - fn close(&self, _tty: Arc) -> Result<(), system_error::SystemError> { - todo!() + fn close(&self, tty: Arc) -> Result<(), system_error::SystemError> { + self.flush_buffer(tty.clone())?; + self.flush_output(tty) } /// ## 重置缓冲区的基本信息 @@ -1577,6 +1640,9 @@ impl TtyLineDiscipline for NTtyLinediscipline { if core.link().is_some() { ldata.packet_mode_flush(core); } + drop(ldata); + + let _ = pty_discard_pending_to(tty.clone()); core.read_wq().wakeup_all(); core.write_wq().wakeup_all(); @@ -1588,6 +1654,14 @@ impl TtyLineDiscipline for NTtyLinediscipline { Ok(()) } + fn flush_output(&self, _tty: Arc) -> Result<(), system_error::SystemError> { + let mut ldata = self.disc_data(); + ldata.opost_pending.clear(); + ldata.opost_pending_offset = 0; + + Ok(()) + } + #[inline(never)] fn read( &self, @@ -1616,24 +1690,28 @@ impl TtyLineDiscipline for NTtyLinediscipline { // 表示接着读 if *cookie { + let tail = ldata.read_tail; + let is_canon = ldata.icanon && !termios.local_mode.contains(LocalMode::EXTPROC); // 规范且非拓展模式 - if ldata.icanon && !termios.local_mode.contains(LocalMode::EXTPROC) { + if is_canon { + drop(termios); // 跳过EOF字符 if len == 0 { ldata.canon_skip_eof(); - } else if ldata.canon_copy_from_read_buf(buf, &mut nr, &mut offset)? { - return Ok(len - nr); + } else { + let _ = ldata.canon_copy_from_read_buf(buf, &mut nr, &mut offset)?; } - } else if ldata.copy_from_read_buf(termios, buf, &mut nr, &mut offset)? { - return Ok(len - nr); + } else { + let _ = ldata.copy_from_read_buf(termios, buf, &mut nr, &mut offset)?; } - // 没有数据可读 - - // todo: kick worker? or 关闭节流? - *cookie = false; - return Ok(len - nr); + let read_tail_moved = tail != ldata.read_tail; + drop(ldata); + if read_tail_moved { + let _ = pty_drain_pending_to(tty.clone()); + } + return Ok(offset); } drop(termios); @@ -1722,8 +1800,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { let more = ldata.canon_copy_from_read_buf(buf, &mut nr, &mut offset)?; if more { *cookie = true; - offset += len - nr; - return Ok(offset); + break; } } else { // 非标准模式 @@ -1738,7 +1815,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { && offset >= minimum { *cookie = true; - return Ok(offset); + break; } } @@ -1747,8 +1824,10 @@ impl TtyLineDiscipline for NTtyLinediscipline { } } let ldata = self.disc_data(); - if tail != ldata.read_tail { - // todo: kick worker? + let read_tail_moved = tail != ldata.read_tail; + drop(ldata); + if read_tail_moved { + let _ = pty_drain_pending_to(tty.clone()); } if offset > 0 { @@ -1767,20 +1846,26 @@ impl TtyLineDiscipline for NTtyLinediscipline { _flags: FileFlags, ) -> Result { let mut nr = len; - let mut ldata = self.disc_data(); + let mut out_buf = Vec::with_capacity(NTTY_BUFSIZE); let pcb = ProcessManager::current_pcb(); let binding = tty.clone(); let core = binding.core(); - let termios = *core.termios(); + let mut termios = *core.termios(); if termios.local_mode.contains(LocalMode::TOSTOP) { TtyJobCtrlManager::tty_check_change(tty.clone(), Signal::SIGTTOU)?; } - ldata.process_echoes(tty.clone()); - // drop(ldata); + let mut output_guard = Some(self.output_lock.lock()); + + self.disc_data().process_echoes(tty.clone()); + self.drain_echoes(tty.clone())?; + let mut offset = 0; loop { if pcb.has_pending_signal_fast() { + if offset != 0 { + break; + } return Err(SystemError::ERESTARTSYS); } if core.flags().contains(TtyFlag::HUPPED) @@ -1788,40 +1873,112 @@ impl TtyLineDiscipline for NTtyLinediscipline { && core.driver().tty_driver_sub_type() != TtyDriverSubType::PtyMaster) || core.flags().contains(TtyFlag::HUPPING) { + if offset != 0 { + break; + } return Err(SystemError::EIO); } if termios.output_mode.contains(OutputMode::OPOST) { - while nr > 0 { - // let mut ldata = self.disc_data(); - // 获得一次处理后的数量 - let ret = ldata.process_output_block(core, core.termios(), &buf[offset..], nr); - let num = match ret { - Ok(num) => num, - Err(e) => { - if e == SystemError::EAGAIN_OR_EWOULDBLOCK { - break; - } else { - return Err(e); - } - } + let mut made_progress = false; + out_buf.clear(); + let pending = self.disc_data().opost_pending_bytes().to_vec(); + out_buf.extend_from_slice(&pending); + + if !out_buf.is_empty() { + let written = tty.write(core, &out_buf, out_buf.len())?; + if written != 0 { + let mut guard = self.disc_data(); + guard.advance_opost_pending(written); + made_progress = true; + } + if written != 0 { + tty.flush_chars(core); + } + } else { + out_buf.clear(); + let space = tty.write_room(core).min(out_buf.capacity()); + let simple_len = if space == 0 { + 0 + } else { + let guard = self.disc_data(); + guard.simple_output_block_len(&termios, &buf[offset..], space.min(nr)) }; - offset += num; - nr -= num; - - if nr == 0 { - break; - } + if simple_len != 0 { + let written = + tty.write(core, &buf[offset..offset + simple_len], simple_len)?; + if written != 0 { + self.disc_data().apply_simple_output_columns( + &termios, + &buf[offset..offset + written], + ); + offset += written; + nr -= written; + made_progress = true; + tty.flush_chars(core); + } + } else if space != 0 && nr != 0 { + let mut guard = self.disc_data(); + let cursor_column = guard.cursor_column; + let canon_cursor_column = guard.canon_cursor_column; + match guard.process_output_char_to_buf( + &termios, + buf[offset], + &mut out_buf, + space, + ) { + Ok(_) => {} + Err(SystemError::ENOBUFS) => { + guard.cursor_column = cursor_column; + guard.canon_cursor_column = canon_cursor_column; + } + Err(err) => { + guard.cursor_column = cursor_column; + guard.canon_cursor_column = canon_cursor_column; + return Err(err); + } + } + drop(guard); + + if out_buf.is_empty() { + offset += 1; + nr -= 1; + made_progress = true; + } else { + let mut sent = 0; + while sent < out_buf.len() { + let written = + tty.write(core, &out_buf[sent..], out_buf.len() - sent)?; + if written == 0 { + if sent == 0 { + let mut guard = self.disc_data(); + guard.cursor_column = cursor_column; + guard.canon_cursor_column = canon_cursor_column; + break; + } else { + let _ = core.write_wq().wait_event_interruptible( + EPollEventType::EPOLLOUT.bits() as u64, + || tty.write_room(core) > 0, + ); + continue; + } + } + sent += written; + tty.flush_chars(core); + } - let c = buf[offset]; - if !ldata.process_output(tty.clone(), c) { - break; + if sent == out_buf.len() { + offset += 1; + nr -= 1; + made_progress = true; + } + } } - offset += 1; - nr -= 1; } - tty.flush_chars(core); + if made_progress { + continue; + } } else { while nr > 0 { let write = tty.write(core, &buf[offset..], nr)?; @@ -1833,23 +1990,59 @@ impl TtyLineDiscipline for NTtyLinediscipline { } } - if nr == 0 { + let opost_pending = termios.output_mode.contains(OutputMode::OPOST) + && !self.disc_data().opost_pending_bytes().is_empty(); + if nr == 0 && !opost_pending { break; } if _flags.contains(FileFlags::O_NONBLOCK) || core.flags().contains(TtyFlag::LDISC_CHANGING) { + if offset != 0 { + break; + } return Err(SystemError::EAGAIN_OR_EWOULDBLOCK); } // 到这里表明没位置可写了 // 休眠一段时间 // 获取到termios读锁,避免termios被更改导致行为异常 - core.write_wq() - .sleep(EPollEventType::EPOLLOUT.bits() as u64); + drop(output_guard.take()); + let wait_result = core.write_wq().wait_event_interruptible( + EPollEventType::EPOLLOUT.bits() as u64, + || { + if core.flags().contains(TtyFlag::HUPPED) + || (core.flags().contains(TtyFlag::OTHER_CLOSED) + && core.driver().tty_driver_sub_type() != TtyDriverSubType::PtyMaster) + || core.flags().contains(TtyFlag::HUPPING) + || core.flags().contains(TtyFlag::LDISC_CHANGING) + { + return true; + } + + let write_room = tty.write_room(core); + if !termios.output_mode.contains(OutputMode::OPOST) { + return write_room > 0; + } + + let guard = self.disc_data(); + let next_input = if nr != 0 { Some(buf[offset]) } else { None }; + guard.opost_progress_possible(&termios, next_input, write_room) + }, + ); + if let Err(err) = wait_result { + output_guard = Some(self.output_lock.lock()); + if offset != 0 { + break; + } + return Err(err); + } + output_guard = Some(self.output_lock.lock()); + termios = *core.termios(); } + drop(output_guard); Ok(offset) } @@ -1867,7 +2060,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { true, )?; - let count = tty.chars_in_buffer(); + let count = tty.chars_in_buffer(tty.core()); user_writer.copy_one_to_user::(&(count as i32), 0)?; return Ok(0); } @@ -1940,8 +2133,8 @@ impl TtyLineDiscipline for NTtyLinediscipline { ldata.line_start = ldata.read_tail; - // 不是规范模式或者有可读数据 - if !termios.local_mode.contains(LocalMode::ICANON) || ldata.read_cnt() != 0 { + // 非规范模式或没有积压输入时不需要伪 EOF;否则提交当前输入。 + if !termios.local_mode.contains(LocalMode::ICANON) || ldata.read_cnt() == 0 { ldata.canon_head = ldata.read_tail; ldata.pushing = false; } else { @@ -2107,7 +2300,8 @@ impl TtyLineDiscipline for NTtyLinediscipline { event.insert(EPollEventType::EPOLLHUP); } - if core.driver().driver_funcs().chars_in_buffer() < 256 + if !core.write_lock().is_locked() + && core.driver().driver_funcs().chars_in_buffer(core) < 256 && core.driver().driver_funcs().write_room(core) > 0 { event.insert(EPollEventType::EPOLLOUT | EPollEventType::EPOLLWRNORM); @@ -2116,8 +2310,16 @@ impl TtyLineDiscipline for NTtyLinediscipline { Ok(event.bits() as usize) } - fn hangup(&self, _tty: Arc) -> Result<(), system_error::SystemError> { - todo!() + fn hangup(&self, tty: Arc) -> Result<(), system_error::SystemError> { + self.flush_buffer(tty.clone())?; + self.flush_output(tty.clone())?; + tty.core().read_wq().wakeup_all(); + tty.core().write_wq().wakeup_all(); + if let Some(link) = tty.core().link() { + link.core().read_wq().wakeup_all(); + link.core().write_wq().wakeup_all(); + } + Ok(()) } fn receive_buf( @@ -2128,7 +2330,12 @@ impl TtyLineDiscipline for NTtyLinediscipline { count: usize, ) -> Result { let mut ldata = self.disc_data(); - ldata.receive_buf_common(tty, buf, flags, count, false) + let ret = ldata.receive_buf_common(tty.clone(), buf, flags, count, false); + drop(ldata); + if let Some(_output_guard) = self.output_lock.try_lock() { + self.drain_echoes(tty)?; + } + ret } fn receive_buf2( @@ -2139,6 +2346,11 @@ impl TtyLineDiscipline for NTtyLinediscipline { count: usize, ) -> Result { let mut ldata = self.disc_data(); - ldata.receive_buf_common(tty, buf, flags, count, true) + let ret = ldata.receive_buf_common(tty.clone(), buf, flags, count, true); + drop(ldata); + if let Some(_output_guard) = self.output_lock.try_lock() { + self.drain_echoes(tty)?; + } + ret } } diff --git a/kernel/src/filesystem/vfs/file.rs b/kernel/src/filesystem/vfs/file.rs index 984bfcfa3c..1ead8735fe 100644 --- a/kernel/src/filesystem/vfs/file.rs +++ b/kernel/src/filesystem/vfs/file.rs @@ -596,6 +596,9 @@ impl File { } pub fn flush_for_close(&self, lock_owner: u64) -> Result<(), SystemError> { + if self.mode().contains(FileMode::FMODE_PATH) { + return Ok(()); + } let inode = self.inode(); inode.flush_file(self.private_data.lock(), lock_owner) } @@ -738,30 +741,32 @@ impl File { ) -> Result { let mut inode = inode; let mut file_type = inode.metadata()?.file_type; - // 检查是否为命名管道(FIFO) - let is_named_pipe = if file_type == FileType::Pipe { - if let Some(SpecialNodeData::Pipe(pipe_inode)) = inode.special_node() { - inode = pipe_inode; - file_type = inode.metadata()?.file_type; - true + let is_path = flags.contains(FileFlags::O_PATH); + + if !is_path { + // 检查是否为命名管道(FIFO) + let is_named_pipe = if file_type == FileType::Pipe { + if let Some(SpecialNodeData::Pipe(pipe_inode)) = inode.special_node() { + inode = pipe_inode; + file_type = inode.metadata()?.file_type; + true + } else { + false + } } else { false - } - } else { - false - }; + }; - // 对于命名管道,自动添加 O_LARGEFILE 标志(符合 Linux 行为) - if is_named_pipe { - flags.insert(FileFlags::O_LARGEFILE); - } + // 对于命名管道,自动添加 O_LARGEFILE 标志(符合 Linux 行为) + if is_named_pipe { + flags.insert(FileFlags::O_LARGEFILE); + } - if !flags.contains(FileFlags::O_PATH) { inode = resolve_device_special_inode(inode, file_type)?; } let metadata = inode.metadata()?; - if metadata.flags.contains(InodeFlags::S_APPEND) { + if !is_path && metadata.flags.contains(InodeFlags::S_APPEND) { flags.insert(FileFlags::O_APPEND); } @@ -780,39 +785,38 @@ impl File { let mut mode = FileMode::open_fmode(flags); let private_data = Mutex::new(private_data_init); - inode.open(private_data.lock(), &flags)?; + if is_path { + mode = FileMode::FMODE_PATH | FileMode::FMODE_OPENED; + } else { + inode.open(private_data.lock(), &flags)?; - // 设置默认能力(由 inode 能力接口统一决定;避免 syscall 层/字符串特判) - if inode.is_stream() { - mode.insert(FileMode::FMODE_STREAM); - } - if inode.supports_seek() { - mode.insert(FileMode::FMODE_LSEEK | FileMode::FMODE_ATOMIC_POS); - } - if inode.supports_pread() { - mode.insert(FileMode::FMODE_PREAD); - } - if inode.supports_pwrite() { - mode.insert(FileMode::FMODE_PWRITE); - } + // 设置默认能力(由 inode 能力接口统一决定;避免 syscall 层/字符串特判) + if inode.is_stream() { + mode.insert(FileMode::FMODE_STREAM); + } + if inode.supports_seek() { + mode.insert(FileMode::FMODE_LSEEK | FileMode::FMODE_ATOMIC_POS); + } + if inode.supports_pread() { + mode.insert(FileMode::FMODE_PREAD); + } + if inode.supports_pwrite() { + mode.insert(FileMode::FMODE_PWRITE); + } - inode.adjust_file_mode_after_open(&private_data.lock(), &mut mode); + inode.adjust_file_mode_after_open(&private_data.lock(), &mut mode); - // TODO: 检查inode是否有read/write方法,设置FMODE_CAN_READ/WRITE - // 这需要在IndexNode trait中添加相应的检查方法 - if mode.contains(FileMode::FMODE_READ) { - mode.insert(FileMode::FMODE_CAN_READ); - } - if mode.contains(FileMode::FMODE_WRITE) { - mode.insert(FileMode::FMODE_CAN_WRITE); - } - - // 标记为已打开 - mode.insert(FileMode::FMODE_OPENED); + // TODO: 检查inode是否有read/write方法,设置FMODE_CAN_READ/WRITE + // 这需要在IndexNode trait中添加相应的检查方法 + if mode.contains(FileMode::FMODE_READ) { + mode.insert(FileMode::FMODE_CAN_READ); + } + if mode.contains(FileMode::FMODE_WRITE) { + mode.insert(FileMode::FMODE_CAN_WRITE); + } - // O_PATH特殊处理 - if flags.contains(FileFlags::O_PATH) { - mode = FileMode::FMODE_PATH | FileMode::FMODE_OPENED; + // 标记为已打开 + mode.insert(FileMode::FMODE_OPENED); } if mode.contains(FileMode::FMODE_WRITE) { @@ -1649,15 +1653,17 @@ impl Drop for File { mnt_inode.mount_fs().dec_write_count(); } } - let r: Result<(), SystemError> = self.inode.close(self.private_data.lock()); - // 打印错误信息 - if let Err(e) = r { - error!( - "pid: {:?} failed to close file: {:?}, errno={:?}", - ProcessManager::current_pcb().raw_pid(), - self, - e - ); + if !self.mode.read().contains(FileMode::FMODE_PATH) { + let r: Result<(), SystemError> = self.inode.close(self.private_data.lock()); + // 打印错误信息 + if let Err(e) = r { + error!( + "pid: {:?} failed to close file: {:?}, errno={:?}", + ProcessManager::current_pcb().raw_pid(), + self, + e + ); + } } } } @@ -1683,6 +1689,11 @@ impl DroppedFd { } } +#[derive(Debug, Clone, Copy)] +pub struct ReservedFd { + fd: i32, +} + /// @brief pcb里面的文件描述符数组 #[derive(Debug)] pub struct FileDescriptorVec { @@ -1690,6 +1701,8 @@ pub struct FileDescriptorVec { fds: Vec>>, /// per-fd 的 close_on_exec 标志(与 fds 并行,对应 Linux fdtable.close_on_exec 位图) cloexec: Vec, + /// 已预留但尚未安装 File 的 fd 槽位。 + reserved: Vec, /// POSIX record lock owner id,对齐 Linux current->files 语义。 lock_owner_id: usize, /// 下一个可能空闲的文件描述符号(用于优化分配,避免O(n²)扫描) @@ -1715,10 +1728,14 @@ impl FileDescriptorVec { let mut cloexec = Vec::with_capacity(FileDescriptorVec::INITIAL_CAPACITY); cloexec.resize(FileDescriptorVec::INITIAL_CAPACITY, false); + let mut reserved = Vec::with_capacity(FileDescriptorVec::INITIAL_CAPACITY); + reserved.resize(FileDescriptorVec::INITIAL_CAPACITY, false); + // 初始化文件描述符数组结构体 return FileDescriptorVec { fds: data, cloexec, + reserved, lock_owner_id: alloc_lock_owner_id(), next_fd: 0, }; @@ -1743,18 +1760,25 @@ impl FileDescriptorVec { res.cloexec[i] = self.cloexec[i]; } } - // 复制 next_fd 以保持相同的分配状态 - res.next_fd = self.next_fd; + // reserved fd 不属于已经安装的 open file description,clone 时不复制。 + // 因此 next_fd 必须按 clone 后的真实空闲槽重新计算。 + res.next_fd = res.first_available_fd_from(0).unwrap_or(res.fds.len()); // 新 fd table 必须拥有新的 record-lock owner(对齐 Linux 新 files_struct)。 res.lock_owner_id = alloc_lock_owner_id(); return res; } + fn first_available_fd_from(&self, start: usize) -> Option { + (start..self.fds.len()).find(|&i| self.fds[i].is_none() && !self.reserved[i]) + } + /// 返回当前已占用的最高文件描述符索引(若无则为None) #[inline] fn highest_open_index(&self) -> Option { // 从高到低查找第一个占用的槽位 - (0..self.fds.len()).rev().find(|&i| self.fds[i].is_some()) + (0..self.fds.len()) + .rev() + .find(|&i| self.fds[i].is_some() || self.reserved[i]) } /// 扩容文件描述符表到指定容量 @@ -1784,8 +1808,16 @@ impl FileDescriptorVec { { return Err(SystemError::ENOMEM); } + if self + .reserved + .try_reserve(new_capacity - current_len) + .is_err() + { + return Err(SystemError::ENOMEM); + } self.fds.resize(new_capacity, None); self.cloexec.resize(new_capacity, false); + self.reserved.resize(new_capacity, false); } else if new_capacity < current_len { // 缩容:允许,但不能丢弃仍在使用的高位fd。 // 若高位fd仍在使用,将缩容目标提升到 (最高已用fd + 1)。 @@ -1794,6 +1826,7 @@ impl FileDescriptorVec { if target < current_len { self.fds.truncate(target); self.cloexec.truncate(target); + self.reserved.truncate(target); // 确保 next_fd 不超过新的容量 if self.next_fd > target { self.next_fd = target; @@ -1824,6 +1857,14 @@ impl FileDescriptorVec { return !(fd < 0 || fd as usize >= self.fds.len()); } + pub fn fd_slot_available(&self, fd: i32) -> bool { + if fd < 0 { + return false; + } + let idx = fd as usize; + idx >= self.fds.len() || (self.fds[idx].is_none() && !self.reserved[idx]) + } + /// 申请文件描述符,并把文件对象存入其中。 /// /// ## 参数 @@ -1877,7 +1918,7 @@ impl FileDescriptorVec { } let x = &mut self.fds[new_fd as usize]; - if x.is_none() { + if x.is_none() && !self.reserved[new_fd as usize] { *x = Some(file); self.cloexec[new_fd as usize] = cloexec; // 更新 next_fd:如果分配的是 next_fd 位置,则推进到下一个 @@ -1895,7 +1936,7 @@ impl FileDescriptorVec { // 从 next_fd 开始查找空位 for i in self.next_fd..max_search { - if self.fds[i].is_none() { + if self.fds[i].is_none() && !self.reserved[i] { self.fds[i] = Some(file); self.cloexec[i] = cloexec; // 更新 next_fd 为下一个位置 @@ -1928,6 +1969,91 @@ impl FileDescriptorVec { } } + /// 预留一个最低可用 fd 槽位,但暂不安装 File。 + /// + /// 用于实现 Linux `get_unused_fd_flags -> open -> fd_install` 这类语义: + /// 如果 fd 预留失败,则后续 open 不应发生。 + pub fn reserve_fd(&mut self, cloexec: bool) -> Result { + let nofile_limit = crate::process::ProcessManager::current_pcb() + .get_rlimit(crate::process::resource::RLimitID::Nofile) + .rlim_cur as usize; + + let max_search = core::cmp::min(self.fds.len(), nofile_limit); + for i in self.next_fd..max_search { + if self.fds[i].is_none() && !self.reserved[i] { + self.reserved[i] = true; + self.cloexec[i] = cloexec; + self.next_fd = i + 1; + return Ok(ReservedFd { fd: i as i32 }); + } + } + + let current_len = self.fds.len(); + if current_len < nofile_limit { + let new_capacity = core::cmp::min( + core::cmp::max(current_len * 2, current_len + 1), + nofile_limit, + ); + self.resize_to_capacity(new_capacity)?; + + self.reserved[current_len] = true; + self.cloexec[current_len] = cloexec; + self.next_fd = current_len + 1; + return Ok(ReservedFd { + fd: current_len as i32, + }); + } + + Err(SystemError::EMFILE) + } + + /// 在先前预留的 fd 槽位安装 File。 + pub fn install_reserved_fd( + &mut self, + reservation: ReservedFd, + file: File, + ) -> Result { + self.install_reserved_fd_arc(reservation, Arc::new(file)) + } + + /// 在先前预留的 fd 槽位安装共享 File。 + pub fn install_reserved_fd_arc( + &mut self, + reservation: ReservedFd, + file: Arc, + ) -> Result { + let fd = reservation.fd; + if fd < 0 || fd as usize >= self.fds.len() { + return Err(SystemError::EBADF); + } + + let idx = fd as usize; + if !self.reserved[idx] || self.fds[idx].is_some() { + return Err(SystemError::EBADF); + } + + self.fds[idx] = Some(file); + self.reserved[idx] = false; + Ok(fd) + } + + /// 释放一个尚未安装 File 的预留 fd。 + pub fn release_reserved_fd(&mut self, reservation: ReservedFd) { + let fd = reservation.fd; + if fd < 0 || fd as usize >= self.fds.len() { + return; + } + + let idx = fd as usize; + if self.reserved[idx] && self.fds[idx].is_none() { + self.reserved[idx] = false; + self.cloexec[idx] = false; + if idx < self.next_fd { + self.next_fd = idx; + } + } + } + /// Atomically install `file` at an exact fd, returning the old fd object if one existed. /// /// The table is resized before the old slot is removed, so allocation failure cannot @@ -1951,6 +2077,10 @@ impl FileDescriptorVec { self.resize_to_capacity(fd_index + 1)?; } + if self.reserved[fd_index] { + return Err(SystemError::EBUSY); + } + let old = self.fds[fd_index].replace(file); self.cloexec[fd_index] = cloexec; if fd_index == self.next_fd { diff --git a/kernel/src/filesystem/vfs/syscall/sys_fcntl.rs b/kernel/src/filesystem/vfs/syscall/sys_fcntl.rs index 67c5582b20..352c446198 100644 --- a/kernel/src/filesystem/vfs/syscall/sys_fcntl.rs +++ b/kernel/src/filesystem/vfs/syscall/sys_fcntl.rs @@ -100,7 +100,7 @@ impl SysFcntlHandle { // 在RLIMIT_NOFILE范围内查找可用的文件描述符 for i in arg..nofile { - if fd_table_guard.get_file_by_fd(i as i32).is_none() { + if fd_table_guard.fd_slot_available(i as i32) { let (newfd, dropped) = if cmd == FcntlCommand::DupFd { do_dup2(fd, i as i32, &mut fd_table_guard)? } else { diff --git a/kernel/src/ipc/generic_signal.rs b/kernel/src/ipc/generic_signal.rs index 7b7d43ea93..03558612d4 100644 --- a/kernel/src/ipc/generic_signal.rs +++ b/kernel/src/ipc/generic_signal.rs @@ -447,6 +447,9 @@ fn sig_stop(sig: Signal) { let pcb = ProcessManager::current_pcb(); // 标记停止事件,供 waitid(WSTOPPED) 可见 pcb.sighand().flags_insert(SignalFlags::CLD_STOPPED); + if !pcb.sighand().flags_contains(SignalFlags::STOP_STOPPED) { + pcb.sighand().set_stop_signal(sig); + } pcb.sighand().flags_insert(SignalFlags::STOP_STOPPED); } ProcessManager::mark_stop().unwrap_or_else(|e| { diff --git a/kernel/src/ipc/sighand.rs b/kernel/src/ipc/sighand.rs index f03a8f1054..7d1d645752 100644 --- a/kernel/src/ipc/sighand.rs +++ b/kernel/src/ipc/sighand.rs @@ -53,6 +53,8 @@ pub struct InnerSigHand { /// 线程组退出码(仿照 Linux 的 signal_struct::group_exit_code) /// 仅当 flags 中包含 GROUP_EXIT 时才有效 pub group_exit_code: usize, + /// 最近一次 job-control stop 的信号号,用于 wait(WSTOPPED) 填充 WSTOPSIG。 + pub stop_signal: Signal, /// 线程组 exec(de-thread)当前执行者 pub group_exec_task: Option>, /// 线程组 exec(de-thread)等待计数(仿照 Linux 的 signal_struct::notify_count) @@ -289,6 +291,15 @@ impl SigHand { g.flags.remove(flag); } + pub fn stop_signal(&self) -> Signal { + self.inner().stop_signal + } + + pub fn set_stop_signal(&self, sig: Signal) { + let mut g = self.inner_mut(); + g.stop_signal = sig; + } + /// 尝试开始线程组 exec(去线程化)流程。 /// /// - 若已经有 GROUP_EXIT 或 GROUP_EXEC 在进行中,则返回 EAGAIN。 @@ -427,6 +438,7 @@ impl Default for InnerSigHand { curr_target: None, flags: SignalFlags::empty(), group_exit_code: 0, + stop_signal: Signal::SIGSTOP, group_exec_task: None, group_exec_notify_count: 0, cnt: 0, diff --git a/kernel/src/ipc/signal.rs b/kernel/src/ipc/signal.rs index 0f49c76810..5e501207e1 100644 --- a/kernel/src/ipc/signal.rs +++ b/kernel/src/ipc/signal.rs @@ -620,6 +620,12 @@ impl Signal { thread_group_leader .sighand() .flags_insert(SignalFlags::CLD_STOPPED); + if !thread_group_leader + .sighand() + .flags_contains(SignalFlags::STOP_STOPPED) + { + thread_group_leader.sighand().set_stop_signal(*self); + } thread_group_leader .sighand() .flags_insert(SignalFlags::STOP_STOPPED); diff --git a/kernel/src/ipc/syscall/sys_rt_sigsuspend.rs b/kernel/src/ipc/syscall/sys_rt_sigsuspend.rs index afa365c0bc..fd08e02478 100644 --- a/kernel/src/ipc/syscall/sys_rt_sigsuspend.rs +++ b/kernel/src/ipc/syscall/sys_rt_sigsuspend.rs @@ -3,7 +3,7 @@ use system_error::SystemError; use crate::arch::ipc::signal::SigSet; use crate::arch::syscall::nr::SYS_RT_SIGSUSPEND; -use crate::ipc::signal::{set_sigprocmask, SigHow}; +use crate::ipc::signal::set_user_sigmask; use crate::process::ProcessManager; use crate::sched::{schedule, SchedMode}; use crate::{ @@ -18,7 +18,7 @@ use crate::{ pub struct SysRtSigSuspend; impl Syscall for SysRtSigSuspend { fn num_args(&self) -> usize { - 1 + 2 } fn handle( @@ -44,14 +44,12 @@ impl Syscall for SysRtSigSuspend { mask -= SigSet::SIGSTOP; let pcb = ProcessManager::current_pcb(); - let old_mask = *pcb.sig_info_irqsave().sig_blocked(); - set_sigprocmask(SigHow::SetMask, mask).unwrap(); - log::trace!("Process enter rt_sigsuspend, new mask: {mask:?}, old mask: {old_mask:?}"); + set_user_sigmask(&mut mask); + log::trace!("Process enter rt_sigsuspend, new mask: {mask:?}"); loop { if pcb.has_pending_signal_fast() && pcb.has_pending_not_masked_signal() { - set_sigprocmask(SigHow::SetMask, old_mask).unwrap(); - return Err(SystemError::EINTR); + return Err(SystemError::ERESTARTNOHAND); } schedule(SchedMode::SM_NONE); } diff --git a/kernel/src/libs/wait_queue.rs b/kernel/src/libs/wait_queue.rs index 46f3acaaeb..481f70861d 100644 --- a/kernel/src/libs/wait_queue.rs +++ b/kernel/src/libs/wait_queue.rs @@ -943,23 +943,85 @@ impl EventWaitQueue { } } - pub fn sleep(&self, events: u64) { + fn register_waker(&self, events: u64, waker: Arc) { + let mut entry = Some((events, waker)); + let mut spare: Vec<(u64, Arc)> = Vec::new(); + + loop { + let needed_capacity; + { + let mut guard = self.wait_list.lock_irqsave(); + if guard.len() < guard.capacity() { + guard.push(entry.take().unwrap()); + return; + } + + needed_capacity = (guard.len() + 1).next_power_of_two().max(4); + if spare.capacity() >= needed_capacity { + spare.extend(guard.drain(..)); + spare = mem::replace(&mut *guard, spare); + continue; + } + } + + spare = Vec::with_capacity(needed_capacity); + } + } + + fn remove_waker(&self, target: &Arc) { + let mut guard = self.wait_list.lock_irqsave(); + guard.retain(|(_, waker)| !Arc::ptr_eq(waker, target)); + } + + pub fn wait_event_interruptible(&self, events: u64, mut cond: F) -> Result<(), SystemError> + where + F: FnMut() -> bool, + { before_sleep_check(0); + if cond() { + return Ok(()); + } + let (waiter, waker) = Waiter::new_pair(); - { - let mut guard = self.wait_list.lock_irqsave(); - guard.push((events, waker)); + loop { + self.register_waker(events, waker.clone()); + + if cond() { + self.remove_waker(&waker); + return Ok(()); + } + + if Signal::signal_pending_state(true, false, &ProcessManager::current_pcb()) { + self.remove_waker(&waker); + waker.close(); + if cond() { + return Ok(()); + } + return Err(SystemError::ERESTARTSYS); + } + + if let Err(err) = waiter.wait(true) { + self.remove_waker(&waker); + waker.close(); + if cond() { + return Ok(()); + } + return Err(err); + } } + } + + pub fn sleep(&self, events: u64) { + before_sleep_check(0); + let (waiter, waker) = Waiter::new_pair(); + self.register_waker(events, waker); let _ = waiter.wait(true); } pub fn sleep_unlock_spinlock(&self, events: u64, to_unlock: SpinLockGuard) { before_sleep_check(1); let (waiter, waker) = Waiter::new_pair(); - { - let mut guard = self.wait_list.lock_irqsave(); - guard.push((events, waker)); - } + self.register_waker(events, waker); drop(to_unlock); let _ = waiter.wait(true); } diff --git a/kernel/src/process/exit.rs b/kernel/src/process/exit.rs index 5f5e070fb8..0f7219486d 100644 --- a/kernel/src/process/exit.rs +++ b/kernel/src/process/exit.rs @@ -402,7 +402,7 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result { && kwo.options.contains(WaitOption::WSTOPPED) && pcb.sighand().flags_contains(SignalFlags::CLD_STOPPED) { - let stopsig = Signal::SIGSTOP as i32; + let stopsig = pcb.sighand().stop_signal() as i32; kwo.no_task_error = None; kwo.ret_info = Some(WaitIdInfo { pid: wait_visible_pid(&pcb), @@ -517,7 +517,7 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result { && kwo.options.contains(WaitOption::WSTOPPED) && pcb.sighand().flags_contains(SignalFlags::CLD_STOPPED) { - let stopsig = Signal::SIGSTOP as i32; + let stopsig = pcb.sighand().stop_signal() as i32; kwo.no_task_error = None; kwo.ret_info = Some(WaitIdInfo { pid: wait_visible_pid(&pcb), @@ -668,7 +668,7 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result { && kwo.options.contains(WaitOption::WSTOPPED) && pcb.sighand().flags_contains(SignalFlags::CLD_STOPPED) { - let stopsig = Signal::SIGSTOP as i32; + let stopsig = pcb.sighand().stop_signal() as i32; kwo.no_task_error = None; kwo.ret_info = Some(WaitIdInfo { pid: wait_visible_pid(&pcb), @@ -795,7 +795,7 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result { && kwo.options.contains(WaitOption::WSTOPPED) && pcb.sighand().flags_contains(SignalFlags::CLD_STOPPED) { - let stopsig = Signal::SIGSTOP as i32; + let stopsig = pcb.sighand().stop_signal() as i32; kwo.no_task_error = None; kwo.ret_info = Some(WaitIdInfo { pid: wait_visible_pid(&pcb), @@ -962,7 +962,7 @@ fn do_waitpid( } ProcessState::Stopped => { // 非 ptrace 停止:报告 stopsig=SIGSTOP - let stopsig = Signal::SIGSTOP as i32; + let stopsig = child_pcb.sighand().stop_signal() as i32; // 由于目前不支持ptrace,因此这个值为false let ptrace = false; diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 0b59ef682b..9b2d9a9e3f 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -32,7 +32,7 @@ use crate::{ CurrentIrqArch, SigStackArch, }, cgroup::{cgroup_root_node, CgroupNode, TaskCgroupRef}, - driver::tty::tty_core::TtyCore, + driver::tty::{tty_core::TtyCore, tty_job_control::TtyJobCtrlManager}, exception::InterruptArch, filesystem::{ fs::FsStruct, @@ -1019,16 +1019,34 @@ impl ProcessManager { pcb.exit_files(); pcb.exit_timers(); - // TODO 由于未实现进程组,tty记录的前台进程组等于当前进程,故退出前要置空 - // 后续相关逻辑需要在SYS_EXIT_GROUP系统调用中实现 - if let Some(tty) = pcb.sig_info_irqsave().tty() { - // 临时解决方案!!! 临时解决方案!!! 引入进程组之后,要重写这个更新前台进程组的逻辑 - let mut g = tty.core().contorl_info_irqsave(); - if g.pgid == Some(pid) { - g.pgid = None; + + let (current_tty, is_session_leader, sid) = { + let siginfo = pcb.sig_info_irqsave(); + (siginfo.tty(), siginfo.is_session_leader, pcb.task_session()) + }; + if let Some(tty) = current_tty { + if is_session_leader { + let tty_pgrp = tty.core().contorl_info_irqsave().pgid.clone(); + if let Some(pgrp) = tty_pgrp { + let _ = crate::ipc::kill::send_signal_to_pgid(&pgrp, Signal::SIGHUP); + } + TtyJobCtrlManager::remove_session_tty(&tty); + if let Some(sid) = sid { + TtyJobCtrlManager::session_clear_tty(sid); + } else { + pcb.sig_info_mut().set_tty(None); + } + } else { + let mut g = tty.core().contorl_info_irqsave(); + if g.pgid == Some(pid) { + g.pgid = None; + } + drop(g); + pcb.sig_info_mut().set_tty(None); } + } else { + pcb.sig_info_mut().set_tty(None); } - pcb.sig_info_mut().set_tty(None); // Linux 语义:zombie 不应出现在 cgroup.procs 中。 // 必须持有 cgroup_accounting_lock 以避免与 cgroup.procs 写入死锁 diff --git a/user/apps/tests/dunitest/suites/normal/hvc_console_backpressure.cc b/user/apps/tests/dunitest/suites/normal/hvc_console_backpressure.cc new file mode 100644 index 0000000000..19f8ad9c69 --- /dev/null +++ b/user/apps/tests/dunitest/suites/normal/hvc_console_backpressure.cc @@ -0,0 +1,64 @@ +#include + +#include +#include +#include +#include +#include + +#include + +namespace { + +class UniqueFd { +public: + explicit UniqueFd(int fd = -1) : fd_(fd) {} + UniqueFd(const UniqueFd&) = delete; + UniqueFd& operator=(const UniqueFd&) = delete; + + ~UniqueFd() { + if (fd_ >= 0) { + close(fd_); + } + } + + int get() const { return fd_; } + +private: + int fd_; +}; + +} // namespace + +TEST(HvcConsoleBackpressureTest, LargeNonblockingWriteReportsProgressOrAgain) { + UniqueFd fd(open("/dev/hvc0", O_WRONLY | O_NONBLOCK)); + if (fd.get() < 0 && errno == ENOENT) { + GTEST_SKIP() << "/dev/hvc0 is not available on this platform"; + } + ASSERT_GE(fd.get(), 0) << "open(/dev/hvc0) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + std::array buf{}; + for (size_t i = 0; i < buf.size(); ++i) { + buf[i] = static_cast('a' + (i % 26)); + } + + ssize_t ret = write(fd.get(), buf.data(), buf.size()); + if (ret < 0) { + EXPECT_TRUE(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + << "unexpected write errno=" << errno << " (" << strerror(errno) << ")"; + } else { + EXPECT_GT(ret, 0); + EXPECT_LE(static_cast(ret), buf.size()); + } + + int pending = -1; + ASSERT_EQ(0, ioctl(fd.get(), TIOCOUTQ, &pending)) + << "TIOCOUTQ failed: errno=" << errno << " (" << strerror(errno) << ")"; + EXPECT_GE(pending, 0); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc index 5b0cb2857a..201c693944 100644 --- a/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc +++ b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc @@ -8,11 +8,16 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include + namespace { #ifndef TIOCPKT @@ -27,6 +32,22 @@ constexpr int kTiocgptn = 0x80045430; constexpr int kTiocgptn = TIOCGPTN; #endif +#ifndef TIOCSPTLCK +constexpr int kTiocsptlck = 0x40045431; +#else +constexpr int kTiocsptlck = TIOCSPTLCK; +#endif + +#ifndef TIOCGPTPEER +constexpr int kTiocgptpeer = 0x5441; +#else +constexpr int kTiocgptpeer = TIOCGPTPEER; +#endif + +#ifndef O_PATH +#define O_PATH 010000000 +#endif + #ifndef TIOCPKT_FLUSHWRITE constexpr unsigned char kTiocpktFlushWrite = 2; #else @@ -72,6 +93,32 @@ class UniqueFd { int fd_ = -1; }; +class ScopedSignalIgnore { +public: + explicit ScopedSignalIgnore(int signum) : signum_(signum) { + struct sigaction action = {}; + action.sa_handler = SIG_IGN; + sigemptyset(&action.sa_mask); + valid_ = sigaction(signum_, &action, &old_) == 0; + } + + ScopedSignalIgnore(const ScopedSignalIgnore&) = delete; + ScopedSignalIgnore& operator=(const ScopedSignalIgnore&) = delete; + + ~ScopedSignalIgnore() { + if (valid_) { + sigaction(signum_, &old_, nullptr); + } + } + + bool valid() const { return valid_; } + +private: + int signum_; + struct sigaction old_ = {}; + bool valid_ = false; +}; + struct PtyPair { UniqueFd master; UniqueFd slave; @@ -107,6 +154,36 @@ PtyPair OpenRawPty(char* name = nullptr) { return pair; } +PtyPair OpenOpostPty() { + int master = -1; + int slave = -1; + if (openpty(&master, &slave, nullptr, nullptr, nullptr) < 0) { + ADD_FAILURE() << "openpty failed: errno=" << errno << " (" << strerror(errno) << ")"; + return {}; + } + + PtyPair pair{UniqueFd(master), UniqueFd(slave)}; + + struct termios term = {}; + if (tcgetattr(pair.slave.get(), &term) < 0) { + ADD_FAILURE() << "tcgetattr failed: errno=" << errno << " (" << strerror(errno) << ")"; + return pair; + } + + term.c_iflag = 0; + term.c_oflag = OPOST | ONLCR; + term.c_lflag = 0; + term.c_cflag |= CS8; + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + + if (tcsetattr(pair.slave.get(), TCSANOW, &term) < 0) { + ADD_FAILURE() << "tcsetattr failed: errno=" << errno << " (" << strerror(errno) << ")"; + } + + return pair; +} + PtyPair OpenCanonicalNoEchoPty() { int master = -1; int slave = -1; @@ -171,6 +248,69 @@ bool IsWouldBlock(int err) { ; } +unsigned char ConfigureSignalFlushSlave(int fd, bool noflsh) { + struct termios term = {}; + if (tcgetattr(fd, &term) < 0) { + ADD_FAILURE() << "tcgetattr failed: errno=" << errno << " (" << strerror(errno) << ")"; + return 0; + } + + term.c_lflag |= ICANON | ISIG; + term.c_lflag &= ~ECHO; + if (noflsh) { + term.c_lflag |= NOFLSH; + } else { + term.c_lflag &= ~NOFLSH; + } + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + + if (tcsetattr(fd, TCSANOW, &term) < 0) { + ADD_FAILURE() << "tcsetattr failed: errno=" << errno << " (" << strerror(errno) << ")"; + return 0; + } + + return static_cast(term.c_cc[VINTR]); +} + +std::string ReadCanonicalLine(int fd) { + struct pollfd pfd = { + .fd = fd, + .events = POLLIN | POLLHUP | POLLERR, + .revents = 0, + }; + + int ret = 0; + do { + ret = poll(&pfd, 1, 5000); + } while (ret < 0 && errno == EINTR); + + if (ret <= 0) { + ADD_FAILURE() << "poll waiting for canonical line failed: errno=" + << (ret < 0 ? errno : ETIMEDOUT) << " (" + << strerror(ret < 0 ? errno : ETIMEDOUT) << ")"; + return {}; + } + if ((pfd.revents & POLLIN) == 0) { + ADD_FAILURE() << "poll returned without POLLIN, revents=" << pfd.revents; + return {}; + } + + char buf[512] = {}; + ssize_t n = 0; + do { + n = read(fd, buf, sizeof(buf)); + } while (n < 0 && errno == EINTR); + + if (n <= 0) { + ADD_FAILURE() << "read canonical line failed: errno=" << (n < 0 ? errno : EIO) + << " (" << strerror(n < 0 ? errno : EIO) << ")"; + return {}; + } + + return std::string(buf, buf + n); +} + struct ConcurrentSlaveOpenArgs { const char* slave_name; int start_read_fd; @@ -178,6 +318,38 @@ struct ConcurrentSlaveOpenArgs { int open_errno; }; +struct WriteAllArgs { + int fd; + const char* data; + size_t len; + size_t written; + int error; +}; + +void* WriteAll(void* raw) { + auto* args = static_cast(raw); + args->written = 0; + args->error = 0; + + while (args->written < args->len) { + ssize_t n = write(args->fd, args->data + args->written, args->len - args->written); + if (n < 0 && errno == EINTR) { + continue; + } + if (n < 0) { + args->error = errno; + return nullptr; + } + if (n == 0) { + args->error = EIO; + return nullptr; + } + args->written += static_cast(n); + } + + return nullptr; +} + void* ConcurrentSlaveOpen(void* raw) { auto* args = static_cast(raw); char token = 0; @@ -269,6 +441,66 @@ TEST(TtyPtyHangup, CanonicalReaderDoesNotMissLineWakeup) { EXPECT_EQ(0, WEXITSTATUS(status)); } +TEST(TtyPtyHangup, SignalFlushDiscardsPendingInputBacklog) { + ScopedSignalIgnore ignore_sigint(SIGINT); + ASSERT_TRUE(ignore_sigint.valid()) << "sigaction(SIGINT, SIG_IGN) failed: errno=" << errno + << " (" << strerror(errno) << ")"; + + PtyPair pair = OpenCanonicalNoEchoPty(); + ASSERT_GE(pair.master.get(), 0); + ASSERT_GE(pair.slave.get(), 0); + + unsigned char vintr = ConfigureSignalFlushSlave(pair.slave.get(), false); + ASSERT_NE(0, vintr) << "VINTR must be enabled for this regression test"; + + constexpr size_t kDragonOsPtyDrainChunk = 256; + std::string stale(kDragonOsPtyDrainChunk - 1, 'a'); + stale.push_back(static_cast(vintr)); + stale.append("stale-backlog-should-be-flushed\n"); + ASSERT_LT(stale.size(), static_cast(16 * 1024)); + + ASSERT_EQ(static_cast(stale.size()), write(pair.master.get(), stale.data(), stale.size())) + << "single stale write failed: errno=" << errno << " (" << strerror(errno) << ")"; + + const std::string fresh = "fresh-after-signal\n"; + ASSERT_EQ(static_cast(fresh.size()), write(pair.master.get(), fresh.data(), fresh.size())) + << "fresh marker write failed: errno=" << errno << " (" << strerror(errno) << ")"; + + EXPECT_EQ(fresh, ReadCanonicalLine(pair.slave.get())); +} + +TEST(TtyPtyHangup, SignalNoflshPreservesPendingInputBacklog) { + ScopedSignalIgnore ignore_sigint(SIGINT); + ASSERT_TRUE(ignore_sigint.valid()) << "sigaction(SIGINT, SIG_IGN) failed: errno=" << errno + << " (" << strerror(errno) << ")"; + + PtyPair pair = OpenCanonicalNoEchoPty(); + ASSERT_GE(pair.master.get(), 0); + ASSERT_GE(pair.slave.get(), 0); + + unsigned char vintr = ConfigureSignalFlushSlave(pair.slave.get(), true); + ASSERT_NE(0, vintr) << "VINTR must be enabled for this regression test"; + + constexpr size_t kDragonOsPtyDrainChunk = 256; + std::string expected(kDragonOsPtyDrainChunk - 1, 'a'); + expected.append("stale-backlog-must-survive\n"); + + std::string input(kDragonOsPtyDrainChunk - 1, 'a'); + input.push_back(static_cast(vintr)); + input.append("stale-backlog-must-survive\n"); + ASSERT_LT(input.size(), static_cast(16 * 1024)); + + ASSERT_EQ(static_cast(input.size()), write(pair.master.get(), input.data(), input.size())) + << "single NOFLSH write failed: errno=" << errno << " (" << strerror(errno) << ")"; + + const std::string fresh = "fresh-after-noflsh\n"; + ASSERT_EQ(static_cast(fresh.size()), write(pair.master.get(), fresh.data(), fresh.size())) + << "fresh marker write failed: errno=" << errno << " (" << strerror(errno) << ")"; + + EXPECT_EQ(expected, ReadCanonicalLine(pair.slave.get())); + EXPECT_EQ(fresh, ReadCanonicalLine(pair.slave.get())); +} + TEST(TtyPtyHangup, MasterPollAfterSlaveCloseReportsHupAndOut) { PtyPair pair = OpenRawPty(); ASSERT_GE(pair.master.get(), 0); @@ -498,6 +730,287 @@ TEST(TtyPtyHangup, MasterOnlyCloseReleasesDevptsIndex) { EXPECT_TRUE(saw_reuse) << "master-only open/close did not visibly reuse any devpts index"; } +TEST(TtyPtyHangup, OPathFifoKeepsPathInodeAndDoesNotBlock) { + std::string path = "/tmp/opath_fifo_" + std::to_string(getpid()); + unlink(path.c_str()); + + ASSERT_EQ(0, mkfifo(path.c_str(), 0600)) + << "mkfifo(" << path << ") failed: errno=" << errno << " (" << strerror(errno) << ")"; + + struct stat path_stat = {}; + ASSERT_EQ(0, lstat(path.c_str(), &path_stat)) + << "lstat(" << path << ") failed: errno=" << errno << " (" << strerror(errno) << ")"; + + UniqueFd fifo(open(path.c_str(), O_PATH | O_NONBLOCK | O_CLOEXEC)); + int saved_errno = errno; + unlink(path.c_str()); + ASSERT_GE(fifo.get(), 0) << "open(O_PATH) on FIFO failed or blocked: errno=" << saved_errno + << " (" << strerror(saved_errno) << ")"; + + struct stat fd_stat = {}; + ASSERT_EQ(0, fstat(fifo.get(), &fd_stat)) + << "fstat(O_PATH FIFO fd) failed: errno=" << errno << " (" << strerror(errno) << ")"; + EXPECT_EQ(path_stat.st_dev, fd_stat.st_dev); + EXPECT_EQ(path_stat.st_ino, fd_stat.st_ino); + EXPECT_EQ(path_stat.st_mode & S_IFMT, fd_stat.st_mode & S_IFMT); +} + +TEST(TtyPtyHangup, TiocgptpeerFailsWhileSlaveLocked) { + UniqueFd master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(master.get(), 0) << "open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + errno = 0; + EXPECT_EQ(-1, ioctl(master.get(), kTiocgptpeer, O_RDWR | O_NOCTTY)); + EXPECT_EQ(EIO, errno) << "locked TIOCGPTPEER should fail with EIO, got errno=" << errno + << " (" << strerror(errno) << ")"; +} + +TEST(TtyPtyHangup, TiocgptpeerOPathSucceedsWhileSlaveLocked) { + UniqueFd master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(master.get(), 0) << "open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + UniqueFd peer(ioctl(master.get(), kTiocgptpeer, O_PATH | O_CLOEXEC)); + ASSERT_GE(peer.get(), 0) << "locked TIOCGPTPEER(O_PATH) failed: errno=" << errno + << " (" << strerror(errno) << ")"; + + int fd_flags = fcntl(peer.get(), F_GETFD); + ASSERT_GE(fd_flags, 0) << "fcntl(F_GETFD) failed: errno=" << errno << " (" << strerror(errno) + << ")"; + EXPECT_NE(0, fd_flags & FD_CLOEXEC); +} + +TEST(TtyPtyHangup, TiocgptpeerOpensUnlockedSlave) { + UniqueFd master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(master.get(), 0) << "open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + int unlock = 0; + ASSERT_EQ(0, ioctl(master.get(), kTiocsptlck, &unlock)) + << "unlock slave failed: errno=" << errno << " (" << strerror(errno) << ")"; + + UniqueFd slave(ioctl(master.get(), kTiocgptpeer, O_RDWR | O_NOCTTY | O_CLOEXEC)); + ASSERT_GE(slave.get(), 0) << "TIOCGPTPEER failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + int fd_flags = fcntl(slave.get(), F_GETFD); + ASSERT_GE(fd_flags, 0) << "fcntl(F_GETFD) failed: errno=" << errno << " (" << strerror(errno) + << ")"; + EXPECT_NE(0, fd_flags & FD_CLOEXEC); + + struct termios term = {}; + ASSERT_EQ(0, tcgetattr(slave.get(), &term)) + << "tcgetattr(peer slave) failed: errno=" << errno << " (" << strerror(errno) << ")"; + term.c_iflag = 0; + term.c_oflag = 0; + term.c_lflag = 0; + term.c_cflag |= CS8; + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + ASSERT_EQ(0, tcsetattr(slave.get(), TCSANOW, &term)) + << "tcsetattr(peer slave) failed: errno=" << errno << " (" << strerror(errno) << ")"; + + ASSERT_EQ(1, write(slave.get(), "q", 1)) + << "write(peer slave) failed: errno=" << errno << " (" << strerror(errno) << ")"; + char ch = 0; + ASSERT_EQ(1, read(master.get(), &ch, 1)) + << "read(master) failed: errno=" << errno << " (" << strerror(errno) << ")"; + EXPECT_EQ('q', ch); +} + +TEST(TtyPtyHangup, TiocgptpeerOPathRejectsTtyOperations) { + UniqueFd master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(master.get(), 0) << "open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + UniqueFd peer(ioctl(master.get(), kTiocgptpeer, O_PATH | O_CLOEXEC)); + ASSERT_GE(peer.get(), 0) << "TIOCGPTPEER(O_PATH) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + char ch = 0; + errno = 0; + EXPECT_EQ(-1, read(peer.get(), &ch, 1)); + EXPECT_EQ(EBADF, errno) << "read on O_PATH peer should fail with EBADF, got errno=" << errno + << " (" << strerror(errno) << ")"; + + errno = 0; + EXPECT_EQ(-1, write(peer.get(), "x", 1)); + EXPECT_EQ(EBADF, errno) << "write on O_PATH peer should fail with EBADF, got errno=" << errno + << " (" << strerror(errno) << ")"; + + struct termios term = {}; + errno = 0; + EXPECT_EQ(-1, tcgetattr(peer.get(), &term)); + EXPECT_EQ(EBADF, errno) << "tcgetattr on O_PATH peer should fail with EBADF, got errno=" + << errno << " (" << strerror(errno) << ")"; + + uint32_t index = UINT32_MAX; + errno = 0; + EXPECT_EQ(-1, ioctl(peer.get(), kTiocgptn, &index)); + EXPECT_EQ(EBADF, errno) << "ioctl(TIOCGPTN) on O_PATH peer should fail with EBADF, got errno=" + << errno << " (" << strerror(errno) << ")"; +} + +TEST(TtyPtyHangup, TiocgptpeerOPathCloseDoesNotAffectRealPeerOpen) { + UniqueFd master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(master.get(), 0) << "open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + int unlock = 0; + ASSERT_EQ(0, ioctl(master.get(), kTiocsptlck, &unlock)) + << "unlock slave failed: errno=" << errno << " (" << strerror(errno) << ")"; + + UniqueFd path_peer(ioctl(master.get(), kTiocgptpeer, O_PATH | O_CLOEXEC)); + ASSERT_GE(path_peer.get(), 0) << "TIOCGPTPEER(O_PATH) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + path_peer.reset(); + + UniqueFd slave(ioctl(master.get(), kTiocgptpeer, O_RDWR | O_NOCTTY)); + ASSERT_GE(slave.get(), 0) << "TIOCGPTPEER real peer failed after closing O_PATH peer: errno=" + << errno << " (" << strerror(errno) << ")"; + + struct termios term = {}; + ASSERT_EQ(0, tcgetattr(slave.get(), &term)) + << "tcgetattr(real peer slave) failed: errno=" << errno << " (" << strerror(errno) + << ")"; + term.c_iflag = 0; + term.c_oflag = 0; + term.c_lflag = 0; + term.c_cflag |= CS8; + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + ASSERT_EQ(0, tcsetattr(slave.get(), TCSANOW, &term)) + << "tcsetattr(real peer slave) failed: errno=" << errno << " (" << strerror(errno) + << ")"; + + ASSERT_EQ(1, write(slave.get(), "p", 1)) + << "write(real peer slave) failed: errno=" << errno << " (" << strerror(errno) << ")"; + char ch = 0; + ASSERT_EQ(1, read(master.get(), &ch, 1)) + << "read(master) failed: errno=" << errno << " (" << strerror(errno) << ")"; + EXPECT_EQ('p', ch); +} + +TEST(TtyPtyHangup, TiocgptpeerOPathDoesNotKeepIndexReserved) { + UniqueFd master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(master.get(), 0) << "open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + uint32_t first_index = UINT32_MAX; + ASSERT_EQ(0, ioctl(master.get(), kTiocgptn, &first_index)) + << "TIOCGPTN failed: errno=" << errno << " (" << strerror(errno) << ")"; + + UniqueFd peer(ioctl(master.get(), kTiocgptpeer, O_PATH | O_CLOEXEC)); + ASSERT_GE(peer.get(), 0) << "TIOCGPTPEER(O_PATH) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + master.reset(); + + bool saw_reused_first_index = false; + std::vector masters; + for (uint32_t i = 0; i < 128; ++i) { + UniqueFd next(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + if (next.get() < 0) { + break; + } + + uint32_t next_index = UINT32_MAX; + ASSERT_EQ(0, ioctl(next.get(), kTiocgptn, &next_index)) + << "later TIOCGPTN failed: errno=" << errno << " (" << strerror(errno) << ")"; + if (next_index == first_index) { + saw_reused_first_index = true; + break; + } + + masters.push_back(std::move(next)); + } + + EXPECT_TRUE(saw_reused_first_index) + << "O_PATH TIOCGPTPEER peer should not keep the devpts index reserved"; +} + +TEST(TtyPtyHangup, TiocgptpeerSurvivesVisibleDevptsUnlink) { + UniqueFd master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(master.get(), 0) << "open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + uint32_t index = UINT32_MAX; + ASSERT_EQ(0, ioctl(master.get(), kTiocgptn, &index)) + << "TIOCGPTN failed: errno=" << errno << " (" << strerror(errno) << ")"; + + int unlock = 0; + ASSERT_EQ(0, ioctl(master.get(), kTiocsptlck, &unlock)) + << "unlock slave failed: errno=" << errno << " (" << strerror(errno) << ")"; + + std::string path = "/dev/pts/" + std::to_string(index); + ASSERT_EQ(0, unlink(path.c_str())) << "unlink(" << path << ") failed: errno=" << errno + << " (" << strerror(errno) << ")"; + + UniqueFd slave(ioctl(master.get(), kTiocgptpeer, O_RDWR | O_NOCTTY)); + ASSERT_GE(slave.get(), 0) << "TIOCGPTPEER after unlink failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + struct termios term = {}; + EXPECT_EQ(0, tcgetattr(slave.get(), &term)) + << "tcgetattr(peer slave) failed: errno=" << errno << " (" << strerror(errno) << ")"; +} + +TEST(TtyPtyHangup, MultipleTiocgptpeerSlaveFdsKeepIndexReserved) { + UniqueFd master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(master.get(), 0) << "open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + uint32_t first_index = UINT32_MAX; + ASSERT_EQ(0, ioctl(master.get(), kTiocgptn, &first_index)) + << "TIOCGPTN failed: errno=" << errno << " (" << strerror(errno) << ")"; + + int unlock = 0; + ASSERT_EQ(0, ioctl(master.get(), kTiocsptlck, &unlock)) + << "unlock slave failed: errno=" << errno << " (" << strerror(errno) << ")"; + + UniqueFd slave_a(ioctl(master.get(), kTiocgptpeer, O_RDWR | O_NOCTTY)); + ASSERT_GE(slave_a.get(), 0) << "first TIOCGPTPEER failed: errno=" << errno << " (" + << strerror(errno) << ")"; + UniqueFd slave_b(ioctl(master.get(), kTiocgptpeer, O_RDWR | O_NOCTTY)); + ASSERT_GE(slave_b.get(), 0) << "second TIOCGPTPEER failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + master.reset(); + slave_a.reset(); + + UniqueFd second_master(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + ASSERT_GE(second_master.get(), 0) << "second open(/dev/ptmx) failed: errno=" << errno << " (" + << strerror(errno) << ")"; + uint32_t second_index = UINT32_MAX; + ASSERT_EQ(0, ioctl(second_master.get(), kTiocgptn, &second_index)) + << "second TIOCGPTN failed: errno=" << errno << " (" << strerror(errno) << ")"; + EXPECT_NE(first_index, second_index) + << "pty index was reused while one TIOCGPTPEER slave fd was still alive"; + + second_master.reset(); + slave_b.reset(); + + bool saw_reused_first_index = false; + std::vector masters; + for (uint32_t i = 0; i < 128; ++i) { + UniqueFd next(open("/dev/ptmx", O_RDWR | O_NOCTTY)); + if (next.get() < 0) { + break; + } + uint32_t next_index = UINT32_MAX; + ASSERT_EQ(0, ioctl(next.get(), kTiocgptn, &next_index)) + << "later TIOCGPTN failed: errno=" << errno << " (" << strerror(errno) << ")"; + if (next_index == first_index) { + saw_reused_first_index = true; + break; + } + masters.push_back(std::move(next)); + } + EXPECT_TRUE(saw_reused_first_index) + << "pty index should be reusable after all TIOCGPTPEER slave fds close"; +} + TEST(TtyPtyHangup, ClosingOneOfMultipleSlaveFdsDoesNotHangupMaster) { PtyPair pair = OpenRawPty(); ASSERT_GE(pair.master.get(), 0); @@ -605,6 +1118,309 @@ TEST(TtyPtyHangup, ChildExitDrainsSlaveOutputBeforeMasterEio) { ExpectReadErrno(pair.master.get(), EIO); } +TEST(TtyPtyHangup, LargeOpostSlaveWriteDrainsAndPreservesOnlcr) { + PtyPair pair = OpenOpostPty(); + ASSERT_GE(pair.master.get(), 0); + ASSERT_GE(pair.slave.get(), 0); + + constexpr size_t kInputBytes = 32 * 1024; + std::string input(kInputBytes, '\n'); + std::vector output(kInputBytes * 2); + + WriteAllArgs args = { + .fd = pair.slave.get(), + .data = input.data(), + .len = input.size(), + .written = 0, + .error = 0, + }; + pthread_t writer = {}; + ASSERT_EQ(0, pthread_create(&writer, nullptr, WriteAll, &args)) << "pthread_create failed"; + + size_t total = 0; + int poll_error = 0; + bool read_ok = true; + while (total < output.size()) { + struct pollfd pfd = { + .fd = pair.master.get(), + .events = POLLIN | POLLHUP | POLLERR, + .revents = 0, + }; + int ret = poll(&pfd, 1, 5000); + if (ret < 0 && errno == EINTR) { + continue; + } + if (ret <= 0) { + poll_error = ret < 0 ? errno : ETIMEDOUT; + read_ok = false; + break; + } + if ((pfd.revents & POLLIN) == 0) { + poll_error = EIO; + read_ok = false; + break; + } + + ssize_t n = read(pair.master.get(), output.data() + total, output.size() - total); + if (n < 0 && errno == EINTR) { + continue; + } + if (n <= 0) { + poll_error = n < 0 ? errno : EIO; + read_ok = false; + break; + } + total += static_cast(n); + } + + if (!read_ok) { + pair.slave.reset(); + pair.master.reset(); + } + ASSERT_EQ(0, pthread_join(writer, nullptr)) << "pthread_join failed"; + ASSERT_TRUE(read_ok) << "timed out or failed while draining PTY output: errno=" << poll_error + << " (" << strerror(poll_error) << "), total=" << total + << ", expected=" << output.size() + << ", writer_written=" << args.written + << ", writer_errno=" << args.error; + ASSERT_EQ(0, args.error) << "writer failed after " << args.written << " bytes: errno=" + << args.error << " (" << strerror(args.error) << ")"; + ASSERT_EQ(input.size(), args.written); + ASSERT_EQ(output.size(), total); + + for (size_t i = 0; i < input.size(); ++i) { + EXPECT_EQ('\r', output[i * 2]) << "missing CR at converted newline " << i; + EXPECT_EQ('\n', output[i * 2 + 1]) << "missing LF at converted newline " << i; + } +} + +TEST(TtyPtyHangup, LargeRawSlaveWriteDrainsWithSmallMasterReads) { + PtyPair pair = OpenRawPty(); + ASSERT_GE(pair.master.get(), 0); + ASSERT_GE(pair.slave.get(), 0); + + constexpr size_t kInputBytes = 32 * 1024; + std::string input(kInputBytes, 'x'); + std::vector output(kInputBytes); + + WriteAllArgs args = { + .fd = pair.slave.get(), + .data = input.data(), + .len = input.size(), + .written = 0, + .error = 0, + }; + pthread_t writer = {}; + ASSERT_EQ(0, pthread_create(&writer, nullptr, WriteAll, &args)) << "pthread_create failed"; + + size_t total = 0; + int poll_error = 0; + bool read_ok = true; + while (total < output.size()) { + struct pollfd pfd = { + .fd = pair.master.get(), + .events = POLLIN | POLLHUP | POLLERR, + .revents = 0, + }; + int ret = poll(&pfd, 1, 5000); + if (ret < 0 && errno == EINTR) { + continue; + } + if (ret <= 0) { + poll_error = ret < 0 ? errno : ETIMEDOUT; + read_ok = false; + break; + } + if ((pfd.revents & POLLIN) == 0) { + poll_error = EIO; + read_ok = false; + break; + } + + const size_t chunk = std::min(257, output.size() - total); + ssize_t n = read(pair.master.get(), output.data() + total, chunk); + if (n < 0 && errno == EINTR) { + continue; + } + if (n <= 0) { + poll_error = n < 0 ? errno : EIO; + read_ok = false; + break; + } + total += static_cast(n); + } + + if (!read_ok) { + pair.slave.reset(); + pair.master.reset(); + } + ASSERT_EQ(0, pthread_join(writer, nullptr)) << "pthread_join failed"; + ASSERT_TRUE(read_ok) << "timed out or failed while draining raw PTY output: errno=" + << poll_error << " (" << strerror(poll_error) << "), total=" << total + << ", expected=" << output.size() + << ", writer_written=" << args.written + << ", writer_errno=" << args.error; + ASSERT_EQ(0, args.error) << "writer failed after " << args.written << " bytes: errno=" + << args.error << " (" << strerror(args.error) << ")"; + ASSERT_EQ(input.size(), args.written); + ASSERT_EQ(output.size(), total); + EXPECT_EQ(input, std::string(output.begin(), output.end())); +} + +TEST(TtyPtyHangup, LargeCanonicalMasterWriteDrainsWithSmallSlaveReads) { + PtyPair pair = OpenCanonicalNoEchoPty(); + ASSERT_GE(pair.master.get(), 0); + ASSERT_GE(pair.slave.get(), 0); + + std::string input; + for (int i = 0; i < 64; ++i) { + input.append(512, static_cast('a' + (i % 26))); + input.push_back('\n'); + } + std::vector output(input.size()); + + WriteAllArgs args = { + .fd = pair.master.get(), + .data = input.data(), + .len = input.size(), + .written = 0, + .error = 0, + }; + pthread_t writer = {}; + ASSERT_EQ(0, pthread_create(&writer, nullptr, WriteAll, &args)) << "pthread_create failed"; + + size_t total = 0; + int poll_error = 0; + bool read_ok = true; + while (total < output.size()) { + struct pollfd pfd = { + .fd = pair.slave.get(), + .events = POLLIN | POLLHUP | POLLERR, + .revents = 0, + }; + int ret = poll(&pfd, 1, 5000); + if (ret < 0 && errno == EINTR) { + continue; + } + if (ret <= 0) { + poll_error = ret < 0 ? errno : ETIMEDOUT; + read_ok = false; + break; + } + if ((pfd.revents & POLLIN) == 0) { + poll_error = EIO; + read_ok = false; + break; + } + + const size_t chunk = std::min(257, output.size() - total); + ssize_t n = read(pair.slave.get(), output.data() + total, chunk); + if (n < 0 && errno == EINTR) { + continue; + } + if (n <= 0) { + poll_error = n < 0 ? errno : EIO; + read_ok = false; + break; + } + total += static_cast(n); + } + + if (!read_ok) { + pair.slave.reset(); + pair.master.reset(); + } + ASSERT_EQ(0, pthread_join(writer, nullptr)) << "pthread_join failed"; + ASSERT_TRUE(read_ok) << "timed out or failed while draining canonical PTY input: errno=" + << poll_error << " (" << strerror(poll_error) << "), total=" << total + << ", expected=" << output.size() + << ", writer_written=" << args.written + << ", writer_errno=" << args.error; + ASSERT_EQ(0, args.error) << "writer failed after " << args.written << " bytes: errno=" + << args.error << " (" << strerror(args.error) << ")"; + ASSERT_EQ(input.size(), args.written); + ASSERT_EQ(output.size(), total); + EXPECT_EQ(input, std::string(output.begin(), output.end())); +} + +TEST(TtyPtyHangup, TciflushDoesNotDiscardLargeOpostSlaveOutput) { + PtyPair pair = OpenOpostPty(); + ASSERT_GE(pair.master.get(), 0); + ASSERT_GE(pair.slave.get(), 0); + + constexpr size_t kInputLines = 24 * 1024; + std::string input(kInputLines, '\n'); + std::vector output(input.size() * 2); + + WriteAllArgs args = { + .fd = pair.slave.get(), + .data = input.data(), + .len = input.size(), + .written = 0, + .error = 0, + }; + pthread_t writer = {}; + ASSERT_EQ(0, pthread_create(&writer, nullptr, WriteAll, &args)) << "pthread_create failed"; + + ASSERT_EQ(0, tcflush(pair.slave.get(), TCIFLUSH)) + << "tcflush(TCIFLUSH) failed: errno=" << errno << " (" << strerror(errno) << ")"; + + size_t total = 0; + int poll_error = 0; + bool read_ok = true; + while (total < output.size()) { + struct pollfd pfd = { + .fd = pair.master.get(), + .events = POLLIN | POLLHUP | POLLERR, + .revents = 0, + }; + int ret = poll(&pfd, 1, 5000); + if (ret < 0 && errno == EINTR) { + continue; + } + if (ret <= 0) { + poll_error = ret < 0 ? errno : ETIMEDOUT; + read_ok = false; + break; + } + if ((pfd.revents & POLLIN) == 0) { + poll_error = EIO; + read_ok = false; + break; + } + + ssize_t n = read(pair.master.get(), output.data() + total, output.size() - total); + if (n < 0 && errno == EINTR) { + continue; + } + if (n <= 0) { + poll_error = n < 0 ? errno : EIO; + read_ok = false; + break; + } + total += static_cast(n); + } + + if (!read_ok) { + pair.slave.reset(); + pair.master.reset(); + } + ASSERT_EQ(0, pthread_join(writer, nullptr)) << "pthread_join failed"; + ASSERT_TRUE(read_ok) << "timed out or failed after TCIFLUSH while draining PTY output: errno=" + << poll_error << " (" << strerror(poll_error) << "), total=" << total + << ", expected=" << output.size() + << ", writer_written=" << args.written + << ", writer_errno=" << args.error; + ASSERT_EQ(0, args.error) << "writer failed after " << args.written << " bytes: errno=" + << args.error << " (" << strerror(args.error) << ")"; + ASSERT_EQ(input.size(), args.written); + ASSERT_EQ(output.size(), total); + for (size_t i = 0; i < output.size(); i += 2) { + EXPECT_EQ('\r', output[i]); + EXPECT_EQ('\n', output[i + 1]); + } +} + } // namespace int main(int argc, char** argv) { diff --git a/user/apps/tests/dunitest/whitelist.txt b/user/apps/tests/dunitest/whitelist.txt index ea801a0497..54119d5028 100644 --- a/user/apps/tests/dunitest/whitelist.txt +++ b/user/apps/tests/dunitest/whitelist.txt @@ -44,6 +44,7 @@ normal/proc_self_exec_cmdline normal/proc_thread_accounting normal/user_namespace_id_map normal/tty_tcflush +normal/hvc_console_backpressure normal/signal_fpstate_sigreturn normal/general_protection_signal normal/ioperm_semantics diff --git a/user/apps/tests/syscall/gvisor/whitelist.txt b/user/apps/tests/syscall/gvisor/whitelist.txt index 4d10b08b84..306590849e 100644 --- a/user/apps/tests/syscall/gvisor/whitelist.txt +++ b/user/apps/tests/syscall/gvisor/whitelist.txt @@ -138,6 +138,7 @@ pipe_test fifo_test unshare_test pty_root_test +pty_test time_test timers_test clock_nanosleep_test