1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::io::Stdin;
6 use std::mem::zeroed;
7 use std::os::unix::io::RawFd;
8 
9 use libc::{
10     isatty, read, tcgetattr, tcsetattr, termios, ECHO, ICANON, ISIG, O_NONBLOCK, STDIN_FILENO,
11     TCSANOW,
12 };
13 
14 use crate::{add_fd_flags, clear_fd_flags, errno_result, Result};
15 
modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()>16 fn modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()> {
17     // Safe because we check the return value of isatty.
18     if unsafe { isatty(fd) } != 1 {
19         return Ok(());
20     }
21 
22     // The following pair are safe because termios gets totally overwritten by tcgetattr and we
23     // check the return result.
24     let mut termios: termios = unsafe { zeroed() };
25     let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) };
26     if ret < 0 {
27         return errno_result();
28     }
29     let mut new_termios = termios;
30     f(&mut new_termios);
31     // Safe because the syscall will only read the extent of termios and we check the return result.
32     let ret = unsafe { tcsetattr(fd, TCSANOW, &new_termios as *const _) };
33     if ret < 0 {
34         return errno_result();
35     }
36 
37     Ok(())
38 }
39 
40 /// Safe only when the FD given is valid and reading the fd will have no Rust safety implications.
read_raw(fd: RawFd, out: &mut [u8]) -> Result<usize>41 unsafe fn read_raw(fd: RawFd, out: &mut [u8]) -> Result<usize> {
42     let ret = read(fd, out.as_mut_ptr() as *mut _, out.len());
43     if ret < 0 {
44         return errno_result();
45     }
46 
47     Ok(ret as usize)
48 }
49 
50 /// Read raw bytes from stdin.
51 ///
52 /// This will block depending on the underlying mode of stdin. This will ignore the usual lock
53 /// around stdin that the stdlib usually uses. If other code is using stdin, it is undefined who
54 /// will get the underlying bytes.
read_raw_stdin(out: &mut [u8]) -> Result<usize>55 pub fn read_raw_stdin(out: &mut [u8]) -> Result<usize> {
56     // Safe because reading from stdin shouldn't have any safety implications.
57     unsafe { read_raw(STDIN_FILENO, out) }
58 }
59 
60 /// Trait for file descriptors that are TTYs, according to `isatty(3)`.
61 ///
62 /// This is marked unsafe because the implementation must promise that the returned RawFd is a valid
63 /// fd and that the lifetime of the returned fd is at least that of the trait object.
64 pub unsafe trait Terminal {
65     /// Gets the file descriptor of the TTY.
tty_fd(&self) -> RawFd66     fn tty_fd(&self) -> RawFd;
67 
68     /// Set this terminal's mode to canonical mode (`ICANON | ECHO | ISIG`).
set_canon_mode(&self) -> Result<()>69     fn set_canon_mode(&self) -> Result<()> {
70         modify_mode(self.tty_fd(), |t| t.c_lflag |= ICANON | ECHO | ISIG)
71     }
72 
73     /// Set this terminal's mode to raw mode (`!(ICANON | ECHO | ISIG)`).
set_raw_mode(&self) -> Result<()>74     fn set_raw_mode(&self) -> Result<()> {
75         modify_mode(self.tty_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG))
76     }
77 
78     /// Sets the non-blocking mode of this terminal's file descriptor.
79     ///
80     /// If `non_block` is `true`, then `read_raw` will not block. If `non_block` is `false`, then
81     /// `read_raw` may block if there is nothing to read.
set_non_block(&self, non_block: bool) -> Result<()>82     fn set_non_block(&self, non_block: bool) -> Result<()> {
83         if non_block {
84             add_fd_flags(self.tty_fd(), O_NONBLOCK)
85         } else {
86             clear_fd_flags(self.tty_fd(), O_NONBLOCK)
87         }
88     }
89 }
90 
91 // Safe because we return a genuine terminal fd that never changes and shares our lifetime.
92 unsafe impl Terminal for Stdin {
tty_fd(&self) -> RawFd93     fn tty_fd(&self) -> RawFd {
94         STDIN_FILENO
95     }
96 }
97