Project 2: In-Memory Filesystem with FUSE

Build a complete, mountable filesystem that stores files in RAM using FUSE (Filesystem in Userspace)

Quick Reference

Attribute Value
Difficulty Intermediate (Level 3)
Time Estimate 2-3 weeks
Language C (Alternatives: C++, Rust with fuse-rs)
Prerequisites C pointers/structs, Project 1 or understanding of filesystem structures
Key Topics VFS operations, FUSE API, inode management, directory traversal
Main Book “The Linux Programming Interface” by Michael Kerrisk

1. Learning Objectives

By completing this project, you will:

  1. Understand the Virtual Filesystem (VFS) abstraction and how Linux presents a unified interface to different filesystems
  2. Implement core file operations (getattr, open, read, write, create, unlink) that handle real system calls
  3. Design in-memory data structures for inodes, directory entries, and file data storage
  4. Work with the FUSE API to create mountable filesystems without writing kernel code
  5. Handle file metadata correctly including permissions, timestamps, link counts, and ownership
  6. Debug userspace filesystems using FUSE’s debugging features and strace
  7. Understand path resolution and how the kernel translates paths to filesystem operations

2. Theoretical Foundation

2.1 Core Concepts

The Virtual Filesystem (VFS) Layer

Linux supports dozens of filesystems (ext4, XFS, Btrfs, NFS, FUSE, etc.), yet applications use identical system calls for all of them. The VFS layer makes this possible by defining a common interface that all filesystems must implement.

User Application
     │
     │ open("/mnt/myfs/file.txt", O_RDONLY)
     ▼
┌─────────────────────────────────────────────────────────────┐
│                    System Call Layer                         │
│                   (sys_open, sys_read, sys_write)           │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│                    VFS (Virtual Filesystem)                  │
│         Provides unified interface for all filesystems       │
│                                                              │
│   Key abstractions:                                          │
│   - superblock: filesystem-wide metadata                     │
│   - inode: per-file metadata (not the name!)                │
│   - dentry: directory entry cache (name → inode)            │
│   - file: open file instance (position, flags)              │
└─────────────────────────────────────────────────────────────┘
     │
     │ Dispatches to appropriate filesystem driver
     ▼
┌──────────┬──────────┬──────────┬──────────┬──────────┐
│   ext4   │   XFS    │   NFS    │   FUSE   │  tmpfs   │
│  driver  │  driver  │  client  │  bridge  │  driver  │
└──────────┴──────────┴──────────┴────┬─────┴──────────┘
                                      │
                                      ▼
                           ┌──────────────────────┐
                           │  Your Userspace      │
                           │  Filesystem (memfs)  │
                           └──────────────────────┘

VFS Key Abstractions:

Abstraction Purpose Your Implementation
superblock Filesystem-wide metadata Global state struct with inode table
inode Per-file metadata (NOT the filename) struct my_inode with mode, size, data pointer
dentry Name-to-inode mapping Directory entries stored in directory inodes
file Open file instance Handled by FUSE; you track via path

FUSE Architecture

FUSE (Filesystem in Userspace) provides a bridge between the kernel VFS and your regular C program:

┌─────────────────────────────────────────────────────────────┐
│                    User Application                          │
│              (ls, cat, echo, mkdir, rm, etc.)               │
└──────────────────────────┬──────────────────────────────────┘
                           │ System calls (open, read, write)
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    Linux Kernel VFS                          │
└──────────────────────────┬──────────────────────────────────┘
                           │ Routes to FUSE kernel module
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    FUSE Kernel Module                        │
│                       (fuse.ko)                              │
│                                                              │
│   Translates VFS operations to /dev/fuse protocol           │
└──────────────────────────┬──────────────────────────────────┘
                           │ Reads/writes to /dev/fuse
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    /dev/fuse                                 │
│              (character device for IPC)                      │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    libfuse Library                           │
│                    (userspace)                               │
│                                                              │
│   - Reads requests from /dev/fuse                           │
│   - Calls YOUR callback functions                           │
│   - Writes responses back to /dev/fuse                      │
└──────────────────────────┬──────────────────────────────────┘
                           │ Your callbacks
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    YOUR FILESYSTEM CODE                      │
│                                                              │
│   myfs_getattr()   - Return file metadata                   │
│   myfs_readdir()   - List directory contents                │
│   myfs_open()      - Open a file                            │
│   myfs_read()      - Read file data                         │
│   myfs_write()     - Write file data                        │
│   myfs_create()    - Create new file                        │
│   myfs_mkdir()     - Create directory                       │
│   myfs_unlink()    - Delete file                            │
│   myfs_rmdir()     - Remove directory                       │
└─────────────────────────────────────────────────────────────┘

How a read() syscall flows through FUSE:

  1. Application calls read(fd, buf, 100)
  2. Kernel VFS dispatches to FUSE kernel module
  3. FUSE module writes request to /dev/fuse
  4. libfuse reads request from /dev/fuse
  5. libfuse calls your myfs_read() callback
  6. Your code returns data
  7. libfuse writes response to /dev/fuse
  8. FUSE module returns data to VFS
  9. Application receives data

The fuse_operations Structure

FUSE filesystems implement callbacks in a struct fuse_operations:

struct fuse_operations {
    // Metadata operations
    int (*getattr)(const char *path, struct stat *stbuf,
                   struct fuse_file_info *fi);
    int (*chmod)(const char *path, mode_t mode,
                 struct fuse_file_info *fi);
    int (*chown)(const char *path, uid_t uid, gid_t gid,
                 struct fuse_file_info *fi);
    int (*utimens)(const char *path, const struct timespec tv[2],
                   struct fuse_file_info *fi);

    // Directory operations
    int (*readdir)(const char *path, void *buf, fuse_fill_dir_t filler,
                   off_t offset, struct fuse_file_info *fi,
                   enum fuse_readdir_flags flags);
    int (*mkdir)(const char *path, mode_t mode);
    int (*rmdir)(const char *path);

    // File operations
    int (*create)(const char *path, mode_t mode,
                  struct fuse_file_info *fi);
    int (*open)(const char *path, struct fuse_file_info *fi);
    int (*read)(const char *path, char *buf, size_t size, off_t offset,
                struct fuse_file_info *fi);
    int (*write)(const char *path, const char *buf, size_t size,
                 off_t offset, struct fuse_file_info *fi);
    int (*truncate)(const char *path, off_t size,
                    struct fuse_file_info *fi);
    int (*unlink)(const char *path);
    int (*rename)(const char *from, const char *to, unsigned int flags);

