standard_lib/fs/dir/
dir_entry.rs

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