1use std::fs::File; 2use std::io::{Read, Write}; 3use std::os::unix::prelude::*; 4use std::path::Path; 5use tempfile::tempfile; 6 7use libc::{_exit, STDOUT_FILENO}; 8use nix::fcntl::{open, OFlag}; 9use nix::pty::*; 10use nix::sys::stat; 11use nix::sys::termios::*; 12use nix::unistd::{close, pause, write}; 13 14/// Regression test for Issue #659 15/// This is the correct way to explicitly close a `PtyMaster` 16#[test] 17fn test_explicit_close() { 18 let mut f = { 19 let m = posix_openpt(OFlag::O_RDWR).unwrap(); 20 close(m.into_raw_fd()).unwrap(); 21 tempfile().unwrap() 22 }; 23 // This should work. But if there's been a double close, then it will 24 // return EBADF 25 f.write_all(b"whatever").unwrap(); 26} 27 28/// Test equivalence of `ptsname` and `ptsname_r` 29#[test] 30#[cfg(any(target_os = "android", target_os = "linux"))] 31fn test_ptsname_equivalence() { 32 let _m = crate::PTSNAME_MTX.lock(); 33 34 // Open a new PTTY master 35 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); 36 assert!(master_fd.as_raw_fd() > 0); 37 38 // Get the name of the slave 39 let slave_name = unsafe { ptsname(&master_fd) }.unwrap(); 40 let slave_name_r = ptsname_r(&master_fd).unwrap(); 41 assert_eq!(slave_name, slave_name_r); 42} 43 44/// Test data copying of `ptsname` 45// TODO need to run in a subprocess, since ptsname is non-reentrant 46#[test] 47#[cfg(any(target_os = "android", target_os = "linux"))] 48fn test_ptsname_copy() { 49 let _m = crate::PTSNAME_MTX.lock(); 50 51 // Open a new PTTY master 52 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); 53 assert!(master_fd.as_raw_fd() > 0); 54 55 // Get the name of the slave 56 let slave_name1 = unsafe { ptsname(&master_fd) }.unwrap(); 57 let slave_name2 = unsafe { ptsname(&master_fd) }.unwrap(); 58 assert_eq!(slave_name1, slave_name2); 59 // Also make sure that the string was actually copied and they point to different parts of 60 // memory. 61 assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr()); 62} 63 64/// Test data copying of `ptsname_r` 65#[test] 66#[cfg(any(target_os = "android", target_os = "linux"))] 67fn test_ptsname_r_copy() { 68 // Open a new PTTY master 69 let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); 70 assert!(master_fd.as_raw_fd() > 0); 71 72 // Get the name of the slave 73 let slave_name1 = ptsname_r(&master_fd).unwrap(); 74 let slave_name2 = ptsname_r(&master_fd).unwrap(); 75 assert_eq!(slave_name1, slave_name2); 76 assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr()); 77} 78 79/// Test that `ptsname` returns different names for different devices 80#[test] 81#[cfg(any(target_os = "android", target_os = "linux"))] 82fn test_ptsname_unique() { 83 let _m = crate::PTSNAME_MTX.lock(); 84 85 // Open a new PTTY master 86 let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap(); 87 assert!(master1_fd.as_raw_fd() > 0); 88 89 // Open a second PTTY master 90 let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap(); 91 assert!(master2_fd.as_raw_fd() > 0); 92 93 // Get the name of the slave 94 let slave_name1 = unsafe { ptsname(&master1_fd) }.unwrap(); 95 let slave_name2 = unsafe { ptsname(&master2_fd) }.unwrap(); 96 assert_ne!(slave_name1, slave_name2); 97} 98 99/// Common setup for testing PTTY pairs 100fn open_ptty_pair() -> (PtyMaster, File) { 101 let _m = crate::PTSNAME_MTX.lock(); 102 103 // Open a new PTTY master 104 let master = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed"); 105 106 // Allow a slave to be generated for it 107 grantpt(&master).expect("grantpt failed"); 108 unlockpt(&master).expect("unlockpt failed"); 109 110 // Get the name of the slave 111 let slave_name = unsafe { ptsname(&master) }.expect("ptsname failed"); 112 113 // Open the slave device 114 let slave_fd = 115 open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty()) 116 .unwrap(); 117 118 #[cfg(target_os = "illumos")] 119 // TODO: rewrite using ioctl! 120 #[allow(clippy::comparison_chain)] 121 { 122 use libc::{ioctl, I_FIND, I_PUSH}; 123 124 // On illumos systems, as per pts(7D), one must push STREAMS modules 125 // after opening a device path returned from ptsname(). 126 let ptem = b"ptem\0"; 127 let ldterm = b"ldterm\0"; 128 let r = unsafe { ioctl(slave_fd, I_FIND, ldterm.as_ptr()) }; 129 if r < 0 { 130 panic!("I_FIND failure"); 131 } else if r == 0 { 132 if unsafe { ioctl(slave_fd, I_PUSH, ptem.as_ptr()) } < 0 { 133 panic!("I_PUSH ptem failure"); 134 } 135 if unsafe { ioctl(slave_fd, I_PUSH, ldterm.as_ptr()) } < 0 { 136 panic!("I_PUSH ldterm failure"); 137 } 138 } 139 } 140 141 let slave = unsafe { File::from_raw_fd(slave_fd) }; 142 143 (master, slave) 144} 145 146/// Test opening a master/slave PTTY pair 147/// 148/// This uses a common `open_ptty_pair` because much of these functions aren't useful by 149/// themselves. So for this test we perform the basic act of getting a file handle for a 150/// master/slave PTTY pair, then just sanity-check the raw values. 151#[test] 152fn test_open_ptty_pair() { 153 let (master, slave) = open_ptty_pair(); 154 assert!(master.as_raw_fd() > 0); 155 assert!(slave.as_raw_fd() > 0); 156} 157 158/// Put the terminal in raw mode. 159fn make_raw(fd: RawFd) { 160 let mut termios = tcgetattr(fd).unwrap(); 161 cfmakeraw(&mut termios); 162 tcsetattr(fd, SetArg::TCSANOW, &termios).unwrap(); 163} 164 165/// Test `io::Read` on the PTTY master 166#[test] 167fn test_read_ptty_pair() { 168 let (mut master, mut slave) = open_ptty_pair(); 169 make_raw(slave.as_raw_fd()); 170 171 let mut buf = [0u8; 5]; 172 slave.write_all(b"hello").unwrap(); 173 master.read_exact(&mut buf).unwrap(); 174 assert_eq!(&buf, b"hello"); 175 176 let mut master = &master; 177 slave.write_all(b"hello").unwrap(); 178 master.read_exact(&mut buf).unwrap(); 179 assert_eq!(&buf, b"hello"); 180} 181 182/// Test `io::Write` on the PTTY master 183#[test] 184fn test_write_ptty_pair() { 185 let (mut master, mut slave) = open_ptty_pair(); 186 make_raw(slave.as_raw_fd()); 187 188 let mut buf = [0u8; 5]; 189 master.write_all(b"adios").unwrap(); 190 slave.read_exact(&mut buf).unwrap(); 191 assert_eq!(&buf, b"adios"); 192 193 let mut master = &master; 194 master.write_all(b"adios").unwrap(); 195 slave.read_exact(&mut buf).unwrap(); 196 assert_eq!(&buf, b"adios"); 197} 198 199#[test] 200fn test_openpty() { 201 // openpty uses ptname(3) internally 202 let _m = crate::PTSNAME_MTX.lock(); 203 204 let pty = openpty(None, None).unwrap(); 205 assert!(pty.master > 0); 206 assert!(pty.slave > 0); 207 208 // Writing to one should be readable on the other one 209 let string = "foofoofoo\n"; 210 let mut buf = [0u8; 10]; 211 write(pty.master, string.as_bytes()).unwrap(); 212 crate::read_exact(pty.slave, &mut buf); 213 214 assert_eq!(&buf, string.as_bytes()); 215 216 // Read the echo as well 217 let echoed_string = "foofoofoo\r\n"; 218 let mut buf = [0u8; 11]; 219 crate::read_exact(pty.master, &mut buf); 220 assert_eq!(&buf, echoed_string.as_bytes()); 221 222 let string2 = "barbarbarbar\n"; 223 let echoed_string2 = "barbarbarbar\r\n"; 224 let mut buf = [0u8; 14]; 225 write(pty.slave, string2.as_bytes()).unwrap(); 226 crate::read_exact(pty.master, &mut buf); 227 228 assert_eq!(&buf, echoed_string2.as_bytes()); 229 230 close(pty.master).unwrap(); 231 close(pty.slave).unwrap(); 232} 233 234#[test] 235fn test_openpty_with_termios() { 236 // openpty uses ptname(3) internally 237 let _m = crate::PTSNAME_MTX.lock(); 238 239 // Open one pty to get attributes for the second one 240 let mut termios = { 241 let pty = openpty(None, None).unwrap(); 242 assert!(pty.master > 0); 243 assert!(pty.slave > 0); 244 let termios = tcgetattr(pty.slave).unwrap(); 245 close(pty.master).unwrap(); 246 close(pty.slave).unwrap(); 247 termios 248 }; 249 // Make sure newlines are not transformed so the data is preserved when sent. 250 termios.output_flags.remove(OutputFlags::ONLCR); 251 252 let pty = openpty(None, &termios).unwrap(); 253 // Must be valid file descriptors 254 assert!(pty.master > 0); 255 assert!(pty.slave > 0); 256 257 // Writing to one should be readable on the other one 258 let string = "foofoofoo\n"; 259 let mut buf = [0u8; 10]; 260 write(pty.master, string.as_bytes()).unwrap(); 261 crate::read_exact(pty.slave, &mut buf); 262 263 assert_eq!(&buf, string.as_bytes()); 264 265 // read the echo as well 266 let echoed_string = "foofoofoo\n"; 267 crate::read_exact(pty.master, &mut buf); 268 assert_eq!(&buf, echoed_string.as_bytes()); 269 270 let string2 = "barbarbarbar\n"; 271 let echoed_string2 = "barbarbarbar\n"; 272 let mut buf = [0u8; 13]; 273 write(pty.slave, string2.as_bytes()).unwrap(); 274 crate::read_exact(pty.master, &mut buf); 275 276 assert_eq!(&buf, echoed_string2.as_bytes()); 277 278 close(pty.master).unwrap(); 279 close(pty.slave).unwrap(); 280} 281 282#[test] 283fn test_forkpty() { 284 use nix::sys::signal::*; 285 use nix::sys::wait::wait; 286 use nix::unistd::ForkResult::*; 287 // forkpty calls openpty which uses ptname(3) internally. 288 let _m0 = crate::PTSNAME_MTX.lock(); 289 // forkpty spawns a child process 290 let _m1 = crate::FORK_MTX.lock(); 291 292 let string = "naninani\n"; 293 let echoed_string = "naninani\r\n"; 294 let pty = unsafe { forkpty(None, None).unwrap() }; 295 match pty.fork_result { 296 Child => { 297 write(STDOUT_FILENO, string.as_bytes()).unwrap(); 298 pause(); // we need the child to stay alive until the parent calls read 299 unsafe { 300 _exit(0); 301 } 302 } 303 Parent { child } => { 304 let mut buf = [0u8; 10]; 305 assert!(child.as_raw() > 0); 306 crate::read_exact(pty.master, &mut buf); 307 kill(child, SIGTERM).unwrap(); 308 wait().unwrap(); // keep other tests using generic wait from getting our child 309 assert_eq!(&buf, echoed_string.as_bytes()); 310 close(pty.master).unwrap(); 311 } 312 } 313} 314