• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
7 use std::mem::size_of;
8 use std::ops::Deref;
9 use std::ops::DerefMut;
10 
11 use base::AsRawDescriptor;
12 use fuse::filesystem::DirEntry;
13 use fuse::filesystem::DirectoryIterator;
14 use zerocopy::FromBytes;
15 use zerocopy::Immutable;
16 use zerocopy::IntoBytes;
17 use zerocopy::KnownLayout;
18 
19 #[repr(C, packed)]
20 #[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
21 struct LinuxDirent64 {
22     d_ino: libc::ino64_t,
23     d_off: libc::off64_t,
24     d_reclen: libc::c_ushort,
25     d_ty: libc::c_uchar,
26 }
27 
28 pub struct ReadDir<P> {
29     buf: P,
30     current: usize,
31     end: usize,
32 }
33 
34 impl<P: DerefMut<Target = [u8]>> ReadDir<P> {
new<D: AsRawDescriptor>(dir: &D, offset: libc::off64_t, mut buf: P) -> io::Result<Self>35     pub fn new<D: AsRawDescriptor>(dir: &D, offset: libc::off64_t, mut buf: P) -> io::Result<Self> {
36         // SAFETY:
37         // Safe because this doesn't modify any memory and we check the return value.
38         let res = unsafe { libc::lseek64(dir.as_raw_descriptor(), offset, libc::SEEK_SET) };
39         if res < 0 {
40             return Err(io::Error::last_os_error());
41         }
42 
43         // SAFETY:
44         // Safe because the kernel guarantees that it will only write to `buf` and we check the
45         // return value.
46         let res = unsafe {
47             libc::syscall(
48                 libc::SYS_getdents64,
49                 dir.as_raw_descriptor(),
50                 buf.as_mut_ptr() as *mut LinuxDirent64,
51                 buf.len() as libc::c_int,
52             )
53         };
54         if res < 0 {
55             return Err(io::Error::last_os_error());
56         }
57 
58         Ok(ReadDir {
59             buf,
60             current: 0,
61             end: res as usize,
62         })
63     }
64 }
65 
66 impl<P> ReadDir<P> {
67     /// Returns the number of bytes from the internal buffer that have not yet been consumed.
remaining(&self) -> usize68     pub fn remaining(&self) -> usize {
69         self.end.saturating_sub(self.current)
70     }
71 }
72 
73 impl<P: Deref<Target = [u8]>> DirectoryIterator for ReadDir<P> {
next(&mut self) -> Option<DirEntry>74     fn next(&mut self) -> Option<DirEntry> {
75         let rem = &self.buf[self.current..self.end];
76         if rem.is_empty() {
77             return None;
78         }
79 
80         let (dirent64, back) =
81             LinuxDirent64::read_from_prefix(rem).expect("unable to get LinuxDirent64 from slice");
82 
83         let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>();
84         debug_assert!(namelen <= back.len(), "back is smaller than `namelen`");
85 
86         // The kernel will pad the name with additional nul bytes until it is 8-byte aligned so
87         // we need to strip those off here.
88         let name = strip_padding(&back[..namelen]);
89         let entry = DirEntry {
90             ino: dirent64.d_ino,
91             offset: dirent64.d_off as u64,
92             type_: dirent64.d_ty as u32,
93             name,
94         };
95 
96         debug_assert!(
97             rem.len() >= dirent64.d_reclen as usize,
98             "rem is smaller than `d_reclen`"
99         );
100         self.current += dirent64.d_reclen as usize;
101         Some(entry)
102     }
103 }
104 
105 // Like `CStr::from_bytes_with_nul` but strips any bytes after the first '\0'-byte. Panics if `b`
106 // doesn't contain any '\0' bytes.
strip_padding(b: &[u8]) -> &CStr107 fn strip_padding(b: &[u8]) -> &CStr {
108     // It would be nice if we could use memchr here but that's locked behind an unstable gate.
109     let pos = b
110         .iter()
111         .position(|&c| c == 0)
112         .expect("`b` doesn't contain any nul bytes");
113 
114     // SAFETY:
115     // Safe because we are creating this string with the first nul-byte we found so we can
116     // guarantee that it is nul-terminated and doesn't contain any interior nuls.
117     unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) }
118 }
119 
120 #[cfg(test)]
121 mod test {
122     use super::*;
123 
124     #[test]
padded_cstrings()125     fn padded_cstrings() {
126         assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b".");
127         assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b"..");
128         assert_eq!(
129             strip_padding(b"normal cstring\0").to_bytes(),
130             b"normal cstring"
131         );
132         assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b"");
133         assert_eq!(
134             strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(),
135             b"interior"
136         );
137     }
138 
139     #[test]
140     #[should_panic(expected = "`b` doesn't contain any nul bytes")]
no_nul_byte()141     fn no_nul_byte() {
142         strip_padding(b"no nul bytes in string");
143     }
144 }
145