Understanding File System Initialization and Operations

File System Initialization

This document details the initialization process of a file system, including the creation of the superblock, bitmap, inodes, and data blocks. It also covers essential file operations such as opening, closing, and reading files.

Superblock Initialization

The sb_init function initializes the superblock with essential file system metadata:

  • Block Size: Determined by the device.
  • Number of Inodes: Calculated based on the number of blocks and the percentage of inodes.
  • Number of Bitmap Blocks: Calculated based on the total blocks, inodes, and superblock size.
  • Number of Data Blocks: Calculated based on the total blocks, inodes, and bitmap blocks.
  • Root Inode: Initialized to 0.
static int sb_init (struct fs * file_system, int num_blocks, int percent_inodes) {
    fs->sb.block_size = block_get_block_size(fs->dev);
    fs->sb.num_inodes = num_blocks * percent_inodes / 100;
    fs->sb.num_bitmap = (num_blocks - 1) - fs->sb.num_inodes; /* Blocks used by inodes */
    fs->sb.num_bitmap = fs->sb.num_bitmap + fs->sb.block_size - 1;
    fs->sb.num_bitmap = fs->sb.num_bitmap / fs->sb.block_size;
    fs->sb.num_data_blocks = num_blocks - fs->sb.num_inodes - fs->sb.num_bitmap - 1;
    fs->sb.root_inode = 0;
    sb_write(fs->dev, &(fs->sb));
    return 1;
}

Bitmap Initialization

The bitmap_init function initializes the bitmap, which tracks the usage of data blocks:

  • Allocates a block of memory.
  • Sets all bits to 0, indicating that all data blocks are initially free.
  • Writes the bitmap to disk.
static int bitmap_init (struct file_system * fs) {
    int size = block_get_block_size(fs->dev);
    char *block = malloc(size);
    if (block == NULL) return -1; /* Error code */
    memset(block, '\0', size);
    for (int i = 0; i < fs->sb.num_bitmap; i++) {
        block_write(fs->dev, block, 1 + i);
    }
    bitmap_read(fs);
    return 1;
}

Inodes Initialization

The inodes_init function initializes the inodes, which store metadata about files:

  • Creates an empty inode structure.
  • Writes the empty inode to disk for each inode.
static int inodes_init (struct file_system * fs) {
    struct disk_inode ino;
    ino.size = -1; /* EMPTY */
    ino.e.start = -1;
    ino.e.size = -1;
    for (int i = 0; i < fs->sb.num_inodes; i++) {
        inode_write(fs, &ino, i);
    }
    return 1;
}

Data Blocks Initialization

The data_init function initializes the data blocks:

  • Allocates a block of memory.
  • Fills the block with a default character (‘@’).
  • Writes the block to disk for each data block.
static int data_init (struct file_system * fs) {
    int size = block_get_block_size(fs->dev);
    char *block = malloc(size);
    if (block == NULL) return -1; /* Error code */
    memset(block, '@', size);
    for (int i = 0; i < fs->sb.num_data_blocks; i++) {
        data_write(fs, block, i);
    }
    return 1;
}

Root Directory Creation

The root_dir_create function creates the root directory:

  • Initializes a block of memory to represent the directory entries.
  • Sets all directory entries to -1, indicating they are empty.
  • Sets the root inode size to 0 and allocates space for the root directory.
  • Writes the root directory to disk.
static int root_dir_create (struct file_system * fs) {
    struct disk_inode root;
    char block[fs->sb.block_size];
    struct entry *dir = (struct entry *)block;
    for (int i = 0; i < fs->sb.block_size / sizeof(struct entry); i++) {
        dir[i].inode = -1;
    }
    root.size = 0;
    root.e.start = 0;
    root.e.size = 8; /* 4 Blocks by default */
    for (int i = 0; i < root.e.size; i++) {
        bitmap_set(fs, root.e.start + i);
        data_write(fs, block, root.e.start + i);
    }
    bitmap_write(fs);
    inode_write(fs, &root, fs->sb.root_inode);
    return 1;
}

