use aes::Aes128;
use alloc::{
boxed::Box,
collections::{BTreeMap, VecDeque},
vec,
};
use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY};
use xts_mode::{get_tweak_default, Xts128};
#[cfg(feature = "std")]
use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList};
use crate::{
Allocator, BlockAddr, BlockLevel, BlockMeta, Disk, Header, Transaction, BLOCK_SIZE,
HEADER_RING, RECORD_SIZE,
};
fn compress_cache() -> Box<[u8]> {
vec![0; lz4_flex::block::get_maximum_output_size(RECORD_SIZE as usize)].into_boxed_slice()
}
pub struct FileSystem<D: Disk> {
pub disk: D,
pub block: u64,
pub header: Header,
pub(crate) allocator: Allocator,
pub(crate) cipher_opt: Option<Xts128<Aes128>>,
pub(crate) compress_cache: Box<[u8]>,
pub node_usages: BTreeMap<u32, u64>,
}
impl<D: Disk> FileSystem<D> {
pub fn open(
mut disk: D,
password_opt: Option<&[u8]>,
block_opt: Option<u64>,
cleanup: bool,
) -> Result<Self> {
for ring_block in block_opt.map_or(0..65536, |x| x..x + 1) {
let mut header = Header::default();
unsafe { disk.read_at(ring_block, &mut header)? };
if !header.valid() {
continue;
}
let block = ring_block - (header.generation() % HEADER_RING);
for i in 0..HEADER_RING {
let mut other_header = Header::default();
unsafe { disk.read_at(block + i, &mut other_header)? };
if !other_header.valid() {
continue;
}
if other_header.generation() > header.generation() {
header = other_header;
}
}
let cipher_opt = match password_opt {
Some(password) => {
if !header.encrypted() {
return Err(Error::new(EKEYREJECTED));
}
match header.cipher(password) {
Some(cipher) => Some(cipher),
None => {
return Err(Error::new(ENOKEY));
}
}
}
None => {
if header.encrypted() {
return Err(Error::new(ENOKEY));
}
None
}
};
let mut fs = FileSystem {
disk,
block,
header,
allocator: Allocator::default(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
unsafe { fs.reset_allocator()? };
if cleanup {
fs.cleanup()?
}
return Ok(fs);
}
Err(Error::new(ENOENT))
}
#[cfg(feature = "std")]
pub fn create(
disk: D,
password_opt: Option<&[u8]>,
ctime: u64,
ctime_nsec: u32,
) -> Result<Self> {
Self::create_reserved(disk, password_opt, &[], ctime, ctime_nsec)
}
#[cfg(feature = "std")]
pub fn create_reserved(
mut disk: D,
password_opt: Option<&[u8]>,
reserved: &[u8],
ctime: u64,
ctime_nsec: u32,
) -> Result<Self> {
let disk_size = disk.size()?;
let disk_blocks = disk_size / BLOCK_SIZE;
let block_offset = (reserved.len() as u64).div_ceil(BLOCK_SIZE);
if disk_blocks < (block_offset + HEADER_RING + 4) {
return Err(Error::new(syscall::error::ENOSPC));
}
let fs_blocks = disk_blocks - block_offset;
for block in 0..block_offset as usize {
let mut data = [0; BLOCK_SIZE as usize];
let mut i = 0;
while i < data.len() && block * BLOCK_SIZE as usize + i < reserved.len() {
data[i] = reserved[block * BLOCK_SIZE as usize + i];
i += 1;
}
unsafe {
disk.write_at(block as u64, &data)?;
}
}
let mut header = Header::new(fs_blocks * BLOCK_SIZE);
let cipher_opt = match password_opt {
Some(password) => {
header.key_slots[0] = KeySlot::new(
password,
Salt::new().unwrap(),
(Key::new().unwrap(), Key::new().unwrap()),
)
.unwrap();
Some(header.key_slots[0].cipher(password).unwrap())
}
None => None,
};
let mut fs = FileSystem {
disk,
block: block_offset,
header,
allocator: Allocator::default(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
let count = unsafe { fs.disk.write_at(fs.block, &fs.header)? };
if count != core::mem::size_of_val(&fs.header) {
#[cfg(feature = "log")]
log::error!("CREATE: WRONG NUMBER OF BYTES");
return Err(Error::new(syscall::error::EIO));
}
fs.tx(|tx| unsafe {
let tree = BlockData::new(
BlockAddr::new(HEADER_RING + 1, BlockMeta::default()),
TreeList::empty(BlockLevel::default()).unwrap(),
);
let mut alloc = BlockData::new(
BlockAddr::new(HEADER_RING + 2, BlockMeta::default()),
AllocList::empty(BlockLevel::default()).unwrap(),
);
let alloc_free = fs_blocks - (HEADER_RING + 4);
alloc.data_mut().entries[0] = AllocEntry::new(HEADER_RING + 4, alloc_free as i64);
tx.header.tree = tx.write_block(tree)?;
tx.header.alloc = tx.write_block(alloc)?;
tx.header_changed = true;
Ok(())
})?;
unsafe {
fs.reset_allocator()?;
}
fs.tx(|tx| unsafe {
let mut root = BlockData::new(
BlockAddr::new(HEADER_RING + 3, BlockMeta::default()),
Node::new(Node::MODE_DIR | 0o755, 0, 0, ctime, ctime_nsec),
);
root.data_mut().set_links(1);
let root_ptr = tx.write_block(root)?;
assert_eq!(tx.insert_tree(root_ptr)?.id(), 1);
Ok(())
})?;
fs.cleanup()?;
Ok(fs)
}
pub fn cleanup(&mut self) -> Result<()> {
let mut tx = Transaction::new(self);
tx.release_unused_nodes()?;
tx.commit(true)
}
pub fn tx<F: FnOnce(&mut Transaction<D>) -> Result<T>, T>(&mut self, f: F) -> Result<T> {
let mut tx = Transaction::new(self);
let t = f(&mut tx)?;
tx.commit(false)?;
Ok(t)
}
pub fn allocator(&self) -> &Allocator {
&self.allocator
}
pub unsafe fn allocator_mut(&mut self) -> &mut Allocator {
&mut self.allocator
}
unsafe fn reset_allocator(&mut self) -> Result<()> {
self.allocator = Allocator::default();
let mut allocs = VecDeque::new();
self.tx(|tx| {
let mut alloc_ptr = tx.header.alloc;
while !alloc_ptr.is_null() {
let alloc = tx.read_block(alloc_ptr)?;
alloc_ptr = alloc.data().prev;
allocs.push_front(alloc);
}
Ok(())
})?;
for alloc in allocs {
for entry in alloc.data().entries.iter() {
let index = entry.index();
let count = entry.count();
if count < 0 {
for i in 0..-count {
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
assert_eq!(self.allocator.allocate_exact(addr), Some(addr));
}
} else {
for i in 0..count {
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
self.allocator.deallocate(addr);
}
}
}
}
Ok(())
}
pub(crate) fn decrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
if let Some(ref cipher) = self.cipher_opt {
cipher.decrypt_area(
data,
BLOCK_SIZE as usize,
addr.index().into(),
get_tweak_default,
);
true
} else {
false
}
}
pub(crate) fn encrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
if let Some(ref cipher) = self.cipher_opt {
cipher.encrypt_area(
data,
BLOCK_SIZE as usize,
addr.index().into(),
get_tweak_default,
);
true
} else {
false
}
}
}