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;
}