@@ -573,18 +573,6 @@ fn PaxIterator(comptime ReaderType: type) type {
573573
574574/// Saves tar file content to the file systems.
575575pub 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+ }
0 commit comments