From 4c574da8117ba59c24465a5c0dac1664b87ccd11 Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 25 Apr 2026 11:17:55 +0900 Subject: [PATCH] fix(du): use getdents64 on Linux to avoid EOVERFLOW on 32-bit architectures On 32-bit Linux (i686), du fails with "Value too large for defined data type" (EOVERFLOW) when reading directories. The root cause is nix::dir::Dir calling libc::readdir(), which uses 32-bit d_ino on 32-bit glibc. Modern filesystems (XFS, Btrfs, ext4+inode64) can return inode numbers exceeding 32 bits. Replace nix::dir::Dir with rustix::fs::RawDir on Linux/Android, which calls getdents64 syscall directly with 64-bit d_ino/d_off. This fixes du and all other utilities using DirFd::read_dir() (rm, chmod, chown, install). On non-Linux Unix (macOS, BSDs), the existing nix implementation is retained. Closes #11848 --- src/uucore/src/lib/features/safe_traversal.rs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/safe_traversal.rs b/src/uucore/src/lib/features/safe_traversal.rs index 98fc81dd928..3e42dd42478 100644 --- a/src/uucore/src/lib/features/safe_traversal.rs +++ b/src/uucore/src/lib/features/safe_traversal.rs @@ -10,7 +10,7 @@ // // spell-checker:ignore CLOEXEC RDONLY TOCTOU closedir dirp fdopendir fstatat openat REMOVEDIR unlinkat smallfile // spell-checker:ignore RAII dirfd fchownat fchown FchmodatFlags fchmodat fchmod mkdirat CREAT WRONLY ELOOP ENOTDIR -// spell-checker:ignore atimensec mtimensec ctimensec +// spell-checker:ignore atimensec mtimensec ctimensec EOVERFLOW getdents #[cfg(test)] use std::os::unix::ffi::OsStringExt; @@ -22,6 +22,7 @@ use std::os::unix::ffi::OsStrExt; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; use std::path::{Path, PathBuf}; +#[cfg(not(any(target_os = "linux", target_os = "android")))] use nix::dir::Dir; use nix::fcntl::{OFlag, openat}; use nix::libc; @@ -29,6 +30,12 @@ use nix::sys::stat::{FchmodatFlags, FileStat, Mode, fchmodat, fstatat, mkdirat}; use nix::unistd::{Gid, Uid, UnlinkatFlags, fchown, fchownat, unlinkat}; use os_display::Quotable; +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::mem::MaybeUninit; + +#[cfg(any(target_os = "linux", target_os = "android"))] +use rustix::fs::RawDir; + use crate::translate; /// Enum to specify symlink following behavior. @@ -108,7 +115,27 @@ impl From for io::Error { } } -// Helper function to read directory entries using nix +// Helper function to read directory entries. +// On Linux, use [`rustix::fs::RawDir`](https://docs.rs/rustix/latest/rustix/fs/struct.RawDir.html) which calls getdents64 directly, +// avoiding EOVERFLOW on 32-bit architectures where libc readdir() uses +// 32-bit d_ino. +#[cfg(any(target_os = "linux", target_os = "android"))] +fn read_dir_entries(fd: &OwnedFd) -> io::Result> { + let mut entries = Vec::new(); + let mut buf = [MaybeUninit::uninit(); 8192]; + let mut iter = RawDir::new(fd, &mut buf); + while let Some(entry_result) = iter.next() { + let entry = entry_result.map_err(io::Error::from)?; + let name_bytes = entry.file_name().to_bytes(); + let name_os = OsStr::from_bytes(name_bytes); + if name_os != "." && name_os != ".." { + entries.push(name_os.to_os_string()); + } + } + Ok(entries) +} + +#[cfg(not(any(target_os = "linux", target_os = "android")))] fn read_dir_entries(fd: &OwnedFd) -> io::Result> { let mut entries = Vec::new();