linux_io/fd/
direntry.rs

1use core::{ffi::CStr, slice};
2
3/// A single directory entry extracted from a buffer populated by `getdents64`.
4#[derive(Debug, Eq, PartialEq, Clone)]
5pub struct DirEntry<'a> {
6    pub ino: linux_unsafe::ino64_t,
7    pub off: linux_unsafe::off64_t,
8    pub entry_type: DirEntryType,
9    pub name: &'a CStr,
10}
11
12/// Directory entry type.
13#[derive(Debug, Eq, PartialEq, Clone, Copy)]
14#[repr(u8)]
15pub enum DirEntryType {
16    Unknown = 0,
17    Fifo = 1,
18    Chr = 2,
19    Dir = 4,
20    Blk = 6,
21    Reg = 8,
22    Lnk = 10,
23    Sock = 12,
24    Wht = 14,
25}
26
27impl From<linux_unsafe::uchar> for DirEntryType {
28    fn from(value: linux_unsafe::uchar) -> Self {
29        match value {
30            linux_unsafe::DT_FIFO => Self::Fifo,
31            linux_unsafe::DT_CHR => Self::Chr,
32            linux_unsafe::DT_DIR => Self::Dir,
33            linux_unsafe::DT_BLK => Self::Blk,
34            linux_unsafe::DT_REG => Self::Reg,
35            linux_unsafe::DT_LNK => Self::Lnk,
36            linux_unsafe::DT_SOCK => Self::Sock,
37            linux_unsafe::DT_WHT => Self::Wht,
38            _ => Self::Unknown,
39        }
40    }
41}
42
43/// An iterator over directory entries in an already-populated `getdents64`
44/// result buffer.
45pub struct DirEntries<'a> {
46    remain: &'a [u8],
47}
48
49impl<'a> DirEntries<'a> {
50    pub fn from_getdents64_buffer(buf: &'a [u8]) -> Self {
51        Self { remain: buf }
52    }
53
54    /// Consume the iterator object and obtain the remaining bytes that it
55    /// hasn't yet transformed into `DirEntry` values.
56    ///
57    /// The result could be passed back to [`Self::from_getdents64_buffer`]
58    /// to continue iterating.
59    pub fn to_remaining_bytes(self) -> &'a [u8] {
60        self.remain
61    }
62}
63
64impl<'a> Iterator for DirEntries<'a> {
65    type Item = DirEntry<'a>;
66
67    fn next(&mut self) -> Option<Self::Item> {
68        let (ret, remain) = dir_entry_from_buf(self.remain);
69        self.remain = remain;
70        ret
71    }
72}
73
74fn dir_entry_from_buf<'a>(buf: &'a [u8]) -> (Option<DirEntry<'a>>, &'a [u8]) {
75    #[derive(Debug)]
76    #[repr(C)]
77    struct DirEntryHeader {
78        // These fields must match the fixed part of linux_unsafe::linux_dirent64
79        d_ino: linux_unsafe::ino64_t,
80        d_off: linux_unsafe::off64_t,
81        d_reclen: linux_unsafe::ushort,
82        d_type: linux_unsafe::uchar,
83        d_name: (),
84    }
85    // NOTE: Because DirEntryHeader has 8-byte alignment, HEADER_SIZE is
86    // 24 (3*8) even though the name begins at offset 19 (NAME_OFFSET).
87    // The kernel leaves padding bytes between the entries to maintain
88    // the needed alignment.
89    const HEADER_SIZE: usize = core::mem::size_of::<DirEntryHeader>();
90    const NAME_OFFSET: usize = core::mem::offset_of!(DirEntryHeader, d_name);
91
92    if buf.len() < HEADER_SIZE {
93        // Not enough bytes left for an entry.
94        return (None, buf);
95    }
96
97    let (raw_len, ino, off, entry_type) = {
98        let hdr_ptr = buf.as_ptr() as *const DirEntryHeader;
99        let hdr = unsafe { &*hdr_ptr };
100        let claimed_len = hdr.d_reclen as usize;
101        if buf.len() < claimed_len || claimed_len < HEADER_SIZE {
102            // Not enough room for the claimed length, or claimed length
103            // shorter than the header. Neither is valid.
104            return (None, buf);
105        }
106        (claimed_len, hdr.d_ino, hdr.d_off, hdr.d_type)
107    };
108
109    let name_len = raw_len - NAME_OFFSET;
110    let name_start = unsafe { buf.as_ptr().add(NAME_OFFSET) } as *const u8;
111    let name = unsafe { slice::from_raw_parts::<'a, _>(name_start, name_len) };
112    let name = CStr::from_bytes_until_nul(name).unwrap();
113
114    let remain = &buf[raw_len..];
115    let ret = DirEntry {
116        ino,
117        off,
118        entry_type: entry_type.into(),
119        name,
120    };
121    (Some(ret), remain)
122}
123
124pub struct AllDirEntries<'file, 'buf, TF, R, D>
125where
126    TF: FnMut(DirEntry<'buf>) -> R,
127{
128    f: Option<&'file crate::File<D>>,
129    buf: &'buf mut [u8],
130    rng: Range,
131    transform: TF,
132}
133
134impl<'file, 'buf, TF, R, D> AllDirEntries<'file, 'buf, TF, R, D>
135where
136    TF: for<'tmp> FnMut(DirEntry<'tmp>) -> R,
137{
138    pub(crate) fn new(f: &'file crate::File<D>, buf: &'buf mut [u8], transform: TF) -> Self {
139        Self {
140            f: Some(f),
141            buf,
142            rng: Range::new(0, 0),
143            transform,
144        }
145    }
146}
147
148fn try_read_entry<'a>(buf: &'a [u8]) -> (Option<DirEntry<'a>>, usize) {
149    if buf.len() == 0 {
150        return (None, 0);
151    }
152    let (ret, remain) = dir_entry_from_buf(buf);
153    if let Some(entry) = ret {
154        let advance = remain.as_ptr() as usize - buf.as_ptr() as usize;
155        (Some(entry), advance)
156    } else {
157        (None, buf.len())
158    }
159}
160
161impl<'file, 'buf, TF, R, D> Iterator for AllDirEntries<'file, 'buf, TF, R, D>
162where
163    TF: for<'tmp> FnMut(DirEntry<'tmp>) -> R,
164{
165    type Item = Result<R, crate::result::Error>;
166
167    fn next(&mut self) -> Option<Self::Item> {
168        if let Some(f) = self.f {
169            {
170                let buf = &self.buf[self.rng.start..self.rng.end];
171                let (maybe_entry, advance) = try_read_entry(buf);
172                self.rng.start += advance;
173                if let Some(entry) = maybe_entry {
174                    return Some(Ok((self.transform)(entry)));
175                }
176            }
177            let result = unsafe {
178                f.getdents_raw(
179                    self.buf.as_mut_ptr() as *mut _,
180                    self.buf.len() as linux_unsafe::int,
181                )
182            };
183            match result {
184                Ok(result_len) => {
185                    self.rng = Range::new(0, result_len);
186                    let buf = &self.buf[self.rng.start..self.rng.end];
187                    let (maybe_entry, advance) = try_read_entry(buf);
188                    self.rng.start += advance;
189                    maybe_entry.map(|entry| Ok((self.transform)(entry)))
190                }
191                Err(e) => {
192                    self.f = None; // no longer usable
193                    Some(Err(e))
194                }
195            }
196        } else {
197            None
198        }
199    }
200}
201
202#[derive(Debug, Clone, Copy)]
203struct Range {
204    start: usize,
205    end: usize,
206}
207
208impl Range {
209    #[inline(always)]
210    fn new(start: usize, end: usize) -> Self {
211        Self { start, end }
212    }
213}