diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index d00beaa4784f..48993f14cb1a 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1181,7 +1181,6 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!?[]const std.tar.pipeToFileSystem(out_dir, reader, .{ .diagnostics = &diagnostics, .strip_components = 0, - // https://github.com/ziglang/zig/issues/17463 .mode_mode = .ignore, .exclude_empty_directories = true, }) 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 var buf: [8000]u8 = undefined; var hasher = Manifest.Hash.init(.{}); hasher.update(hashed_file.normalized_path); + switch (hashed_file.kind) { .file => { var file = try dir.openFile(hashed_file.fs_path, .{}); defer file.close(); - // When implementing https://github.com/ziglang/zig/issues/17463 - // this will change to hard-coded `false`. - hasher.update(&.{ 0, @intFromBool(try isExecutable(file)) }); + // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463 + hasher.update(&.{ 0, 0 }); + var file_header: FileHeader = .{}; while (true) { const bytes_read = try file.read(&buf); if (bytes_read == 0) break; hasher.update(buf[0..bytes_read]); + file_header.update(buf[0..bytes_read]); + } + if (file_header.isExecutable()) { + try setExecutable(file); } }, .link => { @@ -1600,19 +1604,12 @@ fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error try dir.deleteFile(deleted_file.fs_path); } -fn isExecutable(file: fs.File) !bool { - // When implementing https://github.com/ziglang/zig/issues/17463 - // this function will not check the mode but instead check if the file is an ELF - // file or has a shebang line. - if (native_os == .windows) { - // Until this is implemented, this could be a false negative on - // Windows, which is why we do not yet set executable_bit_only above - // when unpacking the tarball. - return false; - } else { - const stat = try file.stat(); - return (stat.mode & std.posix.S.IXUSR) != 0; - } +fn setExecutable(file: fs.File) !void { + if (!std.fs.has_executable_bit) return; + + const S = std.posix.S; + const mode = fs.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; + try file.chmod(mode); } const DeletedFile = struct { @@ -1635,6 +1632,7 @@ const HashedFile = struct { fs.File.OpenError || fs.File.ReadError || fs.File.StatError || + fs.File.ChmodError || fs.Dir.ReadLinkError; const Kind = enum { file, link }; @@ -1746,3 +1744,37 @@ test { _ = Filter; _ = FileType; } + +// Detects executable header: ELF magic header or shebang line. +const FileHeader = struct { + const elf_magic = std.elf.MAGIC; + const shebang = "#!"; + + header: [@max(elf_magic.len, shebang.len)]u8 = undefined, + bytes_read: usize = 0, + + pub fn update(self: *FileHeader, buf: []const u8) void { + if (self.bytes_read >= self.header.len) return; + const n = @min(self.header.len - self.bytes_read, buf.len); + @memcpy(self.header[self.bytes_read..][0..n], buf[0..n]); + self.bytes_read += n; + } + + pub fn isExecutable(self: *FileHeader) bool { + return std.mem.eql(u8, self.header[0..shebang.len], shebang) or + std.mem.eql(u8, self.header[0..elf_magic.len], elf_magic); + } +}; + +test FileHeader { + var h: FileHeader = .{}; + try std.testing.expect(!h.isExecutable()); + + h.update(FileHeader.elf_magic[0..2]); + try std.testing.expect(!h.isExecutable()); + h.update(FileHeader.elf_magic[2..4]); + try std.testing.expect(h.isExecutable()); + + h.update(FileHeader.elf_magic[2..4]); + try std.testing.expect(h.isExecutable()); +}