use super::super::layout::{self, ENTRY_SIZE};
use super::{Error, SectorDriver, Stream, Timestamp, Volume};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct EntrySet {
pub pos: u64,
pub count: u8,
pub attrs: u16,
pub flags: u8,
pub name_len: u8,
pub name_hash: u16,
pub first_cluster: u32,
pub data_length: u64,
pub valid_data_length: u64,
pub created: u32,
pub modified: u32,
pub accessed: u32,
}
impl EntrySet {
pub fn is_dir(&self) -> bool {
self.attrs & layout::ATTR_DIRECTORY != 0
}
pub fn stream(&self) -> Stream {
Stream {
first_cluster: self.first_cluster,
len: self.data_length,
contiguous: self.flags & layout::SECFLAG_NO_FAT_CHAIN != 0,
}
}
pub fn bytes(&self) -> u64 {
self.count as u64 * ENTRY_SIZE as u64
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct SetBuilder<'a> {
name: &'a str,
name_units: u8,
name_entries: u8,
attrs: u16,
flags: u8,
first_cluster: u32,
data_length: u64,
valid_data_length: u64,
stamp: (u32, u8),
name_hash: u16,
}
impl<'a> SetBuilder<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new<E>(
name: &'a str,
attrs: u16,
flags: u8,
first_cluster: u32,
data_length: u64,
valid_data_length: u64,
stamp: (u32, u8),
name_hash: u16,
) -> Result<Self, Error<E>> {
let units = name.encode_utf16().count();
if units == 0 || units > layout::MAX_NAME_UNITS {
return Err(Error::InvalidName);
}
Ok(Self {
name,
name_units: units as u8,
name_entries: units.div_ceil(layout::NAME_UNITS_PER_ENTRY) as u8,
attrs,
flags,
first_cluster,
data_length,
valid_data_length,
stamp,
name_hash,
})
}
pub fn count(&self) -> u8 {
2 + self.name_entries
}
pub fn bytes(&self) -> u64 {
self.count() as u64 * ENTRY_SIZE as u64
}
pub fn entry(&self, index: usize) -> [u8; ENTRY_SIZE] {
let mut e = [0u8; ENTRY_SIZE];
match index {
0 => {
e[0] = layout::ENTRY_FILE;
e[1] = self.count() - 1;
e[4..6].copy_from_slice(&self.attrs.to_le_bytes());
e[8..12].copy_from_slice(&self.stamp.0.to_le_bytes());
e[12..16].copy_from_slice(&self.stamp.0.to_le_bytes());
e[16..20].copy_from_slice(&self.stamp.0.to_le_bytes());
e[20] = self.stamp.1;
e[21] = self.stamp.1;
}
1 => {
e[0] = layout::ENTRY_STREAM_EXTENSION;
e[1] = self.flags;
e[3] = self.name_units;
e[4..6].copy_from_slice(&self.name_hash.to_le_bytes());
e[8..16].copy_from_slice(&self.valid_data_length.to_le_bytes());
e[20..24].copy_from_slice(&self.first_cluster.to_le_bytes());
e[24..32].copy_from_slice(&self.data_length.to_le_bytes());
}
n => {
e[0] = layout::ENTRY_FILE_NAME;
let skip = (n - 2) * layout::NAME_UNITS_PER_ENTRY;
for (i, unit) in self
.name
.encode_utf16()
.skip(skip)
.take(layout::NAME_UNITS_PER_ENTRY)
.enumerate()
{
let at = 2 + i * 2;
e[at..at + 2].copy_from_slice(&unit.to_le_bytes());
}
}
}
e
}
pub fn checksum(&self) -> u16 {
let mut sum = 0u16;
for i in 0..self.count() as usize {
sum = layout::set_checksum_step(sum, i, &self.entry(i));
}
sum
}
}
pub(super) fn stamp_of(t: Timestamp) -> (u32, u8) {
((t.date as u32) << 16 | t.time as u32, t.tenths)
}
pub(super) fn timestamp_of(word: u32, tenths: u8) -> Timestamp {
Timestamp {
date: (word >> 16) as u16,
time: (word & 0xffff) as u16,
tenths,
}
}
pub(super) fn name_is_valid(name: &str) -> bool {
if name.is_empty() || name == "." || name == ".." {
return false;
}
let units = name.encode_utf16().count();
if units > layout::MAX_NAME_UNITS {
return false;
}
!name.chars().any(|c| {
(c as u32) < 0x20 || matches!(c, '"' | '*' | '/' | ':' | '<' | '>' | '?' | '\\' | '|')
})
}
impl<D: SectorDriver, const SECTOR: usize> Volume<D, SECTOR> {
pub(super) fn read_set(
&mut self,
dir: &Stream,
pos: u64,
) -> Result<Option<EntrySet>, Error<D::Error>> {
let Some(primary) = self.read_slot(dir, pos)? else {
return Ok(None);
};
if primary[0] != layout::ENTRY_FILE {
return Ok(None);
}
let secondary = primary[1] as usize;
if secondary < 2
|| secondary > 1 + layout::MAX_NAME_UNITS.div_ceil(layout::NAME_UNITS_PER_ENTRY)
{
return Err(Error::CorruptEntry);
}
let Some(stream) = self.read_slot(dir, pos + ENTRY_SIZE as u64)? else {
return Err(Error::CorruptEntry);
};
if stream[0] != layout::ENTRY_STREAM_EXTENSION {
return Err(Error::CorruptEntry);
}
let count = (1 + secondary) as u8;
let on_disk = layout::le16(&primary, 2);
let mut sum = layout::set_checksum_step(0, 0, &primary);
sum = layout::set_checksum_step(sum, 1, &stream);
for i in 2..count as usize {
let Some(slot) = self.read_slot(dir, pos + (i * ENTRY_SIZE) as u64)? else {
return Err(Error::CorruptEntry);
};
if slot[0] != layout::ENTRY_FILE_NAME {
return Err(Error::CorruptEntry);
}
sum = layout::set_checksum_step(sum, i, &slot);
}
if sum != on_disk {
return Err(Error::CorruptEntry);
}
let name_len = stream[3];
if name_len == 0 || name_len as usize > (count as usize - 2) * layout::NAME_UNITS_PER_ENTRY
{
return Err(Error::CorruptEntry);
}
Ok(Some(EntrySet {
pos,
count,
attrs: layout::le16(&primary, 4),
flags: stream[1],
name_len,
name_hash: layout::le16(&stream, 4),
first_cluster: layout::le32(&stream, 20),
data_length: layout::le64(&stream, 24),
valid_data_length: layout::le64(&stream, 8),
created: layout::le32(&primary, 8),
modified: layout::le32(&primary, 12),
accessed: layout::le32(&primary, 16),
}))
}
pub(super) fn name_matches(
&mut self,
dir: &Stream,
set: &EntrySet,
query: &str,
) -> Result<bool, Error<D::Error>> {
if query.encode_utf16().count() != set.name_len as usize {
return Ok(false);
}
let mut want = query.encode_utf16();
let mut left = set.name_len as usize;
for i in 2..set.count as usize {
let Some(slot) = self.read_slot(dir, set.pos + (i * ENTRY_SIZE) as u64)? else {
return Err(Error::CorruptEntry);
};
let n = left.min(layout::NAME_UNITS_PER_ENTRY);
let mut units = [0u16; layout::NAME_UNITS_PER_ENTRY];
for (j, unit) in units[..n].iter_mut().enumerate() {
*unit = layout::le16(&slot, 2 + j * 2);
}
for &on_disk in &units[..n] {
let Some(q) = want.next() else {
return Ok(false);
};
if on_disk != q && self.up(on_disk)? != self.up(q)? {
return Ok(false);
}
}
left -= n;
if left == 0 {
break;
}
}
Ok(left == 0 && want.next().is_none())
}
pub(super) fn copy_name(
&mut self,
dir: &Stream,
set: &EntrySet,
units: &mut [u16],
) -> Result<usize, Error<D::Error>> {
let total = (set.name_len as usize).min(units.len());
let mut done = 0usize;
for i in 2..set.count as usize {
if done >= total {
break;
}
let Some(slot) = self.read_slot(dir, set.pos + (i * ENTRY_SIZE) as u64)? else {
return Err(Error::CorruptEntry);
};
let n = (total - done).min(layout::NAME_UNITS_PER_ENTRY);
for (j, slot_unit) in units[done..done + n].iter_mut().enumerate() {
*slot_unit = layout::le16(&slot, 2 + j * 2);
}
done += n;
}
Ok(done)
}
pub(super) fn write_set(
&mut self,
dir: &Stream,
pos: u64,
set: &SetBuilder<'_>,
) -> Result<(), Error<D::Error>> {
let checksum = set.checksum();
for i in 0..set.count() as usize {
let mut e = set.entry(i);
if i == 0 {
e[2..4].copy_from_slice(&checksum.to_le_bytes());
}
self.write_slot(dir, pos + (i * ENTRY_SIZE) as u64, &e)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(super) fn update_set(
&mut self,
dir: &Stream,
set: &EntrySet,
first_cluster: u32,
data_length: u64,
valid_data_length: u64,
flags: u8,
touch: bool,
) -> Result<(), Error<D::Error>> {
let Some(mut primary) = self.read_slot(dir, set.pos)? else {
return Err(Error::CorruptEntry);
};
let Some(mut stream) = self.read_slot(dir, set.pos + ENTRY_SIZE as u64)? else {
return Err(Error::CorruptEntry);
};
stream[1] = flags;
stream[8..16].copy_from_slice(&valid_data_length.to_le_bytes());
stream[20..24].copy_from_slice(&first_cluster.to_le_bytes());
stream[24..32].copy_from_slice(&data_length.to_le_bytes());
if touch {
let (word, tenths) = stamp_of(self.now);
primary[12..16].copy_from_slice(&word.to_le_bytes());
primary[16..20].copy_from_slice(&word.to_le_bytes());
primary[21] = tenths;
}
let mut sum = layout::set_checksum_step(0, 0, &primary);
sum = layout::set_checksum_step(sum, 1, &stream);
for i in 2..set.count as usize {
let Some(slot) = self.read_slot(dir, set.pos + (i * ENTRY_SIZE) as u64)? else {
return Err(Error::CorruptEntry);
};
sum = layout::set_checksum_step(sum, i, &slot);
}
primary[2..4].copy_from_slice(&sum.to_le_bytes());
self.write_slot(dir, set.pos, &primary)?;
self.write_slot(dir, set.pos + ENTRY_SIZE as u64, &stream)?;
Ok(())
}
pub(super) fn clear_set(
&mut self,
dir: &Stream,
set: &EntrySet,
) -> Result<(), Error<D::Error>> {
for i in 0..set.count as usize {
let off = set.pos + (i * ENTRY_SIZE) as u64;
let Some(mut slot) = self.read_slot(dir, off)? else {
break;
};
slot[0] &= !layout::ENTRY_INUSE;
self.write_slot(dir, off, &slot)?;
}
Ok(())
}
}