diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 8ab7f0d09a..0f273bad9a 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -573,18 +573,6 @@ fn PaxIterator(comptime ReaderType: type) type { /// Saves tar file content to the file systems. pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) !void { - switch (options.mode_mode) { - .ignore => {}, - .executable_bit_only => { - // This code does not look at the mode bits yet. To implement this feature, - // the implementation must be adjusted to look at the mode, and check the - // user executable bit, then call fchmod on newly created files when - // the executable bit is supposed to be set. - // It also needs to properly deal with ACLs on Windows. - @panic("TODO: unimplemented: tar ModeMode.executable_bit_only"); - }, - } - var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; var iter = iterator(reader, .{ @@ -605,7 +593,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) const file_name = stripComponents(file.name, options.strip_components); if (file_name.len == 0) return error.BadFileName; - if (createDirAndFile(dir, file_name)) |fs_file| { + if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { defer fs_file.close(); try file.writeAll(fs_file); } else |err| { @@ -636,12 +624,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) } } -fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8) !std.fs.File { - const fs_file = dir.createFile(file_name, .{ .exclusive = true }) catch |err| { +fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File { + const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { try dir.makePath(dir_name); - return try dir.createFile(file_name, .{ .exclusive = true }); + return try dir.createFile(file_name, .{ .exclusive = true, .mode = mode }); } } return err; @@ -877,9 +865,9 @@ test "create file and symlink" { var root = testing.tmpDir(.{}); defer root.cleanup(); - var file = try createDirAndFile(root.dir, "file1"); + var file = try createDirAndFile(root.dir, "file1", default_mode); file.close(); - file = try createDirAndFile(root.dir, "a/b/c/file2"); + file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode); file.close(); createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| { @@ -891,7 +879,7 @@ test "create file and symlink" { // Danglink symlnik, file created later try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); - file = try createDirAndFile(root.dir, "g/h/i/file4"); + file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode); file.close(); } @@ -1011,3 +999,69 @@ fn normalizePath(bytes: []u8) []u8 { std.mem.replaceScalar(u8, bytes, std.fs.path.sep, canonical_sep); return bytes; } + +const default_mode = std.fs.File.default_mode; + +// File system mode based on tar header mode and mode_mode options. +fn fileMode(mode: u32, options: PipeOptions) std.fs.File.Mode { + if (!std.fs.has_executable_bit or options.mode_mode == .ignore) + return default_mode; + + const S = std.posix.S; + + // The mode from the tar file is inspected for the owner executable bit. + if (mode & S.IXUSR == 0) + return default_mode; + + // This bit is copied to the group and other executable bits. + // Other bits of the mode are left as the default when creating files. + return default_mode | S.IXUSR | S.IXGRP | S.IXOTH; +} + +test fileMode { + if (!std.fs.has_executable_bit) return error.SkipZigTest; + try testing.expectEqual(default_mode, fileMode(0o744, PipeOptions{ .mode_mode = .ignore })); + try testing.expectEqual(0o777, fileMode(0o744, PipeOptions{})); + try testing.expectEqual(0o666, fileMode(0o644, PipeOptions{})); + try testing.expectEqual(0o666, fileMode(0o655, PipeOptions{})); +} + +test "executable bit" { + if (!std.fs.has_executable_bit) return error.SkipZigTest; + + const S = std.posix.S; + const data = @embedFile("tar/testdata/example.tar"); + + for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| { + var fbs = std.io.fixedBufferStream(data); + const reader = fbs.reader(); + + var tmp = testing.tmpDir(.{ .no_follow = true }); + //defer tmp.cleanup(); + + pipeToFileSystem(tmp.dir, reader, .{ + .strip_components = 1, + .exclude_empty_directories = true, + .mode_mode = opt, + }) catch |err| { + // Skip on platform which don't support symlinks + if (err == error.UnableToCreateSymLink) return error.SkipZigTest; + return err; + }; + + const fs = try tmp.dir.statFile("a/file"); + try testing.expect(fs.kind == .file); + + if (opt == .executable_bit_only) { + // Executable bit is set for user, group and others + try testing.expect(fs.mode & S.IXUSR > 0); + try testing.expect(fs.mode & S.IXGRP > 0); + try testing.expect(fs.mode & S.IXOTH > 0); + } + if (opt == .ignore) { + try testing.expect(fs.mode & S.IXUSR == 0); + try testing.expect(fs.mode & S.IXGRP == 0); + try testing.expect(fs.mode & S.IXOTH == 0); + } + } +} diff --git a/lib/std/tar/testdata/example.tar b/lib/std/tar/testdata/example.tar index d66c407eaa..deeeb65c00 100644 Binary files a/lib/std/tar/testdata/example.tar and b/lib/std/tar/testdata/example.tar differ