Skip to content

Commit bc04a4e

Browse files
committed
fs: Use readdir() instead of readdir_r() on Linux
readdir() is preferred over readdir_r() on Linux and many other platforms because it more gracefully supports long file names. Both glibc and musl (and presumably all other Linux libc implementations) guarantee that readdir() is thread-safe as long as a single DIR* is not accessed concurrently, which is enough to make a readdir()-based implementation of ReadDir safe. This implementation is already used for some other OSes including Fuchsia, Redox, and Solaris. See #40021 for more details. Fixes #86649. Fixes #34668.
1 parent c3e92fe commit bc04a4e

File tree

2 files changed

+30
-13
lines changed

2 files changed

+30
-13
lines changed

library/std/src/sys/unix/fs.rs

+29-12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ use libc::c_char;
3434
use libc::dirfd;
3535
#[cfg(any(target_os = "linux", target_os = "emscripten"))]
3636
use libc::fstatat64;
37+
#[cfg(any(
38+
target_os = "solaris",
39+
target_os = "fuchsia",
40+
target_os = "redox",
41+
target_os = "illumos"
42+
))]
43+
use libc::readdir as readdir64;
44+
#[cfg(target_os = "linux")]
45+
use libc::readdir64;
46+
#[cfg(any(target_os = "emscripten", target_os = "l4re"))]
47+
use libc::readdir64_r;
3748
#[cfg(not(any(
3849
target_os = "linux",
3950
target_os = "emscripten",
@@ -60,9 +71,7 @@ use libc::{
6071
lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
6172
};
6273
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "l4re"))]
63-
use libc::{
64-
dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, readdir64_r, stat64,
65-
};
74+
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
6675

6776
pub use crate::sys_common::fs::try_exists;
6877

@@ -202,6 +211,7 @@ struct InnerReadDir {
202211
pub struct ReadDir {
203212
inner: Arc<InnerReadDir>,
204213
#[cfg(not(any(
214+
target_os = "linux",
205215
target_os = "solaris",
206216
target_os = "illumos",
207217
target_os = "fuchsia",
@@ -218,11 +228,11 @@ unsafe impl Sync for Dir {}
218228
pub struct DirEntry {
219229
entry: dirent64,
220230
dir: Arc<InnerReadDir>,
221-
// We need to store an owned copy of the entry name
222-
// on Solaris and Fuchsia because a) it uses a zero-length
223-
// array to store the name, b) its lifetime between readdir
224-
// calls is not guaranteed.
231+
// We need to store an owned copy of the entry name on platforms that use
232+
// readdir() (not readdir_r()), because a) struct dirent may use a flexible
233+
// array to store the name, b) it lives only until the next readdir() call.
225234
#[cfg(any(
235+
target_os = "linux",
226236
target_os = "solaris",
227237
target_os = "illumos",
228238
target_os = "fuchsia",
@@ -449,6 +459,7 @@ impl Iterator for ReadDir {
449459
type Item = io::Result<DirEntry>;
450460

451461
#[cfg(any(
462+
target_os = "linux",
452463
target_os = "solaris",
453464
target_os = "fuchsia",
454465
target_os = "redox",
@@ -457,12 +468,13 @@ impl Iterator for ReadDir {
457468
fn next(&mut self) -> Option<io::Result<DirEntry>> {
458469
unsafe {
459470
loop {
460-
// Although readdir_r(3) would be a correct function to use here because
461-
// of the thread safety, on Illumos and Fuchsia the readdir(3C) function
462-
// is safe to use in threaded applications and it is generally preferred
463-
// over the readdir_r(3C) function.
471+
// As of POSIX.1-2017, readdir() is not required to be thread safe; only
472+
// readdir_r() is. However, readdir_r() cannot correctly handle platforms
473+
// with unlimited or variable NAME_MAX. Many modern platforms guarantee
474+
// thread safety for readdir() as long an individual DIR* is not accessed
475+
// concurrently, which is sufficient for Rust.
464476
super::os::set_errno(0);
465-
let entry_ptr = libc::readdir(self.inner.dirp.0);
477+
let entry_ptr = readdir64(self.inner.dirp.0);
466478
if entry_ptr.is_null() {
467479
// null can mean either the end is reached or an error occurred.
468480
// So we had to clear errno beforehand to check for an error now.
@@ -486,6 +498,7 @@ impl Iterator for ReadDir {
486498
}
487499

488500
#[cfg(not(any(
501+
target_os = "linux",
489502
target_os = "solaris",
490503
target_os = "fuchsia",
491504
target_os = "redox",
@@ -652,6 +665,7 @@ impl DirEntry {
652665
}
653666

654667
#[cfg(not(any(
668+
target_os = "linux",
655669
target_os = "solaris",
656670
target_os = "illumos",
657671
target_os = "fuchsia",
@@ -661,6 +675,7 @@ impl DirEntry {
661675
unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) }
662676
}
663677
#[cfg(any(
678+
target_os = "linux",
664679
target_os = "solaris",
665680
target_os = "illumos",
666681
target_os = "fuchsia",
@@ -1071,6 +1086,7 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
10711086
Ok(ReadDir {
10721087
inner: Arc::new(inner),
10731088
#[cfg(not(any(
1089+
target_os = "linux",
10741090
target_os = "solaris",
10751091
target_os = "illumos",
10761092
target_os = "fuchsia",
@@ -1606,6 +1622,7 @@ mod remove_dir_impl {
16061622
ReadDir {
16071623
inner: Arc::new(InnerReadDir { dirp, root: dummy_root }),
16081624
#[cfg(not(any(
1625+
target_os = "linux",
16091626
target_os = "solaris",
16101627
target_os = "illumos",
16111628
target_os = "fuchsia",

library/std/src/sys/unix/os.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub fn errno() -> i32 {
7575
}
7676

7777
/// Sets the platform-specific value of errno
78-
#[cfg(all(not(target_os = "linux"), not(target_os = "dragonfly"), not(target_os = "vxworks")))] // needed for readdir and syscall!
78+
#[cfg(all(not(target_os = "dragonfly"), not(target_os = "vxworks")))] // needed for readdir and syscall!
7979
#[allow(dead_code)] // but not all target cfgs actually end up using it
8080
pub fn set_errno(e: i32) {
8181
unsafe { *errno_location() = e as c_int }

0 commit comments

Comments
 (0)