1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright 2019 Intel Corporation. All Rights Reserved.
//
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Copyright 2017 The Chromium OS Authors. All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause

//! Trait for working with [`termios`](http://man7.org/linux/man-pages/man3/termios.3.html).

use std::io::StdinLock;
use std::mem::zeroed;
use std::os::unix::io::RawFd;

use libc::{
    c_int, fcntl, isatty, read, tcgetattr, tcsetattr, termios, ECHO, F_GETFL, F_SETFL, ICANON,
    ISIG, O_NONBLOCK, STDIN_FILENO, TCSANOW,
};

use crate::errno::{errno_result, Result};

fn modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()> {
    // SAFETY: Safe because we check the return value of isatty.
    if unsafe { isatty(fd) } != 1 {
        return Ok(());
    }

    // SAFETY: The following pair are safe because termios gets totally overwritten by tcgetattr
    // and we check the return result.
    let mut termios: termios = unsafe { zeroed() };
    // SAFETY: The parameter is valid and we check the result.
    let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) };
    if ret < 0 {
        return errno_result();
    }
    let mut new_termios = termios;
    f(&mut new_termios);
    // SAFETY: Safe because the syscall will only read the extent of termios and we check the
    // return result.
    let ret = unsafe { tcsetattr(fd, TCSANOW, &new_termios as *const _) };
    if ret < 0 {
        return errno_result();
    }

    Ok(())
}

fn get_flags(fd: RawFd) -> Result<c_int> {
    // SAFETY: Safe because no third parameter is expected and we check the return result.
    let ret = unsafe { fcntl(fd, F_GETFL) };
    if ret < 0 {
        return errno_result();
    }
    Ok(ret)
}

fn set_flags(fd: RawFd, flags: c_int) -> Result<()> {
    // SAFETY: Safe because we supply the third parameter and we check the return result.
    let ret = unsafe { fcntl(fd, F_SETFL, flags) };
    if ret < 0 {
        return errno_result();
    }
    Ok(())
}

/// Trait for file descriptors that are TTYs, according to
/// [`isatty`](http://man7.org/linux/man-pages/man3/isatty.3.html).
///
/// # Safety
///
/// This is marked unsafe because the implementation must ensure that the returned
/// RawFd is a valid fd and that the lifetime of the returned fd is at least that
/// of the trait object.
pub unsafe trait Terminal {
    /// Get the file descriptor of the TTY.
    fn tty_fd(&self) -> RawFd;

    /// Set this terminal to canonical mode (`ICANON | ECHO | ISIG`).
    ///
    /// Enable canonical mode with `ISIG` that generates signal when receiving
    /// any of the characters INTR, QUIT, SUSP, or DSUSP, and with `ECHO` that echo
    /// the input characters. Refer to
    /// [`termios`](http://man7.org/linux/man-pages/man3/termios.3.html).
    fn set_canon_mode(&self) -> Result<()> {
        modify_mode(self.tty_fd(), |t| t.c_lflag |= ICANON | ECHO | ISIG)
    }

    /// Set this terminal to raw mode.
    ///
    /// Unset the canonical mode with (`!(ICANON | ECHO | ISIG)`) which means
    /// input is available character by character, echoing is disabled and special
    /// signal of receiving characters INTR, QUIT, SUSP, or DSUSP is disabled.
    fn set_raw_mode(&self) -> Result<()> {
        modify_mode(self.tty_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG))
    }

    /// Set this terminal to non-blocking mode.
    ///
    /// If `non_block` is `true`, then `read_raw` will not block.
    /// If `non_block` is `false`, then `read_raw` may block if
    /// there is nothing to read.
    fn set_non_block(&self, non_block: bool) -> Result<()> {
        let old_flags = get_flags(self.tty_fd())?;
        let new_flags = if non_block {
            old_flags | O_NONBLOCK
        } else {
            old_flags & !O_NONBLOCK
        };
        if new_flags != old_flags {
            set_flags(self.tty_fd(), new_flags)?
        }
        Ok(())
    }

    /// Read from a [`Terminal`](trait.Terminal.html).
    ///
    /// Read up to `out.len()` bytes from this terminal without any buffering.
    /// This may block, depending on if non-blocking was enabled with `set_non_block`
    /// or if there are any bytes to read.
    /// If there is at least one byte that is readable, this will not block.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate vmm_sys_util;
    /// # use std::io;
    /// # use std::os::unix::io::RawFd;
    /// use vmm_sys_util::terminal::Terminal;
    ///
    /// let stdin_handle = io::stdin();
    /// let stdin = stdin_handle.lock();
    /// assert!(stdin.set_non_block(true).is_ok());
    ///
    /// let mut out = [0u8; 0];
    /// assert_eq!(stdin.read_raw(&mut out[..]).unwrap(), 0);
    /// ```
    fn read_raw(&self, out: &mut [u8]) -> Result<usize> {
        // SAFETY: Safe because read will only modify the pointer up to the length we give it and
        // we check the return result.
        let ret = unsafe { read(self.tty_fd(), out.as_mut_ptr() as *mut _, out.len()) };
        if ret < 0 {
            return errno_result();
        }

        Ok(ret as usize)
    }
}

// SAFETY: Safe because we return a genuine terminal fd that never changes and shares our lifetime.
unsafe impl<'a> Terminal for StdinLock<'a> {
    fn tty_fd(&self) -> RawFd {
        STDIN_FILENO
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::undocumented_unsafe_blocks)]
    use super::*;
    use std::fs::File;
    use std::io;
    use std::os::unix::io::AsRawFd;
    use std::path::Path;

    unsafe impl Terminal for File {
        fn tty_fd(&self) -> RawFd {
            self.as_raw_fd()
        }
    }

    #[test]
    fn test_a_tty() {
        let stdin_handle = io::stdin();
        let stdin = stdin_handle.lock();

        assert!(stdin.set_canon_mode().is_ok());
        assert!(stdin.set_raw_mode().is_ok());
        assert!(stdin.set_raw_mode().is_ok());
        assert!(stdin.set_canon_mode().is_ok());
        assert!(stdin.set_non_block(true).is_ok());
        let mut out = [0u8; 0];
        assert!(stdin.read_raw(&mut out[..]).is_ok());
    }

    #[test]
    fn test_a_non_tty() {
        let file = File::open(Path::new("/dev/zero")).unwrap();
        assert!(file.set_canon_mode().is_ok());
    }
}