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