Skip to content

Commit 7d2fa4a

Browse files
committed
Auto merge of #50630 - sharkdp:fix-50619, r=sfackler
Fix possibly endless loop in ReadDir iterator Certain directories in `/proc` can cause the `ReadDir` iterator to loop indefinitely. We get an error code (22) when calling libc's `readdir_r` on these directories, but `entry_ptr` is `NULL` at the same time, signalling the end of the directory stream. This change introduces an internal state to the iterator such that the `Some(Err(..))` value will only be returned once when calling `next`. Subsequent calls will return `None`. fixes #50619
2 parents fdd9cdc + af75314 commit 7d2fa4a

File tree

1 file changed

+24
-7
lines changed

1 file changed

+24
-7
lines changed

src/libstd/sys/unix/fs.rs

+24-7
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ struct InnerReadDir {
5959
}
6060

6161
#[derive(Clone)]
62-
pub struct ReadDir(Arc<InnerReadDir>);
62+
pub struct ReadDir {
63+
inner: Arc<InnerReadDir>,
64+
end_of_stream: bool,
65+
}
6366

6467
struct Dir(*mut libc::DIR);
6568

@@ -215,7 +218,7 @@ impl fmt::Debug for ReadDir {
215218
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
216219
// This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
217220
// Thus the result will be e g 'ReadDir("/home")'
218-
fmt::Debug::fmt(&*self.0.root, f)
221+
fmt::Debug::fmt(&*self.inner.root, f)
219222
}
220223
}
221224

@@ -231,7 +234,7 @@ impl Iterator for ReadDir {
231234
// is safe to use in threaded applications and it is generally preferred
232235
// over the readdir_r(3C) function.
233236
super::os::set_errno(0);
234-
let entry_ptr = libc::readdir(self.0.dirp.0);
237+
let entry_ptr = libc::readdir(self.inner.dirp.0);
235238
if entry_ptr.is_null() {
236239
// NULL can mean either the end is reached or an error occurred.
237240
// So we had to clear errno beforehand to check for an error now.
@@ -259,14 +262,25 @@ impl Iterator for ReadDir {
259262

260263
#[cfg(not(any(target_os = "solaris", target_os = "fuchsia")))]
261264
fn next(&mut self) -> Option<io::Result<DirEntry>> {
265+
if self.end_of_stream {
266+
return None;
267+
}
268+
262269
unsafe {
263270
let mut ret = DirEntry {
264271
entry: mem::zeroed(),
265272
dir: self.clone(),
266273
};
267274
let mut entry_ptr = ptr::null_mut();
268275
loop {
269-
if readdir64_r(self.0.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 {
276+
if readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 {
277+
if entry_ptr.is_null() {
278+
// We encountered an error (which will be returned in this iteration), but
279+
// we also reached the end of the directory stream. The `end_of_stream`
280+
// flag is enabled to make sure that we return `None` in the next iteration
281+
// (instead of looping forever)
282+
self.end_of_stream = true;
283+
}
270284
return Some(Err(Error::last_os_error()))
271285
}
272286
if entry_ptr.is_null() {
@@ -289,7 +303,7 @@ impl Drop for Dir {
289303

290304
impl DirEntry {
291305
pub fn path(&self) -> PathBuf {
292-
self.dir.0.root.join(OsStr::from_bytes(self.name_bytes()))
306+
self.dir.inner.root.join(OsStr::from_bytes(self.name_bytes()))
293307
}
294308

295309
pub fn file_name(&self) -> OsString {
@@ -298,7 +312,7 @@ impl DirEntry {
298312

299313
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
300314
pub fn metadata(&self) -> io::Result<FileAttr> {
301-
let fd = cvt(unsafe {dirfd(self.dir.0.dirp.0)})?;
315+
let fd = cvt(unsafe {dirfd(self.dir.inner.dirp.0)})?;
302316
let mut stat: stat64 = unsafe { mem::zeroed() };
303317
cvt(unsafe {
304318
fstatat64(fd, self.entry.d_name.as_ptr(), &mut stat, libc::AT_SYMLINK_NOFOLLOW)
@@ -691,7 +705,10 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
691705
Err(Error::last_os_error())
692706
} else {
693707
let inner = InnerReadDir { dirp: Dir(ptr), root };
694-
Ok(ReadDir(Arc::new(inner)))
708+
Ok(ReadDir{
709+
inner: Arc::new(inner),
710+
end_of_stream: false,
711+
})
695712
}
696713
}
697714
}

0 commit comments

Comments
 (0)