| ... | ... | @@ -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 | 67 | pub const Header = struct { |
| 68 | | bytes: *const [block_size]u8, |
| 68 | bytes: *const [BLOCK_SIZE]u8, |
| 69 | 69 | |
| 70 | 70 | pub const FileType = enum(u8) { |
| 71 | 71 | normal_alias = 0, |
| ... | ... | @@ -84,6 +84,19 @@ pub const Header = struct { |
| 84 | 84 | |
| 85 | 85 | pub fn fileSize(header: Header) !u64 { |
| 86 | 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 | 100 | const ltrimmed = std.mem.trimLeft(u8, raw, "0 "); |
| 88 | 101 | const rtrimmed = std.mem.trimRight(u8, ltrimmed, " \x00"); |
| 89 | 102 | if (rtrimmed.len == 0) return 0; |
| ... | ... | @@ -148,7 +161,7 @@ pub const Header = struct { |
| 148 | 161 | fn BufferedReader(comptime ReaderType: type) type { |
| 149 | 162 | return struct { |
| 150 | 163 | unbuffered_reader: ReaderType, |
| 151 | | buffer: [block_size * 8]u8 = undefined, |
| 164 | buffer: [BLOCK_SIZE * 8]u8 = undefined, |
| 152 | 165 | start: usize = 0, |
| 153 | 166 | end: usize = 0, |
| 154 | 167 | |
| ... | ... | @@ -164,14 +177,14 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 164 | 177 | } |
| 165 | 178 | |
| 166 | 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 | 181 | switch (block_bytes.len) { |
| 169 | 182 | 0 => return null, |
| 170 | | 1...(block_size - 1) => return error.UnexpectedEndOfStream, |
| 183 | 1...(BLOCK_SIZE - 1) => return error.UnexpectedEndOfStream, |
| 171 | 184 | else => {}, |
| 172 | 185 | } |
| 173 | | self.advance(block_size); |
| 174 | | return block_bytes[0..block_size]; |
| 186 | self.advance(BLOCK_SIZE); |
| 187 | return block_bytes[0..BLOCK_SIZE]; |
| 175 | 188 | } |
| 176 | 189 | |
| 177 | 190 | pub fn advance(self: *Self, count: usize) void { |
| ... | ... | @@ -258,7 +271,7 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 258 | 271 | pub fn sliceReader(self: *Self, size: usize, auto_advance: bool) Self.SliceReader { |
| 259 | 272 | return .{ |
| 260 | 273 | .size = size, |
| 261 | | .chunk_size = roundedFileSize(size) + block_size, |
| 274 | .chunk_size = roundedFileSize(size) + BLOCK_SIZE, |
| 262 | 275 | .offset = 0, |
| 263 | 276 | .reader = self, |
| 264 | 277 | .auto_advance = auto_advance, |
| ... | ... | @@ -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 | 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 | 289 | inline fn filePadding(file_size: usize) usize { |
| 277 | 290 | return roundedFileSize(file_size) - file_size; |
| 278 | 291 | } |
| ... | ... | @@ -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 |
| 345 | | // files. Internally, the tar format often uses fake "files" to add meta |
| 346 | | // data that describes the next file. These meta data "files" should not |
| 347 | | // normally be visible to the outside. As such, this loop iterates through |
| 348 | | // one or more "header files" until it finds a "normal file". |
| 357 | // Externally, `next` iterates through the tar archive as if it is a |
| 358 | // series of files. Internally, the tar format often uses fake "files" |
| 359 | // to add meta data that describes the next file. These meta data |
| 360 | // "files" should not normally be visible to the outside. As such, this |
| 361 | // loop iterates through one or more "header files" until it finds a |
| 362 | // "normal file". |
| 349 | 363 | pub fn next(self: *Self) !?File { |
| 350 | 364 | var file: File = .{ .reader = &self.reader }; |
| 351 | 365 | self.attrs.free(); |
| 352 | 366 | |
| 353 | 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 | 369 | if (block.isZero()) return null; |
| 356 | 370 | const file_type = block.fileType(); |
| 357 | 371 | const file_size = try block.fileSize(); |
| ... | ... | @@ -572,6 +586,7 @@ const TestCase = struct { |
| 572 | 586 | size: usize = 0, |
| 573 | 587 | link_name: []const u8 = empty_string, |
| 574 | 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 | 592 | path: []const u8, |
| ... | ... | @@ -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 | 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 | 839 | var fs_file = try test_dir.openFile(case.path, .{}); |
| 803 | 840 | defer fs_file.close(); |
| ... | ... | @@ -825,7 +862,7 @@ test "Go test cases" { |
| 825 | 862 | // std.debug.print("actual chksum: {s}\n", .{std.fmt.fmtSliceHexLower(&actual_chksum)}); |
| 826 | 863 | try std.testing.expectEqualStrings(expected_chksum, &actual_chksum); |
| 827 | 864 | } else { |
| 828 | | try actual.skip(); // skip file content |
| 865 | if (!expected.truncated) try actual.skip(); // skip file content |
| 829 | 866 | } |
| 830 | 867 | i += 1; |
| 831 | 868 | } |