use super::super::Read;
use super::entry::CpioEntryHeader;
use super::header::{CpioMagic, HEADER_SIZE, RawNewcHeader, TRAILER_NAME};
use crate::error::{CpioError, Result};
#[cfg(feature = "alloc")]
use alloc::vec;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
fn align4_padding(offset: u64) -> u64 {
(4 - (offset % 4)) % 4
}
#[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 CpioReader<R> {
reader: R,
offset: u64,
finished: bool,
}
io_transform! {
impl<R: Read> CpioReader<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 raw = RawNewcHeader::parse(&mut self.reader).await?;
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());
CpioError::InvalidMagic { found }
})?;
let header = CpioEntryHeader::from_raw(&raw)?;
let namesize = raw.namesize()? as usize;
if namesize == 0 {
return Err(CpioError::InvalidFilename);
}
let name_with_nul_len = namesize;
if name_with_nul_len > name_buf.len() + 1 {
return Err(CpioError::InvalidFilename);
}
let name_len = namesize - 1; if name_len > name_buf.len() {
return Err(CpioError::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;
let header_plus_name = HEADER_SIZE as u64 + namesize as u64;
let pad = align4_padding(header_plus_name);
if pad > 0 {
self.skip_bytes(pad).await?;
}
if &name_buf[..name_len] == TRAILER_NAME {
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;
assert_eq!(buf.len(), size, "buffer size must match entry file size");
if size > 0 {
self.reader.read_exact(&mut buf[..size]).await?;
self.offset += size as u64;
}
let pad = align4_padding(entry.file_size() as u64);
if pad > 0 {
self.skip_bytes(pad).await?;
}
Ok(())
}
pub async fn skip_entry_data(&mut self, entry: &CpioEntry<'_>) -> Result<()> {
let size = entry.file_size() as u64;
let pad = align4_padding(size);
self.skip_bytes(size + pad).await?;
Ok(())
}
#[cfg(feature = "alloc")]
pub async fn skip_entry_data_owned(&mut self, entry: &CpioEntryOwned) -> Result<()> {
let size = entry.file_size() as u64;
let pad = align4_padding(size);
self.skip_bytes(size + pad).await?;
Ok(())
}
async fn skip_bytes(&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;
n -= chunk as u64;
}
Ok(())
}
#[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 raw = RawNewcHeader::parse(&mut self.reader).await?;
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());
CpioError::InvalidMagic { found }
})?;
let header = CpioEntryHeader::from_raw(&raw)?;
let namesize = raw.namesize()? as usize;
if namesize == 0 {
return Err(CpioError::InvalidFilename);
}
let name_len = namesize - 1;
let mut name = vec![0u8; name_len];
self.reader.read_exact(&mut name).await?;
self.offset += name_len as u64;
let mut nul = [0u8; 1];
self.reader.read_exact(&mut nul).await?;
self.offset += 1;
let header_plus_name = HEADER_SIZE as u64 + namesize as u64;
let pad = align4_padding(header_plus_name);
if pad > 0 {
self.skip_bytes(pad).await?;
}
if name.as_slice() == TRAILER_NAME {
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 mut buf = vec![0u8; size];
if size > 0 {
self.reader.read_exact(&mut buf).await?;
self.offset += size as u64;
}
let pad = align4_padding(entry.file_size() as u64);
if pad > 0 {
self.skip_bytes(pad).await?;
}
Ok(buf)
}
}
impl<R: Read + super::super::Seek> CpioReader<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(())
}
}
}