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
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 Metadata;
121    type Request<'a>: Request<
122            'a,
123            Options = Self::Options,
124            Metadata = Self::Metadata,
125            Buffer = bytes::Prefix<'a, { bounded::MAX }>,
126        >
127    where
128        Self: 'a;
129    fn open<E: From<Errno>>(path: &std::ffi::CStr, flags: Open, mode: Mode) -> Result<OwnedFd, E> {
130        let raw = unsafe { libc::open(path.as_ptr(), flags.raw(), mode.raw() as libc::c_uint) };
131        unsafe {
132            <OwnedFd as fd::raw::Adopt>::try_adopt(raw, || kernel::raw::Code::last::<Self>().into())
133        }
134    }
135
136    fn metadata<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<Metadata, E> {
137        let mut value = mem::MaybeUninit::uninit();
138        let raw = unsafe { libc::fstat(file.as_raw_fd(), value.as_mut_ptr()) };
139        // SAFETY: fstat returns zero after filling value or -1 on failure.
140        unsafe {
141            kernel::raw::Status::new(raw).try_zero::<E>(|| kernel::raw::Code::last::<Self>().into())
142        }?;
143        Ok(Metadata::new(unsafe { value.assume_init() }))
144    }
145
146    fn stat<E: From<Errno>>(path: &std::ffi::CStr) -> Result<Metadata, E> {
147        let mut value = mem::MaybeUninit::uninit();
148        let raw = unsafe { libc::stat(path.as_ptr(), value.as_mut_ptr()) };
149        // SAFETY: stat returns zero after filling value or -1 on failure.
150        unsafe {
151            kernel::raw::Status::new(raw).try_zero::<E>(|| kernel::raw::Code::last::<Self>().into())
152        }?;
153        Ok(Metadata::new(unsafe { value.assume_init() }))
154    }
155
156    fn mkdir<E: From<Errno>>(path: &std::ffi::CStr, mode: Mode) -> Result<(), E> {
157        // SAFETY: the terminated path stays borrowed; mkdir returns zero or -1.
158        unsafe {
159            kernel::raw::Status::new(libc::mkdir(path.as_ptr(), mode.raw()))
160                .try_zero(|| kernel::raw::Code::last::<Self>().into())
161        }
162    }
163
164    /// Classifies the named entry itself while retaining its parent directory.
165    fn metadata_at<E: From<Errno>>(
166        dir: Option<BorrowedFd<'_>>,
167        name: &std::ffi::CStr,
168    ) -> Result<Metadata, E> {
169        let mut value = mem::MaybeUninit::uninit();
170        // SAFETY: the parent, name and complete native metadata output stay live.
171        let raw = unsafe {
172            libc::fstatat(
173                dir.map_or(libc::AT_FDCWD, |dir| dir.as_raw_fd()),
174                name.as_ptr(),
175                value.as_mut_ptr(),
176                libc::AT_SYMLINK_NOFOLLOW,
177            )
178        };
179        // SAFETY: fstatat returns zero with initialized metadata or -1 with errno.
180        unsafe {
181            kernel::raw::Status::new(raw).try_zero::<E>(|| kernel::raw::Code::last::<Self>().into())
182        }?;
183        Ok(Metadata::new(unsafe { value.assume_init() }))
184    }
185
186    fn unlink<E: From<Errno>>(path: &std::ffi::CStr) -> Result<(), E> {
187        // SAFETY: path is terminated and retained; unlink returns zero or -1.
188        unsafe {
189            kernel::raw::Status::new(libc::unlink(path.as_ptr()))
190                .try_zero(|| kernel::raw::Code::last::<Self>().into())
191        }
192    }
193
194    fn rename<E: From<Errno>>(from: &std::ffi::CStr, to: &std::ffi::CStr) -> Result<(), E> {
195        // SAFETY: both terminated paths remain borrowed; rename returns zero or -1.
196        unsafe {
197            kernel::raw::Status::new(libc::rename(from.as_ptr(), to.as_ptr()))
198                .try_zero(|| kernel::raw::Code::last::<Self>().into())
199        }
200    }
201
202    /// Attempts an exclusive advisory lock without waiting for its current owner.
203    fn try_lock<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<(), E> {
204        // SAFETY: the descriptor remains borrowed; nonblocking flock returns zero or -1.
205        unsafe {
206            kernel::raw::Status::new(libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB))
207                .try_zero(|| kernel::raw::Code::last::<Self>().into())
208        }
209    }
210
211    fn sync<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<(), E> {
212        // SAFETY: the descriptor remains borrowed; fsync returns zero or -1.
213        unsafe {
214            kernel::raw::Status::new(libc::fsync(file.as_raw_fd()))
215                .try_zero(|| kernel::raw::Code::last::<Self>().into())
216        }
217    }
218
219    fn truncate<E: From<Errno>>(file: BorrowedFd<'_>, len: i64) -> Result<(), E> {
220        // SAFETY: the descriptor remains borrowed; ftruncate returns zero or -1.
221        unsafe {
222            kernel::raw::Status::new(libc::ftruncate(file.as_raw_fd(), len))
223                .try_zero(|| kernel::raw::Code::last::<Self>().into())
224        }
225    }
226
227    fn unlink_at<E: From<Errno>>(dir: BorrowedFd<'_>, name: &std::ffi::CStr) -> Result<(), E> {
228        // SAFETY: the descriptor and terminated name remain borrowed; unlinkat returns zero or -1.
229        unsafe {
230            kernel::raw::Status::new(libc::unlinkat(dir.as_raw_fd(), name.as_ptr(), 0))
231                .try_zero(|| kernel::raw::Code::last::<Self>().into())
232        }
233    }
234
235    fn remove_dir_at<E: From<Errno>>(
236        dir: Option<BorrowedFd<'_>>,
237        name: &std::ffi::CStr,
238    ) -> Result<(), E> {
239        // SAFETY: the parent and terminated name remain borrowed; unlinkat removes
240        // only an empty directory and returns zero or -1 with this thread's errno.
241        unsafe {
242            kernel::raw::Status::new(libc::unlinkat(
243                dir.map_or(libc::AT_FDCWD, |dir| dir.as_raw_fd()),
244                name.as_ptr(),
245                libc::AT_REMOVEDIR,
246            ))
247            .try_zero(|| kernel::raw::Code::last::<Self>().into())
248        }
249    }
250
251    fn open_at<E: From<Errno>>(
252        dir: BorrowedFd<'_>,
253        name: &std::ffi::CStr,
254        flags: Open,
255        mode: Mode,
256    ) -> Result<OwnedFd, E> {
257        let raw = unsafe {
258            libc::openat(
259                dir.as_raw_fd(),
260                name.as_ptr(),
261                flags.raw(),
262                mode.raw() as libc::c_uint,
263            )
264        };
265        unsafe {
266            <OwnedFd as fd::raw::Adopt>::try_adopt(raw, || kernel::raw::Code::last::<Self>().into())
267        }
268    }
269
270    fn rename_at<E: From<Errno>>(
271        dir: BorrowedFd<'_>,
272        old: &std::ffi::CStr,
273        new: &std::ffi::CStr,
274    ) -> Result<(), E> {
275        // SAFETY: the descriptor and terminated names remain borrowed; renameat returns zero or -1.
276        unsafe {
277            kernel::raw::Status::new(libc::renameat(
278                dir.as_raw_fd(),
279                old.as_ptr(),
280                dir.as_raw_fd(),
281                new.as_ptr(),
282            ))
283            .try_zero(|| kernel::raw::Code::last::<Self>().into())
284        }
285    }
286
287    /// Reads one prefix without changing the descriptor's current position.
288    fn read_at<'a, E: From<Errno>>(
289        file: BorrowedFd<'_>,
290        output: &'a mut [u8],
291        offset: Offset,
292    ) -> Result<&'a mut [u8], E> {
293        // SAFETY: MaybeUninit preserves u8's layout; the exclusive loan is unchanged.
294        let output =
295            unsafe { std::slice::from_raw_parts_mut(output.as_mut_ptr().cast(), output.len()) };
296        raw::read_at::<Self, E>(file, output, offset)
297    }
298
299    fn read_uninit_at<'a, E: From<Errno>>(
300        file: BorrowedFd<'_>,
301        output: &'a mut [mem::MaybeUninit<u8>],
302        offset: Offset,
303    ) -> Result<&'a mut [u8], E> {
304        raw::read_at::<Self, E>(file, output, offset)
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}