Skip to content

Commit 05e65bf

Browse files
author
Pat Hickey
authored
Make read_dir always open ".", rather than using dup. (#151)
`dup` creates a new file descriptor that shares a position with the old file descriptor. To ensure that independent `read_dir` calls start from the beginning, always use an open of "." rather than `dup`.
1 parent 63c07e1 commit 05e65bf

File tree

2 files changed

+97
-21
lines changed

2 files changed

+97
-21
lines changed

cap-primitives/src/posish/fs/read_dir_inner.rs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use crate::fs::{
22
open_dir_for_reading, open_dir_for_reading_unchecked, open_entry_impl, read_dir_unchecked,
3-
remove_dir_unchecked, remove_file_unchecked, stat_unchecked, target_o_path, DirEntryInner,
4-
FollowSymlinks, Metadata, OpenOptions, ReadDir,
3+
remove_dir_unchecked, remove_file_unchecked, stat_unchecked, DirEntryInner, FollowSymlinks,
4+
Metadata, OpenOptions, ReadDir,
55
};
66
use posish::fs::Dir;
7-
#[cfg(not(target_os = "wasi"))]
8-
use posish::io::dup;
97
#[cfg(unix)]
108
use std::os::unix::{
119
ffi::OsStrExt,
@@ -36,22 +34,16 @@ impl ReadDirInner {
3634
}
3735

3836
pub(crate) fn read_base_dir(start: &fs::File) -> io::Result<Self> {
39-
if !target_o_path().is_empty() {
40-
// On platforms that use `O_PATH`, open ".".
41-
Ok(Self {
42-
posish: Arc::new(Dir::from(open_dir_for_reading_unchecked(
43-
start,
44-
Component::CurDir.as_ref(),
45-
)?)?),
46-
})
47-
} else {
48-
// On platforms that lack `O_PATH`, we have a full file descriptor,
49-
// so just `dup` it (because we use `fdopendir` which assumes
50-
// ownership_).
51-
Ok(Self {
52-
posish: Arc::new(Dir::from(dup(start)?)?),
53-
})
54-
}
37+
// Open ".", to obtain a new independent file descriptor. Don't use
38+
// `dup` since in that case the resulting file descriptor would share
39+
// a current position with the original, and `read_dir` calls after
40+
// the first `read_dir` call wouldn't start from the beginning.
41+
Ok(Self {
42+
posish: Arc::new(Dir::from(open_dir_for_reading_unchecked(
43+
start,
44+
Component::CurDir.as_ref(),
45+
)?)?),
46+
})
5547
}
5648

5749
pub(crate) fn new_unchecked(start: &fs::File, path: &Path) -> io::Result<Self> {
@@ -92,8 +84,8 @@ impl Iterator for ReadDirInner {
9284
fn next(&mut self) -> Option<Self::Item> {
9385
loop {
9486
let entry = match self.posish.read()? {
95-
Err(e) => return Some(Err(e)),
9687
Ok(entry) => entry,
88+
Err(e) => return Some(Err(e)),
9789
};
9890
let file_name = entry.file_name().to_bytes();
9991
if file_name != Component::CurDir.as_os_str().as_bytes()

tests/readdir.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use cap_std::fs::{Dir, DirEntry};
2+
use std::collections::HashMap;
3+
use std::path::Path;
4+
5+
#[test]
6+
fn test_dir_entries() {
7+
let tmpdir = tempfile::tempdir().expect("construct tempdir");
8+
9+
let entries = dir_entries(&tmpdir.path());
10+
assert_eq!(entries.len(), 0, "empty dir");
11+
12+
let _f1 = std::fs::File::create(tmpdir.path().join("file1")).expect("create file1");
13+
14+
let entries = dir_entries(&tmpdir.path());
15+
assert!(
16+
entries.get("file1").is_some(),
17+
"directory contains `file1`: {:?}",
18+
entries
19+
);
20+
assert_eq!(entries.len(), 1);
21+
22+
let _f2 = std::fs::File::create(tmpdir.path().join("file2")).expect("create file1");
23+
let entries = dir_entries(&tmpdir.path());
24+
assert!(
25+
entries.get("file1").is_some(),
26+
"directory contains `file1`: {:?}",
27+
entries
28+
);
29+
assert!(
30+
entries.get("file2").is_some(),
31+
"directory contains `file2`: {:?}",
32+
entries
33+
);
34+
assert_eq!(entries.len(), 2);
35+
}
36+
37+
#[test]
38+
fn test_reread_entries() {
39+
let tmpdir = tempfile::tempdir().expect("construct tempdir");
40+
let dir = unsafe { Dir::open_ambient_dir(tmpdir.path()).unwrap() };
41+
42+
let entries = read_entries(&dir);
43+
assert_eq!(entries.len(), 0, "empty dir");
44+
45+
let _f1 = std::fs::File::create(tmpdir.path().join("file1")).expect("create file1");
46+
47+
let entries = read_entries(&dir);
48+
assert!(
49+
entries.get("file1").is_some(),
50+
"directory contains `file1`: {:?}",
51+
entries
52+
);
53+
assert_eq!(entries.len(), 1);
54+
55+
let _f2 = std::fs::File::create(tmpdir.path().join("file2")).expect("create file1");
56+
let entries = read_entries(&dir);
57+
assert!(
58+
entries.get("file1").is_some(),
59+
"directory contains `file1`: {:?}",
60+
entries
61+
);
62+
assert!(
63+
entries.get("file2").is_some(),
64+
"directory contains `file2`: {:?}",
65+
entries
66+
);
67+
assert_eq!(entries.len(), 2);
68+
}
69+
70+
fn dir_entries(path: &Path) -> HashMap<String, DirEntry> {
71+
let dir = unsafe { Dir::open_ambient_dir(path).unwrap() };
72+
read_entries(&dir)
73+
}
74+
75+
fn read_entries(dir: &Dir) -> HashMap<String, DirEntry> {
76+
let mut out = HashMap::new();
77+
for e in dir.entries().unwrap() {
78+
let e = e.expect("non-error entry");
79+
let name = e.file_name().to_str().expect("utf8 filename").to_owned();
80+
assert!(out.get(&name).is_none(), "name already read: {}", name);
81+
out.insert(name, e);
82+
}
83+
out
84+
}

0 commit comments

Comments
 (0)