Skip to content

Commit 34bb670

Browse files
ianicandrewrk
authored andcommitted
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: #17463 (comment) 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 parent 4a8121c commit 34bb670

File tree

1 file changed

+49
-17
lines changed

1 file changed

+49
-17
lines changed

src/Package/Fetch.zig

+49-17
Original file line numberDiff line numberDiff line change
@@ -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

1603-
fn 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-
}
1607+
fn 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

16181615
const 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.
1749+
const 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+
1769+
test 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+
}

0 commit comments

Comments
 (0)