1use std::fs::File;
6use std::io;
7use std::mem::ManuallyDrop;
8use std::ops::{Deref, DerefMut};
9use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
10
11pub const FIRST_HIGH_FD: RawFd = 10;
13
14pub struct AutoClosePipes {
16 pub read: OwnedFd,
17 pub write: OwnedFd,
18}
19
20pub fn make_autoclose_pipes() -> io::Result<AutoClosePipes> {
23 let (read_fd, write_fd) =
24 nix::unistd::pipe().map_err(|e| io::Error::from_raw_os_error(e as i32))?;
25
26 let read_fd = heightenize_fd(read_fd)?;
28 let write_fd = heightenize_fd(write_fd)?;
29
30 Ok(AutoClosePipes {
31 read: read_fd,
32 write: write_fd,
33 })
34}
35
36fn heightenize_fd(fd: OwnedFd) -> io::Result<OwnedFd> {
38 let raw_fd = fd.as_raw_fd();
39
40 if raw_fd >= FIRST_HIGH_FD {
41 set_cloexec(raw_fd, true)?;
42 return Ok(fd);
43 }
44
45 let new_fd = nix::fcntl::fcntl(raw_fd, nix::fcntl::FcntlArg::F_DUPFD_CLOEXEC(FIRST_HIGH_FD))
47 .map_err(|e| io::Error::from_raw_os_error(e as i32))?;
48
49 Ok(unsafe { OwnedFd::from_raw_fd(new_fd) })
50}
51
52pub fn set_cloexec(fd: RawFd, should_set: bool) -> io::Result<()> {
54 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
55 if flags < 0 {
56 return Err(io::Error::last_os_error());
57 }
58
59 let new_flags = if should_set {
60 flags | libc::FD_CLOEXEC
61 } else {
62 flags & !libc::FD_CLOEXEC
63 };
64
65 if flags != new_flags {
66 let result = unsafe { libc::fcntl(fd, libc::F_SETFD, new_flags) };
67 if result < 0 {
68 return Err(io::Error::last_os_error());
69 }
70 }
71
72 Ok(())
73}
74
75pub fn make_fd_nonblocking(fd: RawFd) -> io::Result<()> {
77 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
78 if flags < 0 {
79 return Err(io::Error::last_os_error());
80 }
81
82 if (flags & libc::O_NONBLOCK) == 0 {
83 let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
84 if result < 0 {
85 return Err(io::Error::last_os_error());
86 }
87 }
88
89 Ok(())
90}
91
92pub fn make_fd_blocking(fd: RawFd) -> io::Result<()> {
94 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
95 if flags < 0 {
96 return Err(io::Error::last_os_error());
97 }
98
99 if (flags & libc::O_NONBLOCK) != 0 {
100 let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags & !libc::O_NONBLOCK) };
101 if result < 0 {
102 return Err(io::Error::last_os_error());
103 }
104 }
105
106 Ok(())
107}
108
109pub fn close_fd(fd: RawFd) {
111 if fd < 0 {
112 return;
113 }
114 loop {
115 let result = unsafe { libc::close(fd) };
116 if result == 0 {
117 break;
118 }
119 let err = io::Error::last_os_error();
120 if err.raw_os_error() != Some(libc::EINTR) {
121 break;
122 }
123 }
124}
125
126pub fn dup_fd(fd: RawFd) -> io::Result<RawFd> {
128 let new_fd = unsafe { libc::dup(fd) };
129 if new_fd < 0 {
130 Err(io::Error::last_os_error())
131 } else {
132 Ok(new_fd)
133 }
134}
135
136pub fn dup2_fd(src: RawFd, dst: RawFd) -> io::Result<()> {
138 if src == dst {
139 return Ok(());
140 }
141 let result = unsafe { libc::dup2(src, dst) };
142 if result < 0 {
143 Err(io::Error::last_os_error())
144 } else {
145 Ok(())
146 }
147}
148
149pub struct BorrowedFdFile(ManuallyDrop<File>);
151
152impl Deref for BorrowedFdFile {
153 type Target = File;
154 fn deref(&self) -> &Self::Target {
155 &self.0
156 }
157}
158
159impl DerefMut for BorrowedFdFile {
160 fn deref_mut(&mut self) -> &mut Self::Target {
161 &mut self.0
162 }
163}
164
165impl FromRawFd for BorrowedFdFile {
166 unsafe fn from_raw_fd(fd: RawFd) -> Self {
167 Self(ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }))
168 }
169}
170
171impl AsRawFd for BorrowedFdFile {
172 fn as_raw_fd(&self) -> RawFd {
173 self.0.as_raw_fd()
174 }
175}
176
177impl IntoRawFd for BorrowedFdFile {
178 fn into_raw_fd(self) -> RawFd {
179 ManuallyDrop::into_inner(self.0).into_raw_fd()
180 }
181}
182
183impl Clone for BorrowedFdFile {
184 fn clone(&self) -> Self {
185 unsafe { Self::from_raw_fd(self.as_raw_fd()) }
186 }
187}
188
189impl BorrowedFdFile {
190 pub fn stdin() -> Self {
191 unsafe { Self::from_raw_fd(libc::STDIN_FILENO) }
192 }
193
194 pub fn stdout() -> Self {
195 unsafe { Self::from_raw_fd(libc::STDOUT_FILENO) }
196 }
197
198 pub fn stderr() -> Self {
199 unsafe { Self::from_raw_fd(libc::STDERR_FILENO) }
200 }
201}
202
203impl io::Read for BorrowedFdFile {
204 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
205 self.deref_mut().read(buf)
206 }
207}
208
209impl io::Write for BorrowedFdFile {
210 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
211 self.deref_mut().write(buf)
212 }
213
214 fn flush(&mut self) -> io::Result<()> {
215 self.deref_mut().flush()
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum FdType {
226 Unused = 0,
227 External = 1,
228 Internal = 2,
229 Module = 3,
230 Flock = 4,
231 FlockExec = 5,
232 Proc = 6,
233}
234
235pub fn movefd(fd: RawFd) -> RawFd {
238 if fd < 0 || fd >= FIRST_HIGH_FD {
239 return fd;
240 }
241
242 unsafe {
243 let new_fd = libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, FIRST_HIGH_FD);
244 if new_fd != -1 {
245 libc::close(fd);
246 return new_fd;
247 }
248 }
249 fd
250}
251
252pub fn redup(x: RawFd, y: RawFd) -> RawFd {
255 if x < 0 {
256 unsafe { libc::close(y) };
257 return y;
258 }
259
260 if x == y {
261 return y;
262 }
263
264 let result = unsafe { libc::dup2(x, y) };
265 if result == -1 {
266 return -1;
267 }
268
269 unsafe { libc::close(x) };
270 y
271}
272
273pub fn zclose(fd: RawFd) -> i32 {
276 if fd >= 0 {
277 unsafe { libc::close(fd) }
278 } else {
279 -1
280 }
281}
282
283pub fn zdup(fd: RawFd) -> RawFd {
285 if fd < 0 {
286 return -1;
287 }
288 unsafe { libc::dup(fd) }
289}
290
291pub fn fd_is_open(fd: RawFd) -> bool {
293 if fd < 0 {
294 return false;
295 }
296 unsafe { libc::fcntl(fd, libc::F_GETFD, 0) >= 0 }
297}
298
299#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn test_make_autoclose_pipes() {
309 let pipes = make_autoclose_pipes().expect("Failed to create pipes");
310
311 assert!(pipes.read.as_raw_fd() >= FIRST_HIGH_FD);
313 assert!(pipes.write.as_raw_fd() >= FIRST_HIGH_FD);
314
315 let read_flags = unsafe { libc::fcntl(pipes.read.as_raw_fd(), libc::F_GETFD, 0) };
317 let write_flags = unsafe { libc::fcntl(pipes.write.as_raw_fd(), libc::F_GETFD, 0) };
318
319 assert!(read_flags >= 0);
320 assert!(write_flags >= 0);
321 assert_ne!(read_flags & libc::FD_CLOEXEC, 0);
322 assert_ne!(write_flags & libc::FD_CLOEXEC, 0);
323 }
324
325 #[test]
326 fn test_set_cloexec() {
327 let file = std::fs::File::open("/dev/null").unwrap();
328 let fd = file.as_raw_fd();
329
330 set_cloexec(fd, true).unwrap();
332 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
333 assert_ne!(flags & libc::FD_CLOEXEC, 0);
334
335 set_cloexec(fd, false).unwrap();
337 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
338 assert_eq!(flags & libc::FD_CLOEXEC, 0);
339 }
340
341 #[test]
342 fn test_nonblocking() {
343 let file = std::fs::File::open("/dev/null").unwrap();
344 let fd = file.as_raw_fd();
345
346 make_fd_nonblocking(fd).unwrap();
348 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
349 assert_ne!(flags & libc::O_NONBLOCK, 0);
350
351 make_fd_blocking(fd).unwrap();
353 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
354 assert_eq!(flags & libc::O_NONBLOCK, 0);
355 }
356
357 #[test]
358 fn test_borrowed_fd_file_does_not_close() {
359 let file = std::fs::File::open("/dev/null").unwrap();
360 let fd = file.as_raw_fd();
361
362 let borrowed = unsafe { BorrowedFdFile::from_raw_fd(fd) };
364 drop(borrowed);
365
366 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
368 assert!(
369 flags >= 0,
370 "fd should still be valid after dropping BorrowedFdFile"
371 );
372
373 drop(file);
375
376 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
378 assert!(
379 flags < 0,
380 "fd should be invalid after dropping original File"
381 );
382 }
383}