    // ... many more optional operations
};

Critical operations you MUST implement:

Operation When Called What You Return
getattr EVERY file access (ls, stat, open) Fill struct stat with metadata
readdir ls, find, directory iteration Call filler() for each entry
create Creating new files Allocate inode, add to parent dir
open Opening existing files Verify file exists
read Reading file content Copy data to buffer, return bytes read
write Writing file content Store data, return bytes written
unlink Deleting files (rm) Remove dir entry, decrement nlink
mkdir Creating directories Allocate inode, add . and ..
rmdir Removing directories Check empty, remove

The stat Structure

Every filesystem must provide file metadata via struct stat:

struct stat {
    dev_t     st_dev;         // Device ID (your FS ID)
    ino_t     st_ino;         // Inode number (unique per file)
    mode_t    st_mode;        // File type + permissions
    nlink_t   st_nlink;       // Number of hard links
    uid_t     st_uid;         // Owner user ID
    gid_t     st_gid;         // Owner group ID
    off_t     st_size;        // Total size in bytes
    blksize_t st_blksize;     // Block size for I/O (use 4096)
    blkcnt_t  st_blocks;      // Number of 512B blocks allocated
    struct timespec st_atim;  // Last access time
    struct timespec st_mtim;  // Last modification time
    struct timespec st_ctim;  // Last status change time
};

st_mode encoding (CRITICAL to get right):

// File type bits (upper 4 bits of mode) - MUTUALLY EXCLUSIVE
S_IFREG   0100000   // Regular file
S_IFDIR   0040000   // Directory
S_IFLNK   0120000   // Symbolic link
S_IFCHR   0020000   // Character device
S_IFBLK   0060000   // Block device
S_IFIFO   0010000   // FIFO (named pipe)
S_IFSOCK  0140000   // Socket

// Permission bits (lower 9 bits)
S_IRUSR   00400     // Owner read
S_IWUSR   00200     // Owner write
S_IXUSR   00100     // Owner execute
S_IRGRP   00040     // Group read
S_IWGRP   00020     // Group write
S_IXGRP   00010     // Group execute
S_IROTH   00004     // Others read
S_IWOTH   00002     // Others write
S_IXOTH   00001     // Others execute

// Example: Regular file with rw-r--r-- (0644)
mode_t file_mode = S_IFREG | 0644;

// Example: Directory with rwxr-xr-x (0755)
mode_t dir_mode = S_IFDIR | 0755;

// COMMON BUG: Forgetting file type bits!
// WRONG: stbuf->st_mode = 0755;  // ls shows "??????"
// RIGHT: stbuf->st_mode = S_IFDIR | 0755;

Every file has a link count (st_nlink) that tracks how many directory entries point to it:

File link count rules:
┌──────────────────────────────────────────────────────────┐
│ Regular file:                                            │
│   - Starts with nlink=1                                  │
│   - Each hard link adds 1                                │
│   - File deleted when nlink reaches 0 (and no open fds) │
│                                                          │
│ Directory:                                               │
│   - Starts with nlink=2 (parent's entry + own ".")      │
│   - Each subdirectory adds 1 (for its ".." entry)       │
│                                                          │
│ Example:                                                 │
│                                                          │
│   /mydir              nlink=4                            │
│   ├── .               (points to /mydir, counted in 2)  │
│   ├── ..              (points to parent)                 │
│   ├── file.txt        nlink=1                            │
│   ├── subdir1/        nlink=2 (adds 1 to mydir)         │
│   │   ├── .                                              │
│   │   └── ..          (points to /mydir)                │
│   └── subdir2/        nlink=2 (adds 1 to mydir)         │
│       ├── .                                              │
│       └── ..          (points to /mydir)                │
└──────────────────────────────────────────────────────────┘

Why nlink matters:

  • rm decrements nlink; actual deletion happens when nlink=0
  • ls -l uses nlink to show link count
  • Directories with wrong nlink confuse find and other tools

2.2 Why This Matters

Understanding VFS unlocks:

  1. Debugging file issues: When ls shows strange output or permissions seem wrong, you’ll know which operations are failing
  2. Building custom storage: Network filesystems, encrypted filesystems, database-backed filesystems all use FUSE
  3. Interview preparation: “How does a filesystem work?” is a common systems interview question
  4. Operating system internals: VFS is fundamental to understanding how Unix works

Real-world FUSE filesystems:

  • s3fs: Mount Amazon S3 buckets as local directories
  • sshfs: Mount remote servers via SSH
  • encfs: Encrypted overlay filesystem
  • rclone mount: Multi-cloud filesystem (Google Drive, Dropbox, etc.)
  • gcsfuse: Google Cloud Storage filesystem

2.3 Historical Context

The Problem Before FUSE (pre-2005):

  • Filesystem = kernel code = crashes bring down entire system
  • Requires kernel recompilation or module loading
  • Debugging with printk, no gdb
  • One bug = kernel panic = reboot

FUSE Solved This:

  • 2001: Miklos Szeredi begins development
  • 2005: Merged into Linux kernel 2.6.14
  • Enabled explosion of userspace filesystems
  • Made filesystem development accessible to application developers

The Trade-off:

  • Performance: Each operation requires kernel ↔ userspace context switch
  • Typical overhead: 10-30% compared to kernel filesystems
  • For many use cases (cloud storage, archives, debugging), this is acceptable

2.4 Common Misconceptions

Misconception 1: “getattr is only called for stat

  • Reality: getattr is called for EVERY file access
  • ls calls getattr on every file in the directory
  • open calls getattr to check if file exists
  • Keep getattr FAST

Misconception 2: “I can store filenames in inodes”

  • Reality: Filenames belong in directory entries, not inodes
  • An inode can have multiple names (hard links)
  • This is why you can rename a file without changing its inode

Misconception 3: “read() and write() handle files”

  • Reality: They handle paths! FUSE gives you the path, not a file descriptor
  • You must resolve the path to an inode in every callback

Misconception 4: “Directories are just special files”

  • Reality: Yes, but they need special handling:
    • Must contain . (self) and .. (parent)
    • nlink counts subdirectories
    • Cannot be opened with O_RDONLY for reading bytes

3. Project Specification

3.1 What You Will Build

A complete in-memory filesystem called memfs that:

  • Mounts as a real directory on Linux
  • Supports files and directories
  • Handles all basic file operations
  • Stores everything in RAM (data lost on unmount)
  • Works with standard Unix tools (ls, cat, mkdir, rm, etc.)

3.2 Functional Requirements

  1. Mount and Unmount
    • Mount filesystem to any empty directory
    • Unmount cleanly with fusermount -u
    • Support foreground mode (-f) and debug mode (-d)
  2. File Operations
    • Create files (touch, echo > file)
    • Read files (cat, head, tail)
    • Write files (echo >>, text editors)
    • Delete files (rm)
    • Truncate files (truncate -s, >)
  3. Directory Operations
    • Create directories (mkdir)
    • List directories (ls, ls -la)
    • Remove empty directories (rmdir)
    • Navigate directories (cd)
  4. Metadata Operations
    • Get file attributes (stat, ls -l)
    • Update timestamps (touch)
    • Change permissions (chmod)
    • Change ownership (chown) (as root)
  5. Path Operations
    • Resolve absolute paths
    • Handle . and .. correctly
    • Support nested paths (/a/b/c/file.txt)

3.3 Non-Functional Requirements

  • Capacity: Support files up to 100MB
  • File count: Support up to 1000 files/directories
  • Concurrency: Handle sequential access (single-threaded is fine)
  • Errors: Return appropriate errno codes (ENOENT, EACCES, EISDIR, etc.)
  • Performance: Operations complete in reasonable time (< 100ms)

3.4 Example Usage / Output

# Terminal 1: Run the filesystem
$ mkdir /tmp/myfs
$ ./memfs /tmp/myfs
# (Filesystem is now mounted and running)

# Terminal 2: Use the filesystem
$ cd /tmp/myfs
$ pwd
/tmp/myfs

$ ls -la
total 0
drwxr-xr-x 2 user user    0 Dec 20 10:00 .
drwxr-xr-x 3 user user 4096 Dec 20 10:00 ..

$ echo "Hello, FUSE!" > greeting.txt
$ cat greeting.txt
Hello, FUSE!

$ ls -la
total 0
drwxr-xr-x 2 user user    0 Dec 20 10:00 .
drwxr-xr-x 3 user user 4096 Dec 20 10:00 ..
-rw-r--r-- 1 user user   13 Dec 20 10:01 greeting.txt

$ mkdir subdir
$ echo "Nested content" > subdir/nested.txt
$ ls -R
.:
greeting.txt  subdir

./subdir:
nested.txt

$ cat subdir/nested.txt
Nested content

$ stat greeting.txt
  File: greeting.txt
  Size: 13        	Blocks: 0          IO Block: 4096   regular file
Device: 0,0	Inode: 2           Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/   user)   Gid: ( 1000/   user)
Access: 2024-12-20 10:01:00.000000000 +0000
Modify: 2024-12-20 10:01:00.000000000 +0000
Change: 2024-12-20 10:01:00.000000000 +0000

