devices/virtio/fs/
read_dir.rs

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
5use std::ffi::CStr;
6use std::io;
7use std::mem::size_of;
8use std::ops::Deref;
9use std::ops::DerefMut;
10
11use base::AsRawDescriptor;
12use fuse::filesystem::DirEntry;
13use fuse::filesystem::DirectoryIterator;
14use zerocopy::FromBytes;
15use zerocopy::Immutable;
16use zerocopy::IntoBytes;
17use zerocopy::KnownLayout;
18
19#[repr(C, packed)]
20#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
21struct 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
28pub struct ReadDir<P> {
29    buf: P,
30    current: usize,
31    end: usize,
32}
33
34impl<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
66impl<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
73impl<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.
107fn 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)]
121mod 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}