devices/virtio/fs/
read_dir.rs1use std::ffi::CStr;
6use std::ffi::OsStr;
7use std::io;
8use std::mem::size_of;
9use std::ops::Deref;
10use std::ops::DerefMut;
11use std::os::unix::ffi::OsStrExt;
12
13use base::AsRawDescriptor;
14use fuse::filesystem::DirEntry;
15use fuse::filesystem::DirectoryIterator;
16use zerocopy::FromBytes;
17use zerocopy::Immutable;
18use zerocopy::IntoBytes;
19use zerocopy::KnownLayout;
20
21use crate::virtio::fs::allowlist::ReadDirFilter;
22
23#[repr(C, packed)]
24#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
25struct LinuxDirent64 {
26 d_ino: libc::ino64_t,
27 d_off: libc::off64_t,
28 d_reclen: libc::c_ushort,
29 d_ty: libc::c_uchar,
30}
31
32pub struct ReadDir<P> {
33 buf: P,
34 current: usize,
35 end: usize,
36 filter: Option<ReadDirFilter>,
37}
38
39impl<P> ReadDir<P> {
40 pub fn with_filter(mut self, filter: ReadDirFilter) -> Self {
41 self.filter = Some(filter);
42 self
43 }
44}
45
46impl<P: DerefMut<Target = [u8]>> ReadDir<P> {
47 pub fn new<D: AsRawDescriptor>(dir: &D, offset: libc::off64_t, mut buf: P) -> io::Result<Self> {
48 let res = unsafe { libc::lseek64(dir.as_raw_descriptor(), offset, libc::SEEK_SET) };
51 if res < 0 {
52 return Err(io::Error::last_os_error());
53 }
54
55 let res = unsafe {
59 libc::syscall(
60 libc::SYS_getdents64,
61 dir.as_raw_descriptor(),
62 buf.as_mut_ptr() as *mut LinuxDirent64,
63 buf.len() as libc::c_int,
64 )
65 };
66 if res < 0 {
67 return Err(io::Error::last_os_error());
68 }
69
70 Ok(ReadDir {
71 buf,
72 current: 0,
73 end: res as usize,
74 filter: None,
75 })
76 }
77}
78
79impl<P> ReadDir<P> {
80 pub fn remaining(&self) -> usize {
82 self.end.saturating_sub(self.current)
83 }
84}
85
86impl<P: Deref<Target = [u8]>> DirectoryIterator for ReadDir<P> {
87 fn next(&mut self) -> Option<DirEntry> {
88 loop {
89 let rem = &self.buf[self.current..self.end];
90 if rem.is_empty() {
91 return None;
92 }
93
94 let (dirent64, back) = LinuxDirent64::read_from_prefix(rem)
95 .expect("unable to get LinuxDirent64 from slice");
96
97 let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>();
98 debug_assert!(namelen <= back.len(), "back is smaller than `namelen`");
99
100 let name = strip_padding(&back[..namelen]);
101 let entry = DirEntry {
102 ino: dirent64.d_ino,
103 offset: dirent64.d_off as u64,
104 type_: dirent64.d_ty as u32,
105 name,
106 };
107
108 debug_assert!(
109 rem.len() >= dirent64.d_reclen as usize,
110 "rem is smaller than `d_reclen`"
111 );
112 self.current += dirent64.d_reclen as usize;
113
114 if let Some(filter) = &self.filter {
116 match filter {
117 ReadDirFilter::AllowAll => {}
118 ReadDirFilter::DenyAll => {
119 continue;
120 }
121 ReadDirFilter::AllowOnly(allowed_entries) => {
122 let name_bytes = entry.name.to_bytes();
123 if name_bytes != b"." && name_bytes != b".." {
124 let name_os = OsStr::from_bytes(name_bytes);
125 if !allowed_entries.contains(name_os) {
126 continue;
128 }
129 }
130 }
131 }
132 }
133
134 return Some(entry);
135 }
136 }
137}
138
139fn strip_padding(b: &[u8]) -> &CStr {
142 let pos = b
144 .iter()
145 .position(|&c| c == 0)
146 .expect("`b` doesn't contain any nul bytes");
147
148 unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) }
152}
153
154#[cfg(test)]
155mod test {
156 use super::*;
157
158 #[test]
159 fn padded_cstrings() {
160 assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b".");
161 assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b"..");
162 assert_eq!(
163 strip_padding(b"normal cstring\0").to_bytes(),
164 b"normal cstring"
165 );
166 assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b"");
167 assert_eq!(
168 strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(),
169 b"interior"
170 );
171 }
172
173 #[test]
174 #[should_panic(expected = "`b` doesn't contain any nul bytes")]
175 fn no_nul_byte() {
176 strip_padding(b"no nul bytes in string");
177 }
178}