diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 8f8e9a122d0cf78aa80c4712abbbbdadb834410e..677d1974222b1e3cc36657ca2eeb00deacbe7257 100644 --- a/lib/std/Io.zig +++ b/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, diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index d5d08587c7629e68da1ddf4f389b8a11be41cb99..43ce6108ed5fe417c647cdd60bc6c58bf33bdde6 100644 --- a/lib/std/Io/File.zig +++ b/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, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 88120f4b04e723f35da8a2b59e2ef9be7be811f1..345d9728e3d21ad7f004b2c53c6147263a6d9263 100644 --- a/lib/std/Io/Threaded.zig +++ b/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 { diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index 206277253390e3db490953d771c7820e2b217548..427a8fd1b1fb756e3c8c1a64e55acd62768ec7c9 100644 --- a/lib/std/debug/ElfFile.zig +++ b/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, diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 035ed584b2076de6ae3a011121e8aa6d9bd0db73..bf8330d23501f2605eac8138081f6965a151fcc9 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/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); diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index a7326cfd668a293b843bd2bc709a475883efb09c..90f655b95bc64796076545e2c112e7885fd0e49c 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -138,6 +138,7 @@ const ElfDynLibError = error{ ElfSymSectionNotFound, ElfHashTableNotFound, Canceled, + Streaming, } || posix.OpenError || posix.MMapError; pub const ElfDynLib = struct { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 50d63ad63df0591f2f74080bc1305326925c9874..eebff5479dc7dcf8c18735c6c6fe5faa7787cc2d 100644 --- a/lib/std/posix.zig +++ b/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;