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