$ rm greeting.txt
$ ls
subdir

$ rmdir subdir
rmdir: failed to remove 'subdir': Directory not empty

$ rm subdir/nested.txt
$ rmdir subdir
$ ls
(empty)

# Terminal 1: Unmount
$ fusermount -u /tmp/myfs
# (All data is gone - it was in RAM)

3.5 Real World Outcome

When you complete this project:

  1. You have a mountable filesystem - You can mount it just like ext4 or NTFS
  2. Standard tools just work - ls, cat, vim, cp all work transparently
  3. You understand VFS - You can explain what happens for any file operation
  4. Debug any filesystem issue - You know where to look when file operations fail

Concrete verification:

# Your filesystem behaves like any other:
$ mount | grep myfs
memfs on /tmp/myfs type fuse.memfs (rw,nosuid,nodev,relatime,user_id=1000)

# File operations work:
$ dd if=/dev/urandom of=/tmp/myfs/random.bin bs=1K count=100
100+0 records in
100+0 records out
102400 bytes (102 kB) copied, 0.01 s, 10.2 MB/s

$ md5sum /tmp/myfs/random.bin
a1b2c3d4e5f6... /tmp/myfs/random.bin

# Editors work:
$ vim /tmp/myfs/document.txt
# (create, edit, save - all work)

# You can copy to/from the filesystem:
$ cp /etc/passwd /tmp/myfs/
$ diff /etc/passwd /tmp/myfs/passwd
(no differences)

4. Solution Architecture

4.1 High-Level Design

┌─────────────────────────────────────────────────────────────────┐
│                      FUSE Callbacks Layer                        │
│    myfs_getattr()  myfs_readdir()  myfs_read()  myfs_write()   │
│    myfs_create()   myfs_mkdir()    myfs_unlink()  myfs_rmdir() │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                      Path Resolution Layer                       │
│                   resolve_path(path) → inode_num                │
│                   resolve_parent(path) → (parent_ino, name)     │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                      Inode Manager                               │
│         inode_alloc()   inode_get()   inode_free()              │
│         Manages array of struct my_inode                         │
└─────────────────┬───────────────────────────────┬───────────────┘
                  │                               │
                  ▼                               ▼
┌─────────────────────────────────┐  ┌───────────────────────────┐
│      Directory Manager          │  │      Data Manager         │
│  dir_add_entry()                │  │  file_read()              │
│  dir_remove_entry()             │  │  file_write()             │
│  dir_lookup()                   │  │  file_truncate()          │
│  dir_list()                     │  │  (malloc/realloc buffers) │
└─────────────────────────────────┘  └───────────────────────────┘
                  │                               │
                  └───────────────┬───────────────┘
                                  ▼
┌─────────────────────────────────────────────────────────────────┐
│                      In-Memory Storage                           │
│                                                                  │
│   ┌─────────────────────────────────────────────────────┐       │
│   │  Inode Table: struct my_inode inodes[MAX_INODES]    │       │
│   │                                                      │       │
│   │  [0]: unused (inode 0 is invalid)                   │       │
│   │  [1]: root directory (ino=1)                        │       │
│   │       └── entries: [{ino=2, "file.txt"}, ...]       │       │
│   │  [2]: regular file (ino=2)                          │       │
│   │       └── data: "Hello, World!\n"                   │       │
│   │  [3]: free                                           │       │
│   │  ...                                                 │       │
│   └─────────────────────────────────────────────────────┘       │
│                                                                  │
│   ┌─────────────────────────────────────────────────────┐       │
│   │  Inode Bitmap: int bitmap[MAX_INODES]               │       │
│   │  [0]=0, [1]=1, [2]=1, [3]=0, [4]=0, ...             │       │
│   └─────────────────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────────────────┘

