Skip to content
Marwan-NegmPublic

About

A user-space filesystem in C with bitmap allocation, FAT chains, persistent disk images, and an interactive architecture explorer.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

UserFS

A user-space filesystem in C, built around one persistent disk image.

CI Language: C11 Platform: Linux Interactive explorer

Open the interactive explorer →

Architecture · Public API · Presentation · Getting started

UserFS turns a raw 1 GiB disk image into a hierarchical store of files and directories. It combines a free-space bitmap, a global File Allocation Table (FAT), and fixed-size directory entries with a small, POSIX-inspired API.

The project makes filesystem internals inspectable: a name resolves to a directory record, the record points to a chain of blocks, and each block maps to an explicit offset in the image. All persistent state lives inside that one host file.

Created by Marwan Yasser Negm, as part of Sudo Team / STM training 2026.

Explore it in your browser

The interactive website includes:

  • A nine-step walkthrough of format, directory creation, file writes, growth, shrink, block reuse, remount, and deletion.
  • A command lab with a live directory tree, allocation map, entry inspector, and FAT chains.
  • Selectable architecture layers, disk-region explanations, and a guide to all 15 public functions.
  • Direct access to the original 77-slide presentation, architecture diagram, and C source.

The website is a JavaScript teaching model, not a C runtime, FUSE mount, or disk-image editor. Its files are limited to 64 KiB, and its state resets when the page reloads. Real persistence is exercised by the C integration suites.

What it implements

  • Nested directories and regular files, with absolute-path traversal.
  • A fixed 4 KiB block size and 64-byte directory records; no inode table.
  • Next-fit allocation, FAT-linked file and directory growth, and freed-block reuse.
  • Independent file offsets, append mode, seek, read/write, and truncate.
  • Zero-filled file growth, zeroed reused blocks, and cleared truncated tails.
  • Metadata flushing and fsync at successful mutation boundaries.
  • Allocation validation, bounded FAT traversal, and cycle detection.

Getting started

Requirements

  • Linux, including Debian under WSL on Windows.
  • A C11 compiler, GNU Make, and ar (provided by build-essential on Debian/Ubuntu).
  • AddressSanitizer and UndefinedBehaviorSanitizer support for make sanitize.

The library uses pread, pwrite, ftruncate, and fsync; native Windows/MinGW is not the supported build environment. Tests create temporary images with a logical size of 1 GiB. Actual host allocation may be sparse.

git clone https://github.com/Marwan-Negm/userfs.git
cd userfs

make             # Build libuserfs.a
make test        # Run both C integration suites
make sanitize    # Run both suites under ASan + UBSan
make demo        # Write, unmount, remount, and verify a message

The example uses a unique temporary image and removes it afterward. Expected output:

Read after remount: Hello from UserFS!
Persistence verified: the same bytes survived unmount and mount.

See examples/quickstart.c for the complete error-checked program. Link your own application with:

cc -std=c11 -D_FILE_OFFSET_BITS=64 -I. your_program.c libuserfs.a -o your_program

Formatting erases the target image. Call ufs_format only when creating or intentionally resetting an image; use ufs_mount to reopen an existing filesystem.

Original command-line walkthrough

make tools        # Build the nine small utilities in build/
make walkthrough  # Run tests/demo.sh using build/disk.img

make walkthrough reformats build/disk.img on every run. The utilities mount that image, perform their operation, and unmount. For example, after the walkthrough has initialized it:

cd build
./make_dir /notes
./create_file /notes/hello.txt
./write_file /notes/hello.txt "Hello, UserFS!"
./read_file /notes/hello.txt
./file_stat /notes/hello.txt
./list_dir /notes

Architecture

flowchart TD
    A[Application / tests] --> B[userfs.h — public API]
    B --> C[Path resolver and directory entries]
    B --> D[Open descriptor table — runtime only]
    C --> E[Bitmap allocator and FAT chains]
    D --> E
    E --> F[pread / pwrite / fsync]
    F --> G[One persistent 1 GiB disk image]
Loading

On-disk layout

Region Absolute blocks Size Responsibility
Superblock 0 4 KiB Magic, version, geometry, region pointers
Allocation bitmap 1–8 32 KiB One allocation bit per absolute disk block
Global FAT 9–264 1 MiB One 32-bit next-block value per block
Data region 265–262143 1,072,656,384 bytes Directory entries and file payloads

There are 262,144 blocks. The root directory occupies block 265, leaving 261,878 initially free data blocks. FAT values are absolute block indexes: 0 means free, and 0xFFFFFFFF ends a chain.

