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    /// Adds a descriptor flag, retrying only each interrupted native attempt.
26    fn update<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
27        update::<Self, E, true, true>(file)
28    }
29
30    /// Removes one descriptor flag, preserving all other native flags.
31    fn remove<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
32        update::<Self, E, true, false>(file)
33    }
34
35    /// Adds a descriptor flag with one get attempt followed by one set attempt.
36    fn try_update<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
37        update::<Self, E, false, true>(file)
38    }
39
40    /// Consumes descriptor ownership through one native close attempt.
41    /// The returned errno is observable; ownership is never issued for a retry.
42    fn close<E: From<Errno>>(file: OwnedFd) -> Result<(), E> {
43        // SAFETY: ownership is transferred exactly once to this close attempt.
44        let raw = unsafe { libc::close(file.into_raw_fd()) };
45        // SAFETY: close returns zero or -1; no intervening syscall changes errno.
46        unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }
47    }
48
49    fn duplicate<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<OwnedFd, E> {
50        // SAFETY: the descriptor is live; F_DUPFD_CLOEXEC creates a new owned descriptor.
51        let raw = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
52        // SAFETY: a successful result is the fresh descriptor returned above.
53        unsafe { <OwnedFd as fd::raw::Adopt>::try_adopt::<Self, E>(raw) }
54    }
55
56    fn set<const COMMAND: libc::c_int, E: From<Errno>>(
57        file: BorrowedFd<'_>,
58        flags: libc::c_int,
59    ) -> Result<(), E> {
60        set::<Self, E, COMMAND, false>(file, flags, E::from)
61    }
62
63    /// Applies scalar descriptor flags, retrying only interrupted attempts.
64    fn set_retry<const COMMAND: libc::c_int, E: From<Errno>>(
65        file: BorrowedFd<'_>,
66        flags: libc::c_int,
67    ) -> Result<(), E> {
68        set::<Self, E, COMMAND, true>(file, flags, E::from)
69    }
70    fn read<'a, E: From<Errno>>(
71        file: BorrowedFd<'_>,
72        output: &'a mut [u8],
73    ) -> Result<&'a mut [u8], E> {
74        // SAFETY: output exclusively owns the writable extent until read returns.
75        let raw = unsafe { libc::read(file.as_raw_fd(), output.as_mut_ptr().cast(), output.len()) };
76        // SAFETY: read returns -1 or initializes at most this exact output extent.
77        unsafe { crate::io::raw::Buffer::try_native::<Self, E>(output, raw) }
78    }
79
80    fn write<'a, E: From<Errno>>(file: BorrowedFd<'_>, input: &'a [u8]) -> Result<&'a [u8], E> {
81        // SAFETY: input retains the complete readable extent until write returns.
82        let raw = unsafe { libc::write(file.as_raw_fd(), input.as_ptr().cast(), input.len()) };
83        // SAFETY: write returns -1 or a count no larger than this exact input extent.
84        unsafe { crate::io::raw::Buffer::try_native::<Self, E>(input, raw) }
85    }
86}
87
88impl<T: errno::Source> Fd for T {}
89
90fn update<S: Fd + ?Sized, E: Flags, const RETRY: bool, const ENABLED: bool>(
91    file: BorrowedFd<'_>,
92) -> Result<(), E> {
93    const {
94        assert!(
95            (E::READ == libc::F_GETFD && E::WRITE == libc::F_SETFD)
96                || (E::READ == libc::F_GETFL && E::WRITE == libc::F_SETFL)
97        );
98    }
99    // SAFETY: READ admits only a live descriptor's scalar get command; errno
100    // is captured immediately after each native failure on the same thread.
101    let flags = unsafe {
102        kernel::raw::try_retry::<_, E, RETRY>(
103            || {
104                kernel::raw::Status::new(libc::fcntl(file.as_raw_fd(), E::READ))
105                    .try_value::<S, Errno>()
106            },
107            |error| E::from(Failure::Get(error)),
108        )
109    }?
110    .get();
111    let flags = if ENABLED {
112        flags | E::BIT
113    } else {
114        flags & !E::BIT
115    };
116    if E::WRITE == libc::F_SETFD {
117        set::<S, E, { libc::F_SETFD }, RETRY>(file, flags, |error| E::from(Failure::Set(error)))
118    } else {
119        set::<S, E, { libc::F_SETFL }, RETRY>(file, flags, |error| E::from(Failure::Set(error)))
120    }
121}
122
123fn set<S: Fd + ?Sized, E, const COMMAND: libc::c_int, const RETRY: bool>(
124    file: BorrowedFd<'_>,
125    flags: libc::c_int,
126    classify: impl FnOnce(Errno) -> E,
127) -> Result<(), E> {
128    const { assert!(COMMAND == libc::F_SETFD || COMMAND == libc::F_SETFL) };
129    // SAFETY: COMMAND admits only scalar flag updates on this borrowed
130    // descriptor; errno is captured immediately after each native failure.
131    unsafe {
132        kernel::raw::try_retry::<_, E, RETRY>(
133            || {
134                kernel::raw::Status::new(libc::fcntl(file.as_raw_fd(), COMMAND, flags))
135                    .try_zero::<S, Errno>()
136            },
137            classify,
138        )
139    }
140}