• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! `RawDir` and `RawDirEntry`.
2 
3 use core::fmt;
4 use core::mem::{align_of, MaybeUninit};
5 use linux_raw_sys::general::linux_dirent64;
6 
7 use crate::backend::fs::syscalls::getdents_uninit;
8 use crate::fd::AsFd;
9 use crate::ffi::CStr;
10 use crate::fs::FileType;
11 use crate::io;
12 
13 /// A directory iterator implemented with getdents.
14 ///
15 /// Note: This implementation does not handle growing the buffer. If this functionality is
16 /// necessary, you'll need to drop the current iterator, resize the buffer, and then
17 /// re-create the iterator. The iterator is guaranteed to continue where it left off provided
18 /// the file descriptor isn't changed. See the example in [`RawDir::new`].
19 pub struct RawDir<'buf, Fd: AsFd> {
20     fd: Fd,
21     buf: &'buf mut [MaybeUninit<u8>],
22     initialized: usize,
23     offset: usize,
24 }
25 
26 impl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
27     /// Create a new iterator from the given file descriptor and buffer.
28     ///
29     /// Note: the buffer size may be trimmed to accommodate alignment requirements.
30     ///
31     /// # Examples
32     ///
33     /// ## Simple but non-portable
34     ///
35     /// These examples are non-portable, because file systems may not have a maximum file name
36     /// length. If you can make assumptions that bound this length, then these examples may suffice.
37     ///
38     /// Using the heap:
39     ///
40     /// ```notrust
41     /// # // The `notrust` above can be removed when we can depend on Rust 1.60.
42     /// # use std::mem::MaybeUninit;
43     /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir};
44     ///
45     /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap();
46     ///
47     /// let mut buf = Vec::with_capacity(8192);
48     /// let mut iter = RawDir::new(fd, buf.spare_capacity_mut());
49     /// while let Some(entry) = iter.next() {
50     ///     let entry = entry.unwrap();
51     ///     dbg!(&entry);
52     /// }
53     /// ```
54     ///
55     /// Using the stack:
56     ///
57     /// ```
58     /// # use std::mem::MaybeUninit;
59     /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir};
60     ///
61     /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap();
62     ///
63     /// let mut buf = [MaybeUninit::uninit(); 2048];
64     /// let mut iter = RawDir::new(fd, &mut buf);
65     /// while let Some(entry) = iter.next() {
66     ///     let entry = entry.unwrap();
67     ///     dbg!(&entry);
68     /// }
69     /// ```
70     ///
71     /// ## Portable
72     ///
73     /// Heap allocated growing buffer for supporting directory entries with arbitrarily
74     /// large file names:
75     ///
76     /// ```notrust
77     /// # // The `notrust` above can be removed when we can depend on Rust 1.60.
78     /// # use std::mem::MaybeUninit;
79     /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir};
80     /// # use rustix::io::Errno;
81     ///
82     /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap();
83     ///
84     /// let mut buf = Vec::with_capacity(8192);
85     /// 'read: loop {
86     ///     'resize: {
87     ///         let mut iter = RawDir::new(&fd, buf.spare_capacity_mut());
88     ///         while let Some(entry) = iter.next() {
89     ///             let entry = match entry {
90     ///                 Err(Errno::INVAL) => break 'resize,
91     ///                 r => r.unwrap(),
92     ///             };
93     ///             dbg!(&entry);
94     ///         }
95     ///         break 'read;
96     ///     }
97     ///
98     ///     let new_capacity = buf.capacity() * 2;
99     ///     buf.reserve(new_capacity);
100     /// }
101     /// ```
new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self102     pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self {
103         Self {
104             fd,
105             buf: {
106                 let offset = buf.as_ptr().align_offset(align_of::<linux_dirent64>());
107                 if offset < buf.len() {
108                     &mut buf[offset..]
109                 } else {
110                     &mut []
111                 }
112             },
113             initialized: 0,
114             offset: 0,
115         }
116     }
117 }
118 
119 /// A raw directory entry, similar to `std::fs::DirEntry`.
120 ///
121 /// Note that unlike the std version, this may represent the `.` or `..` entries.
122 pub struct RawDirEntry<'a> {
123     file_name: &'a CStr,
124     file_type: u8,
125     inode_number: u64,
126     next_entry_cookie: i64,
127 }
128 
129 impl<'a> fmt::Debug for RawDirEntry<'a> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result130     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131         let mut f = f.debug_struct("RawDirEntry");
132         f.field("file_name", &self.file_name());
133         f.field("file_type", &self.file_type());
134         f.field("ino", &self.ino());
135         f.field("next_entry_cookie", &self.next_entry_cookie());
136         f.finish()
137     }
138 }
139 
140 impl<'a> RawDirEntry<'a> {
141     /// Returns the file name of this directory entry.
142     #[inline]
file_name(&self) -> &CStr143     pub fn file_name(&self) -> &CStr {
144         self.file_name
145     }
146 
147     /// Returns the type of this directory entry.
148     #[inline]
file_type(&self) -> FileType149     pub fn file_type(&self) -> FileType {
150         FileType::from_dirent_d_type(self.file_type)
151     }
152 
153     /// Returns the inode number of this directory entry.
154     #[inline]
155     #[doc(alias = "inode_number")]
ino(&self) -> u64156     pub fn ino(&self) -> u64 {
157         self.inode_number
158     }
159 
160     /// Returns the seek cookie to the next directory entry.
161     #[inline]
162     #[doc(alias = "off")]
next_entry_cookie(&self) -> u64163     pub fn next_entry_cookie(&self) -> u64 {
164         self.next_entry_cookie as u64
165     }
166 }
167 
168 impl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
169     /// Identical to [Iterator::next] except that [Iterator::Item] borrows from self.
170     ///
171     /// Note: this interface will be broken to implement a stdlib iterator API with
172     /// GAT support once one becomes available.
173     #[allow(unsafe_code)]
next(&mut self) -> Option<io::Result<RawDirEntry>>174     pub fn next(&mut self) -> Option<io::Result<RawDirEntry>> {
175         if self.is_buffer_empty() {
176             match getdents_uninit(self.fd.as_fd(), self.buf) {
177                 Ok(bytes_read) if bytes_read == 0 => return None,
178                 Ok(bytes_read) => {
179                     self.initialized = bytes_read;
180                     self.offset = 0;
181                 }
182                 Err(e) => return Some(Err(e)),
183             }
184         }
185 
186         let dirent_ptr = self.buf[self.offset..].as_ptr();
187         // SAFETY:
188         // - This data is initialized by the check above.
189         //   - Assumption: the kernel will not give us partial structs.
190         // - Assumption: the kernel uses proper alignment between structs.
191         // - The starting pointer is aligned (performed in RawDir::new)
192         let dirent = unsafe { &*dirent_ptr.cast::<linux_dirent64>() };
193 
194         self.offset += usize::from(dirent.d_reclen);
195 
196         Some(Ok(RawDirEntry {
197             file_type: dirent.d_type,
198             inode_number: dirent.d_ino,
199             next_entry_cookie: dirent.d_off,
200             // SAFETY: the kernel guarantees a NUL terminated string.
201             file_name: unsafe { CStr::from_ptr(dirent.d_name.as_ptr().cast()) },
202         }))
203     }
204 
205     /// Returns true if the internal buffer is empty and will be refilled when calling
206     /// [`next`][Self::next].
is_buffer_empty(&self) -> bool207     pub fn is_buffer_empty(&self) -> bool {
208         self.offset >= self.initialized
209     }
210 }
211