| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::ffi::CStr; |
| use std::io; |
| use std::mem::size_of; |
| use std::os::unix::io::AsRawFd; |
| |
| use data_model::DataInit; |
| |
| use crate::syscall; |
| |
| #[repr(C, packed)] |
| #[derive(Clone, Copy)] |
| struct LinuxDirent64 { |
| d_ino: libc::ino64_t, |
| d_off: libc::off64_t, |
| d_reclen: libc::c_ushort, |
| d_ty: libc::c_uchar, |
| } |
| unsafe impl DataInit for LinuxDirent64 {} |
| |
| pub struct DirEntry<'r> { |
| pub ino: libc::ino64_t, |
| pub offset: u64, |
| pub type_: u8, |
| pub name: &'r CStr, |
| } |
| |
| pub struct ReadDir<'d, D> { |
| buf: [u8; 256], |
| dir: &'d mut D, |
| current: usize, |
| end: usize, |
| } |
| |
| impl<'d, D: AsRawFd> ReadDir<'d, D> { |
| /// Return the next directory entry. This is implemented as a separate method rather than via |
| /// the `Iterator` trait because rust doesn't currently support generic associated types. |
| #[allow(clippy::should_implement_trait)] |
| pub fn next(&mut self) -> Option<io::Result<DirEntry>> { |
| if self.current >= self.end { |
| let res = syscall!(unsafe { |
| libc::syscall( |
| libc::SYS_getdents64, |
| self.dir.as_raw_fd(), |
| self.buf.as_mut_ptr() as *mut LinuxDirent64, |
| self.buf.len() as libc::c_int, |
| ) |
| }); |
| match res { |
| Ok(end) => { |
| self.current = 0; |
| self.end = end as usize; |
| } |
| Err(e) => return Some(Err(e)), |
| } |
| } |
| |
| let rem = &self.buf[self.current..self.end]; |
| if rem.is_empty() { |
| return None; |
| } |
| |
| // We only use debug asserts here because these values are coming from the kernel and we |
| // trust them implicitly. |
| debug_assert!( |
| rem.len() >= size_of::<LinuxDirent64>(), |
| "not enough space left in `rem`" |
| ); |
| |
| let (front, back) = rem.split_at(size_of::<LinuxDirent64>()); |
| |
| let dirent64 = |
| LinuxDirent64::from_slice(front).expect("unable to get LinuxDirent64 from slice"); |
| |
| let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>(); |
| debug_assert!(namelen <= back.len(), "back is smaller than `namelen`"); |
| |
| // The kernel will pad the name with additional nul bytes until it is 8-byte aligned so |
| // we need to strip those off here. |
| let name = strip_padding(&back[..namelen]); |
| let entry = DirEntry { |
| ino: dirent64.d_ino, |
| offset: dirent64.d_off as u64, |
| type_: dirent64.d_ty, |
| name, |
| }; |
| |
| debug_assert!( |
| rem.len() >= dirent64.d_reclen as usize, |
| "rem is smaller than `d_reclen`" |
| ); |
| self.current += dirent64.d_reclen as usize; |
| Some(Ok(entry)) |
| } |
| } |
| |
| pub fn read_dir<D: AsRawFd>(dir: &mut D, offset: libc::off64_t) -> io::Result<ReadDir<D>> { |
| // Safe because this doesn't modify any memory and we check the return value. |
| syscall!(unsafe { libc::lseek64(dir.as_raw_fd(), offset, libc::SEEK_SET) })?; |
| |
| Ok(ReadDir { |
| buf: [0u8; 256], |
| dir, |
| current: 0, |
| end: 0, |
| }) |
| } |
| |
| // Like `CStr::from_bytes_with_nul` but strips any bytes after the first '\0'-byte. Panics if `b` |
| // doesn't contain any '\0' bytes. |
| fn strip_padding(b: &[u8]) -> &CStr { |
| // It would be nice if we could use memchr here but that's locked behind an unstable gate. |
| let pos = b |
| .iter() |
| .position(|&c| c == 0) |
| .expect("`b` doesn't contain any nul bytes"); |
| |
| // Safe because we are creating this string with the first nul-byte we found so we can |
| // guarantee that it is nul-terminated and doesn't contain any interior nuls. |
| unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| #[test] |
| fn padded_cstrings() { |
| assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b"."); |
| assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b".."); |
| assert_eq!( |
| strip_padding(b"normal cstring\0").to_bytes(), |
| b"normal cstring" |
| ); |
| assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b""); |
| assert_eq!( |
| strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(), |
| b"interior" |
| ); |
| } |
| |
| #[test] |
| #[should_panic(expected = "`b` doesn't contain any nul bytes")] |
| fn no_nul_byte() { |
| strip_padding(b"no nul bytes in string"); |
| } |
| } |