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