From 28a702904aaafd7932711f3f1ad632732975e403 Mon Sep 17 00:00:00 2001 From: Sean Donaghy Date: Wed, 24 Dec 2025 13:18:20 -0800 Subject: [PATCH 1/6] refactors of structs --- src/main.rs | 110 ++++++++++++++++++++-------------------------------- 1 file changed, 41 insertions(+), 69 deletions(-) diff --git a/src/main.rs b/src/main.rs index 79ef653..685031c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,34 +6,36 @@ use bitvec::prelude::*; struct NullFS; impl Filesystem for NullFS {} -const BLK_SIZE: usize = 4096; -const FS_SIZE: usize = 512usize * (1usize << 30); -const NUM_DATA_BLKS: usize = FS_SIZE / BLK_SIZE; - -const DATA_BLK_BITMAP_BYTES: usize = (NUM_DATA_BLKS + 7) / 8; -const NUM_INODES: usize = -const BITMAP_SIZE_BYTES: usize = 4096; -const NUM_DIRECT_PTR: usize = 12; - -// 28 bytes starting at offset 0 -// free inode bitmap can begin at offset 28 and inode table can follow immediately after +// This is the total capacity of the backing storage for the file system +// this includes the space used for the superblock, free object bitmaps, and file data and metadata +const FS_SIZE_BYTES: u64 = 1u64 * (0b1 << 30) as u64; // 1 GB, toy size +const BLK_SIZE_BYTES: u64 = 4096u64; +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; + +// Inodes +const MAX_NUM_INODES: u32 = 10; // toy size +const FREE_INODE_BMAP_SIZE_BYTES: usize = (MAX_NUM_INODES + 7) / 8 as usize; +const NUM_INO_DIRECT_PTR: u8 = 12; + +// free inode bitmap can begin right after this struct and inode table can follow immediately after struct SuperBlock { ino_count: u32, blk_count: u32, free_blk_count: u32, free_ino_count: u32, super_blk_no: u32, - mtime: u32, - wtime: u32, + mtime: u64, + wtime: u64, } impl Default for SuperBlock { fn default() -> Self { Self { - ino_count: 0, - blk_count: NUM_DATA_BLKS as u32, - free_blk_count: NUM_DATA_BLKS as u32, - free_ino_count: 0, + ino_count: MAX_NUM_INODES, + blk_count: NUM_DATA_BLKS, + free_blk_count: NUM_DATA_BLKS - 3u32, // first three blocks are reserved for FS metadata + free_ino_count: MAX_NUM_INODES - 1, // first inode is reserved for the root super_blk_no: 0, mtime: 0, wtime: 0, @@ -41,36 +43,30 @@ impl Default for SuperBlock { } } -trait FreeObjectBitmap { - fn map(&self) -> &BitArray<[u8; N], Lsb0>; +trait FreeObjectBitmap { + const N: usize; + + fn map(&self) -> &BitArray<[u8; Self::N], Lsb0>; fn find_first_free(&self) -> Option { self.map().first_zero() } } +#[derive(Default)] struct FreeBlockBitmap { - map: BitArray<[u8; DATA_BLK_BITMAP_BYTES], Lsb0>, -} - -impl Default for FreeBlockBitmap { - fn default() -> Self { - Self { map: BitArray::ZERO } - } + map: BitArray<[u8; FREE_BLK_BMAP_SIZE_BYTES], Lsb0>, } -impl FreeBlockBitmap { - fn new() -> Self { - Self { map: BitArray::ZERO } - } -} +impl FreeObjectBitmap for FreeBlockBitmap { + const N: usize = FREE_BLK_BMAP_SIZE_BYTES; -impl FreeObjectBitmap for FreeBlockBitmap { - fn map(&self) -> &BitArray<[u8; DATA_BLK_BITMAP_BYTES], Lsb0> { + fn map(&self) -> &BitArray<[u8; Self::N], Lsb0> { &self.map } } + struct Inode { ino_id: u64, // inode number size: u64, // file size @@ -78,62 +74,38 @@ struct Inode { mtime_secs: i64, // Easier to save to disk than SystemTime. Ignored the atime and ctime for now. kind: FileType, perm: u16, - direct_blks: [u32; NUM_DIRECT_PTR], + direct_blks: [u32; NUM_INO_DIRECT_PTR], indirect_blk: u32, dbl_indirect_blk: u32, tri_indirect_blk: u32, } -struct InodeBitmap { - map: BitArray<[u8; BITMAP_SIZE_BYTES], Lsb0>, +#[derive(Default)] +struct FreeInodeBitmap { + map: BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], Lsb0>, } -impl Default for InodeBitmap { - fn default() -> Self { - Self { map: BitArray::ZERO } - } -} - -impl InodeBitmap { - fn new() -> Self { - Self { map: BitArray::ZERO } - } -} +impl FreeObjectBitmap for FreeInodeBitmap { + const N: usize = FREE_INODE_BMAP_SIZE_BYTES; -impl FreeObjectBitmap for InodeBitmap { - fn map(&self) -> &BitArray<[u8; BITMAP_SIZE_BYTES], Lsb0> { + fn map(&self) -> &BitArray<[u8; Self::N], Lsb0> { &self.map } } +#[derive(Default)] struct FSState { - superblock: SuperBlock, - free_blocks: FreeBlockBitmap, - inode_bitmap: InodeBitmap, + superblk: SuperBlock, + free_blks: FreeBlockBitmap, + inode_bitmap: FreeInodeBitmap, inodes: Vec, blks: Vec, } -impl Default for FSState { - fn default() -> Self { - Self { - // TODO: Fix - superblock: SuperBlock::default(), - free_blocks: FreeBlockBitmap::default(), - inode_bitmap: InodeBitmap::default(), - inodes: Vec::default(), - blks: Vec::default(), - } - } -} - impl FSState { - fn new() -> Self { - Self::default() - } fn alloc_inode(&mut self) -> Option { - // TODO: + None } } From 14707cb92d7a816b2981719a855dc039be55887d Mon Sep 17 00:00:00 2001 From: Sean Donaghy Date: Wed, 24 Dec 2025 13:38:27 -0800 Subject: [PATCH 2/6] code compiles and main func runs --- README.md | 6 ++++++ src/main.rs | 23 ++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7a4d2c3..891980e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # Rusty-File-System- +## To Run +``` +sudo mkdir /tmp/nullfs +RUST_LOG=info cargo run -- /tmp/nullfs +``` + ## System Dependencies - fuse3 - libfuse3-dev diff --git a/src/main.rs b/src/main.rs index 685031c..f5bb8fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use fuser::{Filesystem, MountOption, FileType}; +use log::{debug, error, log_enabled, info, Level}; use std::env; use bitvec::prelude::*; @@ -11,12 +12,12 @@ impl Filesystem for NullFS {} const FS_SIZE_BYTES: u64 = 1u64 * (0b1 << 30) as u64; // 1 GB, toy size const BLK_SIZE_BYTES: u64 = 4096u64; 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; +const FREE_BLK_BMAP_SIZE_BYTES: usize = ((NUM_DATA_BLKS + 7) / 8) as usize; // Inodes const MAX_NUM_INODES: u32 = 10; // toy size -const FREE_INODE_BMAP_SIZE_BYTES: usize = (MAX_NUM_INODES + 7) / 8 as usize; -const NUM_INO_DIRECT_PTR: u8 = 12; +const FREE_INODE_BMAP_SIZE_BYTES: usize = ((MAX_NUM_INODES + 7) / 8) as usize; +const NUM_INO_DIRECT_PTR: usize = 12; // free inode bitmap can begin right after this struct and inode table can follow immediately after struct SuperBlock { @@ -43,10 +44,8 @@ impl Default for SuperBlock { } } -trait FreeObjectBitmap { - const N: usize; - - fn map(&self) -> &BitArray<[u8; Self::N], Lsb0>; +trait FreeObjectBitmap { + fn map(&self) -> &BitArray<[u8; N], Lsb0>; fn find_first_free(&self) -> Option { self.map().first_zero() @@ -58,10 +57,9 @@ struct FreeBlockBitmap { map: BitArray<[u8; FREE_BLK_BMAP_SIZE_BYTES], Lsb0>, } -impl FreeObjectBitmap for FreeBlockBitmap { - const N: usize = FREE_BLK_BMAP_SIZE_BYTES; +impl FreeObjectBitmap for FreeBlockBitmap { - fn map(&self) -> &BitArray<[u8; Self::N], Lsb0> { + fn map(&self) -> &BitArray<[u8; FREE_BLK_BMAP_SIZE_BYTES], Lsb0> { &self.map } } @@ -85,10 +83,9 @@ struct FreeInodeBitmap { map: BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], Lsb0>, } -impl FreeObjectBitmap for FreeInodeBitmap { - const N: usize = FREE_INODE_BMAP_SIZE_BYTES; +impl FreeObjectBitmap for FreeInodeBitmap { - fn map(&self) -> &BitArray<[u8; Self::N], Lsb0> { + fn map(&self) -> &BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], Lsb0> { &self.map } } From 5fc7b2236dae7342decb415b972deef222473cfa Mon Sep 17 00:00:00 2001 From: Sean Donaghy Date: Wed, 24 Dec 2025 13:45:45 -0800 Subject: [PATCH 3/6] add CI to run cargo test --- .github/workflows/tests.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..ffb0548 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,16 @@ +name: Tests + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + - run: cargo test From 847a5a12f8b9d8b6add087c04eb3299842f2c267 Mon Sep 17 00:00:00 2001 From: Sean Donaghy Date: Wed, 24 Dec 2025 13:47:58 -0800 Subject: [PATCH 4/6] add system deps --- .github/workflows/tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ffb0548..e3031dc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,4 +13,10 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v5 - - run: cargo test + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y fuse3 libfuse3-dev pkg-config + + - run: cargo test \ No newline at end of file From d8f02be4a43288a7754634811594a6f2a530710a Mon Sep 17 00:00:00 2001 From: Sean Donaghy Date: Wed, 24 Dec 2025 15:15:01 -0800 Subject: [PATCH 5/6] add integration test to mount fs and check for errors --- Cargo.lock | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +++ tests/mount.rs | 27 ++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 tests/mount.rs diff --git a/Cargo.lock b/Cargo.lock index cced1a2..4ac0cf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,12 @@ dependencies = [ "log", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "funty" version = "2.0.0" @@ -238,6 +244,43 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "regex" version = "1.12.2" @@ -267,6 +310,15 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rusty-file-system" version = "0.1.0" @@ -275,6 +327,7 @@ dependencies = [ "env_logger", "fuser", "log", + "tempdir", ] [[package]] @@ -320,6 +373,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand", + "remove_dir_all", +] + [[package]] name = "unicode-ident" version = "1.0.22" diff --git a/Cargo.toml b/Cargo.toml index 8b5000a..31d6784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ env_logger = "0.11" log = "0.4" bitvec = "1.0.1" +[dev-dependencies] +tempdir = "0.3.7" + [[bin]] name = "rusty-file-system" path = "src/main.rs" diff --git a/tests/mount.rs b/tests/mount.rs new file mode 100644 index 0000000..c9fbe56 --- /dev/null +++ b/tests/mount.rs @@ -0,0 +1,27 @@ +use std::process::{Command, Stdio}; +use std::thread; +use std::time::Duration; +use tempdir::TempDir; + +#[test] +fn mount() { + + let tmp_dir = TempDir::new("testdir").unwrap(); + let mountpoint = tmp_dir.path(); + + let mut child = Command::new(env!("CARGO_BIN_EXE_rusty-file-system")) + .arg(mountpoint) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to mount filesystem"); + + thread::sleep(Duration::from_secs(3)); + + child.kill().ok(); + let status = child.wait().unwrap(); + assert!( + !status.success(), + "filesystem exited unexpectedly" + ); +} From f3a32d41e1d8132c2280bba81de7e39be6fbce90 Mon Sep 17 00:00:00 2001 From: Sean Donaghy Date: Wed, 24 Dec 2025 15:20:20 -0800 Subject: [PATCH 6/6] formatting --- .github/workflows/tests.yml | 3 +++ src/main.rs | 35 +++++++++++++++-------------------- tests/mount.rs | 18 +++++++----------- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e3031dc..bfaf1ae 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,4 +19,7 @@ jobs: sudo apt-get update sudo apt-get install -y fuse3 libfuse3-dev pkg-config + - name: Check Formatting + run: cargo fmt --all -- --check + - run: cargo test \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f5bb8fd..c260761 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ -use fuser::{Filesystem, MountOption, FileType}; -use log::{debug, error, log_enabled, info, Level}; -use std::env; use bitvec::prelude::*; - +use fuser::{FileType, Filesystem, MountOption}; +use log::{debug, error, info, log_enabled, Level}; +use std::env; struct NullFS; impl Filesystem for NullFS {} @@ -35,8 +34,8 @@ impl Default for SuperBlock { Self { ino_count: MAX_NUM_INODES, blk_count: NUM_DATA_BLKS, - free_blk_count: NUM_DATA_BLKS - 3u32, // first three blocks are reserved for FS metadata - free_ino_count: MAX_NUM_INODES - 1, // first inode is reserved for the root + free_blk_count: NUM_DATA_BLKS - 3u32, // first three blocks are reserved for FS metadata + free_ino_count: MAX_NUM_INODES - 1, // first inode is reserved for the root super_blk_no: 0, mtime: 0, wtime: 0, @@ -58,24 +57,22 @@ struct FreeBlockBitmap { } impl FreeObjectBitmap for FreeBlockBitmap { - fn map(&self) -> &BitArray<[u8; FREE_BLK_BMAP_SIZE_BYTES], Lsb0> { &self.map } } - struct Inode { - ino_id: u64, // inode number - size: u64, // file size - blocks: u64, // num blocks allocated - mtime_secs: i64, // Easier to save to disk than SystemTime. Ignored the atime and ctime for now. - kind: FileType, - perm: u16, - direct_blks: [u32; NUM_INO_DIRECT_PTR], + ino_id: u64, // inode number + size: u64, // file size + blocks: u64, // num blocks allocated + mtime_secs: i64, // Easier to save to disk than SystemTime. Ignored the atime and ctime for now. + kind: FileType, + perm: u16, + direct_blks: [u32; NUM_INO_DIRECT_PTR], indirect_blk: u32, dbl_indirect_blk: u32, - tri_indirect_blk: u32, + tri_indirect_blk: u32, } #[derive(Default)] @@ -84,7 +81,6 @@ struct FreeInodeBitmap { } impl FreeObjectBitmap for FreeInodeBitmap { - fn map(&self) -> &BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], Lsb0> { &self.map } @@ -96,11 +92,10 @@ struct FSState { free_blks: FreeBlockBitmap, inode_bitmap: FreeInodeBitmap, inodes: Vec, - blks: Vec, + blks: Vec, } impl FSState { - fn alloc_inode(&mut self) -> Option { None } @@ -110,4 +105,4 @@ fn main() { env_logger::init(); let mountpoint = env::args_os().nth(1).unwrap(); fuser::mount2(NullFS, mountpoint, &[MountOption::AutoUnmount]).unwrap(); -} \ No newline at end of file +} diff --git a/tests/mount.rs b/tests/mount.rs index c9fbe56..f51940e 100644 --- a/tests/mount.rs +++ b/tests/mount.rs @@ -5,23 +5,19 @@ use tempdir::TempDir; #[test] fn mount() { - let tmp_dir = TempDir::new("testdir").unwrap(); let mountpoint = tmp_dir.path(); - + let mut child = Command::new(env!("CARGO_BIN_EXE_rusty-file-system")) - .arg(mountpoint) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to mount filesystem"); + .arg(mountpoint) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to mount filesystem"); thread::sleep(Duration::from_secs(3)); child.kill().ok(); let status = child.wait().unwrap(); - assert!( - !status.success(), - "filesystem exited unexpectedly" - ); + assert!(!status.success(), "filesystem exited unexpectedly"); }