aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/node/dir_iterator.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/node/dir_iterator.zig')
-rw-r--r--src/bun.js/node/dir_iterator.zig347
1 files changed, 347 insertions, 0 deletions
diff --git a/src/bun.js/node/dir_iterator.zig b/src/bun.js/node/dir_iterator.zig
new file mode 100644
index 000000000..19db4177d
--- /dev/null
+++ b/src/bun.js/node/dir_iterator.zig
@@ -0,0 +1,347 @@
+// This is copied from std.fs.Dir.Iterator
+// The differences are:
+// - it returns errors in the expected format
+// - doesn't mark BADF as unreachable
+// - It uses PathString instead of []const u8
+
+const builtin = @import("builtin");
+const std = @import("std");
+const os = std.os;
+
+const Dir = std.fs.Dir;
+const JSC = @import("../../jsc.zig");
+const PathString = JSC.PathString;
+
+const IteratorError = error{ AccessDenied, SystemResources } || os.UnexpectedError;
+const mem = std.mem;
+const strings = @import("../../global.zig").strings;
+const Maybe = JSC.Maybe;
+const File = std.fs.File;
+const Result = Maybe(?Entry);
+
+const Entry = JSC.Node.DirEnt;
+
+pub const Iterator = switch (builtin.os.tag) {
+ .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => struct {
+ dir: Dir,
+ seek: i64,
+ buf: [8192]u8, // TODO align(@alignOf(os.system.dirent)),
+ index: usize,
+ end_index: usize,
+
+ const Self = @This();
+
+ pub const Error = IteratorError;
+
+ /// Memory such as file names referenced in this returned entry becomes invalid
+ /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
+ const next = switch (builtin.os.tag) {
+ .macos, .ios => nextDarwin,
+ // .freebsd, .netbsd, .dragonfly, .openbsd => nextBsd,
+ // .solaris => nextSolaris,
+ else => @compileError("unimplemented"),
+ };
+
+ fn nextDarwin(self: *Self) Result {
+ start_over: while (true) {
+ if (self.index >= self.end_index) {
+ const rc = os.system.__getdirentries64(
+ self.dir.fd,
+ &self.buf,
+ self.buf.len,
+ &self.seek,
+ );
+
+ if (rc < 1) {
+ if (rc == 0) return Result{ .result = null };
+ if (Result.errnoSys(rc, .getdirentries64)) |err| {
+ return err;
+ }
+ }
+
+ self.index = 0;
+ self.end_index = @intCast(usize, rc);
+ }
+ const darwin_entry = @ptrCast(*align(1) os.system.dirent, &self.buf[self.index]);
+ const next_index = self.index + darwin_entry.reclen();
+ self.index = next_index;
+
+ const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
+
+ if (strings.eqlComptime(name, ".") or strings.eqlComptime(name, "..") or (darwin_entry.d_ino == 0)) {
+ continue :start_over;
+ }
+
+ const entry_kind = switch (darwin_entry.d_type) {
+ os.DT.BLK => Entry.Kind.BlockDevice,
+ os.DT.CHR => Entry.Kind.CharacterDevice,
+ os.DT.DIR => Entry.Kind.Directory,
+ os.DT.FIFO => Entry.Kind.NamedPipe,
+ os.DT.LNK => Entry.Kind.SymLink,
+ os.DT.REG => Entry.Kind.File,
+ os.DT.SOCK => Entry.Kind.UnixDomainSocket,
+ os.DT.WHT => Entry.Kind.Whiteout,
+ else => Entry.Kind.Unknown,
+ };
+ return .{
+ .result = Entry{
+ .name = PathString.init(name),
+ .kind = entry_kind,
+ },
+ };
+ }
+ }
+ },
+
+ .linux => struct {
+ dir: Dir,
+ // The if guard is solely there to prevent compile errors from missing `linux.dirent64`
+ // definition when compiling for other OSes. It doesn't do anything when compiling for Linux.
+ buf: [8192]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)),
+ index: usize,
+ end_index: usize,
+
+ const Self = @This();
+ const linux = os.linux;
+
+ pub const Error = IteratorError;
+
+ /// Memory such as file names referenced in this returned entry becomes invalid
+ /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
+ pub fn next(self: *Self) Result {
+ start_over: while (true) {
+ if (self.index >= self.end_index) {
+ const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
+ if (Result.errnoSys(rc, .getdents64)) |err| return err;
+ if (rc == 0) return .{ .result = null };
+ self.index = 0;
+ self.end_index = rc;
+ }
+ const linux_entry = @ptrCast(*align(1) linux.dirent64, &self.buf[self.index]);
+ const next_index = self.index + linux_entry.reclen();
+ self.index = next_index;
+
+ const name = mem.sliceTo(@ptrCast([*:0]u8, &linux_entry.d_name), 0);
+
+ // skip . and .. entries
+ if (strings.eqlComptime(name, ".") or strings.eqlComptime(name, "..")) {
+ continue :start_over;
+ }
+
+ const entry_kind = switch (linux_entry.d_type) {
+ linux.DT.BLK => Entry.Kind.BlockDevice,
+ linux.DT.CHR => Entry.Kind.CharacterDevice,
+ linux.DT.DIR => Entry.Kind.Directory,
+ linux.DT.FIFO => Entry.Kind.NamedPipe,
+ linux.DT.LNK => Entry.Kind.SymLink,
+ linux.DT.REG => Entry.Kind.File,
+ linux.DT.SOCK => Entry.Kind.UnixDomainSocket,
+ else => Entry.Kind.Unknown,
+ };
+ return .{
+ .result = Entry{
+ .name = PathString.init(name),
+ .kind = entry_kind,
+ },
+ };
+ }
+ }
+ },
+ .windows => struct {
+ dir: Dir,
+ buf: [8192]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)),
+ index: usize,
+ end_index: usize,
+ first: bool,
+ name_data: [256]u8,
+
+ const Self = @This();
+
+ pub const Error = IteratorError;
+
+ /// Memory such as file names referenced in this returned entry becomes invalid
+ /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
+ pub fn next(self: *Self) Result {
+ while (true) {
+ const w = os.windows;
+ if (self.index >= self.end_index) {
+ var io: w.IO_STATUS_BLOCK = undefined;
+ const rc = w.ntdll.NtQueryDirectoryFile(
+ self.dir.fd,
+ null,
+ null,
+ null,
+ &io,
+ &self.buf,
+ self.buf.len,
+ .FileBothDirectoryInformation,
+ w.FALSE,
+ null,
+ if (self.first) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
+ );
+ self.first = false;
+ if (io.Information == 0) return .{ .result = null };
+ self.index = 0;
+ self.end_index = io.Information;
+ switch (rc) {
+ .SUCCESS => {},
+ .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
+
+ else => return w.unexpectedStatus(rc),
+ }
+ }
+
+ const aligned_ptr = @alignCast(@alignOf(w.FILE_BOTH_DIR_INFORMATION), &self.buf[self.index]);
+ const dir_info = @ptrCast(*w.FILE_BOTH_DIR_INFORMATION, aligned_ptr);
+ if (dir_info.NextEntryOffset != 0) {
+ self.index += dir_info.NextEntryOffset;
+ } else {
+ self.index = self.buf.len;
+ }
+
+ const name_utf16le = @ptrCast([*]u16, &dir_info.FileName)[0 .. dir_info.FileNameLength / 2];
+
+ if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
+ continue;
+ // Trust that Windows gives us valid UTF-16LE
+ const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
+ const name_utf8 = self.name_data[0..name_utf8_len];
+ const kind = blk: {
+ const attrs = dir_info.FileAttributes;
+ if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
+ if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
+ break :blk Entry.Kind.File;
+ };
+ return .{
+ .result = Entry{
+ .name = PathString.init(name_utf8),
+ .kind = kind,
+ },
+ };
+ }
+ }
+ },
+ .wasi => struct {
+ dir: Dir,
+ buf: [8192]u8, // TODO align(@alignOf(os.wasi.dirent_t)),
+ cookie: u64,
+ index: usize,
+ end_index: usize,
+
+ const Self = @This();
+
+ pub const Error = IteratorError;
+
+ /// Memory such as file names referenced in this returned entry becomes invalid
+ /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
+ pub fn next(self: *Self) Result {
+ // We intentinally use fd_readdir even when linked with libc,
+ // since its implementation is exactly the same as below,
+ // and we avoid the code complexity here.
+ const w = os.wasi;
+ start_over: while (true) {
+ if (self.index >= self.end_index) {
+ var bufused: usize = undefined;
+ switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
+ .SUCCESS => {},
+ .BADF => unreachable, // Dir is invalid or was opened without iteration ability
+ .FAULT => unreachable,
+ .NOTDIR => unreachable,
+ .INVAL => unreachable,
+ .NOTCAPABLE => return error.AccessDenied,
+ else => |err| return os.unexpectedErrno(err),
+ }
+ if (bufused == 0) return null;
+ self.index = 0;
+ self.end_index = bufused;
+ }
+ const entry = @ptrCast(*align(1) w.dirent_t, &self.buf[self.index]);
+ const entry_size = @sizeOf(w.dirent_t);
+ const name_index = self.index + entry_size;
+ const name = mem.span(self.buf[name_index .. name_index + entry.d_namlen]);
+
+ const next_index = name_index + entry.d_namlen;
+ self.index = next_index;
+ self.cookie = entry.d_next;
+
+ // skip . and .. entries
+ if (strings.eqlComptime(name, ".") or strings.eqlComptime(name, "..")) {
+ continue :start_over;
+ }
+
+ const entry_kind = switch (entry.d_type) {
+ .BLOCK_DEVICE => Entry.Kind.BlockDevice,
+ .CHARACTER_DEVICE => Entry.Kind.CharacterDevice,
+ .DIRECTORY => Entry.Kind.Directory,
+ .SYMBOLIC_LINK => Entry.Kind.SymLink,
+ .REGULAR_FILE => Entry.Kind.File,
+ .SOCKET_STREAM, .SOCKET_DGRAM => Entry.Kind.UnixDomainSocket,
+ else => Entry.Kind.Unknown,
+ };
+ return Entry{
+ .name = name,
+ .kind = entry_kind,
+ };
+ }
+ }
+ },
+ else => @compileError("unimplemented"),
+};
+
+const WrappedIterator = struct {
+ iter: Iterator,
+ const Self = @This();
+
+ pub const Error = IteratorError;
+
+ pub inline fn next(self: *Self) Result {
+ return self.iter.next();
+ }
+};
+
+pub fn iterate(self: Dir) WrappedIterator {
+ return WrappedIterator{
+ .iter = _iterate(self),
+ };
+}
+
+fn _iterate(self: Dir) Iterator {
+ switch (builtin.os.tag) {
+ .macos,
+ .ios,
+ .freebsd,
+ .netbsd,
+ .dragonfly,
+ .openbsd,
+ .solaris,
+ => return Iterator{
+ .dir = self,
+ .seek = 0,
+ .index = 0,
+ .end_index = 0,
+ .buf = undefined,
+ },
+ .linux, .haiku => return Iterator{
+ .dir = self,
+ .index = 0,
+ .end_index = 0,
+ .buf = undefined,
+ },
+ .windows => return Iterator{
+ .dir = self,
+ .index = 0,
+ .end_index = 0,
+ .first = true,
+ .buf = undefined,
+ .name_data = undefined,
+ },
+ .wasi => return Iterator{
+ .dir = self,
+ .cookie = os.wasi.DIRCOOKIE_START,
+ .index = 0,
+ .end_index = 0,
+ .buf = undefined,
+ },
+ else => @compileError("unimplemented"),
+ }
+}