From 1bb75f9d6276ddf6b69717d9b1c2f68140727425 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 23 Apr 2025 13:32:51 -0700 Subject: [PATCH] stabilize readRemainingArrayList and readRemainingAlloc API --- lib/std/Build.zig | 8 ++-- lib/std/Build/Step/Run.zig | 14 ++++++- lib/std/debug/Dwarf.zig | 2 +- lib/std/fs/Dir.zig | 27 ++++++++----- lib/std/fs/File.zig | 40 ------------------- lib/std/http/test.zig | 46 ++++++++++----------- lib/std/io/BufferedReader.zig | 75 +++++++++++++++++++++++++++++++++-- lib/std/io/Reader.zig | 68 ++++++++++++++++++++++--------- 8 files changed, 178 insertions(+), 102 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index fbb280d425e9c674b1e23b857a7bdec255e8f1e8..1af096207a496100d74b2538b01032a77d63f8b0 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -179,7 +179,8 @@ const InitializedDepContext = struct { }; pub const RunError = error{ - ReadFailure, + ReadFailed, + StreamTooLong, ExitCodeFailure, ProcessTerminated, ExecNotSupported, @@ -2059,9 +2060,8 @@ pub fn runAllowFail( try Step.handleVerbose2(b, null, child.env_map, argv); try child.spawn(); - const stdout = child.stdout.?.readToEndAlloc(b.allocator, .limited(max_output_size)) catch { - return error.ReadFailure; - }; + var file_reader = child.stdout.?.readerStreaming(); + const stdout = try file_reader.interface().readRemainingAlloc(b.allocator, .limited(max_output_size)); errdefer b.allocator.free(stdout); const term = try child.wait(); diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index c5f7149aedf3587c8ec7c58cf55fdf5e632cbc9f..7371820a8fcbf093591b8ca9874f3f89d668c414 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1791,10 +1791,20 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult { stdout_bytes = poller.reader(.stdout).bufferContents(); stderr_bytes = poller.reader(.stderr).bufferContents(); } else { - stdout_bytes = try stdout.readToEndAlloc(arena, run.stdio_limit); + var fr = stdout.readerStreaming(); + stdout_bytes = fr.interface().readRemainingAlloc(arena, run.stdio_limit) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ReadFailed => return fr.err.?, + error.StreamTooLong => return error.StdoutStreamTooLong, + }; } } else if (child.stderr) |stderr| { - stderr_bytes = try stderr.readToEndAlloc(arena, run.stdio_limit); + var fr = stderr.readerStreaming(); + stderr_bytes = fr.interface().readRemainingAlloc(arena, run.stdio_limit) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ReadFailed => return fr.err.?, + error.StreamTooLong => return error.StderrStreamTooLong, + }; } if (stderr_bytes) |bytes| if (bytes.len > 0) { diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 4106b7d621bdea03fffb914ecf87ffec9968e76e..4bca4f1c9d60cdf0f52e567795c8a0a358dde1ce 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -2243,7 +2243,7 @@ pub const ElfModule = struct { var zlib_stream: std.compress.zlib.Decompressor = .init(§ion_reader); - const decompressed_section = zlib_stream.reader().readAlloc(gpa, ch_size) catch continue; + const decompressed_section = zlib_stream.reader().readRemainingAlloc(gpa, .limited(ch_size)) catch continue; if (decompressed_section.len != ch_size) { gpa.free(decompressed_section); continue; diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 22f2565e371649b132a9da1ed08b179c69b955eb..c34b876c8054c00330d892f3cba69a1f9904d195 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -1963,6 +1963,8 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 { return buffer[0..end_index]; } +pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{StreamTooLong}; + /// Reads all the bytes from the named file. On success, caller owns returned /// buffer. pub fn readFileAlloc( @@ -1972,13 +1974,13 @@ pub fn readFileAlloc( /// On other platforms, an opaque sequence of bytes with no particular encoding. file_path: []const u8, /// Used to allocate the result. - gpa: mem.Allocator, + gpa: Allocator, /// If exceeded: /// * The array list's length is increased by exactly one byte past `limit`. /// * The file seek position is advanced by exactly one byte past `limit`. - /// * `error.FileTooBig` is returned. + /// * `error.StreamTooLong` is returned. limit: std.io.Reader.Limit, -) (File.OpenError || File.ReadAllocError)![]u8 { +) ReadFileAllocError![]u8 { return dir.readFileAllocOptions(file_path, gpa, limit, null, .of(u8), null); } @@ -1991,18 +1993,18 @@ pub fn readFileAllocOptions( /// On other platforms, an opaque sequence of bytes with no particular encoding. file_path: []const u8, /// Used to allocate the result. - gpa: mem.Allocator, + gpa: Allocator, /// If exceeded: /// * The array list's length is increased by exactly one byte past `limit`. /// * The file seek position is advanced by exactly one byte past `limit`. - /// * `error.FileTooBig` is returned. + /// * `error.StreamTooLong` is returned. limit: std.io.Reader.Limit, /// If specified, the initial buffer size is calculated using this value, /// otherwise the effective file size is used instead. size_hint: ?usize, comptime alignment: std.mem.Alignment, comptime optional_sentinel: ?u8, -) (File.OpenError || File.ReadAllocError)!(if (optional_sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) { +) ReadFileAllocError!(if (optional_sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) { var buffer: std.ArrayListAlignedUnmanaged(u8, alignment) = .empty; defer buffer.deinit(gpa); try readFileIntoArrayList( @@ -2026,7 +2028,7 @@ pub fn readFileAllocOptions( /// If `limit` is exceeded: /// * The array list's length is increased by exactly one byte past `limit`. /// * The file seek position is advanced by exactly one byte past `limit`. -/// * `error.FileTooBig` is returned. +/// * `error.StreamTooLong` is returned. pub fn readFileIntoArrayList( dir: Dir, /// On Windows, should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). @@ -2040,7 +2042,7 @@ pub fn readFileIntoArrayList( size_hint: ?usize, comptime alignment: ?std.mem.Alignment, list: *std.ArrayListAlignedUnmanaged(u8, alignment), -) (File.OpenError || File.ReadAllocError)!void { +) ReadFileAllocError!void { var file = try dir.openFile(file_path, .{}); defer file.close(); @@ -2049,14 +2051,19 @@ pub fn readFileIntoArrayList( try list.ensureUnusedCapacity(gpa, size); } else if (file.getEndPos()) |size| { // If the file size doesn't fit a usize it'll be certainly exceed the limit. - try list.ensureUnusedCapacity(gpa, std.math.cast(usize, size) orelse return error.FileTooBig); + try list.ensureUnusedCapacity(gpa, std.math.cast(usize, size) orelse return error.StreamTooLong); } else |err| switch (err) { // Ignore most errors; size hint is only an optimization. error.Unexpected, error.AccessDenied, error.PermissionDenied => {}, else => |e| return e, } - try file.readIntoArrayList(gpa, limit, alignment, list); + var file_reader = file.reader(); + file_reader.interface().readRemainingArrayList(gpa, alignment, list, limit) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.StreamTooLong => return error.StreamTooLong, + error.ReadFailed => return file_reader.err.?, + }; } pub const DeleteTreeError = error{ diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index ec6121678fe995e1940086a7399ee84ef929b4b1..a7d96fc358fb81e69a77c7aee0af226568f4b92b 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -788,46 +788,6 @@ pub fn updateTimes( try posix.futimens(self.handle, ×); } -pub const ReadAllocError = ReadError || Allocator.Error || error{FileTooBig}; - -/// Reads all the bytes from the current position to the end of the file. -/// -/// On success, caller owns returned buffer. -/// -/// If `limit` is exceeded, returns `error.FileTooBig`. -pub fn readToEndAlloc(file: File, gpa: Allocator, limit: std.io.Reader.Limit) ReadAllocError![]u8 { - var buffer: std.ArrayListUnmanaged(u8) = .empty; - defer buffer.deinit(gpa); - try buffer.ensureUnusedCapacity(gpa, std.heap.page_size_min); - try readIntoArrayList(file, gpa, limit, null, &buffer); - return buffer.toOwnedSlice(gpa); -} - -/// Reads all the bytes from the current position to the end of the file, -/// appending them into the provided array list. -/// -/// If `limit` is exceeded: -/// * The array list's length is increased by exactly one byte past `limit`. -/// * The file seek position is advanced by exactly one byte past `limit`. -/// * `error.FileTooBig` is returned. -pub fn readIntoArrayList( - file: File, - gpa: Allocator, - limit: std.io.Reader.Limit, - comptime alignment: ?std.mem.Alignment, - list: *std.ArrayListAlignedUnmanaged(u8, alignment), -) ReadAllocError!void { - var remaining = limit; - while (true) { - try list.ensureUnusedCapacity(gpa, 1); - const buffer = remaining.slice1(list.unusedCapacitySlice()); - const n = try read(file, buffer); - if (n == 0) return; - list.items.len += n; - remaining = remaining.subtract(n) orelse return error.FileTooBig; - } -} - pub const ReadError = posix.ReadError; pub const PReadError = posix.PReadError; diff --git a/lib/std/http/test.zig b/lib/std/http/test.zig index 850b829bd052fa86312dd95b59a4309af707bbb2..5946ac461df8da7b302629cdb40d45ba7708dc85 100644 --- a/lib/std/http/test.zig +++ b/lib/std/http/test.zig @@ -70,7 +70,7 @@ test "trailers" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -153,7 +153,7 @@ test "HTTP server handles a chunked transfer coding request" { "content-type: text/plain\r\n" ++ "\r\n" ++ "message from server!\n"; - const response = try stream.reader().readAllAlloc(gpa, expected_response.len); + const response = try stream.reader().readRemainingAlloc(gpa, expected_response.len); defer gpa.free(response); try expectEqualStrings(expected_response, response); } @@ -206,7 +206,7 @@ test "echo content server" { // request.head.target, //}); - const body = try (try request.reader()).readAllAlloc(std.testing.allocator, 8192); + const body = try (try request.reader()).readRemainingAlloc(std.testing.allocator, 8192); defer std.testing.allocator.free(body); try expect(mem.startsWith(u8, request.head.target, "/echo-content")); @@ -291,7 +291,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { var writer = stream.writer().unbuffered(); try writer.writeAll(request_bytes); - const response = try stream.reader().readAllAlloc(gpa, 8192); + const response = try stream.reader().readRemainingAlloc(gpa, 8192); defer gpa.free(response); var expected_response = std.ArrayList(u8).init(gpa); @@ -362,7 +362,7 @@ test "receiving arbitrary http headers from the client" { var writer = stream_writer.interface().unbuffered(); try writer.writeAll(request_bytes); - const response = try stream.reader().readAllAlloc(gpa, 8192); + const response = try stream.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(response); var expected_response = std.ArrayList(u8).init(gpa); @@ -419,7 +419,7 @@ test "general client/server API coverage" { }); const gpa = std.testing.allocator; - const body = try (try request.reader()).readAllAlloc(gpa, 8192); + const body = try (try request.reader()).readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); if (mem.startsWith(u8, request.head.target, "/get")) { @@ -568,7 +568,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -593,7 +593,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192 * 1024); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192 * 1024)); defer gpa.free(body); try expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len); @@ -617,7 +617,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("", body); @@ -643,7 +643,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -668,7 +668,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("", body); @@ -695,7 +695,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -725,7 +725,7 @@ test "general client/server API coverage" { try std.testing.expectEqual(.ok, req.response.status); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("", body); @@ -764,7 +764,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -788,7 +788,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -812,7 +812,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -855,7 +855,7 @@ test "general client/server API coverage" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Encoded redirect successful!\n", body); @@ -946,7 +946,7 @@ test "Server streams both reading and writing" { var server = http.Server.init(&connection_br, &connection_bw); var request = try server.receiveHead(); var read_buffer: [100]u8 = undefined; - var br = try request.reader().buffered(&read_buffer); + var br = (try request.reader()).buffered(&read_buffer); var response = try request.respondStreaming(.{ .respond_options = .{ .transfer_encoding = .none, // Causes keep_alive=false @@ -993,7 +993,7 @@ test "Server streams both reading and writing" { try req.finish(); - const body = try req.reader().readAllAlloc(std.testing.allocator, 8192); + const body = try req.reader().readRemainingAlloc(std.testing.allocator, .limited(8192)); defer std.testing.allocator.free(body); try expectEqualStrings("ONE FISH", body); @@ -1027,7 +1027,7 @@ fn echoTests(client: *http.Client, port: u16) !void { try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -1062,7 +1062,7 @@ fn echoTests(client: *http.Client, port: u16) !void { try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -1118,7 +1118,7 @@ fn echoTests(client: *http.Client, port: u16) !void { try req.wait(); try expectEqual(.ok, req.response.status); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("Hello, World!\n", body); @@ -1258,7 +1258,7 @@ test "redirect to different connection" { try req.send(); try req.wait(); - const body = try req.reader().readAllAlloc(gpa, 8192); + const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); defer gpa.free(body); try expectEqualStrings("good job, you pass", body); diff --git a/lib/std/io/BufferedReader.zig b/lib/std/io/BufferedReader.zig index b4e6d6dc639736c9bf5e421035c9d0a2f3c180e0..d450f1bad89e1397c28aab7de7107cb8bd6c25dc 100644 --- a/lib/std/io/BufferedReader.zig +++ b/lib/std/io/BufferedReader.zig @@ -7,6 +7,7 @@ const testing = std.testing; const BufferedWriter = std.io.BufferedWriter; const Reader = std.io.Reader; const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayListUnmanaged; const BufferedReader = @This(); @@ -373,6 +374,8 @@ pub fn readShort(br: *BufferedReader, buffer: []u8) Reader.ShortError!usize { @panic("TODO"); } +pub const ReadAllocError = Reader.Error || Allocator.Error; + /// The function is inline to avoid the dead code in case `endian` is /// comptime-known and matches host endianness. pub inline fn readSliceEndianAlloc( @@ -389,8 +392,6 @@ pub inline fn readSliceEndianAlloc( return dest; } -pub const ReadAllocError = Reader.Error || Allocator.Error; - pub fn readSliceAlloc(br: *BufferedReader, allocator: Allocator, len: usize) ReadAllocError![]u8 { const dest = try allocator.alloc(u8, len); errdefer allocator.free(dest); @@ -398,6 +399,74 @@ pub fn readSliceAlloc(br: *BufferedReader, allocator: Allocator, len: usize) Rea return dest; } +/// Transfers all bytes from the current position to the end of the stream, up +/// to `limit`, returning them as a caller-owned allocated slice. +/// +/// If `limit` is exceeded, returns `error.StreamTooLong`. In such case, the +/// stream is advanced an unspecified amount, and the consumed data is +/// unrecoverable. The other function listed below does not have this caveat. +/// +/// Asserts `br` was initialized with at least one byte of storage capacity. +/// +/// See also: +/// * `readRemainingArrayList` +pub fn readRemainingAlloc(r: Reader, gpa: Allocator, limit: Reader.Limit) Reader.LimitedAllocError![]u8 { + var buffer: ArrayList(u8) = .empty; + defer buffer.deinit(gpa); + try readRemainingArrayList(r, gpa, null, &buffer, limit); + return buffer.toOwnedSlice(gpa); +} + +/// Transfers all bytes from the current position to the end of the stream, up +/// to `limit`, appending them to `list`. +/// +/// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In +/// such case, the stream is in a well-defined state. The next byte that would +/// be read will be the first one to exceed `limit`, and all preceeding bytes +/// have been appended to `list`. +/// +/// Asserts `br` was initialized with at least one byte of storage capacity. +/// +/// See also: +/// * `readRemainingAlloc` +pub fn readRemainingArrayList( + br: *BufferedReader, + gpa: Allocator, + comptime alignment: ?std.mem.Alignment, + list: *std.ArrayListAlignedUnmanaged(u8, alignment), + limit: Reader.Limit, +) Reader.LimitedAllocError!void { + const buffer = br.buffer; + const buffered = buffer[br.seek..br.end]; + const copy_len = limit.minInt(buffered.len); + try list.ensureUnusedCapacity(gpa, copy_len); + @memcpy(list.unusedCapacitySlice()[0..copy_len], buffer[0..copy_len]); + list.items.len += copy_len; + br.seek += copy_len; + if (copy_len == buffered.len) { + br.seek = 0; + br.end = 0; + } + var remaining = limit.subtract(copy_len).?; + while (true) { + try list.ensureUnusedCapacity(gpa, 1); + const dest = remaining.slice(list.unusedCapacitySlice()); + const additional_buffer = if (@intFromEnum(remaining) == dest.len) buffer else &.{}; + const n = br.unbuffered_reader.readVec(&.{ dest, additional_buffer }) catch |err| switch (err) { + error.EndOfStream => break, + error.ReadFailed => return error.ReadFailed, + }; + if (n >= dest.len) { + br.end = n - dest.len; + list.items.len += dest.len; + if (n == dest.len) return; + return error.StreamTooLong; + } + list.items.len += n; + remaining = remaining.subtract(n).?; + } +} + pub const DelimiterInclusiveError = error{ /// See the `Reader` implementation for detailed diagnostics. ReadFailed, @@ -775,7 +844,7 @@ pub fn writableSliceGreedyAlloc( br.seek = 0; } { - var list: std.ArrayListUnmanaged(u8) = .{ + var list: ArrayList(u8) = .{ .items = br.buffer[0..br.end], .capacity = br.buffer.len, }; diff --git a/lib/std/io/Reader.zig b/lib/std/io/Reader.zig index 7290d9b5580163f0332d539f697c459d0cdf4390..98b8992df4e6e74da07ffdd92386d48e110497c8 100644 --- a/lib/std/io/Reader.zig +++ b/lib/std/io/Reader.zig @@ -2,6 +2,9 @@ const std = @import("../std.zig"); const Reader = @This(); const assert = std.debug.assert; const BufferedWriter = std.io.BufferedWriter; +const BufferedReader = std.io.BufferedReader; +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayListUnmanaged; context: ?*anyopaque, vtable: *const VTable, @@ -166,29 +169,56 @@ pub fn discardRemaining(r: Reader) ShortError!usize { } } -pub const ReadAllocError = std.mem.Allocator.Error || ShortError; +pub const LimitedAllocError = Allocator.Error || ShortError || error{StreamTooLong}; -/// Allocates enough memory to hold all the contents of the stream. If the allocated -/// memory would be greater than `max_size`, returns `error.StreamTooLong`. +/// Transfers all bytes from the current position to the end of the stream, up +/// to `limit`, returning them as a caller-owned allocated slice. /// -/// Caller owns returned memory. +/// If `limit` is exceeded, returns `error.StreamTooLong`. In such case, the +/// stream is advanced one byte beyond the limit, and the consumed data is +/// unrecoverable. Other functions listed below do not have this caveat. /// -/// If this function returns an error, the contents from the stream read so far are lost. -pub fn readAlloc(r: Reader, gpa: std.mem.Allocator, max_size: usize) ReadAllocError![]u8 { - const readFn = r.vtable.read; - var aw: std.io.AllocatingWriter = undefined; - aw.init(gpa); - errdefer aw.deinit(); - var remaining = max_size; - while (remaining > 0) { - const n = readFn(r.context, &aw.buffered_writer, .limited(remaining)) catch |err| switch (err) { - error.WriteFailed => return error.OutOfMemory, - error.EndOfStream => break, +/// See also: +/// * `readRemainingArrayList` +/// * `BufferedReader.readRemainingArrayList` +pub fn readRemainingAlloc(r: Reader, gpa: Allocator, limit: Reader.Limit) LimitedAllocError![]u8 { + var buffer: ArrayList(u8) = .empty; + defer buffer.deinit(gpa); + try readRemainingArrayList(r, gpa, null, &buffer, limit); + return buffer.toOwnedSlice(gpa); +} + +/// Transfers all bytes from the current position to the end of the stream, up +/// to `limit`, appending them to `list`. +/// +/// If `limit` is exceeded: +/// * The array list's length is increased by exactly one byte past `limit`. +/// * The stream seek position is advanced by exactly one byte past `limit`. +/// * `error.StreamTooLong` is returned. +/// +/// The other function listed below has different semantics for an exceeded +/// limit. +/// +/// See also: +/// * `BufferedReader.readRemainingArrayList` +pub fn readRemainingArrayList( + r: Reader, + gpa: Allocator, + comptime alignment: ?std.mem.Alignment, + list: *std.ArrayListAlignedUnmanaged(u8, alignment), + limit: Limit, +) LimitedAllocError!void { + var remaining = limit; + while (true) { + try list.ensureUnusedCapacity(gpa, 1); + const buffer = remaining.slice1(list.unusedCapacitySlice()); + const n = r.vtable.readVec(r.context, &.{buffer}) catch |err| switch (err) { + error.EndOfStream => return, error.ReadFailed => return error.ReadFailed, }; - remaining -= n; + list.items.len += n; + remaining = remaining.subtract(n) orelse return error.StreamTooLong; } - return aw.toOwnedSlice(); } pub const failing: Reader = .{ @@ -209,11 +239,11 @@ pub const ending: Reader = .{ }, }; -pub fn unbuffered(r: Reader) std.io.BufferedReader { +pub fn unbuffered(r: Reader) BufferedReader { return buffered(r, &.{}); } -pub fn buffered(r: Reader, buffer: []u8) std.io.BufferedReader { +pub fn buffered(r: Reader, buffer: []u8) BufferedReader { return .{ .unbuffered_reader = r, .seek = 0, -- 2.54.0