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::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
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub enum ReadError<E> {
106    Syscall(E),
107    Invalid(kernel::Invalid),
108}
109
110impl<S: raw::Directory> Entries<'_, S> {
111    /// The next read requires the previous name's borrow to end.
112    pub fn try_next<E: From<Errno>>(&mut self) -> Result<Option<Entry<'_>>, ReadError<E>> {
113        S::next(self.0)
114    }
115}
116
117/// Selects native file requests while preserving every caller-owned borrow.
118pub trait File: errno::Source + raw::Directory {
119    type Options;
120    type Request<'a>: Request<'a, Options = Self::Options, Buffer = bytes::Prefix<'a, { bounded::MAX }>>
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 {
126            <OwnedFd as fd::raw::Adopt>::try_adopt(raw, || kernel::raw::Code::last::<Self>().into())
127        }
128    }
129
130    fn metadata<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<Metadata, E> {
131        let mut value = mem::MaybeUninit::uninit();
132        let raw = unsafe { libc::fstat(file.as_raw_fd(), value.as_mut_ptr()) };
133        // SAFETY: fstat returns zero after filling value or -1 on failure.
134        unsafe {
135            kernel::raw::Status::new(raw).try_zero::<E>(|| kernel::raw::Code::last::<Self>().into())
136        }?;
137        Ok(Metadata::new(unsafe { value.assume_init() }))
138    }
139
140    fn stat<E: From<Errno>>(path: &std::ffi::CStr) -> Result<Metadata, E> {
141        let mut value = mem::MaybeUninit::uninit();
142        let raw = unsafe { libc::stat(path.as_ptr(), value.as_mut_ptr()) };
143        // SAFETY: stat returns zero after filling value or -1 on failure.
144        unsafe {
145            kernel::raw::Status::new(raw).try_zero::<E>(|| kernel::raw::Code::last::<Self>().into())
146        }?;
147        Ok(Metadata::new(unsafe { value.assume_init() }))
148    }
149
150    fn mkdir<E: From<Errno>>(path: &std::ffi::CStr, mode: Mode) -> Result<(), E> {
151        // SAFETY: the terminated path stays borrowed; mkdir returns zero or -1.
152        unsafe {
153            kernel::raw::Status::new(libc::mkdir(path.as_ptr(), mode.raw()))
154                .try_zero(|| kernel::raw::Code::last::<Self>().into())
155        }
156    }
157
158    /// Classifies the named entry itself while retaining its parent directory.
159    fn metadata_at<E: From<Errno>>(
160        dir: Option<BorrowedFd<'_>>,
161        name: &std::ffi::CStr,
162    ) -> Result<Metadata, E> {
163        let mut value = mem::MaybeUninit::uninit();
164        // SAFETY: the parent, name and complete native metadata output stay live.
165        let raw = unsafe {
166            libc::fstatat(
167                dir.map_or(libc::AT_FDCWD, |dir| dir.as_raw_fd()),
168                name.as_ptr(),
169                value.as_mut_ptr(),
170                libc::AT_SYMLINK_NOFOLLOW,
171            )
172        };
173        // SAFETY: fstatat returns zero with initialized metadata or -1 with errno.
174        unsafe {
175            kernel::raw::Status::new(raw).try_zero::<E>(|| kernel::raw::Code::last::<Self>().into())
176        }?;
177        Ok(Metadata::new(unsafe { value.assume_init() }))
178    }
179
180    fn unlink<E: From<Errno>>(path: &std::ffi::CStr) -> Result<(), E> {
181        // SAFETY: path is terminated and retained; unlink returns zero or -1.
182        unsafe {
183            kernel::raw::Status::new(libc::unlink(path.as_ptr()))
184                .try_zero(|| kernel::raw::Code::last::<Self>().into())
185        }
186    }
187
188    fn rename<E: From<Errno>>(from: &std::ffi::CStr, to: &std::ffi::CStr) -> Result<(), E> {
189        // SAFETY: both terminated paths remain borrowed; rename returns zero or -1.
190        unsafe {
191            kernel::raw::Status::new(libc::rename(from.as_ptr(), to.as_ptr()))
192                .try_zero(|| kernel::raw::Code::last::<Self>().into())
193        }
194    }
195
196    /// Attempts an exclusive advisory lock without waiting for its current owner.
197    fn try_lock<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<(), E> {
198        // SAFETY: the descriptor remains borrowed; nonblocking flock returns zero or -1.
199        unsafe {
200            kernel::raw::Status::new(libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB))
201                .try_zero(|| kernel::raw::Code::last::<Self>().into())
202        }
203    }
204
205    fn sync<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<(), E> {
206        // SAFETY: the descriptor remains borrowed; fsync returns zero or -1.
207        unsafe {
208            kernel::raw::Status::new(libc::fsync(file.as_raw_fd()))
209                .try_zero(|| kernel::raw::Code::last::<Self>().into())
210        }
211    }
212
213    fn truncate<E: From<Errno>>(file: BorrowedFd<'_>, len: i64) -> Result<(), E> {
214        // SAFETY: the descriptor remains borrowed; ftruncate returns zero or -1.
215        unsafe {
216            kernel::raw::Status::new(libc::ftruncate(file.as_raw_fd(), len))
217                .try_zero(|| kernel::raw::Code::last::<Self>().into())
218        }
219    }
220
221    fn unlink_at<E: From<Errno>>(dir: BorrowedFd<'_>, name: &std::ffi::CStr) -> Result<(), E> {
222        // SAFETY: the descriptor and terminated name remain borrowed; unlinkat returns zero or -1.
223        unsafe {
224            kernel::raw::Status::new(libc::unlinkat(dir.as_raw_fd(), name.as_ptr(), 0))
225                .try_zero(|| kernel::raw::Code::last::<Self>().into())
226        }
227    }
228
229    fn remove_dir_at<E: From<Errno>>(
230        dir: Option<BorrowedFd<'_>>,
231        name: &std::ffi::CStr,
232    ) -> Result<(), E> {
233        // SAFETY: the parent and terminated name remain borrowed; unlinkat removes
234        // only an empty directory and returns zero or -1 with this thread's errno.
235        unsafe {
236            kernel::raw::Status::new(libc::unlinkat(
237                dir.map_or(libc::AT_FDCWD, |dir| dir.as_raw_fd()),
238                name.as_ptr(),
239                libc::AT_REMOVEDIR,
240            ))
241            .try_zero(|| kernel::raw::Code::last::<Self>().into())
242        }
243    }
244
245    fn open_at<E: From<Errno>>(
246        dir: BorrowedFd<'_>,
247        name: &std::ffi::CStr,
248        flags: Open,
249        mode: Mode,
250    ) -> Result<OwnedFd, E> {
251        let raw = unsafe {
252            libc::openat(
253                dir.as_raw_fd(),
254                name.as_ptr(),
255                flags.raw(),
256                mode.raw() as libc::c_uint,
257            )
258        };
259        unsafe {
260            <OwnedFd as fd::raw::Adopt>::try_adopt(raw, || kernel::raw::Code::last::<Self>().into())
261        }
262    }
263
264    fn rename_at<E: From<Errno>>(
265        dir: BorrowedFd<'_>,
266        old: &std::ffi::CStr,
267        new: &std::ffi::CStr,
268    ) -> Result<(), E> {
269        // SAFETY: the descriptor and terminated names remain borrowed; renameat returns zero or -1.
270        unsafe {
271            kernel::raw::Status::new(libc::renameat(
272                dir.as_raw_fd(),
273                old.as_ptr(),
274                dir.as_raw_fd(),
275                new.as_ptr(),
276            ))
277            .try_zero(|| kernel::raw::Code::last::<Self>().into())
278        }
279    }
280
281    /// Reads one prefix without changing the descriptor's current position.
282    fn read_at<'a, E: From<Errno>>(
283        file: BorrowedFd<'_>,
284        output: &'a mut [u8],
285        offset: Offset,
286    ) -> Result<&'a mut [u8], E> {
287        const { assert!(libc::off_t::MAX as u64 == i64::MAX as u64) };
288        // SAFETY: the descriptor stays borrowed, output exclusively owns its
289        // writable extent, and the admitted nonnegative offset fits the ABI.
290        let raw = unsafe {
291            libc::pread(
292                file.as_raw_fd(),
293                output.as_mut_ptr().cast(),
294                output.len(),
295                offset.get() as libc::off_t,
296            )
297        };
298        // SAFETY: pread returns -1 or initializes at most this exact output
299        // extent; failure reads the unchanged errno on this thread.
300        unsafe {
301            crate::io::raw::Buffer::try_native(output, raw, || {
302                kernel::raw::Code::last::<Self>().into()
303            })
304        }
305    }
306
307    fn write_at<E: From<Errno>>(
308        file: BorrowedFd<'_>,
309        input: bytes::Prefix<'_, { bounded::MAX }>,
310        offset: Offset,
311    ) -> Result<U32<0, { bounded::MAX as u32 }>, E> {
312        use kernel::raw::Status;
313
314        const { assert!(libc::off_t::MAX as u64 == i64::MAX as u64) };
315        let input = input.into_slice();
316        // SAFETY: the offset's admitted nonnegative range fits the native ABI.
317        let raw = unsafe {
318            libc::pwrite(
319                file.as_raw_fd(),
320                input.as_ptr().cast(),
321                input.len(),
322                offset.get() as libc::off_t,
323            )
324        };
325        // SAFETY: pwrite returns -1 or a count within this Prefix's i32::MAX-bound
326        // input; failure reads the unchanged errno on this thread.
327        Ok(unsafe {
328            Status::from_count(raw).try_count::<E>(|| kernel::raw::Code::last::<Self>().into())
329        }?
330        .bounded())
331    }
332}