#[cfg(target_os = "linux")]
type RawDirent = libc::dirent64;
#[cfg(target_os = "linux")]
#[inline]
unsafe fn getdents(fd: libc::c_int, buf: &mut [u8]) -> isize {
libc::syscall(
libc::SYS_getdents64,
fd as libc::c_uint,
buf.as_mut_ptr(),
buf.len(),
) as isize
}
#[cfg(target_os = "freebsd")]
type RawDirent = crate::sys::dirent;
#[cfg(target_os = "freebsd")]
#[inline]
unsafe fn getdents(fd: libc::c_int, buf: &mut [u8]) -> isize {
crate::sys::getdirentries(
fd,
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
core::ptr::null_mut(),
) as isize
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
type RawDirent = libc::dirent;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[inline]
unsafe fn getdents(fd: libc::c_int, buf: &mut [u8]) -> isize {
let mut offset = core::mem::MaybeUninit::<libc::off_t>::uninit();
libc::syscall(
crate::sys::SYS_GETDIRENTRIES64,
fd,
buf.as_mut_ptr(),
buf.len(),
offset.as_mut_ptr(),
) as isize
}
#[cfg(any(target_os = "netbsd", target_os = "solaris", target_os = "illumos"))]
type RawDirent = libc::dirent;
#[cfg(any(target_os = "netbsd", target_os = "solaris", target_os = "illumos"))]
#[inline]
unsafe fn getdents(fd: libc::c_int, buf: &mut [u8]) -> isize {
crate::sys::getdents(fd, buf.as_mut_ptr() as *mut _, buf.len()) as isize
}
fn parse_int_bytes<I: Iterator<Item = u8>>(it: I) -> Option<libc::c_int> {
let mut num: libc::c_int = 0;
let mut seen_any = false;
for ch in it {
if (b'0'..=b'9').contains(&ch) {
num = num
.checked_mul(10)?
.checked_add((ch - b'0') as libc::c_int)?;
seen_any = true;
} else {
return None;
}
}
if seen_any {
Some(num)
} else {
None
}
}
#[repr(align(8))]
struct DirFdIterBuf {
data: [u8; core::mem::size_of::<RawDirent>()],
}
pub struct DirFdIter {
minfd: libc::c_int,
dirfd: libc::c_int,
dirent_buf: DirFdIterBuf,
dirent_nbytes: usize,
dirent_offset: usize,
}
impl DirFdIter {
#[inline]
pub fn open(minfd: libc::c_int) -> Option<Self> {
#[cfg(target_os = "linux")]
let dirfd = unsafe {
if crate::util::is_wsl_1() {
return None;
}
libc::open(
"/proc/self/fd\0".as_ptr() as *const libc::c_char,
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
)
};
#[cfg(target_os = "freebsd")]
let dirfd = {
let dev_path_ptr = "/dev\0".as_ptr() as *const libc::c_char;
let devfd_path_ptr = "/dev/fd\0".as_ptr() as *const libc::c_char;
let mut dev_stat = core::mem::MaybeUninit::uninit();
let mut devfd_stat = core::mem::MaybeUninit::uninit();
unsafe {
if libc::stat(dev_path_ptr, dev_stat.as_mut_ptr()) == 0
&& libc::stat(devfd_path_ptr, devfd_stat.as_mut_ptr()) == 0
&& dev_stat.assume_init().st_dev != devfd_stat.assume_init().st_dev
{
libc::open(
devfd_path_ptr,
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
)
} else {
-1
}
}
};
#[cfg(target_os = "netbsd")]
let dirfd = unsafe {
libc::open(
"/proc/self/fd\0".as_ptr() as *const libc::c_char,
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
)
};
#[cfg(any(target_os = "macos", target_os = "ios"))]
let dirfd = unsafe {
libc::open(
"/dev/fd\0".as_ptr() as *const libc::c_char,
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
)
};
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
let dirfd = unsafe {
let fd = libc::open(
"/dev/fd\0".as_ptr() as *const libc::c_char,
libc::O_RDONLY | libc::O_CLOEXEC,
);
if fd < 0 {
libc::open(
"/proc/self/fd\0".as_ptr() as *const libc::c_char,
libc::O_RDONLY | libc::O_CLOEXEC,
)
} else {
fd
}
};
if dirfd >= 0 {
Some(Self {
minfd,
dirfd,
dirent_buf: DirFdIterBuf {
data: [0; core::mem::size_of::<RawDirent>()],
},
dirent_nbytes: 0,
dirent_offset: 0,
})
} else {
None
}
}
#[inline]
unsafe fn get_entry_info(&self, offset: usize) -> (Option<libc::c_int>, usize) {
#[allow(clippy::cast_ptr_alignment)] let entry = &*(self.dirent_buf.data.as_ptr().add(offset) as *const RawDirent);
cfg_if::cfg_if! {
if #[cfg(any(
target_os = "freebsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios",
))] {
debug_assert_eq!(entry.d_name[entry.d_namlen as usize], 0);
debug_assert_eq!(
entry.d_name[..entry.d_namlen as usize]
.iter()
.position(|&c| c == 0),
None
);
let fd = parse_int_bytes(
entry.d_name[..entry.d_namlen as usize]
.iter()
.map(|c| *c as u8),
);
} else {
let fd = parse_int_bytes(
entry
.d_name
.iter()
.take_while(|c| **c != 0)
.map(|c| *c as u8),
);
}
}
(fd, entry.d_reclen as usize)
}
#[inline]
pub fn next(&mut self) -> Result<Option<libc::c_int>, ()> {
if self.dirfd < 0 {
return Ok(None);
}
loop {
if self.dirent_offset >= self.dirent_nbytes {
let nbytes = unsafe { getdents(self.dirfd, &mut self.dirent_buf.data) };
match nbytes.cmp(&0) {
core::cmp::Ordering::Greater => {
self.dirent_nbytes = nbytes as usize;
self.dirent_offset = 0;
}
core::cmp::Ordering::Equal => {
unsafe {
libc::close(self.dirfd);
}
self.dirfd = -1;
return Ok(None);
}
_ => return Err(()),
}
}
let (fd, reclen) = unsafe { self.get_entry_info(self.dirent_offset) };
self.dirent_offset += reclen as usize;
if let Some(fd) = fd {
if fd >= self.minfd && fd != self.dirfd {
return Ok(Some(fd));
}
}
}
}
#[inline]
pub fn size_hint(&self) -> (usize, Option<usize>) {
if self.dirfd < 0 {
return (0, Some(0));
}
let mut low = 0;
let mut dirent_offset = self.dirent_offset;
while dirent_offset < self.dirent_nbytes {
let (fd, reclen) = unsafe { self.get_entry_info(dirent_offset) };
dirent_offset += reclen as usize;
if let Some(fd) = fd {
debug_assert!(fd >= self.minfd);
if fd != self.dirfd {
low += 1;
}
}
}
(low, Some(libc::c_int::MAX as usize))
}
}
impl Drop for DirFdIter {
#[inline]
fn drop(&mut self) {
if self.dirfd >= 0 {
unsafe {
libc::close(self.dirfd);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::fmt::Write;
pub struct BufWriter {
pub buf: [u8; 80],
pub i: usize,
}
impl BufWriter {
pub fn new() -> Self {
Self { buf: [0; 80], i: 0 }
}
pub fn iter_bytes(&'_ self) -> impl Iterator<Item = u8> + '_ {
self.buf.iter().take(self.i).cloned()
}
}
impl Write for BufWriter {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if self.i + s.len() > self.buf.len() {
return Err(core::fmt::Error);
}
for &ch in s.as_bytes() {
self.buf[self.i] = ch;
self.i += 1;
}
Ok(())
}
}
#[test]
fn test_parse_int_bytes() {
assert_eq!(parse_int_bytes(b"0".iter().cloned()), Some(0));
assert_eq!(parse_int_bytes(b"10".iter().cloned()), Some(10));
assert_eq!(parse_int_bytes(b"1423".iter().cloned()), Some(1423));
assert_eq!(parse_int_bytes(b" 0".iter().cloned()), None);
assert_eq!(parse_int_bytes(b"0 ".iter().cloned()), None);
assert_eq!(parse_int_bytes(b"-1".iter().cloned()), None);
assert_eq!(parse_int_bytes(b"+1".iter().cloned()), None);
assert_eq!(parse_int_bytes(b"1.".iter().cloned()), None);
assert_eq!(parse_int_bytes(b"".iter().cloned()), None);
let mut buf = BufWriter::new();
write!(&mut buf, "{}", libc::c_int::MAX as libc::c_uint + 1).unwrap();
assert_eq!(parse_int_bytes(buf.iter_bytes()), None);
}
}