authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-03-06 01:54:12-08:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-03-08 02:10:00+01:00
logf16eb18ce8c24ed743aae1faa4980052cb9f4f36
tree9927c1ece6299a3b0ad195d7e5f8bdcfd6b9bd48
parentc91bb87b01f988d2bad4e015f65b0767abcbce36

Use / as path separator when writing tar files

The tar format expects `/`, although some untar implementations do seem to handle Windows-style `\` path separators (7-Zip at least). The tar.Writer API can't really enforce this, though, as doing so would effectively make `\` an illegal character when it's really not. So, it's up to the user to provide paths with the correct path separators. `Build/WebServer.zig` will still output tars with `\` as a path separator on Windows, but that's currently only used during fuzzing which is not yet implemented on Windows.

5 files changed, 47 insertions(+), 3 deletions(-)

lib/compiler/std-docs.zig+14-1
......@@ -208,6 +208,9 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
208208 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
209209 archiver.prefix = "std";
210210
211 var path_buf: std.ArrayList(u8) = .empty;
212 defer path_buf.deinit(gpa);
213
211214 while (try walker.next(io)) |entry| {
212215 switch (entry.kind) {
213216 .file => {
......@@ -227,7 +230,17 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
227230 .interface = Io.File.Reader.initInterface(&.{}),
228231 .size = stat.size,
229232 };
230 try archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime);
233
234 const posix_path = if (comptime std.fs.path.sep == std.fs.path.sep_posix)
235 entry.path
236 else blk: {
237 path_buf.clearRetainingCapacity();
238 try path_buf.appendSlice(gpa, entry.path);
239 std.mem.replaceScalar(u8, path_buf.items, std.fs.path.sep, std.fs.path.sep_posix);
240 break :blk path_buf.items;
241 };
242
243 try archiver.writeFileTimestamp(posix_path, &file_reader, stat.mtime);
231244 }
232245
233246 {
lib/std/Build/WebServer.zig+4
......@@ -526,6 +526,10 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
526526 // resulting in modules named "" and "src". The compiler needs to tell the build system
527527 // about the module graph so that the build system can correctly encode this information in
528528 // the tar file.
529 //
530 // Additionally, this needs to ensure that all path separators for both prefix and
531 // sub_path are using the POSIX-style `/` on platforms that don't use it as their native
532 // path separator.
529533 archiver.prefix = path.root_dir.path orelse graph.cache.cwd;
530534 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
531535 }
lib/std/tar/Writer.zig+12-1
......@@ -17,6 +17,7 @@ pub const Options = struct {
1717};
1818
1919underlying_writer: *Io.Writer,
20/// Assumed to use `/` for any path separators.
2021prefix: []const u8 = "",
2122
2223const Error = error{
......@@ -26,6 +27,7 @@ const Error = error{
2627};
2728
2829/// Sets prefix for all other write* method paths.
30/// `root` is assumed to use `/` for any path separators.
2931pub fn setRoot(w: *Writer, root: []const u8) Error!void {
3032 if (root.len > 0)
3133 try w.writeDir(root, .{});
......@@ -41,6 +43,7 @@ pub const WriteFileError = Io.Writer.FileError || Error || Io.File.Reader.SizeEr
4143
4244pub fn writeFileTimestamp(
4345 w: *Writer,
46 /// Assumed to use `/` for any path separators.
4447 sub_path: []const u8,
4548 file_reader: *Io.File.Reader,
4649 mtime: Io.Timestamp,
......@@ -50,6 +53,7 @@ pub fn writeFileTimestamp(
5053
5154pub fn writeFile(
5255 w: *Writer,
56 /// Assumed to use `/` for any path separators.
5357 sub_path: []const u8,
5458 file_reader: *Io.File.Reader,
5559 /// If you want to match the file format's expectations, it wants number of
......@@ -76,6 +80,7 @@ pub const WriteFileStreamError = Error || Io.Reader.StreamError;
7680/// from `reader`, or returns `error.EndOfStream`.
7781pub fn writeFileStream(
7882 w: *Writer,
83 /// Assumed to use `/` for any path separators.
7984 sub_path: []const u8,
8085 size: u64,
8186 reader: *Io.Reader,
......@@ -87,7 +92,13 @@ pub fn writeFileStream(
8792}
8893
8994/// Writes file using bytes buffer `content` for size and file content.
90pub fn writeFileBytes(w: *Writer, sub_path: []const u8, content: []const u8, options: Options) Error!void {
95pub fn writeFileBytes(
96 w: *Writer,
97 /// Assumed to use `/` for all path separators.
98 sub_path: []const u8,
99 content: []const u8,
100 options: Options,
101) Error!void {
91102 try w.writeHeader(.regular, sub_path, "", content.len, options);
92103 try w.underlying_writer.writeAll(content);
93104 try w.writePadding(content.len);
src/Compilation.zig+13-1
......@@ -5440,6 +5440,9 @@ fn docsCopyModule(
54405440 var archiver: std.tar.Writer = .{ .underlying_writer = &tar_file_writer.interface };
54415441 archiver.prefix = name;
54425442
5443 var path_buf: std.ArrayList(u8) = .empty;
5444 defer path_buf.deinit(comp.gpa);
5445
54435446 var buffer: [1024]u8 = undefined;
54445447
54455448 while (try walker.next(io)) |entry| {
......@@ -5460,7 +5463,16 @@ fn docsCopyModule(
54605463 const stat = try file.stat(io);
54615464 var file_reader: Io.File.Reader = .initSize(file, io, &buffer, stat.size);
54625465
5463 archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| {
5466 const posix_path = if (comptime std.fs.path.sep == std.fs.path.sep_posix)
5467 entry.path
5468 else blk: {
5469 path_buf.clearRetainingCapacity();
5470 try path_buf.appendSlice(comp.gpa, entry.path);
5471 std.mem.replaceScalar(u8, path_buf.items, std.fs.path.sep, std.fs.path.sep_posix);
5472 break :blk path_buf.items;
5473 };
5474
5475 archiver.writeFileTimestamp(posix_path, &file_reader, stat.mtime) catch |err| {
54645476 return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{
54655477 root.fmt(comp), entry.path, err,
54665478 });
src/Package/Fetch.zig+4
......@@ -402,6 +402,10 @@ pub const JobQueue = struct {
402402 },
403403 }
404404 const entry_path = try arena.dupe(u8, entry.path);
405 // If necessary, normalize path separators to POSIX-style since the tar format requires that.
406 if (comptime std.fs.path.sep != std.fs.path.sep_posix) {
407 std.mem.replaceScalar(u8, entry_path, std.fs.path.sep, std.fs.path.sep_posix);
408 }
405409 try scanned_files.append(gpa, entry_path);
406410 }
407411