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