Skip to content

Commit 829bf5a

Browse files
committed
wasi: fixes IterableDir.nextWasi for large directory
Signed-off-by: Takeshi Yoneda <[email protected]>
1 parent a943422 commit 829bf5a

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

lib/std/fs.zig

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,9 @@ pub const IterableDir = struct {
809809
// and we avoid the code complexity here.
810810
const w = os.wasi;
811811
start_over: while (true) {
812-
if (self.index >= self.end_index) {
812+
// According to the WASI spec, the last entry might be truncated,
813+
// so we need to check if the left buffer contains the whole dirent.
814+
if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
813815
var bufused: usize = undefined;
814816
switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
815817
.SUCCESS => {},
@@ -828,6 +830,11 @@ pub const IterableDir = struct {
828830
const entry = @ptrCast(*align(1) w.dirent_t, &self.buf[self.index]);
829831
const entry_size = @sizeOf(w.dirent_t);
830832
const name_index = self.index + entry_size;
833+
if (name_index + entry.d_namlen > self.end_index) {
834+
// This case, the name is truncated, so we need to call readdir to store the entire name.
835+
self.end_index = self.index; // Force fd_readdir in the next loop.
836+
continue :start_over;
837+
}
831838
const name = mem.span(self.buf[name_index .. name_index + entry.d_namlen]);
832839

833840
const next_index = name_index + entry.d_namlen;

lib/std/fs/test.zig

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,43 @@ test "Dir.Iterator" {
186186
try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .Directory }));
187187
}
188188

189+
test "Dir.Iterator many entries" {
190+
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
191+
192+
var tmp_dir = tmpIterableDir(.{});
193+
defer tmp_dir.cleanup();
194+
195+
const num = 1024;
196+
var i: usize = 0;
197+
var buf: [4]u8 = undefined; // Enough to store "1024".
198+
while (i < num) : (i += 1) {
199+
const name = try std.fmt.bufPrint(&buf, "{}", .{i});
200+
const file = try tmp_dir.iterable_dir.dir.createFile(name, .{});
201+
file.close();
202+
}
203+
204+
var arena = ArenaAllocator.init(testing.allocator);
205+
defer arena.deinit();
206+
const allocator = arena.allocator();
207+
208+
var entries = std.ArrayList(IterableDir.Entry).init(allocator);
209+
210+
// Create iterator.
211+
var iter = tmp_dir.iterable_dir.iterate();
212+
while (try iter.next()) |entry| {
213+
// We cannot just store `entry` as on Windows, we're re-using the name buffer
214+
// which means we'll actually share the `name` pointer between entries!
215+
const name = try allocator.dupe(u8, entry.name);
216+
try entries.append(.{ .name = name, .kind = entry.kind });
217+
}
218+
219+
i = 0;
220+
while (i < num) : (i += 1) {
221+
const name = try std.fmt.bufPrint(&buf, "{}", .{i});
222+
try testing.expect(contains(&entries, .{ .name = name, .kind = .File }));
223+
}
224+
}
225+
189226
test "Dir.Iterator twice" {
190227
var tmp_dir = tmpIterableDir(.{});
191228
defer tmp_dir.cleanup();

0 commit comments

Comments
 (0)