extern crate alloc;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use super::extent::{Extent, FileType, Timestamps};
#[derive(Debug, Clone)]
pub struct FileLayout {
pub name: String,
pub extent: Extent,
pub file_type: FileType,
pub timestamps: Timestamps,
pub attributes: u32,
pub symlink_target: Option<String>,
}
impl FileLayout {
pub fn new(name: impl Into<String>, extent: Extent) -> Self {
Self {
name: name.into(),
extent,
file_type: FileType::RegularFile,
timestamps: Timestamps::default(),
attributes: 0,
symlink_target: None,
}
}
pub fn with_type(mut self, file_type: FileType) -> Self {
self.file_type = file_type;
self
}
pub fn with_timestamps(mut self, timestamps: Timestamps) -> Self {
self.timestamps = timestamps;
self
}
pub fn with_attributes(mut self, attributes: u32) -> Self {
self.attributes = attributes;
self
}
pub fn with_symlink_target(mut self, target: impl Into<String>) -> Self {
self.symlink_target = Some(target.into());
self
}
#[inline]
pub fn size(&self) -> u64 {
self.extent.length
}
}
#[derive(Debug, Clone, Default)]
pub struct DirectoryLayout {
pub name: String,
pub files: Vec<FileLayout>,
pub subdirs: Vec<DirectoryLayout>,
pub timestamps: Timestamps,
pub attributes: u32,
pub extent: Option<Extent>,
}
impl DirectoryLayout {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
files: Vec::new(),
subdirs: Vec::new(),
timestamps: Timestamps::default(),
attributes: 0,
extent: None,
}
}
pub fn root() -> Self {
Self::new("")
}
pub fn add_file(&mut self, file: FileLayout) {
self.files.push(file);
}
pub fn add_subdir(&mut self, subdir: DirectoryLayout) {
self.subdirs.push(subdir);
}
pub fn with_timestamps(mut self, timestamps: Timestamps) -> Self {
self.timestamps = timestamps;
self
}
pub fn with_extent(mut self, extent: Extent) -> Self {
self.extent = Some(extent);
self
}
#[inline]
pub fn file_count(&self) -> usize {
self.files.len()
}
#[inline]
pub fn subdir_count(&self) -> usize {
self.subdirs.len()
}
#[inline]
pub fn entry_count(&self) -> usize {
self.files.len() + self.subdirs.len()
}
pub fn iter_files(&self) -> impl Iterator<Item = (&str, &FileLayout)> {
FileIterator::new(self)
}
pub fn find_file(&self, path: &str) -> Option<&FileLayout> {
let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
self.find_file_parts(&parts)
}
fn find_file_parts(&self, parts: &[&str]) -> Option<&FileLayout> {
if parts.is_empty() {
return None;
}
if parts.len() == 1 {
self.files.iter().find(|f| f.name == parts[0])
} else {
self.subdirs
.iter()
.find(|d| d.name == parts[0])
.and_then(|d| d.find_file_parts(&parts[1..]))
}
}
pub fn find_file_mut(&mut self, path: &str) -> Option<&mut FileLayout> {
let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
self.find_file_parts_mut(&parts)
}
fn find_file_parts_mut(&mut self, parts: &[&str]) -> Option<&mut FileLayout> {
if parts.is_empty() {
return None;
}
if parts.len() == 1 {
self.files.iter_mut().find(|f| f.name == parts[0])
} else {
self.subdirs
.iter_mut()
.find(|d| d.name == parts[0])
.and_then(|d| d.find_file_parts_mut(&parts[1..]))
}
}
pub fn get_or_create_dir(&mut self, path: &str) -> &mut DirectoryLayout {
let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
self.get_or_create_dir_parts(&parts)
}
fn get_or_create_dir_parts(&mut self, parts: &[&str]) -> &mut DirectoryLayout {
if parts.is_empty() {
return self;
}
let name = parts[0];
let idx = self.subdirs.iter().position(|d| d.name == name);
let idx = match idx {
Some(i) => i,
None => {
self.subdirs.push(DirectoryLayout::new(name));
self.subdirs.len() - 1
}
};
self.subdirs[idx].get_or_create_dir_parts(&parts[1..])
}
pub fn remove_file(&mut self, path: &str) -> Option<FileLayout> {
let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
self.remove_file_parts(&parts)
}
fn remove_file_parts(&mut self, parts: &[&str]) -> Option<FileLayout> {
if parts.is_empty() {
return None;
}
if parts.len() == 1 {
let idx = self.files.iter().position(|f| f.name == parts[0])?;
Some(self.files.remove(idx))
} else {
self.subdirs
.iter_mut()
.find(|d| d.name == parts[0])
.and_then(|d| d.remove_file_parts(&parts[1..]))
}
}
}
struct FileIterator<'a> {
stack: Vec<(&'a str, &'a DirectoryLayout, usize, usize)>,
path_prefix: String,
}
impl<'a> FileIterator<'a> {
fn new(root: &'a DirectoryLayout) -> Self {
Self {
stack: vec![("", root, 0, 0)],
path_prefix: String::new(),
}
}
}
impl<'a> Iterator for FileIterator<'a> {
type Item = (&'a str, &'a FileLayout);
fn next(&mut self) -> Option<Self::Item> {
while let Some((name, dir, file_idx, subdir_idx)) = self.stack.pop() {
if !name.is_empty() {
if !self.path_prefix.is_empty() {
self.path_prefix.push('/');
}
self.path_prefix.push_str(name);
}
if file_idx < dir.files.len() {
self.stack.push((name, dir, file_idx + 1, subdir_idx));
let file = &dir.files[file_idx];
return Some((&file.name, file));
}
if subdir_idx < dir.subdirs.len() {
self.stack.push((name, dir, file_idx, subdir_idx + 1));
let subdir = &dir.subdirs[subdir_idx];
self.stack.push((&subdir.name, subdir, 0, 0));
continue;
}
if !name.is_empty() {
if let Some(idx) = self.path_prefix.rfind('/') {
self.path_prefix.truncate(idx);
} else {
self.path_prefix.clear();
}
}
}
None
}
}
#[derive(Debug, Clone)]
pub struct AllocationMap {
bitmap: Vec<u8>,
total_sectors: u32,
next_free: u32,
}
impl AllocationMap {
pub fn new(total_sectors: u32) -> Self {
let bitmap_size = (total_sectors as usize).div_ceil(8);
Self {
bitmap: alloc::vec![0u8; bitmap_size],
total_sectors,
next_free: 0,
}
}
pub fn from_existing(used_extents: &[Extent], total_sectors: u32, sector_size: u32) -> Self {
let mut map = Self::new(total_sectors);
for extent in used_extents {
map.mark_used(*extent, sector_size);
}
map
}
pub fn allocate(&mut self, size_bytes: u64, sector_size: u32) -> Option<Extent> {
if size_bytes == 0 {
return Some(Extent::new(self.next_free, 0));
}
let sectors_needed = size_bytes.div_ceil(sector_size as u64) as u32;
let mut start = self.next_free;
let mut consecutive = 0u32;
let mut found_start = start;
while start + consecutive < self.total_sectors {
let current = start + consecutive;
if self.is_free(current) {
if consecutive == 0 {
found_start = current;
}
consecutive += 1;
if consecutive >= sectors_needed {
let extent = Extent::new(found_start, size_bytes);
self.mark_used(extent, sector_size);
return Some(extent);
}
} else {
consecutive = 0;
start = current + 1;
found_start = start;
}
}
if self.next_free > 0 {
start = 0;
consecutive = 0;
found_start = 0;
while start + consecutive < self.next_free {
let current = start + consecutive;
if self.is_free(current) {
if consecutive == 0 {
found_start = current;
}
consecutive += 1;
if consecutive >= sectors_needed {
let extent = Extent::new(found_start, size_bytes);
self.mark_used(extent, sector_size);
return Some(extent);
}
} else {
consecutive = 0;
start = current + 1;
found_start = start;
}
}
}
None
}
pub fn mark_used(&mut self, extent: Extent, sector_size: u32) {
let end = extent.end_sector(sector_size);
for sector in extent.sector..end {
self.set_bit(sector, true);
}
if extent.sector == self.next_free {
self.next_free = end;
while self.next_free < self.total_sectors && !self.is_free(self.next_free) {
self.next_free += 1;
}
}
}
pub fn mark_free(&mut self, extent: Extent, sector_size: u32) {
let end = extent.end_sector(sector_size);
for sector in extent.sector..end {
self.set_bit(sector, false);
}
if extent.sector < self.next_free {
self.next_free = extent.sector;
}
}
#[inline]
pub fn is_free(&self, sector: u32) -> bool {
if sector >= self.total_sectors {
return false;
}
let byte_idx = sector as usize / 8;
let bit_idx = sector % 8;
(self.bitmap[byte_idx] & (1 << bit_idx)) == 0
}
#[inline]
pub fn is_used(&self, sector: u32) -> bool {
!self.is_free(sector)
}
#[inline]
pub fn total_sectors(&self) -> u32 {
self.total_sectors
}
pub fn free_sectors(&self) -> u32 {
let mut count = 0u32;
for sector in 0..self.total_sectors {
if self.is_free(sector) {
count += 1;
}
}
count
}
#[inline]
pub fn used_sectors(&self) -> u32 {
self.total_sectors - self.free_sectors()
}
#[inline]
fn set_bit(&mut self, sector: u32, used: bool) {
if sector >= self.total_sectors {
return;
}
let byte_idx = sector as usize / 8;
let bit_idx = sector % 8;
if used {
self.bitmap[byte_idx] |= 1 << bit_idx;
} else {
self.bitmap[byte_idx] &= !(1 << bit_idx);
}
}
pub fn reserve_initial(&mut self, sectors: u32, sector_size: u32) {
let extent = Extent::new(0, sectors as u64 * sector_size as u64);
self.mark_used(extent, sector_size);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_layout() {
let file = FileLayout::new("test.txt", Extent::new(100, 1024))
.with_type(FileType::RegularFile)
.with_attributes(0x20);
assert_eq!(file.name, "test.txt");
assert_eq!(file.size(), 1024);
assert_eq!(file.extent.sector, 100);
}
#[test]
fn test_directory_layout() {
let mut root = DirectoryLayout::root();
root.add_file(FileLayout::new("file1.txt", Extent::new(100, 1024)));
let mut subdir = DirectoryLayout::new("docs");
subdir.add_file(FileLayout::new("readme.md", Extent::new(200, 512)));
root.add_subdir(subdir);
assert_eq!(root.file_count(), 1);
assert_eq!(root.subdir_count(), 1);
let file = root.find_file("file1.txt");
assert!(file.is_some());
assert_eq!(file.unwrap().name, "file1.txt");
let nested = root.find_file("docs/readme.md");
assert!(nested.is_some());
assert_eq!(nested.unwrap().name, "readme.md");
}
#[test]
fn test_get_or_create_dir() {
let mut root = DirectoryLayout::root();
let dir = root.get_or_create_dir("docs/api/v1");
assert_eq!(dir.name, "v1");
assert_eq!(root.subdirs[0].name, "docs");
assert_eq!(root.subdirs[0].subdirs[0].name, "api");
assert_eq!(root.subdirs[0].subdirs[0].subdirs[0].name, "v1");
}
#[test]
fn test_remove_file() {
let mut root = DirectoryLayout::root();
root.add_file(FileLayout::new("test.txt", Extent::new(100, 1024)));
let removed = root.remove_file("test.txt");
assert!(removed.is_some());
assert_eq!(removed.unwrap().name, "test.txt");
assert_eq!(root.file_count(), 0);
}
#[test]
fn test_allocation_map() {
let mut map = AllocationMap::new(100);
assert_eq!(map.total_sectors(), 100);
assert_eq!(map.free_sectors(), 100);
let extent = map.allocate(20480, 2048).unwrap();
assert_eq!(extent.sector, 0);
assert_eq!(extent.sector_count(2048), 10);
assert_eq!(map.free_sectors(), 90);
let extent2 = map.allocate(4096, 2048).unwrap();
assert_eq!(extent2.sector, 10);
map.mark_free(extent, 2048);
assert_eq!(map.free_sectors(), 98);
let extent3 = map.allocate(2048, 2048).unwrap();
assert_eq!(extent3.sector, 0);
}
#[test]
fn test_allocation_map_reserve() {
let mut map = AllocationMap::new(100);
map.reserve_initial(16, 2048);
let extent = map.allocate(2048, 2048).unwrap();
assert_eq!(extent.sector, 16); }
}