1use std::os::unix::prelude::*; 2use tempfile::tempfile; 3 4use nix::errno::Errno; 5use nix::fcntl; 6use nix::pty::openpty; 7use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags}; 8use nix::unistd::{close, read, write}; 9 10/// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s 11fn write_all(f: RawFd, buf: &[u8]) { 12 let mut len = 0; 13 while len < buf.len() { 14 len += write(f, &buf[len..]).unwrap(); 15 } 16} 17 18// Test tcgetattr on a terminal 19#[test] 20fn test_tcgetattr_pty() { 21 // openpty uses ptname(3) internally 22 let _m = crate::PTSNAME_MTX.lock(); 23 24 let pty = openpty(None, None).expect("openpty failed"); 25 termios::tcgetattr(pty.slave).unwrap(); 26 close(pty.master).expect("closing the master failed"); 27 close(pty.slave).expect("closing the slave failed"); 28} 29 30// Test tcgetattr on something that isn't a terminal 31#[test] 32fn test_tcgetattr_enotty() { 33 let file = tempfile().unwrap(); 34 assert_eq!( 35 termios::tcgetattr(file.as_raw_fd()).err(), 36 Some(Errno::ENOTTY) 37 ); 38} 39 40// Test tcgetattr on an invalid file descriptor 41#[test] 42fn test_tcgetattr_ebadf() { 43 assert_eq!(termios::tcgetattr(-1).err(), Some(Errno::EBADF)); 44} 45 46// Test modifying output flags 47#[test] 48fn test_output_flags() { 49 // openpty uses ptname(3) internally 50 let _m = crate::PTSNAME_MTX.lock(); 51 52 // Open one pty to get attributes for the second one 53 let mut termios = { 54 let pty = openpty(None, None).expect("openpty failed"); 55 assert!(pty.master > 0); 56 assert!(pty.slave > 0); 57 let termios = tcgetattr(pty.slave).expect("tcgetattr failed"); 58 close(pty.master).unwrap(); 59 close(pty.slave).unwrap(); 60 termios 61 }; 62 63 // Make sure postprocessing '\r' isn't specified by default or this test is useless. 64 assert!(!termios 65 .output_flags 66 .contains(OutputFlags::OPOST | OutputFlags::OCRNL)); 67 68 // Specify that '\r' characters should be transformed to '\n' 69 // OPOST is specified to enable post-processing 70 termios 71 .output_flags 72 .insert(OutputFlags::OPOST | OutputFlags::OCRNL); 73 74 // Open a pty 75 let pty = openpty(None, &termios).unwrap(); 76 assert!(pty.master > 0); 77 assert!(pty.slave > 0); 78 79 // Write into the master 80 let string = "foofoofoo\r"; 81 write_all(pty.master, string.as_bytes()); 82 83 // Read from the slave verifying that the output has been properly transformed 84 let mut buf = [0u8; 10]; 85 crate::read_exact(pty.slave, &mut buf); 86 let transformed_string = "foofoofoo\n"; 87 close(pty.master).unwrap(); 88 close(pty.slave).unwrap(); 89 assert_eq!(&buf, transformed_string.as_bytes()); 90} 91 92// Test modifying local flags 93#[test] 94fn test_local_flags() { 95 // openpty uses ptname(3) internally 96 let _m = crate::PTSNAME_MTX.lock(); 97 98 // Open one pty to get attributes for the second one 99 let mut termios = { 100 let pty = openpty(None, None).unwrap(); 101 assert!(pty.master > 0); 102 assert!(pty.slave > 0); 103 let termios = tcgetattr(pty.slave).unwrap(); 104 close(pty.master).unwrap(); 105 close(pty.slave).unwrap(); 106 termios 107 }; 108 109 // Make sure echo is specified by default or this test is useless. 110 assert!(termios.local_flags.contains(LocalFlags::ECHO)); 111 112 // Disable local echo 113 termios.local_flags.remove(LocalFlags::ECHO); 114 115 // Open a new pty with our modified termios settings 116 let pty = openpty(None, &termios).unwrap(); 117 assert!(pty.master > 0); 118 assert!(pty.slave > 0); 119 120 // Set the master is in nonblocking mode or reading will never return. 121 let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap(); 122 let new_flags = 123 fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK; 124 fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap(); 125 126 // Write into the master 127 let string = "foofoofoo\r"; 128 write_all(pty.master, string.as_bytes()); 129 130 // Try to read from the master, which should not have anything as echoing was disabled. 131 let mut buf = [0u8; 10]; 132 let read = read(pty.master, &mut buf).unwrap_err(); 133 close(pty.master).unwrap(); 134 close(pty.slave).unwrap(); 135 assert_eq!(read, Errno::EAGAIN); 136} 137