Skip to main content

dope_sys/io/fd/
mod.rs

1use std::os::fd::{AsRawFd as _, BorrowedFd, IntoRawFd as _, OwnedFd};
2
3use crate::{
4    Errno,
5    io::fd,
6    kernel::{self, errno},
7};
8
9pub mod raw;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Failure {
13    Get(Errno),
14    Set(Errno),
15}
16
17pub trait Flags: From<Failure> + Sized {
18    const READ: libc::c_int;
19    const WRITE: libc::c_int;
20    const BIT: libc::c_int;
21}
22
23/// Native descriptor operations using the calling thread's exact error slot.
24pub trait Fd: errno::Source {
25    fn try_limit() -> Result<(u64, u64), Errno> {
26        let mut limit = std::mem::MaybeUninit::uninit();
27        // SAFETY: the output holds one complete resource limit.
28        let result = unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, limit.as_mut_ptr()) };
29        // SAFETY: getrlimit returns zero or -1 with this thread's errno.
30        unsafe { kernel::raw::Status::new(result).try_zero::<Self, Errno>() }?;
31        // SAFETY: the successful query initialized both limit fields.
32        let limit = unsafe { limit.assume_init() };
33        Ok((limit.rlim_cur.into(), limit.rlim_max.into()))
34    }
35
36    /// Adds a descriptor flag, retrying only each interrupted native attempt.
37    fn update<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
38        update::<Self, E, true, true>(file)
39    }
40
41    /// Removes one descriptor flag, preserving all other native flags.
42    fn remove<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
43        update::<Self, E, true, false>(file)
44    }
45
46    /// Adds a descriptor flag with one get attempt followed by one set attempt.
47    fn try_update<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
48        update::<Self, E, false, true>(file)
49    }
50
51    /// Consumes descriptor ownership through one native close attempt.
52    /// The returned errno is observable; ownership is never issued for a retry.
53    fn close<E: From<Errno>>(file: OwnedFd) -> Result<(), E> {
54        // SAFETY: ownership is transferred exactly once to this close attempt.
55        let raw = unsafe { libc::close(file.into_raw_fd()) };
56        // SAFETY: close returns zero or -1; no intervening syscall changes errno.
57        unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }
58    }
59
60    fn duplicate<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<OwnedFd, E> {
61        // SAFETY: the descriptor is live; F_DUPFD_CLOEXEC creates a new owned descriptor.
62        let raw = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
63        // SAFETY: a successful result is the fresh descriptor returned above.
64        unsafe { <OwnedFd as fd::raw::Adopt>::try_adopt::<Self, E>(raw) }
65    }
66
67    fn set<const COMMAND: libc::c_int, E: From<Errno>>(
68        file: BorrowedFd<'_>,
69        flags: libc::c_int,
70    ) -> Result<(), E> {
71        set::<Self, E, COMMAND, false>(file, flags, E::from)
72    }
73
74    /// Applies scalar descriptor flags, retrying only interrupted attempts.
75    fn set_retry<const COMMAND: libc::c_int, E: From<Errno>>(
76        file: BorrowedFd<'_>,
77        flags: libc::c_int,
78    ) -> Result<(), E> {
79        set::<Self, E, COMMAND, true>(file, flags, E::from)
80    }
81    fn read<'a, E: From<Errno>>(
82        file: BorrowedFd<'_>,
83        output: &'a mut [u8],
84    ) -> Result<&'a mut [u8], E> {
85        // SAFETY: output exclusively owns the writable extent until read returns.
86        let raw = unsafe { libc::read(file.as_raw_fd(), output.as_mut_ptr().cast(), output.len()) };
87        // SAFETY: read returns -1 or initializes at most this exact output extent.
88        unsafe { crate::io::raw::Buffer::try_native::<Self, E>(output, raw) }
89    }
90
91    fn write<'a, E: From<Errno>>(file: BorrowedFd<'_>, input: &'a [u8]) -> Result<&'a [u8], E> {
92        // SAFETY: input retains the complete readable extent until write returns.
93        let raw = unsafe { libc::write(file.as_raw_fd(), input.as_ptr().cast(), input.len()) };
94        // SAFETY: write returns -1 or a count no larger than this exact input extent.
95        unsafe { crate::io::raw::Buffer::try_native::<Self, E>(input, raw) }
96    }
97}
98
99impl<T: errno::Source> Fd for T {}
100
101fn update<S: Fd + ?Sized, E: Flags, const RETRY: bool, const ENABLED: bool>(
102    file: BorrowedFd<'_>,
103) -> Result<(), E> {
104    const {
105        assert!(
106            (E::READ == libc::F_GETFD && E::WRITE == libc::F_SETFD)
107                || (E::READ == libc::F_GETFL && E::WRITE == libc::F_SETFL)
108        );
109    }
110    // SAFETY: READ admits only a live descriptor's scalar get command; errno
111    // is captured immediately after each native failure on the same thread.
112    let flags = unsafe {
113        kernel::raw::try_retry::<_, E, RETRY>(
114            || {
115                kernel::raw::Status::new(libc::fcntl(file.as_raw_fd(), E::READ))
116                    .try_value::<S, Errno>()
117            },
118            |error| E::from(Failure::Get(error)),
119        )
120    }?
121    .get();
122    let flags = if ENABLED {
123        flags | E::BIT
124    } else {
125        flags & !E::BIT
126    };
127    if E::WRITE == libc::F_SETFD {
128        set::<S, E, { libc::F_SETFD }, RETRY>(file, flags, |error| E::from(Failure::Set(error)))
129    } else {
130        set::<S, E, { libc::F_SETFL }, RETRY>(file, flags, |error| E::from(Failure::Set(error)))
131    }
132}
133
134fn set<S: Fd + ?Sized, E, const COMMAND: libc::c_int, const RETRY: bool>(
135    file: BorrowedFd<'_>,
136    flags: libc::c_int,
137    classify: impl FnOnce(Errno) -> E,
138) -> Result<(), E> {
139    const { assert!(COMMAND == libc::F_SETFD || COMMAND == libc::F_SETFL) };
140    // SAFETY: COMMAND admits only scalar flag updates on this borrowed
141    // descriptor; errno is captured immediately after each native failure.
142    unsafe {
143        kernel::raw::try_retry::<_, E, RETRY>(
144            || {
145                kernel::raw::Status::new(libc::fcntl(file.as_raw_fd(), COMMAND, flags))
146                    .try_zero::<S, Errno>()
147            },
148            classify,
149        )
150    }
151}