From 9dc0b4a98f8efe2d249db74ebe52c6880570b7cc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 9 Apr 2025 22:08:56 -0700 Subject: [PATCH] back to the anyerror!usize vtable --- lib/std/compress/flate/inflate.zig | 31 +++-- lib/std/debug/Dwarf.zig | 11 +- lib/std/debug/SelfInfo.zig | 2 +- lib/std/fs/File.zig | 126 ++++++++++---------- lib/std/io/BufferedReader.zig | 74 ++++++++++-- lib/std/io/BufferedWriter.zig | 181 ++++++++--------------------- lib/std/io/Reader.zig | 57 ++------- lib/std/io/Writer.zig | 60 ++-------- lib/std/posix.zig | 7 +- 9 files changed, 228 insertions(+), 321 deletions(-) diff --git a/lib/std/compress/flate/inflate.zig b/lib/std/compress/flate/inflate.zig index de0eecc7e3..9fa0ad90a8 100644 --- a/lib/std/compress/flate/inflate.zig +++ b/lib/std/compress/flate/inflate.zig @@ -326,6 +326,7 @@ pub fn Inflate(comptime container: Container, comptime Lookahead: type) type { /// returned bytes means end of stream reached. With limit=0 returns as /// much data it can. It newer will be more than 65536 bytes, which is /// size of internal buffer. + /// TODO merge this logic into reader_streamRead and reader_streamReadVec pub fn get(self: *Self, limit: usize) Error![]const u8 { while (true) { const out = self.hist.readAtMost(limit); @@ -342,31 +343,27 @@ pub fn Inflate(comptime container: Container, comptime Lookahead: type) type { ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: std.io.Reader.Limit, - ) std.io.Reader.RwResult { + ) anyerror!std.io.Reader.Status { const self: *Self = @alignCast(@ptrCast(ctx)); - const out = bw.writableSlice(1) catch |err| return .{ .write_err = err }; - const in = self.get(limit.min(out.len)) catch |err| return .{ .read_err = err }; - if (in.len == 0) return .{ .read_end = true }; + const out = try bw.writableSlice(1); + const in = try self.get(limit.min(out.len)); @memcpy(out[0..in.len], in); - return .{ .len = in.len }; + bw.advance(in.len); + return .{ .len = in.len, .end = in.len == 0 }; } - fn reader_streamReadVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Result { + fn reader_streamReadVec(ctx: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status { const self: *Self = @alignCast(@ptrCast(ctx)); - var total: usize = 0; - for (data) |buffer| { - if (buffer.len == 0) break; - const out = self.get(buffer.len) catch |err| { - return .{ .len = total, .err = err }; - }; - if (out.len == 0) break; - @memcpy(buffer[0..out.len], out); - total += out.len; + for (data) |out| { + if (out.len == 0) continue; + const in = try self.get(out.len); + @memcpy(out[0..in.len], in); + return .{ .len = @intCast(in.len), .end = in.len == 0 }; } - return .{ .len = total, .end = total == 0 }; + return .{}; } - pub fn streamReadVec(self: *Self, data: []const []u8) std.io.Reader.Result { + pub fn streamReadVec(self: *Self, data: []const []u8) anyerror!std.io.Reader.Status { return reader_streamReadVec(self, data); } diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 6acf7b1dcc..f173967ca9 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -2247,16 +2247,15 @@ pub const ElfModule = struct { errdefer gpa.free(decompressed_section); { - var read_index: usize = 0; + var i: usize = 0; while (true) { - const read_result = zlib_stream.streamReadVec(&.{decompressed_section[read_index..]}); - read_result.err catch { + const status = zlib_stream.streamReadVec(&.{decompressed_section[i..]}) catch { gpa.free(decompressed_section); continue :shdrs; }; - read_index += read_result.len; - if (read_index == decompressed_section.len) break; - if (read_result.end) { + i += status.len; + if (i == decompressed_section.len) break; + if (status.end) { gpa.free(decompressed_section); continue :shdrs; } diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index d1567c7560..7482acadf0 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -2034,7 +2034,7 @@ pub const VirtualMachine = struct { const streams: [2]*std.io.BufferedReader = .{ &cie_stream, &fde_stream }; for (&streams, 0..) |stream, i| { - while (stream.seek < stream.buffer.len) { + while (stream.seek < stream.storageBuffer().len) { const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); prev_row = try self.step(allocator, cie, i == 0, instruction); if (pc < fde.pc_begin + self.current_row.offset) return prev_row; diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index b1aab92855..8005dba4ad 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -1499,7 +1499,7 @@ pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFile error.FileDescriptorNotASocket, error.NetworkUnreachable, error.NetworkSubsystemFailed, - => return self.writeFileAllUnseekable(in_file, args), + => return self.writeFileUnseekableAll(in_file, args), else => |e| return e, }; @@ -1507,53 +1507,11 @@ pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFile /// Does not try seeking in either of the File parameters. /// See `writeFileAll` as an alternative to calling this. -pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void { - // TODO make `try @errorCast(...)` work - return @errorCast(writeFileAllUnseekableInner(self, in_file, args)); -} - -fn writeFileAllUnseekableInner(out_file: File, in_file: File, args: WriteFileOptions) anyerror!void { - const headers = args.headers_and_trailers[0..args.header_count]; - const trailers = args.headers_and_trailers[args.header_count..]; - - try out_file.writevAll(headers); - - // Some possible optimizations here: - // * Could writev buffer multiple times if the amount to discard is larger than 4096 - // * Could combine discard and read in one readv if amount to discard is small - - var buffer: [4096]u8 = undefined; - var remaining = args.in_offset; - while (remaining > 0) { - const n = try in_file.read(buffer[0..@min(buffer.len, remaining)]); - if (n == 0) return error.EndOfStream; - remaining -= n; - } - if (args.in_len) |len| { - remaining = len; - var buffer_index: usize = 0; - while (remaining > 0) { - const n = buffer_index + try in_file.read(buffer[buffer_index..@min(buffer.len, remaining)]); - if (n == 0) return error.EndOfStream; - const written = try out_file.write(buffer[0..n]); - if (written == 0) return error.EndOfStream; - remaining -= written; - std.mem.copyForwards(u8, &buffer, buffer[written..n]); - buffer_index = n - written; - } - } else { - var buffer_index: usize = 0; - while (true) { - const n = buffer_index + try in_file.read(buffer[buffer_index..]); - if (n == 0) break; - const written = try out_file.write(buffer[0..n]); - if (written == 0) return error.EndOfStream; - std.mem.copyForwards(u8, &buffer, buffer[written..n]); - buffer_index = n - written; - } - } - - try out_file.writevAll(trailers); +pub fn writeFileUnseekableAll(out_file: File, in_file: File, args: WriteFileOptions) WriteFileError!void { + _ = out_file; + _ = in_file; + _ = args; + @panic("TODO call writeFileUnseekable multiple times"); } /// Low level function which can fail for OS-specific reasons. @@ -1635,6 +1593,30 @@ pub fn reader(file: File) std.io.Reader { }; } +pub fn unseekableReader(file: File) std.io.Reader { + return .{ + .context = handleToOpaque(file.handle), + .vtable = .{ + .posRead = null, + .posReadVec = null, + .streamRead = reader_streamRead, + .streamReadVec = reader_streamReadVec, + }, + }; +} + +pub fn unstreamableReader(file: File) std.io.Reader { + return .{ + .context = handleToOpaque(file.handle), + .vtable = .{ + .posRead = reader_posRead, + .posReadVec = reader_posReadVec, + .streamRead = null, + .streamReadVec = null, + }, + }; +} + pub fn writer(file: File) std.io.Writer { return .{ .context = handleToOpaque(file.handle), @@ -1692,12 +1674,12 @@ pub fn reader_streamReadVec(context: ?*anyopaque, data: []const []u8) anyerror!s }; } -pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Result { +pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize { const file = opaqueToHandle(context); var splat_buffer: [256]u8 = undefined; if (is_windows) { if (data.len == 1 and splat == 0) return 0; - return .{ .len = windows.WriteFile(file, data[0], null) catch |err| return .{ .err = err } }; + return windows.WriteFile(file, data[0], null); } var iovecs: [max_buffers_len]std.posix.iovec_const = undefined; var len: usize = @min(iovecs.len, data.len); @@ -1706,8 +1688,8 @@ pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: .len = d.len, }; switch (splat) { - 0 => return .{ .len = std.posix.writev(file, iovecs[0 .. len - 1]) catch |err| return .{ .err = err } }, - 1 => return .{ .len = std.posix.writev(file, iovecs[0..len]) catch |err| return .{ .err = err } }, + 0 => return std.posix.writev(file, iovecs[0 .. len - 1]), + 1 => return std.posix.writev(file, iovecs[0..len]), else => { const pattern = data[data.len - 1]; if (pattern.len == 1) { @@ -1725,21 +1707,21 @@ pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat }; len += 1; } - return .{ .len = std.posix.writev(file, iovecs[0..len]) catch |err| return .{ .err = err } }; + return std.posix.writev(file, iovecs[0..len]); } }, } - return .{ .len = std.posix.writev(file, iovecs[0..len]) catch |err| return .{ .err = err } }; + return std.posix.writev(file, iovecs[0..len]); } pub fn writer_writeFile( context: ?*anyopaque, in_file: std.fs.File, - in_offset: u64, + in_offset: std.io.Writer.Offset, in_len: std.io.Writer.FileLen, headers_and_trailers: []const []const u8, headers_len: usize, -) std.io.Writer.Result { +) anyerror!usize { const out_fd = opaqueToHandle(context); const in_fd = in_file.handle; const len_int = switch (in_len) { @@ -1747,6 +1729,26 @@ pub fn writer_writeFile( .entire_file => 0, else => in_len.int(), }; + if (native_os == .linux) sf: { + // Linux sendfile does not support headers or trailers but it does + // support a streaming read from in_file. + if (headers_len > 0) return writer_writeSplat(context, headers_and_trailers[0..headers_len], 1); + const max_count = 0x7ffff000; // Avoid EINVAL. + const smaller_len = if (len_int == 0) max_count else @min(len_int, max_count); + var off: std.os.linux.off_t = undefined; + const off_ptr: ?*std.os.linux.off_t = if (in_offset.toInt()) |offset| b: { + off = try std.math.cast(std.os.linux.off_t, offset); + break :b &off; + } else null; + const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, smaller_len) catch |err| switch (err) { + error.UnsupportedOperation => break :sf, + error.Unseekable => break :sf, + error.Unexpected => break :sf, + else => |e| return e, + }; + if (in_offset.toInt()) |offset| assert(n == off - offset); + return n; + } var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined; const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, headers_and_trailers.len)]; for (iovecs, headers_and_trailers[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len }; @@ -1783,7 +1785,7 @@ fn writeFileUnseekable( @panic("TODO writeFileUnseekable"); } -fn handleToOpaque(handle: Handle) *anyopaque { +fn handleToOpaque(handle: Handle) ?*anyopaque { return switch (@typeInfo(Handle)) { .pointer => @ptrCast(handle), .int => @ptrFromInt(@as(u32, @bitCast(handle))), @@ -1791,7 +1793,7 @@ fn handleToOpaque(handle: Handle) *anyopaque { }; } -fn opaqueToHandle(userdata: *anyopaque) Handle { +fn opaqueToHandle(userdata: ?*anyopaque) Handle { return switch (@typeInfo(Handle)) { .pointer => @ptrCast(userdata), .int => @intCast(@intFromPtr(userdata)), @@ -1976,9 +1978,13 @@ pub fn downgradeLock(file: File) LockError!void { } } +const builtin = @import("builtin"); +const Os = std.builtin.Os; +const native_os = builtin.os.tag; +const is_windows = native_os == .windows; + const File = @This(); const std = @import("../std.zig"); -const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const posix = std.posix; const io = std.io; @@ -1986,7 +1992,5 @@ const math = std.math; const assert = std.debug.assert; const linux = std.os.linux; const windows = std.os.windows; -const Os = std.builtin.Os; const maxInt = std.math.maxInt; -const is_windows = builtin.os.tag == .windows; const Alignment = std.mem.Alignment; diff --git a/lib/std/io/BufferedReader.zig b/lib/std/io/BufferedReader.zig index 05fe0e8912..a158de7e27 100644 --- a/lib/std/io/BufferedReader.zig +++ b/lib/std/io/BufferedReader.zig @@ -23,6 +23,69 @@ pub fn init(br: *BufferedReader, r: Reader, buffer: []u8) void { br.storage.initFixed(buffer); } +const eof_writer: std.io.Writer.VTable = .{ + .writeSplat = eof_writeSplat, + .writeFile = eof_writeFile, +}; +const eof_reader: std.io.Reader.VTable = .{ + .posRead = eof_posRead, + .posReadVec = eof_posReadVec, + .streamRead = eof_streamRead, + .streamReadVec = eof_streamReadVec, +}; + +fn eof_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!Reader.Status { + _ = context; + _ = data; + _ = splat; + return error.NoSpaceLeft; +} + +fn eof_writeFile( + context: ?*anyopaque, + file: std.fs.File, + offset: u64, + len: Reader.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, +) anyerror!Reader.Status { + _ = context; + _ = file; + _ = offset; + _ = len; + _ = headers_and_trailers; + _ = headers_len; + return error.NoSpaceLeft; +} + +fn eof_posRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit, offset: u64) anyerror!Reader.Status { + _ = ctx; + _ = bw; + _ = limit; + _ = offset; + return error.EndOfStream; +} + +fn eof_posReadVec(ctx: ?*anyopaque, data: []const []u8, offset: u64) anyerror!Reader.Status { + _ = ctx; + _ = data; + _ = offset; + return error.EndOfStream; +} + +fn eof_streamRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) Reader.Status { + _ = ctx; + _ = bw; + _ = limit; + return error.EndOfStream; +} + +fn eof_streamReadVec(ctx: ?*anyopaque, data: []const []u8) Reader.Status { + _ = ctx; + _ = data; + return error.EndOfStream; +} + /// Constructs `br` such that it will read from `buffer` and then end. pub fn initFixed(br: *BufferedReader, buffer: []const u8) void { br.* = .{ @@ -31,19 +94,16 @@ pub fn initFixed(br: *BufferedReader, buffer: []const u8) void { .buffer = .initBuffer(@constCast(buffer)), .unbuffered_writer = .{ .context = undefined, - .vtable = &std.io.Writer.VTable.eof, + .vtable = &eof_writer, }, }, - .unbuffered_reader = &.{ - .context = undefined, - .vtable = &std.io.Reader.VTable.eof, - }, + .unbuffered_reader = &.{ .context = undefined, .vtable = &eof_reader }, }; } pub fn storageBuffer(br: *BufferedReader) []u8 { - assert(br.storage.unbuffered_writer.vtable == &std.io.Writer.VTable.eof); - assert(br.unbuffered_reader.vtable == &std.io.Reader.VTable.eof); + assert(br.storage.unbuffered_writer.vtable == &eof_writer); + assert(br.unbuffered_reader.vtable == &eof_reader); return br.storage.buffer.allocatedSlice(); } diff --git a/lib/std/io/BufferedWriter.zig b/lib/std/io/BufferedWriter.zig index 94a9b5db62..8bee5d0ed7 100644 --- a/lib/std/io/BufferedWriter.zig +++ b/lib/std/io/BufferedWriter.zig @@ -43,7 +43,7 @@ const fixed_vtable: Writer.VTable = .{ }; /// Replaces the `BufferedWriter` with a new one that writes to `buffer` and -/// then ends when it is full. +/// returns `error.NoSpaceLeft` when it is full. pub fn initFixed(bw: *BufferedWriter, buffer: []u8) void { bw.* = .{ .unbuffered_writer = .{ @@ -86,25 +86,25 @@ pub fn writableSlice(bw: *BufferedWriter, minimum_length: usize) anyerror![]u8 { return cap_slice; } const buffer = list.items; - const result = bw.unbuffered_writer.write(buffer); - if (result.len == buffer.len) { + const n = try bw.unbuffered_writer.write(buffer); + if (n == buffer.len) { @branchHint(.likely); list.items.len = 0; - try result.err; return list.unusedCapacitySlice(); } - if (result.len > 0) { - const remainder = buffer[result.len..]; + if (n > 0) { + const remainder = buffer[n..]; std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); list.items.len = remainder.len; } - try result.err; return list.unusedCapacitySlice(); } /// After calling `writableSlice`, this function tracks how many bytes were written to it. pub fn advance(bw: *BufferedWriter, n: usize) void { - bw.items.len += n; + const list = &bw.buffer; + list.items.len += n; + assert(list.items.len <= list.capacity); } /// The `data` parameter is mutable because this function needs to mutate the @@ -122,15 +122,15 @@ pub fn writevAll(bw: *BufferedWriter, data: [][]const u8) anyerror!void { } } -pub fn writeSplat(bw: *BufferedWriter, data: []const []const u8, splat: usize) Writer.Result { +pub fn writeSplat(bw: *BufferedWriter, data: []const []const u8, splat: usize) anyerror!usize { return passthru_writeSplat(bw, data, splat); } -pub fn writev(bw: *BufferedWriter, data: []const []const u8) Writer.Result { +pub fn writev(bw: *BufferedWriter, data: []const []const u8) anyerror!usize { return passthru_writeSplat(bw, data, 1); } -fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Result { +fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize { const bw: *BufferedWriter = @alignCast(@ptrCast(context)); const list = &bw.buffer; const buffer = list.allocatedSlice(); @@ -156,45 +156,27 @@ fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: us if (len >= remaining_data.len) { @branchHint(.likely); // Made it past the headers, so we can enable splatting. - const result = bw.unbuffered_writer.writeSplat(send_buffers, splat); - const n = result.len; + const n = try bw.unbuffered_writer.writeSplat(send_buffers, splat); if (n < end) { @branchHint(.unlikely); const remainder = buffer[n..end]; std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); list.items.len = remainder.len; - return .{ - .err = result.err, - .len = end - start_end, - .end = result.end, - }; + return end - start_end; } list.items.len = 0; - return .{ - .err = result.err, - .len = n - start_end, - .end = result.end, - }; + return n - start_end; } - const result = try bw.unbuffered_writer.writeSplat(send_buffers, 1); - const n = result.len; + const n = try bw.unbuffered_writer.writeSplat(send_buffers, 1); if (n < end) { @branchHint(.unlikely); const remainder = buffer[n..end]; std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); list.items.len = remainder.len; - return .{ - .err = result.err, - .len = end - start_end, - .end = result.end, - }; + return end - start_end; } list.items.len = 0; - return .{ - .err = result.err, - .len = n - start_end, - .end = result.end, - }; + return n - start_end; } const pattern = data[data.len - 1]; @@ -204,7 +186,7 @@ fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: us // It was added in the loop above; undo it here. end -= pattern.len; list.items.len = end; - return .{ .len = end - start_end }; + return end - start_end; } const remaining_splat = splat - 1; @@ -212,7 +194,7 @@ fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: us switch (pattern.len) { 0 => { list.items.len = end; - return .{ .len = end - start_end }; + return end - start_end; }, 1 => { const new_end = end + remaining_splat; @@ -220,29 +202,20 @@ fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: us @branchHint(.likely); @memset(buffer[end..new_end], pattern[0]); list.items.len = new_end; - return .{ .len = new_end - start_end }; + return new_end - start_end; } buffers[0] = buffer[0..end]; buffers[1] = pattern; - const result = bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat); - const n = result.len; + const n = try bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat); if (n < end) { @branchHint(.unlikely); const remainder = buffer[n..end]; std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); list.items.len = remainder.len; - return .{ - .err = result.err, - .len = end - start_end, - .end = result.end, - }; + return end - start_end; } list.items.len = 0; - return .{ - .err = result.err, - .len = n - start_end, - .end = result.end, - }; + return n - start_end; }, else => { const new_end = end + pattern.len * remaining_splat; @@ -252,29 +225,20 @@ fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: us @memcpy(buffer[end..][0..pattern.len], pattern); } list.items.len = new_end; - return .{ .len = new_end - start_end }; + return new_end - start_end; } buffers[0] = buffer[0..end]; buffers[1] = pattern; - const result = bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat); - const n = result.len; + const n = try bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat); if (n < end) { @branchHint(.unlikely); const remainder = buffer[n..end]; std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); list.items.len = remainder.len; - return .{ - .err = result.err, - .len = end - start_end, - .end = result.end, - }; + return end - start_end; } list.items.len = 0; - return .{ - .err = result.err, - .len = n - start_end, - .end = result.end, - }; + return n - start_end; }, } } @@ -282,13 +246,12 @@ fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: us /// When this function is called it means the buffer got full, so it's time /// to return an error. However, we still need to make sure all of the /// available buffer has been filled. -fn fixed_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Result { +fn fixed_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize { const bw: *BufferedWriter = @alignCast(@ptrCast(context)); const list = &bw.buffer; - const start_len = list.items.len; for (data) |bytes| { const dest = list.unusedCapacitySlice(); - if (dest.len == 0) return .{ .len = list.items.len - start_len, .end = true }; + if (dest.len == 0) return error.NoSpaceLeft; const len = @min(bytes.len, dest.len); @memcpy(dest[0..len], bytes[0..len]); list.items.len += len; @@ -301,60 +264,43 @@ fn fixed_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize else => for (0..splat - 1) |i| @memcpy(dest[i * pattern.len ..][0..pattern.len], pattern), } list.items.len = list.capacity; - return .{ .len = list.items.len - start_len, .end = true }; + return error.NoSpaceLeft; } -pub fn write(bw: *BufferedWriter, bytes: []const u8) Writer.Result { +pub fn write(bw: *BufferedWriter, bytes: []const u8) anyerror!usize { const list = &bw.buffer; const buffer = list.allocatedSlice(); const end = list.items.len; const new_end = end + bytes.len; if (new_end > buffer.len) { var data: [2][]const u8 = .{ buffer[0..end], bytes }; - const result = bw.unbuffered_writer.writev(&data); - const n = result.len; + const n = try bw.unbuffered_writer.writev(&data); if (n < end) { @branchHint(.unlikely); const remainder = buffer[n..end]; std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); list.items.len = remainder.len; - return .{ - .err = result.err, - .len = 0, - .end = result.end, - }; + return 0; } list.items.len = 0; - return .{ - .err = result.err, - .len = n - end, - .end = result.end, - }; + return n - end; } @memcpy(buffer[end..new_end], bytes); list.items.len = new_end; return bytes.len; } -pub fn writeAll(bw: *BufferedWriter, bytes: []const u8) anyerror!void { - if ((try writeUntilEnd(bw, bytes)) != bytes.len) return error.WriteStreamEnd; -} - +/// Convenience function that calls `writeAll` and then returns `bytes.len`. pub fn writeAllCount(bw: *BufferedWriter, bytes: []const u8) anyerror!usize { try writeAll(bw, bytes); return bytes.len; } -/// If the number returned is less than `bytes.len` it indicates end of stream. -pub fn writeUntilEnd(bw: *BufferedWriter, bytes: []const u8) anyerror!usize { +/// Calls `write` as many times as necessary such that all of `bytes` are +/// transferred. +pub fn writeAll(bw: *BufferedWriter, bytes: []const u8) anyerror!void { var index: usize = 0; - while (true) { - const result = write(bw, bytes[index..]); - try result.err; - index += result.len; - assert(index <= bytes.len); - if (index == bytes.len or result.end) return index; - } + while (index < bytes.len) index += try write(bw, bytes[index..]); } pub fn print(bw: *BufferedWriter, comptime format: []const u8, args: anytype) anyerror!void { @@ -365,65 +311,48 @@ pub fn printCount(bw: *BufferedWriter, comptime format: []const u8, args: anytyp return std.fmt.format(bw, format, args); } -pub fn writeByte(bw: *BufferedWriter, byte: u8) anyerror!void { - if ((try writeByteUntilEnd(bw, byte)) == 0) return error.WriteStreamEnd; -} - +/// Returns 0 or 1 indicating how many bytes were written. pub fn writeByteCount(bw: *BufferedWriter, byte: u8) anyerror!usize { try writeByte(bw, byte); return 1; } -/// Returns 0 or 1 indicating how many bytes were written. -/// `0` means end of stream encountered. -pub fn writeByteUntilEnd(bw: *BufferedWriter, byte: u8) anyerror!usize { +pub fn writeByte(bw: *BufferedWriter, byte: u8) anyerror!void { const list = &bw.buffer; const buffer = list.items; if (buffer.len < list.capacity) { @branchHint(.likely); buffer.ptr[buffer.len] = byte; list.items.len = buffer.len + 1; - return 1; + return; } var buffers: [2][]const u8 = .{ buffer, &.{byte} }; while (true) { - const result = bw.unbuffered_writer.writev(&buffers); - try result.err; - const n = result.len; + const n = try bw.unbuffered_writer.writev(&buffers); if (n == 0) { @branchHint(.unlikely); - if (result.end) return 0; continue; } else if (n >= buffer.len) { @branchHint(.likely); if (n > buffer.len) { @branchHint(.likely); list.items.len = 0; - return 1; + return; } else { buffer[0] = byte; list.items.len = 1; - return 1; + return; } } const remainder = buffer[n..]; std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); buffer[remainder.len] = byte; list.items.len = remainder.len + 1; - return 1; + return; } } -/// Writes the same byte many times, performing the underlying write call as -/// many times as necessary, returning `error.WriteStreamEnd` if the byte -/// could not be repeated `n` times. -pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void { - if ((try splatByteUntilEnd(bw, byte, n)) != n) return error.WriteStreamEnd; -} - -/// Writes the same byte many times, performing the underlying write call as -/// many times as necessary, returning `error.WriteStreamEnd` if the byte -/// could not be repeated `n` times, or returning `n` on success. +/// Convenience function that calls `splatByteAll` and then returns `n`. pub fn splatByteAllCount(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize { try splatByteAll(bw, byte, n); return n; @@ -431,23 +360,15 @@ pub fn splatByteAllCount(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize /// Writes the same byte many times, performing the underlying write call as /// many times as necessary. -/// -/// If the number returned is less than `n` it indicates end of stream. -pub fn splatByteUntilEnd(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize { - var index: usize = 0; - while (true) { - const result = splatByte(bw, byte, n - index); - try result.err; - index += result.len; - assert(index <= n); - if (index == n or result.end) return index; - } +pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void { + var remaining: usize = n; + while (remaining > 0) remaining -= try splatByte(bw, byte, remaining); } /// Writes the same byte many times, allowing short writes. /// /// Does maximum of one underlying `Writer.VTable.writeSplat`. -pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) Writer.Result { +pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize { return passthru_writeSplat(bw, &.{&.{byte}}, n); } diff --git a/lib/std/io/Reader.zig b/lib/std/io/Reader.zig index 2177e917b3..2cc914092b 100644 --- a/lib/std/io/Reader.zig +++ b/lib/std/io/Reader.zig @@ -19,8 +19,8 @@ pub const VTable = struct { /// /// If this is `null` it is equivalent to always returning /// `error.Unseekable`. - posRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) RwResult, - posReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8, offset: u64) Result, + posRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) anyerror!Status, + posReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8, offset: u64) anyerror!Status, /// Writes bytes from the internally tracked stream position to `bw`, or /// returns `error.Unstreamable`, indicating `posRead` should be used @@ -37,25 +37,18 @@ pub const VTable = struct { /// /// If this is `null` it is equivalent to always returning /// `error.Unstreamable`. - streamRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) RwResult, - streamReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8) Result, - - pub const eof: VTable = .{ - .posRead = eof_posRead, - .posReadVec = eof_posReadVec, - .streamRead = eof_streamRead, - .streamReadVec = eof_streamReadVec, - }; + streamRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) anyerror!Status, + streamReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8) anyerror!Status, }; -pub const Result = std.io.Writer.Result; +pub const Len = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } }); -pub const RwResult = struct { - len: usize = 0, - read_err: anyerror!void = {}, - write_err: anyerror!void = {}, - read_end: bool = false, - write_end: bool = false, +pub const Status = packed struct(usize) { + /// Number of bytes that were transferred. Zero does not mean end of + /// stream. + len: Len = 0, + /// Indicates end of stream. + end: bool = false, }; pub const Limit = enum(usize) { @@ -171,31 +164,3 @@ test "when the backing reader provides one byte at a time" { defer std.testing.allocator.free(res); try std.testing.expectEqualStrings(str, res); } - -fn eof_posRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) RwResult { - _ = ctx; - _ = bw; - _ = limit; - _ = offset; - return .{ .end = true }; -} - -fn eof_posReadVec(ctx: ?*anyopaque, data: []const []u8, offset: u64) Result { - _ = ctx; - _ = data; - _ = offset; - return .{ .end = true }; -} - -fn eof_streamRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) RwResult { - _ = ctx; - _ = bw; - _ = limit; - return .{ .end = true }; -} - -fn eof_streamReadVec(ctx: ?*anyopaque, data: []const []u8) Result { - _ = ctx; - _ = data; - return .{ .end = true }; -} diff --git a/lib/std/io/Writer.zig b/lib/std/io/Writer.zig index eff9962927..bdd5de86cc 100644 --- a/lib/std/io/Writer.zig +++ b/lib/std/io/Writer.zig @@ -17,7 +17,7 @@ pub const VTable = struct { /// Number of bytes returned may be zero, which does not mean /// end-of-stream. A subsequent call may return nonzero, or may signal end /// of stream via an error. - writeSplat: *const fn (ctx: ?*anyopaque, data: []const []const u8, splat: usize) Result, + writeSplat: *const fn (ctx: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize, /// Writes contents from an open file. `headers` are written first, then `len` /// bytes of `file` starting from `offset`, then `trailers`. @@ -38,23 +38,7 @@ pub const VTable = struct { /// zero, they can be forwarded directly to `VTable.writev`. headers_and_trailers: []const []const u8, headers_len: usize, - ) Result, - - pub const eof: VTable = .{ - .writeSplat = eof_writeSplat, - .writeFile = eof_writeFile, - }; -}; - -pub const Result = struct { - /// Even when a failure occurs, `len` may be nonzero, and `end` may be - /// true. - err: anyerror!void = {}, - /// Number of bytes that were transferred. When an error occurs, ideally - /// this will be zero, but may not always be the case. - len: usize = 0, - /// Indicates end of stream. - end: bool = false, + ) anyerror!usize, }; pub const Offset = enum(u64) { @@ -90,11 +74,11 @@ pub const FileLen = enum(u64) { } }; -pub fn writev(w: Writer, data: []const []const u8) Result { +pub fn writev(w: Writer, data: []const []const u8) anyerror!usize { return w.vtable.writeSplat(w.context, data, 1); } -pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) Result { +pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) anyerror!usize { return w.vtable.writeSplat(w.context, data, splat); } @@ -105,7 +89,7 @@ pub fn writeFile( len: FileLen, headers_and_trailers: []const []const u8, headers_len: usize, -) Result { +) anyerror!usize { return w.vtable.writeFile(w.context, file, offset, len, headers_and_trailers, headers_len); } @@ -116,14 +100,14 @@ pub fn unimplemented_writeFile( len: FileLen, headers_and_trailers: []const []const u8, headers_len: usize, -) Result { +) anyerror!usize { _ = context; _ = file; _ = offset; _ = len; _ = headers_and_trailers; _ = headers_len; - return .{ .err = error.Unimplemented }; + return error.Unimplemented; } pub fn buffered(w: Writer, buffer: []u8) std.io.BufferedWriter { @@ -146,7 +130,7 @@ pub const @"null": Writer = .{ }, }; -fn null_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Result { +fn null_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize { _ = context; const headers = data[0 .. data.len - 1]; const pattern = data[headers.len..]; @@ -162,14 +146,14 @@ fn null_writeFile( len: FileLen, headers_and_trailers: []const []const u8, headers_len: usize, -) Result { +) anyerror!usize { _ = context; var n: usize = 0; if (len == .entire_file) { const headers = headers_and_trailers[0..headers_len]; for (headers) |bytes| n += bytes.len; if (offset.toInt()) |off| { - const stat = file.stat() catch |err| return .{ .err = err, .len = n }; + const stat = try file.stat(); n += stat.size - off; for (headers_and_trailers[headers_len..]) |bytes| n += bytes.len; return .{ .len = n }; @@ -183,27 +167,3 @@ fn null_writeFile( test @"null" { try @"null".writeAll("yay"); } - -fn eof_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Result { - _ = context; - _ = data; - _ = splat; - return .{ .end = true }; -} - -fn eof_writeFile( - context: ?*anyopaque, - file: std.fs.File, - offset: u64, - len: FileLen, - headers_and_trailers: []const []const u8, - headers_len: usize, -) Result { - _ = context; - _ = file; - _ = offset; - _ = len; - _ = headers_and_trailers; - _ = headers_len; - return .{ .end = true }; -} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 414a9289b3..1d492c146b 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -6359,7 +6359,7 @@ pub const SendFileError = PReadError || WriteError || SendError; pub fn sendfile( out_fd: fd_t, in_fd: fd_t, - in_offset: u64, + in_offset: ?u64, in_len: u64, headers: []const iovec_const, trailers: []const iovec_const, @@ -6390,8 +6390,9 @@ pub fn sendfile( const sendfile_sym = if (lfs64_abi) system.sendfile64 else system.sendfile; while (true) { - var offset: off_t = @bitCast(in_offset); - const rc = sendfile_sym(out_fd, in_fd, &offset, adjusted_count); + var offset: off_t = if (in_offset) |o| o else undefined; + const offset_pointer: ?*off_t = if (in_offset) &offset else null; + const rc = sendfile_sym(out_fd, in_fd, offset_pointer, adjusted_count); switch (errno(rc)) { .SUCCESS => { const amt: usize = @bitCast(rc);