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