use std::fs::File; use std::io::{Read, Write}; use std::os::unix::prelude::*; use std::path::Path; use tempfile::tempfile; use libc::{_exit, STDOUT_FILENO}; use nix::fcntl::{open, OFlag}; use nix::pty::*; use nix::sys::stat; use nix::sys::termios::*; use nix::unistd::{close, pause, write}; /// Regression test for Issue #659 /// This is the correct way to explicitly close a `PtyMaster` #[test] fn test_explicit_close() { let mut f = { let m = posix_openpt(OFlag::O_RDWR).unwrap(); close(m.into_raw_fd()).unwrap(); tempfile().unwrap() }; // This should work. But if there's been a double close, then it will // return EBADF f.write_all(b"whatever").unwrap(); } /// Test equivalence of `ptsname` and `ptsname_r` #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptsname_equivalence() { let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); assert!(master_fd.as_raw_fd() > 0); // Get the name of the slave let slave_name = unsafe { ptsname(&master_fd) }.unwrap(); let slave_name_r = ptsname_r(&master_fd).unwrap(); assert_eq!(slave_name, slave_name_r); } /// Test data copying of `ptsname` // TODO need to run in a subprocess, since ptsname is non-reentrant #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptsname_copy() { let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); assert!(master_fd.as_raw_fd() > 0); // Get the name of the slave let slave_name1 = unsafe { ptsname(&master_fd) }.unwrap(); let slave_name2 = unsafe { ptsname(&master_fd) }.unwrap(); assert_eq!(slave_name1, slave_name2); // Also make sure that the string was actually copied and they point to different parts of // memory. assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr()); } /// Test data copying of `ptsname_r` #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptsname_r_copy() { // Open a new PTTY master let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); assert!(master_fd.as_raw_fd() > 0); // Get the name of the slave let slave_name1 = ptsname_r(&master_fd).unwrap(); let slave_name2 = ptsname_r(&master_fd).unwrap(); assert_eq!(slave_name1, slave_name2); assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr()); } /// Test that `ptsname` returns different names for different devices #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptsname_unique() { let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap(); assert!(master1_fd.as_raw_fd() > 0); // Open a second PTTY master let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap(); assert!(master2_fd.as_raw_fd() > 0); // Get the name of the slave let slave_name1 = unsafe { ptsname(&master1_fd) }.unwrap(); let slave_name2 = unsafe { ptsname(&master2_fd) }.unwrap(); assert_ne!(slave_name1, slave_name2); } /// Common setup for testing PTTY pairs fn open_ptty_pair() -> (PtyMaster, File) { let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed"); // Allow a slave to be generated for it grantpt(&master).expect("grantpt failed"); unlockpt(&master).expect("unlockpt failed"); // Get the name of the slave let slave_name = unsafe { ptsname(&master) }.expect("ptsname failed"); // Open the slave device let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty()) .unwrap(); #[cfg(target_os = "illumos")] // TODO: rewrite using ioctl! #[allow(clippy::comparison_chain)] { use libc::{ioctl, I_FIND, I_PUSH}; // On illumos systems, as per pts(7D), one must push STREAMS modules // after opening a device path returned from ptsname(). let ptem = b"ptem\0"; let ldterm = b"ldterm\0"; let r = unsafe { ioctl(slave_fd, I_FIND, ldterm.as_ptr()) }; if r < 0 { panic!("I_FIND failure"); } else if r == 0 { if unsafe { ioctl(slave_fd, I_PUSH, ptem.as_ptr()) } < 0 { panic!("I_PUSH ptem failure"); } if unsafe { ioctl(slave_fd, I_PUSH, ldterm.as_ptr()) } < 0 { panic!("I_PUSH ldterm failure"); } } } let slave = unsafe { File::from_raw_fd(slave_fd) }; (master, slave) } /// Test opening a master/slave PTTY pair /// /// This uses a common `open_ptty_pair` because much of these functions aren't useful by /// themselves. So for this test we perform the basic act of getting a file handle for a /// master/slave PTTY pair, then just sanity-check the raw values. #[test] fn test_open_ptty_pair() { let (master, slave) = open_ptty_pair(); assert!(master.as_raw_fd() > 0); assert!(slave.as_raw_fd() > 0); } /// Put the terminal in raw mode. fn make_raw(fd: RawFd) { let mut termios = tcgetattr(fd).unwrap(); cfmakeraw(&mut termios); tcsetattr(fd, SetArg::TCSANOW, &termios).unwrap(); } /// Test `io::Read` on the PTTY master #[test] fn test_read_ptty_pair() { let (mut master, mut slave) = open_ptty_pair(); make_raw(slave.as_raw_fd()); let mut buf = [0u8; 5]; slave.write_all(b"hello").unwrap(); master.read_exact(&mut buf).unwrap(); assert_eq!(&buf, b"hello"); let mut master = &master; slave.write_all(b"hello").unwrap(); master.read_exact(&mut buf).unwrap(); assert_eq!(&buf, b"hello"); } /// Test `io::Write` on the PTTY master #[test] fn test_write_ptty_pair() { let (mut master, mut slave) = open_ptty_pair(); make_raw(slave.as_raw_fd()); let mut buf = [0u8; 5]; master.write_all(b"adios").unwrap(); slave.read_exact(&mut buf).unwrap(); assert_eq!(&buf, b"adios"); let mut master = &master; master.write_all(b"adios").unwrap(); slave.read_exact(&mut buf).unwrap(); assert_eq!(&buf, b"adios"); } #[test] fn test_openpty() { // openpty uses ptname(3) internally let _m = crate::PTSNAME_MTX.lock(); let pty = openpty(None, None).unwrap(); assert!(pty.master > 0); assert!(pty.slave > 0); // Writing to one should be readable on the other one let string = "foofoofoo\n"; let mut buf = [0u8; 10]; write(pty.master, string.as_bytes()).unwrap(); crate::read_exact(pty.slave, &mut buf); assert_eq!(&buf, string.as_bytes()); // Read the echo as well let echoed_string = "foofoofoo\r\n"; let mut buf = [0u8; 11]; crate::read_exact(pty.master, &mut buf); assert_eq!(&buf, echoed_string.as_bytes()); let string2 = "barbarbarbar\n"; let echoed_string2 = "barbarbarbar\r\n"; let mut buf = [0u8; 14]; write(pty.slave, string2.as_bytes()).unwrap(); crate::read_exact(pty.master, &mut buf); assert_eq!(&buf, echoed_string2.as_bytes()); close(pty.master).unwrap(); close(pty.slave).unwrap(); } #[test] fn test_openpty_with_termios() { // openpty uses ptname(3) internally let _m = crate::PTSNAME_MTX.lock(); // Open one pty to get attributes for the second one let mut termios = { let pty = openpty(None, None).unwrap(); assert!(pty.master > 0); assert!(pty.slave > 0); let termios = tcgetattr(pty.slave).unwrap(); close(pty.master).unwrap(); close(pty.slave).unwrap(); termios }; // Make sure newlines are not transformed so the data is preserved when sent. termios.output_flags.remove(OutputFlags::ONLCR); let pty = openpty(None, &termios).unwrap(); // Must be valid file descriptors assert!(pty.master > 0); assert!(pty.slave > 0); // Writing to one should be readable on the other one let string = "foofoofoo\n"; let mut buf = [0u8; 10]; write(pty.master, string.as_bytes()).unwrap(); crate::read_exact(pty.slave, &mut buf); assert_eq!(&buf, string.as_bytes()); // read the echo as well let echoed_string = "foofoofoo\n"; crate::read_exact(pty.master, &mut buf); assert_eq!(&buf, echoed_string.as_bytes()); let string2 = "barbarbarbar\n"; let echoed_string2 = "barbarbarbar\n"; let mut buf = [0u8; 13]; write(pty.slave, string2.as_bytes()).unwrap(); crate::read_exact(pty.master, &mut buf); assert_eq!(&buf, echoed_string2.as_bytes()); close(pty.master).unwrap(); close(pty.slave).unwrap(); } #[test] fn test_forkpty() { use nix::sys::signal::*; use nix::sys::wait::wait; use nix::unistd::ForkResult::*; // forkpty calls openpty which uses ptname(3) internally. let _m0 = crate::PTSNAME_MTX.lock(); // forkpty spawns a child process let _m1 = crate::FORK_MTX.lock(); let string = "naninani\n"; let echoed_string = "naninani\r\n"; let pty = unsafe { forkpty(None, None).unwrap() }; match pty.fork_result { Child => { write(STDOUT_FILENO, string.as_bytes()).unwrap(); pause(); // we need the child to stay alive until the parent calls read unsafe { _exit(0); } } Parent { child } => { let mut buf = [0u8; 10]; assert!(child.as_raw() > 0); crate::read_exact(pty.master, &mut buf); kill(child, SIGTERM).unwrap(); wait().unwrap(); // keep other tests using generic wait from getting our child assert_eq!(&buf, echoed_string.as_bytes()); close(pty.master).unwrap(); } } }