Skip to main content

dope_core/io/fs/
mod.rs

1use std::{fs, mem, os::fd};
2
3use dope_sys::io::{fd::Fd as _, file, request::Request};
4use file::File as _;
5use o3::{base::marker::ThreadBound, buffer::bytes};
6
7use crate::{
8    Backend, Errno,
9    driver::{
10        route,
11        route::{
12            kind,
13            kind::{Class, Open, Write},
14        },
15    },
16    io::{fs::metadata::Metadata, transfer},
17};
18
19mod dir;
20
21mod len;
22pub mod metadata;
23pub mod open;
24pub mod path;
25
26pub use dir::Dir;
27pub use len::Len;
28
29/// Native file kind observed from descriptor metadata.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31#[repr(transparent)]
32pub struct Kind(file::Kind);
33
34impl Kind {
35    pub const fn is_regular(self) -> bool {
36        self.0.is_regular()
37    }
38}
39
40impl From<file::Kind> for Kind {
41    fn from(kind: file::Kind) -> Self {
42        Self(kind)
43    }
44}
45
46/// Absolute byte position representable by every file backend.
47#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
48#[repr(transparent)]
49pub struct Offset(file::Offset);
50
51/// Exact position or length outside the absolute file-offset domain.
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub struct Overflow {
54    pub actual: u64,
55    pub maximum: u64,
56}
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub struct Extent {
60    offset: Offset,
61    length: transfer::Len,
62}
63
64impl From<Len> for Offset {
65    fn from(length: Len) -> Self {
66        Self(length.0)
67    }
68}
69
70impl From<transfer::Len> for Offset {
71    fn from(length: transfer::Len) -> Self {
72        Self(file::Offset::clamp(length.into_raw() as u64))
73    }
74}
75
76impl Offset {
77    pub const fn fixed<const OFFSET: u64>() -> Self {
78        const { assert!(OFFSET <= i64::MAX as u64) };
79        Self(file::Offset::clamp(OFFSET))
80    }
81
82    pub const fn try_new(value: u64) -> Result<Self, Overflow> {
83        match file::Offset::new(value) {
84            Some(value) => Ok(Self(value)),
85            None => Err(Overflow {
86                actual: value,
87                maximum: i64::MAX as u64,
88            }),
89        }
90    }
91
92    pub const fn get(self) -> u64 {
93        self.0.get()
94    }
95
96    pub const fn checked_add(self, length: transfer::Len) -> Result<Self, Extent> {
97        let value = self.get() + length.into_raw() as u64;
98        match Self::try_new(value) {
99            Ok(value) => Ok(value),
100            Err(_) => Err(Extent::new(self, length)),
101        }
102    }
103
104    pub const fn checked_sub(self, length: transfer::Len) -> Result<Self, Extent> {
105        match self.0.checked_sub(length.into_raw() as u64) {
106            Some(value) => Ok(Self(value)),
107            None => Err(Extent::new(self, length)),
108        }
109    }
110}
111
112impl Extent {
113    const fn new(offset: Offset, length: transfer::Len) -> Self {
114        Self { offset, length }
115    }
116}
117
118const _: () = {
119    assert!(mem::size_of::<Kind>() == mem::size_of::<file::Kind>());
120    assert!(mem::align_of::<Kind>() == mem::align_of::<file::Kind>());
121    assert!(mem::size_of::<Offset>() == mem::size_of::<u64>());
122    assert!(mem::align_of::<Offset>() == mem::align_of::<u64>());
123    assert!(mem::size_of::<Extent>() == 2 * mem::size_of::<u64>());
124    assert!(mem::align_of::<Extent>() == mem::align_of::<u64>());
125    assert!(mem::size_of::<Result<Offset, Extent>>() == 3 * mem::size_of::<u64>());
126    assert!(mem::align_of::<Result<Offset, Extent>>() == mem::align_of::<u64>());
127};
128
129/// A native file operation borrowing every descriptor and output it captures.
130///
131/// Raw tokens cannot enter the safe file submission boundary:
132///
133///
134///
135/// Keeping the captured buffer until the submission is dropped is valid.
136pub struct Submission<'a, 'd, Tag, C = <Tag as route::Tag>::Class>
137where
138    Tag: route::Tag,
139    C: Class,
140{
141    raw: <Backend as file::File>::Request<'a>,
142    target: route::Operation<'d, Tag, C>,
143}
144
145pub struct OpenRequest {
146    path: path::Confined,
147    beneath: <Backend as file::File>::Options,
148}
149
150impl<'a, 'd, Tag, C> Submission<'a, 'd, Tag, C>
151where
152    Tag: route::Tag,
153    C: Class,
154{
155    fn bind(
156        raw: <Backend as file::File>::Request<'a>,
157        target: route::Operation<'d, Tag, C>,
158    ) -> Self {
159        Self { raw, target }
160    }
161
162    pub(crate) fn into_parts(
163        self,
164    ) -> (
165        <Backend as file::File>::Request<'a>,
166        route::Operation<'d, Tag, C>,
167    ) {
168        (self.raw, self.target)
169    }
170}
171
172impl<'a, 'd, Tag> Submission<'a, 'd, Tag, Write>
173where
174    Tag: route::Tag,
175{
176    #[doc(hidden)]
177    pub fn write_prefix(
178        fd: fd::BorrowedFd<'a>,
179        buffer: &'a [u8],
180        offset: Offset,
181        target: route::Target<'d, Tag>,
182    ) -> Self
183    where
184        Tag: route::Select<Write>,
185    {
186        let input = bytes::Prefix::new(buffer);
187        Self::bind(
188            Request::write(fd, input, offset.0),
189            target.operation::<Write>(),
190        )
191    }
192
193    /// Writes at the descriptor's current position, honoring its append flag.
194    /// The descriptor's owner must serialize writes that share this position.
195    #[doc(hidden)]
196    pub fn write_current_prefix(
197        fd: fd::BorrowedFd<'a>,
198        buffer: bytes::Prefix<'a, { transfer::MAX_BYTES }>,
199        target: route::Target<'d, Tag>,
200    ) -> Self
201    where
202        Tag: route::Select<Write>,
203    {
204        Self::bind(
205            Request::write_current(fd, buffer),
206            target.operation::<Write>(),
207        )
208    }
209}
210
211impl<'a, 'd, Tag> Submission<'a, 'd, Tag, kind::Read>
212where
213    Tag: route::Tag,
214{
215    /// Reads one bounded prefix without changing the descriptor's current position.
216    /// A successful completion reports the exact short count; zero is EOF for
217    /// nonempty output. The native file backend may complete during submission.
218    pub fn read_at(
219        fd: fd::BorrowedFd<'a>,
220        output: bytes::bounded::Value<&'a mut [mem::MaybeUninit<u8>]>,
221        offset: Offset,
222        target: route::Target<'d, Tag>,
223    ) -> Self
224    where
225        Tag: route::Select<kind::Read>,
226    {
227        Self::bind(
228            Request::read(fd, output, offset.0),
229            target.operation::<kind::Read>(),
230        )
231    }
232}
233
234impl<'a, 'd, Tag> Submission<'a, 'd, Tag, kind::Sync>
235where
236    Tag: route::Tag,
237{
238    pub fn metadata(
239        fd: fd::BorrowedFd<'a>,
240        output: &'a mut metadata::Output,
241        target: route::Target<'d, Tag>,
242    ) -> Self
243    where
244        Tag: route::Select<kind::Sync>,
245    {
246        Self::bind(
247            Request::metadata(fd, output),
248            target.operation::<kind::Sync>(),
249        )
250    }
251
252    /// Submit a durability barrier for all writes completed before this operation.
253    pub fn sync(fd: fd::BorrowedFd<'a>, mode: Sync, target: route::Target<'d, Tag>) -> Self
254    where
255        Tag: route::Select<kind::Sync>,
256    {
257        Self::bind(
258            Request::sync(
259                fd,
260                match mode {
261                    Sync::Data => file::Sync::Data,
262                    Sync::All => file::Sync::All,
263                },
264            ),
265            target.operation::<kind::Sync>(),
266        )
267    }
268
269    /// Atomically replaces `new` with `old` within one retained directory.
270    #[doc(hidden)]
271    pub fn rename(rename: path::Rename<'a>, target: route::Target<'d, Tag>) -> Self
272    where
273        Tag: route::Select<kind::Sync>,
274    {
275        Self::bind(
276            Request::rename(
277                fd::AsFd::as_fd(rename.directory()),
278                rename.old(),
279                rename.new_name(),
280            ),
281            target.operation::<kind::Sync>(),
282        )
283    }
284
285    /// Removes one retained directory entry.
286    #[doc(hidden)]
287    pub fn unlink(entry: path::Entry<'a>, target: route::Target<'d, Tag>) -> Self
288    where
289        Tag: route::Select<kind::Sync>,
290    {
291        Self::bind(
292            Request::unlink(fd::AsFd::as_fd(entry.directory()), entry.name()),
293            target.operation::<kind::Sync>(),
294        )
295    }
296}
297
298impl OpenRequest {
299    /// Keeps the confined path non-following and close-on-exec for every opening policy.
300    pub fn new(path: path::Confined, options: open::Options) -> Self {
301        let (flags, mode) = options.nofollow().lower();
302        Self {
303            path,
304            beneath: <<Backend as file::File>::Request<'_> as Request>::options(flags, mode),
305        }
306    }
307
308    pub fn submission<'d, Tag>(
309        &self,
310        target: route::Target<'d, Tag>,
311    ) -> Submission<'_, 'd, Tag, Open>
312    where
313        Tag: route::Tag + route::Select<Open>,
314    {
315        let path = self.path.view();
316        Submission::bind(
317            Request::open(
318                fd::AsFd::as_fd(path.directory()),
319                path.name(),
320                &self.beneath,
321            ),
322            target.operation::<Open>(),
323        )
324    }
325
326    pub fn into_path(self) -> path::Confined {
327        self.path
328    }
329}
330
331/// The durability boundary requested for a submitted file sync.
332pub enum Sync {
333    Data,
334    All,
335}
336
337/// One owned file descriptor, including its kernel file offset and locks.
338#[repr(transparent)]
339pub struct File {
340    fd: fd::OwnedFd,
341    thread: ThreadBound,
342}
343
344impl File {
345    pub fn try_read<'a>(&self, bytes: &'a mut [u8]) -> Result<&'a mut [u8], Errno> {
346        Backend::read::<dope_sys::Errno>(fd::AsFd::as_fd(self), bytes)
347    }
348
349    /// Reads one prefix without changing this file's current position.
350    pub fn try_read_at<'a>(
351        &self,
352        bytes: &'a mut [u8],
353        offset: Offset,
354    ) -> Result<&'a mut [u8], Errno> {
355        Backend::read_at::<dope_sys::Errno>(fd::AsFd::as_fd(self), bytes, offset.0)
356    }
357
358    pub fn try_write<'a>(&self, bytes: &'a [u8]) -> Result<&'a [u8], Errno> {
359        Backend::write::<dope_sys::Errno>(fd::AsFd::as_fd(self), bytes)
360    }
361
362    pub fn metadata(&self) -> Result<Metadata, Errno> {
363        let value = Backend::metadata::<dope_sys::Errno>(fd::AsFd::as_fd(self))?;
364        Ok(Metadata {
365            kind: Kind::from(value.kind()),
366            length: value.size(),
367        })
368    }
369
370    /// Attempts an exclusive advisory lock without waiting for its current owner.
371    pub fn try_lock(&self) -> Result<(), Errno> {
372        Backend::try_lock::<dope_sys::Errno>(fd::AsFd::as_fd(self))
373    }
374
375    pub fn try_sync(&self) -> Result<(), Errno> {
376        Backend::sync::<dope_sys::Errno>(fd::AsFd::as_fd(self))
377    }
378}
379
380impl fd::AsFd for File {
381    fn as_fd(&self) -> fd::BorrowedFd<'_> {
382        self.fd.as_fd()
383    }
384}
385
386impl From<fd::OwnedFd> for File {
387    fn from(file: fd::OwnedFd) -> Self {
388        Self {
389            fd: file,
390            thread: ThreadBound::NEW,
391        }
392    }
393}
394
395impl From<fs::File> for File {
396    fn from(file: fs::File) -> Self {
397        Self {
398            fd: file.into(),
399            thread: ThreadBound::NEW,
400        }
401    }
402}
403
404impl From<File> for fs::File {
405    fn from(file: File) -> Self {
406        file.fd.into()
407    }
408}
409
410impl From<File> for fd::OwnedFd {
411    fn from(file: File) -> Self {
412        file.fd
413    }
414}
415
416const _: () = {
417    assert!(size_of::<File>() == size_of::<fd::OwnedFd>());
418    assert!(align_of::<File>() == align_of::<fd::OwnedFd>());
419};