Skip to content

Commit 692c19a

Browse files
committed
Refactor ReadDir into a state machine
1 parent 529aae6 commit 692c19a

File tree

1 file changed

+101
-70
lines changed
  • library/std/src/sys/pal/wasi

1 file changed

+101
-70
lines changed

library/std/src/sys/pal/wasi/fs.rs

+101-70
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,26 @@ pub struct FileAttr {
2727

2828
pub struct ReadDir {
2929
inner: Arc<ReadDirInner>,
30-
cookie: Option<wasi::Dircookie>,
31-
buf: Vec<u8>,
32-
offset: usize,
33-
cap: usize,
30+
state: ReadDirState,
31+
}
32+
33+
enum ReadDirState {
34+
/// Next DirEntry should be read from contents of buf at `offset`
35+
FillBuffer {
36+
next_read_offset: wasi::Dircookie,
37+
buf: Vec<u8>,
38+
},
39+
ProcessEntry {
40+
buf: Vec<u8>,
41+
next_read_offset: Option<wasi::Dircookie>,
42+
offset: usize,
43+
},
44+
/// Do not fetch any more entries, process all entries
45+
RunUntilExhaustion {
46+
buf: Vec<u8>,
47+
offset: usize,
48+
},
49+
Done,
3450
}
3551

3652
struct ReadDirInner {
@@ -147,11 +163,8 @@ impl FileType {
147163
impl ReadDir {
148164
fn new(dir: File, root: PathBuf) -> ReadDir {
149165
ReadDir {
150-
cookie: Some(0),
151-
buf: vec![0; 128],
152-
offset: 0,
153-
cap: 0,
154166
inner: Arc::new(ReadDirInner { dir, root }),
167+
state: ReadDirState::FillBuffer { next_read_offset: 0, buf: vec![0; 128] },
155168
}
156169
}
157170
}
@@ -162,81 +175,99 @@ impl fmt::Debug for ReadDir {
162175
}
163176
}
164177

178+
impl core::iter::FusedIterator for ReadDir {}
179+
165180
impl Iterator for ReadDir {
166181
type Item = io::Result<DirEntry>;
167182

168183
fn next(&mut self) -> Option<io::Result<DirEntry>> {
169-
loop {
170-
// If we've reached the capacity of our buffer then we need to read
171-
// some more from the OS, otherwise we pick up at our old offset.
172-
let offset = if self.offset == self.cap {
173-
let cookie = self.cookie.take()?;
174-
match self.inner.dir.fd.readdir(&mut self.buf, cookie) {
175-
Ok(bytes) => {
176-
// No more entries if we read less than buffer size
177-
if bytes < self.buf.len() {
178-
self.cookie = None;
179-
if bytes == 0 {
180-
return None;
181-
}
184+
match &mut self.state {
185+
ReadDirState::FillBuffer { next_read_offset, ref mut buf } => {
186+
let result = self.inner.dir.fd.readdir(buf, *next_read_offset);
187+
match result {
188+
Ok(read_bytes) => {
189+
if read_bytes < buf.len() {
190+
buf.truncate(read_bytes);
191+
self.state =
192+
ReadDirState::RunUntilExhaustion { buf: mem::take(buf), offset: 0 };
182193
} else {
183-
self.cookie = Some(cookie);
194+
debug_assert_eq!(read_bytes, buf.len());
195+
self.state = ReadDirState::ProcessEntry {
196+
buf: mem::take(buf),
197+
offset: 0,
198+
next_read_offset: Some(*next_read_offset),
199+
};
184200
}
185-
self.cap = self.buf.len();
186-
self.offset = 0;
187-
0
201+
self.next()
202+
}
203+
Err(e) => {
204+
self.state = ReadDirState::Done;
205+
return Some(Err(e));
188206
}
189-
Err(e) => return Some(Err(e)),
190-
}
191-
} else {
192-
self.offset
193-
};
194-
let data = &self.buf[offset..self.cap];
195-
196-
// If we're not able to read a directory entry then that means it
197-
// must have been truncated at the end of the buffer, so reset our
198-
// offset so we can go back and reread into the buffer, picking up
199-
// where we last left off.
200-
let dirent_size = mem::size_of::<wasi::Dirent>();
201-
if data.len() < dirent_size {
202-
assert!(self.buf.len() >= dirent_size);
203-
self.offset = self.cap;
204-
continue;
205-
}
206-
let (dirent, data) = data.split_at(dirent_size);
207-
let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) };
208-
209-
// If the file name was truncated, then we need to reinvoke
210-
// `readdir` so we truncate our buffer to start over and reread this
211-
// descriptor. Note that if our offset is 0 that means the file name
212-
// is massive and we need a bigger buffer.
213-
if data.len() < dirent.d_namlen as usize {
214-
if offset == 0 {
215-
let amt_to_add = self.buf.capacity();
216-
self.buf.extend(iter::repeat(0).take(amt_to_add));
217207
}
218-
assert!(self.cookie.is_some());
219-
self.offset = self.cap;
220-
continue;
221208
}
222-
self.cookie.as_mut().map(|cookie| {
223-
*cookie = dirent.d_next;
224-
});
225-
self.offset = offset + dirent_size + dirent.d_namlen as usize;
209+
ReadDirState::ProcessEntry { ref mut buf, next_read_offset, offset } => {
210+
let contents = &buf[*offset..];
211+
const DIRENT_SIZE: usize = crate::mem::size_of::<wasi::Dirent>();
212+
if contents.len() >= DIRENT_SIZE {
213+
let (dirent, data) = contents.split_at(DIRENT_SIZE);
214+
let dirent =
215+
unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) };
216+
// If the file name was truncated, then we need to reinvoke
217+
// `readdir` so we truncate our buffer to start over and reread this
218+
// descriptor.
219+
if data.len() < dirent.d_namlen as usize {
220+
if buf.len() < dirent.d_namlen as usize + DIRENT_SIZE {
221+
buf.resize(dirent.d_namlen as usize + DIRENT_SIZE, 0);
222+
}
223+
if let Some(next_read_offset) = *next_read_offset {
224+
self.state =
225+
ReadDirState::FillBuffer { next_read_offset, buf: mem::take(buf) };
226+
} else {
227+
self.state = ReadDirState::Done;
228+
}
229+
230+
return self.next();
231+
}
232+
next_read_offset.as_mut().map(|cookie| {
233+
*cookie = dirent.d_next;
234+
});
235+
*offset = *offset + DIRENT_SIZE + dirent.d_namlen as usize;
236+
237+
let name = &data[..(dirent.d_namlen as usize)];
226238

227-
let name = &data[..(dirent.d_namlen as usize)];
239+
// These names are skipped on all other platforms, so let's skip
240+
// them here too
241+
if name == b"." || name == b".." {
242+
return self.next();
243+
}
228244

229-
// These names are skipped on all other platforms, so let's skip
230-
// them here too
231-
if name == b"." || name == b".." {
232-
continue;
245+
return Some(Ok(DirEntry {
246+
meta: dirent,
247+
name: name.to_vec(),
248+
inner: self.inner.clone(),
249+
}));
250+
} else if let Some(next_read_offset) = *next_read_offset {
251+
self.state = ReadDirState::FillBuffer { next_read_offset, buf: mem::take(buf) };
252+
} else {
253+
self.state = ReadDirState::Done;
254+
}
255+
self.next()
233256
}
257+
ReadDirState::RunUntilExhaustion { buf, offset } => {
258+
if *offset >= buf.len() {
259+
self.state = ReadDirState::Done;
260+
} else {
261+
self.state = ReadDirState::ProcessEntry {
262+
buf: mem::take(buf),
263+
offset: *offset,
264+
next_read_offset: None,
265+
};
266+
}
234267

235-
return Some(Ok(DirEntry {
236-
meta: dirent,
237-
name: name.to_vec(),
238-
inner: self.inner.clone(),
239-
}));
268+
self.next()
269+
}
270+
ReadDirState::Done => None,
240271
}
241272
}
242273
}

0 commit comments

Comments
 (0)