Skip to content
239 changes: 222 additions & 17 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use bitvec::prelude::*;
use fuser::{FileType, Filesystem, MountOption};
use log::{debug, error, info, log_enabled, Level};
use std::env;
use std::time::{SystemTime, UNIX_EPOCH};

struct NullFS;
impl Filesystem for NullFS {}
Expand All @@ -10,7 +11,7 @@ impl Filesystem for NullFS {}
// this includes the space used for the FSMetadata, free object bitmaps, and file data and metadata
const FS_SIZE_BYTES: u64 = 1u64 * (0b1 << 30) as u64; // 1 GB
const BLK_SIZE_BYTES: u64 = 4096u64;
// some blocks reserved for FSMetadata, free inode and data block bitmaps, and inode table
// 0 -> FSMetadata, 1->InodeBitmap, 2 -> Freeblock bitmap
const RESERVED_DATA_BLKS: u32 = 3;
const NUM_DATA_BLKS: u32 = (FS_SIZE_BYTES / BLK_SIZE_BYTES) as u32;
const FREE_BLK_BMAP_SIZE_BYTES: usize = ((NUM_DATA_BLKS + 7) / 8) as usize;
Expand All @@ -20,6 +21,7 @@ const MAX_NUM_INODES: u32 = 10;
const RESERVED_INODES: u32 = 2; // 0: null inode, 1: root
const FREE_INODE_BMAP_SIZE_BYTES: usize = ((MAX_NUM_INODES + 7) / 8) as usize;
const NUM_INO_DIRECT_PTR: usize = 12;
const INVALID_PTR: u32 = 0;

// free inode bitmap can begin right after this struct and inode table can follow immediately after
struct FSMetadata {
Expand Down Expand Up @@ -51,11 +53,52 @@ struct Block {
data: [u8; BLK_SIZE_BYTES as usize],
}

#[derive(Debug)]
enum BitMapError {
RestrictedEntry,
AlreadyAlloced,
AlreadyFree,
}

trait FreeObjectBitmap<const N: usize> {
fn map(&self) -> &BitArray<[u8; N], Lsb0>;
const RESERVED: usize;
const MAX: usize;

fn map(&mut self) -> &mut BitArray<[u8; N], Lsb0>;

fn find_first_free(&mut self) -> Option<usize> {
for idx in Self::RESERVED..Self::MAX {
if !self.map()[idx] {
return Some(idx);
}
}
None
}

fn set_alloc(&mut self, idx: usize) -> Result<(), BitMapError> {
if idx < Self::RESERVED || idx >= Self::MAX {
return Err(BitMapError::RestrictedEntry);
}
if self.map()[idx] == true {
error!("The index is already alloced, no change");
return Err(BitMapError::AlreadyAlloced);
} else {
self.map().set(idx, true);
Ok(())
}
}

fn find_first_free(&self) -> Option<usize> {
self.map().first_zero()
fn set_free(&mut self, idx: usize) -> Result<(), BitMapError> {
if idx < Self::RESERVED || idx >= Self::MAX {
return Err(BitMapError::RestrictedEntry);
}
if self.map()[idx] == false {
error!("The index is already free, no change");
return Err(BitMapError::AlreadyFree);
} else {
self.map().set(idx, false);
Ok(())
}
}
}

Expand All @@ -72,8 +115,10 @@ impl Default for FreeBlockBitmap {
}

impl FreeObjectBitmap<FREE_BLK_BMAP_SIZE_BYTES> for FreeBlockBitmap {
fn map(&self) -> &BitArray<[u8; FREE_BLK_BMAP_SIZE_BYTES], Lsb0> {
&self.map
const RESERVED: usize = RESERVED_DATA_BLKS as usize;
const MAX: usize = NUM_DATA_BLKS as usize;
fn map(&mut self) -> &mut BitArray<[u8; FREE_BLK_BMAP_SIZE_BYTES], Lsb0> {
&mut self.map
}
}

Expand All @@ -91,6 +136,30 @@ struct Inode {
tri_indirect_blk: u32,
}

fn secs_from_unix_epoch() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}

impl Inode {
fn new(ino_id: u64, kind: FileType, perm: u16) -> Self {
Self {
ino_id,
size: 0,
blocks: 0,
mtime_secs: secs_from_unix_epoch(),
kind,
perm,
direct_blks: [INVALID_PTR; NUM_INO_DIRECT_PTR],
indirect_blk: INVALID_PTR,
dbl_indirect_blk: INVALID_PTR,
tri_indirect_blk: INVALID_PTR,
}
}
}

struct FreeInodeBitmap {
map: BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], Lsb0>,
}
Expand All @@ -104,35 +173,46 @@ impl Default for FreeInodeBitmap {
}

impl FreeObjectBitmap<FREE_INODE_BMAP_SIZE_BYTES> for FreeInodeBitmap {
fn map(&self) -> &BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], Lsb0> {
&self.map
const RESERVED: usize = RESERVED_INODES as usize;
const MAX: usize = MAX_NUM_INODES as usize;
fn map(&mut self) -> &mut BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], Lsb0> {
&mut self.map
}
}

struct FSState {
metadata: FSMetadata,
free_inode_bitmap: FreeInodeBitmap,
inode_bitmap: FreeInodeBitmap,
inodes: [Option<Inode>; MAX_NUM_INODES as usize],
free_blk_bitmap: FreeBlockBitmap,
blk_bitmap: FreeBlockBitmap,
blks: [Option<Block>; NUM_DATA_BLKS as usize],
}

