Skip to content

Instantly share code, notes, and snippets.

@fkohlgrueber
Created October 18, 2019 10:14
Show Gist options
  • Save fkohlgrueber/fc2bc9c3753ccc4a03d80d7f6c9bbcf0 to your computer and use it in GitHub Desktop.
Save fkohlgrueber/fc2bc9c3753ccc4a03d80d7f6c9bbcf0 to your computer and use it in GitHub Desktop.
// tested using nix 0.15.0
extern crate nix;
use std::path::Path;
use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname};
use nix::fcntl::{OFlag, open};
use nix::sys::{stat, wait};
use nix::unistd::{fork, ForkResult, setsid, dup2};
use nix::libc::{STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::io::prelude::*;
use std::io::{BufReader, LineWriter};
#[derive(Debug)]
pub enum Error {
Nix(nix::Error),
Io(std::io::Error)
}
impl From<nix::Error> for Error {
fn from(other: nix::Error) -> Error {
Error::Nix(other)
}
}
impl From<std::io::Error> for Error {
fn from(other: std::io::Error) -> Error {
Error::Io(other)
}
}
pub fn run() -> Result<(), Error> {
// Open a new PTY master
let master_fd = posix_openpt(OFlag::O_RDWR)?;
// Allow a slave to be generated for it
grantpt(&master_fd)?;
unlockpt(&master_fd)?;
// Get the name of the slave
let slave_name = unsafe { ptsname(&master_fd)? };
match fork() {
Ok(ForkResult::Child) => {
setsid()?; // create new session with child as session leader
let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty())?;
// assign stdin, stdout, stderr to the tty, just like a terminal does
dup2(slave_fd, STDIN_FILENO)?;
dup2(slave_fd, STDOUT_FILENO)?;
dup2(slave_fd, STDERR_FILENO)?;
std::process::Command::new("cat").status()?;
}
Ok(ForkResult::Parent { child: _ }) => {
let f = unsafe { std::fs::File::from_raw_fd(master_fd.as_raw_fd()) };
let mut reader = BufReader::new(&f);
let mut writer = LineWriter::new(&f);
writer.write_all(b"hello world\n")?;
let mut s = String::new();
reader.read_line(&mut s)?; // what we just wrote in
reader.read_line(&mut s)?; // what cat wrote out
writer.write(&[3])?; // send ^C
writer.flush()?;
let mut buf = [0; 2]; // needs bytewise read as ^C has no newline
reader.read(&mut buf)?;
s += &String::from_utf8_lossy(&buf).to_string();
println!("{}", s);
println!("cat exit code: {:?}", wait::wait()?); // make sure cat really exited
}
Err(_) => println!("error"),
}
Ok(())
}
fn main() {
run().expect("could not execute command");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment