From 7a9c3012282ede0489ac5583b0f97c720d7e7c6d Mon Sep 17 00:00:00 2001 From: Marvin Friedrich Date: Sun, 13 Apr 2025 03:42:16 +0200 Subject: [PATCH 1/4] config: Read config files from limine.d --- common/fs/file.h | 13 ++++++++++ common/fs/file.s2.c | 8 ++++++ common/lib/config.c | 59 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/common/fs/file.h b/common/fs/file.h index 86474bd0..ead8dfc3 100644 --- a/common/fs/file.h +++ b/common/fs/file.h @@ -14,6 +14,17 @@ extern bool case_insensitive_fopen; bool fs_get_guid(struct guid *guid, struct volume *part); char *fs_get_label(struct volume *part); +enum { + DIR_ENTRY_TYPE_FILE, + DIR_ENTRY_TYPE_DIRECTORY, +}; + +#define DIR_ENTRY_NAME_LEN 384 +struct dir_entry { + char name[DIR_ENTRY_NAME_LEN]; + int type; +}; + struct file_handle { bool is_memfile; bool readall; @@ -23,6 +34,7 @@ struct file_handle { void *fd; void (*read)(void *fd, void *buf, uint64_t loc, uint64_t count); void (*close)(void *fd); + void *(*readdir)(void *fd, size_t *out_size); uint64_t size; #if defined (UEFI) EFI_HANDLE efi_part_handle; @@ -35,6 +47,7 @@ struct file_handle { struct file_handle *fopen(struct volume *part, const char *filename); void fread(struct file_handle *fd, void *buf, uint64_t loc, uint64_t count); void fclose(struct file_handle *fd); +struct dir_entry *freaddir(struct file_handle *fd, size_t *out_size); void *freadall(struct file_handle *fd, uint32_t type); void *freadall_mode(struct file_handle *fd, uint32_t type, bool allow_high_allocs #if defined (__i386__) diff --git a/common/fs/file.s2.c b/common/fs/file.s2.c index 8528f19a..7efa5e75 100644 --- a/common/fs/file.s2.c +++ b/common/fs/file.s2.c @@ -86,6 +86,14 @@ void fread(struct file_handle *fd, void *buf, uint64_t loc, uint64_t count) { } } +struct dir_entry *freaddir(struct file_handle *fd, size_t *out_size) { + if (!fd || !fd->readdir) { + return NULL; + } + + return fd->readdir(fd, out_size); +} + void *freadall(struct file_handle *fd, uint32_t type) { return freadall_mode(fd, type, false #if defined (__i386__) diff --git a/common/lib/config.c b/common/lib/config.c index a82aa08f..da5b4d2b 100644 --- a/common/lib/config.c +++ b/common/lib/config.c @@ -28,7 +28,11 @@ no_unwind bool bad_config = false; static char *config_addr; int init_config_disk(struct volume *part) { - struct file_handle *f; + struct file_handle *f; // The config file + struct file_handle *dir; // The limine.d directory + struct file_handle **files; // Files inside the limine.d directory + size_t num_files = 0; // Amount of files inside limine.d + size_t config_size = 0; bool old_cif = case_insensitive_fopen; case_insensitive_fopen = true; @@ -49,11 +53,62 @@ int init_config_disk(struct volume *part) { opened: case_insensitive_fopen = old_cif; + config_size = f->size + 2; + + // Get the path of the loaded config file so we can get limine.d/ from the same directory. + char* loaded_dir = ext_mem_alloc(f->path_len); + memcpy(loaded_dir, f->path, f->path_len); + for (int i = f->path_len; i >= 0; i--) { + if (loaded_dir[i] == '/') { + memcpy(loaded_dir + i, "/limine.d/", 11); + break; + } + } + + // Open the directory and get the amount of memory we need to allocate. + if ((dir = fopen(part, loaded_dir)) != NULL) { + size_t num_entries; + struct dir_entry *dir_entries = freaddir(dir, &num_entries); + files = ext_mem_alloc(num_entries * sizeof(struct file_handle *)); + for (size_t i = 0; i < num_entries; i++) { + if (dir_entries[i].type == DIR_ENTRY_TYPE_FILE) { + // Build an absolute path. + char *file_path = ext_mem_alloc(strlen(loaded_dir) + strlen(dir_entries[i].name)); + memcpy(file_path, loaded_dir, strlen(loaded_dir)); + memcpy(file_path + strlen(loaded_dir), dir_entries[i].name, strlen(dir_entries[i].name)); + + struct file_handle *entry = fopen(part, file_path); + if (entry) { + config_size += entry->size; + files[num_files++] = entry; + } + } + } + } - size_t config_size = f->size + 2; config_addr = ext_mem_alloc(config_size); + // Read the main config file. fread(f, config_addr, 0, f->size); + size_t config_cursor = f->size; + + // Sort all files by name in ascending order. + struct file_handle* temp = NULL; + for (size_t i = 1; i < num_files; i++) { + for (size_t j = 0; j < num_files - i; j++) { + if (strcmp(files[j]->path, files[j + 1]->path) > 0) { + temp = files[j]; + files[j] = files[j + 1]; + files[j + 1] = temp; + } + } + } + + // Concatenate all limine.d files. + for (size_t i = 0; i < num_files; i++) { + fread(files[i], config_addr + config_cursor, 0, files[i]->size); + config_cursor += files[i]->size; + } fclose(f); From 4a5e54dcad67ca62b1d09b2284d9ca74e92ca2a7 Mon Sep 17 00:00:00 2001 From: Marvin Friedrich Date: Sun, 13 Apr 2025 03:42:50 +0200 Subject: [PATCH 2/4] fs/fat: Implement readdir --- common/fs/fat32.s2.c | 136 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/common/fs/fat32.s2.c b/common/fs/fat32.s2.c index c01904b4..f86e3ba3 100644 --- a/common/fs/fat32.s2.c +++ b/common/fs/fat32.s2.c @@ -85,6 +85,11 @@ struct fat32_directory_entry { uint32_t file_size_bytes; } __attribute__((packed)); +struct fat32_directory_handle { + struct fat32_context context; + struct fat32_directory_entry entry; +}; + struct fat32_lfn_entry { uint8_t sequence_number; char name1[10]; @@ -453,6 +458,7 @@ char *fat32_get_label(struct volume *part) { static void fat32_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count); static void fat32_close(struct file_handle *file); +static struct dir_entry *fat32_readdir(struct file_handle *file, size_t *out_size); struct file_handle *fat32_open(struct volume *part, const char *path) { struct fat32_context context; @@ -515,6 +521,22 @@ struct file_handle *fat32_open(struct volume *part, const char *path) { if (expect_directory) { _current_directory = current_file; current_directory = &_current_directory; + + if (path[current_index] == 0) { + struct file_handle *handle = ext_mem_alloc(sizeof(struct file_handle)); + struct fat32_directory_handle *ret = ext_mem_alloc(sizeof(struct fat32_directory_handle)); + + ret->context = context; + ret->entry = _current_directory; + + handle->fd = (void *)ret; + handle->readdir = (void *)fat32_readdir; + handle->vol = part; +#if defined (UEFI) + handle->efi_part_handle = part->efi_part_handle; +#endif + return handle; + } } else { struct file_handle *handle = ext_mem_alloc(sizeof(struct file_handle)); struct fat32_file_handle *ret = ext_mem_alloc(sizeof(struct fat32_file_handle)); @@ -551,3 +573,117 @@ static void fat32_close(struct file_handle *file) { pmm_free(f->cluster_chain, f->chain_len * sizeof(uint32_t)); pmm_free(f, sizeof(struct fat32_file_handle)); } + +static struct dir_entry *fat32_readdir(struct file_handle *file, size_t *out_size) { + if (!file) + return NULL; + + struct fat32_directory_handle *d = file->fd; + uint32_t current_cluster_number = d->entry.cluster_num_low; + if (d->context.type == 32) + current_cluster_number |= (uint32_t)d->entry.cluster_num_high << 16; + + size_t dir_chain_len = 0; + uint32_t *directory_cluster_chain = cache_cluster_chain(&d->context, current_cluster_number, &dir_chain_len); + + if (directory_cluster_chain == NULL) + return NULL; + + struct fat32_directory_entry *directory_entries; + size_t block_size = d->context.sectors_per_cluster * d->context.bytes_per_sector; + directory_entries = ext_mem_alloc(dir_chain_len * block_size); + read_cluster_chain(&d->context, directory_cluster_chain, directory_entries, 0, dir_chain_len * block_size); + char current_lfn[FAT32_LFN_MAX_FILENAME_LENGTH] = {0}; + + const size_t num_entries = (dir_chain_len * block_size) / sizeof(struct fat32_directory_entry); + struct dir_entry *buffer = ext_mem_alloc(num_entries * sizeof(struct dir_entry)); + size_t buffer_idx = 0; + + for (size_t i = 0; i < num_entries; i++) { + if (directory_entries[i].file_name_and_ext[0] == 0x00) { + // no more entries here + break; + } + + if (directory_entries[i].attribute == FAT32_LFN_ATTRIBUTE) { + struct fat32_lfn_entry* lfn = (struct fat32_lfn_entry*) &directory_entries[i]; + + if (lfn->sequence_number & 0b01000000) { + // this lfn is the first entry in the table, clear the lfn buffer + memset(current_lfn, ' ', sizeof(current_lfn)); + } + + const unsigned int lfn_index = ((lfn->sequence_number & 0b00011111) - 1U) * 13U; + if (lfn_index >= FAT32_LFN_MAX_ENTRIES * 13) { + continue; + } + + fat32_lfncpy(current_lfn + lfn_index + 00, lfn->name1, 5); + fat32_lfncpy(current_lfn + lfn_index + 05, lfn->name2, 6); + fat32_lfncpy(current_lfn + lfn_index + 11, lfn->name3, 2); + + if (lfn_index != 0) + continue; + + // Remove trailing spaces. + for (int j = SIZEOF_ARRAY(current_lfn) - 2; j >= -1; j--) { + if (j == -1 || current_lfn[j] != ' ') { + current_lfn[j + 1] = 0; + break; + } + } + + continue; + } + + if (directory_entries[i].attribute & (1 << 3)) { + // It is a volume label, skip + continue; + } + + if (i > 0 && directory_entries[i - 1].attribute == FAT32_LFN_ATTRIBUTE) { + memcpy(buffer[buffer_idx].name, current_lfn, sizeof(current_lfn)); + } else { + // SFN + char sfn[8 + 1 + 3 + 1]; // 8 + '.' + 3 + NUL + size_t sfn_idx = 8; + memcpy(sfn, directory_entries[i].file_name_and_ext, 8); + // Remove trailing spaces. + for (int j = 7; j >= 0; j--) { + if (sfn[j] == ' ') { + sfn[j] = 0; + sfn_idx = j; + continue; + } + break; + } + // Check if we have a filename extension. + if (directory_entries[i].file_name_and_ext[8] != ' ') { + sfn[sfn_idx++] = '.'; + memcpy(sfn + sfn_idx, directory_entries[i].file_name_and_ext + 8, 3); + for (int j = 2; j >= 0; j--) { + if (sfn[sfn_idx + j] == ' ') { + sfn[sfn_idx + j] = 0; + continue; + } + break; + } + } + memcpy(buffer[buffer_idx].name, sfn, sizeof(sfn)); + } + + if (directory_entries[i].attribute == FAT32_ATTRIBUTE_SUBDIRECTORY) { + buffer[buffer_idx].type = DIR_ENTRY_TYPE_DIRECTORY; + } else { + buffer[buffer_idx].type = DIR_ENTRY_TYPE_FILE; + } + + buffer_idx++; + } + + pmm_free(directory_cluster_chain, dir_chain_len * sizeof(uint32_t)); + + *out_size = buffer_idx; + + return buffer; +} From d07ff227cb6d58e3b0b69452deadf7ab7b8db6da Mon Sep 17 00:00:00 2001 From: Marvin Friedrich Date: Sun, 13 Apr 2025 03:43:10 +0200 Subject: [PATCH 3/4] test: Use split config files --- .gitignore | 3 +++ test/limine.conf | 35 ---------------------------- test/limine.d/00-multiboot.conf | 9 +++++++ test/limine.d/01-efi-chainload.conf | 5 ++++ test/limine.d/02-bios-chainload.conf | 5 ++++ test/limine.d/03-legacy.conf | 12 ++++++++++ 6 files changed, 34 insertions(+), 35 deletions(-) create mode 100644 test/limine.d/00-multiboot.conf create mode 100644 test/limine.d/01-efi-chainload.conf create mode 100644 test/limine.d/02-bios-chainload.conf create mode 100644 test/limine.d/03-legacy.conf diff --git a/.gitignore b/.gitignore index e9e4cbd4..018dc037 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ *.dtb *~ +# Keep limine.d, since the .d conflicts with the '*.d' rule +!test/limine.d + /bin /build /toolchain-files diff --git a/test/limine.conf b/test/limine.conf index c2bc08fc..945438c2 100644 --- a/test/limine.conf +++ b/test/limine.conf @@ -22,38 +22,3 @@ backdrop: 008080 module_path: boot():/boot/bg.jpg dtb_path: boot():/boot/device_tree.dtb - -/Multiboot2 Test - comment: Test of the multiboot2 boot protocol. - - protocol: multiboot2 - kernel_path: boot():/boot/multiboot2.elf - kernel_cmdline: This is an example kernel command line. - - module_path: boot():/boot/bg.jpg - module_string: This is the first module. - -/EFI Chainloading - comment: Test EFI image chainloading. - - protocol: efi_chainload - image_path: boot():/EFI/BOOT/BOOTX64.EFI - -/BIOS Chainloading - comment: Test BIOS chainloading. - - protocol: bios_chainload - drive: 1 - -/+Legacy - comment: Directory containing legacy entries. - - //Multiboot1 Test - comment: Test of the multiboot1 boot protocol. - - protocol: multiboot1 - kernel_path: boot():/boot/multiboot.elf - kernel_cmdline: This is an example kernel command line. - - module_path: boot():/boot/bg.jpg - module_string: This is the first module. diff --git a/test/limine.d/00-multiboot.conf b/test/limine.d/00-multiboot.conf new file mode 100644 index 00000000..747c34c4 --- /dev/null +++ b/test/limine.d/00-multiboot.conf @@ -0,0 +1,9 @@ +/Multiboot2 Test + comment: Test of the multiboot2 boot protocol. + + protocol: multiboot2 + kernel_path: boot():/boot/multiboot2.elf + kernel_cmdline: This is an example kernel command line. + + module_path: boot():/boot/bg.jpg + module_string: This is the first module. diff --git a/test/limine.d/01-efi-chainload.conf b/test/limine.d/01-efi-chainload.conf new file mode 100644 index 00000000..5b277377 --- /dev/null +++ b/test/limine.d/01-efi-chainload.conf @@ -0,0 +1,5 @@ +/EFI Chainloading + comment: Test EFI image chainloading. + + protocol: efi_chainload + image_path: boot():/EFI/BOOT/BOOTX64.EFI diff --git a/test/limine.d/02-bios-chainload.conf b/test/limine.d/02-bios-chainload.conf new file mode 100644 index 00000000..725f30bf --- /dev/null +++ b/test/limine.d/02-bios-chainload.conf @@ -0,0 +1,5 @@ +/BIOS Chainloading + comment: Test BIOS chainloading. + + protocol: bios_chainload + drive: 1 diff --git a/test/limine.d/03-legacy.conf b/test/limine.d/03-legacy.conf new file mode 100644 index 00000000..33c12f7d --- /dev/null +++ b/test/limine.d/03-legacy.conf @@ -0,0 +1,12 @@ +/+Legacy + comment: Directory containing legacy entries. + + //Multiboot1 Test + comment: Test of the multiboot1 boot protocol. + + protocol: multiboot1 + kernel_path: boot():/boot/multiboot.elf + kernel_cmdline: This is an example kernel command line. + + module_path: boot():/boot/bg.jpg + module_string: This is the first module. From 951c7c58cb28bf8f1f32deb314f8740df8d8d654 Mon Sep 17 00:00:00 2001 From: Marvin Friedrich Date: Sun, 13 Apr 2025 03:43:54 +0200 Subject: [PATCH 4/4] docs: Document split config file loading --- CONFIG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONFIG.md b/CONFIG.md index aec29781..ae8ce2a0 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -25,6 +25,13 @@ candidates in subsequent partitions or directories are ignored. It is thus imperative that the intended config file is placed in a location that will not be shadowed by another candidate config file. +## Split config files + +Limine allows loading multiple additional config file snippets from a directory +named `limine.d/`. All files in that directory will be appended to +`limine.conf`, sorted by their file name in ascending order. +`limine.d/` has to be located in the same directory as `limine.conf`. + ## Structure of the config file The Limine configuration file is comprised of *menu entries* and *options*.