A small ext2-style filesystem built on top of an in-memory disk simulator, intended as a learning vehicle for filesystem internals (super block, group descriptors, inode tables, block & inode bitmaps, direct/indirect blocks, directory entries, etc.).
Note: This is an educational toy — it is not binary-compatible with the real Linux ext2 driver. In particular,
inode.blockscounts data blocks (not 512-byte sectors as real ext2 does), and the dump/debug commands assume the simulator's flat memory layout.
| Field | Value |
|---|---|
| Total volume size | 2,147,487,744 B (2 GB) |
| Sector size | 1024 B |
| Block size | 2048 B (= 2 sectors) |
| Total sectors | 2,097,152 + 4 |
| Total blocks | 1,048,578 |
| Block groups | 64 |
| Blocks per group | 16,384 |
| Total inodes | 1,024 |
| Inodes per group | 16 |
| Reserved inodes | 11 (in group 0) |
| Inode size | 128 B |
| Volume label | EXT2 BY NC |
Per-group on-disk layout (each group keeps its own copy for redundancy):
+----+----+----+----+-------------------+--------------------------+
| SB | GD | BBM| IBM| Inode table | Data blocks |
+----+----+----+----+-------------------+--------------------------+
1 1 1 1 13 16367 blocks
Boot block (1 block) sits at the very start of the volume, before group 0.
make # builds ./shell
./shell # launches the interactive filesystem shell
make clean # removes object files and the binaryBuilds cleanly with modern clang/gcc (tested on macOS Darwin 25 with
clang 16+). The Makefile relaxes a few legacy warnings to errors so the
codebase compiles on stricter toolchains; see Makefile for
the exact flag set.
After launching ./shell, you must format then mount before any other
operation. The prompt shows nc22-ext2 : [/<currentDir>]#.
| Command | Effect |
|---|---|
format [label] |
Initialize the disk as ext2 with optional label |
mount |
Read the super block / GDT and prepare for I/O |
umount |
Release in-memory FS state |
exit / quit |
Tear down disk and exit |
| Command | Effect |
|---|---|
ls |
List entries in current directory |
touch <name> |
Create an empty file |
fill <name> <size> -c|-a |
Create (-c) or append (-a) bytes |
cat <name> |
Print file contents to stdout |
rm <name> |
Remove a file |
| Command | Effect |
|---|---|
cd [name] |
Change directory (., .., or a child) |
mkdir <name> |
Create a directory |
rmdir <name> |
Remove an empty directory |
mkdirst <count> |
Create directories named 0 through count-1 (testing) |
| Command | Effect |
|---|---|
df |
Show free / used block counts |
dumpsuperblock |
Hex-dump the super block (block 1) |
dumpgd |
Hex-dump the group descriptor table |
dumpblockbitmap |
Hex-dump the block bitmap |
dumpinodebitmap |
Hex-dump the inode bitmap |
dumpinodetable |
Hex-dump the first two inode-table blocks |
dumpdatablockbyname <n> |
Hex-dump the data block of a named entry |
dumpdatablockbynum <i> |
Hex-dump data block at index i |
dumpfileinode <n> |
Print inode number + 15 block pointers |
Files use the legacy 8.3 short-name format: up to 8 characters before the
optional ., up to 3 after. Letters are uppercased. Allowed characters are
A–Z, a–z, 0–9, and .. Names like .bashrc, valid_2.txt,
nine99999.txt, or BIGNAME.LONGEXT are rejected as invalid.
| File | Role |
|---|---|
shell.c |
Interactive REPL, command dispatch, argument parsing |
shell.h |
SHELL_ENTRY, SHELL_FS_OPERATIONS, list helpers |
ext2.c |
Core filesystem logic — format, lookup, allocation, read/write, indirect blocks |
ext2.h |
On-disk structs (super block, group descriptor, inode, directory entry) and forward declarations |
ext2_shell.c |
Glue between the generic SHELL_* interface and ext2_* operations |
ext2_shell.h |
Glue header |
disksim.c |
In-memory disk simulator (read_sector/write_sector) |
disksim.h |
Disk simulator header |
disk.h |
DISK_OPERATIONS interface |
entrylist.c |
Linked list used by ls to accumulate directory entries |
common.h |
Logging macros, EXT2_SUCCESS/EXT2_ERROR |
types.h |
BYTE, WORD, DWORD, QWORD aliases |
-
fs->gdis a cache for group 0's descriptor only. Volume-wide free counts are kept infs->sb(and synchronised across all on-disk SB copies on every allocation/free). Locality decisions insideget_available_data_blockread the GD table fresh from disk so they see the right group's state. -
location.blockalways stores an absolute block number. Bothfind_entry_on_rootandfind_entry_on_datause this convention so that callers likeinsert_entrycan compare directly againstinode.block[i]. The data_read/block_read math is invariant to the (group, block-relative) vs (0, absolute) representation, so this normalization is safe. -
block_read/block_writeoperate on whole blocks (2 sectors at a time) regardless of the underlying disk sector size. Themeta_*anddata_*helpers translate (group, block) into absolute sector indices, always reserving one block for the boot sector at the start of the volume. -
Directory entries are fixed 32 bytes (see
EXT2_DIR_ENTRYinext2.h), so each block holds exactly 64 entries. Growing a directory past 64 entries triggersexpand_block, which threads the new block through the inode's direct, indirect, double-indirect, or triple-indirect pointers as appropriate.
This repository started as a college lab project. A polish/bug-fix pass
re-shaped it into a build-clean, runnable, modern-toolchain version. See
CHANGES.md for the full list — highlights:
- Fixed a stack overflow in
process_meta_data_for_block_freethat segfaulted everyrmandrmdir. - Fixed two
&retEntry->fstypos (passing pointer-to-pointer where a pointer was expected). - Fixed a
set_entrythat returnedEXT2_ERRORon success. - Fixed the
&& FILE_TYPE_DIRalways-true branch that madeext2_removereject every file as if it were a directory. - Fixed
ext2_write's seek loop, which doubledblockSizeinstead of addingMAX_BLOCK_SIZE— large writes landed in the wrong block. - Fixed
find_entry_on_datareading 32 bytes past its buffer when a directory grew past one block. - Fixed
format_namewriting pastregularName[10]and matching.bashrcas if it were the.self-link. - Fixed
disksim_initchecking the wrong pointer for NULL after the secondmallocand leaking a 2 GB buffer indisksim_uninit. - Made the build clean on modern
clang(forward declarations, ctype.h, 64-bit-safe pointer formatting in dump output). - Added input validation to
fill,dumpfileinode,dumpdatablockbyname,dumpdatablockbynum, andcdso malformed invocations no longer segfault. - Replaced the original mojibaked Korean prompt placeholder (
Çйø :) with a meaningful identifier (nc22-ext2 :).