authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-05 23:32:18+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-06 13:00:57-07:00
log34bb670bb695969ce57cb3fc0e1bb673a1cc7243
tree751db6d2115ab382af6536e928bfc6d885fca627
parent4a8121c1abfe6ffe9c6720e2841d80a36927b11a

package manager: set executable bit

Based on file content. Detects elf magic header or shebang line. Executable bit is ignored in hash calculation, as it was before this. So packages hashes are not changed. Reference: https://github.com/ziglang/zig/issues/17463#issuecomment-1984798880 Fixes: 17463 Test is here: https://github.com/ianic/zig-fetch-test/blob/7c4600d7bb263f9b72fe3d0b70071f42be89e25c/src/main.zig#L307 (if #19500 got accepted I'll move this test to the Fetch.zig)

1 files changed, 49 insertions(+), 17 deletions(-)

src/Package/Fetch.zig+49-17
......@@ -1181,7 +1181,6 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!?[]const
11811181 std.tar.pipeToFileSystem(out_dir, reader, .{
11821182 .diagnostics = &diagnostics,
11831183 .strip_components = 0,
1184 // https://github.com/ziglang/zig/issues/17463
11851184 .mode_mode = .ignore,
11861185 .exclude_empty_directories = true,
11871186 }) catch |err| return f.fail(f.location_tok, try eb.printString(
......@@ -1569,17 +1568,22 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void
15691568 var buf: [8000]u8 = undefined;
15701569 var hasher = Manifest.Hash.init(.{});
15711570 hasher.update(hashed_file.normalized_path);
1571
15721572 switch (hashed_file.kind) {
15731573 .file => {
15741574 var file = try dir.openFile(hashed_file.fs_path, .{});
15751575 defer file.close();
1576 // When implementing https://github.com/ziglang/zig/issues/17463
1577 // this will change to hard-coded `false`.
1578 hasher.update(&.{ 0, @intFromBool(try isExecutable(file)) });
1576 // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463
1577 hasher.update(&.{ 0, 0 });
1578 var file_header: FileHeader = .{};
15791579 while (true) {
15801580 const bytes_read = try file.read(&buf);
15811581 if (bytes_read == 0) break;
15821582 hasher.update(buf[0..bytes_read]);
1583 file_header.update(buf[0..bytes_read]);
1584 }
1585 if (file_header.isExecutable()) {
1586 try setExecutable(file);
15831587 }
15841588 },
15851589 .link => {
......@@ -1600,19 +1604,12 @@ fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error
16001604 try dir.deleteFile(deleted_file.fs_path);
16011605}
16021606
1603fn isExecutable(file: fs.File) !bool {
1604 // When implementing https://github.com/ziglang/zig/issues/17463
1605 // this function will not check the mode but instead check if the file is an ELF
1606 // file or has a shebang line.
1607 if (native_os == .windows) {
1608 // Until this is implemented, this could be a false negative on
1609 // Windows, which is why we do not yet set executable_bit_only above
1610 // when unpacking the tarball.
1611 return false;
1612 } else {
1613 const stat = try file.stat();
1614 return (stat.mode & std.posix.S.IXUSR) != 0;
1615 }
1607fn setExecutable(file: fs.File) !void {
1608 if (!std.fs.has_executable_bit) return;
1609
1610 const S = std.posix.S;
1611 const mode = fs.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH;
1612 try file.chmod(mode);
16161613}
16171614
16181615const DeletedFile = struct {
......@@ -1635,6 +1632,7 @@ const HashedFile = struct {
16351632 fs.File.OpenError ||
16361633 fs.File.ReadError ||
16371634 fs.File.StatError ||
1635 fs.File.ChmodError ||
16381636 fs.Dir.ReadLinkError;
16391637
16401638 const Kind = enum { file, link };
......@@ -1746,3 +1744,37 @@ test {
17461744 _ = Filter;
17471745 _ = FileType;
17481746}
1747
1748// Detects executable header: ELF magic header or shebang line.
1749const FileHeader = struct {
1750 const elf_magic = std.elf.MAGIC;
1751 const shebang = "#!";
1752
1753 header: [@max(elf_magic.len, shebang.len)]u8 = undefined,
1754 bytes_read: usize = 0,
1755
1756 pub fn update(self: *FileHeader, buf: []const u8) void {
1757 if (self.bytes_read >= self.header.len) return;
1758 const n = @min(self.header.len - self.bytes_read, buf.len);
1759 @memcpy(self.header[self.bytes_read..][0..n], buf[0..n]);
1760 self.bytes_read += n;
1761 }
1762
1763 pub fn isExecutable(self: *FileHeader) bool {
1764 return std.mem.eql(u8, self.header[0..shebang.len], shebang) or
1765 std.mem.eql(u8, self.header[0..elf_magic.len], elf_magic);
1766 }
1767};
1768
1769test FileHeader {
1770 var h: FileHeader = .{};
1771 try std.testing.expect(!h.isExecutable());
1772
1773 h.update(FileHeader.elf_magic[0..2]);
1774 try std.testing.expect(!h.isExecutable());
1775 h.update(FileHeader.elf_magic[2..4]);
1776 try std.testing.expect(h.isExecutable());
1777
1778 h.update(FileHeader.elf_magic[2..4]);
1779 try std.testing.expect(h.isExecutable());
1780}