1use std::fs::File;
13use std::io;
14use std::mem::ManuallyDrop;
15use std::ops::{Deref, DerefMut};
16use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
17
18pub const FIRST_HIGH_FD: RawFd = 10;
22
23pub struct AutoClosePipes {
29 pub read: OwnedFd,
31 pub write: OwnedFd,
33}
34
35pub fn make_autoclose_pipes() -> io::Result<AutoClosePipes> {
42 let (read_fd, write_fd) =
43 nix::unistd::pipe().map_err(|e| io::Error::from_raw_os_error(e as i32))?;
44
45 let read_fd = heightenize_fd(read_fd)?;
47 let write_fd = heightenize_fd(write_fd)?;
48
49 Ok(AutoClosePipes {
50 read: read_fd,
51 write: write_fd,
52 })
53}
54
55fn heightenize_fd(fd: OwnedFd) -> io::Result<OwnedFd> {
60 let raw_fd = fd.as_raw_fd();
61
62 if raw_fd >= FIRST_HIGH_FD {
63 set_cloexec(raw_fd, true)?;
64 return Ok(fd);
65 }
66
67 let new_fd = nix::fcntl::fcntl(raw_fd, nix::fcntl::FcntlArg::F_DUPFD_CLOEXEC(FIRST_HIGH_FD))
69 .map_err(|e| io::Error::from_raw_os_error(e as i32))?;
70
71 Ok(unsafe { OwnedFd::from_raw_fd(new_fd) })
72}
73
74pub fn set_cloexec(fd: RawFd, should_set: bool) -> io::Result<()> {
80 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
81 if flags < 0 {
82 return Err(io::Error::last_os_error());
83 }
84
85 let new_flags = if should_set {
86 flags | libc::FD_CLOEXEC
87 } else {
88 flags & !libc::FD_CLOEXEC
89 };
90
91 if flags != new_flags {
92 let result = unsafe { libc::fcntl(fd, libc::F_SETFD, new_flags) };
93 if result < 0 {
94 return Err(io::Error::last_os_error());
95 }
96 }
97
98 Ok(())
99}
100
101pub fn make_fd_nonblocking(fd: RawFd) -> io::Result<()> {
105 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
106 if flags < 0 {
107 return Err(io::Error::last_os_error());
108 }
109
110 if (flags & libc::O_NONBLOCK) == 0 {
111 let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
112 if result < 0 {
113 return Err(io::Error::last_os_error());
114 }
115 }
116
117 Ok(())
118}
119
120pub fn make_fd_blocking(fd: RawFd) -> io::Result<()> {
124 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
125 if flags < 0 {
126 return Err(io::Error::last_os_error());
127 }
128
129 if (flags & libc::O_NONBLOCK) != 0 {
130 let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags & !libc::O_NONBLOCK) };
131 if result < 0 {
132 return Err(io::Error::last_os_error());
133 }
134 }
135
136 Ok(())
137}
138
139pub fn close_fd(fd: RawFd) {
143 if fd < 0 {
144 return;
145 }
146 loop {
147 let result = unsafe { libc::close(fd) };
148 if result == 0 {
149 break;
150 }
151 let err = io::Error::last_os_error();
152 if err.raw_os_error() != Some(libc::EINTR) {
153 break;
154 }
155 }
156}
157
158pub fn dup_fd(fd: RawFd) -> io::Result<RawFd> {
163 let new_fd = unsafe { libc::dup(fd) };
164 if new_fd < 0 {
165 Err(io::Error::last_os_error())
166 } else {
167 Ok(new_fd)
168 }
169}
170
171pub fn dup2_fd(src: RawFd, dst: RawFd) -> io::Result<()> {
175 if src == dst {
176 return Ok(());
177 }
178 let result = unsafe { libc::dup2(src, dst) };
179 if result < 0 {
180 Err(io::Error::last_os_error())
181 } else {
182 Ok(())
183 }
184}
185
186pub struct BorrowedFdFile(ManuallyDrop<File>);
192
193impl Deref for BorrowedFdFile {
194 type Target = File;
195 fn deref(&self) -> &Self::Target {
196 &self.0
197 }
198}
199
200impl DerefMut for BorrowedFdFile {
201 fn deref_mut(&mut self) -> &mut Self::Target {
202 &mut self.0
203 }
204}
205
206impl FromRawFd for BorrowedFdFile {
207 unsafe fn from_raw_fd(fd: RawFd) -> Self {
208 Self(ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }))
209 }
210}
211
212impl AsRawFd for BorrowedFdFile {
213 fn as_raw_fd(&self) -> RawFd {
214 self.0.as_raw_fd()
215 }
216}
217
218impl IntoRawFd for BorrowedFdFile {
219 fn into_raw_fd(self) -> RawFd {
220 ManuallyDrop::into_inner(self.0).into_raw_fd()
221 }
222}
223
224impl Clone for BorrowedFdFile {
225 fn clone(&self) -> Self {
226 unsafe { Self::from_raw_fd(self.as_raw_fd()) }
227 }
228}
229
230impl BorrowedFdFile {
231 pub fn stdin() -> Self {
233 unsafe { Self::from_raw_fd(libc::STDIN_FILENO) }
234 }
235 pub fn stdout() -> Self {
237 unsafe { Self::from_raw_fd(libc::STDOUT_FILENO) }
238 }
239 pub fn stderr() -> Self {
241 unsafe { Self::from_raw_fd(libc::STDERR_FILENO) }
242 }
243}
244
245impl io::Read for BorrowedFdFile {
246 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
247 self.deref_mut().read(buf)
248 }
249}
250
251impl io::Write for BorrowedFdFile {
252 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
253 self.deref_mut().write(buf)
254 }
255
256 fn flush(&mut self) -> io::Result<()> {
257 self.deref_mut().flush()
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub enum FdType {
271 Unused = 0,
272 External = 1,
273 Internal = 2,
274 Module = 3,
275 Flock = 4,
276 FlockExec = 5,
277 Proc = 6,
278}
279
280pub fn movefd(fd: RawFd) -> RawFd {
286 if !(0..FIRST_HIGH_FD).contains(&fd) {
287 return fd;
288 }
289
290 unsafe {
291 let new_fd = libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, FIRST_HIGH_FD);
292 if new_fd != -1 {
293 libc::close(fd);
294 return new_fd;
295 }
296 }
297 fd
298}
299
300pub fn redup(x: RawFd, y: RawFd) -> RawFd {
305 if x < 0 {
306 unsafe { libc::close(y) };
307 return y;
308 }
309
310 if x == y {
311 return y;
312 }
313
314 let result = unsafe { libc::dup2(x, y) };
315 if result == -1 {
316 return -1;
317 }
318
319 unsafe { libc::close(x) };
320 y
321}
322
323pub fn zclose(fd: RawFd) -> i32 {
328 if fd >= 0 {
329 unsafe { libc::close(fd) }
330 } else {
331 -1
332 }
333}
334
335pub fn zdup(fd: RawFd) -> RawFd {
340 if fd < 0 {
341 return -1;
342 }
343 unsafe { libc::dup(fd) }
344}
345
346pub fn fd_is_open(fd: RawFd) -> bool {
351 if fd < 0 {
352 return false;
353 }
354 unsafe { libc::fcntl(fd, libc::F_GETFD, 0) >= 0 }
355}
356
357#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_make_autoclose_pipes() {
367 let _g = crate::test_util::global_state_lock();
368 let pipes = make_autoclose_pipes().expect("Failed to create pipes");
369
370 assert!(pipes.read.as_raw_fd() >= FIRST_HIGH_FD);
372 assert!(pipes.write.as_raw_fd() >= FIRST_HIGH_FD);
373
374 let read_flags = unsafe { libc::fcntl(pipes.read.as_raw_fd(), libc::F_GETFD, 0) };
376 let write_flags = unsafe { libc::fcntl(pipes.write.as_raw_fd(), libc::F_GETFD, 0) };
377
378 assert!(read_flags >= 0);
379 assert!(write_flags >= 0);
380 assert_ne!(read_flags & libc::FD_CLOEXEC, 0);
381 assert_ne!(write_flags & libc::FD_CLOEXEC, 0);
382 }
383
384 #[test]
385 fn test_set_cloexec() {
386 let _g = crate::test_util::global_state_lock();
387 let file = std::fs::File::open("/dev/null").unwrap();
388 let fd = file.as_raw_fd();
389
390 set_cloexec(fd, true).unwrap();
392 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
393 assert_ne!(flags & libc::FD_CLOEXEC, 0);
394
395 set_cloexec(fd, false).unwrap();
397 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
398 assert_eq!(flags & libc::FD_CLOEXEC, 0);
399 }
400
401 #[test]
402 fn test_nonblocking() {
403 let _g = crate::test_util::global_state_lock();
404 let file = std::fs::File::open("/dev/null").unwrap();
405 let fd = file.as_raw_fd();
406
407 make_fd_nonblocking(fd).unwrap();
409 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
410 assert_ne!(flags & libc::O_NONBLOCK, 0);
411
412 make_fd_blocking(fd).unwrap();
414 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
415 assert_eq!(flags & libc::O_NONBLOCK, 0);
416 }
417
418 #[test]
419 fn test_borrowed_fd_file_does_not_close() {
420 let _g = crate::test_util::global_state_lock();
421 let file = std::fs::File::open("/dev/null").unwrap();
422 let fd = file.as_raw_fd();
423
424 {
431 let _borrowed = unsafe { BorrowedFdFile::from_raw_fd(fd) };
432 }
433
434 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
436 assert!(
437 flags >= 0,
438 "fd should still be valid after dropping BorrowedFdFile"
439 );
440
441 drop(file);
443
444 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
446 assert!(
447 flags < 0,
448 "fd should be invalid after dropping original File"
449 );
450 }
451
452 #[test]
457 fn fd_is_open_true_for_stdin_stdout_stderr() {
458 let _g = crate::test_util::global_state_lock();
459 assert!(fd_is_open(0));
461 assert!(fd_is_open(1));
462 assert!(fd_is_open(2));
463 }
464
465 #[test]
466 fn fd_is_open_false_for_huge_unused_fd() {
467 let _g = crate::test_util::global_state_lock();
468 assert!(!fd_is_open(99_999));
470 }
471
472 #[test]
473 fn fd_is_open_false_after_close() {
474 let _g = crate::test_util::global_state_lock();
475 let file = std::fs::File::open("/dev/null").unwrap();
476 let fd = file.as_raw_fd();
477 assert!(fd_is_open(fd));
478 drop(file);
479 assert!(!fd_is_open(fd));
481 }
482
483 #[test]
488 fn dup_fd_yields_independent_open_fd() {
489 let _g = crate::test_util::global_state_lock();
490 let file = std::fs::File::open("/dev/null").unwrap();
491 let fd = file.as_raw_fd();
492 let dup = dup_fd(fd).unwrap();
493 assert!(fd_is_open(dup));
494 assert_ne!(dup, fd, "dup must yield a fresh descriptor");
495 close_fd(dup);
497 assert!(fd_is_open(fd));
498 drop(file);
499 }
500
501 #[test]
502 fn dup_fd_on_invalid_fd_fails() {
503 let _g = crate::test_util::global_state_lock();
504 let r = dup_fd(99_998);
506 assert!(r.is_err(), "dup of invalid fd must error: {:?}", r);
507 }
508
509 #[test]
510 fn close_fd_on_invalid_fd_is_silent() {
511 let _g = crate::test_util::global_state_lock();
512 close_fd(99_997);
514 }
515
516 #[test]
521 fn movefd_returns_a_valid_high_fd() {
522 let _g = crate::test_util::global_state_lock();
523 let file = std::fs::File::open("/dev/null").unwrap();
524 let fd = file.as_raw_fd();
525 let dup = dup_fd(fd).unwrap();
526 let moved = movefd(dup);
527 if moved >= 0 {
530 assert!(fd_is_open(moved));
531 close_fd(moved);
532 }
533 drop(file);
534 }
535
536 #[test]
537 fn zclose_on_open_fd_returns_zero() {
538 let _g = crate::test_util::global_state_lock();
539 let file = std::fs::File::open("/dev/null").unwrap();
540 let fd = dup_fd(file.as_raw_fd()).unwrap();
541 let r = zclose(fd);
542 assert_eq!(r, 0, "zclose should return 0 on successful close");
543 drop(file);
544 }
545
546 #[test]
547 fn zdup_returns_new_open_fd() {
548 let _g = crate::test_util::global_state_lock();
549 let file = std::fs::File::open("/dev/null").unwrap();
550 let fd = file.as_raw_fd();
551 let dup = zdup(fd);
552 if dup >= 0 {
553 assert!(fd_is_open(dup));
554 assert_ne!(dup, fd);
555 close_fd(dup);
556 }
557 drop(file);
558 }
559
560 #[test]
561 fn zdup_on_invalid_fd_returns_negative() {
562 let _g = crate::test_util::global_state_lock();
563 let r = zdup(99_996);
564 assert!(r < 0, "zdup of invalid fd should be negative: {}", r);
565 }
566}