use super::super::index;
use super::super::tag;
use super::dir::Resolved;
use super::mdir::{self, Data, Struct, StructOut};
use super::{Contents, Error, Fill, FlashDriver, Volume};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct File {
pair: [u32; 2],
id: u16,
head: u32,
inline: bool,
size: u32,
pos: u32,
}
impl File {
pub fn len(&self) -> u32 {
self.size
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn pos(&self) -> u32 {
self.pos
}
pub fn is_inline(&self) -> bool {
self.inline
}
pub fn seek(&mut self, pos: u32) {
self.pos = pos;
}
pub fn seek_to_end<D: FlashDriver, const B: usize, const P: usize>(
&mut self,
_vol: &mut Volume<D, B, P>,
) {
self.pos = self.size;
}
fn contents<D: FlashDriver, const B: usize, const P: usize>(
&self,
vol: &mut Volume<D, B, P>,
) -> Result<Contents, Error<D::Error>> {
if !self.inline {
return Ok(if self.size == 0 {
Contents::Empty
} else {
Contents::Ctz {
head: self.head,
size: self.size,
}
});
}
let m = vol.fetch(self.pair)?;
let bs = vol.bs();
match mdir::struct_of(&vol.buf[..bs], &m, self.id) {
Some(Struct::Inline { off, len }) => Ok(Contents::Inline {
block: m.pair[0],
off,
len,
}),
Some(Struct::Ctz { head, size }) => Ok(Contents::Ctz { head, size }),
_ => Ok(Contents::Empty),
}
}
pub fn read<D: FlashDriver, const B: usize, const P: usize>(
&mut self,
vol: &mut Volume<D, B, P>,
buf: &mut [u8],
) -> Result<usize, Error<D::Error>> {
let want = buf.len().min(self.size.saturating_sub(self.pos) as usize);
if want == 0 {
return Ok(0);
}
let src = self.contents(vol)?;
let mut done = 0usize;
while done < want {
let n = vol.read_contents(&src, self.pos, &mut buf[done..want])?;
if n == 0 {
break;
}
done += n;
self.pos += n as u32;
}
Ok(done)
}
pub fn read_exact<D: FlashDriver, const B: usize, const P: usize>(
&mut self,
vol: &mut Volume<D, B, P>,
buf: &mut [u8],
) -> Result<(), Error<D::Error>> {
if self.read(vol, buf)? == buf.len() {
Ok(())
} else {
Err(Error::InvalidOffset)
}
}
pub fn write<D: FlashDriver, const B: usize, const P: usize>(
&mut self,
vol: &mut Volume<D, B, P>,
buf: &[u8],
) -> Result<usize, Error<D::Error>> {
if buf.is_empty() {
return Ok(0);
}
let len = u32::try_from(buf.len()).map_err(|_| Error::FileTooLarge)?;
let end = self.pos.checked_add(len).ok_or(Error::FileTooLarge)?;
let new_size = self.size.max(end);
self.apply(vol, self.pos, buf, new_size)?;
self.pos = end;
Ok(buf.len())
}
pub fn write_all<D: FlashDriver, const B: usize, const P: usize>(
&mut self,
vol: &mut Volume<D, B, P>,
buf: &[u8],
) -> Result<(), Error<D::Error>> {
self.write(vol, buf)?;
Ok(())
}
pub fn set_len<D: FlashDriver, const B: usize, const P: usize>(
&mut self,
vol: &mut Volume<D, B, P>,
new_len: u32,
) -> Result<(), Error<D::Error>> {
if new_len == self.size {
return Ok(());
}
if new_len > self.size {
let at = self.size;
self.apply(vol, at, &[], new_len)?;
self.pos = self.pos.min(new_len);
return Ok(());
}
if new_len > vol.geom.file_max {
return Err(Error::FileTooLarge);
}
let src = self.contents(vol)?;
match src {
Contents::Ctz { head, size } if new_len > 0 => {
let (new_head, _) = vol.ctz_find(head, size, new_len - 1)?;
let (keep, _) = index::index_of(vol.geom.block_size, new_len - 1);
vol.release_ctz(head, size, keep + 1)?;
let m = vol.fetch(self.pair)?;
let out = vol.commit(
&m,
super::Edit::SetStruct {
id: self.id,
data: StructOut::Ctz {
head: new_head,
size: new_len,
},
},
)?;
self.head = new_head;
self.follow(&out)?;
}
Contents::Ctz { head, size } => {
vol.release_ctz(head, size, 0)?;
let m = vol.fetch(self.pair)?;
let out = vol.commit(
&m,
super::Edit::SetStruct {
id: self.id,
data: StructOut::Inline(Data::Bytes(&[])),
},
)?;
self.inline = true;
self.head = 0;
self.follow(&out)?;
}
Contents::Inline { off, len, .. } => {
let m = vol.fetch(self.pair)?;
let out = vol.commit(
&m,
super::Edit::SetStruct {
id: self.id,
data: StructOut::Inline(Data::Patch {
old: (off, len),
at: 0,
new: &[],
len: new_len,
}),
},
)?;
self.follow(&out)?;
}
Contents::Empty => {}
}
self.size = new_len;
self.pos = self.pos.min(new_len);
Ok(())
}
pub fn sync<D: FlashDriver, const B: usize, const P: usize>(
&mut self,
vol: &mut Volume<D, B, P>,
) -> Result<(), Error<D::Error>> {
vol.sync()
}
fn apply<D: FlashDriver, const B: usize, const P: usize>(
&mut self,
vol: &mut Volume<D, B, P>,
at: u32,
data: &[u8],
new_size: u32,
) -> Result<(), Error<D::Error>> {
if new_size > vol.geom.file_max {
return Err(Error::FileTooLarge);
}
let src = self.contents(vol)?;
if new_size <= vol.geom.inline_max
&& matches!(src, Contents::Inline { .. } | Contents::Empty)
{
let old = match src {
Contents::Inline { off, len, .. } => (off, len),
_ => (0, 0),
};
let m = vol.fetch(self.pair)?;
let out = vol.commit(
&m,
super::Edit::SetStruct {
id: self.id,
data: StructOut::Inline(Data::Patch {
old,
at,
new: data,
len: new_size,
}),
},
)?;
self.inline = true;
self.head = 0;
self.size = new_size;
self.follow(&out)?;
return Ok(());
}
let bs = vol.geom.block_size;
let old_size = src.len();
let (from_index, prev) = match src {
Contents::Ctz { head, size } if size > 0 => {
let touch = at.min(old_size);
let (k, _) = index::index_of(bs, touch);
let prev = if k == 0 {
None
} else {
let last_byte = index::block_start(bs, k) - 1;
Some(vol.ctz_find(head, size, last_byte)?.0)
};
(k, prev)
}
_ => (0, None),
};
let base = index::block_start(bs, from_index);
let fill = Fill {
old: src,
at,
new: data,
};
let head = vol
.write_ctz(from_index, prev, base, &fill, new_size - base)?
.ok_or(Error::Corrupt("nothing written to the file"))?;
if let Contents::Ctz {
head: old_head,
size,
} = src
{
vol.release_ctz(old_head, size, from_index)?;
}
let m = vol.fetch(self.pair)?;
let out = vol.commit(
&m,
super::Edit::SetStruct {
id: self.id,
data: StructOut::Ctz {
head,
size: new_size,
},
},
)?;
self.inline = false;
self.head = head;
self.size = new_size;
self.follow(&out)?;
Ok(())
}
fn follow<E>(&mut self, out: &super::CommitOut) -> Result<(), Error<E>> {
let placed = out.placed.ok_or(Error::Corrupt("file entry vanished"))?;
self.pair = placed.pair;
self.id = placed.id;
Ok(())
}
}
impl<D: FlashDriver, const BLOCK: usize, const PROG: usize> Volume<D, BLOCK, PROG> {
pub fn open_file(&mut self, path: &str) -> Result<File, Error<D::Error>> {
let Resolved::Entry { pair, id, kind } = self.resolve(path)? else {
return Err(Error::IsADirectory);
};
if kind == tag::TYPE_DIR as u8 {
return Err(Error::IsADirectory);
}
self.file_at(pair, id)
}
pub fn create_file(&mut self, path: &str) -> Result<File, Error<D::Error>> {
let (head, name) = self.parent_of(path)?;
self.check_name(name)?;
if self.find_in_dir(head, name.as_bytes())?.is_some() {
return Err(Error::AlreadyExists);
}
let placed = self.insert_entry(
head,
tag::TYPE_REG as u8,
name.as_bytes(),
StructOut::Inline(Data::Bytes(&[])),
)?;
Ok(File {
pair: placed.pair,
id: placed.id,
head: 0,
inline: true,
size: 0,
pos: 0,
})
}
pub fn open_or_create_file(&mut self, path: &str) -> Result<File, Error<D::Error>> {
match self.try_resolve(path)? {
Some(Resolved::Entry { pair, id, kind }) => {
if kind == tag::TYPE_DIR as u8 {
return Err(Error::IsADirectory);
}
self.file_at(pair, id)
}
Some(Resolved::Root) => Err(Error::IsADirectory),
None => self.create_file(path),
}
}
fn file_at(&mut self, pair: [u32; 2], id: u16) -> Result<File, Error<D::Error>> {
let m = self.fetch(pair)?;
let bs = self.bs();
let (inline, head, size) = match mdir::struct_of(&self.buf[..bs], &m, id) {
Some(Struct::Inline { len, .. }) => (true, 0, len),
Some(Struct::Ctz { head, size }) => (false, head, size),
Some(Struct::Dir(_)) => return Err(Error::IsADirectory),
None => (true, 0, 0),
};
Ok(File {
pair: m.pair,
id,
head,
inline,
size,
pos: 0,
})
}
}