use crate::error::BackhandError;
use crate::kinds::Kind;
use crate::v4::data::Added;
use crate::v4::dir::{Dir, DirEntry};
use crate::v4::inode::{
BasicDeviceSpecialFile, BasicDirectory, BasicFile, BasicSymlink, ExtendedDirectory,
ExtendedFile, IPCNode, Inode, InodeHeader, InodeId, InodeInner,
};
use crate::v4::metadata::MetadataWriter;
use crate::v4::squashfs::SuperBlock;
use crate::v4::unix_string::OsStrExt;
use crate::{Id, NodeHeader, SquashfsBlockDevice, SquashfsCharacterDevice, SquashfsSymlink};
use std::ffi::OsStr;
use std::fmt;
#[derive(Clone)]
pub(crate) struct Entry<'a> {
pub start: u32,
pub offset: u16,
pub inode: u32,
pub t: InodeId,
pub name_size: u16,
pub name: &'a [u8],
}
impl<'a> Entry<'a> {
pub fn name(&self) -> Result<String, BackhandError> {
Ok(core::str::from_utf8(self.name)?.to_string())
}
fn find_id_index(id_table: &[Id], id: u32) -> Result<u16, BackhandError> {
id_table
.iter()
.position(|a| a.num == id)
.and_then(|pos| u16::try_from(pos).ok())
.ok_or(BackhandError::IdNotFoundInTable)
}
#[allow(clippy::too_many_arguments)]
pub fn path(
name: &'a OsStr,
header: NodeHeader,
inode: u32,
children_num: usize,
parent_inode: u32,
inode_writer: &mut MetadataWriter,
file_size: usize,
block_offset: u16,
block_index: u32,
superblock: &SuperBlock,
kind: &Kind,
id_table: &[Id],
) -> Result<Self, BackhandError> {
let uid = Self::find_id_index(id_table, header.uid)?;
let gid = Self::find_id_index(id_table, header.gid)?;
let header = InodeHeader {
inode_number: inode,
uid,
gid,
permissions: header.permissions,
mtime: header.mtime,
};
let dir_inode = if file_size > u16::MAX as usize {
Inode::new(
InodeId::ExtendedDirectory,
header,
InodeInner::ExtendedDirectory(ExtendedDirectory {
link_count: 2 + u32::try_from(children_num).map_err(
|e: std::num::TryFromIntError| {
BackhandError::NumericConversion(format!(
"ExtendedDirectory link_count from children_num: {}",
e
))
},
)?,
file_size: file_size.try_into().map_err(|e: std::num::TryFromIntError| {
BackhandError::NumericConversion(format!(
"ExtendedDirectory file_size: {}",
e
))
})?, block_index,
parent_inode,
index_count: 0,
block_offset,
xattr_index: 0xffff_ffff,
dir_index: vec![],
}),
)
} else {
Inode::new(
InodeId::BasicDirectory,
header,
InodeInner::BasicDirectory(BasicDirectory {
block_index,
link_count: 2 + u32::try_from(children_num).map_err(
|e: std::num::TryFromIntError| {
BackhandError::NumericConversion(format!(
"BasicDirectory link_count from children_num: {}",
e
))
},
)?,
file_size: file_size.try_into().map_err(|e: std::num::TryFromIntError| {
BackhandError::NumericConversion(format!("BasicDirectory file_size: {}", e))
})?, block_offset,
parent_inode,
}),
)
};
Ok(dir_inode.to_bytes(name.as_bytes(), inode_writer, superblock, kind))
}
#[allow(clippy::too_many_arguments)]
pub fn file(
node_path: &'a OsStr,
header: NodeHeader,
inode: u32,
inode_writer: &mut MetadataWriter,
file_size: usize,
added: &Added,
superblock: &SuperBlock,
kind: &Kind,
id_table: &[Id],
) -> Result<Self, BackhandError> {
let uid = Self::find_id_index(id_table, header.uid)?;
let gid = Self::find_id_index(id_table, header.gid)?;
let header = InodeHeader {
inode_number: inode,
uid,
gid,
permissions: header.permissions,
mtime: header.mtime,
};
match added {
Added::Data { blocks_start, block_sizes } => {
match (
<usize as TryInto<u32>>::try_into(file_size),
<u64 as TryInto<u32>>::try_into(*blocks_start),
) {
(Ok(file_size), Ok(blocks_start)) => {
let file_inode = Inode::new(
InodeId::BasicFile,
header,
InodeInner::BasicFile(BasicFile {
blocks_start,
frag_index: 0xffffffff, block_offset: 0x0, file_size,
block_sizes: block_sizes.to_vec(),
}),
);
Ok(file_inode.to_bytes(
node_path.as_bytes(),
inode_writer,
superblock,
kind,
))
}
(_, _) => {
let file_inode = Inode::new(
InodeId::ExtendedFile,
header,
InodeInner::ExtendedFile(ExtendedFile {
blocks_start: *blocks_start,
frag_index: 0xffffffff, block_offset: 0x0, file_size: file_size as u64,
sparse: 0,
block_sizes: block_sizes.to_vec(),
link_count: 0,
xattr_index: 0xffffffff, }),
);
Ok(file_inode.to_bytes(
node_path.as_bytes(),
inode_writer,
superblock,
kind,
))
}
}
}
Added::Fragment { frag_index, block_offset } => {
let file_inode = Inode::new(
InodeId::BasicFile,
header,
InodeInner::BasicFile(BasicFile {
blocks_start: 0,
frag_index: *frag_index,
block_offset: *block_offset,
file_size: file_size.try_into().map_err(
|e: std::num::TryFromIntError| {
BackhandError::NumericConversion(format!(
"BasicFile file_size from fragment: {}",
e
))
},
)?,
block_sizes: vec![],
}),
);
Ok(file_inode.to_bytes(node_path.as_bytes(), inode_writer, superblock, kind))
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn symlink(
node_path: &'a OsStr,
header: NodeHeader,
symlink: &SquashfsSymlink,
inode: u32,
inode_writer: &mut MetadataWriter,
superblock: &SuperBlock,
kind: &Kind,
id_table: &[Id],
) -> Result<Self, BackhandError> {
let uid = Self::find_id_index(id_table, header.uid)?;
let gid = Self::find_id_index(id_table, header.gid)?;
let header = InodeHeader {
inode_number: inode,
uid,
gid,
permissions: header.permissions,
mtime: header.mtime,
};
let link = symlink.link.as_os_str().as_bytes();
let sym_inode = Inode::new(
InodeId::BasicSymlink,
header,
InodeInner::BasicSymlink(BasicSymlink {
link_count: 0x1,
target_size: link.len().try_into().map_err(|e: std::num::TryFromIntError| {
BackhandError::NumericConversion(format!("Symlink target_size: {}", e))
})?,
target_path: link.to_vec(),
}),
);
Ok(sym_inode.to_bytes(node_path.as_bytes(), inode_writer, superblock, kind))
}
#[allow(clippy::too_many_arguments)]
pub fn char(
node_path: &'a OsStr,
header: NodeHeader,
char_device: &SquashfsCharacterDevice,
inode: u32,
inode_writer: &mut MetadataWriter,
superblock: &SuperBlock,
kind: &Kind,
id_table: &[Id],
) -> Result<Self, BackhandError> {
let uid = Self::find_id_index(id_table, header.uid)?;
let gid = Self::find_id_index(id_table, header.gid)?;
let header = InodeHeader {
inode_number: inode,
uid,
gid,
permissions: header.permissions,
mtime: header.mtime,
};
let char_inode = Inode::new(
InodeId::BasicCharacterDevice,
header,
InodeInner::BasicCharacterDevice(BasicDeviceSpecialFile {
link_count: 0x1,
device_number: char_device.device_number,
}),
);
Ok(char_inode.to_bytes(node_path.as_bytes(), inode_writer, superblock, kind))
}
#[allow(clippy::too_many_arguments)]
pub fn block_device(
node_path: &'a OsStr,
header: NodeHeader,
block_device: &SquashfsBlockDevice,
inode: u32,
inode_writer: &mut MetadataWriter,
superblock: &SuperBlock,
kind: &Kind,
id_table: &[Id],
) -> Result<Self, BackhandError> {
let uid = Self::find_id_index(id_table, header.uid)?;
let gid = Self::find_id_index(id_table, header.gid)?;
let header = InodeHeader {
inode_number: inode,
uid,
gid,
permissions: header.permissions,
mtime: header.mtime,
};
let block_inode = Inode::new(
InodeId::BasicBlockDevice,
header,
InodeInner::BasicBlockDevice(BasicDeviceSpecialFile {
link_count: 0x1,
device_number: block_device.device_number,
}),
);
Ok(block_inode.to_bytes(node_path.as_bytes(), inode_writer, superblock, kind))
}
#[allow(clippy::too_many_arguments)]
pub fn named_pipe(
node_path: &'a OsStr,
header: NodeHeader,
inode: u32,
inode_writer: &mut MetadataWriter,
superblock: &SuperBlock,
kind: &Kind,
id_table: &[Id],
) -> Result<Self, BackhandError> {
let uid = Self::find_id_index(id_table, header.uid)?;
let gid = Self::find_id_index(id_table, header.gid)?;
let header = InodeHeader {
inode_number: inode,
uid,
gid,
permissions: header.permissions,
mtime: header.mtime,
};
let char_inode = Inode::new(
InodeId::BasicNamedPipe,
header,
InodeInner::BasicNamedPipe(IPCNode { link_count: 0x1 }),
);
Ok(char_inode.to_bytes(node_path.as_bytes(), inode_writer, superblock, kind))
}
#[allow(clippy::too_many_arguments)]
pub fn socket(
node_path: &'a OsStr,
header: NodeHeader,
inode: u32,
inode_writer: &mut MetadataWriter,
superblock: &SuperBlock,
kind: &Kind,
id_table: &[Id],
) -> Result<Self, BackhandError> {
let uid = Self::find_id_index(id_table, header.uid)?;
let gid = Self::find_id_index(id_table, header.gid)?;
let header = InodeHeader {
inode_number: inode,
uid,
gid,
permissions: header.permissions,
mtime: header.mtime,
};
let char_inode = Inode::new(
InodeId::BasicSocket,
header,
InodeInner::BasicSocket(IPCNode { link_count: 0x1 }),
);
Ok(char_inode.to_bytes(node_path.as_bytes(), inode_writer, superblock, kind))
}
}
impl fmt::Debug for Entry<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Entry")
.field("start", &self.start)
.field("offset", &self.offset)
.field("inode", &self.inode)
.field("t", &self.t)
.field("name_size", &self.name_size)
.field("name", &self.name().unwrap_or_else(|_| "<invalid utf8>".to_string()))
.finish()
}
}
impl Entry<'_> {
fn create_dir(
creating_dir: &Vec<&Self>,
start: u32,
lowest_inode: u32,
) -> Result<Dir, BackhandError> {
let mut dir = Dir::new(lowest_inode);
dir.count = creating_dir.len().try_into().map_err(|e: std::num::TryFromIntError| {
BackhandError::NumericConversion(format!("Dir count: {}", e))
})?;
if dir.count >= 256 {
return Err(BackhandError::InternalState(format!("dir.count({}) >= 256", dir.count)));
}
dir.start = start;
for e in creating_dir {
let inode = e.inode;
let new_entry = DirEntry {
offset: e.offset,
inode_offset: (inode - lowest_inode).try_into().map_err(
|e: std::num::TryFromIntError| {
BackhandError::NumericConversion(format!("DirEntry inode_offset: {}", e))
},
)?,
t: e.t.into_base_type(),
name_size: e.name_size,
name: e.name.to_vec(),
};
dir.push(new_entry);
}
Ok(dir)
}
pub(crate) fn into_dir(entries: Vec<Self>) -> Result<Vec<Dir>, BackhandError> {
let mut dirs = vec![];
let mut creating_dir = vec![];
let mut lowest_inode = u32::MAX;
let mut iter = entries.iter().peekable();
let mut creating_start = if let Some(entry) = iter.peek() {
entry.start
} else {
return Ok(vec![]);
};
while let Some(e) = iter.next() {
if e.inode < lowest_inode {
lowest_inode = e.inode;
}
creating_dir.push(e);
if let Some(next) = &iter.peek() {
let max_inode = (next.inode as u64).abs_diff(lowest_inode as u64) > i16::MAX as u64;
if next.start != creating_start || creating_dir.len() >= 255 || max_inode {
let dir = Self::create_dir(&creating_dir, creating_start, lowest_inode)?;
dirs.push(dir);
creating_dir = vec![];
creating_start = next.start;
lowest_inode = u32::MAX;
}
}
if iter.peek().is_none() {
let dir = Self::create_dir(&creating_dir, creating_start, lowest_inode)?;
dirs.push(dir);
}
}
Ok(dirs)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entry() {
let entries = vec![
Entry {
start: 0,
offset: 0x100,
inode: 1,
t: InodeId::BasicDirectory,
name_size: 0x01,
name: b"aa",
},
Entry {
start: 1,
offset: 0x300,
inode: 5,
t: InodeId::BasicDirectory,
name_size: 0x01,
name: b"bb",
},
Entry {
start: 1,
offset: 0x200,
inode: 6,
t: InodeId::BasicDirectory,
name_size: 0x01,
name: b"zz",
},
];
let dir = Entry::into_dir(entries).unwrap();
assert_eq!(
vec![
Dir {
count: 0x0,
start: 0x0,
inode_num: 0x1,
dir_entries: vec![DirEntry {
offset: 0x100,
inode_offset: 0x0,
t: InodeId::BasicDirectory,
name_size: 0x1,
name: b"aa".to_vec(),
},],
},
Dir {
count: 0x1,
start: 0x1,
inode_num: 0x5,
dir_entries: vec![
DirEntry {
offset: 0x300,
inode_offset: 0x0,
t: InodeId::BasicDirectory,
name_size: 0x1,
name: b"bb".to_vec(),
},
DirEntry {
offset: 0x200,
inode_offset: 0x1,
t: InodeId::BasicDirectory,
name_size: 0x1,
name: b"zz".to_vec(),
},
],
},
],
dir
);
}
}