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