summaryrefslogtreecommitdiffstats
path: root/third_party/rust/nix/test/test_pty.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/nix/test/test_pty.rs')
-rw-r--r--third_party/rust/nix/test/test_pty.rs276
1 files changed, 276 insertions, 0 deletions
diff --git a/third_party/rust/nix/test/test_pty.rs b/third_party/rust/nix/test/test_pty.rs
new file mode 100644
index 0000000000..4cc6620c3c
--- /dev/null
+++ b/third_party/rust/nix/test/test_pty.rs
@@ -0,0 +1,276 @@
+use std::fs::File;
+use std::io::{Read, Write};
+use std::os::unix::prelude::*;
+use std::path::Path;
+
+use libc::{_exit, STDOUT_FILENO};
+use nix::fcntl::{open, OFlag};
+use nix::pty::*;
+use nix::sys::stat;
+use nix::sys::termios::*;
+use nix::unistd::{pause, write};
+
+/// 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();
+
+ // 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();
+
+ // 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();
+
+ // Open a second PTTY master
+ let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap();
+
+ // 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.
+#[test]
+fn test_open_ptty_pair() {
+ let (_, _) = open_ptty_pair();
+}
+
+/// Put the terminal in raw mode.
+fn make_raw<Fd: AsFd>(fd: Fd) {
+ 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);
+
+ 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);
+
+ 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();
+
+ // Writing to one should be readable on the other one
+ let string = "foofoofoo\n";
+ let mut buf = [0u8; 10];
+ write(pty.master.as_raw_fd(), 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.as_raw_fd(), string2.as_bytes()).unwrap();
+ crate::read_exact(&pty.master, &mut buf);
+
+ assert_eq!(&buf, echoed_string2.as_bytes());
+}
+
+#[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();
+ tcgetattr(&pty.slave).unwrap()
+ };
+ // 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
+
+ // Writing to one should be readable on the other one
+ let string = "foofoofoo\n";
+ let mut buf = [0u8; 10];
+ write(pty.master.as_raw_fd(), 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.as_raw_fd(), string2.as_bytes()).unwrap();
+ crate::read_exact(&pty.master, &mut buf);
+
+ assert_eq!(&buf, echoed_string2.as_bytes());
+}
+
+#[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());
+ }
+ }
+}