| ... | @@ -62,10 +62,10 @@ pub const Options = struct { | ... | @@ -62,10 +62,10 @@ pub const Options = struct { |
| 62 | }; | 62 | }; |
| 63 | }; | 63 | }; |
| 64 | | 64 | |
| 65 | const block_size = 512; | 65 | const BLOCK_SIZE = 512; |
| 66 | | 66 | |
| 67 | pub const Header = struct { | 67 | pub const Header = struct { |
| 68 | bytes: *const [block_size]u8, | 68 | bytes: *const [BLOCK_SIZE]u8, |
| 69 | | 69 | |
| 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 { |
| 84 | | 84 | |
| 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 { |
| 148 | fn BufferedReader(comptime ReaderType: type) type { | 161 | fn 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, |
| 154 | | 167 | |
| ... | @@ -164,14 +177,14 @@ fn BufferedReader(comptime ReaderType: type) type { | ... | @@ -164,14 +177,14 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 164 | } | 177 | } |
| 165 | | 178 | |
| 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 | } |
| 176 | | 189 | |
| 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 | } |
| 269 | | 282 | |
| 270 | // file_size rouneded to te block boundary | 283 | // File size rounded to te block boundary. |
| 271 | inline fn roundedFileSize(file_size: usize) usize { | 284 | inline 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 | } |
| 274 | | 287 | |
| 275 | // number of padding bytes at the last file block | 288 | // Number of padding bytes in the last file block. |
| 276 | inline fn filePadding(file_size: usize) usize { | 289 | inline 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 | }; |
| 343 | | 356 | |
| 344 | // Externally, Next iterates through the tar archive as if it is a series of | 357 | // Externally, `next` iterates through the tar archive as if it is a |
| 345 | // files. Internally, the tar format often uses fake "files" to add meta | 358 | // series of files. Internally, the tar format often uses fake "files" |
| 346 | // data that describes the next file. These meta data "files" should not | 359 | // to add meta data that describes the next file. These meta data |
| 347 | // normally be visible to the outside. As such, this loop iterates through | 360 | // "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(); |
| 352 | | 366 | |
| 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 | }; |
| 576 | | 591 | |
| 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 | }; |
| 798 | | 835 | |
| 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; |
| 801 | | 838 | |
| 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 content | 865 | if (!expected.truncated) try actual.skip(); // skip file content |
| 829 | } | 866 | } |
| 830 | i += 1; | 867 | i += 1; |
| 831 | } | 868 | } |