1 // Copyright 2020 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::{ffi::CStr, io::Result, mem::size_of, os::unix::io::AsRawFd};
6
7 use data_model::DataInit;
8
9 use crate::syscall;
10
11 #[repr(C, packed)]
12 #[derive(Clone, Copy)]
13 struct LinuxDirent64 {
14 d_ino: libc::ino64_t,
15 d_off: libc::off64_t,
16 d_reclen: libc::c_ushort,
17 d_ty: libc::c_uchar,
18 }
19 unsafe impl DataInit for LinuxDirent64 {}
20
21 pub struct DirEntry<'r> {
22 pub ino: libc::ino64_t,
23 pub offset: u64,
24 pub type_: u8,
25 pub name: &'r CStr,
26 }
27
28 pub struct ReadDir<'d, D> {
29 buf: [u8; 256],
30 dir: &'d mut D,
31 current: usize,
32 end: usize,
33 }
34
35 impl<'d, D: AsRawFd> ReadDir<'d, D> {
36 /// Return the next directory entry. This is implemented as a separate method rather than via
37 /// the `Iterator` trait because rust doesn't currently support generic associated types.
38 #[allow(clippy::should_implement_trait)]
next(&mut self) -> Option<Result<DirEntry>>39 pub fn next(&mut self) -> Option<Result<DirEntry>> {
40 if self.current >= self.end {
41 let res: Result<libc::c_long> = syscall!(unsafe {
42 libc::syscall(
43 libc::SYS_getdents64,
44 self.dir.as_raw_fd(),
45 self.buf.as_mut_ptr() as *mut LinuxDirent64,
46 self.buf.len() as libc::c_int,
47 )
48 })
49 .map_err(|e| e.into());
50 match res {
51 Ok(end) => {
52 self.current = 0;
53 self.end = end as usize;
54 }
55 Err(e) => return Some(Err(e)),
56 }
57 }
58
59 let rem = &self.buf[self.current..self.end];
60 if rem.is_empty() {
61 return None;
62 }
63
64 // We only use debug asserts here because these values are coming from the kernel and we
65 // trust them implicitly.
66 debug_assert!(
67 rem.len() >= size_of::<LinuxDirent64>(),
68 "not enough space left in `rem`"
69 );
70
71 let (front, back) = rem.split_at(size_of::<LinuxDirent64>());
72
73 let dirent64 =
74 LinuxDirent64::from_slice(front).expect("unable to get LinuxDirent64 from slice");
75
76 let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>();
77 debug_assert!(namelen <= back.len(), "back is smaller than `namelen`");
78
79 // The kernel will pad the name with additional nul bytes until it is 8-byte aligned so
80 // we need to strip those off here.
81 let name = strip_padding(&back[..namelen]);
82 let entry = DirEntry {
83 ino: dirent64.d_ino,
84 offset: dirent64.d_off as u64,
85 type_: dirent64.d_ty,
86 name,
87 };
88
89 debug_assert!(
90 rem.len() >= dirent64.d_reclen as usize,
91 "rem is smaller than `d_reclen`"
92 );
93 self.current += dirent64.d_reclen as usize;
94 Some(Ok(entry))
95 }
96 }
97
read_dir<D: AsRawFd>(dir: &mut D, offset: libc::off64_t) -> Result<ReadDir<D>>98 pub fn read_dir<D: AsRawFd>(dir: &mut D, offset: libc::off64_t) -> Result<ReadDir<D>> {
99 // Safe because this doesn't modify any memory and we check the return value.
100 syscall!(unsafe { libc::lseek64(dir.as_raw_fd(), offset, libc::SEEK_SET) })?;
101
102 Ok(ReadDir {
103 buf: [0u8; 256],
104 dir,
105 current: 0,
106 end: 0,
107 })
108 }
109
110 // Like `CStr::from_bytes_with_nul` but strips any bytes after the first '\0'-byte. Panics if `b`
111 // doesn't contain any '\0' bytes.
strip_padding(b: &[u8]) -> &CStr112 fn strip_padding(b: &[u8]) -> &CStr {
113 // It would be nice if we could use memchr here but that's locked behind an unstable gate.
114 let pos = b
115 .iter()
116 .position(|&c| c == 0)
117 .expect("`b` doesn't contain any nul bytes");
118
119 // Safe because we are creating this string with the first nul-byte we found so we can
120 // guarantee that it is nul-terminated and doesn't contain any interior nuls.
121 unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) }
122 }
123
124 #[cfg(test)]
125 mod test {
126 use super::*;
127
128 #[test]
padded_cstrings()129 fn padded_cstrings() {
130 assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b".");
131 assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b"..");
132 assert_eq!(
133 strip_padding(b"normal cstring\0").to_bytes(),
134 b"normal cstring"
135 );
136 assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b"");
137 assert_eq!(
138 strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(),
139 b"interior"
140 );
141 }
142
143 #[test]
144 #[should_panic(expected = "`b` doesn't contain any nul bytes")]
no_nul_byte()145 fn no_nul_byte() {
146 strip_padding(b"no nul bytes in string");
147 }
148 }
149