2019-02-03 09:05:30 +01:00
|
|
|
// Common functions that are unfortunately missing on illumos and
|
|
|
|
// Solaris, but often needed by other crates.
|
|
|
|
|
|
|
|
use unix::solarish::*;
|
|
|
|
|
2020-08-03 06:14:05 +02:00
|
|
|
const PTEM: &[u8] = b"ptem\0";
|
|
|
|
const LDTERM: &[u8] = b"ldterm\0";
|
|
|
|
|
2019-02-03 09:05:30 +01:00
|
|
|
pub unsafe fn cfmakeraw(termios: *mut ::termios) {
|
2021-04-06 02:57:12 +02:00
|
|
|
(*termios).c_iflag &=
|
|
|
|
!(IMAXBEL | IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
fix cfmakeraw() for illumos and Solaris
This change fixes two issues. First, the current cfmakeraw()
implementation in this crate appears to be making a stack copy of the
input "struct termios" before modifying it, rather than correctly
modifying the original through the pointer. Before this modification
the routine did not, thus, set the flags for raw mode.
Second, we address the default settings of the MIN and TIME terminal
options. On at least FreeBSD and Linux systems, the modern default
value for MIN appears to be 1; i.e., block and wait for at least one
input byte. On most Solaris and illumos systems, the MIN control
character slot overlaps with EOF, and thus has a default value of 4.
This breaks at least the examples in the "termion" crate, and probably
quite a lot of other software written first and foremost for Linux
systems. We need to force the MIN value to 1 while switching to raw
mode.
2020-04-04 18:53:26 +02:00
|
|
|
(*termios).c_oflag &= !OPOST;
|
|
|
|
(*termios).c_lflag &= !(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
|
|
|
(*termios).c_cflag &= !(CSIZE | PARENB);
|
|
|
|
(*termios).c_cflag |= CS8;
|
|
|
|
|
|
|
|
// By default, most software expects a pending read to block until at
|
|
|
|
// least one byte becomes available. As per termio(7I), this requires
|
|
|
|
// setting the MIN and TIME parameters appropriately.
|
|
|
|
//
|
|
|
|
// As a somewhat unfortunate artefact of history, the MIN and TIME slots
|
|
|
|
// in the control character array overlap with the EOF and EOL slots used
|
|
|
|
// for canonical mode processing. Because the EOF character needs to be
|
|
|
|
// the ASCII EOT value (aka Control-D), it has the byte value 4. When
|
|
|
|
// switching to raw mode, this is interpreted as a MIN value of 4; i.e.,
|
|
|
|
// reads will block until at least four bytes have been input.
|
|
|
|
//
|
|
|
|
// Other platforms with a distinct MIN slot like Linux and FreeBSD appear
|
|
|
|
// to default to a MIN value of 1, so we'll force that value here:
|
|
|
|
(*termios).c_cc[VMIN] = 1;
|
|
|
|
(*termios).c_cc[VTIME] = 0;
|
2019-02-03 09:05:30 +01:00
|
|
|
}
|
|
|
|
|
2021-04-06 02:57:12 +02:00
|
|
|
pub unsafe fn cfsetspeed(termios: *mut ::termios, speed: ::speed_t) -> ::c_int {
|
2019-02-03 09:05:30 +01:00
|
|
|
// Neither of these functions on illumos or Solaris actually ever
|
|
|
|
// return an error
|
|
|
|
::cfsetispeed(termios, speed);
|
|
|
|
::cfsetospeed(termios, speed);
|
|
|
|
0
|
|
|
|
}
|
2020-08-03 06:14:05 +02:00
|
|
|
|
|
|
|
unsafe fn bail(fdm: ::c_int, fds: ::c_int) -> ::c_int {
|
|
|
|
let e = *___errno();
|
|
|
|
if fds >= 0 {
|
|
|
|
::close(fds);
|
|
|
|
}
|
|
|
|
if fdm >= 0 {
|
|
|
|
::close(fdm);
|
|
|
|
}
|
|
|
|
*___errno() = e;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn openpty(
|
|
|
|
amain: *mut ::c_int,
|
|
|
|
asubord: *mut ::c_int,
|
|
|
|
name: *mut ::c_char,
|
|
|
|
termp: *const termios,
|
|
|
|
winp: *const ::winsize,
|
|
|
|
) -> ::c_int {
|
|
|
|
// Open the main pseudo-terminal device, making sure not to set it as the
|
|
|
|
// controlling terminal for this process:
|
|
|
|
let fdm = ::posix_openpt(O_RDWR | O_NOCTTY);
|
|
|
|
if fdm < 0 {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set permissions and ownership on the subordinate device and unlock it:
|
|
|
|
if ::grantpt(fdm) < 0 || ::unlockpt(fdm) < 0 {
|
|
|
|
return bail(fdm, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the path name of the subordinate device:
|
|
|
|
let subordpath = ::ptsname(fdm);
|
|
|
|
if subordpath.is_null() {
|
|
|
|
return bail(fdm, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the subordinate device without setting it as the controlling
|
|
|
|
// terminal for this process:
|
|
|
|
let fds = ::open(subordpath, O_RDWR | O_NOCTTY);
|
|
|
|
if fds < 0 {
|
|
|
|
return bail(fdm, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the STREAMS modules are already pushed:
|
|
|
|
let setup = ::ioctl(fds, I_FIND, LDTERM.as_ptr());
|
|
|
|
if setup < 0 {
|
|
|
|
return bail(fdm, fds);
|
|
|
|
} else if setup == 0 {
|
|
|
|
// The line discipline is not present, so push the appropriate STREAMS
|
|
|
|
// modules for the subordinate device:
|
2021-04-06 02:57:12 +02:00
|
|
|
if ::ioctl(fds, I_PUSH, PTEM.as_ptr()) < 0 || ::ioctl(fds, I_PUSH, LDTERM.as_ptr()) < 0 {
|
2020-08-03 06:14:05 +02:00
|
|
|
return bail(fdm, fds);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If provided, set the terminal parameters:
|
|
|
|
if !termp.is_null() && ::tcsetattr(fds, TCSAFLUSH, termp) != 0 {
|
|
|
|
return bail(fdm, fds);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If provided, set the window size:
|
|
|
|
if !winp.is_null() && ::ioctl(fds, TIOCSWINSZ, winp) < 0 {
|
|
|
|
return bail(fdm, fds);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the caller wants the name of the subordinate device, copy it out.
|
|
|
|
//
|
|
|
|
// Note that this is a terrible interface: there appears to be no standard
|
|
|
|
// upper bound on the copy length for this pointer. Nobody should pass
|
|
|
|
// anything but NULL here, preferring instead to use ptsname(3C) directly.
|
|
|
|
if !name.is_null() {
|
|
|
|
::strcpy(name, subordpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
*amain = fdm;
|
|
|
|
*asubord = fds;
|
|
|
|
0
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn forkpty(
|
|
|
|
amain: *mut ::c_int,
|
|
|
|
name: *mut ::c_char,
|
|
|
|
termp: *const termios,
|
|
|
|
winp: *const ::winsize,
|
|
|
|
) -> ::pid_t {
|
|
|
|
let mut fds = -1;
|
|
|
|
|
|
|
|
if openpty(amain, &mut fds, name, termp, winp) != 0 {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
let pid = ::fork();
|
|
|
|
if pid < 0 {
|
|
|
|
return bail(*amain, fds);
|
|
|
|
} else if pid > 0 {
|
|
|
|
// In the parent process, we close the subordinate device and return the
|
|
|
|
// process ID of the new child:
|
|
|
|
::close(fds);
|
|
|
|
return pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The rest of this function executes in the child process.
|
|
|
|
|
|
|
|
// Close the main side of the pseudo-terminal pair:
|
|
|
|
::close(*amain);
|
|
|
|
|
|
|
|
// Use TIOCSCTTY to set the subordinate device as our controlling
|
|
|
|
// terminal. This will fail (with ENOTTY) if we are not the leader in
|
|
|
|
// our own session, so we call setsid() first. Finally, arrange for
|
|
|
|
// the pseudo-terminal to occupy the standard I/O descriptors.
|
|
|
|
if ::setsid() < 0
|
|
|
|
|| ::ioctl(fds, TIOCSCTTY, 0) < 0
|
|
|
|
|| ::dup2(fds, 0) < 0
|
|
|
|
|| ::dup2(fds, 1) < 0
|
|
|
|
|| ::dup2(fds, 2) < 0
|
|
|
|
{
|
|
|
|
// At this stage there are no particularly good ways to handle failure.
|
|
|
|
// Exit as abruptly as possible, using _exit() to avoid messing with any
|
|
|
|
// state still shared with the parent process.
|
|
|
|
::_exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
// Close the inherited descriptor, taking care to avoid closing the standard
|
|
|
|
// descriptors by mistake:
|
|
|
|
if fds > 2 {
|
|
|
|
::close(fds);
|
|
|
|
}
|
|
|
|
|
|
|
|
0
|
|
|
|
}
|