mod builder;
mod format;
mod unpack;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet};
use std::fs::File;
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;
use std::path::Path;
use std::rc::Rc;
pub use builder::Builder;
use format::{
apply_pax_header, bytes_to_path, parse_decimal, parse_header, parse_number, parse_pax,
parse_sparse_csv, parse_sparse_pairs, pax_text_checked, pax_u64_checked, pax_value,
push_sparse_pair, trim_metadata, validate_sparse, verify_checksum,
};
pub use unpack::{
EntryCallback, EntryInfo, EntryUnpacker, Progress, ProgressCallback, SkipReason, SkippedEntry,
UnpackOptions, UnpackSummary,
};
use unpack::{ProgressReporter, unpack_archive};
const BLOCK: u64 = 512;
const MAX_METADATA_SIZE: u64 = 1024 * 1024;
const MAX_SPARSE_SEGMENTS: usize = 1_000_000;
const PAX_HEADER_KEYS: [&str; 7] = ["path", "linkpath", "size", "mode", "uid", "gid", "mtime"];
type PaxRecords = Vec<(String, Vec<u8>)>;
struct PaxLayer {
parent: Option<Rc<Self>>,
records: BTreeMap<String, Vec<u8>>,
}
impl Drop for PaxLayer {
fn drop(&mut self) {
let mut parent = self.parent.take();
while let Some(mut layer) = parent {
parent = Rc::get_mut(&mut layer).and_then(|layer| layer.parent.take());
}
}
}
pub type Result<T> = io::Result<T>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SparseSegment {
pub offset: u64,
pub len: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum EntryType {
File,
Directory,
Symlink,
Hardlink,
CharDevice,
BlockDevice,
Fifo,
Other(u8),
}
impl EntryType {
fn from_flag(flag: u8) -> Self {
match flag {
0 | b'0' | b'7' | b'S' => Self::File,
b'5' => Self::Directory,
b'2' => Self::Symlink,
b'1' => Self::Hardlink,
b'3' => Self::CharDevice,
b'4' => Self::BlockDevice,
b'6' => Self::Fifo,
other => Self::Other(other),
}
}
const fn type_flag(self) -> u8 {
match self {
Self::File => b'0',
Self::Directory => b'5',
Self::Symlink => b'2',
Self::Hardlink => b'1',
Self::CharDevice => b'3',
Self::BlockDevice => b'4',
Self::Fifo => b'6',
Self::Other(flag) => flag,
}
}
}
#[derive(Clone, Debug)]
pub struct Header {
path: Vec<u8>,
link_name: Option<Vec<u8>>,
mode: u32,
uid: u64,
gid: u64,
stored_size: u64,
mtime: i64,
type_flag: u8,
}
impl Header {
#[must_use]
pub fn new_gnu(entry_type: EntryType) -> Self {
Self {
path: Vec::new(),
link_name: None,
mode: 0,
uid: 0,
gid: 0,
stored_size: 0,
mtime: 0,
type_flag: entry_type.type_flag(),
}
}
#[must_use]
pub const fn mode(&self) -> u32 {
self.mode
}
#[must_use]
pub const fn uid(&self) -> u64 {
self.uid
}
#[must_use]
pub const fn gid(&self) -> u64 {
self.gid
}
#[must_use]
pub const fn stored_size(&self) -> u64 {
self.stored_size
}
#[must_use]
pub const fn mtime(&self) -> i64 {
self.mtime
}
#[must_use]
pub const fn type_flag(&self) -> u8 {
self.type_flag
}
pub const fn set_mode(&mut self, mode: u32) {
self.mode = mode;
}
pub const fn set_uid(&mut self, uid: u64) {
self.uid = uid;
}
pub const fn set_gid(&mut self, gid: u64) {
self.gid = gid;
}
pub const fn set_size(&mut self, size: u64) {
self.stored_size = size;
}
pub const fn set_mtime(&mut self, mtime: i64) {
self.mtime = mtime;
}
pub fn link_name(&self) -> Option<Cow<'_, Path>> {
self.link_name.as_deref().map(bytes_to_path)
}
}
struct ReaderState<R> {
reader: R,
raw_bytes: u64,
pending: u64,
padding: u64,
generation: u64,
}
impl<R: Read> ReaderState<R> {
fn read_counted(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.reader.read(buf)?;
self.raw_bytes = self.raw_bytes.saturating_add(n as u64);
Ok(n)
}
fn read_exact_counted(&mut self, mut buf: &mut [u8]) -> io::Result<()> {
while !buf.is_empty() {
let n = self.read_counted(buf)?;
if n == 0 {
return Err(error(ErrorKind::UnexpectedEof, "truncated tar archive"));
}
buf = &mut buf[n..];
}
Ok(())
}
fn skip(&mut self, mut amount: u64) -> io::Result<()> {
let mut buf = [0_u8; 8192];
while amount != 0 {
let want = usize::try_from(amount.min(buf.len() as u64)).unwrap_or(buf.len());
self.read_exact_counted(&mut buf[..want])?;
amount -= want as u64;
}
Ok(())
}
fn finish_entry(&mut self) -> io::Result<()> {
self.skip(self.pending)?;
self.pending = 0;
self.skip(self.padding)?;
self.padding = 0;
Ok(())
}
fn read_entry_data(&mut self, generation: u64, buf: &mut [u8]) -> io::Result<usize> {
if generation != self.generation {
return Err(error(
ErrorKind::InvalidInput,
"entry is stale because iteration advanced",
));
}
let want = usize::try_from(self.pending.min(buf.len() as u64)).unwrap_or(buf.len());
if want == 0 {
return Ok(0);
}
let n = self.read_counted(&mut buf[..want])?;
if n == 0 {
return Err(error(ErrorKind::UnexpectedEof, "truncated entry data"));
}
self.pending -= n as u64;
Ok(n)
}
}
pub struct Archive<R: Read> {
state: Rc<RefCell<ReaderState<R>>>,
}
impl<R: Read> Archive<R> {
pub fn new(reader: R) -> Self {
Self {
state: Rc::new(RefCell::new(ReaderState {
reader,
raw_bytes: 0,
pending: 0,
padding: 0,
generation: 0,
})),
}
}
pub fn entries(&mut self) -> Result<Entries<'_, R>> {
if self.state.borrow().raw_bytes != 0 {
return Err(error(
ErrorKind::InvalidInput,
"cannot restart entry iteration after consuming archive bytes",
));
}
Ok(Entries {
state: Rc::clone(&self.state),
done: false,
zero_blocks: 0,
global_pax: BTreeMap::new(),
pending_global_pax_bytes: 0,
global_pax_snapshot: None,
local_pax: None,
long_name: None,
long_link: None,
marker: PhantomData,
})
}
pub fn unpack<P: AsRef<Path>>(
mut self,
dest: P,
opts: &mut UnpackOptions,
) -> Result<UnpackSummary> {
unpack_archive(&mut self, dest.as_ref(), opts)
}
}
pub struct Entries<'a, R: Read> {
state: Rc<RefCell<ReaderState<R>>>,
done: bool,
zero_blocks: u8,
global_pax: BTreeMap<String, Vec<u8>>,
pending_global_pax_bytes: u64,
global_pax_snapshot: Option<Rc<PaxLayer>>,
local_pax: Option<Vec<(String, Vec<u8>)>>,
long_name: Option<Vec<u8>>,
long_link: Option<Vec<u8>>,
marker: PhantomData<&'a mut Archive<R>>,
}
impl<R: Read> Iterator for Entries<'_, R> {
type Item = Result<Entry<R>>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
match self.next_entry() {
Ok(Some(entry)) => Some(Ok(entry)),
Ok(None) => {
self.done = true;
None
}
Err(err) => {
self.done = true;
Some(Err(err))
}
}
}
}
impl<R: Read> Entries<'_, R> {
#[allow(clippy::too_many_lines)]
fn next_entry(&mut self) -> Result<Option<Entry<R>>> {
loop {
let mut block = [0_u8; 512];
{
let mut state = self.state.borrow_mut();
state.finish_entry()?;
state.generation = state.generation.wrapping_add(1);
let mut first = [0_u8; 1];
let n = state.read_counted(&mut first)?;
if n == 0 {
return self.finish_stream();
}
block[0] = first[0];
state.read_exact_counted(&mut block[1..])?;
}
if block.iter().all(|byte| *byte == 0) {
self.zero_blocks += 1;
if self.zero_blocks == 2 {
return self.finish_stream();
}
continue;
}
if self.zero_blocks != 0 {
return Err(invalid(
"tar zero block was not followed by a second zero block",
));
}
self.zero_blocks = 0;
verify_checksum(&block)?;
let mut header = parse_header(&block)?;
let flag = header.type_flag;
let size = header.stored_size;
let identity = &block[257..265];
let is_ustar = identity == b"ustar\x0000";
let is_gnu = identity == b"ustar \0";
if matches!(flag, b'x' | b'g') && !is_ustar {
return Err(invalid("PAX extension requires a USTAR carrier"));
}
if matches!(flag, b'L' | b'K') && !(is_ustar || is_gnu) {
return Err(invalid(
"GNU long-name/link extension requires a USTAR or GNU carrier",
));
}
if matches!(flag, b'x' | b'g' | b'L' | b'K') {
if size > MAX_METADATA_SIZE {
return Err(error(
ErrorKind::InvalidData,
"tar metadata exceeds 1 MiB limit",
));
}
if flag == b'g' {
let total = self
.pending_global_pax_bytes
.checked_add(size)
.ok_or_else(|| invalid("global PAX metadata size overflow"))?;
if total > MAX_METADATA_SIZE {
return Err(invalid("pending global PAX metadata exceeds 1 MiB limit"));
}
self.pending_global_pax_bytes = total;
}
let payload = self.read_metadata(size)?;
match flag {
b'x' => {
if self.local_pax.is_some() {
return Err(invalid("two local PAX headers describe one entry"));
}
self.local_pax = Some(parse_pax(&payload)?);
}
b'g' => {
let records = parse_pax(&payload)?;
if records
.iter()
.any(|(key, _)| key.starts_with("GNU.sparse."))
{
return Err(invalid("GNU sparse PAX metadata is not valid globally"));
}
for (key, value) in &records {
self.global_pax.insert(key.clone(), value.clone());
}
if let Some(layer) = self.global_pax_snapshot.as_mut().and_then(Rc::get_mut)
{
layer.records.extend(records);
} else {
self.global_pax_snapshot = Some(Rc::new(PaxLayer {
parent: self.global_pax_snapshot.take(),
records: records.into_iter().collect(),
}));
}
}
b'L' => {
if self.long_name.is_some() {
return Err(invalid("two GNU long-name headers describe one entry"));
}
self.long_name = Some(trim_metadata(payload));
}
b'K' => {
if self.long_link.is_some() {
return Err(invalid("two GNU long-link headers describe one entry"));
}
self.long_link = Some(trim_metadata(payload));
}
_ => unreachable!(),
}
continue;
}
let pax_global = self.global_pax_snapshot.clone();
let pax_local = self.local_pax.take().unwrap_or_default();
let pax_local_keys = pax_local
.iter()
.map(|(key, _)| key.clone())
.collect::<BTreeSet<_>>();
let mut pax = Vec::new();
for key in PAX_HEADER_KEYS {
if let Some(value) = self
.global_pax
.get(key)
.filter(|_| !pax_local_keys.contains(key))
{
pax.push((key.to_owned(), value.clone()));
}
}
for (key, value) in self.global_pax.range("GNU.sparse.".to_owned()..) {
if !key.starts_with("GNU.sparse.") {
break;
}
if !pax_local_keys.contains(key) {
pax.push((key.clone(), value.clone()));
}
}
pax.extend(
pax_local
.iter()
.filter(|(key, _)| {
PAX_HEADER_KEYS.contains(&key.as_str()) || key.starts_with("GNU.sparse.")
})
.cloned(),
);
if self.long_name.is_some()
&& pax_value(&pax, "path").is_some_and(|path| !path.is_empty())
{
return Err(invalid(
"PAX path and GNU long-name describe the same entry",
));
}
if self.long_link.is_some()
&& pax_value(&pax, "linkpath").is_some_and(|link| !link.is_empty())
{
return Err(invalid(
"PAX linkpath and GNU long-link describe the same entry",
));
}
apply_pax_header(&mut header, &pax)?;
if let Some(name) = self.long_name.take() {
header.path = name;
}
if let Some(link) = self.long_link.take() {
header.link_name = Some(link);
}
let pax_size = header.stored_size;
let has_pax_sparse = pax.iter().any(|(key, _)| key.starts_with("GNU.sparse."));
if has_pax_sparse && !matches!(flag, 0 | b'0' | b'7') {
return Err(invalid("GNU sparse PAX metadata requires a regular file"));
}
let physical_size = if has_pax_sparse { size } else { pax_size };
if matches!(flag, b'1'..=b'6') && (size != 0 || physical_size != 0) {
return Err(invalid("nonregular tar entry cannot carry payload"));
}
header.stored_size = physical_size;
let generation;
{
let mut state = self.state.borrow_mut();
state.pending = physical_size;
state.padding = padding(physical_size);
generation = state.generation;
}
let (sparse, logical_size) = if flag == b'S' {
self.parse_old_gnu_sparse(&block, physical_size)?
} else {
self.parse_pax_sparse(&pax, physical_size)?
};
if has_pax_sparse && pax_size != physical_size && pax_size != logical_size {
return Err(invalid(
"GNU sparse PAX size conflicts with physical and logical sizes",
));
}
if let Some(name) = pax_value(&pax, "GNU.sparse.name") {
header.path = name.to_vec();
}
if header.path.is_empty() && pax_value(&pax, "path").is_some_and(<[u8]>::is_empty) {
return Err(invalid("PAX path deletion leaves entry without a path"));
}
if matches!(flag, b'1' | b'2')
&& pax_value(&pax, "linkpath").is_some_and(<[u8]>::is_empty)
&& header.link_name.as_ref().is_none_or(Vec::is_empty)
{
return Err(invalid(
"PAX linkpath deletion leaves link without a target",
));
}
let kind = EntryType::from_flag(flag);
if header.path.is_empty() {
return Err(invalid("tar entry has an empty effective path"));
}
if header.path.contains(&0) {
return Err(invalid("tar entry path contains a NUL byte"));
}
if matches!(kind, EntryType::Symlink | EntryType::Hardlink)
&& header
.link_name
.as_ref()
.is_none_or(|target| target.is_empty() || target.contains(&0))
{
return Err(invalid("tar link entry has an empty or NUL-bearing target"));
}
self.pending_global_pax_bytes = 0;
return Ok(Some(Entry {
state: Rc::clone(&self.state),
header,
kind,
pax_global,
pax_local,
pax_local_keys,
sparse,
logical_size,
logical_pos: 0,
sparse_index: 0,
generation,
extraction_started: false,
}));
}
}
fn finish_stream(&self) -> Result<Option<Entry<R>>> {
if self.local_pax.is_some() || self.long_name.is_some() || self.long_link.is_some() {
return Err(invalid("extension entry was not followed by a member"));
}
Ok(None)
}
fn read_metadata(&self, size: u64) -> Result<Vec<u8>> {
let alloc =
usize::try_from(size).map_err(|_| invalid("metadata size cannot fit in memory"))?;
let mut payload = vec![0_u8; alloc];
let mut state = self.state.borrow_mut();
state.read_exact_counted(&mut payload)?;
state.skip(padding(size))?;
Ok(payload)
}
fn parse_old_gnu_sparse(
&self,
block: &[u8; 512],
physical: u64,
) -> Result<(Option<Vec<SparseSegment>>, u64)> {
if &block[257..265] != b"ustar \0" {
return Err(invalid("old GNU sparse entry lacks a GNU header"));
}
let mut map = Vec::new();
for chunk in block[386..482].chunks_exact(24) {
if !push_sparse_pair(&mut map, &chunk[..12], &chunk[12..])? {
break;
}
}
let mut extended = block[482] != 0;
let mut metadata_bytes = 96_u64;
while extended {
metadata_bytes = metadata_bytes
.checked_add(BLOCK)
.ok_or_else(|| invalid("old GNU sparse metadata size overflow"))?;
if metadata_bytes > MAX_METADATA_SIZE {
return Err(invalid("old GNU sparse metadata exceeds 1 MiB limit"));
}
let mut ext = [0_u8; 512];
self.state.borrow_mut().read_exact_counted(&mut ext)?;
for chunk in ext[..504].chunks_exact(24) {
if !push_sparse_pair(&mut map, &chunk[..12], &chunk[12..])? {
break;
}
}
extended = ext[504] != 0;
if map.len() > MAX_SPARSE_SEGMENTS {
return Err(invalid("sparse map has too many segments"));
}
}
let logical = parse_number(&block[483..495])?;
validate_sparse(&map, logical, physical, true)?;
Ok((Some(map), logical))
}
fn parse_pax_sparse(
&self,
pax: &[(String, Vec<u8>)],
physical: u64,
) -> Result<(Option<Vec<SparseSegment>>, u64)> {
let has_sparse = pax.iter().any(|(key, _)| key.starts_with("GNU.sparse."));
if !has_sparse {
return Ok((None, physical));
}
let major = pax_text_checked(pax, "GNU.sparse.major")?;
let minor = pax_text_checked(pax, "GNU.sparse.minor")?;
if major == Some("1") && minor == Some("0") {
if pax.iter().any(|(key, _)| {
matches!(
key.as_str(),
"GNU.sparse.map"
| "GNU.sparse.numblocks"
| "GNU.sparse.offset"
| "GNU.sparse.numbytes"
| "GNU.sparse.size"
)
}) {
return Err(invalid(
"GNU sparse 1.0 metadata mixes sparse representations",
));
}
let logical = pax_u64_checked(pax, "GNU.sparse.realsize")?
.ok_or_else(|| invalid("PAX sparse 1.0 lacks GNU.sparse.realsize"))?;
let (map, map_bytes) = self.read_sparse_1_0_map()?;
let packed = physical
.checked_sub(map_bytes)
.ok_or_else(|| invalid("sparse map exceeds entry size"))?;
validate_sparse(&map, logical, packed, true)?;
return Ok((Some(map), logical));
}
let is_version_zero = major == Some("0") && matches!(minor, Some("0" | "1"));
if (major.is_some() || minor.is_some()) && !is_version_zero {
return Err(invalid(
"unsupported or contradictory GNU sparse PAX version",
));
}
let size = pax_u64_checked(pax, "GNU.sparse.size")?;
let realsize = pax_u64_checked(pax, "GNU.sparse.realsize")?;
if size
.zip(realsize)
.is_some_and(|(size, realsize)| size != realsize)
{
return Err(invalid(
"GNU sparse PAX metadata has conflicting logical sizes",
));
}
let logical = size
.or(realsize)
.ok_or_else(|| invalid("GNU sparse PAX metadata lacks logical size"))?;
let map = if let Some(value) = pax_text_checked(pax, "GNU.sparse.map")? {
if pax
.iter()
.any(|(key, _)| matches!(key.as_str(), "GNU.sparse.offset" | "GNU.sparse.numbytes"))
{
return Err(invalid("GNU sparse PAX metadata mixes maps and pairs"));
}
let map = parse_sparse_csv(value)?;
if pax_u64_checked(pax, "GNU.sparse.numblocks")?
.is_some_and(|count| usize::try_from(count).ok() != Some(map.len()))
{
return Err(invalid("GNU sparse PAX map count does not match"));
}
map
} else if let Some(count) = pax_u64_checked(pax, "GNU.sparse.numblocks")? {
parse_sparse_pairs(pax, count)?
} else {
return Err(invalid("orphaned GNU sparse PAX metadata"));
};
validate_sparse(&map, logical, physical, true)?;
Ok((Some(map), logical))
}
fn read_sparse_1_0_map(&self) -> Result<(Vec<SparseSegment>, u64)> {
let mut consumed = 0_u64;
let count = self.read_sparse_line(&mut consumed)?;
let count =
usize::try_from(count).map_err(|_| invalid("sparse segment count is too large"))?;
if count > MAX_SPARSE_SEGMENTS {
return Err(invalid("sparse map has too many segments"));
}
let mut map = Vec::new();
for _ in 0..count {
let offset = self.read_sparse_line(&mut consumed)?;
let len = self.read_sparse_line(&mut consumed)?;
map.push(SparseSegment { offset, len });
}
let map_bytes = consumed
.checked_add(padding(consumed))
.ok_or_else(|| invalid("sparse map overflow"))?;
let extra = map_bytes - consumed;
let mut state = self.state.borrow_mut();
if extra > state.pending {
return Err(invalid("sparse map exceeds entry size"));
}
state.skip(extra)?;
state.pending -= extra;
Ok((map, map_bytes))
}
fn read_sparse_line(&self, consumed: &mut u64) -> Result<u64> {
let mut digits = Vec::with_capacity(20);
loop {
if *consumed >= MAX_METADATA_SIZE {
return Err(invalid("GNU sparse 1.0 metadata exceeds 1 MiB limit"));
}
let mut byte = [0_u8; 1];
let mut state = self.state.borrow_mut();
if state.pending == 0 {
return Err(invalid("truncated GNU sparse 1.0 map"));
}
state.read_exact_counted(&mut byte)?;
state.pending -= 1;
drop(state);
*consumed += 1;
if byte[0] == b'\n' {
break;
}
if !byte[0].is_ascii_digit() || digits.len() == 20 {
return Err(invalid("invalid decimal in GNU sparse 1.0 map"));
}
digits.push(byte[0]);
}
if digits.is_empty() {
return Err(invalid("empty decimal in GNU sparse 1.0 map"));
}
parse_decimal(&digits)
}
}
pub struct Entry<R: Read> {
state: Rc<RefCell<ReaderState<R>>>,
header: Header,
kind: EntryType,
pax_global: Option<Rc<PaxLayer>>,
pax_local: PaxRecords,
pax_local_keys: BTreeSet<String>,
sparse: Option<Vec<SparseSegment>>,
logical_size: u64,
logical_pos: u64,
sparse_index: usize,
generation: u64,
extraction_started: bool,
}
impl<R: Read> Entry<R> {
#[must_use]
pub const fn header(&self) -> &Header {
&self.header
}
pub fn path(&self) -> Result<Cow<'_, Path>> {
Ok(bytes_to_path(&self.header.path))
}
#[must_use]
pub const fn entry_type(&self) -> EntryType {
self.kind
}
#[must_use]
pub const fn size(&self) -> u64 {
self.logical_size
}
#[must_use]
pub fn sparse_map(&self) -> Option<&[SparseSegment]> {
self.sparse.as_deref()
}
pub fn pax_extensions(&self) -> impl Iterator<Item = (&str, &[u8])> {
let mut layers = Vec::new();
let mut layer = self.pax_global.as_deref();
while let Some(current) = layer {
layers.push(current);
layer = current.parent.as_deref();
}
let mut global = BTreeMap::new();
for layer in layers.into_iter().rev() {
for (key, value) in &layer.records {
if value.is_empty() {
global.remove(key.as_str());
} else {
global.insert(key.as_str(), value.as_slice());
}
}
}
global.retain(|key, _| !self.pax_local_keys.contains(*key));
global.into_iter().chain(
self.pax_local
.iter()
.map(|(key, value)| (key.as_str(), value.as_slice())),
)
}
#[must_use]
pub fn bytes_read(&self) -> u64 {
self.state.borrow().raw_bytes
}
fn read_physical(&mut self, buf: &mut [u8]) -> Result<usize> {
self.state
.borrow_mut()
.read_entry_data(self.generation, buf)
}
fn copy_sparse_to(
&mut self,
file: &mut File,
progress: &mut ProgressReporter<'_>,
) -> Result<()> {
file.set_len(self.logical_size)?;
let map = self.sparse.clone().unwrap_or_default();
let mut buf = vec![0_u8; 64 * 1024];
for segment in map {
file.seek(SeekFrom::Start(segment.offset))?;
let mut remaining = segment.len;
while remaining != 0 {
let want = usize::try_from(remaining.min(buf.len() as u64)).unwrap_or(buf.len());
let n = self.read_physical(&mut buf[..want])?;
if n == 0 {
return Err(error(ErrorKind::UnexpectedEof, "truncated sparse data"));
}
file.write_all(&buf[..n])?;
remaining -= n as u64;
progress.update(self.bytes_read())?;
}
}
Ok(())
}
}
impl<R: Read> Read for Entry<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() || self.logical_pos >= self.logical_size {
return Ok(0);
}
if self.generation != self.state.borrow().generation {
return Err(error(
ErrorKind::InvalidInput,
"entry is stale because iteration advanced",
));
}
let remaining = self.logical_size - self.logical_pos;
let limit = usize::try_from(remaining.min(buf.len() as u64)).unwrap_or(buf.len());
let Some(map) = self.sparse.as_ref() else {
let n = self.read_physical(&mut buf[..limit])?;
self.logical_pos += n as u64;
return Ok(n);
};
while self.sparse_index < map.len()
&& self.logical_pos
>= map[self.sparse_index]
.offset
.saturating_add(map[self.sparse_index].len)
{
self.sparse_index += 1;
}
if self.sparse_index == map.len() || self.logical_pos < map[self.sparse_index].offset {
let hole_end = map
.get(self.sparse_index)
.map_or(self.logical_size, |segment| segment.offset);
let n =
usize::try_from((hole_end - self.logical_pos).min(limit as u64)).unwrap_or(limit);
buf[..n].fill(0);
self.logical_pos += n as u64;
return Ok(n);
}
let segment_end = map[self.sparse_index].offset + map[self.sparse_index].len;
let nmax =
usize::try_from((segment_end - self.logical_pos).min(limit as u64)).unwrap_or(limit);
let n = self.read_physical(&mut buf[..nmax])?;
self.logical_pos += n as u64;
Ok(n)
}
}
fn padding(size: u64) -> u64 {
(BLOCK - size % BLOCK) % BLOCK
}
fn invalid(message: &'static str) -> io::Error {
error(ErrorKind::InvalidData, message)
}
fn error(kind: ErrorKind, message: &'static str) -> io::Error {
io::Error::new(kind, message)
}