File System Creation

The mfs_mkfs function creates a new file system:

  • Allocates memory for the file system structure.
  • Creates the underlying block device.
  • Initializes the superblock, bitmap, inodes, data blocks, and root directory.
int mfs_mkfs (char *name, int num_blocks, int size_block, int percent_inodes) {
    printf("Creating file system with %s %d blocks of size %d and %d percent inodes\n", name, num_blocks, size_block, percent_inodes);
    struct file_system *fs = malloc(sizeof(struct file_system));
    if (fs == NULL) return -1; /* Error code */
    memset(fs, '\0', sizeof(struct file_system));
    fs->dev = block_create(name, num_blocks, size_block);
    if (fs->dev == NULL) {
        printf("Error creating data files of the system %s\n", name);
        perror("creating");
        return -1;
    }
    if (sb_init(fs, num_blocks, percent_inodes) <= 0) return -1;
    if (bitmap_init(fs) <= 0) return -1;
    if (inodes_init(fs) <= 0) return -1;
    if (data_init(fs) <= 0) return -1;
    if (root_dir_create(fs) <= 0) return -1;
    return 0;
}

File System Debugging and Operations

Superblock Printing

The sb_print function prints the superblock information:

  • Block size.
  • Number of inodes.
  • Number of bitmap blocks.
  • Number of data blocks.
static int sb_print (struct file_system * fs) {
    printf("Printing superblock info:\n");
    printf("block_size = %d\n", fs->sb.block_size);
    printf("num_inodes = %d\n", fs->sb.num_inodes);
    printf("num_bitmap = %d\n", fs->sb.num_bitmap);
    printf("num_data_blocks = %d\n", fs->sb.num_data_blocks);
    return 1;
}

Bitmap Printing

The bitmap_print function prints the used data blocks:

static int bitmap_print (struct file_system * fs) {
    for (int i = 0; i < fs->sb.num_data_blocks; i++) {
        if (bitmap_get(fs, i)) printf("%d data block is used\n", i);
    }
    return 1;
}

Inodes Printing

The inodes_print function prints the used inodes:

static int inodes_print (struct file_system * fs) {
    for (int i = 0; i < fs->sb.num_inodes; i++) {
        struct disk_inode ino;
        inode_read(fs, &ino, i);
        if (ino.size == -1) continue;
        printf("Inode %d used\n", i);
        printf("\tsize = %d\n", ino.size);
        printf("\tstart = %d\n", ino.e.start);
        printf("\tnum = %d\n", ino.e.size);
    }
    return 1;
}

Data Blocks Printing

The data_print function prints the content of used data blocks:

static int data_print (struct file_system * fs) {
    char unitialized[fs->sb.block_size];
    memset(unitialized, '@', fs->sb.block_size);
    for (int i = 0; i < fs->sb.num_data_blocks; i++) {
        char block[fs->sb.block_size];
        data_read(fs, block, i);
        if (memcmp(block, unitialized, fs->sb.block_size) == 0) continue;
        printf("Received %d block used\n", i);
        printf("*****\n\n");
        printf("%s", block);
        printf("\n\n*****\n");
    }
    return 1;
}

Root Directory Printing

The root_dir_print function prints the content of the root directory:

static int root_dir_print (struct file_system * fs) {
    struct disk_inode root;
    char block[fs->sb.block_size];
    struct entry *dir = (struct entry *)block;
    printf("Printing root directory:\n");
    inode_read(fs, &root, fs->sb.root_inode);
    for (int i = 0; i < root.e.size; i++) {
        data_read(fs, block, root.e.start + i);
        for (int j = 0; j < fs->sb.block_size / sizeof(struct entry); j++) {
            if (dir[j].inode != -1) {
                printf("\t%s: inode %d\n", words[j].name, dir[j].inode);
            }
        }
    }
    return 1;
}

File System Debugging

