Author: Hanna Abrahem
A user-space inode-based virtual filesystem implemented in C/C++. The filesystem is stored in a single binary disk image file and is accessed through a custom shell and a VFS API (libvfs.so).
The disk image is divided into fixed regions, each exactly one or more 512-byte blocks:
Block 0 : Superblock
Block 1 : Inode bitmap (1 bit per inode, 1 = allocated)
Block 2 : Data bitmap (1 bit per data block, 1 = allocated)
Blocks 3..N-1 : Inode table (32 inodes max, each with 10 direct + 1 indirect block pointer)
Blocks N..end : Data region (file contents + directory entries)
Directory entries on disk use the disk_t struct (24-byte name, inode index, used flag). Each directory inode points to one data block holding an array of disk_t entries (up to 16 entries per 512-byte block).
In memory, the filesystem maintains:
superblock*— metadata about block layoutinode_table[]— all inode structs loaded into RAMinode_bitmap[]/data_bitmap[]— allocation bitmapsopen_file_table[]— runtime open file descriptors (up to 32)fs_root— vnode for the root directory/
makeThis builds:
libvfs.so— shared library with the VFS APIshell— interactive filesystem shellformat— disk image formatterprint_disk— disk debugging tool- All test binaries under
test/
Before running the shell or any tests, a disk image must be formatted:
./format DISK -s 1 # creates a 1 MB disk image named DISK
./format DISK -s 4 # creates a 4 MB disk imageThis writes the superblock, bitmaps, inode table, and zeroed data region to the file. Root inode (inode 0) and root data block (block 0) are pre-allocated.
./shellThe shell loads the DISK image on startup. The following commands are supported:
| Command | Description |
|---|---|
ls |
List all files and directories in the current directory |
mkdir <name> |
Create a new directory |
rmdir <name> |
Remove an empty directory |
touch <name> |
Create an empty file |
write <name> <text> |
Write text to a file (truncates first) |
cat <name> |
Print the contents of a file |
save [name] |
Save (flush) the filesystem to disk (default: DISK-save) |
exit |
Exit the shell |
Any unrecognized command is passed to the OS via fork/execvp.
- Initializes a fresh disk image of configurable size
- Writes superblock, inode bitmap, data bitmap, inode table, and zeroed data region
- Pre-allocates root inode (inode 0) and root data block (block 0)
- Loads superblock, bitmaps, and inode table from disk into memory
- Creates the in-memory
fs_rootvnode pointing to inode 0 - Initializes the open-file table
- Scans parent directory's data block for the filename
- Allocates a free inode from the inode bitmap
- Writes a new
disk_tentry into the parent directory block - Flushes updated bitmaps, inode table, and directory block to disk immediately
- Returns a virtual file descriptor (
vfd_t) intoopen_file_table
- Frees all existing data blocks in the inode
- Resets file size and block count to 0
- Writes bytes at current file offset (tracked per open descriptor)
- Allocates new 512-byte data blocks on demand as content grows
- Supports files up to 10 blocks (5120 bytes) via direct block pointers
- Updates file size in inode if write extends end of file
- Flushes data bitmap and inode table to disk after every write
- Reads from current file offset, block by block
- Clamps read to actual file size (no reading past EOF)
- Advances file offset after each read
- Frees the in-memory vnode and open-file table slot
- Flushes disk buffers
- Opens a directory as a descriptor in
open_file_table f_readdirsteps throughdisk_tentries in the directory's data block, skipping unused slots- Returns name, inode number, type, and size for each entry
- Allocates a new inode and a new data block for the directory
- Zeroes the directory's data block on disk
- Adds an entry to the parent directory
- Flushes all metadata to disk
- Verifies directory exists and is empty
- Frees inode and data block bits
- Removes entry from parent directory
- Flushes all metadata to disk
- Scans all direct block directory entries of a given directory vnode
- Returns a freshly allocated vnode if found,
NULLwithENOENTotherwise
- Flushes in-memory superblock, bitmaps, and inode table to the current disk image
- Optionally copies the entire disk image to a new file if a different name is provided
- Closes disk file handle
- Frees all heap-allocated filesystem state (superblock, bitmaps, inode table,
fs_root)
- Standalone tool to dump the disk image without mounting it
- Prints superblock fields, allocated inode/data bitmaps, and inode table metadata
.
├── format.c # Disk formatter
├── vfs.c # Core VFS implementation
├── vfs.h # VFS API and struct definitions
├── inode.h # Superblock and inode struct definitions
├── shell.c # Interactive shell
├── libparser.c/h # Command-line parser
├── print_disk.c # Disk image debugging tool
├── Makefile
└── test/
├── test_open.c
├── test_write.c
├── test_read.c
├── test_save.c
├── test_find.c
├── test_fileexist.c
├── create.c
└── create-eexist.c
Each test binary operates on the DISK image. Make sure a freshly formatted disk exists before running:
./format DISK
./test/test_open
./test/test_write
./test/test_read
./test/test_save
./test/test_find
./test/create-eexist