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 {...@@ -208,6 +208,9 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
208 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };208 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
209 archiver.prefix = "std";209 archiver.prefix = "std";
210210
211 var path_buf: std.ArrayList(u8) = .empty;
212 defer path_buf.deinit(gpa);
213
211 while (try walker.next(io)) |entry| {214 while (try walker.next(io)) |entry| {
212 switch (entry.kind) {215 switch (entry.kind) {
213 .file => {216 .file => {
...@@ -227,7 +230,17 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {...@@ -227,7 +230,17 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
227 .interface = Io.File.Reader.initInterface(&.{}),230 .interface = Io.File.Reader.initInterface(&.{}),
228 .size = stat.size,231 .size = stat.size,
229 };232 };
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);
231 }244 }
232245
233 {246 {
lib/std/Build/WebServer.zig+4
...@@ -526,6 +526,10 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons...@@ -526,6 +526,10 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
526 // resulting in modules named "" and "src". The compiler needs to tell the build system526 // resulting in modules named "" and "src". The compiler needs to tell the build system
527 // about the module graph so that the build system can correctly encode this information in527 // about the module graph so that the build system can correctly encode this information in
528 // the tar file.528 // 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.
529 archiver.prefix = path.root_dir.path orelse graph.cache.cwd;533 archiver.prefix = path.root_dir.path orelse graph.cache.cwd;
530 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));534 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
531 }535 }
lib/std/tar/Writer.zig+12-1
...@@ -17,6 +17,7 @@ pub const Options = struct {...@@ -17,6 +17,7 @@ pub const Options = struct {
17};17};
1818
19underlying_writer: *Io.Writer,19underlying_writer: *Io.Writer,
20/// Assumed to use `/` for any path separators.
20prefix: []const u8 = "",21prefix: []const u8 = "",
2122
22const Error = error{23const Error = error{
...@@ -26,6 +27,7 @@ const Error = error{...@@ -26,6 +27,7 @@ const Error = error{
26};27};
2728
28/// Sets prefix for all other write* method paths.29/// Sets prefix for all other write* method paths.
30/// `root` is assumed to use `/` for any path separators.
29pub fn setRoot(w: *Writer, root: []const u8) Error!void {31pub fn setRoot(w: *Writer, root: []const u8) Error!void {
30 if (root.len > 0)32 if (root.len > 0)
31 try w.writeDir(root, .{});33 try w.writeDir(root, .{});
...@@ -41,6 +43,7 @@ pub const WriteFileError = Io.Writer.FileError || Error || Io.File.Reader.SizeEr...@@ -41,6 +43,7 @@ pub const WriteFileError = Io.Writer.FileError || Error || Io.File.Reader.SizeEr
4143
42pub fn writeFileTimestamp(44pub fn writeFileTimestamp(
43 w: *Writer,45 w: *Writer,
46 /// Assumed to use `/` for any path separators.
44 sub_path: []const u8,47 sub_path: []const u8,
45 file_reader: *Io.File.Reader,48 file_reader: *Io.File.Reader,
46 mtime: Io.Timestamp,49 mtime: Io.Timestamp,
...@@ -50,6 +53,7 @@ pub fn writeFileTimestamp(...@@ -50,6 +53,7 @@ pub fn writeFileTimestamp(
5053
51pub fn writeFile(54pub fn writeFile(
52 w: *Writer,55 w: *Writer,
56 /// Assumed to use `/` for any path separators.
53 sub_path: []const u8,57 sub_path: []const u8,
54 file_reader: *Io.File.Reader,58 file_reader: *Io.File.Reader,
55 /// If you want to match the file format's expectations, it wants number of59 /// 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;...@@ -76,6 +80,7 @@ pub const WriteFileStreamError = Error || Io.Reader.StreamError;
76/// from `reader`, or returns `error.EndOfStream`.80/// from `reader`, or returns `error.EndOfStream`.
77pub fn writeFileStream(81pub fn writeFileStream(
78 w: *Writer,82 w: *Writer,
83 /// Assumed to use `/` for any path separators.
79 sub_path: []const u8,84 sub_path: []const u8,
80 size: u64,85 size: u64,
81 reader: *Io.Reader,86 reader: *Io.Reader,
...@@ -87,7 +92,13 @@ pub fn writeFileStream(...@@ -87,7 +92,13 @@ pub fn writeFileStream(
87}92}
8893
89/// Writes file using bytes buffer `content` for size and file content.94/// 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 {
91 try w.writeHeader(.regular, sub_path, "", content.len, options);102 try w.writeHeader(.regular, sub_path, "", content.len, options);
92 try w.underlying_writer.writeAll(content);103 try w.underlying_writer.writeAll(content);
93 try w.writePadding(content.len);104 try w.writePadding(content.len);
src/Compilation.zig+13-1
...@@ -5440,6 +5440,9 @@ fn docsCopyModule(...@@ -5440,6 +5440,9 @@ fn docsCopyModule(
5440 var archiver: std.tar.Writer = .{ .underlying_writer = &tar_file_writer.interface };5440 var archiver: std.tar.Writer = .{ .underlying_writer = &tar_file_writer.interface };
5441 archiver.prefix = name;5441 archiver.prefix = name;
54425442
5443 var path_buf: std.ArrayList(u8) = .empty;
5444 defer path_buf.deinit(comp.gpa);
5445
5443 var buffer: [1024]u8 = undefined;5446 var buffer: [1024]u8 = undefined;
54445447
5445 while (try walker.next(io)) |entry| {5448 while (try walker.next(io)) |entry| {
...@@ -5460,7 +5463,16 @@ fn docsCopyModule(...@@ -5460,7 +5463,16 @@ fn docsCopyModule(
5460 const stat = try file.stat(io);5463 const stat = try file.stat(io);
5461 var file_reader: Io.File.Reader = .initSize(file, io, &buffer, stat.size);5464 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| {
5464 return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{5476 return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{
5465 root.fmt(comp), entry.path, err,5477 root.fmt(comp), entry.path, err,
5466 });5478 });
src/Package/Fetch.zig+4
...@@ -402,6 +402,10 @@ pub const JobQueue = struct {...@@ -402,6 +402,10 @@ pub const JobQueue = struct {
402 },402 },
403 }403 }
404 const entry_path = try arena.dupe(u8, entry.path);404 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 }
405 try scanned_files.append(gpa, entry_path);409 try scanned_files.append(gpa, entry_path);
406 }410 }
407411