Skip to content

Commit e217b59

Browse files
committed
build and pass tests on openbsd
1 parent 3706084 commit e217b59

File tree

7 files changed

+224
-16
lines changed

7 files changed

+224
-16
lines changed

Cargo.lock

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ license.workspace = true
9797
edition.workspace = true
9898
rust-version.workspace = true
9999
include.workspace = true
100+
build = "build/main.rs"
100101

101102
[package.metadata.docs.rs]
102103
all-features = true
@@ -123,5 +124,8 @@ tempfile = "3"
123124
thiserror = "2.0.0"
124125
which = { version = "8.0", optional = true }
125126

127+
[target.'cfg(target_os = "openbsd")'.build-dependencies]
128+
bindgen = { version = "0.72.1", default-features = false }
129+
126130
[lints]
127131
workspace = true

build/main.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
fn main() {
2+
#[cfg(target_os = "openbsd")] generate_openbsd_bindings();
3+
}
4+
5+
#[cfg(target_os = "openbsd")]
6+
fn generate_openbsd_bindings() {
7+
use std::path::PathBuf;
8+
use std::env;
9+
10+
let bindings = bindgen::Builder::default()
11+
.clang_macro_fallback()
12+
.header("build/wrapper.h")
13+
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
14+
.generate()
15+
.unwrap_or_else(|e|
16+
panic!("Unable to generate bindings: {e}")
17+
);
18+
19+
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
20+
bindings
21+
.write_to_file(out_path.join("openbsd_bindings.rs"))
22+
.expect("couldn't write bindings");
23+
}

build/wrapper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include <sys/tty.h>

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub mod error;
7272
pub mod process;
7373
pub mod reader;
7474
pub mod session;
75+
#[cfg(target_os = "openbsd")] pub mod openbsd;
7576

7677
pub use reader::ReadUntil;
7778
pub use session::{spawn, spawn_bash, spawn_python, spawn_stream, spawn_with_options};

src/openbsd.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#![allow(warnings)]
2+
3+
include!(concat!(env!("OUT_DIR"), "/openbsd_bindings.rs"));

src/process.rs

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use crate::error::Error;
44
use nix;
55
use nix::fcntl::{open, OFlag};
6-
use nix::libc::STDERR_FILENO;
7-
use nix::pty::{grantpt, posix_openpt, unlockpt, PtyMaster};
6+
use nix::libc::{ioctl, STDERR_FILENO, TIOCSCTTY};
7+
use nix::pty::{grantpt, unlockpt, PtyMaster};
88
pub use nix::sys::{signal, wait};
99
use nix::sys::{stat, termios};
1010
use nix::unistd::{
@@ -17,6 +17,8 @@ use std::os::unix::io::AsRawFd;
1717
use std::os::unix::process::CommandExt;
1818
use std::process::Command;
1919
use std::{thread, time};
20+
use std::os::fd::OwnedFd;
21+
use std::path::PathBuf;
2022

2123
/// Start a process in a forked tty so you can interact with it the same as you would
2224
/// within a terminal
@@ -85,36 +87,123 @@ fn ptsname_r(fd: &PtyMaster) -> nix::Result<String> {
8587
}
8688
}
8789