4.2 Key Components

Component Responsibility Key Decisions
FUSE Callbacks Interface with libfuse, call internal functions Thin layer: validate args, call managers, return errors
Path Resolution Convert path string to inode number Iterative traversal, handle . and ..
Inode Manager Allocate, lookup, free inodes Fixed array vs dynamic; reuse freed inodes
Directory Manager Manage directory entries Store entries in inode struct vs separate
Data Manager Store file contents Dynamic allocation with realloc

4.3 Data Structures

#define MAX_INODES 1024
#define MAX_NAME_LEN 255
#define MAX_DIR_ENTRIES 256

// File types
typedef enum {
    MY_FILE,
    MY_DIR,
    MY_SYMLINK  // Extension
} my_file_type;

// Directory entry
struct dir_entry {
    uint32_t ino;            // Inode number (0 = unused)
    char name[MAX_NAME_LEN + 1];  // Null-terminated filename
};

// Inode structure
struct my_inode {
    uint32_t ino;            // Inode number
    my_file_type type;       // File type
    mode_t mode;             // Permissions (not including file type)
    uid_t uid;               // Owner user ID
    gid_t gid;               // Owner group ID
    nlink_t nlink;           // Link count
    off_t size;              // Size in bytes
    struct timespec atime;   // Access time
    struct timespec mtime;   // Modification time
    struct timespec ctime;   // Status change time

    union {
        // For regular files
        struct {
            char *data;           // File content (malloc'd)
            size_t capacity;      // Allocated size
        } file;

        // For directories
        struct {
            struct dir_entry *entries;  // Array of entries
            size_t count;               // Number of entries
            size_t capacity;            // Allocated slots
        } dir;

        // For symlinks (extension)
        struct {
            char *target;         // Symlink target path
        } symlink;
    };
};

// Global filesystem state
struct myfs_state {
    struct my_inode inodes[MAX_INODES];
    int inode_bitmap[MAX_INODES];  // 1 = used, 0 = free
    uint32_t next_ino;             // Next inode to try
    pthread_mutex_t lock;          // For thread safety (optional)
};

// Global instance
static struct myfs_state *fs_state;

4.4 Algorithm Overview

Key Algorithm: Path Resolution

resolve_path("/home/user/file.txt") → inode_number

1. If path == "/", return ROOT_INODE (1)
2. Start at current_ino = ROOT_INODE
3. Split path by "/" → ["home", "user", "file.txt"]
4. For each component:
   a. Get inode for current_ino
   b. If not a directory, return -ENOTDIR
   c. Search entries for component name
   d. If not found, return -ENOENT
   e. current_ino = found entry's inode
5. Return current_ino

Key Algorithm: File Write with Sparse Handling

myfs_write(path, buf, size, offset)

1. Resolve path to inode
2. If offset + size > capacity:
   - Reallocate data buffer to offset + size
   - If offset > current_size:
     - Zero-fill gap from current_size to offset
3. memcpy(data + offset, buf, size)
4. If offset + size > size, update size
5. Update mtime and ctime
6. Return bytes written

Complexity Analysis:

Operation Time Complexity Space Complexity
Path resolution O(d * e) where d=depth, e=entries O(1)
getattr O(d * e) O(1)
readdir O(e) entries O(1)
read/write O(d * e + n) where n=bytes O(n) for realloc
create/unlink O(d * e) O(1)

5. Implementation Guide

5.1 Development Environment Setup

# Install FUSE development libraries
# Ubuntu/Debian:
sudo apt update
sudo apt install libfuse3-dev pkg-config build-essential

# Fedora/RHEL:
sudo dnf install fuse3-devel pkg-config gcc

# Arch Linux:
sudo pacman -S fuse3 pkg-config base-devel

# Verify installation
pkg-config --cflags --libs fuse3
# Should output: -I/usr/include/fuse3 -lfuse3 -lpthread

# Create project directory
mkdir memfs && cd memfs

5.2 Project Structure

memfs/
├── Makefile              # Build configuration
├── memfs.c               # Main file with FUSE callbacks
├── inode.h               # Inode structure definitions
├── inode.c               # Inode management functions
├── dir.h                 # Directory operation declarations
├── dir.c                 # Directory operations
├── path.h                # Path resolution declarations
├── path.c                # Path resolution implementation
├── tests/
│   ├── test_basic.sh     # Basic functionality tests
│   ├── test_stress.sh    # Stress tests
│   └── test_unit.c       # Unit tests for internal functions
└── README.md             # Usage instructions

Simple single-file structure (recommended to start):

memfs/
├── Makefile
├── memfs.c               # Everything in one file initially
└── tests/
    └── test_basic.sh

5.3 The Core Question You’re Answering

“What actually happens when a program calls open(), read(), or write()? How does the kernel translate those syscalls into filesystem operations?”

When you type cat file.txt:

  1. cat calls open("file.txt", O_RDONLY) - Who handles this?
  2. cat calls read(fd, buf, 4096) - Where does data come from?
  3. cat calls close(fd) - What gets cleaned up?

Most developers can’t answer these questions beyond “the OS handles it.” After this project, you’ll know EXACTLY what happens because YOU implemented the filesystem that handles these calls.

5.4 Concepts You Must Understand First

Stop and research these before coding:

1. FUSE API Basics

  • What is fuse_main() and what does it do?
  • What is struct fuse_operations and how do you populate it?
  • How do FUSE callbacks receive the path vs. the file descriptor?
  • What do the return values of FUSE callbacks mean?
  • Book Reference: “The Linux Programming Interface” Ch. 14 (File Systems)

2. The stat Structure

  • What is every field in struct stat?
  • How do you encode file type in st_mode?
  • What’s the difference between atime, mtime, and ctime?
  • Why does st_nlink matter?
  • Book Reference: “The Linux Programming Interface” Ch. 15 (File Attributes)

3. Directory Entry Handling

  • How are . and .. special?
  • Why are filenames in directory entries, not inodes?
  • What is fuse_fill_dir_t and how do you use it?
  • How do you iterate through directory entries?

