authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2023-12-18 21:39:07+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-13 19:37:33-07:00
log7d3a31872eda55438259b54818baaa90b6ecd74c
tree0f37119cde4ccd495925d05ca70a7b939c252f16
parent7923a53996f8d24ad27823db3a45a6dd4a2bf317

tar: improve diagnostic reporting

Using Python testtar file (mentioned in #14310) to test diagnostic reporting. Added computing checksum by using both unsigned and signed header bytes values. Added skipping gnu exteneded sparse headers while reporting unsupported header in diagnostic. Note on testing: wget https://github.com/python/cpython/raw/3.11/Lib/test/testtar.tar -O /tmp/testtar.tar ``` test "Python testtar.tar file" { const file_name = "testtar.tar"; var file = try std.fs.cwd().openFile("/tmp/" ++ file_name, .{}); defer file.close(); var diag = Options.Diagnostics{ .allocator = std.testing.allocator }; defer diag.deinit(); var iter = iterator(file.reader(), &diag); while (try iter.next()) |f| { std.debug.print("supported: {} {s} {d}\n", .{ f.kind, f.name, f.size }); try f.skip(); } for (diag.errors.items) |e| { switch (e) { .unsupported_file_type => |u| { std.debug.print("unsupported: {} {s}\n", .{ u.file_type, u.file_name }); }, else => unreachable, } } } ```

1 files changed, 31 insertions(+), 9 deletions(-)

lib/std/tar.zig+31-9
......@@ -105,6 +105,8 @@ pub const Header = struct {
105105 // used to store the path or link name for the next file.
106106 gnu_long_name = 'L',
107107 gnu_long_link = 'K',
108 gnu_sparse = 'S',
109 solaris_extended_header = 'X',
108110 _,
109111 };
110112
......@@ -194,16 +196,21 @@ pub const Header = struct {
194196 return std.fmt.parseInt(u64, rtrimmed, 8) catch return error.TarHeader;
195197 }
196198
199 const Chksums = struct {
200 unsigned: u64,
201 signed: i64,
202 };
203
197204 // Sum of all bytes in the header block. The chksum field is treated as if
198205 // it were filled with spaces (ASCII 32).
199 fn computeChksum(header: Header) u64 {
200 var sum: u64 = 0;
201 for (header.bytes, 0..) |b, i| {
202 if (148 <= i and i < 156) continue; // skip chksum field bytes
203 sum += b;
206 fn computeChksum(header: Header) Chksums {
207 var cs: Chksums = .{ .signed = 0, .unsigned = 0 };
208 for (header.bytes, 0..) |v, i| {
209 const b = if (148 <= i and i < 156) 32 else v; // Treating chksum bytes as spaces.
210 cs.unsigned += b;
211 cs.signed += @as(i8, @bitCast(b));
204212 }
205 // Treating chksum bytes as spaces. 256 = 8 * 32, 8 spaces.
206 return if (sum > 0) sum + 256 else 0;
213 return cs;
207214 }
208215
209216 // Checks calculated chksum with value of chksum field.
......@@ -211,8 +218,9 @@ pub const Header = struct {
211218 // Zero value indicates empty block.
212219 pub fn checkChksum(header: Header) !u64 {
213220 const field = try header.chksum();
214 const computed = header.computeChksum();
215 if (field != computed) return error.TarHeaderChksum;
221 const cs = header.computeChksum();
222 if (field == 0 and cs.unsigned == 256) return 0;
223 if (field != cs.unsigned and field != cs.signed) return error.TarHeaderChksum;
216224 return field;
217225 }
218226};
......@@ -387,11 +395,25 @@ fn Iterator(comptime ReaderType: type) type {
387395 .file_name = try d.allocator.dupe(u8, header.name()),
388396 .file_type = kind,
389397 } });
398 if (kind == .gnu_sparse) {
399 try self.skipGnuSparseExtendedHeaders(header);
400 }
401 self.reader.skipBytes(size, .{}) catch return error.TarHeadersTooBig;
390402 },
391403 }
392404 }
393405 return null;
394406 }
407
408 fn skipGnuSparseExtendedHeaders(self: *Self, header: Header) !void {
409 var is_extended = header.bytes[482] > 0;
410 while (is_extended) {
411 var buf: [Header.SIZE]u8 = undefined;
412 const n = try self.reader.readAll(&buf);
413 if (n < Header.SIZE) return error.UnexpectedEndOfStream;
414 is_extended = buf[504] > 0;
415 }
416 }
395417 };
396418}
397419