use alloc::{
boxed::Box,
collections::BTreeMap,
format,
rc::Rc,
string::{String, ToString},
vec::Vec,
};
use core::cell::{Cell, RefCell};
use crate::{
allocator::BlockAllocator,
block_device::BlockDevice,
cache::BlockCache,
commit::{CommitEntry, MetadataCommitWriter, checked_u10},
format::{
LFS_NULL, LFS_TYPE_CREATE, LFS_TYPE_CTZSTRUCT, LFS_TYPE_DELETE, LFS_TYPE_DIR,
LFS_TYPE_DIRSTRUCT, LFS_TYPE_HARDTAIL, LFS_TYPE_INLINESTRUCT, LFS_TYPE_MOVESTATE,
LFS_TYPE_REG, LFS_TYPE_SOFTTAIL, LFS_TYPE_SUPERBLOCK, LFS_TYPE_USERATTR, Tag, ctz, le32,
npw2, popc,
},
metadata::{FileData, FileRecord, GlobalState, MetadataPair, MetadataTail, StorageRef},
path::{components, join_path, normalize_dir_path},
types::{
Config, DirEntry, DirectoryUsage, Error, FileType, FilesystemLimits, FilesystemOptions,
FsInfo, Result, WalkEntry,
},
writer::ImageBuilder,
};
mod handles;
mod mutable;
mod read;
const SUPPORTED_DISK_VERSION: u32 = 0x0002_0001;
#[derive(Debug, Clone)]
pub struct Filesystem<'a> {
image: ImageStorage<'a>,
cfg: Config,
root: MetadataPair,
info: FsInfo,
options: FilesystemOptions,
global_state: GlobalState,
allocation_seed: u32,
}
#[derive(Debug)]
pub struct FilesystemMut<D: BlockDevice + 'static> {
fs: Filesystem<'static>,
device: Box<D>,
cache: BlockCache,
allocator: BlockAllocator,
block_cycles: Option<u32>,
}
pub struct FileWriter<'fs, D: BlockDevice + 'static> {
fs: &'fs mut FilesystemMut<D>,
path: String,
data: Vec<u8>,
pos: usize,
stream: Option<StreamingWrite>,
}
#[derive(Clone)]
struct StreamingWrite {
allocator: BlockAllocator,
blocks: Vec<u32>,
current: Option<StreamingBlock>,
len: usize,
target: StreamingTarget,
}
#[derive(Clone)]
struct StreamingBlock {
block: u32,
bytes: Vec<u8>,
off: usize,
mode: StreamingBlockMode,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum StreamingBlockMode {
New,
ExistingTail,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum StreamingTarget {
Create,
Replace,
}
#[derive(Clone)]
struct MergeWrite {
original_len: usize,
patches: Vec<FilePatch>,
}
#[derive(Clone)]
struct FilePatch {
off: usize,
data: Vec<u8>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct FileOptions {
read: bool,
write: bool,
create: bool,
create_new: bool,
truncate: bool,
append: bool,
}
pub struct FileHandle<'fs, D: BlockDevice + 'static> {
fs: &'fs mut FilesystemMut<D>,
path: String,
data: Vec<u8>,
pos: usize,
len: usize,
stream_read: bool,
stream_source: Option<FileData>,
stream_target: StreamingTarget,
stream: Option<StreamingWrite>,
merge: Option<MergeWrite>,
readable: bool,
writable: bool,
dirty: bool,
}
pub struct DirHandle<'fs, 'a> {
fs: &'fs Filesystem<'a>,
head: [u32; 2],
pos: usize,
}
enum ImageStorage<'a> {
Borrowed(&'a [u8]),
Owned(Vec<u8>),
Device {
device: &'a dyn BlockDevice,
cache: Rc<ReadBlockCache>,
},
}
impl core::fmt::Debug for ImageStorage<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Borrowed(image) => f
.debug_tuple("Borrowed")
.field(&format_args!("{} bytes", image.len()))
.finish(),
Self::Owned(image) => f
.debug_tuple("Owned")
.field(&format_args!("{} bytes", image.len()))
.finish(),
Self::Device { .. } => f.debug_tuple("Device").field(&"<block-device>").finish(),
}
}
}
impl<'a> Clone for ImageStorage<'a> {
fn clone(&self) -> Self {
match self {
Self::Borrowed(image) => Self::Borrowed(*image),
Self::Owned(image) => Self::Owned(image.clone()),
Self::Device { device, cache } => Self::Device {
device: *device,
cache: cache.clone(),
},
}
}
}
#[derive(Debug)]
struct ReadBlockCache {
slots: RefCell<Vec<ReadBlockCacheSlot>>,
next: Cell<usize>,
chunk_size: usize,
}
#[derive(Debug)]
struct ReadBlockCacheSlot {
block: Option<u32>,
off: usize,
len: usize,
data: Vec<u8>,
}
impl ReadBlockCache {
fn new(cache_size: usize, slots: usize) -> Self {
let slots = core::cmp::max(slots, 1);
let cache_size = core::cmp::max(cache_size, 1);
Self {
slots: RefCell::new(
(0..slots)
.map(|_| ReadBlockCacheSlot {
block: None,
off: 0,
len: 0,
data: alloc::vec![0xff; cache_size],
})
.collect(),
),
next: Cell::new(0),
chunk_size: cache_size,
}
}
fn read(
&self,
device: &dyn BlockDevice,
cfg: Config,
block: u32,
off: usize,
out: &mut [u8],
) -> Result<()> {
if off.checked_add(out.len()).ok_or(Error::OutOfBounds)? > cfg.block_size {
return Err(Error::OutOfBounds);
}
let mut copied = 0usize;
while copied < out.len() {
let absolute = off + copied;
let chunk_off = (absolute / self.chunk_size) * self.chunk_size;
let chunk_len = core::cmp::min(self.chunk_size, cfg.block_size - chunk_off);
let in_chunk = absolute - chunk_off;
let len = core::cmp::min(out.len() - copied, chunk_len - in_chunk);
self.read_chunk(
device,
block,
chunk_off,
chunk_len,
in_chunk,
&mut out[copied..copied + len],
)?;
copied += len;
}
Ok(())
}
fn read_chunk(
&self,
device: &dyn BlockDevice,
block: u32,
off: usize,
chunk_len: usize,
in_chunk: usize,
out: &mut [u8],
) -> Result<()> {
let mut slots = self.slots.borrow_mut();
if let Some(slot) = slots
.iter()
.find(|slot| slot.block == Some(block) && slot.off == off && slot.len == chunk_len)
{
out.copy_from_slice(&slot.data[in_chunk..in_chunk + out.len()]);
return Ok(());
}
let slot_index = self.next.get() % slots.len();
self.next.set((slot_index + 1) % slots.len());
let slot = &mut slots[slot_index];
if slot.data.len() < chunk_len {
slot.data.resize(chunk_len, 0xff);
}
device.read(block, off, &mut slot.data[..chunk_len])?;
slot.block = Some(block);
slot.off = off;
slot.len = chunk_len;
out.copy_from_slice(&slot.data[in_chunk..in_chunk + out.len()]);
Ok(())
}
}
fn ctz_data_start(index: usize) -> Result<usize> {
if index == 0 {
Ok(0)
} else {
let skips = index.trailing_zeros() as usize + 1;
skips.checked_mul(4).ok_or(Error::NoSpace)
}
}
fn program_nor_bytes(block: &mut [u8], off: usize, data: &[u8]) -> Result<()> {
let end = off.checked_add(data.len()).ok_or(Error::NoSpace)?;
if end > block.len() {
return Err(Error::NoSpace);
}
for (dst, src) in block[off..end].iter_mut().zip(data) {
*dst &= *src;
}
Ok(())
}
fn root_create_id(files: &[FileRecord], name: &str) -> Result<u16> {
let id = files
.iter()
.filter(|file| file.name.as_str() < name)
.count()
.checked_add(1)
.ok_or(Error::Unsupported)?;
let id = u16::try_from(id).map_err(|_| Error::Unsupported)?;
if id < 0x3ff {
Ok(id)
} else {
Err(Error::Unsupported)
}
}
fn dir_create_id(files: &[FileRecord], name: &str) -> Result<u16> {
let id = files
.iter()
.filter(|file| file.name.as_str() < name)
.count();
let id = u16::try_from(id).map_err(|_| Error::Unsupported)?;
if id < 0x3ff {
Ok(id)
} else {
Err(Error::Unsupported)
}
}