blob: eb8a549fed3e9da0206944738ce018d262aac9b4 [file] [log] [blame]
// 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");
}
}