use binrw::BinRead;
use constant::{
FLAG_FSID_VERSION_2, FLAG_SHIFTED_ROOT_OFFSET, MAGIC, PAD_SIZE, PAGE_SIZE, ROMBUFFERMASK,
S_ISDIR, SUPPORTED_FLAGS,
};
use constant::{ROMBUFFER_BITS, ROMBUFFERSIZE};
use error::Result;
use log::warn;
use sblk::SuperBlock;
use std::cmp::Ordering;
use std::ops::Deref;
use std::{
cell::RefCell,
fs::File,
io::{Read, Seek, SeekFrom},
path::Path,
rc::Rc,
};
pub use walk::DirEntry;
pub use walk::ReadDir;
mod constant;
mod error;
mod sblk;
mod walk;
pub struct Cramfs<R: Read + Seek>(Rc<CramfsInner<R>>);
impl<R: Read + Seek> Clone for Cramfs<R> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<R: Read + Seek> Deref for Cramfs<R> {
type Target = Rc<CramfsInner<R>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Cramfs<File> {
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let file = File::open(path)?;
Self::from_reader(file)
}
}
impl<R: Read + Seek> Cramfs<R> {
pub fn from_reader(mut reader: R) -> Result<Self> {
let (start, super_block) = CramfsInner::test_super(&mut reader)?;
let mut inner = CramfsInner {
start,
super_block,
reader: RefCell::new(reader),
read_buffer_block: RefCell::new(!0),
read_buffer: RefCell::new([0u8; ROMBUFFERSIZE as usize * 2]),
};
inner.test_crc()?;
Ok(Self(Rc::new(inner)))
}
pub fn root(&self) -> Result<DirEntry<R>> {
let root = self.0.super_block.root;
if !S_ISDIR(root.mode()) {
return Err("root inode is not directory".into());
}
let offset = self.0.super_block.root.offset() << 2;
if self.0.super_block.flags & FLAG_SHIFTED_ROOT_OFFSET == 0
&& (offset != SuperBlock::size_of_no_padding() as u32
&& offset != PAD_SIZE as u32 + SuperBlock::size_of_no_padding() as u32)
{
return Err(format!("bad root offset {}", offset).into());
}
Ok(DirEntry::new(self.clone(), "/", root))
}
}
#[doc(hidden)]
pub struct CramfsInner<R: Read + Seek> {
start: u64,
reader: RefCell<R>,
super_block: SuperBlock,
read_buffer_block: RefCell<u64>,
read_buffer: RefCell<[u8; ROMBUFFERSIZE as usize * 2]>,
}
impl<R: Read + Seek> CramfsInner<R> {
fn romfs_read(&self, offset: u64) -> Result<Vec<u8>> {
let block = offset >> ROMBUFFER_BITS;
let mut read_buffer = self.read_buffer.borrow_mut();
if block != *self.read_buffer_block.borrow() {
*self.read_buffer_block.borrow_mut() = block;
self.reader
.borrow_mut()
.seek(SeekFrom::Start(block << ROMBUFFER_BITS))?;
let mut total_read = 0;
let buffer_len = read_buffer.len();
while total_read < buffer_len {
match self
.reader
.borrow_mut()
.read(&mut read_buffer[total_read..])
{
Ok(0) => break,
Ok(n) => total_read += n,
Err(e) => return Err(e.into()),
}
}
}
let buffer = &read_buffer[(offset & ROMBUFFERMASK) as usize..];
Ok(buffer.to_vec())
}
fn test_crc(&mut self) -> Result<()> {
if !(self.super_block.flags & FLAG_FSID_VERSION_2) != 0 {
return Ok(());
}
self.reader.borrow_mut().seek(SeekFrom::Start(self.start))?;
let mut buffer = vec![0; self.super_block.size as usize];
self.reader.borrow_mut().read_exact(&mut buffer)?;
buffer[SuperBlock::CRC_OFFSET..][..SuperBlock::CRC_SIZE].copy_from_slice(&[0u8; 4]);
let crc = crc32fast::hash(&buffer);
if crc != self.super_block.fsid.crc {
return Err("crc error".into());
}
Ok(())
}
fn test_super(reader: &mut R) -> Result<(u64, SuperBlock)> {
let mut super_block = SuperBlock::read(reader)?;
let mut start = 0;
if super_block.magic != MAGIC {
reader.seek(SeekFrom::Start(PAD_SIZE))?;
super_block = SuperBlock::read(reader)?;
if super_block.magic == MAGIC {
start = PAD_SIZE;
}
}
if super_block.magic != MAGIC {
return Err("superblock magic not found".into());
}
if (super_block.flags & !SUPPORTED_FLAGS) != 0 {
return Err("unsupported filesystem features".into());
}
if super_block.size < PAGE_SIZE {
return Err(format!("superblock size {} too small", super_block.size).into());
}
if (super_block.flags & FLAG_FSID_VERSION_2) != 0 {
if super_block.fsid.files == 0 {
return Err("zero file count".into());
}
let length = reader.seek(SeekFrom::End(0))?;
match length.cmp(&(super_block.size as u64)) {
Ordering::Less => {
return Err(format!(
"file length too short, {} is smaller than {}",
length, super_block.size,
)
.into());
}
Ordering::Greater => {
warn!("file extends past end of filesystem");
}
_ => {}
}
} else {
warn!("old cramfs format");
}
Ok((start, super_block))
}
}