From ddd6c4f3acf043606390d3361c7b09bdee33798d Mon Sep 17 00:00:00 2001 From: Connor-GH Date: Wed, 3 Jun 2026 19:33:09 -0500 Subject: [PATCH] ptyd: implement TIOCGPTN and TIOC{S,G}PTLCK This functionality is needed according to POSIX. Additionally, some logic had to be changed in order for the ptsname to be correct for the manager terminal (ptmx). If you want to obtain a pty, you must go through the `posix_openpt`-`grantpt`-`unlockpt`-`ptsname`-`open` process. Alternatively, relibc as a function called `openpty`. This is part of my ptyd series. This can be safely merged once the MRs for relibc/ and userutils/ are merged at the same time. --- drivers/redoxerd/src/main.rs | 2 +- ptyd/src/controlterm.rs | 6 ++- ptyd/src/main.rs | 2 + ptyd/src/ptlock.rs | 93 ++++++++++++++++++++++++++++++++++++ ptyd/src/ptname.rs | 83 ++++++++++++++++++++++++++++++++ ptyd/src/pty.rs | 2 + ptyd/src/scheme.rs | 11 +++++ 7 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 ptyd/src/ptlock.rs create mode 100644 ptyd/src/ptname.rs diff --git a/drivers/redoxerd/src/main.rs b/drivers/redoxerd/src/main.rs index 5ecd0b4672..4551ea2e5d 100644 --- a/drivers/redoxerd/src/main.rs +++ b/drivers/redoxerd/src/main.rs @@ -82,7 +82,7 @@ fn handle( fn getpty(columns: u32, lines: u32) -> io::Result<(RawFd, String)> { let master = libredox::call::open( - "/scheme/pty", + "/scheme/pty/ptmx", libredox::flag::O_CLOEXEC | libredox::flag::O_RDWR | libredox::flag::O_CREAT diff --git a/ptyd/src/controlterm.rs b/ptyd/src/controlterm.rs index f1f6fc42f8..ae8011c905 100644 --- a/ptyd/src/controlterm.rs +++ b/ptyd/src/controlterm.rs @@ -6,6 +6,7 @@ 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 { @@ -36,7 +37,10 @@ impl Resource for PtyControlTerm { } fn path(&mut self, buf: &mut [u8]) -> Result { - self.pty.borrow_mut().path(buf) + FpathWriter::with(buf, "pty", |w| { + write!(w, "{}", "ptmx").unwrap(); + Ok(()) + }) } fn read(&mut self, buf: &mut [u8]) -> Result { diff --git a/ptyd/src/main.rs b/ptyd/src/main.rs index e6bf429eb8..6eec7ea161 100644 --- a/ptyd/src/main.rs +++ b/ptyd/src/main.rs @@ -7,6 +7,8 @@ use syscall::data::TimeSpec; mod controlterm; mod pgrp; +mod ptlock; +mod ptname; mod pty; mod resource; mod scheme; diff --git a/ptyd/src/ptlock.rs b/ptyd/src/ptlock.rs new file mode 100644 index 0000000000..c6d742b3cb --- /dev/null +++ b/ptyd/src/ptlock.rs @@ -0,0 +1,93 @@ +use std::cell::RefCell; +use std::rc::Weak; + +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 PtyLock { + pty: Weak>, + flags: usize, +} + +impl PtyLock { + pub fn new(pty: Weak>, flags: usize) -> Self { + PtyLock { pty, flags } + } +} + +impl Resource for PtyLock { + 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)) + } + } + + // FIXME assuming c_int has same size as u32 + fn read(&mut self, buf: &mut [u8]) -> Result { + if let Some(pty_lock) = self.pty.upgrade() { + let pty = pty_lock.borrow(); + + let lock_buf = buf + .get_mut(..4) + .and_then(|b| <&mut [u8; 4]>::try_from(b).ok()) + .ok_or(Error::new(EBADF))?; + *lock_buf = (pty.locked as u32).to_ne_bytes(); + Ok(4) + } else { + Ok(0) + } + } + + fn write(&mut self, buf: &[u8]) -> Result { + if let Some(pty_lock) = self.pty.upgrade() { + let mut pty = pty_lock.borrow_mut(); + + let lock_val = u32::from_ne_bytes( + buf.get(..4) + .and_then(|b| <[u8; 4]>::try_from(b).ok()) + .ok_or(Error::new(EBADF))?, + ); + pty.locked = lock_val != 0; + Ok(4) + } else { + Ok(0) + } + } + + 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/ptname.rs b/ptyd/src/ptname.rs new file mode 100644 index 0000000000..434d5dd5a0 --- /dev/null +++ b/ptyd/src/ptname.rs @@ -0,0 +1,83 @@ +use std::cell::RefCell; +use std::rc::Weak; + +use syscall::error::{Error, Result, EBADF, EINVAL, EIO, 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 PtsName { + pty: Weak>, + flags: usize, +} + +impl PtsName { + pub fn new(pty: Weak>, flags: usize) -> Self { + PtsName { pty, flags } + } +} + +impl Resource for PtsName { + 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 { + if let Some(pty_lock) = self.pty.upgrade() { + let pty = pty_lock.borrow(); + if pty.locked { + Err(Error::new(EIO)) + } else { + let id_buf = buf + .get_mut(..4) + .and_then(|b| <&mut [u8; 4]>::try_from(b).ok()) + .ok_or(Error::new(EBADF))?; + *id_buf = (pty.id as u32).to_ne_bytes(); + Ok(4) + } + } else { + Err(Error::new(EPIPE)) + } + } + + fn write(&mut self, _buf: &[u8]) -> Result { + Err(Error::new(EBADF)) + } + + 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 bcc46ac35a..933bb80768 100644 --- a/ptyd/src/pty.rs +++ b/ptyd/src/pty.rs @@ -7,6 +7,7 @@ use syscall::error::Result; pub struct Pty { pub id: usize, pub pgrp: usize, + pub locked: bool, pub termios: Termios, pub winsize: Winsize, pub cooked: Vec, @@ -21,6 +22,7 @@ impl Pty { Pty { id, pgrp: 0, + locked: true, termios: Termios::default(), winsize: Winsize::default(), cooked: Vec::new(), diff --git a/ptyd/src/scheme.rs b/ptyd/src/scheme.rs index c7136a4e81..2d34ac3cb4 100644 --- a/ptyd/src/scheme.rs +++ b/ptyd/src/scheme.rs @@ -12,6 +12,8 @@ use syscall::schemev2::NewFdFlags; use crate::controlterm::PtyControlTerm; use crate::pgrp::PtyPgrp; +use crate::ptlock::PtyLock; +use crate::ptname::PtsName; use crate::pty::Pty; use crate::resource::Resource; use crate::subterm::PtySubTerm; @@ -72,7 +74,12 @@ impl SchemeSync for PtyScheme { let id = self.next_id; self.next_id += 1; + // This happens if we are passed "/scheme/pty" and not "/scheme/pty/ptmx". if path.is_empty() { + return Err(Error::new(ENOENT)); + } + + if path == "ptmx" { let pty = Rc::new(RefCell::new(Pty::new(id))); self.handles.insert( id, @@ -119,6 +126,10 @@ impl SchemeSync for PtyScheme { Box::new(PtyTermios::new(old_resource.pty(), old_resource.flags())) } else if buf == b"winsize" { Box::new(PtyWinsize::new(old_resource.pty(), old_resource.flags())) + } else if buf == b"ptlock" { + 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 { return Err(Error::new(EINVAL)); }