1use core::fmt;
4
5const BLOCK_SIZE: usize = 512;
6const NAME_RANGE: core::ops::Range<usize> = 0..100;
7const SIZE_RANGE: core::ops::Range<usize> = 124..136;
8const CHECKSUM_RANGE: core::ops::Range<usize> = 148..156;
9const TYPE_OFFSET: usize = 156;
10const PREFIX_RANGE: core::ops::Range<usize> = 345..500;
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13enum WriteState {
14 Ready,
15 File { remaining: u64, size: u64 },
16 Finished,
17}
18
19pub struct TarWriter {
21 state: WriteState,
22}
23
24impl TarWriter {
25 pub const fn new() -> Self {
26 Self {
27 state: WriteState::Ready,
28 }
29 }
30
31 pub fn begin_file<E>(
32 &mut self,
33 path: &[u8],
34 size: u64,
35 mode: u32,
36 mut output: impl FnMut(&[u8]) -> Result<(), E>,
37 ) -> Result<(), TarWriteError<E>> {
38 if self.state != WriteState::Ready {
39 return Err(TarWriteError::InvalidState);
40 }
41 if path.is_empty()
42 || path.len() > NAME_RANGE.len()
43 || path[0] == b'/'
44 || path.contains(&0)
45 || path
46 .split(|byte| *byte == b'/')
47 .any(|component| component == b"..")
48 {
49 return Err(TarWriteError::InvalidPath);
50 }
51
52 let mut header = [0u8; BLOCK_SIZE];
53 header[..path.len()].copy_from_slice(path);
54 write_octal(&mut header[100..108], mode as u64).map_err(cast_write_error)?;
55 write_octal(&mut header[108..116], 0).map_err(cast_write_error)?;
56 write_octal(&mut header[116..124], 0).map_err(cast_write_error)?;
57 write_octal(&mut header[SIZE_RANGE], size).map_err(cast_write_error)?;
58 write_octal(&mut header[136..148], 0).map_err(cast_write_error)?;
59 header[CHECKSUM_RANGE].fill(b' ');
60 header[TYPE_OFFSET] = b'0';
61 header[257..263].copy_from_slice(b"ustar\0");
62 header[263..265].copy_from_slice(b"00");
63 let checksum = header.iter().map(|byte| *byte as u64).sum();
64 write_checksum(&mut header[CHECKSUM_RANGE], checksum).map_err(cast_write_error)?;
65 output(&header).map_err(TarWriteError::Output)?;
66 self.state = WriteState::File {
67 remaining: size,
68 size,
69 };
70 Ok(())
71 }
72
73 pub fn write_file_data<E>(
74 &mut self,
75 bytes: &[u8],
76 mut output: impl FnMut(&[u8]) -> Result<(), E>,
77 ) -> Result<(), TarWriteError<E>> {
78 let WriteState::File { remaining, size } = self.state else {
79 return Err(TarWriteError::InvalidState);
80 };
81 if bytes.len() as u64 > remaining {
82 return Err(TarWriteError::TooMuchData);
83 }
84 output(bytes).map_err(TarWriteError::Output)?;
85 self.state = WriteState::File {
86 remaining: remaining - bytes.len() as u64,
87 size,
88 };
89 Ok(())
90 }
91
92 pub fn end_file<E>(
93 &mut self,
94 mut output: impl FnMut(&[u8]) -> Result<(), E>,
95 ) -> Result<(), TarWriteError<E>> {
96 let WriteState::File { remaining, size } = self.state else {
97 return Err(TarWriteError::InvalidState);
98 };
99 if remaining != 0 {
100 return Err(TarWriteError::SizeMismatch { remaining });
101 }
102 let size_remainder = (size % BLOCK_SIZE as u64) as usize;
103 let padding = (BLOCK_SIZE - size_remainder) % BLOCK_SIZE;
104 if padding != 0 {
105 output(&[0; BLOCK_SIZE][..padding]).map_err(TarWriteError::Output)?;
106 }
107 self.state = WriteState::Ready;
108 Ok(())
109 }
110
111 pub fn finish<E>(
112 &mut self,
113 mut output: impl FnMut(&[u8]) -> Result<(), E>,
114 ) -> Result<(), TarWriteError<E>> {
115 if self.state != WriteState::Ready {
116 return Err(TarWriteError::InvalidState);
117 }
118 output(&[0; BLOCK_SIZE * 2]).map_err(TarWriteError::Output)?;
119 self.state = WriteState::Finished;
120 Ok(())
121 }
122}
123
124impl Default for TarWriter {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131pub enum TarWriteError<E> {
132 InvalidState,
133 InvalidPath,
134 ValueTooLarge,
135 TooMuchData,
136 SizeMismatch { remaining: u64 },
137 Output(E),
138}
139
140impl<E: fmt::Display> fmt::Display for TarWriteError<E> {
141 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self {
143 Self::InvalidState => formatter.write_str("invalid tar writer state"),
144 Self::InvalidPath => formatter.write_str("invalid tar member path"),
145 Self::ValueTooLarge => formatter.write_str("tar header value is too large"),
146 Self::TooMuchData => formatter.write_str("tar member received too much data"),
147 Self::SizeMismatch { remaining } => {
148 write!(formatter, "tar member is missing {remaining} bytes")
149 }
150 Self::Output(error) => write!(formatter, "tar writer output failed: {error}"),
151 }
152 }
153}
154
155fn write_octal(
156 field: &mut [u8],
157 mut value: u64,
158) -> Result<(), TarWriteError<core::convert::Infallible>> {
159 field.fill(b'0');
160 let digits = field
161 .len()
162 .checked_sub(1)
163 .ok_or(TarWriteError::ValueTooLarge)?;
164 field[digits] = 0;
165 for index in (0..digits).rev() {
166 field[index] = b'0' + (value & 7) as u8;
167 value >>= 3;
168 }
169 if value != 0 {
170 return Err(TarWriteError::ValueTooLarge);
171 }
172 Ok(())
173}
174
175fn write_checksum(
176 field: &mut [u8],
177 mut value: u64,
178) -> Result<(), TarWriteError<core::convert::Infallible>> {
179 field.fill(b'0');
180 field[6] = 0;
181 field[7] = b' ';
182 for index in (0..6).rev() {
183 field[index] = b'0' + (value & 7) as u8;
184 value >>= 3;
185 }
186 if value != 0 {
187 return Err(TarWriteError::ValueTooLarge);
188 }
189 Ok(())
190}
191
192fn cast_write_error<E>(error: TarWriteError<core::convert::Infallible>) -> TarWriteError<E> {
193 match error {
194 TarWriteError::InvalidState => TarWriteError::InvalidState,
195 TarWriteError::InvalidPath => TarWriteError::InvalidPath,
196 TarWriteError::ValueTooLarge => TarWriteError::ValueTooLarge,
197 TarWriteError::TooMuchData => TarWriteError::TooMuchData,
198 TarWriteError::SizeMismatch { remaining } => TarWriteError::SizeMismatch { remaining },
199 TarWriteError::Output(never) => match never {},
200 }
201}
202
203#[derive(Clone, Copy, Debug, Eq, PartialEq)]
205pub enum ExtractError<E> {
206 InvalidChecksum,
208 InvalidSize,
210 InvalidEndMarker,
212 Output(E),
214}
215
216impl<E: fmt::Display> fmt::Display for ExtractError<E> {
217 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218 match self {
219 Self::InvalidChecksum => formatter.write_str("invalid tar header checksum"),
220 Self::InvalidSize => formatter.write_str("invalid tar entry size"),
221 Self::InvalidEndMarker => formatter.write_str("invalid tar end marker"),
222 Self::Output(error) => write!(formatter, "tar output failed: {error}"),
223 }
224 }
225}
226
227#[derive(Clone, Copy, Debug, Eq, PartialEq)]
229pub enum FinishError {
230 UnexpectedEof,
232 NotFound,
234}
235
236impl fmt::Display for FinishError {
237 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
238 match self {
239 Self::UnexpectedEof => formatter.write_str("unexpected end of tar stream"),
240 Self::NotFound => formatter.write_str("tar entry not found"),
241 }
242 }
243}
244
245pub struct EntryExtractor<'target> {
255 target: &'target [u8],
256 header: [u8; BLOCK_SIZE],
257 header_len: usize,
258 entry_remaining: u64,
259 padding_remaining: usize,
260 selected: bool,
261 found: bool,
262 zero_blocks: u8,
263}
264
265impl<'target> EntryExtractor<'target> {
266 pub const fn new(target: &'target [u8]) -> Self {
268 Self {
269 target,
270 header: [0; BLOCK_SIZE],
271 header_len: 0,
272 entry_remaining: 0,
273 padding_remaining: 0,
274 selected: false,
275 found: false,
276 zero_blocks: 0,
277 }
278 }
279
280 pub fn push<E>(
286 &mut self,
287 mut input: &[u8],
288 mut output: impl FnMut(&[u8]) -> Result<(), E>,
289 ) -> Result<(), ExtractError<E>> {
290 while !input.is_empty() {
291 if self.zero_blocks == 2 {
292 return Ok(());
293 }
294
295 if self.entry_remaining != 0 {
296 let length = input.len().min(u64_to_usize(self.entry_remaining));
297 let bytes = &input[..length];
298 if self.selected {
299 output(bytes).map_err(ExtractError::Output)?;
300 }
301 self.entry_remaining -= length as u64;
302 input = &input[length..];
303 continue;
304 }
305
306 if self.padding_remaining != 0 {
307 let length = input.len().min(self.padding_remaining);
308 self.padding_remaining -= length;
309 input = &input[length..];
310 continue;
311 }
312
313 let length = input.len().min(BLOCK_SIZE - self.header_len);
314 self.header[self.header_len..self.header_len + length]
315 .copy_from_slice(&input[..length]);
316 self.header_len += length;
317 input = &input[length..];
318
319 if self.header_len == BLOCK_SIZE {
320 self.consume_header()?;
321 self.header_len = 0;
322 }
323 }
324
325 Ok(())
326 }
327
328 pub const fn found(&self) -> bool {
330 self.found
331 }
332
333 pub fn finish(&self) -> Result<(), FinishError> {
335 if self.zero_blocks != 2
336 || self.header_len != 0
337 || self.entry_remaining != 0
338 || self.padding_remaining != 0
339 {
340 return Err(FinishError::UnexpectedEof);
341 }
342 if !self.found {
343 return Err(FinishError::NotFound);
344 }
345 Ok(())
346 }
347
348 fn consume_header<E>(&mut self) -> Result<(), ExtractError<E>> {
349 if self.header.iter().all(|byte| *byte == 0) {
350 self.zero_blocks += 1;
351 return Ok(());
352 }
353 if self.zero_blocks != 0 {
354 return Err(ExtractError::InvalidEndMarker);
355 }
356
357 verify_checksum(&self.header)?;
358 let size = parse_number(&self.header[SIZE_RANGE]).ok_or(ExtractError::InvalidSize)?;
359 let is_regular = matches!(self.header[TYPE_OFFSET], 0 | b'0');
360 self.selected = !self.found && is_regular && path_matches(&self.header, self.target);
361 self.found |= self.selected;
362 self.entry_remaining = size;
363 self.padding_remaining = padding_for(size);
364 Ok(())
365 }
366}
367
368fn verify_checksum<E>(header: &[u8; BLOCK_SIZE]) -> Result<(), ExtractError<E>> {
369 let expected = parse_octal(&header[CHECKSUM_RANGE]).ok_or(ExtractError::InvalidChecksum)?;
370 let actual = header
371 .iter()
372 .enumerate()
373 .map(|(index, byte)| {
374 if CHECKSUM_RANGE.contains(&index) {
375 u64::from(b' ')
376 } else {
377 u64::from(*byte)
378 }
379 })
380 .sum::<u64>();
381 if actual == expected {
382 Ok(())
383 } else {
384 Err(ExtractError::InvalidChecksum)
385 }
386}
387
388fn parse_number(field: &[u8]) -> Option<u64> {
389 if field.first().copied()? & 0x80 == 0 {
390 return parse_octal(field);
391 }
392
393 let mut value = u64::from(field[0] & 0x7f);
394 for byte in &field[1..] {
395 value = value.checked_mul(256)?.checked_add(u64::from(*byte))?;
396 }
397 Some(value)
398}
399
400fn parse_octal(field: &[u8]) -> Option<u64> {
401 let mut value = 0u64;
402 let mut saw_digit = false;
403 let mut saw_terminator = false;
404
405 for byte in field {
406 match *byte {
407 b'0'..=b'7' if !saw_terminator => {
408 saw_digit = true;
409 value = value.checked_mul(8)?.checked_add(u64::from(*byte - b'0'))?;
410 }
411 b' ' if !saw_digit => {}
412 0 | b' ' if saw_digit => saw_terminator = true,
413 _ => return None,
414 }
415 }
416
417 saw_digit.then_some(value)
418}
419
420fn path_matches(header: &[u8; BLOCK_SIZE], target: &[u8]) -> bool {
421 let name = nul_terminated(&header[NAME_RANGE]);
422 let prefix = nul_terminated(&header[PREFIX_RANGE]);
423 if prefix.is_empty() {
424 return target == name;
425 }
426
427 target.len() == prefix.len() + 1 + name.len()
428 && target.starts_with(prefix)
429 && target[prefix.len()] == b'/'
430 && &target[prefix.len() + 1..] == name
431}
432
433fn nul_terminated(field: &[u8]) -> &[u8] {
434 let length = field
435 .iter()
436 .position(|byte| *byte == 0)
437 .unwrap_or(field.len());
438 &field[..length]
439}
440
441fn padding_for(size: u64) -> usize {
442 ((BLOCK_SIZE as u64 - size % BLOCK_SIZE as u64) % BLOCK_SIZE as u64) as usize
443}
444
445fn u64_to_usize(value: u64) -> usize {
446 usize::try_from(value).unwrap_or(usize::MAX)
447}
448
449pub struct ArchiveBuffers<'a> {
451 pub path: &'a mut [u8],
453 pub link: &'a mut [u8],
455 pub pax: &'a mut [u8],
457}
458
459#[derive(Clone, Copy, Debug, Eq, PartialEq)]
461pub enum EntryKind {
462 Regular,
463 HardLink,
464 SymbolicLink,
465 CharacterDevice,
466 BlockDevice,
467 Directory,
468 Fifo,
469 Contiguous,
470 Other(u8),
471}
472
473#[derive(Clone, Copy)]
475pub struct Entry<'a> {
476 pub path: &'a [u8],
477 pub kind: EntryKind,
478 pub size: u64,
479 pub mode: u64,
480 pub uid: u64,
481 pub gid: u64,
482 pub mtime: u64,
483 pub link_target: Option<&'a [u8]>,
484 pub device_major: Option<u64>,
485 pub device_minor: Option<u64>,
486 pax: &'a [u8],
487}
488
489impl<'a> Entry<'a> {
490 pub fn pax_records(&self) -> PaxRecords<'a> {
492 PaxRecords::new(self.pax)
493 }
494}
495
496#[derive(Clone, Copy, Debug, Eq, PartialEq)]
498pub struct PaxRecord<'a> {
499 pub key: &'a [u8],
500 pub value: &'a [u8],
501}
502
503pub struct PaxRecords<'a> {
505 bytes: &'a [u8],
506 position: usize,
507 failed: bool,
508}
509
510impl<'a> PaxRecords<'a> {
511 const fn new(bytes: &'a [u8]) -> Self {
512 Self {
513 bytes,
514 position: 0,
515 failed: false,
516 }
517 }
518}
519
520impl<'a> Iterator for PaxRecords<'a> {
521 type Item = Result<PaxRecord<'a>, PaxError>;
522
523 fn next(&mut self) -> Option<Self::Item> {
524 if self.failed || self.position == self.bytes.len() {
525 return None;
526 }
527 match parse_pax_record(self.bytes, self.position) {
528 Ok((record, next)) => {
529 self.position = next;
530 Some(Ok(record))
531 }
532 Err(error) => {
533 self.failed = true;
534 Some(Err(error))
535 }
536 }
537 }
538}
539
540pub trait LayerEventSink {
542 type Error;
543
544 fn begin_entry(&mut self, entry: Entry<'_>) -> Result<(), Self::Error>;
545 fn entry_data(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
546 fn end_entry(&mut self) -> Result<(), Self::Error>;
547 fn whiteout(&mut self, path: &[u8]) -> Result<(), Self::Error>;
548 fn opaque_directory(&mut self, path: &[u8]) -> Result<(), Self::Error>;
549}
550
551pub trait TransactionalLayerSink: LayerEventSink {
553 fn begin_layer(&mut self) -> Result<(), Self::Error>;
554 fn commit_layer(&mut self) -> Result<(), Self::Error>;
555 fn abort_layer(&mut self);
556}
557
558#[derive(Clone, Copy, Debug, Eq, PartialEq)]
559enum ArchiveEntry {
560 None,
561 Normal,
562 LongPath,
563 LongLink,
564 Pax,
565}
566
567pub struct Archive<'a> {
569 header: [u8; BLOCK_SIZE],
570 header_len: usize,
571 entry_remaining: u64,
572 padding_remaining: usize,
573 current: ArchiveEntry,
574 entry_open: bool,
575 zero_blocks: u8,
576 path: &'a mut [u8],
577 path_len: usize,
578 link: &'a mut [u8],
579 link_len: usize,
580 pax: &'a mut [u8],
581 pax_len: usize,
582 pending_long_path: bool,
583 pending_long_link: bool,
584 pending_pax: bool,
585}
586
587impl<'a> Archive<'a> {
588 pub fn new(buffers: ArchiveBuffers<'a>) -> Self {
589 Self {
590 header: [0; BLOCK_SIZE],
591 header_len: 0,
592 entry_remaining: 0,
593 padding_remaining: 0,
594 current: ArchiveEntry::None,
595 entry_open: false,
596 zero_blocks: 0,
597 path: buffers.path,
598 path_len: 0,
599 link: buffers.link,
600 link_len: 0,
601 pax: buffers.pax,
602 pax_len: 0,
603 pending_long_path: false,
604 pending_long_link: false,
605 pending_pax: false,
606 }
607 }
608
609 pub fn push<S: LayerEventSink>(
610 &mut self,
611 mut input: &[u8],
612 sink: &mut S,
613 ) -> Result<(), ArchiveError<S::Error>> {
614 while !input.is_empty() {
615 if self.zero_blocks == 2 {
616 return Ok(());
617 }
618 if self.entry_remaining != 0 {
619 let length = input.len().min(u64_to_usize(self.entry_remaining));
620 let bytes = &input[..length];
621 match self.current {
622 ArchiveEntry::Normal => sink.entry_data(bytes).map_err(ArchiveError::Sink)?,
623 ArchiveEntry::LongPath => {
624 append_scratch(self.path, &mut self.path_len, bytes, Scratch::Path)?
625 }
626 ArchiveEntry::LongLink => {
627 append_scratch(self.link, &mut self.link_len, bytes, Scratch::Link)?
628 }
629 ArchiveEntry::Pax => {
630 append_scratch(self.pax, &mut self.pax_len, bytes, Scratch::Pax)?
631 }
632 ArchiveEntry::None => return Err(ArchiveError::InvalidState),
633 }
634 self.entry_remaining -= length as u64;
635 input = &input[length..];
636 if self.entry_remaining == 0 {
637 self.finish_content(sink)?;
638 }
639 continue;
640 }
641 if self.padding_remaining != 0 {
642 let length = input.len().min(self.padding_remaining);
643 self.padding_remaining -= length;
644 input = &input[length..];
645 if self.padding_remaining == 0 {
646 self.current = ArchiveEntry::None;
647 }
648 continue;
649 }
650
651 let length = input.len().min(BLOCK_SIZE - self.header_len);
652 self.header[self.header_len..self.header_len + length]
653 .copy_from_slice(&input[..length]);
654 self.header_len += length;
655 input = &input[length..];
656 if self.header_len == BLOCK_SIZE {
657 self.consume_header(sink)?;
658 self.header_len = 0;
659 }
660 }
661 Ok(())
662 }
663
664 pub fn finish(&self) -> Result<(), ArchiveFinishError> {
665 if self.zero_blocks != 2
666 || self.header_len != 0
667 || self.entry_remaining != 0
668 || self.padding_remaining != 0
669 || self.entry_open
670 || self.current != ArchiveEntry::None
671 {
672 return Err(ArchiveFinishError::UnexpectedEof);
673 }
674 if self.pending_long_path || self.pending_long_link || self.pending_pax {
675 return Err(ArchiveFinishError::DanglingExtension);
676 }
677 Ok(())
678 }
679
680 fn consume_header<S: LayerEventSink>(
681 &mut self,
682 sink: &mut S,
683 ) -> Result<(), ArchiveError<S::Error>> {
684 if self.header.iter().all(|byte| *byte == 0) {
685 if self.current != ArchiveEntry::None {
686 return Err(ArchiveError::InvalidState);
687 }
688 self.zero_blocks += 1;
689 return Ok(());
690 }
691 if self.zero_blocks != 0 {
692 return Err(ArchiveError::InvalidEndMarker);
693 }
694 verify_checksum::<()>(&self.header).map_err(|_| ArchiveError::InvalidChecksum)?;
695
696 let mut size = parse_number(&self.header[SIZE_RANGE]).ok_or(ArchiveError::InvalidSize)?;
697 let type_flag = self.header[TYPE_OFFSET];
698 match type_flag {
699 b'L' | b'K' | b'x' => {
700 self.current = match type_flag {
701 b'L' => {
702 self.path_len = 0;
703 ArchiveEntry::LongPath
704 }
705 b'K' => {
706 self.link_len = 0;
707 ArchiveEntry::LongLink
708 }
709 _ => {
710 self.pax_len = 0;
711 ArchiveEntry::Pax
712 }
713 };
714 self.entry_remaining = size;
715 self.padding_remaining = padding_for(size);
716 if size == 0 {
717 self.finish_content(sink)?;
718 }
719 return Ok(());
720 }
721 b'g' => return Err(ArchiveError::UnsupportedGlobalPax),
722 _ => {}
723 }
724
725 if self.pending_pax {
726 validate_pax(&self.pax[..self.pax_len])?;
727 if let Some(value) = pax_value(&self.pax[..self.pax_len], b"size")? {
728 size = decimal(value).ok_or(ArchiveError::InvalidSize)?;
729 }
730 }
731
732 if self.pending_pax {
733 if let Some(value) = pax_value(&self.pax[..self.pax_len], b"path")? {
734 self.path_len = copy_scratch(self.path, value, Scratch::Path)?;
735 } else if !self.pending_long_path {
736 self.path_len = header_path(&self.header, self.path)?;
737 }
738 } else if !self.pending_long_path {
739 self.path_len = header_path(&self.header, self.path)?;
740 }
741 trim_nul(self.path, &mut self.path_len);
742 self.path_len = normalize_path(self.path, self.path_len)?;
743
744 if self.pending_pax {
745 if let Some(value) = pax_value(&self.pax[..self.pax_len], b"linkpath")? {
746 self.link_len = copy_scratch(self.link, value, Scratch::Link)?;
747 } else if !self.pending_long_link {
748 self.link_len = copy_scratch(
749 self.link,
750 nul_terminated(&self.header[157..257]),
751 Scratch::Link,
752 )?;
753 }
754 } else if !self.pending_long_link {
755 self.link_len = copy_scratch(
756 self.link,
757 nul_terminated(&self.header[157..257]),
758 Scratch::Link,
759 )?;
760 }
761 trim_nul(self.link, &mut self.link_len);
762
763 let kind = entry_kind(type_flag);
764 match kind {
765 EntryKind::HardLink if self.link_len != 0 => {
766 self.link_len = normalize_path(self.link, self.link_len)?;
767 }
768 EntryKind::SymbolicLink if self.link_len != 0 => {
769 validate_symbolic_link(&self.path[..self.path_len], &self.link[..self.link_len])?;
770 }
771 _ => {}
772 }
773
774 self.pending_long_path = false;
775 self.pending_long_link = false;
776 self.current = ArchiveEntry::Normal;
777 self.entry_remaining = size;
778 self.padding_remaining = padding_for(size);
779
780 if let Some(whiteout) = whiteout(self.path, &mut self.path_len) {
781 if kind != EntryKind::Regular || size != 0 {
782 return Err(ArchiveError::InvalidWhiteout);
783 }
784 match whiteout {
785 Whiteout::Remove => sink
786 .whiteout(&self.path[..self.path_len])
787 .map_err(ArchiveError::Sink)?,
788 Whiteout::Opaque => sink
789 .opaque_directory(&self.path[..self.path_len])
790 .map_err(ArchiveError::Sink)?,
791 }
792 self.pending_pax = false;
793 self.pax_len = 0;
794 self.current = ArchiveEntry::None;
795 return Ok(());
796 }
797
798 let mode = parse_number(&self.header[100..108]).ok_or(ArchiveError::InvalidMetadata)?;
799 let uid = parse_number(&self.header[108..116]).ok_or(ArchiveError::InvalidMetadata)?;
800 let gid = parse_number(&self.header[116..124]).ok_or(ArchiveError::InvalidMetadata)?;
801 let mtime = parse_number(&self.header[136..148]).ok_or(ArchiveError::InvalidMetadata)?;
802 let has_device = matches!(kind, EntryKind::CharacterDevice | EntryKind::BlockDevice);
803 let device_major = has_device
804 .then(|| parse_number(&self.header[329..337]))
805 .flatten();
806 let device_minor = has_device
807 .then(|| parse_number(&self.header[337..345]))
808 .flatten();
809 if has_device && (device_major.is_none() || device_minor.is_none()) {
810 return Err(ArchiveError::InvalidMetadata);
811 }
812 let link_target = matches!(kind, EntryKind::HardLink | EntryKind::SymbolicLink)
813 .then_some(&self.link[..self.link_len]);
814 sink.begin_entry(Entry {
815 path: &self.path[..self.path_len],
816 kind,
817 size,
818 mode,
819 uid,
820 gid,
821 mtime,
822 link_target,
823 device_major,
824 device_minor,
825 pax: if self.pending_pax {
826 &self.pax[..self.pax_len]
827 } else {
828 &[]
829 },
830 })
831 .map_err(ArchiveError::Sink)?;
832 self.entry_open = true;
833 self.pending_pax = false;
834 self.pax_len = 0;
835 if size == 0 {
836 self.finish_content(sink)?;
837 }
838 Ok(())
839 }
840
841 fn finish_content<S: LayerEventSink>(
842 &mut self,
843 sink: &mut S,
844 ) -> Result<(), ArchiveError<S::Error>> {
845 match self.current {
846 ArchiveEntry::Normal => {
847 if self.entry_open {
848 sink.end_entry().map_err(ArchiveError::Sink)?;
849 self.entry_open = false;
850 }
851 }
852 ArchiveEntry::LongPath => {
853 trim_nul(self.path, &mut self.path_len);
854 self.pending_long_path = true;
855 }
856 ArchiveEntry::LongLink => {
857 trim_nul(self.link, &mut self.link_len);
858 self.pending_long_link = true;
859 }
860 ArchiveEntry::Pax => {
861 validate_pax(&self.pax[..self.pax_len])?;
862 self.pending_pax = true;
863 }
864 ArchiveEntry::None => return Err(ArchiveError::InvalidState),
865 }
866 if self.padding_remaining == 0 {
867 self.current = ArchiveEntry::None;
868 }
869 Ok(())
870 }
871}
872
873#[derive(Clone, Copy, Debug, Eq, PartialEq)]
874pub enum Scratch {
875 Path,
876 Link,
877 Pax,
878}
879
880#[derive(Debug, Eq, PartialEq)]
881pub enum ArchiveError<E> {
882 InvalidChecksum,
883 InvalidSize,
884 InvalidMetadata,
885 InvalidEndMarker,
886 InvalidPath,
887 InvalidLink,
888 InvalidWhiteout,
889 InvalidPax(PaxError),
890 UnsupportedGlobalPax,
891 BufferTooSmall(Scratch),
892 InvalidState,
893 Sink(E),
894}
895
896impl<E: fmt::Display> fmt::Display for ArchiveError<E> {
897 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
898 match self {
899 Self::InvalidChecksum => formatter.write_str("invalid tar header checksum"),
900 Self::InvalidSize => formatter.write_str("invalid tar entry size"),
901 Self::InvalidMetadata => formatter.write_str("invalid tar entry metadata"),
902 Self::InvalidEndMarker => formatter.write_str("invalid tar end marker"),
903 Self::InvalidPath => formatter.write_str("unsafe or invalid tar path"),
904 Self::InvalidLink => formatter.write_str("unsafe tar link target"),
905 Self::InvalidWhiteout => formatter.write_str("invalid OCI whiteout entry"),
906 Self::InvalidPax(error) => write!(formatter, "invalid PAX header: {error}"),
907 Self::UnsupportedGlobalPax => {
908 formatter.write_str("global PAX headers are not supported")
909 }
910 Self::BufferTooSmall(buffer) => write!(formatter, "{buffer:?} buffer is too small"),
911 Self::InvalidState => formatter.write_str("invalid tar parser state"),
912 Self::Sink(error) => write!(formatter, "layer sink failed: {error}"),
913 }
914 }
915}
916
917#[derive(Clone, Copy, Debug, Eq, PartialEq)]
918pub enum ArchiveFinishError {
919 UnexpectedEof,
920 DanglingExtension,
921}
922
923impl fmt::Display for ArchiveFinishError {
924 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
925 formatter.write_str(match self {
926 Self::UnexpectedEof => "unexpected end of tar archive",
927 Self::DanglingExtension => "tar archive ended after an extension header",
928 })
929 }
930}
931
932#[derive(Clone, Copy, Debug, Eq, PartialEq)]
933pub enum PaxError {
934 InvalidLength,
935 InvalidRecord,
936}
937
938impl fmt::Display for PaxError {
939 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
940 formatter.write_str(match self {
941 Self::InvalidLength => "invalid record length",
942 Self::InvalidRecord => "invalid key/value record",
943 })
944 }
945}
946
947impl<E> From<PaxError> for ArchiveError<E> {
948 fn from(error: PaxError) -> Self {
949 Self::InvalidPax(error)
950 }
951}
952
953#[derive(Clone, Copy)]
954enum Whiteout {
955 Remove,
956 Opaque,
957}
958
959fn entry_kind(type_flag: u8) -> EntryKind {
960 match type_flag {
961 0 | b'0' => EntryKind::Regular,
962 b'1' => EntryKind::HardLink,
963 b'2' => EntryKind::SymbolicLink,
964 b'3' => EntryKind::CharacterDevice,
965 b'4' => EntryKind::BlockDevice,
966 b'5' => EntryKind::Directory,
967 b'6' => EntryKind::Fifo,
968 b'7' => EntryKind::Contiguous,
969 other => EntryKind::Other(other),
970 }
971}
972
973fn header_path<E>(
974 header: &[u8; BLOCK_SIZE],
975 destination: &mut [u8],
976) -> Result<usize, ArchiveError<E>> {
977 let name = nul_terminated(&header[NAME_RANGE]);
978 let prefix = nul_terminated(&header[PREFIX_RANGE]);
979 let needed = name.len() + usize::from(!prefix.is_empty()) + prefix.len();
980 if needed > destination.len() {
981 return Err(ArchiveError::BufferTooSmall(Scratch::Path));
982 }
983 let mut length = 0;
984 if !prefix.is_empty() {
985 destination[..prefix.len()].copy_from_slice(prefix);
986 length += prefix.len();
987 destination[length] = b'/';
988 length += 1;
989 }
990 destination[length..length + name.len()].copy_from_slice(name);
991 Ok(length + name.len())
992}
993
994fn append_scratch<E>(
995 destination: &mut [u8],
996 length: &mut usize,
997 bytes: &[u8],
998 kind: Scratch,
999) -> Result<(), ArchiveError<E>> {
1000 let end = length
1001 .checked_add(bytes.len())
1002 .ok_or(ArchiveError::BufferTooSmall(kind))?;
1003 let output = destination
1004 .get_mut(*length..end)
1005 .ok_or(ArchiveError::BufferTooSmall(kind))?;
1006 output.copy_from_slice(bytes);
1007 *length = end;
1008 Ok(())
1009}
1010
1011fn copy_scratch<E>(
1012 destination: &mut [u8],
1013 bytes: &[u8],
1014 kind: Scratch,
1015) -> Result<usize, ArchiveError<E>> {
1016 if bytes.len() > destination.len() {
1017 return Err(ArchiveError::BufferTooSmall(kind));
1018 }
1019 destination[..bytes.len()].copy_from_slice(bytes);
1020 Ok(bytes.len())
1021}
1022
1023fn trim_nul(bytes: &[u8], length: &mut usize) {
1024 while *length != 0 && bytes[*length - 1] == 0 {
1025 *length -= 1;
1026 }
1027}
1028
1029fn normalize_path<E>(bytes: &mut [u8], length: usize) -> Result<usize, ArchiveError<E>> {
1030 if length == 0 || bytes[0] == b'/' || bytes[..length].contains(&0) {
1031 return Err(ArchiveError::InvalidPath);
1032 }
1033 let mut read = 0;
1034 let mut write = 0;
1035 while read < length {
1036 while read < length && bytes[read] == b'/' {
1037 read += 1;
1038 }
1039 let start = read;
1040 while read < length && bytes[read] != b'/' {
1041 read += 1;
1042 }
1043 let component_len = read - start;
1044 if component_len == 0 || &bytes[start..read] == b"." {
1045 continue;
1046 }
1047 if &bytes[start..read] == b".." {
1048 return Err(ArchiveError::InvalidPath);
1049 }
1050 if write != 0 {
1051 bytes[write] = b'/';
1052 write += 1;
1053 }
1054 bytes.copy_within(start..read, write);
1055 write += component_len;
1056 }
1057 if write == 0 {
1058 bytes[0] = b'.';
1059 write = 1;
1060 }
1061 Ok(write)
1062}
1063
1064fn validate_symbolic_link<E>(path: &[u8], target: &[u8]) -> Result<(), ArchiveError<E>> {
1065 if target.is_empty() || target.contains(&0) {
1066 return Err(ArchiveError::InvalidLink);
1067 }
1068 let mut depth = if target[0] == b'/' {
1069 0
1070 } else {
1071 path.split(|byte| *byte == b'/').count().saturating_sub(1)
1072 };
1073 for component in target.split(|byte| *byte == b'/') {
1074 match component {
1075 b"" | b"." => {}
1076 b".." => {
1077 depth = depth.checked_sub(1).ok_or(ArchiveError::InvalidLink)?;
1078 }
1079 _ => depth += 1,
1080 }
1081 }
1082 Ok(())
1083}
1084
1085fn whiteout(path: &mut [u8], length: &mut usize) -> Option<Whiteout> {
1086 let basename = path[..*length]
1087 .iter()
1088 .rposition(|byte| *byte == b'/')
1089 .map_or(0, |position| position + 1);
1090 let name = &path[basename..*length];
1091 if name == b".wh..wh..opq" {
1092 if basename == 0 {
1093 path[0] = b'.';
1094 *length = 1;
1095 } else {
1096 *length = basename - 1;
1097 }
1098 return Some(Whiteout::Opaque);
1099 }
1100 let target = name.strip_prefix(b".wh.")?;
1101 if target.is_empty() {
1102 return None;
1103 }
1104 path.copy_within(basename + 4..*length, basename);
1105 *length -= 4;
1106 Some(Whiteout::Remove)
1107}
1108
1109fn validate_pax<E>(bytes: &[u8]) -> Result<(), ArchiveError<E>> {
1110 for record in PaxRecords::new(bytes) {
1111 record?;
1112 }
1113 Ok(())
1114}
1115
1116fn pax_value<'a, E>(bytes: &'a [u8], key: &[u8]) -> Result<Option<&'a [u8]>, ArchiveError<E>> {
1117 let mut found = None;
1118 for record in PaxRecords::new(bytes) {
1119 let record = record?;
1120 if record.key == key {
1121 if found.is_some() {
1122 return Err(ArchiveError::InvalidPax(PaxError::InvalidRecord));
1123 }
1124 found = Some(record.value);
1125 }
1126 }
1127 Ok(found)
1128}
1129
1130fn parse_pax_record(bytes: &[u8], position: usize) -> Result<(PaxRecord<'_>, usize), PaxError> {
1131 let space = bytes[position..]
1132 .iter()
1133 .position(|byte| *byte == b' ')
1134 .map(|offset| position + offset)
1135 .ok_or(PaxError::InvalidLength)?;
1136 let length = decimal(&bytes[position..space]).ok_or(PaxError::InvalidLength)?;
1137 let length = usize::try_from(length).map_err(|_| PaxError::InvalidLength)?;
1138 let end = position
1139 .checked_add(length)
1140 .ok_or(PaxError::InvalidLength)?;
1141 let record = bytes.get(space + 1..end).ok_or(PaxError::InvalidLength)?;
1142 let record = record.strip_suffix(b"\n").ok_or(PaxError::InvalidRecord)?;
1143 let equals = record
1144 .iter()
1145 .position(|byte| *byte == b'=')
1146 .ok_or(PaxError::InvalidRecord)?;
1147 if equals == 0 {
1148 return Err(PaxError::InvalidRecord);
1149 }
1150 Ok((
1151 PaxRecord {
1152 key: &record[..equals],
1153 value: &record[equals + 1..],
1154 },
1155 end,
1156 ))
1157}
1158
1159fn decimal(bytes: &[u8]) -> Option<u64> {
1160 if bytes.is_empty() {
1161 return None;
1162 }
1163 bytes.iter().try_fold(0u64, |value, byte| {
1164 value.checked_mul(10)?.checked_add(u64::from(
1165 byte.checked_sub(b'0').filter(|digit| *digit <= 9)?,
1166 ))
1167 })
1168}
1169
1170#[cfg(test)]
1171mod tests {
1172 use super::{
1173 Archive, ArchiveBuffers, Entry, EntryExtractor, EntryKind, ExtractError, FinishError,
1174 LayerEventSink, TarWriteError, TarWriter, BLOCK_SIZE,
1175 };
1176 use std::{format, string::ToString, vec::Vec};
1177
1178 type OwnedPaxRecord = (Vec<u8>, Vec<u8>);
1179 type OwnedEntry = (Vec<u8>, EntryKind, Vec<OwnedPaxRecord>);
1180
1181 #[derive(Default)]
1182 struct Events {
1183 entries: Vec<OwnedEntry>,
1184 contents: Vec<u8>,
1185 whiteouts: Vec<Vec<u8>>,
1186 opaque: Vec<Vec<u8>>,
1187 }
1188
1189 #[test]
1190 fn writes_deterministic_ustar_files() {
1191 let mut bytes = Vec::new();
1192 let mut writer = TarWriter::new();
1193 writer
1194 .begin_file(b"blobs/sha256/abc", 5, 0o644, |chunk| {
1195 bytes.extend_from_slice(chunk);
1196 Ok::<_, ()>(())
1197 })
1198 .unwrap();
1199 writer
1200 .write_file_data(b"hello", |chunk| {
1201 bytes.extend_from_slice(chunk);
1202 Ok::<_, ()>(())
1203 })
1204 .unwrap();
1205 writer
1206 .end_file(|chunk| {
1207 bytes.extend_from_slice(chunk);
1208 Ok::<_, ()>(())
1209 })
1210 .unwrap();
1211 writer
1212 .finish(|chunk| {
1213 bytes.extend_from_slice(chunk);
1214 Ok::<_, ()>(())
1215 })
1216 .unwrap();
1217
1218 assert_eq!(&bytes[..18], b"blobs/sha256/abc\0\0");
1219 assert_eq!(&bytes[BLOCK_SIZE..BLOCK_SIZE + 5], b"hello");
1220 assert_eq!(bytes.len(), BLOCK_SIZE * 4);
1221
1222 let mut invalid = TarWriter::new();
1223 assert_eq!(
1224 invalid.begin_file(b"../escape", 0, 0o644, |_| Ok::<_, ()>(())),
1225 Err(TarWriteError::InvalidPath)
1226 );
1227 }
1228
1229 impl LayerEventSink for Events {
1230 type Error = ();
1231
1232 fn begin_entry(&mut self, entry: Entry<'_>) -> Result<(), Self::Error> {
1233 let pax = entry
1234 .pax_records()
1235 .map(|record| {
1236 let record = record.unwrap();
1237 (record.key.to_vec(), record.value.to_vec())
1238 })
1239 .collect();
1240 self.entries.push((entry.path.to_vec(), entry.kind, pax));
1241 Ok(())
1242 }
1243
1244 fn entry_data(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
1245 self.contents.extend_from_slice(bytes);
1246 Ok(())
1247 }
1248
1249 fn end_entry(&mut self) -> Result<(), Self::Error> {
1250 Ok(())
1251 }
1252
1253 fn whiteout(&mut self, path: &[u8]) -> Result<(), Self::Error> {
1254 self.whiteouts.push(path.to_vec());
1255 Ok(())
1256 }
1257
1258 fn opaque_directory(&mut self, path: &[u8]) -> Result<(), Self::Error> {
1259 self.opaque.push(path.to_vec());
1260 Ok(())
1261 }
1262 }
1263
1264 #[test]
1265 fn streams_layer_events_and_whiteouts_at_every_fragment_size() {
1266 let mut bytes = Vec::new();
1267 append_entry(&mut bytes, b"./etc//config", b"value", b'0', b"");
1268 append_entry(&mut bytes, b"etc/.wh.deleted", b"", b'0', b"");
1269 append_entry(&mut bytes, b"var/lib/.wh..wh..opq", b"", b'0', b"");
1270 finish_archive(&mut bytes);
1271
1272 for fragment in 1..=BLOCK_SIZE + 1 {
1273 let mut path = [0; 256];
1274 let mut link = [0; 256];
1275 let mut pax = [0; 512];
1276 let mut archive = Archive::new(ArchiveBuffers {
1277 path: &mut path,
1278 link: &mut link,
1279 pax: &mut pax,
1280 });
1281 let mut events = Events::default();
1282 for chunk in bytes.chunks(fragment) {
1283 archive.push(chunk, &mut events).unwrap();
1284 }
1285 archive.finish().unwrap();
1286 assert_eq!(events.entries[0].0, b"etc/config");
1287 assert_eq!(events.contents, b"value");
1288 assert_eq!(events.whiteouts, [b"etc/deleted".to_vec()]);
1289 assert_eq!(events.opaque, [b"var/lib".to_vec()]);
1290 }
1291 }
1292
1293 #[test]
1294 fn applies_pax_and_gnu_long_path_overrides_without_allocating() {
1295 let mut bytes = Vec::new();
1296 let record = pax_record("path", "pax/overridden/file");
1297 append_entry(&mut bytes, b"PaxHeaders.0", &record, b'x', b"");
1298 append_entry(&mut bytes, b"ignored", b"pax", b'0', b"");
1299
1300 let long = [b'a'; 140];
1301 let mut long_nul = long.to_vec();
1302 long_nul.push(0);
1303 append_entry(&mut bytes, b"LongLink", &long_nul, b'L', b"");
1304 append_entry(&mut bytes, b"ignored", b"gnu", b'0', b"");
1305 finish_archive(&mut bytes);
1306
1307 let mut path = [0; 256];
1308 let mut link = [0; 256];
1309 let mut pax = [0; 512];
1310 let mut archive = Archive::new(ArchiveBuffers {
1311 path: &mut path,
1312 link: &mut link,
1313 pax: &mut pax,
1314 });
1315 let mut events = Events::default();
1316 archive.push(&bytes, &mut events).unwrap();
1317 archive.finish().unwrap();
1318 assert_eq!(events.entries[0].0, b"pax/overridden/file");
1319 assert_eq!(events.entries[0].2[0].0, b"path");
1320 assert_eq!(events.entries[1].0, long);
1321 assert_eq!(events.contents, b"paxgnu");
1322 }
1323
1324 #[test]
1325 fn rejects_paths_that_escape_the_root() {
1326 let mut bytes = Vec::new();
1327 append_entry(&mut bytes, b"../escape", b"", b'0', b"");
1328 let mut path = [0; 64];
1329 let mut link = [0; 64];
1330 let mut pax = [0; 64];
1331 let mut archive = Archive::new(ArchiveBuffers {
1332 path: &mut path,
1333 link: &mut link,
1334 pax: &mut pax,
1335 });
1336 assert!(archive.push(&bytes, &mut Events::default()).is_err());
1337 }
1338
1339 #[test]
1340 fn extracts_a_regular_file_from_arbitrary_fragments() {
1341 let mut archive = Vec::new();
1342 append_entry(&mut archive, b"first", b"skip", b'0', b"");
1343 append_entry(&mut archive, b"wanted", b"contents", b'0', b"");
1344 append_entry(&mut archive, b"wanted", b"later", b'0', b"");
1345 finish_archive(&mut archive);
1346
1347 for fragment_size in 1..=BLOCK_SIZE + 1 {
1348 let mut extractor = EntryExtractor::new(b"wanted");
1349 let mut output = Vec::new();
1350 for fragment in archive.chunks(fragment_size) {
1351 extractor
1352 .push(fragment, |bytes| {
1353 output.extend_from_slice(bytes);
1354 Ok::<_, ()>(())
1355 })
1356 .unwrap();
1357 }
1358 extractor.finish().unwrap();
1359 assert_eq!(output, b"contents");
1360 }
1361 }
1362
1363 #[test]
1364 fn matches_a_ustar_prefix_without_allocating_a_path() {
1365 let mut archive = Vec::new();
1366 append_entry(
1367 &mut archive,
1368 b"settings.yaml",
1369 b"value",
1370 b'0',
1371 b"etc/datadog-agent",
1372 );
1373 finish_archive(&mut archive);
1374 let mut extractor = EntryExtractor::new(b"etc/datadog-agent/settings.yaml");
1375 let mut output = Vec::new();
1376 extractor
1377 .push(&archive, |bytes| {
1378 output.extend_from_slice(bytes);
1379 Ok::<_, ()>(())
1380 })
1381 .unwrap();
1382 extractor.finish().unwrap();
1383 assert_eq!(output, b"value");
1384 }
1385
1386 #[test]
1387 fn recognizes_an_empty_regular_file() {
1388 let mut archive = Vec::new();
1389 append_entry(&mut archive, b"empty", b"", b'0', b"");
1390 finish_archive(&mut archive);
1391 let mut extractor = EntryExtractor::new(b"empty");
1392 extractor.push(&archive, |_| Ok::<_, ()>(())).unwrap();
1393 assert!(extractor.found());
1394 extractor.finish().unwrap();
1395 }
1396
1397 #[test]
1398 fn does_not_extract_a_directory_with_the_requested_name() {
1399 let mut archive = Vec::new();
1400 append_entry(&mut archive, b"wanted", b"", b'5', b"");
1401 finish_archive(&mut archive);
1402 let mut extractor = EntryExtractor::new(b"wanted");
1403 extractor.push(&archive, |_| Ok::<_, ()>(())).unwrap();
1404 assert_eq!(extractor.finish(), Err(FinishError::NotFound));
1405 }
1406
1407 #[test]
1408 fn rejects_an_invalid_checksum() {
1409 let mut archive = Vec::new();
1410 append_entry(&mut archive, b"wanted", b"contents", b'0', b"");
1411 archive[0] ^= 1;
1412 let mut extractor = EntryExtractor::new(b"wanted");
1413 assert_eq!(
1414 extractor.push(&archive, |_| Ok::<_, ()>(())),
1415 Err(ExtractError::InvalidChecksum)
1416 );
1417 }
1418
1419 #[test]
1420 fn reports_a_truncated_stream() {
1421 let mut archive = Vec::new();
1422 append_entry(&mut archive, b"wanted", b"contents", b'0', b"");
1423 let mut extractor = EntryExtractor::new(b"wanted");
1424 extractor
1425 .push(&archive[..BLOCK_SIZE + 2], |_| Ok::<_, ()>(()))
1426 .unwrap();
1427 assert_eq!(extractor.finish(), Err(FinishError::UnexpectedEof));
1428 }
1429
1430 fn append_entry(archive: &mut Vec<u8>, name: &[u8], contents: &[u8], kind: u8, prefix: &[u8]) {
1431 let mut header = [0u8; BLOCK_SIZE];
1432 header[..name.len()].copy_from_slice(name);
1433 write_octal(&mut header[100..108], 0o644);
1434 write_octal(&mut header[108..116], 0);
1435 write_octal(&mut header[116..124], 0);
1436 write_octal(&mut header[124..136], contents.len() as u64);
1437 write_octal(&mut header[136..148], 0);
1438 header[148..156].fill(b' ');
1439 header[156] = kind;
1440 header[257..263].copy_from_slice(b"ustar\0");
1441 header[263..265].copy_from_slice(b"00");
1442 header[345..345 + prefix.len()].copy_from_slice(prefix);
1443 let checksum = header.iter().map(|byte| u64::from(*byte)).sum();
1444 write_octal(&mut header[148..156], checksum);
1445
1446 archive.extend_from_slice(&header);
1447 archive.extend_from_slice(contents);
1448 archive.resize(archive.len() + super::padding_for(contents.len() as u64), 0);
1449 }
1450
1451 fn finish_archive(archive: &mut Vec<u8>) {
1452 archive.resize(archive.len() + 2 * BLOCK_SIZE, 0);
1453 }
1454
1455 fn pax_record(key: &str, value: &str) -> Vec<u8> {
1456 let payload = key.len() + 1 + value.len() + 1;
1457 let mut digits = 1;
1458 loop {
1459 let length = digits + 1 + payload;
1460 let actual_digits = length.to_string().len();
1461 if actual_digits == digits {
1462 return format!("{length} {key}={value}\n").into_bytes();
1463 }
1464 digits = actual_digits;
1465 }
1466 }
1467
1468 fn write_octal(field: &mut [u8], mut value: u64) {
1469 field.fill(b'0');
1470 *field.last_mut().unwrap() = 0;
1471 let digits = field.len() - 1;
1472 for byte in field[..digits].iter_mut().rev() {
1473 *byte = b'0' + (value % 8) as u8;
1474 value /= 8;
1475 }
1476 assert_eq!(value, 0);
1477 }
1478}