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.
This commit is contained in:
Connor-GH
2026-06-03 19:33:09 -05:00
parent 2272746215
commit ddd6c4f3ac
7 changed files with 197 additions and 2 deletions
+1 -1
View File
@@ -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
+5 -1
View File
@@ -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<usize> {
self.pty.borrow_mut().path(buf)
FpathWriter::with(buf, "pty", |w| {
write!(w, "{}", "ptmx").unwrap();
Ok(())
})
}
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
+2
View File
@@ -7,6 +7,8 @@ use syscall::data::TimeSpec;
mod controlterm;
mod pgrp;
mod ptlock;
mod ptname;
mod pty;
mod resource;
mod scheme;
+93
View File
@@ -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<RefCell<Pty>>,
flags: usize,
}
impl PtyLock {
pub fn new(pty: Weak<RefCell<Pty>>, flags: usize) -> Self {
PtyLock { pty, flags }
}
}
impl Resource for PtyLock {
fn pty(&self) -> Weak<RefCell<Pty>> {
self.pty.clone()
}
fn flags(&self) -> usize {
self.flags
}
fn path(&mut self, buf: &mut [u8]) -> Result<usize> {
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<usize> {
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<usize> {
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<usize> {
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<EventFlags> {
Err(Error::new(EBADF))
}
fn events(&mut self) -> EventFlags {
EventFlags::empty()
}
}
+83
View File
@@ -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<RefCell<Pty>>,
flags: usize,
}
impl PtsName {
pub fn new(pty: Weak<RefCell<Pty>>, flags: usize) -> Self {
PtsName { pty, flags }
}
}
impl Resource for PtsName {
fn pty(&self) -> Weak<RefCell<Pty>> {
self.pty.clone()
}
fn flags(&self) -> usize {
self.flags
}
fn path(&mut self, buf: &mut [u8]) -> Result<usize> {
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<usize> {
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<usize> {
Err(Error::new(EBADF))
}
fn sync(&mut self) -> Result<()> {
Ok(())
}
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize> {
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<EventFlags> {
Err(Error::new(EBADF))
}
fn events(&mut self) -> EventFlags {
EventFlags::empty()
}
}
+2
View File
@@ -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<u8>,
@@ -21,6 +22,7 @@ impl Pty {
Pty {
id,
pgrp: 0,
locked: true,
termios: Termios::default(),
winsize: Winsize::default(),
cooked: Vec::new(),
+11
View File
@@ -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));
}