| ... | ... | @@ -198,6 +198,16 @@ fn nullStr(str: []const u8) []const u8 { |
| 198 | 198 | return str; |
| 199 | 199 | } |
| 200 | 200 | |
| 201 | // File size rounded to te block boundary. |
| 202 | inline fn roundedFileSize(file_size: usize) usize { |
| 203 | return std.mem.alignForward(usize, file_size, BLOCK_SIZE); |
| 204 | } |
| 205 | |
| 206 | // Number of padding bytes in the last file block. |
| 207 | inline fn filePadding(file_size: usize) usize { |
| 208 | return roundedFileSize(file_size) - file_size; |
| 209 | } |
| 210 | |
| 201 | 211 | fn BufferedReader(comptime ReaderType: type) type { |
| 202 | 212 | return struct { |
| 203 | 213 | unbuffered_reader: ReaderType, |
| ... | ... | @@ -207,16 +217,32 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 207 | 217 | |
| 208 | 218 | const Self = @This(); |
| 209 | 219 | |
| 210 | | pub fn readChunk(self: *Self, count: usize) ![]const u8 { |
| 211 | | self.ensureCapacity(1024); |
| 212 | | |
| 220 | fn readChunk(self: *Self, count: usize) ![]const u8 { |
| 221 | self.ensureCapacity(BLOCK_SIZE * 2); |
| 213 | 222 | const ask = @min(self.buffer.len - self.end, count -| (self.end - self.start)); |
| 214 | 223 | self.end += try self.unbuffered_reader.readAtLeast(self.buffer[self.end..], ask); |
| 215 | | |
| 216 | 224 | return self.buffer[self.start..self.end]; |
| 217 | 225 | } |
| 218 | 226 | |
| 219 | | pub fn readBlock(self: *Self) !?[]const u8 { |
| 227 | // Returns slice of size count or part of it. |
| 228 | pub fn readSlice(self: *Self, count: usize) ![]const u8 { |
| 229 | if (count <= self.end - self.start) { |
| 230 | // fastpath, we have enough bytes in buffer |
| 231 | return self.buffer[self.start .. self.start + count]; |
| 232 | } |
| 233 | |
| 234 | const chunk_size = roundedFileSize(count) + BLOCK_SIZE; |
| 235 | const temp = try self.readChunk(chunk_size); |
| 236 | if (temp.len == 0) return error.UnexpectedEndOfStream; |
| 237 | return temp[0..@min(count, temp.len)]; |
| 238 | } |
| 239 | |
| 240 | // Returns tar header block, 512 bytes. Before reading advances buffer |
| 241 | // for padding of the previous block, to position reader at the start of |
| 242 | // new block. After reading advances for block size, to position reader |
| 243 | // at the start of the file body. |
| 244 | pub fn readBlock(self: *Self, padding: usize) !?[]const u8 { |
| 245 | try self.skip(padding); |
| 220 | 246 | const block_bytes = try self.readChunk(BLOCK_SIZE * 2); |
| 221 | 247 | switch (block_bytes.len) { |
| 222 | 248 | 0 => return null, |
| ... | ... | @@ -227,11 +253,19 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 227 | 253 | return block_bytes[0..BLOCK_SIZE]; |
| 228 | 254 | } |
| 229 | 255 | |
| 256 | // Retruns byte at current position in buffer. |
| 257 | pub fn readByte(self: *@This()) u8 { |
| 258 | return self.buffer[self.start]; |
| 259 | } |
| 260 | |
| 261 | // Advances reader for count bytes, assumes that we have that number of |
| 262 | // bytes in buffer. |
| 230 | 263 | pub fn advance(self: *Self, count: usize) void { |
| 231 | 264 | self.start += count; |
| 232 | 265 | assert(self.start <= self.end); |
| 233 | 266 | } |
| 234 | 267 | |
| 268 | // Advances reader without assuming that count bytes are in the buffer. |
| 235 | 269 | pub fn skip(self: *Self, count: usize) !void { |
| 236 | 270 | if (self.start + count > self.end) { |
| 237 | 271 | try self.unbuffered_reader.skipBytes(self.start + count - self.end, .{}); |
| ... | ... | @@ -241,14 +275,6 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 241 | 275 | } |
| 242 | 276 | } |
| 243 | 277 | |
| 244 | | pub fn skipPadding(self: *Self, file_size: usize) !void { |
| 245 | | return self.skip(filePadding(file_size)); |
| 246 | | } |
| 247 | | |
| 248 | | pub fn skipFile(self: *Self, file_size: usize) !void { |
| 249 | | return self.skip(roundedFileSize(file_size)); |
| 250 | | } |
| 251 | | |
| 252 | 278 | inline fn ensureCapacity(self: *Self, count: usize) void { |
| 253 | 279 | if (self.buffer.len - self.start < count) { |
| 254 | 280 | const dest_end = self.end - self.start; |
| ... | ... | @@ -258,16 +284,26 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 258 | 284 | } |
| 259 | 285 | } |
| 260 | 286 | |
| 261 | | pub fn write(self: *Self, writer: anytype, size: usize) !void { |
| 262 | | var rdr = self.sliceReader(size, true); |
| 287 | // Write count bytes to the writer. |
| 288 | pub fn write(self: *Self, writer: anytype, count: usize) !void { |
| 289 | if (self.read(count)) |buf| { |
| 290 | try writer.writeAll(buf); |
| 291 | return; |
| 292 | } |
| 293 | var rdr = self.sliceReader(count); |
| 263 | 294 | while (try rdr.next()) |slice| { |
| 264 | 295 | try writer.writeAll(slice); |
| 265 | 296 | } |
| 266 | 297 | } |
| 267 | 298 | |
| 268 | | // copy dst.len bytes into dst |
| 299 | // Copy dst.len bytes into dst buffer. |
| 269 | 300 | pub fn copy(self: *Self, dst: []u8) ![]const u8 { |
| 270 | | var rdr = self.sliceReader(dst.len, true); |
| 301 | if (self.read(dst.len)) |buf| { |
| 302 | // fastpath we already have enough bytes in buffer |
| 303 | @memcpy(dst, buf); |
| 304 | return dst; |
| 305 | } |
| 306 | var rdr = self.sliceReader(dst.len); |
| 271 | 307 | var pos: usize = 0; |
| 272 | 308 | while (try rdr.next()) |slice| : (pos += slice.len) { |
| 273 | 309 | @memcpy(dst[pos .. pos + slice.len], slice); |
| ... | ... | @@ -275,91 +311,151 @@ fn BufferedReader(comptime ReaderType: type) type { |
| 275 | 311 | return dst; |
| 276 | 312 | } |
| 277 | 313 | |
| 314 | // Retruns count bytes from buffer and advances for that number of |
| 315 | // bytes. If we don't have that much bytes buffered returns null. |
| 316 | fn read(self: *Self, count: usize) ?[]const u8 { |
| 317 | if (count <= self.end - self.start) { |
| 318 | const buf = self.buffer[self.start .. self.start + count]; |
| 319 | self.advance(count); |
| 320 | return buf; |
| 321 | } |
| 322 | return null; |
| 323 | } |
| 324 | |
| 278 | 325 | const SliceReader = struct { |
| 279 | 326 | size: usize, |
| 280 | | chunk_size: usize, |
| 281 | 327 | offset: usize, |
| 282 | 328 | reader: *Self, |
| 283 | | auto_advance: bool, |
| 284 | | |
| 285 | | pub fn next(self: *@This()) !?[]const u8 { |
| 286 | | if (self.offset >= self.size) return null; |
| 287 | 329 | |
| 288 | | const temp = try self.reader.readChunk(self.chunk_size - self.offset); |
| 289 | | if (temp.len == 0) return error.UnexpectedEndOfStream; |
| 290 | | const slice = temp[0..@min(self.remainingSize(), temp.len)]; |
| 291 | | if (self.auto_advance) try self.advance(slice.len); |
| 330 | pub fn next(self: *SliceReader) !?[]const u8 { |
| 331 | const remaining_size = self.size - self.offset; |
| 332 | if (remaining_size == 0) return null; |
| 333 | const slice = try self.reader.readSlice(remaining_size); |
| 334 | self.advance(slice.len); |
| 292 | 335 | return slice; |
| 293 | 336 | } |
| 294 | 337 | |
| 295 | | pub fn advance(self: *@This(), len: usize) !void { |
| 338 | fn advance(self: *SliceReader, len: usize) void { |
| 296 | 339 | self.offset += len; |
| 297 | | try self.reader.skip(len); |
| 298 | | } |
| 299 | | |
| 300 | | pub fn byte(self: *@This()) u8 { |
| 301 | | return self.reader.buffer[self.reader.start]; |
| 302 | | } |
| 303 | | |
| 304 | | pub fn copy(self: *@This(), dst: []u8) ![]const u8 { |
| 305 | | _ = try self.reader.copy(dst); |
| 306 | | self.offset += dst.len; |
| 307 | | return dst; |
| 308 | | } |
| 309 | | |
| 310 | | pub fn remainingSize(self: *@This()) usize { |
| 311 | | return self.size - self.offset; |
| 340 | self.reader.advance(len); |
| 312 | 341 | } |
| 313 | 342 | }; |
| 314 | 343 | |
| 315 | | pub fn sliceReader(self: *Self, size: usize, auto_advance: bool) Self.SliceReader { |
| 344 | pub fn sliceReader(self: *Self, size: usize) SliceReader { |
| 316 | 345 | return .{ |
| 317 | 346 | .size = size, |
| 318 | | .chunk_size = roundedFileSize(size) + BLOCK_SIZE, |
| 347 | .reader = self, |
| 319 | 348 | .offset = 0, |
| 349 | }; |
| 350 | } |
| 351 | |
| 352 | pub fn paxFileReader(self: *Self, size: usize) PaxFileReader { |
| 353 | return .{ |
| 354 | .size = size, |
| 320 | 355 | .reader = self, |
| 321 | | .auto_advance = auto_advance, |
| 356 | .offset = 0, |
| 322 | 357 | }; |
| 323 | 358 | } |
| 324 | | }; |
| 325 | | } |
| 326 | 359 | |
| 327 | | // File size rounded to te block boundary. |
| 328 | | inline fn roundedFileSize(file_size: usize) usize { |
| 329 | | return std.mem.alignForward(usize, file_size, BLOCK_SIZE); |
| 330 | | } |
| 360 | const PaxFileReader = struct { |
| 361 | size: usize, |
| 362 | offset: usize = 0, |
| 363 | reader: *Self, |
| 331 | 364 | |
| 332 | | // Number of padding bytes in the last file block. |
| 333 | | inline fn filePadding(file_size: usize) usize { |
| 334 | | return roundedFileSize(file_size) - file_size; |
| 365 | const PaxKey = enum { |
| 366 | path, |
| 367 | linkpath, |
| 368 | size, |
| 369 | }; |
| 370 | |
| 371 | const PaxAttribute = struct { |
| 372 | key: PaxKey, |
| 373 | value_len: usize, |
| 374 | parent: *PaxFileReader, |
| 375 | |
| 376 | // Copies pax attribute value into destination buffer. |
| 377 | // Must be called with destination buffer of size at least value_len. |
| 378 | pub fn value(self: PaxAttribute, dst: []u8) ![]u8 { |
| 379 | assert(dst.len >= self.value_len); |
| 380 | const buf = dst[0..self.value_len]; |
| 381 | _ = try self.parent.reader.copy(buf); |
| 382 | self.parent.offset += buf.len; |
| 383 | try self.parent.checkAttributeEnding(); |
| 384 | return buf; |
| 385 | } |
| 386 | }; |
| 387 | |
| 388 | // Caller of the next has to call value in PaxAttribute, to advance |
| 389 | // reader across value. |
| 390 | pub fn next(self: *PaxFileReader) !?PaxAttribute { |
| 391 | const rdr = self.reader; |
| 392 | _ = rdr; |
| 393 | |
| 394 | while (true) { |
| 395 | const remaining_size = self.size - self.offset; |
| 396 | if (remaining_size == 0) return null; |
| 397 | |
| 398 | const inf = try parsePaxAttribute( |
| 399 | try self.reader.readSlice(remaining_size), |
| 400 | remaining_size, |
| 401 | ); |
| 402 | const key: PaxKey = if (inf.is("path")) |
| 403 | .path |
| 404 | else if (inf.is("linkpath")) |
| 405 | .linkpath |
| 406 | else if (inf.is("size")) |
| 407 | .size |
| 408 | else { |
| 409 | try self.advance(inf.value_off + inf.value_len); |
| 410 | try self.checkAttributeEnding(); |
| 411 | continue; |
| 412 | }; |
| 413 | try self.advance(inf.value_off); // position reader at the start of the value |
| 414 | return PaxAttribute{ .key = key, .value_len = inf.value_len, .parent = self }; |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | fn checkAttributeEnding(self: *PaxFileReader) !void { |
| 419 | if (self.reader.readByte() != '\n') return error.InvalidPaxAttribute; |
| 420 | try self.advance(1); |
| 421 | } |
| 422 | |
| 423 | fn advance(self: *PaxFileReader, len: usize) !void { |
| 424 | self.offset += len; |
| 425 | try self.reader.skip(len); |
| 426 | } |
| 427 | }; |
| 428 | }; |
| 335 | 429 | } |
| 336 | 430 | |
| 337 | 431 | fn Iterator(comptime ReaderType: type) type { |
| 338 | 432 | const BufferedReaderType = BufferedReader(ReaderType); |
| 339 | 433 | return struct { |
| 340 | | attrs: struct { |
| 341 | | buffer: [std.fs.MAX_PATH_BYTES * 2]u8 = undefined, |
| 434 | // scratch buffer for file attributes |
| 435 | scratch: struct { |
| 436 | // size: two paths (name and link_name) and size (24 in pax attribute) |
| 437 | buffer: [std.fs.MAX_PATH_BYTES * 2 + 24]u8 = undefined, |
| 342 | 438 | tail: usize = 0, |
| 343 | 439 | |
| 440 | // Allocate size of the buffer for some attribute. |
| 344 | 441 | fn alloc(self: *@This(), size: usize) ![]u8 { |
| 345 | | if (size > self.len()) return error.NameTooLong; |
| 442 | const free_size = self.buffer.len - self.tail; |
| 443 | if (size > free_size) return error.TarScratchBufferOverflow; |
| 346 | 444 | const head = self.tail; |
| 347 | 445 | self.tail += size; |
| 348 | 446 | assert(self.tail <= self.buffer.len); |
| 349 | 447 | return self.buffer[head..self.tail]; |
| 350 | 448 | } |
| 351 | 449 | |
| 450 | // Free whole buffer. |
| 352 | 451 | fn free(self: *@This()) void { |
| 353 | 452 | self.tail = 0; |
| 354 | 453 | } |
| 355 | | |
| 356 | | fn len(self: *@This()) usize { |
| 357 | | return self.buffer.len - self.tail; |
| 358 | | } |
| 359 | 454 | } = .{}, |
| 360 | 455 | |
| 361 | 456 | reader: BufferedReaderType, |
| 362 | 457 | diagnostics: ?*Options.Diagnostics, |
| 458 | padding: usize = 0, // bytes of file padding |
| 363 | 459 | |
| 364 | 460 | const Self = @This(); |
| 365 | 461 | |
| ... | ... | @@ -372,28 +468,22 @@ fn Iterator(comptime ReaderType: type) type { |
| 372 | 468 | |
| 373 | 469 | pub fn write(self: File, writer: anytype) !void { |
| 374 | 470 | try self.reader.write(writer, self.size); |
| 375 | | try self.skipPadding(); |
| 376 | 471 | } |
| 377 | 472 | |
| 378 | 473 | pub fn skip(self: File) !void { |
| 379 | | try self.reader.skip(roundedFileSize(self.size)); |
| 380 | | } |
| 381 | | |
| 382 | | fn skipPadding(self: File) !void { |
| 383 | | try self.reader.skip(filePadding(self.size)); |
| 474 | try self.reader.skip(self.size); |
| 384 | 475 | } |
| 385 | 476 | |
| 386 | 477 | fn chksum(self: File) ![16]u8 { |
| 387 | 478 | var sum = [_]u8{0} ** 16; |
| 388 | 479 | if (self.size == 0) return sum; |
| 389 | 480 | |
| 390 | | var rdr = self.reader.sliceReader(self.size, true); |
| 481 | var rdr = self.reader.sliceReader(self.size); |
| 391 | 482 | var h = std.crypto.hash.Md5.init(.{}); |
| 392 | 483 | while (try rdr.next()) |slice| { |
| 393 | 484 | h.update(slice); |
| 394 | 485 | } |
| 395 | 486 | h.final(&sum); |
| 396 | | try self.skipPadding(); |
| 397 | 487 | return sum; |
| 398 | 488 | } |
| 399 | 489 | }; |
| ... | ... | @@ -406,64 +496,65 @@ fn Iterator(comptime ReaderType: type) type { |
| 406 | 496 | // "normal file". |
| 407 | 497 | pub fn next(self: *Self) !?File { |
| 408 | 498 | var file: File = .{ .reader = &self.reader }; |
| 409 | | self.attrs.free(); |
| 499 | self.scratch.free(); |
| 410 | 500 | |
| 411 | | while (try self.reader.readBlock()) |block_bytes| { |
| 412 | | const block = Header{ .bytes = block_bytes[0..BLOCK_SIZE] }; |
| 413 | | if (try block.checkChksum() == 0) return null; // zero block found |
| 414 | | const file_type = block.fileType(); |
| 415 | | const file_size = try block.fileSize(); |
| 501 | while (try self.reader.readBlock(self.padding)) |block_bytes| { |
| 502 | const header = Header{ .bytes = block_bytes[0..BLOCK_SIZE] }; |
| 503 | if (try header.checkChksum() == 0) return null; // zero block found |
| 504 | |
| 505 | const file_type = header.fileType(); |
| 506 | const file_size = try header.fileSize(); |
| 507 | self.padding = filePadding(file_size); |
| 416 | 508 | |
| 417 | 509 | switch (file_type) { |
| 510 | // file types to retrun from next |
| 418 | 511 | .directory, .normal, .symbolic_link => { |
| 419 | 512 | if (file.size == 0) file.size = file_size; |
| 513 | self.padding = filePadding(file.size); |
| 514 | |
| 420 | 515 | if (file.name.len == 0) |
| 421 | | file.name = try block.fullFileName((try self.attrs.alloc(std.fs.MAX_PATH_BYTES))[0..std.fs.MAX_PATH_BYTES]); |
| 422 | | if (file.link_name.len == 0) file.link_name = block.linkName(); |
| 516 | file.name = try header.fullFileName((try self.scratch.alloc(std.fs.MAX_PATH_BYTES))[0..std.fs.MAX_PATH_BYTES]); |
| 517 | if (file.link_name.len == 0) file.link_name = header.linkName(); |
| 423 | 518 | file.file_type = file_type; |
| 424 | 519 | return file; |
| 425 | 520 | }, |
| 426 | | .global_extended_header => { |
| 427 | | self.reader.skipFile(file_size) catch return error.TarHeadersTooBig; |
| 521 | // prefix header types |
| 522 | .gnu_long_name => { |
| 523 | file.name = nullStr(try self.reader.copy(try self.scratch.alloc(file_size))); |
| 524 | }, |
| 525 | .gnu_long_link => { |
| 526 | file.link_name = nullStr(try self.reader.copy(try self.scratch.alloc(file_size))); |
| 428 | 527 | }, |
| 429 | 528 | .extended_header => { |
| 430 | 529 | if (file_size == 0) continue; |
| 431 | | // TODO: ovo resetiranje je nezgodno |
| 432 | | self.attrs.free(); |
| 530 | // use just last extended header data |
| 531 | self.scratch.free(); |
| 433 | 532 | file = File{ .reader = &self.reader }; |
| 434 | 533 | |
| 435 | | var rdr = self.reader.sliceReader(file_size, false); |
| 436 | | while (try rdr.next()) |slice| { |
| 437 | | const attr = try parsePaxAttribute(slice, rdr.remainingSize()); |
| 438 | | try rdr.advance(attr.value_off); |
| 439 | | if (attr.is("path")) { |
| 440 | | file.name = try noNull(try rdr.copy(try self.attrs.alloc(attr.value_len))); |
| 441 | | } else if (attr.is("linkpath")) { |
| 442 | | file.link_name = try noNull(try rdr.copy(try self.attrs.alloc(attr.value_len))); |
| 443 | | } else if (attr.is("size")) { |
| 444 | | var buf = [_]u8{'0'} ** 32; |
| 445 | | file.size = try std.fmt.parseInt(usize, try rdr.copy(buf[0..attr.value_len]), 10); |
| 446 | | } else { |
| 447 | | try rdr.advance(attr.value_len); |
| 534 | var rdr = self.reader.paxFileReader(file_size); |
| 535 | while (try rdr.next()) |attr| { |
| 536 | switch (attr.key) { |
| 537 | .path => { |
| 538 | file.name = try noNull(try attr.value(try self.scratch.alloc(attr.value_len))); |
| 539 | }, |
| 540 | .linkpath => { |
| 541 | file.link_name = try noNull(try attr.value(try self.scratch.alloc(attr.value_len))); |
| 542 | }, |
| 543 | .size => { |
| 544 | file.size = try std.fmt.parseInt(usize, try attr.value(try self.scratch.alloc(attr.value_len)), 10); |
| 545 | }, |
| 448 | 546 | } |
| 449 | | if (rdr.byte() != '\n') return error.InvalidPaxAttribute; |
| 450 | | try rdr.advance(1); |
| 451 | 547 | } |
| 452 | | try self.reader.skipPadding(file_size); |
| 453 | | }, |
| 454 | | .gnu_long_name => { |
| 455 | | file.name = nullStr(try self.reader.copy(try self.attrs.alloc(file_size))); |
| 456 | | try self.reader.skipPadding(file_size); |
| 457 | 548 | }, |
| 458 | | .gnu_long_link => { |
| 459 | | file.link_name = nullStr(try self.reader.copy(try self.attrs.alloc(file_size))); |
| 460 | | try self.reader.skipPadding(file_size); |
| 549 | // ignored header types |
| 550 | .global_extended_header => { |
| 551 | self.reader.skip(file_size) catch return error.TarHeadersTooBig; |
| 461 | 552 | }, |
| 462 | | .hard_link => return error.TarUnsupportedFileType, |
| 553 | // unsupported header types |
| 463 | 554 | else => { |
| 464 | 555 | const d = self.diagnostics orelse return error.TarUnsupportedFileType; |
| 465 | 556 | try d.errors.append(d.allocator, .{ .unsupported_file_type = .{ |
| 466 | | .file_name = try d.allocator.dupe(u8, block.name()), |
| 557 | .file_name = try d.allocator.dupe(u8, header.name()), |
| 467 | 558 | .file_type = file_type, |
| 468 | 559 | } }); |
| 469 | 560 | }, |