Skip to main content

dope_sys/io/file/
mod.rs

1use std::{
2    mem,
3    os::fd::{AsRawFd as _, BorrowedFd, OwnedFd},
4};
5
6use o3::{
7    alloc,
8    base::num::bounded::{U32, U64},
9    buffer::bytes::{self, bounded},
10};
11pub use sync::Sync;
12
13use crate::{
14    Errno,
15    io::{fd, request::Request},
16    kernel::{self, errno},
17};
18
19mod kind;
20mod metadata;
21mod mode;
22mod open;
23pub mod raw;
24mod sync;
25
26pub use kind::Kind;
27pub use metadata::{Error as MetadataError, Metadata};
28pub use mode::Mode;
29pub use open::Open;
30
31/// An absolute file position, excluding native current-position sentinels.
32#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
33#[repr(transparent)]
34pub struct Offset(U64<0, { i64::MAX as u64 }>);
35
36impl Offset {
37    pub const fn new(value: u64) -> Option<Self> {
38        match U64::new(value) {
39            Some(value) => Some(Self(value)),
40            None => None,
41        }
42    }
43
44    pub const fn clamp(value: u64) -> Self {
45        Self(U64::clamp(value))
46    }
47
48    pub const fn get(self) -> u64 {
49        self.0.get()
50    }
51
52    /// Subtracts a byte count while retaining the admitted absolute range.
53    pub const fn checked_sub(self, value: u64) -> Option<Self> {
54        match self.0.checked_sub(value) {
55            Some(value) => Some(Self(value)),
56            None => None,
57        }
58    }
59}
60
61/// A decoded file completion retaining any descriptor it owns.
62pub type Opened = Result<OwnedFd, Errno>;
63
64/// A native directory stream whose descriptor and buffered entries have one owner.
65#[repr(transparent)]
66pub struct Dir<S: raw::Directory>(S::Stream);
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum OpenError<E> {
70    Syscall(E),
71    Allocation(alloc::Exhausted),
72}
73
74impl<S: File> Dir<S> {
75    pub fn open<E: From<Errno>>(path: &std::ffi::CStr) -> Result<Self, OpenError<E>> {
76        S::open_dir(path).map(Self)
77    }
78
79    pub fn from_fd<E: From<Errno>>(file: OwnedFd) -> Result<Self, OpenError<E>> {
80        S::from_fd(file).map(Self)
81    }
82
83    pub fn entries(&mut self) -> (BorrowedFd<'_>, Entries<'_, S>) {
84        let file = S::descriptor(&self.0);
85        (
86            // SAFETY: Directory preserves this descriptor until the stream is dropped,
87            // and its mutable borrow excludes dropping the owner for both returned borrows.
88            unsafe { BorrowedFd::borrow_raw(file.get()) },
89            Entries(&mut self.0),
90        )
91    }
92}
93
94#[repr(transparent)]
95pub struct Entries<'a, S: raw::Directory>(&'a mut S::Stream);
96
97/// One name and the file kind already supplied by its native directory record.
98#[derive(Clone, Copy, Debug)]
99pub struct Entry<'a> {
100    pub name: &'a std::ffi::CStr,
101    pub kind: Option<Kind>,
102}
103
104impl<S: raw::Directory> Entries<'_, S> {
105    /// The next read requires the previous name's borrow to end.
106    pub fn try_next(&mut self) -> Result<Option<Entry<'_>>, Errno> {
107        S::next(self.0)
108    }
109}
110
111/// Selects native file requests while preserving every caller-owned borrow.
112pub trait File: errno::Source + raw::Directory {
113    type Options;
114    type Metadata;
115    type Request<'a>: Request<
116            'a,
117            Options = Self::Options,
118            Metadata = Self::Metadata,
119            Buffer = bytes::Prefix<'a, { bounded::MAX }>,
120        >
121    where
122        Self: 'a;
123    fn open<E: From<Errno>>(path: &std::ffi::CStr, flags: Open, mode: Mode) -> Result<OwnedFd, E> {
124        let raw = unsafe { libc::open(path.as_ptr(), flags.raw(), mode.raw() as libc::c_uint) };
125        unsafe { <OwnedFd as fd::raw::Adopt>::try_adopt::<Self, E>(raw) }
126    }
127
128    fn metadata<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<Metadata, E> {
129        let mut value = mem::MaybeUninit::uninit();
130        let raw = unsafe { libc::fstat(file.as_raw_fd(), value.as_mut_ptr()) };
131        // SAFETY: fstat returns zero after filling value or -1 on failure.
132        unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }?;
133        Ok(Metadata::new(unsafe { value.assume_init() }))
134    }
135
136    fn stat<E: From<Errno>>(path: &std::ffi::CStr) -> Result<Metadata, E> {
137        let mut value = mem::MaybeUninit::uninit();
138        let raw = unsafe { libc::stat(path.as_ptr(), value.as_mut_ptr()) };
139        // SAFETY: stat returns zero after filling value or -1 on failure.
140        unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }?;
141        Ok(Metadata::new(unsafe { value.assume_init() }))
142    }
143
144    fn mkdir<E: From<Errno>>(path: &std::ffi::CStr, mode: Mode) -> Result<(), E> {
145        // SAFETY: the terminated path stays borrowed; mkdir returns zero or -1.
146        unsafe {
147            kernel::raw::Status::new(libc::mkdir(path.as_ptr(), mode.raw())).try_zero::<Self, E>()
148        }
149    }
150
151    /// Classifies the named entry itself while retaining its parent directory.
152    fn metadata_at<E: From<Errno>>(
153        dir: Option<BorrowedFd<'_>>,
154        name: &std::ffi::CStr,
155    ) -> Result<Metadata, E> {
156        let mut value = mem::MaybeUninit::uninit();
157        // SAFETY: the parent, name and complete native metadata output stay live.
158        let raw = unsafe {
159            libc::fstatat(
160                dir.map_or(libc::AT_FDCWD, |dir| dir.as_raw_fd()),
161                name.as_ptr(),
162                value.as_mut_ptr(),
163                libc::AT_SYMLINK_NOFOLLOW,
164            )
165        };
166        // SAFETY: fstatat returns zero with initialized metadata or -1 with errno.
167        unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }?;
168        Ok(Metadata::new(unsafe { value.assume_init() }))
169    }
170
171    fn unlink<E: From<Errno>>(path: &std::ffi::CStr) -> Result<(), E> {
172        // SAFETY: path is terminated and retained; unlink returns zero or -1.
173        unsafe { kernel::raw::Status::new(libc::unlink(path.as_ptr())).try_zero::<Self, E>() }
174    }
175
176    fn rename<E: From<Errno>>(from: &std::ffi::CStr, to: &std::ffi::CStr) -> Result<(), E> {
177        // SAFETY: both terminated paths remain borrowed; rename returns zero or -1.
178        unsafe {
179            kernel::raw::Status::new(libc::rename(from.as_ptr(), to.as_ptr())).try_zero::<Self, E>()
180        }
181    }
182
183    /// Attempts an exclusive advisory lock without waiting for its current owner.
184    fn try_lock<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<(), E> {
185        // SAFETY: the descriptor remains borrowed; nonblocking flock returns zero or -1.
186        unsafe {
187            kernel::raw::Status::new(libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB))
188                .try_zero::<Self, E>()
189        }
190    }
191
192    fn sync<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<(), E> {
193        // SAFETY: the descriptor remains borrowed; fsync returns zero or -1.
194        unsafe { kernel::raw::Status::new(libc::fsync(file.as_raw_fd())).try_zero::<Self, E>() }
195    }
196
197    fn truncate<E: From<Errno>>(file: BorrowedFd<'_>, len: i64) -> Result<(), E> {
198        // SAFETY: the descriptor remains borrowed; ftruncate returns zero or -1.
199        unsafe {
200            kernel::raw::Status::new(libc::ftruncate(file.as_raw_fd(), len)).try_zero::<Self, E>()
201        }
202    }
203
204    fn unlink_at<E: From<Errno>>(dir: BorrowedFd<'_>, name: &std::ffi::CStr) -> Result<(), E> {
205        // SAFETY: the descriptor and terminated name remain borrowed; unlinkat returns zero or -1.
206        unsafe {
207            kernel::raw::Status::new(libc::unlinkat(dir.as_raw_fd(), name.as_ptr(), 0))
208                .try_zero::<Self, E>()
209        }
210    }
211
212    fn remove_dir_at<E: From<Errno>>(
213        dir: Option<BorrowedFd<'_>>,
214        name: &std::ffi::CStr,
215    ) -> Result<(), E> {
216        // SAFETY: the parent and terminated name remain borrowed; unlinkat removes
217        // only an empty directory and returns zero or -1 with this thread's errno.
218        unsafe {
219            kernel::raw::Status::new(libc::unlinkat(
220                dir.map_or(libc::AT_FDCWD, |dir| dir.as_raw_fd()),
221                name.as_ptr(),
222                libc::AT_REMOVEDIR,
223            ))
224            .try_zero::<Self, E>()
225        }
226    }
227
228    fn open_at<E: From<Errno>>(
229        dir: BorrowedFd<'_>,
230        name: &std::ffi::CStr,
231        flags: Open,
232        mode: Mode,
233    ) -> Result<OwnedFd, E> {
234        let raw = unsafe {
235            libc::openat(
236                dir.as_raw_fd(),
237                name.as_ptr(),
238                flags.raw(),
239                mode.raw() as libc::c_uint,
240            )
241        };
242        unsafe { <OwnedFd as fd::raw::Adopt>::try_adopt::<Self, E>(raw) }
243    }
244
245    fn rename_at<E: From<Errno>>(
246        dir: BorrowedFd<'_>,
247        old: &std::ffi::CStr,
248        new: &std::ffi::CStr,
249    ) -> Result<(), E> {
250        // SAFETY: the descriptor and terminated names remain borrowed; renameat returns zero or -1.
251        unsafe {
252            kernel::raw::Status::new(libc::renameat(
253                dir.as_raw_fd(),
254                old.as_ptr(),
255                dir.as_raw_fd(),
256                new.as_ptr(),
257            ))
258            .try_zero::<Self, E>()
259        }
260    }
261
262    /// Reads one prefix without changing the descriptor's current position.
263    fn read_at<'a, E: From<Errno>>(
264        file: BorrowedFd<'_>,
265        output: &'a mut [u8],
266        offset: Offset,
267    ) -> Result<&'a mut [u8], E> {
268        // SAFETY: MaybeUninit preserves u8's layout; the exclusive loan is unchanged.
269        let output =
270            unsafe { std::slice::from_raw_parts_mut(output.as_mut_ptr().cast(), output.len()) };
271        raw::read_at::<Self, E>(file, output, offset)
272    }
273
274    fn read_uninit_at<'a, E: From<Errno>>(
275        file: BorrowedFd<'_>,
276        output: &'a mut [mem::MaybeUninit<u8>],
277        offset: Offset,
278    ) -> Result<&'a mut [u8], E> {
279        raw::read_at::<Self, E>(file, output, offset)
280    }
281
282    fn write_at<E: From<Errno>>(
283        file: BorrowedFd<'_>,
284        input: bytes::Prefix<'_, { bounded::MAX }>,
285        offset: Offset,
286    ) -> Result<U32<0, { bounded::MAX as u32 }>, E> {
287        use kernel::raw::Status;
288
289        const { assert!(libc::off_t::MAX as u64 == i64::MAX as u64) };
290        let input = input.as_slice();
291        // SAFETY: the offset's admitted nonnegative range fits the native ABI.
292        let raw = unsafe {
293            libc::pwrite(
294                file.as_raw_fd(),
295                input.as_ptr().cast(),
296                input.len(),
297                offset.get() as libc::off_t,
298            )
299        };
300        // SAFETY: pwrite returns -1 or a count within this Prefix's i32::MAX-bound
301        // input; failure reads the unchanged errno on this thread.
302        Ok(unsafe { Status::from_count(raw).try_count::<Self, E>() }?.bounded())
303    }
304}