authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-01 17:17:36+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-02 14:02:43-07:00
logb88ae8dbd84886d3b9b26509034720f755a0e28a
tree374c7862485e1ce6e727749830552e5193fa5db7
parent2a7cedfeb99fb1c230af6ceab75f9137920a3639

std.tar: implement executable bit only


2 files changed, 73 insertions(+), 19 deletions(-)

lib/std/tar.zig+73-19
...@@ -573,18 +573,6 @@ fn PaxIterator(comptime ReaderType: type) type {...@@ -573,18 +573,6 @@ fn PaxIterator(comptime ReaderType: type) type {
573573
574/// Saves tar file content to the file systems.574/// Saves tar file content to the file systems.
575pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) !void {575pub 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
588 var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;576 var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
589 var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;577 var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
590 var iter = iterator(reader, .{578 var iter = iterator(reader, .{
...@@ -605,7 +593,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)...@@ -605,7 +593,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
605 const file_name = stripComponents(file.name, options.strip_components);593 const file_name = stripComponents(file.name, options.strip_components);
606 if (file_name.len == 0) return error.BadFileName;594 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| {
609 defer fs_file.close();597 defer fs_file.close();
610 try file.writeAll(fs_file);598 try file.writeAll(fs_file);
611 } else |err| {599 } else |err| {
...@@ -636,12 +624,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)...@@ -636,12 +624,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
636 }624 }
637}625}
638626
639fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8) !std.fs.File {627fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File {
640 const fs_file = dir.createFile(file_name, .{ .exclusive = true }) catch |err| {628 const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| {
641 if (err == error.FileNotFound) {629 if (err == error.FileNotFound) {
642 if (std.fs.path.dirname(file_name)) |dir_name| {630 if (std.fs.path.dirname(file_name)) |dir_name| {
643 try dir.makePath(dir_name);631 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 });
645 }633 }
646 }634 }
647 return err;635 return err;
...@@ -877,9 +865,9 @@ test "create file and symlink" {...@@ -877,9 +865,9 @@ test "create file and symlink" {
877 var root = testing.tmpDir(.{});865 var root = testing.tmpDir(.{});
878 defer root.cleanup();866 defer root.cleanup();
879867
880 var file = try createDirAndFile(root.dir, "file1");868 var file = try createDirAndFile(root.dir, "file1", default_mode);
881 file.close();869 file.close();
882 file = try createDirAndFile(root.dir, "a/b/c/file2");870 file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode);
883 file.close();871 file.close();
884872
885 createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| {873 createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| {
...@@ -891,7 +879,7 @@ test "create file and symlink" {...@@ -891,7 +879,7 @@ test "create file and symlink" {
891879
892 // Danglink symlnik, file created later880 // Danglink symlnik, file created later
893 try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3");881 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);
895 file.close();883 file.close();
896}884}
897885
...@@ -1011,3 +999,69 @@ fn normalizePath(bytes: []u8) []u8 {...@@ -1011,3 +999,69 @@ fn normalizePath(bytes: []u8) []u8 {
1011 std.mem.replaceScalar(u8, bytes, std.fs.path.sep, canonical_sep);999 std.mem.replaceScalar(u8, bytes, std.fs.path.sep, canonical_sep);
1012 return bytes;1000 return bytes;
1013}1001}
1002
1003const default_mode = std.fs.File.default_mode;
1004
1005// File system mode based on tar header mode and mode_mode options.
1006fn 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
1021test 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
1029test "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
Binary files a/lib/std/tar/testdata/example.tar and b/lib/std/tar/testdata/example.tar differ