standard_lib/fs/dir/
dir_entry.rs

1use std::ffi::OsStr;
2use std::io::RawOsError;
3use std::mem::MaybeUninit;
4use std::num::NonZero;
5use std::os::unix::ffi::OsStrExt;
6use std::ptr::NonNull;
7use std::slice;
8
9use crate::collections::contiguous::Array;
10use crate::fs::dir::Directory;
11use crate::fs::error::RemovedDirectoryError;
12use crate::fs::file::ReadWrite;
13use crate::fs::panic::{BadFdPanic, BadStackAddrPanic, NotADirPanic, Panic, UnexpectedErrorPanic};
14use crate::fs::{File, FileType, OwnedPath, Rel};
15use crate::util;
16
17#[derive(Debug)]
18#[repr(C)]
19pub(crate) struct DirEntrySized {
20    pub d_ino: u64,
21    pub d_off: i64,
22    pub d_reclen: u8,
23    pub d_type: u8,
24}
25
26// TODO: Add wrapped methods on DirEntry for the ..at syscalls, e.g. fstatat.
27
28/// An entry in a [`Directory`]'s records, taking the form of a reference to the parent `Directory`
29/// and the relative path to this entry, along with a few other pieces of information.
30/// 
31/// Among these other pieces of information, is the entry's inode number and a hint about the
32/// [`FileType`] of the entry. Unfortunately, this field is often not present, and therefore
33/// considered only a hint. If the `FileType` is present and has some value, it can be trusted
34/// (subject to TOCTOU restrictions), but the possibility of it not existing should always be
35/// considered or even expected.
36#[derive(Debug)]
37pub struct DirEntry<'a> {
38    pub parent: &'a Directory,
39    pub path: OwnedPath<Rel>,
40    pub file_type_hint: Option<FileType>,
41    pub inode_num: NonZero<u64>,
42    // Neither of these have any relevance to users.
43    // pub d_off: i64,
44    // pub d_reclen: u8,
45}
46
47impl<'a> DirEntry<'a> {
48    pub fn name(&self) -> &OsStr {
49        self.path.as_os_str_no_lead()
50    }
51
52    pub fn open_file(&self) -> Result<File<ReadWrite>, RawOsError> {
53        File::options().open_dir_entry(self)
54    }
55
56    // pub fn open_dir(&self) -> Result<Directory, _>; // openat
57    
58    // TODO: forward to path methods
59}
60
61pub(crate) const BUFFER_SIZE: usize = 1024;
62
63/// An iterator over a [`Directory`]'s contained entries. Obtainable via
64/// [`Directory::read_entries`], this buffered iterator produces [`DirEntry`]s for the referenced
65/// directory.
66/// 
67/// When reading from the file system, the redundant "." and ".." entries are skipped, along with
68/// any deleted entries.
69// TODO: Heaps of TOCTOU notes.
70pub struct DirEntries<'a> {
71    pub(crate) dir: &'a Directory,
72    pub(crate) buf: Array<MaybeUninit<u8>>,
73    pub(crate) head: NonNull<MaybeUninit<u8>>,
74    pub(crate) rem: usize,
75}
76
77// TODO: impl other Iterator traits
78
79impl<'a> Iterator for DirEntries<'a> {
80    type Item = Result<DirEntry<'a>, RemovedDirectoryError>;
81
82    fn next(&mut self) -> Option<Self::Item> {
83        if self.rem == 0 {
84            loop {
85                match unsafe { util::fs::getdents(
86                    *self.dir.fd,
87                    self.buf.as_ptr().cast_mut().cast(),
88                    self.buf.size()
89                ) } {
90                    -1 => match util::fs::err_no() {
91                        libc::EBADF => BadFdPanic.panic(),
92                        libc::EFAULT => BadStackAddrPanic.panic(),
93                        // TODO: Handle (array) overflows etc here? Smart size selection?
94                        // If the buffer isn't large enough, double it. Panics in the event of an
95                        // overflow.
96                        // This is currently the only way that the buffer size can change.
97                        libc::EINVAL => {
98                            self.buf = Array::new_uninit(self.buf.size * 2);
99                            continue;
100                        },
101                        // TODO: Do I really have to return this? Result from an Iterator is gross.
102                        libc::ENOENT => return Some(Err(RemovedDirectoryError)),
103                        libc::ENOTDIR => NotADirPanic.panic(),
104                        e => UnexpectedErrorPanic(e).panic(),
105                    },
106                    0 => None?,
107                    count => self.rem = count as usize,
108                }
109                break;
110            }
111        }
112        let sized = unsafe { self.head.cast::<DirEntrySized>().as_ref() };
113
114        let entry_size = sized.d_reclen as usize;
115
116        let entry = if let Ok(inode_num) = NonZero::try_from(sized.d_ino) {
117            let char_head = unsafe { self.head.add(19).cast() };
118            let mut char_tail = char_head;
119            while unsafe { char_tail.read() } != 0 {
120                char_tail = unsafe { char_tail.add(1) };
121            }
122
123            let name = unsafe { OwnedPath::<Rel>::from_os_str_sanitized(OsStr::from_bytes(
124                slice::from_ptr_range(char_head.as_ptr()..char_tail.as_ptr())
125            )) };
126
127            if name.as_os_str() == OsStr::new("/") || name.as_os_str() == OsStr::new("/..") {
128                // Skip redundant "." and ".." entries. "." will be normalized to "/", which we
129                // don't want either.
130                None
131            } else {
132                Some(DirEntry {
133                    parent: self.dir,
134                    inode_num,
135                    file_type_hint: FileType::from_dirent_type(sized.d_type),
136                    path: name,
137                })
138            }
139        } else {
140            // Skip entries with inode number zero, these are considered deleted on some file
141            // systems and generally invalid on others.
142            None
143        };
144
145        // head + entry_size - 1: first nul terminator for string.
146        // head + entry_size: start of next dirent.
147        self.head = unsafe { self.head.add(entry_size) };
148        // If head is now outside the bounds of the buffer, there should be no remaining elements
149        // and head will be reset next iteration anyway.
150        // It turns out this is also was glibc does with readdir.
151        self.rem -= entry_size;
152
153        match entry {
154            Some(e) => Some(Ok(e)),
155            // If a None has made it to this point without propagating immediately, we are skipping
156            // a value and need to iterate again.
157            None => self.next(),
158        }
159    }
160}