Source
pub const Iterator = switch (native_os) {
.macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
dir: Dir,
seek: i64,
buf: [1024]u8, // TODO align(@alignOf(posix.system.dirent)),
index: usize,
end_index: usize,
first_iter: bool,
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) Error!?Entry {
switch (native_os) {
.macos, .ios => return self.nextDarwin(),
.freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
.solaris, .illumos => return self.nextSolaris(),
else => @compileError("unimplemented"),
}
}
fn nextDarwin(self: *Self) !?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
self.first_iter = false;
}
const rc = posix.system.getdirentries(
self.dir.fd,
&self.buf,
self.buf.len,
&self.seek,
);
if (rc == 0) return null;
if (rc < 0) {
switch (posix.errno(rc)) {
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
else => |err| return posix.unexpectedErrno(err),
}
}
self.index = 0;
self.end_index = @as(usize, @intCast(rc));
}
const darwin_entry = @as(*align(1) posix.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.name))[0..darwin_entry.namlen];
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) {
continue :start_over;
}
const entry_kind: Entry.Kind = switch (darwin_entry.type) {
posix.DT.BLK => .block_device,
posix.DT.CHR => .character_device,
posix.DT.DIR => .directory,
posix.DT.FIFO => .named_pipe,
posix.DT.LNK => .sym_link,
posix.DT.REG => .file,
posix.DT.SOCK => .unix_domain_socket,
posix.DT.WHT => .whiteout,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
fn nextSolaris(self: *Self) !?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
self.first_iter = false;
}
const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
switch (posix.errno(rc)) {
.SUCCESS => {},
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
else => |err| return posix.unexpectedErrno(err),
}
if (rc == 0) return null;
self.index = 0;
self.end_index = @as(usize, @intCast(rc));
}
const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
const next_index = self.index + entry.reclen;
self.index = next_index;
const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
continue :start_over;
// Solaris dirent doesn't expose type, so we have to call stat to get it.
const stat_info = posix.fstatat(
self.dir.fd,
name,
posix.AT.SYMLINK_NOFOLLOW,
) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.SymLinkLoop => unreachable,
error.FileNotFound => unreachable, // lost the race
else => |e| return e,
};
const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) {
posix.S.IFIFO => .named_pipe,
posix.S.IFCHR => .character_device,
posix.S.IFDIR => .directory,
posix.S.IFBLK => .block_device,
posix.S.IFREG => .file,
posix.S.IFLNK => .sym_link,
posix.S.IFSOCK => .unix_domain_socket,
posix.S.IFDOOR => .door,
posix.S.IFPORT => .event_port,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
fn nextBsd(self: *Self) !?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
self.first_iter = false;
}
const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
switch (posix.errno(rc)) {
.SUCCESS => {},
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
// Introduced in freebsd 13.2: directory unlinked but still open.
// To be consistent, iteration ends if the directory being iterated is deleted during iteration.
.NOENT => return null,
else => |err| return posix.unexpectedErrno(err),
}
if (rc == 0) return null;
self.index = 0;
self.end_index = @as(usize, @intCast(rc));
}
const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
const next_index = self.index +
if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
self.index = next_index;
const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
const skip_zero_fileno = switch (native_os) {
// fileno=0 is used to mark invalid entries or deleted files.
.openbsd, .netbsd => true,
else => false,
};
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
(skip_zero_fileno and bsd_entry.fileno == 0))
{
continue :start_over;
}
const entry_kind: Entry.Kind = switch (bsd_entry.type) {
posix.DT.BLK => .block_device,
posix.DT.CHR => .character_device,
posix.DT.DIR => .directory,
posix.DT.FIFO => .named_pipe,
posix.DT.LNK => .sym_link,
posix.DT.REG => .file,
posix.DT.SOCK => .unix_domain_socket,
posix.DT.WHT => .whiteout,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.first_iter = true;
}
},
.haiku => struct {
dir: Dir,
buf: [@sizeOf(DirEnt) + posix.PATH_MAX]u8 align(@alignOf(DirEnt)),
offset: usize,
index: usize,
end_index: usize,
first_iter: bool,
const Self = @This();
const DirEnt = posix.system.DirEnt;
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) Error!?Entry {
while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
switch (@as(posix.E, @enumFromInt(posix.system._kern_rewind_dir(self.dir.fd)))) {
.SUCCESS => {},
.BADF => unreachable, // Dir is invalid
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
else => |err| return posix.unexpectedErrno(err),
}
self.first_iter = false;
}
const rc = posix.system._kern_read_dir(
self.dir.fd,
&self.buf,
self.buf.len,
self.buf.len / @sizeOf(DirEnt),
);
if (rc == 0) return null;
if (rc < 0) {
switch (@as(posix.E, @enumFromInt(rc))) {
.BADF => unreachable, // Dir is invalid
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
.OVERFLOW => unreachable,
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
else => |err| return posix.unexpectedErrno(err),
}
}
self.offset = 0;
self.index = 0;
self.end_index = @intCast(rc);
}
const dirent: *DirEnt = @ptrCast(@alignCast(&self.buf[self.offset]));
self.offset += dirent.reclen;
self.index += 1;
const name = mem.span(dirent.getName());
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or dirent.ino == 0) continue;
var stat_info: posix.Stat = undefined;
switch (@as(posix.E, @enumFromInt(posix.system._kern_read_stat(
self.dir.fd,
name,
false,
&stat_info,
0,
)))) {
.SUCCESS => {},
.INVAL => unreachable,
.BADF => unreachable, // Dir is invalid
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
.FAULT => unreachable,
.NAMETOOLONG => unreachable,
.LOOP => unreachable,
.NOENT => continue,
else => |err| return posix.unexpectedErrno(err),
}
const statmode = stat_info.mode & posix.S.IFMT;
const entry_kind: Entry.Kind = switch (statmode) {
posix.S.IFDIR => .directory,
posix.S.IFBLK => .block_device,
posix.S.IFCHR => .character_device,
posix.S.IFLNK => .sym_link,
posix.S.IFREG => .file,
posix.S.IFIFO => .named_pipe,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.first_iter = true;
}
},
.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: [1024]u8 align(@alignOf(linux.dirent64)),
index: usize,
end_index: usize,
first_iter: bool,
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) Error!?Entry {
return self.nextLinux() catch |err| switch (err) {
// To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
// This matches the behavior of non-Linux UNIX platforms.
error.DirNotFound => null,
else => |e| return e,
};
}
pub const ErrorLinux = error{DirNotFound} || IteratorError;
/// Implementation of `next` that can return `error.DirNotFound` if the directory being
/// iterated was deleted during iteration (this error is Linux specific).
pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
self.first_iter = false;
}
const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
switch (linux.E.init(rc)) {
.SUCCESS => {},
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
.FAULT => unreachable,
.NOTDIR => unreachable,
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
.INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
.ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
else => |err| return posix.unexpectedErrno(err),
}
if (rc == 0) return 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.name)), 0);
// skip . and .. entries
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
continue :start_over;
}
const entry_kind: Entry.Kind = switch (linux_entry.type) {
linux.DT.BLK => .block_device,
linux.DT.CHR => .character_device,
linux.DT.DIR => .directory,
linux.DT.FIFO => .named_pipe,
linux.DT.LNK => .sym_link,
linux.DT.REG => .file,
linux.DT.SOCK => .unix_domain_socket,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.first_iter = true;
}
},
.windows => struct {
dir: Dir,
buf: [1024]u8 align(@alignOf(windows.FILE_BOTH_DIR_INFORMATION)),
index: usize,
end_index: usize,
first_iter: bool,
name_data: [fs.max_name_bytes]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) Error!?Entry {
const w = windows;
while (true) {
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_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
);
self.first_iter = false;
if (io.Information == 0) return 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),
}
}
// While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
// this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
if (dir_info.NextEntryOffset != 0) {
self.index += dir_info.NextEntryOffset;
} else {
self.index = self.buf.len;
}
const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
continue;
const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
const name_wtf8 = self.name_data[0..name_wtf8_len];
const kind: Entry.Kind = blk: {
const attrs = dir_info.FileAttributes;
if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link;
break :blk .file;
};
return Entry{
.name = name_wtf8,
.kind = kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.first_iter = true;
}
},
.wasi => struct {
dir: Dir,
buf: [1024]u8, // TODO align(@alignOf(posix.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) Error!?Entry {
return self.nextWasi() catch |err| switch (err) {
// To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
// This matches the behavior of non-Linux UNIX platforms.
error.DirNotFound => null,
else => |e| return e,
};
}
pub const ErrorWasi = error{DirNotFound} || IteratorError;
/// Implementation of `next` that can return platform-dependent errors depending on the host platform.
/// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
/// iterated was deleted during iteration.
pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
// 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 = std.os.wasi;
start_over: while (true) {
// According to the WASI spec, the last entry might be truncated,
// so we need to check if the left buffer contains the whole dirent.
if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
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,
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
else => |err| return posix.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;
if (name_index + entry.namlen > self.end_index) {
// This case, the name is truncated, so we need to call readdir to store the entire name.
self.end_index = self.index; // Force fd_readdir in the next loop.
continue :start_over;
}
const name = self.buf[name_index .. name_index + entry.namlen];
const next_index = name_index + entry.namlen;
self.index = next_index;
self.cookie = entry.next;
// skip . and .. entries
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
continue :start_over;
}
const entry_kind: Entry.Kind = switch (entry.type) {
.BLOCK_DEVICE => .block_device,
.CHARACTER_DEVICE => .character_device,
.DIRECTORY => .directory,
.SYMBOLIC_LINK => .sym_link,
.REGULAR_FILE => .file,
.SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.cookie = std.os.wasi.DIRCOOKIE_START;
}
},
else => @compileError("unimplemented"),
}