33use crate :: error:: Error ;
44use nix;
55use 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 } ;
88pub use nix:: sys:: { signal, wait} ;
99use nix:: sys:: { stat, termios} ;
1010use nix:: unistd:: {
@@ -17,6 +17,8 @@ use std::os::unix::io::AsRawFd;
1717use std:: os:: unix:: process:: CommandExt ;
1818use std:: process:: Command ;
1919use 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+
88174impl 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