1 use std::os::unix::prelude::*;
2 use tempfile::tempfile;
3
4 use nix::errno::Errno;
5 use nix::fcntl;
6 use nix::pty::openpty;
7 use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags};
8 use nix::unistd::{close, read, write};
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();
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]
test_tcgetattr_enotty()32 fn 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]
test_tcgetattr_ebadf()42 fn test_tcgetattr_ebadf() {
43 assert_eq!(termios::tcgetattr(-1).err(), Some(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 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]
test_local_flags()94 fn 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