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