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 {...@@ -184,6 +184,11 @@ pub const SelectiveWalker = struct {
184184
185 pub const Error = Io.Dir.Iterator.Error || Allocator.Error;185 pub const Error = Io.Dir.Iterator.Error || Allocator.Error;
186186
187 const StackItem = struct {
188 iter: Dir.Iterator,
189 dirname_len: usize,
190 };
191
187 /// After each call to this function, and on deinit(), the memory returned192 /// After each call to this function, and on deinit(), the memory returned
188 /// from this function becomes invalid. A copy must be made in order to keep193 /// from this function becomes invalid. A copy must be made in order to keep
189 /// a reference to the path.194 /// a reference to the path.
...@@ -268,7 +273,7 @@ pub const SelectiveWalker = struct {...@@ -268,7 +273,7 @@ pub const SelectiveWalker = struct {
268/// Recursively iterates over a directory, but requires the user to273/// Recursively iterates over a directory, but requires the user to
269/// opt-in to recursing into each directory entry.274/// opt-in to recursing into each directory entry.
270///275///
271/// `dir` must have been opened with `OpenOptions{.iterate = true}`.276/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
272///277///
273/// `Walker.deinit` releases allocated memory and directory handles.278/// `Walker.deinit` releases allocated memory and directory handles.
274///279///
...@@ -278,7 +283,7 @@ pub const SelectiveWalker = struct {...@@ -278,7 +283,7 @@ pub const SelectiveWalker = struct {
278///283///
279/// See also `walk`.284/// See also `walk`.
280pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker {285pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker {
281 var stack: std.ArrayList(Walker.StackItem) = .empty;286 var stack: std.ArrayList(SelectiveWalker.StackItem) = .empty;
282287
283 try stack.append(allocator, .{288 try stack.append(allocator, .{
284 .iter = dir.iterate(),289 .iter = dir.iterate(),
...@@ -312,11 +317,6 @@ pub const Walker = struct {...@@ -312,11 +317,6 @@ pub const Walker = struct {
312 }317 }
313 };318 };
314319
315 const StackItem = struct {
316 iter: Dir.Iterator,
317 dirname_len: usize,
318 };
319
320 /// After each call to this function, and on deinit(), the memory returned320 /// After each call to this function, and on deinit(), the memory returned
321 /// from this function becomes invalid. A copy must be made in order to keep321 /// from this function becomes invalid. A copy must be made in order to keep
322 /// a reference to the path.322 /// a reference to the path.
lib/std/Io/Threaded.zig+144-37
...@@ -3271,12 +3271,113 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {...@@ -3271,12 +3271,113 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
3271 for (dirs) |dir| posix.close(dir.handle);3271 for (dirs) |dir| posix.close(dir.handle);
3272}3272}
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;
3275 const t: *Threaded = @ptrCast(@alignCast(userdata));3281 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;
3277 _ = dir_reader;3378 _ = dir_reader;
3278 _ = buffer;3379 _ = buffer;
3279 @panic("TODO");3380 return error.Unimplemented;
3280}3381}
32813382
3282const dirRealPath = switch (native_os) {3383const dirRealPath = switch (native_os) {
...@@ -5908,11 +6009,16 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi...@@ -5908,11 +6009,16 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
5908 const current_thread = Thread.getCurrent(t);6009 const current_thread = Thread.getCurrent(t);
5909 const fd = file.handle;6010 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) {
5912 try current_thread.beginSyscall();6018 try current_thread.beginSyscall();
5913 while (true) {6019 while (true) {
5914 var result: u64 = undefined;6020 var new_offset: std.os.wasi.filesize_t = undefined;
5915 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {6021 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
5916 .SUCCESS => {6022 .SUCCESS => {
5917 current_thread.endSyscall();6023 current_thread.endSyscall();
5918 return;6024 return;
...@@ -5930,6 +6036,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi...@@ -5930,6 +6036,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
5930 .OVERFLOW => return error.Unseekable,6036 .OVERFLOW => return error.Unseekable,
5931 .SPIPE => return error.Unseekable,6037 .SPIPE => return error.Unseekable,
5932 .NXIO => return error.Unseekable,6038 .NXIO => return error.Unseekable,
6039 .NOTCAPABLE => return error.AccessDenied,
5933 else => |err| return posix.unexpectedErrno(err),6040 else => |err| return posix.unexpectedErrno(err),
5934 }6041 }
5935 },6042 },
...@@ -5937,40 +6044,40 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi...@@ -5937,40 +6044,40 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
5937 }6044 }
5938 }6045 }
59396046
5940 if (native_os == .windows) {6047 if (posix.SEEK == void) return error.Unseekable;
5941 try current_thread.checkCancel();
5942 return windows.SetFilePointerEx_BEGIN(fd, offset);
5943 }
59446048
5945 if (native_os == .wasi and !builtin.link_libc) while (true) {6049 return posixSeekTo(current_thread, fd, offset);
5946 var new_offset: std.os.wasi.filesize_t = undefined;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) {
5947 try current_thread.beginSyscall();6054 try current_thread.beginSyscall();
5948 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {6055 while (true) {
5949 .SUCCESS => {6056 var result: u64 = undefined;
5950 current_thread.endSyscall();6057 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
5951 return;6058 .SUCCESS => {
5952 },6059 current_thread.endSyscall();
5953 .INTR => {6060 return;
5954 try current_thread.checkCancel();6061 },
5955 continue;6062 .INTR => {
5956 },6063 try current_thread.checkCancel();
5957 .CANCELED => return current_thread.endSyscallCanceled(),6064 continue;
5958 else => |e| {6065 },
5959 current_thread.endSyscall();6066 .CANCELED => return current_thread.endSyscallCanceled(),
5960 switch (e) {6067 else => |e| {
5961 .BADF => |err| return errnoBug(err), // File descriptor used after closed.6068 current_thread.endSyscall();
5962 .INVAL => return error.Unseekable,6069 switch (e) {
5963 .OVERFLOW => return error.Unseekable,6070 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5964 .SPIPE => return error.Unseekable,6071 .INVAL => return error.Unseekable,
5965 .NXIO => return error.Unseekable,6072 .OVERFLOW => return error.Unseekable,
5966 .NOTCAPABLE => return error.AccessDenied,6073 .SPIPE => return error.Unseekable,
5967 else => |err| return posix.unexpectedErrno(err),6074 .NXIO => return error.Unseekable,
5968 }6075 else => |err| return posix.unexpectedErrno(err),
5969 },6076 }
6077 },
6078 }
5970 }6079 }
5971 };6080 }
5972
5973 if (posix.SEEK == void) return error.Unseekable;
59746081
5975 try current_thread.beginSyscall();6082 try current_thread.beginSyscall();
5976 while (true) {6083 while (true) {
lib/std/posix.zig-171
...@@ -2986,177 +2986,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {...@@ -2986,177 +2986,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
2986 }2986 }
2987}2987}
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
3160pub const FcntlError = error{2989pub const FcntlError = error{
3161 PermissionDenied,2990 PermissionDenied,
3162 FileBusy,2991 FileBusy,