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 {
2929 allocator: std.mem.Allocator,
3030 errors: std.ArrayListUnmanaged(Error) = .{},
3131
32 root_entries: usize = 0,
33 root_dir: ?[]const u8 = null,
34
3235 pub const Error = union(enum) {
3336 unable_to_create_sym_link: struct {
3437 code: anyerror,
......@@ -45,6 +48,45 @@ pub const Diagnostics = struct {
4548 },
4649 };
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
4890 pub fn deinit(d: *Diagnostics) void {
4991 for (d.errors.items) |item| {
5092 switch (item) {
......@@ -61,6 +103,10 @@ pub const Diagnostics = struct {
61103 }
62104 }
63105 d.errors.deinit(d.allocator);
106 if (d.root_dir) |r| {
107 d.allocator.free(r);
108 d.root_dir = null;
109 }
64110 d.* = undefined;
65111 }
66112};
......@@ -580,19 +626,21 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
580626 .link_name_buffer = &link_name_buffer,
581627 .diagnostics = options.diagnostics,
582628 });
629
583630 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
584636 switch (file.kind) {
585637 .directory => {
586 const file_name = stripComponents(file.name, options.strip_components);
587638 if (file_name.len != 0 and !options.exclude_empty_directories) {
588639 try dir.makePath(file_name);
589640 }
590641 },
591642 .file => {
592 if (file.size == 0 and file.name.len == 0) return;
593 const file_name = stripComponents(file.name, options.strip_components);
594643 if (file_name.len == 0) return error.BadFileName;
595
596644 if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| {
597645 defer fs_file.close();
598646 try file.writeAll(fs_file);
......@@ -605,12 +653,8 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
605653 }
606654 },
607655 .sym_link => {
608 // The file system path of the symbolic link.
609 const file_name = stripComponents(file.name, options.strip_components);
610656 if (file_name.len == 0) return error.BadFileName;
611 // The data inside the symbolic link.
612657 const link_name = file.link_name;
613
614658 createDirAndSymlink(dir, link_name, file_name) catch |err| {
615659 const d = options.diagnostics orelse return error.UnableToCreateSymLink;
616660 try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{
......@@ -799,6 +843,7 @@ test PaxIterator {
799843
800844test {
801845 _ = @import("tar/test.zig");
846 _ = Diagnostics;
802847}
803848
804849test "header parse size" {
......@@ -993,6 +1038,55 @@ test pipeToFileSystem {
9931038 );
9941039}
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
9961090fn normalizePath(bytes: []u8) []u8 {
9971091 const canonical_sep = std.fs.path.sep_posix;
9981092 if (std.fs.path.sep == canonical_sep) return bytes;