#[derive(Debug)]
enum InodeError {
NoFreeInodesOnAlloc,
InodeNotFound,
InvalidInoId,
}

impl FSState {}

// we have to implement Default ourselves here
// because the Default trait is not implemented for static arrays
// above a certain size
impl Default for FSState {
fn default() -> Self {
let mut metadata = FSMetadata::default();
let free_inode_bitmap = FreeInodeBitmap::default();
let mut inodes = [None; MAX_NUM_INODES as usize];
let free_blk_bitmap = FreeBlockBitmap::default();
let mut blks = [None; NUM_DATA_BLKS as usize];
let metadata = FSMetadata::default();
let inode_bitmap = FreeInodeBitmap::default();
let inodes = [None; MAX_NUM_INODES as usize];
let blk_bitmap = FreeBlockBitmap::default();
let blks = [None; NUM_DATA_BLKS as usize];

Self {
metadata,
free_inode_bitmap,
inode_bitmap,
inodes,
free_blk_bitmap,
blk_bitmap,
blks,
}
}
Expand All @@ -143,3 +223,128 @@ fn main() {
let mountpoint = env::args_os().nth(1).unwrap();
fuser::mount2(NullFS, mountpoint, &[MountOption::AutoUnmount]).unwrap();
}

#[cfg(test)]
mod tests {
use super::*;

// Test find_first_free
#[test]
fn test_find_first_free_returns_first_unreserved_index() {
let mut bitmap = FreeInodeBitmap::default();
assert_eq!(bitmap.find_first_free(), Some(RESERVED_INODES as usize));
}

#[test]
fn test_find_first_free_skips_allocated_indices() {
let mut bitmap = FreeInodeBitmap::default();
bitmap.map.set(2, true);
assert_eq!(bitmap.find_first_free(), Some(3));
}

#[test]
fn test_find_first_free_returns_none_when_full() {
let mut bitmap = FreeInodeBitmap::default();
bitmap.map.fill(true);
assert_eq!(bitmap.find_first_free(), None);
}

// Test set_alloc
#[test]
fn test_set_alloc_succeeds_for_valid_free_index() {
let mut bitmap = FreeInodeBitmap::default();
let idx = RESERVED_INODES as usize;
assert!(bitmap.set_alloc(idx).is_ok());
assert_eq!(bitmap.map[idx], true);
}

#[test]
fn test_set_alloc_fails_for_reserved_index() {
let mut bitmap = FreeInodeBitmap::default();
let result = bitmap.set_alloc(0);
assert!(matches!(result, Err(BitMapError::RestrictedEntry)));
}

#[test]
fn test_set_alloc_fails_for_index_beyond_max() {
let mut bitmap = FreeInodeBitmap::default();
let result = bitmap.set_alloc(MAX_NUM_INODES as usize + 1);
assert!(matches!(result, Err(BitMapError::RestrictedEntry)));
}

#[test]
fn test_set_alloc_fails_for_already_allocated_index() {
let mut bitmap = FreeInodeBitmap::default();
let idx = RESERVED_INODES as usize;
bitmap.map.set(idx, true);
let result = bitmap.set_alloc(idx);
assert!(matches!(result, Err(BitMapError::AlreadyAlloced)));
}

// Test set_free
#[test]
fn test_set_free_succeeds_for_valid_allocated_index() {
let mut bitmap = FreeInodeBitmap::default();
let idx = RESERVED_INODES as usize;
bitmap.map.set(idx, true); // First allocate it
assert!(bitmap.set_free(idx).is_ok());
assert_eq!(bitmap.map[idx], false);
}

#[test]
fn test_set_free_fails_for_reserved_index() {
let mut bitmap = FreeInodeBitmap::default();
let result = bitmap.set_free(0);
assert!(matches!(result, Err(BitMapError::RestrictedEntry)));
assert_eq!(bitmap.map[0], true)
}

#[test]
fn test_set_free_fails_for_index_beyond_max() {
let mut bitmap = FreeInodeBitmap::default();
let result = bitmap.set_free(MAX_NUM_INODES as usize + 1);
assert!(matches!(result, Err(BitMapError::RestrictedEntry)));
}

#[test]
fn test_set_free_fails_for_already_free_index() {
let mut bitmap = FreeInodeBitmap::default();
let idx = RESERVED_INODES as usize;
let result = bitmap.set_free(idx);
assert!(matches!(result, Err(BitMapError::AlreadyFree)));
}

// Test with FreeBlockBitmap to ensure trait works for both implementations
#[test]
fn test_free_block_bitmap_find_first_free() {
let mut bitmap = FreeBlockBitmap::default();
assert_eq!(bitmap.find_first_free(), Some(RESERVED_DATA_BLKS as usize));
}

#[test]
fn test_free_block_bitmap_set_alloc_and_free() {
let mut bitmap = FreeBlockBitmap::default();
let idx = RESERVED_DATA_BLKS as usize;

// Allocate
assert!(bitmap.set_alloc(idx).is_ok());
assert_eq!(bitmap.map[idx], true);

// Free
assert!(bitmap.set_free(idx).is_ok());
assert_eq!(bitmap.map[idx], false);
}

#[test]
fn test_free_block_bitmap_max() {
let mut bitmap = FreeBlockBitmap::default();
let idx = NUM_DATA_BLKS as usize;
let idx2 = 4 as usize;

assert!(bitmap.set_alloc(idx2).is_ok());
assert_eq!(bitmap.map[idx2], true);

let result = bitmap.set_alloc(idx);
assert!(matches!(result, Err(BitMapError::RestrictedEntry)));
}
}