authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-01 18:42:29-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-01 18:42:29-07:00
logea6e0e33a7630fb78550a5567a98420c3377350c
treebee5ca7e7c42188b4764493d90b4695116622df8
parent1fba88450db12f184d76f0651f7ca933322c1fc0

zig build: add executable bit and file path to package hash

Unfortunately, due to the Windows equivalent of executable permissions being a bit tricky, there is follow-up work to be done. What is done in this commit is the hash modifications. At the fetch layer, executable bits inside packages are ignored. In the hash computation layer, executable bit is implemented for POSIX but not yet for Windows. This means that the hash will not break again in the future for packages that do not have any executable files, but it will break for packages that do. This is a hash-breaking change. Closes #14308

3 files changed, 48 insertions(+), 2 deletions(-)

lib/std/fs/file.zig+2-1
......@@ -179,7 +179,7 @@ pub const File = struct {
179179 lock_nonblocking: bool = false,
180180
181181 /// For POSIX systems this is the file system mode the file will
182 /// be created with.
182 /// be created with. On other systems this is always 0.
183183 mode: Mode = default_mode,
184184
185185 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
......@@ -307,6 +307,7 @@ pub const File = struct {
307307 /// is unique to each filesystem.
308308 inode: INode,
309309 size: u64,
310 /// This is available on POSIX systems and is always 0 otherwise.
310311 mode: Mode,
311312 kind: Kind,
312313
lib/std/tar.zig+23
......@@ -1,6 +1,18 @@
11pub const Options = struct {
22 /// Number of directory levels to skip when extracting files.
33 strip_components: u32 = 0,
4 /// How to handle the "mode" property of files from within the tar file.
5 mode_mode: ModeMode = .executable_bit_only,
6
7 const ModeMode = enum {
8 /// The mode from the tar file is completely ignored. Files are created
9 /// with the default mode when creating files.
10 ignore,
11 /// The mode from the tar file is inspected for the owner executable bit
12 /// only. This bit is copied to the group and other executable bits.
13 /// Other bits of the mode are left as the default when creating files.
14 executable_bit_only,
15 };
416};
517
618pub const Header = struct {
......@@ -72,6 +84,17 @@ pub const Header = struct {
7284};
7385
7486pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void {
87 switch (options.mode_mode) {
88 .ignore => {},
89 .executable_bit_only => {
90 // This code does not look at the mode bits yet. To implement this feature,
91 // the implementation must be adjusted to look at the mode, and check the
92 // user executable bit, then call fchmod on newly created files when
93 // the executable bit is supposed to be set.
94 // It also needs to properly deal with ACLs on Windows.
95 @panic("TODO: unimplemented: tar ModeMode.executable_bit_only");
96 },
97 }
7598 var file_name_buffer: [255]u8 = undefined;
7699 var buffer: [512 * 8]u8 = undefined;
77100 var start: usize = 0;
src/Package.zig+23-1
......@@ -1,5 +1,6 @@
11const Package = @This();
22
3const builtin = @import("builtin");
34const std = @import("std");
45const fs = std.fs;
56const mem = std.mem;
......@@ -440,6 +441,12 @@ fn unpackTarball(
440441
441442 try std.tar.pipeToFileSystem(out_dir, decompress.reader(), .{
442443 .strip_components = 1,
444 // TODO: we would like to set this to executable_bit_only, but two
445 // things need to happen before that:
446 // 1. the tar implementation needs to support it
447 // 2. the hashing algorithm here needs to support detecting the is_executable
448 // bit on Windows from the ACLs (see the isExecutable function).
449 .mode_mode = .ignore,
443450 });
444451}
445452
......@@ -468,7 +475,7 @@ const HashedFile = struct {
468475 hash: [Hash.digest_length]u8,
469476 failure: Error!void,
470477
471 const Error = fs.File.OpenError || fs.File.ReadError;
478 const Error = fs.File.OpenError || fs.File.ReadError || fs.File.StatError;
472479
473480 fn lessThan(context: void, lhs: *const HashedFile, rhs: *const HashedFile) bool {
474481 _ = context;
......@@ -544,6 +551,8 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void
544551 var buf: [8000]u8 = undefined;
545552 var file = try dir.openFile(hashed_file.path, .{});
546553 var hasher = Hash.init(.{});
554 hasher.update(hashed_file.path);
555 hasher.update(&.{ 0, @boolToInt(try isExecutable(file)) });
547556 while (true) {
548557 const bytes_read = try file.read(&buf);
549558 if (bytes_read == 0) break;
......@@ -552,6 +561,19 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void
552561 hasher.final(&hashed_file.hash);
553562}
554563
564fn isExecutable(file: fs.File) !bool {
565 if (builtin.os.tag == .windows) {
566 // TODO check the ACL on Windows.
567 // Until this is implemented, this could be a false negative on
568 // Windows, which is why we do not yet set executable_bit_only above
569 // when unpacking the tarball.
570 return false;
571 } else {
572 const stat = try file.stat();
573 return (stat.mode & std.os.S.IXUSR) != 0;
574 }
575}
576
555577const hex_charset = "0123456789abcdef";
556578
557579fn hex64(x: u64) [16]u8 {