Commit 71ff6e0ef7

Andrew Kelley <andrew@ziglang.org>
2025-10-13 09:00:05
std: fix seekBy unit test
1 parent d408032
lib/std/debug/SelfInfo/Elf.zig
@@ -354,6 +354,7 @@ const Module = struct {
             error.LockedMemoryLimitExceeded,
             error.ProcessFdQuotaExceeded,
             error.SystemFdQuotaExceeded,
+            error.Streaming,
             => return error.ReadFailed,
         };
         errdefer elf_file.deinit(gpa);
lib/std/debug/ElfFile.zig
@@ -108,6 +108,7 @@ pub const LoadError = error{
     LockedMemoryLimitExceeded,
     ProcessFdQuotaExceeded,
     SystemFdQuotaExceeded,
+    Streaming,
     Canceled,
     Unexpected,
 };
@@ -409,7 +410,7 @@ fn loadInner(
     arena: Allocator,
     elf_file: std.fs.File,
     opt_crc: ?u32,
-) (LoadError || error{ CrcMismatch, Canceled })!LoadInnerResult {
+) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult {
     const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
         const file_len = std.math.cast(
             usize,
lib/std/Io/File.zig
@@ -71,6 +71,8 @@ pub const StatError = error{
     /// not hold the required rights to get its filestat information.
     AccessDenied,
     PermissionDenied,
+    /// Attempted to stat a non-file stream.
+    Streaming,
 } || Io.Cancelable || Io.UnexpectedError;
 
 /// Returns `Stat` containing basic information about the `File`.
@@ -357,10 +359,6 @@ pub const Reader = struct {
                 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
             },
             .streaming, .streaming_reading => {
-                if (std.posix.SEEK == void) {
-                    r.seek_err = error.Unseekable;
-                    return error.Unseekable;
-                }
                 const seek_err = r.seek_err orelse e: {
                     if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
                         setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
@@ -384,6 +382,7 @@ pub const Reader = struct {
         }
     }
 
+    /// Repositions logical read offset relative to the beginning of the file.
     pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
         const io = r.io;
         switch (r.mode) {
@@ -527,25 +526,65 @@ pub const Reader = struct {
         const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
         const io = r.io;
         const file = r.file;
-        const pos = r.pos;
         switch (r.mode) {
             .positional, .positional_reading => {
                 const size = r.getSize() catch {
                     r.mode = r.mode.toStreaming();
                     return 0;
                 };
-                const delta = @min(@intFromEnum(limit), size - pos);
-                r.pos = pos + delta;
+                const logical_pos = logicalPos(r);
+                const delta = @min(@intFromEnum(limit), size - logical_pos);
+                setLogicalPos(r, logical_pos + delta);
                 return delta;
             },
             .streaming, .streaming_reading => {
+                // Unfortunately we can't seek forward without knowing the
+                // size because the seek syscalls provided to us will not
+                // return the true end position if a seek would exceed the
+                // end.
+                fallback: {
+                    if (r.size_err == null and r.seek_err == null) break :fallback;
+
+                    const buffered_len = r.interface.bufferedLen();
+                    var remaining = @intFromEnum(limit);
+                    if (remaining <= buffered_len) {
+                        r.interface.seek += remaining;
+                        return remaining;
+                    }
+                    remaining -= buffered_len;
+                    r.interface.seek = 0;
+                    r.interface.end = 0;
+
+                    var trash_buffer: [128]u8 = undefined;
+                    var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]};
+                    var iovecs_buffer: [max_buffers_len][]u8 = undefined;
+                    const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
+                    const dest = iovecs_buffer[0..dest_n];
+                    assert(dest[0].len > 0);
+                    const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {
+                        r.err = err;
+                        return error.ReadFailed;
+                    };
+                    if (n == 0) {
+                        r.size = r.pos;
+                        return error.EndOfStream;
+                    }
+                    r.pos += n;
+                    if (n > data_size) {
+                        r.interface.end += n - data_size;
+                        remaining -= data_size;
+                    } else {
+                        remaining -= n;
+                    }
+                    return @intFromEnum(limit) - remaining;
+                }
                 const size = r.getSize() catch return 0;
-                const n = @min(size - pos, std.math.maxInt(i64), @intFromEnum(limit));
+                const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit));
                 io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {
                     r.seek_err = err;
                     return 0;
                 };
-                r.pos = pos + n;
+                r.pos += n;
                 return n;
             },
             .failure => return error.ReadFailed,
lib/std/Io/Threaded.zig
@@ -869,7 +869,6 @@ fn dirStatPathPosix(
     const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
 
     const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
-    const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
 
     while (true) {
         try pool.checkCancel();
@@ -895,7 +894,9 @@ fn dirStatPathPosix(
 
 fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
-    const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
+
+    if (posix.Stat == void) return error.Streaming;
+
     while (true) {
         try pool.checkCancel();
         var stat = std.mem.zeroes(posix.Stat);
@@ -969,6 +970,10 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
 
 const have_flock = @TypeOf(posix.system.flock) != void;
 const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
+const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
+const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
+const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
+const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
 
 fn dirCreateFilePosix(
     userdata: ?*anyopaque,
@@ -1423,7 +1428,6 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
         }
     };
 
-    const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
     while (true) {
         try pool.checkCancel();
         const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));
@@ -1461,11 +1465,59 @@ fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekErr
 
 fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
-    try pool.checkCancel();
+    const fd = file.handle;
 
-    _ = file;
-    _ = offset;
-    @panic("TODO");
+    if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) while (true) {
+        try pool.checkCancel();
+        var result: u64 = undefined;
+        switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
+            .SUCCESS => return,
+            .INTR => continue,
+            .BADF => |err| return errnoBug(err), // Always a race condition.
+            .INVAL => return error.Unseekable,
+            .OVERFLOW => return error.Unseekable,
+            .SPIPE => return error.Unseekable,
+            .NXIO => return error.Unseekable,
+            else => |err| return posix.unexpectedErrno(err),
+        }
+    };
+
+    if (native_os == .windows) {
+        try pool.checkCancel();
+        return windows.SetFilePointerEx_BEGIN(fd, offset);
+    }
+
+    if (native_os == .wasi and !builtin.link_libc) while (true) {
+        try pool.checkCancel();
+        var new_offset: std.os.wasi.filesize_t = undefined;
+        switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
+            .SUCCESS => return,
+            .INTR => continue,
+            .BADF => |err| return errnoBug(err), // 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 posix.unexpectedErrno(err),
+        }
+    };
+
+    if (posix.SEEK == void) return error.Unseekable;
+
+    while (true) {
+        try pool.checkCancel();
+        switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {
+            .SUCCESS => return,
+            .INTR => continue,
+            .BADF => |err| return errnoBug(err), // Always a race condition.
+            .INVAL => return error.Unseekable,
+            .OVERFLOW => return error.Unseekable,
+            .SPIPE => return error.Unseekable,
+            .NXIO => return error.Unseekable,
+            else => |err| return posix.unexpectedErrno(err),
+        }
+    }
 }
 
 fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {
lib/std/dynamic_library.zig
@@ -138,6 +138,7 @@ const ElfDynLibError = error{
     ElfSymSectionNotFound,
     ElfHashTableNotFound,
     Canceled,
+    Streaming,
 } || posix.OpenError || posix.MMapError;
 
 pub const ElfDynLib = struct {
lib/std/Io.zig
@@ -666,8 +666,8 @@ pub const VTable = struct {
     fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.ReadStreamingError!usize,
     /// Returns 0 on end of stream.
     fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
-    fileSeekBy: *const fn (?*anyopaque, File, offset: i64) File.SeekError!void,
-    fileSeekTo: *const fn (?*anyopaque, File, offset: u64) File.SeekError!void,
+    fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
+    fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
 
     now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
     sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
lib/std/posix.zig
@@ -488,6 +488,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
         error.NameTooLong => unreachable,
         error.FileNotFound => unreachable,
         error.InvalidUtf8 => unreachable,
+        error.Streaming => unreachable,
         error.Canceled => return error.Canceled,
         else => |e| return e,
     };
@@ -5262,7 +5263,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
 
 pub const SeekError = std.Io.File.SeekError;
 
-/// Repositions read/write file offset relative to the beginning.
 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;