From d74674ca1d7687bece05cd0c2a069a484c62d901 Mon Sep 17 00:00:00 2001 From: longjin Date: Sun, 21 Jun 2026 07:22:14 +0000 Subject: [PATCH 1/7] fix(tty): implement TIOCGPTPEER and correct devpts slave lifecycle TIOCGPTPEER must open the peer inode cached on the master rather than re-resolving /dev/pts/N after unlink. Track multiple slave fds with a counter and add fd-table reservation so ioctl can install the new fd atomically. Add dunitest coverage for lock, unlink, and index reuse. Signed-off-by: longjin Co-authored-by: Cursor --- kernel/src/driver/tty/pty/unix98pty.rs | 86 +++++++++-- kernel/src/driver/tty/tty_core.rs | 2 + kernel/src/driver/tty/tty_device.rs | 32 +++- kernel/src/filesystem/vfs/file.rs | 134 +++++++++++++++- .../src/filesystem/vfs/syscall/sys_fcntl.rs | 2 +- .../dunitest/suites/normal/tty_pty_hangup.cc | 145 ++++++++++++++++++ 6 files changed, 378 insertions(+), 23 deletions(-) diff --git a/kernel/src/driver/tty/pty/unix98pty.rs b/kernel/src/driver/tty/pty/unix98pty.rs index f2872e882a..528a875b61 100644 --- a/kernel/src/driver/tty/pty/unix98pty.rs +++ b/kernel/src/driver/tty/pty/unix98pty.rs @@ -53,6 +53,9 @@ 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, } @@ -63,8 +66,8 @@ 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 后允许归还)。 @@ -78,10 +81,16 @@ 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()), } @@ -96,7 +105,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 +134,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 +192,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 +218,10 @@ impl PtyDevPtsLink { devpts.free_index(self.index); } } + + fn slave_inode(&self) -> Result, SystemError> { + Ok(self.slave_inode.clone()) + } } #[derive(Debug)] @@ -526,17 +555,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 +576,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 +595,30 @@ 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(); + } + } +} diff --git a/kernel/src/driver/tty/tty_core.rs b/kernel/src/driver/tty/tty_core.rs index ae70937145..9b7d4b24e9 100644 --- a/kernel/src/driver/tty/tty_core.rs +++ b/kernel/src/driver/tty/tty_core.rs @@ -869,6 +869,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..4fd2679b38 100644 --- a/kernel/src/driver/tty/tty_device.rs +++ b/kernel/src/driver/tty/tty_device.rs @@ -31,8 +31,9 @@ use crate::{ epoll::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}, @@ -443,7 +444,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 +483,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/filesystem/vfs/file.rs b/kernel/src/filesystem/vfs/file.rs index 984bfcfa3c..7f3ca7eebd 100644 --- a/kernel/src/filesystem/vfs/file.rs +++ b/kernel/src/filesystem/vfs/file.rs @@ -1683,6 +1683,11 @@ impl DroppedFd { } } +#[derive(Debug, Clone, Copy)] +pub struct ReservedFd { + fd: i32, +} + /// @brief pcb里面的文件描述符数组 #[derive(Debug)] pub struct FileDescriptorVec { @@ -1690,6 +1695,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 +1722,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 +1754,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 +1802,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 +1820,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 +1851,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 +1912,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 +1930,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 +1963,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 +2071,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/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc index 5b0cb2857a..3258b2eb38 100644 --- a/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc +++ b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc @@ -13,6 +13,9 @@ #include #include +#include +#include + namespace { #ifndef TIOCPKT @@ -27,6 +30,18 @@ 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 TIOCPKT_FLUSHWRITE constexpr unsigned char kTiocpktFlushWrite = 2; #else @@ -498,6 +513,136 @@ TEST(TtyPtyHangup, MasterOnlyCloseReleasesDevptsIndex) { EXPECT_TRUE(saw_reuse) << "master-only open/close did not visibly reuse any devpts index"; } +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, 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, 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); From 77ce34edc722c06d620db79fb21596e1f93ba627 Mon Sep 17 00:00:00 2001 From: longjin Date: Sun, 21 Jun 2026 12:35:15 +0000 Subject: [PATCH 2/7] fix(tty): align pty lifecycle with Linux semantics Signed-off-by: longjin --- kernel/src/arch/x86_64/ipc/signal.rs | 12 +- kernel/src/driver/tty/pty/mod.rs | 3 - kernel/src/driver/tty/pty/unix98pty.rs | 133 +++++++- kernel/src/driver/tty/termios.rs | 78 +++-- kernel/src/driver/tty/tty_core.rs | 9 + kernel/src/driver/tty/tty_job_control.rs | 50 ++- kernel/src/driver/tty/tty_ldisc/ntty.rs | 296 +++++++++--------- kernel/src/filesystem/vfs/file.rs | 114 +++---- kernel/src/ipc/generic_signal.rs | 3 + kernel/src/ipc/sighand.rs | 12 + kernel/src/ipc/signal.rs | 6 + kernel/src/ipc/syscall/sys_rt_sigsuspend.rs | 12 +- kernel/src/process/exit.rs | 10 +- kernel/src/process/mod.rs | 36 ++- .../dunitest/suites/normal/tty_pty_hangup.cc | 156 +++++++++ user/apps/tests/syscall/gvisor/whitelist.txt | 1 + 16 files changed, 651 insertions(+), 280 deletions(-) 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/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 528a875b61..55db4f8083 100644 --- a/kernel/src/driver/tty/pty/unix98pty.rs +++ b/kernel/src/driver/tty/pty/unix98pty.rs @@ -1,6 +1,8 @@ use alloc::{ + collections::VecDeque, string::ToString, sync::{Arc, Weak}, + vec::Vec, }; use system_error::SystemError; @@ -30,6 +32,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() @@ -58,6 +62,8 @@ struct PtyDevPtsLink { slave_inode: Arc, index: usize, state: Mutex, + master_to_slave: Mutex>, + slave_to_master: Mutex>, } #[derive(Debug, Default)] @@ -93,9 +99,101 @@ impl PtyDevPtsLink { slave_inode, index, state: Mutex::new(PtyDevPtsState::default()), + master_to_slave: Mutex::new(VecDeque::new()), + slave_to_master: Mutex::new(VecDeque::new()), } } + fn queue_for_source(&self, subtype: TtyDriverSubType) -> Option<&Mutex>> { + match subtype { + TtyDriverSubType::PtyMaster => Some(&self.master_to_slave), + TtyDriverSubType::PtySlave => Some(&self.slave_to_master), + _ => None, + } + } + + fn pending_write_room(&self, subtype: TtyDriverSubType) -> usize { + self.queue_for_source(subtype) + .map(|queue| PTY_BUFFER_LIMIT.saturating_sub(queue.lock().len())) + .unwrap_or(0) + } + + fn clear_pending_from(&self, subtype: TtyDriverSubType) { + if let Some(queue) = self.queue_for_source(subtype) { + queue.lock().clear(); + } + } + + 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 accepted = { + let mut queue = queue.lock(); + let room = PTY_BUFFER_LIMIT.saturating_sub(queue.len()); + let accepted = nr.min(room); + for c in &buf[..accepted] { + queue.push_back(*c); + } + accepted + }; + + if accepted != 0 { + self.drain_to_peer(subtype, to)?; + } + + Ok(accepted) + } + + fn drain_to_peer( + &self, + subtype: TtyDriverSubType, + to: Arc, + ) -> Result<(), SystemError> { + let Some(queue) = self.queue_for_source(subtype) else { + return Err(SystemError::ENODEV); + }; + + loop { + let mut chunk = Vec::new(); + { + let mut queue = queue.lock(); + while chunk.len() < PTY_DRAIN_CHUNK { + let Some(c) = queue.pop_front() else { + break; + }; + chunk.push(c); + } + } + + if chunk.is_empty() { + break; + } + + let delivered = to + .core() + .port() + .unwrap() + .receive_buf(&chunk, &[], chunk.len())?; + if delivered < chunk.len() { + let mut queue = queue.lock(); + for c in chunk[delivered..].iter().rev() { + queue.push_front(*c); + } + break; + } + } + + Ok(()) + } + fn on_close(&self, subtype: TtyDriverSubType) { match subtype { TtyDriverSubType::PtyMaster => { @@ -271,16 +369,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> { @@ -288,6 +397,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 @@ -622,3 +737,17 @@ pub fn pty_file_close(tty: &TtyCore) { } } } + +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(()); + }; + + hook.drain_to_peer(peer.core().driver().tty_driver_sub_type(), tty) +} 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 9b7d4b24e9..4582359c3d 100644 --- a/kernel/src/driver/tty/tty_core.rs +++ b/kernel/src/driver/tty/tty_core.rs @@ -195,6 +195,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); } 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/ntty.rs b/kernel/src/driver/tty/tty_ldisc/ntty.rs index a1483f1019..9fe199214f 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,6 +10,7 @@ use system_error::SystemError; use crate::{ arch::ipc::signal::Signal, driver::tty::{ + pty::unix98pty::pty_drain_pending_to, termios::{ControlCharIndex, InputMode, LocalMode, OutputMode, Termios}, tty_core::{EchoOperation, TtyCore, TtyCoreData, TtyFlag, TtyIoctlCmd, TtyPacketStatus}, tty_driver::{TtyDriverFlag, TtyDriverSubType, TtyOperation}, @@ -35,6 +36,10 @@ fn ntty_buf_mask(idx: usize) -> usize { return idx & (NTTY_BUFSIZE - 1); } +fn is_ascii_control(c: u8) -> bool { + c < b' ' || c == 0x7f +} + #[derive(Debug)] pub struct NTtyLinediscipline { pub data: SpinLock, @@ -177,42 +182,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; @@ -609,6 +609,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 +632,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 +672,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 +683,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' '); @@ -845,9 +849,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()); } @@ -1165,85 +1167,6 @@ 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()); - - // 如果读取数量大于了可用空间,则取最小的为真正的写入数量 - if nr > space { - nr = space - } - - 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; - } - 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; - } - '\r' => { - if termios.output_mode.contains(OutputMode::ONOCR) && self.cursor_column == 0 { - // 光标已经在第0列,则不输出回车符 - break; - } - - if termios.output_mode.contains(OutputMode::OCRNL) { - break; - } - self.cursor_column = 0; - self.canon_cursor_column = 0; - } - '\t' => { - break; - } - _ => { - // 判断是否为控制字符 - if !(c as char).is_control() { - if termios.output_mode.contains(OutputMode::OLCUC) { - break; - } - - // 判断是否为utf8模式下的连续字符 - if !(termios.input_mode.contains(InputMode::IUTF8) - && (c as usize) & 0xc0 == 0x80) - { - self.cursor_column += 1; - } - } - } - } - } - - drop(termios); - return tty.write(core, buf, cnt); - } - /// ## 处理回显 pub fn process_echoes(&mut self, tty: Arc) { if self.echo_mark == self.echo_tail { @@ -1418,17 +1341,6 @@ impl NTtyData { 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 - } - // ## 设置带有 OPOST 处理的tty输出一个字符 pub fn do_output_char( &mut self, @@ -1513,7 +1425,7 @@ impl NTtyData { } _ => { // 判断是否为控制字符 - if !(c as char).is_control() { + if !is_ascii_control(c) { if termios.output_mode.contains(OutputMode::OLCUC) { c = c.to_ascii_uppercase(); } @@ -1534,6 +1446,91 @@ impl NTtyData { Ok(1) } + fn process_output_char_to_buf( + &mut self, + termios: &Termios, + mut c: u8, + out: &mut Vec, + space: usize, + ) -> Result { + let used = out.len(); + if used >= space { + return Err(SystemError::ENOBUFS); + } + + if c as usize == 8 { + if self.cursor_column > 0 { + self.cursor_column -= 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) { + if used + 2 > space { + return Err(SystemError::ENOBUFS); + } + self.cursor_column = 0; + self.canon_cursor_column = 0; + 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 { + return Ok(0); + } + + if termios.output_mode.contains(OutputMode::OCRNL) { + c = b'\n'; + if termios.output_mode.contains(OutputMode::ONLRET) { + self.cursor_column = 0; + self.canon_cursor_column = 0; + } + } else { + self.cursor_column = 0; + self.canon_cursor_column = 0; + } + } + '\t' => { + let spaces = 8 - (self.cursor_column & 7) as usize; + if termios.output_mode.contains(OutputMode::TABDLY) + && OutputMode::TABDLY.bits() == OutputMode::XTABS.bits() + { + if used + spaces > space { + return Err(SystemError::ENOBUFS); + } + self.cursor_column += spaces as u32; + out.extend_from_slice(&b" "[..spaces]); + return Ok(spaces); + } + self.cursor_column += spaces as u32; + } + _ => { + if !is_ascii_control(c) { + if termios.output_mode.contains(OutputMode::OLCUC) { + c = c.to_ascii_uppercase(); + } + + if !(termios.input_mode.contains(InputMode::IUTF8) + && (c as usize) & 0xc0 == 0x80) + { + self.cursor_column += 1; + } + } + } + } + + out.push(c); + Ok(1) + } + fn packet_mode_flush(&self, tty: &TtyCoreData) { let link = tty.link().unwrap(); if link.core().contorl_info_irqsave().packet { @@ -1747,8 +1744,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,7 +1766,8 @@ impl TtyLineDiscipline for NTtyLinediscipline { _flags: FileFlags, ) -> Result { let mut nr = len; - let mut ldata = self.disc_data(); + let mut out_buf = Vec::with_capacity(len.saturating_mul(2).min(NTTY_BUFSIZE)); + let mut ldata = Some(self.disc_data()); let pcb = ProcessManager::current_pcb(); let binding = tty.clone(); let core = binding.core(); @@ -1776,7 +1776,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { TtyJobCtrlManager::tty_check_change(tty.clone(), Signal::SIGTTOU)?; } - ldata.process_echoes(tty.clone()); + ldata.as_mut().unwrap().process_echoes(tty.clone()); // drop(ldata); let mut offset = 0; loop { @@ -1791,38 +1791,44 @@ impl TtyLineDiscipline for NTtyLinediscipline { 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 { + out_buf.clear(); + let space = tty.write_room(core); + let mut consumed = 0; + { + let ldata = ldata.as_mut().unwrap(); + while consumed < nr { + let c = buf[offset + consumed]; + match ldata.process_output_char_to_buf(&termios, c, &mut out_buf, space) { + Ok(_) => { + consumed += 1; + } + Err(SystemError::ENOBUFS) => { break; - } else { - return Err(e); + } + Err(err) => { + return Err(err); } } - }; + } + } - offset += num; - nr -= num; + drop(ldata.take()); - if nr == 0 { - break; + if !out_buf.is_empty() { + let written = tty.write(core, &out_buf, out_buf.len())?; + if written == 0 { + consumed = 0; } + } - let c = buf[offset]; - if !ldata.process_output(tty.clone(), c) { - break; - } - offset += 1; - nr -= 1; + if consumed != 0 { + offset += consumed; + nr -= consumed; } tty.flush_chars(core); } else { + drop(ldata.take()); while nr > 0 { let write = tty.write(core, &buf[offset..], nr)?; if write == 0 { @@ -1846,8 +1852,12 @@ impl TtyLineDiscipline for NTtyLinediscipline { // 到这里表明没位置可写了 // 休眠一段时间 // 获取到termios读锁,避免termios被更改导致行为异常 + drop(ldata.take()); core.write_wq() .sleep(EPollEventType::EPOLLOUT.bits() as u64); + if termios.output_mode.contains(OutputMode::OPOST) { + ldata = Some(self.disc_data()); + } } Ok(offset) @@ -1940,8 +1950,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 { diff --git a/kernel/src/filesystem/vfs/file.rs b/kernel/src/filesystem/vfs/file.rs index 7f3ca7eebd..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)?; - - // 设置默认能力(由 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); - } + if is_path { + mode = FileMode::FMODE_PATH | FileMode::FMODE_OPENED; + } else { + inode.open(private_data.lock(), &flags)?; - inode.adjust_file_mode_after_open(&private_data.lock(), &mut mode); + // 设置默认能力(由 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); + } - // 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); - } + inode.adjust_file_mode_after_open(&private_data.lock(), &mut mode); - // 标记为已打开 - 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 + ); + } } } } 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/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/tty_pty_hangup.cc b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc index 3258b2eb38..ea56ff2a9b 100644 --- a/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc +++ b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,10 @@ constexpr int kTiocgptpeer = 0x5441; constexpr int kTiocgptpeer = TIOCGPTPEER; #endif +#ifndef O_PATH +#define O_PATH 010000000 +#endif + #ifndef TIOCPKT_FLUSHWRITE constexpr unsigned char kTiocpktFlushWrite = 2; #else @@ -513,6 +518,31 @@ 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 << " (" @@ -524,6 +554,21 @@ TEST(TtyPtyHangup, TiocgptpeerFailsWhileSlaveLocked) { << " (" << 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 << " (" @@ -562,6 +607,117 @@ TEST(TtyPtyHangup, TiocgptpeerOpensUnlockedSlave) { 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 << " (" 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 From bf99e04402389b11249bd35ac4852c38659a4155 Mon Sep 17 00:00:00 2001 From: longjin Date: Sun, 21 Jun 2026 13:50:59 +0000 Subject: [PATCH 3/7] fix(tty): wake pty writers after backlog drain Signed-off-by: longjin --- kernel/src/driver/tty/pty/unix98pty.rs | 4 +- kernel/src/driver/tty/tty_ldisc/ntty.rs | 133 +++++++++++++---- .../dunitest/suites/normal/tty_pty_hangup.cc | 135 ++++++++++++++++++ 3 files changed, 242 insertions(+), 30 deletions(-) diff --git a/kernel/src/driver/tty/pty/unix98pty.rs b/kernel/src/driver/tty/pty/unix98pty.rs index 55db4f8083..a3dd201ebd 100644 --- a/kernel/src/driver/tty/pty/unix98pty.rs +++ b/kernel/src/driver/tty/pty/unix98pty.rs @@ -749,5 +749,7 @@ pub fn pty_drain_pending_to(tty: Arc) -> Result<(), SystemError> { return Ok(()); }; - hook.drain_to_peer(peer.core().driver().tty_driver_sub_type(), tty) + hook.drain_to_peer(peer.core().driver().tty_driver_sub_type(), tty)?; + peer.tty_wakeup(); + Ok(()) } diff --git a/kernel/src/driver/tty/tty_ldisc/ntty.rs b/kernel/src/driver/tty/tty_ldisc/ntty.rs index 9fe199214f..b3fafd745f 100644 --- a/kernel/src/driver/tty/tty_ldisc/ntty.rs +++ b/kernel/src/driver/tty/tty_ldisc/ntty.rs @@ -115,6 +115,9 @@ pub struct NTtyData { cursor_column: u32, /// 规范模式下光标所在列 canon_cursor_column: u32, + /// OPOST 处理后尚未被底层 driver 接收的输出字节。 + opost_pending: Vec, + opost_pending_offset: usize, /// 回显缓冲区的尾指针 echo_tail: usize, @@ -150,6 +153,8 @@ impl NTtyData { echo: false, cursor_column: 0, canon_cursor_column: 0, + opost_pending: Vec::new(), + 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(), @@ -160,6 +165,28 @@ 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 save_opost_pending(&mut self, bytes: &[u8]) { + self.opost_pending.clear(); + self.opost_pending.extend_from_slice(bytes); + self.opost_pending_offset = 0; + } + + 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; + } + } + #[inline] pub fn read_cnt(&self) -> usize { self.read_head - self.read_tail @@ -1570,6 +1597,8 @@ impl TtyLineDiscipline for NTtyLinediscipline { ldata.pushing = false; ldata.lookahead_count = 0; ldata.no_room = false; + ldata.opost_pending.clear(); + ldata.opost_pending_offset = 0; if core.link().is_some() { ldata.packet_mode_flush(core); @@ -1791,42 +1820,83 @@ impl TtyLineDiscipline for NTtyLinediscipline { return Err(SystemError::EIO); } if termios.output_mode.contains(OutputMode::OPOST) { - out_buf.clear(); - let space = tty.write_room(core); - let mut consumed = 0; - { - let ldata = ldata.as_mut().unwrap(); - while consumed < nr { - let c = buf[offset + consumed]; - match ldata.process_output_char_to_buf(&termios, c, &mut out_buf, space) { - Ok(_) => { - consumed += 1; - } - Err(SystemError::ENOBUFS) => { - break; - } - Err(err) => { - return Err(err); + let mut made_progress = false; + let pending = ldata.as_ref().unwrap().opost_pending_bytes().to_vec(); + + if !pending.is_empty() { + drop(ldata.take()); + let written = tty.write(core, &pending, pending.len())?; + let mut guard = self.disc_data(); + if written != 0 { + guard.advance_opost_pending(written); + made_progress = true; + } + ldata = Some(guard); + if written != 0 { + tty.flush_chars(core); + } + } else { + out_buf.clear(); + let space = tty.write_room(core); + let mut consumed = 0; + let (cursor_column, canon_cursor_column) = { + let ldata = ldata.as_ref().unwrap(); + (ldata.cursor_column, ldata.canon_cursor_column) + }; + + { + let ldata = ldata.as_mut().unwrap(); + while consumed < nr { + let c = buf[offset + consumed]; + match ldata.process_output_char_to_buf(&termios, c, &mut out_buf, space) + { + Ok(_) => { + consumed += 1; + } + Err(SystemError::ENOBUFS) => { + break; + } + Err(err) => { + return Err(err); + } } } } - } - - drop(ldata.take()); - if !out_buf.is_empty() { - let written = tty.write(core, &out_buf, out_buf.len())?; - if written == 0 { - consumed = 0; + drop(ldata.take()); + + if !out_buf.is_empty() { + let written = tty.write(core, &out_buf, out_buf.len())?; + let mut guard = self.disc_data(); + if written == 0 { + guard.cursor_column = cursor_column; + guard.canon_cursor_column = canon_cursor_column; + } else { + if written < out_buf.len() { + guard.save_opost_pending(&out_buf[written..]); + } + offset += consumed; + nr -= consumed; + made_progress = true; + } + ldata = Some(guard); + if written != 0 { + tty.flush_chars(core); + } + } else { + ldata = Some(self.disc_data()); + if consumed != 0 { + offset += consumed; + nr -= consumed; + made_progress = true; + tty.flush_chars(core); + } } } - if consumed != 0 { - offset += consumed; - nr -= consumed; + if made_progress { + continue; } - - tty.flush_chars(core); } else { drop(ldata.take()); while nr > 0 { @@ -1839,13 +1909,18 @@ impl TtyLineDiscipline for NTtyLinediscipline { } } - if nr == 0 { + let opost_pending = termios.output_mode.contains(OutputMode::OPOST) + && !ldata.as_ref().unwrap().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); } 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 ea56ff2a9b..00ddceea8e 100644 --- a/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc +++ b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc @@ -127,6 +127,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; @@ -198,6 +228,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; @@ -906,6 +968,79 @@ 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) << ")"; + 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; + } +} + } // namespace int main(int argc, char** argv) { From fcbaf1ca813d9859fb517c56b17b41518ffa6055 Mon Sep 17 00:00:00 2001 From: longjin Date: Mon, 22 Jun 2026 03:29:48 +0000 Subject: [PATCH 4/7] fix(tty): harden pty backlog drain semantics Signed-off-by: longjin --- kernel/src/driver/tty/pty/unix98pty.rs | 278 +++++++++++++++--- kernel/src/driver/tty/tty_core.rs | 4 + kernel/src/driver/tty/tty_ldisc/mod.rs | 3 + kernel/src/driver/tty/tty_ldisc/ntty.rs | 158 +++++++--- kernel/src/libs/wait_queue.rs | 78 ++++- .../dunitest/suites/normal/tty_pty_hangup.cc | 233 ++++++++++++++- 6 files changed, 668 insertions(+), 86 deletions(-) diff --git a/kernel/src/driver/tty/pty/unix98pty.rs b/kernel/src/driver/tty/pty/unix98pty.rs index a3dd201ebd..f7163144bd 100644 --- a/kernel/src/driver/tty/pty/unix98pty.rs +++ b/kernel/src/driver/tty/pty/unix98pty.rs @@ -1,8 +1,12 @@ use alloc::{ - collections::VecDeque, + boxed::Box, string::ToString, sync::{Arc, Weak}, - vec::Vec, + vec, +}; +use core::{ + hint::spin_loop, + sync::atomic::{AtomicBool, Ordering}, }; use system_error::SystemError; @@ -23,6 +27,7 @@ use crate::{ libs::{ casting::DowncastArc, mutex::{Mutex, MutexGuard}, + spinlock::SpinLock, }, mm::VirtAddr, process::ProcessManager, @@ -62,8 +67,14 @@ struct PtyDevPtsLink { slave_inode: Arc, index: usize, state: Mutex, - master_to_slave: Mutex>, - slave_to_master: 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_flushing: AtomicBool, + slave_to_master_flushing: AtomicBool, } #[derive(Debug, Default)] @@ -80,6 +91,73 @@ struct PtyDevPtsState { 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, +} + +impl PtyByteQueue { + fn new() -> Self { + Self { + buf: vec![0; PTY_BUFFER_LIMIT] + .into_boxed_slice() + .try_into() + .unwrap(), + head: 0, + len: 0, + } + } + + fn is_empty(&self) -> bool { + self.len == 0 + } + + fn room(&self) -> usize { + PTY_BUFFER_LIMIT - self.len + } + + fn clear(&mut self) { + self.head = 0; + self.len = 0; + } + + 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; + if self.len == 0 { + self.head = 0; + } + } +} + impl crate::driver::tty::tty_driver::TtyCorePrivateField for PtyDevPtsLink { fn as_any(&self) -> &dyn core::any::Any { self @@ -99,12 +177,18 @@ impl PtyDevPtsLink { slave_inode, index, state: Mutex::new(PtyDevPtsState::default()), - master_to_slave: Mutex::new(VecDeque::new()), - slave_to_master: Mutex::new(VecDeque::new()), + 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_flushing: AtomicBool::new(false), + slave_to_master_flushing: AtomicBool::new(false), } } - fn queue_for_source(&self, subtype: TtyDriverSubType) -> Option<&Mutex>> { + 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), @@ -112,16 +196,59 @@ impl PtyDevPtsLink { } } + fn state_flags_for_source( + &self, + subtype: TtyDriverSubType, + ) -> Option<(&AtomicBool, &AtomicBool, &AtomicBool)> { + match subtype { + TtyDriverSubType::PtyMaster => Some(( + &self.master_to_slave_draining, + &self.master_to_slave_drain_requested, + &self.master_to_slave_flushing, + )), + TtyDriverSubType::PtySlave => Some(( + &self.slave_to_master_draining, + &self.slave_to_master_drain_requested, + &self.slave_to_master_flushing, + )), + _ => None, + } + } + fn pending_write_room(&self, subtype: TtyDriverSubType) -> usize { self.queue_for_source(subtype) - .map(|queue| PTY_BUFFER_LIMIT.saturating_sub(queue.lock().len())) + .map(|queue| queue.lock_irqsave().room()) .unwrap_or(0) } - fn clear_pending_from(&self, subtype: TtyDriverSubType) { - if let Some(queue) = self.queue_for_source(subtype) { - queue.lock().clear(); + 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, 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); + queue.lock_irqsave().clear(); + draining.store(false, Ordering::Release); + flushing.store(false, Ordering::Release); + Ok(()) } fn write_to_peer( @@ -134,17 +261,27 @@ impl PtyDevPtsLink { let Some(queue) = self.queue_for_source(subtype) else { return Err(SystemError::ENODEV); }; + let Some((_, _, flushing)) = self.state_flags_for_source(subtype) else { + return Err(SystemError::ENODEV); + }; - let accepted = { - let mut queue = queue.lock(); - let room = PTY_BUFFER_LIMIT.saturating_sub(queue.len()); - let accepted = nr.min(room); - for c in &buf[..accepted] { - queue.push_back(*c); - } - accepted + while flushing.load(Ordering::Acquire) { + spin_loop(); + } + + let mut accepted = { + let mut queue = queue.lock_irqsave(); + queue.push_slice(&buf[..nr]) }; + if accepted == 0 { + self.drain_to_peer(subtype, to.clone())?; + accepted = { + let mut queue = queue.lock_irqsave(); + queue.push_slice(&buf[..nr]) + }; + } + if accepted != 0 { self.drain_to_peer(subtype, to)?; } @@ -156,42 +293,79 @@ impl PtyDevPtsLink { &self, subtype: TtyDriverSubType, to: Arc, - ) -> Result<(), SystemError> { + ) -> Result { let Some(queue) = self.queue_for_source(subtype) else { return Err(SystemError::ENODEV); }; + let Some((draining, drain_requested, flushing)) = self.state_flags_for_source(subtype) + else { + return Err(SystemError::ENODEV); + }; + + let mut result = DrainResult::default(); + if flushing.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 { - let mut chunk = Vec::new(); - { - let mut queue = queue.lock(); - while chunk.len() < PTY_DRAIN_CHUNK { - let Some(c) = queue.pop_front() else { - break; - }; - chunk.push(c); - } + if flushing.load(Ordering::Acquire) { + result.still_pending = !queue.lock_irqsave().is_empty(); + draining.store(false, Ordering::Release); + break; } - if chunk.is_empty() { + 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 = to - .core() - .port() - .unwrap() - .receive_buf(&chunk, &[], chunk.len())?; - if delivered < chunk.len() { - let mut queue = queue.lock(); - for c in chunk[delivered..].iter().rev() { - queue.push_front(*c); - } + let delivered = + match to + .core() + .port() + .unwrap() + .receive_buf(&chunk[..chunk_len], &[], chunk_len) + { + Ok(delivered) => delivered, + Err(err) => { + draining.store(false, Ordering::Release); + return Err(err); + } + }; + queue.lock_irqsave().advance_front(delivered); + result.delivered += delivered; + result.freed_backlog += delivered; + if delivered < chunk_len { + result.still_pending = true; + draining.store(false, Ordering::Release); break; } } - Ok(()) + if !result.still_pending { + result.still_pending = !queue.lock_irqsave().is_empty(); + } + + Ok(result) } fn on_close(&self, subtype: TtyDriverSubType) { @@ -399,7 +573,7 @@ impl TtyOperation for Unix98PtyDriverInner { 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()); + hook.clear_pending_from(tty.driver().tty_driver_sub_type())?; } } @@ -749,7 +923,25 @@ pub fn pty_drain_pending_to(tty: Arc) -> Result<(), SystemError> { return Ok(()); }; - hook.drain_to_peer(peer.core().driver().tty_driver_sub_type(), tty)?; + 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(()) } diff --git a/kernel/src/driver/tty/tty_core.rs b/kernel/src/driver/tty/tty_core.rs index 4582359c3d..1a6226b419 100644 --- a/kernel/src/driver/tty/tty_core.rs +++ b/kernel/src/driver/tty/tty_core.rs @@ -219,16 +219,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); 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 b3fafd745f..905249a6d4 100644 --- a/kernel/src/driver/tty/tty_ldisc/ntty.rs +++ b/kernel/src/driver/tty/tty_ldisc/ntty.rs @@ -10,7 +10,7 @@ use system_error::SystemError; use crate::{ arch::ipc::signal::Signal, driver::tty::{ - pty::unix98pty::pty_drain_pending_to, + pty::unix98pty::{pty_discard_pending_to, pty_drain_pending_to}, termios::{ControlCharIndex, InputMode, LocalMode, OutputMode, Termios}, tty_core::{EchoOperation, TtyCore, TtyCoreData, TtyFlag, TtyIoctlCmd, TtyPacketStatus}, tty_driver::{TtyDriverFlag, TtyDriverSubType, TtyOperation}, @@ -40,6 +40,11 @@ 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, @@ -153,7 +158,7 @@ impl NTtyData { echo: false, cursor_column: 0, canon_cursor_column: 0, - opost_pending: Vec::new(), + 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(), @@ -187,6 +192,38 @@ impl NTtyData { } } + 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 @@ -1435,9 +1472,7 @@ 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() - { + if output_mode_has_xtabs(&termios) { // 配置的tab选项是真正输出空格到驱动 if space < spaces { // 空间不够 @@ -1527,9 +1562,7 @@ impl NTtyData { } '\t' => { let spaces = 8 - (self.cursor_column & 7) as usize; - if termios.output_mode.contains(OutputMode::TABDLY) - && OutputMode::TABDLY.bits() == OutputMode::XTABS.bits() - { + if output_mode_has_xtabs(termios) { if used + spaces > space { return Err(SystemError::ENOBUFS); } @@ -1578,8 +1611,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) } /// ## 重置缓冲区的基本信息 @@ -1597,12 +1631,13 @@ impl TtyLineDiscipline for NTtyLinediscipline { ldata.pushing = false; ldata.lookahead_count = 0; ldata.no_room = false; - ldata.opost_pending.clear(); - ldata.opost_pending_offset = 0; 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(); @@ -1614,6 +1649,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, @@ -1642,24 +1685,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); @@ -1748,8 +1795,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 { // 非标准模式 @@ -1764,7 +1810,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { && offset >= minimum { *cookie = true; - return Ok(offset); + break; } } @@ -1795,7 +1841,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { _flags: FileFlags, ) -> Result { let mut nr = len; - let mut out_buf = Vec::with_capacity(len.saturating_mul(2).min(NTTY_BUFSIZE)); + let mut out_buf = Vec::with_capacity(NTTY_BUFSIZE); let mut ldata = Some(self.disc_data()); let pcb = ProcessManager::current_pcb(); let binding = tty.clone(); @@ -1810,6 +1856,9 @@ impl TtyLineDiscipline for NTtyLinediscipline { 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) @@ -1817,15 +1866,22 @@ 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) { let mut made_progress = false; - let pending = ldata.as_ref().unwrap().opost_pending_bytes().to_vec(); + out_buf.clear(); + { + let pending = ldata.as_ref().unwrap().opost_pending_bytes(); + out_buf.extend_from_slice(pending); + } - if !pending.is_empty() { + if !out_buf.is_empty() { drop(ldata.take()); - let written = tty.write(core, &pending, pending.len())?; + let written = tty.write(core, &out_buf, out_buf.len())?; let mut guard = self.disc_data(); if written != 0 { guard.advance_opost_pending(written); @@ -1837,7 +1893,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { } } else { out_buf.clear(); - let space = tty.write_room(core); + let space = tty.write_room(core).min(out_buf.capacity()); let mut consumed = 0; let (cursor_column, canon_cursor_column) = { let ldata = ldata.as_ref().unwrap(); @@ -1928,8 +1984,34 @@ impl TtyLineDiscipline for NTtyLinediscipline { // 休眠一段时间 // 获取到termios读锁,避免termios被更改导致行为异常 drop(ldata.take()); - core.write_wq() - .sleep(EPollEventType::EPOLLOUT.bits() as u64); + 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 { + if offset != 0 { + break; + } + return Err(err); + } if termios.output_mode.contains(OutputMode::OPOST) { ldata = Some(self.disc_data()); } @@ -2201,8 +2283,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( 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/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc index 00ddceea8e..07b304506a 100644 --- a/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc +++ b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -1029,7 +1030,10 @@ TEST(TtyPtyHangup, LargeOpostSlaveWriteDrainsAndPreservesOnlcr) { } 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) << ")"; + << " (" << 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); @@ -1041,6 +1045,233 @@ TEST(TtyPtyHangup, LargeOpostSlaveWriteDrainsAndPreservesOnlcr) { } } +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) { From 95478ac711d2c32da9ad63afb54a2997e50197f8 Mon Sep 17 00:00:00 2001 From: longjin Date: Mon, 22 Jun 2026 05:19:22 +0000 Subject: [PATCH 5/7] fix(tty): discard pty input backlog on signal flush Signed-off-by: longjin --- kernel/src/driver/tty/pty/unix98pty.rs | 174 ++++++++++++++++-- kernel/src/driver/tty/tty_ldisc/ntty.rs | 6 +- .../dunitest/suites/normal/tty_pty_hangup.cc | 149 +++++++++++++++ 3 files changed, 315 insertions(+), 14 deletions(-) diff --git a/kernel/src/driver/tty/pty/unix98pty.rs b/kernel/src/driver/tty/pty/unix98pty.rs index f7163144bd..8f98725425 100644 --- a/kernel/src/driver/tty/pty/unix98pty.rs +++ b/kernel/src/driver/tty/pty/unix98pty.rs @@ -73,6 +73,8 @@ struct PtyDevPtsLink { 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, } @@ -103,6 +105,7 @@ struct PtyByteQueue { buf: Box<[u8; PTY_BUFFER_LIMIT]>, head: usize, len: usize, + discard_len: usize, } impl PtyByteQueue { @@ -114,6 +117,7 @@ impl PtyByteQueue { .unwrap(), head: 0, len: 0, + discard_len: 0, } } @@ -125,9 +129,16 @@ impl PtyByteQueue { PTY_BUFFER_LIMIT - self.len } - fn clear(&mut self) { + 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 { @@ -152,10 +163,22 @@ impl PtyByteQueue { 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 { @@ -183,6 +206,8 @@ impl PtyDevPtsLink { 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), } @@ -199,16 +224,18 @@ impl PtyDevPtsLink { fn state_flags_for_source( &self, subtype: TtyDriverSubType, - ) -> Option<(&AtomicBool, &AtomicBool, &AtomicBool)> { + ) -> 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, @@ -225,7 +252,8 @@ impl PtyDevPtsLink { let Some(queue) = self.queue_for_source(subtype) else { return Err(SystemError::ENODEV); }; - let Some((draining, drain_requested, flushing)) = self.state_flags_for_source(subtype) + let Some((draining, drain_requested, discarding, flushing)) = + self.state_flags_for_source(subtype) else { return Err(SystemError::ENODEV); }; @@ -245,12 +273,59 @@ impl PtyDevPtsLink { } 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, @@ -261,24 +336,38 @@ impl PtyDevPtsLink { let Some(queue) = self.queue_for_source(subtype) else { return Err(SystemError::ENODEV); }; - let Some((_, _, flushing)) = self.state_flags_for_source(subtype) else { + let Some((_, _, discarding, flushing)) = self.state_flags_for_source(subtype) else { return Err(SystemError::ENODEV); }; - while flushing.load(Ordering::Acquire) { - spin_loop(); - } + let mut accepted = loop { + while flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { + spin_loop(); + } - let mut accepted = { let mut queue = queue.lock_irqsave(); - queue.push_slice(&buf[..nr]) + 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 = { + accepted = loop { + while flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { + spin_loop(); + } + let mut queue = queue.lock_irqsave(); - queue.push_slice(&buf[..nr]) + if flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { + drop(queue); + spin_loop(); + continue; + } + break queue.push_slice(&buf[..nr]); }; } @@ -297,13 +386,14 @@ impl PtyDevPtsLink { let Some(queue) = self.queue_for_source(subtype) else { return Err(SystemError::ENODEV); }; - let Some((draining, drain_requested, flushing)) = self.state_flags_for_source(subtype) + 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) { + if flushing.load(Ordering::Acquire) || discarding.load(Ordering::Acquire) { result.still_pending = !queue.lock_irqsave().is_empty(); return Ok(result); } @@ -322,6 +412,28 @@ impl PtyDevPtsLink { 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); @@ -347,6 +459,7 @@ impl PtyDevPtsLink { { Ok(delivered) => delivered, Err(err) => { + let _ = self.discard_requested_prefix(queue); draining.store(false, Ordering::Release); return Err(err); } @@ -354,6 +467,22 @@ impl PtyDevPtsLink { 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); @@ -945,3 +1074,22 @@ pub fn pty_discard_pending_to(tty: Arc) -> Result<(), SystemError> { 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/tty_ldisc/ntty.rs b/kernel/src/driver/tty/tty_ldisc/ntty.rs index 905249a6d4..3670a1e860 100644 --- a/kernel/src/driver/tty/tty_ldisc/ntty.rs +++ b/kernel/src/driver/tty/tty_ldisc/ntty.rs @@ -10,7 +10,9 @@ use system_error::SystemError; use crate::{ arch::ipc::signal::Signal, driver::tty::{ - pty::unix98pty::{pty_discard_pending_to, pty_drain_pending_to}, + 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_driver::{TtyDriverFlag, TtyDriverSubType, TtyOperation}, @@ -878,6 +880,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()); } 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 07b304506a..201c693944 100644 --- a/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc +++ b/user/apps/tests/dunitest/suites/normal/tty_pty_hangup.cc @@ -93,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; @@ -222,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; @@ -352,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); From 45be36149ffb81db18ca7277ec4971001a234b0e Mon Sep 17 00:00:00 2001 From: longjin Date: Mon, 22 Jun 2026 06:54:08 +0000 Subject: [PATCH 6/7] fix(tty): handle hvc console backpressure Signed-off-by: longjin --- kernel/src/driver/char/virtio_console.rs | 469 +++++++++++++++++- kernel/src/driver/tty/tty_core.rs | 4 +- kernel/src/driver/tty/tty_driver.rs | 2 +- kernel/src/driver/tty/tty_ldisc/ntty.rs | 4 +- .../suites/normal/hvc_console_backpressure.cc | 64 +++ user/apps/tests/dunitest/whitelist.txt | 1 + 6 files changed, 521 insertions(+), 23 deletions(-) create mode 100644 user/apps/tests/dunitest/suites/normal/hvc_console_backpressure.cc 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/tty_core.rs b/kernel/src/driver/tty/tty_core.rs index 1a6226b419..4a60d403d9 100644 --- a/kernel/src/driver/tty/tty_core.rs +++ b/kernel/src/driver/tty/tty_core.rs @@ -673,8 +673,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] 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_ldisc/ntty.rs b/kernel/src/driver/tty/tty_ldisc/ntty.rs index 3670a1e860..7f502f0ce7 100644 --- a/kernel/src/driver/tty/tty_ldisc/ntty.rs +++ b/kernel/src/driver/tty/tty_ldisc/ntty.rs @@ -2038,7 +2038,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); } @@ -2278,7 +2278,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { event.insert(EPollEventType::EPOLLHUP); } - if core.driver().driver_funcs().chars_in_buffer() < 256 + if core.driver().driver_funcs().chars_in_buffer(core) < 256 && core.driver().driver_funcs().write_room(core) > 0 { event.insert(EPollEventType::EPOLLOUT | EPollEventType::EPOLLWRNORM); 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/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 From bd9551cf6785a8fe22e51bbea5982bc43b8978d5 Mon Sep 17 00:00:00 2001 From: longjin Date: Mon, 22 Jun 2026 08:36:19 +0000 Subject: [PATCH 7/7] fix(tty): serialize n_tty output accounting Signed-off-by: longjin --- kernel/src/driver/tty/tty_core.rs | 87 +++- kernel/src/driver/tty/tty_device.rs | 31 +- kernel/src/driver/tty/tty_ldisc/ntty.rs | 579 +++++++++++++----------- 3 files changed, 420 insertions(+), 277 deletions(-) diff --git a/kernel/src/driver/tty/tty_core.rs b/kernel/src/driver/tty/tty_core.rs index 4a60d403d9..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(), }), }); } @@ -369,6 +446,8 @@ pub struct TtyCoreData { read_wq: EventWaitQueue, /// 写等待队列 write_wq: EventWaitQueue, + /// 串行化整个 tty write 调用,等价于 Linux tty->atomic_write_lock。 + write_lock: TtySleepLock, /// 端口 port: RwLock>>, /// 前台进程 @@ -479,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() diff --git a/kernel/src/driver/tty/tty_device.rs b/kernel/src/driver/tty/tty_device.rs index 4fd2679b38..2642acb9dc 100644 --- a/kernel/src/driver/tty/tty_device.rs +++ b/kernel/src/driver/tty/tty_device.rs @@ -28,7 +28,7 @@ use crate::{ filesystem::{ devfs::{devfs_register, DevFS, DeviceINode, LockedDevFSInode}, devpts::{DevPtsFs, LockedDevPtsFSInode}, - epoll::EPollItem, + epoll::{EPollEventType, EPollItem}, kernfs::KernFSInode, vfs::{ file::{File, FileFlags}, @@ -363,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; @@ -377,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; @@ -387,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); } } @@ -395,6 +419,9 @@ impl IndexNode for TtyDevice { // todo: 更新时间 } + drop(write_guard); + core.write_wq() + .wakeup_any(EPollEventType::EPOLLOUT.bits() as u64); Ok(written) } diff --git a/kernel/src/driver/tty/tty_ldisc/ntty.rs b/kernel/src/driver/tty/tty_ldisc/ntty.rs index 7f502f0ce7..675838e150 100644 --- a/kernel/src/driver/tty/tty_ldisc/ntty.rs +++ b/kernel/src/driver/tty/tty_ldisc/ntty.rs @@ -14,7 +14,10 @@ use crate::{ 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, }, @@ -50,6 +53,14 @@ fn output_mode_has_xtabs(termios: &Termios) -> bool { #[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 { @@ -74,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)] @@ -180,12 +231,6 @@ impl NTtyData { } } - fn save_opost_pending(&mut self, bytes: &[u8]) { - self.opost_pending.clear(); - self.opost_pending.extend_from_slice(bytes); - self.opost_pending_offset = 0; - } - fn advance_opost_pending(&mut self, count: usize) { self.opost_pending_offset += count; if self.opost_pending_offset >= self.opost_pending.len() { @@ -396,8 +441,6 @@ impl NTtyData { } self.echo_commit = self.echo_head; - drop(termios); - let _ = self.echoes(tty); } pub fn receive_buf_standard( @@ -935,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; @@ -948,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) { @@ -1236,163 +1274,111 @@ impl NTtyData { } /// ## 处理回显 - pub fn process_echoes(&mut self, tty: Arc) { - if self.echo_mark == self.echo_tail { - return; - } - self.echo_commit = self.echo_mark; - let echoed = self.echoes(tty.clone()); - - if echoed.is_ok() && echoed.unwrap() > 0 { - tty.flush_chars(tty.core()); + pub fn process_echoes(&mut self, _tty: Arc) { + if self.echo_mark != self.echo_tail { + self.echo_commit = self.echo_mark; } } - #[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(); + 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; + } - while ntty_buf_mask(self.echo_commit) != ntty_buf_mask(tail) { - 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) { - self.echo_tail = tail; - return Ok(ospace - space); - } - - // 获取到start,之后取第一个作为op - let op = EchoOperation::from_u8(self.echo_buf[ntty_buf_mask(tail + 1)]); - - match op { - EchoOperation::Start => { - if space == 0 { - break; - } + 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 tty - .put_char(tty.core(), EchoOperation::Start.to_u8()) - .is_err() - { - tty.write(core, &[EchoOperation::Start.to_u8()], 1)?; - } + if EchoOperation::from_u8(c) == EchoOperation::Start { + if ntty_buf_mask(self.echo_commit) == ntty_buf_mask(tail + 1) { + return None; + } - self.cursor_column += 1; - space -= 1; - tail += 2; - } - EchoOperation::MoveBackCol => { - if self.cursor_column > 0 { - self.cursor_column -= 1; - } - tail += 2; + match EchoOperation::from_u8(self.echo_buf[ntty_buf_mask(tail + 1)]) { + EchoOperation::Start => { + if space < 1 { + return None; } - EchoOperation::SetCanonCol => { - self.canon_cursor_column = self.cursor_column; - tail += 2; + bytes.push(EchoOperation::Start.to_u8()); + cursor_column += 1; + tail += 2; + } + 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; } - 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; + 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; } - 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; - } - } + 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; } - } else { - if termios.output_mode.contains(OutputMode::OPOST) { - let ret = self.do_output_char(tty.clone(), c, space); - - if ret.is_err() { - break; - } - space -= ret.unwrap(); - } else { - if space == 0 { - break; + EchoOperation::Undefined(ch) => match ch { + 8 => { + if space < 2 { + return None; + } + bytes.extend_from_slice(&[8, b' ']); + cursor_column = cursor_column.saturating_sub(1); + tail += 1; } - - if tty.put_char(tty.core(), c).is_err() { - tty.write(core, &[c], 1)?; + _ => { + if space < 2 { + return None; + } + bytes.extend_from_slice(&[b'^', ch ^ 0o100]); + cursor_column += 2; + tail += 2; } - space -= 1; - } - tail += 1; + }, } + } 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; } - // 如果回显缓冲区接近满(在下一次提交之前可能会发生回显溢出的情况),则丢弃足够的尾部数据以防止随后的溢出。 + Some(EchoStep { + bytes, + tail: self.echo_discard_tail(tail), + cursor_column, + canon_cursor_column, + }) + } + + 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() { @@ -1404,111 +1390,94 @@ impl NTtyData { tail += 1; } } + tail + } - self.echo_tail = tail; - return Ok(ospace - space); + 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; } - // ## 设置带有 OPOST 处理的tty输出一个字符 - pub fn do_output_char( - &mut self, - tty: Arc, - c: u8, + fn format_output_char( + termios: &Termios, + mut c: u8, + out: &mut Vec, space: usize, + cursor_column: &mut u32, + canon_cursor_column: &mut u32, ) -> 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)?; - } + *cursor_column = cursor_column.saturating_sub(1); + out.push(c); return Ok(1); } + match c as char { '\n' => { if termios.output_mode.contains(OutputMode::ONLRET) { - // 回车符 - self.cursor_column = 0; + *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)?; + *cursor_column = 0; + *canon_cursor_column = 0; + out.extend_from_slice(b"\r\n"); return Ok(2); } - - self.canon_cursor_column = self.cursor_column; + *canon_cursor_column = *cursor_column; } '\r' => { - if termios.output_mode.contains(OutputMode::ONOCR) && self.cursor_column == 0 { - // 光标已经在第0列,则不输出回车符 + if termios.output_mode.contains(OutputMode::ONOCR) && *cursor_column == 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; + *cursor_column = 0; + *canon_cursor_column = 0; } } else { - self.cursor_column = 0; - self.canon_cursor_column = 0; + *cursor_column = 0; + *canon_cursor_column = 0; } } '\t' => { - // 计算输出一个\t需要的空间 - let spaces = 8 - (self.cursor_column & 7) as usize; - if output_mode_has_xtabs(&termios) { - // 配置的tab选项是真正输出空格到驱动 - if space < spaces { - // 空间不够 + let spaces = 8 - (*cursor_column & 7) as usize; + 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)?; + *cursor_column += spaces as u32; + out.extend_from_slice(&b" "[..spaces]); return Ok(spaces); } - self.cursor_column += spaces as u32; + *cursor_column += spaces as u32; } _ => { - // 判断是否为控制字符 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) { - self.cursor_column += 1; + *cursor_column += 1; } } } } - if tty.put_char(tty.core(), c).is_err() { - tty.write(core, &[c], 1)?; - } + out.push(c); Ok(1) } @@ -1595,6 +1564,38 @@ impl NTtyData { 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 { @@ -1846,17 +1847,19 @@ impl TtyLineDiscipline for NTtyLinediscipline { ) -> Result { let mut nr = len; let mut out_buf = Vec::with_capacity(NTTY_BUFSIZE); - let mut ldata = Some(self.disc_data()); 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.as_mut().unwrap().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() { @@ -1878,78 +1881,97 @@ impl TtyLineDiscipline for NTtyLinediscipline { if termios.output_mode.contains(OutputMode::OPOST) { let mut made_progress = false; out_buf.clear(); - { - let pending = ldata.as_ref().unwrap().opost_pending_bytes(); - out_buf.extend_from_slice(pending); - } + let pending = self.disc_data().opost_pending_bytes().to_vec(); + out_buf.extend_from_slice(&pending); if !out_buf.is_empty() { - drop(ldata.take()); let written = tty.write(core, &out_buf, out_buf.len())?; - let mut guard = self.disc_data(); if written != 0 { + let mut guard = self.disc_data(); guard.advance_opost_pending(written); made_progress = true; } - ldata = Some(guard); if written != 0 { tty.flush_chars(core); } } else { out_buf.clear(); let space = tty.write_room(core).min(out_buf.capacity()); - let mut consumed = 0; - let (cursor_column, canon_cursor_column) = { - let ldata = ldata.as_ref().unwrap(); - (ldata.cursor_column, ldata.canon_cursor_column) + let simple_len = if space == 0 { + 0 + } else { + let guard = self.disc_data(); + guard.simple_output_block_len(&termios, &buf[offset..], space.min(nr)) }; - { - let ldata = ldata.as_mut().unwrap(); - while consumed < nr { - let c = buf[offset + consumed]; - match ldata.process_output_char_to_buf(&termios, c, &mut out_buf, space) - { - Ok(_) => { - consumed += 1; - } - Err(SystemError::ENOBUFS) => { - break; - } - Err(err) => { - return Err(err); - } - } + 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); } - } - - drop(ldata.take()); - - if !out_buf.is_empty() { - let written = tty.write(core, &out_buf, out_buf.len())?; + } else if space != 0 && nr != 0 { let mut guard = self.disc_data(); - if written == 0 { - guard.cursor_column = cursor_column; - guard.canon_cursor_column = canon_cursor_column; - } else { - if written < out_buf.len() { - guard.save_opost_pending(&out_buf[written..]); + 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); } - offset += consumed; - nr -= consumed; - made_progress = true; - } - ldata = Some(guard); - if written != 0 { - tty.flush_chars(core); } - } else { - ldata = Some(self.disc_data()); - if consumed != 0 { - offset += consumed; - nr -= consumed; + drop(guard); + + if out_buf.is_empty() { + offset += 1; + nr -= 1; made_progress = true; - tty.flush_chars(core); + } 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); + } + + if sent == out_buf.len() { + offset += 1; + nr -= 1; + made_progress = true; + } } } } @@ -1958,7 +1980,6 @@ impl TtyLineDiscipline for NTtyLinediscipline { continue; } } else { - drop(ldata.take()); while nr > 0 { let write = tty.write(core, &buf[offset..], nr)?; if write == 0 { @@ -1970,7 +1991,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { } let opost_pending = termios.output_mode.contains(OutputMode::OPOST) - && !ldata.as_ref().unwrap().opost_pending_bytes().is_empty(); + && !self.disc_data().opost_pending_bytes().is_empty(); if nr == 0 && !opost_pending { break; } @@ -1987,7 +2008,7 @@ impl TtyLineDiscipline for NTtyLinediscipline { // 到这里表明没位置可写了 // 休眠一段时间 // 获取到termios读锁,避免termios被更改导致行为异常 - drop(ldata.take()); + drop(output_guard.take()); let wait_result = core.write_wq().wait_event_interruptible( EPollEventType::EPOLLOUT.bits() as u64, || { @@ -2011,16 +2032,17 @@ impl TtyLineDiscipline for NTtyLinediscipline { }, ); if let Err(err) = wait_result { + output_guard = Some(self.output_lock.lock()); if offset != 0 { break; } return Err(err); } - if termios.output_mode.contains(OutputMode::OPOST) { - ldata = Some(self.disc_data()); - } + output_guard = Some(self.output_lock.lock()); + termios = *core.termios(); } + drop(output_guard); Ok(offset) } @@ -2278,7 +2300,8 @@ impl TtyLineDiscipline for NTtyLinediscipline { event.insert(EPollEventType::EPOLLHUP); } - if core.driver().driver_funcs().chars_in_buffer(core) < 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); @@ -2307,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( @@ -2318,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 } }