]>
Commit | Line | Data |
---|---|---|
fe692bf9 FG |
1 | use super::super::c; |
2 | use super::super::conv::owned_fd; | |
fe692bf9 FG |
3 | #[cfg(not(any(solarish, target_os = "haiku")))] |
4 | use super::types::FileType; | |
5 | use crate::fd::{AsFd, BorrowedFd}; | |
781aab86 FG |
6 | use crate::ffi::CStr; |
7 | #[cfg(target_os = "wasi")] | |
8 | use crate::ffi::CString; | |
fe692bf9 FG |
9 | use crate::fs::{fcntl_getfl, fstat, openat, Mode, OFlags, Stat}; |
10 | #[cfg(not(any( | |
11 | solarish, | |
12 | target_os = "haiku", | |
13 | target_os = "netbsd", | |
14 | target_os = "redox", | |
15 | target_os = "wasi", | |
16 | )))] | |
17 | use crate::fs::{fstatfs, StatFs}; | |
18 | #[cfg(not(any(solarish, target_os = "haiku", target_os = "redox", target_os = "wasi")))] | |
19 | use crate::fs::{fstatvfs, StatVfs}; | |
20 | use crate::io; | |
21 | #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] | |
22 | use crate::process::fchdir; | |
781aab86 | 23 | #[cfg(target_os = "wasi")] |
fe692bf9 | 24 | use alloc::borrow::ToOwned; |
781aab86 FG |
25 | #[cfg(not(any(linux_like, target_os = "openbsd")))] |
26 | use c::dirent as libc_dirent; | |
fe692bf9 FG |
27 | #[cfg(not(linux_like))] |
28 | use c::readdir as libc_readdir; | |
29 | #[cfg(linux_like)] | |
781aab86 | 30 | use c::{dirent64 as libc_dirent, readdir64 as libc_readdir}; |
fe692bf9 | 31 | use core::fmt; |
781aab86 | 32 | use core::mem::zeroed; |
fe692bf9 FG |
33 | use core::ptr::NonNull; |
34 | use libc_errno::{errno, set_errno, Errno}; | |
35 | ||
36 | /// `DIR*` | |
37 | #[repr(transparent)] | |
38 | pub struct Dir(NonNull<c::DIR>); | |
39 | ||
40 | impl Dir { | |
41 | /// Construct a `Dir` that reads entries from the given directory | |
42 | /// file descriptor. | |
43 | #[inline] | |
44 | pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> { | |
45 | Self::_read_from(fd.as_fd()) | |
46 | } | |
47 | ||
48 | #[inline] | |
49 | fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> { | |
50 | // Given an arbitrary `OwnedFd`, it's impossible to know whether the | |
51 | // user holds a `dup`'d copy which could continue to modify the | |
52 | // file description state, which would cause Undefined Behavior after | |
53 | // our call to `fdopendir`. To prevent this, we obtain an independent | |
54 | // `OwnedFd`. | |
55 | let flags = fcntl_getfl(fd)?; | |
56 | let fd_for_dir = openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty())?; | |
57 | ||
58 | let raw = owned_fd(fd_for_dir); | |
59 | unsafe { | |
60 | let libc_dir = c::fdopendir(raw); | |
61 | ||
62 | if let Some(libc_dir) = NonNull::new(libc_dir) { | |
63 | Ok(Self(libc_dir)) | |
64 | } else { | |
65 | let err = io::Errno::last_os_error(); | |
66 | let _ = c::close(raw); | |
67 | Err(err) | |
68 | } | |
69 | } | |
70 | } | |
71 | ||
72 | /// `rewinddir(self)` | |
73 | #[inline] | |
74 | pub fn rewind(&mut self) { | |
75 | unsafe { c::rewinddir(self.0.as_ptr()) } | |
76 | } | |
77 | ||
78 | /// `readdir(self)`, where `None` means the end of the directory. | |
79 | pub fn read(&mut self) -> Option<io::Result<DirEntry>> { | |
80 | set_errno(Errno(0)); | |
81 | let dirent_ptr = unsafe { libc_readdir(self.0.as_ptr()) }; | |
82 | if dirent_ptr.is_null() { | |
83 | let curr_errno = errno().0; | |
84 | if curr_errno == 0 { | |
85 | // We successfully reached the end of the stream. | |
86 | None | |
87 | } else { | |
88 | // `errno` is unknown or non-zero, so an error occurred. | |
89 | Some(Err(io::Errno(curr_errno))) | |
90 | } | |
91 | } else { | |
92 | // We successfully read an entry. | |
93 | unsafe { | |
fe692bf9 FG |
94 | // We have our own copy of OpenBSD's dirent; check that the |
95 | // layout minimally matches libc's. | |
96 | #[cfg(target_os = "openbsd")] | |
781aab86 | 97 | check_dirent_layout(&*dirent_ptr); |
fe692bf9 FG |
98 | |
99 | let result = DirEntry { | |
781aab86 | 100 | dirent: read_dirent(&*dirent_ptr.cast()), |
fe692bf9 | 101 | |
781aab86 FG |
102 | #[cfg(target_os = "wasi")] |
103 | name: CStr::from_ptr((*dirent_ptr).d_name.as_ptr()).to_owned(), | |
fe692bf9 FG |
104 | }; |
105 | ||
106 | Some(Ok(result)) | |
107 | } | |
108 | } | |
109 | } | |
110 | ||
111 | /// `fstat(self)` | |
112 | #[inline] | |
113 | pub fn stat(&self) -> io::Result<Stat> { | |
114 | fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) }) | |
115 | } | |
116 | ||
117 | /// `fstatfs(self)` | |
118 | #[cfg(not(any( | |
119 | solarish, | |
120 | target_os = "haiku", | |
121 | target_os = "netbsd", | |
122 | target_os = "redox", | |
123 | target_os = "wasi", | |
124 | )))] | |
125 | #[inline] | |
126 | pub fn statfs(&self) -> io::Result<StatFs> { | |
127 | fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) }) | |
128 | } | |
129 | ||
130 | /// `fstatvfs(self)` | |
131 | #[cfg(not(any(solarish, target_os = "haiku", target_os = "redox", target_os = "wasi")))] | |
132 | #[inline] | |
133 | pub fn statvfs(&self) -> io::Result<StatVfs> { | |
134 | fstatvfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) }) | |
135 | } | |
136 | ||
137 | /// `fchdir(self)` | |
138 | #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] | |
139 | #[inline] | |
140 | pub fn chdir(&self) -> io::Result<()> { | |
141 | fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.0.as_ptr())) }) | |
142 | } | |
143 | } | |
144 | ||
781aab86 FG |
145 | // A `dirent` pointer returned from `readdir` may not point to a full `dirent` |
146 | // struct, as the name is NUL-terminated and memory may not be allocated for | |
147 | // the full extent of the struct. Copy the fields one at a time. | |
148 | unsafe fn read_dirent(input: &libc_dirent) -> libc_dirent { | |
149 | #[cfg(not(any(solarish, target_os = "aix", target_os = "haiku")))] | |
150 | let d_type = input.d_type; | |
151 | ||
152 | #[cfg(not(any( | |
153 | apple, | |
154 | freebsdlike, | |
155 | target_os = "aix", | |
156 | target_os = "haiku", | |
157 | target_os = "netbsd", | |
158 | target_os = "wasi", | |
159 | )))] | |
160 | let d_off = input.d_off; | |
161 | ||
162 | #[cfg(target_os = "aix")] | |
163 | let d_offset = input.d_offset; | |
164 | ||
165 | #[cfg(not(any(freebsdlike, netbsdlike)))] | |
166 | let d_ino = input.d_ino; | |
167 | ||
168 | #[cfg(any(freebsdlike, netbsdlike))] | |
169 | let d_fileno = input.d_fileno; | |
170 | ||
171 | #[cfg(not(any(target_os = "dragonfly", target_os = "wasi")))] | |
172 | let d_reclen = input.d_reclen; | |
173 | ||
174 | #[cfg(any(bsd, target_os = "aix"))] | |
175 | let d_namlen = input.d_namlen; | |
176 | ||
177 | #[cfg(apple)] | |
178 | let d_seekoff = input.d_seekoff; | |
179 | ||
180 | #[cfg(target_os = "haiku")] | |
181 | let d_dev = input.d_dev; | |
182 | #[cfg(target_os = "haiku")] | |
183 | let d_pdev = input.d_pdev; | |
184 | #[cfg(target_os = "haiku")] | |
185 | let d_pino = input.d_pino; | |
186 | ||
187 | // Construct the input. Rust will give us an error if any OS has a input | |
188 | // with a field that we missed here. And we can avoid blindly copying the | |
189 | // whole `d_name` field, which may not be entirely allocated. | |
190 | #[cfg_attr(target_os = "wasi", allow(unused_mut))] | |
191 | #[cfg(not(freebsdlike))] | |
192 | let mut dirent = libc_dirent { | |
193 | #[cfg(not(any(solarish, target_os = "aix", target_os = "haiku")))] | |
194 | d_type, | |
195 | #[cfg(not(any( | |
196 | apple, | |
197 | target_os = "aix", | |
198 | target_os = "freebsd", // Until FreeBSD 12 | |
199 | target_os = "haiku", | |
200 | target_os = "netbsd", | |
201 | target_os = "wasi", | |
202 | )))] | |
203 | d_off, | |
204 | #[cfg(target_os = "aix")] | |
205 | d_offset, | |
206 | #[cfg(not(any(netbsdlike, target_os = "freebsd")))] | |
207 | d_ino, | |
208 | #[cfg(any(netbsdlike, target_os = "freebsd"))] | |
209 | d_fileno, | |
210 | #[cfg(not(target_os = "wasi"))] | |
211 | d_reclen, | |
212 | #[cfg(any(apple, netbsdlike, target_os = "aix", target_os = "freebsd"))] | |
213 | d_namlen, | |
214 | #[cfg(apple)] | |
215 | d_seekoff, | |
216 | // The `d_name` field is NUL-terminated, and we need to be careful not | |
217 | // to read bytes past the NUL, even though they're within the nominal | |
218 | // extent of the `struct dirent`, because they may not be allocated. So | |
219 | // don't read it from `dirent_ptr`. | |
220 | // | |
221 | // In theory this could use `MaybeUninit::uninit().assume_init()`, but | |
222 | // that [invokes undefined behavior]. | |
223 | // | |
224 | // [invokes undefined behavior]: https://doc.rust-lang.org/stable/core/mem/union.MaybeUninit.html#initialization-invariant | |
225 | d_name: zeroed(), | |
226 | #[cfg(target_os = "openbsd")] | |
227 | __d_padding: zeroed(), | |
228 | #[cfg(target_os = "haiku")] | |
229 | d_dev, | |
230 | #[cfg(target_os = "haiku")] | |
231 | d_pdev, | |
232 | #[cfg(target_os = "haiku")] | |
233 | d_pino, | |
234 | }; | |
235 | /* | |
236 | pub d_ino: ino_t, | |
237 | pub d_pino: i64, | |
238 | pub d_reclen: ::c_ushort, | |
239 | pub d_name: [::c_char; 1024], // Max length is _POSIX_PATH_MAX | |
240 | */ | |
241 | ||
242 | // On dragonfly and FreeBSD 12, `dirent` has some non-public padding fields | |
243 | // so we can't directly initialize it. | |
244 | #[cfg(freebsdlike)] | |
245 | let mut dirent = { | |
246 | let mut dirent: libc_dirent = zeroed(); | |
247 | dirent.d_fileno = d_fileno; | |
248 | dirent.d_namlen = d_namlen; | |
249 | dirent.d_type = d_type; | |
250 | #[cfg(target_os = "freebsd")] | |
251 | { | |
252 | dirent.d_reclen = d_reclen; | |
253 | } | |
254 | dirent | |
255 | }; | |
256 | ||
257 | // Copy from d_name, reading up to and including the first NUL. | |
258 | #[cfg(not(target_os = "wasi"))] | |
259 | { | |
260 | let name_len = CStr::from_ptr(input.d_name.as_ptr()) | |
261 | .to_bytes_with_nul() | |
262 | .len(); | |
263 | dirent.d_name[..name_len].copy_from_slice(&input.d_name[..name_len]); | |
264 | } | |
265 | ||
266 | dirent | |
267 | } | |
268 | ||
fe692bf9 FG |
269 | /// `Dir` implements `Send` but not `Sync`, because we use `readdir` which is |
270 | /// not guaranteed to be thread-safe. Users can wrap this in a `Mutex` if they | |
271 | /// need `Sync`, which is effectively what'd need to do to implement `Sync` | |
272 | /// ourselves. | |
273 | unsafe impl Send for Dir {} | |
274 | ||
275 | impl Drop for Dir { | |
276 | #[inline] | |
277 | fn drop(&mut self) { | |
278 | unsafe { c::closedir(self.0.as_ptr()) }; | |
279 | } | |
280 | } | |
281 | ||
282 | impl Iterator for Dir { | |
283 | type Item = io::Result<DirEntry>; | |
284 | ||
285 | #[inline] | |
286 | fn next(&mut self) -> Option<Self::Item> { | |
287 | Self::read(self) | |
288 | } | |
289 | } | |
290 | ||
291 | impl fmt::Debug for Dir { | |
292 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
293 | f.debug_struct("Dir") | |
294 | .field("fd", unsafe { &c::dirfd(self.0.as_ptr()) }) | |
295 | .finish() | |
296 | } | |
297 | } | |
298 | ||
299 | /// `struct dirent` | |
300 | #[derive(Debug)] | |
301 | pub struct DirEntry { | |
781aab86 | 302 | dirent: libc_dirent, |
fe692bf9 | 303 | |
781aab86 | 304 | #[cfg(target_os = "wasi")] |
fe692bf9 FG |
305 | name: CString, |
306 | } | |
307 | ||
308 | impl DirEntry { | |
309 | /// Returns the file name of this directory entry. | |
310 | #[inline] | |
311 | pub fn file_name(&self) -> &CStr { | |
781aab86 FG |
312 | #[cfg(not(target_os = "wasi"))] |
313 | unsafe { | |
314 | CStr::from_ptr(self.dirent.d_name.as_ptr()) | |
315 | } | |
316 | ||
317 | #[cfg(target_os = "wasi")] | |
fe692bf9 FG |
318 | &self.name |
319 | } | |
320 | ||
321 | /// Returns the type of this directory entry. | |
322 | #[cfg(not(any(solarish, target_os = "aix", target_os = "haiku")))] | |
323 | #[inline] | |
324 | pub fn file_type(&self) -> FileType { | |
781aab86 | 325 | FileType::from_dirent_d_type(self.dirent.d_type) |
fe692bf9 FG |
326 | } |
327 | ||
328 | /// Return the inode number of this directory entry. | |
329 | #[cfg(not(any(freebsdlike, netbsdlike)))] | |
330 | #[inline] | |
331 | pub fn ino(&self) -> u64 { | |
781aab86 | 332 | self.dirent.d_ino as u64 |
fe692bf9 FG |
333 | } |
334 | ||
335 | /// Return the inode number of this directory entry. | |
336 | #[cfg(any(freebsdlike, netbsdlike))] | |
337 | #[inline] | |
338 | pub fn ino(&self) -> u64 { | |
339 | #[allow(clippy::useless_conversion)] | |
781aab86 | 340 | self.dirent.d_fileno.into() |
fe692bf9 FG |
341 | } |
342 | } | |
343 | ||
344 | /// libc's OpenBSD `dirent` has a private field so we can't construct it | |
345 | /// directly, so we declare it ourselves to make all fields accessible. | |
346 | #[cfg(target_os = "openbsd")] | |
347 | #[repr(C)] | |
348 | #[derive(Debug)] | |
349 | struct libc_dirent { | |
350 | d_fileno: c::ino_t, | |
351 | d_off: c::off_t, | |
352 | d_reclen: u16, | |
353 | d_type: u8, | |
354 | d_namlen: u8, | |
355 | __d_padding: [u8; 4], | |
356 | d_name: [c::c_char; 256], | |
357 | } | |
358 | ||
359 | /// We have our own copy of OpenBSD's dirent; check that the layout | |
360 | /// minimally matches libc's. | |
361 | #[cfg(target_os = "openbsd")] | |
362 | fn check_dirent_layout(dirent: &c::dirent) { | |
363 | use crate::utils::as_ptr; | |
364 | use core::mem::{align_of, size_of}; | |
365 | ||
366 | // Check that the basic layouts match. | |
367 | assert_eq!(size_of::<libc_dirent>(), size_of::<c::dirent>()); | |
368 | assert_eq!(align_of::<libc_dirent>(), align_of::<c::dirent>()); | |
369 | ||
370 | // Check that the field offsets match. | |
371 | assert_eq!( | |
372 | { | |
373 | let z = libc_dirent { | |
374 | d_fileno: 0_u64, | |
375 | d_off: 0_i64, | |
376 | d_reclen: 0_u16, | |
377 | d_type: 0_u8, | |
378 | d_namlen: 0_u8, | |
379 | __d_padding: [0_u8; 4], | |
380 | d_name: [0 as c::c_char; 256], | |
381 | }; | |
382 | let base = as_ptr(&z) as usize; | |
383 | ( | |
384 | (as_ptr(&z.d_fileno) as usize) - base, | |
385 | (as_ptr(&z.d_off) as usize) - base, | |
386 | (as_ptr(&z.d_reclen) as usize) - base, | |
387 | (as_ptr(&z.d_type) as usize) - base, | |
388 | (as_ptr(&z.d_namlen) as usize) - base, | |
389 | (as_ptr(&z.d_name) as usize) - base, | |
390 | ) | |
391 | }, | |
392 | { | |
393 | let z = dirent; | |
394 | let base = as_ptr(z) as usize; | |
395 | ( | |
396 | (as_ptr(&z.d_fileno) as usize) - base, | |
397 | (as_ptr(&z.d_off) as usize) - base, | |
398 | (as_ptr(&z.d_reclen) as usize) - base, | |
399 | (as_ptr(&z.d_type) as usize) - base, | |
400 | (as_ptr(&z.d_namlen) as usize) - base, | |
401 | (as_ptr(&z.d_name) as usize) - base, | |
402 | ) | |
403 | } | |
404 | ); | |
405 | } |