std.tar: add strip components error to diagnostics

This was the only kind of error which was raised in pipeToFileSystem and
not added to Diagnostics.
Shell tar silently ignores paths which are stripped out when used with
`--strip-components` switch. This enables that same behavior, errors
will be collected in diagnostics but caller is free to ignore that type
of diagnostics errors.
Enables use case where caller knows structure of the tar file and want
to extract only some deeply nested folders ignoring upper files/folders.

Fixes: #17620 by giving caller options:
- not provide diagnostic and get errors
- provide diagnostics and analyze errors
- provide diagnostics and ignore errors
This commit is contained in:
Igor Anić 2024-04-11 16:32:07 +02:00 committed by Andrew Kelley
parent fe66a12a23
commit 035c1b6522
2 changed files with 39 additions and 3 deletions

View File

@ -46,6 +46,9 @@ pub const Diagnostics = struct {
file_name: []const u8,
file_type: Header.Kind,
},
components_outside_stripped_prefix: struct {
file_name: []const u8,
},
};
fn findRoot(d: *Diagnostics, path: []const u8) !void {
@ -97,6 +100,9 @@ pub const Diagnostics = struct {
.unsupported_file_type => |info| {
d.allocator.free(info.file_name);
},
.components_outside_stripped_prefix => |info| {
d.allocator.free(info.file_name);
},
}
}
d.errors.deinit(d.allocator);
@ -623,18 +629,24 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
while (try iter.next()) |file| {
const file_name = stripComponents(file.name, options.strip_components);
if (file_name.len == 0 and file.kind != .directory) {
const d = options.diagnostics orelse return error.TarComponentsOutsideStrippedPrefix;
try d.errors.append(d.allocator, .{ .components_outside_stripped_prefix = .{
.file_name = try d.allocator.dupe(u8, file.name),
} });
continue;
}
if (options.diagnostics) |d| {
try d.findRoot(file_name);
}
switch (file.kind) {
.directory => {
if (file_name.len != 0 and !options.exclude_empty_directories) {
if (file_name.len > 0 and !options.exclude_empty_directories) {
try dir.makePath(file_name);
}
},
.file => {
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);
@ -647,7 +659,6 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
}
},
.sym_link => {
if (file_name.len == 0) return error.BadFileName;
const link_name = file.link_name;
createDirAndSymlink(dir, link_name, file_name) catch |err| {
const d = options.diagnostics orelse return error.UnableToCreateSymLink;
@ -1096,6 +1107,30 @@ test "findRoot without explicit root dir" {
try testing.expectEqualStrings("root", diagnostics.root_dir);
}
test "pipeToFileSystem strip_components" {
const data = @embedFile("tar/testdata/example.tar");
var fbs = std.io.fixedBufferStream(data);
const reader = fbs.reader();
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 = 3,
.diagnostics = &diagnostics,
}) catch |err| {
// Skip on platform which don't support symlinks
if (err == error.UnableToCreateSymLink) return error.SkipZigTest;
return err;
};
try testing.expectEqual(2, diagnostics.errors.items.len);
try testing.expectEqualStrings("example/b/symlink", diagnostics.errors.items[0].components_outside_stripped_prefix.file_name);
try testing.expectEqualStrings("example/a/file", diagnostics.errors.items[1].components_outside_stripped_prefix.file_name);
}
fn normalizePath(bytes: []u8) []u8 {
const canonical_sep = std.fs.path.sep_posix;
if (std.fs.path.sep == canonical_sep) return bytes;

View File

@ -1189,6 +1189,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes
.unable_to_create_file => |i| res.unableToCreateFile(stripRoot(i.file_name, res.root_dir), i.code),
.unable_to_create_sym_link => |i| res.unableToCreateSymLink(stripRoot(i.file_name, res.root_dir), i.link_name, i.code),
.unsupported_file_type => |i| res.unsupportedFileType(stripRoot(i.file_name, res.root_dir), @intFromEnum(i.file_type)),
.components_outside_stripped_prefix => {}, // impossible with strip_components = 0
}
}
}