Metadata without inodes

Each object stores its metadata directly in its parent directory:

struct disk_dir_entry {
    char filename[48];          // Up to 47 bytes + terminator
    uint32_t size;              // Exact regular-file size
    uint32_t first_block_index; // First absolute block in the FAT chain
    uint8_t is_directory;
    uint8_t is_used;
    uint8_t reserved[6];
};                             // 64 bytes; 64 entries per block

An empty file uses size = 0 and first_block_index = 0. Every directory owns at least one block. When its entries fill a block, the directory grows through its FAT chain; removed slots are reused before further growth.

Read the full design · View the original architecture diagram

Public API

Full declarations, flags, and public structures are in userfs.h.

Group Functions
Image lifecycle ufs_format, ufs_mount, ufs_unmount
Directories ufs_mkdir, ufs_rmdir, ufs_listdir
File lifecycle ufs_create, ufs_unlink
Descriptors ufs_open, ufs_close
Data access ufs_read, ufs_write, ufs_seek, ufs_truncate
Metadata ufs_stat

Operations return -1 on failure and set errno. Successful reads and writes return a byte count; seek returns the new offset; listdir returns the number of entries copied, capped by the provided capacity. Open returns a library descriptor. Other successful operations return 0.

Open flags are UFS_O_RDONLY, UFS_O_WRONLY, or UFS_O_RDWR, optionally combined with UFS_O_APPEND. Opening does not create or truncate a file. Append re-reads the file size for every write.

Scope and limits

Property Contract
Execution Single process, one mounted image; no thread synchronization or multi-process locking
Image size Exactly 1 GiB
Path length At most 255 bytes; absolute paths only
Component length At most 47 bytes; . and .. are rejected
Open descriptors At most 32, with independent offsets
File size Stored in 32 bits; growth is also limited by available image blocks
Directory size Reported as 0; directory storage is tracked through its chain
Delete semantics An open file cannot be unlinked (EBUSY); directories must be empty
Disk representation Native struct representation; intended for little-endian Linux/x86
Recovery No journal or full filesystem-repair tool

This is an educational library, not a kernel filesystem or FUSE driver. It does not implement permissions, owners, timestamps, links, rename, or arbitrary crash recovery. Seeking past EOF does not allocate blocks; a later write allocates and zero-fills the gap, so sparse gaps are not stored as holes inside UserFS.

Successful mutations flush allocation metadata and call fsync. This is distinct from transaction atomicity during power loss. Mount validates the geometry and allocation structures, including the root chain; it is not a complete reachability/ownership fsck.

Tests

The two C suites inspect raw disk geometry and exercise persistence, nested paths, multi-block I/O, zero-filled gaps, truncate growth/shrink, append behavior, directory expansion and slot reuse, descriptor limits, reclamation, ENOSPC rollback, and selected corruption cases.

GitHub Actions builds the library and utilities, runs both suites normally and with sanitizers, verifies the persistence example, and runs the command-line walkthrough. A separate job checks the browser model.

make test
make sanitize

Website development and hosting

The website consists of static HTML, CSS, and JavaScript in docs/. No application dependencies or build step are required. Use Node.js 20 or newer for the local server and model tests:

npm start        # http://127.0.0.1:4173
npm run check    # JavaScript syntax
npm test         # Browser-model scenarios

The layout uses Google Fonts when available and falls back to system fonts. The interactive model runs locally in the visitor's browser.

GitHub Pages publishes the /docs folder on main. Its URL is configured in the repository's About section and linked at the top of this README. Changes pushed to that folder are published automatically by GitHub Pages.

Repository guide

userfs.c                 C implementation
userfs.h                 Public API and limits
Makefile                 Library, tests, utilities, and demos
examples/quickstart.c    Complete persistence example
tests/                   Integration suites and small CLI utilities
readme/DESIGN.md          Detailed format and behavior specification
readme/*.png              Original architecture diagram
presentation/*.pptx      Original 77-slide walkthrough
docs/                    Interactive GitHub Pages website
scripts/serve.mjs         Dependency-free local website server
.github/workflows/ci.yml  Automated checks

Contributing

See CONTRIBUTING.md for the development workflow. Keep changes traceable from the public API to the disk format, and accompany bug fixes with focused regression tests.

The original presentation and diagram are preserved as supporting material. The C source and executable tests are the authority for implemented behavior. No license has been selected for this repository.

About

A user-space filesystem in C with bitmap allocation, FAT chains, persistent disk images, and an interactive architecture explorer.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages