| ... | ... | @@ -29,6 +29,9 @@ pub const Diagnostics = struct { |
| 29 | 29 | allocator: std.mem.Allocator, |
| 30 | 30 | errors: std.ArrayListUnmanaged(Error) = .{}, |
| 31 | 31 | |
| 32 | root_entries: usize = 0, |
| 33 | root_dir: ?[]const u8 = null, |
| 34 | |
| 32 | 35 | pub const Error = union(enum) { |
| 33 | 36 | unable_to_create_sym_link: struct { |
| 34 | 37 | code: anyerror, |
| ... | ... | @@ -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 | 90 | pub fn deinit(d: *Diagnostics) void { |
| 49 | 91 | for (d.errors.items) |item| { |
| 50 | 92 | switch (item) { |
| ... | ... | @@ -61,6 +103,10 @@ pub const Diagnostics = struct { |
| 61 | 103 | } |
| 62 | 104 | } |
| 63 | 105 | d.errors.deinit(d.allocator); |
| 106 | if (d.root_dir) |r| { |
| 107 | d.allocator.free(r); |
| 108 | d.root_dir = null; |
| 109 | } |
| 64 | 110 | d.* = undefined; |
| 65 | 111 | } |
| 66 | 112 | }; |
| ... | ... | @@ -580,19 +626,21 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) |
| 580 | 626 | .link_name_buffer = &link_name_buffer, |
| 581 | 627 | .diagnostics = options.diagnostics, |
| 582 | 628 | }); |
| 629 | |
| 583 | 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 | 636 | switch (file.kind) { |
| 585 | 637 | .directory => { |
| 586 | | const file_name = stripComponents(file.name, options.strip_components); |
| 587 | 638 | if (file_name.len != 0 and !options.exclude_empty_directories) { |
| 588 | 639 | try dir.makePath(file_name); |
| 589 | 640 | } |
| 590 | 641 | }, |
| 591 | 642 | .file => { |
| 592 | | if (file.size == 0 and file.name.len == 0) return; |
| 593 | | const file_name = stripComponents(file.name, options.strip_components); |
| 594 | 643 | if (file_name.len == 0) return error.BadFileName; |
| 595 | | |
| 596 | 644 | if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { |
| 597 | 645 | defer fs_file.close(); |
| 598 | 646 | try file.writeAll(fs_file); |
| ... | ... | @@ -605,12 +653,8 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) |
| 605 | 653 | } |
| 606 | 654 | }, |
| 607 | 655 | .sym_link => { |
| 608 | | // The file system path of the symbolic link. |
| 609 | | const file_name = stripComponents(file.name, options.strip_components); |
| 610 | 656 | if (file_name.len == 0) return error.BadFileName; |
| 611 | | // The data inside the symbolic link. |
| 612 | 657 | const link_name = file.link_name; |
| 613 | | |
| 614 | 658 | createDirAndSymlink(dir, link_name, file_name) catch |err| { |
| 615 | 659 | const d = options.diagnostics orelse return error.UnableToCreateSymLink; |
| 616 | 660 | try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ |
| ... | ... | @@ -799,6 +843,7 @@ test PaxIterator { |
| 799 | 843 | |
| 800 | 844 | test { |
| 801 | 845 | _ = @import("tar/test.zig"); |
| 846 | _ = Diagnostics; |
| 802 | 847 | } |
| 803 | 848 | |
| 804 | 849 | test "header parse size" { |
| ... | ... | @@ -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 | 1090 | fn normalizePath(bytes: []u8) []u8 { |
| 997 | 1091 | const canonical_sep = std.fs.path.sep_posix; |
| 998 | 1092 | if (std.fs.path.sep == canonical_sep) return bytes; |