diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2025-12-10 22:21:51 -0800 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2025-12-23 22:15:09 -0800 |
| commit | 7bc0166b7c34ac0120f50aceb7132ffaa4aeec83 (patch) | |
| tree | e9fff72403af7557e5b7b4aa3aa7cc781981d3e1 /lib | |
| parent | b042e935228db5d46271d4d3d17afeb9ba5d7ce3 (diff) | |
| download | zig-7bc0166b7c34ac0120f50aceb7132ffaa4aeec83.tar.gz zig-7bc0166b7c34ac0120f50aceb7132ffaa4aeec83.zip | |
std.Io: implement dirRead for Linux
unfortunately, Io.Dir.SelectiveWalker is copying the iterator which
has multiple problems
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/std/Io/Dir.zig | 14 | ||||
| -rw-r--r-- | lib/std/Io/Threaded.zig | 181 | ||||
| -rw-r--r-- | lib/std/posix.zig | 171 |
3 files changed, 151 insertions, 215 deletions
diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index a9d28440eb..5da2b16f82 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -184,6 +184,11 @@ pub const SelectiveWalker = struct { pub const Error = Io.Dir.Iterator.Error || Allocator.Error; + const StackItem = struct { + iter: Dir.Iterator, + dirname_len: usize, + }; + /// After each call to this function, and on deinit(), the memory returned /// from this function becomes invalid. A copy must be made in order to keep /// a reference to the path. @@ -268,7 +273,7 @@ pub const SelectiveWalker = struct { /// Recursively iterates over a directory, but requires the user to /// opt-in to recursing into each directory entry. /// -/// `dir` must have been opened with `OpenOptions{.iterate = true}`. +/// `dir` must have been opened with `OpenOptions.iterate` set to `true`. /// /// `Walker.deinit` releases allocated memory and directory handles. /// @@ -278,7 +283,7 @@ pub const SelectiveWalker = struct { /// /// See also `walk`. pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker { - var stack: std.ArrayList(Walker.StackItem) = .empty; + var stack: std.ArrayList(SelectiveWalker.StackItem) = .empty; try stack.append(allocator, .{ .iter = dir.iterate(), @@ -312,11 +317,6 @@ pub const Walker = struct { } }; - const StackItem = struct { - iter: Dir.Iterator, - dirname_len: usize, - }; - /// After each call to this function, and on deinit(), the memory returned /// from this function becomes invalid. A copy must be made in order to keep /// a reference to the path. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 41e93cee5b..55cc21db0e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3271,12 +3271,113 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { for (dirs) |dir| posix.close(dir.handle); } -fn dirRead(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { +const dirRead = switch (native_os) { + .linux => dirReadLinux, + else => dirReadUnimplemented, +}; + +fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + const linux = std.os.linux; const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; + const current_thread = Thread.getCurrent(t); + const Header = extern struct { + fill_end: usize, + }; + const header: *Header = @ptrCast(&dr.buffer); + const header_end: usize = @sizeOf(Header); + if (dr.index < header_end) { + // Initialize header. + dr.index = header_end; + header.* = .{ .fill_end = header_end }; + } + var buffer_index: usize = 0; + while (buffer.len - buffer_index != 0) { + if (header.fill_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 = while (true) { + const rc = linux.getdents64(dr.dir.handle, dents_buffer.ptr, dents_buffer.len); + switch (linux.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), + // 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. + .NOENT => { + dr.state = .finished; + return 0; + }, + .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net. + .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory. + else => |err| return posix.unexpectedErrno(err), + } + }, + } + }; + if (n == 0) { + dr.state = .finished; + return 0; + } + dr.index = header_end; + header.fill_end = header_end + n; + } + const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]); + const next_index = dr.index + linux_entry.reclen; + 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) { + 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, + }; + buffer[buffer_index] = .{ + .name = name, + .kind = entry_kind, + .inode = linux_entry.ino, + }; + buffer_index += 1; + } + return buffer_index; +} + +fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + _ = userdata; _ = dir_reader; _ = buffer; - @panic("TODO"); + return error.Unimplemented; } const dirRealPath = switch (native_os) { @@ -5908,11 +6009,16 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi const current_thread = Thread.getCurrent(t); const fd = file.handle; - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + if (native_os == .windows) { + try current_thread.checkCancel(); + return windows.SetFilePointerEx_BEGIN(fd, offset); + } + + if (native_os == .wasi and !builtin.link_libc) { try current_thread.beginSyscall(); while (true) { - var result: u64 = undefined; - switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { + var new_offset: std.os.wasi.filesize_t = undefined; + switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { .SUCCESS => { current_thread.endSyscall(); return; @@ -5930,6 +6036,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi .OVERFLOW => return error.Unseekable, .SPIPE => return error.Unseekable, .NXIO => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, else => |err| return posix.unexpectedErrno(err), } }, @@ -5937,40 +6044,40 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi } } - if (native_os == .windows) { - try current_thread.checkCancel(); - return windows.SetFilePointerEx_BEGIN(fd, offset); - } + if (posix.SEEK == void) return error.Unseekable; - if (native_os == .wasi and !builtin.link_libc) while (true) { - var new_offset: std.os.wasi.filesize_t = undefined; + return posixSeekTo(current_thread, fd, offset); +} + +fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekError!void { + if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { try current_thread.beginSyscall(); - switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { - .SUCCESS => { - current_thread.endSyscall(); - return; - }, - .INTR => { - try current_thread.checkCancel(); - continue; - }, - .CANCELED => return current_thread.endSyscallCanceled(), - else => |e| { - current_thread.endSyscall(); - switch (e) { - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), - } - }, + while (true) { + var result: u64 = undefined; + switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } - }; - - if (posix.SEEK == void) return error.Unseekable; + } try current_thread.beginSyscall(); while (true) { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 609cf62e82..aa270e611d 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -2986,177 +2986,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { } } -pub const SeekError = std.Io.File.SeekError; - -pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, offset, &result, SEEK.SET))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (native_os == .windows) { - return windows.SetFilePointerEx_BEGIN(fd, offset); - } - if (native_os == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.SET))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Repositions read/write file offset relative to the current offset. -pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.CUR))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (native_os == .windows) { - return windows.SetFilePointerEx_CURRENT(fd, offset); - } - if (native_os == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.CUR))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Repositions read/write file offset relative to the end. -pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.END))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (native_os == .windows) { - return windows.SetFilePointerEx_END(fd, offset); - } - if (native_os == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, offset, .END, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.END))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Returns the read/write file offset relative to the beginning. -pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, 0, &result, SEEK.CUR))) { - .SUCCESS => return result, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (native_os == .windows) { - return windows.SetFilePointerEx_CURRENT_get(fd); - } - if (native_os == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, 0, .CUR, &new_offset)) { - .SUCCESS => return new_offset, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - const rc = lseek_sym(fd, 0, SEEK.CUR); - switch (errno(rc)) { - .SUCCESS => return @bitCast(rc), - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - pub const FcntlError = error{ PermissionDenied, FileBusy, |
