use core::{ mem::size_of, ops::{Deref, DerefMut}, slice, }; use crate::{ error::{Error, Result, EINVAL}, ENAMETOOLONG, }; #[derive(Clone, Copy, Debug, Default)] #[repr(packed)] pub struct DirentHeader { pub inode: u64, /// A filesystem-specific opaque value used to uniquely identify directory entries. This value, /// in the last returned entry from a SYS_GETDENTS invocation, shall be passed to the next /// call. pub next_opaque_id: u64, // This struct intentionally does not include a "next" offset field, unlike Linux, to easily // guarantee the iterator will be reasonably deterministic, even if the scheme is adversarial. pub record_len: u16, /// A `DirentKind`. /// /// May not be directly available (Unspecified), and if so needs to be looked using fstat. pub kind: u8, } impl Deref for DirentHeader { type Target = [u8]; fn deref(&self) -> &[u8] { unsafe { slice::from_raw_parts(self as *const Self as *const u8, size_of::()) } } } impl DerefMut for DirentHeader { fn deref_mut(&mut self) -> &mut [u8] { unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, size_of::()) } } } // Note: Must match relibc/include/bits/dirent.h #[derive(Clone, Copy, Debug, Default)] #[repr(u8)] pub enum DirentKind { #[default] Unspecified = 0, CharDev = 2, Directory = 4, BlockDev = 6, Regular = 8, Symlink = 10, Socket = 12, } impl DirentKind { // TODO: derive(FromPrimitive) pub fn try_from_raw(raw: u8) -> Option { Some(match raw { 0 => Self::Unspecified, 2 => Self::CharDev, 4 => Self::Directory, 6 => Self::BlockDev, 8 => Self::Regular, 10 => Self::Symlink, 12 => Self::Socket, _ => return None, }) } } pub struct DirentIter<'a>(&'a [u8]); impl<'a> DirentIter<'a> { pub const fn new(buffer: &'a [u8]) -> Self { Self(buffer) } } #[derive(Debug)] pub struct Invalid; impl<'a> Iterator for DirentIter<'a> { type Item = Result<(&'a DirentHeader, &'a [u8]), Invalid>; fn next(&mut self) -> Option { if self.0.len() < size_of::() { return None; } let header = unsafe { &*(self.0.as_ptr().cast::()) }; if self.0.len() < usize::from(header.record_len) { return Some(Err(Invalid)); } let (this, remaining) = self.0.split_at(usize::from(header.record_len)); self.0 = remaining; let name_and_nul = &this[size_of::()..]; let name = &name_and_nul[..name_and_nul.len() - 1]; Some(Ok((header, name))) } } #[derive(Debug)] pub struct DirentBuf { buffer: B, // Exists in order to allow future extensions to the DirentHeader struct. // TODO: Might add an upper bound to protect against cache miss DoS. The kernel currently // forbids any other value than size_of::(). header_size: u16, written: usize, } /// Abstraction between &mut [u8] and the kernel's UserSliceWo. pub trait Buffer<'a>: Sized + 'a { fn empty() -> Self; fn length(&self) -> usize; /// Split all of `self` into two disjoint contiguous subbuffers of lengths `index` and `length /// - index` respectively. /// /// Returns None if and only if `index > length`. fn split_at(self, index: usize) -> Option<[Self; 2]>; /// Copy from `src`, lengths must match exactly. /// /// Allowed to overwrite subsequent buffer space, for performance reasons. Can be changed in /// the future if too restrictive. fn copy_from_slice_exact(self, src: &[u8]) -> Result<()>; /// Write zeroes to this part of the buffer. /// /// Allowed to overwrite subsequent buffer space, for performance reasons. Can be changed in /// the future if too restrictive. fn zero_out(self) -> Result<()>; } impl<'a> Buffer<'a> for &'a mut [u8] { fn empty() -> Self { &mut [] } fn length(&self) -> usize { self.len() } fn split_at(self, index: usize) -> Option<[Self; 2]> { self.split_at_mut_checked(index).map(|(a, b)| [a, b]) } fn copy_from_slice_exact(self, src: &[u8]) -> Result<()> { self.copy_from_slice(src); Ok(()) } fn zero_out(self) -> Result<()> { self.fill(0); Ok(()) } } pub struct DirEntry<'name> { pub inode: u64, pub next_opaque_id: u64, pub name: &'name str, pub kind: DirentKind, } impl<'a, B: Buffer<'a>> DirentBuf { pub fn new(buffer: B, header_size: u16) -> Option { if usize::from(header_size) < size_of::() { return None; } Some(Self { buffer, header_size, written: 0, }) } pub fn entry(&mut self, entry: DirEntry<'_>) -> Result<()> { let name16 = u16::try_from(entry.name.len()).map_err(|_| Error::new(EINVAL))?; let record_len = self .header_size .checked_add(name16) // XXX: NUL byte. Unfortunately this is probably the only performant way to be // compatible with C. .and_then(|l| l.checked_add(1)) .ok_or(Error::new(ENAMETOOLONG))?; let [this, remaining] = core::mem::replace(&mut self.buffer, B::empty()) .split_at(usize::from(record_len)) .ok_or(Error::new(EINVAL))?; let [this_header_variable, this_name_and_nul] = this .split_at(usize::from(self.header_size)) .expect("already know header_size + ... >= header_size"); let [this_name, this_name_nul] = this_name_and_nul .split_at(usize::from(name16)) .expect("already know name.len() <= name.len() + 1"); // Every write here is currently sequential, allowing the buffer trait to do optimizations // where subbuffer writes are out-of-bounds (but inside the total buffer). let [this_header, this_header_extra] = this_header_variable .split_at(size_of::()) .expect("already checked header_size <= size_of Header"); this_header.copy_from_slice_exact(&DirentHeader { record_len, next_opaque_id: entry.next_opaque_id, inode: entry.inode, kind: entry.kind as u8, })?; this_header_extra.zero_out()?; this_name.copy_from_slice_exact(entry.name.as_bytes())?; this_name_nul.copy_from_slice_exact(&[0])?; self.written += usize::from(record_len); self.buffer = remaining; Ok(()) } pub fn finalize(self) -> usize { self.written } }