4. Memory Management for File Data

  • How do you dynamically grow a buffer with realloc()?
  • What happens when write() is called at an offset past current EOF?
  • How do you handle file truncation?
  • Book Reference: “C Programming: A Modern Approach” Ch. 17 - K.N. King

5. Error Handling with errno

  • What is errno and how does FUSE use it?
  • What do common errors mean: ENOENT, EACCES, EEXIST, EISDIR, ENOTDIR, ENOTEMPTY?
  • How do you return errors from FUSE callbacks?
  • Book Reference: “The Linux Programming Interface” Ch. 3 (System Programming Concepts)

5.5 Questions to Guide Your Design

Before implementing, think through these:

Phase 1: Minimal Skeleton

  1. What headers do you need for FUSE 3?
  2. How do you define FUSE_USE_VERSION?
  3. What’s the minimal fuse_operations that lets you mount?
  4. How do you compile and link with libfuse?

Phase 2: getattr and readdir

  1. When getattr("/") is called, what should you return?
  2. How do you fill struct stat for a directory vs. a file?
  3. What is fuse_fill_dir_t filler and how do you call it?
  4. Should you include . and .. in readdir output?

Phase 3: Inode Management

  1. How do you allocate a new inode number?
  2. How do you initialize the root directory at mount time?
  3. When should you reuse freed inode numbers?
  4. How do you map inode numbers to inode structs?

Phase 4: File Creation and Data

  1. What happens when create() is called?
  2. How do you add an entry to the parent directory?
  3. When write() is called, how do you store the data?
  4. What if write() is called at offset 1000 in a new file?

Phase 5: Directory Operations

  1. How does mkdir() differ from create()?
  2. Why must new directories contain . and ..?
  3. When can rmdir() succeed vs. fail with ENOTEMPTY?
  4. How do you update parent’s nlink when creating subdirectories?

5.6 Thinking Exercise

Trace a File Creation by Hand

Before coding, trace what happens when you run echo "hello" > /mnt/myfs/test.txt:

Shell: echo "hello" > /mnt/myfs/test.txt

1. Shell calls open("/mnt/myfs/test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644)
   → Kernel: What's at /mnt/myfs/test.txt?
   → FUSE: getattr("/mnt/myfs/test.txt")
   → Your code: Look up path... not found!
   → Return: -ENOENT

2. Since O_CREAT is set and file doesn't exist:
   → FUSE: create("/mnt/myfs/test.txt", 0644, fi)
   → Your code:
      a. Find parent directory inode for "/mnt/myfs"
      b. Allocate new inode for the file
      c. Initialize inode: type=FILE, mode=0644, size=0, data=NULL
      d. Add entry "test.txt" to parent directory
      e. Update parent's mtime
   → Return: 0 (success)

3. Shell calls write(fd, "hello\n", 6)
   → FUSE: write("/mnt/myfs/test.txt", "hello\n", 6, offset=0, fi)
   → Your code:
      a. Resolve path to inode
      b. Allocate data buffer (6 bytes or more)
      c. Copy "hello\n" to buffer
      d. Update size to 6
      e. Update mtime
   → Return: 6 (bytes written)

4. Shell calls close(fd)
   → FUSE: release() (if implemented)
   → Your code: Nothing to do for in-memory FS
   → Return: 0

Questions while tracing:

  • What inode number does the new file get?
  • What’s in the root directory’s entries after creation?
  • What are the atime, mtime, ctime values after write()?
  • What happens if the parent directory doesn’t exist?

5.7 Hints in Layers

Hint 1: Start with the Minimal Skeleton

Get this compiling and mounting before adding functionality:

#define FUSE_USE_VERSION 31
#include <fuse.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

static int myfs_getattr(const char *path, struct stat *stbuf,
                        struct fuse_file_info *fi) {
    (void)fi;
    memset(stbuf, 0, sizeof(struct stat));

    if (strcmp(path, "/") == 0) {
        stbuf->st_mode = S_IFDIR | 0755;
        stbuf->st_nlink = 2;
        stbuf->st_uid = getuid();
        stbuf->st_gid = getgid();
        return 0;
    }

    return -ENOENT;
}

static int myfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                        off_t offset, struct fuse_file_info *fi,
                        enum fuse_readdir_flags flags) {
    (void)offset;
    (void)fi;
    (void)flags;

    if (strcmp(path, "/") != 0)
        return -ENOENT;

    filler(buf, ".", NULL, 0, 0);
    filler(buf, "..", NULL, 0, 0);

    return 0;
}

static struct fuse_operations myfs_ops = {
    .getattr = myfs_getattr,
    .readdir = myfs_readdir,
};

int main(int argc, char *argv[]) {
    return fuse_main(argc, argv, &myfs_ops, NULL);
}

Compile and test:

gcc -Wall myfs.c -o myfs $(pkg-config fuse3 --cflags --libs)
mkdir /tmp/myfs
./myfs /tmp/myfs
ls /tmp/myfs    # Should show empty directory
ls -la /tmp/myfs  # Should show . and ..
fusermount -u /tmp/myfs

Hint 2: Add Inode Infrastructure

Before adding file operations, build your inode table:

#define MAX_INODES 1024
#define ROOT_INO 1

struct my_inode {
    int used;
    mode_t mode;
    nlink_t nlink;
    off_t size;
    uid_t uid;
    gid_t gid;
    // ... other fields
};

static struct my_inode inodes[MAX_INODES];

void init_fs() {
    memset(inodes, 0, sizeof(inodes));

    // Initialize root directory
    inodes[ROOT_INO].used = 1;
    inodes[ROOT_INO].mode = S_IFDIR | 0755;
    inodes[ROOT_INO].nlink = 2;
    inodes[ROOT_INO].uid = getuid();
    inodes[ROOT_INO].gid = getgid();
}

Hint 3: Implement Path Resolution

This is the core of your filesystem:

// Returns inode number or 0 if not found
uint32_t resolve_path(const char *path) {
    if (strcmp(path, "/") == 0)
        return ROOT_INO;

    char *path_copy = strdup(path);
    char *token = strtok(path_copy + 1, "/");  // Skip leading /
    uint32_t current = ROOT_INO;

    while (token != NULL) {
        struct my_inode *dir = &inodes[current];

        // Find entry with matching name
        uint32_t found = 0;
        for (size_t i = 0; i < dir->dir.count; i++) {
            if (strcmp(dir->dir.entries[i].name, token) == 0) {
                found = dir->dir.entries[i].ino;
                break;
            }
        }

        if (found == 0) {
            free(path_copy);
            return 0;  // Not found
        }

        current = found;
        token = strtok(NULL, "/");
    }

    free(path_copy);
    return current;
}

