opentitanlib/util/
raw_tty.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use std::io::{IsTerminal, Read, Result};
6use std::ops::{Deref, DerefMut};
7use std::os::fd::{AsFd, BorrowedFd};
8
9use rustix::termios::{self, Termios};
10use tokio::io::AsyncRead;
11
12/// Raw TTY wrapper over any file descriptor.
13///
14/// The fd is converted into raw terminal mode and restored to its original state on drop, if it's a terminal.
15/// For non-terminal fds, nothing will be performed.
16pub struct RawTty<T: AsFd> {
17    termios: Option<Termios>,
18    fd: T,
19}
20
21impl<T: AsFd> Deref for RawTty<T> {
22    type Target = T;
23
24    fn deref(&self) -> &T {
25        &self.fd
26    }
27}
28
29impl<T: AsFd> DerefMut for RawTty<T> {
30    fn deref_mut(&mut self) -> &mut T {
31        &mut self.fd
32    }
33}
34
35impl<T: AsFd> AsFd for RawTty<T> {
36    fn as_fd(&self) -> BorrowedFd<'_> {
37        self.fd.as_fd()
38    }
39}
40
41impl<T: AsFd + Read> Read for RawTty<T> {
42    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
43        self.fd.read(buf)
44    }
45}
46
47impl<T: AsFd + AsyncRead> AsyncRead for RawTty<T> {
48    fn poll_read(
49        self: std::pin::Pin<&mut Self>,
50        cx: &mut std::task::Context<'_>,
51        buf: &mut tokio::io::ReadBuf<'_>,
52    ) -> std::task::Poll<Result<()>> {
53        // SAFETY: we never move `fd` out from `Self`, so it's structually pinned.
54        unsafe { self.map_unchecked_mut(|x| &mut x.fd) }.poll_read(cx, buf)
55    }
56}
57
58impl<T: AsFd> RawTty<T> {
59    pub fn new(fd: T) -> Result<Self> {
60        let termios = if fd.as_fd().is_terminal() {
61            let termios = termios::tcgetattr(&fd)?;
62
63            let mut raw_termios = termios.clone();
64            raw_termios.make_raw();
65            termios::tcsetattr(&fd, termios::OptionalActions::Now, &raw_termios)?;
66
67            Some(termios)
68        } else {
69            None
70        };
71
72        Ok(Self { termios, fd })
73    }
74}
75
76impl<T: AsFd> Drop for RawTty<T> {
77    fn drop(&mut self) {
78        if let Some(termios) = self.termios.as_ref() {
79            termios::tcsetattr(&self.fd, termios::OptionalActions::Now, termios).unwrap();
80        }
81    }
82}