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;
78use libc::{EBADF, EFAULT, EINVAL, ENOENT, ENOTDIR};
910use 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;
1718#[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 {
21pub d_ino: u64,
22pub d_off: i64,
23pub d_reclen: u8,
24pub d_type: u8,
25}
2627// TODO: Add wrapped methods on DirEntry for the ..at syscalls, e.g. fstatat.
2829/// 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> {
39pub parent: &'a Directory,
40pub path: OwnedPath<Rel>,
41pub file_type_hint: Option<FileType>,
42pub inode_num: NonZero<u64>,
43// Neither of these have any relevance to users.
44 // pub d_off: i64,
45 // pub d_reclen: u8,
46}
4748impl<'a> DirEntry<'a> {
49pub fn name(&self) -> &OsStr {
50self.path.as_os_str_no_lead()
51 }
5253pub fn open_file(&self) -> Result<File<ReadWrite>, OpenError> {
54 File::options().open_dir_entry(self)
55 }
5657// pub fn open_dir(&self) -> Result<Directory, _>; // openat
5859 // TODO: forward to path methods
60}
6162pub(crate) const BUFFER_SIZE: usize = 1024;
6364/// 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> {
71pub(crate) dir: &'a Directory,
72pub(crate) buf: Array<MaybeUninit<u8>>,
73pub(crate) head: NonNull<MaybeUninit<u8>>,
74pub(crate) rem: usize,
75}
7677// TODO: impl other Iterator traits
7879impl<'a> Iterator for DirEntries<'a> {
80type Item = Result<DirEntry<'a>, RemovedDirectoryError>;
8182fn next(&mut self) -> Option<Self::Item> {
83if self.rem == 0 {
84loop {
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).
93match unsafe { util::fs::getdents(
94*self.dir.fd,
95self.buf.as_ptr().cast_mut().cast(),
96self.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.
105EINVAL => {
106self.buf = Array::new_uninit(self.buf.size * 2);
107continue;
108 },
109// TODO: Do I really have to return this? Result from an Iterator is gross.
110ENOENT => return Some(Err(RemovedDirectoryError)),
111 ENOTDIR => NotADirPanic.panic(),
112 e => UnexpectedErrorPanic(e).panic(),
113 },
1140 => None?,
115 count => self.rem = count as usize,
116 }
117break;
118 }
119 }
120let sized = unsafe { self.head.cast::<DirEntrySized>().as_ref() };
121122let entry_size = sized.d_reclen as usize;
123124let entry = if let Ok(inode_num) = NonZero::try_from(sized.d_ino) {
125let char_head = unsafe { self.head.add(19).cast() };
126let mut char_tail = char_head;
127while unsafe { char_tail.read() } != 0 {
128 char_tail = unsafe { char_tail.add(1) };
129 }
130131let name = unsafe { OwnedPath::<Rel>::from(OsStr::from_bytes(
132 slice::from_ptr_range(char_head.as_ptr()..char_tail.as_ptr())
133 )) };
134135if 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.
138None
139} else {
140Some(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.
150None
151};
152153// head + entry_size - 1: first nul terminator for string.
154 // head + entry_size: start of next dirent.
155self.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.
159self.rem -= entry_size;
160161match entry {
162Some(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.
165None => self.next(),
166 }
167 }
168}