13da5c369Sopenharmony_ciuse std::os::unix::prelude::*;
23da5c369Sopenharmony_ciuse tempfile::tempfile;
33da5c369Sopenharmony_ci
43da5c369Sopenharmony_ciuse nix::errno::Errno;
53da5c369Sopenharmony_ciuse nix::fcntl;
63da5c369Sopenharmony_ciuse nix::pty::openpty;
73da5c369Sopenharmony_ciuse nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags};
83da5c369Sopenharmony_ciuse nix::unistd::{close, read, write};
93da5c369Sopenharmony_ci
103da5c369Sopenharmony_ci/// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s
113da5c369Sopenharmony_cifn write_all(f: RawFd, buf: &[u8]) {
123da5c369Sopenharmony_ci    let mut len = 0;
133da5c369Sopenharmony_ci    while len < buf.len() {
143da5c369Sopenharmony_ci        len += write(f, &buf[len..]).unwrap();
153da5c369Sopenharmony_ci    }
163da5c369Sopenharmony_ci}
173da5c369Sopenharmony_ci
183da5c369Sopenharmony_ci// Test tcgetattr on a terminal
193da5c369Sopenharmony_ci#[test]
203da5c369Sopenharmony_cifn test_tcgetattr_pty() {
213da5c369Sopenharmony_ci    // openpty uses ptname(3) internally
223da5c369Sopenharmony_ci    let _m = crate::PTSNAME_MTX.lock();
233da5c369Sopenharmony_ci
243da5c369Sopenharmony_ci    let pty = openpty(None, None).expect("openpty failed");
253da5c369Sopenharmony_ci    termios::tcgetattr(pty.slave).unwrap();
263da5c369Sopenharmony_ci    close(pty.master).expect("closing the master failed");
273da5c369Sopenharmony_ci    close(pty.slave).expect("closing the slave failed");
283da5c369Sopenharmony_ci}
293da5c369Sopenharmony_ci
303da5c369Sopenharmony_ci// Test tcgetattr on something that isn't a terminal
313da5c369Sopenharmony_ci#[test]
323da5c369Sopenharmony_cifn test_tcgetattr_enotty() {
333da5c369Sopenharmony_ci    let file = tempfile().unwrap();
343da5c369Sopenharmony_ci    assert_eq!(
353da5c369Sopenharmony_ci        termios::tcgetattr(file.as_raw_fd()).err(),
363da5c369Sopenharmony_ci        Some(Errno::ENOTTY)
373da5c369Sopenharmony_ci    );
383da5c369Sopenharmony_ci}
393da5c369Sopenharmony_ci
403da5c369Sopenharmony_ci// Test tcgetattr on an invalid file descriptor
413da5c369Sopenharmony_ci#[test]
423da5c369Sopenharmony_cifn test_tcgetattr_ebadf() {
433da5c369Sopenharmony_ci    assert_eq!(termios::tcgetattr(-1).err(), Some(Errno::EBADF));
443da5c369Sopenharmony_ci}
453da5c369Sopenharmony_ci
463da5c369Sopenharmony_ci// Test modifying output flags
473da5c369Sopenharmony_ci#[test]
483da5c369Sopenharmony_cifn test_output_flags() {
493da5c369Sopenharmony_ci    // openpty uses ptname(3) internally
503da5c369Sopenharmony_ci    let _m = crate::PTSNAME_MTX.lock();
513da5c369Sopenharmony_ci
523da5c369Sopenharmony_ci    // Open one pty to get attributes for the second one
533da5c369Sopenharmony_ci    let mut termios = {
543da5c369Sopenharmony_ci        let pty = openpty(None, None).expect("openpty failed");
553da5c369Sopenharmony_ci        assert!(pty.master > 0);
563da5c369Sopenharmony_ci        assert!(pty.slave > 0);
573da5c369Sopenharmony_ci        let termios = tcgetattr(pty.slave).expect("tcgetattr failed");
583da5c369Sopenharmony_ci        close(pty.master).unwrap();
593da5c369Sopenharmony_ci        close(pty.slave).unwrap();
603da5c369Sopenharmony_ci        termios
613da5c369Sopenharmony_ci    };
623da5c369Sopenharmony_ci
633da5c369Sopenharmony_ci    // Make sure postprocessing '\r' isn't specified by default or this test is useless.
643da5c369Sopenharmony_ci    assert!(!termios
653da5c369Sopenharmony_ci        .output_flags
663da5c369Sopenharmony_ci        .contains(OutputFlags::OPOST | OutputFlags::OCRNL));
673da5c369Sopenharmony_ci
683da5c369Sopenharmony_ci    // Specify that '\r' characters should be transformed to '\n'
693da5c369Sopenharmony_ci    // OPOST is specified to enable post-processing
703da5c369Sopenharmony_ci    termios
713da5c369Sopenharmony_ci        .output_flags
723da5c369Sopenharmony_ci        .insert(OutputFlags::OPOST | OutputFlags::OCRNL);
733da5c369Sopenharmony_ci
743da5c369Sopenharmony_ci    // Open a pty
753da5c369Sopenharmony_ci    let pty = openpty(None, &termios).unwrap();
763da5c369Sopenharmony_ci    assert!(pty.master > 0);
773da5c369Sopenharmony_ci    assert!(pty.slave > 0);
783da5c369Sopenharmony_ci
793da5c369Sopenharmony_ci    // Write into the master
803da5c369Sopenharmony_ci    let string = "foofoofoo\r";
813da5c369Sopenharmony_ci    write_all(pty.master, string.as_bytes());
823da5c369Sopenharmony_ci
833da5c369Sopenharmony_ci    // Read from the slave verifying that the output has been properly transformed
843da5c369Sopenharmony_ci    let mut buf = [0u8; 10];
853da5c369Sopenharmony_ci    crate::read_exact(pty.slave, &mut buf);
863da5c369Sopenharmony_ci    let transformed_string = "foofoofoo\n";
873da5c369Sopenharmony_ci    close(pty.master).unwrap();
883da5c369Sopenharmony_ci    close(pty.slave).unwrap();
893da5c369Sopenharmony_ci    assert_eq!(&buf, transformed_string.as_bytes());
903da5c369Sopenharmony_ci}
913da5c369Sopenharmony_ci
923da5c369Sopenharmony_ci// Test modifying local flags
933da5c369Sopenharmony_ci#[test]
943da5c369Sopenharmony_cifn test_local_flags() {
953da5c369Sopenharmony_ci    // openpty uses ptname(3) internally
963da5c369Sopenharmony_ci    let _m = crate::PTSNAME_MTX.lock();
973da5c369Sopenharmony_ci
983da5c369Sopenharmony_ci    // Open one pty to get attributes for the second one
993da5c369Sopenharmony_ci    let mut termios = {
1003da5c369Sopenharmony_ci        let pty = openpty(None, None).unwrap();
1013da5c369Sopenharmony_ci        assert!(pty.master > 0);
1023da5c369Sopenharmony_ci        assert!(pty.slave > 0);
1033da5c369Sopenharmony_ci        let termios = tcgetattr(pty.slave).unwrap();
1043da5c369Sopenharmony_ci        close(pty.master).unwrap();
1053da5c369Sopenharmony_ci        close(pty.slave).unwrap();
1063da5c369Sopenharmony_ci        termios
1073da5c369Sopenharmony_ci    };
1083da5c369Sopenharmony_ci
1093da5c369Sopenharmony_ci    // Make sure echo is specified by default or this test is useless.
1103da5c369Sopenharmony_ci    assert!(termios.local_flags.contains(LocalFlags::ECHO));
1113da5c369Sopenharmony_ci
1123da5c369Sopenharmony_ci    // Disable local echo
1133da5c369Sopenharmony_ci    termios.local_flags.remove(LocalFlags::ECHO);
1143da5c369Sopenharmony_ci
1153da5c369Sopenharmony_ci    // Open a new pty with our modified termios settings
1163da5c369Sopenharmony_ci    let pty = openpty(None, &termios).unwrap();
1173da5c369Sopenharmony_ci    assert!(pty.master > 0);
1183da5c369Sopenharmony_ci    assert!(pty.slave > 0);
1193da5c369Sopenharmony_ci
1203da5c369Sopenharmony_ci    // Set the master is in nonblocking mode or reading will never return.
1213da5c369Sopenharmony_ci    let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap();
1223da5c369Sopenharmony_ci    let new_flags =
1233da5c369Sopenharmony_ci        fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK;
1243da5c369Sopenharmony_ci    fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap();
1253da5c369Sopenharmony_ci
1263da5c369Sopenharmony_ci    // Write into the master
1273da5c369Sopenharmony_ci    let string = "foofoofoo\r";
1283da5c369Sopenharmony_ci    write_all(pty.master, string.as_bytes());
1293da5c369Sopenharmony_ci
1303da5c369Sopenharmony_ci    // Try to read from the master, which should not have anything as echoing was disabled.
1313da5c369Sopenharmony_ci    let mut buf = [0u8; 10];
1323da5c369Sopenharmony_ci    let read = read(pty.master, &mut buf).unwrap_err();
1333da5c369Sopenharmony_ci    close(pty.master).unwrap();
1343da5c369Sopenharmony_ci    close(pty.slave).unwrap();
1353da5c369Sopenharmony_ci    assert_eq!(read, Errno::EAGAIN);
1363da5c369Sopenharmony_ci}
137