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(stream: &mut Stream) -> Result<Option<file::Entry<'_>>, Errno> {
42        if stream.offset == stream.filled {
43            // SAFETY: the exclusively borrowed buffer supplies exactly CAPACITY
44            // writable bytes; getdents64 reports complete native directory records.
45            let result = unsafe {
46                libc::syscall(
47                    libc::SYS_getdents64,
48                    stream.file.as_raw_fd(),
49                    stream.buffer.as_mut_ptr(),
50                    CAPACITY,
51                )
52            };
53            // SAFETY: the exact syscall operand bounds every successful count by
54            // CAPACITY; failure leaves this thread's positive errno unchanged.
55            let count = unsafe {
56                kernel::raw::Status::from_count(result as libc::ssize_t).try_count::<Self, Errno>()
57            };
58            stream.filled = match count {
59                Ok(count) => count.get(),
60                Err(Errno::NOT_FOUND) => 0,
61                Err(error) => return Err(error),
62            };
63            stream.offset = 0;
64            if stream.filled == 0 {
65                return Ok(None);
66            }
67        }
68        // SAFETY: the completed batch retains this record's initialized native
69        // fields and terminated name. Each positive d_reclen advances to the next
70        // complete record or filled; the cursor cannot leave the completed batch.
71        // Padding remains MaybeUninit and is never read or exposed as bytes.
72        let pointer = unsafe {
73            stream
74                .buffer
75                .as_ptr()
76                .add(stream.offset)
77                .cast::<libc::dirent64>()
78        };
79        stream.offset += unsafe { (&raw const (*pointer).d_reclen).read_unaligned() } as usize;
80        let kind = unsafe { (&raw const (*pointer).d_type).read() };
81        let name = unsafe { std::ffi::CStr::from_ptr((&raw const (*pointer).d_name).cast()) };
82        Ok(Some(file::Entry {
83            name,
84            kind: file::Kind::from_dirent(kind),
85        }))
86    }
87}