From 1d0495678dc3729feb40939ddcaaef552efadbda Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 15 Feb 2025 22:17:30 -0800 Subject: [PATCH] fixes writeFile should be split into two --- lib/std/fs/File.zig | 30 +++++++++++--- lib/std/io/BufferedWriter.zig | 77 +++++++++++++++++++++++------------ lib/std/io/Writer.zig | 2 +- 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 75871dab32..f5901cea1c 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -1611,18 +1611,38 @@ const interface = struct { fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize { const file = opaqueToHandle(context); + var splat_buffer: [256]u8 = undefined; if (is_windows) { if (data.len == 1 and splat == 0) return 0; return windows.WriteFile(file, data[0], null); } - var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined; - const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, data.len)]; - for (iovecs, data[0..iovecs.len]) |*v, d| v.* = .{ + var iovecs: [max_buffers_len]std.posix.iovec_const = undefined; + var len: usize = @min(iovecs.len, data.len); + for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{ .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length. .len = d.len, }; - const send_iovecs = if (splat == 0) iovecs[0 .. iovecs.len - 1] else iovecs; - return std.posix.writev(file, send_iovecs); + switch (splat) { + 0 => return std.posix.writev(file, iovecs[0 .. len - 1]), + 1 => return std.posix.writev(file, iovecs[0..len]), + else => { + const pattern = data[data.len - 1]; + if (pattern.len == 1) { + const memset_len = @min(splat_buffer.len, splat); + const buf = splat_buffer[0..memset_len]; + @memset(buf, pattern[0]); + iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len }; + var remaining_splat = splat - buf.len; + while (remaining_splat > 0 and len < iovecs.len) { + iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len }; + remaining_splat -= splat_buffer.len; + len += 1; + } + return std.posix.writev(file, iovecs[0..len]); + } + }, + } + return std.posix.writev(file, iovecs[0..len]); } fn writeFile( diff --git a/lib/std/io/BufferedWriter.zig b/lib/std/io/BufferedWriter.zig index 6c3a7d5737..a1be3acc5c 100644 --- a/lib/std/io/BufferedWriter.zig +++ b/lib/std/io/BufferedWriter.zig @@ -76,7 +76,7 @@ pub fn flush(bw: *BufferedWriter) anyerror!void { /// The `data` parameter is mutable because this function needs to mutate the /// fields in order to handle partial writes from `Writer.VTable.writev`. -pub fn writevAll(bw: *BufferedWriter, data: []const []const u8) anyerror!void { +pub fn writevAll(bw: *BufferedWriter, data: [][]const u8) anyerror!void { var i: usize = 0; while (true) { var n = try passthru_writeSplat(bw, data[i..], 1); @@ -164,7 +164,7 @@ fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usi @branchHint(.likely); @memset(buffer[end..new_end], pattern[0]); bw.end = new_end; - return end - start_end; + return new_end - start_end; } buffers[0] = buffer[0..end]; buffers[1] = pattern; @@ -186,8 +186,8 @@ fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usi while (end < new_end) : (end += pattern.len) { @memcpy(buffer[end..][0..pattern.len], pattern); } - bw.end = end; - return end - start_end; + bw.end = new_end; + return new_end - start_end; } buffers[0] = buffer[0..end]; buffers[1] = pattern; @@ -471,32 +471,55 @@ pub const WriteFileOptions = struct { pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOptions) anyerror!void { const headers_and_trailers = options.headers_and_trailers; const headers = headers_and_trailers[0..options.headers_len]; - var len = options.len; - var i: usize = 0; - var offset = options.offset; - if (len == .zero) return writevAll(bw, headers_and_trailers[i..]); - while (i < headers_and_trailers.len) { - var n = try writeFile(bw, file, offset, len, headers_and_trailers[i..], headers.len - i); - while (i < headers.len and n >= headers[i].len) { - n -= headers[i].len; - i += 1; - } - if (i < headers.len) { - headers[i] = headers[i][n..]; - continue; - } - if (n >= len.int()) { - n -= len.int(); - while (n >= headers_and_trailers[i].len) { - n -= headers_and_trailers[i].len; + if (options.len == .zero) return writevAll(bw, headers_and_trailers); + if (options.len == .entire_file) { + // When reading the whole file, we cannot include the trailers in the + // call that reads from the file handle, because we have no way to + // determine whether a partial write is past the end of the file or + // not. + var i: usize = 0; + var offset = options.offset; + while (true) { + var n = try writeFile(bw, file, offset, .entire_file, headers[i..], headers.len - i); + while (i < headers.len and n >= headers[i].len) { + n -= headers[i].len; i += 1; - if (i >= headers_and_trailers.len) return; } - headers_and_trailers[i] = headers_and_trailers[i][n..]; - return writevAll(bw, headers_and_trailers[i..]); + if (i < headers.len) { + headers[i] = headers[i][n..]; + continue; + } + if (n == 0) break; + offset += n; + } + } else { + var len = options.len.int(); + var i: usize = 0; + var offset = options.offset; + while (true) { + var n = try writeFile(bw, file, offset, .init(len), headers_and_trailers[i..], headers.len - i); + while (i < headers.len and n >= headers[i].len) { + n -= headers[i].len; + i += 1; + } + if (i < headers.len) { + headers[i] = headers[i][n..]; + continue; + } + if (n >= len) { + n -= len; + if (i >= headers_and_trailers.len) return; + while (n >= headers_and_trailers[i].len) { + n -= headers_and_trailers[i].len; + i += 1; + if (i >= headers_and_trailers.len) return; + } + headers_and_trailers[i] = headers_and_trailers[i][n..]; + return writevAll(bw, headers_and_trailers[i..]); + } + offset += n; + len -= n; } - offset += n; - len = if (len == .entire_file) .entire_file else .init(len.int() - n); } } diff --git a/lib/std/io/Writer.zig b/lib/std/io/Writer.zig index 5f52cc9050..24254b75d6 100644 --- a/lib/std/io/Writer.zig +++ b/lib/std/io/Writer.zig @@ -47,7 +47,7 @@ pub const VTable = struct { pub fn init(integer: u64) FileLen { const result: FileLen = @enumFromInt(integer); - assert(result != .none); + assert(result != .entire_file); return result; }