authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-27 06:49:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-27 20:06:32+01:00
log4b85f05f060ee3365bd86e1885bbefb9e685c81b
tree12a8ceeca354e72fdcaa2eb1c83e471434ce1935
parentb490412cd28e4bc76de9dfe39ac1d99435a86814

Fetch: fix recompression not preserving symlinks

closes #31660

1 files changed, 50 insertions(+), 28 deletions(-)

src/Package/Fetch.zig+50-28
...@@ -383,7 +383,7 @@ pub const JobQueue = struct {...@@ -383,7 +383,7 @@ pub const JobQueue = struct {
383 // However, if we want Zig users to be able to share cached package383 // However, if we want Zig users to be able to share cached package
384 // data with each other via peer-to-peer protocols, we benefit greatly384 // data with each other via peer-to-peer protocols, we benefit greatly
385 // from the data being identical on everyone's computers.385 // from the data being identical on everyone's computers.
386 var scanned_files: std.ArrayList([]const u8) = .empty;386 var scanned_files: std.ArrayList(ScannedFile) = .empty;
387 defer scanned_files.deinit(gpa);387 defer scanned_files.deinit(gpa);
388388
389 var pkg_dir = try jq.root_pkg_path.openDir(io, pkg_hash_slice, .{ .iterate = true });389 var pkg_dir = try jq.root_pkg_path.openDir(io, pkg_hash_slice, .{ .iterate = true });
...@@ -394,22 +394,25 @@ pub const JobQueue = struct {...@@ -394,22 +394,25 @@ pub const JobQueue = struct {
394 defer walker.deinit();394 defer walker.deinit();
395395
396 while (try walker.next(io)) |entry| {396 while (try walker.next(io)) |entry| {
397 switch (entry.kind) {397 const symlink = switch (entry.kind) {
398 .directory => continue,398 .directory => continue,
399 .file, .sym_link => {},399 .file => false,
400 else => {400 .sym_link => true,
401 return error.IllegalFileType;401 else => return error.IllegalFileType,
402 },402 };
403 }
404 const entry_path = try arena.dupe(u8, entry.path);403 const entry_path = try arena.dupe(u8, entry.path);
405 // If necessary, normalize path separators to POSIX-style since the tar format requires that.404 // 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) {405 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);406 std.mem.replaceScalar(u8, entry_path, std.fs.path.sep, std.fs.path.sep_posix);
408 }407 }
409 try scanned_files.append(gpa, entry_path);408 try scanned_files.append(gpa, .{
409 .ptr = entry_path.ptr,
410 .len = @intCast(entry_path.len),
411 .symlink = symlink,
412 });
410 }413 }
411414
412 std.mem.sortUnstable([]const u8, scanned_files.items, {}, stringCmp);415 std.mem.sortUnstable(ScannedFile, scanned_files.items, {}, stringCmp);
413 }416 }
414417
415 prog_node.setEstimatedTotalItems(scanned_files.items.len);418 prog_node.setEstimatedTotalItems(scanned_files.items.len);
...@@ -432,16 +435,26 @@ pub const JobQueue = struct {...@@ -432,16 +435,26 @@ pub const JobQueue = struct {
432 archiver.prefix = pkg_hash_slice;435 archiver.prefix = pkg_hash_slice;
433436
434 var file_read_buffer: [4096]u8 = undefined;437 var file_read_buffer: [4096]u8 = undefined;
435438 var link_buf: [fs.max_path_bytes]u8 = undefined;
436 for (scanned_files.items) |entry_path| {439
437 var file = try pkg_dir.openFile(io, entry_path, .{});440 for (scanned_files.items) |scanned_file| {
438 defer file.close(io);441 const entry_path = scanned_file.ptr[0..scanned_file.len];
439 var file_reader: Io.File.Reader = .init(file, io, &file_read_buffer);442 if (scanned_file.symlink) {
440 archiver.writeFile(entry_path, &file_reader, 0) catch |err| switch (err) {443 const link_name = link_buf[0..try pkg_dir.readLink(io, entry_path, &link_buf)];
441 error.ReadFailed => return file_reader.err.?,444 archiver.writeLink(entry_path, link_name, .{}) catch |err| switch (err) {
442 error.WriteFailed => return file_writer.err.?,445 error.WriteFailed => return file_writer.err.?,
443 else => |e| return e,446 else => |e| return e,
444 };447 };
448 } else {
449 var file = try pkg_dir.openFile(io, entry_path, .{});
450 defer file.close(io);
451 var file_reader: Io.File.Reader = .init(file, io, &file_read_buffer);
452 archiver.writeFile(entry_path, &file_reader, 0) catch |err| switch (err) {
453 error.ReadFailed => return file_reader.err.?,
454 error.WriteFailed => return file_writer.err.?,
455 else => |e| return e,
456 };
457 }
445 prog_node.completeOne();458 prog_node.completeOne();
446 }459 }
447460
...@@ -455,8 +468,14 @@ pub const JobQueue = struct {...@@ -455,8 +468,14 @@ pub const JobQueue = struct {
455 }468 }
456};469};
457470
458fn stringCmp(_: void, lhs: []const u8, rhs: []const u8) bool {471const ScannedFile = struct {
459 return std.mem.lessThan(u8, lhs, rhs);472 ptr: [*]const u8,
473 len: u32,
474 symlink: bool,
475};
476
477fn stringCmp(_: void, lhs: ScannedFile, rhs: ScannedFile) bool {
478 return std.mem.lessThan(u8, lhs.ptr[0..lhs.len], rhs.ptr[0..rhs.len]);
460}479}
461480
462pub const Location = union(enum) {481pub const Location = union(enum) {
...@@ -1819,10 +1838,8 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1819,10 +1838,8 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1819 assert(!f.job_queue.recursive);1838 assert(!f.job_queue.recursive);
1820 // Print something to stdout that can be text diffed to figure out why1839 // Print something to stdout that can be text diffed to figure out why
1821 // the package hash is different.1840 // the package hash is different.
1822 dumpHashInfo(io, all_files.items) catch |err| {1841 dumpHashInfo(io, all_files.items) catch |err|
1823 std.debug.print("unable to write to stdout: {s}\n", .{@errorName(err)});1842 std.process.fatal("unable to write to stdout: {t}", .{err});
1824 std.process.exit(1);
1825 };
1826 }1843 }
18271844
1828 return .{1845 return .{
...@@ -1834,11 +1851,16 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1834,11 +1851,16 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1834fn dumpHashInfo(io: Io, all_files: []const *const HashedFile) !void {1851fn dumpHashInfo(io: Io, all_files: []const *const HashedFile) !void {
1835 var stdout_buffer: [1024]u8 = undefined;1852 var stdout_buffer: [1024]u8 = undefined;
1836 var stdout_writer: Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);1853 var stdout_writer: Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);
1837 const w = &stdout_writer.interface;1854 dumpHashInfoWriter(&stdout_writer.interface, all_files) catch |err| switch (err) {
1855 error.WriteFailed => return stdout_writer.err.?,
1856 };
1857 try stdout_writer.flush();
1858}
1859
1860fn dumpHashInfoWriter(w: *Io.Writer, all_files: []const *const HashedFile) Io.Writer.Error!void {
1838 for (all_files) |hashed_file| {1861 for (all_files) |hashed_file| {
1839 try w.print("{t}: {x}: {s}\n", .{ hashed_file.kind, &hashed_file.hash, hashed_file.normalized_path });1862 try w.print("{t}: {x}: {s}\n", .{ hashed_file.kind, &hashed_file.hash, hashed_file.normalized_path });
1840 }1863 }
1841 try w.flush();
1842}1864}
18431865
1844fn workerHashFile(io: Io, dir: Io.Dir, hashed_file: *HashedFile) void {1866fn workerHashFile(io: Io, dir: Io.Dir, hashed_file: *HashedFile) void {