Hint 4: Debug with FUSE Flags

Run in foreground with debug output:

./myfs -f -d /tmp/myfs

This shows every FUSE operation as it happens:

unique: 2, opcode: LOOKUP (1), nodeid: 1, insize: 48, pid: 1234
LOOKUP /test.txt
   unique: 2, success, outsize: 144

Use strace on client programs:

strace ls /tmp/myfs 2>&1 | grep -E "(stat|getdents|open)"

5.8 The Interview Questions They’ll Ask

Prepare to answer these:

  1. “What is FUSE and how does it work?”
    • Expected: Userspace filesystem through /dev/fuse, kernel module bridges VFS to user process
  2. “What is getattr, and why is it called so frequently?”
    • Expected: It’s stat(). Called for every file access. Must be fast.
  3. “How does a filesystem resolve a path like ‘/home/user/file.txt’?”
    • Expected: Start at root inode, read directory, find “home”, get its inode, repeat
  4. “What’s the difference between an inode and a directory entry?”
    • Expected: Inode stores metadata (not name). Directory entry maps name → inode.
  5. “Why do directories have link count >= 2?”
    • Expected: Parent’s entry + own “.” entry. Each subdirectory adds 1 for its “..”.
  6. “What should read() return if offset is past EOF?”
    • Expected: Return 0 (EOF), not an error.
  7. “How would you implement hard links?”
    • Expected: Multiple directory entries pointing to same inode. Increment nlink.
  8. “What happens when you delete a file that’s still open?”
    • Expected: nlink goes to 0, but data preserved until last fd closed (FUSE handles this).

5.9 Books That Will Help

Topic Book Chapter
VFS and filesystem concepts “The Linux Programming Interface” by Kerrisk Ch. 14-15
Directory and link operations “The Linux Programming Interface” by Kerrisk Ch. 18
Filesystem implementation “Operating Systems: Three Easy Pieces” Ch. 39-40
Linux kernel VFS internals “Understanding the Linux Kernel” by Bovet & Cesati Ch. 12
FUSE tutorial FUSE Tutorial Full
System programming in C “Advanced Programming in the UNIX Environment” Ch. 4-5

5.10 Implementation Phases

Phase 1: FUSE Skeleton (Days 1-2)

Goals:

  • Set up build environment with libfuse3
  • Create minimal mountable filesystem
  • Implement getattr for root directory only
  • Implement readdir for root (empty)

Tasks:

  1. Install libfuse3-dev
  2. Create Makefile with correct flags
  3. Write minimal memfs.c with getattr and readdir
  4. Test: mount, ls, unmount

Checkpoint:

$ ./memfs /tmp/myfs
$ ls /tmp/myfs
$ ls -la /tmp/myfs
drwxr-xr-x 2 user user 0 Dec 20 10:00 .
drwxr-xr-x ... ..
$ fusermount -u /tmp/myfs

Phase 2: Inode Infrastructure (Days 2-4)

Goals:

  • Define inode structure with all necessary fields
  • Initialize root directory inode
  • Create inode allocation/deallocation functions
  • Update getattr to use inode data

Tasks:

  1. Define struct my_inode with mode, size, uid, gid, times
  2. Create inode_alloc() returning next free inode number
  3. Create inode_free() marking inode as available
  4. Initialize root inode (ino=1) at startup
  5. Modify getattr to read from inode table

Checkpoint:

# Should still work, now backed by real inode structure
$ ./memfs /tmp/myfs
$ stat /tmp/myfs
# Shows real uid, gid, timestamps

Phase 3: Path Resolution (Days 4-5)

Goals:

  • Tokenize path strings
  • Traverse directory tree
  • Return inode for any valid path

Tasks:

  1. Implement resolve_path() for simple paths
  2. Handle “/” specially
  3. Implement resolve_parent() to get parent inode and basename
  4. Test with paths like “/file.txt”, “/dir/file.txt”

Checkpoint:

// Unit tests:
assert(resolve_path("/") == ROOT_INO);
// After creating /test.txt:
assert(resolve_path("/test.txt") == some_ino);
assert(resolve_path("/nonexistent") == 0);

Phase 4: File Creation and Deletion (Days 5-7)

Goals:

  • Implement create() callback
  • Implement unlink() callback
  • Manage directory entries

Tasks:

  1. Implement dir_add_entry() to add to directory
  2. Implement dir_remove_entry() to remove from directory
  3. Implement myfs_create() - allocate inode, add entry
  4. Implement myfs_unlink() - remove entry, free if nlink=0
  5. Handle parent directory mtime updates

Checkpoint:

$ touch /tmp/myfs/test.txt
$ ls /tmp/myfs
test.txt
$ rm /tmp/myfs/test.txt
$ ls /tmp/myfs
(empty)

Phase 5: Read and Write (Days 7-10)

Goals:

  • Store file data in memory
  • Handle read with offset and size
  • Handle write with growing buffers
  • Update timestamps correctly

Tasks:

  1. Add data pointer and capacity to inode struct
  2. Implement myfs_open() - just verify file exists
  3. Implement myfs_read() - copy data to buffer
  4. Implement myfs_write() - grow buffer, copy from buffer
  5. Handle sparse writes (offset > current size)
  6. Update atime on read, mtime/ctime on write

Checkpoint:

$ echo "Hello World" > /tmp/myfs/test.txt
$ cat /tmp/myfs/test.txt
Hello World
$ echo "More text" >> /tmp/myfs/test.txt
$ cat /tmp/myfs/test.txt
Hello World
More text

Phase 6: Directory Operations (Days 10-12)

Goals:

  • Implement mkdir and rmdir
  • Handle nlink correctly for directories
  • Ensure directories contain . and ..

Tasks:

  1. Implement myfs_mkdir() - create dir inode with . and ..
  2. Implement myfs_rmdir() - check empty, remove
  3. Update parent’s nlink on mkdir (increment) and rmdir (decrement)
  4. Handle nested directories

Checkpoint:

