authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-17 12:22:24-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:26-07:00
logf3be721785013de85faf5d0fa454a7a8bc049c0b
treedaffef21b2b9c4489ed6b346be982769e79f7141
parent716b4489be09bc34ac5470658722924460d36a5d

build runner compiling


17 files changed, 521 insertions(+), 531 deletions(-)

lib/std/Build.zig+1-1
......@@ -2803,7 +2803,7 @@ pub fn dumpBadGetPathHelp(
28032803 src_builder: *Build,
28042804 asking_step: ?*Step,
28052805) anyerror!void {
2806 var buffered_writer = stderr.unbufferedWriter();
2806 var buffered_writer = stderr.writer().unbuffered();
28072807 const w = &buffered_writer;
28082808 try w.print(
28092809 \\getPath() was called on a GeneratedFile that wasn't built yet.
lib/std/Build/Fuzz/WebServer.zig+2-1
......@@ -522,7 +522,8 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void {
522522
523523 var cwd_cache: ?[]const u8 = null;
524524
525 var archiver = std.tar.writer(response.writer());
525 var response_writer = response.writer().unbuffered();
526 var archiver: std.tar.Writer = .{ .underlying_writer = &response_writer };
526527
527528 for (deduped_paths) |joined_path| {
528529 var file = joined_path.root_dir.handle.openFile(joined_path.sub_path, .{}) catch |err| {
lib/std/Build/Step.zig+1-1
......@@ -287,7 +287,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
287287
288288/// For debugging purposes, prints identifying information about this Step.
289289pub fn dump(step: *Step, file: std.fs.File) void {
290 var bw = file.unbufferedWriter();
290 var bw = file.writer().unbuffered();
291291 const tty_config = std.io.tty.detectConfig(file);
292292 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
293293 bw.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{
lib/std/Build/Step/Options.zig+6-4
......@@ -55,14 +55,14 @@ fn printType(
5555 switch (T) {
5656 []const []const u8 => {
5757 if (name) |payload| {
58 try out.print(gpa, "pub const {}: []const []const u8 = ", .{std.zig.fmtId(payload)});
58 try out.print(gpa, "pub const {f}: []const []const u8 = ", .{std.zig.fmtId(payload)});
5959 }
6060
6161 try out.appendSlice(gpa, "&[_][]const u8{\n");
6262
6363 for (value) |slice| {
6464 try out.appendNTimes(gpa, ' ', indent);
65 try out.print(gpa, " \"{}\",\n", .{std.zig.fmtEscapes(slice)});
65 try out.print(gpa, " \"{f}\",\n", .{std.zig.fmtEscapes(slice)});
6666 }
6767
6868 if (name != null) {
......@@ -75,7 +75,9 @@ fn printType(
7575 },
7676 []const u8 => {
7777 if (name) |some| {
78 try out.print(gpa, "pub const {}: []const u8 = \"{}\";", .{ std.zig.fmtId(some), std.zig.fmtEscapes(value) });
78 try out.print(gpa, "pub const {f}: []const u8 = \"{f}\";", .{
79 std.zig.fmtId(some), std.zig.fmtEscapes(value),
80 });
7981 } else {
8082 try out.print(gpa, "\"{}\",", .{std.zig.fmtEscapes(value)});
8183 }
......@@ -293,7 +295,7 @@ fn printEnum(
293295
294296 inline for (val.fields) |field| {
295297 try out.appendNTimes(gpa, ' ', indent);
296 try out.print(gpa, " {p} = {d},\n", .{ std.zig.fmtId(field.name), field.value });
298 try out.print(gpa, " {fp} = {d},\n", .{ std.zig.fmtId(field.name), field.value });
297299 }
298300
299301 if (!val.is_exhaustive) {
lib/std/Target.zig+2-2
......@@ -305,8 +305,8 @@ pub const Os = struct {
305305 ver: WindowsVersion,
306306 comptime fmt_str: []const u8,
307307 _: std.fmt.FormatOptions,
308 writer: anytype,
309 ) @TypeOf(writer).Error!void {
308 writer: *std.io.BufferedWriter,
309 ) anyerror!void {
310310 const maybe_name = std.enums.tagName(WindowsVersion, ver);
311311 if (comptime std.mem.eql(u8, fmt_str, "s")) {
312312 if (maybe_name) |name|
lib/std/debug.zig+3-3
......@@ -212,7 +212,7 @@ pub fn unlockStdErr() void {
212212/// in fact unbuffered and does not need to be flushed.
213213pub fn lockStdErr2() std.io.BufferedWriter {
214214 std.Progress.lockStdErr();
215 return io.getStdErr().unbufferedWriter();
215 return io.getStdErr().writer().unbuffered();
216216}
217217
218218/// 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
14681468}
14691469
14701470fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
1471 var stderr = io.getStdErr().unbufferedWriter();
1471 var stderr = io.getStdErr().writer().unbuffered();
14721472 _ = switch (sig) {
14731473 posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL
14741474 // 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:
15641564}
15651565
15661566fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void {
1567 var stderr = io.getStdErr().unbufferedWriter();
1567 var stderr = io.getStdErr().writer().unbuffered();
15681568 _ = switch (msg) {
15691569 0 => stderr.print("{s}\n", .{label.?}),
15701570 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
lib/std/fmt.zig+1-1
......@@ -853,7 +853,7 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr
853853/// Count the characters needed for format.
854854pub fn count(comptime fmt: []const u8, args: anytype) u64 {
855855 var counting_writer: std.io.CountingWriter = .{ .child_writer = std.io.null_writer };
856 var bw = counting_writer.unbufferedWriter();
856 var bw = counting_writer.writer().unbuffered();
857857 bw.print(fmt, args) catch unreachable;
858858 return counting_writer.bytes_written;
859859}
lib/std/fs/File.zig-7
......@@ -1602,13 +1602,6 @@ pub fn writer(file: File) std.io.Writer {
16021602 };
16031603}
16041604
1605pub fn unbufferedWriter(file: File) std.io.BufferedWriter {
1606 return .{
1607 .buffer = &.{},
1608 .unbuffered_writer = writer(file),
1609 };
1610}
1611
16121605const interface = struct {
16131606 /// Number of slices to store on the stack, when trying to send as many byte
16141607 /// vectors through the underlying write calls as possible.
lib/std/io/BufferedWriter.zig+6-5
......@@ -620,13 +620,14 @@ pub fn printValue(
620620 }
621621
622622 if (std.meta.hasMethod(T, "format")) {
623 if (fmt.len == 0) {
624 // @deprecated()
623 if (fmt.len > 0 and fmt[0] == 'f') {
624 return value.format(fmt[1..], options, bw);
625 } else {
626 //@deprecated();
625627 // After 0.14.0 is tagged, uncomment this next line:
626 //@compileError("ambiguous format string; specify {f} to call print method, or {any} to skip it");
628 //@compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it");
629 //and then delete the `hasMethod` condition
627630 return value.format(fmt, options, bw);
628 } else if (fmt[0] == 'f') {
629 return value.format(fmt[1..], options, bw);
630631 }
631632 }
632633
lib/std/io/CountingWriter.zig+1-8
......@@ -22,13 +22,6 @@ pub fn writer(cw: *CountingWriter) Writer {
2222 };
2323}
2424
25pub fn unbufferedWriter(cw: *CountingWriter) std.io.BufferedWriter {
26 return .{
27 .buffer = &.{},
28 .unbuffered_writer = writer(cw),
29 };
30}
31
3225fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
3326 const cw: *CountingWriter = @alignCast(@ptrCast(context));
3427 const n = try cw.child_writer.writeSplat(data, splat);
......@@ -52,7 +45,7 @@ fn passthru_writeFile(
5245
5346test CountingWriter {
5447 var cw: CountingWriter = .{ .child_writer = std.io.null_writer };
55 var bw = cw.unbufferedWriter();
48 var bw = cw.writer().unbuffered();
5649 const bytes = "yay";
5750 try bw.writeAll(bytes);
5851 try testing.expect(cw.bytes_written == bytes.len);
lib/std/io/Writer.zig+7-6
......@@ -86,12 +86,6 @@ pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void {
8686 while (index < bytes.len) index += try w.vtable.writeSplat(w.context, &.{bytes[index..]}, 1);
8787}
8888
89///// Directly calls `writeAll` many times to render the formatted text. To
90///// enable buffering, call `std.io.BufferedWriter.print` instead.
91//pub fn unbufferedPrint(w: Writer, comptime format: []const u8, args: anytype) anyerror!void {
92// return std.fmt.format(w, format, args);
93//}
94
9589/// The `data` parameter is mutable because this function needs to mutate the
9690/// fields in order to handle partial writes from `VTable.writev`.
9791pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void {
......@@ -106,3 +100,10 @@ pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void {
106100 data[i] = data[i][n..];
107101 }
108102}
103
104pub fn unbuffered(w: Writer) std.io.BufferedWriter {
105 return .{
106 .buffer = &.{},
107 .unbuffered_writer = w,
108 };
109}
lib/std/process/Child.zig+1-1
......@@ -1004,7 +1004,7 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
10041004
10051005fn writeIntFd(fd: i32, value: ErrInt) !void {
10061006 const file: File = .{ .handle = fd };
1007 var bw = file.unbufferedWriter();
1007 var bw = file.writer().unbuffered();
10081008 bw.writeInt(u64, @intCast(value), .little) catch return error.SystemResources;
10091009}
10101010
lib/std/tar.zig+3-3
......@@ -19,7 +19,7 @@ const std = @import("std");
1919const assert = std.debug.assert;
2020const testing = std.testing;
2121
22pub const writer = @import("tar/writer.zig").writer;
22pub const Writer = @import("tar/Writer.zig");
2323
2424/// Provide this to receive detailed error messages.
2525/// When this is provided, some errors which would otherwise be returned
......@@ -604,7 +604,7 @@ fn PaxIterator(comptime ReaderType: type) type {
604604 }
605605
606606 fn readUntil(self: *Self, delimiter: u8) ![]const u8 {
607 var fbs = std.io.fixedBufferStream(&self.scratch);
607 var fbs: std.io.FixedBufferStream = .{ .buffer = &self.scratch };
608608 try self.reader.streamUntilDelimiter(fbs.writer(), delimiter, null);
609609 return fbs.getWritten();
610610 }
......@@ -855,7 +855,7 @@ test PaxIterator {
855855
856856test {
857857 _ = @import("tar/test.zig");
858 _ = @import("tar/writer.zig");
858 _ = Writer;
859859 _ = Diagnostics;
860860}
861861
lib/std/tar/Writer.zig created+485
......@@ -0,0 +1,485 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const testing = std.testing;
4const Writer = @This();
5
6const block_size = @sizeOf(Header);
7const empty_block: [block_size]u8 = [_]u8{0} ** block_size;
8
9/// Options for writing file/dir/link. If left empty 0o664 is used for
10/// file mode and current time for mtime.
11pub const Options = struct {
12 /// File system permission mode.
13 mode: u32 = 0,
14 /// File system modification time.
15 mtime: u64 = 0,
16};
17const Self = @This();
18
19underlying_writer: *std.io.BufferedWriter,
20prefix: []const u8 = "",
21mtime_now: u64 = 0,
22
23/// Sets prefix for all other write* method paths.
24pub fn setRoot(self: *Self, root: []const u8) !void {
25 if (root.len > 0)
26 try self.writeDir(root, .{});
27
28 self.prefix = root;
29}
30
31/// Writes directory.
32pub fn writeDir(self: *Self, sub_path: []const u8, opt: Options) !void {
33 try self.writeHeader(.directory, sub_path, "", 0, opt);
34}
35
36/// Writes file system file.
37pub fn writeFile(self: *Self, sub_path: []const u8, file: std.fs.File) !void {
38 const stat = try file.stat();
39 const mtime: u64 = @intCast(@divFloor(stat.mtime, std.time.ns_per_s));
40
41 var header = Header{};
42 try self.setPath(&header, sub_path);
43 try header.setSize(stat.size);
44 try header.setMtime(mtime);
45 try header.write(self.underlying_writer);
46
47 try self.underlying_writer.writeFileAll(file, .{ .len = .init(stat.size) });
48 try self.writePadding(stat.size);
49}
50
51/// Writes file reading file content from `reader`. Number of bytes in
52/// reader must be equal to `size`.
53pub fn writeFileStream(self: *Self, sub_path: []const u8, size: usize, reader: anytype, opt: Options) !void {
54 try self.writeHeader(.regular, sub_path, "", @intCast(size), opt);
55
56 var counting_reader = std.io.countingReader(reader);
57 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
58 try fifo.pump(counting_reader.reader(), self.underlying_writer);
59 if (counting_reader.bytes_read != size) return error.WrongReaderSize;
60 try self.writePadding(size);
61}
62
63/// Writes file using bytes buffer `content` for size and file content.
64pub fn writeFileBytes(self: *Self, sub_path: []const u8, content: []const u8, opt: Options) !void {
65 try self.writeHeader(.regular, sub_path, "", @intCast(content.len), opt);
66 try self.underlying_writer.writeAll(content);
67 try self.writePadding(content.len);
68}
69
70/// Writes symlink.
71pub fn writeLink(self: *Self, sub_path: []const u8, link_name: []const u8, opt: Options) !void {
72 try self.writeHeader(.symbolic_link, sub_path, link_name, 0, opt);
73}
74
75/// Writes fs.Dir.WalkerEntry. Uses `mtime` from file system entry and
76/// default for entry mode .
77pub fn writeEntry(self: *Self, entry: std.fs.Dir.Walker.Entry) !void {
78 switch (entry.kind) {
79 .directory => {
80 try self.writeDir(entry.path, .{ .mtime = try entryMtime(entry) });
81 },
82 .file => {
83 var file = try entry.dir.openFile(entry.basename, .{});
84 defer file.close();
85 try self.writeFile(entry.path, file);
86 },
87 .sym_link => {
88 var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
89 const link_name = try entry.dir.readLink(entry.basename, &link_name_buffer);
90 try self.writeLink(entry.path, link_name, .{ .mtime = try entryMtime(entry) });
91 },
92 else => {
93 return error.UnsupportedWalkerEntryKind;
94 },
95 }
96}
97
98fn writeHeader(
99 self: *Self,
100 typeflag: Header.FileType,
101 sub_path: []const u8,
102 link_name: []const u8,
103 size: u64,
104 opt: Options,
105) !void {
106 var header = Header.init(typeflag);
107 try self.setPath(&header, sub_path);
108 try header.setSize(size);
109 try header.setMtime(if (opt.mtime != 0) opt.mtime else self.mtimeNow());
110 if (opt.mode != 0)
111 try header.setMode(opt.mode);
112 if (typeflag == .symbolic_link)
113 header.setLinkname(link_name) catch |err| switch (err) {
114 error.NameTooLong => try self.writeExtendedHeader(.gnu_long_link, &.{link_name}),
115 else => return err,
116 };
117 try header.write(self.underlying_writer);
118}
119
120fn mtimeNow(self: *Self) u64 {
121 if (self.mtime_now == 0)
122 self.mtime_now = @intCast(std.time.timestamp());
123 return self.mtime_now;
124}
125
126fn entryMtime(entry: std.fs.Dir.Walker.Entry) !u64 {
127 const stat = try entry.dir.statFile(entry.basename);
128 return @intCast(@divFloor(stat.mtime, std.time.ns_per_s));
129}
130
131/// Writes path in posix header, if don't fit (in name+prefix; 100+155
132/// bytes) writes it in gnu extended header.
133fn setPath(self: *Self, header: *Header, sub_path: []const u8) !void {
134 header.setPath(self.prefix, sub_path) catch |err| switch (err) {
135 error.NameTooLong => {
136 // write extended header
137 const buffers: []const []const u8 = if (self.prefix.len == 0)
138 &.{sub_path}
139 else
140 &.{ self.prefix, "/", sub_path };
141 try self.writeExtendedHeader(.gnu_long_name, buffers);
142 },
143 else => return err,
144 };
145}
146
147/// Writes gnu extended header: gnu_long_name or gnu_long_link.
148fn writeExtendedHeader(self: *Self, typeflag: Header.FileType, buffers: []const []const u8) !void {
149 var len: usize = 0;
150 for (buffers) |buf|
151 len += buf.len;
152
153 var header = Header.init(typeflag);
154 try header.setSize(len);
155 try header.write(self.underlying_writer);
156 for (buffers) |buf|
157 try self.underlying_writer.writeAll(buf);
158 try self.writePadding(len);
159}
160
161fn writePadding(self: *Self, bytes: u64) !void {
162 const pos: usize = @intCast(bytes % block_size);
163 if (pos == 0) return;
164 try self.underlying_writer.writeAll(empty_block[pos..]);
165}
166
167/// Tar should finish with two zero blocks, but 'reasonable system must
168/// not assume that such a block exists when reading an archive' (from
169/// reference). In practice it is safe to skip this finish.
170pub fn finish(self: *Self) !void {
171 try self.underlying_writer.writeAll(&empty_block);
172 try self.underlying_writer.writeAll(&empty_block);
173}
174
175/// A struct that is exactly 512 bytes and matches tar file format. This is
176/// intended to be used for outputting tar files; for parsing there is
177/// `std.tar.Header`.
178const Header = extern struct {
179 // This struct was originally copied from
180 // https://github.com/mattnite/tar/blob/main/src/main.zig which is MIT
181 // licensed.
182 //
183 // The name, linkname, magic, uname, and gname are null-terminated character
184 // strings. All other fields are zero-filled octal numbers in ASCII. Each
185 // numeric field of width w contains w minus 1 digits, and a null.
186 // Reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html
187 // POSIX header: byte offset
188 name: [100]u8 = [_]u8{0} ** 100, // 0
189 mode: [7:0]u8 = default_mode.file, // 100
190 uid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 108
191 gid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 116
192 size: [11:0]u8 = [_:0]u8{'0'} ** 11, // 124
193 mtime: [11:0]u8 = [_:0]u8{'0'} ** 11, // 136
194 checksum: [7:0]u8 = [_:0]u8{' '} ** 7, // 148
195 typeflag: FileType = .regular, // 156
196 linkname: [100]u8 = [_]u8{0} ** 100, // 157
197 magic: [6]u8 = [_]u8{ 'u', 's', 't', 'a', 'r', 0 }, // 257
198 version: [2]u8 = [_]u8{ '0', '0' }, // 263
199 uname: [32]u8 = [_]u8{0} ** 32, // unused 265
200 gname: [32]u8 = [_]u8{0} ** 32, // unused 297
201 devmajor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 329
202 devminor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 337
203 prefix: [155]u8 = [_]u8{0} ** 155, // 345
204 pad: [12]u8 = [_]u8{0} ** 12, // unused 500
205
206 pub const FileType = enum(u8) {
207 regular = '0',
208 symbolic_link = '2',
209 directory = '5',
210 gnu_long_name = 'L',
211 gnu_long_link = 'K',
212 };
213
214 const default_mode = struct {
215 const file = [_:0]u8{ '0', '0', '0', '0', '6', '6', '4' }; // 0o664
216 const dir = [_:0]u8{ '0', '0', '0', '0', '7', '7', '5' }; // 0o775
217 const sym_link = [_:0]u8{ '0', '0', '0', '0', '7', '7', '7' }; // 0o777
218 const other = [_:0]u8{ '0', '0', '0', '0', '0', '0', '0' }; // 0o000
219 };
220
221 pub fn init(typeflag: FileType) Header {
222 return .{
223 .typeflag = typeflag,
224 .mode = switch (typeflag) {
225 .directory => default_mode.dir,
226 .symbolic_link => default_mode.sym_link,
227 .regular => default_mode.file,
228 else => default_mode.other,
229 },
230 };
231 }
232
233 pub fn setSize(self: *Header, size: u64) !void {
234 try octal(&self.size, size);
235 }
236
237 fn octal(buf: []u8, value: u64) !void {
238 var remainder: u64 = value;
239 var pos: usize = buf.len;
240 while (remainder > 0 and pos > 0) {
241 pos -= 1;
242 const c: u8 = @as(u8, @intCast(remainder % 8)) + '0';
243 buf[pos] = c;
244 remainder /= 8;
245 if (pos == 0 and remainder > 0) return error.OctalOverflow;
246 }
247 }
248
249 pub fn setMode(self: *Header, mode: u32) !void {
250 try octal(&self.mode, mode);
251 }
252
253 // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.
254 // mtime == 0 will use current time
255 pub fn setMtime(self: *Header, mtime: u64) !void {
256 try octal(&self.mtime, mtime);
257 }
258
259 pub fn updateChecksum(self: *Header) !void {
260 var checksum: usize = ' '; // other 7 self.checksum bytes are initialized to ' '
261 for (std.mem.asBytes(self)) |val|
262 checksum += val;
263 try octal(&self.checksum, checksum);
264 }
265
266 pub fn write(self: *Header, output_writer: anytype) !void {
267 try self.updateChecksum();
268 try output_writer.writeAll(std.mem.asBytes(self));
269 }
270
271 pub fn setLinkname(self: *Header, link: []const u8) !void {
272 if (link.len > self.linkname.len) return error.NameTooLong;
273 @memcpy(self.linkname[0..link.len], link);
274 }
275
276 pub fn setPath(self: *Header, prefix: []const u8, sub_path: []const u8) !void {
277 const max_prefix = self.prefix.len;
278 const max_name = self.name.len;
279 const sep = std.fs.path.sep_posix;
280
281 if (prefix.len + sub_path.len > max_name + max_prefix or prefix.len > max_prefix)
282 return error.NameTooLong;
283
284 // both fit into name
285 if (prefix.len > 0 and prefix.len + sub_path.len < max_name) {
286 @memcpy(self.name[0..prefix.len], prefix);
287 self.name[prefix.len] = sep;
288 @memcpy(self.name[prefix.len + 1 ..][0..sub_path.len], sub_path);
289 return;
290 }
291
292 // sub_path fits into name
293 // there is no prefix or prefix fits into prefix
294 if (sub_path.len <= max_name) {
295 @memcpy(self.name[0..sub_path.len], sub_path);
296 @memcpy(self.prefix[0..prefix.len], prefix);
297 return;
298 }
299
300 if (prefix.len > 0) {
301 @memcpy(self.prefix[0..prefix.len], prefix);
302 self.prefix[prefix.len] = sep;
303 }
304 const prefix_pos = if (prefix.len > 0) prefix.len + 1 else 0;
305
306 // add as much to prefix as you can, must split at /
307 const prefix_remaining = max_prefix - prefix_pos;
308 if (std.mem.lastIndexOf(u8, sub_path[0..@min(prefix_remaining, sub_path.len)], &.{'/'})) |sep_pos| {
309 @memcpy(self.prefix[prefix_pos..][0..sep_pos], sub_path[0..sep_pos]);
310 if ((sub_path.len - sep_pos - 1) > max_name) return error.NameTooLong;
311 @memcpy(self.name[0..][0 .. sub_path.len - sep_pos - 1], sub_path[sep_pos + 1 ..]);
312 return;
313 }
314
315 return error.NameTooLong;
316 }
317
318 comptime {
319 assert(@sizeOf(Header) == 512);
320 }
321
322 test "setPath" {
323 const cases = [_]struct {
324 in: []const []const u8,
325 out: []const []const u8,
326 }{
327 .{
328 .in = &.{ "", "123456789" },
329 .out = &.{ "", "123456789" },
330 },
331 // can fit into name
332 .{
333 .in = &.{ "prefix", "sub_path" },
334 .out = &.{ "", "prefix/sub_path" },
335 },
336 // no more both fits into name
337 .{
338 .in = &.{ "prefix", "0123456789/" ** 8 ++ "basename" },
339 .out = &.{ "prefix", "0123456789/" ** 8 ++ "basename" },
340 },
341 // put as much as you can into prefix the rest goes into name
342 .{
343 .in = &.{ "prefix", "0123456789/" ** 10 ++ "basename" },
344 .out = &.{ "prefix/" ++ "0123456789/" ** 9 ++ "0123456789", "basename" },
345 },
346
347 .{
348 .in = &.{ "prefix", "0123456789/" ** 15 ++ "basename" },
349 .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/0123456789/basename" },
350 },
351 .{
352 .in = &.{ "prefix", "0123456789/" ** 21 ++ "basename" },
353 .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/" ** 8 ++ "basename" },
354 },
355 .{
356 .in = &.{ "", "012345678/" ** 10 ++ "foo" },
357 .out = &.{ "012345678/" ** 9 ++ "012345678", "foo" },
358 },
359 };
360
361 for (cases) |case| {
362 var header = Header.init(.regular);
363 try header.setPath(case.in[0], case.in[1]);
364 try testing.expectEqualStrings(case.out[0], str(&header.prefix));
365 try testing.expectEqualStrings(case.out[1], str(&header.name));
366 }
367
368 const error_cases = [_]struct {
369 in: []const []const u8,
370 }{
371 // basename can't fit into name (106 characters)
372 .{ .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" } },
373 // cant fit into 255 + sep
374 .{ .in = &.{ "prefix", "0123456789/" ** 22 ++ "basename" } },
375 // can fit but sub_path can't be split (there is no separator)
376 .{ .in = &.{ "prefix", "0123456789" ** 10 ++ "a" } },
377 .{ .in = &.{ "prefix", "0123456789" ** 14 ++ "basename" } },
378 };
379
380 for (error_cases) |case| {
381 var header = Header.init(.regular);
382 try testing.expectError(
383 error.NameTooLong,
384 header.setPath(case.in[0], case.in[1]),
385 );
386 }
387 }
388
389 // Breaks string on first null character.
390 fn str(s: []const u8) []const u8 {
391 for (s, 0..) |c, i| {
392 if (c == 0) return s[0..i];
393 }
394 return s;
395 }
396};
397
398test {
399 _ = Header;
400}
401
402test "write files" {
403 const files = [_]struct {
404 path: []const u8,
405 content: []const u8,
406 }{
407 .{ .path = "foo", .content = "bar" },
408 .{ .path = "a12345678/" ** 10 ++ "foo", .content = "a" ** 511 },
409 .{ .path = "b12345678/" ** 24 ++ "foo", .content = "b" ** 512 },
410 .{ .path = "c12345678/" ** 25 ++ "foo", .content = "c" ** 513 },
411 .{ .path = "d12345678/" ** 51 ++ "foo", .content = "d" ** 1025 },
412 .{ .path = "e123456789" ** 11, .content = "e" },
413 };
414
415 var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
416 var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
417
418 // with root
419 {
420 const root = "root";
421
422 var output: std.io.AllocatingWriter = undefined;
423 var wrt: Writer = .{ .underlying_writer = output.init(testing.allocator) };
424 defer output.deinit();
425 try wrt.setRoot(root);
426 for (files) |file|
427 try wrt.writeFileBytes(file.path, file.content, .{});
428
429 var input: std.io.FixedBufferStream = .{ .buffer = output.getWritten() };
430 var iter = std.tar.iterator(
431 input.reader(),
432 .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer },
433 );
434
435 // first entry is directory with prefix
436 {
437 const actual = (try iter.next()).?;
438 try testing.expectEqualStrings(root, actual.name);
439 try testing.expectEqual(std.tar.FileKind.directory, actual.kind);
440 }
441
442 var i: usize = 0;
443 while (try iter.next()) |actual| {
444 defer i += 1;
445 const expected = files[i];
446 try testing.expectEqualStrings(root, actual.name[0..root.len]);
447 try testing.expectEqual('/', actual.name[root.len..][0]);
448 try testing.expectEqualStrings(expected.path, actual.name[root.len + 1 ..]);
449
450 var content = std.ArrayList(u8).init(testing.allocator);
451 defer content.deinit();
452 try actual.writeAll(content.writer());
453 try testing.expectEqualSlices(u8, expected.content, content.items);
454 }
455 }
456 // without root
457 {
458 var output: std.io.AllocatingWriter = undefined;
459 var wrt: Writer = .{ .underlying_writer = output.init(testing.allocator) };
460 defer output.deinit();
461 for (files) |file| {
462 var content = std.io.fixedBufferStream(file.content);
463 try wrt.writeFileStream(file.path, file.content.len, content.reader(), .{});
464 }
465
466 var input: std.io.FixedBufferStream = .{ .buffer = output.getWritten() };
467 var iter = std.tar.iterator(
468 input.reader(),
469 .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer },
470 );
471
472 var i: usize = 0;
473 while (try iter.next()) |actual| {
474 defer i += 1;
475 const expected = files[i];
476 try testing.expectEqualStrings(expected.path, actual.name);
477
478 var content = std.ArrayList(u8).init(testing.allocator);
479 defer content.deinit();
480 try actual.writeAll(content.writer());
481 try testing.expectEqualSlices(u8, expected.content, content.items);
482 }
483 try wrt.finish();
484 }
485}
lib/std/tar/writer.zig deleted-486
......@@ -1,486 +0,0 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const testing = std.testing;
4
5pub const Writer = struct {
6 const block_size = @sizeOf(Header);
7 const empty_block: [block_size]u8 = [_]u8{0} ** block_size;
8
9 /// Options for writing file/dir/link. If left empty 0o664 is used for
10 /// file mode and current time for mtime.
11 pub const Options = struct {
12 /// File system permission mode.
13 mode: u32 = 0,
14 /// File system modification time.
15 mtime: u64 = 0,
16 };
17 const Self = @This();
18
19 underlying_writer: *std.io.BufferedWriter,
20 prefix: []const u8 = "",
21 mtime_now: u64 = 0,
22
23 /// Sets prefix for all other write* method paths.
24 pub fn setRoot(self: *Self, root: []const u8) !void {
25 if (root.len > 0)
26 try self.writeDir(root, .{});
27
28 self.prefix = root;
29 }
30
31 /// Writes directory.
32 pub fn writeDir(self: *Self, sub_path: []const u8, opt: Options) !void {
33 try self.writeHeader(.directory, sub_path, "", 0, opt);
34 }
35
36 /// Writes file system file.
37 pub fn writeFile(self: *Self, sub_path: []const u8, file: std.fs.File) !void {
38 const stat = try file.stat();
39 const mtime: u64 = @intCast(@divFloor(stat.mtime, std.time.ns_per_s));
40
41 var header = Header{};
42 try self.setPath(&header, sub_path);
43 try header.setSize(stat.size);
44 try header.setMtime(mtime);
45 try header.write(self.underlying_writer);
46
47 try self.underlying_writer.writeFileAll(file, .{ .len = .init(stat.size) });
48 try self.writePadding(stat.size);
49 }
50
51 /// Writes file reading file content from `reader`. Number of bytes in
52 /// reader must be equal to `size`.
53 pub fn writeFileStream(self: *Self, sub_path: []const u8, size: usize, reader: anytype, opt: Options) !void {
54 try self.writeHeader(.regular, sub_path, "", @intCast(size), opt);
55
56 var counting_reader = std.io.countingReader(reader);
57 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
58 try fifo.pump(counting_reader.reader(), self.underlying_writer);
59 if (counting_reader.bytes_read != size) return error.WrongReaderSize;
60 try self.writePadding(size);
61 }
62
63 /// Writes file using bytes buffer `content` for size and file content.
64 pub fn writeFileBytes(self: *Self, sub_path: []const u8, content: []const u8, opt: Options) !void {
65 try self.writeHeader(.regular, sub_path, "", @intCast(content.len), opt);
66 try self.underlying_writer.writeAll(content);
67 try self.writePadding(content.len);
68 }
69
70 /// Writes symlink.
71 pub fn writeLink(self: *Self, sub_path: []const u8, link_name: []const u8, opt: Options) !void {
72 try self.writeHeader(.symbolic_link, sub_path, link_name, 0, opt);
73 }
74
75 /// Writes fs.Dir.WalkerEntry. Uses `mtime` from file system entry and
76 /// default for entry mode .
77 pub fn writeEntry(self: *Self, entry: std.fs.Dir.Walker.Entry) !void {
78 switch (entry.kind) {
79 .directory => {
80 try self.writeDir(entry.path, .{ .mtime = try entryMtime(entry) });
81 },
82 .file => {
83 var file = try entry.dir.openFile(entry.basename, .{});
84 defer file.close();
85 try self.writeFile(entry.path, file);
86 },
87 .sym_link => {
88 var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
89 const link_name = try entry.dir.readLink(entry.basename, &link_name_buffer);
90 try self.writeLink(entry.path, link_name, .{ .mtime = try entryMtime(entry) });
91 },
92 else => {
93 return error.UnsupportedWalkerEntryKind;
94 },
95 }
96 }
97
98 fn writeHeader(
99 self: *Self,
100 typeflag: Header.FileType,
101 sub_path: []const u8,
102 link_name: []const u8,
103 size: u64,
104 opt: Options,
105 ) !void {
106 var header = Header.init(typeflag);
107 try self.setPath(&header, sub_path);
108 try header.setSize(size);
109 try header.setMtime(if (opt.mtime != 0) opt.mtime else self.mtimeNow());
110 if (opt.mode != 0)
111 try header.setMode(opt.mode);
112 if (typeflag == .symbolic_link)
113 header.setLinkname(link_name) catch |err| switch (err) {
114 error.NameTooLong => try self.writeExtendedHeader(.gnu_long_link, &.{link_name}),
115 else => return err,
116 };
117 try header.write(self.underlying_writer);
118 }
119
120 fn mtimeNow(self: *Self) u64 {
121 if (self.mtime_now == 0)
122 self.mtime_now = @intCast(std.time.timestamp());
123 return self.mtime_now;
124 }
125
126 fn entryMtime(entry: std.fs.Dir.Walker.Entry) !u64 {
127 const stat = try entry.dir.statFile(entry.basename);
128 return @intCast(@divFloor(stat.mtime, std.time.ns_per_s));
129 }
130
131 /// Writes path in posix header, if don't fit (in name+prefix; 100+155
132 /// bytes) writes it in gnu extended header.
133 fn setPath(self: *Self, header: *Header, sub_path: []const u8) !void {
134 header.setPath(self.prefix, sub_path) catch |err| switch (err) {
135 error.NameTooLong => {
136 // write extended header
137 const buffers: []const []const u8 = if (self.prefix.len == 0)
138 &.{sub_path}
139 else
140 &.{ self.prefix, "/", sub_path };
141 try self.writeExtendedHeader(.gnu_long_name, buffers);
142 },
143 else => return err,
144 };
145 }
146
147 /// Writes gnu extended header: gnu_long_name or gnu_long_link.
148 fn writeExtendedHeader(self: *Self, typeflag: Header.FileType, buffers: []const []const u8) !void {
149 var len: usize = 0;
150 for (buffers) |buf|
151 len += buf.len;
152
153 var header = Header.init(typeflag);
154 try header.setSize(len);
155 try header.write(self.underlying_writer);
156 for (buffers) |buf|
157 try self.underlying_writer.writeAll(buf);
158 try self.writePadding(len);
159 }
160
161 fn writePadding(self: *Self, bytes: u64) !void {
162 const pos: usize = @intCast(bytes % block_size);
163 if (pos == 0) return;
164 try self.underlying_writer.writeAll(empty_block[pos..]);
165 }
166
167 /// Tar should finish with two zero blocks, but 'reasonable system must
168 /// not assume that such a block exists when reading an archive' (from
169 /// reference). In practice it is safe to skip this finish.
170 pub fn finish(self: *Self) !void {
171 try self.underlying_writer.writeAll(&empty_block);
172 try self.underlying_writer.writeAll(&empty_block);
173 }
174};
175
176/// A struct that is exactly 512 bytes and matches tar file format. This is
177/// intended to be used for outputting tar files; for parsing there is
178/// `std.tar.Header`.
179const Header = extern struct {
180 // This struct was originally copied from
181 // https://github.com/mattnite/tar/blob/main/src/main.zig which is MIT
182 // licensed.
183 //
184 // The name, linkname, magic, uname, and gname are null-terminated character
185 // strings. All other fields are zero-filled octal numbers in ASCII. Each
186 // numeric field of width w contains w minus 1 digits, and a null.
187 // Reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html
188 // POSIX header: byte offset
189 name: [100]u8 = [_]u8{0} ** 100, // 0
190 mode: [7:0]u8 = default_mode.file, // 100
191 uid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 108
192 gid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 116
193 size: [11:0]u8 = [_:0]u8{'0'} ** 11, // 124
194 mtime: [11:0]u8 = [_:0]u8{'0'} ** 11, // 136
195 checksum: [7:0]u8 = [_:0]u8{' '} ** 7, // 148
196 typeflag: FileType = .regular, // 156
197 linkname: [100]u8 = [_]u8{0} ** 100, // 157
198 magic: [6]u8 = [_]u8{ 'u', 's', 't', 'a', 'r', 0 }, // 257
199 version: [2]u8 = [_]u8{ '0', '0' }, // 263
200 uname: [32]u8 = [_]u8{0} ** 32, // unused 265
201 gname: [32]u8 = [_]u8{0} ** 32, // unused 297
202 devmajor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 329
203 devminor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 337
204 prefix: [155]u8 = [_]u8{0} ** 155, // 345
205 pad: [12]u8 = [_]u8{0} ** 12, // unused 500
206
207 pub const FileType = enum(u8) {
208 regular = '0',
209 symbolic_link = '2',
210 directory = '5',
211 gnu_long_name = 'L',
212 gnu_long_link = 'K',
213 };
214
215 const default_mode = struct {
216 const file = [_:0]u8{ '0', '0', '0', '0', '6', '6', '4' }; // 0o664
217 const dir = [_:0]u8{ '0', '0', '0', '0', '7', '7', '5' }; // 0o775
218 const sym_link = [_:0]u8{ '0', '0', '0', '0', '7', '7', '7' }; // 0o777
219 const other = [_:0]u8{ '0', '0', '0', '0', '0', '0', '0' }; // 0o000
220 };
221
222 pub fn init(typeflag: FileType) Header {
223 return .{
224 .typeflag = typeflag,
225 .mode = switch (typeflag) {
226 .directory => default_mode.dir,
227 .symbolic_link => default_mode.sym_link,
228 .regular => default_mode.file,
229 else => default_mode.other,
230 },
231 };
232 }
233
234 pub fn setSize(self: *Header, size: u64) !void {
235 try octal(&self.size, size);
236 }
237
238 fn octal(buf: []u8, value: u64) !void {
239 var remainder: u64 = value;
240 var pos: usize = buf.len;
241 while (remainder > 0 and pos > 0) {
242 pos -= 1;
243 const c: u8 = @as(u8, @intCast(remainder % 8)) + '0';
244 buf[pos] = c;
245 remainder /= 8;
246 if (pos == 0 and remainder > 0) return error.OctalOverflow;
247 }
248 }
249
250 pub fn setMode(self: *Header, mode: u32) !void {
251 try octal(&self.mode, mode);
252 }
253
254 // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.
255 // mtime == 0 will use current time
256 pub fn setMtime(self: *Header, mtime: u64) !void {
257 try octal(&self.mtime, mtime);
258 }
259
260 pub fn updateChecksum(self: *Header) !void {
261 var checksum: usize = ' '; // other 7 self.checksum bytes are initialized to ' '
262 for (std.mem.asBytes(self)) |val|
263 checksum += val;
264 try octal(&self.checksum, checksum);
265 }
266
267 pub fn write(self: *Header, output_writer: anytype) !void {
268 try self.updateChecksum();
269 try output_writer.writeAll(std.mem.asBytes(self));
270 }
271
272 pub fn setLinkname(self: *Header, link: []const u8) !void {
273 if (link.len > self.linkname.len) return error.NameTooLong;
274 @memcpy(self.linkname[0..link.len], link);
275 }
276
277 pub fn setPath(self: *Header, prefix: []const u8, sub_path: []const u8) !void {
278 const max_prefix = self.prefix.len;
279 const max_name = self.name.len;
280 const sep = std.fs.path.sep_posix;
281
282 if (prefix.len + sub_path.len > max_name + max_prefix or prefix.len > max_prefix)
283 return error.NameTooLong;
284
285 // both fit into name
286 if (prefix.len > 0 and prefix.len + sub_path.len < max_name) {
287 @memcpy(self.name[0..prefix.len], prefix);
288 self.name[prefix.len] = sep;
289 @memcpy(self.name[prefix.len + 1 ..][0..sub_path.len], sub_path);
290 return;
291 }
292
293 // sub_path fits into name
294 // there is no prefix or prefix fits into prefix
295 if (sub_path.len <= max_name) {
296 @memcpy(self.name[0..sub_path.len], sub_path);
297 @memcpy(self.prefix[0..prefix.len], prefix);
298 return;
299 }
300
301 if (prefix.len > 0) {
302 @memcpy(self.prefix[0..prefix.len], prefix);
303 self.prefix[prefix.len] = sep;
304 }
305 const prefix_pos = if (prefix.len > 0) prefix.len + 1 else 0;
306
307 // add as much to prefix as you can, must split at /
308 const prefix_remaining = max_prefix - prefix_pos;
309 if (std.mem.lastIndexOf(u8, sub_path[0..@min(prefix_remaining, sub_path.len)], &.{'/'})) |sep_pos| {
310 @memcpy(self.prefix[prefix_pos..][0..sep_pos], sub_path[0..sep_pos]);
311 if ((sub_path.len - sep_pos - 1) > max_name) return error.NameTooLong;
312 @memcpy(self.name[0..][0 .. sub_path.len - sep_pos - 1], sub_path[sep_pos + 1 ..]);
313 return;
314 }
315
316 return error.NameTooLong;
317 }
318
319 comptime {
320 assert(@sizeOf(Header) == 512);
321 }
322
323 test setPath {
324 const cases = [_]struct {
325 in: []const []const u8,
326 out: []const []const u8,
327 }{
328 .{
329 .in = &.{ "", "123456789" },
330 .out = &.{ "", "123456789" },
331 },
332 // can fit into name
333 .{
334 .in = &.{ "prefix", "sub_path" },
335 .out = &.{ "", "prefix/sub_path" },
336 },
337 // no more both fits into name
338 .{
339 .in = &.{ "prefix", "0123456789/" ** 8 ++ "basename" },
340 .out = &.{ "prefix", "0123456789/" ** 8 ++ "basename" },
341 },
342 // put as much as you can into prefix the rest goes into name
343 .{
344 .in = &.{ "prefix", "0123456789/" ** 10 ++ "basename" },
345 .out = &.{ "prefix/" ++ "0123456789/" ** 9 ++ "0123456789", "basename" },
346 },
347
348 .{
349 .in = &.{ "prefix", "0123456789/" ** 15 ++ "basename" },
350 .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/0123456789/basename" },
351 },
352 .{
353 .in = &.{ "prefix", "0123456789/" ** 21 ++ "basename" },
354 .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/" ** 8 ++ "basename" },
355 },
356 .{
357 .in = &.{ "", "012345678/" ** 10 ++ "foo" },
358 .out = &.{ "012345678/" ** 9 ++ "012345678", "foo" },
359 },
360 };
361
362 for (cases) |case| {
363 var header = Header.init(.regular);
364 try header.setPath(case.in[0], case.in[1]);
365 try testing.expectEqualStrings(case.out[0], str(&header.prefix));
366 try testing.expectEqualStrings(case.out[1], str(&header.name));
367 }
368
369 const error_cases = [_]struct {
370 in: []const []const u8,
371 }{
372 // basename can't fit into name (106 characters)
373 .{ .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" } },
374 // cant fit into 255 + sep
375 .{ .in = &.{ "prefix", "0123456789/" ** 22 ++ "basename" } },
376 // can fit but sub_path can't be split (there is no separator)
377 .{ .in = &.{ "prefix", "0123456789" ** 10 ++ "a" } },
378 .{ .in = &.{ "prefix", "0123456789" ** 14 ++ "basename" } },
379 };
380
381 for (error_cases) |case| {
382 var header = Header.init(.regular);
383 try testing.expectError(
384 error.NameTooLong,
385 header.setPath(case.in[0], case.in[1]),
386 );
387 }
388 }
389
390 // Breaks string on first null character.
391 fn str(s: []const u8) []const u8 {
392 for (s, 0..) |c, i| {
393 if (c == 0) return s[0..i];
394 }
395 return s;
396 }
397};
398
399test {
400 _ = Header;
401}
402
403test "write files" {
404 const files = [_]struct {
405 path: []const u8,
406 content: []const u8,
407 }{
408 .{ .path = "foo", .content = "bar" },
409 .{ .path = "a12345678/" ** 10 ++ "foo", .content = "a" ** 511 },
410 .{ .path = "b12345678/" ** 24 ++ "foo", .content = "b" ** 512 },
411 .{ .path = "c12345678/" ** 25 ++ "foo", .content = "c" ** 513 },
412 .{ .path = "d12345678/" ** 51 ++ "foo", .content = "d" ** 1025 },
413 .{ .path = "e123456789" ** 11, .content = "e" },
414 };
415
416 var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
417 var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
418
419 // with root
420 {
421 const root = "root";
422
423 var output: std.io.AllocatingWriter = undefined;
424 var wrt: Writer = .{ .underlying_writer = output.init(testing.allocator) };
425 defer output.deinit();
426 try wrt.setRoot(root);
427 for (files) |file|
428 try wrt.writeFileBytes(file.path, file.content, .{});
429
430 var input: std.io.FixedBufferStream = .{ .buffer = output.getWritten() };
431 var iter = std.tar.iterator(
432 input.reader(),
433 .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer },
434 );
435
436 // first entry is directory with prefix
437 {
438 const actual = (try iter.next()).?;
439 try testing.expectEqualStrings(root, actual.name);
440 try testing.expectEqual(std.tar.FileKind.directory, actual.kind);
441 }
442
443 var i: usize = 0;
444 while (try iter.next()) |actual| {
445 defer i += 1;
446 const expected = files[i];
447 try testing.expectEqualStrings(root, actual.name[0..root.len]);
448 try testing.expectEqual('/', actual.name[root.len..][0]);
449 try testing.expectEqualStrings(expected.path, actual.name[root.len + 1 ..]);
450
451 var content = std.ArrayList(u8).init(testing.allocator);
452 defer content.deinit();
453 try actual.writeAll(content.writer());
454 try testing.expectEqualSlices(u8, expected.content, content.items);
455 }
456 }
457 // without root
458 {
459 var output: std.io.AllocatingWriter = undefined;
460 var wrt: Writer = .{ .underlying_writer = output.init(testing.allocator) };
461 defer output.deinit();
462 for (files) |file| {
463 var content = std.io.fixedBufferStream(file.content);
464 try wrt.writeFileStream(file.path, file.content.len, content.reader(), .{});
465 }
466
467 var input: std.io.FixedBufferStream = .{ .buffer = output.getWritten() };
468 var iter = std.tar.iterator(
469 input.reader(),
470 .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer },
471 );
472
473 var i: usize = 0;
474 while (try iter.next()) |actual| {
475 defer i += 1;
476 const expected = files[i];
477 try testing.expectEqualStrings(expected.path, actual.name);
478
479 var content = std.ArrayList(u8).init(testing.allocator);
480 defer content.deinit();
481 try actual.writeAll(content.writer());
482 try testing.expectEqualSlices(u8, expected.content, content.items);
483 }
484 try wrt.finish();
485 }
486}
lib/std/zig.zig+1-1
......@@ -416,7 +416,7 @@ fn formatId(
416416 bytes: []const u8,
417417 comptime fmt: []const u8,
418418 options: std.fmt.FormatOptions,
419 writer: anytype,
419 writer: *std.io.BufferedWriter,
420420) !void {
421421 const allow_primitive, const allow_underscore = comptime parse_fmt: {
422422 var allow_primitive = false;
lib/std/zig/ErrorBundle.zig+1-1
......@@ -194,7 +194,7 @@ fn renderErrorMessageToWriter(
194194) anyerror!void {
195195 const ttyconf = options.ttyconf;
196196 var counting_writer: std.io.CountingWriter = .{ .child_writer = bw.writer() };
197 var counting_bw = counting_writer.unbufferedWriter();
197 var counting_bw = counting_writer.writer().unbuffered();
198198 const err_msg = eb.getErrorMessage(err_msg_index);
199199 if (err_msg.src_loc != .none) {
200200 const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));