Files
bun.sh/src/bun.js/node/dir_iterator.zig
Meghan Denny e209ae81dd meta: ensure there's a single 'bun' import per file in zig (#10137)
* meta: ensure there's a single 'bun' import per file in zig

* undo this change in codegen
2024-04-09 22:41:07 -07:00

464 lines
19 KiB
Zig

// 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
// - Windows can be configured to return []const u16
const builtin = @import("builtin");
const std = @import("std");
const os = std.os;
const Dir = std.fs.Dir;
const JSC = bun.JSC;
const PathString = JSC.PathString;
const bun = @import("root").bun;
const IteratorError = error{ AccessDenied, SystemResources } || os.UnexpectedError;
const mem = std.mem;
const strings = bun.strings;
const Maybe = JSC.Maybe;
const File = std.fs.File;
pub const IteratorResult = struct {
name: PathString,
kind: Entry.Kind,
};
const Result = Maybe(?IteratorResult);
const IteratorResultW = struct {
// fake PathString to have less `if (Environment.isWindows) ...`
name: struct {
data: [:0]const u16,
pub fn slice(this: @This()) []const u16 {
return this.data;
}
pub fn sliceAssumeZ(this: @This()) [:0]const u16 {
return this.data;
}
},
kind: Entry.Kind,
};
const ResultW = Maybe(?IteratorResultW);
const Entry = JSC.Node.Dirent;
pub const Iterator = NewIterator(false);
pub const IteratorW = NewIterator(true);
pub fn NewIterator(comptime use_windows_ospath: bool) type {
return 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;
fn fd(self: *Self) os.fd_t {
return self.dir.fd;
}
/// 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 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 = @as(usize, @intCast(rc));
}
const darwin_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
const next_index = self.index + darwin_entry.reclen();
self.index = next_index;
const name = @as([*]u8, @ptrCast(&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.block_device,
os.DT.CHR => Entry.Kind.character_device,
os.DT.DIR => Entry.Kind.directory,
os.DT.FIFO => Entry.Kind.named_pipe,
os.DT.LNK => Entry.Kind.sym_link,
os.DT.REG => Entry.Kind.file,
os.DT.SOCK => Entry.Kind.unix_domain_socket,
os.DT.WHT => Entry.Kind.whiteout,
else => Entry.Kind.unknown,
};
return .{
.result = IteratorResult{
.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;
fn fd(self: *Self) os.fd_t {
return self.dir.fd;
}
/// 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 = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
const next_index = self.index + linux_entry.reclen();
self.index = next_index;
const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&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.block_device,
linux.DT.CHR => Entry.Kind.character_device,
linux.DT.DIR => Entry.Kind.directory,
linux.DT.FIFO => Entry.Kind.named_pipe,
linux.DT.LNK => Entry.Kind.sym_link,
linux.DT.REG => Entry.Kind.file,
linux.DT.SOCK => Entry.Kind.unix_domain_socket,
else => Entry.Kind.unknown,
};
return .{
.result = IteratorResult{
.name = PathString.init(name),
.kind = entry_kind,
},
};
}
}
},
.windows => struct {
dir: Dir,
buf: [8192]u8 align(@alignOf(os.windows.FILE_DIRECTORY_INFORMATION)),
index: usize,
end_index: usize,
first: bool,
name_data: if (use_windows_ospath) [257]u16 else [513]u8,
const Self = @This();
pub const Error = IteratorError;
const ResultT = if (use_windows_ospath) ResultW else Result;
fn fd(self: *Self) os.fd_t {
return self.dir.fd;
}
/// 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) ResultT {
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,
.FileDirectoryInformation,
w.FALSE,
null,
if (self.first) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
);
self.first = false;
if (io.Information == 0) {
bun.sys.syslog("NtQueryDirectoryFile({}) = 0", .{bun.toFD(self.dir.fd)});
return .{ .result = null };
}
self.index = 0;
self.end_index = io.Information;
// If the handle is not a directory, we'll get STATUS_INVALID_PARAMETER.
if (rc == .INVALID_PARAMETER) {
bun.sys.syslog("NtQueryDirectoryFile({}) = {s}", .{ bun.toFD(self.dir.fd), @tagName(rc) });
return .{
.err = .{
.errno = @intFromEnum(bun.C.SystemErrno.ENOTDIR),
.syscall = .NtQueryDirectoryFile,
},
};
}
if (rc == .NO_MORE_FILES) {
bun.sys.syslog("NtQueryDirectoryFile({}) = {s}", .{ bun.toFD(self.dir.fd), @tagName(rc) });
self.end_index = self.index;
return .{ .result = null };
}
if (rc != .SUCCESS) {
bun.sys.syslog("NtQueryDirectoryFile({}) = {s}", .{ bun.toFD(self.dir.fd), @tagName(rc) });
if ((bun.windows.Win32Error.fromNTStatus(rc).toSystemErrno())) |errno| {
return .{
.err = .{
.errno = @intFromEnum(errno),
.syscall = .NtQueryDirectoryFile,
},
};
}
return .{
.err = .{
.errno = @intFromEnum(bun.C.SystemErrno.EUNKNOWN),
.syscall = .NtQueryDirectoryFile,
},
};
}
bun.sys.syslog("NtQueryDirectoryFile({}) = {d}", .{ bun.toFD(self.dir.fd), self.end_index });
}
const dir_info: *w.FILE_DIRECTORY_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
if (dir_info.NextEntryOffset != 0) {
self.index += dir_info.NextEntryOffset;
} else {
self.index = self.buf.len;
}
const dir_info_name = @as([*]const u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
if (mem.eql(u16, dir_info_name, &[_]u16{'.'}) or mem.eql(u16, dir_info_name, &[_]u16{ '.', '.' }))
continue;
const kind = blk: {
const attrs = dir_info.FileAttributes;
const isdir = attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0;
const islink = attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0;
// on windows symlinks can be directories, too. We prioritize the
// "sym_link" kind over the "directory" kind
if (islink) break :blk Entry.Kind.sym_link;
if (isdir) break :blk Entry.Kind.directory;
break :blk Entry.Kind.file;
};
if (use_windows_ospath) {
const length = dir_info.FileNameLength / 2;
@memcpy(self.name_data[0..length], @as([*]u16, @ptrCast(&dir_info.FileName))[0..length]);
self.name_data[length] = 0;
const name_utf16le = self.name_data[0..length :0];
return .{
.result = IteratorResultW{
.kind = kind,
.name = .{ .data = name_utf16le },
},
};
}
// Trust that Windows gives us valid UTF-16LE
const name_utf8 = strings.fromWPath(self.name_data[0..], dir_info_name);
return .{
.result = IteratorResult{
.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;
fn fd(self: *Self) os.fd_t {
return self.dir.fd;
}
/// 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.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 = @as(*align(1) w.dirent_t, @ptrCast(&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.block_device,
.CHARACTER_DEVICE => Entry.Kind.character_device,
.DIRECTORY => Entry.Kind.directory,
.SYMBOLIC_LINK => Entry.Kind.sym_link,
.REGULAR_FILE => Entry.Kind.file,
.SOCKET_STREAM, .SOCKET_DGRAM => Entry.Kind.unix_domain_socket,
else => Entry.Kind.unknown,
};
return IteratorResult{
.name = name,
.kind = entry_kind,
};
}
}
},
else => @compileError("unimplemented"),
};
}
pub const PathType = enum { u8, u16 };
pub fn NewWrappedIterator(comptime path_type: PathType) type {
const IteratorType = if (path_type == .u16) IteratorW else Iterator;
const ResultType = if (path_type == .u16) ResultW else Result;
return struct {
iter: IteratorType,
const Self = @This();
pub inline fn next(self: *Self) ResultType {
return self.iter.next();
}
pub inline fn fd(self: *Self) os.fd_t {
return self.iter.fd();
}
pub const Error = IteratorError;
pub fn init(dir: Dir) Self {
return Self{
.iter = switch (builtin.os.tag) {
.macos,
.ios,
.freebsd,
.netbsd,
.dragonfly,
.openbsd,
.solaris,
=> IteratorType{
.dir = dir,
.seek = 0,
.index = 0,
.end_index = 0,
.buf = undefined,
},
.linux, .haiku => IteratorType{
.dir = dir,
.index = 0,
.end_index = 0,
.buf = undefined,
},
.windows => IteratorType{
.dir = dir,
.index = 0,
.end_index = 0,
.first = true,
.buf = undefined,
.name_data = undefined,
},
.wasi => IteratorType{
.dir = dir,
.cookie = os.wasi.DIRCOOKIE_START,
.index = 0,
.end_index = 0,
.buf = undefined,
},
else => @compileError("unimplemented"),
},
};
}
};
}
pub const WrappedIterator = NewWrappedIterator(.u8);
pub const WrappedIteratorW = NewWrappedIterator(.u16);
pub fn iterate(self: Dir, comptime path_type: PathType) NewWrappedIterator(path_type) {
return NewWrappedIterator(path_type).init(self);
}