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