Building a Customized Virtual File System in C++

Mayur PatilMayur Patil
5 min read

Managing files efficiently is at the heart of any operating system. A Virtual File System (VFS) provides an abstraction layer for file operations, making interactions simple and intuitive for developers. This blog walks through building a Customized Virtual File System in C++, with features like file creation, reading, writing, and more.

Whether you're a C++ enthusiast or keen on learning systems programming, this project will give you hands-on experience with data structures, memory management, and file operations.


🔎 1. Introduction: What are we building and why?

In this project, we build a Customized Virtual File System (VFS) to simulate file management. Here’s why this project is exciting:

  • It mimics how real-world operating systems handle file operations (e.g., Linux’s ext4 or NTFS).

  • It deepens your understanding of data structures like linked lists, inodes, and file tables.

  • It’s a hands-on way to explore systems programming with C++.

The VFS supports:

  • File operations like create, read, write, delete, and truncate.

  • File permissions (read, write, or both).

  • In-memory simulation of files with metadata management.

By the end, you’ll have a functioning VFS with a command-line interface (CLI) to interact with the file system.


📚 2. Concept Recap: Key File System Components

Before diving into the implementation, let’s recap some key concepts:

Superblock:

Tracks the total and free inodes in the file system. It acts as a manager for metadata about the file system.

Inode:

Represents an individual file. It stores metadata like file name, size, type, permissions, and content buffer.

File Table:

Maintains information about open files during runtime, including read/write offsets and access modes.

UFDT (User File Descriptor Table):

Maps file descriptors to file tables, enabling user processes to interact with files.

Diagrammatically, here’s how the VFS is structured:

Superblock --> Inodes (Linked List) --> File Table --> UFDT --> User Commands

💻 3. Implementation Walkthrough

Step 1: Setting Up the Superblock and Inodes

The superblock and inodes are initialized during system startup. Each inode represents a file and is stored in a dynamically allocated linked list.

void InitialiseSuperBlock() {
    for (int i = 0; i < MAXINODE; i++) {
        UFDTArr[i].ptrfiletable = NULL;
    }
    SUPERBLOCKobj.TotalInodes = MAXINODE;
    SUPERBLOCKobj.FreeInode = MAXINODE;
}

void CreateDILB() {
    PINODE temp = NULL;
    for (int i = 1; i <= MAXINODE; i++) {
        PINODE newn = (PINODE)malloc(sizeof(INODE));
        newn->LinkCount = newn->ReferenceCount = 0;
        newn->FileType = newn->FileSize = 0;
        newn->Buffer = NULL;
        newn->next = NULL;
        newn->InodeNumber = i;
        if (temp == NULL) {
            head = newn;
            temp = head;
        } else {
            temp->next = newn;
            temp = temp->next;
        }
    }
}

Step 2: Creating and Deleting Files

These core functions handle file creation and deletion, ensuring proper metadata management.

int CreateFile(char *name, int permission) {
    if (name == NULL || permission == 0 || permission > 3) return -1;
    if (SUPERBLOCKobj.FreeInode == 0) return -2;

    (SUPERBLOCKobj.FreeInode)--;
    PINODE temp = head;
    while (temp != NULL) {
        if (temp->FileType == 0) break;
        temp = temp->next;
    }

    int i = 0;
    while (i < MAXINODE) {
        if (UFDTArr[i].ptrfiletable == NULL) break;
        i++;
    }

    UFDTArr[i].ptrfiletable = (PFILETABLE)malloc(sizeof(FILETABLE));
    UFDTArr[i].ptrfiletable->count = 1;
    UFDTArr[i].ptrfiletable->mode = permission;
    UFDTArr[i].ptrfiletable->readoffset = 0;
    UFDTArr[i].ptrfiletable->writeoffset = 0;
    UFDTArr[i].ptrfiletable->ptrinode = temp;

    strcpy(temp->FileName, name);
    temp->FileType = REGULAR;
    temp->ReferenceCount = 1;
    temp->LinkCount = 1;
    temp->FileSize = MAXFILESIZE;
    temp->FileActualSize = 0;
    temp->permission = permission;
    temp->Buffer = (char *)malloc(MAXFILESIZE);
    memset(temp->Buffer, 0, MAXFILESIZE);

    return i;
}

