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