authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-11 16:32:07+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-03 12:12:50-07:00
log035c1b65229083e01bc5ec69f2ad529ba0c56062
treeb7e5ed6d5e6d2da7a8f19d68191ad748ff1bc83a
parentfe66a12a23eda90b89b06dc28957f3baac01ee89

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

2 files changed, 39 insertions(+), 3 deletions(-)

lib/std/tar.zig+38-3
...@@ -46,6 +46,9 @@ pub const Diagnostics = struct {...@@ -46,6 +46,9 @@ pub const Diagnostics = struct {
46 file_name: []const u8,46 file_name: []const u8,
47 file_type: Header.Kind,47 file_type: Header.Kind,
48 },48 },
49 components_outside_stripped_prefix: struct {
50 file_name: []const u8,
51 },
49 };52 };
5053
51 fn findRoot(d: *Diagnostics, path: []const u8) !void {54 fn findRoot(d: *Diagnostics, path: []const u8) !void {
...@@ -97,6 +100,9 @@ pub const Diagnostics = struct {...@@ -97,6 +100,9 @@ pub const Diagnostics = struct {
97 .unsupported_file_type => |info| {100 .unsupported_file_type => |info| {
98 d.allocator.free(info.file_name);101 d.allocator.free(info.file_name);
99 },102 },
103 .components_outside_stripped_prefix => |info| {
104 d.allocator.free(info.file_name);
105 },
100 }106 }
101 }107 }
102 d.errors.deinit(d.allocator);108 d.errors.deinit(d.allocator);
...@@ -623,18 +629,24 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)...@@ -623,18 +629,24 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
623629
624 while (try iter.next()) |file| {630 while (try iter.next()) |file| {
625 const file_name = stripComponents(file.name, options.strip_components);631 const file_name = stripComponents(file.name, options.strip_components);
632 if (file_name.len == 0 and file.kind != .directory) {
633 const d = options.diagnostics orelse return error.TarComponentsOutsideStrippedPrefix;
634 try d.errors.append(d.allocator, .{ .components_outside_stripped_prefix = .{
635 .file_name = try d.allocator.dupe(u8, file.name),
636 } });
637 continue;
638 }
626 if (options.diagnostics) |d| {639 if (options.diagnostics) |d| {
627 try d.findRoot(file_name);640 try d.findRoot(file_name);
628 }641 }
629642
630 switch (file.kind) {643 switch (file.kind) {
631 .directory => {644 .directory => {
632 if (file_name.len != 0 and !options.exclude_empty_directories) {645 if (file_name.len > 0 and !options.exclude_empty_directories) {
633 try dir.makePath(file_name);646 try dir.makePath(file_name);
634 }647 }
635 },648 },
636 .file => {649 .file => {
637 if (file_name.len == 0) return error.BadFileName;
638 if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| {650 if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| {
639 defer fs_file.close();651 defer fs_file.close();
640 try file.writeAll(fs_file);652 try file.writeAll(fs_file);
...@@ -647,7 +659,6 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)...@@ -647,7 +659,6 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions)
647 }659 }
648 },660 },
649 .sym_link => {661 .sym_link => {
650 if (file_name.len == 0) return error.BadFileName;
651 const link_name = file.link_name;662 const link_name = file.link_name;
652 createDirAndSymlink(dir, link_name, file_name) catch |err| {663 createDirAndSymlink(dir, link_name, file_name) catch |err| {
653 const d = options.diagnostics orelse return error.UnableToCreateSymLink;664 const d = options.diagnostics orelse return error.UnableToCreateSymLink;
...@@ -1096,6 +1107,30 @@ test "findRoot without explicit root dir" {...@@ -1096,6 +1107,30 @@ test "findRoot without explicit root dir" {
1096 try testing.expectEqualStrings("root", diagnostics.root_dir);1107 try testing.expectEqualStrings("root", diagnostics.root_dir);
1097}1108}
10981109
1110test "pipeToFileSystem strip_components" {
1111 const data = @embedFile("tar/testdata/example.tar");
1112 var fbs = std.io.fixedBufferStream(data);
1113 const reader = fbs.reader();
1114
1115 var tmp = testing.tmpDir(.{ .no_follow = true });
1116 defer tmp.cleanup();
1117 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1118 defer diagnostics.deinit();
1119
1120 pipeToFileSystem(tmp.dir, reader, .{
1121 .strip_components = 3,
1122 .diagnostics = &diagnostics,
1123 }) catch |err| {
1124 // Skip on platform which don't support symlinks
1125 if (err == error.UnableToCreateSymLink) return error.SkipZigTest;
1126 return err;
1127 };
1128
1129 try testing.expectEqual(2, diagnostics.errors.items.len);
1130 try testing.expectEqualStrings("example/b/symlink", diagnostics.errors.items[0].components_outside_stripped_prefix.file_name);
1131 try testing.expectEqualStrings("example/a/file", diagnostics.errors.items[1].components_outside_stripped_prefix.file_name);
1132}
1133
1099fn normalizePath(bytes: []u8) []u8 {1134fn normalizePath(bytes: []u8) []u8 {
1100 const canonical_sep = std.fs.path.sep_posix;1135 const canonical_sep = std.fs.path.sep_posix;
1101 if (std.fs.path.sep == canonical_sep) return bytes;1136 if (std.fs.path.sep == canonical_sep) return bytes;
src/Package/Fetch.zig+1
...@@ -1189,6 +1189,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes...@@ -1189,6 +1189,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes
1189 .unable_to_create_file => |i| res.unableToCreateFile(stripRoot(i.file_name, res.root_dir), i.code),1189 .unable_to_create_file => |i| res.unableToCreateFile(stripRoot(i.file_name, res.root_dir), i.code),
1190 .unable_to_create_sym_link => |i| res.unableToCreateSymLink(stripRoot(i.file_name, res.root_dir), i.link_name, i.code),1190 .unable_to_create_sym_link => |i| res.unableToCreateSymLink(stripRoot(i.file_name, res.root_dir), i.link_name, i.code),
1191 .unsupported_file_type => |i| res.unsupportedFileType(stripRoot(i.file_name, res.root_dir), @intFromEnum(i.file_type)),1191 .unsupported_file_type => |i| res.unsupportedFileType(stripRoot(i.file_name, res.root_dir), @intFromEnum(i.file_type)),
1192 .components_outside_stripped_prefix => {}, // impossible with strip_components = 0
1192 }1193 }
1193 }1194 }
1194 }1195 }