use super::super::Read;
use super::entry::CpioEntryHeader;
use super::header::{CpioMagic, HEADER_SIZE, RawNewcHeader, TRAILER_NAME};
use crate::error::{Error, Result};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
fn align4_padding(offset: u64) -> u64 {
(4 - (offset % 4)) % 4
}
const PATH_MAX: usize = 4096;
fn validate_header(magic: CpioMagic, header: &CpioEntryHeader) -> Result<()> {
if magic == CpioMagic::Newc && header.check != 0 {
return Err(Error::InvalidHeader {
reason: "070701 c_check must be zero",
});
}
Ok(())
}
fn filename_len(bytes: &[u8]) -> Result<usize> {
let end = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
if bytes[end..].iter().any(|byte| *byte != 0) {
return Err(Error::InvalidFilename);
}
Ok(end)
}
#[derive(Debug)]
pub struct CpioEntry<'a> {
header: CpioEntryHeader,
magic: CpioMagic,
name: &'a [u8],
entry_offset: u64,
}
impl<'a> CpioEntry<'a> {
pub fn header(&self) -> &CpioEntryHeader {
&self.header
}
pub fn magic(&self) -> CpioMagic {
self.magic
}
pub fn name(&self) -> &[u8] {
self.name
}
pub fn name_str(&self) -> core::result::Result<&str, core::str::Utf8Error> {
core::str::from_utf8(self.name)
}
pub fn file_type(&self) -> crate::mode::FileType {
self.header.file_type()
}
pub fn entry_offset(&self) -> u64 {
self.entry_offset
}
pub fn file_size(&self) -> u32 {
self.header.filesize
}
}
#[cfg(feature = "alloc")]
#[derive(Debug)]
pub struct CpioEntryOwned {
header: CpioEntryHeader,
magic: CpioMagic,
name: Vec<u8>,
entry_offset: u64,
}
#[cfg(feature = "alloc")]
impl CpioEntryOwned {
pub fn header(&self) -> &CpioEntryHeader {
&self.header
}
pub fn magic(&self) -> CpioMagic {
self.magic
}
pub fn name(&self) -> &[u8] {
&self.name
}
pub fn name_str(&self) -> core::result::Result<&str, core::str::Utf8Error> {
core::str::from_utf8(&self.name)
}
pub fn file_type(&self) -> crate::mode::FileType {
self.header.file_type()
}
pub fn entry_offset(&self) -> u64 {
self.entry_offset
}
pub fn file_size(&self) -> u32 {
self.header.filesize
}
}
pub struct CpioArchiveReader<R> {
reader: R,
offset: u64,
finished: bool,
}
io_transform! {
impl<R: Read> CpioArchiveReader<R> {
pub fn new(reader: R) -> Self {
Self {
reader,
offset: 0,
finished: false,
}
}
pub fn offset(&self) -> u64 {
self.offset
}
pub async fn next_entry_with_buf<'buf>(
&mut self,
name_buf: &'buf mut [u8],
) -> Result<Option<CpioEntry<'buf>>> {
if self.finished {
return Ok(None);
}
let entry_offset = self.offset;
let Some(raw) = RawNewcHeader::parse_optional(&mut self.reader).await? else {
self.finished = true;
return Ok(None);
};
self.offset += HEADER_SIZE as u64;
let magic = raw.magic().ok_or_else(|| {
let mut found = [0u8; 6];
found.copy_from_slice(raw.magic_bytes());
Error::InvalidMagic { found }
})?;
let header = CpioEntryHeader::from_raw(&raw)?;
validate_header(magic, &header)?;
let namesize = raw.namesize()? as usize;
if namesize == 0 || namesize > PATH_MAX {
return Err(Error::InvalidFilename);
}
let name_with_nul_len = namesize;
if name_with_nul_len > name_buf.len() + 1 {
return Err(Error::InvalidFilename);
}
let name_len = namesize - 1; if name_len > name_buf.len() {
return Err(Error::InvalidFilename);
}
self.reader.read_exact(&mut name_buf[..name_len]).await?;
self.offset += name_len as u64;
let mut nul = [0u8; 1];
self.reader.read_exact(&mut nul).await?;
self.offset += 1;
if nul[0] != 0 {
return Err(Error::InvalidFilename);
}
let name_len = filename_len(&name_buf[..name_len])?;
let header_plus_name = HEADER_SIZE as u64 + namesize as u64;
let pad = align4_padding(header_plus_name);
if pad > 0 {
self.skip_zero_padding(pad).await?;
}
if &name_buf[..name_len] == TRAILER_NAME {
if header.filesize != 0 {
return Err(Error::InvalidHeader {
reason: "TRAILER!!! c_filesize must be zero",
});
}
self.finished = true;
return Ok(None);
}
Ok(Some(CpioEntry {
header,
magic,
name: &name_buf[..name_len],
entry_offset,
}))
}
pub async fn read_entry_data(&mut self, entry: &CpioEntry<'_>, buf: &mut [u8]) -> Result<()> {
let size = entry.file_size() as usize;
if buf.len() != size {
return Err(Error::BufferSizeMismatch {
expected: size,
actual: buf.len(),
});
}
if size > 0 {
self.reader.read_exact(&mut buf[..size]).await?;
self.offset += size as u64;
}
self.verify_checksum(entry.magic, entry.header.check, buf)?;
let pad = align4_padding(entry.file_size() as u64);
if pad > 0 {
self.skip_zero_padding(pad).await?;
}
Ok(())
}
pub async fn skip_entry_data(&mut self, entry: &CpioEntry<'_>) -> Result<()> {
self.skip_data_and_verify(entry.magic, entry.header.check, entry.file_size())
.await?;
Ok(())
}
#[cfg(feature = "alloc")]
pub async fn skip_entry_data_owned(&mut self, entry: &CpioEntryOwned) -> Result<()> {
self.skip_data_and_verify(entry.magic, entry.header.check, entry.file_size())
.await?;
Ok(())
}
async fn skip_zero_padding(&mut self, mut n: u64) -> Result<()> {
let mut discard = [0u8; 256];
while n > 0 {
let chunk = n.min(discard.len() as u64) as usize;
self.reader.read_exact(&mut discard[..chunk]).await?;
self.offset += chunk as u64;
if discard[..chunk].iter().any(|byte| *byte != 0) {
return Err(Error::InvalidHeader {
reason: "alignment padding must be zero",
});
}
n -= chunk as u64;
}
Ok(())
}
fn verify_checksum(&self, magic: CpioMagic, expected: u32, data: &[u8]) -> Result<()> {
if magic != CpioMagic::NewcCrc {
return Ok(());
}
let computed = data
.iter()
.fold(0_u32, |sum, byte| sum.wrapping_add(*byte as u32));
if computed != expected {
return Err(Error::ChecksumMismatch { expected, computed });
}
Ok(())
}
async fn skip_data_and_verify(
&mut self,
magic: CpioMagic,
expected: u32,
size: u32,
) -> Result<()> {
let mut remaining = size as u64;
let mut computed = 0_u32;
let mut discard = [0_u8; 256];
while remaining > 0 {
let chunk = remaining.min(discard.len() as u64) as usize;
self.reader.read_exact(&mut discard[..chunk]).await?;
self.offset += chunk as u64;
if magic == CpioMagic::NewcCrc {
computed = discard[..chunk]
.iter()
.fold(computed, |sum, byte| sum.wrapping_add(*byte as u32));
}
remaining -= chunk as u64;
}
if magic == CpioMagic::NewcCrc && computed != expected {
return Err(Error::ChecksumMismatch { expected, computed });
}
let pad = align4_padding(size as u64);
if pad > 0 {
self.skip_zero_padding(pad).await?;
}
Ok(())
}
#[cfg(feature = "alloc")]
async fn read_exact_alloc(&mut self, len: usize) -> Result<Vec<u8>> {
const CHUNK: usize = 64 * 1024;
let mut buf = Vec::new();
let mut filled = 0;
while filled < len {
let want = (len - filled).min(CHUNK);
buf.resize(filled + want, 0);
self.reader.read_exact(&mut buf[filled..]).await?;
self.offset += want as u64;
filled += want;
}
Ok(buf)
}
#[cfg(feature = "alloc")]
pub async fn next_entry_alloc(&mut self) -> Result<Option<CpioEntryOwned>> {
if self.finished {
return Ok(None);
}
let entry_offset = self.offset;
let Some(raw) = RawNewcHeader::parse_optional(&mut self.reader).await? else {
self.finished = true;
return Ok(None);
};
self.offset += HEADER_SIZE as u64;
let magic = raw.magic().ok_or_else(|| {
let mut found = [0u8; 6];
found.copy_from_slice(raw.magic_bytes());
Error::InvalidMagic { found }
})?;
let header = CpioEntryHeader::from_raw(&raw)?;
validate_header(magic, &header)?;
let namesize = raw.namesize()? as usize;
if namesize == 0 || namesize > PATH_MAX {
return Err(Error::InvalidFilename);
}
let name_len = namesize - 1;
let name = self.read_exact_alloc(name_len).await?;
let mut nul = [0u8; 1];
self.reader.read_exact(&mut nul).await?;
self.offset += 1;
if nul[0] != 0 {
return Err(Error::InvalidFilename);
}
let name_len = filename_len(&name)?;
let mut name = name;
name.truncate(name_len);
let header_plus_name = HEADER_SIZE as u64 + namesize as u64;
let pad = align4_padding(header_plus_name);
if pad > 0 {
self.skip_zero_padding(pad).await?;
}
if name.as_slice() == TRAILER_NAME {
if header.filesize != 0 {
return Err(Error::InvalidHeader {
reason: "TRAILER!!! c_filesize must be zero",
});
}
self.finished = true;
return Ok(None);
}
Ok(Some(CpioEntryOwned {
header,
magic,
name,
entry_offset,
}))
}
#[cfg(feature = "alloc")]
pub async fn read_entry_data_alloc(&mut self, entry: &CpioEntryOwned) -> Result<Vec<u8>> {
let size = entry.file_size() as usize;
let buf = self.read_exact_alloc(size).await?;
self.verify_checksum(entry.magic, entry.header.check, &buf)?;
let pad = align4_padding(entry.file_size() as u64);
if pad > 0 {
self.skip_zero_padding(pad).await?;
}
Ok(buf)
}
}
impl<R: Read + super::super::Seek> CpioArchiveReader<R> {
pub async fn seek_to_entry(&mut self, offset: u64) -> Result<()> {
use super::super::SeekFrom;
self.reader.seek(SeekFrom::Start(offset)).await?;
self.offset = offset;
self.finished = false;
Ok(())
}
}
}