$ mkdir /tmp/myfs/subdir
$ ls -la /tmp/myfs
drwxr-xr-x 3 user user 0 ...  .
...
drwxr-xr-x 2 user user 0 ...  subdir
$ echo "nested" > /tmp/myfs/subdir/file.txt
$ cat /tmp/myfs/subdir/file.txt
nested
$ rmdir /tmp/myfs/subdir
rmdir: failed to remove 'subdir': Directory not empty
$ rm /tmp/myfs/subdir/file.txt
$ rmdir /tmp/myfs/subdir
$ ls /tmp/myfs
(empty)

Phase 7: Metadata Operations (Days 12-14)

Goals:

  • Implement chmod, chown, utimens
  • Implement truncate
  • Handle all edge cases

Tasks:

  1. Implement myfs_chmod() - update mode (preserve type bits)
  2. Implement myfs_chown() - update uid/gid
  3. Implement myfs_utimens() - update atime/mtime
  4. Implement myfs_truncate() - resize file

Checkpoint:

$ echo "test" > /tmp/myfs/file.txt
$ chmod 600 /tmp/myfs/file.txt
$ ls -l /tmp/myfs/file.txt
-rw------- 1 user user 5 ... file.txt
$ truncate -s 0 /tmp/myfs/file.txt
$ cat /tmp/myfs/file.txt
(empty)
$ truncate -s 1000 /tmp/myfs/file.txt
$ ls -l /tmp/myfs/file.txt
-rw------- 1 user user 1000 ... file.txt

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Inode storage Dynamic allocation vs Fixed array Fixed array Simpler, predictable memory, fine for 1000 files
Directory entries In inode struct vs Separate table In inode struct (union) Keeps related data together
Path resolution Recursive vs Iterative Iterative Avoids stack overflow on deep paths
File data storage Fixed size vs Dynamic realloc Dynamic realloc Memory efficient, handles any file size
Thread safety None vs Mutex Start without, add if needed Simpler to debug single-threaded first
FUSE version FUSE 2 vs FUSE 3 FUSE 3 Modern API, better maintained

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Smoke Tests Basic functionality works mount, ls, unmount
Unit Tests Individual functions correct path_resolve, inode_alloc
Integration Tests Components work together create + write + read
Edge Cases Boundary conditions handled empty files, long names, deep paths
Stress Tests System handles load 1000 files, large files

6.2 Critical Test Cases

  1. Mount and Unmount
    • Mount to empty directory
    • Unmount cleanly
    • Remount to same directory
  2. File Lifecycle
    • Create file, verify exists
    • Write data, read back same data
    • Append data, read complete file
    • Delete file, verify gone
  3. Directory Lifecycle
    • Create directory
    • Create nested directory
    • Create file in nested directory
    • Delete files and directories in order
  4. Metadata
    • Correct permissions after chmod
    • Correct ownership after chown (as root)
    • Timestamps update appropriately
  5. Edge Cases
    • Empty file (size 0)
    • Large file (10MB)
    • Long filename (255 chars)
    • Deep nesting (/a/b/c/d/e/f/g/h/i/j/file.txt)
    • Many files in one directory (100+)

6.3 Test Script

#!/bin/bash
# test_memfs.sh

set -e  # Exit on first error

MOUNT=/tmp/myfs_test_$$
BINARY=./memfs

cleanup() {
    fusermount -u $MOUNT 2>/dev/null || true
    rmdir $MOUNT 2>/dev/null || true
}
trap cleanup EXIT

# Setup
mkdir -p $MOUNT
$BINARY $MOUNT

echo "=== Basic Operations ==="

# Test 1: Empty directory listing
echo "Test 1: Empty listing..."
[ "$(ls $MOUNT | wc -l)" -eq 0 ] && echo "PASS" || echo "FAIL"

# Test 2: File creation and read
echo "Test 2: File creation..."
echo "hello" > $MOUNT/test.txt
[ "$(cat $MOUNT/test.txt)" = "hello" ] && echo "PASS" || echo "FAIL"

# Test 3: File append
echo "Test 3: File append..."
echo "world" >> $MOUNT/test.txt
[ "$(cat $MOUNT/test.txt)" = $'hello\nworld' ] && echo "PASS" || echo "FAIL"

# Test 4: Directory creation
echo "Test 4: Directory creation..."
mkdir $MOUNT/subdir
[ -d $MOUNT/subdir ] && echo "PASS" || echo "FAIL"

# Test 5: Nested file
echo "Test 5: Nested file..."
echo "nested" > $MOUNT/subdir/nested.txt
[ "$(cat $MOUNT/subdir/nested.txt)" = "nested" ] && echo "PASS" || echo "FAIL"

# Test 6: File deletion
echo "Test 6: File deletion..."
rm $MOUNT/test.txt
[ ! -f $MOUNT/test.txt ] && echo "PASS" || echo "FAIL"

# Test 7: Non-empty rmdir fails
echo "Test 7: Non-empty rmdir..."
rmdir $MOUNT/subdir 2>/dev/null && echo "FAIL" || echo "PASS"

# Test 8: Proper cleanup
echo "Test 8: Cleanup..."
rm $MOUNT/subdir/nested.txt
rmdir $MOUNT/subdir
[ ! -d $MOUNT/subdir ] && echo "PASS" || echo "FAIL"

# Test 9: Permissions
echo "Test 9: Permissions..."
echo "test" > $MOUNT/perm.txt
chmod 000 $MOUNT/perm.txt
ls -l $MOUNT/perm.txt | grep -q "^----------" && echo "PASS" || echo "FAIL"
chmod 644 $MOUNT/perm.txt

# Test 10: Large file
echo "Test 10: Large file..."
dd if=/dev/urandom of=$MOUNT/large.bin bs=1M count=1 2>/dev/null
[ "$(stat -c%s $MOUNT/large.bin)" -eq 1048576 ] && echo "PASS" || echo "FAIL"

# Cleanup
rm $MOUNT/perm.txt $MOUNT/large.bin

echo "=== All tests completed ==="

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Missing file type in st_mode ls shows ??????? instead of permissions Always OR file type: S_IFREG \| 0644
getattr returns wrong value Files not found that exist Check return value: 0 for success, -errno for error
readdir missing . and .. Some tools behave strangely Always include . and .. entries
Wrong nlink for directories find doesn’t descend into directories Directories start with nlink=2, add 1 per subdirectory
Not handling offset in read Only first 4KB of file works Use offset parameter: data + offset
Not growing buffer on write Write past current size fails or corrupts realloc when offset + size > capacity
Stale mount point “Transport endpoint not connected” fusermount -u /path or sudo umount -f /path
Not updating timestamps make and other tools confused Update mtime on write, atime on read, ctime on metadata change

