• 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, 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