Project 3: Persistent Block-Based Filesystem

Project 3: Persistent Block-Based Filesystem

Project Overview

Attribute Details
Difficulty Expert
Time Estimate 3-4 weeks
Language C
Knowledge Area Filesystems / Storage
Main Book โ€œOperating Systems: Three Easy Piecesโ€ by Arpaci-Dusseau
Prerequisites Project 2 completed

What Youโ€™ll Build

Extend your FUSE filesystem to persist data to a file (simulating a disk). Implement proper block allocation with bitmaps, inode structures, and data blocksโ€”a mini ext2.

Real World Output:

$ ./mkfs.myfs -s 100M disk.img    # Create 100MB filesystem
$ ./myfs disk.img /mnt/myfs       # Mount it
$ cp -r ~/Documents/* /mnt/myfs/  # Copy real files
$ umount /mnt/myfs                # Unmount
$ ./myfs disk.img /mnt/myfs       # Remount - files persist!
$ ls /mnt/myfs
Documents report.pdf photos/
$ ./fsck.myfs disk.img            # Your own filesystem checker
Checking superblock... OK
Checking inode bitmap... OK
Checking block bitmap... OK
Free blocks: 24531/25600

Learning Objectives

By completing this project, you will:

  1. Design on-disk data structures that survive restarts
  2. Implement block allocation using bitmaps
  3. Manage indirect block pointers for large file support
  4. Handle the persistence challenge: writing structured data to disk
  5. Build filesystem utilities (mkfs, fsck)
  6. Understand crash consistency issues (preparation for Project 4)

The Core Question Youโ€™re Answering

โ€œHow do you turn ephemeral bits in RAM into persistent data on disk that survives crashes and reboots?โ€

This is the fundamental question that separates toy filesystems from real ones. In-memory filesystems are elegant, but the moment you need persistence, you enter a world of complexity: block allocation, fragmentation, indirect pointers, and the eternal โ€œwhat if the power fails during a write?โ€ problem.

Every file youโ€™ve ever saved went through this exact process. Youโ€™re about to build that magic yourself.


Deep Theoretical Foundation

1. Why Block-Based Storage?

Disks (HDDs and SSDs) operate on fixed-size units:

  • Sectors: Hardware unit (512 bytes or 4096 bytes)
  • Blocks: Filesystem unit (typically 1KB, 2KB, or 4KB)

Why not byte-addressable storage?

  • Disk controllers are optimized for block transfers
  • Metadata overhead would be enormous for byte tracking
  • Caching works better with fixed-size units

Block size tradeoffs:

Block Size Pros Cons
1KB Less wasted space for small files More blocks to track, slower for large files
4KB Matches page size, faster I/O ~2KB average waste per small file
64KB Excellent sequential performance Huge waste for small files

2. On-Disk Layout Design

A typical Unix filesystem organizes disk space like this:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Block 0     โ”‚ Superblock                                      โ”‚
โ”‚             โ”‚ - Magic number, version                         โ”‚
โ”‚             โ”‚ - Total blocks, free blocks                     โ”‚
โ”‚             โ”‚ - Inode count, block size                       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Block 1-2   โ”‚ Inode Bitmap                                    โ”‚
โ”‚             โ”‚ - 1 bit per inode (used/free)                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Block 3-4   โ”‚ Block Bitmap                                    โ”‚
โ”‚             โ”‚ - 1 bit per data block (used/free)              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Block 5-N   โ”‚ Inode Table                                     โ”‚
โ”‚             โ”‚ - Fixed-size inode structures                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Block N+1   โ”‚ Data Blocks                                     โ”‚
โ”‚   to End    โ”‚ - File and directory contents                   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

3. Superblock Design

The superblock is the filesystemโ€™s master record:

struct myfs_superblock {
    uint32_t s_magic;           // Magic number (e.g., 0xDEADBEEF)
    uint32_t s_version;         // Filesystem version
    uint32_t s_block_size;      // Block size in bytes
    uint32_t s_blocks_count;    // Total blocks in filesystem
    uint32_t s_free_blocks;     // Free block count
    uint32_t s_inodes_count;    // Total inodes
    uint32_t s_free_inodes;     // Free inode count
    uint32_t s_inode_bitmap;    // Block number of inode bitmap
    uint32_t s_block_bitmap;    // Block number of block bitmap
    uint32_t s_inode_table;     // First block of inode table
    uint32_t s_first_data;      // First data block
    uint32_t s_root_inode;      // Inode number of root directory
    uint64_t s_mount_time;      // Last mount timestamp
    uint64_t s_write_time;      // Last write timestamp
    uint8_t  s_uuid[16];        // Filesystem UUID
    char     s_label[64];       // Volume label
} __attribute__((packed));

Why these fields?

  • s_magic: Verify this is your filesystem type
  • s_free_*: Avoid scanning bitmaps for every allocation
  • s_first_data: Know where file data starts
  • s_uuid: Identify filesystem across mounts

4. Bitmap-Based Allocation

Bitmaps provide O(1) check for individual blocks and O(n) search for free blocks:

// Block bitmap operations
#define BITMAP_GET(bitmap, n) ((bitmap[(n)/8] >> ((n)%8)) & 1)
#define BITMAP_SET(bitmap, n) (bitmap[(n)/8] |= (1 << ((n)%8)))
#define BITMAP_CLR(bitmap, n) (bitmap[(n)/8] &= ~(1 << ((n)%8)))

// Find first free block
int bitmap_find_free(uint8_t *bitmap, size_t size) {
    for (size_t i = 0; i < size; i++) {
        if (bitmap[i] != 0xFF) {  // At least one free bit
            for (int j = 0; j < 8; j++) {
                if (!(bitmap[i] & (1 << j))) {
                    return i * 8 + j;
                }
            }
        }
    }
    return -1;  // No free blocks
}

Optimization: Next-Free Hint

Store โ€œnext freeโ€ pointer in superblock to avoid scanning from start:

uint32_t alloc_block(struct fs_state *fs) {
    // Start scanning from hint
    int blk = bitmap_find_free_from(fs->block_bitmap,
                                     fs->sb.s_next_free_block,
                                     fs->sb.s_blocks_count);
    if (blk < 0) {
        // Wrap around to start
        blk = bitmap_find_free(fs->block_bitmap, fs->sb.s_next_free_block);
    }
    if (blk >= 0) {
        BITMAP_SET(fs->block_bitmap, blk);
        fs->sb.s_free_blocks--;
        fs->sb.s_next_free_block = blk + 1;
    }
    return blk;
}

5. Inode Structure for Persistence

On-disk inodes must be fixed-size and carefully designed:

#define DIRECT_BLOCKS 12
#define INODE_SIZE 128

struct myfs_inode {
    uint16_t i_mode;            // File type + permissions
    uint16_t i_uid;             // Owner user ID
    uint16_t i_gid;             // Owner group ID
    uint16_t i_links_count;     // Hard link count
    uint32_t i_size;            // File size (lower 32 bits)
    uint32_t i_size_high;       // File size (upper 32 bits for >4GB)
    uint32_t i_atime;           // Access time
    uint32_t i_mtime;           // Modification time
    uint32_t i_ctime;           // Status change time
    uint32_t i_blocks;          // Number of 512-byte blocks
    uint32_t i_block[15];       // Block pointers:
                                // [0-11]: Direct blocks
                                // [12]: Single indirect
                                // [13]: Double indirect
                                // [14]: Triple indirect
    uint32_t i_generation;      // File version (for NFS)
    uint32_t i_reserved[2];     // Future expansion
} __attribute__((packed));

_Static_assert(sizeof(struct myfs_inode) == INODE_SIZE, "Inode size mismatch");

6. Indirect Block Pointers

For files larger than 12 ร— block_size, use indirection:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Inode                                                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ i_block[0]  โ†’ Data Block 0                                       โ”‚
โ”‚ i_block[1]  โ†’ Data Block 1                                       โ”‚
โ”‚ ...                                                              โ”‚
โ”‚ i_block[11] โ†’ Data Block 11                   (48KB direct)      โ”‚
โ”‚ i_block[12] โ†’ Indirect Block โ†’ [ptr0, ptr1, ...ptr1023]         โ”‚
โ”‚                                 โ†“     โ†“           โ†“              โ”‚
โ”‚                              Data  Data  ...   Data              โ”‚
โ”‚                              (4MB with 4KB blocks)               โ”‚
โ”‚ i_block[13] โ†’ Double Indirect Block โ†’ [ind0, ind1, ...]         โ”‚
โ”‚                                        โ†“                         โ”‚
โ”‚                                     Indirect โ†’ [data blocks]     โ”‚
โ”‚                              (4GB with 4KB blocks)               โ”‚
โ”‚ i_block[14] โ†’ Triple Indirect                                    โ”‚
โ”‚                              (4TB with 4KB blocks)               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Calculating maximum file size (4KB blocks, 4-byte pointers):

Level Blocks Bytes
Direct (12) 12 48 KB
Single Indirect 1024 4 MB
Double Indirect 1024ยฒ = 1M 4 GB
Triple Indirect 1024ยณ = 1G 4 TB
Total ~1 billion ~4 TB

7. Directory Entry Format

Directories are files containing entries:

struct myfs_dirent {
    uint32_t d_ino;         // Inode number (0 = deleted)
    uint16_t d_rec_len;     // Total entry length
    uint8_t  d_name_len;    // Name length
    uint8_t  d_file_type;   // File type hint
    char     d_name[];      // Filename (variable length)
} __attribute__((packed));

Variable-length entries:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ inode  โ”‚rec_len โ”‚name_lenโ”‚typeโ”‚   name...   โ”‚
โ”‚  (4)   โ”‚  (2)   โ”‚  (1)   โ”‚(1) โ”‚ (name_len)  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ†‘
         d_rec_len includes padding to 4-byte boundary

8. The Write Path

When writing data to disk, multiple structures need updating:

write("/myfile", data, 100)
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 1. Resolve path โ†’ inode                                      โ”‚
โ”‚ 2. If new file: allocate inode, add directory entry          โ”‚
โ”‚ 3. If extending file: allocate new blocks                    โ”‚
โ”‚ 4. Write data to block(s)                                    โ”‚
โ”‚ 5. Update inode (size, mtime, block pointers)                โ”‚
โ”‚ 6. Update bitmaps if blocks allocated                        โ”‚
โ”‚ 7. Update superblock (free counts)                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Critical insight: If power fails mid-write, any of these steps might be incomplete. This is the crash consistency problem (solved in Project 4).


Project Specification

Functional Requirements

  1. mkfs.myfs utility
    • Create formatted filesystem image
    • Configurable size (1MB - 1TB)
    • Initialize superblock, bitmaps, root directory
  2. FUSE filesystem
    • Mount existing filesystem images
    • All file operations from Project 2
    • Data persists across unmount/remount
  3. Block allocation
    • Bitmap-based free space tracking
    • Allocate/free data blocks
    • Track in superblock
  4. Large file support
    • Direct blocks for small files
    • Single indirect for larger files
    • Double indirect for very large files
  5. fsck.myfs utility
    • Verify superblock integrity
    • Check bitmap consistency
    • Report orphaned blocks/inodes

Non-Functional Requirements

  • Support filesystems up to 1TB
  • Support files up to 4GB minimum
  • Handle thousands of files
  • Mount time < 1 second

Solution Architecture

Component Design

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      mkfs.myfs                                   โ”‚
โ”‚                 (Create new filesystem)                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚ Creates disk.img
                         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Disk Image (disk.img)                         โ”‚
โ”‚   [Superblock][Bitmaps][Inode Table][Data Blocks]               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        โ”‚                                         โ”‚
โ”‚                        โ–ผ                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚                  Block I/O Layer                         โ”‚    โ”‚
โ”‚  โ”‚          (read_block, write_block, sync)                 โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                            โ”‚                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚                         โ–ผ                               โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  Superblock   โ”‚ โ”‚   Bitmap      โ”‚ โ”‚    Inode      โ”‚ โ”‚    โ”‚
โ”‚  โ”‚  โ”‚   Manager     โ”‚ โ”‚   Manager     โ”‚ โ”‚   Manager     โ”‚ โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚    โ”‚
โ”‚  โ”‚                         โ”‚                               โ”‚    โ”‚
โ”‚  โ”‚                         โ–ผ                               โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚    โ”‚
โ”‚  โ”‚  โ”‚               Directory Manager                  โ”‚   โ”‚    โ”‚
โ”‚  โ”‚  โ”‚           (parse/update dir entries)             โ”‚   โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚    โ”‚
โ”‚  โ”‚                         โ”‚                               โ”‚    โ”‚
โ”‚  โ”‚                         โ–ผ                               โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚    โ”‚
โ”‚  โ”‚  โ”‚                 File Manager                     โ”‚   โ”‚    โ”‚
โ”‚  โ”‚  โ”‚        (handle indirect blocks, data I/O)        โ”‚   โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚    โ”‚
โ”‚  โ”‚                                                         โ”‚    โ”‚
โ”‚  โ”‚                    Filesystem Core                      โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                            โ”‚                                     โ”‚
โ”‚                            โ–ผ                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚                    FUSE Callbacks                        โ”‚    โ”‚
โ”‚  โ”‚      (myfs_getattr, myfs_read, myfs_write, ...)         โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                                  โ”‚
โ”‚                       myfs (FUSE daemon)                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚
                         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                       fsck.myfs                                  โ”‚
โ”‚                 (Check filesystem integrity)                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Data Structures

// Filesystem runtime state
struct myfs_state {
    int disk_fd;                      // Open file descriptor for image
    struct myfs_superblock sb;        // Cached superblock
    uint8_t *inode_bitmap;            // Cached inode bitmap
    uint8_t *block_bitmap;            // Cached block bitmap
    struct myfs_inode *inode_cache;   // LRU inode cache
    size_t cache_size;
    pthread_mutex_t lock;
    int dirty;                        // Need to sync?
};

// Block I/O buffer
struct block_buffer {
    uint32_t block_num;
    uint8_t data[BLOCK_SIZE];
    int dirty;
    int pin_count;
};

// Indirect block (for reading block pointer arrays)
struct indirect_block {
    uint32_t ptrs[BLOCK_SIZE / sizeof(uint32_t)];
};

Key Functions

// Block I/O
int read_block(struct myfs_state *fs, uint32_t blk, void *buf);
int write_block(struct myfs_state *fs, uint32_t blk, const void *buf);
int sync_all(struct myfs_state *fs);

// Bitmap operations
int alloc_inode(struct myfs_state *fs);
void free_inode(struct myfs_state *fs, uint32_t ino);
int alloc_block(struct myfs_state *fs);
void free_block(struct myfs_state *fs, uint32_t blk);

// Inode operations
int read_inode(struct myfs_state *fs, uint32_t ino, struct myfs_inode *inode);
int write_inode(struct myfs_state *fs, uint32_t ino, struct myfs_inode *inode);
uint32_t inode_get_block(struct myfs_state *fs, struct myfs_inode *inode,
                          uint32_t logical_block);
int inode_set_block(struct myfs_state *fs, struct myfs_inode *inode,
                    uint32_t logical_block, uint32_t physical_block);

// Directory operations
int dir_lookup(struct myfs_state *fs, struct myfs_inode *dir,
               const char *name, uint32_t *ino_out);
int dir_add(struct myfs_state *fs, struct myfs_inode *dir,
            const char *name, uint32_t ino, uint8_t type);
int dir_remove(struct myfs_state *fs, struct myfs_inode *dir,
               const char *name);

// File data operations
ssize_t file_read(struct myfs_state *fs, struct myfs_inode *inode,
                  char *buf, size_t size, off_t offset);
ssize_t file_write(struct myfs_state *fs, struct myfs_inode *inode,
                   const char *buf, size_t size, off_t offset);
int file_truncate(struct myfs_state *fs, struct myfs_inode *inode,
                  off_t new_size);

Implementation Guide

Phase 1: mkfs Utility (Days 1-3)

Goals:

  • Create disk image of specified size
  • Initialize superblock with correct values
  • Set up empty bitmaps
  • Create root directory inode

Steps:

  1. Parse command-line arguments (size, block size)
  2. Calculate layout (how many blocks for bitmaps, inodes, data)
  3. Write superblock
  4. Initialize bitmaps (mark metadata blocks as used)
  5. Create root inode (inode 1)
int mkfs_main(const char *image_path, size_t size_mb) {
    int fd = open(image_path, O_RDWR | O_CREAT | O_TRUNC, 0644);
    ftruncate(fd, size_mb * 1024 * 1024);

    uint32_t block_size = 4096;
    uint32_t total_blocks = (size_mb * 1024 * 1024) / block_size;

    // Calculate layout
    uint32_t inode_count = total_blocks / 4;  // 1 inode per 4 blocks
    uint32_t inode_bitmap_blocks = (inode_count + block_size*8 - 1) / (block_size*8);
    uint32_t block_bitmap_blocks = (total_blocks + block_size*8 - 1) / (block_size*8);
    uint32_t inode_table_blocks = (inode_count * INODE_SIZE) / block_size;

    // Fill in superblock
    struct myfs_superblock sb = {
        .s_magic = MYFS_MAGIC,
        .s_block_size = block_size,
        .s_blocks_count = total_blocks,
        .s_inodes_count = inode_count,
        .s_inode_bitmap = 1,
        .s_block_bitmap = 1 + inode_bitmap_blocks,
        .s_inode_table = 1 + inode_bitmap_blocks + block_bitmap_blocks,
        .s_first_data = 1 + inode_bitmap_blocks + block_bitmap_blocks + inode_table_blocks,
        .s_root_inode = 1,
        // ...
    };

    // Write superblock
    write_at(fd, 0, &sb, sizeof(sb));

    // Initialize bitmaps (all zeros = all free)
    // Mark metadata blocks as used in block bitmap
    // Create root directory inode

    close(fd);
    return 0;
}

Testing:

./mkfs.myfs -s 10M test.img
hexdump -C test.img | head -20  # Should see magic number

Phase 2: Block I/O Layer (Days 3-5)

Goals:

  • Read/write blocks from disk image
  • Cache frequently accessed blocks
  • Implement sync for durability

Steps:

  1. Open disk image file
  2. Implement read_block() with seek + read
  3. Implement write_block() with seek + write
  4. Add simple write-back cache
int read_block(struct myfs_state *fs, uint32_t blk, void *buf) {
    if (blk >= fs->sb.s_blocks_count)
        return -EINVAL;

    off_t offset = (off_t)blk * fs->sb.s_block_size;
    if (pread(fs->disk_fd, buf, fs->sb.s_block_size, offset) != fs->sb.s_block_size)
        return -EIO;

    return 0;
}

int write_block(struct myfs_state *fs, uint32_t blk, const void *buf) {
    if (blk >= fs->sb.s_blocks_count)
        return -EINVAL;

    off_t offset = (off_t)blk * fs->sb.s_block_size;
    if (pwrite(fs->disk_fd, buf, fs->sb.s_block_size, offset) != fs->sb.s_block_size)
        return -EIO;

    return 0;
}

Phase 3: Bitmap Management (Days 5-7)

Goals:

  • Load bitmaps into memory
  • Allocate/free inodes
  • Allocate/free data blocks

Steps:

  1. Load bitmaps on mount
  2. Implement alloc_inode() and free_inode()
  3. Implement alloc_block() and free_block()
  4. Update superblock free counts
  5. Flush bitmaps on sync/unmount
int alloc_inode(struct myfs_state *fs) {
    pthread_mutex_lock(&fs->lock);

    for (uint32_t i = 1; i < fs->sb.s_inodes_count; i++) {
        if (!BITMAP_GET(fs->inode_bitmap, i)) {
            BITMAP_SET(fs->inode_bitmap, i);
            fs->sb.s_free_inodes--;
            fs->dirty = 1;
            pthread_mutex_unlock(&fs->lock);
            return i;
        }
    }

    pthread_mutex_unlock(&fs->lock);
    return -ENOSPC;
}

Phase 4: Inode Operations (Days 7-10)

Goals:

  • Read/write inodes from inode table
  • Calculate inode disk location
  • Handle inode caching

Steps:

  1. Calculate inode offset: inode_table_start + (ino - 1) * INODE_SIZE
  2. Read inode from disk
  3. Update and write back
  4. Add simple cache for hot inodes
int read_inode(struct myfs_state *fs, uint32_t ino, struct myfs_inode *inode) {
    if (ino < 1 || ino >= fs->sb.s_inodes_count)
        return -EINVAL;

    uint32_t block = fs->sb.s_inode_table +
                     ((ino - 1) * INODE_SIZE) / fs->sb.s_block_size;
    uint32_t offset = ((ino - 1) * INODE_SIZE) % fs->sb.s_block_size;

    uint8_t buf[fs->sb.s_block_size];
    int ret = read_block(fs, block, buf);
    if (ret < 0) return ret;

    memcpy(inode, buf + offset, sizeof(*inode));
    return 0;
}

Phase 5: Indirect Block Support (Days 10-14)

Goals:

  • Map logical block numbers to physical blocks
  • Allocate indirect blocks as needed
  • Support files up to 4GB+

Steps:

  1. inode_get_block(): Given logical block N, return physical block
  2. Handle direct blocks (0-11)
  3. Handle single indirect (12-1035)
  4. Handle double indirect (1036+)
  5. Allocate indirect blocks on write
uint32_t inode_get_block(struct myfs_state *fs, struct myfs_inode *inode,
                          uint32_t logical) {
    uint32_t ptrs_per_block = fs->sb.s_block_size / sizeof(uint32_t);

    // Direct blocks
    if (logical < DIRECT_BLOCKS) {
        return inode->i_block[logical];
    }

    logical -= DIRECT_BLOCKS;

    // Single indirect
    if (logical < ptrs_per_block) {
        if (inode->i_block[12] == 0) return 0;

        struct indirect_block ind;
        read_block(fs, inode->i_block[12], &ind);
        return ind.ptrs[logical];
    }

    logical -= ptrs_per_block;

    // Double indirect
    if (logical < ptrs_per_block * ptrs_per_block) {
        if (inode->i_block[13] == 0) return 0;

        struct indirect_block dbl;
        read_block(fs, inode->i_block[13], &dbl);

        uint32_t ind_idx = logical / ptrs_per_block;
        uint32_t ptr_idx = logical % ptrs_per_block;

        if (dbl.ptrs[ind_idx] == 0) return 0;

        struct indirect_block ind;
        read_block(fs, dbl.ptrs[ind_idx], &ind);
        return ind.ptrs[ptr_idx];
    }

    // Triple indirect (implement similarly)
    return 0;
}

Phase 6: Directory Operations (Days 14-18)

Goals:

  • Parse directory entry format
  • Add/remove entries
  • Handle entry reclamation

Steps:

  1. Read directory data blocks
  2. Parse variable-length entries
  3. Search for name match
  4. Add new entries (find space or extend)
  5. Remove entries (mark inode=0, merge with previous)
int dir_lookup(struct myfs_state *fs, struct myfs_inode *dir,
               const char *name, uint32_t *ino_out) {
    uint8_t buf[fs->sb.s_block_size];
    off_t pos = 0;

    while (pos < dir->i_size) {
        uint32_t blk = inode_get_block(fs, dir, pos / fs->sb.s_block_size);
        if (blk == 0) break;

        read_block(fs, blk, buf);

        uint32_t offset = pos % fs->sb.s_block_size;
        while (offset < fs->sb.s_block_size && offset + pos < dir->i_size) {
            struct myfs_dirent *de = (struct myfs_dirent *)(buf + offset);

            if (de->d_ino != 0 && de->d_name_len == strlen(name) &&
                memcmp(de->d_name, name, de->d_name_len) == 0) {
                *ino_out = de->d_ino;
                return 0;
            }

            offset += de->d_rec_len;
        }

        pos = (pos / fs->sb.s_block_size + 1) * fs->sb.s_block_size;
    }

    return -ENOENT;
}

Phase 7: File Read/Write (Days 18-22)

Goals:

  • Read data from file blocks
  • Write data, allocating blocks as needed
  • Handle sparse files

Steps:

  1. Calculate which blocks contain requested range
  2. For reads: handle holes (return zeros)
  3. For writes: allocate blocks, handle growth
  4. Update inode size and times

Phase 8: Integration and Testing (Days 22-28)

Goals:

  • Connect all components to FUSE
  • Build fsck utility
  • Test persistence across mounts

Testing scenarios:

# Basic persistence test
./mkfs.myfs -s 100M test.img
./myfs test.img /tmp/myfs
echo "test data" > /tmp/myfs/file.txt
fusermount -u /tmp/myfs
./myfs test.img /tmp/myfs
cat /tmp/myfs/file.txt  # Should show "test data"

# Large file test
dd if=/dev/urandom of=/tmp/myfs/large.bin bs=1M count=50
md5sum /tmp/myfs/large.bin > checksum.txt
fusermount -u /tmp/myfs
./myfs test.img /tmp/myfs
md5sum -c checksum.txt  # Should pass

# fsck test
./fsck.myfs test.img

Testing Strategy

Unit Tests

// Test bitmap operations
void test_bitmap() {
    uint8_t bitmap[16] = {0};

    BITMAP_SET(bitmap, 0);
    assert(BITMAP_GET(bitmap, 0) == 1);
    assert(BITMAP_GET(bitmap, 1) == 0);

    BITMAP_SET(bitmap, 100);
    assert(BITMAP_GET(bitmap, 100) == 1);

    BITMAP_CLR(bitmap, 100);
    assert(BITMAP_GET(bitmap, 100) == 0);

    int free = bitmap_find_free(bitmap, sizeof(bitmap));
    assert(free == 1);  // 0 is still set
}

// Test inode block mapping
void test_block_mapping() {
    // Mock filesystem with 4KB blocks
    // Test direct block access
    // Test indirect block access
    // Test boundary conditions
}

Integration Tests

#!/bin/bash
# Test filesystem persistence

DISK=/tmp/test_disk.img
MNT=/tmp/test_mnt

cleanup() {
    fusermount -u $MNT 2>/dev/null
    rm -f $DISK
    rmdir $MNT 2>/dev/null
}
trap cleanup EXIT

# Create filesystem
./mkfs.myfs -s 50M $DISK
mkdir -p $MNT
./myfs $DISK $MNT

# Write test data
echo "persistent data" > $MNT/test.txt
mkdir $MNT/subdir
echo "nested" > $MNT/subdir/nested.txt

# Get checksums
md5sum $MNT/test.txt > /tmp/checksums.txt
md5sum $MNT/subdir/nested.txt >> /tmp/checksums.txt

# Unmount and remount
fusermount -u $MNT
./myfs $DISK $MNT

# Verify persistence
md5sum -c /tmp/checksums.txt && echo "PASS: Data persisted" || echo "FAIL"

# Verify fsck
./fsck.myfs $DISK && echo "PASS: Filesystem consistent" || echo "FAIL"

Common Pitfalls and Debugging

Pitfall 1: Off-by-One in Inode Numbers

Problem: Inodes are numbered from 1, not 0.

// WRONG
offset = ino * INODE_SIZE;

// CORRECT
offset = (ino - 1) * INODE_SIZE;

Pitfall 2: Not Syncing on Unmount

Problem: Data lost because writes were cached.

Solution: Implement FUSE destroy callback:

static void myfs_destroy(void *private_data) {
    struct myfs_state *fs = private_data;
    sync_all(fs);
    close(fs->disk_fd);
}

Pitfall 3: Indirect Block Allocation

Problem: Forgetting to allocate and zero indirect blocks.

// When setting a single indirect pointer
if (inode->i_block[12] == 0) {
    int blk = alloc_block(fs);
    if (blk < 0) return blk;

    // Zero the indirect block
    uint8_t zeros[fs->sb.s_block_size];
    memset(zeros, 0, sizeof(zeros));
    write_block(fs, blk, zeros);

    inode->i_block[12] = blk;
}

Debugging Techniques

1. Hexdump verification:

# Check superblock
hexdump -C disk.img | head -10

# Check inode table
hexdump -C -s 4096 -n 128 disk.img

2. Add extensive logging:

#define DEBUG 1
#if DEBUG
#define LOG(fmt, ...) fprintf(stderr, "[myfs] " fmt "\n", ##__VA_ARGS__)
#else
#define LOG(fmt, ...)
#endif

LOG("alloc_block: returning block %u", blk);

3. Create debugging dump tool:

./myfs-dump disk.img superblock
./myfs-dump disk.img inode 5
./myfs-dump disk.img block 100

Extensions and Challenges

Extension 1: fsck Repair Mode

Add --fix flag to repair common issues:

  • Orphaned blocks (allocated but not referenced)
  • Wrong free counts in superblock
  • Unreachable inodes

Extension 2: Defragmentation Tool

Identify fragmented files and relocate blocks for contiguous storage.

Extension 3: Sparse File Support

Properly handle files with holes (unallocated regions that read as zeros).

Extension 4: Quotas

Track per-user block and inode usage, enforce limits.


Interview Questions

Conceptual

  1. โ€œExplain the difference between an inode and a directory entry.โ€
    • Expected: Inode = metadata + block pointers. Directory entry = name โ†’ inode mapping.
  2. โ€œWhy do filesystems use indirect pointers?โ€
    • Expected: Fixed inode size, support large files, small files stay efficient.
  3. โ€œWhat happens when you delete a file?โ€
    • Expected: Decrement nlink, if 0: free inode, free data blocks, update bitmaps.

Implementation

  1. โ€œHow do you calculate an inodeโ€™s disk location?โ€
    • Expected: inode_table_start + (ino - 1) * inode_size
  2. โ€œWhatโ€™s the maximum file size with 12 direct + 1 indirect + 1 double indirect?โ€
    • Expected: Calculate based on block size and pointer size.
  3. โ€œWhy might you find allocated but unreferenced blocks after a crash?โ€
    • Expected: Crash between block allocation and directory entry write.

Self-Assessment Checklist

  • mkfs creates valid filesystem with correct layout
  • Files persist across unmount/remount
  • Large files (>48KB) work correctly
  • Directories with many entries work
  • fsck reports no errors on clean filesystem
  • Free counts in superblock are accurate
  • Indirect blocks are properly managed
  • Can copy real files (cp, tar) to filesystem

Resources

Books

  • โ€œOperating Systems: Three Easy Piecesโ€ Ch. 40-41 - File System Implementation
  • โ€œUnderstanding the Linux Kernelโ€ Ch. 18 - ext2/ext3 Implementation
  • โ€œThe C Programming Languageโ€ Ch. 8.7 - Storage Allocator

Online