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