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