1extern crate alloc;
6
7use alloc::string::String;
8use alloc::vec;
9use alloc::vec::Vec;
10use hadris_path::{Component, VPath};
11
12use super::extent::{Extent, FileType, Timestamps};
13
14fn path_parts(path: &str) -> Option<Vec<&str>> {
15 let mut parts = Vec::new();
16 for component in VPath::new(path).components() {
17 match component {
18 Component::Root | Component::Current => {}
19 Component::Parent => return None,
20 Component::Normal(component) => parts.push(component),
21 }
22 }
23 Some(parts)
24}
25
26#[derive(Debug, Clone)]
32pub struct FileLayout {
33 pub name: String,
35 pub extent: Extent,
37 pub file_type: FileType,
39 pub timestamps: Timestamps,
41 pub attributes: u32,
43 pub symlink_target: Option<String>,
45}
46
47impl FileLayout {
48 pub fn new(name: impl Into<String>, extent: Extent) -> Self {
50 Self {
51 name: name.into(),
52 extent,
53 file_type: FileType::RegularFile,
54 timestamps: Timestamps::default(),
55 attributes: 0,
56 symlink_target: None,
57 }
58 }
59
60 pub fn with_type(mut self, file_type: FileType) -> Self {
62 self.file_type = file_type;
63 self
64 }
65
66 pub fn with_timestamps(mut self, timestamps: Timestamps) -> Self {
68 self.timestamps = timestamps;
69 self
70 }
71
72 pub fn with_attributes(mut self, attributes: u32) -> Self {
74 self.attributes = attributes;
75 self
76 }
77
78 pub fn with_symlink_target(mut self, target: impl Into<String>) -> Self {
80 self.symlink_target = Some(target.into());
81 self
82 }
83
84 #[inline]
86 pub fn size(&self) -> u64 {
87 self.extent.length
88 }
89}
90
91#[derive(Debug, Clone, Default)]
96pub struct DirectoryLayout {
97 pub name: String,
99 pub files: Vec<FileLayout>,
101 pub subdirs: Vec<DirectoryLayout>,
103 pub timestamps: Timestamps,
105 pub attributes: u32,
107 pub extent: Option<Extent>,
109}
110
111impl DirectoryLayout {
112 pub fn new(name: impl Into<String>) -> Self {
114 Self {
115 name: name.into(),
116 files: Vec::new(),
117 subdirs: Vec::new(),
118 timestamps: Timestamps::default(),
119 attributes: 0,
120 extent: None,
121 }
122 }
123
124 pub fn root() -> Self {
126 Self::new("")
127 }
128
129 pub fn add_file(&mut self, file: FileLayout) {
131 self.files.push(file);
132 }
133
134 pub fn add_subdir(&mut self, subdir: DirectoryLayout) {
136 self.subdirs.push(subdir);
137 }
138
139 pub fn with_timestamps(mut self, timestamps: Timestamps) -> Self {
141 self.timestamps = timestamps;
142 self
143 }
144
145 pub fn with_extent(mut self, extent: Extent) -> Self {
147 self.extent = Some(extent);
148 self
149 }
150
151 #[inline]
153 pub fn file_count(&self) -> usize {
154 self.files.len()
155 }
156
157 #[inline]
159 pub fn subdir_count(&self) -> usize {
160 self.subdirs.len()
161 }
162
163 #[inline]
165 pub fn entry_count(&self) -> usize {
166 self.files.len() + self.subdirs.len()
167 }
168
169 pub fn iter_files(&self) -> impl Iterator<Item = (&str, &FileLayout)> {
171 FileIterator::new(self)
172 }
173
174 pub fn find_file(&self, path: &str) -> Option<&FileLayout> {
176 let parts = path_parts(path)?;
177 self.find_file_parts(&parts)
178 }
179
180 fn find_file_parts(&self, parts: &[&str]) -> Option<&FileLayout> {
182 if parts.is_empty() {
183 return None;
184 }
185
186 if parts.len() == 1 {
187 self.files.iter().find(|f| f.name == parts[0])
189 } else {
190 self.subdirs
192 .iter()
193 .find(|d| d.name == parts[0])
194 .and_then(|d| d.find_file_parts(&parts[1..]))
195 }
196 }
197
198 pub fn find_file_mut(&mut self, path: &str) -> Option<&mut FileLayout> {
200 let parts = path_parts(path)?;
201 self.find_file_parts_mut(&parts)
202 }
203
204 fn find_file_parts_mut(&mut self, parts: &[&str]) -> Option<&mut FileLayout> {
206 if parts.is_empty() {
207 return None;
208 }
209
210 if parts.len() == 1 {
211 self.files.iter_mut().find(|f| f.name == parts[0])
212 } else {
213 self.subdirs
214 .iter_mut()
215 .find(|d| d.name == parts[0])
216 .and_then(|d| d.find_file_parts_mut(&parts[1..]))
217 }
218 }
219
220 pub fn get_or_create_dir(&mut self, path: &str) -> &mut DirectoryLayout {
222 let parts = path_parts(path).unwrap_or_default();
223 self.get_or_create_dir_parts(&parts)
224 }
225
226 fn get_or_create_dir_parts(&mut self, parts: &[&str]) -> &mut DirectoryLayout {
228 if parts.is_empty() {
229 return self;
230 }
231
232 let name = parts[0];
233
234 let idx = self.subdirs.iter().position(|d| d.name == name);
236 let idx = match idx {
237 Some(i) => i,
238 None => {
239 self.subdirs.push(DirectoryLayout::new(name));
240 self.subdirs.len() - 1
241 }
242 };
243
244 self.subdirs[idx].get_or_create_dir_parts(&parts[1..])
245 }
246
247 pub fn remove_file(&mut self, path: &str) -> Option<FileLayout> {
249 let parts = path_parts(path)?;
250 self.remove_file_parts(&parts)
251 }
252
253 fn remove_file_parts(&mut self, parts: &[&str]) -> Option<FileLayout> {
255 if parts.is_empty() {
256 return None;
257 }
258
259 if parts.len() == 1 {
260 let idx = self.files.iter().position(|f| f.name == parts[0])?;
261 Some(self.files.remove(idx))
262 } else {
263 self.subdirs
264 .iter_mut()
265 .find(|d| d.name == parts[0])
266 .and_then(|d| d.remove_file_parts(&parts[1..]))
267 }
268 }
269}
270
271struct FileIterator<'a> {
273 stack: Vec<(&'a str, &'a DirectoryLayout, usize, usize)>,
274 path_prefix: String,
275}
276
277impl<'a> FileIterator<'a> {
278 fn new(root: &'a DirectoryLayout) -> Self {
279 Self {
280 stack: vec![("", root, 0, 0)],
281 path_prefix: String::new(),
282 }
283 }
284}
285
286impl<'a> Iterator for FileIterator<'a> {
287 type Item = (&'a str, &'a FileLayout);
288
289 fn next(&mut self) -> Option<Self::Item> {
290 while let Some((name, dir, file_idx, subdir_idx)) = self.stack.pop() {
291 if !name.is_empty() {
293 if !self.path_prefix.is_empty() {
294 self.path_prefix.push('/');
295 }
296 self.path_prefix.push_str(name);
297 }
298
299 if file_idx < dir.files.len() {
301 self.stack.push((name, dir, file_idx + 1, subdir_idx));
303 let file = &dir.files[file_idx];
304 return Some((&file.name, file));
307 }
308
309 if subdir_idx < dir.subdirs.len() {
311 self.stack.push((name, dir, file_idx, subdir_idx + 1));
313 let subdir = &dir.subdirs[subdir_idx];
314 self.stack.push((&subdir.name, subdir, 0, 0));
315 continue;
316 }
317
318 if !name.is_empty() {
320 if let Some(idx) = self.path_prefix.rfind('/') {
321 self.path_prefix.truncate(idx);
322 } else {
323 self.path_prefix.clear();
324 }
325 }
326 }
327 None
328 }
329}
330
331#[derive(Debug, Clone)]
336pub struct AllocationMap {
337 bitmap: Vec<u8>,
339 total_sectors: u32,
341 next_free: u32,
343}
344
345impl AllocationMap {
346 pub fn new(total_sectors: u32) -> Self {
348 let bitmap_size = (total_sectors as usize).div_ceil(8);
349 Self {
350 bitmap: alloc::vec![0u8; bitmap_size],
351 total_sectors,
352 next_free: 0,
353 }
354 }
355
356 pub fn from_existing(used_extents: &[Extent], total_sectors: u32, sector_size: u32) -> Self {
358 let mut map = Self::new(total_sectors);
359 for extent in used_extents {
360 map.mark_used(*extent, sector_size);
361 }
362 map
363 }
364
365 pub fn allocate(&mut self, size_bytes: u64, sector_size: u32) -> Option<Extent> {
369 if size_bytes == 0 {
370 return Some(Extent::new(self.next_free, 0));
371 }
372
373 let sectors_needed = size_bytes.div_ceil(sector_size as u64) as u32;
374
375 let mut start = self.next_free;
377 let mut consecutive = 0u32;
378 let mut found_start = start;
379
380 while start + consecutive < self.total_sectors {
381 let current = start + consecutive;
382 if self.is_free(current) {
383 if consecutive == 0 {
384 found_start = current;
385 }
386 consecutive += 1;
387 if consecutive >= sectors_needed {
388 let extent = Extent::new(found_start, size_bytes);
390 self.mark_used(extent, sector_size);
391 return Some(extent);
392 }
393 } else {
394 consecutive = 0;
396 start = current + 1;
397 found_start = start;
398 }
399 }
400
401 if self.next_free > 0 {
403 start = 0;
404 consecutive = 0;
405 found_start = 0;
406
407 while start + consecutive < self.next_free {
408 let current = start + consecutive;
409 if self.is_free(current) {
410 if consecutive == 0 {
411 found_start = current;
412 }
413 consecutive += 1;
414 if consecutive >= sectors_needed {
415 let extent = Extent::new(found_start, size_bytes);
416 self.mark_used(extent, sector_size);
417 return Some(extent);
418 }
419 } else {
420 consecutive = 0;
421 start = current + 1;
422 found_start = start;
423 }
424 }
425 }
426
427 None
428 }
429
430 pub fn mark_used(&mut self, extent: Extent, sector_size: u32) {
432 let end = extent.end_sector(sector_size);
433 for sector in extent.sector..end {
434 self.set_bit(sector, true);
435 }
436 if extent.sector == self.next_free {
438 self.next_free = end;
439 while self.next_free < self.total_sectors && !self.is_free(self.next_free) {
441 self.next_free += 1;
442 }
443 }
444 }
445
446 pub fn mark_free(&mut self, extent: Extent, sector_size: u32) {
448 let end = extent.end_sector(sector_size);
449 for sector in extent.sector..end {
450 self.set_bit(sector, false);
451 }
452 if extent.sector < self.next_free {
454 self.next_free = extent.sector;
455 }
456 }
457
458 #[inline]
460 pub fn is_free(&self, sector: u32) -> bool {
461 if sector >= self.total_sectors {
462 return false;
463 }
464 let byte_idx = sector as usize / 8;
465 let bit_idx = sector % 8;
466 (self.bitmap[byte_idx] & (1 << bit_idx)) == 0
467 }
468
469 #[inline]
471 pub fn is_used(&self, sector: u32) -> bool {
472 !self.is_free(sector)
473 }
474
475 #[inline]
477 pub fn total_sectors(&self) -> u32 {
478 self.total_sectors
479 }
480
481 pub fn free_sectors(&self) -> u32 {
483 let mut count = 0u32;
484 for sector in 0..self.total_sectors {
485 if self.is_free(sector) {
486 count += 1;
487 }
488 }
489 count
490 }
491
492 #[inline]
494 pub fn used_sectors(&self) -> u32 {
495 self.total_sectors - self.free_sectors()
496 }
497
498 #[inline]
500 fn set_bit(&mut self, sector: u32, used: bool) {
501 if sector >= self.total_sectors {
502 return;
503 }
504 let byte_idx = sector as usize / 8;
505 let bit_idx = sector % 8;
506 if used {
507 self.bitmap[byte_idx] |= 1 << bit_idx;
508 } else {
509 self.bitmap[byte_idx] &= !(1 << bit_idx);
510 }
511 }
512
513 pub fn reserve_initial(&mut self, sectors: u32, sector_size: u32) {
515 let extent = Extent::new(0, sectors as u64 * sector_size as u64);
516 self.mark_used(extent, sector_size);
517 }
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523
524 #[test]
525 fn test_file_layout() {
526 let file = FileLayout::new("test.txt", Extent::new(100, 1024))
527 .with_type(FileType::RegularFile)
528 .with_attributes(0x20);
529
530 assert_eq!(file.name, "test.txt");
531 assert_eq!(file.size(), 1024);
532 assert_eq!(file.extent.sector, 100);
533 }
534
535 #[test]
536 fn test_directory_layout() {
537 let mut root = DirectoryLayout::root();
538 root.add_file(FileLayout::new("file1.txt", Extent::new(100, 1024)));
539
540 let mut subdir = DirectoryLayout::new("docs");
541 subdir.add_file(FileLayout::new("readme.md", Extent::new(200, 512)));
542 root.add_subdir(subdir);
543
544 assert_eq!(root.file_count(), 1);
545 assert_eq!(root.subdir_count(), 1);
546
547 let file = root.find_file("file1.txt");
548 assert!(file.is_some());
549 assert_eq!(file.unwrap().name, "file1.txt");
550
551 let nested = root.find_file("docs/readme.md");
552 assert!(nested.is_some());
553 assert_eq!(nested.unwrap().name, "readme.md");
554 }
555
556 #[test]
557 fn test_get_or_create_dir() {
558 let mut root = DirectoryLayout::root();
559 let dir = root.get_or_create_dir("docs/api/v1");
560
561 assert_eq!(dir.name, "v1");
562 assert_eq!(root.subdirs[0].name, "docs");
563 assert_eq!(root.subdirs[0].subdirs[0].name, "api");
564 assert_eq!(root.subdirs[0].subdirs[0].subdirs[0].name, "v1");
565 }
566
567 #[test]
568 fn test_remove_file() {
569 let mut root = DirectoryLayout::root();
570 root.add_file(FileLayout::new("test.txt", Extent::new(100, 1024)));
571
572 let removed = root.remove_file("test.txt");
573 assert!(removed.is_some());
574 assert_eq!(removed.unwrap().name, "test.txt");
575 assert_eq!(root.file_count(), 0);
576 }
577
578 #[test]
579 fn path_traversal_rejects_parent_escape() {
580 let mut root = DirectoryLayout::root();
581 assert!(root.find_file("../file.txt").is_none());
582 assert!(root.remove_file("../file.txt").is_none());
583 assert_eq!(root.get_or_create_dir("../docs").name, "");
584 }
585
586 #[test]
587 fn test_allocation_map() {
588 let mut map = AllocationMap::new(100);
589 assert_eq!(map.total_sectors(), 100);
590 assert_eq!(map.free_sectors(), 100);
591
592 let extent = map.allocate(20480, 2048).unwrap();
594 assert_eq!(extent.sector, 0);
595 assert_eq!(extent.sector_count(2048), 10);
596 assert_eq!(map.free_sectors(), 90);
597
598 let extent2 = map.allocate(4096, 2048).unwrap();
600 assert_eq!(extent2.sector, 10);
601
602 map.mark_free(extent, 2048);
604 assert_eq!(map.free_sectors(), 98);
605
606 let extent3 = map.allocate(2048, 2048).unwrap();
608 assert_eq!(extent3.sector, 0);
609 }
610
611 #[test]
612 fn test_allocation_map_reserve() {
613 let mut map = AllocationMap::new(100);
614 map.reserve_initial(16, 2048); let extent = map.allocate(2048, 2048).unwrap();
617 assert_eq!(extent.sector, 16); }
619}