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: BorrowedFd<'_> = match self {
77 FileDesc::Owned(fd: &OwnedFd) => fd.as_fd(),
78 FileDesc::Borrowed(fd: &BorrowedFd<'_>) => fd.as_fd(),
79 };
80 let result: usize = rustix::io::read(fd, buf:buffer)?;
81 Ok(result)
82 }
83
84 pub fn raw_fd(&self) -> RawFd {
85 match self {
86 FileDesc::Owned(fd: &OwnedFd) => fd.as_raw_fd(),
87 FileDesc::Borrowed(fd: &BorrowedFd<'_>) => 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: &OwnedFd) => fd.as_fd(),
117 FileDesc::Borrowed(fd: &BorrowedFd<'_>) => 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: BorrowedFd<'static> = rustix::stdio::stdin();
147 let fd: FileDesc<'_> = if rustix::termios::isatty(fd:stdin) {
148 FileDesc::Borrowed(stdin)
149 } else {
150 let dev_tty: File = File::options().read(true).write(true).open(path:"/dev/tty")?;
151 FileDesc::Owned(dev_tty.into())
152 };
153 Ok(fd)
154}
155