authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2023-11-29 15:28:38+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-13 19:37:33-07:00
loge1424b84b87903df265cc052f3dac17d1ec1c3be
tree1f19c18bb24edf23fe662706174bf179e57fe554
parent6d5283e83550998953f8784ba2b08a413a41baf2

tar: add parsing size in gnu extended format

Reference: https://www.gnu.org/software/tar/manual/html_node/Extensions.html#Extensions If the leading byte is 0x80 (128), the non-leading bytes of the field are concatenated in big-endian order, with the result being a positive number expressed in binary form.

1 files changed, 56 insertions(+), 19 deletions(-)

lib/std/tar.zig+56-19
...@@ -62,10 +62,10 @@ pub const Options = struct {...@@ -62,10 +62,10 @@ pub const Options = struct {
62 };62 };
63};63};
6464
65const block_size = 512;65const BLOCK_SIZE = 512;
6666
67pub const Header = struct {67pub const Header = struct {
68 bytes: *const [block_size]u8,68 bytes: *const [BLOCK_SIZE]u8,
6969
70 pub const FileType = enum(u8) {70 pub const FileType = enum(u8) {
71 normal_alias = 0,71 normal_alias = 0,
...@@ -84,6 +84,19 @@ pub const Header = struct {...@@ -84,6 +84,19 @@ pub const Header = struct {
8484
85 pub fn fileSize(header: Header) !u64 {85 pub fn fileSize(header: Header) !u64 {
86 const raw = header.bytes[124..][0..12];86 const raw = header.bytes[124..][0..12];
87 // If the leading byte is 0xff (255), all the bytes of the field
88 // (including the leading byte) are concatenated in big-endian order,
89 // with the result being a negative number expressed in two’s
90 // complement form.
91 if (raw[0] == 0xff) return error.SizeNegative;
92 // If the leading byte is 0x80 (128), the non-leading bytes of the
93 // field are concatenated in big-endian order.
94 if (raw[0] == 0x80) {
95 if (raw[1] + raw[2] + raw[3] != 0) return error.SizeTooBig;
96 return std.mem.readInt(u64, raw[4..12], .big);
97 }
98 // Zero-filled octal number in ASCII. Each numeric field of width w
99 // contains w minus 1 digits, and a null
87 const ltrimmed = std.mem.trimLeft(u8, raw, "0 ");100 const ltrimmed = std.mem.trimLeft(u8, raw, "0 ");
88 const rtrimmed = std.mem.trimRight(u8, ltrimmed, " \x00");101 const rtrimmed = std.mem.trimRight(u8, ltrimmed, " \x00");
89 if (rtrimmed.len == 0) return 0;102 if (rtrimmed.len == 0) return 0;
...@@ -148,7 +161,7 @@ pub const Header = struct {...@@ -148,7 +161,7 @@ pub const Header = struct {
148fn BufferedReader(comptime ReaderType: type) type {161fn BufferedReader(comptime ReaderType: type) type {
149 return struct {162 return struct {
150 unbuffered_reader: ReaderType,163 unbuffered_reader: ReaderType,
151 buffer: [block_size * 8]u8 = undefined,164 buffer: [BLOCK_SIZE * 8]u8 = undefined,
152 start: usize = 0,165 start: usize = 0,
153 end: usize = 0,166 end: usize = 0,
154167
...@@ -164,14 +177,14 @@ fn BufferedReader(comptime ReaderType: type) type {...@@ -164,14 +177,14 @@ fn BufferedReader(comptime ReaderType: type) type {
164 }177 }
165178
166 pub fn readBlock(self: *Self) !?[]const u8 {179 pub fn readBlock(self: *Self) !?[]const u8 {
167 const block_bytes = try self.readChunk(block_size * 2);180 const block_bytes = try self.readChunk(BLOCK_SIZE * 2);
168 switch (block_bytes.len) {181 switch (block_bytes.len) {
169 0 => return null,182 0 => return null,
170 1...(block_size - 1) => return error.UnexpectedEndOfStream,183 1...(BLOCK_SIZE - 1) => return error.UnexpectedEndOfStream,
171 else => {},184 else => {},
172 }185 }
173 self.advance(block_size);186 self.advance(BLOCK_SIZE);
174 return block_bytes[0..block_size];187 return block_bytes[0..BLOCK_SIZE];
175 }188 }
176189
177 pub fn advance(self: *Self, count: usize) void {190 pub fn advance(self: *Self, count: usize) void {
...@@ -258,7 +271,7 @@ fn BufferedReader(comptime ReaderType: type) type {...@@ -258,7 +271,7 @@ fn BufferedReader(comptime ReaderType: type) type {
258 pub fn sliceReader(self: *Self, size: usize, auto_advance: bool) Self.SliceReader {271 pub fn sliceReader(self: *Self, size: usize, auto_advance: bool) Self.SliceReader {
259 return .{272 return .{
260 .size = size,273 .size = size,
261 .chunk_size = roundedFileSize(size) + block_size,274 .chunk_size = roundedFileSize(size) + BLOCK_SIZE,
262 .offset = 0,275 .offset = 0,
263 .reader = self,276 .reader = self,
264 .auto_advance = auto_advance,277 .auto_advance = auto_advance,
...@@ -267,12 +280,12 @@ fn BufferedReader(comptime ReaderType: type) type {...@@ -267,12 +280,12 @@ fn BufferedReader(comptime ReaderType: type) type {
267 };280 };
268}281}
269282
270// file_size rouneded to te block boundary283// File size rounded to te block boundary.
271inline fn roundedFileSize(file_size: usize) usize {284inline fn roundedFileSize(file_size: usize) usize {
272 return std.mem.alignForward(usize, file_size, block_size);285 return std.mem.alignForward(usize, file_size, BLOCK_SIZE);
273}286}
274287
275// number of padding bytes at the last file block288// Number of padding bytes in the last file block.
276inline fn filePadding(file_size: usize) usize {289inline fn filePadding(file_size: usize) usize {
277 return roundedFileSize(file_size) - file_size;290 return roundedFileSize(file_size) - file_size;
278}291}
...@@ -341,17 +354,18 @@ fn Iterator(comptime ReaderType: type) type {...@@ -341,17 +354,18 @@ fn Iterator(comptime ReaderType: type) type {
341 }354 }
342 };355 };
343356
344 // Externally, Next iterates through the tar archive as if it is a series of357 // Externally, `next` iterates through the tar archive as if it is a
345 // files. Internally, the tar format often uses fake "files" to add meta358 // series of files. Internally, the tar format often uses fake "files"
346 // data that describes the next file. These meta data "files" should not359 // to add meta data that describes the next file. These meta data
347 // normally be visible to the outside. As such, this loop iterates through360 // "files" should not normally be visible to the outside. As such, this
348 // one or more "header files" until it finds a "normal file".361 // loop iterates through one or more "header files" until it finds a
362 // "normal file".
349 pub fn next(self: *Self) !?File {363 pub fn next(self: *Self) !?File {
350 var file: File = .{ .reader = &self.reader };364 var file: File = .{ .reader = &self.reader };
351 self.attrs.free();365 self.attrs.free();
352366
353 while (try self.reader.readBlock()) |block_bytes| {367 while (try self.reader.readBlock()) |block_bytes| {
354 const block: Header = .{ .bytes = block_bytes[0..block_size] };368 const block: Header = .{ .bytes = block_bytes[0..BLOCK_SIZE] };
355 if (block.isZero()) return null;369 if (block.isZero()) return null;
356 const file_type = block.fileType();370 const file_type = block.fileType();
357 const file_size = try block.fileSize();371 const file_size = try block.fileSize();
...@@ -572,6 +586,7 @@ const TestCase = struct {...@@ -572,6 +586,7 @@ const TestCase = struct {
572 size: usize = 0,586 size: usize = 0,
573 link_name: []const u8 = empty_string,587 link_name: []const u8 = empty_string,
574 file_type: Header.FileType = .normal,588 file_type: Header.FileType = .normal,
589 truncated: bool = false, // when there is no file body, just header, usefull for huge files
575 };590 };
576591
577 path: []const u8,592 path: []const u8,
...@@ -794,10 +809,32 @@ test "Go test cases" {...@@ -794,10 +809,32 @@ test "Go test cases" {
794 },809 },
795 },810 },
796 },811 },
812 .{
813 // Has size in gnu extended format. To represent size bigger than 8 GB.
814 .path = "writer-big.tar",
815 .files = &[_]TestCase.File{
816 .{
817 .name = "tmp/16gig.txt",
818 .size = 16 * 1024 * 1024 * 1024,
819 .truncated = true,
820 },
821 },
822 },
823 .{
824 // Size in gnu extended format, and name in pax attribute.
825 .path = "writer-big-long.tar",
826 .files = &[_]TestCase.File{
827 .{
828 .name = "longname/" ** 15 ++ "16gig.txt",
829 .size = 16 * 1024 * 1024 * 1024,
830 .truncated = true,
831 },
832 },
833 },
797 };834 };
798835
799 for (cases) |case| {836 for (cases) |case| {
800 // if (!std.mem.eql(u8, case.path, "pax.tar")) continue;837 //if (!std.mem.eql(u8, case.path, "pax-pos-size-file.tar")) continue;
801838
802 var fs_file = try test_dir.openFile(case.path, .{});839 var fs_file = try test_dir.openFile(case.path, .{});
803 defer fs_file.close();840 defer fs_file.close();
...@@ -825,7 +862,7 @@ test "Go test cases" {...@@ -825,7 +862,7 @@ test "Go test cases" {
825 // std.debug.print("actual chksum: {s}\n", .{std.fmt.fmtSliceHexLower(&actual_chksum)});862 // std.debug.print("actual chksum: {s}\n", .{std.fmt.fmtSliceHexLower(&actual_chksum)});
826 try std.testing.expectEqualStrings(expected_chksum, &actual_chksum);863 try std.testing.expectEqualStrings(expected_chksum, &actual_chksum);
827 } else {864 } else {
828 try actual.skip(); // skip file content865 if (!expected.truncated) try actual.skip(); // skip file content
829 }866 }
830 i += 1;867 i += 1;
831 }868 }