authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-03 19:44:51+02:00
committergravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-03 19:44:51+02:00
logb1e70edd907da91bc0863b541d04a15f2093f9a0
treeb7a4e4c126c1b5a09ce93dcf320cce6830205ff1
parent24304a43854d62572daab4b5e922bb7436c73c50

tar: find package root dir in pipeToFileSystem

While iterating over all files in tarball set root_dir in diagnostic if there is single root in tarball. Will be used in package manager with strip_components = 0 to find the root of the fetched package.

1 files changed, 102 insertions(+), 8 deletions(-)

lib/std/tar.zig+102-8
...@@ -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) = .{},
3131
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 };
4750
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 {
799843
800test {844test {
801 _ = @import("tar/test.zig");845 _ = @import("tar/test.zig");
846 _ = Diagnostics;
802}847}
803848
804test "header parse size" {849test "header parse size" {
...@@ -993,6 +1038,55 @@ test pipeToFileSystem {...@@ -993,6 +1038,55 @@ test pipeToFileSystem {
993 );1038 );
994}1039}
9951040
1041test "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
996fn normalizePath(bytes: []u8) []u8 {1090fn 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;