@@ -25,8 +25,20 @@ pub const output = @import("tar/output.zig");
25
25
pub const PipeOptions = struct {
26
26
/// Number of directory levels to skip when extracting files.
27
27
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 ,
28
30
/// Prevents creation of empty directories.
29
31
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
+ };
30
42
};
31
43
32
44
const Header = struct {
@@ -526,7 +538,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
526
538
const file_name = stripComponents (file .name , options .strip_components );
527
539
if (file_name .len == 0 ) return error .BadFileName ;
528
540
529
- var fs_file = try createDirAndFile (dir , file_name );
541
+ var fs_file = try createDirAndFile (dir , file_name , fileMode ( file . mode , options ) );
530
542
defer fs_file .close ();
531
543
try file .writeAll (fs_file );
532
544
},
@@ -543,12 +555,31 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
543
555
}
544
556
}
545
557
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 | {
548
579
if (err == error .FileNotFound ) {
549
580
if (std .fs .path .dirname (file_name )) | dir_name | {
550
581
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 });
552
583
}
553
584
}
554
585
return err ;
@@ -784,9 +815,9 @@ test "create file and symlink" {
784
815
var root = testing .tmpDir (.{});
785
816
defer root .cleanup ();
786
817
787
- var file = try createDirAndFile (root .dir , "file1" );
818
+ var file = try createDirAndFile (root .dir , "file1" , default_mode );
788
819
file .close ();
789
- file = try createDirAndFile (root .dir , "a/b/c/file2" );
820
+ file = try createDirAndFile (root .dir , "a/b/c/file2" , default_mode );
790
821
file .close ();
791
822
792
823
createDirAndSymlink (root .dir , "a/b/c/file2" , "symlink1" ) catch | err | {
@@ -798,7 +829,7 @@ test "create file and symlink" {
798
829
799
830
// Danglink symlnik, file created later
800
831
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 );
802
833
file .close ();
803
834
}
804
835
@@ -893,6 +924,7 @@ test pipeToFileSystem {
893
924
pipeToFileSystem (dir , reader , .{
894
925
.strip_components = 1 ,
895
926
.exclude_empty_directories = true ,
927
+ .mode_mode = .ignore ,
896
928
}) catch | err | {
897
929
// Skip on platform which don't support symlinks
898
930
if (err == error .UnableToCreateSymLink ) return error .SkipZigTest ;
@@ -911,6 +943,45 @@ test pipeToFileSystem {
911
943
);
912
944
}
913
945
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
+
914
985
fn normalizePath (bytes : []u8 ) []u8 {
915
986
const canonical_sep = std .fs .path .sep_posix ;
916
987
if (std .fs .path .sep == canonical_sep ) return bytes ;
0 commit comments