Skip to content

Commit ac2899f

Browse files
committed
std.tar: enable setting executable bit
In pipeToFileSystem when option mode_mode option is set on posix file systems.
1 parent 438c199 commit ac2899f

File tree

2 files changed

+78
-7
lines changed

2 files changed

+78
-7
lines changed

lib/std/tar.zig

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,20 @@ pub const output = @import("tar/output.zig");
2525
pub const PipeOptions = struct {
2626
/// Number of directory levels to skip when extracting files.
2727
strip_components: u32 = 0,
28+
/// How to handle the "mode" property of files from within the tar file.
29+
mode_mode: ModeMode = .executable_bit_only,
2830
/// Prevents creation of empty directories.
2931
exclude_empty_directories: bool = false,
32+
33+
pub const ModeMode = enum {
34+
/// The mode from the tar file is completely ignored. Files are created
35+
/// with the default mode when creating files.
36+
ignore,
37+
/// The mode from the tar file is inspected for the owner executable bit
38+
/// only. This bit is copied to the group and other executable bits.
39+
/// Other bits of the mode are left as the default when creating files.
40+
executable_bit_only,
41+
};
3042
};
3143

3244
const Header = struct {
@@ -526,7 +538,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
526538
const file_name = stripComponents(file.name, options.strip_components);
527539
if (file_name.len == 0) return error.BadFileName;
528540

529-
var fs_file = try createDirAndFile(dir, file_name);
541+
var fs_file = try createDirAndFile(dir, file_name, fileMode(file.mode, options));
530542
defer fs_file.close();
531543
try file.writeAll(fs_file);
532544
},
@@ -543,12 +555,31 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
543555
}
544556
}
545557

546-
fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8) !std.fs.File {
547-
const fs_file = dir.createFile(file_name, .{ .exclusive = true }) catch |err| {
558+
const default_mode = std.fs.File.default_mode;
559+
560+
fn fileMode(mode: u32, options: PipeOptions) std.fs.File.Mode {
561+
const S = std.posix.S;
562+
if (!std.fs.has_executable_bit or
563+
options.mode_mode == .ignore or
564+
mode & S.IXUSR == 0)
565+
return default_mode;
566+
return default_mode | S.IXUSR | S.IXGRP | S.IXOTH;
567+
}
568+
569+
test fileMode {
570+
if (!std.fs.has_executable_bit) return error.SkipZigTest;
571+
try testing.expectEqual(default_mode, fileMode(0o744, PipeOptions{ .mode_mode = .ignore }));
572+
try testing.expectEqual(0o777, fileMode(0o744, PipeOptions{}));
573+
try testing.expectEqual(0o666, fileMode(0o644, PipeOptions{}));
574+
try testing.expectEqual(0o666, fileMode(0o655, PipeOptions{}));
575+
}
576+
577+
fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File {
578+
const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| {
548579
if (err == error.FileNotFound) {
549580
if (std.fs.path.dirname(file_name)) |dir_name| {
550581
try dir.makePath(dir_name);
551-
return try dir.createFile(file_name, .{ .exclusive = true });
582+
return try dir.createFile(file_name, .{ .exclusive = true, .mode = mode });
552583
}
553584
}
554585
return err;
@@ -784,9 +815,9 @@ test "create file and symlink" {
784815
var root = testing.tmpDir(.{});
785816
defer root.cleanup();
786817

787-
var file = try createDirAndFile(root.dir, "file1");
818+
var file = try createDirAndFile(root.dir, "file1", default_mode);
788819
file.close();
789-
file = try createDirAndFile(root.dir, "a/b/c/file2");
820+
file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode);
790821
file.close();
791822

792823
createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| {
@@ -798,7 +829,7 @@ test "create file and symlink" {
798829

799830
// Danglink symlnik, file created later
800831
try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3");
801-
file = try createDirAndFile(root.dir, "g/h/i/file4");
832+
file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode);
802833
file.close();
803834
}
804835

@@ -893,6 +924,7 @@ test pipeToFileSystem {
893924
pipeToFileSystem(dir, reader, .{
894925
.strip_components = 1,
895926
.exclude_empty_directories = true,
927+
.mode_mode = .ignore,
896928
}) catch |err| {
897929
// Skip on platform which don't support symlinks
898930
if (err == error.UnableToCreateSymLink) return error.SkipZigTest;
@@ -911,6 +943,45 @@ test pipeToFileSystem {
911943
);
912944
}
913945

946+
test "executable bit" {
947+
if (!std.fs.has_executable_bit) return error.SkipZigTest;
948+
const S = std.posix.S;
949+
950+
const data = @embedFile("tar/testdata/example.tar");
951+
952+
for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| {
953+
var fbs = std.io.fixedBufferStream(data);
954+
const reader = fbs.reader();
955+
956+
var tmp = testing.tmpDir(.{ .no_follow = true });
957+
defer tmp.cleanup();
958+
959+
pipeToFileSystem(tmp.dir, reader, .{
960+
.strip_components = 1,
961+
.exclude_empty_directories = true,
962+
.mode_mode = opt,
963+
}) catch |err| {
964+
// Skip on platform which don't support symlinks
965+
if (err == error.UnableToCreateSymLink) return error.SkipZigTest;
966+
return err;
967+
};
968+
969+
const fs = try tmp.dir.statFile("a/file");
970+
try testing.expect(fs.kind == .file);
971+
if (opt == .executable_bit_only) {
972+
// Executable bit is set for user, group and others
973+
try testing.expect(fs.mode & S.IXUSR > 0);
974+
try testing.expect(fs.mode & S.IXGRP > 0);
975+
try testing.expect(fs.mode & S.IXOTH > 0);
976+
}
977+
if (opt == .ignore) {
978+
try testing.expect(fs.mode & S.IXUSR == 0);
979+
try testing.expect(fs.mode & S.IXGRP == 0);
980+
try testing.expect(fs.mode & S.IXOTH == 0);
981+
}
982+
}
983+
}
984+
914985
fn normalizePath(bytes: []u8) []u8 {
915986
const canonical_sep = std.fs.path.sep_posix;
916987
if (std.fs.path.sep == canonical_sep) return bytes;

lib/std/tar/testdata/example.tar

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)