Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/fs/read_dir.rs
5394 views
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> {
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.
68
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> {
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.
107
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]
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")]
141
fn no_nul_byte() {
142
strip_padding(b"no nul bytes in string");
143
}
144
}
145
146