]> git.proxmox.com Git - rustc.git/blame - vendor/rustix-0.37.6/src/backend/libc/fs/dir.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / vendor / rustix-0.37.6 / src / backend / libc / fs / dir.rs
CommitLineData
fe692bf9
FG
1use super::super::c;
2use super::super::conv::owned_fd;
fe692bf9
FG
3#[cfg(not(any(solarish, target_os = "haiku")))]
4use super::types::FileType;
5use crate::fd::{AsFd, BorrowedFd};
781aab86
FG
6use crate::ffi::CStr;
7#[cfg(target_os = "wasi")]
8use crate::ffi::CString;
fe692bf9
FG
9use 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)))]
17use crate::fs::{fstatfs, StatFs};
18#[cfg(not(any(solarish, target_os = "haiku", target_os = "redox", target_os = "wasi")))]
19use crate::fs::{fstatvfs, StatVfs};
20use crate::io;
21#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
22use crate::process::fchdir;
781aab86 23#[cfg(target_os = "wasi")]
fe692bf9 24use alloc::borrow::ToOwned;
781aab86
FG
25#[cfg(not(any(linux_like, target_os = "openbsd")))]
26use c::dirent as libc_dirent;
fe692bf9
FG
27#[cfg(not(linux_like))]
28use c::readdir as libc_readdir;
29#[cfg(linux_like)]
781aab86 30use c::{dirent64 as libc_dirent, readdir64 as libc_readdir};
fe692bf9 31use core::fmt;
781aab86 32use core::mem::zeroed;
fe692bf9
FG
33use core::ptr::NonNull;
34use libc_errno::{errno, set_errno, Errno};
35
36/// `DIR*`
37#[repr(transparent)]
38pub struct Dir(NonNull<c::DIR>);
39
40impl 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.
148unsafe 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.
273unsafe impl Send for Dir {}
274
275impl Drop for Dir {
276 #[inline]
277 fn drop(&mut self) {
278 unsafe { c::closedir(self.0.as_ptr()) };
279 }
280}
281
282impl 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
291impl 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)]
301pub struct DirEntry {
781aab86 302 dirent: libc_dirent,
fe692bf9 303
781aab86 304 #[cfg(target_os = "wasi")]
fe692bf9
FG
305 name: CString,
306}
307
308impl 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)]
349struct 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")]
362fn 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}