| ... | @@ -29,6 +29,9 @@ pub const Diagnostics = struct { | ... | @@ -29,6 +29,9 @@ pub const Diagnostics = struct { |
| 29 | allocator: std.mem.Allocator, | 29 | allocator: std.mem.Allocator, |
| 30 | errors: std.ArrayListUnmanaged(Error) = .{}, | 30 | errors: std.ArrayListUnmanaged(Error) = .{}, |
| 31 | | 31 | |
| | 32 | root_entries: usize = 0, |
| | 33 | root_dir: ?[]const u8 = null, |
| | 34 | |
| 32 | pub const Error = union(enum) { | 35 | pub const Error = union(enum) { |
| 33 | unable_to_create_sym_link: struct { | 36 | unable_to_create_sym_link: struct { |
| 34 | code: anyerror, | 37 | code: anyerror, |
| ... | @@ -45,6 +48,45 @@ pub const Diagnostics = struct { | ... | @@ -45,6 +48,45 @@ pub const Diagnostics = struct { |
| 45 | }, | 48 | }, |
| 46 | }; | 49 | }; |
| 47 | | 50 | |
| | 51 | fn findRoot(d: *Diagnostics, path: []const u8, kind: FileKind) !void { |
| | 52 | if (rootDir(path)) |root_dir| { |
| | 53 | d.root_entries += 1; |
| | 54 | if (kind == .directory and d.root_entries == 1) { |
| | 55 | d.root_dir = try d.allocator.dupe(u8, root_dir); |
| | 56 | return; |
| | 57 | } |
| | 58 | if (d.root_dir) |r| { |
| | 59 | d.allocator.free(r); |
| | 60 | d.root_dir = null; |
| | 61 | } |
| | 62 | } |
| | 63 | } |
| | 64 | |
| | 65 | // If path is package root returns root_dir name, otherwise null. |
| | 66 | fn rootDir(path: []const u8) ?[]const u8 { |
| | 67 | if (path.len == 0) return null; |
| | 68 | |
| | 69 | const start_index: usize = if (path[0] == '/') 1 else 0; |
| | 70 | const end_index: usize = if (path[path.len - 1] == '/') path.len - 1 else path.len; |
| | 71 | const buf = path[start_index..end_index]; |
| | 72 | return if (std.mem.indexOfScalarPos(u8, buf, 0, '/') == null) |
| | 73 | buf |
| | 74 | else |
| | 75 | null; |
| | 76 | } |
| | 77 | |
| | 78 | test rootDir { |
| | 79 | const expectEqualStrings = testing.expectEqualStrings; |
| | 80 | const expect = testing.expect; |
| | 81 | |
| | 82 | try expectEqualStrings("a", rootDir("a").?); |
| | 83 | try expectEqualStrings("b", rootDir("b").?); |
| | 84 | try expectEqualStrings("c", rootDir("/c").?); |
| | 85 | try expectEqualStrings("d", rootDir("/d/").?); |
| | 86 | try expect(rootDir("a/b") == null); |
| | 87 | try expect(rootDir("") == null); |
| | 88 | } |
| | 89 | |
| 48 | pub fn deinit(d: *Diagnostics) void { | 90 | pub fn deinit(d: *Diagnostics) void { |
| 49 | for (d.errors.items) |item| { | 91 | for (d.errors.items) |item| { |
| 50 | switch (item) { | 92 | switch (item) { |
| ... | @@ -61,6 +103,10 @@ pub const Diagnostics = struct { | ... | @@ -61,6 +103,10 @@ pub const Diagnostics = struct { |
| 61 | } | 103 | } |
| 62 | } | 104 | } |
| 63 | d.errors.deinit(d.allocator); | 105 | d.errors.deinit(d.allocator); |
| | 106 | if (d.root_dir) |r| { |
| | 107 | d.allocator.free(r); |
| | 108 | d.root_dir = null; |
| | 109 | } |
| 64 | d.* = undefined; | 110 | d.* = undefined; |
| 65 | } | 111 | } |
| 66 | }; | 112 | }; |
| ... | @@ -580,19 +626,21 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) | ... | @@ -580,19 +626,21 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) |
| 580 | .link_name_buffer = &link_name_buffer, | 626 | .link_name_buffer = &link_name_buffer, |
| 581 | .diagnostics = options.diagnostics, | 627 | .diagnostics = options.diagnostics, |
| 582 | }); | 628 | }); |
| | 629 | |
| 583 | while (try iter.next()) |file| { | 630 | while (try iter.next()) |file| { |
| | 631 | const file_name = stripComponents(file.name, options.strip_components); |
| | 632 | if (options.diagnostics) |d| { |
| | 633 | try d.findRoot(file_name, file.kind); |
| | 634 | } |
| | 635 | |
| 584 | switch (file.kind) { | 636 | switch (file.kind) { |
| 585 | .directory => { | 637 | .directory => { |
| 586 | const file_name = stripComponents(file.name, options.strip_components); | | |
| 587 | if (file_name.len != 0 and !options.exclude_empty_directories) { | 638 | if (file_name.len != 0 and !options.exclude_empty_directories) { |
| 588 | try dir.makePath(file_name); | 639 | try dir.makePath(file_name); |
| 589 | } | 640 | } |
| 590 | }, | 641 | }, |
| 591 | .file => { | 642 | .file => { |
| 592 | if (file.size == 0 and file.name.len == 0) return; | | |
| 593 | const file_name = stripComponents(file.name, options.strip_components); | | |
| 594 | if (file_name.len == 0) return error.BadFileName; | 643 | if (file_name.len == 0) return error.BadFileName; |
| 595 | | | |
| 596 | if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { | 644 | if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { |
| 597 | defer fs_file.close(); | 645 | defer fs_file.close(); |
| 598 | try file.writeAll(fs_file); | 646 | try file.writeAll(fs_file); |
| ... | @@ -605,12 +653,8 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) | ... | @@ -605,12 +653,8 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) |
| 605 | } | 653 | } |
| 606 | }, | 654 | }, |
| 607 | .sym_link => { | 655 | .sym_link => { |
| 608 | // The file system path of the symbolic link. | | |
| 609 | const file_name = stripComponents(file.name, options.strip_components); | | |
| 610 | if (file_name.len == 0) return error.BadFileName; | 656 | if (file_name.len == 0) return error.BadFileName; |
| 611 | // The data inside the symbolic link. | | |
| 612 | const link_name = file.link_name; | 657 | const link_name = file.link_name; |
| 613 | | | |
| 614 | createDirAndSymlink(dir, link_name, file_name) catch |err| { | 658 | createDirAndSymlink(dir, link_name, file_name) catch |err| { |
| 615 | const d = options.diagnostics orelse return error.UnableToCreateSymLink; | 659 | const d = options.diagnostics orelse return error.UnableToCreateSymLink; |
| 616 | try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ | 660 | try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ |
| ... | @@ -799,6 +843,7 @@ test PaxIterator { | ... | @@ -799,6 +843,7 @@ test PaxIterator { |
| 799 | | 843 | |
| 800 | test { | 844 | test { |
| 801 | _ = @import("tar/test.zig"); | 845 | _ = @import("tar/test.zig"); |
| | 846 | _ = Diagnostics; |
| 802 | } | 847 | } |
| 803 | | 848 | |
| 804 | test "header parse size" { | 849 | test "header parse size" { |
| ... | @@ -993,6 +1038,55 @@ test pipeToFileSystem { | ... | @@ -993,6 +1038,55 @@ test pipeToFileSystem { |
| 993 | ); | 1038 | ); |
| 994 | } | 1039 | } |
| 995 | | 1040 | |
| | 1041 | test "pipeToFileSystem root_dir" { |
| | 1042 | const data = @embedFile("tar/testdata/example.tar"); |
| | 1043 | var fbs = std.io.fixedBufferStream(data); |
| | 1044 | const reader = fbs.reader(); |
| | 1045 | |
| | 1046 | // with strip_components = 1 |
| | 1047 | { |
| | 1048 | var tmp = testing.tmpDir(.{ .no_follow = true }); |
| | 1049 | defer tmp.cleanup(); |
| | 1050 | var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; |
| | 1051 | defer diagnostics.deinit(); |
| | 1052 | |
| | 1053 | pipeToFileSystem(tmp.dir, reader, .{ |
| | 1054 | .strip_components = 1, |
| | 1055 | .diagnostics = &diagnostics, |
| | 1056 | }) catch |err| { |
| | 1057 | // Skip on platform which don't support symlinks |
| | 1058 | if (err == error.UnableToCreateSymLink) return error.SkipZigTest; |
| | 1059 | return err; |
| | 1060 | }; |
| | 1061 | |
| | 1062 | // there is no root_dir |
| | 1063 | try testing.expect(diagnostics.root_dir == null); |
| | 1064 | try testing.expectEqual(3, diagnostics.root_entries); |
| | 1065 | } |
| | 1066 | |
| | 1067 | // with strip_components = 0 |
| | 1068 | { |
| | 1069 | fbs.reset(); |
| | 1070 | var tmp = testing.tmpDir(.{ .no_follow = true }); |
| | 1071 | defer tmp.cleanup(); |
| | 1072 | var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; |
| | 1073 | defer diagnostics.deinit(); |
| | 1074 | |
| | 1075 | pipeToFileSystem(tmp.dir, reader, .{ |
| | 1076 | .strip_components = 0, |
| | 1077 | .diagnostics = &diagnostics, |
| | 1078 | }) catch |err| { |
| | 1079 | // Skip on platform which don't support symlinks |
| | 1080 | if (err == error.UnableToCreateSymLink) return error.SkipZigTest; |
| | 1081 | return err; |
| | 1082 | }; |
| | 1083 | |
| | 1084 | // root_dir found |
| | 1085 | try testing.expectEqualStrings("example", diagnostics.root_dir.?); |
| | 1086 | try testing.expectEqual(1, diagnostics.root_entries); |
| | 1087 | } |
| | 1088 | } |
| | 1089 | |
| 996 | fn normalizePath(bytes: []u8) []u8 { | 1090 | fn normalizePath(bytes: []u8) []u8 { |
| 997 | const canonical_sep = std.fs.path.sep_posix; | 1091 | const canonical_sep = std.fs.path.sep_posix; |
| 998 | if (std.fs.path.sep == canonical_sep) return bytes; | 1092 | if (std.fs.path.sep == canonical_sep) return bytes; |