1 use std::io::Write;
2 use std::path::Path;
3 use std::os::unix::prelude::*;
4 use tempfile::tempfile;
5
6 use libc::{_exit, STDOUT_FILENO};
7 use nix::fcntl::{OFlag, open};
8 use nix::pty::*;
9 use nix::sys::stat;
10 use nix::sys::termios::*;
11 use nix::unistd::{write, close, pause};
12
13 /// Regression test for Issue #659
14 /// This is the correct way to explicitly close a `PtyMaster`
15 #[test]
test_explicit_close()16 fn test_explicit_close() {
17 let mut f = {
18 let m = posix_openpt(OFlag::O_RDWR).unwrap();
19 close(m.into_raw_fd()).unwrap();
20 tempfile().unwrap()
21 };
22 // This should work. But if there's been a double close, then it will
23 // return EBADF
24 f.write_all(b"whatever").unwrap();
25 }
26
27 /// Test equivalence of `ptsname` and `ptsname_r`
28 #[test]
29 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptsname_equivalence()30 fn test_ptsname_equivalence() {
31 let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
32
33 // Open a new PTTY master
34 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
35 assert!(master_fd.as_raw_fd() > 0);
36
37 // Get the name of the slave
38 let slave_name = unsafe { ptsname(&master_fd) }.unwrap() ;
39 let slave_name_r = ptsname_r(&master_fd).unwrap();
40 assert_eq!(slave_name, slave_name_r);
41 }
42
43 /// Test data copying of `ptsname`
44 // TODO need to run in a subprocess, since ptsname is non-reentrant
45 #[test]
46 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptsname_copy()47 fn test_ptsname_copy() {
48 let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
49
50 // Open a new PTTY master
51 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
52 assert!(master_fd.as_raw_fd() > 0);
53
54 // Get the name of the slave
55 let slave_name1 = unsafe { ptsname(&master_fd) }.unwrap();
56 let slave_name2 = unsafe { ptsname(&master_fd) }.unwrap();
57 assert_eq!(slave_name1, slave_name2);
58 // Also make sure that the string was actually copied and they point to different parts of
59 // memory.
60 assert!(slave_name1.as_ptr() != slave_name2.as_ptr());
61 }
62
63 /// Test data copying of `ptsname_r`
64 #[test]
65 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptsname_r_copy()66 fn test_ptsname_r_copy() {
67 // Open a new PTTY master
68 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
69 assert!(master_fd.as_raw_fd() > 0);
70
71 // Get the name of the slave
72 let slave_name1 = ptsname_r(&master_fd).unwrap();
73 let slave_name2 = ptsname_r(&master_fd).unwrap();
74 assert_eq!(slave_name1, slave_name2);
75 assert!(slave_name1.as_ptr() != slave_name2.as_ptr());
76 }
77
78 /// Test that `ptsname` returns different names for different devices
79 #[test]
80 #[cfg(any(target_os = "android", target_os = "linux"))]
test_ptsname_unique()81 fn test_ptsname_unique() {
82 let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
83
84 // Open a new PTTY master
85 let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap();
86 assert!(master1_fd.as_raw_fd() > 0);
87
88 // Open a second PTTY master
89 let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap();
90 assert!(master2_fd.as_raw_fd() > 0);
91
92 // Get the name of the slave
93 let slave_name1 = unsafe { ptsname(&master1_fd) }.unwrap();
94 let slave_name2 = unsafe { ptsname(&master2_fd) }.unwrap();
95 assert!(slave_name1 != slave_name2);
96 }
97
98 /// Test opening a master/slave PTTY pair
99 ///
100 /// This is a single larger test because much of these functions aren't useful by themselves. So for
101 /// this test we perform the basic act of getting a file handle for a connect master/slave PTTY
102 /// pair.
103 #[test]
test_open_ptty_pair()104 fn test_open_ptty_pair() {
105 let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
106
107 // Open a new PTTY master
108 let master_fd = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed");
109 assert!(master_fd.as_raw_fd() > 0);
110
111 // Allow a slave to be generated for it
112 grantpt(&master_fd).expect("grantpt failed");
113 unlockpt(&master_fd).expect("unlockpt failed");
114
115 // Get the name of the slave
116 let slave_name = unsafe { ptsname(&master_fd) }.expect("ptsname failed");
117
118 // Open the slave device
119 let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty()).unwrap();
120 assert!(slave_fd > 0);
121 }
122
123 #[test]
test_openpty()124 fn test_openpty() {
125 // openpty uses ptname(3) internally
126 let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
127
128 let pty = openpty(None, None).unwrap();
129 assert!(pty.master > 0);
130 assert!(pty.slave > 0);
131
132 // Writing to one should be readable on the other one
133 let string = "foofoofoo\n";
134 let mut buf = [0u8; 10];
135 write(pty.master, string.as_bytes()).unwrap();
136 ::read_exact(pty.slave, &mut buf);
137
138 assert_eq!(&buf, string.as_bytes());
139
140 // Read the echo as well
141 let echoed_string = "foofoofoo\r\n";
142 let mut buf = [0u8; 11];
143 ::read_exact(pty.master, &mut buf);
144 assert_eq!(&buf, echoed_string.as_bytes());
145
146 let string2 = "barbarbarbar\n";
147 let echoed_string2 = "barbarbarbar\r\n";
148 let mut buf = [0u8; 14];
149 write(pty.slave, string2.as_bytes()).unwrap();
150 ::read_exact(pty.master, &mut buf);
151
152 assert_eq!(&buf, echoed_string2.as_bytes());
153
154 close(pty.master).unwrap();
155 close(pty.slave).unwrap();
156 }
157
158 #[test]
test_openpty_with_termios()159 fn test_openpty_with_termios() {
160 // openpty uses ptname(3) internally
161 let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
162
163 // Open one pty to get attributes for the second one
164 let mut termios = {
165 let pty = openpty(None, None).unwrap();
166 assert!(pty.master > 0);
167 assert!(pty.slave > 0);
168 let termios = tcgetattr(pty.master).unwrap();
169 close(pty.master).unwrap();
170 close(pty.slave).unwrap();
171 termios
172 };
173 // Make sure newlines are not transformed so the data is preserved when sent.
174 termios.output_flags.remove(OutputFlags::ONLCR);
175
176 let pty = openpty(None, &termios).unwrap();
177 // Must be valid file descriptors
178 assert!(pty.master > 0);
179 assert!(pty.slave > 0);
180
181 // Writing to one should be readable on the other one
182 let string = "foofoofoo\n";
183 let mut buf = [0u8; 10];
184 write(pty.master, string.as_bytes()).unwrap();
185 ::read_exact(pty.slave, &mut buf);
186
187 assert_eq!(&buf, string.as_bytes());
188
189 // read the echo as well
190 let echoed_string = "foofoofoo\n";
191 ::read_exact(pty.master, &mut buf);
192 assert_eq!(&buf, echoed_string.as_bytes());
193
194 let string2 = "barbarbarbar\n";
195 let echoed_string2 = "barbarbarbar\n";
196 let mut buf = [0u8; 13];
197 write(pty.slave, string2.as_bytes()).unwrap();
198 ::read_exact(pty.master, &mut buf);
199
200 assert_eq!(&buf, echoed_string2.as_bytes());
201
202 close(pty.master).unwrap();
203 close(pty.slave).unwrap();
204 }
205
206 #[test]
test_forkpty()207 fn test_forkpty() {
208 use nix::unistd::ForkResult::*;
209 use nix::sys::signal::*;
210 use nix::sys::wait::wait;
211 // forkpty calls openpty which uses ptname(3) internally.
212 let _m0 = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
213 // forkpty spawns a child process
214 let _m1 = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
215
216 let string = "naninani\n";
217 let echoed_string = "naninani\r\n";
218 let pty = forkpty(None, None).unwrap();
219 match pty.fork_result {
220 Child => {
221 write(STDOUT_FILENO, string.as_bytes()).unwrap();
222 pause(); // we need the child to stay alive until the parent calls read
223 unsafe { _exit(0); }
224 },
225 Parent { child } => {
226 let mut buf = [0u8; 10];
227 assert!(child.as_raw() > 0);
228 ::read_exact(pty.master, &mut buf);
229 kill(child, SIGTERM).unwrap();
230 wait().unwrap(); // keep other tests using generic wait from getting our child
231 assert_eq!(&buf, echoed_string.as_bytes());
232 close(pty.master).unwrap();
233 },
234 }
235 }
236