• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Intel Corporation. All Rights Reserved.
2 //
3 // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 //
5 // Copyright 2017 The Chromium OS Authors. All rights reserved.
6 //
7 // SPDX-License-Identifier: BSD-3-Clause
8 
9 //! Trait for working with [`termios`](http://man7.org/linux/man-pages/man3/termios.3.html).
10 
11 use std::io::StdinLock;
12 use std::mem::zeroed;
13 use std::os::unix::io::RawFd;
14 
15 use libc::{
16     c_int, fcntl, isatty, read, tcgetattr, tcsetattr, termios, ECHO, F_GETFL, F_SETFL, ICANON,
17     ISIG, O_NONBLOCK, STDIN_FILENO, TCSANOW,
18 };
19 
20 use crate::errno::{errno_result, Result};
21 
modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()>22 fn modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()> {
23     // SAFETY: Safe because we check the return value of isatty.
24     if unsafe { isatty(fd) } != 1 {
25         return Ok(());
26     }
27 
28     // SAFETY: The following pair are safe because termios gets totally overwritten by tcgetattr
29     // and we check the return result.
30     let mut termios: termios = unsafe { zeroed() };
31     // SAFETY: The parameter is valid and we check the result.
32     let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) };
33     if ret < 0 {
34         return errno_result();
35     }
36     let mut new_termios = termios;
37     f(&mut new_termios);
38     // SAFETY: Safe because the syscall will only read the extent of termios and we check the
39     // return result.
40     let ret = unsafe { tcsetattr(fd, TCSANOW, &new_termios as *const _) };
41     if ret < 0 {
42         return errno_result();
43     }
44 
45     Ok(())
46 }
47 
get_flags(fd: RawFd) -> Result<c_int>48 fn get_flags(fd: RawFd) -> Result<c_int> {
49     // SAFETY: Safe because no third parameter is expected and we check the return result.
50     let ret = unsafe { fcntl(fd, F_GETFL) };
51     if ret < 0 {
52         return errno_result();
53     }
54     Ok(ret)
55 }
56 
set_flags(fd: RawFd, flags: c_int) -> Result<()>57 fn set_flags(fd: RawFd, flags: c_int) -> Result<()> {
58     // SAFETY: Safe because we supply the third parameter and we check the return result.
59     let ret = unsafe { fcntl(fd, F_SETFL, flags) };
60     if ret < 0 {
61         return errno_result();
62     }
63     Ok(())
64 }
65 
66 /// Trait for file descriptors that are TTYs, according to
67 /// [`isatty`](http://man7.org/linux/man-pages/man3/isatty.3.html).
68 ///
69 /// # Safety
70 ///
71 /// This is marked unsafe because the implementation must ensure that the returned
72 /// RawFd is a valid fd and that the lifetime of the returned fd is at least that
73 /// of the trait object.
74 pub unsafe trait Terminal {
75     /// Get the file descriptor of the TTY.
tty_fd(&self) -> RawFd76     fn tty_fd(&self) -> RawFd;
77 
78     /// Set this terminal to canonical mode (`ICANON | ECHO | ISIG`).
79     ///
80     /// Enable canonical mode with `ISIG` that generates signal when receiving
81     /// any of the characters INTR, QUIT, SUSP, or DSUSP, and with `ECHO` that echo
82     /// the input characters. Refer to
83     /// [`termios`](http://man7.org/linux/man-pages/man3/termios.3.html).
set_canon_mode(&self) -> Result<()>84     fn set_canon_mode(&self) -> Result<()> {
85         modify_mode(self.tty_fd(), |t| t.c_lflag |= ICANON | ECHO | ISIG)
86     }
87 
88     /// Set this terminal to raw mode.
89     ///
90     /// Unset the canonical mode with (`!(ICANON | ECHO | ISIG)`) which means
91     /// input is available character by character, echoing is disabled and special
92     /// signal of receiving characters INTR, QUIT, SUSP, or DSUSP is disabled.
set_raw_mode(&self) -> Result<()>93     fn set_raw_mode(&self) -> Result<()> {
94         modify_mode(self.tty_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG))
95     }
96 
97     /// Set this terminal to non-blocking mode.
98     ///
99     /// If `non_block` is `true`, then `read_raw` will not block.
100     /// If `non_block` is `false`, then `read_raw` may block if
101     /// there is nothing to read.
set_non_block(&self, non_block: bool) -> Result<()>102     fn set_non_block(&self, non_block: bool) -> Result<()> {
103         let old_flags = get_flags(self.tty_fd())?;
104         let new_flags = if non_block {
105             old_flags | O_NONBLOCK
106         } else {
107             old_flags & !O_NONBLOCK
108         };
109         if new_flags != old_flags {
110             set_flags(self.tty_fd(), new_flags)?
111         }
112         Ok(())
113     }
114 
115     /// Read from a [`Terminal`](trait.Terminal.html).
116     ///
117     /// Read up to `out.len()` bytes from this terminal without any buffering.
118     /// This may block, depending on if non-blocking was enabled with `set_non_block`
119     /// or if there are any bytes to read.
120     /// If there is at least one byte that is readable, this will not block.
121     ///
122     /// # Examples
123     ///
124     /// ```
125     /// extern crate vmm_sys_util;
126     /// # use std::io;
127     /// # use std::os::unix::io::RawFd;
128     /// use vmm_sys_util::terminal::Terminal;
129     ///
130     /// let stdin_handle = io::stdin();
131     /// let stdin = stdin_handle.lock();
132     /// assert!(stdin.set_non_block(true).is_ok());
133     ///
134     /// let mut out = [0u8; 0];
135     /// assert_eq!(stdin.read_raw(&mut out[..]).unwrap(), 0);
136     /// ```
read_raw(&self, out: &mut [u8]) -> Result<usize>137     fn read_raw(&self, out: &mut [u8]) -> Result<usize> {
138         // SAFETY: Safe because read will only modify the pointer up to the length we give it and
139         // we check the return result.
140         let ret = unsafe { read(self.tty_fd(), out.as_mut_ptr() as *mut _, out.len()) };
141         if ret < 0 {
142             return errno_result();
143         }
144 
145         Ok(ret as usize)
146     }
147 }
148 
149 // SAFETY: Safe because we return a genuine terminal fd that never changes and shares our lifetime.
150 unsafe impl<'a> Terminal for StdinLock<'a> {
tty_fd(&self) -> RawFd151     fn tty_fd(&self) -> RawFd {
152         STDIN_FILENO
153     }
154 }
155 
156 #[cfg(test)]
157 mod tests {
158     #![allow(clippy::undocumented_unsafe_blocks)]
159     use super::*;
160     use std::fs::File;
161     use std::io;
162     use std::os::unix::io::AsRawFd;
163     use std::path::Path;
164 
165     unsafe impl Terminal for File {
tty_fd(&self) -> RawFd166         fn tty_fd(&self) -> RawFd {
167             self.as_raw_fd()
168         }
169     }
170 
171     #[test]
test_a_tty()172     fn test_a_tty() {
173         let stdin_handle = io::stdin();
174         let stdin = stdin_handle.lock();
175 
176         assert!(stdin.set_canon_mode().is_ok());
177         assert!(stdin.set_raw_mode().is_ok());
178         assert!(stdin.set_raw_mode().is_ok());
179         assert!(stdin.set_canon_mode().is_ok());
180         assert!(stdin.set_non_block(true).is_ok());
181         let mut out = [0u8; 0];
182         assert!(stdin.read_raw(&mut out[..]).is_ok());
183     }
184 
185     #[test]
test_a_non_tty()186     fn test_a_non_tty() {
187         let file = File::open(Path::new("/dev/zero")).unwrap();
188         assert!(file.set_canon_mode().is_ok());
189     }
190 }
191