Skip to content

Commit b88ae8d

Browse files
ianicandrewrk
authored andcommitted
std.tar: implement executable bit only
1 parent 2a7cedf commit b88ae8d

File tree

2 files changed

+73
-19
lines changed

2 files changed

+73
-19
lines changed

lib/std/tar.zig

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -573,18 +573,6 @@ fn PaxIterator(comptime ReaderType: type) type {
573573

574574
/// Saves tar file content to the file systems.
575575
pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) !void {
576-
switch (options.mode_mode) {
577-
.ignore => {},
578-
.executable_bit_only => {
579-
// This code does not look at the mode bits yet. To implement this feature,
580-
// the implementation must be adjusted to look at the mode, and check the
581-
// user executable bit, then call fchmod on newly created files when
582-
// the executable bit is supposed to be set.
583-
// It also needs to properly deal with ACLs on Windows.
584-
@panic("TODO: unimplemented: tar ModeMode.executable_bit_only");
585-
},
586-
}
587-
588576
var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
589577
var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
590578
var iter = iterator(reader, .{
@@ -605,7 +593,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
605593
const file_name = stripComponents(file.name, options.strip_components);
606594
if (file_name.len == 0) return error.BadFileName;
607595

608-
if (createDirAndFile(dir, file_name)) |fs_file| {
596+
if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| {
609597
defer fs_file.close();
610598
try file.writeAll(fs_file);
611599
} else |err| {
@@ -636,12 +624,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
636624
}
637625
}
638626

639-
fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8) !std.fs.File {
640-
const fs_file = dir.createFile(file_name, .{ .exclusive = true }) catch |err| {
627+
fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File {
628+
const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| {
641629
if (err == error.FileNotFound) {
642630
if (std.fs.path.dirname(file_name)) |dir_name| {
643631
try dir.makePath(dir_name);
644-
return try dir.createFile(file_name, .{ .exclusive = true });
632+
return try dir.createFile(file_name, .{ .exclusive = true, .mode = mode });
645633
}
646634
}
647635
return err;
@@ -877,9 +865,9 @@ test "create file and symlink" {
877865
var root = testing.tmpDir(.{});
878866
defer root.cleanup();
879867

880-
var file = try createDirAndFile(root.dir, "file1");
868+
var file = try createDirAndFile(root.dir, "file1", default_mode);
881869
file.close();
882-
file = try createDirAndFile(root.dir, "a/b/c/file2");
870+
file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode);
883871
file.close();
884872

885873
createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| {
@@ -891,7 +879,7 @@ test "create file and symlink" {
891879

892880
// Danglink symlnik, file created later
893881
try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3");
894-
file = try createDirAndFile(root.dir, "g/h/i/file4");
882+
file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode);
895883
file.close();
896884
}
897885

@@ -1011,3 +999,69 @@ fn normalizePath(bytes: []u8) []u8 {
1011999
std.mem.replaceScalar(u8, bytes, std.fs.path.sep, canonical_sep);
10121000
return bytes;
10131001
}
1002+
1003+
const default_mode = std.fs.File.default_mode;
1004+
1005+
// File system mode based on tar header mode and mode_mode options.
1006+
fn fileMode(mode: u32, options: PipeOptions) std.fs.File.Mode {
1007+
if (!std.fs.has_executable_bit or options.mode_mode == .ignore)
1008+
return default_mode;
1009+
1010+
const S = std.posix.S;
1011+
1012+
// The mode from the tar file is inspected for the owner executable bit.
1013+
if (mode & S.IXUSR == 0)
1014+
return default_mode;
1015+
1016+
// This bit is copied to the group and other executable bits.
1017+
// Other bits of the mode are left as the default when creating files.
1018+
return default_mode | S.IXUSR | S.IXGRP | S.IXOTH;
1019+
}
1020+
1021+
test fileMode {
1022+
if (!std.fs.has_executable_bit) return error.SkipZigTest;
1023+
try testing.expectEqual(default_mode, fileMode(0o744, PipeOptions{ .mode_mode = .ignore }));
1024+
try testing.expectEqual(0o777, fileMode(0o744, PipeOptions{}));
1025+
try testing.expectEqual(0o666, fileMode(0o644, PipeOptions{}));
1026+
try testing.expectEqual(0o666, fileMode(0o655, PipeOptions{}));
1027+
}
1028+
1029+
test "executable bit" {
1030+
if (!std.fs.has_executable_bit) return error.SkipZigTest;
1031+
1032+
const S = std.posix.S;
1033+
const data = @embedFile("tar/testdata/example.tar");
1034+
1035+
for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| {
1036+
var fbs = std.io.fixedBufferStream(data);
1037+
const reader = fbs.reader();
1038+
1039+
var tmp = testing.tmpDir(.{ .no_follow = true });
1040+
//defer tmp.cleanup();
1041+
1042+
pipeToFileSystem(tmp.dir, reader, .{
1043+
.strip_components = 1,
1044+
.exclude_empty_directories = true,
1045+
.mode_mode = opt,
1046+
}) catch |err| {
1047+
// Skip on platform which don't support symlinks
1048+
if (err == error.UnableToCreateSymLink) return error.SkipZigTest;
1049+
return err;
1050+
};
1051+
1052+
const fs = try tmp.dir.statFile("a/file");
1053+
try testing.expect(fs.kind == .file);
1054+
1055+
if (opt == .executable_bit_only) {
1056+
// Executable bit is set for user, group and others
1057+
try testing.expect(fs.mode & S.IXUSR > 0);
1058+
try testing.expect(fs.mode & S.IXGRP > 0);
1059+
try testing.expect(fs.mode & S.IXOTH > 0);
1060+
}
1061+
if (opt == .ignore) {
1062+
try testing.expect(fs.mode & S.IXUSR == 0);
1063+
try testing.expect(fs.mode & S.IXGRP == 0);
1064+
try testing.expect(fs.mode & S.IXOTH == 0);
1065+
}
1066+
}
1067+
}

lib/std/tar/testdata/example.tar

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)