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