int rm_File(char *name) {
    int fd = GetFDFromName(name);
    if (fd == -1) return -1;

    (UFDTArr[fd].ptrfiletable->ptrinode->LinkCount)--;
    if (UFDTArr[fd].ptrfiletable->ptrinode->LinkCount == 0) {
        UFDTArr[fd].ptrfiletable->ptrinode->FileType = 0;
        free(UFDTArr[fd].ptrfiletable->ptrinode->Buffer);
        free(UFDTArr[fd].ptrfiletable);
    }
    UFDTArr[fd].ptrfiletable = NULL;
    (SUPERBLOCKobj.FreeInode)++;
}

Step 3: Reading and Writing Files

File operations like reading and writing are controlled through offsets and buffers.

int ReadFile(int fd, char *arr, int isize) {
    if (UFDTArr[fd].ptrfiletable == NULL) return -1;
    int read_size = UFDTArr[fd].ptrfiletable->ptrinode->FileActualSize -
                    UFDTArr[fd].ptrfiletable->readoffset;
    if (read_size < isize) {
        strncpy(arr, UFDTArr[fd].ptrfiletable->ptrinode->Buffer +
                          UFDTArr[fd].ptrfiletable->readoffset,
                read_size);
        UFDTArr[fd].ptrfiletable->readoffset += read_size;
    } else {
        strncpy(arr, UFDTArr[fd].ptrfiletable->ptrinode->Buffer +
                          UFDTArr[fd].ptrfiletable->readoffset,
                isize);
        UFDTArr[fd].ptrfiletable->readoffset += isize;
    }
    return isize;
}

Step 4: Command-Line Interface

The CLI lets users interact with the VFS using commands like create, read, write, and delete.

int main() {
    char command[4][80], str[80];
    int count = 0;

    InitialiseSuperBlock();
    CreateDILB();

    while (1) {
        printf("\nCustomised VFS : > ");
        fgets(str, 80, stdin);

        count = sscanf(str, "%s %s %s %s", command[0], command[1], command[2],
                       command[3]);

        if (strcmp(command[0], "create") == 0) {
            CreateFile(command[1], atoi(command[2]));
        } else if (strcmp(command[0], "read") == 0) {
            // Handle read command
        } else if (strcmp(command[0], "write") == 0) {
            // Handle write command
        } else if (strcmp(command[0], "exit") == 0) {
            printf("Terminating the Customised Virtual File System\n");
            break;
        }
    }
    return 0;
}

🧠 4. Challenges & Learnings

Challenges:

  1. Memory Management: Allocating and freeing memory for buffers and inodes required careful handling to avoid leaks.

  2. Command Parsing: Implementing a robust parser for CLI commands was tricky but a great learning experience.

Learnings:

  • The importance of efficient data structures for real-world systems.

  • How operating systems manage file metadata through inodes and file tables.


🏃‍♂️ 5. How to Run It

Prerequisites:

  • A C++ compiler (e.g., GCC or Clang).

Steps:

  1. Clone the GitHub repository:

     git clone https://github.com/Mayurdpatil67/virtual-file-system.git
     cd virtual-file-system
    
  2. Compile the code:

     g++ CVFS.cpp -o CVFS
    
  3. Run the VFS:

     ./CVFS
    

Example Commands:

create file1 3
write file1
read file1 10
ls
rm file1

💻 6. GitHub Repository

The complete source code for this project is available on GitHub. Feel free to clone, fork, or contribute to the repository:


🌟 7. Conclusion

This project demonstrates the power and flexibility of a Virtual File System. You’ve learned how to:

  • Simulate file management with inodes, superblocks, and file tables.

  • Implement core file operations in C++.

  • Build a CLI for user interaction.

What’s Next?

  • Add directory support to organize files hierarchically.

  • Implement file compression to optimize storage.

  • Extend the CLI with advanced commands.

Happy coding! 🚀

0
Subscribe to my newsletter

Read articles from Mayur Patil directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mayur Patil
Mayur Patil

Skilled in Java & Spring Boot , Backedn Enthusiast