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 {
47            kernel::raw::Status::new(raw).try_zero(|| kernel::raw::Code::last::<Self>().into())
48        }
49    }
50
51    fn duplicate<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<OwnedFd, E> {
52        // SAFETY: the descriptor is live; F_DUPFD_CLOEXEC creates a new owned descriptor.
53        let raw = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
54        // SAFETY: a successful result is the fresh descriptor returned above.
55        unsafe {
56            <OwnedFd as fd::raw::Adopt>::try_adopt(raw, || kernel::raw::Code::last::<Self>().into())
57        }
58    }
59
60    fn set<const COMMAND: libc::c_int, E: From<Errno>>(
61        file: BorrowedFd<'_>,
62        flags: libc::c_int,
63    ) -> Result<(), E> {
64        set::<Self, E, COMMAND, false>(file, flags, E::from)
65    }
66
67    /// Applies scalar descriptor flags, retrying only interrupted attempts.
68    fn set_retry<const COMMAND: libc::c_int, E: From<Errno>>(
69        file: BorrowedFd<'_>,
70        flags: libc::c_int,
71    ) -> Result<(), E> {
72        set::<Self, E, COMMAND, true>(file, flags, E::from)
73    }
74    fn read<'a, E: From<Errno>>(
75        file: BorrowedFd<'_>,
76        output: &'a mut [u8],
77    ) -> Result<&'a mut [u8], E> {
78        // SAFETY: output exclusively owns the writable extent until read returns.
79        let raw = unsafe { libc::read(file.as_raw_fd(), output.as_mut_ptr().cast(), output.len()) };
80        // SAFETY: read returns -1 or initializes at most this exact output extent.
81        unsafe {
82            crate::io::raw::Buffer::try_native(output, raw, || {
83                kernel::raw::Code::last::<Self>().into()
84            })
85        }
86    }
87
88    fn write<'a, E: From<Errno>>(file: BorrowedFd<'_>, input: &'a [u8]) -> Result<&'a [u8], E> {
89        // SAFETY: input retains the complete readable extent until write returns.
90        let raw = unsafe { libc::write(file.as_raw_fd(), input.as_ptr().cast(), input.len()) };
91        // SAFETY: write returns -1 or a count no larger than this exact input extent.
92        unsafe {
93            crate::io::raw::Buffer::try_native(input, raw, || {
94                kernel::raw::Code::last::<Self>().into()
95            })
96        }
97    }
98}
99
100impl<T: errno::Source> Fd for T {}
101
102fn update<S: Fd + ?Sized, E: Flags, const RETRY: bool, const ENABLED: bool>(
103    file: BorrowedFd<'_>,
104) -> Result<(), E> {
105    const {
106        assert!(
107            (E::READ == libc::F_GETFD && E::WRITE == libc::F_SETFD)
108                || (E::READ == libc::F_GETFL && E::WRITE == libc::F_SETFL)
109        );
110    }
111    // SAFETY: READ admits only a live descriptor's scalar get command; errno
112    // is captured immediately after each native failure on the same thread.
113    let flags = unsafe {
114        kernel::raw::try_retry::<_, E, RETRY>(
115            || {
116                kernel::raw::Status::new(libc::fcntl(file.as_raw_fd(), E::READ))
117                    .try_value::<Errno>(|| kernel::raw::Code::last::<S>().into())
118            },
119            |error| E::from(Failure::Get(error)),
120        )
121    }?
122    .get();
123    let flags = if ENABLED {
124        flags | E::BIT
125    } else {
126        flags & !E::BIT
127    };
128    if E::WRITE == libc::F_SETFD {
129        set::<S, E, { libc::F_SETFD }, RETRY>(file, flags, |error| E::from(Failure::Set(error)))
130    } else {
131        set::<S, E, { libc::F_SETFL }, RETRY>(file, flags, |error| E::from(Failure::Set(error)))
132    }
133}
134
135fn set<S: Fd + ?Sized, E, const COMMAND: libc::c_int, const RETRY: bool>(
136    file: BorrowedFd<'_>,
137    flags: libc::c_int,
138    classify: impl FnOnce(Errno) -> E,
139) -> Result<(), E> {
140    const { assert!(COMMAND == libc::F_SETFD || COMMAND == libc::F_SETFL) };
141    // SAFETY: COMMAND admits only scalar flag updates on this borrowed
142    // descriptor; errno is captured immediately after each native failure.
143    unsafe {
144        kernel::raw::try_retry::<_, E, RETRY>(
145            || {
146                kernel::raw::Status::new(libc::fcntl(file.as_raw_fd(), COMMAND, flags))
147                    .try_zero::<Errno>(|| kernel::raw::Code::last::<S>().into())
148            },
149            classify,
150        )
151    }
152}