Skip to main content

dope_uring/raw/
dir.rs

1use std::{
2    mem::MaybeUninit,
3    os::fd::{AsRawFd as _, OwnedFd},
4};
5
6use dope_sys::{Errno, io::file, kernel};
7use o3::{alloc::BoxArrayExt as _, base::marker::ThreadBound};
8
9use crate::Uring;
10
11const CAPACITY: usize = 32 * 1024;
12
13pub struct Stream {
14    file: OwnedFd,
15    buffer: Box<[MaybeUninit<u8>; CAPACITY]>,
16    offset: usize,
17    filled: usize,
18    _thread: ThreadBound,
19}
20
21// SAFETY: the stream exclusively owns its descriptor and buffer. Refilling only
22// writes the buffer after the previous Entry borrow has ended.
23unsafe impl file::raw::Directory for Uring {
24    type Stream = Stream;
25
26    fn from_fd<E: From<Errno>>(file: OwnedFd) -> Result<Stream, file::OpenError<E>> {
27        Ok(Stream {
28            file,
29            buffer: Box::try_uninit().map_err(file::OpenError::Allocation)?,
30            offset: 0,
31            filled: 0,
32            _thread: ThreadBound::NEW,
33        })
34    }
35
36    fn descriptor(stream: &Stream) -> kernel::raw::Descriptor {
37        // SAFETY: the stream exclusively owns this live nonnegative descriptor.
38        unsafe { kernel::raw::Descriptor::new(stream.file.as_raw_fd()) }
39    }
40
41    fn next<E: From<Errno>>(
42        stream: &mut Stream,
43    ) -> Result<Option<file::Entry<'_>>, file::ReadError<E>> {
44        if stream.offset == stream.filled {
45            // SAFETY: the exclusively borrowed buffer supplies exactly CAPACITY
46            // writable bytes; getdents64 reports complete native directory records.
47            let result = unsafe {
48                libc::syscall(
49                    libc::SYS_getdents64,
50                    stream.file.as_raw_fd(),
51                    stream.buffer.as_mut_ptr(),
52                    CAPACITY,
53                )
54            };
55            // SAFETY: the exact syscall operand bounds every successful count by
56            // CAPACITY; failure leaves this thread's positive errno unchanged.
57            let count = unsafe {
58                kernel::raw::Status::from_count(result as libc::ssize_t)
59                    .try_count::<Errno>(|| kernel::raw::Code::last::<Self>().into())
60            };
61            stream.filled = match count {
62                Ok(count) => count.get(),
63                Err(Errno::NOT_FOUND) => 0,
64                Err(error) => return Err(file::ReadError::Syscall(E::from(error))),
65            };
66            stream.offset = 0;
67            if stream.filled == 0 {
68                return Ok(None);
69            }
70        }
71        // SAFETY: the completed batch retains this record's initialized native
72        // fields and terminated name. Each positive d_reclen advances to the next
73        // complete record or filled; the cursor cannot leave the completed batch.
74        // Padding remains MaybeUninit and is never read or exposed as bytes.
75        let pointer = unsafe {
76            stream
77                .buffer
78                .as_ptr()
79                .add(stream.offset)
80                .cast::<libc::dirent64>()
81        };
82        stream.offset += unsafe { (&raw const (*pointer).d_reclen).read_unaligned() } as usize;
83        let kind = unsafe { (&raw const (*pointer).d_type).read() };
84        let name = unsafe { std::ffi::CStr::from_ptr((&raw const (*pointer).d_name).cast()) };
85        Ok(Some(file::Entry {
86            name,
87            kind: file::Kind::from_dirent(kind),
88        }))
89    }
90}