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());
}
}