aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2025-12-10 23:55:09 -0800
committerAndrew Kelley <andrew@ziglang.org>2025-12-23 22:15:09 -0800
commit91fa252cf20554495e719cacf83f488bef740a67 (patch)
tree7989142e4aa9e79b89106076960051c8fa978c02
parent94ef56ee26558daea3c7a1468f898c94735d1658 (diff)
downloadzig-91fa252cf20554495e719cacf83f488bef740a67.tar.gz
zig-91fa252cf20554495e719cacf83f488bef740a67.zip
std.Io: implement dir reading for BSDs
-rw-r--r--lib/std/Io.zig8
-rw-r--r--lib/std/Io/Dir.zig6
-rw-r--r--lib/std/Io/Threaded.zig293
3 files changed, 297 insertions, 10 deletions
diff --git a/lib/std/Io.zig b/lib/std/Io.zig
index fdd813536c..9db215c747 100644
--- a/lib/std/Io.zig
+++ b/lib/std/Io.zig
@@ -2183,16 +2183,12 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
/// See also:
/// * `tryLockStderrWriter`
pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer {
- const result = try io.vtable.lockStderrWriter(io.userdata, buffer);
- result.io = io;
- return result;
+ return io.vtable.lockStderrWriter(io.userdata, buffer);
}
/// Same as `lockStderrWriter` but uncancelable and non-blocking.
pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer {
- const result = io.vtable.tryLockStderrWriter(io.userdata, buffer) orelse return null;
- result.io = io;
- return result;
+ return io.vtable.tryLockStderrWriter(io.userdata, buffer);
}
pub fn unlockStderrWriter(io: Io) void {
diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig
index 2c6bee99a5..4e70a7be89 100644
--- a/lib/std/Io/Dir.zig
+++ b/lib/std/Io/Dir.zig
@@ -143,6 +143,12 @@ pub const Reader = struct {
if (r.state == .finished) return null;
}
}
+
+ pub fn reset(r: *Reader) void {
+ r.state = .reset;
+ r.index = 0;
+ r.end = 0;
+ }
};
/// This API is designed for convenience rather than performance:
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
index 05b00b555f..61974f4717 100644
--- a/lib/std/Io/Threaded.zig
+++ b/lib/std/Io/Threaded.zig
@@ -1870,10 +1870,14 @@ fn dirStatPathPosix(
const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
+ return posixStatPath(current_thread, dir.handle, sub_path_posix, flags);
+}
+
+fn posixStatPath(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatPathError!File.Stat {
try current_thread.beginSyscall();
while (true) {
var stat = std.mem.zeroes(posix.Stat);
- switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) {
+ switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) {
.SUCCESS => {
current_thread.endSyscall();
return statFromPosix(&stat);
@@ -3273,6 +3277,12 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
const dirRead = switch (native_os) {
.linux => dirReadLinux,
+ .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => dirReadDarwin,
+ .freebsd, .netbsd, .dragonfly, .openbsd => dirReadBsd,
+ .illumos => dirReadIllumos,
+ .haiku => dirReadHaiku,
+ .windows => dirReadWindows,
+ .wasi => dirReadWasi,
else => dirReadUnimplemented,
};
@@ -3339,7 +3349,6 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
dr.index = next_index;
const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);
- // Skip "." and ".." entries.
if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
const entry_kind: File.Kind = switch (linux_entry.type) {
@@ -3362,6 +3371,278 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
return buffer_index;
}
+fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const current_thread = Thread.getCurrent(t);
+ const Header = extern struct {
+ seek: i64,
+ };
+ const header: *Header = @ptrCast(&dr.buffer);
+ const header_end: usize = @sizeOf(Header);
+ if (dr.index < header_end) {
+ // Initialize header.
+ dr.index = header_end;
+ header.* = .{ .seek = 0 };
+ }
+ var buffer_index: usize = 0;
+ while (buffer.len - buffer_index != 0) {
+ if (dr.end - dr.index == 0) {
+ // Refill the buffer, unless we've already created references to
+ // buffered data.
+ if (buffer_index != 0) break;
+ if (dr.state == .reset) {
+ posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
+ error.Unseekable => return error.Unexpected,
+ else => |e| return e,
+ };
+ dr.state = .reading;
+ }
+ const dents_buffer = dr.buffer[header_end..];
+ try current_thread.beginSyscall();
+ const n: usize = while (true) {
+ const rc = posix.system.getdirentries(dr.dir.fd, dents_buffer.ptr, dents_buffer.len, &header.seek);
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ current_thread.endSyscall();
+ break @intCast(rc);
+ },
+ .INTR => {
+ try current_thread.checkCancel();
+ continue;
+ },
+ .CANCELED => return current_thread.endSyscallCanceled(),
+ else => |e| {
+ current_thread.endSyscall();
+ switch (e) {
+ .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
+ .FAULT => |err| return errnoBug(err),
+ .NOTDIR => |err| return errnoBug(err),
+ .INVAL => |err| return errnoBug(err),
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
+ };
+ if (n == 0) {
+ dr.state = .finished;
+ return 0;
+ }
+ dr.index = header_end;
+ dr.end = header_end + n;
+ }
+ const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
+ const next_index = dr.index + darwin_entry.reclen;
+ dr.index = next_index;
+
+ const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
+ if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0))
+ continue;
+
+ const entry_kind: File.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,
+ };
+ buffer[buffer_index] = .{
+ .name = name,
+ .kind = entry_kind,
+ .inode = darwin_entry.ino,
+ };
+ buffer_index += 1;
+ }
+ return buffer_index;
+}
+
+fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const current_thread = Thread.getCurrent(t);
+ var buffer_index: usize = 0;
+ while (buffer.len - buffer_index != 0) {
+ if (dr.end - dr.index == 0) {
+ // Refill the buffer, unless we've already created references to
+ // buffered data.
+ if (buffer_index != 0) break;
+ if (dr.state == .reset) {
+ posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
+ error.Unseekable => return error.Unexpected,
+ else => |e| return e,
+ };
+ dr.state = .reading;
+ }
+ try current_thread.beginSyscall();
+ const n: usize = while (true) {
+ const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ current_thread.endSyscall();
+ break @intCast(rc);
+ },
+ .INTR => {
+ try current_thread.checkCancel();
+ continue;
+ },
+ .CANCELED => return current_thread.endSyscallCanceled(),
+ else => |e| {
+ current_thread.endSyscall();
+ switch (e) {
+ .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
+ .FAULT => |err| return errnoBug(err),
+ .NOTDIR => |err| return errnoBug(err),
+ .INVAL => |err| return errnoBug(err),
+ // 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 => {
+ dr.state = .finished;
+ return 0;
+ },
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
+ };
+ if (n == 0) {
+ dr.state = .finished;
+ return 0;
+ }
+ dr.index = 0;
+ dr.end = n;
+ }
+ const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
+ const next_index = dr.index +
+ if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
+ dr.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 (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or
+ (skip_zero_fileno and bsd_entry.fileno == 0))
+ {
+ continue;
+ }
+
+ const entry_kind: File.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,
+ };
+ buffer[buffer_index] = .{
+ .name = name,
+ .kind = entry_kind,
+ .inode = bsd_entry.fileno,
+ };
+ buffer_index += 1;
+ }
+ return buffer_index;
+}
+
+fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const current_thread = Thread.getCurrent(t);
+ var buffer_index: usize = 0;
+ while (buffer.len - buffer_index != 0) {
+ if (dr.end - dr.index == 0) {
+ // Refill the buffer, unless we've already created references to
+ // buffered data.
+ if (buffer_index != 0) break;
+ if (dr.state == .reset) {
+ posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
+ error.Unseekable => return error.Unexpected,
+ else => |e| return e,
+ };
+ dr.state = .reading;
+ }
+ try current_thread.beginSyscall();
+ const n: usize = while (true) {
+ const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ current_thread.endSyscall();
+ break rc;
+ },
+ .INTR => {
+ try current_thread.checkCancel();
+ continue;
+ },
+ .CANCELED => return current_thread.endSyscallCanceled(),
+ else => |e| {
+ current_thread.endSyscall();
+ switch (e) {
+ .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
+ .FAULT => |err| return errnoBug(err),
+ .NOTDIR => |err| return errnoBug(err),
+ .INVAL => |err| return errnoBug(err),
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
+ };
+ if (n == 0) {
+ dr.state = .finished;
+ return 0;
+ }
+ dr.index = 0;
+ dr.end = n;
+ }
+ const entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
+ const next_index = dr.index + entry.reclen;
+ dr.index = next_index;
+
+ const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
+ if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
+
+ // illumos dirent doesn't expose type, so we have to call stat to get it.
+ const stat = try posixStatPath(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW);
+
+ buffer[buffer_index] = .{
+ .name = name,
+ .kind = stat.kind,
+ .inode = entry.ino,
+ };
+ buffer_index += 1;
+ }
+ return buffer_index;
+}
+
+fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
+ _ = userdata;
+ _ = dr;
+ _ = buffer;
+ @panic("TODO");
+}
+
+fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
+ _ = userdata;
+ _ = dr;
+ _ = buffer;
+ @panic("TODO");
+}
+
+fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
+ _ = userdata;
+ _ = dr;
+ _ = buffer;
+ @panic("TODO");
+}
+
fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
_ = userdata;
_ = dir_reader;
@@ -9628,8 +9909,10 @@ fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Wri
// Only global mutex since this is Threaded.
Io.stderr_thread_mutex.lock();
if (!t.stderr_writer_initialized) {
+ const io_t = ioBasic(t);
if (is_windows) t.stderr_writer.file = .stderr();
- t.stderr_writer.mode = try .detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple);
+ t.stderr_writer.io = io_t;
+ t.stderr_writer.mode = try .detect(io_t, t.stderr_writer.file, true, .streaming_simple);
t.stderr_writer_initialized = true;
}
std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
@@ -9643,8 +9926,10 @@ fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer {
// Only global mutex since this is Threaded.
if (!Io.stderr_thread_mutex.tryLock()) return null;
if (!t.stderr_writer_initialized) {
+ const io_t = ioBasic(t);
if (is_windows) t.stderr_writer.file = .stderr();
- t.stderr_writer.mode = File.Writer.Mode.detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple) catch
+ t.stderr_writer.io = io_t;
+ t.stderr_writer.mode = File.Writer.Mode.detect(io_t, t.stderr_writer.file, true, .streaming_simple) catch
return null;
t.stderr_writer_initialized = true;
}