The mfs_debug function performs a debugging of the file system:

  • Prints the superblock information.
  • Prints the used data blocks.
  • Prints the used inodes.
  • Prints the content of used data blocks.
  • Prints the content of the root directory.
int mfs_debug (char *name) {
    printf("%s file system debugging\n", name);
    fs_init();
    if (sb_print(fs) <= 0) return -1;
    if (bitmap_print(fs) <= 0) return -1;
    if (inodes_print(fs) <= 0) return -1;
    if (data_print(fs) <= 0) return -1;
    if (root_dir_print(fs) <= 0) return -1;
    return 0;
}

File Operations

Opening a File

The mfs_open function opens a file:

  • Finds the inode of the file.
  • If the file does not exist and the O_CREAT flag is set, it creates a new file.
  • Allocates a file descriptor.
  • Initializes the file structure.
int mfs_open (const char *pathname, int flags) {
    fs_init();
    int inode;
    int fd = -1;
    char block[fs->sb.block_size];
    struct entry *dir = (struct entry *)block;
    inode = inode(fs, &fs->root, pathname);
    if (inode == -1 && (flags & O_CREAT)) {
        for (int i = 0; i < fs->root.e.size && inode == -1; i++) {
            data_read(fs, block, fs->root.e.start + i);
            for (int j = 0; j < fs->sb.block_size / sizeof(struct entry); j++) {
                if (dir[j].inode == -1) {
                    inode = get_free_inode(fs);
                    if (inode == -1) return -1;
                    dir[j].inode = inode;
                    strncpy(dir[j].name, pathname, ENTRY_SIZE);
                    data_write(fs, block, fs->root.e.start + i);
                    inode = dir[j].inode;
                    break;
                }
            }
        }
    }
    if (inode == -1) return -1;
    for (int i = 0; i < NUM_FILES; i++) {
        if (fs->file[i].num == -1) {
            fd = i;
            break;
        }
    }
    if (fd != -1) {
        inode_read(fs, &fs->file[fd].ino, inode);
        fs->file[fd].pos = 0;
        fs->file[fd].inode = inode;
    }
    return fd;
}

Closing a File

The mfs_close function closes a file:

  • Checks if the file descriptor is valid.
  • Writes the inode back to disk.
int mfs_close (int fd) {
    if (fd < 0 || fd >= NUM_FILES) return -1;
    if (fs->file[fd].num == -1) {
        printf("Trying to close unopened fd\n");
        return -1;
    }
    inode_write(fs, &fs->file[fd].ino, fs->file[fd].num);
    return 0;
}

Reading from a File

The mfs_read function reads data from a file:

  • Checks if the file descriptor is valid.
  • Calculates the start position and the number of blocks to read.
  • Reads the data from the disk into the buffer.
int mfs_read (int fd, void *buf, size_t count) {
    int start, not_done;
    int num_block;
    char block[fs->sb.block_size];
    char *next;
    if (fd < 0 || fd >= NUM_FILES) return -1;
    if (fs->file[fd].num == -1) {
        printf("Trying to read unopened fd\n");
        return -1;
    }
    if (count > fs->file[fd].ino.size - fs->file[fd].pos) {
        count = fs->file[fd].ino.size - fs->file[fd].pos;
    }
    start = fs->file[fd].pos % fs->sb.block_size;
    num_block = (fs->file[fd].pos / fs->sb.block_size) + fs->file[fd].ino.e.start;
    not_done = count;
    next = buf;
    while (not_done) {
        int len;
        if (not_done > fs->sb.block_size - start) {
            len = fs->sb.block_size - start;
        } else {
            len = not_done;
        }
        if (len != fs->sb.block_size) {
            data_read(fs, block, num_block);
            memcpy(next, block + start, len);
        } else {
            data_read(fs, next, num_block);
        }
        num_block++;
        not_done -= len;
        next += len;
        start = 0;
    }
    fs->file[fd].pos += count;
    return count;
}