authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-06-03 11:32:16-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:29-07:00
log9d163c7ac3fbc6adfa7c6fa299647d0731f96167
treebced44850d91d83946197e55dcb9f837ea2236cc
parent1e2aab2f97843572138076b21e2efb568e060a33

std.net: update to new IO API


4 files changed, 203 insertions(+), 148 deletions(-)

lib/std/fs/File.zig+5
......@@ -1213,6 +1213,10 @@ pub const Writer = struct {
12131213 const max_buffers_len = 16;
12141214
12151215 pub fn init(file: File, buffer: []u8) std.io.Writer {
1216 return initMode(file, buffer, .positional);
1217 }
1218
1219 pub fn initMode(file: File, buffer: []u8, init_mode: Writer.Mode) std.io.Writer {
12161220 return .{
12171221 .file = file,
12181222 .interface = .{
......@@ -1223,6 +1227,7 @@ pub const Writer = struct {
12231227 },
12241228 .buffer = buffer,
12251229 },
1230 .mode = init_mode,
12261231 };
12271232 }
12281233
lib/std/io/Reader.zig+3-3
......@@ -1392,19 +1392,19 @@ pub fn Hashed(comptime Hasher: type) type {
13921392 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
13931393 const data = w.writableVector(limit);
13941394 const n = try this.in.readVec(data);
1395 w.advanceVector(n);
1395 const result = w.advanceVector(n);
13961396 var remaining: usize = n;
13971397 for (data) |slice| {
13981398 if (remaining < slice.len) {
13991399 this.hasher.update(slice[0..remaining]);
1400 return n;
1400 return result;
14011401 } else {
14021402 remaining -= slice.len;
14031403 this.hasher.update(slice);
14041404 }
14051405 }
14061406 assert(remaining == 0);
1407 return n;
1407 return result;
14081408 }
14091409
14101410 fn discard(r: *Reader, limit: Limit) Error!usize {
lib/std/io/Writer.zig+16-11
......@@ -1672,16 +1672,7 @@ pub fn discardingDrain(w: *Writer, data: []const []const u8, splat: usize) Error
16721672
16731673pub fn discardingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
16741674 if (File.Handle == void) return error.Unimplemented;
1675 if (w.end != 0) {
1676 if (@intFromEnum(limit) >= w.end) {
1677 w.end = 0;
1678 } else {
1679 const remaining = w.buffer[@intFromEnum(limit)..w.end];
1680 @memmove(w.buffer[0..remaining.len], remaining);
1681 w.end = remaining.len;
1682 }
1683 return 0;
1684 }
1675 w.end = 0;
16851676 if (file_reader.getSize()) |size| {
16861677 const n = limit.minInt(size - file_reader.pos);
16871678 file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
......@@ -1694,6 +1685,19 @@ pub fn discardingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) F
16941685 }
16951686}
16961687
1688/// This function is used by `VTable.drain` function implementations to
1689/// implement partial drains.
1690pub fn consume(w: *Writer, n: usize) usize {
1691 if (n < w.end) {
1692 const remaining = w.buffer[n..w.end];
1693 @memmove(w.buffer[0..remaining.len], remaining);
1694 w.end = remaining.len;
1695 return 0;
1696 }
1697 defer w.end = 0;
1698 return n - w.end;
1699}
1700
16971701/// For use when the `Writer` implementation can cannot offer a more efficient
16981702/// implementation than a basic read/write loop on the file.
16991703pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
......@@ -1768,13 +1772,14 @@ pub fn Hashed(comptime Hasher: type) type {
17681772 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
17691773 const this: *@This() = @alignCast(@fieldParentPtr("interface", w));
17701774 const aux_n = try this.out.writeSplatAux(w.buffered(), data, splat);
1771 if (aux_n <= w.end) {
1775 if (aux_n < w.end) {
17721776 this.hasher.update(w.buffer[0..aux_n]);
17731777 const remaining = w.buffer[aux_n..w.end];
17741778 @memmove(w.buffer[0..remaining.len], remaining);
17751779 w.end = remaining.len;
17761780 return 0;
17771781 }
1782 this.hasher.update(w.buffered());
17781783 const n = aux_n - w.end;
17791784 w.end = 0;
17801785 var remaining: usize = n;
lib/std/net.zig+179-134
......@@ -13,6 +13,7 @@ const native_os = builtin.os.tag;
1313const windows = std.os.windows;
1414const Allocator = std.mem.Allocator;
1515const ArrayList = std.ArrayListUnmanaged;
16const File = std.fs.File;
1617
1718// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
1819// first release to support them.
......@@ -853,7 +854,7 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
853854
854855// TODO: Instead of having a massive error set, make the error set have categories, and then
855856// store the sub-error as a diagnostic value.
856const GetAddressListError = Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
857const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
857858 TemporaryNameServerFailure,
858859 NameServerFailure,
859860 AddressFamilyNotSupported,
......@@ -1363,9 +1364,8 @@ fn linuxLookupNameFromHosts(
13631364 defer file.close();
13641365
13651366 var line_buf: [512]u8 = undefined;
1366 var file_reader = file.reader();
1367 var br = file_reader.interface().buffered(&line_buf);
1368 return parseHosts(gpa, addrs, canon, name, family, port, &br) catch |err| switch (err) {
1367 var file_reader = file.reader(&line_buf);
1368 return parseHosts(gpa, addrs, canon, name, family, port, &file_reader.interface) catch |err| switch (err) {
13691369 error.OutOfMemory => return error.OutOfMemory,
13701370 error.ReadFailed => return file_reader.err.?,
13711371 };
......@@ -1378,7 +1378,7 @@ fn parseHosts(
13781378 name: []const u8,
13791379 family: posix.sa_family_t,
13801380 port: u16,
1381 br: *std.io.Reader,
1381 br: *io.Reader,
13821382) error{ OutOfMemory, ReadFailed }!void {
13831383 while (true) {
13841384 const line = br.takeDelimiterExclusive('\n') catch |err| switch (err) {
......@@ -1584,15 +1584,14 @@ const ResolvConf = struct {
15841584 defer file.close();
15851585
15861586 var line_buf: [512]u8 = undefined;
1587 var file_reader = file.reader();
1588 var br = file_reader.interface().buffered(&line_buf);
1589 return parse(rc, &br) catch |err| switch (err) {
1587 var file_reader = file.reader(&line_buf);
1588 return parse(rc, &file_reader.interface) catch |err| switch (err) {
15901589 error.ReadFailed => return file_reader.err.?,
15911590 else => |e| return e,
15921591 };
15931592 }
15941593
1595 fn parse(rc: *ResolvConf, br: *std.io.Reader) !void {
1594 fn parse(rc: *ResolvConf, br: *io.Reader) !void {
15961595 const gpa = rc.gpa;
15971596 while (br.takeSentinel('\n')) |line_with_comment| {
15981597 const line = line: {
......@@ -1893,7 +1892,10 @@ pub const Stream = struct {
18931892
18941893 pub const Reader = switch (native_os) {
18951894 .windows => struct {
1896 stream: Stream,
1895 /// Use `interface` to access portably.
1896 interface_state: io.Reader,
1897 /// Use `getStream` to access portably.
1898 net_stream: Stream,
18971899 err: ?Error = null,
18981900
18991901 pub const Error = ReadError;
......@@ -1902,29 +1904,27 @@ pub const Stream = struct {
19021904 return r.stream;
19031905 }
19041906
1905 pub fn interface(r: *Reader) std.io.Reader {
1907 pub fn interface(r: *Reader) *io.Reader {
1908 return &r.interface_state;
1909 }
1910
1911 pub fn init(net_stream: Stream, buffer: []u8) Reader {
19061912 return .{
1907 .context = r.stream.handle,
1908 .vtable = &.{
1909 .read = read,
1910 .readVec = readVec,
1911 .discard = discard,
1913 .interface_state = .{
1914 .context = undefined,
1915 .vtable = &.{
1916 .stream = stream,
1917 .discard = discard,
1918 },
1919 .buffer = buffer,
19121920 },
1921 .net_stream = net_stream,
19131922 };
19141923 }
19151924
1916 fn read(
1917 context: ?*anyopaque,
1918 bw: *std.io.Writer,
1919 limit: std.io.Limit,
1920 ) std.io.Reader.Error!usize {
1921 const buf = limit.slice(try bw.writableSliceGreedy(1));
1922 const n = try readVec(context, &.{buf});
1923 bw.advance(n);
1924 return n;
1925 }
1926
1927 fn readVec(context: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize {
1925 fn stream(io_r: *io.Reader, io_w: *io.Writer, limit: io.Limit) io.Reader.StreamError!usize {
1926 const r: *Reader = @fieldParentPtr("interface", io_r);
1927 const data = io_w.writableVector(limit);
19281928 var iovecs: [max_buffers_len]windows.WSABUF = undefined;
19291929 var iovecs_i: usize = 0;
19301930 for (data) |d| {
......@@ -1939,7 +1939,7 @@ pub const Stream = struct {
19391939 if (bufs.len == 0) return .{}; // Prevent false positive end detection on empty `data`.
19401940 var n: u32 = undefined;
19411941 var flags: u32 = 0;
1942 const rc = windows.ws2_32.WSARecvFrom(context, bufs.ptr, bufs.len, &n, &flags, null, null, null, null);
1942 const rc = windows.ws2_32.WSARecvFrom(r.net_stream.handle, bufs.ptr, bufs.len, &n, &flags, null, null, null, null);
19431943 if (rc != 0) switch (windows.ws2_32.WSAGetLastError()) {
19441944 .WSAECONNRESET => return error.ConnectionResetByPeer,
19451945 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
......@@ -1955,22 +1955,35 @@ pub const Stream = struct {
19551955 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
19561956 else => |err| return windows.unexpectedWSAError(err),
19571957 };
1958 return .{ .len = n, .end = n == 0 };
1958 if (n == 0) return error.EndOfStream;
1959 return io_w.advanceVector(n);
19591960 }
19601961
1961 fn discard(context: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {
1962 _ = context;
1962 fn discard(io_r: *io.Reader, limit: io.Limit) io.Reader.Error!usize {
1963 const r: *Reader = @fieldParentPtr("interface", io_r);
1964 _ = r;
19631965 _ = limit;
19641966 @panic("TODO");
19651967 }
19661968 },
19671969 else => struct {
1968 file_reader: std.fs.File.Reader,
1970 file_reader: File.Reader,
19691971
19701972 pub const Error = ReadError;
19711973
1972 pub fn interface(r: *Reader) std.io.Reader {
1973 return r.file_reader.interface();
1974 pub fn interface(r: *Reader) *io.Reader {
1975 return &r.file_reader.interface;
1976 }
1977
1978 pub fn init(net_stream: Stream, buffer: []u8) Reader {
1979 return .{
1980 .file_reader = .{
1981 .interface = File.Reader.initInterface(buffer),
1982 .file = .{ .handle = net_stream.handle },
1983 .mode = .streaming,
1984 .seek_err = error.Unseekable,
1985 },
1986 };
19741987 }
19751988
19761989 pub fn getStream(r: *const Reader) Stream {
......@@ -1981,16 +1994,22 @@ pub const Stream = struct {
19811994
19821995 pub const Writer = switch (native_os) {
19831996 .windows => struct {
1997 /// This field is present on all systems.
1998 interface: io.Writer,
1999 /// Use `getStream` for cross-platform support.
19842000 stream: Stream,
19852001
19862002 pub const Error = WriteError;
19872003
1988 pub fn interface(w: *Writer) std.io.Writer {
2004 pub fn init(stream: Stream, buffer: []u8) Writer {
19892005 return .{
1990 .context = w.stream.handle,
1991 .vtable = &.{
1992 .writeSplat = writeSplat,
1993 .writeFile = writeFile,
2006 .stream = stream,
2007 .interface = .{
2008 .context = undefined,
2009 .vtable = &.{
2010 .drain = drain,
2011 },
2012 .buffer = buffer,
19942013 },
19952014 };
19962015 }
......@@ -1999,41 +2018,63 @@ pub const Stream = struct {
19992018 return w.stream;
20002019 }
20012020
2002 fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
2021 fn drain(io_w: *io.Writer, data: []const []const u8, splat: usize) io.Writer.Error!usize {
2022 const w: *Writer = @fieldParentPtr("interface", io_w);
2023 const buffered = io_w.buffered();
20032024 comptime assert(native_os == .windows);
2004 if (data.len == 1 and splat == 0) return 0;
2005 var splat_buffer: [256]u8 = undefined;
2025 var splat_buffer: [splat_buffer_len]u8 = undefined;
20062026 var iovecs: [max_buffers_len]windows.WSABUF = undefined;
2007 var len: u32 = @min(iovecs.len, data.len);
2008 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
2009 .buf = if (d.len == 0) "" else d.ptr, // TODO: does Windows allow ptr=undefined len=0 ?
2010 .len = d.len,
2011 };
2027 var len: u32 = 0;
2028 if (buffered.len != 0) {
2029 iovecs[len] = .{
2030 .base = buffered.ptr,
2031 .len = buffered.len,
2032 };
2033 len += 1;
2034 }
2035 for (data[0..data.len]) |bytes| {
2036 if (bytes.len == 0) continue;
2037 iovecs[len] = .{
2038 .buf = bytes.ptr,
2039 .len = bytes.len,
2040 };
2041 len += 1;
2042 }
2043 const pattern = data[data.len - 1];
20122044 switch (splat) {
20132045 0 => len -= 1,
20142046 1 => {},
2015 else => {
2016 const pattern = data[data.len - 1];
2017 if (pattern.len == 1) {
2047 else => switch (pattern.len) {
2048 0 => {},
2049 1 => {
2050 // Replace the 1-byte buffer with a bigger one.
20182051 const memset_len = @min(splat_buffer.len, splat);
20192052 const buf = splat_buffer[0..memset_len];
20202053 @memset(buf, pattern[0]);
2021 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
2054 iovecs[len - 1] = .{ .buf = buf.ptr, .len = buf.len };
20222055 var remaining_splat = splat - buf.len;
20232056 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2024 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
2057 iovecs[len] = .{ .buf = &splat_buffer, .len = splat_buffer.len };
20252058 remaining_splat -= splat_buffer.len;
20262059 len += 1;
20272060 }
20282061 if (remaining_splat > 0 and len < iovecs.len) {
2029 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
2062 iovecs[len] = .{ .buf = &splat_buffer, .len = remaining_splat };
20302063 len += 1;
20312064 }
2032 }
2065 },
2066 else => for (0..splat - 1) |_| {
2067 if (iovecs.len - len == 0) break;
2068 iovecs[len] = .{
2069 .buf = pattern.ptr,
2070 .len = pattern.len,
2071 };
2072 len += 1;
2073 },
20332074 },
20342075 }
20352076 var n: u32 = undefined;
2036 const rc = windows.ws2_32.WSASend(context, &iovecs, len, &n, 0, null, null);
2077 const rc = windows.ws2_32.WSASend(w.stream.handle, &iovecs, len, &n, 0, null, null);
20372078 if (rc == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
20382079 .WSAECONNABORTED => return error.ConnectionResetByPeer,
20392080 .WSAECONNRESET => return error.ConnectionResetByPeer,
......@@ -2054,123 +2095,127 @@ pub const Stream = struct {
20542095 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
20552096 else => |err| return windows.unexpectedWSAError(err),
20562097 };
2057 return n;
2058 }
2059
2060 fn writeFile(
2061 context: *anyopaque,
2062 in_file: std.fs.File,
2063 in_offset: u64,
2064 in_len: std.io.Writer.FileLen,
2065 headers_and_trailers: []const []const u8,
2066 headers_len: usize,
2067 ) std.io.Writer.FileError!usize {
2068 const len_int = switch (in_len) {
2069 .zero => return writeSplat(context, headers_and_trailers, 1),
2070 .entire_file => std.math.maxInt(usize),
2071 else => in_len.int(),
2072 };
2073 if (headers_len > 0) return writeSplat(context, headers_and_trailers[0..headers_len], 1);
2074 var file_contents_buffer: [4096]u8 = undefined;
2075 const read_buffer = file_contents_buffer[0..@min(file_contents_buffer.len, len_int)];
2076 const n = try windows.ReadFile(in_file.handle, read_buffer, in_offset);
2077 return writeSplat(context, &.{read_buffer[0..n]}, 1);
2098 return io_w.consume(n);
20782099 }
20792100 },
20802101 else => struct {
2081 file_writer: std.fs.File.Writer,
2102 /// This field is present on all systems.
2103 interface: io.Writer,
2104
20822105 err: ?Error = null,
2106 file_writer: File.Writer,
20832107
20842108 pub const Error = WriteError;
20852109
2086 pub fn interface(w: *Writer) std.io.Writer {
2110 pub fn init(stream: Stream, buffer: []u8) Writer {
20872111 return .{
2088 .context = &w.file_writer,
2089 .vtable = &.{
2090 .writeSplat = writeSplat,
2091 .writeFile = std.fs.File.Writer.writeFile,
2112 .interface = .{
2113 .context = undefined,
2114 .vtable = &.{
2115 .drain = drain,
2116 .sendFile = sendFile,
2117 },
2118 .buffer = buffer,
20922119 },
2120 .file_writer = .initMode(stream.handle, &.{}, .streaming),
20932121 };
20942122 }
20952123
2096 fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
2097 const fw: *std.fs.File.Writer = @alignCast(@ptrCast(context));
2098 const w: *Writer = @fieldParentPtr("file_writer", fw);
2099 var splat_buffer: [256]u8 = undefined;
2124 pub fn getStream(w: *const Writer) Stream {
2125 return .{ .handle = w.file_writer.file.handle };
2126 }
2127
2128 fn drain(io_w: *io.Writer, data: []const []const u8, splat: usize) io.Writer.Error!usize {
2129 const w: *Writer = @fieldParentPtr("interface", io_w);
2130 const buffered = io_w.buffered();
2131 var splat_buffer: [splat_buffer_len]u8 = undefined;
21002132 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
2101 var len: usize = @min(iovecs.len, data.len);
2102 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
2103 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
2104 .len = d.len,
2105 };
2106 var msg: posix.msghdr_const = .{
2107 .name = null,
2108 .namelen = 0,
2109 .iov = &iovecs,
2110 .iovlen = len,
2111 .control = null,
2112 .controllen = 0,
2113 .flags = 0,
2133 var msg: posix.msghdr_const = msg: {
2134 var i: usize = 0;
2135 if (buffered.len != 0) {
2136 iovecs[i] = .{
2137 .base = buffered.ptr,
2138 .len = buffered.len,
2139 };
2140 i += 1;
2141 }
2142 for (data[0..data.len]) |bytes| {
2143 // OS checks ptr addr before length so zero length vectors must be omitted.
2144 if (bytes.len == 0) continue;
2145 iovecs[i] = .{
2146 .base = bytes.ptr,
2147 .len = bytes.len,
2148 };
2149 i += 1;
2150 if (iovecs.len - i == 0) break;
2151 }
2152 break :msg .{
2153 .name = null,
2154 .namelen = 0,
2155 .iov = &iovecs,
2156 .iovlen = i,
2157 .control = null,
2158 .controllen = 0,
2159 .flags = 0,
2160 };
21142161 };
2162 const pattern = data[data.len - 1];
21152163 switch (splat) {
2116 0 => msg.iovlen = len - 1,
2164 0 => msg.iovlen -= 1,
21172165 1 => {},
2118 else => {
2119 const pattern = data[data.len - 1];
2120 if (pattern.len == 1) {
2166 else => switch (pattern.len) {
2167 0 => {},
2168 1 => {
2169 // Replace the 1-byte buffer with a bigger one.
21212170 const memset_len = @min(splat_buffer.len, splat);
21222171 const buf = splat_buffer[0..memset_len];
21232172 @memset(buf, pattern[0]);
2124 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
2173 iovecs[msg.iovlen - 1] = .{ .base = buf.ptr, .len = buf.len };
21252174 var remaining_splat = splat - buf.len;
2126 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2127 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
2175 while (remaining_splat > splat_buffer.len and msg.iovlen < iovecs.len) {
2176 iovecs[msg.iovlen] = .{ .base = &splat_buffer, .len = splat_buffer.len };
21282177 remaining_splat -= splat_buffer.len;
2129 len += 1;
2178 msg.iovlen += 1;
21302179 }
2131 if (remaining_splat > 0 and len < iovecs.len) {
2132 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
2133 len += 1;
2180 if (remaining_splat > 0 and msg.iovlen < iovecs.len) {
2181 iovecs[msg.iovlen] = .{ .base = &splat_buffer, .len = remaining_splat };
2182 msg.iovlen += 1;
21342183 }
2135 msg.iovlen = len;
2136 }
2184 },
2185 else => for (0..splat - 1) |_| {
2186 if (iovecs.len - msg.iovlen == 0) break;
2187 iovecs[msg.iovlen] = .{
2188 .base = pattern.ptr,
2189 .len = pattern.len,
2190 };
2191 msg.iovlen += 1;
2192 },
21372193 },
21382194 }
21392195 const flags = posix.MSG.NOSIGNAL;
2140 return std.posix.sendmsg(fw.file.handle, &msg, flags) catch |err| {
2196 return io_w.consume(std.posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| {
21412197 w.err = err;
21422198 return error.WriteFailed;
2143 };
2199 });
21442200 }
21452201
2146 pub fn getStream(w: *const Writer) Stream {
2147 return .{ .handle = w.file_writer.file.handle };
2202 fn sendFile(io_w: *io.Writer, file_reader: *File.Reader, limit: io.Limit) io.Writer.FileError!usize {
2203 const w: *Writer = @fieldParentPtr("interface", io_w);
2204 return io_w.sendFileTo(&w.file_writer.interface, file_reader, limit);
21482205 }
21492206 },
21502207 };
21512208
2152 pub fn reader(stream: Stream) Reader {
2153 return switch (native_os) {
2154 .windows => .{ .stream = stream },
2155 else => .{ .file_reader = .{
2156 .file = .{ .handle = stream.handle },
2157 .mode = .streaming,
2158 .seek_err = error.Unseekable,
2159 } },
2160 };
2209 pub fn reader(stream: Stream, buffer: []u8) Reader {
2210 return .init(stream, buffer);
21612211 }
21622212
2163 pub fn writer(stream: Stream) Writer {
2164 return switch (native_os) {
2165 .windows => .{ .stream = stream },
2166 else => .{ .file_writer = .{
2167 .file = .{ .handle = stream.handle },
2168 .mode = .streaming,
2169 } },
2170 };
2213 pub fn writer(stream: Stream, buffer: []u8) Writer {
2214 return .init(stream, buffer);
21712215 }
21722216
21732217 const max_buffers_len = 8;
2218 const splat_buffer_len = 256;
21742219};
21752220
21762221pub const Server = struct {