Luna/kernel/src/init/InitRD.cpp
apio 78d72c2f0c Kernel, libc: Add a getdents() system call
This is meant to be a low-level interface to implement dirent.h on top of.
2022-10-23 14:03:46 +02:00

407 lines
12 KiB
C++

#define MODULE "initrd"
#include "init/InitRD.h"
#include "bootboot.h"
#include "fs/VFS.h"
#include "io/Serial.h"
#include "log/Log.h"
#include "memory/MemoryManager.h"
#include "misc/utils.h"
#include "std/errno.h"
#include "std/stdlib.h"
#include "std/string.h"
extern BOOTBOOT bootboot;
static void* initrd_base;
static bool initrd_initialized = false;
static VFS::Node initrd_root;
bool InitRD::is_initialized()
{
return initrd_initialized;
}
static inline uint64_t get_file_size_in_blocks(InitRD::File f)
{
return f.size_in_blocks;
}
inline uint64_t InitRD::get_total_blocks()
{
return bootboot.initrd_size / TAR_BLOCKSIZE;
}
inline InitRD::TarHeader* InitRD::get_block(uint64_t block_index)
{
return (TarHeader*)((uintptr_t)initrd_base + block_index * TAR_BLOCKSIZE);
}
inline bool InitRD::is_valid_header(TarHeader* header)
{
return strncmp(header->magic, TAR_MAGIC, 5) == 0;
}
uint64_t InitRD::get_file_physical_address(InitRD::File& file)
{
return (uint64_t)file.addr - (uint64_t)initrd_base + (uint64_t)bootboot.initrd_ptr;
}
InitRD::File InitRD::get_file(TarHeader* header)
{
File result;
result.size = 0;
memcpy(result.name, header->name, 100);
int multiplier =
1; // why they decided to store the size as an octal-encoded string instead of an integer is beyond me
for (int i = 10; i >= 0; i--)
{
result.size += (multiplier * (header->size[i] - 48));
multiplier *= 8;
}
result.addr = (void*)((uint64_t)header + TAR_BLOCKSIZE);
result.size_in_blocks = Utilities::get_blocks_from_size(TAR_BLOCKSIZE, result.size);
return result;
}
InitRD::File InitRD::open(const char* filename)
{
uint64_t block = 0;
uint64_t total_blocks = get_total_blocks();
while (block < total_blocks)
{
TarHeader* hdr = (TarHeader*)get_block(block);
if (hdr->typeflag == 53)
{
block++;
continue;
}
if (!is_valid_header(hdr))
{
block++;
continue;
}
auto f = get_file(hdr);
if (strncmp(hdr->name, filename, strlen(filename)) == 0) { return f; }
block += get_file_size_in_blocks(f) + 1;
}
File nullFile;
nullFile.addr = 0;
nullFile.size = 0;
memcpy(nullFile.name, "NULL", 5);
return nullFile;
}
void InitRD::for_each(void (*callback)(File& f))
{
uint64_t block = 0;
uint64_t total_blocks = get_total_blocks();
while (block < total_blocks)
{
TarHeader* hdr = (TarHeader*)get_block(block);
if (hdr->typeflag == 53)
{
block++;
continue;
}
if (!is_valid_header(hdr))
{
block++;
continue;
}
auto f = get_file(hdr);
block += get_file_size_in_blocks(f) + 1;
callback(f);
}
}
#define INITRD_MAX_FILES_IN_DIR 16
#define INITRD_MAX_FILES 64
namespace InitRD
{
struct Directory
{
char name[64];
int entries = 0;
VFS::Node* files[INITRD_MAX_FILES_IN_DIR];
};
}
void initrd_for_each_dir(void (*callback)(InitRD::Directory& f))
{
uint64_t block = 0;
uint64_t total_blocks = InitRD::get_total_blocks();
while (block < total_blocks)
{
InitRD::TarHeader* hdr = (InitRD::TarHeader*)InitRD::get_block(block);
if (!InitRD::is_valid_header(hdr))
{
block++;
continue;
}
if (hdr->typeflag == 53)
{
InitRD::Directory dir;
strncpy(dir.name, hdr->name, sizeof(dir.name));
callback(dir);
block++;
continue;
}
auto f = get_file(hdr);
block += get_file_size_in_blocks(f) + 1;
}
}
static InitRD::File files[INITRD_MAX_FILES];
static uint32_t total_files = 0;
static InitRD::Directory dirs[INITRD_MAX_FILES];
static uint32_t total_dirs = 0;
static VFS::Node nodes[INITRD_MAX_FILES + (INITRD_MAX_FILES - 1)]; // One of the dirs is the initrd_root
static uint32_t total_nodes = 0;
ssize_t initrd_read(VFS::Node* node, size_t offset, size_t length, char* buffer)
{
if (!node) return -1;
if (node->inode >= total_files) return -1;
InitRD::File& file = files[node->inode];
if (offset > file.size) return -1;
if (offset + length > file.size) { length = file.size - offset; }
memcpy(buffer, (void*)((uintptr_t)file.addr + offset), length);
return length;
}
VFS::Node* initrd_scan_dir(VFS::Node* node, const char* filename)
{
if (!node) return 0;
if (node->inode >= total_dirs) return 0;
InitRD::Directory dir = dirs[node->inode];
for (int i = 0; i < dir.entries; i++)
{
if (strncmp(dir.files[i]->name, filename, sizeof(VFS::Node::name)) == 0) { return dir.files[i]; }
}
return 0;
}
VFS::Node* initrd_read_dir(VFS::Node* node, long offset)
{
if (!node) return 0;
if (node->inode >= total_dirs) return 0;
InitRD::Directory dir = dirs[node->inode];
if (offset >= dir.entries) return 0;
return dir.files[offset];
}
int initrd_mkdir(VFS::Node* node, const char* name) // FIXME: Return proper error numbers.
{
if (total_dirs >= 32)
{
kwarnln("mkdir() failed: too many directories");
return -ENOSPC;
}
if (node->inode > total_dirs)
{
kwarnln("mkdir() failed: invalid node");
return -EINVAL;
}
if (!(node->type & VFS_DIRECTORY))
{
kwarnln("mkdir() failed: not a directory");
return -ENOTDIR;
}
InitRD::Directory& parent = dirs[node->inode];
if (parent.entries == INITRD_MAX_FILES_IN_DIR)
{
kwarnln("mkdir() failed: parent is full");
return -ENOSPC;
}
uint64_t inode = total_dirs;
VFS::Node& new_node = nodes[total_nodes++];
new_node.inode = inode;
new_node.find_func = initrd_scan_dir;
new_node.mkdir_func = initrd_mkdir;
new_node.length = 0;
new_node.type = VFS_DIRECTORY;
strncpy(new_node.name, name, sizeof(new_node.name));
InitRD::Directory dir;
strncpy(dir.name, name, sizeof(dir.name));
dir.entries = 0;
dirs[total_dirs++] = dir; // FIXME: Right now this isn't of worry, but there is a possibility for a TOCTOU bug here.
// Should use a spinlock or something.
node->length++;
parent.files[parent.entries++] = &new_node;
return 0;
}
static bool initrd_register_dir(InitRD::Directory& dir, uint64_t inode)
{
const char* filename = dir.name;
VFS::Node* current_node = &initrd_root;
while (true)
{
while (*filename == '/') { filename++; }
if (*filename == 0) { return false; }
size_t path_section_size = 0;
while (filename[path_section_size] && filename[path_section_size] != '/') { path_section_size++; }
if (filename[path_section_size]) // We are in a '/'
{
char* buffer = (char*)kmalloc(path_section_size + 1);
memcpy(buffer, filename, path_section_size);
buffer[path_section_size] = 0;
if (!current_node->find_func) { return false; }
VFS::Node* child = current_node->find_func(current_node, buffer);
if (!child) { return false; }
current_node = child;
kfree(buffer);
}
else
{
if (strncmp(filename, ".", path_section_size) != 0) // The current path section is not '.'
{
if (strncmp(filename, "..", path_section_size) == 0) { return false; }
if (!current_node->find_func) { return false; }
InitRD::Directory& parent = dirs[current_node->inode];
if (parent.entries == INITRD_MAX_FILES_IN_DIR) { return false; }
char* buffer = (char*)kmalloc(path_section_size + 1);
memcpy(buffer, filename, path_section_size);
buffer[path_section_size] = 0;
VFS::Node& node = nodes[total_nodes++];
node.inode = inode;
node.find_func = initrd_scan_dir;
node.length = 0;
node.type = VFS_DIRECTORY;
node.mkdir_func = initrd_mkdir;
node.readdir_func = initrd_read_dir;
node.length = 0;
strncpy(node.name, buffer, sizeof(node.name));
strncpy(dir.name, buffer, sizeof(dir.name));
parent.files[parent.entries++] = &node;
current_node->length++;
kfree(buffer);
return true;
}
else { return false; }
}
filename += path_section_size;
}
}
static bool initrd_register_file(InitRD::File& f, uint64_t inode)
{
const char* filename = f.name;
VFS::Node* current_node = &initrd_root;
while (true)
{
while (*filename == '/') { filename++; }
if (*filename == 0) { return false; }
size_t path_section_size = 0;
while (filename[path_section_size] && filename[path_section_size] != '/') { path_section_size++; }
if (filename[path_section_size]) // We are in a '/'
{
char* buffer = (char*)kmalloc(path_section_size + 1);
memcpy(buffer, filename, path_section_size);
buffer[path_section_size] = 0;
if (!current_node->find_func) { return false; }
VFS::Node* child = current_node->find_func(current_node, buffer);
if (!child) { return false; }
current_node = child;
kfree(buffer);
}
else
{
if (strncmp(filename, ".", path_section_size) != 0) // The current path section is not '.'
{
if (strncmp(filename, "..", path_section_size) == 0) { return false; }
if (!current_node->find_func) { return false; }
InitRD::Directory& parent = dirs[current_node->inode];
if (parent.entries == INITRD_MAX_FILES_IN_DIR) { return false; }
char* buffer = (char*)kmalloc(path_section_size + 1);
memcpy(buffer, filename, path_section_size);
buffer[path_section_size] = 0;
VFS::Node& node = nodes[total_nodes++];
node.inode = inode;
node.read_func = initrd_read;
node.length = f.size;
node.type = VFS_FILE;
strncpy(node.name, buffer, sizeof(node.name));
strncpy(f.name, buffer, sizeof(f.name));
parent.files[parent.entries++] = &node;
current_node->length++;
kfree(buffer);
return true;
}
else { return false; }
}
filename += path_section_size;
}
}
static void initrd_scan()
{
initrd_for_each_dir([](InitRD::Directory& dir) {
if (total_dirs >= 32)
{
kwarnln("Failed to register directory %s: Too many directories in initrd", dir.name);
return;
}
uint64_t inode = total_dirs;
if (initrd_register_dir(dir, inode)) dirs[total_dirs++] = dir;
});
InitRD::for_each([](InitRD::File& f) {
if (total_files >= 32)
{
kwarnln("Failed to register file %s: Too many files in initrd", f.name);
return;
}
uint64_t inode = total_files;
if (initrd_register_file(f, inode)) files[total_files++] = f;
});
}
static void initrd_initialize_root()
{
initrd_root.length = 0;
initrd_root.inode = 0;
initrd_root.type |= VFS_DIRECTORY;
InitRD::Directory& root = dirs[0];
total_dirs++;
strncpy(initrd_root.name, "initrd", sizeof(initrd_root.name));
strncpy(root.name, "initrd", sizeof(root.name));
initrd_root.find_func = initrd_scan_dir;
initrd_root.mkdir_func = initrd_mkdir;
initrd_root.readdir_func = initrd_read_dir;
}
void InitRD::init()
{
initrd_base = MemoryManager::get_unaligned_mappings(
(void*)bootboot.initrd_ptr, Utilities::get_blocks_from_size(PAGE_SIZE, bootboot.initrd_size));
kdbgln("physical base at %lx, size %lx, mapped to %p", bootboot.initrd_ptr, bootboot.initrd_size, initrd_base);
kdbgln("total blocks: %ld", get_total_blocks());
void* leak = kmalloc(4); // leak some memory so that kmalloc doesn't continously allocate and free pages
initrd_initialize_root();
initrd_scan();
VFS::mount_root(&initrd_root);
initrd_initialized = true;
kfree(leak);
}