From b1e70edd907da91bc0863b541d04a15f2093f9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Wed, 3 Apr 2024 19:44:51 +0200 Subject: [PATCH] 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. --- lib/std/tar.zig | 110 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 0f273bad9a..2977bc16cc 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -29,6 +29,9 @@ pub const Diagnostics = struct { allocator: std.mem.Allocator, errors: std.ArrayListUnmanaged(Error) = .{}, + root_entries: usize = 0, + root_dir: ?[]const u8 = null, + pub const Error = union(enum) { unable_to_create_sym_link: struct { code: anyerror, @@ -45,6 +48,45 @@ pub const Diagnostics = struct { }, }; + fn findRoot(d: *Diagnostics, path: []const u8, kind: FileKind) !void { + if (rootDir(path)) |root_dir| { + d.root_entries += 1; + if (kind == .directory and d.root_entries == 1) { + d.root_dir = try d.allocator.dupe(u8, root_dir); + return; + } + if (d.root_dir) |r| { + d.allocator.free(r); + d.root_dir = null; + } + } + } + + // If path is package root returns root_dir name, otherwise null. + fn rootDir(path: []const u8) ?[]const u8 { + if (path.len == 0) return null; + + const start_index: usize = if (path[0] == '/') 1 else 0; + const end_index: usize = if (path[path.len - 1] == '/') path.len - 1 else path.len; + const buf = path[start_index..end_index]; + return if (std.mem.indexOfScalarPos(u8, buf, 0, '/') == null) + buf + else + null; + } + + test rootDir { + const expectEqualStrings = testing.expectEqualStrings; + const expect = testing.expect; + + try expectEqualStrings("a", rootDir("a").?); + try expectEqualStrings("b", rootDir("b").?); + try expectEqualStrings("c", rootDir("/c").?); + try expectEqualStrings("d", rootDir("/d/").?); + try expect(rootDir("a/b") == null); + try expect(rootDir("") == null); + } + pub fn deinit(d: *Diagnostics) void { for (d.errors.items) |item| { switch (item) { @@ -61,6 +103,10 @@ pub const Diagnostics = struct { } } d.errors.deinit(d.allocator); + if (d.root_dir) |r| { + d.allocator.free(r); + d.root_dir = null; + } d.* = undefined; } }; @@ -580,19 +626,21 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) .link_name_buffer = &link_name_buffer, .diagnostics = options.diagnostics, }); + while (try iter.next()) |file| { + const file_name = stripComponents(file.name, options.strip_components); + if (options.diagnostics) |d| { + try d.findRoot(file_name, file.kind); + } + switch (file.kind) { .directory => { - const file_name = stripComponents(file.name, options.strip_components); if (file_name.len != 0 and !options.exclude_empty_directories) { try dir.makePath(file_name); } }, .file => { - if (file.size == 0 and file.name.len == 0) return; - const file_name = stripComponents(file.name, options.strip_components); if (file_name.len == 0) return error.BadFileName; - if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { defer fs_file.close(); try file.writeAll(fs_file); @@ -605,12 +653,8 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) } }, .sym_link => { - // The file system path of the symbolic link. - const file_name = stripComponents(file.name, options.strip_components); if (file_name.len == 0) return error.BadFileName; - // The data inside the symbolic link. const link_name = file.link_name; - createDirAndSymlink(dir, link_name, file_name) catch |err| { const d = options.diagnostics orelse return error.UnableToCreateSymLink; try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ @@ -799,6 +843,7 @@ test PaxIterator { test { _ = @import("tar/test.zig"); + _ = Diagnostics; } test "header parse size" { @@ -993,6 +1038,55 @@ test pipeToFileSystem { ); } +test "pipeToFileSystem root_dir" { + const data = @embedFile("tar/testdata/example.tar"); + var fbs = std.io.fixedBufferStream(data); + const reader = fbs.reader(); + + // with strip_components = 1 + { + var tmp = testing.tmpDir(.{ .no_follow = true }); + defer tmp.cleanup(); + var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; + defer diagnostics.deinit(); + + pipeToFileSystem(tmp.dir, reader, .{ + .strip_components = 1, + .diagnostics = &diagnostics, + }) catch |err| { + // Skip on platform which don't support symlinks + if (err == error.UnableToCreateSymLink) return error.SkipZigTest; + return err; + }; + + // there is no root_dir + try testing.expect(diagnostics.root_dir == null); + try testing.expectEqual(3, diagnostics.root_entries); + } + + // with strip_components = 0 + { + fbs.reset(); + var tmp = testing.tmpDir(.{ .no_follow = true }); + defer tmp.cleanup(); + var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; + defer diagnostics.deinit(); + + pipeToFileSystem(tmp.dir, reader, .{ + .strip_components = 0, + .diagnostics = &diagnostics, + }) catch |err| { + // Skip on platform which don't support symlinks + if (err == error.UnableToCreateSymLink) return error.SkipZigTest; + return err; + }; + + // root_dir found + try testing.expectEqualStrings("example", diagnostics.root_dir.?); + try testing.expectEqual(1, diagnostics.root_entries); + } +} + fn normalizePath(bytes: []u8) []u8 { const canonical_sep = std.fs.path.sep_posix; if (std.fs.path.sep == canonical_sep) return bytes;