authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-10 22:21:51-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:09-08:00
log7bc0166b7c34ac0120f50aceb7132ffaa4aeec83
treee9fff72403af7557e5b7b4aa3aa7cc781981d3e1
parentb042e935228db5d46271d4d3d17afeb9ba5d7ce3

std.Io: implement dirRead for Linux

unfortunately, Io.Dir.SelectiveWalker is copying the iterator which has multiple problems

3 files changed, 151 insertions(+), 215 deletions(-)

lib/std/Io/Dir.zig+7-7
......@@ -184,6 +184,11 @@ pub const SelectiveWalker = struct {
184184
185185 pub const Error = Io.Dir.Iterator.Error || Allocator.Error;
186186
187 const StackItem = struct {
188 iter: Dir.Iterator,
189 dirname_len: usize,
190 };
191
187192 /// After each call to this function, and on deinit(), the memory returned
188193 /// from this function becomes invalid. A copy must be made in order to keep
189194 /// a reference to the path.
......@@ -268,7 +273,7 @@ pub const SelectiveWalker = struct {
268273/// Recursively iterates over a directory, but requires the user to
269274/// opt-in to recursing into each directory entry.
270275///
271/// `dir` must have been opened with `OpenOptions{.iterate = true}`.
276/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
272277///
273278/// `Walker.deinit` releases allocated memory and directory handles.
274279///
......@@ -278,7 +283,7 @@ pub const SelectiveWalker = struct {
278283///
279284/// See also `walk`.
280285pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker {
281 var stack: std.ArrayList(Walker.StackItem) = .empty;
286 var stack: std.ArrayList(SelectiveWalker.StackItem) = .empty;
282287
283288 try stack.append(allocator, .{
284289 .iter = dir.iterate(),
......@@ -312,11 +317,6 @@ pub const Walker = struct {
312317 }
313318 };
314319
315 const StackItem = struct {
316 iter: Dir.Iterator,
317 dirname_len: usize,
318 };
319
320320 /// After each call to this function, and on deinit(), the memory returned
321321 /// from this function becomes invalid. A copy must be made in order to keep
322322 /// a reference to the path.
lib/std/Io/Threaded.zig+144-37
......@@ -3271,12 +3271,113 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
32713271 for (dirs) |dir| posix.close(dir.handle);
32723272}
32733273
3274fn dirRead(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3274const dirRead = switch (native_os) {
3275 .linux => dirReadLinux,
3276 else => dirReadUnimplemented,
3277};
3278
3279fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3280 const linux = std.os.linux;
32753281 const t: *Threaded = @ptrCast(@alignCast(userdata));
3276 _ = t;
3282 const current_thread = Thread.getCurrent(t);
3283 const Header = extern struct {
3284 fill_end: usize,
3285 };
3286 const header: *Header = @ptrCast(&dr.buffer);
3287 const header_end: usize = @sizeOf(Header);
3288 if (dr.index < header_end) {
3289 // Initialize header.
3290 dr.index = header_end;
3291 header.* = .{ .fill_end = header_end };
3292 }
3293 var buffer_index: usize = 0;
3294 while (buffer.len - buffer_index != 0) {
3295 if (header.fill_end - dr.index == 0) {
3296 // Refill the buffer, unless we've already created references to
3297 // buffered data.
3298 if (buffer_index != 0) break;
3299 if (dr.state == .reset) {
3300 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3301 error.Unseekable => return error.Unexpected,
3302 else => |e| return e,
3303 };
3304 dr.state = .reading;
3305 }
3306 const dents_buffer = dr.buffer[header_end..];
3307 try current_thread.beginSyscall();
3308 const n = while (true) {
3309 const rc = linux.getdents64(dr.dir.handle, dents_buffer.ptr, dents_buffer.len);
3310 switch (linux.errno(rc)) {
3311 .SUCCESS => {
3312 current_thread.endSyscall();
3313 break rc;
3314 },
3315 .INTR => {
3316 try current_thread.checkCancel();
3317 continue;
3318 },
3319 .CANCELED => return current_thread.endSyscallCanceled(),
3320 else => |e| {
3321 current_thread.endSyscall();
3322 switch (e) {
3323 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
3324 .FAULT => |err| return errnoBug(err),
3325 .NOTDIR => |err| return errnoBug(err),
3326 // To be consistent across platforms, iteration
3327 // ends if the directory being iterated is deleted
3328 // during iteration. This matches the behavior of
3329 // non-Linux UNIX platforms.
3330 .NOENT => {
3331 dr.state = .finished;
3332 return 0;
3333 },
3334 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
3335 .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory.
3336 else => |err| return posix.unexpectedErrno(err),
3337 }
3338 },
3339 }
3340 };
3341 if (n == 0) {
3342 dr.state = .finished;
3343 return 0;
3344 }
3345 dr.index = header_end;
3346 header.fill_end = header_end + n;
3347 }
3348 const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]);
3349 const next_index = dr.index + linux_entry.reclen;
3350 dr.index = next_index;
3351
3352 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);
3353 // Skip "." and ".." entries.
3354 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
3355
3356 const entry_kind: File.Kind = switch (linux_entry.type) {
3357 linux.DT.BLK => .block_device,
3358 linux.DT.CHR => .character_device,
3359 linux.DT.DIR => .directory,
3360 linux.DT.FIFO => .named_pipe,
3361 linux.DT.LNK => .sym_link,
3362 linux.DT.REG => .file,
3363 linux.DT.SOCK => .unix_domain_socket,
3364 else => .unknown,
3365 };
3366 buffer[buffer_index] = .{
3367 .name = name,
3368 .kind = entry_kind,
3369 .inode = linux_entry.ino,
3370 };
3371 buffer_index += 1;
3372 }
3373 return buffer_index;
3374}
3375
3376fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3377 _ = userdata;
32773378 _ = dir_reader;
32783379 _ = buffer;
3279 @panic("TODO");
3380 return error.Unimplemented;
32803381}
32813382
32823383const dirRealPath = switch (native_os) {
......@@ -5908,11 +6009,16 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
59086009 const current_thread = Thread.getCurrent(t);
59096010 const fd = file.handle;
59106011
5911 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
6012 if (native_os == .windows) {
6013 try current_thread.checkCancel();
6014 return windows.SetFilePointerEx_BEGIN(fd, offset);
6015 }
6016
6017 if (native_os == .wasi and !builtin.link_libc) {
59126018 try current_thread.beginSyscall();
59136019 while (true) {
5914 var result: u64 = undefined;
5915 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
6020 var new_offset: std.os.wasi.filesize_t = undefined;
6021 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
59166022 .SUCCESS => {
59176023 current_thread.endSyscall();
59186024 return;
......@@ -5930,6 +6036,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
59306036 .OVERFLOW => return error.Unseekable,
59316037 .SPIPE => return error.Unseekable,
59326038 .NXIO => return error.Unseekable,
6039 .NOTCAPABLE => return error.AccessDenied,
59336040 else => |err| return posix.unexpectedErrno(err),
59346041 }
59356042 },
......@@ -5937,40 +6044,40 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
59376044 }
59386045 }
59396046
5940 if (native_os == .windows) {
5941 try current_thread.checkCancel();
5942 return windows.SetFilePointerEx_BEGIN(fd, offset);
5943 }
6047 if (posix.SEEK == void) return error.Unseekable;
59446048
5945 if (native_os == .wasi and !builtin.link_libc) while (true) {
5946 var new_offset: std.os.wasi.filesize_t = undefined;
6049 return posixSeekTo(current_thread, fd, offset);
6050}
6051
6052fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekError!void {
6053 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
59476054 try current_thread.beginSyscall();
5948 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
5949 .SUCCESS => {
5950 current_thread.endSyscall();
5951 return;
5952 },
5953 .INTR => {
5954 try current_thread.checkCancel();
5955 continue;
5956 },
5957 .CANCELED => return current_thread.endSyscallCanceled(),
5958 else => |e| {
5959 current_thread.endSyscall();
5960 switch (e) {
5961 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5962 .INVAL => return error.Unseekable,
5963 .OVERFLOW => return error.Unseekable,
5964 .SPIPE => return error.Unseekable,
5965 .NXIO => return error.Unseekable,
5966 .NOTCAPABLE => return error.AccessDenied,
5967 else => |err| return posix.unexpectedErrno(err),
5968 }
5969 },
6055 while (true) {
6056 var result: u64 = undefined;
6057 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
6058 .SUCCESS => {
6059 current_thread.endSyscall();
6060 return;
6061 },
6062 .INTR => {
6063 try current_thread.checkCancel();
6064 continue;
6065 },
6066 .CANCELED => return current_thread.endSyscallCanceled(),
6067 else => |e| {
6068 current_thread.endSyscall();
6069 switch (e) {
6070 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
6071 .INVAL => return error.Unseekable,
6072 .OVERFLOW => return error.Unseekable,
6073 .SPIPE => return error.Unseekable,
6074 .NXIO => return error.Unseekable,
6075 else => |err| return posix.unexpectedErrno(err),
6076 }
6077 },
6078 }
59706079 }
5971 };
5972
5973 if (posix.SEEK == void) return error.Unseekable;
6080 }
59746081
59756082 try current_thread.beginSyscall();
59766083 while (true) {
lib/std/posix.zig-171
......@@ -2986,177 +2986,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
29862986 }
29872987}
29882988
2989pub const SeekError = std.Io.File.SeekError;
2990
2991pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
2992 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
2993 var result: u64 = undefined;
2994 switch (errno(system.llseek(fd, offset, &result, SEEK.SET))) {
2995 .SUCCESS => return,
2996 .BADF => unreachable, // always a race condition
2997 .INVAL => return error.Unseekable,
2998 .OVERFLOW => return error.Unseekable,
2999 .SPIPE => return error.Unseekable,
3000 .NXIO => return error.Unseekable,
3001 else => |err| return unexpectedErrno(err),
3002 }
3003 }
3004 if (native_os == .windows) {
3005 return windows.SetFilePointerEx_BEGIN(fd, offset);
3006 }
3007 if (native_os == .wasi and !builtin.link_libc) {
3008 var new_offset: wasi.filesize_t = undefined;
3009 switch (wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
3010 .SUCCESS => return,
3011 .BADF => unreachable, // always a race condition
3012 .INVAL => return error.Unseekable,
3013 .OVERFLOW => return error.Unseekable,
3014 .SPIPE => return error.Unseekable,
3015 .NXIO => return error.Unseekable,
3016 .NOTCAPABLE => return error.AccessDenied,
3017 else => |err| return unexpectedErrno(err),
3018 }
3019 }
3020
3021 const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
3022 switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.SET))) {
3023 .SUCCESS => return,
3024 .BADF => unreachable, // always a race condition
3025 .INVAL => return error.Unseekable,
3026 .OVERFLOW => return error.Unseekable,
3027 .SPIPE => return error.Unseekable,
3028 .NXIO => return error.Unseekable,
3029 else => |err| return unexpectedErrno(err),
3030 }
3031}
3032
3033/// Repositions read/write file offset relative to the current offset.
3034pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
3035 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
3036 var result: u64 = undefined;
3037 switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.CUR))) {
3038 .SUCCESS => return,
3039 .BADF => unreachable, // always a race condition
3040 .INVAL => return error.Unseekable,
3041 .OVERFLOW => return error.Unseekable,
3042 .SPIPE => return error.Unseekable,
3043 .NXIO => return error.Unseekable,
3044 else => |err| return unexpectedErrno(err),
3045 }
3046 }
3047 if (native_os == .windows) {
3048 return windows.SetFilePointerEx_CURRENT(fd, offset);
3049 }
3050 if (native_os == .wasi and !builtin.link_libc) {
3051 var new_offset: wasi.filesize_t = undefined;
3052 switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) {
3053 .SUCCESS => return,
3054 .BADF => unreachable, // always a race condition
3055 .INVAL => return error.Unseekable,
3056 .OVERFLOW => return error.Unseekable,
3057 .SPIPE => return error.Unseekable,
3058 .NXIO => return error.Unseekable,
3059 .NOTCAPABLE => return error.AccessDenied,
3060 else => |err| return unexpectedErrno(err),
3061 }
3062 }
3063 const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
3064 switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.CUR))) {
3065 .SUCCESS => return,
3066 .BADF => unreachable, // always a race condition
3067 .INVAL => return error.Unseekable,
3068 .OVERFLOW => return error.Unseekable,
3069 .SPIPE => return error.Unseekable,
3070 .NXIO => return error.Unseekable,
3071 else => |err| return unexpectedErrno(err),
3072 }
3073}
3074
3075/// Repositions read/write file offset relative to the end.
3076pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
3077 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
3078 var result: u64 = undefined;
3079 switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.END))) {
3080 .SUCCESS => return,
3081 .BADF => unreachable, // always a race condition
3082 .INVAL => return error.Unseekable,
3083 .OVERFLOW => return error.Unseekable,
3084 .SPIPE => return error.Unseekable,
3085 .NXIO => return error.Unseekable,
3086 else => |err| return unexpectedErrno(err),
3087 }
3088 }
3089 if (native_os == .windows) {
3090 return windows.SetFilePointerEx_END(fd, offset);
3091 }
3092 if (native_os == .wasi and !builtin.link_libc) {
3093 var new_offset: wasi.filesize_t = undefined;
3094 switch (wasi.fd_seek(fd, offset, .END, &new_offset)) {
3095 .SUCCESS => return,
3096 .BADF => unreachable, // always a race condition
3097 .INVAL => return error.Unseekable,
3098 .OVERFLOW => return error.Unseekable,
3099 .SPIPE => return error.Unseekable,
3100 .NXIO => return error.Unseekable,
3101 .NOTCAPABLE => return error.AccessDenied,
3102 else => |err| return unexpectedErrno(err),
3103 }
3104 }
3105 const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
3106 switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.END))) {
3107 .SUCCESS => return,
3108 .BADF => unreachable, // always a race condition
3109 .INVAL => return error.Unseekable,
3110 .OVERFLOW => return error.Unseekable,
3111 .SPIPE => return error.Unseekable,
3112 .NXIO => return error.Unseekable,
3113 else => |err| return unexpectedErrno(err),
3114 }
3115}
3116
3117/// Returns the read/write file offset relative to the beginning.
3118pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 {
3119 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
3120 var result: u64 = undefined;
3121 switch (errno(system.llseek(fd, 0, &result, SEEK.CUR))) {
3122 .SUCCESS => return result,
3123 .BADF => unreachable, // always a race condition
3124 .INVAL => return error.Unseekable,
3125 .OVERFLOW => return error.Unseekable,
3126 .SPIPE => return error.Unseekable,
3127 .NXIO => return error.Unseekable,
3128 else => |err| return unexpectedErrno(err),
3129 }
3130 }
3131 if (native_os == .windows) {
3132 return windows.SetFilePointerEx_CURRENT_get(fd);
3133 }
3134 if (native_os == .wasi and !builtin.link_libc) {
3135 var new_offset: wasi.filesize_t = undefined;
3136 switch (wasi.fd_seek(fd, 0, .CUR, &new_offset)) {
3137 .SUCCESS => return new_offset,
3138 .BADF => unreachable, // always a race condition
3139 .INVAL => return error.Unseekable,
3140 .OVERFLOW => return error.Unseekable,
3141 .SPIPE => return error.Unseekable,
3142 .NXIO => return error.Unseekable,
3143 .NOTCAPABLE => return error.AccessDenied,
3144 else => |err| return unexpectedErrno(err),
3145 }
3146 }
3147 const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
3148 const rc = lseek_sym(fd, 0, SEEK.CUR);
3149 switch (errno(rc)) {
3150 .SUCCESS => return @bitCast(rc),
3151 .BADF => unreachable, // always a race condition
3152 .INVAL => return error.Unseekable,
3153 .OVERFLOW => return error.Unseekable,
3154 .SPIPE => return error.Unseekable,
3155 .NXIO => return error.Unseekable,
3156 else => |err| return unexpectedErrno(err),
3157 }
3158}
3159
31602989pub const FcntlError = error{
31612990 PermissionDenied,
31622991 FileBusy,