authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-02-27 19:43:30+01:00
committergravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-03 17:06:20+02:00
loga5a928b9668f244a7c5d53f5c75de0152c79f5f6
tree8be198c4bf2f360366af055d3c8ff85cb3965493
parentbc5076715be066540b0b61a5599404f45bfa6d5f

package manager: don't strip components in tar

Unpack tar without removing leading root folder. Then find package root in unpacked tmp folder.

1 files changed, 86 insertions(+), 11 deletions(-)

src/Package/Fetch.zig+86-11
......@@ -439,7 +439,8 @@ fn runResource(
439439 const s = fs.path.sep_str;
440440 const cache_root = f.job_queue.global_cache;
441441 const rand_int = std.crypto.random.int(u64);
442 const tmp_dir_sub_path = "tmp" ++ s ++ Manifest.hex64(rand_int);
442 const tmp_dir_sub_path = "tmp" ++ s ++ Manifest.hex64(rand_int); // root of the temporary directory
443 var tmp_package_root_sub_path: ?[]const u8 = null; // package root inside temporary directory
443444
444445 {
445446 const tmp_directory_path = try cache_root.join(arena, &.{tmp_dir_sub_path});
......@@ -463,6 +464,34 @@ fn runResource(
463464
464465 try unpackResource(f, resource, uri_path, tmp_directory);
465466
467 // Strip leading root directory if needed.
468 if (findPackageRootSubPath(arena, tmp_directory) catch null) |sub_path| {
469 // Position tmp_directory to sub_path.
470 const handle = tmp_directory.handle.openDir(sub_path, .{ .iterate = true }) catch |err| {
471 try eb.addRootErrorMessage(.{
472 .msg = try eb.printString("fail to open temporary directory '{s}' sub path '{s}': {s}", .{
473 tmp_directory_path, sub_path, @errorName(err),
474 }),
475 });
476 return error.FetchFailed;
477 };
478 tmp_package_root_sub_path = try fs.path.join(arena, &[_][]const u8{ tmp_dir_sub_path, sub_path });
479 tmp_directory.handle.close();
480 tmp_directory = .{
481 .path = try cache_root.join(arena, &.{tmp_package_root_sub_path.?}),
482 .handle = handle,
483 };
484 } else {
485 // btrfs workaround; reopen tmp_directory
486 if (native_os == .linux and f.job_queue.work_around_btrfs_bug) {
487 // https://github.com/ziglang/zig/issues/17095
488 tmp_directory.handle.close();
489 tmp_directory.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{
490 .iterate = true,
491 }) catch @panic("btrfs workaround failed");
492 }
493 }
494
466495 // Load, parse, and validate the unpacked build.zig.zon file. It is allowed
467496 // for the file to be missing, in which case this fetched package is
468497 // considered to be a "naked" package.
......@@ -481,15 +510,6 @@ fn runResource(
481510
482511 // Compute the package hash based on the remaining files in the temporary
483512 // directory.
484
485 if (native_os == .linux and f.job_queue.work_around_btrfs_bug) {
486 // https://github.com/ziglang/zig/issues/17095
487 tmp_directory.handle.close();
488 tmp_directory.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{
489 .iterate = true,
490 }) catch @panic("btrfs workaround failed");
491 }
492
493513 f.actual_hash = try computeHash(f, tmp_directory, filter);
494514 }
495515
......@@ -503,7 +523,11 @@ fn runResource(
503523 .root_dir = cache_root,
504524 .sub_path = try arena.dupe(u8, "p" ++ s ++ Manifest.hexDigest(f.actual_hash)),
505525 };
506 renameTmpIntoCache(cache_root.handle, tmp_dir_sub_path, f.package_root.sub_path) catch |err| {
526 renameTmpIntoCache(
527 cache_root.handle,
528 if (tmp_package_root_sub_path) |p| p else tmp_dir_sub_path,
529 f.package_root.sub_path,
530 ) catch |err| {
507531 const src = try cache_root.join(arena, &.{tmp_dir_sub_path});
508532 const dest = try cache_root.join(arena, &.{f.package_root.sub_path});
509533 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
......@@ -512,6 +536,10 @@ fn runResource(
512536 ) });
513537 return error.FetchFailed;
514538 };
539 // Remove temporary directory root if that not already done in rename.
540 if (tmp_package_root_sub_path) |_| {
541 cache_root.handle.deleteTree(tmp_dir_sub_path) catch {};
542 }
515543
516544 // Validate the computed hash against the expected hash. If invalid, this
517545 // job is done.
......@@ -608,6 +636,19 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
608636 }
609637}
610638
639// Finds package root subpath.
640// Skips single root directory, returns null in all other cases.
641fn findPackageRootSubPath(allocator: Allocator, parent: Cache.Directory) !?[]const u8 {
642 var iter = parent.handle.iterate();
643 if (try iter.next()) |entry| {
644 if (try iter.next() != null) return null;
645 if (entry.kind == .directory) {
646 return try allocator.dupe(u8, entry.name);
647 }
648 }
649 return null;
650}
651
611652fn queueJobsForDeps(f: *Fetch) RunError!void {
612653 assert(f.job_queue.recursive);
613654
......@@ -1700,3 +1741,37 @@ test {
17001741 _ = Filter;
17011742 _ = FileType;
17021743}
1744
1745test "findPackageRootSubPath" {
1746 const testing = std.testing;
1747
1748 var root = std.testing.tmpDir(.{ .iterate = true });
1749 defer root.cleanup();
1750
1751 // folder1
1752 // ├── folder2
1753 // ├── file1
1754 //
1755 try root.dir.makePath("folder1/folder2");
1756 (try root.dir.createFile("folder1/file1", .{})).close();
1757
1758 // start at root returns folder1 as package root
1759 const sub_path = (try findPackageRootSubPath(
1760 testing.allocator,
1761 Cache.Directory{ .path = null, .handle = root.dir },
1762 )).?;
1763 try testing.expectEqualStrings("folder1", sub_path);
1764 testing.allocator.free(sub_path);
1765
1766 // start at folder1 returns null
1767 try testing.expect(null == (try findPackageRootSubPath(
1768 testing.allocator,
1769 Cache.Directory{ .path = null, .handle = try root.dir.openDir("folder1", .{ .iterate = true }) },
1770 )));
1771
1772 // start at folder1/folder2 returns null
1773 try testing.expect(null == (try findPackageRootSubPath(
1774 testing.allocator,
1775 Cache.Directory{ .path = null, .handle = try root.dir.openDir("folder1/folder2", .{ .iterate = true }) },
1776 )));
1777}