authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-10 23:55:09-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:09-08:00
log91fa252cf20554495e719cacf83f488bef740a67
tree7989142e4aa9e79b89106076960051c8fa978c02
parent94ef56ee26558daea3c7a1468f898c94735d1658

std.Io: implement dir reading for BSDs


3 files changed, 297 insertions(+), 10 deletions(-)

lib/std/Io.zig+2-6
...@@ -2183,16 +2183,12 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {...@@ -2183,16 +2183,12 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
2183/// See also:2183/// See also:
2184/// * `tryLockStderrWriter`2184/// * `tryLockStderrWriter`
2185pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer {2185pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer {
2186 const result = try io.vtable.lockStderrWriter(io.userdata, buffer);2186 return io.vtable.lockStderrWriter(io.userdata, buffer);
2187 result.io = io;
2188 return result;
2189}2187}
21902188
2191/// Same as `lockStderrWriter` but uncancelable and non-blocking.2189/// Same as `lockStderrWriter` but uncancelable and non-blocking.
2192pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer {2190pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer {
2193 const result = io.vtable.tryLockStderrWriter(io.userdata, buffer) orelse return null;2191 return io.vtable.tryLockStderrWriter(io.userdata, buffer);
2194 result.io = io;
2195 return result;
2196}2192}
21972193
2198pub fn unlockStderrWriter(io: Io) void {2194pub fn unlockStderrWriter(io: Io) void {
lib/std/Io/Dir.zig+6
...@@ -143,6 +143,12 @@ pub const Reader = struct {...@@ -143,6 +143,12 @@ pub const Reader = struct {
143 if (r.state == .finished) return null;143 if (r.state == .finished) return null;
144 }144 }
145 }145 }
146
147 pub fn reset(r: *Reader) void {
148 r.state = .reset;
149 r.index = 0;
150 r.end = 0;
151 }
146};152};
147153
148/// This API is designed for convenience rather than performance:154/// This API is designed for convenience rather than performance:
lib/std/Io/Threaded.zig+289-4
...@@ -1870,10 +1870,14 @@ fn dirStatPathPosix(...@@ -1870,10 +1870,14 @@ fn dirStatPathPosix(
18701870
1871 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;1871 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
18721872
1873 return posixStatPath(current_thread, dir.handle, sub_path_posix, flags);
1874}
1875
1876fn posixStatPath(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatPathError!File.Stat {
1873 try current_thread.beginSyscall();1877 try current_thread.beginSyscall();
1874 while (true) {1878 while (true) {
1875 var stat = std.mem.zeroes(posix.Stat);1879 var stat = std.mem.zeroes(posix.Stat);
1876 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) {1880 switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) {
1877 .SUCCESS => {1881 .SUCCESS => {
1878 current_thread.endSyscall();1882 current_thread.endSyscall();
1879 return statFromPosix(&stat);1883 return statFromPosix(&stat);
...@@ -3273,6 +3277,12 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {...@@ -3273,6 +3277,12 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
32733277
3274const dirRead = switch (native_os) {3278const dirRead = switch (native_os) {
3275 .linux => dirReadLinux,3279 .linux => dirReadLinux,
3280 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => dirReadDarwin,
3281 .freebsd, .netbsd, .dragonfly, .openbsd => dirReadBsd,
3282 .illumos => dirReadIllumos,
3283 .haiku => dirReadHaiku,
3284 .windows => dirReadWindows,
3285 .wasi => dirReadWasi,
3276 else => dirReadUnimplemented,3286 else => dirReadUnimplemented,
3277};3287};
32783288
...@@ -3339,7 +3349,6 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir...@@ -3339,7 +3349,6 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
3339 dr.index = next_index;3349 dr.index = next_index;
33403350
3341 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);3351 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);
3342 // Skip "." and ".." entries.
3343 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;3352 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
33443353
3345 const entry_kind: File.Kind = switch (linux_entry.type) {3354 const entry_kind: File.Kind = switch (linux_entry.type) {
...@@ -3362,6 +3371,278 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir...@@ -3362,6 +3371,278 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
3362 return buffer_index;3371 return buffer_index;
3363}3372}
33643373
3374fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3375 const t: *Threaded = @ptrCast(@alignCast(userdata));
3376 const current_thread = Thread.getCurrent(t);
3377 const Header = extern struct {
3378 seek: i64,
3379 };
3380 const header: *Header = @ptrCast(&dr.buffer);
3381 const header_end: usize = @sizeOf(Header);
3382 if (dr.index < header_end) {
3383 // Initialize header.
3384 dr.index = header_end;
3385 header.* = .{ .seek = 0 };
3386 }
3387 var buffer_index: usize = 0;
3388 while (buffer.len - buffer_index != 0) {
3389 if (dr.end - dr.index == 0) {
3390 // Refill the buffer, unless we've already created references to
3391 // buffered data.
3392 if (buffer_index != 0) break;
3393 if (dr.state == .reset) {
3394 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3395 error.Unseekable => return error.Unexpected,
3396 else => |e| return e,
3397 };
3398 dr.state = .reading;
3399 }
3400 const dents_buffer = dr.buffer[header_end..];
3401 try current_thread.beginSyscall();
3402 const n: usize = while (true) {
3403 const rc = posix.system.getdirentries(dr.dir.fd, dents_buffer.ptr, dents_buffer.len, &header.seek);
3404 switch (posix.errno(rc)) {
3405 .SUCCESS => {
3406 current_thread.endSyscall();
3407 break @intCast(rc);
3408 },
3409 .INTR => {
3410 try current_thread.checkCancel();
3411 continue;
3412 },
3413 .CANCELED => return current_thread.endSyscallCanceled(),
3414 else => |e| {
3415 current_thread.endSyscall();
3416 switch (e) {
3417 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
3418 .FAULT => |err| return errnoBug(err),
3419 .NOTDIR => |err| return errnoBug(err),
3420 .INVAL => |err| return errnoBug(err),
3421 else => |err| return posix.unexpectedErrno(err),
3422 }
3423 },
3424 }
3425 };
3426 if (n == 0) {
3427 dr.state = .finished;
3428 return 0;
3429 }
3430 dr.index = header_end;
3431 dr.end = header_end + n;
3432 }
3433 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
3434 const next_index = dr.index + darwin_entry.reclen;
3435 dr.index = next_index;
3436
3437 const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
3438 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0))
3439 continue;
3440
3441 const entry_kind: File.Kind = switch (darwin_entry.type) {
3442 posix.DT.BLK => .block_device,
3443 posix.DT.CHR => .character_device,
3444 posix.DT.DIR => .directory,
3445 posix.DT.FIFO => .named_pipe,
3446 posix.DT.LNK => .sym_link,
3447 posix.DT.REG => .file,
3448 posix.DT.SOCK => .unix_domain_socket,
3449 posix.DT.WHT => .whiteout,
3450 else => .unknown,
3451 };
3452 buffer[buffer_index] = .{
3453 .name = name,
3454 .kind = entry_kind,
3455 .inode = darwin_entry.ino,
3456 };
3457 buffer_index += 1;
3458 }
3459 return buffer_index;
3460}
3461
3462fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3463 const t: *Threaded = @ptrCast(@alignCast(userdata));
3464 const current_thread = Thread.getCurrent(t);
3465 var buffer_index: usize = 0;
3466 while (buffer.len - buffer_index != 0) {
3467 if (dr.end - dr.index == 0) {
3468 // Refill the buffer, unless we've already created references to
3469 // buffered data.
3470 if (buffer_index != 0) break;
3471 if (dr.state == .reset) {
3472 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3473 error.Unseekable => return error.Unexpected,
3474 else => |e| return e,
3475 };
3476 dr.state = .reading;
3477 }
3478 try current_thread.beginSyscall();
3479 const n: usize = while (true) {
3480 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
3481 switch (posix.errno(rc)) {
3482 .SUCCESS => {
3483 current_thread.endSyscall();
3484 break @intCast(rc);
3485 },
3486 .INTR => {
3487 try current_thread.checkCancel();
3488 continue;
3489 },
3490 .CANCELED => return current_thread.endSyscallCanceled(),
3491 else => |e| {
3492 current_thread.endSyscall();
3493 switch (e) {
3494 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
3495 .FAULT => |err| return errnoBug(err),
3496 .NOTDIR => |err| return errnoBug(err),
3497 .INVAL => |err| return errnoBug(err),
3498 // Introduced in freebsd 13.2: directory unlinked
3499 // but still open. To be consistent, iteration ends
3500 // if the directory being iterated is deleted
3501 // during iteration.
3502 .NOENT => {
3503 dr.state = .finished;
3504 return 0;
3505 },
3506 else => |err| return posix.unexpectedErrno(err),
3507 }
3508 },
3509 }
3510 };
3511 if (n == 0) {
3512 dr.state = .finished;
3513 return 0;
3514 }
3515 dr.index = 0;
3516 dr.end = n;
3517 }
3518 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
3519 const next_index = dr.index +
3520 if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
3521 dr.index = next_index;
3522
3523 const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
3524
3525 const skip_zero_fileno = switch (native_os) {
3526 // fileno=0 is used to mark invalid entries or deleted files.
3527 .openbsd, .netbsd => true,
3528 else => false,
3529 };
3530 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or
3531 (skip_zero_fileno and bsd_entry.fileno == 0))
3532 {
3533 continue;
3534 }
3535
3536 const entry_kind: File.Kind = switch (bsd_entry.type) {
3537 posix.DT.BLK => .block_device,
3538 posix.DT.CHR => .character_device,
3539 posix.DT.DIR => .directory,
3540 posix.DT.FIFO => .named_pipe,
3541 posix.DT.LNK => .sym_link,
3542 posix.DT.REG => .file,
3543 posix.DT.SOCK => .unix_domain_socket,
3544 posix.DT.WHT => .whiteout,
3545 else => .unknown,
3546 };
3547 buffer[buffer_index] = .{
3548 .name = name,
3549 .kind = entry_kind,
3550 .inode = bsd_entry.fileno,
3551 };
3552 buffer_index += 1;
3553 }
3554 return buffer_index;
3555}
3556
3557fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3558 const t: *Threaded = @ptrCast(@alignCast(userdata));
3559 const current_thread = Thread.getCurrent(t);
3560 var buffer_index: usize = 0;
3561 while (buffer.len - buffer_index != 0) {
3562 if (dr.end - dr.index == 0) {
3563 // Refill the buffer, unless we've already created references to
3564 // buffered data.
3565 if (buffer_index != 0) break;
3566 if (dr.state == .reset) {
3567 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3568 error.Unseekable => return error.Unexpected,
3569 else => |e| return e,
3570 };
3571 dr.state = .reading;
3572 }
3573 try current_thread.beginSyscall();
3574 const n: usize = while (true) {
3575 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
3576 switch (posix.errno(rc)) {
3577 .SUCCESS => {
3578 current_thread.endSyscall();
3579 break rc;
3580 },
3581 .INTR => {
3582 try current_thread.checkCancel();
3583 continue;
3584 },
3585 .CANCELED => return current_thread.endSyscallCanceled(),
3586 else => |e| {
3587 current_thread.endSyscall();
3588 switch (e) {
3589 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
3590 .FAULT => |err| return errnoBug(err),
3591 .NOTDIR => |err| return errnoBug(err),
3592 .INVAL => |err| return errnoBug(err),
3593 else => |err| return posix.unexpectedErrno(err),
3594 }
3595 },
3596 }
3597 };
3598 if (n == 0) {
3599 dr.state = .finished;
3600 return 0;
3601 }
3602 dr.index = 0;
3603 dr.end = n;
3604 }
3605 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
3606 const next_index = dr.index + entry.reclen;
3607 dr.index = next_index;
3608
3609 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
3610 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
3611
3612 // illumos dirent doesn't expose type, so we have to call stat to get it.
3613 const stat = try posixStatPath(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW);
3614
3615 buffer[buffer_index] = .{
3616 .name = name,
3617 .kind = stat.kind,
3618 .inode = entry.ino,
3619 };
3620 buffer_index += 1;
3621 }
3622 return buffer_index;
3623}
3624
3625fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3626 _ = userdata;
3627 _ = dr;
3628 _ = buffer;
3629 @panic("TODO");
3630}
3631
3632fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3633 _ = userdata;
3634 _ = dr;
3635 _ = buffer;
3636 @panic("TODO");
3637}
3638
3639fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3640 _ = userdata;
3641 _ = dr;
3642 _ = buffer;
3643 @panic("TODO");
3644}
3645
3365fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {3646fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3366 _ = userdata;3647 _ = userdata;
3367 _ = dir_reader;3648 _ = dir_reader;
...@@ -9628,8 +9909,10 @@ fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Wri...@@ -9628,8 +9909,10 @@ fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Wri
9628 // Only global mutex since this is Threaded.9909 // Only global mutex since this is Threaded.
9629 Io.stderr_thread_mutex.lock();9910 Io.stderr_thread_mutex.lock();
9630 if (!t.stderr_writer_initialized) {9911 if (!t.stderr_writer_initialized) {
9912 const io_t = ioBasic(t);
9631 if (is_windows) t.stderr_writer.file = .stderr();9913 if (is_windows) t.stderr_writer.file = .stderr();
9632 t.stderr_writer.mode = try .detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple);9914 t.stderr_writer.io = io_t;
9915 t.stderr_writer.mode = try .detect(io_t, t.stderr_writer.file, true, .streaming_simple);
9633 t.stderr_writer_initialized = true;9916 t.stderr_writer_initialized = true;
9634 }9917 }
9635 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};9918 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
...@@ -9643,8 +9926,10 @@ fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer {...@@ -9643,8 +9926,10 @@ fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer {
9643 // Only global mutex since this is Threaded.9926 // Only global mutex since this is Threaded.
9644 if (!Io.stderr_thread_mutex.tryLock()) return null;9927 if (!Io.stderr_thread_mutex.tryLock()) return null;
9645 if (!t.stderr_writer_initialized) {9928 if (!t.stderr_writer_initialized) {
9929 const io_t = ioBasic(t);
9646 if (is_windows) t.stderr_writer.file = .stderr();9930 if (is_windows) t.stderr_writer.file = .stderr();
9647 t.stderr_writer.mode = File.Writer.Mode.detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple) catch9931 t.stderr_writer.io = io_t;
9932 t.stderr_writer.mode = File.Writer.Mode.detect(io_t, t.stderr_writer.file, true, .streaming_simple) catch
9648 return null;9933 return null;
9649 t.stderr_writer_initialized = true;9934 t.stderr_writer_initialized = true;
9650 }9935 }