authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-13 00:00:05-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:49-07:00
log71ff6e0ef748fbc78cd469317f546c99c0dc5074
tree3f2d60e4af02b30b0ce81452d5507c3d1f37a518
parentd40803284e6395e7a4a065a73c9fb09d1c0ff7a8

std: fix seekBy unit test


7 files changed, 114 insertions(+), 20 deletions(-)

lib/std/Io.zig+2-2
...@@ -666,8 +666,8 @@ pub const VTable = struct {...@@ -666,8 +666,8 @@ pub const VTable = struct {
666 fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.ReadStreamingError!usize,666 fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.ReadStreamingError!usize,
667 /// Returns 0 on end of stream.667 /// Returns 0 on end of stream.
668 fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,668 fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
669 fileSeekBy: *const fn (?*anyopaque, File, offset: i64) File.SeekError!void,669 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
670 fileSeekTo: *const fn (?*anyopaque, File, offset: u64) File.SeekError!void,670 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
671671
672 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,672 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
673 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,673 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
lib/std/Io/File.zig+48-9
...@@ -71,6 +71,8 @@ pub const StatError = error{...@@ -71,6 +71,8 @@ pub const StatError = error{
71 /// not hold the required rights to get its filestat information.71 /// not hold the required rights to get its filestat information.
72 AccessDenied,72 AccessDenied,
73 PermissionDenied,73 PermissionDenied,
74 /// Attempted to stat a non-file stream.
75 Streaming,
74} || Io.Cancelable || Io.UnexpectedError;76} || Io.Cancelable || Io.UnexpectedError;
7577
76/// Returns `Stat` containing basic information about the `File`.78/// Returns `Stat` containing basic information about the `File`.
...@@ -357,10 +359,6 @@ pub const Reader = struct {...@@ -357,10 +359,6 @@ pub const Reader = struct {
357 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));359 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
358 },360 },
359 .streaming, .streaming_reading => {361 .streaming, .streaming_reading => {
360 if (std.posix.SEEK == void) {
361 r.seek_err = error.Unseekable;
362 return error.Unseekable;
363 }
364 const seek_err = r.seek_err orelse e: {362 const seek_err = r.seek_err orelse e: {
365 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {363 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
366 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));364 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
...@@ -384,6 +382,7 @@ pub const Reader = struct {...@@ -384,6 +382,7 @@ pub const Reader = struct {
384 }382 }
385 }383 }
386384
385 /// Repositions logical read offset relative to the beginning of the file.
387 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {386 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
388 const io = r.io;387 const io = r.io;
389 switch (r.mode) {388 switch (r.mode) {
...@@ -527,25 +526,65 @@ pub const Reader = struct {...@@ -527,25 +526,65 @@ pub const Reader = struct {
527 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));526 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
528 const io = r.io;527 const io = r.io;
529 const file = r.file;528 const file = r.file;
530 const pos = r.pos;
531 switch (r.mode) {529 switch (r.mode) {
532 .positional, .positional_reading => {530 .positional, .positional_reading => {
533 const size = r.getSize() catch {531 const size = r.getSize() catch {
534 r.mode = r.mode.toStreaming();532 r.mode = r.mode.toStreaming();
535 return 0;533 return 0;
536 };534 };
537 const delta = @min(@intFromEnum(limit), size - pos);535 const logical_pos = logicalPos(r);
538 r.pos = pos + delta;536 const delta = @min(@intFromEnum(limit), size - logical_pos);
537 setLogicalPos(r, logical_pos + delta);
539 return delta;538 return delta;
540 },539 },
541 .streaming, .streaming_reading => {540 .streaming, .streaming_reading => {
541 // Unfortunately we can't seek forward without knowing the
542 // size because the seek syscalls provided to us will not
543 // return the true end position if a seek would exceed the
544 // end.
545 fallback: {
546 if (r.size_err == null and r.seek_err == null) break :fallback;
547
548 const buffered_len = r.interface.bufferedLen();
549 var remaining = @intFromEnum(limit);
550 if (remaining <= buffered_len) {
551 r.interface.seek += remaining;
552 return remaining;
553 }
554 remaining -= buffered_len;
555 r.interface.seek = 0;
556 r.interface.end = 0;
557
558 var trash_buffer: [128]u8 = undefined;
559 var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]};
560 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
561 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
562 const dest = iovecs_buffer[0..dest_n];
563 assert(dest[0].len > 0);
564 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {
565 r.err = err;
566 return error.ReadFailed;
567 };
568 if (n == 0) {
569 r.size = r.pos;
570 return error.EndOfStream;
571 }
572 r.pos += n;
573 if (n > data_size) {
574 r.interface.end += n - data_size;
575 remaining -= data_size;
576 } else {
577 remaining -= n;
578 }
579 return @intFromEnum(limit) - remaining;
580 }
542 const size = r.getSize() catch return 0;581 const size = r.getSize() catch return 0;
543 const n = @min(size - pos, std.math.maxInt(i64), @intFromEnum(limit));582 const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit));
544 io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {583 io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {
545 r.seek_err = err;584 r.seek_err = err;
546 return 0;585 return 0;
547 };586 };
548 r.pos = pos + n;587 r.pos += n;
549 return n;588 return n;
550 },589 },
551 .failure => return error.ReadFailed,590 .failure => return error.ReadFailed,
lib/std/Io/Threaded.zig+59-7
...@@ -869,7 +869,6 @@ fn dirStatPathPosix(...@@ -869,7 +869,6 @@ fn dirStatPathPosix(
869 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);869 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
870870
871 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;871 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
872 const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
873872
874 while (true) {873 while (true) {
875 try pool.checkCancel();874 try pool.checkCancel();
...@@ -895,7 +894,9 @@ fn dirStatPathPosix(...@@ -895,7 +894,9 @@ fn dirStatPathPosix(
895894
896fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {895fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
897 const pool: *Pool = @ptrCast(@alignCast(userdata));896 const pool: *Pool = @ptrCast(@alignCast(userdata));
898 const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;897
898 if (posix.Stat == void) return error.Streaming;
899
899 while (true) {900 while (true) {
900 try pool.checkCancel();901 try pool.checkCancel();
901 var stat = std.mem.zeroes(posix.Stat);902 var stat = std.mem.zeroes(posix.Stat);
...@@ -969,6 +970,10 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File....@@ -969,6 +970,10 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
969970
970const have_flock = @TypeOf(posix.system.flock) != void;971const have_flock = @TypeOf(posix.system.flock) != void;
971const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;972const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
973const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
974const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
975const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
976const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
972977
973fn dirCreateFilePosix(978fn dirCreateFilePosix(
974 userdata: ?*anyopaque,979 userdata: ?*anyopaque,
...@@ -1423,7 +1428,6 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset...@@ -1423,7 +1428,6 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
1423 }1428 }
1424 };1429 };
14251430
1426 const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
1427 while (true) {1431 while (true) {
1428 try pool.checkCancel();1432 try pool.checkCancel();
1429 const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));1433 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...@@ -1461,11 +1465,59 @@ fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekErr
14611465
1462fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {1466fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {
1463 const pool: *Pool = @ptrCast(@alignCast(userdata));1467 const pool: *Pool = @ptrCast(@alignCast(userdata));
1464 try pool.checkCancel();1468 const fd = file.handle;
14651469
1466 _ = file;1470 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) while (true) {
1467 _ = offset;1471 try pool.checkCancel();
1468 @panic("TODO");1472 var result: u64 = undefined;
1473 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
1474 .SUCCESS => return,
1475 .INTR => continue,
1476 .BADF => |err| return errnoBug(err), // Always a race condition.
1477 .INVAL => return error.Unseekable,
1478 .OVERFLOW => return error.Unseekable,
1479 .SPIPE => return error.Unseekable,
1480 .NXIO => return error.Unseekable,
1481 else => |err| return posix.unexpectedErrno(err),
1482 }
1483 };
1484
1485 if (native_os == .windows) {
1486 try pool.checkCancel();
1487 return windows.SetFilePointerEx_BEGIN(fd, offset);
1488 }
1489
1490 if (native_os == .wasi and !builtin.link_libc) while (true) {
1491 try pool.checkCancel();
1492 var new_offset: std.os.wasi.filesize_t = undefined;
1493 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
1494 .SUCCESS => return,
1495 .INTR => continue,
1496 .BADF => |err| return errnoBug(err), // Always a race condition.
1497 .INVAL => return error.Unseekable,
1498 .OVERFLOW => return error.Unseekable,
1499 .SPIPE => return error.Unseekable,
1500 .NXIO => return error.Unseekable,
1501 .NOTCAPABLE => return error.AccessDenied,
1502 else => |err| return posix.unexpectedErrno(err),
1503 }
1504 };
1505
1506 if (posix.SEEK == void) return error.Unseekable;
1507
1508 while (true) {
1509 try pool.checkCancel();
1510 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {
1511 .SUCCESS => return,
1512 .INTR => continue,
1513 .BADF => |err| return errnoBug(err), // Always a race condition.
1514 .INVAL => return error.Unseekable,
1515 .OVERFLOW => return error.Unseekable,
1516 .SPIPE => return error.Unseekable,
1517 .NXIO => return error.Unseekable,
1518 else => |err| return posix.unexpectedErrno(err),
1519 }
1520 }
1469}1521}
14701522
1471fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {1523fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {
lib/std/debug/ElfFile.zig+2-1
...@@ -108,6 +108,7 @@ pub const LoadError = error{...@@ -108,6 +108,7 @@ pub const LoadError = error{
108 LockedMemoryLimitExceeded,108 LockedMemoryLimitExceeded,
109 ProcessFdQuotaExceeded,109 ProcessFdQuotaExceeded,
110 SystemFdQuotaExceeded,110 SystemFdQuotaExceeded,
111 Streaming,
111 Canceled,112 Canceled,
112 Unexpected,113 Unexpected,
113};114};
...@@ -409,7 +410,7 @@ fn loadInner(...@@ -409,7 +410,7 @@ fn loadInner(
409 arena: Allocator,410 arena: Allocator,
410 elf_file: std.fs.File,411 elf_file: std.fs.File,
411 opt_crc: ?u32,412 opt_crc: ?u32,
412) (LoadError || error{ CrcMismatch, Canceled })!LoadInnerResult {413) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult {
413 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {414 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
414 const file_len = std.math.cast(415 const file_len = std.math.cast(
415 usize,416 usize,
lib/std/debug/SelfInfo/Elf.zig+1
...@@ -354,6 +354,7 @@ const Module = struct {...@@ -354,6 +354,7 @@ const Module = struct {
354 error.LockedMemoryLimitExceeded,354 error.LockedMemoryLimitExceeded,
355 error.ProcessFdQuotaExceeded,355 error.ProcessFdQuotaExceeded,
356 error.SystemFdQuotaExceeded,356 error.SystemFdQuotaExceeded,
357 error.Streaming,
357 => return error.ReadFailed,358 => return error.ReadFailed,
358 };359 };
359 errdefer elf_file.deinit(gpa);360 errdefer elf_file.deinit(gpa);
lib/std/dynamic_library.zig+1
...@@ -138,6 +138,7 @@ const ElfDynLibError = error{...@@ -138,6 +138,7 @@ const ElfDynLibError = error{
138 ElfSymSectionNotFound,138 ElfSymSectionNotFound,
139 ElfHashTableNotFound,139 ElfHashTableNotFound,
140 Canceled,140 Canceled,
141 Streaming,
141} || posix.OpenError || posix.MMapError;142} || posix.OpenError || posix.MMapError;
142143
143pub const ElfDynLib = struct {144pub const ElfDynLib = struct {
lib/std/posix.zig+1-1
...@@ -488,6 +488,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr...@@ -488,6 +488,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
488 error.NameTooLong => unreachable,488 error.NameTooLong => unreachable,
489 error.FileNotFound => unreachable,489 error.FileNotFound => unreachable,
490 error.InvalidUtf8 => unreachable,490 error.InvalidUtf8 => unreachable,
491 error.Streaming => unreachable,
491 error.Canceled => return error.Canceled,492 error.Canceled => return error.Canceled,
492 else => |e| return e,493 else => |e| return e,
493 };494 };
...@@ -5262,7 +5263,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {...@@ -5262,7 +5263,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
52625263
5263pub const SeekError = std.Io.File.SeekError;5264pub const SeekError = std.Io.File.SeekError;
52645265
5265/// Repositions read/write file offset relative to the beginning.
5266pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {5266pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
5267 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {5267 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
5268 var result: u64 = undefined;5268 var result: u64 = undefined;