From cc03dca2c824718c060e5ec308df3f6d33620590 Mon Sep 17 00:00:00 2001 From: Connor-GH Date: Tue, 7 Jul 2026 16:43:18 -0500 Subject: [PATCH] ptyd: implement tcflush, tcdrain, and tcsendbreak --- Cargo.lock | 1 + ptyd/Cargo.toml | 1 + ptyd/src/controlterm.rs | 6 ++- ptyd/src/main.rs | 3 ++ ptyd/src/ptflow.rs | 100 ++++++++++++++++++++++++++++++++++++ ptyd/src/ptflush.rs | 110 ++++++++++++++++++++++++++++++++++++++++ ptyd/src/ptsendbreak.rs | 91 +++++++++++++++++++++++++++++++++ ptyd/src/pty.rs | 2 + ptyd/src/scheme.rs | 9 ++++ 9 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 ptyd/src/ptflow.rs create mode 100644 ptyd/src/ptflush.rs create mode 100644 ptyd/src/ptsendbreak.rs diff --git a/Cargo.lock b/Cargo.lock index b34acc689d..46d637c1bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,6 +1658,7 @@ name = "ptyd" version = "0.1.0" dependencies = [ "daemon", + "libc", "libredox", "redox-scheme", "redox_event", diff --git a/ptyd/Cargo.toml b/ptyd/Cargo.toml index 0edb56a730..3e487ab3bc 100644 --- a/ptyd/Cargo.toml +++ b/ptyd/Cargo.toml @@ -12,6 +12,7 @@ scheme-utils = { path = "../scheme-utils" } redox_event.workspace = true redox_syscall.workspace = true redox_termios.workspace = true +libc.workspace = true [lints] workspace = true diff --git a/ptyd/src/controlterm.rs b/ptyd/src/controlterm.rs index df42314c5f..e82f57db6d 100644 --- a/ptyd/src/controlterm.rs +++ b/ptyd/src/controlterm.rs @@ -6,7 +6,6 @@ use syscall::flag::{EventFlags, F_GETFL, F_SETFL, O_ACCMODE, O_NONBLOCK}; use crate::pty::Pty; use crate::resource::Resource; -use scheme_utils::FpathWriter; /// Read side of a pipe pub struct PtyControlTerm { @@ -106,6 +105,11 @@ impl Resource for PtyControlTerm { let mut events = EventFlags::empty(); let pty = self.pty.borrow(); + // If pty is stopped (e.g. `tcflush(fd, TCOOFF)`), do not send the event + // to the terminal to write the characters to the screen. + if pty.stopped { + return EventFlags::empty(); + } if pty.miso.front().is_some() { if !self.notified_read { self.notified_read = true; diff --git a/ptyd/src/main.rs b/ptyd/src/main.rs index b0fed5676a..b9002da93c 100644 --- a/ptyd/src/main.rs +++ b/ptyd/src/main.rs @@ -7,8 +7,11 @@ use syscall::data::TimeSpec; mod controlterm; mod pgrp; +mod ptflow; +mod ptflush; mod ptlock; mod ptname; +mod ptsendbreak; mod pty; mod resource; mod scheme; diff --git a/ptyd/src/ptflow.rs b/ptyd/src/ptflow.rs new file mode 100644 index 0000000000..7d690e94b2 --- /dev/null +++ b/ptyd/src/ptflow.rs @@ -0,0 +1,100 @@ +use core::ops::DerefMut; +use std::cell::RefCell; +use std::rc::Weak; + +use libc::{c_int, TCIOFF, TCION, TCOOFF, TCOON}; +use syscall::error::{Error, Result, EBADF, EINVAL, EPIPE}; +use syscall::flag::{EventFlags, F_GETFL, F_SETFL, O_ACCMODE}; + +use crate::pty::Pty; +use crate::resource::Resource; + +/// Read side of a pipe +pub struct PtFlow { + pty: Weak>, + flags: usize, +} + +impl PtFlow { + pub fn new(pty: Weak>, flags: usize) -> Self { + PtFlow { pty, flags } + } + + fn flow(&mut self, buf: &[u8]) -> Result { + let action = u32::from_ne_bytes( + buf.get(..4) + .and_then(|b| <[u8; 4]>::try_from(b).ok()) + .ok_or(Error::new(EINVAL))?, + ); + let action = action as c_int; + + match action { + TCOON | TCOOFF => { + let pty_lock = self.pty.upgrade().ok_or(Error::new(EPIPE))?; + let mut pty = pty_lock.borrow_mut(); + let pty = pty.deref_mut(); + + if action == TCOON { + pty.stopped = false; + } else if action == TCOOFF { + pty.stopped = true; + } + } + TCION | TCIOFF => { + // We are a pty, and the start and stop characters + // only have to be written if we are a tty + } + _ => return Err(Error::new(EINVAL)), + } + Ok(4) + } +} + +impl Resource for PtFlow { + fn pty(&self) -> Weak> { + self.pty.clone() + } + + fn flags(&self) -> usize { + self.flags + } + + fn path(&mut self, buf: &mut [u8]) -> Result { + if let Some(pty_lock) = self.pty.upgrade() { + pty_lock.borrow_mut().path(buf) + } else { + Err(Error::new(EPIPE)) + } + } + + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(Error::new(EBADF)) + } + + fn write(&mut self, buf: &[u8]) -> Result { + self.flow(buf) + } + + fn sync(&mut self) -> Result<()> { + Ok(()) + } + + fn fcntl(&mut self, cmd: usize, arg: usize) -> Result { + match cmd { + F_GETFL => Ok(self.flags), + F_SETFL => { + self.flags = (self.flags & O_ACCMODE) | (arg & !O_ACCMODE); + Ok(0) + } + _ => Err(Error::new(EINVAL)), + } + } + + fn fevent(&mut self) -> Result { + Err(Error::new(EBADF)) + } + + fn events(&mut self) -> EventFlags { + EventFlags::empty() + } +} diff --git a/ptyd/src/ptflush.rs b/ptyd/src/ptflush.rs new file mode 100644 index 0000000000..c800d4ca97 --- /dev/null +++ b/ptyd/src/ptflush.rs @@ -0,0 +1,110 @@ +use std::cell::RefCell; +use std::rc::Weak; + +use libc::{c_int, TCIFLUSH, TCIOFLUSH, TCOFLUSH}; +use syscall::error::{Error, Result, EBADF, EINVAL, EPIPE}; +use syscall::flag::{EventFlags, F_GETFL, F_SETFL, O_ACCMODE}; + +use crate::pty::Pty; +use crate::resource::Resource; + +/// Read side of a pipe +pub struct PtFlush { + pty: Weak>, + flags: usize, +} + +impl PtFlush { + pub fn new(pty: Weak>, flags: usize) -> Self { + PtFlush { pty, flags } + } + fn flush_write(&mut self) -> Result { + if let Some(pty) = self.pty.upgrade() { + pty.borrow_mut().miso.clear(); + Ok(0) + } else { + Err(Error::new(EPIPE)) + } + } + + fn flush_read(&mut self) -> Result { + if let Some(pty) = self.pty.upgrade() { + pty.borrow_mut().mosi.clear(); + Ok(0) + } else { + Err(Error::new(EPIPE)) + } + } + + fn flush(&mut self, buf: &[u8]) -> Result { + let action = u32::from_ne_bytes( + buf.get(..4) + .and_then(|b| <[u8; 4]>::try_from(b).ok()) + .ok_or(Error::new(EINVAL))?, + ); + + match action as c_int { + TCIFLUSH => { + self.flush_read()?; + } + TCOFLUSH => { + self.flush_write()?; + } + TCIOFLUSH => { + self.flush_read()?; + self.flush_write()?; + } + _ => return Err(Error::new(EINVAL)), + } + Ok(4) + } +} + +impl Resource for PtFlush { + fn pty(&self) -> Weak> { + self.pty.clone() + } + + fn flags(&self) -> usize { + self.flags + } + + fn path(&mut self, buf: &mut [u8]) -> Result { + if let Some(pty_lock) = self.pty.upgrade() { + pty_lock.borrow_mut().path(buf) + } else { + Err(Error::new(EPIPE)) + } + } + + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(Error::new(EBADF)) + } + + fn write(&mut self, buf: &[u8]) -> Result { + self.flush(buf) + } + + fn sync(&mut self) -> Result<()> { + Ok(()) + } + + fn fcntl(&mut self, cmd: usize, arg: usize) -> Result { + match cmd { + F_GETFL => Ok(self.flags), + F_SETFL => { + self.flags = (self.flags & O_ACCMODE) | (arg & !O_ACCMODE); + Ok(0) + } + _ => Err(Error::new(EINVAL)), + } + } + + fn fevent(&mut self) -> Result { + Err(Error::new(EBADF)) + } + + fn events(&mut self) -> EventFlags { + EventFlags::empty() + } +} diff --git a/ptyd/src/ptsendbreak.rs b/ptyd/src/ptsendbreak.rs new file mode 100644 index 0000000000..568860493d --- /dev/null +++ b/ptyd/src/ptsendbreak.rs @@ -0,0 +1,91 @@ +use std::cell::RefCell; +use std::rc::Weak; + +use libc::{c_int, nanosleep, timespec}; +use syscall::error::{Error, Result, EBADF, EINVAL, EPIPE}; +use syscall::flag::{EventFlags, F_GETFL, F_SETFL, O_ACCMODE}; + +use crate::pty::Pty; +use crate::resource::Resource; + +/// Read side of a pipe +pub struct PtSendbreak { + pty: Weak>, + flags: usize, +} + +impl PtSendbreak { + pub fn new(pty: Weak>, flags: usize) -> Self { + PtSendbreak { pty, flags } + } + + fn sendbreak(&mut self, _duration: c_int) -> Result { + // TODO: send break here + let _ = unsafe { + // POSIX specifies that we need to sleep for 0.25 to 0.5 seconds. + // FreeBSD uses 0.4, and that seems reasonable. + let tm = timespec { + tv_sec: 0, + tv_nsec: 400000000, + }; + nanosleep(&tm, core::ptr::null_mut()) + }; + // TODO: end break here + + Ok(4) + } +} + +impl Resource for PtSendbreak { + fn pty(&self) -> Weak> { + self.pty.clone() + } + + fn flags(&self) -> usize { + self.flags + } + + fn path(&mut self, buf: &mut [u8]) -> Result { + if let Some(pty_lock) = self.pty.upgrade() { + pty_lock.borrow_mut().path(buf) + } else { + Err(Error::new(EPIPE)) + } + } + + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(Error::new(EBADF)) + } + + fn write(&mut self, buf: &[u8]) -> Result { + let duration = c_int::from_ne_bytes( + buf.get(..4) + .and_then(|b| <[u8; 4]>::try_from(b).ok()) + .ok_or(Error::new(EINVAL))?, + ); + self.sendbreak(duration) + } + + fn sync(&mut self) -> Result<()> { + Ok(()) + } + + fn fcntl(&mut self, cmd: usize, arg: usize) -> Result { + match cmd { + F_GETFL => Ok(self.flags), + F_SETFL => { + self.flags = (self.flags & O_ACCMODE) | (arg & !O_ACCMODE); + Ok(0) + } + _ => Err(Error::new(EINVAL)), + } + } + + fn fevent(&mut self) -> Result { + Err(Error::new(EBADF)) + } + + fn events(&mut self) -> EventFlags { + EventFlags::empty() + } +} diff --git a/ptyd/src/pty.rs b/ptyd/src/pty.rs index 933bb80768..18cb6d9a3f 100644 --- a/ptyd/src/pty.rs +++ b/ptyd/src/pty.rs @@ -8,6 +8,7 @@ pub struct Pty { pub id: usize, pub pgrp: usize, pub locked: bool, + pub stopped: bool, pub termios: Termios, pub winsize: Winsize, pub cooked: Vec, @@ -23,6 +24,7 @@ impl Pty { id, pgrp: 0, locked: true, + stopped: false, termios: Termios::default(), winsize: Winsize::default(), cooked: Vec::new(), diff --git a/ptyd/src/scheme.rs b/ptyd/src/scheme.rs index 2d34ac3cb4..8ae985cb7e 100644 --- a/ptyd/src/scheme.rs +++ b/ptyd/src/scheme.rs @@ -12,8 +12,11 @@ use syscall::schemev2::NewFdFlags; use crate::controlterm::PtyControlTerm; use crate::pgrp::PtyPgrp; +use crate::ptflow::PtFlow; +use crate::ptflush::PtFlush; use crate::ptlock::PtyLock; use crate::ptname::PtsName; +use crate::ptsendbreak::PtSendbreak; use crate::pty::Pty; use crate::resource::Resource; use crate::subterm::PtySubTerm; @@ -130,6 +133,12 @@ impl SchemeSync for PtyScheme { Box::new(PtyLock::new(old_resource.pty(), old_resource.flags())) } else if buf == b"ptsname" { Box::new(PtsName::new(old_resource.pty(), old_resource.flags())) + } else if buf == b"flush" { + Box::new(PtFlush::new(old_resource.pty(), old_resource.flags())) + } else if buf == b"sendbreak" { + Box::new(PtSendbreak::new(old_resource.pty(), old_resource.flags())) + } else if buf == b"flow" { + Box::new(PtFlow::new(old_resource.pty(), old_resource.flags())) } else { return Err(Error::new(EINVAL)); }