rustix/backend/linux_raw/fs/
dir.rs

1use crate::fd::{AsFd, BorrowedFd, OwnedFd};
2use crate::ffi::{CStr, CString};
3use crate::fs::{
4    fcntl_getfl, fstat, fstatfs, fstatvfs, openat, FileType, Mode, OFlags, Stat, StatFs, StatVfs,
5};
6use crate::io;
7#[cfg(feature = "process")]
8use crate::process::fchdir;
9use crate::utils::as_ptr;
10use alloc::borrow::ToOwned;
11use alloc::vec::Vec;
12use core::fmt;
13use core::mem::size_of;
14use linux_raw_sys::general::{linux_dirent64, SEEK_SET};
15
16/// `DIR*`
17pub struct Dir {
18    /// The `OwnedFd` that we read directory entries from.
19    fd: OwnedFd,
20
21    /// Have we seen any errors in this iteration?
22    any_errors: bool,
23
24    /// Should we rewind the stream on the next iteration?
25    rewind: bool,
26
27    /// The buffer for `linux_dirent64` entries.
28    buf: Vec<u8>,
29
30    /// Where we are in the buffer.
31    pos: usize,
32}
33
34impl Dir {
35    /// Take ownership of `fd` and construct a `Dir` that reads entries from
36    /// the given directory file descriptor.
37    #[inline]
38    pub fn new<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<Self> {
39        Self::_new(fd.into())
40    }
41
42    #[inline]
43    fn _new(fd: OwnedFd) -> io::Result<Self> {
44        Ok(Self {
45            fd,
46            any_errors: false,
47            rewind: false,
48            buf: Vec::new(),
49            pos: 0,
50        })
51    }
52
53    /// Borrow `fd` and construct a `Dir` that reads entries from the given
54    /// directory file descriptor.
55    #[inline]
56    pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
57        Self::_read_from(fd.as_fd())
58    }
59
60    #[inline]
61    fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
62        let flags = fcntl_getfl(fd)?;
63        let fd_for_dir = openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty())?;
64
65        Ok(Self {
66            fd: fd_for_dir,
67            any_errors: false,
68            rewind: false,
69            buf: Vec::new(),
70            pos: 0,
71        })
72    }
73
74    /// `rewinddir(self)`
75    #[inline]
76    pub fn rewind(&mut self) {
77        self.any_errors = false;
78        self.rewind = true;
79        self.pos = self.buf.len();
80    }
81
82    /// `readdir(self)`, where `None` means the end of the directory.
83    pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
84        // If we've seen errors, don't continue to try to read anything further.
85        if self.any_errors {
86            return None;
87        }
88
89        // If a rewind was requested, seek to the beginning.
90        if self.rewind {
91            self.rewind = false;
92            match io::retry_on_intr(|| {
93                crate::backend::fs::syscalls::_seek(self.fd.as_fd(), 0, SEEK_SET)
94            }) {
95                Ok(_) => (),
96                Err(err) => {
97                    self.any_errors = true;
98                    return Some(Err(err));
99                }
100            }
101        }
102
103        // Compute linux_dirent64 field offsets.
104        let z = linux_dirent64 {
105            d_ino: 0_u64,
106            d_off: 0_i64,
107            d_type: 0_u8,
108            d_reclen: 0_u16,
109            d_name: Default::default(),
110        };
111        let base = as_ptr(&z) as usize;
112        let offsetof_d_reclen = (as_ptr(&z.d_reclen) as usize) - base;
113        let offsetof_d_name = (as_ptr(&z.d_name) as usize) - base;
114        let offsetof_d_ino = (as_ptr(&z.d_ino) as usize) - base;
115        let offsetof_d_type = (as_ptr(&z.d_type) as usize) - base;
116
117        // Test if we need more entries, and if so, read more.
118        if self.buf.len() - self.pos < size_of::<linux_dirent64>() {
119            match self.read_more()? {
120                Ok(()) => (),
121                Err(err) => return Some(Err(err)),
122            }
123        }
124
125        // We successfully read an entry. Extract the fields.
126        let pos = self.pos;
127
128        // Do an unaligned u16 load.
129        let d_reclen = u16::from_ne_bytes([
130            self.buf[pos + offsetof_d_reclen],
131            self.buf[pos + offsetof_d_reclen + 1],
132        ]);
133        assert!(self.buf.len() - pos >= d_reclen as usize);
134        self.pos += d_reclen as usize;
135
136        // Read the NUL-terminated name from the `d_name` field. Without
137        // `unsafe`, we need to scan for the NUL twice: once to obtain a size
138        // for the slice, and then once within `CStr::from_bytes_with_nul`.
139        let name_start = pos + offsetof_d_name;
140        let name_len = self.buf[name_start..]
141            .iter()
142            .position(|x| *x == b'\0')
143            .unwrap();
144        let name = CStr::from_bytes_with_nul(&self.buf[name_start..][..=name_len]).unwrap();
145        let name = name.to_owned();
146        assert!(name.as_bytes().len() <= self.buf.len() - name_start);
147
148        // Do an unaligned u64 load.
149        let d_ino = u64::from_ne_bytes([
150            self.buf[pos + offsetof_d_ino],
151            self.buf[pos + offsetof_d_ino + 1],
152            self.buf[pos + offsetof_d_ino + 2],
153            self.buf[pos + offsetof_d_ino + 3],
154            self.buf[pos + offsetof_d_ino + 4],
155            self.buf[pos + offsetof_d_ino + 5],
156            self.buf[pos + offsetof_d_ino + 6],
157            self.buf[pos + offsetof_d_ino + 7],
158        ]);
159
160        let d_type = self.buf[pos + offsetof_d_type];
161
162        // Check that our types correspond to the `linux_dirent64` types.
163        let _ = linux_dirent64 {
164            d_ino,
165            d_off: 0,
166            d_type,
167            d_reclen,
168            d_name: Default::default(),
169        };
170
171        Some(Ok(DirEntry {
172            d_ino,
173            d_type,
174            name,
175        }))
176    }
177
178    #[must_use]
179    fn read_more(&mut self) -> Option<io::Result<()>> {
180        // The first few times we're called, we allocate a relatively small
181        // buffer, because many directories are small. If we're called more,
182        // use progressively larger allocations, up to a fixed maximum.
183        //
184        // The specific sizes and policy here have not been tuned in detail yet
185        // and may need to be adjusted. In doing so, we should be careful to
186        // avoid unbounded buffer growth. This buffer only exists to share the
187        // cost of a `getdents` call over many entries, so if it gets too big,
188        // cache and heap usage will outweigh the benefit. And ultimately,
189        // directories can contain more entries than we can allocate contiguous
190        // memory for, so we'll always need to cap the size at some point.
191        if self.buf.len() < 1024 * size_of::<linux_dirent64>() {
192            self.buf.reserve(32 * size_of::<linux_dirent64>());
193        }
194        self.buf.resize(self.buf.capacity(), 0);
195        let nread = match io::retry_on_intr(|| {
196            crate::backend::fs::syscalls::getdents(self.fd.as_fd(), &mut self.buf)
197        }) {
198            Ok(nread) => nread,
199            Err(io::Errno::NOENT) => {
200                self.any_errors = true;
201                return None;
202            }
203            Err(err) => {
204                self.any_errors = true;
205                return Some(Err(err));
206            }
207        };
208        self.buf.resize(nread, 0);
209        self.pos = 0;
210        if nread == 0 {
211            None
212        } else {
213            Some(Ok(()))
214        }
215    }
216
217    /// `fstat(self)`
218    #[inline]
219    pub fn stat(&self) -> io::Result<Stat> {
220        fstat(&self.fd)
221    }
222
223    /// `fstatfs(self)`
224    #[inline]
225    pub fn statfs(&self) -> io::Result<StatFs> {
226        fstatfs(&self.fd)
227    }
228
229    /// `fstatvfs(self)`
230    #[inline]
231    pub fn statvfs(&self) -> io::Result<StatVfs> {
232        fstatvfs(&self.fd)
233    }
234
235    /// `fchdir(self)`
236    #[cfg(feature = "process")]
237    #[cfg_attr(docsrs, doc(cfg(feature = "process")))]
238    #[inline]
239    pub fn chdir(&self) -> io::Result<()> {
240        fchdir(&self.fd)
241    }
242}
243
244impl Iterator for Dir {
245    type Item = io::Result<DirEntry>;
246
247    #[inline]
248    fn next(&mut self) -> Option<Self::Item> {
249        Self::read(self)
250    }
251}
252
253impl fmt::Debug for Dir {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        f.debug_struct("Dir").field("fd", &self.fd).finish()
256    }
257}
258
259/// `struct dirent`
260#[derive(Debug)]
261pub struct DirEntry {
262    d_ino: u64,
263    d_type: u8,
264    name: CString,
265}
266
267impl DirEntry {
268    /// Returns the file name of this directory entry.
269    #[inline]
270    pub fn file_name(&self) -> &CStr {
271        &self.name
272    }
273
274    /// Returns the type of this directory entry.
275    #[inline]
276    pub fn file_type(&self) -> FileType {
277        FileType::from_dirent_d_type(self.d_type)
278    }
279
280    /// Return the inode number of this directory entry.
281    #[inline]
282    pub fn ino(&self) -> u64 {
283        self.d_ino
284    }
285}
286
287#[test]
288fn dir_iterator_handles_io_errors() {
289    // create a dir, keep the FD, then delete the dir
290    let tmp = tempfile::tempdir().unwrap();
291    let fd = crate::fs::openat(
292        crate::fs::CWD,
293        tmp.path(),
294        crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
295        crate::fs::Mode::empty(),
296    )
297    .unwrap();
298
299    let file_fd = crate::fs::openat(
300        &fd,
301        tmp.path().join("test.txt"),
302        crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
303        crate::fs::Mode::RWXU,
304    )
305    .unwrap();
306
307    let mut dir = Dir::read_from(&fd).unwrap();
308
309    // Reach inside the `Dir` and replace its directory with a file, which
310    // will cause the subsequent `getdents64` to fail.
311    crate::io::dup2(&file_fd, &mut dir.fd).unwrap();
312
313    assert!(matches!(dir.next(), Some(Err(_))));
314    assert!(dir.next().is_none());
315}