crossterm/terminal/sys/
file_descriptor.rs

1use std::{
2    fs, io,
3    os::unix::{
4        io::{IntoRawFd, RawFd},
5        prelude::AsRawFd,
6    },
7};
8
9use libc::size_t;
10
11/// A file descriptor wrapper.
12///
13/// It allows to retrieve raw file descriptor, write to the file descriptor and
14/// mainly it closes the file descriptor once dropped.
15#[derive(Debug)]
16pub struct FileDesc {
17    fd: RawFd,
18    close_on_drop: bool,
19}
20
21impl FileDesc {
22    /// Constructs a new `FileDesc` with the given `RawFd`.
23    ///
24    /// # Arguments
25    ///
26    /// * `fd` - raw file descriptor
27    /// * `close_on_drop` - specify if the raw file descriptor should be closed once the `FileDesc` is dropped
28    pub fn new(fd: RawFd, close_on_drop: bool) -> FileDesc {
29        FileDesc { fd, close_on_drop }
30    }
31
32    pub fn read(&self, buffer: &mut [u8], size: usize) -> io::Result<usize> {
33        let result = unsafe {
34            libc::read(
35                self.fd,
36                buffer.as_mut_ptr() as *mut libc::c_void,
37                size as size_t,
38            )
39        };
40
41        if result < 0 {
42            Err(io::Error::last_os_error())
43        } else {
44            Ok(result as usize)
45        }
46    }
47
48    /// Returns the underlying file descriptor.
49    pub fn raw_fd(&self) -> RawFd {
50        self.fd
51    }
52}
53
54impl Drop for FileDesc {
55    fn drop(&mut self) {
56        if self.close_on_drop {
57            // Note that errors are ignored when closing a file descriptor. The
58            // reason for this is that if an error occurs we don't actually know if
59            // the file descriptor was closed or not, and if we retried (for
60            // something like EINTR), we might close another valid file descriptor
61            // opened after we closed ours.
62            let _ = unsafe { libc::close(self.fd) };
63        }
64    }
65}
66
67impl AsRawFd for FileDesc {
68    fn as_raw_fd(&self) -> RawFd {
69        self.raw_fd()
70    }
71}
72
73/// Creates a file descriptor pointing to the standard input or `/dev/tty`.
74pub fn tty_fd() -> io::Result<FileDesc> {
75    let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
76        (libc::STDIN_FILENO, false)
77    } else {
78        (
79            fs::OpenOptions::new()
80                .read(true)
81                .write(true)
82                .open("/dev/tty")?
83                .into_raw_fd(),
84            true,
85        )
86    };
87
88    Ok(FileDesc::new(fd, close_on_drop))
89}