14.5 File Creation
14.5.1 The file_create Implementation
When creating a new file, several critical operations must be performed in sequence:
-
Inode Allocation: Each file requires an inode to track its size, location, and metadata. The process begins by allocating an inode number from the inode bitmap. This involves updating the inode bitmap, initializing the new inode structure in the inode table, and reserving space for the file descriptor.
-
Storage Block Allocation: The inode's i_sectors field stores the actual disk sector addresses where file data resides. Available blocks must be allocated from the block bitmap, which gets updated accordingly. The data area starting from data_start_lba receives these newly allocated sectors.
-
Directory Entry Creation: Every new file must exist within a parent directory. The parent directory's inode size increases by one directory entry size. The new directory entry must be written to one of the parent directory's sectors, potentially requiring allocation of a new sector if existing ones are full.
-
Rollback Handling: If any step fails during the creation process, all previously successful operations must be rolled back to maintain system consistency.
-
Disk Synchronization: Modified in-memory structures—including inode bitmap, block bitmap, the new file's inode, and the parent directory's inode—must be synchronized to persistent storage.
-
Open File List Update: The new file's inode gets added to the open inodes linked list.
-
File Descriptor Installation: A valid file descriptor must be installed in the process control block's file descriptor table.
int32_t file_create(struct dir* parent_dir, char* filename, uint8_t flags) {
void* buffer = sys_malloc(1024);
if (buffer == NULL) {
printk("file_create: failed to allocate I/O buffer\n");
return -1;
}
uint8_t rollback_flags = 0;
int32_t inode_number = inode_bitmap_alloc(cur_part);
if (inode_number == -1) {
printk("file_create: inode allocation failed\n");
goto cleanup;
}
struct inode* file_inode = (struct inode*)sys_malloc(sizeof(struct inode));
if (file_inode == NULL) {
printk("file_create: inode memory allocation failed\n");
rollback_flags = 1;
goto cleanup;
}
inode_init(inode_number, file_inode);
int file_descriptor = get_free_slot_in_global();
if (file_descriptor == -1) {
printk("file_create: maximum open files exceeded\n");
rollback_flags = 2;
goto cleanup;
}
file_table[file_descriptor].fd_inode = file_inode;
file_table[file_descriptor].fd_pos = 0;
file_table[file_descriptor].fd_flags = flags;
file_table[file_descriptor].fd_inode->write_deny = false;
struct dir_entry dir_entry;
memset(&dir_entry, 0, sizeof(struct dir_entry));
create_dir_entry(filename, inode_number, FT_REGULAR, &dir_entry);
if (!sync_dir_entry(parent_dir, &dir_entry, buffer)) {
printk("file_create: directory entry sync failed\n");
rollback_flags = 3;
goto cleanup;
}
memset(buffer, 0, 1024);
inode_sync(cur_part, parent_dir->inode, buffer);
memset(buffer, 0, 1024);
inode_sync(cur_part, file_inode, buffer);
bitmap_sync(cur_part, inode_number, INODE_BITMAP);
list_push(&cur_part->open_inodes, &file_inode->inode_tag);
file_inode->i_open_cnts = 1;
sys_free(buffer);
return pcb_fd_install(file_descriptor);
cleanup:
switch (rollback_flags) {
case 3:
memset(&file_table[file_descriptor], 0, sizeof(struct file));
case 2:
sys_free(file_inode);
case 1:
bitmap_set(&cur_part->inode_bitmap, inode_number, 0);
break;
}
sys_free(buffer);
return -1;
}
The file_create funtcion creates a regular file named filename within the parent_dir directory using the specified flags. On success, it returns the file descriptor; on failure, it returns -1. The implementation flow involves: allocating a shared buffer, obtaining an inode number, inserting the inode into the file table, creating the directory entry, synchronizing all memory data to disk, adding the inode to the open inodes list, and installing the file descriptor in the process control block.
14.5.2 The sys_open Implementation
int32_t sys_open(const char* pathname, uint8_t flags) {
if (pathname[strlen(pathname) - 1] == '/') {
printk("sys_open: cannot open a directory: %s\n", pathname);
return -1;
}
ASSERT(flags <= 7);
int32_t file_descriptor = -1;
struct path_search_record search_record;
memset(&search_record, 0, sizeof(struct path_search_record));
uint32_t path_depth = pathr_depth_cnt((char*)pathname);
int inode_number = search_file(pathname, &search_record);
bool file_exists = (inode_number != -1);
if (search_record.file_type == FT_DIRECTORY) {
printk("sys_open: cannot open directory with open(), use opendir()\n");
dir_close(search_record.parent_dir);
return -1;
}
uint32_t searched_depth = pathr_depth_cnt(search_record.searched_path);
if (path_depth != searched_depth) {
printk("sys_open: cannot access %s - subpath does not exist\n", pathname);
dir_close(search_record.parent_dir);
return -1;
}
if (!file_exists && !(flags & O_CREAT)) {
printk("sys_open: file not found in %s\n", search_record.searched_path);
dir_close(search_record.parent_dir);
return -1;
} else if (file_exists && (flags & O_CREAT)) {
printk("sys_open: file already exists: %s\n", pathname);
dir_close(search_record.parent_dir);
return -1;
}
switch (flags & O_CREAT) {
case O_CREAT:
printk("creating new file\n");
file_descriptor = file_create(
search_record.parent_dir,
(strrchr(pathname, '/') + 1),
flags
);
dir_close(search_record.parent_dir);
break;
}
return file_descriptor;
}
The sys_open function handles both file opening and creation. It returns a file descriptor from the process control block's fd_table on success, or -1 on failure. Currently, only the creation functionality is implemented. The function first searches for the specified path using search_file. If the file doesn't exist and the O_CREAT flag is set, file_create is invoked to create the new file. Several validation checks prevent incorrect usage: the function rejects attempts to open directories, verifies that all intermediate path components exist, ensures the O_CREAT flag is provided when creating new files, and prevents creation when the target file already exists.
14.5.3 Filesystem Initialization
void filesys_init(void) {
uint8_t channel_number = 0, device_number, partition_index = 0;
struct super_block* superblock_buffer =
(struct super_block*)sys_malloc(SECTOR_SIZE);
if (superblock_buffer == NULL) {
PANIC("memory allocation failed for superblock");
}
printk("initializing filesystem...\n");
while (channel_number < channel_count) {
device_number = 0;
while (device_number < 2) {
if (device_number == 0) {
device_number++;
continue;
}
struct disk* disk_device = &channels[channel_number].devices[device_number];
struct partition* partition = disk_device->prim_parts;
while (partition_index < 12) {
if (partition_index == 4) {
partition = disk_device->logic_parts;
}
if (partition->sec_count != 0) {
memset(superblock_buffer, 0, SECTOR_SIZE);
ide_read(disk_device, partition->start_lba + 1, superblock_buffer, 1);
if (superblock_buffer->magic == 0x19590318) {
printk("%s: existing filesystem detected\n", partition->name);
} else {
printk("formatting %s partition...\n",
disk_device->name, partition->name);
partition_format(partition);
}
}
partition_index++;
partition++;
}
device_number++;
}
channel_number++;
}
sys_free(superblock_buffer);
char default_partition[8] = "sdb1";
list_traversal(&partition_list, mount_partition, (int)default_partition);
open_root_dir(cur_part);
uint32_t file_descriptor_index = 0;
while (file_descriptor_index < MAX_FILE_OPEN) {
file_table[file_descriptor_index++].fd_inode = NULL;
}
}
The filesys_init function searches for and initializes filesystem structures on disk partitions. When no valid filesystem is found, it formats the partition to create a new one. During initialization, the function mounts the default partition through list_traversal, opens the root directory of the current partision, and initializes the global file table by clearing all entries.