diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 1ebd26a59f9edb2533bcba3f6a72663092caeb4f..3c2525a07fada44c26c3435668fc9180e71449ef 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -2803,7 +2803,7 @@ pub fn dumpBadGetPathHelp( src_builder: *Build, asking_step: ?*Step, ) anyerror!void { - var buffered_writer = stderr.unbufferedWriter(); + var buffered_writer = stderr.writer().unbuffered(); const w = &buffered_writer; try w.print( \\getPath() was called on a GeneratedFile that wasn't built yet. diff --git a/lib/std/Build/Fuzz/WebServer.zig b/lib/std/Build/Fuzz/WebServer.zig index ab44d4e7af6f7f01198e735ed32a6ec076cb563b..d0baa61d181bb80c42572bfd04d2136982f13941 100644 --- a/lib/std/Build/Fuzz/WebServer.zig +++ b/lib/std/Build/Fuzz/WebServer.zig @@ -522,7 +522,8 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { var cwd_cache: ?[]const u8 = null; - var archiver = std.tar.writer(response.writer()); + var response_writer = response.writer().unbuffered(); + var archiver: std.tar.Writer = .{ .underlying_writer = &response_writer }; for (deduped_paths) |joined_path| { var file = joined_path.root_dir.handle.openFile(joined_path.sub_path, .{}) catch |err| { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 68dbbcdd183c72dac041422ec41975cf1eacd94d..4d5d8e93e584b72a1a11f13a9948111d65fc0d6a 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -287,7 +287,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T { /// For debugging purposes, prints identifying information about this Step. pub fn dump(step: *Step, file: std.fs.File) void { - var bw = file.unbufferedWriter(); + var bw = file.writer().unbuffered(); const tty_config = std.io.tty.detectConfig(file); const debug_info = std.debug.getSelfDebugInfo() catch |err| { bw.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{ diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 7e5dc175baa2be1a31cae293e395425dbb06ee4f..6162da9e0db83c24eaf42b295c3f46f708dceae5 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -55,14 +55,14 @@ fn printType( switch (T) { []const []const u8 => { if (name) |payload| { - try out.print(gpa, "pub const {}: []const []const u8 = ", .{std.zig.fmtId(payload)}); + try out.print(gpa, "pub const {f}: []const []const u8 = ", .{std.zig.fmtId(payload)}); } try out.appendSlice(gpa, "&[_][]const u8{\n"); for (value) |slice| { try out.appendNTimes(gpa, ' ', indent); - try out.print(gpa, " \"{}\",\n", .{std.zig.fmtEscapes(slice)}); + try out.print(gpa, " \"{f}\",\n", .{std.zig.fmtEscapes(slice)}); } if (name != null) { @@ -75,7 +75,9 @@ fn printType( }, []const u8 => { if (name) |some| { - try out.print(gpa, "pub const {}: []const u8 = \"{}\";", .{ std.zig.fmtId(some), std.zig.fmtEscapes(value) }); + try out.print(gpa, "pub const {f}: []const u8 = \"{f}\";", .{ + std.zig.fmtId(some), std.zig.fmtEscapes(value), + }); } else { try out.print(gpa, "\"{}\",", .{std.zig.fmtEscapes(value)}); } @@ -293,7 +295,7 @@ fn printEnum( inline for (val.fields) |field| { try out.appendNTimes(gpa, ' ', indent); - try out.print(gpa, " {p} = {d},\n", .{ std.zig.fmtId(field.name), field.value }); + try out.print(gpa, " {fp} = {d},\n", .{ std.zig.fmtId(field.name), field.value }); } if (!val.is_exhaustive) { diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 18d37e6bf62a9fb44cb5da407eb95919ea0965ee..26e5cab1820b861ad69b8b1b1cfb36ce3907f403 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -305,8 +305,8 @@ pub const Os = struct { ver: WindowsVersion, comptime fmt_str: []const u8, _: std.fmt.FormatOptions, - writer: anytype, - ) @TypeOf(writer).Error!void { + writer: *std.io.BufferedWriter, + ) anyerror!void { const maybe_name = std.enums.tagName(WindowsVersion, ver); if (comptime std.mem.eql(u8, fmt_str, "s")) { if (maybe_name) |name| diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7614492fed549404010008ac77a9a0b6c2ed5749..d0e478b18ac89b795b1dcdb0e4a86e1329e7e2d5 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -212,7 +212,7 @@ pub fn unlockStdErr() void { /// in fact unbuffered and does not need to be flushed. pub fn lockStdErr2() std.io.BufferedWriter { std.Progress.lockStdErr(); - return io.getStdErr().unbufferedWriter(); + return io.getStdErr().writer().unbuffered(); } /// Print to stderr, unbuffered, and silently returning on failure. Intended @@ -1468,7 +1468,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa } fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { - var stderr = io.getStdErr().unbufferedWriter(); + var stderr = io.getStdErr().writer().unbuffered(); _ = switch (sig) { posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL // x86_64 doesn't have a full 64-bit virtual address space. @@ -1564,7 +1564,7 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: } fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void { - var stderr = io.getStdErr().unbufferedWriter(); + var stderr = io.getStdErr().writer().unbuffered(); _ = switch (msg) { 0 => stderr.print("{s}\n", .{label.?}), 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index b6568ce008720f14371660a6e548cb385936fcba..7016497ff0ac600a12c276f393cc30d88c3542f1 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -853,7 +853,7 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr /// Count the characters needed for format. pub fn count(comptime fmt: []const u8, args: anytype) u64 { var counting_writer: std.io.CountingWriter = .{ .child_writer = std.io.null_writer }; - var bw = counting_writer.unbufferedWriter(); + var bw = counting_writer.writer().unbuffered(); bw.print(fmt, args) catch unreachable; return counting_writer.bytes_written; } diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 872b9ba02189f1f3313f8ed34377184d96045636..8b5f2ffc8e8e518d39734b0f6d14a617d8daba09 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -1602,13 +1602,6 @@ pub fn writer(file: File) std.io.Writer { }; } -pub fn unbufferedWriter(file: File) std.io.BufferedWriter { - return .{ - .buffer = &.{}, - .unbuffered_writer = writer(file), - }; -} - const interface = struct { /// Number of slices to store on the stack, when trying to send as many byte /// vectors through the underlying write calls as possible. diff --git a/lib/std/io/BufferedWriter.zig b/lib/std/io/BufferedWriter.zig index dfaeb3aaa3a80dc6809355268f552e5ccdcc17c8..b23e190af32b8184ac0a38ae87fc8e98b41833da 100644 --- a/lib/std/io/BufferedWriter.zig +++ b/lib/std/io/BufferedWriter.zig @@ -620,13 +620,14 @@ pub fn printValue( } if (std.meta.hasMethod(T, "format")) { - if (fmt.len == 0) { - // @deprecated() + if (fmt.len > 0 and fmt[0] == 'f') { + return value.format(fmt[1..], options, bw); + } else { + //@deprecated(); // After 0.14.0 is tagged, uncomment this next line: - //@compileError("ambiguous format string; specify {f} to call print method, or {any} to skip it"); + //@compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it"); + //and then delete the `hasMethod` condition return value.format(fmt, options, bw); - } else if (fmt[0] == 'f') { - return value.format(fmt[1..], options, bw); } } diff --git a/lib/std/io/CountingWriter.zig b/lib/std/io/CountingWriter.zig index 21e0ac132c025e8ca357cfb58c11fc2c32ca289f..fcc9fdfb6218d714b63ba32fb90b9530b92b0d51 100644 --- a/lib/std/io/CountingWriter.zig +++ b/lib/std/io/CountingWriter.zig @@ -22,13 +22,6 @@ pub fn writer(cw: *CountingWriter) Writer { }; } -pub fn unbufferedWriter(cw: *CountingWriter) std.io.BufferedWriter { - return .{ - .buffer = &.{}, - .unbuffered_writer = writer(cw), - }; -} - fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize { const cw: *CountingWriter = @alignCast(@ptrCast(context)); const n = try cw.child_writer.writeSplat(data, splat); @@ -52,7 +45,7 @@ fn passthru_writeFile( test CountingWriter { var cw: CountingWriter = .{ .child_writer = std.io.null_writer }; - var bw = cw.unbufferedWriter(); + var bw = cw.writer().unbuffered(); const bytes = "yay"; try bw.writeAll(bytes); try testing.expect(cw.bytes_written == bytes.len); diff --git a/lib/std/io/Writer.zig b/lib/std/io/Writer.zig index 24254b75d604845af34a2a5a4126d6fe6650dfa8..75a1668ac9cf0def6a47a70cf0dfb594950f4cd2 100644 --- a/lib/std/io/Writer.zig +++ b/lib/std/io/Writer.zig @@ -86,12 +86,6 @@ pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void { while (index < bytes.len) index += try w.vtable.writeSplat(w.context, &.{bytes[index..]}, 1); } -///// Directly calls `writeAll` many times to render the formatted text. To -///// enable buffering, call `std.io.BufferedWriter.print` instead. -//pub fn unbufferedPrint(w: Writer, comptime format: []const u8, args: anytype) anyerror!void { -// return std.fmt.format(w, format, args); -//} - /// The `data` parameter is mutable because this function needs to mutate the /// fields in order to handle partial writes from `VTable.writev`. pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void { @@ -106,3 +100,10 @@ pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void { data[i] = data[i][n..]; } } + +pub fn unbuffered(w: Writer) std.io.BufferedWriter { + return .{ + .buffer = &.{}, + .unbuffered_writer = w, + }; +} diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index ef00319948d3b888805486549437945bc4f096dc..b3224525da8ec46940c39f46eeca20eb29a3f084 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -1004,7 +1004,7 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { fn writeIntFd(fd: i32, value: ErrInt) !void { const file: File = .{ .handle = fd }; - var bw = file.unbufferedWriter(); + var bw = file.writer().unbuffered(); bw.writeInt(u64, @intCast(value), .little) catch return error.SystemResources; } diff --git a/lib/std/tar.zig b/lib/std/tar.zig index f3aba4d381a9e8900c74e7b21dcf01f9d2e3ef33..a8b448ffeaf7dfbddb42150432610ff688fdc258 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -19,7 +19,7 @@ const std = @import("std"); const assert = std.debug.assert; const testing = std.testing; -pub const writer = @import("tar/writer.zig").writer; +pub const Writer = @import("tar/Writer.zig"); /// Provide this to receive detailed error messages. /// When this is provided, some errors which would otherwise be returned @@ -604,7 +604,7 @@ fn PaxIterator(comptime ReaderType: type) type { } fn readUntil(self: *Self, delimiter: u8) ![]const u8 { - var fbs = std.io.fixedBufferStream(&self.scratch); + var fbs: std.io.FixedBufferStream = .{ .buffer = &self.scratch }; try self.reader.streamUntilDelimiter(fbs.writer(), delimiter, null); return fbs.getWritten(); } @@ -855,7 +855,7 @@ test PaxIterator { test { _ = @import("tar/test.zig"); - _ = @import("tar/writer.zig"); + _ = Writer; _ = Diagnostics; } diff --git a/lib/std/tar/Writer.zig b/lib/std/tar/Writer.zig new file mode 100644 index 0000000000000000000000000000000000000000..76f017f2dca55afd4824aaa309062764c2f9f289 --- /dev/null +++ b/lib/std/tar/Writer.zig @@ -0,0 +1,485 @@ +const std = @import("std"); +const assert = std.debug.assert; +const testing = std.testing; +const Writer = @This(); + +const block_size = @sizeOf(Header); +const empty_block: [block_size]u8 = [_]u8{0} ** block_size; + +/// Options for writing file/dir/link. If left empty 0o664 is used for +/// file mode and current time for mtime. +pub const Options = struct { + /// File system permission mode. + mode: u32 = 0, + /// File system modification time. + mtime: u64 = 0, +}; +const Self = @This(); + +underlying_writer: *std.io.BufferedWriter, +prefix: []const u8 = "", +mtime_now: u64 = 0, + +/// Sets prefix for all other write* method paths. +pub fn setRoot(self: *Self, root: []const u8) !void { + if (root.len > 0) + try self.writeDir(root, .{}); + + self.prefix = root; +} + +/// Writes directory. +pub fn writeDir(self: *Self, sub_path: []const u8, opt: Options) !void { + try self.writeHeader(.directory, sub_path, "", 0, opt); +} + +/// Writes file system file. +pub fn writeFile(self: *Self, sub_path: []const u8, file: std.fs.File) !void { + const stat = try file.stat(); + const mtime: u64 = @intCast(@divFloor(stat.mtime, std.time.ns_per_s)); + + var header = Header{}; + try self.setPath(&header, sub_path); + try header.setSize(stat.size); + try header.setMtime(mtime); + try header.write(self.underlying_writer); + + try self.underlying_writer.writeFileAll(file, .{ .len = .init(stat.size) }); + try self.writePadding(stat.size); +} + +/// Writes file reading file content from `reader`. Number of bytes in +/// reader must be equal to `size`. +pub fn writeFileStream(self: *Self, sub_path: []const u8, size: usize, reader: anytype, opt: Options) !void { + try self.writeHeader(.regular, sub_path, "", @intCast(size), opt); + + var counting_reader = std.io.countingReader(reader); + var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init(); + try fifo.pump(counting_reader.reader(), self.underlying_writer); + if (counting_reader.bytes_read != size) return error.WrongReaderSize; + try self.writePadding(size); +} + +/// Writes file using bytes buffer `content` for size and file content. +pub fn writeFileBytes(self: *Self, sub_path: []const u8, content: []const u8, opt: Options) !void { + try self.writeHeader(.regular, sub_path, "", @intCast(content.len), opt); + try self.underlying_writer.writeAll(content); + try self.writePadding(content.len); +} + +/// Writes symlink. +pub fn writeLink(self: *Self, sub_path: []const u8, link_name: []const u8, opt: Options) !void { + try self.writeHeader(.symbolic_link, sub_path, link_name, 0, opt); +} + +/// Writes fs.Dir.WalkerEntry. Uses `mtime` from file system entry and +/// default for entry mode . +pub fn writeEntry(self: *Self, entry: std.fs.Dir.Walker.Entry) !void { + switch (entry.kind) { + .directory => { + try self.writeDir(entry.path, .{ .mtime = try entryMtime(entry) }); + }, + .file => { + var file = try entry.dir.openFile(entry.basename, .{}); + defer file.close(); + try self.writeFile(entry.path, file); + }, + .sym_link => { + var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; + const link_name = try entry.dir.readLink(entry.basename, &link_name_buffer); + try self.writeLink(entry.path, link_name, .{ .mtime = try entryMtime(entry) }); + }, + else => { + return error.UnsupportedWalkerEntryKind; + }, + } +} + +fn writeHeader( + self: *Self, + typeflag: Header.FileType, + sub_path: []const u8, + link_name: []const u8, + size: u64, + opt: Options, +) !void { + var header = Header.init(typeflag); + try self.setPath(&header, sub_path); + try header.setSize(size); + try header.setMtime(if (opt.mtime != 0) opt.mtime else self.mtimeNow()); + if (opt.mode != 0) + try header.setMode(opt.mode); + if (typeflag == .symbolic_link) + header.setLinkname(link_name) catch |err| switch (err) { + error.NameTooLong => try self.writeExtendedHeader(.gnu_long_link, &.{link_name}), + else => return err, + }; + try header.write(self.underlying_writer); +} + +fn mtimeNow(self: *Self) u64 { + if (self.mtime_now == 0) + self.mtime_now = @intCast(std.time.timestamp()); + return self.mtime_now; +} + +fn entryMtime(entry: std.fs.Dir.Walker.Entry) !u64 { + const stat = try entry.dir.statFile(entry.basename); + return @intCast(@divFloor(stat.mtime, std.time.ns_per_s)); +} + +/// Writes path in posix header, if don't fit (in name+prefix; 100+155 +/// bytes) writes it in gnu extended header. +fn setPath(self: *Self, header: *Header, sub_path: []const u8) !void { + header.setPath(self.prefix, sub_path) catch |err| switch (err) { + error.NameTooLong => { + // write extended header + const buffers: []const []const u8 = if (self.prefix.len == 0) + &.{sub_path} + else + &.{ self.prefix, "/", sub_path }; + try self.writeExtendedHeader(.gnu_long_name, buffers); + }, + else => return err, + }; +} + +/// Writes gnu extended header: gnu_long_name or gnu_long_link. +fn writeExtendedHeader(self: *Self, typeflag: Header.FileType, buffers: []const []const u8) !void { + var len: usize = 0; + for (buffers) |buf| + len += buf.len; + + var header = Header.init(typeflag); + try header.setSize(len); + try header.write(self.underlying_writer); + for (buffers) |buf| + try self.underlying_writer.writeAll(buf); + try self.writePadding(len); +} + +fn writePadding(self: *Self, bytes: u64) !void { + const pos: usize = @intCast(bytes % block_size); + if (pos == 0) return; + try self.underlying_writer.writeAll(empty_block[pos..]); +} + +/// Tar should finish with two zero blocks, but 'reasonable system must +/// not assume that such a block exists when reading an archive' (from +/// reference). In practice it is safe to skip this finish. +pub fn finish(self: *Self) !void { + try self.underlying_writer.writeAll(&empty_block); + try self.underlying_writer.writeAll(&empty_block); +} + +/// A struct that is exactly 512 bytes and matches tar file format. This is +/// intended to be used for outputting tar files; for parsing there is +/// `std.tar.Header`. +const Header = extern struct { + // This struct was originally copied from + // https://github.com/mattnite/tar/blob/main/src/main.zig which is MIT + // licensed. + // + // The name, linkname, magic, uname, and gname are null-terminated character + // strings. All other fields are zero-filled octal numbers in ASCII. Each + // numeric field of width w contains w minus 1 digits, and a null. + // Reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html + // POSIX header: byte offset + name: [100]u8 = [_]u8{0} ** 100, // 0 + mode: [7:0]u8 = default_mode.file, // 100 + uid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 108 + gid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 116 + size: [11:0]u8 = [_:0]u8{'0'} ** 11, // 124 + mtime: [11:0]u8 = [_:0]u8{'0'} ** 11, // 136 + checksum: [7:0]u8 = [_:0]u8{' '} ** 7, // 148 + typeflag: FileType = .regular, // 156 + linkname: [100]u8 = [_]u8{0} ** 100, // 157 + magic: [6]u8 = [_]u8{ 'u', 's', 't', 'a', 'r', 0 }, // 257 + version: [2]u8 = [_]u8{ '0', '0' }, // 263 + uname: [32]u8 = [_]u8{0} ** 32, // unused 265 + gname: [32]u8 = [_]u8{0} ** 32, // unused 297 + devmajor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 329 + devminor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 337 + prefix: [155]u8 = [_]u8{0} ** 155, // 345 + pad: [12]u8 = [_]u8{0} ** 12, // unused 500 + + pub const FileType = enum(u8) { + regular = '0', + symbolic_link = '2', + directory = '5', + gnu_long_name = 'L', + gnu_long_link = 'K', + }; + + const default_mode = struct { + const file = [_:0]u8{ '0', '0', '0', '0', '6', '6', '4' }; // 0o664 + const dir = [_:0]u8{ '0', '0', '0', '0', '7', '7', '5' }; // 0o775 + const sym_link = [_:0]u8{ '0', '0', '0', '0', '7', '7', '7' }; // 0o777 + const other = [_:0]u8{ '0', '0', '0', '0', '0', '0', '0' }; // 0o000 + }; + + pub fn init(typeflag: FileType) Header { + return .{ + .typeflag = typeflag, + .mode = switch (typeflag) { + .directory => default_mode.dir, + .symbolic_link => default_mode.sym_link, + .regular => default_mode.file, + else => default_mode.other, + }, + }; + } + + pub fn setSize(self: *Header, size: u64) !void { + try octal(&self.size, size); + } + + fn octal(buf: []u8, value: u64) !void { + var remainder: u64 = value; + var pos: usize = buf.len; + while (remainder > 0 and pos > 0) { + pos -= 1; + const c: u8 = @as(u8, @intCast(remainder % 8)) + '0'; + buf[pos] = c; + remainder /= 8; + if (pos == 0 and remainder > 0) return error.OctalOverflow; + } + } + + pub fn setMode(self: *Header, mode: u32) !void { + try octal(&self.mode, mode); + } + + // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time. + // mtime == 0 will use current time + pub fn setMtime(self: *Header, mtime: u64) !void { + try octal(&self.mtime, mtime); + } + + pub fn updateChecksum(self: *Header) !void { + var checksum: usize = ' '; // other 7 self.checksum bytes are initialized to ' ' + for (std.mem.asBytes(self)) |val| + checksum += val; + try octal(&self.checksum, checksum); + } + + pub fn write(self: *Header, output_writer: anytype) !void { + try self.updateChecksum(); + try output_writer.writeAll(std.mem.asBytes(self)); + } + + pub fn setLinkname(self: *Header, link: []const u8) !void { + if (link.len > self.linkname.len) return error.NameTooLong; + @memcpy(self.linkname[0..link.len], link); + } + + pub fn setPath(self: *Header, prefix: []const u8, sub_path: []const u8) !void { + const max_prefix = self.prefix.len; + const max_name = self.name.len; + const sep = std.fs.path.sep_posix; + + if (prefix.len + sub_path.len > max_name + max_prefix or prefix.len > max_prefix) + return error.NameTooLong; + + // both fit into name + if (prefix.len > 0 and prefix.len + sub_path.len < max_name) { + @memcpy(self.name[0..prefix.len], prefix); + self.name[prefix.len] = sep; + @memcpy(self.name[prefix.len + 1 ..][0..sub_path.len], sub_path); + return; + } + + // sub_path fits into name + // there is no prefix or prefix fits into prefix + if (sub_path.len <= max_name) { + @memcpy(self.name[0..sub_path.len], sub_path); + @memcpy(self.prefix[0..prefix.len], prefix); + return; + } + + if (prefix.len > 0) { + @memcpy(self.prefix[0..prefix.len], prefix); + self.prefix[prefix.len] = sep; + } + const prefix_pos = if (prefix.len > 0) prefix.len + 1 else 0; + + // add as much to prefix as you can, must split at / + const prefix_remaining = max_prefix - prefix_pos; + if (std.mem.lastIndexOf(u8, sub_path[0..@min(prefix_remaining, sub_path.len)], &.{'/'})) |sep_pos| { + @memcpy(self.prefix[prefix_pos..][0..sep_pos], sub_path[0..sep_pos]); + if ((sub_path.len - sep_pos - 1) > max_name) return error.NameTooLong; + @memcpy(self.name[0..][0 .. sub_path.len - sep_pos - 1], sub_path[sep_pos + 1 ..]); + return; + } + + return error.NameTooLong; + } + + comptime { + assert(@sizeOf(Header) == 512); + } + + test "setPath" { + const cases = [_]struct { + in: []const []const u8, + out: []const []const u8, + }{ + .{ + .in = &.{ "", "123456789" }, + .out = &.{ "", "123456789" }, + }, + // can fit into name + .{ + .in = &.{ "prefix", "sub_path" }, + .out = &.{ "", "prefix/sub_path" }, + }, + // no more both fits into name + .{ + .in = &.{ "prefix", "0123456789/" ** 8 ++ "basename" }, + .out = &.{ "prefix", "0123456789/" ** 8 ++ "basename" }, + }, + // put as much as you can into prefix the rest goes into name + .{ + .in = &.{ "prefix", "0123456789/" ** 10 ++ "basename" }, + .out = &.{ "prefix/" ++ "0123456789/" ** 9 ++ "0123456789", "basename" }, + }, + + .{ + .in = &.{ "prefix", "0123456789/" ** 15 ++ "basename" }, + .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/0123456789/basename" }, + }, + .{ + .in = &.{ "prefix", "0123456789/" ** 21 ++ "basename" }, + .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/" ** 8 ++ "basename" }, + }, + .{ + .in = &.{ "", "012345678/" ** 10 ++ "foo" }, + .out = &.{ "012345678/" ** 9 ++ "012345678", "foo" }, + }, + }; + + for (cases) |case| { + var header = Header.init(.regular); + try header.setPath(case.in[0], case.in[1]); + try testing.expectEqualStrings(case.out[0], str(&header.prefix)); + try testing.expectEqualStrings(case.out[1], str(&header.name)); + } + + const error_cases = [_]struct { + in: []const []const u8, + }{ + // basename can't fit into name (106 characters) + .{ .in = &.{ "zig", "test/cases/compile_errors/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig" } }, + // cant fit into 255 + sep + .{ .in = &.{ "prefix", "0123456789/" ** 22 ++ "basename" } }, + // can fit but sub_path can't be split (there is no separator) + .{ .in = &.{ "prefix", "0123456789" ** 10 ++ "a" } }, + .{ .in = &.{ "prefix", "0123456789" ** 14 ++ "basename" } }, + }; + + for (error_cases) |case| { + var header = Header.init(.regular); + try testing.expectError( + error.NameTooLong, + header.setPath(case.in[0], case.in[1]), + ); + } + } + + // Breaks string on first null character. + fn str(s: []const u8) []const u8 { + for (s, 0..) |c, i| { + if (c == 0) return s[0..i]; + } + return s; + } +}; + +test { + _ = Header; +} + +test "write files" { + const files = [_]struct { + path: []const u8, + content: []const u8, + }{ + .{ .path = "foo", .content = "bar" }, + .{ .path = "a12345678/" ** 10 ++ "foo", .content = "a" ** 511 }, + .{ .path = "b12345678/" ** 24 ++ "foo", .content = "b" ** 512 }, + .{ .path = "c12345678/" ** 25 ++ "foo", .content = "c" ** 513 }, + .{ .path = "d12345678/" ** 51 ++ "foo", .content = "d" ** 1025 }, + .{ .path = "e123456789" ** 11, .content = "e" }, + }; + + var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; + var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; + + // with root + { + const root = "root"; + + var output: std.io.AllocatingWriter = undefined; + var wrt: Writer = .{ .underlying_writer = output.init(testing.allocator) }; + defer output.deinit(); + try wrt.setRoot(root); + for (files) |file| + try wrt.writeFileBytes(file.path, file.content, .{}); + + var input: std.io.FixedBufferStream = .{ .buffer = output.getWritten() }; + var iter = std.tar.iterator( + input.reader(), + .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer }, + ); + + // first entry is directory with prefix + { + const actual = (try iter.next()).?; + try testing.expectEqualStrings(root, actual.name); + try testing.expectEqual(std.tar.FileKind.directory, actual.kind); + } + + var i: usize = 0; + while (try iter.next()) |actual| { + defer i += 1; + const expected = files[i]; + try testing.expectEqualStrings(root, actual.name[0..root.len]); + try testing.expectEqual('/', actual.name[root.len..][0]); + try testing.expectEqualStrings(expected.path, actual.name[root.len + 1 ..]); + + var content = std.ArrayList(u8).init(testing.allocator); + defer content.deinit(); + try actual.writeAll(content.writer()); + try testing.expectEqualSlices(u8, expected.content, content.items); + } + } + // without root + { + var output: std.io.AllocatingWriter = undefined; + var wrt: Writer = .{ .underlying_writer = output.init(testing.allocator) }; + defer output.deinit(); + for (files) |file| { + var content = std.io.fixedBufferStream(file.content); + try wrt.writeFileStream(file.path, file.content.len, content.reader(), .{}); + } + + var input: std.io.FixedBufferStream = .{ .buffer = output.getWritten() }; + var iter = std.tar.iterator( + input.reader(), + .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer }, + ); + + var i: usize = 0; + while (try iter.next()) |actual| { + defer i += 1; + const expected = files[i]; + try testing.expectEqualStrings(expected.path, actual.name); + + var content = std.ArrayList(u8).init(testing.allocator); + defer content.deinit(); + try actual.writeAll(content.writer()); + try testing.expectEqualSlices(u8, expected.content, content.items); + } + try wrt.finish(); + } +} diff --git a/lib/std/tar/writer.zig b/lib/std/tar/writer.zig deleted file mode 100644 index aae368996cdfc210080da88217bf3ec89dc6d6e8..0000000000000000000000000000000000000000 --- a/lib/std/tar/writer.zig +++ /dev/null @@ -1,486 +0,0 @@ -const std = @import("std"); -const assert = std.debug.assert; -const testing = std.testing; - -pub const Writer = struct { - const block_size = @sizeOf(Header); - const empty_block: [block_size]u8 = [_]u8{0} ** block_size; - - /// Options for writing file/dir/link. If left empty 0o664 is used for - /// file mode and current time for mtime. - pub const Options = struct { - /// File system permission mode. - mode: u32 = 0, - /// File system modification time. - mtime: u64 = 0, - }; - const Self = @This(); - - underlying_writer: *std.io.BufferedWriter, - prefix: []const u8 = "", - mtime_now: u64 = 0, - - /// Sets prefix for all other write* method paths. - pub fn setRoot(self: *Self, root: []const u8) !void { - if (root.len > 0) - try self.writeDir(root, .{}); - - self.prefix = root; - } - - /// Writes directory. - pub fn writeDir(self: *Self, sub_path: []const u8, opt: Options) !void { - try self.writeHeader(.directory, sub_path, "", 0, opt); - } - - /// Writes file system file. - pub fn writeFile(self: *Self, sub_path: []const u8, file: std.fs.File) !void { - const stat = try file.stat(); - const mtime: u64 = @intCast(@divFloor(stat.mtime, std.time.ns_per_s)); - - var header = Header{}; - try self.setPath(&header, sub_path); - try header.setSize(stat.size); - try header.setMtime(mtime); - try header.write(self.underlying_writer); - - try self.underlying_writer.writeFileAll(file, .{ .len = .init(stat.size) }); - try self.writePadding(stat.size); - } - - /// Writes file reading file content from `reader`. Number of bytes in - /// reader must be equal to `size`. - pub fn writeFileStream(self: *Self, sub_path: []const u8, size: usize, reader: anytype, opt: Options) !void { - try self.writeHeader(.regular, sub_path, "", @intCast(size), opt); - - var counting_reader = std.io.countingReader(reader); - var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init(); - try fifo.pump(counting_reader.reader(), self.underlying_writer); - if (counting_reader.bytes_read != size) return error.WrongReaderSize; - try self.writePadding(size); - } - - /// Writes file using bytes buffer `content` for size and file content. - pub fn writeFileBytes(self: *Self, sub_path: []const u8, content: []const u8, opt: Options) !void { - try self.writeHeader(.regular, sub_path, "", @intCast(content.len), opt); - try self.underlying_writer.writeAll(content); - try self.writePadding(content.len); - } - - /// Writes symlink. - pub fn writeLink(self: *Self, sub_path: []const u8, link_name: []const u8, opt: Options) !void { - try self.writeHeader(.symbolic_link, sub_path, link_name, 0, opt); - } - - /// Writes fs.Dir.WalkerEntry. Uses `mtime` from file system entry and - /// default for entry mode . - pub fn writeEntry(self: *Self, entry: std.fs.Dir.Walker.Entry) !void { - switch (entry.kind) { - .directory => { - try self.writeDir(entry.path, .{ .mtime = try entryMtime(entry) }); - }, - .file => { - var file = try entry.dir.openFile(entry.basename, .{}); - defer file.close(); - try self.writeFile(entry.path, file); - }, - .sym_link => { - var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; - const link_name = try entry.dir.readLink(entry.basename, &link_name_buffer); - try self.writeLink(entry.path, link_name, .{ .mtime = try entryMtime(entry) }); - }, - else => { - return error.UnsupportedWalkerEntryKind; - }, - } - } - - fn writeHeader( - self: *Self, - typeflag: Header.FileType, - sub_path: []const u8, - link_name: []const u8, - size: u64, - opt: Options, - ) !void { - var header = Header.init(typeflag); - try self.setPath(&header, sub_path); - try header.setSize(size); - try header.setMtime(if (opt.mtime != 0) opt.mtime else self.mtimeNow()); - if (opt.mode != 0) - try header.setMode(opt.mode); - if (typeflag == .symbolic_link) - header.setLinkname(link_name) catch |err| switch (err) { - error.NameTooLong => try self.writeExtendedHeader(.gnu_long_link, &.{link_name}), - else => return err, - }; - try header.write(self.underlying_writer); - } - - fn mtimeNow(self: *Self) u64 { - if (self.mtime_now == 0) - self.mtime_now = @intCast(std.time.timestamp()); - return self.mtime_now; - } - - fn entryMtime(entry: std.fs.Dir.Walker.Entry) !u64 { - const stat = try entry.dir.statFile(entry.basename); - return @intCast(@divFloor(stat.mtime, std.time.ns_per_s)); - } - - /// Writes path in posix header, if don't fit (in name+prefix; 100+155 - /// bytes) writes it in gnu extended header. - fn setPath(self: *Self, header: *Header, sub_path: []const u8) !void { - header.setPath(self.prefix, sub_path) catch |err| switch (err) { - error.NameTooLong => { - // write extended header - const buffers: []const []const u8 = if (self.prefix.len == 0) - &.{sub_path} - else - &.{ self.prefix, "/", sub_path }; - try self.writeExtendedHeader(.gnu_long_name, buffers); - }, - else => return err, - }; - } - - /// Writes gnu extended header: gnu_long_name or gnu_long_link. - fn writeExtendedHeader(self: *Self, typeflag: Header.FileType, buffers: []const []const u8) !void { - var len: usize = 0; - for (buffers) |buf| - len += buf.len; - - var header = Header.init(typeflag); - try header.setSize(len); - try header.write(self.underlying_writer); - for (buffers) |buf| - try self.underlying_writer.writeAll(buf); - try self.writePadding(len); - } - - fn writePadding(self: *Self, bytes: u64) !void { - const pos: usize = @intCast(bytes % block_size); - if (pos == 0) return; - try self.underlying_writer.writeAll(empty_block[pos..]); - } - - /// Tar should finish with two zero blocks, but 'reasonable system must - /// not assume that such a block exists when reading an archive' (from - /// reference). In practice it is safe to skip this finish. - pub fn finish(self: *Self) !void { - try self.underlying_writer.writeAll(&empty_block); - try self.underlying_writer.writeAll(&empty_block); - } -}; - -/// A struct that is exactly 512 bytes and matches tar file format. This is -/// intended to be used for outputting tar files; for parsing there is -/// `std.tar.Header`. -const Header = extern struct { - // This struct was originally copied from - // https://github.com/mattnite/tar/blob/main/src/main.zig which is MIT - // licensed. - // - // The name, linkname, magic, uname, and gname are null-terminated character - // strings. All other fields are zero-filled octal numbers in ASCII. Each - // numeric field of width w contains w minus 1 digits, and a null. - // Reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html - // POSIX header: byte offset - name: [100]u8 = [_]u8{0} ** 100, // 0 - mode: [7:0]u8 = default_mode.file, // 100 - uid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 108 - gid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 116 - size: [11:0]u8 = [_:0]u8{'0'} ** 11, // 124 - mtime: [11:0]u8 = [_:0]u8{'0'} ** 11, // 136 - checksum: [7:0]u8 = [_:0]u8{' '} ** 7, // 148 - typeflag: FileType = .regular, // 156 - linkname: [100]u8 = [_]u8{0} ** 100, // 157 - magic: [6]u8 = [_]u8{ 'u', 's', 't', 'a', 'r', 0 }, // 257 - version: [2]u8 = [_]u8{ '0', '0' }, // 263 - uname: [32]u8 = [_]u8{0} ** 32, // unused 265 - gname: [32]u8 = [_]u8{0} ** 32, // unused 297 - devmajor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 329 - devminor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 337 - prefix: [155]u8 = [_]u8{0} ** 155, // 345 - pad: [12]u8 = [_]u8{0} ** 12, // unused 500 - - pub const FileType = enum(u8) { - regular = '0', - symbolic_link = '2', - directory = '5', - gnu_long_name = 'L', - gnu_long_link = 'K', - }; - - const default_mode = struct { - const file = [_:0]u8{ '0', '0', '0', '0', '6', '6', '4' }; // 0o664 - const dir = [_:0]u8{ '0', '0', '0', '0', '7', '7', '5' }; // 0o775 - const sym_link = [_:0]u8{ '0', '0', '0', '0', '7', '7', '7' }; // 0o777 - const other = [_:0]u8{ '0', '0', '0', '0', '0', '0', '0' }; // 0o000 - }; - - pub fn init(typeflag: FileType) Header { - return .{ - .typeflag = typeflag, - .mode = switch (typeflag) { - .directory => default_mode.dir, - .symbolic_link => default_mode.sym_link, - .regular => default_mode.file, - else => default_mode.other, - }, - }; - } - - pub fn setSize(self: *Header, size: u64) !void { - try octal(&self.size, size); - } - - fn octal(buf: []u8, value: u64) !void { - var remainder: u64 = value; - var pos: usize = buf.len; - while (remainder > 0 and pos > 0) { - pos -= 1; - const c: u8 = @as(u8, @intCast(remainder % 8)) + '0'; - buf[pos] = c; - remainder /= 8; - if (pos == 0 and remainder > 0) return error.OctalOverflow; - } - } - - pub fn setMode(self: *Header, mode: u32) !void { - try octal(&self.mode, mode); - } - - // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time. - // mtime == 0 will use current time - pub fn setMtime(self: *Header, mtime: u64) !void { - try octal(&self.mtime, mtime); - } - - pub fn updateChecksum(self: *Header) !void { - var checksum: usize = ' '; // other 7 self.checksum bytes are initialized to ' ' - for (std.mem.asBytes(self)) |val| - checksum += val; - try octal(&self.checksum, checksum); - } - - pub fn write(self: *Header, output_writer: anytype) !void { - try self.updateChecksum(); - try output_writer.writeAll(std.mem.asBytes(self)); - } - - pub fn setLinkname(self: *Header, link: []const u8) !void { - if (link.len > self.linkname.len) return error.NameTooLong; - @memcpy(self.linkname[0..link.len], link); - } - - pub fn setPath(self: *Header, prefix: []const u8, sub_path: []const u8) !void { - const max_prefix = self.prefix.len; - const max_name = self.name.len; - const sep = std.fs.path.sep_posix; - - if (prefix.len + sub_path.len > max_name + max_prefix or prefix.len > max_prefix) - return error.NameTooLong; - - // both fit into name - if (prefix.len > 0 and prefix.len + sub_path.len < max_name) { - @memcpy(self.name[0..prefix.len], prefix); - self.name[prefix.len] = sep; - @memcpy(self.name[prefix.len + 1 ..][0..sub_path.len], sub_path); - return; - } - - // sub_path fits into name - // there is no prefix or prefix fits into prefix - if (sub_path.len <= max_name) { - @memcpy(self.name[0..sub_path.len], sub_path); - @memcpy(self.prefix[0..prefix.len], prefix); - return; - } - - if (prefix.len > 0) { - @memcpy(self.prefix[0..prefix.len], prefix); - self.prefix[prefix.len] = sep; - } - const prefix_pos = if (prefix.len > 0) prefix.len + 1 else 0; - - // add as much to prefix as you can, must split at / - const prefix_remaining = max_prefix - prefix_pos; - if (std.mem.lastIndexOf(u8, sub_path[0..@min(prefix_remaining, sub_path.len)], &.{'/'})) |sep_pos| { - @memcpy(self.prefix[prefix_pos..][0..sep_pos], sub_path[0..sep_pos]); - if ((sub_path.len - sep_pos - 1) > max_name) return error.NameTooLong; - @memcpy(self.name[0..][0 .. sub_path.len - sep_pos - 1], sub_path[sep_pos + 1 ..]); - return; - } - - return error.NameTooLong; - } - - comptime { - assert(@sizeOf(Header) == 512); - } - - test setPath { - const cases = [_]struct { - in: []const []const u8, - out: []const []const u8, - }{ - .{ - .in = &.{ "", "123456789" }, - .out = &.{ "", "123456789" }, - }, - // can fit into name - .{ - .in = &.{ "prefix", "sub_path" }, - .out = &.{ "", "prefix/sub_path" }, - }, - // no more both fits into name - .{ - .in = &.{ "prefix", "0123456789/" ** 8 ++ "basename" }, - .out = &.{ "prefix", "0123456789/" ** 8 ++ "basename" }, - }, - // put as much as you can into prefix the rest goes into name - .{ - .in = &.{ "prefix", "0123456789/" ** 10 ++ "basename" }, - .out = &.{ "prefix/" ++ "0123456789/" ** 9 ++ "0123456789", "basename" }, - }, - - .{ - .in = &.{ "prefix", "0123456789/" ** 15 ++ "basename" }, - .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/0123456789/basename" }, - }, - .{ - .in = &.{ "prefix", "0123456789/" ** 21 ++ "basename" }, - .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/" ** 8 ++ "basename" }, - }, - .{ - .in = &.{ "", "012345678/" ** 10 ++ "foo" }, - .out = &.{ "012345678/" ** 9 ++ "012345678", "foo" }, - }, - }; - - for (cases) |case| { - var header = Header.init(.regular); - try header.setPath(case.in[0], case.in[1]); - try testing.expectEqualStrings(case.out[0], str(&header.prefix)); - try testing.expectEqualStrings(case.out[1], str(&header.name)); - } - - const error_cases = [_]struct { - in: []const []const u8, - }{ - // basename can't fit into name (106 characters) - .{ .in = &.{ "zig", "test/cases/compile_errors/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig" } }, - // cant fit into 255 + sep - .{ .in = &.{ "prefix", "0123456789/" ** 22 ++ "basename" } }, - // can fit but sub_path can't be split (there is no separator) - .{ .in = &.{ "prefix", "0123456789" ** 10 ++ "a" } }, - .{ .in = &.{ "prefix", "0123456789" ** 14 ++ "basename" } }, - }; - - for (error_cases) |case| { - var header = Header.init(.regular); - try testing.expectError( - error.NameTooLong, - header.setPath(case.in[0], case.in[1]), - ); - } - } - - // Breaks string on first null character. - fn str(s: []const u8) []const u8 { - for (s, 0..) |c, i| { - if (c == 0) return s[0..i]; - } - return s; - } -}; - -test { - _ = Header; -} - -test "write files" { - const files = [_]struct { - path: []const u8, - content: []const u8, - }{ - .{ .path = "foo", .content = "bar" }, - .{ .path = "a12345678/" ** 10 ++ "foo", .content = "a" ** 511 }, - .{ .path = "b12345678/" ** 24 ++ "foo", .content = "b" ** 512 }, - .{ .path = "c12345678/" ** 25 ++ "foo", .content = "c" ** 513 }, - .{ .path = "d12345678/" ** 51 ++ "foo", .content = "d" ** 1025 }, - .{ .path = "e123456789" ** 11, .content = "e" }, - }; - - var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; - var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; - - // with root - { - const root = "root"; - - var output: std.io.AllocatingWriter = undefined; - var wrt: Writer = .{ .underlying_writer = output.init(testing.allocator) }; - defer output.deinit(); - try wrt.setRoot(root); - for (files) |file| - try wrt.writeFileBytes(file.path, file.content, .{}); - - var input: std.io.FixedBufferStream = .{ .buffer = output.getWritten() }; - var iter = std.tar.iterator( - input.reader(), - .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer }, - ); - - // first entry is directory with prefix - { - const actual = (try iter.next()).?; - try testing.expectEqualStrings(root, actual.name); - try testing.expectEqual(std.tar.FileKind.directory, actual.kind); - } - - var i: usize = 0; - while (try iter.next()) |actual| { - defer i += 1; - const expected = files[i]; - try testing.expectEqualStrings(root, actual.name[0..root.len]); - try testing.expectEqual('/', actual.name[root.len..][0]); - try testing.expectEqualStrings(expected.path, actual.name[root.len + 1 ..]); - - var content = std.ArrayList(u8).init(testing.allocator); - defer content.deinit(); - try actual.writeAll(content.writer()); - try testing.expectEqualSlices(u8, expected.content, content.items); - } - } - // without root - { - var output: std.io.AllocatingWriter = undefined; - var wrt: Writer = .{ .underlying_writer = output.init(testing.allocator) }; - defer output.deinit(); - for (files) |file| { - var content = std.io.fixedBufferStream(file.content); - try wrt.writeFileStream(file.path, file.content.len, content.reader(), .{}); - } - - var input: std.io.FixedBufferStream = .{ .buffer = output.getWritten() }; - var iter = std.tar.iterator( - input.reader(), - .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer }, - ); - - var i: usize = 0; - while (try iter.next()) |actual| { - defer i += 1; - const expected = files[i]; - try testing.expectEqualStrings(expected.path, actual.name); - - var content = std.ArrayList(u8).init(testing.allocator); - defer content.deinit(); - try actual.writeAll(content.writer()); - try testing.expectEqualSlices(u8, expected.content, content.items); - } - try wrt.finish(); - } -} diff --git a/lib/std/zig.zig b/lib/std/zig.zig index ffc439d967aa717908d7095b333300629c303168..01951cdd0e8dad269ff530b5a1004d29c219caf8 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -416,7 +416,7 @@ fn formatId( bytes: []const u8, comptime fmt: []const u8, options: std.fmt.FormatOptions, - writer: anytype, + writer: *std.io.BufferedWriter, ) !void { const allow_primitive, const allow_underscore = comptime parse_fmt: { var allow_primitive = false; diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 51767ea1de6396da26f69e2dd991642bcfbfd98e..1022dbf80492b066466bac3452ffc91b6d46cebf 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -194,7 +194,7 @@ fn renderErrorMessageToWriter( ) anyerror!void { const ttyconf = options.ttyconf; var counting_writer: std.io.CountingWriter = .{ .child_writer = bw.writer() }; - var counting_bw = counting_writer.unbufferedWriter(); + var counting_bw = counting_writer.writer().unbuffered(); const err_msg = eb.getErrorMessage(err_msg_index); if (err_msg.src_loc != .none) { const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));