diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 7772b8f2906d..d2703f6c1e36 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -1285,7 +1285,7 @@ const ElfFileHelper = struct { for (consolidated.items) |cmd| { switch (cmd) { .write_data => |data| { - var iovec = [_]std.posix.iovec_const{.{ .iov_base = data.data.ptr, .iov_len = data.data.len }}; + var iovec = [_]std.io.WriteBuffers{.{ .ptr = data.data.ptr, .len = data.data.len }}; try out_file.pwritevAll(&iovec, data.out_offset); }, .copy_range => |range| { diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index ff2307e8124d..b93e2d2f9b28 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -344,7 +344,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { @compileError("The Writer interface is only defined for ArrayList(u8) " ++ "but the given type is ArrayList(" ++ @typeName(T) ++ ")") else - std.io.Writer(*Self, Allocator.Error, appendWrite); + std.io.Writer(*Self, Allocator.Error, appendWritev); /// Initializes a Writer which will append to the list. pub fn writer(self: *Self) Writer { @@ -354,9 +354,13 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { /// Same as `append` except it returns the number of bytes written, which is always the same /// as `m.len`. The purpose of this function existing is to match `std.io.Writer` API. /// Invalidates element pointers if additional memory is needed. - fn appendWrite(self: *Self, m: []const u8) Allocator.Error!usize { - try self.appendSlice(m); - return m.len; + fn appendWritev(self: *Self, iov: []std.io.WriteBuffers) Allocator.Error!usize { + var written: usize = 0; + for (iov) |v| { + try self.appendSlice(v.ptr[0..v.len]); + written += v.len; + } + return written; } /// Append a value to the list `n` times. @@ -930,7 +934,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ @compileError("The Writer interface is only defined for ArrayList(u8) " ++ "but the given type is ArrayList(" ++ @typeName(T) ++ ")") else - std.io.Writer(WriterContext, Allocator.Error, appendWrite); + std.io.Writer(WriterContext, Allocator.Error, appendWritev); /// Initializes a Writer which will append to the list. pub fn writer(self: *Self, allocator: Allocator) Writer { @@ -941,12 +945,16 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// which is always the same as `m.len`. The purpose of this function /// existing is to match `std.io.Writer` API. /// Invalidates element pointers if additional memory is needed. - fn appendWrite(context: WriterContext, m: []const u8) Allocator.Error!usize { - try context.self.appendSlice(context.allocator, m); - return m.len; + fn appendWritev(context: WriterContext, iov: []std.io.WriteBuffers) Allocator.Error!usize { + var written: usize = 0; + for (iov) |v| { + try context.self.appendSlice(context.allocator, v.ptr[0..v.len]); + written += v.len; + } + return written; } - pub const FixedWriter = std.io.Writer(*Self, Allocator.Error, appendWriteFixed); + pub const FixedWriter = std.io.Writer(*Self, Allocator.Error, appendWritevFixed); /// Initializes a Writer which will append to the list but will return /// `error.OutOfMemory` rather than increasing capacity. @@ -955,13 +963,18 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ } /// The purpose of this function existing is to match `std.io.Writer` API. - fn appendWriteFixed(self: *Self, m: []const u8) error{OutOfMemory}!usize { - const available_capacity = self.capacity - self.items.len; - if (m.len > available_capacity) - return error.OutOfMemory; - - self.appendSliceAssumeCapacity(m); - return m.len; + fn appendWritevFixed(self: *Self, iov: []std.io.WriteBuffers) error{OutOfMemory}!usize { + var written: usize = 0; + for (iov) |v| { + const m = v.ptr[0..v.len]; + const available_capacity = self.capacity - self.items.len; + if (m.len > available_capacity) + return error.OutOfMemory; + + self.appendSliceAssumeCapacity(m); + written += m.len; + } + return written; } /// Append a value to the list `n` times. diff --git a/lib/std/bounded_array.zig b/lib/std/bounded_array.zig index 9867754dcd0a..98cf9372bcca 100644 --- a/lib/std/bounded_array.zig +++ b/lib/std/bounded_array.zig @@ -271,7 +271,7 @@ pub fn BoundedArrayAligned( @compileError("The Writer interface is only defined for BoundedArray(u8, ...) " ++ "but the given type is BoundedArray(" ++ @typeName(T) ++ ", ...)") else - std.io.Writer(*Self, error{Overflow}, appendWrite); + std.io.Writer(*Self, error{Overflow}, appendWritev); /// Initializes a writer which will write into the array. pub fn writer(self: *Self) Writer { @@ -280,9 +280,14 @@ pub fn BoundedArrayAligned( /// Same as `appendSlice` except it returns the number of bytes written, which is always the same /// as `m.len`. The purpose of this function existing is to match `std.io.Writer` API. - fn appendWrite(self: *Self, m: []const u8) error{Overflow}!usize { - try self.appendSlice(m); - return m.len; + fn appendWritev(self: *Self, iov: []std.io.WriteBuffers) error{Overflow}!usize { + var written: usize = 0; + for (iov) |v| { + const m = v.ptr[0..v.len]; + try self.appendSlice(m); + written += m.len; + } + return written; } }; } diff --git a/lib/std/c.zig b/lib/std/c.zig index a7468efd7d9f..7e74f57b7f0d 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -2,8 +2,8 @@ const std = @import("std"); const builtin = @import("builtin"); const c = @This(); const page_size = std.mem.page_size; -const iovec = std.posix.iovec; -const iovec_const = std.posix.iovec_const; +const iovec = std.io.ReadBuffers; +const iovec_const = std.io.WriteBuffers; const wasi = @import("c/wasi.zig"); const native_abi = builtin.abi; const native_arch = builtin.cpu.arch; diff --git a/lib/std/compress.zig b/lib/std/compress.zig index 200489c18ab5..985e74345914 100644 --- a/lib/std/compress.zig +++ b/lib/std/compress.zig @@ -19,12 +19,18 @@ pub fn HashedReader( hasher: HasherType, pub const Error = ReaderType.Error; - pub const Reader = std.io.Reader(*@This(), Error, read); + pub const Reader = std.io.Reader(*@This(), Error, readv); - pub fn read(self: *@This(), buf: []u8) Error!usize { - const amt = try self.child_reader.read(buf); - self.hasher.update(buf[0..amt]); - return amt; + pub fn readv(self: *@This(), iov: []std.io.ReadBuffers) Error!usize { + const n_read = try self.child_reader.readv(iov); + var hashed_amt: usize = 0; + for (iov) |v| { + const to_hash = @min(n_read - hashed_amt, v.len); + if (to_hash == 0) break; + self.hasher.update(v.ptr[0..to_hash]); + hashed_amt += to_hash; + } + return n_read; } pub fn reader(self: *@This()) Reader { @@ -51,10 +57,16 @@ pub fn HashedWriter( pub const Error = WriterType.Error; pub const Writer = std.io.Writer(*@This(), Error, write); - pub fn write(self: *@This(), buf: []const u8) Error!usize { - const amt = try self.child_writer.write(buf); - self.hasher.update(buf[0..amt]); - return amt; + pub fn write(self: *@This(), iov: []std.io.WriteBuffers) Error!usize { + const n_written = try self.child_writer.writev(iov); + var hashed_amt: usize = 0; + for (iov) |v| { + const to_hash = @min(n_written - hashed_amt, v.len); + if (to_hash == 0) break; + self.hasher.update(v.ptr[0..to_hash]); + hashed_amt += to_hash; + } + return n_written; } pub fn writer(self: *@This()) Writer { diff --git a/lib/std/compress/flate/deflate.zig b/lib/std/compress/flate/deflate.zig index 794ab02247c3..421890fc9e86 100644 --- a/lib/std/compress/flate/deflate.zig +++ b/lib/std/compress/flate/deflate.zig @@ -354,16 +354,20 @@ fn Deflate(comptime container: Container, comptime WriterType: type, comptime Bl } // Writer interface - - pub const Writer = io.Writer(*Self, Error, write); + pub const Writer = io.Writer(*Self, Error, writev); pub const Error = BlockWriterType.Error; /// Write `input` of uncompressed data. /// See compress. - pub fn write(self: *Self, input: []const u8) !usize { - var fbs = io.fixedBufferStream(input); - try self.compress(fbs.reader()); - return input.len; + pub fn writev(self: *Self, iov: []std.io.WriteBuffers) !usize { + var written: usize = 0; + for (iov) |v| { + const input = v.ptr[0..v.len]; + var fbs = io.fixedBufferStream(input); + try self.compress(fbs.reader()); + written += input.len; + } + return written; } pub fn writer(self: *Self) Writer { @@ -558,7 +562,7 @@ test "tokenization" { const cww = cw.writer(); var df = try Deflate(container, @TypeOf(cww), TestTokenWriter).init(cww, .{}); - _ = try df.write(c.data); + _ = try df.writer().write(c.data); try df.flush(); // df.token_writer.show(); @@ -579,6 +583,8 @@ const TestTokenWriter = struct { pos: usize = 0, actual: [128]Token = undefined, + pub const Error = error{}; + pub fn init(_: anytype) Self { return .{}; } diff --git a/lib/std/compress/flate/inflate.zig b/lib/std/compress/flate/inflate.zig index cf23961b2132..03af0b519530 100644 --- a/lib/std/compress/flate/inflate.zig +++ b/lib/std/compress/flate/inflate.zig @@ -340,13 +340,15 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type, comp } // Reader interface - - pub const Reader = std.io.Reader(*Self, Error, read); + pub const Reader = std.io.Reader(*Self, Error, readv); /// Returns the number of bytes read. It may be less than buffer.len. /// If the number of bytes read is 0, it means end of stream. /// End of stream is not an error condition. - pub fn read(self: *Self, buffer: []u8) Error!usize { + pub fn readv(self: *Self, iovecs: []std.io.ReadBuffers) Error!usize { + if (iovecs.len == 0) return 0; + const first = iovecs[0]; + const buffer = first.ptr[0..first.len]; const out = try self.get(buffer.len); @memcpy(buffer[0..out.len], out); return out.len; diff --git a/lib/std/compress/lzma.zig b/lib/std/compress/lzma.zig index ff05bc1c8beb..a39c047f2a68 100644 --- a/lib/std/compress/lzma.zig +++ b/lib/std/compress/lzma.zig @@ -30,7 +30,7 @@ pub fn Decompress(comptime ReaderType: type) type { Allocator.Error || error{ CorruptInput, EndOfStream, Overflow }; - pub const Reader = std.io.Reader(*Self, Error, read); + pub const Reader = std.io.Reader(*Self, Error, readv); allocator: Allocator, in_reader: ReaderType, @@ -63,7 +63,11 @@ pub fn Decompress(comptime ReaderType: type) type { self.* = undefined; } - pub fn read(self: *Self, output: []u8) Error!usize { + pub fn readv(self: *Self, iovecs: []std.io.ReadBuffers) Error!usize { + if (iovecs.len == 0) return 0; + const first = iovecs[0]; + const output = first.ptr[0..first.len]; + const writer = self.to_read.writer(self.allocator); while (self.to_read.items.len < output.len) { switch (try self.state.process(self.allocator, self.in_reader, writer, &self.buffer, &self.decoder)) { diff --git a/lib/std/compress/xz.zig b/lib/std/compress/xz.zig index e844c234ffc8..acca884ca166 100644 --- a/lib/std/compress/xz.zig +++ b/lib/std/compress/xz.zig @@ -34,7 +34,7 @@ pub fn Decompress(comptime ReaderType: type) type { const Self = @This(); pub const Error = ReaderType.Error || block.Decoder(ReaderType).Error; - pub const Reader = std.io.Reader(*Self, Error, read); + pub const Reader = std.io.Reader(*Self, Error, readv); allocator: Allocator, block_decoder: block.Decoder(ReaderType), @@ -71,7 +71,10 @@ pub fn Decompress(comptime ReaderType: type) type { return .{ .context = self }; } - pub fn read(self: *Self, buffer: []u8) Error!usize { + pub fn readv(self: *Self, iov: []std.io.ReadBuffers) Error!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const buffer = first.ptr[0..first.len]; if (buffer.len == 0) return 0; diff --git a/lib/std/compress/zstandard.zig b/lib/std/compress/zstandard.zig index 9092a2d13083..d939b20c12d5 100644 --- a/lib/std/compress/zstandard.zig +++ b/lib/std/compress/zstandard.zig @@ -50,7 +50,7 @@ pub fn Decompressor(comptime ReaderType: type) type { OutOfMemory, }; - pub const Reader = std.io.Reader(*Self, Error, read); + pub const Reader = std.io.Reader(*Self, Error, readv); pub fn init(source: ReaderType, options: DecompressorOptions) Self { return .{ @@ -105,7 +105,10 @@ pub fn Decompressor(comptime ReaderType: type) type { return .{ .context = self }; } - pub fn read(self: *Self, buffer: []u8) Error!usize { + pub fn readv(self: *Self, iov: []std.io.ReadBuffers) Error!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const buffer = first.ptr[0..first.len]; if (buffer.len == 0) return 0; var size: usize = 0; diff --git a/lib/std/compress/zstandard/readers.zig b/lib/std/compress/zstandard/readers.zig index f95573f77bbf..eb923ce30947 100644 --- a/lib/std/compress/zstandard/readers.zig +++ b/lib/std/compress/zstandard/readers.zig @@ -4,7 +4,7 @@ pub const ReversedByteReader = struct { remaining_bytes: usize, bytes: []const u8, - const Reader = std.io.Reader(*ReversedByteReader, error{}, readFn); + const Reader = std.io.Reader(*ReversedByteReader, error{}, readvFn); pub fn init(bytes: []const u8) ReversedByteReader { return .{ @@ -17,9 +17,13 @@ pub const ReversedByteReader = struct { return .{ .context = self }; } - fn readFn(ctx: *ReversedByteReader, buffer: []u8) !usize { + fn readvFn(ctx: *ReversedByteReader, iov: []std.io.ReadBuffers) !usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const buffer = first.ptr[0..first.len]; if (ctx.remaining_bytes == 0) return 0; const byte_index = ctx.remaining_bytes - 1; + std.debug.assert(buffer.len > 0); buffer[0] = ctx.bytes[byte_index]; // buffer[0] = @bitReverse(ctx.bytes[byte_index]); ctx.remaining_bytes = byte_index; diff --git a/lib/std/crypto/sha2.zig b/lib/std/crypto/sha2.zig index 31884c73818a..907305bf78f1 100644 --- a/lib/std/crypto/sha2.zig +++ b/lib/std/crypto/sha2.zig @@ -392,11 +392,15 @@ fn Sha2x32(comptime params: Sha2Params32) type { } pub const Error = error{}; - pub const Writer = std.io.Writer(*Self, Error, write); + pub const Writer = std.io.Writer(*Self, Error, writev); - fn write(self: *Self, bytes: []const u8) Error!usize { - self.update(bytes); - return bytes.len; + fn writev(self: *Self, iov: []std.io.WriteBuffers) Error!usize { + var written: usize = 0; + for (iov) |v| { + self.update(v.ptr[0..v.len]); + written += v.len; + } + return written; } pub fn writer(self: *Self) Writer { diff --git a/lib/std/crypto/siphash.zig b/lib/std/crypto/siphash.zig index 5d1ac4f87469..98e7188f64df 100644 --- a/lib/std/crypto/siphash.zig +++ b/lib/std/crypto/siphash.zig @@ -240,11 +240,15 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) } pub const Error = error{}; - pub const Writer = std.io.Writer(*Self, Error, write); + pub const Writer = std.io.Writer(*Self, Error, writev); - fn write(self: *Self, bytes: []const u8) Error!usize { - self.update(bytes); - return bytes.len; + fn writev(self: *Self, iov: []std.io.WriteBuffers) Error!usize { + var written: usize = 0; + for (iov) |v| { + self.update(v.ptr[0..v.len]); + written += v.len; + } + return written; } pub fn writer(self: *Self) Writer { diff --git a/lib/std/crypto/tls.zig b/lib/std/crypto/tls.zig index 7fff68471caa..e1e54f162a0a 100644 --- a/lib/std/crypto/tls.zig +++ b/lib/std/crypto/tls.zig @@ -460,7 +460,7 @@ pub const Decoder = struct { } /// Use this function to increase `their_end`. - pub fn readAtLeast(d: *Decoder, stream: anytype, their_amt: usize) !void { + pub fn readAtLeast(d: *Decoder, reader: std.io.AnyReader, their_amt: usize) !void { assert(!d.disable_reads); const existing_amt = d.cap - d.idx; d.their_end = d.idx + their_amt; @@ -468,16 +468,17 @@ pub const Decoder = struct { const request_amt = their_amt - existing_amt; const dest = d.buf[d.cap..]; if (request_amt > dest.len) return error.TlsRecordOverflow; - const actual_amt = try stream.readAtLeast(dest, request_amt); + + const actual_amt = try reader.readAtLeast(dest, request_amt); if (actual_amt < request_amt) return error.TlsConnectionTruncated; d.cap += actual_amt; } /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`. /// Use when `our_amt` is calculated by us, not by them. - pub fn readAtLeastOurAmt(d: *Decoder, stream: anytype, our_amt: usize) !void { + pub fn readAtLeastOurAmt(d: *Decoder, reader: std.io.AnyReader, our_amt: usize) !void { assert(!d.disable_reads); - try readAtLeast(d, stream, our_amt); + try d.readAtLeast(reader, our_amt); d.our_end = d.idx + our_amt; } diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index 682c1ffe0ebb..e6ea1dd8751d 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -13,7 +13,9 @@ const int2 = tls.int2; const int3 = tls.int3; const array = tls.array; const enum_array = tls.enum_array; +const Stream = std.io.AnyStream; +inner_stream: Stream, read_seq: u64, write_seq: u64, /// The starting index of cleartext bytes inside `partially_read_buffer`. @@ -45,102 +47,12 @@ application_cipher: tls.ApplicationCipher, /// `partial_ciphertext_end` describe the span of the segments. partially_read_buffer: [tls.max_ciphertext_record_len]u8, -/// This is an example of the type that is needed by the read and write -/// functions. It can have any fields but it must at least have these -/// functions. -/// -/// Note that `std.net.Stream` conforms to this interface. -/// -/// This declaration serves as documentation only. -pub const StreamInterface = struct { - /// Can be any error set. - pub const ReadError = error{}; - - /// Returns the number of bytes read. The number read may be less than the - /// buffer space provided. End-of-stream is indicated by a return value of 0. - /// - /// The `iovecs` parameter is mutable because so that function may to - /// mutate the fields in order to handle partial reads from the underlying - /// stream layer. - pub fn readv(this: @This(), iovecs: []std.posix.iovec) ReadError!usize { - _ = .{ this, iovecs }; - @panic("unimplemented"); - } - - /// Can be any error set. - pub const WriteError = error{}; - - /// Returns the number of bytes read, which may be less than the buffer - /// space provided. A short read does not indicate end-of-stream. - pub fn writev(this: @This(), iovecs: []const std.posix.iovec_const) WriteError!usize { - _ = .{ this, iovecs }; - @panic("unimplemented"); - } - - /// Returns the number of bytes read, which may be less than the buffer - /// space provided, indicating end-of-stream. - /// The `iovecs` parameter is mutable in case this function needs to mutate - /// the fields in order to handle partial writes from the underlying layer. - pub fn writevAll(this: @This(), iovecs: []std.posix.iovec_const) WriteError!usize { - // This can be implemented in terms of writev, or specialized if desired. - _ = .{ this, iovecs }; - @panic("unimplemented"); - } -}; - -pub fn InitError(comptime Stream: type) type { - return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || tls.AlertDescription.Error || error{ - InsufficientEntropy, - DiskQuota, - LockViolation, - NotOpenForWriting, - TlsUnexpectedMessage, - TlsIllegalParameter, - TlsDecryptFailure, - TlsRecordOverflow, - TlsBadRecordMac, - CertificateFieldHasInvalidLength, - CertificateHostMismatch, - CertificatePublicKeyInvalid, - CertificateExpired, - CertificateFieldHasWrongDataType, - CertificateIssuerMismatch, - CertificateNotYetValid, - CertificateSignatureAlgorithmMismatch, - CertificateSignatureAlgorithmUnsupported, - CertificateSignatureInvalid, - CertificateSignatureInvalidLength, - CertificateSignatureNamedCurveUnsupported, - CertificateSignatureUnsupportedBitCount, - TlsCertificateNotVerified, - TlsBadSignatureScheme, - TlsBadRsaSignatureBitCount, - InvalidEncoding, - IdentityElement, - SignatureVerificationFailed, - TlsDecryptError, - TlsConnectionTruncated, - TlsDecodeError, - UnsupportedCertificateVersion, - CertificateTimeInvalid, - CertificateHasUnrecognizedObjectId, - CertificateHasInvalidBitString, - MessageTooLong, - NegativeIntoUnsigned, - TargetTooSmall, - BufferTooSmall, - InvalidSignature, - NotSquare, - NonCanonical, - WeakPublicKey, - }; -} - -/// Initiates a TLS handshake and establishes a TLSv1.3 session with `stream`, which -/// must conform to `StreamInterface`. +/// Initiates a TLS handshake and establishes a TLSv1.3 session with `inner_stream`. /// /// `host` is only borrowed during this function call. -pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) InitError(@TypeOf(stream))!Client { +pub fn init(inner_stream: Stream, ca_bundle: Certificate.Bundle, host: []const u8) !Client { + const reader = inner_stream.reader(); + const writer = inner_stream.writer(); const host_len: u16 = @intCast(host.len); var random_buffer: [128]u8 = undefined; @@ -215,17 +127,17 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In } ++ int2(@intCast(out_handshake.len + host_len)) ++ out_handshake; { - var iovecs = [_]std.posix.iovec_const{ + var iovecs = [_]std.io.WriteBuffers{ .{ - .iov_base = &plaintext_header, - .iov_len = plaintext_header.len, + .ptr = &plaintext_header, + .len = plaintext_header.len, }, .{ - .iov_base = host.ptr, - .iov_len = host.len, + .ptr = host.ptr, + .len = host.len, }, }; - try stream.writevAll(&iovecs); + try writer.writevAll(&iovecs); } const client_hello_bytes1 = plaintext_header[5..]; @@ -234,11 +146,11 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In var handshake_buffer: [8000]u8 = undefined; var d: tls.Decoder = .{ .buf = &handshake_buffer }; { - try d.readAtLeastOurAmt(stream, tls.record_header_len); + try d.readAtLeastOurAmt(reader, tls.record_header_len); const ct = d.decode(tls.ContentType); d.skip(2); // legacy_record_version const record_len = d.decode(u16); - try d.readAtLeast(stream, record_len); + try d.readAtLeast(reader, record_len); const server_hello_fragment = d.buf[d.idx..][0..record_len]; var ptd = try d.sub(record_len); switch (ct) { @@ -428,12 +340,12 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In const now_sec = std.time.timestamp(); while (true) { - try d.readAtLeastOurAmt(stream, tls.record_header_len); + try d.readAtLeastOurAmt(reader, tls.record_header_len); const record_header = d.buf[d.idx..][0..5]; const ct = d.decode(tls.ContentType); d.skip(2); // legacy_version const record_len = d.decode(u16); - try d.readAtLeast(stream, record_len); + try d.readAtLeast(reader, record_len); var record_decoder = try d.sub(record_len); switch (ct) { .change_cipher_spec => { @@ -466,6 +378,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In break :nonce @as(V, p.server_handshake_iv) ^ operand; }; read_seq += 1; + std.debug.print("", .{}); P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, p.server_handshake_key) catch return error.TlsBadRecordMac; break :c cleartext; @@ -677,11 +590,11 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key); const both_msgs = client_change_cipher_spec_msg ++ finished_msg; - var both_msgs_vec = [_]std.posix.iovec_const{.{ - .iov_base = &both_msgs, - .iov_len = both_msgs.len, + var both_msgs_vec = [_]std.io.WriteBuffers{.{ + .ptr = &both_msgs, + .len = both_msgs.len, }}; - try stream.writevAll(&both_msgs_vec); + try writer.writevAll(&both_msgs_vec); const client_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); const server_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); @@ -697,6 +610,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In }; const leftover = d.rest(); var client: Client = .{ + .inner_stream = inner_stream, .read_seq = 0, .write_seq = 0, .partial_cleartext_idx = 0, @@ -723,39 +637,13 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In } } -/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`. -/// Returns the number of plaintext bytes sent, which may be fewer than `bytes.len`. -pub fn write(c: *Client, stream: anytype, bytes: []const u8) !usize { - return writeEnd(c, stream, bytes, false); -} - -/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`. -pub fn writeAll(c: *Client, stream: anytype, bytes: []const u8) !void { - var index: usize = 0; - while (index < bytes.len) { - index += try c.write(stream, bytes[index..]); - } -} - -/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`. -/// If `end` is true, then this function additionally sends a `close_notify` alert, -/// which is necessary for the server to distinguish between a properly finished -/// TLS session, or a truncation attack. -pub fn writeAllEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !void { - var index: usize = 0; - while (index < bytes.len) { - index += try c.writeEnd(stream, bytes[index..], end); - } -} - -/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`. /// Returns the number of plaintext bytes sent, which may be fewer than `bytes.len`. /// If `end` is true, then this function additionally sends a `close_notify` alert, /// which is necessary for the server to distinguish between a properly finished /// TLS session, or a truncation attack. -pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usize { +pub fn writeEnd(c: *Client, bytes: []const u8, end: bool) !usize { var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined; - var iovecs_buf: [6]std.posix.iovec_const = undefined; + var iovecs_buf: [6]std.io.WriteBuffers = undefined; var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data); if (end) { prepared.iovec_end += prepareCiphertextRecord( @@ -767,36 +655,26 @@ pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usiz ).iovec_end; } - const iovec_end = prepared.iovec_end; - const overhead_len = prepared.overhead_len; + const iovecs = iovecs_buf[0..prepared.iovec_end]; - // Ideally we would call writev exactly once here, however, we must ensure - // that we don't return with a record partially written. - var i: usize = 0; - var total_amt: usize = 0; - while (true) { - var amt = try stream.writev(iovecs_buf[i..iovec_end]); - while (amt >= iovecs_buf[i].iov_len) { - const encrypted_amt = iovecs_buf[i].iov_len; - total_amt += encrypted_amt - overhead_len; - amt -= encrypted_amt; - i += 1; - // Rely on the property that iovecs delineate records, meaning that - // if amt equals zero here, we have fortunately found ourselves - // with a short read that aligns at the record boundary. - if (i >= iovec_end) return total_amt; - // We also cannot return on a vector boundary if the final close_notify is - // not sent; otherwise the caller would not know to retry the call. - if (amt == 0 and (!end or i < iovec_end - 1)) return total_amt; - } - iovecs_buf[i].iov_base += amt; - iovecs_buf[i].iov_len -= amt; - } + var n_written: usize = 0; + for (iovecs) |iov| n_written += iov.len; + + try c.inner_stream.writer().writevAll(iovecs); + + return n_written; +} + +pub fn writev(c: *Client, iovecs: []std.io.WriteBuffers) !usize { + if (iovecs.len == 0) return 0; + const first = iovecs[0]; + const bytes = first.ptr[0..first.len]; + return try c.writeEnd(bytes, false); } fn prepareCiphertextRecord( c: *Client, - iovecs: []std.posix.iovec_const, + iovecs: []std.io.WriteBuffers, ciphertext_buf: []u8, bytes: []const u8, inner_content_type: tls.ContentType, @@ -860,12 +738,13 @@ fn prepareCiphertextRecord( break :nonce @as(V, p.client_iv) ^ operand; }; c.write_seq += 1; // TODO send key_update on overflow + std.debug.print("", .{}); P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key); const record = ciphertext_buf[record_start..ciphertext_end]; iovecs[iovec_end] = .{ - .iov_base = record.ptr, - .iov_len = record.len, + .ptr = record.ptr, + .len = record.len, }; iovec_end += 1; } @@ -879,65 +758,39 @@ pub fn eof(c: Client) bool { c.partial_ciphertext_idx >= c.partial_ciphertext_end; } -/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. -/// Returns the number of bytes read, calling the underlying read function the -/// minimal number of times until the buffer has at least `len` bytes filled. -/// If the number read is less than `len` it means the stream reached the end. -/// Reaching the end of the stream is not an error condition. -pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize { - var iovecs = [1]std.posix.iovec{.{ .iov_base = buffer.ptr, .iov_len = buffer.len }}; - return readvAtLeast(c, stream, &iovecs, len); -} - -/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. -pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize { - return readAtLeast(c, stream, buffer, 1); -} - -/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. -/// Returns the number of bytes read. If the number read is smaller than -/// `buffer.len`, it means the stream reached the end. Reaching the end of the -/// stream is not an error condition. -pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize { - return readAtLeast(c, stream, buffer, buffer.len); -} - -/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. /// Returns the number of bytes read. If the number read is less than the space /// provided it means the stream reached the end. Reaching the end of the /// stream is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying stream layer. -pub fn readv(c: *Client, stream: anytype, iovecs: []std.posix.iovec) !usize { - return readvAtLeast(c, stream, iovecs, 1); +pub fn readv(c: *Client, iovecs: []std.io.ReadBuffers) !usize { + return c.readvAtLeast(iovecs, 1); } -/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. /// Returns the number of bytes read, calling the underlying read function the /// minimal number of times until the iovecs have at least `len` bytes filled. /// If the number read is less than `len` it means the stream reached the end. /// Reaching the end of the stream is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying stream layer. -pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len: usize) !usize { +pub fn readvAtLeast(c: *Client, iovecs: []std.io.ReadBuffers, len: usize) !usize { if (c.eof()) return 0; var off_i: usize = 0; var vec_i: usize = 0; while (true) { - var amt = try c.readvAdvanced(stream, iovecs[vec_i..]); + var amt = try c.readvAdvanced(iovecs[vec_i..]); off_i += amt; if (c.eof() or off_i >= len) return off_i; - while (amt >= iovecs[vec_i].iov_len) { - amt -= iovecs[vec_i].iov_len; + while (amt >= iovecs[vec_i].len) { + amt -= iovecs[vec_i].len; vec_i += 1; } - iovecs[vec_i].iov_base += amt; - iovecs[vec_i].iov_len -= amt; + iovecs[vec_i].ptr += amt; + iovecs[vec_i].len -= amt; } } -/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. /// Returns number of bytes that have been read, populated inside `iovecs`. A /// return value of zero bytes does not mean end of stream. Instead, check the `eof()` /// for the end of stream. The `eof()` may be true after any call to @@ -945,7 +798,7 @@ pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len: /// function asserts that `eof()` is `false`. /// See `readv` for a higher level function that has the same, familiar API as /// other read functions, such as `std.fs.File.read`. -pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iovec) !usize { +pub fn readvAdvanced(c: *Client, iovecs: []const std.io.ReadBuffers) !usize { var vp: VecPut = .{ .iovecs = iovecs }; // Give away the buffered cleartext we have, if any. @@ -998,14 +851,14 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove c.partial_cleartext_idx = 0; const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..]; - var ask_iovecs_buf: [2]std.posix.iovec = .{ + var ask_iovecs_buf: [2]std.io.ReadBuffers = .{ .{ - .iov_base = first_iov.ptr, - .iov_len = first_iov.len, + .ptr = first_iov.ptr, + .len = first_iov.len, }, .{ - .iov_base = &in_stack_buffer, - .iov_len = in_stack_buffer.len, + .ptr = &in_stack_buffer, + .len = in_stack_buffer.len, }, }; @@ -1014,7 +867,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len); const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len); const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len); - const actual_read_len = try stream.readv(ask_iovecs); + const actual_read_len = try c.inner_stream.reader().readv(ask_iovecs); if (actual_read_len == 0) { // This is either a truncation attack, a bug in the server, or an // intentional omission of the close_notify message due to truncation @@ -1316,6 +1169,16 @@ fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 { } } +pub fn close(c: *Client) void { + _ = c.writeEnd("", true) catch {}; +} + +pub const GenericStream = std.io.GenericStream(*Client, anyerror, readv, anyerror, writev, close); + +pub fn stream(c: *Client) GenericStream { + return .{ .context = c }; +} + const builtin = @import("builtin"); const native_endian = builtin.cpu.arch.endian(); @@ -1352,7 +1215,7 @@ fn SchemeEddsa(comptime scheme: tls.SignatureScheme) type { /// Abstraction for sending multiple byte buffers to a slice of iovecs. const VecPut = struct { - iovecs: []const std.posix.iovec, + iovecs: []const std.io.ReadBuffers, idx: usize = 0, off: usize = 0, total: usize = 0, @@ -1364,12 +1227,12 @@ const VecPut = struct { var bytes_i: usize = 0; while (true) { const v = vp.iovecs[vp.idx]; - const dest = v.iov_base[vp.off..v.iov_len]; + const dest = v.ptr[vp.off..v.len]; const src = bytes[bytes_i..][0..@min(dest.len, bytes.len - bytes_i)]; @memcpy(dest[0..src.len], src); bytes_i += src.len; vp.off += src.len; - if (vp.off >= v.iov_len) { + if (vp.off >= v.len) { vp.off = 0; vp.idx += 1; if (vp.idx >= vp.iovecs.len) { @@ -1388,7 +1251,7 @@ const VecPut = struct { fn peek(vp: VecPut) []u8 { if (vp.idx >= vp.iovecs.len) return &.{}; const v = vp.iovecs[vp.idx]; - return v.iov_base[vp.off..v.iov_len]; + return v.ptr[vp.off..v.len]; } // After writing to the result of peek(), one can call next() to @@ -1396,7 +1259,7 @@ const VecPut = struct { fn next(vp: *VecPut, len: usize) void { vp.total += len; vp.off += len; - if (vp.off >= vp.iovecs[vp.idx].iov_len) { + if (vp.off >= vp.iovecs[vp.idx].len) { vp.off = 0; vp.idx += 1; } @@ -1405,22 +1268,22 @@ const VecPut = struct { fn freeSize(vp: VecPut) usize { if (vp.idx >= vp.iovecs.len) return 0; var total: usize = 0; - total += vp.iovecs[vp.idx].iov_len - vp.off; + total += vp.iovecs[vp.idx].len - vp.off; if (vp.idx + 1 >= vp.iovecs.len) return total; - for (vp.iovecs[vp.idx + 1 ..]) |v| total += v.iov_len; + for (vp.iovecs[vp.idx + 1 ..]) |v| total += v.len; return total; } }; /// Limit iovecs to a specific byte size. -fn limitVecs(iovecs: []std.posix.iovec, len: usize) []std.posix.iovec { +fn limitVecs(iovecs: []std.io.ReadBuffers, len: usize) []std.io.ReadBuffers { var bytes_left: usize = len; for (iovecs, 0..) |*iovec, vec_i| { - if (bytes_left <= iovec.iov_len) { - iovec.iov_len = bytes_left; + if (bytes_left <= iovec.len) { + iovec.len = bytes_left; return iovecs[0 .. vec_i + 1]; } - bytes_left -= iovec.iov_len; + bytes_left -= iovec.len; } return iovecs; } @@ -1462,7 +1325,3 @@ else .AES_128_GCM_SHA256, .AES_256_GCM_SHA384, }); - -test { - _ = StreamInterface; -} diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index a26086700258..7ae79bc3a3d5 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -38,8 +38,8 @@ pub fn LinearFifo( count: usize, const Self = @This(); - pub const Reader = std.io.Reader(*Self, error{}, readFn); - pub const Writer = std.io.Writer(*Self, error{OutOfMemory}, appendWrite); + pub const Reader = std.io.Reader(*Self, error{}, readvFn); + pub const Writer = std.io.Writer(*Self, error{OutOfMemory}, appendWritev); // Type of Self argument for slice operations. // If buffer is inline (Static) then we need to ensure we haven't @@ -232,8 +232,15 @@ pub fn LinearFifo( /// Same as `read` except it returns an error union /// The purpose of this function existing is to match `std.io.Reader` API. - fn readFn(self: *Self, dest: []u8) error{}!usize { - return self.read(dest); + fn readvFn(self: *Self, iov: []std.io.ReadBuffers) error{}!usize { + var n_read: usize = 0; + for (iov) |v| { + const n = self.read(v.ptr[0..v.len]); + if (n == 0) return n_read; + + n_read += n; + } + return n_read; } pub fn reader(self: *Self) Reader { @@ -321,9 +328,13 @@ pub fn LinearFifo( /// Same as `write` except it returns the number of bytes written, which is always the same /// as `bytes.len`. The purpose of this function existing is to match `std.io.Writer` API. - fn appendWrite(self: *Self, bytes: []const u8) error{OutOfMemory}!usize { - try self.write(bytes); - return bytes.len; + fn appendWritev(self: *Self, iov: []std.io.WriteBuffers) error{OutOfMemory}!usize { + var written: usize = 0; + for (iov) |v| { + try self.write(v.ptr[0..v.len]); + written += v.len; + } + return written; } pub fn writer(self: *Self) Writer { diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 0b5058c5fe3c..1b4eda69e7d0 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -1133,12 +1133,12 @@ pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize { } /// See https://github.com/ziglang/zig/issues/7699 -pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize { +pub fn readv(self: File, iovecs: []io.ReadBuffers) ReadError!usize { if (is_windows) { // TODO improve this to use ReadFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; - return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null); + return windows.ReadFile(self.handle, first.ptr[0..first.len], null); } return posix.readv(self.handle, iovecs); @@ -1153,7 +1153,7 @@ pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize { /// reads from the underlying OS layer. /// * The OS layer expects pointer addresses to be inside the application's address space /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer -/// addresses when the length is zero. So this function modifies the iov_base fields +/// addresses when the length is zero. So this function modifies the ptr fields /// when the length is zero. /// /// Related open issue: https://github.com/ziglang/zig/issues/7699 @@ -1165,7 +1165,7 @@ pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize { // addresses outside the application's address space. var garbage: [1]u8 = undefined; for (iovecs) |*v| { - if (v.iov_len == 0) v.iov_base = &garbage; + if (v.len == 0) v.ptr = &garbage; } var i: usize = 0; @@ -1174,15 +1174,15 @@ pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize { var amt = try self.readv(iovecs[i..]); var eof = amt == 0; off += amt; - while (amt >= iovecs[i].iov_len) { - amt -= iovecs[i].iov_len; + while (amt >= iovecs[i].len) { + amt -= iovecs[i].len; i += 1; if (i >= iovecs.len) return off; eof = false; } if (eof) return off; - iovecs[i].iov_base += amt; - iovecs[i].iov_len -= amt; + iovecs[i].ptr += amt; + iovecs[i].len -= amt; } } @@ -1194,7 +1194,7 @@ pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!u // TODO improve this to use ReadFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; - return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset); + return windows.ReadFile(self.handle, first.ptr[0..first.len], offset); } return posix.preadv(self.handle, iovecs, offset); @@ -1217,15 +1217,15 @@ pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usiz var amt = try self.preadv(iovecs[i..], offset + off); var eof = amt == 0; off += amt; - while (amt >= iovecs[i].iov_len) { - amt -= iovecs[i].iov_len; + while (amt >= iovecs[i].len) { + amt -= iovecs[i].len; i += 1; if (i >= iovecs.len) return off; eof = false; } if (eof) return off; - iovecs[i].iov_base += amt; - iovecs[i].iov_len -= amt; + iovecs[i].ptr += amt; + iovecs[i].len -= amt; } } @@ -1268,12 +1268,12 @@ pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void { /// See https://github.com/ziglang/zig/issues/7699 /// See equivalent function: `std.net.Stream.writev`. -pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize { +pub fn writev(self: File, iovecs: []io.WriteBuffers) WriteError!usize { if (is_windows) { // TODO improve this to use WriteFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; - return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null); + return windows.WriteFile(self.handle, first.ptr[0..first.len], null); } return posix.writev(self.handle, iovecs); @@ -1284,7 +1284,7 @@ pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize { /// writes from the underlying OS layer. /// * The OS layer expects pointer addresses to be inside the application's address space /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer -/// addresses when the length is zero. So this function modifies the iov_base fields +/// addresses when the length is zero. So this function modifies the ptr fields /// when the length is zero. /// See https://github.com/ziglang/zig/issues/7699 /// See equivalent function: `std.net.Stream.writevAll`. @@ -1296,19 +1296,19 @@ pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void { // addresses outside the application's address space. var garbage: [1]u8 = undefined; for (iovecs) |*v| { - if (v.iov_len == 0) v.iov_base = &garbage; + if (v.len == 0) v.ptr = &garbage; } var i: usize = 0; while (true) { var amt = try self.writev(iovecs[i..]); - while (amt >= iovecs[i].iov_len) { - amt -= iovecs[i].iov_len; + while (amt >= iovecs[i].len) { + amt -= iovecs[i].len; i += 1; if (i >= iovecs.len) return; } - iovecs[i].iov_base += amt; - iovecs[i].iov_len -= amt; + iovecs[i].ptr += amt; + iovecs[i].len -= amt; } } @@ -1320,7 +1320,7 @@ pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError // TODO improve this to use WriteFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; - return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset); + return windows.WriteFile(self.handle, first.ptr[0..first.len], offset); } return posix.pwritev(self.handle, iovecs, offset); @@ -1339,13 +1339,13 @@ pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteEr while (true) { var amt = try self.pwritev(iovecs[i..], offset + off); off += amt; - while (amt >= iovecs[i].iov_len) { - amt -= iovecs[i].iov_len; + while (amt >= iovecs[i].len) { + amt -= iovecs[i].len; i += 1; if (i >= iovecs.len) return; } - iovecs[i].iov_base += amt; - iovecs[i].iov_len -= amt; + iovecs[i].ptr += amt; + iovecs[i].len -= amt; } } @@ -1456,13 +1456,13 @@ fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix var i: usize = 0; while (i < headers.len) { amt = try posix.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags); - while (amt >= headers[i].iov_len) { - amt -= headers[i].iov_len; + while (amt >= headers[i].len) { + amt -= headers[i].len; i += 1; if (i >= headers.len) break :hdrs; } - headers[i].iov_base += amt; - headers[i].iov_len -= amt; + headers[i].ptr += amt; + headers[i].len -= amt; } } if (count == 0) { @@ -1482,24 +1482,24 @@ fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix } var i: usize = 0; while (i < trailers.len) { - while (amt >= trailers[i].iov_len) { - amt -= trailers[i].iov_len; + while (amt >= trailers[i].len) { + amt -= trailers[i].len; i += 1; if (i >= trailers.len) return; } - trailers[i].iov_base += amt; - trailers[i].iov_len -= amt; + trailers[i].ptr += amt; + trailers[i].len -= amt; amt = try posix.writev(self.handle, trailers[i..]); } } -pub const Reader = io.Reader(File, ReadError, read); +pub const Reader = io.Reader(File, ReadError, readv); pub fn reader(file: File) Reader { return .{ .context = file }; } -pub const Writer = io.Writer(File, WriteError, write); +pub const Writer = io.Writer(File, WriteError, writev); pub fn writer(file: File) Writer { return .{ .context = file }; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 3aa932cf0147..6add3668c509 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1276,22 +1276,22 @@ test "writev, readv" { var buf2: [line2.len]u8 = undefined; var write_vecs = [_]posix.iovec_const{ .{ - .iov_base = line1, - .iov_len = line1.len, + .ptr = line1, + .len = line1.len, }, .{ - .iov_base = line2, - .iov_len = line2.len, + .ptr = line2, + .len = line2.len, }, }; var read_vecs = [_]posix.iovec{ .{ - .iov_base = &buf2, - .iov_len = buf2.len, + .ptr = &buf2, + .len = buf2.len, }, .{ - .iov_base = &buf1, - .iov_len = buf1.len, + .ptr = &buf1, + .len = buf1.len, }, }; @@ -1318,22 +1318,22 @@ test "pwritev, preadv" { var buf2: [line2.len]u8 = undefined; var write_vecs = [_]posix.iovec_const{ .{ - .iov_base = line1, - .iov_len = line1.len, + .ptr = line1, + .len = line1.len, }, .{ - .iov_base = line2, - .iov_len = line2.len, + .ptr = line2, + .len = line2.len, }, }; var read_vecs = [_]posix.iovec{ .{ - .iov_base = &buf2, - .iov_len = buf2.len, + .ptr = &buf2, + .len = buf2.len, }, .{ - .iov_base = &buf1, - .iov_len = buf1.len, + .ptr = &buf1, + .len = buf1.len, }, }; @@ -1378,12 +1378,12 @@ test "sendfile" { const line2 = "second line\n"; var vecs = [_]posix.iovec_const{ .{ - .iov_base = line1, - .iov_len = line1.len, + .ptr = line1, + .len = line1.len, }, .{ - .iov_base = line2, - .iov_len = line2.len, + .ptr = line2, + .len = line2.len, }, }; @@ -1401,20 +1401,20 @@ test "sendfile" { const trailer2 = "second trailer\n"; var hdtr = [_]posix.iovec_const{ .{ - .iov_base = header1, - .iov_len = header1.len, + .ptr = header1, + .len = header1.len, }, .{ - .iov_base = header2, - .iov_len = header2.len, + .ptr = header2, + .len = header2.len, }, .{ - .iov_base = trailer1, - .iov_len = trailer1.len, + .ptr = trailer1, + .len = trailer1.len, }, .{ - .iov_base = trailer2, - .iov_len = trailer2.len, + .ptr = trailer2, + .len = trailer2.len, }, }; diff --git a/lib/std/http.zig b/lib/std/http.zig index af966d89e75d..975c6703e6db 100644 --- a/lib/std/http.zig +++ b/lib/std/http.zig @@ -37,7 +37,7 @@ pub const Method = enum(u64) { return x; } - pub fn write(self: Method, w: anytype) !void { + pub fn write(self: Method, w: std.io.AnyWriter) !void { const bytes = std.mem.asBytes(&@intFromEnum(self)); const str = std.mem.sliceTo(bytes, 0); try w.writeAll(str); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 0e70b839b4e0..eaab504b0ef6 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -113,7 +113,8 @@ pub const ConnectionPool = struct { pool.used.remove(node); if (node.data.closing or pool.free_size == 0) { - node.data.close(allocator); + node.data.close(); + allocator.free(node.data.host); return allocator.destroy(node); } @@ -121,7 +122,8 @@ pub const ConnectionPool = struct { const popped = pool.free.popFirst() orelse unreachable; pool.free_len -= 1; - popped.data.close(allocator); + popped.data.close(); + allocator.free(popped.data.host); allocator.destroy(popped); } @@ -173,7 +175,7 @@ pub const ConnectionPool = struct { defer allocator.destroy(node); next = node.next; - node.data.close(allocator); + node.data.deinit(allocator); } next = pool.used.first; @@ -181,7 +183,7 @@ pub const ConnectionPool = struct { defer allocator.destroy(node); next = node.next; - node.data.close(allocator); + node.data.deinit(allocator); } pool.* = undefined; @@ -190,9 +192,8 @@ pub const ConnectionPool = struct { /// An interface to either a plain or TLS connection. pub const Connection = struct { - stream: net.Stream, - /// undefined unless protocol is tls. - tls_client: if (!disable_tls) *std.crypto.tls.Client else void, + socket: net.Socket, + tls_client: (if (disable_tls) void else *std.crypto.tls.Client) = if (disable_tls) {} else undefined, /// The protocol that this connection is using. protocol: Protocol, @@ -220,8 +221,18 @@ pub const Connection = struct { pub const Protocol = enum { plain, tls }; - pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize { - return conn.tls_client.readv(conn.stream, buffers) catch |err| { + inline fn any(conn: *Connection) std.io.AnyStream { + switch (conn.protocol) { + .plain => return conn.socket.stream().any(), + .tls => { + if (disable_tls) unreachable; + return conn.tls_client.stream().any(); + }, + } + } + + pub fn readvDirect(conn: *Connection, buffers: []std.io.ReadBuffers) ReadError!usize { + return conn.any().reader().readv(buffers) catch |err| { // https://github.com/ziglang/zig/issues/2473 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert; @@ -234,26 +245,12 @@ pub const Connection = struct { }; } - pub fn readvDirect(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize { - if (conn.protocol == .tls) { - if (disable_tls) unreachable; - - return conn.readvDirectTls(buffers); - } - - return conn.stream.readv(buffers) catch |err| switch (err) { - error.ConnectionTimedOut => return error.ConnectionTimedOut, - error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer, - else => return error.UnexpectedReadFailure, - }; - } - /// Refills the read buffer with data from the connection. pub fn fill(conn: *Connection) ReadError!void { if (conn.read_end != conn.read_start) return; - var iovecs = [1]std.posix.iovec{ - .{ .iov_base = &conn.read_buf, .iov_len = conn.read_buf.len }, + var iovecs = [1]std.io.ReadBuffers{ + .{ .ptr = &conn.read_buf, .len = conn.read_buf.len }, }; const nread = try conn.readvDirect(&iovecs); if (nread == 0) return error.EndOfStream; @@ -288,9 +285,9 @@ pub const Connection = struct { return available_read; } - var iovecs = [2]std.posix.iovec{ - .{ .iov_base = buffer.ptr, .iov_len = buffer.len }, - .{ .iov_base = &conn.read_buf, .iov_len = conn.read_buf.len }, + var iovecs = [2]std.io.ReadBuffers{ + .{ .ptr = buffer.ptr, .len = buffer.len }, + .{ .ptr = &conn.read_buf, .len = conn.read_buf.len }, }; const nread = try conn.readvDirect(&iovecs); @@ -303,6 +300,13 @@ pub const Connection = struct { return nread; } + pub fn readv(conn: *Connection, iov: []std.io.ReadBuffers) ReadError!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const buffer = first.ptr[0..first.len]; + return try conn.read(buffer); + } + pub const ReadError = error{ TlsFailure, TlsAlert, @@ -312,39 +316,16 @@ pub const Connection = struct { EndOfStream, }; - pub const Reader = std.io.Reader(*Connection, ReadError, read); - - pub fn reader(conn: *Connection) Reader { - return Reader{ .context = conn }; - } - - pub fn writeAllDirectTls(conn: *Connection, buffer: []const u8) WriteError!void { - return conn.tls_client.writeAll(conn.stream, buffer) catch |err| switch (err) { - error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer, - else => return error.UnexpectedWriteFailure, - }; - } - - pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void { - if (conn.protocol == .tls) { - if (disable_tls) unreachable; - - return conn.writeAllDirectTls(buffer); - } - - return conn.stream.writeAll(buffer) catch |err| switch (err) { - error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer, - else => return error.UnexpectedWriteFailure, - }; - } - /// Writes the given buffer to the connection. pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize { if (conn.write_buf.len - conn.write_end < buffer.len) { try conn.flush(); if (buffer.len > conn.write_buf.len) { - try conn.writeAllDirect(buffer); + conn.any().writer().writeAll(buffer) catch |err| switch (err) { + error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer, + else => return error.UnexpectedWriteFailure, + }; return buffer.len; } } @@ -355,6 +336,13 @@ pub const Connection = struct { return buffer.len; } + pub fn writev(conn: *Connection, iov: []std.io.WriteBuffers) WriteError!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const buffer = first.ptr[0..first.len]; + return try conn.write(buffer); + } + /// Returns a buffer to be filled with exactly len bytes to write to the connection. pub fn allocWriteBuffer(conn: *Connection, len: BufferSize) WriteError![]u8 { if (conn.write_buf.len - conn.write_end < len) try conn.flush(); @@ -366,33 +354,31 @@ pub const Connection = struct { pub fn flush(conn: *Connection) WriteError!void { if (conn.write_end == 0) return; - try conn.writeAllDirect(conn.write_buf[0..conn.write_end]); + try conn.any().writer().writeAll(conn.write_buf[0..conn.write_end]); conn.write_end = 0; } - pub const WriteError = error{ + pub const WriteError = anyerror || error{ ConnectionResetByPeer, UnexpectedWriteFailure, }; - pub const Writer = std.io.Writer(*Connection, WriteError, write); - - pub fn writer(conn: *Connection) Writer { - return Writer{ .context = conn }; + pub fn close(conn: *Connection) void { + conn.any().close(); } - /// Closes the connection. - pub fn close(conn: *Connection, allocator: Allocator) void { - if (conn.protocol == .tls) { - if (disable_tls) unreachable; - - // try to cleanly close the TLS connection, for any server that cares. - _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {}; - allocator.destroy(conn.tls_client); + pub fn deinit(conn: *Connection, allocator: Allocator) void { + if (!disable_tls) { + if (conn.protocol == .tls) allocator.destroy(conn.tls_client); } - - conn.stream.close(); allocator.free(conn.host); + conn.* = undefined; + } + + pub const GenericStream = std.io.GenericStream(*Connection, ReadError, readv, WriteError, writev, close); + + pub fn stream(conn: *Connection) GenericStream { + return .{ .context = conn }; } }; @@ -807,9 +793,9 @@ pub const Request = struct { return error.UnsupportedTransferEncoding; const connection = req.connection.?; - const w = connection.writer(); + const w = connection.stream().writer(); - try req.method.write(w); + try req.method.write(w.any()); try w.writeByte(' '); if (req.method == .CONNECT) { @@ -919,14 +905,17 @@ pub const Request = struct { const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError; - const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead); + const TransferReader = std.io.Reader(*Request, TransferReadError, transferReadv); fn transferReader(req: *Request) TransferReader { return .{ .context = req }; } - fn transferRead(req: *Request, buf: []u8) TransferReadError!usize { + fn transferReadv(req: *Request, iov: []std.io.ReadBuffers) TransferReadError!usize { if (req.response.parser.done) return 0; + if (iov.len == 0) return 0; + const first = iov[0]; + const buf = first.ptr[0..first.len]; var index: usize = 0; while (index == 0) { @@ -1031,7 +1020,11 @@ pub const Request = struct { // skip the body of the redirect response, this will at least // leave the connection in a known good state. req.response.skip = true; - assert(try req.transferRead(&.{}) == 0); // we're skipping, no buffer is necessary + // we're skipping, no buffer is necessary + var buf: [0]u8 = undefined; + var iovecs = [_]std.io.ReadBuffers{.{ .ptr = &buf, .len = 0 }}; + const n_read = try req.transferReadv(&iovecs); + assert(n_read == 0); if (req.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects; @@ -1115,20 +1108,20 @@ pub const Request = struct { pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{ DecompressionFailure, InvalidTrailers }; - pub const Reader = std.io.Reader(*Request, ReadError, read); + pub const Reader = std.io.Reader(*Request, ReadError, readv); pub fn reader(req: *Request) Reader { return .{ .context = req }; } /// Reads data from the response body. Must be called after `wait`. - pub fn read(req: *Request, buffer: []u8) ReadError!usize { + pub fn readv(req: *Request, iov: []std.io.ReadBuffers) ReadError!usize { const out_index = switch (req.response.compression) { - .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure, - .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure, + .deflate => |*deflate| deflate.readv(iov) catch return error.DecompressionFailure, + .gzip => |*gzip| gzip.readv(iov) catch return error.DecompressionFailure, // https://github.com/ziglang/zig/issues/18937 //.zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure, - else => try req.transferRead(buffer), + else => try req.transferReadv(iov), }; if (out_index > 0) return out_index; @@ -1142,20 +1135,9 @@ pub const Request = struct { return 0; } - /// Reads data from the response body. Must be called after `wait`. - pub fn readAll(req: *Request, buffer: []u8) !usize { - var index: usize = 0; - while (index < buffer.len) { - const amt = try read(req, buffer[index..]); - if (amt == 0) break; - index += amt; - } - return index; - } - pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong }; - pub const Writer = std.io.Writer(*Request, WriteError, write); + pub const Writer = std.io.Writer(*Request, WriteError, writev); pub fn writer(req: *Request) Writer { return .{ .context = req }; @@ -1163,44 +1145,42 @@ pub const Request = struct { /// Write `bytes` to the server. The `transfer_encoding` field determines how data will be sent. /// Must be called after `send` and before `finish`. - pub fn write(req: *Request, bytes: []const u8) WriteError!usize { + pub fn writev(req: *Request, iov: []std.io.WriteBuffers) WriteError!usize { + var len: usize = 0; + for (iov) |v| len += v.len; + switch (req.transfer_encoding) { .chunked => { - if (bytes.len > 0) { - try req.connection.?.writer().print("{x}\r\n", .{bytes.len}); - try req.connection.?.writer().writeAll(bytes); - try req.connection.?.writer().writeAll("\r\n"); + var w = req.connection.?.stream().writer(); + + if (len > 0) { + try w.print("{x}\r\n", .{len}); + try w.writevAll(iov); + try w.writeAll("\r\n"); } - return bytes.len; + return len; }, - .content_length => |*len| { - if (len.* < bytes.len) return error.MessageTooLong; + .content_length => |*l| { + const cwriter = req.connection.?.stream().writer(); + + if (l.* < len) return error.MessageTooLong; - const amt = try req.connection.?.write(bytes); - len.* -= amt; + const amt = try cwriter.writev(iov); + l.* -= amt; return amt; }, .none => return error.NotWriteable, } } - /// Write `bytes` to the server. The `transfer_encoding` field determines how data will be sent. - /// Must be called after `send` and before `finish`. - pub fn writeAll(req: *Request, bytes: []const u8) WriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try write(req, bytes[index..]); - } - } - pub const FinishError = WriteError || error{MessageNotCompleted}; /// Finish the body of a request. This notifies the server that you have no more data to send. /// Must be called after `send`. pub fn finish(req: *Request) FinishError!void { switch (req.transfer_encoding) { - .chunked => try req.connection.?.writer().writeAll("0\r\n\r\n"), + .chunked => try req.connection.?.stream().writer().writeAll("0\r\n\r\n"), .content_length => |len| if (len != 0) return error.MessageNotCompleted, .none => {}, } @@ -1347,7 +1327,7 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec errdefer client.allocator.destroy(conn); conn.* = .{ .data = undefined }; - const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) { + const socket = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) { error.ConnectionRefused => return error.ConnectionRefused, error.NetworkUnreachable => return error.NetworkUnreachable, error.ConnectionTimedOut => return error.ConnectionTimedOut, @@ -1358,12 +1338,10 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses, else => return error.UnexpectedConnectFailure, }; - errdefer stream.close(); - - conn.data = .{ - .stream = stream, - .tls_client = undefined, + errdefer socket.close(); + conn.data = Connection{ + .socket = socket, .protocol = protocol, .host = try client.allocator.dupe(u8, host), .port = port, @@ -1376,7 +1354,17 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client); errdefer client.allocator.destroy(conn.data.tls_client); - conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed; + const any_stream = std.io.AnyStream{ + .context = @ptrCast(&conn.data.socket), // will outlive `tls.Client` since it's heap allocated + .readvFn = conn.data.socket.stream().any().readvFn, + .writevFn = conn.data.socket.stream().any().writevFn, + .closeFn = conn.data.socket.stream().any().closeFn, + }; + conn.data.tls_client.* = std.crypto.tls.Client.init( + any_stream, + client.ca_bundle, + host, + ) catch return error.TlsInitializationFailed; // This is appropriate for HTTPS because the HTTP headers contain // the content length which is used to detect truncation attacks. conn.data.tls_client.allow_truncation_attacks = true; @@ -1404,14 +1392,12 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti errdefer client.allocator.destroy(conn); conn.* = .{ .data = undefined }; - const stream = try std.net.connectUnixSocket(path); - errdefer stream.close(); + const socket = try std.net.connectUnixSocket(path); + errdefer socket.close(); - conn.data = .{ - .stream = stream, - .tls_client = undefined, + conn.data = Connection{ + .socket = socket, .protocol = .plain, - .host = try client.allocator.dupe(u8, path), .port = 0, }; @@ -1753,7 +1739,7 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult { try req.send(.{ .raw_uri = options.raw_uri }); - if (options.payload) |payload| try req.writeAll(payload); + if (options.payload) |payload| try req.writer().writeAll(payload); try req.finish(); try req.wait(); @@ -1763,7 +1749,7 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult { // Take advantage of request internals to discard the response body // and make the connection available for another request. req.response.skip = true; - assert(try req.transferRead(&.{}) == 0); // No buffer is necessary when skipping. + assert(try req.transferReadv(&.{}) == 0); // No buffer is necessary when skipping. }, .dynamic => |list| { const max_append_size = options.max_append_size orelse 2 * 1024 * 1024; diff --git a/lib/std/http/Server.zig b/lib/std/http/Server.zig index 5290241b6e04..e37bfa012506 100644 --- a/lib/std/http/Server.zig +++ b/lib/std/http/Server.zig @@ -89,7 +89,7 @@ pub fn receiveHead(s: *Server) ReceiveHeadError!Request { const buf = s.read_buffer[s.read_buffer_len..]; if (buf.len == 0) return error.HttpHeadersOversize; - const read_n = s.connection.stream.read(buf) catch + const read_n = s.connection.stream().reader().read(buf) catch return error.HttpHeadersUnreadable; if (read_n == 0) { if (s.read_buffer_len > 0) { @@ -391,7 +391,7 @@ pub const Request = struct { request: *Request, content: []const u8, options: RespondOptions, - ) Response.WriteError!void { + ) !void { const max_extra_headers = 25; assert(options.status != .@"continue"); assert(options.extra_headers.len <= max_extra_headers); @@ -418,7 +418,7 @@ pub const Request = struct { h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n"); if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"); h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n"); - try request.server.connection.stream.writeAll(h.items); + try request.server.connection.stream().writer().writeAll(h.items); return; } h.fixedWriter().print("{s} {d} {s}\r\n", .{ @@ -438,46 +438,46 @@ pub const Request = struct { } var chunk_header_buffer: [18]u8 = undefined; - var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined; + var iovecs: [max_extra_headers * 4 + 3]std.io.WriteBuffers = undefined; var iovecs_len: usize = 0; iovecs[iovecs_len] = .{ - .iov_base = h.items.ptr, - .iov_len = h.items.len, + .ptr = h.items.ptr, + .len = h.items.len, }; iovecs_len += 1; for (options.extra_headers) |header| { iovecs[iovecs_len] = .{ - .iov_base = header.name.ptr, - .iov_len = header.name.len, + .ptr = header.name.ptr, + .len = header.name.len, }; iovecs_len += 1; iovecs[iovecs_len] = .{ - .iov_base = ": ", - .iov_len = 2, + .ptr = ": ", + .len = 2, }; iovecs_len += 1; if (header.value.len != 0) { iovecs[iovecs_len] = .{ - .iov_base = header.value.ptr, - .iov_len = header.value.len, + .ptr = header.value.ptr, + .len = header.value.len, }; iovecs_len += 1; } iovecs[iovecs_len] = .{ - .iov_base = "\r\n", - .iov_len = 2, + .ptr = "\r\n", + .len = 2, }; iovecs_len += 1; } iovecs[iovecs_len] = .{ - .iov_base = "\r\n", - .iov_len = 2, + .ptr = "\r\n", + .len = 2, }; iovecs_len += 1; @@ -492,39 +492,39 @@ pub const Request = struct { ) catch unreachable; iovecs[iovecs_len] = .{ - .iov_base = chunk_header.ptr, - .iov_len = chunk_header.len, + .ptr = chunk_header.ptr, + .len = chunk_header.len, }; iovecs_len += 1; iovecs[iovecs_len] = .{ - .iov_base = content.ptr, - .iov_len = content.len, + .ptr = content.ptr, + .len = content.len, }; iovecs_len += 1; iovecs[iovecs_len] = .{ - .iov_base = "\r\n", - .iov_len = 2, + .ptr = "\r\n", + .len = 2, }; iovecs_len += 1; } iovecs[iovecs_len] = .{ - .iov_base = "0\r\n\r\n", - .iov_len = 5, + .ptr = "0\r\n\r\n", + .len = 5, }; iovecs_len += 1; } else if (content.len > 0) { iovecs[iovecs_len] = .{ - .iov_base = content.ptr, - .iov_len = content.len, + .ptr = content.ptr, + .len = content.len, }; iovecs_len += 1; } } - try request.server.connection.stream.writevAll(iovecs[0..iovecs_len]); + try request.server.connection.stream().writer().writevAll(iovecs[0..iovecs_len]); } pub const RespondStreamingOptions = struct { @@ -604,7 +604,7 @@ pub const Request = struct { }; return .{ - .stream = request.server.connection.stream, + .stream = request.server.connection.stream(), .send_buffer = options.send_buffer, .send_buffer_start = 0, .send_buffer_end = h.items.len, @@ -619,12 +619,15 @@ pub const Request = struct { }; } - pub const ReadError = net.Stream.ReadError || error{ + pub const ReadError = anyerror || error{ HttpChunkInvalid, HttpHeadersOversize, }; - fn read_cl(context: *const anyopaque, buffer: []u8) ReadError!usize { + fn read_cl(context: *const anyopaque, iov: []std.io.ReadBuffers) ReadError!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const buffer = first.ptr[0..first.len]; const request: *Request = @constCast(@alignCast(@ptrCast(context))); const s = request.server; @@ -648,11 +651,14 @@ pub const Request = struct { const available = s.read_buffer[s.next_request_start..s.read_buffer_len]; if (available.len > 0) return available; s.next_request_start = head_end; - s.read_buffer_len = head_end + try s.connection.stream.read(s.read_buffer[head_end..]); + s.read_buffer_len = head_end + try s.connection.stream().reader().read(s.read_buffer[head_end..]); return s.read_buffer[head_end..s.read_buffer_len]; } - fn read_chunked(context: *const anyopaque, buffer: []u8) ReadError!usize { + fn readv_chunked(context: *const anyopaque, iov: []std.io.ReadBuffers) ReadError!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const buffer = first.ptr[0..first.len]; const request: *Request = @constCast(@alignCast(@ptrCast(context))); const s = request.server; @@ -710,7 +716,7 @@ pub const Request = struct { const buf = s.read_buffer[s.read_buffer_len..]; if (buf.len == 0) return error.HttpHeadersOversize; - const read_n = try s.connection.stream.read(buf); + const read_n = try s.connection.stream().reader().read(buf); s.read_buffer_len += read_n; const bytes = buf[0..read_n]; const end = hp.feed(bytes); @@ -752,7 +758,7 @@ pub const Request = struct { /// request's expect field to `null`. /// /// Asserts that this function is only called once. - pub fn reader(request: *Request) ReaderError!std.io.AnyReader { + pub fn reader(request: *Request) !std.io.AnyReader { const s = request.server; assert(s.state == .received_head); s.state = .receiving_body; @@ -760,7 +766,7 @@ pub const Request = struct { if (request.head.expect) |expect| { if (mem.eql(u8, expect, "100-continue")) { - try request.server.connection.stream.writeAll("HTTP/1.1 100 Continue\r\n\r\n"); + try request.server.connection.stream().writer().writeAll("HTTP/1.1 100 Continue\r\n\r\n"); request.head.expect = null; } else { return error.HttpExpectationFailed; @@ -771,7 +777,7 @@ pub const Request = struct { .chunked => { request.reader_state = .{ .chunk_parser = http.ChunkParser.init }; return .{ - .readFn = read_chunked, + .readvFn = readv_chunked, .context = request, }; }, @@ -780,7 +786,7 @@ pub const Request = struct { .remaining_content_length = request.head.content_length orelse 0, }; return .{ - .readFn = read_cl, + .readvFn = read_cl, .context = request, }; }, @@ -821,7 +827,7 @@ pub const Request = struct { }; pub const Response = struct { - stream: net.Stream, + stream: net.Socket.GenericStream, send_buffer: []u8, /// Index of the first byte in `send_buffer`. /// This is 0 unless a short write happens in `write`. @@ -845,14 +851,12 @@ pub const Response = struct { chunked, }; - pub const WriteError = net.Stream.WriteError; - /// When using content-length, asserts that the amount of data sent matches /// the value sent in the header, then calls `flush`. /// Otherwise, transfer-encoding: chunked is being used, and it writes the /// end-of-stream message, then flushes the stream to the system. /// Respects the value of `elide_body` to omit all data after the headers. - pub fn end(r: *Response) WriteError!void { + pub fn end(r: *Response) !void { switch (r.transfer_encoding) { .content_length => |len| { assert(len == 0); // Trips when end() called before all bytes written. @@ -877,26 +881,19 @@ pub const Response = struct { /// flushes the stream to the system. /// Respects the value of `elide_body` to omit all data after the headers. /// Asserts there are at most 25 trailers. - pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void { + pub fn endChunked(r: *Response, options: EndChunkedOptions) !void { assert(r.transfer_encoding == .chunked); try flush_chunked(r, options.trailers); r.* = undefined; } - /// If using content-length, asserts that writing these bytes to the client - /// would not exceed the content-length value sent in the HTTP header. - /// May return 0, which does not indicate end of stream. The caller decides - /// when the end of stream occurs by calling `end`. - pub fn write(r: *Response, bytes: []const u8) WriteError!usize { - switch (r.transfer_encoding) { - .content_length, .none => return write_cl(r, bytes), - .chunked => return write_chunked(r, bytes), - } - } - - fn write_cl(context: *const anyopaque, bytes: []const u8) WriteError!usize { + fn write_cl(context: *const anyopaque, iov: []std.io.WriteBuffers) !usize { const r: *Response = @constCast(@alignCast(@ptrCast(context))); + if (iov.len == 0) return 0; + const first = iov[0]; + const bytes = first.ptr[0..first.len]; + var trash: u64 = std.math.maxInt(u64); const len = switch (r.transfer_encoding) { .content_length => |*len| len, @@ -910,17 +907,17 @@ pub const Response = struct { if (bytes.len + r.send_buffer_end > r.send_buffer.len) { const send_buffer_len = r.send_buffer_end - r.send_buffer_start; - var iovecs: [2]std.posix.iovec_const = .{ + var iovecs: [2]std.io.WriteBuffers = .{ .{ - .iov_base = r.send_buffer.ptr + r.send_buffer_start, - .iov_len = send_buffer_len, + .ptr = r.send_buffer.ptr + r.send_buffer_start, + .len = send_buffer_len, }, .{ - .iov_base = bytes.ptr, - .iov_len = bytes.len, + .ptr = bytes.ptr, + .len = bytes.len, }, }; - const n = try r.stream.writev(&iovecs); + const n = try r.stream.writer().writev(&iovecs); if (n >= send_buffer_len) { // It was enough to reset the buffer. @@ -944,10 +941,14 @@ pub const Response = struct { return bytes.len; } - fn write_chunked(context: *const anyopaque, bytes: []const u8) WriteError!usize { + fn write_chunked(context: *const anyopaque, iov: []std.io.WriteBuffers) !usize { const r: *Response = @constCast(@alignCast(@ptrCast(context))); assert(r.transfer_encoding == .chunked); + if (iov.len == 0) return 0; + const first = iov[0]; + const bytes = first.ptr[0..first.len]; + if (r.elide_body) return bytes.len; @@ -957,31 +958,31 @@ pub const Response = struct { var header_buf: [18]u8 = undefined; const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{chunk_len}) catch unreachable; - var iovecs: [5]std.posix.iovec_const = .{ + var iovecs: [5]std.io.WriteBuffers = .{ .{ - .iov_base = r.send_buffer.ptr + r.send_buffer_start, - .iov_len = send_buffer_len - r.chunk_len, + .ptr = r.send_buffer.ptr + r.send_buffer_start, + .len = send_buffer_len - r.chunk_len, }, .{ - .iov_base = chunk_header.ptr, - .iov_len = chunk_header.len, + .ptr = chunk_header.ptr, + .len = chunk_header.len, }, .{ - .iov_base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len, - .iov_len = r.chunk_len, + .ptr = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len, + .len = r.chunk_len, }, .{ - .iov_base = bytes.ptr, - .iov_len = bytes.len, + .ptr = bytes.ptr, + .len = bytes.len, }, .{ - .iov_base = "\r\n", - .iov_len = 2, + .ptr = "\r\n", + .len = 2, }, }; // TODO make this writev instead of writevAll, which involves // complicating the logic of this function. - try r.stream.writevAll(&iovecs); + try r.stream.writer().writevAll(&iovecs); r.send_buffer_start = 0; r.send_buffer_end = 0; r.chunk_len = 0; @@ -995,32 +996,23 @@ pub const Response = struct { return bytes.len; } - /// If using content-length, asserts that writing these bytes to the client - /// would not exceed the content-length value sent in the HTTP header. - pub fn writeAll(r: *Response, bytes: []const u8) WriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try write(r, bytes[index..]); - } - } - /// Sends all buffered data to the client. /// This is redundant after calling `end`. /// Respects the value of `elide_body` to omit all data after the headers. - pub fn flush(r: *Response) WriteError!void { + pub fn flush(r: *Response) !void { switch (r.transfer_encoding) { .none, .content_length => return flush_cl(r), .chunked => return flush_chunked(r, null), } } - fn flush_cl(r: *Response) WriteError!void { - try r.stream.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]); + fn flush_cl(r: *Response) !void { + try r.stream.writer().writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]); r.send_buffer_start = 0; r.send_buffer_end = 0; } - fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) WriteError!void { + fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) !void { const max_trailers = 25; if (end_trailers) |trailers| assert(trailers.len <= max_trailers); assert(r.transfer_encoding == .chunked); @@ -1028,7 +1020,7 @@ pub const Response = struct { const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len]; if (r.elide_body) { - try r.stream.writeAll(http_headers); + try r.stream.writer().writeAll(http_headers); r.send_buffer_start = 0; r.send_buffer_end = 0; r.chunk_len = 0; @@ -1038,78 +1030,78 @@ pub const Response = struct { var header_buf: [18]u8 = undefined; const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable; - var iovecs: [max_trailers * 4 + 5]std.posix.iovec_const = undefined; + var iovecs: [max_trailers * 4 + 5]std.io.WriteBuffers = undefined; var iovecs_len: usize = 0; iovecs[iovecs_len] = .{ - .iov_base = http_headers.ptr, - .iov_len = http_headers.len, + .ptr = http_headers.ptr, + .len = http_headers.len, }; iovecs_len += 1; if (r.chunk_len > 0) { iovecs[iovecs_len] = .{ - .iov_base = chunk_header.ptr, - .iov_len = chunk_header.len, + .ptr = chunk_header.ptr, + .len = chunk_header.len, }; iovecs_len += 1; iovecs[iovecs_len] = .{ - .iov_base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len, - .iov_len = r.chunk_len, + .ptr = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len, + .len = r.chunk_len, }; iovecs_len += 1; iovecs[iovecs_len] = .{ - .iov_base = "\r\n", - .iov_len = 2, + .ptr = "\r\n", + .len = 2, }; iovecs_len += 1; } if (end_trailers) |trailers| { iovecs[iovecs_len] = .{ - .iov_base = "0\r\n", - .iov_len = 3, + .ptr = "0\r\n", + .len = 3, }; iovecs_len += 1; for (trailers) |trailer| { iovecs[iovecs_len] = .{ - .iov_base = trailer.name.ptr, - .iov_len = trailer.name.len, + .ptr = trailer.name.ptr, + .len = trailer.name.len, }; iovecs_len += 1; iovecs[iovecs_len] = .{ - .iov_base = ": ", - .iov_len = 2, + .ptr = ": ", + .len = 2, }; iovecs_len += 1; if (trailer.value.len != 0) { iovecs[iovecs_len] = .{ - .iov_base = trailer.value.ptr, - .iov_len = trailer.value.len, + .ptr = trailer.value.ptr, + .len = trailer.value.len, }; iovecs_len += 1; } iovecs[iovecs_len] = .{ - .iov_base = "\r\n", - .iov_len = 2, + .ptr = "\r\n", + .len = 2, }; iovecs_len += 1; } iovecs[iovecs_len] = .{ - .iov_base = "\r\n", - .iov_len = 2, + .ptr = "\r\n", + .len = 2, }; iovecs_len += 1; } - try r.stream.writevAll(iovecs[0..iovecs_len]); + try r.stream.writer().writevAll(iovecs[0..iovecs_len]); r.send_buffer_start = 0; r.send_buffer_end = 0; r.chunk_len = 0; @@ -1117,7 +1109,7 @@ pub const Response = struct { pub fn writer(r: *Response) std.io.AnyWriter { return .{ - .writeFn = switch (r.transfer_encoding) { + .writevFn = switch (r.transfer_encoding) { .none, .content_length => write_cl, .chunked => write_chunked, }, diff --git a/lib/std/http/protocol.zig b/lib/std/http/protocol.zig index 78511f435d67..e1f70b82ab93 100644 --- a/lib/std/http/protocol.zig +++ b/lib/std/http/protocol.zig @@ -281,7 +281,7 @@ const MockBufferedConnection = struct { pub fn fill(conn: *MockBufferedConnection) ReadError!void { if (conn.end != conn.start) return; - const nread = try conn.conn.read(conn.buf[0..]); + const nread = try conn.conn.reader().read(conn.buf[0..]); if (nread == 0) return error.EndOfStream; conn.start = 0; conn.end = @as(u16, @truncate(nread)); @@ -313,7 +313,7 @@ const MockBufferedConnection = struct { if (left > conn.buf.len) { // skip the buffer if the output is large enough - return conn.conn.read(buffer[out_index..]); + return conn.conn.reader().read(buffer[out_index..]); } try conn.fill(); diff --git a/lib/std/http/test.zig b/lib/std/http/test.zig index e2aa810d580d..57af1490047f 100644 --- a/lib/std/http/test.zig +++ b/lib/std/http/test.zig @@ -14,8 +14,8 @@ test "trailers" { var header_buffer: [1024]u8 = undefined; var remaining: usize = 1; while (remaining != 0) : (remaining -= 1) { - const conn = try net_server.accept(); - defer conn.stream.close(); + var conn = try net_server.accept(); + defer conn.stream().close(); var server = http.Server.init(conn, &header_buffer); @@ -33,9 +33,9 @@ test "trailers" { var response = request.respondStreaming(.{ .send_buffer = &send_buffer, }); - try response.writeAll("Hello, "); + try response.writer().writeAll("Hello, "); try response.flush(); - try response.writeAll("World!\n"); + try response.writer().writeAll("World!\n"); try response.flush(); try response.endChunked(.{ .trailers = &.{ @@ -96,8 +96,8 @@ test "HTTP server handles a chunked transfer coding request" { const test_server = try createTestServer(struct { fn run(net_server: *std.net.Server) !void { var header_buffer: [8192]u8 = undefined; - const conn = try net_server.accept(); - defer conn.stream.close(); + var conn = try net_server.accept(); + defer conn.stream().close(); var server = http.Server.init(conn, &header_buffer); var request = try server.receiveHead(); @@ -133,9 +133,10 @@ test "HTTP server handles a chunked transfer coding request" { "\r\n"; const gpa = std.testing.allocator; - const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); + const socket = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); + const stream = socket.stream(); defer stream.close(); - try stream.writeAll(request_bytes); + try stream.writer().writeAll(request_bytes); const expected_response = "HTTP/1.1 200 OK\r\n" ++ @@ -155,8 +156,8 @@ test "echo content server" { var read_buffer: [1024]u8 = undefined; accept: while (true) { - const conn = try net_server.accept(); - defer conn.stream.close(); + var conn = try net_server.accept(); + defer conn.stream().close(); var http_server = http.Server.init(conn, &read_buffer); @@ -237,8 +238,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { var header_buffer: [1000]u8 = undefined; var remaining: usize = 1; while (remaining != 0) : (remaining -= 1) { - const conn = try net_server.accept(); - defer conn.stream.close(); + var conn = try net_server.accept(); + defer conn.stream().close(); var server = http.Server.init(conn, &header_buffer); @@ -256,7 +257,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { for (0..500) |i| { var buf: [30]u8 = undefined; const line = try std.fmt.bufPrint(&buf, "{d}, ah ha ha!\n", .{i}); - try response.writeAll(line); + try response.writer().writeAll(line); total += line.len; } try expectEqual(7390, total); @@ -269,9 +270,10 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { const request_bytes = "GET /foo HTTP/1.1\r\n\r\n"; const gpa = std.testing.allocator; - const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); + const socket = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); + const stream = socket.stream(); defer stream.close(); - try stream.writeAll(request_bytes); + try stream.writer().writeAll(request_bytes); const response = try stream.reader().readAllAlloc(gpa, 8192); defer gpa.free(response); @@ -301,8 +303,8 @@ test "receiving arbitrary http headers from the client" { var read_buffer: [666]u8 = undefined; var remaining: usize = 1; while (remaining != 0) : (remaining -= 1) { - const conn = try net_server.accept(); - defer conn.stream.close(); + var conn = try net_server.accept(); + defer conn.stream().close(); var server = http.Server.init(conn, &read_buffer); try expectEqual(.ready, server.state); @@ -332,9 +334,10 @@ test "receiving arbitrary http headers from the client" { "aoeu: asdf \r\n" ++ "\r\n"; const gpa = std.testing.allocator; - const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); + const socket = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); + const stream = socket.stream(); defer stream.close(); - try stream.writeAll(request_bytes); + try stream.writer().writeAll(request_bytes); const response = try stream.reader().readAllAlloc(gpa, 8192); defer gpa.free(response); @@ -362,7 +365,7 @@ test "general client/server API coverage" { var client_header_buffer: [1024]u8 = undefined; outer: while (global.handle_new_requests) { var connection = try net_server.accept(); - defer connection.stream.close(); + defer connection.stream().close(); var http_server = http.Server.init(connection, &client_header_buffer); @@ -853,6 +856,8 @@ test "general client/server API coverage" { req.response.parser.done = true; req.connection.?.closing = false; requests[i] = req; + + if (i == total_connections - 1) global.handle_new_requests = false; } for (0..total_connections) |i| { @@ -864,21 +869,14 @@ test "general client/server API coverage" { } client.deinit(); - - { - global.handle_new_requests = false; - - const conn = try std.net.tcpConnectToAddress(test_server.net_server.listen_address); - conn.close(); - } } test "Server streams both reading and writing" { const test_server = try createTestServer(struct { fn run(net_server: *std.net.Server) anyerror!void { var header_buffer: [1024]u8 = undefined; - const conn = try net_server.accept(); - defer conn.stream.close(); + var conn = try net_server.accept(); + defer conn.stream().close(); var server = http.Server.init(conn, &header_buffer); var request = try server.receiveHead(); @@ -925,8 +923,8 @@ test "Server streams both reading and writing" { try req.send(.{}); try req.wait(); - try req.writeAll("one "); - try req.writeAll("fish"); + try req.writer().writeAll("one "); + try req.writer().writeAll("fish"); try req.finish(); @@ -957,8 +955,8 @@ fn echoTests(client: *http.Client, port: u16) !void { req.transfer_encoding = .{ .content_length = 14 }; try req.send(.{}); - try req.writeAll("Hello, "); - try req.writeAll("World!\n"); + try req.writer().writeAll("Hello, "); + try req.writer().writeAll("World!\n"); try req.finish(); try req.wait(); @@ -991,8 +989,8 @@ fn echoTests(client: *http.Client, port: u16) !void { req.transfer_encoding = .chunked; try req.send(.{}); - try req.writeAll("Hello, "); - try req.writeAll("World!\n"); + try req.writer().writeAll("Hello, "); + try req.writer().writeAll("World!\n"); try req.finish(); try req.wait(); @@ -1045,8 +1043,8 @@ fn echoTests(client: *http.Client, port: u16) !void { req.transfer_encoding = .chunked; try req.send(.{}); - try req.writeAll("Hello, "); - try req.writeAll("World!\n"); + try req.writer().writeAll("Hello, "); + try req.writer().writeAll("World!\n"); try req.finish(); try req.wait(); @@ -1104,10 +1102,6 @@ const TestServer = struct { fn createTestServer(S: type) !*TestServer { if (builtin.single_threaded) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_llvm and native_endian == .big) { - // https://github.com/ziglang/zig/issues/13782 - return error.SkipZigTest; - } const address = try std.net.Address.parseIp("127.0.0.1", 0); const test_server = try std.testing.allocator.create(TestServer); @@ -1121,8 +1115,8 @@ test "redirect to different connection" { fn run(net_server: *std.net.Server) anyerror!void { var header_buffer: [888]u8 = undefined; - const conn = try net_server.accept(); - defer conn.stream.close(); + var conn = try net_server.accept(); + defer conn.stream().close(); var server = http.Server.init(conn, &header_buffer); var request = try server.receiveHead(); @@ -1142,8 +1136,8 @@ test "redirect to different connection" { var header_buffer: [999]u8 = undefined; var send_buffer: [100]u8 = undefined; - const conn = try net_server.accept(); - defer conn.stream.close(); + var conn = try net_server.accept(); + defer conn.stream().close(); const new_loc = try std.fmt.bufPrint(&send_buffer, "http://127.0.0.1:{d}/ok", .{ global.other_port.?, diff --git a/lib/std/io.zig b/lib/std/io.zig index 58724c582ea6..aeebc52a37de 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -14,6 +14,11 @@ const meta = std.meta; const File = std.fs.File; const Allocator = std.mem.Allocator; +// Abstract these away from the posix layer. +// They may change in solving https://github.com/ziglang/zig/issues/7699 +pub const ReadBuffers = std.posix.iovec; +pub const WriteBuffers = std.posix.iovec_const; + fn getStdOutHandle() posix.fd_t { if (is_windows) { if (builtin.zig_backend == .stage2_aarch64) { @@ -80,18 +85,20 @@ pub fn GenericReader( /// Returns the number of bytes read. It may be less than buffer.len. /// If the number of bytes read is 0, it means end of stream. /// End of stream is not an error condition. - comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize, + comptime readvFn: fn (context: Context, iov: []ReadBuffers) ReadError!usize, ) type { return struct { context: Context, pub const Error = ReadError; - pub const NoEofError = ReadError || error{ - EndOfStream, - }; + pub const NoEofError = ReadError || error{EndOfStream}; + + pub inline fn readv(self: Self, iov: []ReadBuffers) Error!usize { + return readvFn(self.context, iov); + } pub inline fn read(self: Self, buffer: []u8) Error!usize { - return readFn(self.context, buffer); + return @errorCast(self.any().read(buffer)); } pub inline fn readAll(self: Self, buffer: []u8) Error!usize { @@ -285,18 +292,24 @@ pub fn GenericReader( return @errorCast(self.any().readEnum(Enum, endian)); } + /// Reads the stream until the end, ignoring all the data. + /// Returns the number of bytes discarded. + pub inline fn discard(self: Self) anyerror!u64 { + return @errorCast(self.any().discard()); + } + pub inline fn any(self: *const Self) AnyReader { return .{ .context = @ptrCast(&self.context), - .readFn = typeErasedReadFn, + .readvFn = typeErasedReadvFn, }; } const Self = @This(); - fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize { + fn typeErasedReadvFn(context: *const anyopaque, iov: []ReadBuffers) anyerror!usize { const ptr: *const Context = @alignCast(@ptrCast(context)); - return readFn(ptr.*, buffer); + return readvFn(ptr.*, iov); } }; } @@ -304,7 +317,7 @@ pub fn GenericReader( pub fn GenericWriter( comptime Context: type, comptime WriteError: type, - comptime writeFn: fn (context: Context, bytes: []const u8) WriteError!usize, + comptime writevFn: fn (context: Context, iov: []WriteBuffers) WriteError!usize, ) type { return struct { context: Context, @@ -312,8 +325,16 @@ pub fn GenericWriter( const Self = @This(); pub const Error = WriteError; + pub inline fn writev(self: Self, iov: []WriteBuffers) Error!usize { + return writevFn(self.context, iov); + } + + pub inline fn writevAll(self: Self, iov: []WriteBuffers) Error!void { + return @errorCast(self.any().writevAll(iov)); + } + pub inline fn write(self: Self, bytes: []const u8) Error!usize { - return writeFn(self.context, bytes); + return @errorCast(self.any().write(bytes)); } pub inline fn writeAll(self: Self, bytes: []const u8) Error!void { @@ -347,13 +368,60 @@ pub fn GenericWriter( pub inline fn any(self: *const Self) AnyWriter { return .{ .context = @ptrCast(&self.context), - .writeFn = typeErasedWriteFn, + .writevFn = typeErasedWritevFn, + }; + } + + fn typeErasedWritevFn(context: *const anyopaque, iov: []WriteBuffers) anyerror!usize { + const ptr: *const Context = @alignCast(@ptrCast(context)); + return writevFn(ptr.*, iov); + } + }; +} + +pub fn GenericStream( + comptime Context: type, + comptime ReadError: type, + /// Returns the number of bytes read. It may be less than buffer.len. + /// If the number of bytes read is 0, it means end of stream. + /// End of stream is not an error condition. + comptime readvFn: fn (context: Context, iov: []ReadBuffers) ReadError!usize, + comptime WriteError: type, + comptime writevFn: fn (context: Context, iov: []WriteBuffers) WriteError!usize, + comptime closeFn: fn (context: Context) void, +) type { + return struct { + context: Context, + + const ReaderType = GenericReader(Context, ReadError, readvFn); + const WriterType = GenericWriter(Context, WriteError, writevFn); + + const Self = @This(); + + pub inline fn reader(self: *const Self) ReaderType { + return .{ .context = self.context }; + } + + pub inline fn writer(self: *const Self) WriterType { + return .{ .context = self.context }; + } + + pub inline fn close(self: *const Self) void { + closeFn(self.context); + } + + pub inline fn any(self: *const Self) AnyStream { + return .{ + .context = @ptrCast(&self.context), + .readvFn = self.reader().any().readvFn, + .writevFn = self.writer().any().writevFn, + .closeFn = typeErasedCloseFn, }; } - fn typeErasedWriteFn(context: *const anyopaque, bytes: []const u8) anyerror!usize { + fn typeErasedCloseFn(context: *const anyopaque) void { const ptr: *const Context = @alignCast(@ptrCast(context)); - return writeFn(ptr.*, bytes); + return closeFn(ptr.*); } }; } @@ -367,6 +435,7 @@ pub const Writer = GenericWriter; pub const AnyReader = @import("io/Reader.zig"); pub const AnyWriter = @import("io/Writer.zig"); +pub const AnyStream = @import("io/Stream.zig"); pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; @@ -415,10 +484,12 @@ pub const tty = @import("io/tty.zig"); /// A Writer that doesn't write to anything. pub const null_writer = @as(NullWriter, .{ .context = {} }); -const NullWriter = Writer(void, error{}, dummyWrite); -fn dummyWrite(context: void, data: []const u8) error{}!usize { +const NullWriter = Writer(void, error{}, dummyWritev); +fn dummyWritev(context: void, iov: []std.io.WriteBuffers) error{}!usize { _ = context; - return data.len; + var written: usize = 0; + for (iov) |v| written += v.len; + return written; } test "null_writer" { @@ -433,7 +504,7 @@ pub fn poll( const enum_fields = @typeInfo(StreamEnum).Enum.fields; var result: Poller(StreamEnum) = undefined; - if (is_windows) result.windows = .{ + if (builtin.os.tag == .windows) result.windows = .{ .first_read_done = false, .overlapped = [1]windows.OVERLAPPED{ mem.zeroes(windows.OVERLAPPED), @@ -452,7 +523,7 @@ pub fn poll( .head = 0, .count = 0, }; - if (is_windows) { + if (builtin.os.tag == .windows) { result.windows.active.handles_buf[i] = @field(files, enum_fields[i].name).handle; } else { result.poll_fds[i] = .{ @@ -496,7 +567,7 @@ pub fn Poller(comptime StreamEnum: type) type { const Self = @This(); pub fn deinit(self: *Self) void { - if (is_windows) { + if (builtin.os.tag == .windows) { // cancel any pending IO to prevent clobbering OVERLAPPED value for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| { _ = windows.kernel32.CancelIo(h); @@ -507,7 +578,7 @@ pub fn Poller(comptime StreamEnum: type) type { } pub fn poll(self: *Self) !bool { - if (is_windows) { + if (builtin.os.tag == .windows) { return pollWindows(self, null); } else { return pollPosix(self, null); @@ -515,7 +586,7 @@ pub fn Poller(comptime StreamEnum: type) type { } pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool { - if (is_windows) { + if (builtin.os.tag == .windows) { return pollWindows(self, nanoseconds); } else { return pollPosix(self, nanoseconds); @@ -694,6 +765,7 @@ pub fn PollFiles(comptime StreamEnum: type) type { test { _ = AnyReader; _ = AnyWriter; + _ = AnyStream; _ = @import("io/bit_reader.zig"); _ = @import("io/bit_writer.zig"); _ = @import("io/buffered_atomic_file.zig"); diff --git a/lib/std/io/Reader.zig b/lib/std/io/Reader.zig index a769fe4c0421..7b2e57f8ecd4 100644 --- a/lib/std/io/Reader.zig +++ b/lib/std/io/Reader.zig @@ -1,20 +1,37 @@ +const std = @import("../std.zig"); +const Self = @This(); +const math = std.math; +const assert = std.debug.assert; +const mem = std.mem; +const testing = std.testing; +const native_endian = @import("builtin").target.cpu.arch.endian(); +const ReadBuffers = std.io.ReadBuffers; + context: *const anyopaque, -readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize, +readvFn: *const fn (context: *const anyopaque, iov: []ReadBuffers) anyerror!usize, pub const Error = anyerror; +/// Returns the number of bytes read. It may be less than buffer.len. +/// If the number of bytes read is 0, it means end of stream. +/// End of stream is not an error condition. +pub fn readv(self: Self, iov: []ReadBuffers) anyerror!usize { + return self.readvFn(self.context, iov); +} + /// Returns the number of bytes read. It may be less than buffer.len. /// If the number of bytes read is 0, it means end of stream. /// End of stream is not an error condition. pub fn read(self: Self, buffer: []u8) anyerror!usize { - return self.readFn(self.context, buffer); + var iov = [_]ReadBuffers{.{ .ptr = buffer.ptr, .len = buffer.len }}; + return self.readv(&iov); } /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it /// means the stream reached the end. Reaching the end of a stream is not an error /// condition. pub fn readAll(self: Self, buffer: []u8) anyerror!usize { - return readAtLeast(self, buffer, buffer.len); + return self.readAtLeast(buffer, buffer.len); } /// Returns the number of bytes read, calling the underlying read @@ -372,14 +389,6 @@ pub fn discard(self: Self) anyerror!u64 { } } -const std = @import("../std.zig"); -const Self = @This(); -const math = std.math; -const assert = std.debug.assert; -const mem = std.mem; -const testing = std.testing; -const native_endian = @import("builtin").target.cpu.arch.endian(); - test { _ = @import("Reader/test.zig"); } diff --git a/lib/std/io/Stream.zig b/lib/std/io/Stream.zig new file mode 100644 index 000000000000..65255fc3cbdd --- /dev/null +++ b/lib/std/io/Stream.zig @@ -0,0 +1,36 @@ +const std = @import("../std.zig"); +const assert = std.debug.assert; +const mem = std.mem; +const iovec = std.io.ReadBuffers; +const iovec_const = std.io.WriteBuffers; + +context: *const anyopaque, +readvFn: *const fn (context: *const anyopaque, iov: []iovec) anyerror!usize, +writevFn: *const fn (context: *const anyopaque, iov: []iovec_const) anyerror!usize, +closeFn: *const fn (context: *const anyopaque) void, + +const Self = @This(); +pub const Error = anyerror; + +pub fn writev(self: Self, iov: []iovec_const) anyerror!usize { + return self.writevFn(self.context, iov); +} + +/// Returns the number of bytes read. It may be less than buffer.len. +/// If the number of bytes read is 0, it means end of stream. +/// End of stream is not an error condition. +pub fn readv(self: Self, iov: []iovec) anyerror!usize { + return self.readvFn(self.context, iov); +} + +pub fn reader(self: Self) std.io.AnyReader { + return .{ .context = self.context, .readvFn = self.readvFn }; +} + +pub fn writer(self: Self) std.io.AnyWriter { + return .{ .context = self.context, .writevFn = self.writevFn }; +} + +pub fn close(self: Self) void { + return self.closeFn(self.context); +} diff --git a/lib/std/io/Writer.zig b/lib/std/io/Writer.zig index dfcae48b1eb8..195bfa6d3d44 100644 --- a/lib/std/io/Writer.zig +++ b/lib/std/io/Writer.zig @@ -1,22 +1,42 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const mem = std.mem; +const WriteBuffers = std.io.WriteBuffers; context: *const anyopaque, -writeFn: *const fn (context: *const anyopaque, bytes: []const u8) anyerror!usize, +writevFn: *const fn (context: *const anyopaque, iov: []WriteBuffers) anyerror!usize, const Self = @This(); pub const Error = anyerror; +pub fn writev(self: Self, iov: []WriteBuffers) anyerror!usize { + return self.writevFn(self.context, iov); +} + +pub fn writevAll(self: Self, iovecs: []WriteBuffers) anyerror!void { + if (iovecs.len == 0) return; + + var i: usize = 0; + while (true) { + var amt = try self.writev(iovecs[i..]); + while (amt >= iovecs[i].len) { + amt -= iovecs[i].len; + i += 1; + if (i >= iovecs.len) return; + } + iovecs[i].ptr += amt; + iovecs[i].len -= amt; + } +} + pub fn write(self: Self, bytes: []const u8) anyerror!usize { - return self.writeFn(self.context, bytes); + var iov = [_]WriteBuffers{.{ .ptr = bytes.ptr, .len = bytes.len }}; + return self.writev(&iov); } pub fn writeAll(self: Self, bytes: []const u8) anyerror!void { - var index: usize = 0; - while (index != bytes.len) { - index += try self.write(bytes[index..]); - } + var iov = [_]WriteBuffers{.{ .ptr = bytes.ptr, .len = bytes.len }}; + return self.writevAll(&iov); } pub fn print(self: Self, comptime format: []const u8, args: anytype) anyerror!void { diff --git a/lib/std/io/buffered_reader.zig b/lib/std/io/buffered_reader.zig index ca132202a7df..c268562cf63f 100644 --- a/lib/std/io/buffered_reader.zig +++ b/lib/std/io/buffered_reader.zig @@ -12,11 +12,14 @@ pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) ty end: usize = 0, pub const Error = ReaderType.Error; - pub const Reader = io.Reader(*Self, Error, read); + pub const Reader = io.Reader(*Self, Error, readv); const Self = @This(); - pub fn read(self: *Self, dest: []u8) Error!usize { + pub fn readv(self: *Self, iov: []std.io.ReadBuffers) Error!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const dest = first.ptr[0..first.len]; var dest_index: usize = 0; while (dest_index < dest.len) { @@ -60,7 +63,7 @@ test "OneByte" { const Error = error{NoError}; const Self = @This(); - const Reader = io.Reader(*Self, Error, read); + const Reader = io.Reader(*Self, Error, readv); fn init(str: []const u8) Self { return Self{ @@ -69,7 +72,10 @@ test "OneByte" { }; } - fn read(self: *Self, dest: []u8) Error!usize { + fn readv(self: *Self, iov: []std.io.ReadBuffers) Error!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const dest = first.ptr[0..first.len]; if (self.str.len <= self.curr or dest.len == 0) return 0; @@ -135,11 +141,11 @@ test "Block" { .unbuffered_reader = BlockReader.init(block, 2), }; var out_buf: [4]u8 = undefined; - _ = try test_buf_reader.read(&out_buf); + _ = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, &out_buf, block); - _ = try test_buf_reader.read(&out_buf); + _ = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, &out_buf, block); - try testing.expectEqual(try test_buf_reader.read(&out_buf), 0); + try testing.expectEqual(try test_buf_reader.reader().read(&out_buf), 0); } // len out < block @@ -148,13 +154,13 @@ test "Block" { .unbuffered_reader = BlockReader.init(block, 2), }; var out_buf: [3]u8 = undefined; - _ = try test_buf_reader.read(&out_buf); + _ = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, &out_buf, "012"); - _ = try test_buf_reader.read(&out_buf); + _ = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, &out_buf, "301"); - const n = try test_buf_reader.read(&out_buf); + const n = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, out_buf[0..n], "23"); - try testing.expectEqual(try test_buf_reader.read(&out_buf), 0); + try testing.expectEqual(try test_buf_reader.reader().read(&out_buf), 0); } // len out > block @@ -163,11 +169,11 @@ test "Block" { .unbuffered_reader = BlockReader.init(block, 2), }; var out_buf: [5]u8 = undefined; - _ = try test_buf_reader.read(&out_buf); + _ = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, &out_buf, "01230"); - const n = try test_buf_reader.read(&out_buf); + const n = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, out_buf[0..n], "123"); - try testing.expectEqual(try test_buf_reader.read(&out_buf), 0); + try testing.expectEqual(try test_buf_reader.reader().read(&out_buf), 0); } // len out == 0 @@ -176,7 +182,7 @@ test "Block" { .unbuffered_reader = BlockReader.init(block, 2), }; var out_buf: [0]u8 = undefined; - _ = try test_buf_reader.read(&out_buf); + _ = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, &out_buf, ""); } @@ -186,10 +192,10 @@ test "Block" { .unbuffered_reader = BlockReader.init(block, 2), }; var out_buf: [4]u8 = undefined; - _ = try test_buf_reader.read(&out_buf); + _ = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, &out_buf, block); - _ = try test_buf_reader.read(&out_buf); + _ = try test_buf_reader.reader().read(&out_buf); try testing.expectEqualSlices(u8, &out_buf, block); - try testing.expectEqual(try test_buf_reader.read(&out_buf), 0); + try testing.expectEqual(try test_buf_reader.reader().read(&out_buf), 0); } } diff --git a/lib/std/io/buffered_writer.zig b/lib/std/io/buffered_writer.zig index 906d6cce4926..6e46b0717675 100644 --- a/lib/std/io/buffered_writer.zig +++ b/lib/std/io/buffered_writer.zig @@ -10,7 +10,7 @@ pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) ty end: usize = 0, pub const Error = WriterType.Error; - pub const Writer = io.Writer(*Self, Error, write); + pub const Writer = io.Writer(*Self, Error, writev); const Self = @This(); @@ -23,17 +23,22 @@ pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) ty return .{ .context = self }; } - pub fn write(self: *Self, bytes: []const u8) Error!usize { - if (self.end + bytes.len > self.buf.len) { - try self.flush(); - if (bytes.len > self.buf.len) - return self.unbuffered_writer.write(bytes); + pub fn writev(self: *Self, iov: []std.io.WriteBuffers) Error!usize { + var written: usize = 0; + for (iov) |v| { + const bytes = v.ptr[0..v.len]; + if (self.end + bytes.len > self.buf.len) { + try self.flush(); + if (bytes.len > self.buf.len) + return self.unbuffered_writer.write(bytes); + } + + const new_end = self.end + bytes.len; + @memcpy(self.buf[self.end..new_end], bytes); + self.end = new_end; + written += bytes.len; } - - const new_end = self.end + bytes.len; - @memcpy(self.buf[self.end..new_end], bytes); - self.end = new_end; - return bytes.len; + return written; } }; } diff --git a/lib/std/io/counting_reader.zig b/lib/std/io/counting_reader.zig index 2ff9b8a08fe3..f5e7cddbfd57 100644 --- a/lib/std/io/counting_reader.zig +++ b/lib/std/io/counting_reader.zig @@ -9,15 +9,17 @@ pub fn CountingReader(comptime ReaderType: anytype) type { bytes_read: u64 = 0, pub const Error = ReaderType.Error; - pub const Reader = io.Reader(*@This(), Error, read); + pub const Reader = io.Reader(*@This(), Error, readv); - pub fn read(self: *@This(), buf: []u8) Error!usize { - const amt = try self.child_reader.read(buf); + const Self = @This(); + + pub fn readv(self: *Self, iov: []std.io.ReadBuffers) Error!usize { + const amt = try self.child_reader.readv(iov); self.bytes_read += amt; return amt; } - pub fn reader(self: *@This()) Reader { + pub fn reader(self: *Self) Reader { return .{ .context = self }; } }; diff --git a/lib/std/io/counting_writer.zig b/lib/std/io/counting_writer.zig index 9043e1a47c17..b95822f84829 100644 --- a/lib/std/io/counting_writer.zig +++ b/lib/std/io/counting_writer.zig @@ -9,12 +9,12 @@ pub fn CountingWriter(comptime WriterType: type) type { child_stream: WriterType, pub const Error = WriterType.Error; - pub const Writer = io.Writer(*Self, Error, write); + pub const Writer = io.Writer(*Self, Error, writev); const Self = @This(); - pub fn write(self: *Self, bytes: []const u8) Error!usize { - const amt = try self.child_stream.write(bytes); + pub fn writev(self: *Self, iov: []std.io.WriteBuffers) Error!usize { + const amt = try self.child_stream.writev(iov); self.bytes_written += amt; return amt; } diff --git a/lib/std/io/fixed_buffer_stream.zig b/lib/std/io/fixed_buffer_stream.zig index 14e5e5de43eb..8dd3ddce754a 100644 --- a/lib/std/io/fixed_buffer_stream.zig +++ b/lib/std/io/fixed_buffer_stream.zig @@ -17,8 +17,8 @@ pub fn FixedBufferStream(comptime Buffer: type) type { pub const SeekError = error{}; pub const GetSeekPosError = error{}; - pub const Reader = io.Reader(*Self, ReadError, read); - pub const Writer = io.Writer(*Self, WriteError, write); + pub const Reader = io.Reader(*Self, ReadError, readv); + pub const Writer = io.Writer(*Self, WriteError, writev); pub const SeekableStream = io.SeekableStream( *Self, @@ -44,31 +44,39 @@ pub fn FixedBufferStream(comptime Buffer: type) type { return .{ .context = self }; } - pub fn read(self: *Self, dest: []u8) ReadError!usize { - const size = @min(dest.len, self.buffer.len - self.pos); - const end = self.pos + size; + pub fn readv(self: *Self, iov: []std.io.ReadBuffers) ReadError!usize { + var read: usize = 0; + for (iov) |v| { + const size = @min(v.len, self.buffer.len - self.pos); + const end = self.pos + size; - @memcpy(dest[0..size], self.buffer[self.pos..end]); - self.pos = end; + @memcpy(v.ptr[0..size], self.buffer[self.pos..end]); + self.pos = end; + read += size; + } - return size; + return read; } /// If the returned number of bytes written is less than requested, the /// buffer is full. Returns `error.NoSpaceLeft` when no bytes would be written. /// Note: `error.NoSpaceLeft` matches the corresponding error from /// `std.fs.File.WriteError`. - pub fn write(self: *Self, bytes: []const u8) WriteError!usize { - if (bytes.len == 0) return 0; - if (self.pos >= self.buffer.len) return error.NoSpaceLeft; - - const n = @min(self.buffer.len - self.pos, bytes.len); - @memcpy(self.buffer[self.pos..][0..n], bytes[0..n]); - self.pos += n; - - if (n == 0) return error.NoSpaceLeft; + pub fn writev(self: *Self, iov: []std.io.WriteBuffers) WriteError!usize { + var written: usize = 0; + for (iov) |v| { + if (v.len == 0) continue; + if (self.pos >= self.buffer.len) return error.NoSpaceLeft; + + const n = @min(self.buffer.len - self.pos, v.len); + @memcpy(self.buffer[self.pos..][0..n], v.ptr[0..n]); + self.pos += n; + + if (n == 0) return error.NoSpaceLeft; + written += n; + } - return n; + return written; } pub fn seekTo(self: *Self, pos: u64) SeekError!void { diff --git a/lib/std/io/limited_reader.zig b/lib/std/io/limited_reader.zig index d7e250388139..7fc39af9296d 100644 --- a/lib/std/io/limited_reader.zig +++ b/lib/std/io/limited_reader.zig @@ -9,15 +9,16 @@ pub fn LimitedReader(comptime ReaderType: type) type { bytes_left: u64, pub const Error = ReaderType.Error; - pub const Reader = io.Reader(*Self, Error, read); + pub const Reader = io.Reader(*Self, Error, readv); const Self = @This(); - pub fn read(self: *Self, dest: []u8) Error!usize { - const max_read = @min(self.bytes_left, dest.len); - const n = try self.inner_reader.read(dest[0..max_read]); - self.bytes_left -= n; - return n; + pub fn readv(self: *Self, iov: []std.io.ReadBuffers) Error!usize { + for (iov) |*v| { + v.len = @min(self.bytes_left, v.len); + self.bytes_left -= v.len; + } + return try self.inner_reader.readv(iov); } pub fn reader(self: *Self) Reader { diff --git a/lib/std/io/multi_writer.zig b/lib/std/io/multi_writer.zig index 9cd4600e634c..b7d6097500e4 100644 --- a/lib/std/io/multi_writer.zig +++ b/lib/std/io/multi_writer.zig @@ -15,16 +15,22 @@ pub fn MultiWriter(comptime Writers: type) type { streams: Writers, pub const Error = ErrSet; - pub const Writer = io.Writer(*Self, Error, write); + pub const Writer = io.Writer(*Self, Error, writev); pub fn writer(self: *Self) Writer { return .{ .context = self }; } - pub fn write(self: *Self, bytes: []const u8) Error!usize { - inline for (self.streams) |stream| - try stream.writeAll(bytes); - return bytes.len; + pub fn writev(self: *Self, iov: []std.io.WriteBuffers) Error!usize { + var written: usize = 0; + for (iov) |v| written += v.len; + inline for (self.streams) |stream| { + for (iov) |v| { + const bytes = v.ptr[0..v.len]; + try stream.writeAll(bytes); + } + } + return written; } }; } diff --git a/lib/std/io/stream_source.zig b/lib/std/io/stream_source.zig index 6e06af8204e0..c69e67d365a2 100644 --- a/lib/std/io/stream_source.zig +++ b/lib/std/io/stream_source.zig @@ -26,8 +26,8 @@ pub const StreamSource = union(enum) { pub const SeekError = io.FixedBufferStream([]u8).SeekError || (if (has_file) std.fs.File.SeekError else error{}); pub const GetSeekPosError = io.FixedBufferStream([]u8).GetSeekPosError || (if (has_file) std.fs.File.GetSeekPosError else error{}); - pub const Reader = io.Reader(*StreamSource, ReadError, read); - pub const Writer = io.Writer(*StreamSource, WriteError, write); + pub const Reader = io.Reader(*StreamSource, ReadError, readv); + pub const Writer = io.Writer(*StreamSource, WriteError, writev); pub const SeekableStream = io.SeekableStream( *StreamSource, SeekError, @@ -38,19 +38,19 @@ pub const StreamSource = union(enum) { getEndPos, ); - pub fn read(self: *StreamSource, dest: []u8) ReadError!usize { + pub fn readv(self: *StreamSource, iov: []std.io.ReadBuffers) ReadError!usize { switch (self.*) { - .buffer => |*x| return x.read(dest), - .const_buffer => |*x| return x.read(dest), - .file => |x| if (!has_file) unreachable else return x.read(dest), + .buffer => |*x| return x.readv(iov), + .const_buffer => |*x| return x.readv(iov), + .file => |x| if (!has_file) unreachable else return x.readv(iov), } } - pub fn write(self: *StreamSource, bytes: []const u8) WriteError!usize { + pub fn writev(self: *StreamSource, iov: []std.io.WriteBuffers) WriteError!usize { switch (self.*) { - .buffer => |*x| return x.write(bytes), + .buffer => |*x| return x.writev(iov), .const_buffer => return error.AccessDenied, - .file => |x| if (!has_file) unreachable else return x.write(bytes), + .file => |x| if (!has_file) unreachable else return x.writev(iov), } } diff --git a/lib/std/json/stringify_test.zig b/lib/std/json/stringify_test.zig index c87e400a8445..c9051a1fb2da 100644 --- a/lib/std/json/stringify_test.zig +++ b/lib/std/json/stringify_test.zig @@ -298,7 +298,7 @@ test "stringify tuple" { fn testStringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { const ValidationWriter = struct { const Self = @This(); - pub const Writer = std.io.Writer(*Self, Error, write); + pub const Writer = std.io.Writer(*Self, Error, writev); pub const Error = error{ TooMuchData, DifferentData, @@ -314,7 +314,10 @@ fn testStringify(expected: []const u8, value: anytype, options: StringifyOptions return .{ .context = self }; } - fn write(self: *Self, bytes: []const u8) Error!usize { + fn writev(self: *Self, iov: []std.io.WriteBuffers) Error!usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const bytes = first.ptr[0..first.len]; if (self.expected_remaining.len < bytes.len) { std.debug.print( \\====== expected this output: ========= diff --git a/lib/std/net.zig b/lib/std/net.zig index b12fb1932d8f..f7bf901a701b 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -237,9 +237,9 @@ pub const Address = extern union { const sockfd = try posix.socket(address.any.family, sock_flags, proto); var s: Server = .{ .listen_address = undefined, - .stream = .{ .handle = sockfd }, + .socket = .{ .handle = sockfd }, }; - errdefer s.stream.close(); + errdefer s.socket.close(); if (options.reuse_address or options.reuse_port) { try posix.setsockopt( @@ -709,26 +709,26 @@ pub const Ip6Address = extern struct { } }; -pub fn connectUnixSocket(path: []const u8) !Stream { +pub fn connectUnixSocket(path: []const u8) !Socket { const opt_non_block = 0; const sockfd = try posix.socket( posix.AF.UNIX, posix.SOCK.STREAM | posix.SOCK.CLOEXEC | opt_non_block, 0, ); - errdefer Stream.close(.{ .handle = sockfd }); + errdefer Socket.close(.{ .handle = sockfd }); var addr = try std.net.Address.initUnix(path); try posix.connect(sockfd, &addr.any, addr.getOsSockLen()); - return .{ .handle = sockfd }; + return Socket{ .handle = sockfd }; } fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 { if (native_os == .linux) { var ifr: posix.ifreq = undefined; const sockfd = try posix.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0); - defer Stream.close(.{ .handle = sockfd }); + defer Socket.close(.{ .handle = sockfd }); @memcpy(ifr.ifrn.name[0..name.len], name); ifr.ifrn.name[name.len] = 0; @@ -750,7 +750,7 @@ fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 { const index = std.c.if_nametoindex(if_slice); if (index == 0) return error.InterfaceNotFound; - return @as(u32, @bitCast(index)); + return @bitCast(index); } @compileError("std.net.if_nametoindex unimplemented for this OS"); @@ -773,7 +773,7 @@ pub const AddressList = struct { pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError; /// All memory allocated with `allocator` will be freed before this function returns. -pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream { +pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) TcpConnectToHostError!Socket { const list = try getAddressList(allocator, name, port); defer list.deinit(); @@ -792,16 +792,16 @@ pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) T pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError; -pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream { +pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Socket { const nonblock = 0; const sock_flags = posix.SOCK.STREAM | nonblock | (if (native_os == .windows) 0 else posix.SOCK.CLOEXEC); const sockfd = try posix.socket(address.any.family, sock_flags, posix.IPPROTO.TCP); - errdefer Stream.close(.{ .handle = sockfd }); + errdefer Socket.close(.{ .handle = sockfd }); try posix.connect(sockfd, &address.any, address.getOsSockLen()); - return Stream{ .handle = sockfd }; + return Socket{ .handle = sockfd }; } const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{ @@ -1127,7 +1127,7 @@ fn linuxLookupName( var prefixlen: i32 = 0; const sock_flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC; if (posix.socket(addr.addr.any.family, sock_flags, posix.IPPROTO.UDP)) |fd| syscalls: { - defer Stream.close(.{ .handle = fd }); + defer Socket.close(.{ .handle = fd }); posix.connect(fd, da, dalen) catch break :syscalls; key |= DAS_USABLE; posix.getsockname(fd, sa, &salen) catch break :syscalls; @@ -1612,7 +1612,7 @@ fn resMSendRc( }, else => |e| return e, }; - defer Stream.close(.{ .handle = fd }); + defer Socket.close(.{ .handle = fd }); // Past this point, there are no errors. Each individual query will // yield either no reply (indicated by zero length) or an answer @@ -1787,144 +1787,76 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) } } -pub const Stream = struct { +pub const Socket = struct { /// Underlying platform-defined type which may or may not be /// interchangeable with a file system file descriptor. handle: posix.socket_t, - pub fn close(s: Stream) void { - switch (native_os) { - .windows => windows.closesocket(s.handle) catch unreachable, - else => posix.close(s.handle), - } - } - pub const ReadError = posix.ReadError; pub const WriteError = posix.WriteError; + pub const GenericStream = io.GenericStream(Socket, ReadError, readv, WriteError, writev, close); - pub const Reader = io.Reader(Stream, ReadError, read); - pub const Writer = io.Writer(Stream, WriteError, write); - - pub fn reader(self: Stream) Reader { - return .{ .context = self }; - } - - pub fn writer(self: Stream) Writer { - return .{ .context = self }; - } - - pub fn read(self: Stream, buffer: []u8) ReadError!usize { - if (native_os == .windows) { - return windows.ReadFile(self.handle, buffer, null); - } - - return posix.read(self.handle, buffer); - } - - pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize { + pub fn readv(s: Socket, iovecs: []posix.iovec) ReadError!usize { if (native_os == .windows) { // TODO improve this to use ReadFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; - return windows.ReadFile(s.handle, first.iov_base[0..first.iov_len], null); + return windows.ReadFile(s.handle, first.ptr[0..first.len], null); } - + var len: usize = 0; + for (iovecs) |v| len += v.len; return posix.readv(s.handle, iovecs); } - /// Returns the number of bytes read. If the number read is smaller than - /// `buffer.len`, it means the stream reached the end. Reaching the end of - /// a stream is not an error condition. - pub fn readAll(s: Stream, buffer: []u8) ReadError!usize { - return readAtLeast(s, buffer, buffer.len); - } - - /// Returns the number of bytes read, calling the underlying read function - /// the minimal number of times until the buffer has at least `len` bytes - /// filled. If the number read is less than `len` it means the stream - /// reached the end. Reaching the end of the stream is not an error - /// condition. - pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize { - assert(len <= buffer.len); - var index: usize = 0; - while (index < len) { - const amt = try s.read(buffer[index..]); - if (amt == 0) break; - index += amt; - } - return index; - } - - /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's - /// file system thread instead of non-blocking. It needs to be reworked to properly - /// use non-blocking I/O. - pub fn write(self: Stream, buffer: []const u8) WriteError!usize { - if (native_os == .windows) { - return windows.WriteFile(self.handle, buffer, null); - } - - return posix.write(self.handle, buffer); - } - - pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try self.write(bytes[index..]); - } - } - /// See https://github.com/ziglang/zig/issues/7699 /// See equivalent function: `std.fs.File.writev`. - pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize { + pub fn writev(self: Socket, iovecs: []const posix.iovec_const) WriteError!usize { return posix.writev(self.handle, iovecs); } - /// The `iovecs` parameter is mutable because this function needs to mutate the fields in - /// order to handle partial writes from the underlying OS layer. - /// See https://github.com/ziglang/zig/issues/7699 - /// See equivalent function: `std.fs.File.writevAll`. - pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void { - if (iovecs.len == 0) return; - - var i: usize = 0; - while (true) { - var amt = try self.writev(iovecs[i..]); - while (amt >= iovecs[i].iov_len) { - amt -= iovecs[i].iov_len; - i += 1; - if (i >= iovecs.len) return; - } - iovecs[i].iov_base += amt; - iovecs[i].iov_len -= amt; + pub fn close(s: Socket) void { + switch (native_os) { + .windows => windows.closesocket(s.handle) catch unreachable, + else => posix.close(s.handle), } } + + pub fn stream(self: Socket) GenericStream { + return .{ .context = self }; + } }; pub const Server = struct { listen_address: Address, - stream: std.net.Stream, + socket: Socket, pub const Connection = struct { - stream: std.net.Stream, address: Address, + socket: Socket, + + pub fn stream(conn: *Connection) Socket.GenericStream { + return conn.socket.stream(); + } }; pub fn deinit(s: *Server) void { - s.stream.close(); + s.socket.close(); s.* = undefined; } pub const AcceptError = posix.AcceptError; - /// Blocks until a client connects to the server. The returned `Connection` has - /// an open stream. + /// Blocks until a client connects to the server. + /// If tls_options are supplied, will await a client handshake. + /// The returned `Connection` has an open stream. pub fn accept(s: *Server) AcceptError!Connection { var accepted_addr: Address = undefined; var addr_len: posix.socklen_t = @sizeOf(Address); - const fd = try posix.accept(s.stream.handle, &accepted_addr.any, &addr_len, posix.SOCK.CLOEXEC); + const fd = try posix.accept(s.socket.handle, &accepted_addr.any, &addr_len, posix.SOCK.CLOEXEC); + const socket = Socket{ .handle = fd }; return .{ - .stream = .{ .handle = fd }, .address = accepted_addr, + .socket = socket, }; } }; @@ -1932,6 +1864,6 @@ pub const Server = struct { test { _ = @import("net/test.zig"); _ = Server; - _ = Stream; + _ = Socket; _ = Address; } diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index 3e316c545643..3646ae64d7bf 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -189,7 +189,7 @@ test "listen on a port, send bytes, receive bytes" { const socket = try net.tcpConnectToAddress(server_address); defer socket.close(); - _ = try socket.writer().writeAll("Hello world!"); + _ = try socket.stream().writer().writeAll("Hello world!"); } }; @@ -197,9 +197,9 @@ test "listen on a port, send bytes, receive bytes" { defer t.join(); var client = try server.accept(); - defer client.stream.close(); + defer client.stream().close(); var buf: [16]u8 = undefined; - const n = try client.stream.reader().read(&buf); + const n = try client.stream().reader().read(&buf); try testing.expectEqual(@as(usize, 12), n); try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]); @@ -280,7 +280,7 @@ test "listen on a unix socket, send bytes, receive bytes" { const socket = try net.connectUnixSocket(path); defer socket.close(); - _ = try socket.writer().writeAll("Hello world!"); + _ = try socket.stream().writer().writeAll("Hello world!"); } }; @@ -288,9 +288,9 @@ test "listen on a unix socket, send bytes, receive bytes" { defer t.join(); var client = try server.accept(); - defer client.stream.close(); + defer client.stream().close(); var buf: [16]u8 = undefined; - const n = try client.stream.reader().read(&buf); + const n = try client.stream().reader().read(&buf); try testing.expectEqual(@as(usize, 12), n); try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]); diff --git a/lib/std/os/emscripten.zig b/lib/std/os/emscripten.zig index a37b2b1f5c91..4f0b72d40bad 100644 --- a/lib/std/os/emscripten.zig +++ b/lib/std/os/emscripten.zig @@ -2,7 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const wasi = std.os.wasi; const iovec = std.posix.iovec; -const iovec_const = std.posix.iovec_const; +const iovec_const = std.io.WriteBuffers; const c = std.c; pub const FILE = c.FILE; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 9ff272a3328e..7dce4f2f4ee7 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -19,7 +19,7 @@ const is_ppc = native_arch.isPPC(); const is_ppc64 = native_arch.isPPC64(); const is_sparc = native_arch.isSPARC(); const iovec = std.posix.iovec; -const iovec_const = std.posix.iovec_const; +const iovec_const = std.io.WriteBuffers; const ACCMODE = std.posix.ACCMODE; test { @@ -1636,7 +1636,7 @@ pub fn sendmsg(fd: i32, msg: *const msghdr_const, flags: u32) usize { pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize { if (@typeInfo(usize).Int.bits > @typeInfo(@typeInfo(mmsghdr).Struct.fields[1].type).Int.bits) { // workaround kernel brokenness: - // if adding up all iov_len overflows a i32 then split into multiple calls + // if adding up all len overflows a i32 then split into multiple calls // see https://www.openwall.com/lists/musl/2014/06/07/5 const kvlen = if (vlen > IOV_MAX) IOV_MAX else vlen; // matches kernel var next_unsent: usize = 0; @@ -1644,7 +1644,7 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize var size: i32 = 0; const msg_iovlen = @as(usize, @intCast(msg.msg_hdr.msg_iovlen)); // kernel side this is treated as unsigned for (msg.msg_hdr.msg_iov[0..msg_iovlen]) |iov| { - if (iov.iov_len > std.math.maxInt(i32) or @addWithOverflow(size, @as(i32, @intCast(iov.iov_len)))[1] != 0) { + if (iov.len > std.math.maxInt(i32) or @addWithOverflow(size, @as(i32, @intCast(iov.len)))[1] != 0) { // batch-send all messages up to the current message if (next_unsent < i) { const batch_size = i - next_unsent; @@ -1660,7 +1660,7 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize next_unsent = i + 1; break; } - size += iov.iov_len; + size += iov.len; } } if (next_unsent < kvlen or next_unsent == 0) { // want to make sure at least one syscall occurs (e.g. to trigger MSG.EOR) diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index 0e3c4ce33858..879b9ff24e70 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -1770,7 +1770,7 @@ test "readv" { try ring.register_files(registered_fds[0..]); var buffer = [_]u8{42} ** 128; - var iovecs = [_]posix.iovec{posix.iovec{ .iov_base = &buffer, .iov_len = buffer.len }}; + var iovecs = [_]posix.iovec{posix.iovec{ .ptr = &buffer, .len = buffer.len }}; const sqe = try ring.read(0xcccccccc, fd_index, .{ .iovecs = iovecs[0..] }, 0); try testing.expectEqual(linux.IORING_OP.READV, sqe.opcode); sqe.flags |= linux.IOSQE_FIXED_FILE; @@ -1807,11 +1807,11 @@ test "writev/fsync/readv" { const buffer_write = [_]u8{42} ** 128; const iovecs_write = [_]posix.iovec_const{ - posix.iovec_const{ .iov_base = &buffer_write, .iov_len = buffer_write.len }, + posix.iovec_const{ .ptr = &buffer_write, .len = buffer_write.len }, }; var buffer_read = [_]u8{0} ** 128; var iovecs_read = [_]posix.iovec{ - posix.iovec{ .iov_base = &buffer_read, .iov_len = buffer_read.len }, + posix.iovec{ .ptr = &buffer_read, .len = buffer_read.len }, }; const sqe_writev = try ring.writev(0xdddddddd, fd, iovecs_write[0..], 17); @@ -1999,8 +1999,8 @@ test "write_fixed/read_fixed" { raw_buffers[0][0.."foobar".len].* = "foobar".*; var buffers = [2]posix.iovec{ - .{ .iov_base = &raw_buffers[0], .iov_len = raw_buffers[0].len }, - .{ .iov_base = &raw_buffers[1], .iov_len = raw_buffers[1].len }, + .{ .ptr = &raw_buffers[0], .len = raw_buffers[0].len }, + .{ .ptr = &raw_buffers[1], .len = raw_buffers[1].len }, }; ring.register_buffers(&buffers) catch |err| switch (err) { error.SystemResources => { @@ -2026,18 +2026,18 @@ test "write_fixed/read_fixed" { try testing.expectEqual(linux.io_uring_cqe{ .user_data = 0x45454545, - .res = @as(i32, @intCast(buffers[0].iov_len)), + .res = @as(i32, @intCast(buffers[0].len)), .flags = 0, }, cqe_write); try testing.expectEqual(linux.io_uring_cqe{ .user_data = 0x12121212, - .res = @as(i32, @intCast(buffers[1].iov_len)), + .res = @as(i32, @intCast(buffers[1].len)), .flags = 0, }, cqe_read); - try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].iov_base[0..3]); - try testing.expectEqualSlices(u8, "foobar", buffers[1].iov_base[3..9]); - try testing.expectEqualSlices(u8, "zz", buffers[1].iov_base[9..11]); + try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].ptr[0..3]); + try testing.expectEqualSlices(u8, "foobar", buffers[1].ptr[3..9]); + try testing.expectEqualSlices(u8, "zz", buffers[1].ptr[9..11]); } test "openat" { @@ -2193,7 +2193,7 @@ test "sendmsg/recvmsg" { const buffer_send = [_]u8{42} ** 128; const iovecs_send = [_]posix.iovec_const{ - posix.iovec_const{ .iov_base = &buffer_send, .iov_len = buffer_send.len }, + posix.iovec_const{ .ptr = &buffer_send, .len = buffer_send.len }, }; const msg_send: posix.msghdr_const = .{ .name = &address_server.any, @@ -2211,7 +2211,7 @@ test "sendmsg/recvmsg" { var buffer_recv = [_]u8{0} ** 128; var iovecs_recv = [_]posix.iovec{ - posix.iovec{ .iov_base = &buffer_recv, .iov_len = buffer_recv.len }, + posix.iovec{ .ptr = &buffer_recv, .len = buffer_recv.len }, }; const addr = [_]u8{0} ** 4; var address_recv = net.Address.initIp4(addr, 0); diff --git a/lib/std/os/linux/io_uring_sqe.zig b/lib/std/os/linux/io_uring_sqe.zig index 7306cf8eedfa..6bfee2c93dbb 100644 --- a/lib/std/os/linux/io_uring_sqe.zig +++ b/lib/std/os/linux/io_uring_sqe.zig @@ -117,12 +117,12 @@ pub const io_uring_sqe = extern struct { } pub fn prep_read_fixed(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: *std.posix.iovec, offset: u64, buffer_index: u16) void { - sqe.prep_rw(.READ_FIXED, fd, @intFromPtr(buffer.iov_base), buffer.iov_len, offset); + sqe.prep_rw(.READ_FIXED, fd, @intFromPtr(buffer.ptr), buffer.len, offset); sqe.buf_index = buffer_index; } pub fn prep_write_fixed(sqe: *linux.io_uring_sqe, fd: linux.fd_t, buffer: *std.posix.iovec, offset: u64, buffer_index: u16) void { - sqe.prep_rw(.WRITE_FIXED, fd, @intFromPtr(buffer.iov_base), buffer.iov_len, offset); + sqe.prep_rw(.WRITE_FIXED, fd, @intFromPtr(buffer.ptr), buffer.len, offset); sqe.buf_index = buffer_index; } diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index d0f8bdbb661a..bd2b5e3ef859 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -18,7 +18,7 @@ comptime { } pub const iovec_t = std.posix.iovec; -pub const ciovec_t = std.posix.iovec_const; +pub const ciovec_t = std.io.WriteBuffers; pub extern "wasi_snapshot_preview1" fn args_get(argv: [*][*:0]u8, argv_buf: [*]u8) errno_t; pub extern "wasi_snapshot_preview1" fn args_sizes_get(argc: *usize, argv_buf_size: *usize) errno_t; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index fb2262e267bc..e620bf86d089 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -173,13 +173,13 @@ pub const W_OK = system.W_OK; pub const X_OK = system.X_OK; pub const iovec = extern struct { - iov_base: [*]u8, - iov_len: usize, + ptr: [*]u8, + len: usize, }; pub const iovec_const = extern struct { - iov_base: [*]const u8, - iov_len: usize, + ptr: [*]const u8, + len: usize, }; pub const ACCMODE = enum(u2) { @@ -796,8 +796,8 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { } if (native_os == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ - .iov_base = buf.ptr, - .iov_len = buf.len, + .ptr = buf.ptr, + .len = buf.len, }}; var nread: usize = undefined; @@ -865,7 +865,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { // TODO improve this to use ReadFileScatter if (iov.len == 0) return 0; const first = iov[0]; - return read(fd, first.iov_base[0..first.iov_len]); + return read(fd, first.ptr[0..first.len]); } if (native_os == .wasi and !builtin.link_libc) { var nread: usize = undefined; @@ -932,8 +932,8 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { } if (native_os == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ - .iov_base = buf.ptr, - .iov_len = buf.len, + .ptr = buf.ptr, + .len = buf.len, }}; var nread: usize = undefined; @@ -1077,7 +1077,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { // So we simply read into the first vector only. if (iov.len == 0) return 0; const first = iov[0]; - return pread(fd, first.iov_base[0..first.iov_len], offset); + return pread(fd, first.ptr[0..first.len], offset); } if (native_os == .wasi and !builtin.link_libc) { var nread: usize = undefined; @@ -1186,8 +1186,8 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { if (native_os == .wasi and !builtin.link_libc) { const ciovs = [_]iovec_const{iovec_const{ - .iov_base = bytes.ptr, - .iov_len = bytes.len, + .ptr = bytes.ptr, + .len = bytes.len, }}; var nwritten: usize = undefined; switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) { @@ -1263,7 +1263,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { // TODO improve this to use WriteFileScatter if (iov.len == 0) return 0; const first = iov[0]; - return write(fd, first.iov_base[0..first.iov_len]); + return write(fd, first.ptr[0..first.len]); } if (native_os == .wasi and !builtin.link_libc) { var nwritten: usize = undefined; @@ -1340,8 +1340,8 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { } if (native_os == .wasi and !builtin.link_libc) { const ciovs = [1]iovec_const{iovec_const{ - .iov_base = bytes.ptr, - .iov_len = bytes.len, + .ptr = bytes.ptr, + .len = bytes.len, }}; var nwritten: usize = undefined; @@ -1432,7 +1432,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz // So we simply write the first vector only. if (iov.len == 0) return 0; const first = iov[0]; - return pwrite(fd, first.iov_base[0..first.iov_len], offset); + return pwrite(fd, first.ptr[0..first.len], offset); } if (native_os == .wasi and !builtin.link_libc) { var nwritten: usize = undefined; @@ -6353,7 +6353,7 @@ pub fn sendfile( fn count_iovec_bytes(iovs: []const iovec_const) usize { var count: usize = 0; for (iovs) |iov| { - count += iov.iov_len; + count += iov.len; } return count; } diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 1020bef4b7b0..766dee5ae6dd 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -820,7 +820,7 @@ test "shutdown socket" { error.SocketNotConnected => {}, else => |e| return e, }; - std.net.Stream.close(.{ .handle = sock }); + std.net.Socket.close(.{ .handle = sock }); } test "sigaction" { @@ -939,7 +939,7 @@ test "writev longer than IOV_MAX" { var file = try tmp.dir.createFile("pwritev", .{}); defer file.close(); - const iovecs = [_]posix.iovec_const{.{ .iov_base = "a", .iov_len = 1 }} ** (posix.IOV_MAX + 1); + var iovecs = [_]posix.iovec_const{.{ .ptr = "a", .len = 1 }} ** (posix.IOV_MAX + 1); const amt = try file.writev(&iovecs); try testing.expectEqual(@as(usize, posix.IOV_MAX), amt); } diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 13da27ca846d..6c9fcb265129 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -269,7 +269,7 @@ pub const FileKind = enum { file, }; -/// Iteartor over entries in the tar file represented by reader. +/// Iterator over tar entries pub fn Iterator(comptime ReaderType: type) type { return struct { reader: ReaderType, @@ -295,17 +295,22 @@ pub fn Iterator(comptime ReaderType: type) type { unread_bytes: *u64, parent_reader: ReaderType, - pub const Reader = std.io.Reader(File, ReaderType.Error, File.read); + pub const Reader = std.io.Reader(File, ReaderType.Error, File.readv); pub fn reader(self: File) Reader { return .{ .context = self }; } - pub fn read(self: File, dest: []u8) ReaderType.Error!usize { - const buf = dest[0..@min(dest.len, self.unread_bytes.*)]; - const n = try self.parent_reader.read(buf); - self.unread_bytes.* -= n; - return n; + pub fn readv(self: File, iov: []std.io.ReadBuffers) ReaderType.Error!usize { + var n_read: usize = 0; + for (iov) |v| { + const dest = v.ptr[0..v.len]; + const buf = dest[0..@min(dest.len, self.unread_bytes.*)]; + const n = try self.parent_reader.read(buf); + self.unread_bytes.* -= n; + n_read += n; + } + return n_read; } // Writes file content to writer. diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index bf5fdba231b2..dcc11adf459a 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -146,16 +146,16 @@ pub fn serveMessage( header: OutMessage.Header, bufs: []const []const u8, ) !void { - var iovecs: [10]std.posix.iovec_const = undefined; + var iovecs: [10]std.io.WriteBuffers = undefined; const header_le = bswap(header); iovecs[0] = .{ - .iov_base = @as([*]const u8, @ptrCast(&header_le)), - .iov_len = @sizeOf(OutMessage.Header), + .ptr = @as([*]const u8, @ptrCast(&header_le)), + .len = @sizeOf(OutMessage.Header), }; for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { iovec.* = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, + .ptr = buf.ptr, + .len = buf.len, }; } try s.out.writevAll(iovecs[0 .. bufs.len + 1]); diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index c6a6f3ce710d..fbd11a909ecf 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -3329,7 +3329,7 @@ fn AutoIndentingStream(comptime UnderlyingWriter: type) type { return struct { const Self = @This(); pub const WriteError = UnderlyingWriter.Error; - pub const Writer = std.io.Writer(*Self, WriteError, write); + pub const Writer = std.io.Writer(*Self, WriteError, writev); underlying_writer: UnderlyingWriter, @@ -3355,12 +3355,15 @@ fn AutoIndentingStream(comptime UnderlyingWriter: type) type { return .{ .context = self }; } - pub fn write(self: *Self, bytes: []const u8) WriteError!usize { - if (bytes.len == 0) - return @as(usize, 0); + pub fn writev(self: *Self, iov: []std.io.WriteBuffers) WriteError!usize { + var n_written: usize = 0; + for (iov) |v| { + if (v.len == 0) return n_written; - try self.applyIndent(); - return self.writeNoIndent(bytes); + try self.applyIndent(); + n_written += try self.writeNoIndent(v.ptr[0..v.len]); + } + return n_written; } // Change the indent delta without changing the final indentation level diff --git a/src/Compilation.zig b/src/Compilation.zig index 7a84848b80b2..93b75932844c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2741,7 +2741,7 @@ const Header = extern struct { /// saved, such as the target and most CLI flags. A cache hit will only occur /// when subsequent compiler invocations use the same set of flags. pub fn saveState(comp: *Compilation) !void { - var bufs_list: [19]std.posix.iovec_const = undefined; + var bufs_list: [19]std.io.WriteBuffers = undefined; var bufs_len: usize = 0; const lf = comp.bin_file orelse return; @@ -2808,12 +2808,12 @@ pub fn saveState(comp: *Compilation) !void { try af.finish(); } -fn addBuf(bufs_list: []std.posix.iovec_const, bufs_len: *usize, buf: []const u8) void { +fn addBuf(bufs_list: []std.io.WriteBuffers, bufs_len: *usize, buf: []const u8) void { const i = bufs_len.*; bufs_len.* = i + 1; bufs_list[i] = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, + .ptr = buf.ptr, + .len = buf.len, }; } @@ -3791,9 +3791,9 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { break :p padding_buffer[0..n]; }; - var header_and_trailer: [2]std.posix.iovec_const = .{ - .{ .iov_base = header_bytes.ptr, .iov_len = header_bytes.len }, - .{ .iov_base = padding.ptr, .iov_len = padding.len }, + var header_and_trailer: [2]std.io.WriteBuffers = .{ + .{ .ptr = header_bytes.ptr, .len = header_bytes.len }, + .{ .ptr = padding.ptr, .len = padding.len }, }; try tar_file.writeFileAll(file, .{ diff --git a/src/Module.zig b/src/Module.zig index 237a0dd6ada0..416093a31e7a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2396,26 +2396,26 @@ pub fn astGenFile(mod: *Module, file: *File) !void { .stat_inode = stat.inode, .stat_mtime = stat.mtime, }; - var iovecs = [_]std.posix.iovec_const{ + var iovecs = [_]std.io.WriteBuffers{ .{ - .iov_base = @as([*]const u8, @ptrCast(&header)), - .iov_len = @sizeOf(Zir.Header), + .ptr = @as([*]const u8, @ptrCast(&header)), + .len = @sizeOf(Zir.Header), }, .{ - .iov_base = @as([*]const u8, @ptrCast(file.zir.instructions.items(.tag).ptr)), - .iov_len = file.zir.instructions.len, + .ptr = @as([*]const u8, @ptrCast(file.zir.instructions.items(.tag).ptr)), + .len = file.zir.instructions.len, }, .{ - .iov_base = data_ptr, - .iov_len = file.zir.instructions.len * 8, + .ptr = data_ptr, + .len = file.zir.instructions.len * 8, }, .{ - .iov_base = file.zir.string_bytes.ptr, - .iov_len = file.zir.string_bytes.len, + .ptr = file.zir.string_bytes.ptr, + .len = file.zir.string_bytes.len, }, .{ - .iov_base = @as([*]const u8, @ptrCast(file.zir.extra.ptr)), - .iov_len = file.zir.extra.len * 4, + .ptr = @as([*]const u8, @ptrCast(file.zir.extra.ptr)), + .len = file.zir.extra.len * 4, }, }; cache_file.writevAll(&iovecs) catch |err| { @@ -2484,22 +2484,22 @@ fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.File) else @as([*]u8, @ptrCast(zir.instructions.items(.data).ptr)); - var iovecs = [_]std.posix.iovec{ + var iovecs = [_]std.io.ReadBuffers{ .{ - .iov_base = @as([*]u8, @ptrCast(zir.instructions.items(.tag).ptr)), - .iov_len = header.instructions_len, + .ptr = @as([*]u8, @ptrCast(zir.instructions.items(.tag).ptr)), + .len = header.instructions_len, }, .{ - .iov_base = data_ptr, - .iov_len = header.instructions_len * 8, + .ptr = data_ptr, + .len = header.instructions_len * 8, }, .{ - .iov_base = zir.string_bytes.ptr, - .iov_len = header.string_bytes_len, + .ptr = zir.string_bytes.ptr, + .len = header.string_bytes_len, }, .{ - .iov_base = @as([*]u8, @ptrCast(zir.extra.ptr)), - .iov_len = header.extra_len * 4, + .ptr = @as([*]u8, @ptrCast(zir.extra.ptr)), + .len = header.extra_len * 4, }, }; const amt_read = try cache_file.readvAll(&iovecs); diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index a40bb539f78b..83c2c435ab64 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -807,16 +807,16 @@ const Resource = union(enum) { fn reader(resource: *Resource) std.io.AnyReader { return .{ .context = resource, - .readFn = read, + .readvFn = readv, }; } - fn read(context: *const anyopaque, buffer: []u8) anyerror!usize { + fn readv(context: *const anyopaque, iov: []std.io.ReadBuffers) anyerror!usize { const resource: *Resource = @constCast(@ptrCast(@alignCast(context))); switch (resource.*) { - .file => |*f| return f.read(buffer), - .http_request => |*r| return r.read(buffer), - .git => |*g| return g.fetch_stream.read(buffer), + .file => |*f| return f.readv(iov), + .http_request => |*r| return r.readv(iov), + .git => |*g| return g.fetch_stream.reader().readv(iov), .dir => unreachable, } } diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 36652bd88c55..e39a62a815b0 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -10,6 +10,8 @@ const testing = std.testing; const Allocator = mem.Allocator; const Sha1 = std.crypto.hash.Sha1; const assert = std.debug.assert; +const hashedWriter = std.compress.hashedWriter; +const hashedReader = std.compress.hashedReader; pub const oid_length = Sha1.digest_length; pub const fmt_oid_length = 2 * oid_length; @@ -667,7 +669,7 @@ pub const Session = struct { errdefer request.deinit(); request.transfer_encoding = .{ .content_length = body.items.len }; try request.send(.{}); - try request.writeAll(body.items); + try request.writer().writeAll(body.items); try request.finish(); try request.wait(); @@ -772,7 +774,7 @@ pub const Session = struct { errdefer request.deinit(); request.transfer_encoding = .{ .content_length = body.items.len }; try request.send(.{}); - try request.writeAll(body.items); + try request.writer().writeAll(body.items); try request.finish(); try request.wait(); @@ -819,7 +821,7 @@ pub const Session = struct { ProtocolError, UnexpectedPacket, }; - pub const Reader = std.io.Reader(*FetchStream, ReadError, read); + pub const Reader = std.io.Reader(*FetchStream, ReadError, readv); const StreamCode = enum(u8) { pack_data = 1, @@ -857,6 +859,13 @@ pub const Session = struct { return size; } }; + + pub fn readv(stream: *FetchStream, iov: []std.io.ReadBuffers) !usize { + if (iov.len == 0) return 0; + const first = iov[0]; + const buf = first.ptr[0..first.len]; + return try stream.read(buf); + } }; const PackHeader = struct { @@ -1113,7 +1122,7 @@ fn indexPackFirstPass( ) ![Sha1.digest_length]u8 { var pack_buffered_reader = std.io.bufferedReader(pack.reader()); var pack_counting_reader = std.io.countingReader(pack_buffered_reader.reader()); - var pack_hashed_reader = std.compress.hashedReader(pack_counting_reader.reader(), Sha1.init(.{})); + var pack_hashed_reader = hashedReader(pack_counting_reader.reader(), Sha1.init(.{})); const pack_reader = pack_hashed_reader.reader(); const pack_header = try PackHeader.read(pack_reader); @@ -1121,7 +1130,7 @@ fn indexPackFirstPass( var current_entry: u32 = 0; while (current_entry < pack_header.total_objects) : (current_entry += 1) { const entry_offset = pack_counting_reader.bytes_read; - var entry_crc32_reader = std.compress.hashedReader(pack_reader, std.hash.Crc32.init()); + var entry_crc32_reader = hashedReader(pack_reader, std.hash.Crc32.init()); const entry_header = try EntryHeader.read(entry_crc32_reader.reader()); switch (entry_header) { .commit, .tree, .blob, .tag => |object| { @@ -1325,36 +1334,6 @@ fn expandDelta(base_object: anytype, delta_reader: anytype, writer: anytype) !vo } } -fn HashedWriter( - comptime WriterType: anytype, - comptime HasherType: anytype, -) type { - return struct { - child_writer: WriterType, - hasher: HasherType, - - const Error = WriterType.Error; - const Writer = std.io.Writer(*@This(), Error, write); - - fn write(hashed_writer: *@This(), buf: []const u8) Error!usize { - const amt = try hashed_writer.child_writer.write(buf); - hashed_writer.hasher.update(buf); - return amt; - } - - fn writer(hashed_writer: *@This()) Writer { - return .{ .context = hashed_writer }; - } - }; -} - -fn hashedWriter( - writer: anytype, - hasher: anytype, -) HashedWriter(@TypeOf(writer), @TypeOf(hasher)) { - return .{ .child_writer = writer, .hasher = hasher }; -} - test "packfile indexing and checkout" { // To verify the contents of this packfile without using the code in this // file: diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 10795529f976..04aac786b0d9 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -7500,12 +7500,12 @@ const ArrayListWriter = ErrorOnlyGenericWriter(std.ArrayList(u8).Writer.Error); fn arrayListWriter(list: *std.ArrayList(u8)) ArrayListWriter { return .{ .context = .{ .context = list, - .writeFn = struct { - fn write(context: *const anyopaque, bytes: []const u8) anyerror!usize { + .writevFn = struct { + fn writev(context: *const anyopaque, iov: []std.io.WriteBuffers) anyerror!usize { const l: *std.ArrayList(u8) = @alignCast(@constCast(@ptrCast(context))); - return l.writer().write(bytes); + return l.writer().writev(iov); } - }.write, + }.writev, } }; } @@ -7524,25 +7524,28 @@ fn IndentWriter(comptime UnderlyingWriter: type) type { pub fn writer(self: *Self) Writer { return .{ .context = .{ .context = self, - .writeFn = writeAny, + .writevFn = writevAny, } }; } - pub fn write(self: *Self, bytes: []const u8) Error!usize { - if (bytes.len == 0) return @as(usize, 0); - - const current_indent = self.indent_count * Self.indent_delta; - if (self.current_line_empty and current_indent > 0) { - try self.underlying_writer.writeByteNTimes(' ', current_indent); + pub fn writev(self: *Self, iov: []std.io.WriteBuffers) Error!usize { + var written: usize = 0; + for (iov) |v| { + const bytes = v.ptr[0..v.len]; + const current_indent = self.indent_count * Self.indent_delta; + if (self.current_line_empty and current_indent > 0) { + try self.underlying_writer.writeByteNTimes(' ', current_indent); + } + self.current_line_empty = false; + written += try self.writeNoIndent(bytes); } - self.current_line_empty = false; - return self.writeNoIndent(bytes); + return written; } - fn writeAny(context: *const anyopaque, bytes: []const u8) anyerror!usize { + fn writevAny(context: *const anyopaque, iov: []std.io.WriteBuffers) anyerror!usize { const self: *Self = @alignCast(@constCast(@ptrCast(context))); - return self.write(bytes); + return self.writev(iov); } pub fn insertNewline(self: *Self) Error!void { @@ -7576,10 +7579,10 @@ fn IndentWriter(comptime UnderlyingWriter: type) type { /// maintaining ease of error handling. fn ErrorOnlyGenericWriter(comptime Error: type) type { return std.io.GenericWriter(std.io.AnyWriter, Error, struct { - fn write(context: std.io.AnyWriter, bytes: []const u8) Error!usize { - return @errorCast(context.write(bytes)); + fn writev(context: std.io.AnyWriter, iov: []std.io.WriteBuffers) Error!usize { + return @errorCast(context.writev(iov)); } - }.write); + }.writev); } fn toCIntBits(zig_bits: u32) ?u32 { diff --git a/src/link/C.zig b/src/link/C.zig index 59ebed800b77..2f7343383369 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -482,15 +482,15 @@ pub fn flushModule(self: *C, arena: Allocator, prog_node: *std.Progress.Node) !v } f.all_buffers.items[ctypes_index] = .{ - .iov_base = if (f.ctypes_buf.items.len > 0) f.ctypes_buf.items.ptr else "", - .iov_len = f.ctypes_buf.items.len, + .ptr = if (f.ctypes_buf.items.len > 0) f.ctypes_buf.items.ptr else "", + .len = f.ctypes_buf.items.len, }; f.file_size += f.ctypes_buf.items.len; const lazy_fwd_decl_len = self.lazy_fwd_decl_buf.items.len; f.all_buffers.items[lazy_index] = .{ - .iov_base = if (lazy_fwd_decl_len > 0) self.lazy_fwd_decl_buf.items.ptr else "", - .iov_len = lazy_fwd_decl_len, + .ptr = if (lazy_fwd_decl_len > 0) self.lazy_fwd_decl_buf.items.ptr else "", + .len = lazy_fwd_decl_len, }; f.file_size += lazy_fwd_decl_len; @@ -518,7 +518,7 @@ const Flush = struct { asm_buf: std.ArrayListUnmanaged(u8) = .{}, /// We collect a list of buffers to write, and write them all at once with pwritev 😎 - all_buffers: std.ArrayListUnmanaged(std.posix.iovec_const) = .{}, + all_buffers: std.ArrayListUnmanaged(std.io.WriteBuffers) = .{}, /// Keeps track of the total bytes of `all_buffers`. file_size: u64 = 0, @@ -526,7 +526,7 @@ const Flush = struct { fn appendBufAssumeCapacity(f: *Flush, buf: []const u8) void { if (buf.len == 0) return; - f.all_buffers.appendAssumeCapacity(.{ .iov_base = buf.ptr, .iov_len = buf.len }); + f.all_buffers.appendAssumeCapacity(.{ .ptr = buf.ptr, .len = buf.len }); f.file_size += buf.len; } @@ -752,14 +752,14 @@ pub fn flushEmitH(module: *Module) !void { // We collect a list of buffers to write, and write them all at once with pwritev 😎 const num_buffers = emit_h.decl_table.count() + 1; - var all_buffers = try std.ArrayList(std.posix.iovec_const).initCapacity(module.gpa, num_buffers); + var all_buffers = try std.ArrayList(std.io.WriteBuffers).initCapacity(module.gpa, num_buffers); defer all_buffers.deinit(); var file_size: u64 = zig_h.len; if (zig_h.len != 0) { all_buffers.appendAssumeCapacity(.{ - .iov_base = zig_h, - .iov_len = zig_h.len, + .ptr = zig_h, + .len = zig_h.len, }); } @@ -768,8 +768,8 @@ pub fn flushEmitH(module: *Module) !void { const buf = decl_emit_h.fwd_decl.items; if (buf.len != 0) { all_buffers.appendAssumeCapacity(.{ - .iov_base = buf.ptr, - .iov_len = buf.len, + .ptr = buf.ptr, + .len = buf.len, }); file_size += buf.len; } diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 26eb536c0814..f4e21826d4c9 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2118,38 +2118,38 @@ fn pwriteDbgLineNops( const page_of_nops = [1]u8{DW.LNS.negate_stmt} ** 4096; const three_byte_nop = [3]u8{ DW.LNS.advance_pc, 0b1000_0000, 0 }; - var vecs: [512]std.posix.iovec_const = undefined; + var vecs: [512]std.io.WriteBuffers = undefined; var vec_index: usize = 0; { var padding_left = prev_padding_size; if (padding_left % 2 != 0) { vecs[vec_index] = .{ - .iov_base = &three_byte_nop, - .iov_len = three_byte_nop.len, + .ptr = &three_byte_nop, + .len = three_byte_nop.len, }; vec_index += 1; padding_left -= three_byte_nop.len; } while (padding_left > page_of_nops.len) { vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, + .ptr = &page_of_nops, + .len = page_of_nops.len, }; vec_index += 1; padding_left -= page_of_nops.len; } if (padding_left > 0) { vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, + .ptr = &page_of_nops, + .len = padding_left, }; vec_index += 1; } } vecs[vec_index] = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, + .ptr = buf.ptr, + .len = buf.len, }; if (buf.len > 0) vec_index += 1; @@ -2157,24 +2157,24 @@ fn pwriteDbgLineNops( var padding_left = next_padding_size; if (padding_left % 2 != 0) { vecs[vec_index] = .{ - .iov_base = &three_byte_nop, - .iov_len = three_byte_nop.len, + .ptr = &three_byte_nop, + .len = three_byte_nop.len, }; vec_index += 1; padding_left -= three_byte_nop.len; } while (padding_left > page_of_nops.len) { vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, + .ptr = &page_of_nops, + .len = page_of_nops.len, }; vec_index += 1; padding_left -= page_of_nops.len; } if (padding_left > 0) { vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, + .ptr = &page_of_nops, + .len = padding_left, }; vec_index += 1; } @@ -2235,30 +2235,30 @@ fn pwriteDbgInfoNops( defer tracy.end(); const page_of_nops = [1]u8{@intFromEnum(AbbrevCode.padding)} ** 4096; - var vecs: [32]std.posix.iovec_const = undefined; + var vecs: [32]std.io.WriteBuffers = undefined; var vec_index: usize = 0; { var padding_left = prev_padding_size; while (padding_left > page_of_nops.len) { vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, + .ptr = &page_of_nops, + .len = page_of_nops.len, }; vec_index += 1; padding_left -= page_of_nops.len; } if (padding_left > 0) { vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, + .ptr = &page_of_nops, + .len = padding_left, }; vec_index += 1; } } vecs[vec_index] = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, + .ptr = buf.ptr, + .len = buf.len, }; if (buf.len > 0) vec_index += 1; @@ -2266,16 +2266,16 @@ fn pwriteDbgInfoNops( var padding_left = next_padding_size; while (padding_left > page_of_nops.len) { vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, + .ptr = &page_of_nops, + .len = page_of_nops.len, }; vec_index += 1; padding_left -= page_of_nops.len; } if (padding_left > 0) { vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, + .ptr = &page_of_nops, + .len = padding_left, }; vec_index += 1; } @@ -2284,8 +2284,8 @@ fn pwriteDbgInfoNops( if (trailing_zero) { var zbuf = [1]u8{0}; vecs[vec_index] = .{ - .iov_base = &zbuf, - .iov_len = zbuf.len, + .ptr = &zbuf, + .len = zbuf.len, }; vec_index += 1; } diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 4c86bb3a894d..1ea5ccc50c95 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -965,13 +965,13 @@ fn updateDeclCode( if (elf_file.base.child_pid) |pid| { switch (builtin.os.tag) { .linux => { - var code_vec: [1]std.posix.iovec_const = .{.{ - .iov_base = code.ptr, - .iov_len = code.len, + var code_vec: [1]std.io.WriteBuffers = .{.{ + .ptr = code.ptr, + .len = code.len, }}; - var remote_vec: [1]std.posix.iovec_const = .{.{ - .iov_base = @as([*]u8, @ptrFromInt(@as(usize, @intCast(sym.address(.{}, elf_file))))), - .iov_len = code.len, + var remote_vec: [1]std.io.WriteBuffers = .{.{ + .ptr = @as([*]u8, @ptrFromInt(@as(usize, @intCast(sym.address(.{}, elf_file))))), + .len = code.len, }}; const rc = std.os.linux.process_vm_writev(pid, &code_vec, &remote_vec, 0); switch (std.os.linux.E.init(rc)) { diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index 0e7cb9054551..25443c81cf59 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -317,13 +317,13 @@ pub const ZigGotSection = struct { if (elf_file.base.child_pid) |pid| { switch (builtin.os.tag) { .linux => { - var local_vec: [1]std.posix.iovec_const = .{.{ - .iov_base = &buf, - .iov_len = buf.len, + var local_vec: [1]std.io.WriteBuffers = .{.{ + .ptr = &buf, + .len = buf.len, }}; - var remote_vec: [1]std.posix.iovec_const = .{.{ - .iov_base = @as([*]u8, @ptrFromInt(@as(usize, @intCast(vaddr)))), - .iov_len = buf.len, + var remote_vec: [1]std.io.WriteBuffers = .{.{ + .ptr = @as([*]u8, @ptrFromInt(@as(usize, @intCast(vaddr)))), + .len = buf.len, }}; const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0); switch (std.os.linux.E.init(rc)) { diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index e14fc18a55d0..ec95ceec42ff 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -722,7 +722,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node defer gpa.free(got_table); // + 4 for header, got, symbols, linecountinfo - var iovecs = try gpa.alloc(std.posix.iovec_const, self.atomCount() + 4 - self.externCount()); + var iovecs = try gpa.alloc(std.io.WriteBuffers, self.atomCount() + 4 - self.externCount()); defer gpa.free(iovecs); const file = self.base.file.?; @@ -732,7 +732,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node const hdr_size = if (self.sixtyfour_bit) @as(usize, 40) else 32; const hdr_slice: []u8 = hdr_buf[0..hdr_size]; var foff = hdr_size; - iovecs[0] = .{ .iov_base = hdr_slice.ptr, .iov_len = hdr_slice.len }; + iovecs[0] = .{ .ptr = hdr_slice.ptr, .len = hdr_slice.len }; var iovecs_i: usize = 1; var text_i: u64 = 0; @@ -761,7 +761,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node linecount = out.end_line; } foff += out.code.len; - iovecs[iovecs_i] = .{ .iov_base = out.code.ptr, .iov_len = out.code.len }; + iovecs[iovecs_i] = .{ .ptr = out.code.ptr, .len = out.code.len }; iovecs_i += 1; const off = self.getAddr(text_i, .t); text_i += out.code.len; @@ -791,7 +791,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node const text_atom = if (meta.text_state != .unused) self.getAtomPtr(meta.text_atom) else continue; const code = text_atom.code.getOwnedCode().?; foff += code.len; - iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len }; + iovecs[iovecs_i] = .{ .ptr = code.ptr, .len = code.len }; iovecs_i += 1; const off = self.getAddr(text_i, .t); text_i += code.len; @@ -816,7 +816,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node } } // global offset table is in data - iovecs[iovecs_i] = .{ .iov_base = got_table.ptr, .iov_len = got_table.len }; + iovecs[iovecs_i] = .{ .ptr = got_table.ptr, .len = got_table.len }; iovecs_i += 1; // data var data_i: u64 = got_size; @@ -828,7 +828,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node const code = entry.value_ptr.*; foff += code.len; - iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len }; + iovecs[iovecs_i] = .{ .ptr = code.ptr, .len = code.len }; iovecs_i += 1; const off = self.getAddr(data_i, .d); data_i += code.len; @@ -851,7 +851,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node const code = atom.code.getOwnedCode().?; // unnamed consts must own their code log.debug("write unnamed const: ({s})", .{self.syms.items[atom.sym_index.?].name}); foff += code.len; - iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len }; + iovecs[iovecs_i] = .{ .ptr = code.ptr, .len = code.len }; iovecs_i += 1; const off = self.getAddr(data_i, .d); data_i += code.len; @@ -872,7 +872,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node const code = atom.code.getOwnedCode().?; log.debug("write anon decl: {s}", .{self.syms.items[atom.sym_index.?].name}); foff += code.len; - iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len }; + iovecs[iovecs_i] = .{ .ptr = code.ptr, .len = code.len }; iovecs_i += 1; const off = self.getAddr(data_i, .d); data_i += code.len; @@ -892,7 +892,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node const data_atom = if (meta.rodata_state != .unused) self.getAtomPtr(meta.rodata_atom) else continue; const code = data_atom.code.getOwnedCode().?; // lazy symbols must own their code foff += code.len; - iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len }; + iovecs[iovecs_i] = .{ .ptr = code.ptr, .len = code.len }; iovecs_i += 1; const off = self.getAddr(data_i, .d); data_i += code.len; @@ -933,9 +933,9 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node const syms = try sym_buf.toOwnedSlice(); defer gpa.free(syms); assert(2 + self.atomCount() - self.externCount() == iovecs_i); // we didn't write all the decls - iovecs[iovecs_i] = .{ .iov_base = syms.ptr, .iov_len = syms.len }; + iovecs[iovecs_i] = .{ .ptr = syms.ptr, .len = syms.len }; iovecs_i += 1; - iovecs[iovecs_i] = .{ .iov_base = linecountinfo.items.ptr, .iov_len = linecountinfo.items.len }; + iovecs[iovecs_i] = .{ .ptr = linecountinfo.items.ptr, .len = linecountinfo.items.len }; iovecs_i += 1; // generate the header self.hdr = .{ diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index c8fea56c1630..d8e9cc1434da 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3046,9 +3046,9 @@ fn writeToFile( } // finally, write the entire binary into the file. - var iovec = [_]std.posix.iovec_const{.{ - .iov_base = binary_bytes.items.ptr, - .iov_len = binary_bytes.items.len, + var iovec = [_]std.io.WriteBuffers{.{ + .ptr = binary_bytes.items.ptr, + .len = binary_bytes.items.len, }}; try wasm.base.file.?.writevAll(&iovec); } diff --git a/src/main.zig b/src/main.zig index be2083a0f8e4..7b4396f5a176 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3374,13 +3374,13 @@ fn buildOutputType( }); defer server.deinit(); - const conn = try server.accept(); - defer conn.stream.close(); + var conn = try server.accept(); + defer conn.stream().close(); try serve( comp, - .{ .handle = conn.stream.handle }, - .{ .handle = conn.stream.handle }, + .{ .handle = conn.socket.handle }, + .{ .handle = conn.socket.handle }, test_exec_args.items, self_exe_path, arg_mode, diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 8e4dd2c091e0..81d2b8f36f80 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -1016,11 +1016,10 @@ test "generic type constructed from inferred error set of unresolved function" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { - fn write(_: void, bytes: []const u8) !usize { - _ = bytes; + fn writev(_: void, _: []std.io.WriteBuffers) !usize { return 0; } - const T = std.io.Writer(void, @typeInfo(@typeInfo(@TypeOf(write)).Fn.return_type.?).ErrorUnion.error_set, write); + const T = std.io.Writer(void, @typeInfo(@typeInfo(@TypeOf(writev)).Fn.return_type.?).ErrorUnion.error_set, writev); fn writer() T { return .{ .context = {} }; }