7.2 Debugging Strategies

1. Run in Foreground with Debug

./memfs -f -d /tmp/myfs 2>&1 | tee debug.log

Shows every FUSE operation:

unique: 1, opcode: LOOKUP (1), nodeid: 1, insize: 48
   LOOKUP /test.txt
unique: 1, error: -2 (No such file or directory), outsize: 16

2. Add Extensive Logging

#include <syslog.h>

void init_logging() {
    openlog("memfs", LOG_PID | LOG_PERROR, LOG_USER);
}

// In each callback:
static int myfs_getattr(const char *path, struct stat *stbuf, ...) {
    syslog(LOG_DEBUG, "getattr: path=%s", path);
    // ...
    syslog(LOG_DEBUG, "getattr: returning %d, mode=%o, size=%ld",
           ret, stbuf->st_mode, stbuf->st_size);
    return ret;
}

View logs: journalctl -f -t memfs

3. Use strace on Client Programs

strace -e trace=file ls /tmp/myfs 2>&1

Shows exactly what syscalls are made:

openat(AT_FDCWD, "/tmp/myfs", O_RDONLY|O_DIRECTORY) = 3
getdents64(3, [...], 32768) = 72
close(3) = 0

4. Check /proc/mounts

cat /proc/mounts | grep myfs
# memfs /tmp/myfs fuse.memfs rw,nosuid,nodev,relatime,user_id=1000 0 0

7.3 Performance Traps

Trap Impact Solution
Slow path resolution Every operation slow Cache recently resolved paths
Reallocating on every write Many small writes slow Allocate in chunks (e.g., 4KB minimum)
Linear directory search Large directories slow Use hash table for entries (advanced)
Copying data in read/write Large files slow Minimize copies, consider mmap (advanced)

8. Extensions & Challenges

8.1 Beginner Extensions

  • Symbolic links: Implement symlink() and readlink()
  • rename(): Move/rename files and directories
  • statfs(): Report filesystem statistics (for df)
  • access(): Permission checking

8.2 Intermediate Extensions

  • Hard links: Multiple names pointing to same inode
  • Extended attributes: setxattr(), getxattr()
  • File locking: flock() support
  • Direct I/O: O_DIRECT support for large files

8.3 Advanced Extensions

  • Persistence: Save/load filesystem to disk file
  • Compression: Transparent file compression
  • Encryption: Encrypt file data at rest
  • Multi-threading: Handle concurrent operations safely
  • Quotas: Per-user storage limits

9. Real-World Connections

9.1 Industry Applications

Cloud Storage Filesystems:

  • s3fs-fuse: Mount Amazon S3 buckets as local directories
  • gcsfuse: Google Cloud Storage access
  • rclone mount: Multi-cloud filesystem (Dropbox, Google Drive, OneDrive)

Your project teaches the same fundamentals these use.

Encryption Filesystems:

  • EncFS: Encrypted overlay filesystem
  • gocryptfs: Modern encrypted FUSE filesystem
  • CryFS: Cloud-storage-optimized encryption

All implement the same FUSE operations you’re implementing.

Development Tools:

  • sshfs: Mount remote directories via SSH
  • unionfs-fuse: Overlay multiple directories (used in Docker)
  • bindfs: Remount with different permissions
  • libfuse: github.com/libfuse/libfuse - The library you’re using
  • fuse-rs: Rust bindings for FUSE
  • go-fuse: Go bindings for FUSE
  • python-fuse: Python bindings

9.3 Interview Relevance

Systems engineering interviews often ask:

  1. “Design a filesystem” - You can now describe inode structures, directory entries, block allocation
  2. “How does ls work?” - You implemented the readdir() that ls calls
  3. “Explain the difference between hard and soft links” - You understand inodes vs directory entries
  4. “What happens when you delete an open file?” - You know about nlink and reference counting

10. Resources

10.1 Essential Reading

  • “The Linux Programming Interface” by Michael Kerrisk - Chapters 14-15 (Files), 18 (Directories)
  • “Operating Systems: Three Easy Pieces” by Arpaci-Dusseau - Chapters 39-40 (Filesystems)
  • FUSE documentation - libfuse.github.io/doxygen

10.2 Video Resources

  • FUSE Tutorial Video - Search “FUSE filesystem tutorial” on YouTube
  • MIT 6.S081 Operating Systems - Filesystem lectures (free on YouTube)

10.3 Tools & Documentation

  • libfuse GitHub: github.com/libfuse/libfuse
  • FUSE examples: libfuse/example/ directory has reference implementations
  • fusermount: Unmount FUSE filesystems
  • strace: Trace system calls for debugging

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain what VFS is and why it exists
  • I can describe the flow of a read() system call through FUSE
  • I can explain the difference between an inode and a directory entry
  • I understand why getattr is called so frequently
  • I can explain how path resolution works step by step
  • I know why directories have nlink >= 2

11.2 Implementation

  • Filesystem mounts and unmounts cleanly
  • Files can be created, read, written, and deleted
  • Directories can be created and removed
  • Nested paths work correctly
  • Permissions are handled correctly
  • Timestamps update appropriately
  • All test cases pass

11.3 Growth

  • I can debug FUSE filesystems using -d flag and strace
  • I can extend this filesystem with new operations
  • I can explain my design decisions and trade-offs
  • I could implement this again without looking at my code
  • I can answer common interview questions about filesystems

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Filesystem mounts and unmounts
  • ls and ls -la work on root and subdirectories
  • touch, echo >, cat work for files
  • mkdir and rmdir work for directories
  • Basic test script passes

Full Completion:

  • All minimum criteria plus:
  • Permissions work (chmod)
  • Truncate works
  • Nested directories to depth 5+
  • Files up to 10MB supported
  • All edge case tests pass
  • Timestamps update correctly

Excellence (Going Above & Beyond):

  • Symbolic links implemented
  • rename() implemented
  • Hard links implemented
  • Performance benchmarks documented
  • Multi-threaded operation with proper locking
  • Comprehensive documentation

This guide was generated from FILESYSTEM_INTERNALS_LEARNING_PROJECTS.md. For the complete learning path, see the parent directory.