use crate::header::Mode;
use self::directory::Directory;
use self::header::PfsHeader;
use self::inode::Inode;
use aes::Aes128;
use aes::cipher::KeyInit;
use snafu::{OptionExt, ResultExt, Snafu, ensure};
use std::sync::Arc;
use xts_mode::Xts128;
pub mod directory;
pub mod file;
pub mod header;
pub mod image;
pub mod inode;
pub mod pfsc;
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum OpenError {
#[snafu(display("invalid block size"))]
InvalidBlockSize,
#[snafu(display("cannot parse inode"))]
ParseInodeFailed { source: inode::FromRawError },
#[snafu(display("cannot read block #{block}"))]
ReadBlockFailed { block: u32, source: std::io::Error },
#[snafu(display("invalid super-root"))]
InvalidSuperRoot,
#[snafu(display("cannot load block map for inode #{inode}"))]
LoadBlockMapFailed {
inode: usize,
source: inode::LoadBlocksError,
},
}
#[derive(Debug, Snafu)]
#[snafu(module)]
#[non_exhaustive]
pub enum OpenSliceError {
#[snafu(display("cannot parse header"))]
ReadHeaderFailed { source: header::ReadError },
#[snafu(display("block size too small for encryption"))]
EncryptionBlockSizeTooSmall,
#[snafu(display("encryption required but no EKPFS is provided"))]
EmptyEkpfs,
#[snafu(transparent)]
Open { source: OpenError },
}
#[derive(Debug, Snafu)]
#[snafu(module)]
#[non_exhaustive]
pub enum OpenImageError {
#[snafu(display("cannot read header from image"))]
ReadHeaderIoFailed { source: std::io::Error },
#[snafu(display("cannot parse header"))]
ReadHeaderFailed { source: header::ReadError },
#[snafu(display("unsupported mode: {mode}"))]
UnsupportedMode { mode: Mode },
#[snafu(transparent)]
Open { source: OpenError },
}
#[must_use]
pub struct Pfs<'a> {
image: Box<dyn image::Image + 'a>,
inodes: Vec<Inode>,
block_maps: Vec<Vec<u32>>,
root: usize,
block_size: u32,
data: Option<&'a [u8]>,
}
unsafe impl Send for Pfs<'_> {}
unsafe impl Sync for Pfs<'_> {}
impl<'a> std::fmt::Debug for Pfs<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Pfs")
.field("inode_count", &self.inodes.len())
.field("root", &self.root)
.field("block_size", &self.block_size)
.field("slice_backed", &self.data.is_some())
.finish_non_exhaustive()
}
}
impl<'a> Pfs<'a> {
#[must_use]
pub fn inode_count(&self) -> usize {
self.inodes.len()
}
pub fn root(self: &Arc<Self>) -> Directory<'a> {
Directory::new(self.clone(), self.root)
}
#[must_use]
pub fn block_size(&self) -> u32 {
self.block_size
}
pub(crate) fn image(&self) -> &dyn image::Image {
&*self.image
}
pub(crate) fn inode(&self, index: usize) -> &Inode {
&self.inodes[index]
}
pub(crate) fn block_map(&self, inode: usize) -> &[u32] {
&self.block_maps[inode]
}
}
pub fn open_slice<'a>(
data: &'a [u8],
ekpfs: Option<&[u8]>,
) -> Result<Arc<Pfs<'a>>, OpenSliceError> {
let header = PfsHeader::from_bytes(data).context(open_slice_error::ReadHeaderFailedSnafu)?;
let (image, backing_data): (Box<dyn image::Image + 'a>, Option<&'a [u8]>) =
if header.mode().is_encrypted() {
ensure!(
(header.block_size() as usize) >= image::XTS_BLOCK_SIZE,
open_slice_error::EncryptionBlockSizeTooSmallSnafu
);
let ekpfs_bytes = ekpfs.context(open_slice_error::EmptyEkpfsSnafu)?;
let key_seed = header.key_seed();
let (data_key, tweak_key) = image::get_xts_keys(ekpfs_bytes, key_seed);
let cipher_1 = Aes128::new((&data_key).into());
let cipher_2 = Aes128::new((&tweak_key).into());
let enc = image::EncryptedSlice::new(
data,
Xts128::<Aes128>::new(cipher_1, cipher_2),
(header.block_size() as usize) / image::XTS_BLOCK_SIZE,
);
(Box::new(enc), None)
} else {
(Box::new(image::UnencryptedSlice::new(data)), Some(data))
};
Ok(open_inner(image, &header, backing_data)?)
}
pub fn open_image<'a>(image: impl image::Image + 'a) -> Result<Arc<Pfs<'a>>, OpenImageError> {
let mut header_buf = [0u8; header::HEADER_SIZE];
image
.read_exact_at(0, &mut header_buf)
.context(open_image_error::ReadHeaderIoFailedSnafu)?;
let header =
PfsHeader::from_bytes(&header_buf).context(open_image_error::ReadHeaderFailedSnafu)?;
ensure!(
!header.mode().is_encrypted(),
open_image_error::UnsupportedModeSnafu {
mode: header.mode()
}
);
Ok(open_inner(Box::new(image), &header, None)?)
}
fn open_inner<'a>(
image: Box<dyn image::Image + 'a>,
header: &PfsHeader,
data: Option<&'a [u8]>,
) -> Result<Arc<Pfs<'a>>, OpenError> {
let mode = header.mode();
let block_size = header.block_size();
let inode_count = header.inode_count();
let inode_block_count = header.inode_block_count();
let super_root = header.super_root_inode();
ensure!(
block_size > 0 && block_size.is_power_of_two(),
InvalidBlockSizeSnafu
);
let mut inodes: Vec<Inode> = Vec::with_capacity(inode_count);
let mut block_buf = vec![0; block_size as usize];
for block_num in 0..inode_block_count {
let offset = (block_size as u64) + (block_num as u64) * (block_size as u64);
image
.read_exact_at(offset, &mut block_buf)
.context(ReadBlockFailedSnafu { block: block_num })?;
if parse_inodes_from_block(&block_buf, mode, &mut inodes, inode_count)? {
break;
}
}
ensure!(super_root < inodes.len(), InvalidSuperRootSnafu);
let block_maps = precompute_block_maps(&inodes, image.as_ref(), block_size)?;
Ok(Arc::new(Pfs {
image,
inodes,
block_maps,
root: super_root,
block_size,
data,
}))
}
fn precompute_block_maps(
inodes: &[Inode],
image: &dyn image::Image,
block_size: u32,
) -> Result<Vec<Vec<u32>>, OpenError> {
let mut maps = Vec::with_capacity(inodes.len());
for (i, inode) in inodes.iter().enumerate() {
let block_map = inode
.load_block_map(image, block_size)
.context(LoadBlockMapFailedSnafu { inode: i })?;
maps.push(block_map);
}
Ok(maps)
}
fn parse_inodes_from_block(
block_data: &[u8],
mode: Mode,
inodes: &mut Vec<Inode>,
inode_count: usize,
) -> Result<bool, OpenError> {
let reader = if mode.is_signed() {
Inode::from_raw32_signed
} else {
Inode::from_raw32_unsigned
};
let mut src = block_data;
while inodes.len() < inode_count {
let inode = match reader(inodes.len(), &mut src) {
Ok(v) => v,
Err(inode::FromRawError::TooSmall) => {
return Ok(false);
}
err => err.context(ParseInodeFailedSnafu)?,
};
inodes.push(inode);
}
Ok(true)
}