crossterm/terminal/sys/
file_descriptor.rs

1use std::io;
2
3#[cfg(feature = "libc")]
4use libc::size_t;
5#[cfg(not(feature = "libc"))]
6use rustix::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
7#[cfg(feature = "libc")]
8use std::{
9    fs,
10    marker::PhantomData,
11    os::unix::{
12        io::{IntoRawFd, RawFd},
13        prelude::AsRawFd,
14    },
15};
16
17/// A file descriptor wrapper.
18///
19/// It allows to retrieve raw file descriptor, write to the file descriptor and
20/// mainly it closes the file descriptor once dropped.
21#[derive(Debug)]
22#[cfg(feature = "libc")]
23pub struct FileDesc<'a> {
24    fd: RawFd,
25    close_on_drop: bool,
26    phantom: PhantomData<&'a ()>,
27}
28
29#[cfg(not(feature = "libc"))]
30pub enum FileDesc<'a> {
31    Owned(OwnedFd),
32    Borrowed(BorrowedFd<'a>),
33}
34
35#[cfg(feature = "libc")]
36impl FileDesc<'_> {
37    /// Constructs a new `FileDesc` with the given `RawFd`.
38    ///
39    /// # Arguments
40    ///
41    /// * `fd` - raw file descriptor
42    /// * `close_on_drop` - specify if the raw file descriptor should be closed once the `FileDesc` is dropped
43    pub fn new(fd: RawFd, close_on_drop: bool) -> FileDesc<'static> {
44        FileDesc {
45            fd,
46            close_on_drop,
47            phantom: PhantomData,
48        }
49    }
50
51    pub fn read(&self, buffer: &mut [u8]) -> io::Result<usize> {
52        let result = unsafe {
53            libc::read(
54                self.fd,
55                buffer.as_mut_ptr() as *mut libc::c_void,
56                buffer.len() as size_t,
57            )
58        };
59
60        if result < 0 {
61            Err(io::Error::last_os_error())
62        } else {
63            Ok(result as usize)
64        }
65    }
66
67    /// Returns the underlying file descriptor.
68    pub fn raw_fd(&self) -> RawFd {
69        self.fd
70    }
71}
72
73#[cfg(not(feature = "libc"))]
74impl FileDesc<'_> {
75    pub fn read(&self, buffer: &mut [u8]) -> io::Result<usize> {
76        let fd = match self {
77            FileDesc::Owned(fd) => fd.as_fd(),
78            FileDesc::Borrowed(fd) => fd.as_fd(),
79        };
80        let result = rustix::io::read(fd, buffer)?;
81        Ok(result)
82    }
83
84    pub fn raw_fd(&self) -> RawFd {
85        match self {
86            FileDesc::Owned(fd) => fd.as_raw_fd(),
87            FileDesc::Borrowed(fd) => fd.as_raw_fd(),
88        }
89    }
90}
91
92#[cfg(feature = "libc")]
93impl Drop for FileDesc<'_> {
94    fn drop(&mut self) {
95        if self.close_on_drop {
96            // Note that errors are ignored when closing a file descriptor. The
97            // reason for this is that if an error occurs we don't actually know if
98            // the file descriptor was closed or not, and if we retried (for
99            // something like EINTR), we might close another valid file descriptor
100            // opened after we closed ours.
101            let _ = unsafe { libc::close(self.fd) };
102        }
103    }
104}
105
106impl AsRawFd for FileDesc<'_> {
107    fn as_raw_fd(&self) -> RawFd {
108        self.raw_fd()
109    }
110}
111
112#[cfg(not(feature = "libc"))]
113impl AsFd for FileDesc<'_> {
114    fn as_fd(&self) -> BorrowedFd<'_> {
115        match self {
116            FileDesc::Owned(fd) => fd.as_fd(),
117            FileDesc::Borrowed(fd) => fd.as_fd(),
118        }
119    }
120}
121
122#[cfg(feature = "libc")]
123/// Creates a file descriptor pointing to the standard input or `/dev/tty`.
124pub fn tty_fd() -> io::Result<FileDesc<'static>> {
125    let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
126        (libc::STDIN_FILENO, false)
127    } else {
128        (
129            fs::OpenOptions::new()
130                .read(true)
131                .write(true)
132                .open("/dev/tty")?
133                .into_raw_fd(),
134            true,
135        )
136    };
137
138    Ok(FileDesc::new(fd, close_on_drop))
139}
140
141#[cfg(not(feature = "libc"))]
142/// Creates a file descriptor pointing to the standard input or `/dev/tty`.
143pub fn tty_fd() -> io::Result<FileDesc<'static>> {
144    use std::fs::File;
145
146    let stdin = rustix::stdio::stdin();
147    let fd = if rustix::termios::isatty(stdin) {
148        FileDesc::Borrowed(stdin)
149    } else {
150        let dev_tty = File::options().read(true).write(true).open("/dev/tty")?;
151        FileDesc::Owned(dev_tty.into())
152    };
153    Ok(fd)
154}