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