Skip to content

package manager: set executable bit #19554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 49 additions & 17 deletions src/Package/Fetch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 => {
Expand All @@ -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 {
Expand All @@ -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 };
Expand Down Expand Up @@ -1746,3 +1744,37 @@ test {
_ = Filter;
_ = FileType;
}

// Detects executable header: ELF magic header or shebang line.
const FileHeader = struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What a delightful little abstraction. I love it!

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());
}