authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-13 18:57:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:26-07:00
log1164d5ece5b12b573c6501c94b9ad9e326199ba9
treece3d85d2a8fa90930f93e0c97835e40bece10ce9
parent383afd19d73c7d98c8d1f7fe9d474b980517372e

tweak std.io.Writer and followups

remove std.fs.Dir.readFileAllocOptions, replace with more flexible API readFileIntoArrayList remove std.fs.File.readToEndAllocOptions, replace with more flexible API readIntoArrayList update std.fs.File to new reader/writer API add helper functions to std.io.Reader.Limit replace std.io.Writer.FileLen with std.io.Reader.Limit make offset a type rather than u64 so that it can distinguish between streaming read and positional read avoid an unnecessary allocation in std.zig.readSourceFileToEndAlloc when there is a UTF-16 little endian BOM.

8 files changed, 263 insertions(+), 210 deletions(-)

lib/compiler/std-docs.zig+23-13
......@@ -1,13 +1,12 @@
11const builtin = @import("builtin");
22const std = @import("std");
33const mem = std.mem;
4const io = std.io;
54const Allocator = std.mem.Allocator;
65const assert = std.debug.assert;
76const Cache = std.Build.Cache;
87
98fn usage() noreturn {
10 io.getStdOut().writeAll(
9 std.fs.File.stdout().writeAll(
1110 \\Usage: zig std [options]
1211 \\
1312 \\Options:
......@@ -63,7 +62,7 @@ pub fn main() !void {
6362 var http_server = try address.listen(.{});
6463 const port = http_server.listen_address.in.getPort();
6564 const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port});
66 std.io.getStdOut().writeAll(url_with_newline) catch {};
65 std.fs.File.stdout().writeAll(url_with_newline) catch {};
6766 if (should_open_browser) {
6867 openBrowserTab(gpa, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {
6968 std.log.err("unable to open browser: {s}", .{@errorName(err)});
......@@ -155,18 +154,29 @@ fn serveDocsFile(
155154 name: []const u8,
156155 content_type: []const u8,
157156) !void {
158 const gpa = context.gpa;
159 // The desired API is actually sendfile, which will require enhancing std.http.Server.
160 // We load the file with every request so that the user can make changes to the file
161 // and refresh the HTML page without restarting this server.
162 const file_contents = try context.lib_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);
163 defer gpa.free(file_contents);
164 try request.respond(file_contents, .{
165 .extra_headers = &.{
166 .{ .name = "content-type", .value = content_type },
167 cache_control_header,
157 // Open the file with every request so that the user can make changes to
158 // the file and refresh the HTML page without restarting this server.
159 var file = try context.lib_dir.openFile(name, .{});
160 defer file.close();
161 const content_length = std.math.cast(usize, (try file.stat()).size) orelse return error.FileTooBig;
162
163 var send_buffer: [4000]u8 = undefined;
164 var response = request.respondStreaming(.{
165 .send_buffer = &send_buffer,
166 .content_length = content_length,
167 .respond_options = .{
168 .extra_headers = &.{
169 .{ .name = "content-type", .value = content_type },
170 cache_control_header,
171 },
168172 },
169173 });
174
175 try response.writer().unbuffered().writeFileAll(file, .{
176 .offset = .zero,
177 .limit = .init(content_length),
178 });
179 try response.end();
170180}
171181
172182fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
lib/std/fs/Dir.zig+54-30
......@@ -1963,41 +1963,65 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
19631963 return buffer[0..end_index];
19641964}
19651965
1966/// On success, caller owns returned buffer.
1967/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1968/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1969/// On WASI, `file_path` should be encoded as valid UTF-8.
1970/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1971pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
1972 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, .of(u8), null);
1973}
1974
1975/// On success, caller owns returned buffer.
1976/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1977/// If `size_hint` is specified the initial buffer size is calculated using
1978/// that value, otherwise the effective file size is used instead.
1979/// Allows specifying alignment and a sentinel value.
1980/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1981/// On WASI, `file_path` should be encoded as valid UTF-8.
1982/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1983pub fn readFileAllocOptions(
1984 self: Dir,
1985 allocator: mem.Allocator,
1966/// Reads all the bytes from the named file. On success, caller owns returned
1967/// buffer.
1968pub fn readFileAlloc(
1969 dir: Dir,
1970 /// On Windows, should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1971 /// On WASI, should be encoded as valid UTF-8.
1972 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1973 file_path: []const u8,
1974 /// Used to allocate the result.
1975 gpa: mem.Allocator,
1976 /// If exceeded:
1977 /// * The array list's length is increased by exactly one byte past `limit`.
1978 /// * The file seek position is advanced by exactly one byte past `limit`.
1979 /// * `error.FileTooBig` is returned.
1980 limit: std.io.Reader.Limit,
1981) (File.OpenError || File.ReadAllocError)![]u8 {
1982 var buffer: std.ArrayListUnmanaged(u8) = .empty;
1983 defer buffer.deinit(gpa);
1984 try readFileIntoArrayList(dir, file_path, gpa, limit, null, &buffer);
1985 return buffer.toOwnedSlice(gpa);
1986}
1987
1988/// Reads all the bytes from the named file, appending them into the provided
1989/// array list.
1990///
1991/// If `limit` is exceeded:
1992/// * The array list's length is increased by exactly one byte past `limit`.
1993/// * The file seek position is advanced by exactly one byte past `limit`.
1994/// * `error.FileTooBig` is returned.
1995pub fn readFileIntoArrayList(
1996 dir: Dir,
1997 /// On Windows, should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1998 /// On WASI, should be encoded as valid UTF-8.
1999 /// On other platforms, an opaque sequence of bytes with no particular encoding.
19862000 file_path: []const u8,
1987 max_bytes: usize,
2001 gpa: Allocator,
2002 limit: std.io.Reader.Limit,
2003 /// If specified, the initial buffer size is calculated using this value,
2004 /// otherwise the effective file size is used instead.
19882005 size_hint: ?usize,
1989 comptime alignment: std.mem.Alignment,
1990 comptime optional_sentinel: ?u8,
1991) !(if (optional_sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1992 var file = try self.openFile(file_path, .{});
2006 comptime alignment: ?std.mem.Alignment,
2007 list: *std.ArrayListAligned(u8, alignment),
2008) (File.OpenError || File.ReadAllocError)!void {
2009 var file = try dir.openFile(file_path, .{});
19932010 defer file.close();
19942011
1995 // If the file size doesn't fit a usize it'll be certainly greater than
1996 // `max_bytes`
1997 const stat_size = size_hint orelse std.math.cast(usize, try file.getEndPos()) orelse
1998 return error.FileTooBig;
2012 // Apply size hint by adjusting the array list's capacity.
2013 if (size_hint) |size| {
2014 try list.ensureUnusedCapacity(gpa, size);
2015 } else if (file.getEndPos()) |size| {
2016 // If the file size doesn't fit a usize it'll be certainly exceed the limit.
2017 try list.ensureUnusedCapacity(gpa, std.math.cast(usize, size) orelse return error.FileTooBig);
2018 } else |err| switch (err) {
2019 // Ignore most errors; size hint is only an optimization.
2020 error.Unseekable, error.Unexpected, error.AccessDenied, error.PermissionDenied => {},
2021 else => |e| return e,
2022 }
19992023
2000 return file.readToEndAllocOptions(allocator, max_bytes, stat_size, alignment, optional_sentinel);
2024 try file.readIntoArrayList(gpa, limit, alignment, list);
20012025}
20022026
20032027pub const DeleteTreeError = error{
lib/std/fs/File.zig+104-98
......@@ -1142,46 +1142,43 @@ pub fn updateTimes(
11421142 try posix.futimens(self.handle, &times);
11431143}
11441144
1145/// Reads all the bytes from the current position to the end of the file.
1146/// On success, caller owns returned buffer.
1147/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1148pub fn readToEndAlloc(self: File, allocator: Allocator, max_bytes: usize) ![]u8 {
1149 return self.readToEndAllocOptions(allocator, max_bytes, null, .of(u8), null);
1150}
1145pub const ReadAllocError = ReadError || Allocator.Error || error{FileTooBig};
11511146
11521147/// Reads all the bytes from the current position to the end of the file.
1148///
11531149/// On success, caller owns returned buffer.
1154/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1155/// If `size_hint` is specified the initial buffer size is calculated using
1156/// that value, otherwise an arbitrary value is used instead.
1157/// Allows specifying alignment and a sentinel value.
1158pub fn readToEndAllocOptions(
1159 self: File,
1160 allocator: Allocator,
1161 max_bytes: usize,
1162 size_hint: ?usize,
1163 comptime alignment: Alignment,
1164 comptime optional_sentinel: ?u8,
1165) !(if (optional_sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1166 // If no size hint is provided fall back to the size=0 code path
1167 const size = size_hint orelse 0;
1168
1169 // The file size returned by stat is used as hint to set the buffer
1170 // size. If the reported size is zero, as it happens on Linux for files
1171 // in /proc, a small buffer is allocated instead.
1172 const initial_cap = @min((if (size > 0) size else 1024), max_bytes) + @intFromBool(optional_sentinel != null);
1173 var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
1174 defer array_list.deinit();
1175
1176 self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
1177 error.StreamTooLong => return error.FileTooBig,
1178 else => |e| return e,
1179 };
1150///
1151/// If `limit` is exceeded, returns `error.FileTooBig`.
1152pub fn readToEndAlloc(file: File, gpa: Allocator, limit: std.io.Reader.Limit) ReadAllocError![]u8 {
1153 var buffer: std.ArrayListUnmanaged(u8) = .empty;
1154 defer buffer.deinit(gpa);
1155 try buffer.ensureUnusedCapacity(gpa, std.heap.page_size_min);
1156 try readIntoArrayList(file, gpa, limit, null, &buffer);
1157 return buffer.toOwnedSlice(gpa);
1158}
11801159
1181 if (optional_sentinel) |sentinel| {
1182 return try array_list.toOwnedSliceSentinel(sentinel);
1183 } else {
1184 return try array_list.toOwnedSlice();
1160/// Reads all the bytes from the current position to the end of the file,
1161/// appending them into the provided array list.
1162///
1163/// If `limit` is exceeded:
1164/// * The array list's length is increased by exactly one byte past `limit`.
1165/// * The file seek position is advanced by exactly one byte past `limit`.
1166/// * `error.FileTooBig` is returned.
1167pub fn readIntoArrayList(
1168 file: File,
1169 gpa: Allocator,
1170 limit: std.io.Reader.Limit,
1171 comptime alignment: ?std.mem.Alignment,
1172 list: *std.ArrayListAligned(u8, alignment),
1173) ReadAllocError!void {
1174 var remaining = limit;
1175 while (true) {
1176 try list.ensureUnusedCapacity(gpa, 1);
1177 const buffer = remaining.slice1(list.unusedCapacitySlice());
1178 const n = try read(file, buffer);
1179 if (n == 0) return;
1180 list.items.len += n;
1181 remaining = remaining.subtract(n) orelse return error.FileTooBig;
11851182 }
11861183}
11871184
......@@ -1584,35 +1581,19 @@ fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix
15841581pub fn reader(file: File) std.io.Reader {
15851582 return .{
15861583 .context = handleToOpaque(file.handle),
1587 .vtable = .{
1588 .posRead = reader_posRead,
1589 .posReadVec = reader_posReadVec,
1590 .streamRead = reader_streamRead,
1591 .streamReadVec = reader_streamReadVec,
1592 },
1593 };
1594}
1595
1596pub fn unseekableReader(file: File) std.io.Reader {
1597 return .{
1598 .context = handleToOpaque(file.handle),
1599 .vtable = .{
1600 .posRead = null,
1601 .posReadVec = null,
1602 .streamRead = reader_streamRead,
1603 .streamReadVec = reader_streamReadVec,
1584 .vtable = &.{
1585 .read = streamRead,
1586 .readv = streamReadVec,
16041587 },
16051588 };
16061589}
16071590
1608pub fn unstreamableReader(file: File) std.io.Reader {
1591pub fn positionalReader(file: File) std.io.PositionalReader {
16091592 return .{
16101593 .context = handleToOpaque(file.handle),
1611 .vtable = .{
1612 .posRead = reader_posRead,
1613 .posReadVec = reader_posReadVec,
1614 .streamRead = null,
1615 .streamReadVec = null,
1594 .vtable = &.{
1595 .read = posRead,
1596 .readv = posReadVec,
16161597 },
16171598 };
16181599}
......@@ -1621,8 +1602,8 @@ pub fn writer(file: File) std.io.Writer {
16211602 return .{
16221603 .context = handleToOpaque(file.handle),
16231604 .vtable = &.{
1624 .writeSplat = writer_writeSplat,
1625 .writeFile = writer_writeFile,
1605 .writeSplat = writeSplat,
1606 .writeFile = writeFile,
16261607 },
16271608 };
16281609}
......@@ -1631,19 +1612,18 @@ pub fn writer(file: File) std.io.Writer {
16311612/// vectors through the underlying write calls as possible.
16321613const max_buffers_len = 16;
16331614
1634pub fn reader_posRead(
1615fn posRead(
16351616 context: ?*anyopaque,
16361617 bw: *std.io.BufferedWriter,
16371618 limit: std.io.Reader.Limit,
16381619 offset: u64,
16391620) std.io.Reader.Result {
1640 const file = opaqueToHandle(context);
1641 const len: std.io.Writer.Len = if (limit.unwrap()) |l| .init(l) else .entire_file;
1642 return writer.writeFile(bw, file, .init(offset), len, &.{}, 0);
1621 const file = opaqueToFile(context);
1622 return bw.writeFile(file, .init(offset), limit, &.{}, 0);
16431623}
16441624
1645pub fn reader_posReadVec(context: *anyopaque, data: []const []u8, offset: u64) anyerror!std.io.Reader.Status {
1646 const file = opaqueToHandle(context);
1625fn posReadVec(context: *anyopaque, data: []const []u8, offset: u64) anyerror!std.io.Reader.Status {
1626 const file = opaqueToFile(context);
16471627 const n = try file.preadv(data, offset);
16481628 return .{
16491629 .len = n,
......@@ -1651,35 +1631,57 @@ pub fn reader_posReadVec(context: *anyopaque, data: []const []u8, offset: u64) a
16511631 };
16521632}
16531633
1654pub fn reader_streamRead(
1634fn streamRead(
16551635 context: ?*anyopaque,
16561636 bw: *std.io.BufferedWriter,
16571637 limit: std.io.Reader.Limit,
16581638) anyerror!std.io.Reader.Status {
1659 const file = opaqueToHandle(context);
1660 const len: std.io.Writer.Len = if (limit.unwrap()) |l| .init(l) else .entire_file;
1661 const n = try writer.writeFile(bw, file, .none, len, &.{}, 0);
1639 const file = opaqueToFile(context);
1640 const n = try bw.writeFile(file, .none, limit, &.{}, 0);
16621641 return .{
1663 .len = n,
1642 .len = @intCast(n),
16641643 .end = n == 0,
16651644 };
16661645}
16671646
1668pub fn reader_streamReadVec(context: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status {
1669 const file = opaqueToHandle(context);
1670 const n = try file.readv(data);
1671 return .{
1672 .len = n,
1673 .end = n == 0,
1674 };
1647fn streamReadVec(context: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status {
1648 const handle = opaqueToHandle(context);
1649
1650 if (is_windows) {
1651 // Unfortunately, `ReadFileScatter` cannot be used since it requires
1652 // page alignment, so we are stuck using only the first slice.
1653 // Avoid empty slices to prevent false positive end detections.
1654 var i: usize = 0;
1655 while (true) : (i += 1) {
1656 if (i >= data.len) return .{};
1657 if (data[i].len > 0) break;
1658 }
1659 const n = try windows.ReadFile(handle, data[i], null);
1660 return .{ .len = n, .end = n == 0 };
1661 }
1662
1663 var iovecs: [max_buffers_len]std.posix.iovec = undefined;
1664 var iovecs_i: usize = 0;
1665 for (data) |d| {
1666 // Since the OS checks pointer address before length, we must omit
1667 // length-zero vectors.
1668 if (d.len == 0) continue;
1669 iovecs[iovecs_i] = .{ .base = d.ptr, .len = d.len };
1670 iovecs_i += 1;
1671 if (iovecs_i >= iovecs.len) break;
1672 }
1673 const send_vecs = iovecs[0..iovecs_i];
1674 if (send_vecs.len == 0) return .{}; // Prevent false positive end detection on empty `data`.
1675 const n = try posix.readv(handle, send_vecs);
1676 return .{ .len = @intCast(n), .end = n == 0 };
16751677}
16761678
1677pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1678 const file = opaqueToHandle(context);
1679fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1680 const handle = opaqueToHandle(context);
16791681 var splat_buffer: [256]u8 = undefined;
16801682 if (is_windows) {
16811683 if (data.len == 1 and splat == 0) return 0;
1682 return windows.WriteFile(file, data[0], null);
1684 return windows.WriteFile(handle, data[0], null);
16831685 }
16841686 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
16851687 var len: usize = @min(iovecs.len, data.len);
......@@ -1688,8 +1690,8 @@ pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat:
16881690 .len = d.len,
16891691 };
16901692 switch (splat) {
1691 0 => return std.posix.writev(file, iovecs[0 .. len - 1]),
1692 1 => return std.posix.writev(file, iovecs[0..len]),
1693 0 => return std.posix.writev(handle, iovecs[0 .. len - 1]),
1694 1 => return std.posix.writev(handle, iovecs[0..len]),
16931695 else => {
16941696 const pattern = data[data.len - 1];
16951697 if (pattern.len == 1) {
......@@ -1707,38 +1709,38 @@ pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat:
17071709 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
17081710 len += 1;
17091711 }
1710 return std.posix.writev(file, iovecs[0..len]);
1712 return std.posix.writev(handle, iovecs[0..len]);
17111713 }
17121714 },
17131715 }
1714 return std.posix.writev(file, iovecs[0..len]);
1716 return std.posix.writev(handle, iovecs[0..len]);
17151717}
17161718
1717pub fn writer_writeFile(
1719fn writeFile(
17181720 context: ?*anyopaque,
17191721 in_file: std.fs.File,
17201722 in_offset: std.io.Writer.Offset,
1721 in_len: std.io.Writer.FileLen,
1723 in_limit: std.io.Writer.Limit,
17221724 headers_and_trailers: []const []const u8,
17231725 headers_len: usize,
17241726) anyerror!usize {
17251727 const out_fd = opaqueToHandle(context);
17261728 const in_fd = in_file.handle;
1727 const len_int = switch (in_len) {
1728 .zero => return writer_writeSplat(context, headers_and_trailers, 1),
1729 .entire_file => 0,
1730 else => in_len.int(),
1729 const len_int = switch (in_limit) {
1730 .zero => return writeSplat(context, headers_and_trailers, 1),
1731 .none => 0,
1732 else => in_limit.toInt().?,
17311733 };
17321734 if (native_os == .linux) sf: {
17331735 // Linux sendfile does not support headers or trailers but it does
17341736 // support a streaming read from in_file.
1735 if (headers_len > 0) return writer_writeSplat(context, headers_and_trailers[0..headers_len], 1);
1737 if (headers_len > 0) return writeSplat(context, headers_and_trailers[0..headers_len], 1);
17361738 const max_count = 0x7ffff000; // Avoid EINVAL.
17371739 const smaller_len = if (len_int == 0) max_count else @min(len_int, max_count);
17381740 var off: std.os.linux.off_t = undefined;
17391741 const off_ptr: ?*std.os.linux.off_t = if (in_offset.toInt()) |offset| b: {
17401742 off = std.math.cast(std.os.linux.off_t, offset) orelse
1741 return writer_writeSplat(context, headers_and_trailers, 1);
1743 return writeSplat(context, headers_and_trailers, 1);
17421744 break :b &off;
17431745 } else null;
17441746 if (true) @panic("TODO");
......@@ -1753,7 +1755,7 @@ pub fn writer_writeFile(
17531755 } else if (n == 0 and len_int == 0) {
17541756 // The caller wouldn't be able to tell that the file transfer is
17551757 // done and would incorrectly repeat the same call.
1756 return writer_writeSplat(context, headers_and_trailers, 1);
1758 return writeSplat(context, headers_and_trailers, 1);
17571759 }
17581760 return n;
17591761 }
......@@ -1770,7 +1772,7 @@ pub fn writer_writeFile(
17701772 error.FileDescriptorNotASocket,
17711773 error.NetworkUnreachable,
17721774 error.NetworkSubsystemFailed,
1773 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_len, headers_and_trailers, headers_len),
1775 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_limit, headers_and_trailers, headers_len),
17741776
17751777 else => |e| return e,
17761778 };
......@@ -1780,14 +1782,14 @@ fn writeFileUnseekable(
17801782 out_fd: Handle,
17811783 in_fd: Handle,
17821784 in_offset: u64,
1783 in_len: std.io.Writer.FileLen,
1785 in_limit: std.io.Writer.Limit,
17841786 headers_and_trailers: []const []const u8,
17851787 headers_len: usize,
17861788) anyerror!usize {
17871789 _ = out_fd;
17881790 _ = in_fd;
17891791 _ = in_offset;
1790 _ = in_len;
1792 _ = in_limit;
17911793 _ = headers_and_trailers;
17921794 _ = headers_len;
17931795 @panic("TODO writeFileUnseekable");
......@@ -1809,6 +1811,10 @@ fn opaqueToHandle(userdata: ?*anyopaque) Handle {
18091811 };
18101812}
18111813
1814fn opaqueToFile(userdata: ?*anyopaque) File {
1815 return .{ .handle = opaqueToHandle(userdata) };
1816}
1817
18121818pub const SeekableStream = io.SeekableStream(
18131819 File,
18141820 SeekError,
lib/std/io/BufferedReader.zig+2-2
......@@ -43,14 +43,14 @@ fn eof_writeFile(
4343 context: ?*anyopaque,
4444 file: std.fs.File,
4545 offset: std.io.Writer.Offset,
46 len: std.io.Writer.FileLen,
46 limit: std.io.Writer.Limit,
4747 headers_and_trailers: []const []const u8,
4848 headers_len: usize,
4949) anyerror!usize {
5050 _ = context;
5151 _ = file;
5252 _ = offset;
53 _ = len;
53 _ = limit;
5454 _ = headers_and_trailers;
5555 _ = headers_len;
5656 return error.NoSpaceLeft;
lib/std/io/BufferedWriter.zig+15-15
......@@ -410,19 +410,19 @@ pub fn writeStructEndian(bw: *BufferedWriter, value: anytype, endian: std.builti
410410pub fn writeFile(
411411 bw: *BufferedWriter,
412412 file: std.fs.File,
413 offset: u64,
414 len: Writer.FileLen,
413 offset: Writer.Offset,
414 limit: Writer.Limit,
415415 headers_and_trailers: []const []const u8,
416416 headers_len: usize,
417417) anyerror!usize {
418 return passthru_writeFile(bw, file, offset, len, headers_and_trailers, headers_len);
418 return passthru_writeFile(bw, file, offset, limit, headers_and_trailers, headers_len);
419419}
420420
421421fn passthru_writeFile(
422422 context: ?*anyopaque,
423423 file: std.fs.File,
424 offset: u64,
425 len: Writer.FileLen,
424 offset: Writer.Offset,
425 limit: Writer.Limit,
426426 headers_and_trailers: []const []const u8,
427427 headers_len: usize,
428428) anyerror!usize {
......@@ -430,7 +430,7 @@ fn passthru_writeFile(
430430 const buffer = bw.buffer;
431431 if (buffer.len == 0) return track(
432432 &bw.count,
433 try bw.unbuffered_writer.writeFile(file, offset, len, headers_and_trailers, headers_len),
433 try bw.unbuffered_writer.writeFile(file, offset, limit, headers_and_trailers, headers_len),
434434 );
435435 const start_end = bw.end;
436436 const headers = headers_and_trailers[0..headers_len];
......@@ -457,7 +457,7 @@ fn passthru_writeFile(
457457 @memcpy(remaining_buffers_for_trailers[0..send_trailers_len], trailers[0..send_trailers_len]);
458458 const send_headers_len = 1 + buffers_len;
459459 const send_buffers = buffers[0 .. send_headers_len + send_trailers_len];
460 const n = try bw.unbuffered_writer.writeFile(file, offset, len, send_buffers, send_headers_len);
460 const n = try bw.unbuffered_writer.writeFile(file, offset, limit, send_buffers, send_headers_len);
461461 if (n < end) {
462462 @branchHint(.unlikely);
463463 const remainder = buffer[n..end];
......@@ -487,7 +487,7 @@ fn passthru_writeFile(
487487 @memcpy(remaining_buffers[0..send_trailers_len], trailers[0..send_trailers_len]);
488488 const send_headers_len = 1;
489489 const send_buffers = buffers[0 .. send_headers_len + send_trailers_len];
490 const n = try bw.unbuffered_writer.writeFile(file, offset, len, send_buffers, send_headers_len);
490 const n = try bw.unbuffered_writer.writeFile(file, offset, limit, send_buffers, send_headers_len);
491491 if (n < end) {
492492 @branchHint(.unlikely);
493493 const remainder = buffer[n..end];
......@@ -500,26 +500,26 @@ fn passthru_writeFile(
500500}
501501
502502pub const WriteFileOptions = struct {
503 offset: u64 = 0,
503 offset: Writer.Offset = .none,
504504 /// If the size of the source file is known, it is likely that passing the
505505 /// size here will save one syscall.
506 len: Writer.FileLen = .entire_file,
506 limit: Writer.Limit = .none,
507507 /// Headers and trailers must be passed together so that in case `len` is
508508 /// zero, they can be forwarded directly to `Writer.VTable.writev`.
509509 ///
510510 /// The parameter is mutable because this function needs to mutate the
511511 /// fields in order to handle partial writes from `Writer.VTable.writeFile`.
512512 headers_and_trailers: [][]const u8 = &.{},
513 /// The number of trailers is inferred from `headers_and_trailers.len -
514 /// headers_len`.
513 /// The number of trailers is inferred from
514 /// `headers_and_trailers.len - headers_len`.
515515 headers_len: usize = 0,
516516};
517517
518518pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOptions) anyerror!void {
519519 const headers_and_trailers = options.headers_and_trailers;
520520 const headers = headers_and_trailers[0..options.headers_len];
521 if (options.len == .zero) return writevAll(bw, headers_and_trailers);
522 if (options.len == .entire_file) {
521 if (options.limit == .zero) return writevAll(bw, headers_and_trailers);
522 if (options.limit == .none) {
523523 // When reading the whole file, we cannot include the trailers in the
524524 // call that reads from the file handle, because we have no way to
525525 // determine whether a partial write is past the end of the file or
......@@ -540,7 +540,7 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp
540540 offset += n;
541541 }
542542 } else {
543 var len = options.len.int();
543 var len = options.limit.toInt().?;
544544 var i: usize = 0;
545545 var offset = options.offset;
546546 while (true) {
lib/std/io/Reader.zig+36-2
......@@ -48,11 +48,45 @@ pub const Status = packed struct(usize) {
4848};
4949
5050pub const Limit = enum(usize) {
51 zero = 0,
5152 none = std.math.maxInt(usize),
5253 _,
5354
54 pub fn min(l: Limit, int: usize) usize {
55 return @min(int, @intFromEnum(l));
55 /// `std.math.maxInt(usize)` is interpreted to mean "no limit".
56 pub fn init(n: usize) Limit {
57 return @enumFromInt(n);
58 }
59
60 pub fn min(l: Limit, n: usize) usize {
61 return @min(n, @intFromEnum(l));
62 }
63
64 pub fn slice(l: Limit, s: []u8) []u8 {
65 return s[0..min(l, s.len)];
66 }
67
68 pub fn toInt(l: Limit) ?usize {
69 return if (l == .none) null else @intFromEnum(l);
70 }
71
72 /// Reduces a slice to account for the limit, leaving room for one extra
73 /// byte above the limit, allowing for the use case of differentiating
74 /// between end-of-stream and reaching the limit.
75 pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 {
76 assert(non_empty_buffer.len >= 1);
77 return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)];
78 }
79
80 pub fn nonzero(l: Limit) bool {
81 return @intFromEnum(l) > 0;
82 }
83
84 /// Return a new limit reduced by `amount` or return `null` indicating
85 /// limit would be exceeded.
86 pub fn subtract(l: Limit, amount: usize) ?Limit {
87 if (l == .none) return .{ .next = .none };
88 if (amount > @intFromEnum(l)) return null;
89 return @enumFromInt(@intFromEnum(l) - amount);
5690 }
5791};
5892
lib/std/io/Writer.zig+17-29
......@@ -31,9 +31,11 @@ pub const VTable = struct {
3131 writeFile: *const fn (
3232 ctx: ?*anyopaque,
3333 file: std.fs.File,
34 /// If this is `none`, `file` will be streamed. Otherwise, it will be
35 /// read positionally without affecting the seek position.
3436 offset: Offset,
35 /// When zero, it means copy until the end of the file is reached.
36 len: FileLen,
37 /// Maximum amount of bytes to read from the file.
38 limit: Limit,
3739 /// Headers and trailers must be passed together so that in case `len` is
3840 /// zero, they can be forwarded directly to `VTable.writev`.
3941 headers_and_trailers: []const []const u8,
......@@ -41,7 +43,10 @@ pub const VTable = struct {
4143 ) anyerror!usize,
4244};
4345
46pub const Limit = std.io.Reader.Limit;
47
4448pub const Offset = enum(u64) {
49 zero = 0,
4550 /// Indicates to read the file as a stream.
4651 none = std.math.maxInt(u64),
4752 _,
......@@ -53,24 +58,7 @@ pub const Offset = enum(u64) {
5358 }
5459
5560 pub fn toInt(o: Offset) ?u64 {
56 if (o == .none) return null;
57 return @intFromEnum(o);
58 }
59};
60
61pub const FileLen = enum(u64) {
62 zero = 0,
63 entire_file = std.math.maxInt(u64),
64 _,
65
66 pub fn init(integer: u64) FileLen {
67 const result: FileLen = @enumFromInt(integer);
68 assert(result != .entire_file);
69 return result;
70 }
71
72 pub fn int(len: FileLen) u64 {
73 return @intFromEnum(len);
61 return if (o == .none) null else @intFromEnum(o);
7462 }
7563};
7664
......@@ -85,26 +73,26 @@ pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) anyerror!us
8573pub fn writeFile(
8674 w: Writer,
8775 file: std.fs.File,
88 offset: u64,
89 len: FileLen,
76 offset: Offset,
77 limit: Limit,
9078 headers_and_trailers: []const []const u8,
9179 headers_len: usize,
9280) anyerror!usize {
93 return w.vtable.writeFile(w.context, file, offset, len, headers_and_trailers, headers_len);
81 return w.vtable.writeFile(w.context, file, offset, limit, headers_and_trailers, headers_len);
9482}
9583
9684pub fn unimplemented_writeFile(
9785 context: ?*anyopaque,
9886 file: std.fs.File,
9987 offset: Offset,
100 len: FileLen,
88 limit: Limit,
10189 headers_and_trailers: []const []const u8,
10290 headers_len: usize,
10391) anyerror!usize {
10492 _ = context;
10593 _ = file;
10694 _ = offset;
107 _ = len;
95 _ = limit;
10896 _ = headers_and_trailers;
10997 _ = headers_len;
11098 return error.Unimplemented;
......@@ -143,7 +131,7 @@ fn null_writeFile(
143131 context: ?*anyopaque,
144132 file: std.fs.File,
145133 offset: Offset,
146 len: FileLen,
134 limit: Limit,
147135 headers_and_trailers: []const []const u8,
148136 headers_len: usize,
149137) anyerror!usize {
......@@ -152,7 +140,7 @@ fn null_writeFile(
152140 if (offset == .none) {
153141 @panic("TODO seek the file forwards");
154142 }
155 if (len == .entire_file) {
143 const limit_int = limit.toInt() orelse {
156144 const headers = headers_and_trailers[0..headers_len];
157145 for (headers) |bytes| n += bytes.len;
158146 if (offset.toInt()) |off| {
......@@ -162,9 +150,9 @@ fn null_writeFile(
162150 return n;
163151 }
164152 @panic("TODO stream from file until eof, counting");
165 }
153 };
166154 for (headers_and_trailers) |bytes| n += bytes.len;
167 return len.int() + n;
155 return limit_int + n;
168156}
169157
170158test @"null" {
lib/std/zig.zig+12-21
......@@ -543,20 +543,18 @@ test isUnderscore {
543543 try std.testing.expect(!isUnderscore("\\x5f"));
544544}
545545
546pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?usize) ![:0]u8 {
547 const source_code = input.readToEndAllocOptions(
548 gpa,
549 max_src_size,
550 size_hint,
551 .of(u8),
552 0,
553 ) catch |err| switch (err) {
546pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: usize) ![:0]u8 {
547 var buffer: std.ArrayListAlignedUnmanaged(u8, .@"2") = .empty;
548 defer buffer.deinit(gpa);
549
550 try buffer.ensureUnusedCapacity(size_hint);
551
552 input.readIntoArrayList(gpa, .init(max_src_size), .@"2", &buffer) catch |err| switch (err) {
554553 error.ConnectionResetByPeer => unreachable,
555554 error.ConnectionTimedOut => unreachable,
556555 error.NotOpenForReading => unreachable,
557556 else => |e| return e,
558557 };
559 errdefer gpa.free(source_code);
560558
561559 // Detect unsupported file types with their Byte Order Mark
562560 const unsupported_boms = [_][]const u8{
......@@ -565,30 +563,23 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?
565563 "\xfe\xff", // UTF-16 big endian
566564 };
567565 for (unsupported_boms) |bom| {
568 if (std.mem.startsWith(u8, source_code, bom)) {
566 if (std.mem.startsWith(u8, buffer.items, bom)) {
569567 return error.UnsupportedEncoding;
570568 }
571569 }
572570
573571 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
574 if (std.mem.startsWith(u8, source_code, "\xff\xfe")) {
575 if (source_code.len % 2 != 0) return error.InvalidEncoding;
576 // TODO: after wrangle-writer-buffering branch is merged,
577 // avoid this unnecessary allocation
578 const aligned_copy = try gpa.alloc(u16, source_code.len / 2);
579 defer gpa.free(aligned_copy);
580 @memcpy(std.mem.sliceAsBytes(aligned_copy), source_code);
581 const source_code_utf8 = std.unicode.utf16LeToUtf8AllocZ(gpa, aligned_copy) catch |err| switch (err) {
572 if (std.mem.startsWith(u8, buffer.items, "\xff\xfe")) {
573 if (buffer.items.len % 2 != 0) return error.InvalidEncoding;
574 return std.unicode.utf16LeToUtf8AllocZ(gpa, buffer.items) catch |err| switch (err) {
582575 error.DanglingSurrogateHalf => error.UnsupportedEncoding,
583576 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
584577 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
585578 else => |e| return e,
586579 };
587 gpa.free(source_code);
588 return source_code_utf8;
589580 }
590581
591 return source_code;
582 return buffer.toOwnedSliceSentinel(0);
592583}
593584
594585pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {