Rollup merge of #93459 - tavianator:dirent-copy-only-reclen, r=cuviper

fs: Don't copy d_name from struct dirent

The dirent returned from readdir() is only guaranteed to be valid for
d_reclen bytes on common platforms.  Since we copy the name separately
anyway, we can copy everything except d_name into DirEntry::entry.

Fixes #93384.
This commit is contained in:
Matthias Krüger 2022-01-30 00:04:16 +01:00 committed by GitHub
commit 0d08bbc8c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 26 additions and 2 deletions

View file

@ -1504,3 +1504,19 @@ fn create_dir_long_paths() {
let path = Path::new("");
assert_eq!(path.canonicalize().unwrap_err().kind(), crate::io::ErrorKind::NotFound);
}
/// Ensure ReadDir works on large directories.
/// Regression test for https://github.com/rust-lang/rust/issues/93384.
#[test]
fn read_large_dir() {
let tmpdir = tmpdir();
let count = 32 * 1024;
for i in 0..count {
check!(fs::File::create(tmpdir.join(&i.to_string())));
}
for entry in fs::read_dir(tmpdir.path()).unwrap() {
entry.unwrap();
}
}

View file

@ -489,10 +489,18 @@ impl Iterator for ReadDir {
};
}
// Only d_reclen bytes of *entry_ptr are valid, so we can't just copy the
// whole thing (#93384). Instead, copy everything except the name.
let entry_bytes = entry_ptr as *const u8;
let entry_name = ptr::addr_of!((*entry_ptr).d_name) as *const u8;
let name_offset = entry_name.offset_from(entry_bytes) as usize;
let mut entry: dirent64 = mem::zeroed();
ptr::copy_nonoverlapping(entry_bytes, &mut entry as *mut _ as *mut u8, name_offset);
let ret = DirEntry {
entry: *entry_ptr,
entry,
// d_name is guaranteed to be null-terminated.
name: CStr::from_ptr((*entry_ptr).d_name.as_ptr()).to_owned(),
name: CStr::from_ptr(entry_name as *const _).to_owned(),
dir: Arc::clone(&self.inner),
};
if ret.name_bytes() != b"." && ret.name_bytes() != b".." {