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