use alloc::vec::Vec;
use deku::{DekuRead, DekuWrite};
use super::SfsFs;
use super::error::SfsError;
use super::name_string::NameString;
use super::super_block::SuperBlock;
use super::time_stamp::TimeStamp;
use crate::celled::Celled;
use crate::dev::Device;
use crate::dev::address::Address;
use crate::error::Error;
use crate::fs::error::FsError;
pub const ENTRY_SIZE: u64 = 64;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryType {
VolumeIdentifier = 0x01,
StartingMarker = 0x02,
Unused = 0x10,
Directory = 0x11,
File = 0x12,
Unusable = 0x18,
DeletedDirectory = 0x19,
DeletedFile = 0x1A,
Continuation(u8),
}
impl From<u8> for EntryType {
fn from(value: u8) -> Self {
match value {
0x01 => Self::VolumeIdentifier,
0x02 => Self::StartingMarker,
0x11 => Self::Directory,
0x12 => Self::File,
0x18 => Self::Unusable,
0x19 => Self::DeletedDirectory,
0x1A => Self::DeletedFile,
i @ 0x20..=0xFF => Self::Continuation(i),
0x10 | _ => Self::Unused,
}
}
}
impl From<EntryType> for u8 {
fn from(value: EntryType) -> Self {
match value {
EntryType::VolumeIdentifier => 0x01,
EntryType::StartingMarker => 0x02,
EntryType::Unused => 0x10,
EntryType::Directory => 0x11,
EntryType::File => 0x12,
EntryType::Unusable => 0x18,
EntryType::DeletedDirectory => 0x19,
EntryType::DeletedFile => 0x1A,
EntryType::Continuation(i) => i,
}
}
}
pub(super) trait Entry: Sized {
fn validity_check(&self, super_block: &SuperBlock) -> Result<(), Error<SfsError>>;
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>>;
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct VolumeIdentifierEntry {
pub entry_type: u8,
pub reserved: [u8; 3],
pub format_time: i64,
pub volume_name: [u8; 52],
}
impl VolumeIdentifierEntry {
pub fn parse_volume_name(&self) -> Result<NameString, Error<SfsError>> {
NameString::new_from_start(&self.volume_name)
}
#[must_use]
pub fn parse_format_time(&self) -> TimeStamp {
TimeStamp::from(self.format_time)
}
}
impl Entry for VolumeIdentifierEntry {
fn validity_check(&self, _super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::VolumeIdentifier != self.entry_type.into() {
return Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::VolumeIdentifier,
given: self.entry_type.into(),
})));
}
self.parse_volume_name()?;
if self.reserved == [0, 0, 0] {
Ok(())
} else {
Err(Error::Fs(FsError::Implementation(SfsError::BadVolumeIdentifierEntry(self.reserved))))
}
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self {
entry_type: bytes[0],
reserved: core::array::from_fn(|i| bytes[i + 1]),
format_time: i64::from_le_bytes(core::array::from_fn(|i| bytes[i + 4])),
volume_name: core::array::from_fn(|i| bytes[i + 12]),
};
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct StartingMarkerEntry {
pub entry_type: u8,
pub reserved: [u8; 63],
}
impl Entry for StartingMarkerEntry {
fn validity_check(&self, _super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::StartingMarker == self.entry_type.into() {
Ok(())
} else {
Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::StartingMarker,
given: self.entry_type.into(),
})))
}
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self {
entry_type: bytes[0],
reserved: core::array::from_fn(|i| bytes[i + 1]),
};
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct UnusedEntry {
pub entry_type: u8,
pub reserved: [u8; 63],
}
impl Entry for UnusedEntry {
fn validity_check(&self, _super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::Unused == self.entry_type.into() {
Ok(())
} else {
Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::Unused,
given: self.entry_type.into(),
})))
}
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self {
entry_type: bytes[0],
reserved: core::array::from_fn(|i| bytes[i + 1]),
};
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct DirectoryEntry {
pub entry_type: u8,
pub continuation_nb: u8,
pub last_modification_time: i64,
pub path: [u8; 54],
}
impl DirectoryEntry {
#[must_use]
pub fn parse_last_modification_time(&self) -> TimeStamp {
TimeStamp::from(self.last_modification_time)
}
pub fn parse_path(&self) -> Result<NameString, Error<SfsError>> {
NameString::new_from_start(&self.path)
}
}
impl Entry for DirectoryEntry {
fn validity_check(&self, _super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::Directory != self.entry_type.into() {
return Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::Directory,
given: self.entry_type.into(),
})));
}
self.parse_path().map(|_| ())
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self {
entry_type: bytes[0],
continuation_nb: bytes[1],
last_modification_time: i64::from_le_bytes(core::array::from_fn(|i| bytes[i + 2])),
path: core::array::from_fn(|i| bytes[i + 10]),
};
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct FileEntry {
pub entry_type: u8,
pub continuation_nb: u8,
pub last_modification_time: i64,
pub data_starting_block: u64,
pub data_ending_block: u64,
pub length: u64,
pub path: [u8; 30],
}
impl FileEntry {
#[must_use]
pub fn parse_last_modification_time(&self) -> TimeStamp {
TimeStamp::from(self.last_modification_time)
}
pub fn parse_path(&self) -> Result<NameString, Error<SfsError>> {
NameString::new_from_start(&self.path)
}
#[must_use]
pub const fn is_data_region_valid(&self, super_block: &SuperBlock) -> bool {
let necessary_blocks =
if self.length == 0 { 0 } else { ((self.length - 1) / (super_block.bytes_per_block() as u64)) + 1 };
self.data_starting_block < self.data_ending_block
&& self.data_ending_block - self.data_starting_block == necessary_blocks
&& ((super_block.is_block_in_data_area(self.data_starting_block)
&& super_block.is_block_in_data_area(self.data_ending_block))
|| ((self.data_starting_block == 0) && (self.data_ending_block == 0) && (self.length == 0)))
}
}
impl Entry for FileEntry {
fn validity_check(&self, super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::File != self.entry_type.into() {
return Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::File,
given: self.entry_type.into(),
})));
}
self.parse_path()?;
if self.is_data_region_valid(super_block) {
Ok(())
} else {
Err(Error::Fs(FsError::Implementation(SfsError::BadDataRegion {
region_start: self.data_starting_block,
region_end: self.data_ending_block,
length: self.length,
})))
}
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self {
entry_type: bytes[0],
continuation_nb: bytes[1],
last_modification_time: i64::from_le_bytes(core::array::from_fn(|i| bytes[i + 2])),
data_starting_block: u64::from_le_bytes(core::array::from_fn(|i| bytes[i + 10])),
data_ending_block: u64::from_le_bytes(core::array::from_fn(|i| bytes[i + 18])),
length: u64::from_le_bytes(core::array::from_fn(|i| bytes[i + 26])),
path: core::array::from_fn(|i| bytes[i + 34]),
};
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct UnusableEntry {
pub entry_type: u8,
pub reserved_1: [u8; 9],
pub data_starting_block: u64,
pub data_ending_block: u64,
pub reserved_2: [u8; 38],
}
impl UnusableEntry {
#[must_use]
pub const fn is_data_region_valid(&self, super_block: &SuperBlock) -> bool {
self.data_starting_block <= self.data_ending_block
&& super_block.is_block_in_data_area(self.data_starting_block)
&& super_block.is_block_in_data_area(self.data_ending_block)
}
}
impl Entry for UnusableEntry {
fn validity_check(&self, super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::Unusable != self.entry_type.into() {
Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::Unusable,
given: self.entry_type.into(),
})))
} else if !self.is_data_region_valid(super_block) {
Err(Error::Fs(FsError::Implementation(SfsError::BadDataRegion {
region_start: self.data_starting_block,
region_end: self.data_ending_block,
length: (1 + self.data_ending_block - self.data_starting_block)
* u64::from(super_block.bytes_per_block()),
})))
} else {
Ok(())
}
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self {
entry_type: bytes[0],
reserved_1: core::array::from_fn(|i| bytes[i + 1]),
data_starting_block: u64::from_le_bytes(core::array::from_fn(|i| bytes[i + 10])),
data_ending_block: u64::from_le_bytes(core::array::from_fn(|i| bytes[i + 18])),
reserved_2: core::array::from_fn(|i| bytes[i + 26]),
};
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct DeletedDirectoryEntry {
pub entry_type: u8,
pub continuation_nb: u8,
pub last_modification_time: i64,
pub path: [u8; 54],
}
impl DeletedDirectoryEntry {
#[must_use]
pub fn parse_last_modification_time(&self) -> TimeStamp {
TimeStamp::from(self.last_modification_time)
}
pub fn parse_path(&self) -> Result<NameString, Error<SfsError>> {
NameString::new_from_start(&self.path)
}
}
impl From<DirectoryEntry> for DeletedDirectoryEntry {
fn from(value: DirectoryEntry) -> Self {
Self {
entry_type: value.entry_type,
continuation_nb: value.continuation_nb,
last_modification_time: value.last_modification_time,
path: value.path,
}
}
}
impl From<DeletedDirectoryEntry> for DirectoryEntry {
fn from(value: DeletedDirectoryEntry) -> Self {
Self {
entry_type: value.entry_type,
continuation_nb: value.continuation_nb,
last_modification_time: value.last_modification_time,
path: value.path,
}
}
}
impl Entry for DeletedDirectoryEntry {
fn validity_check(&self, _super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::DeletedDirectory != self.entry_type.into() {
return Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::DeletedDirectory,
given: self.entry_type.into(),
})));
}
self.parse_path().map(|_| ())
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self {
entry_type: bytes[0],
continuation_nb: bytes[1],
last_modification_time: i64::from_le_bytes(core::array::from_fn(|i| bytes[i + 2])),
path: core::array::from_fn(|i| bytes[i + 10]),
};
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct DeletedFileEntry {
pub entry_type: u8,
pub continuation_nb: u8,
pub last_modification_time: i64,
pub data_starting_block: u64,
pub data_ending_block: u64,
pub length: u64,
pub path: [u8; 30],
}
impl DeletedFileEntry {
#[must_use]
pub fn parse_last_modification_time(&self) -> TimeStamp {
TimeStamp::from(self.last_modification_time)
}
pub fn parse_path(&self) -> Result<NameString, Error<SfsError>> {
NameString::new_from_start(&self.path)
}
#[must_use]
pub const fn is_data_region_valid(&self, super_block: &SuperBlock) -> bool {
let necessary_blocks =
if self.length == 0 { 0 } else { ((self.length - 1) / (super_block.bytes_per_block() as u64)) + 1 };
self.data_starting_block < self.data_ending_block
&& self.data_ending_block - self.data_starting_block == necessary_blocks
&& ((super_block.is_block_in_data_area(self.data_starting_block)
&& super_block.is_block_in_data_area(self.data_ending_block))
|| ((self.data_starting_block == 0) && (self.data_ending_block == 0) && (self.length == 0)))
}
}
impl From<FileEntry> for DeletedFileEntry {
fn from(value: FileEntry) -> Self {
Self {
entry_type: value.entry_type,
continuation_nb: value.continuation_nb,
last_modification_time: value.last_modification_time,
data_starting_block: value.data_starting_block,
data_ending_block: value.data_ending_block,
length: value.length,
path: value.path,
}
}
}
impl From<DeletedFileEntry> for FileEntry {
fn from(value: DeletedFileEntry) -> Self {
Self {
entry_type: value.entry_type,
continuation_nb: value.continuation_nb,
last_modification_time: value.last_modification_time,
data_starting_block: value.data_starting_block,
data_ending_block: value.data_ending_block,
length: value.length,
path: value.path,
}
}
}
impl Entry for DeletedFileEntry {
fn validity_check(&self, super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::DeletedFile != self.entry_type.into() {
return Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::DeletedFile,
given: self.entry_type.into(),
})));
}
self.parse_path()?;
if self.is_data_region_valid(super_block) {
Ok(())
} else {
Err(Error::Fs(FsError::Implementation(SfsError::BadDataRegion {
region_start: self.data_starting_block,
region_end: self.data_ending_block,
length: self.length,
})))
}
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self {
entry_type: bytes[0],
continuation_nb: bytes[1],
last_modification_time: i64::from_le_bytes(core::array::from_fn(|i| bytes[i + 2])),
data_starting_block: u64::from_le_bytes(core::array::from_fn(|i| bytes[i + 10])),
data_ending_block: u64::from_le_bytes(core::array::from_fn(|i| bytes[i + 18])),
length: u64::from_le_bytes(core::array::from_fn(|i| bytes[i + 26])),
path: core::array::from_fn(|i| bytes[i + 34]),
};
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
pub struct ContinuationEntry {
entry_name: [u8; 64],
}
impl ContinuationEntry {
pub fn parse_entry_name(&self) -> Result<NameString, Error<SfsError>> {
NameString::new_from_start(&self.entry_name)
}
}
impl Entry for ContinuationEntry {
fn validity_check(&self, _super_block: &SuperBlock) -> Result<(), Error<SfsError>> {
if EntryType::Continuation(self.entry_name[0]) != self.entry_name[0].into() {
return Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::DeletedFile,
given: self.entry_name[0].into(),
})));
}
self.parse_entry_name().map(|_| ())
}
fn parse(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let entry = Self { entry_name: bytes };
entry.validity_check(super_block).map(|()| entry)
}
}
#[derive(Debug, Clone, Copy)]
pub enum EntryTypeWithEntry {
VolumeIdentifier(VolumeIdentifierEntry),
StartingMarker(StartingMarkerEntry),
Unused(UnusedEntry),
Directory(DirectoryEntry),
File(FileEntry),
Unusable(UnusableEntry),
DeletedDirectory(DeletedDirectoryEntry),
DeletedFile(DeletedFileEntry),
Continuation(ContinuationEntry),
}
impl EntryTypeWithEntry {
pub fn parse_bytes(bytes: [u8; 64], super_block: &SuperBlock) -> Result<Self, Error<SfsError>> {
let enty_type = EntryType::from(bytes[0]);
match enty_type {
EntryType::VolumeIdentifier => {
Ok(Self::VolumeIdentifier(VolumeIdentifierEntry::parse(bytes, super_block)?))
},
EntryType::StartingMarker => Ok(Self::StartingMarker(StartingMarkerEntry::parse(bytes, super_block)?)),
EntryType::Unused => Ok(Self::Unused(UnusedEntry::parse(bytes, super_block)?)),
EntryType::Directory => Ok(Self::Directory(DirectoryEntry::parse(bytes, super_block)?)),
EntryType::File => Ok(Self::File(FileEntry::parse(bytes, super_block)?)),
EntryType::Unusable => Ok(Self::Unusable(UnusableEntry::parse(bytes, super_block)?)),
EntryType::DeletedDirectory => {
Ok(Self::DeletedDirectory(DeletedDirectoryEntry::parse(bytes, super_block)?))
},
EntryType::DeletedFile => Ok(Self::DeletedFile(DeletedFileEntry::parse(bytes, super_block)?)),
EntryType::Continuation(_) => Ok(Self::Continuation(ContinuationEntry::parse(bytes, super_block)?)),
}
}
unsafe fn parse_at<Dev: Device>(
celled_device: &Celled<Dev>,
super_block: &SuperBlock,
starting_addr: Address,
) -> Result<Self, Error<SfsError>> {
let mut device = celled_device.lock();
let slice = device.slice(starting_addr..starting_addr + ENTRY_SIZE)?;
let bytes: [u8; 64] = unsafe { (*slice).try_into().unwrap_unchecked() };
Self::parse_bytes(bytes, super_block)
}
#[must_use]
pub fn starting_addr(super_block: &SuperBlock, index: u64) -> Address {
super_block.index_area_starting_addr() + super_block.index_size - index * ENTRY_SIZE
}
pub fn parse<Dev: Device>(
celled_device: &Celled<Dev>,
super_block: &SuperBlock,
index: u64,
) -> Result<Self, Error<SfsError>> {
unsafe { Self::parse_at(celled_device, super_block, Self::starting_addr(super_block, index)) }
}
#[must_use]
pub const fn variant(&self) -> EntryType {
match self {
Self::VolumeIdentifier(_) => EntryType::VolumeIdentifier,
Self::StartingMarker(_) => EntryType::StartingMarker,
Self::Unused(_) => EntryType::Unused,
Self::Directory(_) => EntryType::Directory,
Self::File(_) => EntryType::File,
Self::Unusable(_) => EntryType::Unusable,
Self::DeletedDirectory(_) => EntryType::DeletedDirectory,
Self::DeletedFile(_) => EntryType::DeletedFile,
Self::Continuation(entry) => EntryType::Continuation(entry.entry_name[0]),
}
}
}
impl From<EntryTypeWithEntry> for EntryType {
fn from(value: EntryTypeWithEntry) -> Self {
value.variant()
}
}
pub fn parse_full_path<Dev: Device>(
celled_device: &Celled<Dev>,
super_block: &SuperBlock,
entry_number: u64,
) -> Result<Option<NameString>, Error<SfsError>> {
let entry = EntryTypeWithEntry::parse(celled_device, super_block, entry_number)?;
let (mut name, continuation_nb) = match entry {
EntryTypeWithEntry::Directory(directory_entry) => {
(directory_entry.parse_path()?, u64::from(directory_entry.continuation_nb))
},
EntryTypeWithEntry::File(file_entry) => (file_entry.parse_path()?, u64::from(file_entry.continuation_nb)),
EntryTypeWithEntry::DeletedDirectory(deleted_directory_entry) => {
(deleted_directory_entry.parse_path()?, u64::from(deleted_directory_entry.continuation_nb))
},
EntryTypeWithEntry::DeletedFile(deleted_file_entry) => {
(deleted_file_entry.parse_path()?, u64::from(deleted_file_entry.continuation_nb))
},
EntryTypeWithEntry::Continuation(continuation_entry) => (continuation_entry.parse_entry_name()?, 0),
_ => return Ok(None),
};
for idx in 1..=continuation_nb {
let entry = EntryTypeWithEntry::parse(celled_device, super_block, entry_number + idx)?;
let EntryTypeWithEntry::Continuation(continuation_entry) = entry else {
return Err(Error::Fs(FsError::Implementation(SfsError::WrongEntryType {
expected: EntryType::Continuation(0x20),
given: entry.into(),
})));
};
name.join(&continuation_entry.parse_entry_name()?);
}
Ok(Some(name))
}
pub fn parse_entries_until<Dev: Device, F: Fn(EntryTypeWithEntry, u64) -> Result<bool, Error<SfsError>>>(
filesystem: &SfsFs<Dev>,
predicate: F,
) -> Result<Vec<EntryTypeWithEntry>, Error<SfsError>> {
let fs = filesystem.lock();
let super_block = fs.super_block();
let mut entries = Vec::new();
let starting_addr = super_block.index_area_starting_addr();
let ending_byte = super_block.filesystem_size();
let nb_entries = (ending_byte - starting_addr.index()) / 64;
for idx in 1..=nb_entries {
let entry = EntryTypeWithEntry::parse(&fs.device, super_block, idx)?;
entries.push(entry);
if predicate(entry, idx)? {
break;
}
}
Ok(entries)
}
pub fn find_entry<Dev: Device, F: Fn(EntryTypeWithEntry, u64, &Celled<Dev>) -> Result<bool, Error<SfsError>>>(
filesystem: &SfsFs<Dev>,
predicate: F,
) -> Result<Option<(EntryTypeWithEntry, u64)>, Error<SfsError>> {
let fs = filesystem.lock();
let super_block = fs.super_block();
let starting_addr = super_block.index_area_starting_addr();
let ending_byte = super_block.filesystem_size();
let nb_entries = (ending_byte - starting_addr.index()) / 64;
for idx in 1..=nb_entries {
let entry = EntryTypeWithEntry::parse(&fs.device, super_block, idx)?;
if predicate(entry, idx, &fs.device)? {
return Ok(Some((entry, idx + 1)));
}
}
Ok(None)
}
pub fn find_all_entries<Dev: Device, F: Fn(EntryTypeWithEntry, u64, &Celled<Dev>) -> Result<bool, Error<SfsError>>>(
filesystem: &SfsFs<Dev>,
predicate: F,
) -> Result<Vec<(EntryTypeWithEntry, u64)>, Error<SfsError>> {
let fs = filesystem.lock();
let super_block = fs.super_block();
let mut entries = Vec::new();
let starting_addr = super_block.index_area_starting_addr();
let ending_byte = super_block.filesystem_size();
let nb_entries = (ending_byte - starting_addr.index()) / 64;
for idx in 1..=nb_entries {
let entry = EntryTypeWithEntry::parse(&fs.device, super_block, idx)?;
if predicate(entry, idx, &fs.device)? {
entries.push((entry, idx));
}
}
Ok(entries)
}
#[cfg(test)]
mod test {
use core::str::FromStr;
use spin::Lazy;
use super::{Entry, UnusableEntry, VolumeIdentifierEntry};
use crate::fs::sfs::index_area::{
ContinuationEntry, DeletedDirectoryEntry, DeletedFileEntry, DirectoryEntry, EntryType, EntryTypeWithEntry,
FileEntry, StartingMarkerEntry, UnusedEntry,
};
use crate::fs::sfs::name_string::NameString;
use crate::fs::sfs::super_block::SuperBlock;
use crate::fs::sfs::time_stamp::TimeStamp;
static TEST_SUPER_BLOCK: Lazy<SuperBlock> = Lazy::new(|| SuperBlock {
time_stamp: *TimeStamp::now().unwrap(),
data_size: 10,
index_size: 10 * 64,
magic: *b"SFS",
version: 0x10,
total_blocks: 20,
rsvd_blocks: 3,
block_size: 1,
crc: 0xEC,
});
const TEST_VOLUME_IDENTIFIER_ENTRY: [u8; 64] = [
0x01, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, b'f', b'o', b'o', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const TEST_STARTING_MARKER_ENTRY: [u8; 64] = [
0x02, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 5, 1, 44, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const TEST_UNUSED_ENTRY: [u8; 64] = [
0x10, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 5, 1, 44, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const TEST_DIRECTORY_ENTRY: [u8; 64] = [
0x11, 1, 5, 0, 0, 0, 0, 0, 0, 0, b'f', b'o', b'o', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const TEST_FILE_ENTRY: [u8; 64] = [
0x12, 1, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 88, 2, 0, 0, 0, 0, 0, 0, b'f',
b'o', b'o', b'/', b'b', b'a', b'r', b'.', b't', b'x', b't', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
];
const TEST_UNUSABLE_ENTRY: [u8; 64] = [
0x18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const TEST_DELETED_DIRECTORY_ENTRY: [u8; 64] = [
0x19, 1, 5, 0, 0, 0, 0, 0, 0, 0, b'f', b'o', b'o', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const TEST_DELETED_FILE_ENTRY: [u8; 64] = [
0x1A, 1, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 88, 2, 0, 0, 0, 0, 0, 0, b'f',
b'o', b'o', b'/', b'b', b'a', b'r', b'.', b't', b'x', b't', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
];
const TEST_CONTINUATION_ENTRY: [u8; 64] = [
b'f', b'o', b'o', b'/', b'b', b'a', b'r', b'.', b't', b'x', b't', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
];
#[test]
fn parse_volume_identifier_entry() {
let entry = VolumeIdentifierEntry::parse(TEST_VOLUME_IDENTIFIER_ENTRY, &TEST_SUPER_BLOCK)
.expect("Could not parse the test volume identifier entry");
assert_eq!(EntryType::from(entry.entry_type), EntryType::VolumeIdentifier);
assert_eq!(entry.parse_format_time(), TimeStamp::from(5));
assert_eq!(entry.parse_volume_name().unwrap(), NameString::from_str("foo").unwrap());
}
#[test]
fn parse_starting_marker_entry() {
let entry = StartingMarkerEntry::parse(TEST_STARTING_MARKER_ENTRY, &TEST_SUPER_BLOCK)
.expect("Could not parse the test starting marker entry");
assert_eq!(EntryType::from(entry.entry_type), EntryType::StartingMarker);
}
#[test]
fn parse_unused_entry() {
let entry =
UnusedEntry::parse(TEST_UNUSED_ENTRY, &TEST_SUPER_BLOCK).expect("Could not parse the test unused entry");
assert_eq!(EntryType::from(entry.entry_type), EntryType::Unused);
}
#[test]
fn parse_directory_entry() {
let entry = DirectoryEntry::parse(TEST_DIRECTORY_ENTRY, &TEST_SUPER_BLOCK)
.expect("Could not parse the test directory entry");
assert_eq!(EntryType::from(entry.entry_type), EntryType::Directory);
assert_eq!(entry.parse_last_modification_time(), TimeStamp::from(5));
assert_eq!(entry.parse_path().unwrap(), NameString::from_str("foo").unwrap());
}
#[test]
fn parse_file_entry() {
let entry = FileEntry::parse(TEST_FILE_ENTRY, &TEST_SUPER_BLOCK).expect("Could not parse the test file entry");
assert_eq!(EntryType::from(entry.entry_type), EntryType::File);
assert_eq!(entry.parse_last_modification_time(), TimeStamp::from(5));
assert_eq!(entry.parse_path().unwrap(), NameString::from_str("foo/bar.txt").unwrap());
let data_starting_block = entry.data_starting_block;
let data_ending_block = entry.data_ending_block;
let length = entry.length;
assert_eq!(data_starting_block, 6);
assert_eq!(data_ending_block, 9);
assert_eq!(length, 600);
}
#[test]
fn parse_unusable_entry() {
let entry = UnusableEntry::parse(TEST_UNUSABLE_ENTRY, &TEST_SUPER_BLOCK)
.expect("Could not parse the test unusable entry");
assert_eq!(EntryType::from(entry.entry_type), EntryType::Unusable);
let data_starting_block = entry.data_starting_block;
let data_ending_block = entry.data_ending_block;
assert_eq!(data_starting_block, 7);
assert_eq!(data_ending_block, 7);
}
#[test]
fn parse_deleted_directory_entry() {
let entry = DeletedDirectoryEntry::parse(TEST_DELETED_DIRECTORY_ENTRY, &TEST_SUPER_BLOCK)
.expect("Could not parse the test deleted directory entry");
assert_eq!(EntryType::from(entry.entry_type), EntryType::DeletedDirectory);
assert_eq!(entry.parse_last_modification_time(), TimeStamp::from(5));
assert_eq!(entry.parse_path().unwrap(), NameString::from_str("foo").unwrap());
}
#[test]
fn parse_deleted_file_entry() {
let entry = DeletedFileEntry::parse(TEST_DELETED_FILE_ENTRY, &TEST_SUPER_BLOCK)
.expect("Could not parse the test deleted file entry");
assert_eq!(EntryType::from(entry.entry_type), EntryType::DeletedFile);
assert_eq!(entry.parse_last_modification_time(), TimeStamp::from(5));
assert_eq!(entry.parse_path().unwrap(), NameString::from_str("foo/bar.txt").unwrap());
let data_starting_block = entry.data_starting_block;
let data_ending_block = entry.data_ending_block;
let length = entry.length;
assert_eq!(data_starting_block, 6);
assert_eq!(data_ending_block, 9);
assert_eq!(length, 600);
}
#[test]
fn parse_continuation_entry() {
let entry = ContinuationEntry::parse(TEST_CONTINUATION_ENTRY, &TEST_SUPER_BLOCK)
.expect("Could not parse the test continuation entry");
assert_eq!(EntryType::from(entry.entry_name[0]), EntryType::Continuation(entry.entry_name[0]));
assert_eq!(entry.parse_entry_name().unwrap(), NameString::from_str("foo/bar.txt").unwrap());
}
#[test]
fn parse_entries() {
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_VOLUME_IDENTIFIER_ENTRY, &TEST_SUPER_BLOCK)
.unwrap()
.variant(),
EntryType::VolumeIdentifier
);
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_STARTING_MARKER_ENTRY, &TEST_SUPER_BLOCK)
.unwrap()
.variant(),
EntryType::StartingMarker
);
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_UNUSED_ENTRY, &TEST_SUPER_BLOCK).unwrap().variant(),
EntryType::Unused
);
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_DIRECTORY_ENTRY, &TEST_SUPER_BLOCK)
.unwrap()
.variant(),
EntryType::Directory
);
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_FILE_ENTRY, &TEST_SUPER_BLOCK).unwrap().variant(),
EntryType::File
);
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_UNUSABLE_ENTRY, &TEST_SUPER_BLOCK).unwrap().variant(),
EntryType::Unusable
);
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_DELETED_DIRECTORY_ENTRY, &TEST_SUPER_BLOCK)
.unwrap()
.variant(),
EntryType::DeletedDirectory
);
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_DELETED_FILE_ENTRY, &TEST_SUPER_BLOCK)
.unwrap()
.variant(),
EntryType::DeletedFile
);
assert_eq!(
EntryTypeWithEntry::parse_bytes(TEST_CONTINUATION_ENTRY, &TEST_SUPER_BLOCK)
.unwrap()
.variant(),
EntryType::Continuation(TEST_CONTINUATION_ENTRY[0])
);
}
}