Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Tests

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5

- name: Install system dependencies
run: |
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
63 changes: 63 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
124 changes: 44 additions & 80 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
use fuser::{Filesystem, MountOption, FileType};
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 {}

const BLK_SIZE: usize = 4096;
const FS_SIZE: usize = 512usize * (1usize << 30);
const NUM_DATA_BLKS: usize = FS_SIZE / BLK_SIZE;
// 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;

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;
// 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: usize = 12;

// 28 bytes starting at offset 0
// free inode bitmap can begin at offset 28 and inode table can follow immediately after
// 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,
Expand All @@ -49,96 +51,58 @@ trait FreeObjectBitmap<const N: usize> {
}
}

#[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<DATA_BLK_BITMAP_BYTES> for FreeBlockBitmap {
fn map(&self) -> &BitArray<[u8; DATA_BLK_BITMAP_BYTES], Lsb0> {
impl FreeObjectBitmap<FREE_BLK_BMAP_SIZE_BYTES> 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_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,
}

struct InodeBitmap {
map: BitArray<[u8; BITMAP_SIZE_BYTES], Lsb0>,
}

impl Default for InodeBitmap {
fn default() -> Self {
Self { map: BitArray::ZERO }
}
tri_indirect_blk: u32,
}

impl InodeBitmap {
fn new() -> Self {
Self { map: BitArray::ZERO }
}
#[derive(Default)]
struct FreeInodeBitmap {
map: BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], Lsb0>,
}

impl FreeObjectBitmap<BITMAP_SIZE_BYTES> for InodeBitmap {
fn map(&self) -> &BitArray<[u8; BITMAP_SIZE_BYTES], Lsb0> {
impl FreeObjectBitmap<FREE_INODE_BMAP_SIZE_BYTES> for FreeInodeBitmap {
fn map(&self) -> &BitArray<[u8; FREE_INODE_BMAP_SIZE_BYTES], 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<Inode>,
blks: Vec<u8>,
}

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(),
}
}
blks: Vec<u8>,
}

impl FSState {
fn new() -> Self {
Self::default()
}

fn alloc_inode(&mut self) -> Option<u64> {
// TODO:
None
}
}

fn main() {
env_logger::init();
let mountpoint = env::args_os().nth(1).unwrap();
fuser::mount2(NullFS, mountpoint, &[MountOption::AutoUnmount]).unwrap();
}
}
23 changes: 23 additions & 0 deletions tests/mount.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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");
}