90+
#[cfg(not(target_os = "openbsd"))]
91+
fn open_pty() -> nix::Result<OpenPty> {
92+
// Open a new PTY master
93+
let master_fd = nix::pty::posix_openpt(OFlag::O_RDWR)?;
94+
95+
// Allow a slave to be generated for it
96+
grantpt(&master_fd)?;
97+
unlockpt(&master_fd)?;
98+
99+
// on Linux this is the libc function, on OSX this is our implementation of ptsname_r
100+
let slave_name = PathBuf::from(ptsname_r(&master_fd)?);
101+
102+
Ok(
103+
OpenPty {
104+
master_fd,
105+
slave_fd: None,
106+
slave_name,
107+
}
108+
)
109+
}
110+
111+
#[cfg(target_os = "openbsd")]
112+
/// Open pty on OpenBSD
113+
///
114+
/// OpenBSD does not have either ptsname_r or the Linux's ioctls.
115+
/// pty(4) references and documents this method of pty creation
116+
fn open_pty() -> nix::Result<OpenPty> {
117+
use crate::openbsd::{PATH_PTMDEV, PTMGET};
118+
use crate::openbsd::ptmget;
119+
use nix::libc::{c_char, ioctl, open, O_RDWR};
120+
use std::ffi::CStr;
121+
use std::os::fd::{FromRawFd, OwnedFd};
122+
123+
// ioctls on /dev/ptm is the underlying mechanism for pty management
124+
// on OpenBSD
125+
let fd = unsafe {
126+
match open(PATH_PTMDEV.as_ptr() as *const c_char, O_RDWR) {
127+
-1 => return Err(nix::Error::last()),
128+
fd => OwnedFd::from_raw_fd(fd),
129+
}
130+
};
131+
132+
// here, we get the fds and names for the master and slave devices
133+
// right away
134+
let mut info = std::mem::MaybeUninit::<ptmget>::uninit();
135+
let info = unsafe {
136+
match ioctl(fd.as_raw_fd(), PTMGET.into(), info.as_mut_ptr()) {
137+
-1 => return Err(nix::Error::last()),
138+
_ => info.assume_init(),
139+
}
140+
};
141+
142+
let master_fd = unsafe {
143+
PtyMaster::from_owned_fd(
144+
OwnedFd::from_raw_fd(info.cfd)
145+
)
146+
};
147+
let slave_fd = Some(
148+
unsafe { OwnedFd::from_raw_fd(info.sfd) }
149+
);
150+
151+
// on OpenBSD these are no-ops (only checking for the argument fd
152+
// to be a pty master), but they may become required some day
153+
grantpt(&master_fd)?;
154+
unlockpt(&master_fd)?;
155+
156+
Ok(
157+
OpenPty {
158+
master_fd,
159+
slave_fd,
160+
slave_name: PathBuf::from(
161+
unsafe { CStr::from_ptr(info.sn.as_ptr()) }
162+
.to_string_lossy().into_owned()
163+
)
164+
}
165+
)
166+
}
167+
168+
struct OpenPty {
169+
master_fd: PtyMaster,
170+
slave_fd: Option<OwnedFd>,
171+
slave_name: PathBuf,
172+
}
173+
88174
impl PtyProcess {
89175
/// Start a process in a forked pty
90176
pub fn new(mut command: Command) -> Result<Self, Error> {
91-
// Open a new PTY master
92-
let master_fd = posix_openpt(OFlag::O_RDWR)?;
93-
94-
// Allow a slave to be generated for it
95-
grantpt(&master_fd)?;
96-
unlockpt(&master_fd)?;
97-
98-
// on Linux this is the libc function, on OSX this is our implementation of ptsname_r
99-
let slave_name = ptsname_r(&master_fd)?;
177+
let OpenPty { master_fd, slave_fd, slave_name } = open_pty()?;
100178

101179
match unsafe { fork()? } {
102180
ForkResult::Child => {
103181
// Avoid leaking master fd
104182
close(master_fd.as_raw_fd())?;
105183

106184
setsid()?; // create new session with child as session leader
107-
let slave_fd = open(
108-
std::path::Path::new(&slave_name),
109-
OFlag::O_RDWR,
110-
stat::Mode::empty(),
111-
)?;
185+
let slave_fd = slave_fd
186+
.ok_or(()).or_else(|_|
187+
open(
188+
std::path::Path::new(&slave_name),
189+
OFlag::O_RDWR,
190+
stat::Mode::empty(),
191+
)
192+
)?;
112193

113194
// assign stdin, stdout, stderr to the tty, just like a terminal does
114195
dup2_stdin(&slave_fd)?;
115196
dup2_stdout(&slave_fd)?;
116197
dup2_stderr(&slave_fd)?;
117198

199+
// While Linux and macOS are lenient with the requirements
200+
// for receiving signals through the pty, OpenBSD does check
201+
// the process's controlling terminal and this is necessary.
202+
#[cfg(target_os = "openbsd")]
203+
if unsafe { ioctl(slave_fd.as_raw_fd(), TIOCSCTTY.into()) } == -1 {
204+
return Err(nix::Error::last().into())
205+
}
206+
118207
// Avoid leaking slave fd
119208
if slave_fd.as_raw_fd() > STDERR_FILENO {
120209
close(slave_fd)?;

0 commit comments

Comments
 (0)