1mod builder;
7mod format;
8mod unpack;
9
10use std::borrow::Cow;
11use std::cell::RefCell;
12use std::collections::{BTreeMap, BTreeSet};
13use std::fs::File;
14use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
15use std::marker::PhantomData;
16use std::path::Path;
17use std::rc::Rc;
18
19pub use builder::Builder;
20use format::{
21 apply_pax_header, bytes_to_path, parse_decimal, parse_header, parse_number, parse_pax,
22 parse_sparse_csv, parse_sparse_pairs, pax_text_checked, pax_u64_checked, pax_value,
23 push_sparse_pair, trim_metadata, validate_sparse, verify_checksum,
24};
25pub use unpack::{
26 EntryCallback, EntryInfo, EntryUnpacker, Progress, ProgressCallback, SkipReason, SkippedEntry,
27 UnpackOptions, UnpackSummary,
28};
29use unpack::{ProgressReporter, unpack_archive};
30
31const BLOCK: u64 = 512;
32const MAX_METADATA_SIZE: u64 = 1024 * 1024;
33const MAX_SPARSE_SEGMENTS: usize = 1_000_000;
34const PAX_HEADER_KEYS: [&str; 7] = ["path", "linkpath", "size", "mode", "uid", "gid", "mtime"];
35type PaxRecords = Vec<(String, Vec<u8>)>;
36
37struct PaxLayer {
38 parent: Option<Rc<Self>>,
39 records: BTreeMap<String, Vec<u8>>,
40}
41
42impl Drop for PaxLayer {
43 fn drop(&mut self) {
44 let mut parent = self.parent.take();
47 while let Some(mut layer) = parent {
48 parent = Rc::get_mut(&mut layer).and_then(|layer| layer.parent.take());
49 }
50 }
51}
52
53pub type Result<T> = io::Result<T>;
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub struct SparseSegment {
59 pub offset: u64,
61 pub len: u64,
63}
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
67#[non_exhaustive]
68pub enum EntryType {
69 File,
71 Directory,
73 Symlink,
75 Hardlink,
77 CharDevice,
79 BlockDevice,
81 Fifo,
83 Other(u8),
85}
86
87impl EntryType {
88 fn from_flag(flag: u8) -> Self {
89 match flag {
90 0 | b'0' | b'7' | b'S' => Self::File,
91 b'5' => Self::Directory,
92 b'2' => Self::Symlink,
93 b'1' => Self::Hardlink,
94 b'3' => Self::CharDevice,
95 b'4' => Self::BlockDevice,
96 b'6' => Self::Fifo,
97 other => Self::Other(other),
98 }
99 }
100
101 const fn type_flag(self) -> u8 {
102 match self {
103 Self::File => b'0',
104 Self::Directory => b'5',
105 Self::Symlink => b'2',
106 Self::Hardlink => b'1',
107 Self::CharDevice => b'3',
108 Self::BlockDevice => b'4',
109 Self::Fifo => b'6',
110 Self::Other(flag) => flag,
111 }
112 }
113}
114
115#[derive(Clone, Debug)]
117pub struct Header {
118 path: Vec<u8>,
119 link_name: Option<Vec<u8>>,
120 mode: u32,
121 uid: u64,
122 gid: u64,
123 stored_size: u64,
124 mtime: i64,
125 type_flag: u8,
126}
127
128impl Header {
129 #[must_use]
135 pub fn new_gnu(entry_type: EntryType) -> Self {
136 Self {
137 path: Vec::new(),
138 link_name: None,
139 mode: 0,
140 uid: 0,
141 gid: 0,
142 stored_size: 0,
143 mtime: 0,
144 type_flag: entry_type.type_flag(),
145 }
146 }
147
148 #[must_use]
150 pub const fn mode(&self) -> u32 {
151 self.mode
152 }
153 #[must_use]
155 pub const fn uid(&self) -> u64 {
156 self.uid
157 }
158 #[must_use]
160 pub const fn gid(&self) -> u64 {
161 self.gid
162 }
163 #[must_use]
165 pub const fn stored_size(&self) -> u64 {
166 self.stored_size
167 }
168 #[must_use]
170 pub const fn mtime(&self) -> i64 {
171 self.mtime
172 }
173 #[must_use]
175 pub const fn type_flag(&self) -> u8 {
176 self.type_flag
177 }
178
179 pub const fn set_mode(&mut self, mode: u32) {
181 self.mode = mode;
182 }
183
184 pub const fn set_uid(&mut self, uid: u64) {
186 self.uid = uid;
187 }
188
189 pub const fn set_gid(&mut self, gid: u64) {
191 self.gid = gid;
192 }
193
194 pub const fn set_size(&mut self, size: u64) {
196 self.stored_size = size;
197 }
198
199 pub const fn set_mtime(&mut self, mtime: i64) {
201 self.mtime = mtime;
202 }
203
204 pub fn link_name(&self) -> Option<Cow<'_, Path>> {
206 self.link_name.as_deref().map(bytes_to_path)
207 }
208}
209
210struct ReaderState<R> {
211 reader: R,
212 raw_bytes: u64,
213 pending: u64,
214 padding: u64,
215 generation: u64,
216}
217
218impl<R: Read> ReaderState<R> {
219 fn read_counted(&mut self, buf: &mut [u8]) -> io::Result<usize> {
220 let n = self.reader.read(buf)?;
221 self.raw_bytes = self.raw_bytes.saturating_add(n as u64);
222 Ok(n)
223 }
224
225 fn read_exact_counted(&mut self, mut buf: &mut [u8]) -> io::Result<()> {
226 while !buf.is_empty() {
227 let n = self.read_counted(buf)?;
228 if n == 0 {
229 return Err(error(ErrorKind::UnexpectedEof, "truncated tar archive"));
230 }
231 buf = &mut buf[n..];
232 }
233 Ok(())
234 }
235
236 fn skip(&mut self, mut amount: u64) -> io::Result<()> {
237 let mut buf = [0_u8; 8192];
238 while amount != 0 {
239 let want = usize::try_from(amount.min(buf.len() as u64)).unwrap_or(buf.len());
240 self.read_exact_counted(&mut buf[..want])?;
241 amount -= want as u64;
242 }
243 Ok(())
244 }
245
246 fn finish_entry(&mut self) -> io::Result<()> {
247 self.skip(self.pending)?;
248 self.pending = 0;
249 self.skip(self.padding)?;
250 self.padding = 0;
251 Ok(())
252 }
253
254 fn read_entry_data(&mut self, generation: u64, buf: &mut [u8]) -> io::Result<usize> {
255 if generation != self.generation {
256 return Err(error(
257 ErrorKind::InvalidInput,
258 "entry is stale because iteration advanced",
259 ));
260 }
261 let want = usize::try_from(self.pending.min(buf.len() as u64)).unwrap_or(buf.len());
262 if want == 0 {
263 return Ok(0);
264 }
265 let n = self.read_counted(&mut buf[..want])?;
266 if n == 0 {
267 return Err(error(ErrorKind::UnexpectedEof, "truncated entry data"));
268 }
269 self.pending -= n as u64;
270 Ok(n)
271 }
272}
273
274pub struct Archive<R: Read> {
276 state: Rc<RefCell<ReaderState<R>>>,
277}
278
279impl<R: Read> Archive<R> {
280 pub fn new(reader: R) -> Self {
282 Self {
283 state: Rc::new(RefCell::new(ReaderState {
284 reader,
285 raw_bytes: 0,
286 pending: 0,
287 padding: 0,
288 generation: 0,
289 })),
290 }
291 }
292
293 pub fn entries(&mut self) -> Result<Entries<'_, R>> {
299 if self.state.borrow().raw_bytes != 0 {
300 return Err(error(
301 ErrorKind::InvalidInput,
302 "cannot restart entry iteration after consuming archive bytes",
303 ));
304 }
305 Ok(Entries {
306 state: Rc::clone(&self.state),
307 done: false,
308 zero_blocks: 0,
309 global_pax: BTreeMap::new(),
310 pending_global_pax_bytes: 0,
311 global_pax_snapshot: None,
312 local_pax: None,
313 long_name: None,
314 long_link: None,
315 marker: PhantomData,
316 })
317 }
318
319 pub fn unpack<P: AsRef<Path>>(
326 mut self,
327 dest: P,
328 opts: &mut UnpackOptions,
329 ) -> Result<UnpackSummary> {
330 unpack_archive(&mut self, dest.as_ref(), opts)
331 }
332}
333
334pub struct Entries<'a, R: Read> {
336 state: Rc<RefCell<ReaderState<R>>>,
337 done: bool,
338 zero_blocks: u8,
339 global_pax: BTreeMap<String, Vec<u8>>,
340 pending_global_pax_bytes: u64,
341 global_pax_snapshot: Option<Rc<PaxLayer>>,
342 local_pax: Option<Vec<(String, Vec<u8>)>>,
343 long_name: Option<Vec<u8>>,
344 long_link: Option<Vec<u8>>,
345 marker: PhantomData<&'a mut Archive<R>>,
346}
347
348impl<R: Read> Iterator for Entries<'_, R> {
349 type Item = Result<Entry<R>>;
350
351 fn next(&mut self) -> Option<Self::Item> {
352 if self.done {
353 return None;
354 }
355 match self.next_entry() {
356 Ok(Some(entry)) => Some(Ok(entry)),
357 Ok(None) => {
358 self.done = true;
359 None
360 }
361 Err(err) => {
362 self.done = true;
363 Some(Err(err))
364 }
365 }
366 }
367}
368
369impl<R: Read> Entries<'_, R> {
370 #[allow(clippy::too_many_lines)]
371 fn next_entry(&mut self) -> Result<Option<Entry<R>>> {
372 loop {
373 let mut block = [0_u8; 512];
374 {
375 let mut state = self.state.borrow_mut();
376 state.finish_entry()?;
377 state.generation = state.generation.wrapping_add(1);
378 let mut first = [0_u8; 1];
379 let n = state.read_counted(&mut first)?;
380 if n == 0 {
381 return self.finish_stream();
382 }
383 block[0] = first[0];
384 state.read_exact_counted(&mut block[1..])?;
385 }
386 if block.iter().all(|byte| *byte == 0) {
387 self.zero_blocks += 1;
388 if self.zero_blocks == 2 {
389 return self.finish_stream();
390 }
391 continue;
392 }
393 if self.zero_blocks != 0 {
394 return Err(invalid(
395 "tar zero block was not followed by a second zero block",
396 ));
397 }
398 self.zero_blocks = 0;
399 verify_checksum(&block)?;
400 let mut header = parse_header(&block)?;
401 let flag = header.type_flag;
402 let size = header.stored_size;
403
404 let identity = &block[257..265];
405 let is_ustar = identity == b"ustar\x0000";
406 let is_gnu = identity == b"ustar \0";
407 if matches!(flag, b'x' | b'g') && !is_ustar {
408 return Err(invalid("PAX extension requires a USTAR carrier"));
409 }
410 if matches!(flag, b'L' | b'K') && !(is_ustar || is_gnu) {
411 return Err(invalid(
412 "GNU long-name/link extension requires a USTAR or GNU carrier",
413 ));
414 }
415
416 if matches!(flag, b'x' | b'g' | b'L' | b'K') {
417 if size > MAX_METADATA_SIZE {
418 return Err(error(
419 ErrorKind::InvalidData,
420 "tar metadata exceeds 1 MiB limit",
421 ));
422 }
423 if flag == b'g' {
424 let total = self
425 .pending_global_pax_bytes
426 .checked_add(size)
427 .ok_or_else(|| invalid("global PAX metadata size overflow"))?;
428 if total > MAX_METADATA_SIZE {
429 return Err(invalid("pending global PAX metadata exceeds 1 MiB limit"));
430 }
431 self.pending_global_pax_bytes = total;
432 }
433 let payload = self.read_metadata(size)?;
434 match flag {
435 b'x' => {
436 if self.local_pax.is_some() {
437 return Err(invalid("two local PAX headers describe one entry"));
438 }
439 self.local_pax = Some(parse_pax(&payload)?);
440 }
441 b'g' => {
442 let records = parse_pax(&payload)?;
443 if records
445 .iter()
446 .any(|(key, _)| key.starts_with("GNU.sparse."))
447 {
448 return Err(invalid("GNU sparse PAX metadata is not valid globally"));
449 }
450 for (key, value) in &records {
451 self.global_pax.insert(key.clone(), value.clone());
452 }
453 if let Some(layer) = self.global_pax_snapshot.as_mut().and_then(Rc::get_mut)
454 {
455 layer.records.extend(records);
456 } else {
457 self.global_pax_snapshot = Some(Rc::new(PaxLayer {
458 parent: self.global_pax_snapshot.take(),
459 records: records.into_iter().collect(),
460 }));
461 }
462 }
463 b'L' => {
464 if self.long_name.is_some() {
465 return Err(invalid("two GNU long-name headers describe one entry"));
466 }
467 self.long_name = Some(trim_metadata(payload));
468 }
469 b'K' => {
470 if self.long_link.is_some() {
471 return Err(invalid("two GNU long-link headers describe one entry"));
472 }
473 self.long_link = Some(trim_metadata(payload));
474 }
475 _ => unreachable!(),
476 }
477 continue;
478 }
479
480 let pax_global = self.global_pax_snapshot.clone();
481 let pax_local = self.local_pax.take().unwrap_or_default();
482 let pax_local_keys = pax_local
483 .iter()
484 .map(|(key, _)| key.clone())
485 .collect::<BTreeSet<_>>();
486
487 let mut pax = Vec::new();
490 for key in PAX_HEADER_KEYS {
491 if let Some(value) = self
492 .global_pax
493 .get(key)
494 .filter(|_| !pax_local_keys.contains(key))
495 {
496 pax.push((key.to_owned(), value.clone()));
497 }
498 }
499 for (key, value) in self.global_pax.range("GNU.sparse.".to_owned()..) {
500 if !key.starts_with("GNU.sparse.") {
501 break;
502 }
503 if !pax_local_keys.contains(key) {
504 pax.push((key.clone(), value.clone()));
505 }
506 }
507 pax.extend(
508 pax_local
509 .iter()
510 .filter(|(key, _)| {
511 PAX_HEADER_KEYS.contains(&key.as_str()) || key.starts_with("GNU.sparse.")
512 })
513 .cloned(),
514 );
515 if self.long_name.is_some()
516 && pax_value(&pax, "path").is_some_and(|path| !path.is_empty())
517 {
518 return Err(invalid(
519 "PAX path and GNU long-name describe the same entry",
520 ));
521 }
522 if self.long_link.is_some()
523 && pax_value(&pax, "linkpath").is_some_and(|link| !link.is_empty())
524 {
525 return Err(invalid(
526 "PAX linkpath and GNU long-link describe the same entry",
527 ));
528 }
529 apply_pax_header(&mut header, &pax)?;
530 if let Some(name) = self.long_name.take() {
531 header.path = name;
532 }
533 if let Some(link) = self.long_link.take() {
534 header.link_name = Some(link);
535 }
536
537 let pax_size = header.stored_size;
541 let has_pax_sparse = pax.iter().any(|(key, _)| key.starts_with("GNU.sparse."));
542 if has_pax_sparse && !matches!(flag, 0 | b'0' | b'7') {
543 return Err(invalid("GNU sparse PAX metadata requires a regular file"));
544 }
545 let physical_size = if has_pax_sparse { size } else { pax_size };
546 if matches!(flag, b'1'..=b'6') && (size != 0 || physical_size != 0) {
547 return Err(invalid("nonregular tar entry cannot carry payload"));
548 }
549 header.stored_size = physical_size;
550 let generation;
551 {
552 let mut state = self.state.borrow_mut();
553 state.pending = physical_size;
554 state.padding = padding(physical_size);
555 generation = state.generation;
556 }
557
558 let (sparse, logical_size) = if flag == b'S' {
559 self.parse_old_gnu_sparse(&block, physical_size)?
560 } else {
561 self.parse_pax_sparse(&pax, physical_size)?
562 };
563 if has_pax_sparse && pax_size != physical_size && pax_size != logical_size {
564 return Err(invalid(
565 "GNU sparse PAX size conflicts with physical and logical sizes",
566 ));
567 }
568 if let Some(name) = pax_value(&pax, "GNU.sparse.name") {
569 header.path = name.to_vec();
570 }
571 if header.path.is_empty() && pax_value(&pax, "path").is_some_and(<[u8]>::is_empty) {
572 return Err(invalid("PAX path deletion leaves entry without a path"));
573 }
574 if matches!(flag, b'1' | b'2')
575 && pax_value(&pax, "linkpath").is_some_and(<[u8]>::is_empty)
576 && header.link_name.as_ref().is_none_or(Vec::is_empty)
577 {
578 return Err(invalid(
579 "PAX linkpath deletion leaves link without a target",
580 ));
581 }
582 let kind = EntryType::from_flag(flag);
583 if header.path.is_empty() {
584 return Err(invalid("tar entry has an empty effective path"));
585 }
586 if header.path.contains(&0) {
587 return Err(invalid("tar entry path contains a NUL byte"));
588 }
589 if matches!(kind, EntryType::Symlink | EntryType::Hardlink)
590 && header
591 .link_name
592 .as_ref()
593 .is_none_or(|target| target.is_empty() || target.contains(&0))
594 {
595 return Err(invalid("tar link entry has an empty or NUL-bearing target"));
596 }
597 self.pending_global_pax_bytes = 0;
598 return Ok(Some(Entry {
599 state: Rc::clone(&self.state),
600 header,
601 kind,
602 pax_global,
603 pax_local,
604 pax_local_keys,
605 sparse,
606 logical_size,
607 logical_pos: 0,
608 sparse_index: 0,
609 generation,
610 extraction_started: false,
611 }));
612 }
613 }
614
615 fn finish_stream(&self) -> Result<Option<Entry<R>>> {
616 if self.local_pax.is_some() || self.long_name.is_some() || self.long_link.is_some() {
617 return Err(invalid("extension entry was not followed by a member"));
618 }
619 Ok(None)
620 }
621
622 fn read_metadata(&self, size: u64) -> Result<Vec<u8>> {
623 let alloc =
624 usize::try_from(size).map_err(|_| invalid("metadata size cannot fit in memory"))?;
625 let mut payload = vec![0_u8; alloc];
626 let mut state = self.state.borrow_mut();
627 state.read_exact_counted(&mut payload)?;
628 state.skip(padding(size))?;
629 Ok(payload)
630 }
631
632 fn parse_old_gnu_sparse(
633 &self,
634 block: &[u8; 512],
635 physical: u64,
636 ) -> Result<(Option<Vec<SparseSegment>>, u64)> {
637 if &block[257..265] != b"ustar \0" {
638 return Err(invalid("old GNU sparse entry lacks a GNU header"));
639 }
640 let mut map = Vec::new();
641 for chunk in block[386..482].chunks_exact(24) {
642 if !push_sparse_pair(&mut map, &chunk[..12], &chunk[12..])? {
643 break;
644 }
645 }
646 let mut extended = block[482] != 0;
647 let mut metadata_bytes = 96_u64;
649 while extended {
650 metadata_bytes = metadata_bytes
651 .checked_add(BLOCK)
652 .ok_or_else(|| invalid("old GNU sparse metadata size overflow"))?;
653 if metadata_bytes > MAX_METADATA_SIZE {
654 return Err(invalid("old GNU sparse metadata exceeds 1 MiB limit"));
655 }
656 let mut ext = [0_u8; 512];
657 self.state.borrow_mut().read_exact_counted(&mut ext)?;
658 for chunk in ext[..504].chunks_exact(24) {
659 if !push_sparse_pair(&mut map, &chunk[..12], &chunk[12..])? {
660 break;
661 }
662 }
663 extended = ext[504] != 0;
664 if map.len() > MAX_SPARSE_SEGMENTS {
665 return Err(invalid("sparse map has too many segments"));
666 }
667 }
668 let logical = parse_number(&block[483..495])?;
669 validate_sparse(&map, logical, physical, true)?;
670 Ok((Some(map), logical))
671 }
672
673 fn parse_pax_sparse(
674 &self,
675 pax: &[(String, Vec<u8>)],
676 physical: u64,
677 ) -> Result<(Option<Vec<SparseSegment>>, u64)> {
678 let has_sparse = pax.iter().any(|(key, _)| key.starts_with("GNU.sparse."));
679 if !has_sparse {
680 return Ok((None, physical));
681 }
682 let major = pax_text_checked(pax, "GNU.sparse.major")?;
683 let minor = pax_text_checked(pax, "GNU.sparse.minor")?;
684 if major == Some("1") && minor == Some("0") {
685 if pax.iter().any(|(key, _)| {
686 matches!(
687 key.as_str(),
688 "GNU.sparse.map"
689 | "GNU.sparse.numblocks"
690 | "GNU.sparse.offset"
691 | "GNU.sparse.numbytes"
692 | "GNU.sparse.size"
693 )
694 }) {
695 return Err(invalid(
696 "GNU sparse 1.0 metadata mixes sparse representations",
697 ));
698 }
699 let logical = pax_u64_checked(pax, "GNU.sparse.realsize")?
700 .ok_or_else(|| invalid("PAX sparse 1.0 lacks GNU.sparse.realsize"))?;
701 let (map, map_bytes) = self.read_sparse_1_0_map()?;
702 let packed = physical
703 .checked_sub(map_bytes)
704 .ok_or_else(|| invalid("sparse map exceeds entry size"))?;
705 validate_sparse(&map, logical, packed, true)?;
706 return Ok((Some(map), logical));
707 }
708 let is_version_zero = major == Some("0") && matches!(minor, Some("0" | "1"));
709 if (major.is_some() || minor.is_some()) && !is_version_zero {
710 return Err(invalid(
711 "unsupported or contradictory GNU sparse PAX version",
712 ));
713 }
714 let size = pax_u64_checked(pax, "GNU.sparse.size")?;
715 let realsize = pax_u64_checked(pax, "GNU.sparse.realsize")?;
716 if size
717 .zip(realsize)
718 .is_some_and(|(size, realsize)| size != realsize)
719 {
720 return Err(invalid(
721 "GNU sparse PAX metadata has conflicting logical sizes",
722 ));
723 }
724 let logical = size
725 .or(realsize)
726 .ok_or_else(|| invalid("GNU sparse PAX metadata lacks logical size"))?;
727 let map = if let Some(value) = pax_text_checked(pax, "GNU.sparse.map")? {
728 if pax
729 .iter()
730 .any(|(key, _)| matches!(key.as_str(), "GNU.sparse.offset" | "GNU.sparse.numbytes"))
731 {
732 return Err(invalid("GNU sparse PAX metadata mixes maps and pairs"));
733 }
734 let map = parse_sparse_csv(value)?;
735 if pax_u64_checked(pax, "GNU.sparse.numblocks")?
736 .is_some_and(|count| usize::try_from(count).ok() != Some(map.len()))
737 {
738 return Err(invalid("GNU sparse PAX map count does not match"));
739 }
740 map
741 } else if let Some(count) = pax_u64_checked(pax, "GNU.sparse.numblocks")? {
742 parse_sparse_pairs(pax, count)?
743 } else {
744 return Err(invalid("orphaned GNU sparse PAX metadata"));
745 };
746 validate_sparse(&map, logical, physical, true)?;
747 Ok((Some(map), logical))
748 }
749
750 fn read_sparse_1_0_map(&self) -> Result<(Vec<SparseSegment>, u64)> {
751 let mut consumed = 0_u64;
752 let count = self.read_sparse_line(&mut consumed)?;
753 let count =
754 usize::try_from(count).map_err(|_| invalid("sparse segment count is too large"))?;
755 if count > MAX_SPARSE_SEGMENTS {
756 return Err(invalid("sparse map has too many segments"));
757 }
758 let mut map = Vec::new();
759 for _ in 0..count {
760 let offset = self.read_sparse_line(&mut consumed)?;
761 let len = self.read_sparse_line(&mut consumed)?;
762 map.push(SparseSegment { offset, len });
763 }
764 let map_bytes = consumed
765 .checked_add(padding(consumed))
766 .ok_or_else(|| invalid("sparse map overflow"))?;
767 let extra = map_bytes - consumed;
768 let mut state = self.state.borrow_mut();
769 if extra > state.pending {
770 return Err(invalid("sparse map exceeds entry size"));
771 }
772 state.skip(extra)?;
773 state.pending -= extra;
774 Ok((map, map_bytes))
775 }
776
777 fn read_sparse_line(&self, consumed: &mut u64) -> Result<u64> {
778 let mut digits = Vec::with_capacity(20);
779 loop {
780 if *consumed >= MAX_METADATA_SIZE {
781 return Err(invalid("GNU sparse 1.0 metadata exceeds 1 MiB limit"));
782 }
783 let mut byte = [0_u8; 1];
784 let mut state = self.state.borrow_mut();
785 if state.pending == 0 {
786 return Err(invalid("truncated GNU sparse 1.0 map"));
787 }
788 state.read_exact_counted(&mut byte)?;
789 state.pending -= 1;
790 drop(state);
791 *consumed += 1;
792 if byte[0] == b'\n' {
793 break;
794 }
795 if !byte[0].is_ascii_digit() || digits.len() == 20 {
796 return Err(invalid("invalid decimal in GNU sparse 1.0 map"));
797 }
798 digits.push(byte[0]);
799 }
800 if digits.is_empty() {
801 return Err(invalid("empty decimal in GNU sparse 1.0 map"));
802 }
803 parse_decimal(&digits)
804 }
805}
806
807pub struct Entry<R: Read> {
809 state: Rc<RefCell<ReaderState<R>>>,
810 header: Header,
811 kind: EntryType,
812 pax_global: Option<Rc<PaxLayer>>,
813 pax_local: PaxRecords,
814 pax_local_keys: BTreeSet<String>,
815 sparse: Option<Vec<SparseSegment>>,
816 logical_size: u64,
817 logical_pos: u64,
818 sparse_index: usize,
819 generation: u64,
820 extraction_started: bool,
821}
822
823impl<R: Read> Entry<R> {
824 #[must_use]
826 pub const fn header(&self) -> &Header {
827 &self.header
828 }
829 pub fn path(&self) -> Result<Cow<'_, Path>> {
835 Ok(bytes_to_path(&self.header.path))
836 }
837 #[must_use]
839 pub const fn entry_type(&self) -> EntryType {
840 self.kind
841 }
842 #[must_use]
844 pub const fn size(&self) -> u64 {
845 self.logical_size
846 }
847 #[must_use]
849 pub fn sparse_map(&self) -> Option<&[SparseSegment]> {
850 self.sparse.as_deref()
851 }
852 pub fn pax_extensions(&self) -> impl Iterator<Item = (&str, &[u8])> {
854 let mut layers = Vec::new();
855 let mut layer = self.pax_global.as_deref();
856 while let Some(current) = layer {
857 layers.push(current);
858 layer = current.parent.as_deref();
859 }
860 let mut global = BTreeMap::new();
861 for layer in layers.into_iter().rev() {
862 for (key, value) in &layer.records {
863 if value.is_empty() {
864 global.remove(key.as_str());
865 } else {
866 global.insert(key.as_str(), value.as_slice());
867 }
868 }
869 }
870 global.retain(|key, _| !self.pax_local_keys.contains(*key));
871 global.into_iter().chain(
872 self.pax_local
873 .iter()
874 .map(|(key, value)| (key.as_str(), value.as_slice())),
875 )
876 }
877 #[must_use]
879 pub fn bytes_read(&self) -> u64 {
880 self.state.borrow().raw_bytes
881 }
882
883 fn read_physical(&mut self, buf: &mut [u8]) -> Result<usize> {
884 self.state
885 .borrow_mut()
886 .read_entry_data(self.generation, buf)
887 }
888
889 fn copy_sparse_to(
890 &mut self,
891 file: &mut File,
892 progress: &mut ProgressReporter<'_>,
893 ) -> Result<()> {
894 file.set_len(self.logical_size)?;
895 let map = self.sparse.clone().unwrap_or_default();
896 let mut buf = vec![0_u8; 64 * 1024];
897 for segment in map {
898 file.seek(SeekFrom::Start(segment.offset))?;
899 let mut remaining = segment.len;
900 while remaining != 0 {
901 let want = usize::try_from(remaining.min(buf.len() as u64)).unwrap_or(buf.len());
902 let n = self.read_physical(&mut buf[..want])?;
903 if n == 0 {
904 return Err(error(ErrorKind::UnexpectedEof, "truncated sparse data"));
905 }
906 file.write_all(&buf[..n])?;
907 remaining -= n as u64;
908 progress.update(self.bytes_read())?;
909 }
910 }
911 Ok(())
912 }
913}
914
915impl<R: Read> Read for Entry<R> {
916 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
917 if buf.is_empty() || self.logical_pos >= self.logical_size {
918 return Ok(0);
919 }
920 if self.generation != self.state.borrow().generation {
921 return Err(error(
922 ErrorKind::InvalidInput,
923 "entry is stale because iteration advanced",
924 ));
925 }
926 let remaining = self.logical_size - self.logical_pos;
927 let limit = usize::try_from(remaining.min(buf.len() as u64)).unwrap_or(buf.len());
928 let Some(map) = self.sparse.as_ref() else {
929 let n = self.read_physical(&mut buf[..limit])?;
930 self.logical_pos += n as u64;
931 return Ok(n);
932 };
933 while self.sparse_index < map.len()
934 && self.logical_pos
935 >= map[self.sparse_index]
936 .offset
937 .saturating_add(map[self.sparse_index].len)
938 {
939 self.sparse_index += 1;
940 }
941 if self.sparse_index == map.len() || self.logical_pos < map[self.sparse_index].offset {
942 let hole_end = map
943 .get(self.sparse_index)
944 .map_or(self.logical_size, |segment| segment.offset);
945 let n =
946 usize::try_from((hole_end - self.logical_pos).min(limit as u64)).unwrap_or(limit);
947 buf[..n].fill(0);
948 self.logical_pos += n as u64;
949 return Ok(n);
950 }
951 let segment_end = map[self.sparse_index].offset + map[self.sparse_index].len;
952 let nmax =
953 usize::try_from((segment_end - self.logical_pos).min(limit as u64)).unwrap_or(limit);
954 let n = self.read_physical(&mut buf[..nmax])?;
955 self.logical_pos += n as u64;
956 Ok(n)
957 }
958}
959
960fn padding(size: u64) -> u64 {
961 (BLOCK - size % BLOCK) % BLOCK
962}
963fn invalid(message: &'static str) -> io::Error {
964 error(ErrorKind::InvalidData, message)
965}
966fn error(kind: ErrorKind, message: &'static str) -> io::Error {
967 io::Error::new(kind, message)
968}