]>
Commit | Line | Data |
---|---|---|
fe692bf9 FG |
1 | use crate::fd::{AsFd, BorrowedFd, OwnedFd}; |
2 | use crate::ffi::{CStr, CString}; | |
3 | use crate::fs::{ | |
4 | fcntl_getfl, fstat, fstatfs, fstatvfs, openat, FileType, Mode, OFlags, Stat, StatFs, StatVfs, | |
5 | }; | |
6 | use crate::io; | |
7 | use crate::process::fchdir; | |
8 | use crate::utils::as_ptr; | |
9 | use alloc::borrow::ToOwned; | |
10 | use alloc::vec::Vec; | |
11 | use core::fmt; | |
12 | use core::mem::size_of; | |
13 | use linux_raw_sys::general::{linux_dirent64, SEEK_SET}; | |
14 | ||
15 | /// `DIR*` | |
16 | pub struct Dir { | |
17 | /// The `OwnedFd` that we read directory entries from. | |
18 | fd: OwnedFd, | |
19 | ||
20 | buf: Vec<u8>, | |
21 | pos: usize, | |
22 | next: Option<u64>, | |
23 | } | |
24 | ||
25 | impl Dir { | |
26 | /// Construct a `Dir` that reads entries from the given directory | |
27 | /// file descriptor. | |
28 | #[inline] | |
29 | pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> { | |
30 | Self::_read_from(fd.as_fd()) | |
31 | } | |
32 | ||
33 | #[inline] | |
34 | fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> { | |
35 | let flags = fcntl_getfl(fd)?; | |
36 | let fd_for_dir = openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty())?; | |
37 | ||
38 | Ok(Self { | |
39 | fd: fd_for_dir, | |
40 | buf: Vec::new(), | |
41 | pos: 0, | |
42 | next: None, | |
43 | }) | |
44 | } | |
45 | ||
46 | /// `rewinddir(self)` | |
47 | #[inline] | |
48 | pub fn rewind(&mut self) { | |
49 | self.pos = self.buf.len(); | |
50 | self.next = Some(0); | |
51 | } | |
52 | ||
53 | /// `readdir(self)`, where `None` means the end of the directory. | |
54 | pub fn read(&mut self) -> Option<io::Result<DirEntry>> { | |
55 | if let Some(next) = self.next.take() { | |
56 | match crate::backend::fs::syscalls::_seek(self.fd.as_fd(), next as i64, SEEK_SET) { | |
57 | Ok(_) => (), | |
58 | Err(err) => return Some(Err(err)), | |
59 | } | |
60 | } | |
61 | ||
62 | // Compute linux_dirent64 field offsets. | |
63 | let z = linux_dirent64 { | |
64 | d_ino: 0_u64, | |
65 | d_off: 0_i64, | |
66 | d_type: 0_u8, | |
67 | d_reclen: 0_u16, | |
68 | d_name: Default::default(), | |
69 | }; | |
70 | let base = as_ptr(&z) as usize; | |
71 | let offsetof_d_reclen = (as_ptr(&z.d_reclen) as usize) - base; | |
72 | let offsetof_d_name = (as_ptr(&z.d_name) as usize) - base; | |
73 | let offsetof_d_ino = (as_ptr(&z.d_ino) as usize) - base; | |
74 | let offsetof_d_type = (as_ptr(&z.d_type) as usize) - base; | |
75 | ||
76 | // Test if we need more entries, and if so, read more. | |
77 | if self.buf.len() - self.pos < size_of::<linux_dirent64>() { | |
78 | match self.read_more()? { | |
79 | Ok(()) => (), | |
80 | Err(e) => return Some(Err(e)), | |
81 | } | |
82 | } | |
83 | ||
84 | // We successfully read an entry. Extract the fields. | |
85 | let pos = self.pos; | |
86 | ||
87 | // Do an unaligned u16 load. | |
88 | let d_reclen = u16::from_ne_bytes([ | |
89 | self.buf[pos + offsetof_d_reclen], | |
90 | self.buf[pos + offsetof_d_reclen + 1], | |
91 | ]); | |
92 | assert!(self.buf.len() - pos >= d_reclen as usize); | |
93 | self.pos += d_reclen as usize; | |
94 | ||
95 | // Read the NUL-terminated name from the `d_name` field. Without | |
96 | // `unsafe`, we need to scan for the NUL twice: once to obtain a size | |
97 | // for the slice, and then once within `CStr::from_bytes_with_nul`. | |
98 | let name_start = pos + offsetof_d_name; | |
99 | let name_len = self.buf[name_start..] | |
100 | .iter() | |
101 | .position(|x| *x == b'\0') | |
102 | .unwrap(); | |
103 | let name = | |
104 | CStr::from_bytes_with_nul(&self.buf[name_start..name_start + name_len + 1]).unwrap(); | |
105 | let name = name.to_owned(); | |
106 | assert!(name.as_bytes().len() <= self.buf.len() - name_start); | |
107 | ||
108 | // Do an unaligned u64 load. | |
109 | let d_ino = u64::from_ne_bytes([ | |
110 | self.buf[pos + offsetof_d_ino], | |
111 | self.buf[pos + offsetof_d_ino + 1], | |
112 | self.buf[pos + offsetof_d_ino + 2], | |
113 | self.buf[pos + offsetof_d_ino + 3], | |
114 | self.buf[pos + offsetof_d_ino + 4], | |
115 | self.buf[pos + offsetof_d_ino + 5], | |
116 | self.buf[pos + offsetof_d_ino + 6], | |
117 | self.buf[pos + offsetof_d_ino + 7], | |
118 | ]); | |
119 | ||
120 | let d_type = self.buf[pos + offsetof_d_type]; | |
121 | ||
122 | // Check that our types correspond to the `linux_dirent64` types. | |
123 | let _ = linux_dirent64 { | |
124 | d_ino, | |
125 | d_off: 0, | |
126 | d_type, | |
127 | d_reclen, | |
128 | d_name: Default::default(), | |
129 | }; | |
130 | ||
131 | Some(Ok(DirEntry { | |
132 | d_ino, | |
133 | d_type, | |
134 | name, | |
135 | })) | |
136 | } | |
137 | ||
138 | fn read_more(&mut self) -> Option<io::Result<()>> { | |
139 | let og_len = self.buf.len(); | |
140 | // Capacity increment currently chosen by wild guess. | |
141 | self.buf | |
142 | .resize(self.buf.capacity() + 32 * size_of::<linux_dirent64>(), 0); | |
143 | let nread = match crate::backend::fs::syscalls::getdents(self.fd.as_fd(), &mut self.buf) { | |
144 | Ok(nread) => nread, | |
145 | Err(err) => { | |
146 | self.buf.resize(og_len, 0); | |
147 | return Some(Err(err)); | |
148 | } | |
149 | }; | |
150 | self.buf.resize(nread, 0); | |
151 | self.pos = 0; | |
152 | if nread == 0 { | |
153 | None | |
154 | } else { | |
155 | Some(Ok(())) | |
156 | } | |
157 | } | |
158 | ||
159 | /// `fstat(self)` | |
160 | #[inline] | |
161 | pub fn stat(&self) -> io::Result<Stat> { | |
162 | fstat(&self.fd) | |
163 | } | |
164 | ||
165 | /// `fstatfs(self)` | |
166 | #[inline] | |
167 | pub fn statfs(&self) -> io::Result<StatFs> { | |
168 | fstatfs(&self.fd) | |
169 | } | |
170 | ||
171 | /// `fstatvfs(self)` | |
172 | #[inline] | |
173 | pub fn statvfs(&self) -> io::Result<StatVfs> { | |
174 | fstatvfs(&self.fd) | |
175 | } | |
176 | ||
177 | /// `fchdir(self)` | |
178 | #[inline] | |
179 | pub fn chdir(&self) -> io::Result<()> { | |
180 | fchdir(&self.fd) | |
181 | } | |
182 | } | |
183 | ||
184 | impl Iterator for Dir { | |
185 | type Item = io::Result<DirEntry>; | |
186 | ||
187 | #[inline] | |
188 | fn next(&mut self) -> Option<Self::Item> { | |
189 | Self::read(self) | |
190 | } | |
191 | } | |
192 | ||
193 | impl fmt::Debug for Dir { | |
194 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
195 | f.debug_struct("Dir").field("fd", &self.fd).finish() | |
196 | } | |
197 | } | |
198 | ||
199 | /// `struct dirent` | |
200 | #[derive(Debug)] | |
201 | pub struct DirEntry { | |
202 | d_ino: u64, | |
203 | d_type: u8, | |
204 | name: CString, | |
205 | } | |
206 | ||
207 | impl DirEntry { | |
208 | /// Returns the file name of this directory entry. | |
209 | #[inline] | |
210 | pub fn file_name(&self) -> &CStr { | |
211 | &self.name | |
212 | } | |
213 | ||
214 | /// Returns the type of this directory entry. | |
215 | #[inline] | |
216 | pub fn file_type(&self) -> FileType { | |
217 | FileType::from_dirent_d_type(self.d_type) | |
218 | } | |
219 | ||
220 | /// Return the inode number of this directory entry. | |
221 | #[inline] | |
222 | pub fn ino(&self) -> u64 { | |
223 | self.d_ino | |
224 | } | |
225 | } |