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...@@ -1181,7 +1181,6 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!?[]const
1181 std.tar.pipeToFileSystem(out_dir, reader, .{1181 std.tar.pipeToFileSystem(out_dir, reader, .{
1182 .diagnostics = &diagnostics,1182 .diagnostics = &diagnostics,
1183 .strip_components = 0,1183 .strip_components = 0,
1184 // https://github.com/ziglang/zig/issues/17463
1185 .mode_mode = .ignore,1184 .mode_mode = .ignore,
1186 .exclude_empty_directories = true,1185 .exclude_empty_directories = true,
1187 }) catch |err| return f.fail(f.location_tok, try eb.printString(1186 }) 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...@@ -1569,17 +1568,22 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void
1569 var buf: [8000]u8 = undefined;1568 var buf: [8000]u8 = undefined;
1570 var hasher = Manifest.Hash.init(.{});1569 var hasher = Manifest.Hash.init(.{});
1571 hasher.update(hashed_file.normalized_path);1570 hasher.update(hashed_file.normalized_path);
1571
1572 switch (hashed_file.kind) {1572 switch (hashed_file.kind) {
1573 .file => {1573 .file => {
1574 var file = try dir.openFile(hashed_file.fs_path, .{});1574 var file = try dir.openFile(hashed_file.fs_path, .{});
1575 defer file.close();1575 defer file.close();
1576 // When implementing https://github.com/ziglang/zig/issues/174631576 // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463
1577 // this will change to hard-coded `false`.1577 hasher.update(&.{ 0, 0 });
1578 hasher.update(&.{ 0, @intFromBool(try isExecutable(file)) });1578 var file_header: FileHeader = .{};
1579 while (true) {1579 while (true) {
1580 const bytes_read = try file.read(&buf);1580 const bytes_read = try file.read(&buf);
1581 if (bytes_read == 0) break;1581 if (bytes_read == 0) break;
1582 hasher.update(buf[0..bytes_read]);1582 hasher.update(buf[0..bytes_read]);
1583 file_header.update(buf[0..bytes_read]);
1584 }
1585 if (file_header.isExecutable()) {
1586 try setExecutable(file);
1583 }1587 }
1584 },1588 },
1585 .link => {1589 .link => {
...@@ -1600,19 +1604,12 @@ fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error...@@ -1600,19 +1604,12 @@ fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error
1600 try dir.deleteFile(deleted_file.fs_path);1604 try dir.deleteFile(deleted_file.fs_path);
1601}1605}
16021606
1603fn isExecutable(file: fs.File) !bool {1607fn setExecutable(file: fs.File) !void {
1604 // When implementing https://github.com/ziglang/zig/issues/174631608 if (!std.fs.has_executable_bit) return;
1605 // this function will not check the mode but instead check if the file is an ELF1609
1606 // file or has a shebang line.1610 const S = std.posix.S;
1607 if (native_os == .windows) {1611 const mode = fs.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH;
1608 // Until this is implemented, this could be a false negative on1612 try file.chmod(mode);
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 }
1616}1613}
16171614
1618const DeletedFile = struct {1615const DeletedFile = struct {
...@@ -1635,6 +1632,7 @@ const HashedFile = struct {...@@ -1635,6 +1632,7 @@ const HashedFile = struct {
1635 fs.File.OpenError ||1632 fs.File.OpenError ||
1636 fs.File.ReadError ||1633 fs.File.ReadError ||
1637 fs.File.StatError ||1634 fs.File.StatError ||
1635 fs.File.ChmodError ||
1638 fs.Dir.ReadLinkError;1636 fs.Dir.ReadLinkError;
16391637
1640 const Kind = enum { file, link };1638 const Kind = enum { file, link };
...@@ -1746,3 +1744,37 @@ test {...@@ -1746,3 +1744,37 @@ test {
1746 _ = Filter;1744 _ = Filter;
1747 _ = FileType;1745 _ = FileType;
1748}1746}
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}