From 5f33407ff91623f6ea85229253cf07ae7d8c1d5c Mon Sep 17 00:00:00 2001 From: nkraetzschmar <9020053+nkraetzschmar@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:22:45 +0200 Subject: [PATCH] feat: add write support to I/O stack (disk, ESP, FAT32) --- .github/workflows/build.yml | 2 +- .gitignore | 4 +- Makefile | 42 +++- bios_services.c | 40 ++++ bios_services.h | 17 +- bios_services_emu.c | 52 ++++- fat32.c | 421 ++++++++++++++++++++++++++++++++++-- fat32.h | 28 ++- fat32_internal.h | 7 + gpt.c | 18 ++ gpt.h | 9 +- io_buf.h | 6 +- lib.h | 22 +- linux.h | 6 +- loader.c | 5 +- loader.h | 16 +- loader_internal.h | 6 + make_test_fat32_disk.sh | 48 ++++ prefix.h | 11 + setup_header.h | 2 + test_fat32.c | 174 +++++++++++++++ test_loader.c | 101 +++++---- types.h | 2 + 23 files changed, 917 insertions(+), 122 deletions(-) create mode 100644 fat32_internal.h create mode 100644 loader_internal.h create mode 100755 make_test_fat32_disk.sh create mode 100644 prefix.h create mode 100644 test_fat32.c diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82d5c02..1a1a1d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,4 +29,4 @@ jobs: run: podman run --rm --read-only -v "$PWD:/workspace" -w /workspace dev make all - name: test timeout-minutes: 1 - run: podman run --rm --read-only -v "$PWD:/workspace" -w /workspace dev make test_loader emu_test test uki_test kexec_test + run: podman run --rm --read-only -v "$PWD:/workspace" -w /workspace dev make test diff --git a/.gitignore b/.gitignore index 715a72e..5227fea 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,8 @@ bootloader_emu busybox busybox.tar.bz2 -demo.img demo_uki.img +demo.img dependencies.make disk hello @@ -19,5 +19,7 @@ kexec.cpio mini_kexec purgatory.h serial.log +test_fat32 +test_fat32_disk test_loader uki_disk diff --git a/Makefile b/Makefile index 5723e0b..83b973d 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,13 @@ MAKEFLAGS += --no-builtin-rules .SILENT: -.PHONY: all clean distclean test uki_test kexec_test +UNIT_TESTS := loader fat32 +.PHONY: all clean distclean test unit_tests qemu_test uki_test kexec_test emu_test $(UNIT_TESTS:%=run_test_%) CC := gcc CC_X86 := x86_64-linux-gnu-gcc CFLAGS := -std=c23 -Os -g -Wall -Wextra -Wdeclaration-after-statement -Werror -fpack-struct -fno-builtin +CFLAGS_USER := $(CFLAGS) -DNO_STATIC_INTERNALS CFLAGS_M16 := $(CFLAGS) -m16 -march=i386 -nostdinc -ffreestanding -fno-pic -fno-stack-protector -fno-asynchronous-unwind-tables -fno-unwind-tables -fno-exceptions -ffunction-sections -fdata-sections LD := ld @@ -22,6 +24,10 @@ OBJDUMP_FLAGS_M16 := -m i8086 -M intel all: disk uki.efi kexec.cpio bootloader_emu kernel +test: unit_tests emu_test qemu_test uki_test kexec_test + +unit_tests: $(UNIT_TESTS:%=run_test_%) + clean: git clean -e '!kernel' -e '!kernel.tar.xz' -e '!busybox.tar.bz2' -fX @@ -29,7 +35,7 @@ distclean: git clean -fX rm -rf kernel_headers busybox -test: disk +qemu_test: disk echo 'running $< in qemu' ./run_vm.sh -n '$<' | tee serial.log grep -F 'Found GPT disk: 01234567-ABCD-0123-ABCD-0123456789AB' < serial.log > /dev/null @@ -70,23 +76,37 @@ dependencies.make: *.c include dependencies.make -bootloader.o: main.o io_buf.o lib.o gpt.o fat32.o loader.o linux.o +bootloader.o: main.emu.o io_buf.emu.o lib.emu.o gpt.emu.o fat32.emu.o loader.emu.o linux.emu.o echo 'linking $^ -> $@' $(LD) -r -o '$@' $^ - $(OBJCOPY) --keep-global-symbol=init '$@' + $(OBJCOPY) --keep-global-symbol=bootloader_init '$@' $(OBJDUMP) -h '$@' bootloader_emu: bootloader.o bios_services_emu.o echo 'linking $^ -> $@' $(CC) -o '$@' $^ -test_loader: lib.o gpt.o fat32.o io_buf.o +test_loader: lib.emu.o gpt.emu.o fat32.emu.o io_buf.emu.o loader.emu.o + +test_fat32: lib.emu.o gpt.emu.o fat32.emu.o io_buf.emu.o + +test_fat32_inputs := test_fat32_disk + +test_fat32_disk: make_test_fat32_disk.sh + echo 'creating $@' + ./$< '$@' test_%: test_%.o echo 'linking $^ -> $@' $(CC) -o '$@' $^ - echo 'running $@' - ./'$@' + +define UNIT_TEST_RULE +run_test_$(1): test_$(1) $$(test_$(1)_inputs) + echo 'running $$<' + ./'$$<' $$(test_$(1)_inputs) +endef + +$(foreach unit,$(UNIT_TESTS),$(eval $(call UNIT_TEST_RULE,$(unit)))) disk: make_disk.sh mbr.bin bootloader.bin kernel initrd.cpio echo 'creating $@' @@ -157,7 +177,13 @@ uki.efi: pe_inject.py uki_base.efi kernel_stub.bin pe_loader.bin %.o: echo 'compiling $< -> $@' - $(CC) $(CFLAGS) -c '$<' -o '$@' + $(CC) $(CFLAGS_USER) -c '$<' -o '$@' + $(OBJDUMP) -h '$@' + +%.emu.o: %.o + echo 'prefixing $< -> $@' + $(OBJCOPY) --prefix-symbols=bootloader_ '$<' '$@' + $(OBJCOPY) --redefine-sym bootloader_memset=memset '$@' $(OBJDUMP) -h '$@' %.m16.o: diff --git a/bios_services.c b/bios_services.c index 8cc656d..822fbd5 100644 --- a/bios_services.c +++ b/bios_services.c @@ -78,6 +78,46 @@ int16 disk_read(uint8 *buffer, uint16 sectors, uint32 lba) return 0; } +int16 disk_write(const uint8 *buffer, uint16 sectors, uint32 lba) +{ + static struct disk_address_packet disk_address_packet; + + uint8 result; + uint8 error; + uint16 offset; + uint16 segment; + + if (sectors > 64) return -1; + + if ((uint32) buffer > 0x000fffff) return -1; + offset = ((uint32) buffer & 0x0000ffff); + segment = ((uint32) buffer & 0x000f0000) >> 4; + + disk_address_packet = (struct disk_address_packet) { + .size = 0x10, + .reserved = 0x00, + .sectors = sectors, + .buffer_offset = offset, + .buffer_segment = segment, + .lba_low = lba, + .lba_high = 0x00000000 + }; + + __asm__ __volatile__ ( "int $0x13\nsetb %[res]\nmov %%ah, %[err]" + : [res] "=r" (result), [err] "=r" (error) + : "a" (0x4300), "d" (0x0080), "S" (&disk_address_packet) + : ); + + if (result != 0) { + print_str("disk write failed with ERR "); + print_hex_be(&error, 0x0001); + print_str("\r\n"); + return -1; + } + + return 0; +} + int16 mem_move(uint8 *dst, const uint8 *src, uint32 len) { static struct global_descriptor_table global_descriptor_table[6]; diff --git a/bios_services.h b/bios_services.h index 8ef8b12..cd21c7f 100644 --- a/bios_services.h +++ b/bios_services.h @@ -1,6 +1,11 @@ -void halt(); -void reset(); -void print_char(char c); -int16 disk_read(uint8 *buffer, uint16 sectors, uint32 lba); -int16 mem_move(uint8 *dst, const uint8 *src, uint32 len); -void exec_kernel(); +#pragma once + +#include "prefix.h" + +void PREFIX(halt)(); +void PREFIX(reset)(); +void PREFIX(print_char)(char c); +int16 PREFIX(disk_read)(uint8 *buffer, uint16 sectors, uint32 lba); +int16 PREFIX(disk_write)(const uint8 *buffer, uint16 sectors, uint32 lba); +int16 PREFIX(mem_move)(uint8 *dst, const uint8 *src, uint32 len); +void PREFIX(exec_kernel)(); diff --git a/bios_services_emu.c b/bios_services_emu.c index 80f9414..45f943d 100644 --- a/bios_services_emu.c +++ b/bios_services_emu.c @@ -5,6 +5,8 @@ #include #include +#define PREFIX(name) bootloader_##name + #include "types.h" #include "bios_services.h" @@ -15,7 +17,7 @@ static int debug = 0; static int disk_fd = -1; -void init(); +void bootloader_init(); int main(int argc, char **argv) { @@ -29,34 +31,34 @@ int main(int argc, char **argv) exit(1); } - disk_fd = open(argv[1], O_RDONLY); + disk_fd = open(argv[1], O_RDWR); if (disk_fd == -1) { perror("open"); exit(1); } - init(); + bootloader_init(); } -void halt() +void bootloader_halt() { debug_printf(); while (1) pause(); } -void reset() +void bootloader_reset() { debug_printf(); exit(0); } -void print_char(char c) +void bootloader_print_char(char c) { if (c != 0) fputc(c, stdout); else fflush(stdout); } -int16 disk_read(uint8 *buffer, uint16 sectors, uint32 lba) +int16 bootloader_disk_read(uint8 *buffer, uint16 sectors, uint32 lba) { uint32 len; uint32 offset; @@ -88,13 +90,45 @@ int16 disk_read(uint8 *buffer, uint16 sectors, uint32 lba) return 0; } -int16 mem_move(uint8 *dst, const uint8 *src, uint32 len) +int16 bootloader_disk_write(const uint8 *buffer, uint16 sectors, uint32 lba) +{ + uint32 len; + uint32 offset; + ssize_t len_written; + + debug_printf("buffer=0x%08lx, sectors=%u, lba=%u)", (size_t) buffer, sectors, lba); + + if (sectors > 64) return -1; + + len = sectors * 0x0200; + offset = lba * 0x0200; + + while (len) { + len_written = pwrite(disk_fd, buffer, len, offset); + + if (len_written == -1) { + perror("pwrite"); + return -1; + } else if (len_written == 0) { + fprintf(stderr, "unexpected zero-length write\n"); + return -1; + } + + len -= len_written; + offset += len_written; + buffer += len_written; + } + + return 0; +} + +int16 bootloader_mem_move(uint8 *dst, const uint8 *src, uint32 len) { debug_printf("dst=0x%08lx, src=0x%08lx, len=%u)", (size_t) dst, (size_t) src, len); return 0; } -void exec_kernel() +void bootloader_exec_kernel() { debug_printf(); exit(0); diff --git a/fat32.c b/fat32.c index 36ff988..e91a00c 100644 --- a/fat32.c +++ b/fat32.c @@ -1,5 +1,6 @@ #include "types.h" #include "fat32.h" +#include "fat32_internal.h" #include "lib.h" #include "gpt.h" #include "io_buf.h" @@ -83,8 +84,18 @@ static struct { uint32 start_cluster; uint32 current_cluster; uint32 offset; + int32 entry_index; } file; +static uint32 load_dir(struct fat32_dir_entry *entries, uint32 capacity); +static int16 store_dir(const struct fat32_dir_entry *entries, uint32 entries_len); +static int16 for_each_dir_entry_loaded(struct fat32_dir_entry *entries, uint32 entries_len, void (*callback)(const char *entry_name, uint32 entry_cluster, uint32 entry_size)); +static int16 open_loaded(struct fat32_dir_entry *entries, uint32 entries_len, const char *name); +static int16 unlink_loaded(struct fat32_dir_entry *entries); +static int16 link_loaded(struct fat32_dir_entry *entries, uint32 entries_len, const char *name); +static int16 rename_loaded(struct fat32_dir_entry *entries, uint32 entries_len, const char *name); +static int16 chdir_loaded(struct fat32_dir_entry *entries, uint32 entries_len, const char *dir); + static int16 check_bpb(struct fat32_bpb *bpb) { if (bpb->signature != 0xaa55) { @@ -155,6 +166,7 @@ int16 load_fat32() file.start_cluster = 0; file.current_cluster = 0; file.offset = 0; + file.entry_index = -1; return 0; } @@ -189,6 +201,32 @@ static char * decode_name(struct fat32_dir_entry *entry) return buf; } +static uint32 load_dir(struct fat32_dir_entry *entries, uint32 capacity) +{ + uint32 read_sectors; + + file.size = 0x00000000; + file.start_cluster = fs.current_dir; + reset_seek(); + + read_sectors = read((uint8 *) entries, capacity / 0x0010); + return read_sectors * 0x0010; +} + +static int16 store_dir(const struct fat32_dir_entry *entries, uint32 entries_len) +{ + uint32 sectors; + + file.size = 0x00000000; + file.start_cluster = fs.current_dir; + reset_seek(); + + sectors = (entries_len + 0x000f) / 0x0010; + if (write((const uint8 *) entries, sectors) != sectors) return -1; + + return 0; +} + static struct fat32_dir_entry * next_entry(struct fat32_dir_entry **entries, uint32 *entries_len, char **entry_name) { static char buf[0x0100]; @@ -239,24 +277,20 @@ static struct fat32_dir_entry * next_entry(struct fat32_dir_entry **entries, uin int16 for_each_dir_entry(void (*callback)(const char *entry_name, uint32 entry_cluster, uint32 entry_size)) { - uint32 read_sectors; - struct fat32_dir_entry *entries; uint32 entries_len; + entries_len = load_dir((struct fat32_dir_entry *) io_buf, 0x00000800); + if (entries_len == 0) return -1; + + return for_each_dir_entry_loaded((struct fat32_dir_entry *) io_buf, entries_len, callback); +} + +static int16 for_each_dir_entry_loaded(struct fat32_dir_entry *entries, uint32 entries_len, void (*callback)(const char *entry_name, uint32 entry_cluster, uint32 entry_size)) +{ struct fat32_dir_entry *entry; char *entry_name; uint32 entry_cluster; - file.size = 0x00000000; - file.start_cluster = fs.current_dir; - reset_seek(); - - read_sectors = read(io_buf, 0x00000080); - if (read_sectors == 0x00000000) return -1; - - entries = (struct fat32_dir_entry *) io_buf; - entries_len = read_sectors * 0x0010; - while ((entry = next_entry(&entries, &entries_len, &entry_name)) != (void *) 0) { entry_cluster = (((uint32) entry->cluster_high) << 0x10) + entry->cluster_low; callback(entry_name, entry_cluster, entry->size); @@ -269,31 +303,37 @@ void open_cluster(uint32 cluster, uint32 size) { file.size = size; file.start_cluster = cluster; + file.entry_index = -1; reset_seek(); } int16 open(const char *name) { - uint32 read_sectors; - struct fat32_dir_entry *entries; uint32 entries_len; + entries_len = load_dir((struct fat32_dir_entry *) io_buf, 0x00000800); + if (entries_len == 0) return -1; + + return open_loaded((struct fat32_dir_entry *) io_buf, entries_len, name); +} + +static int16 open_loaded(struct fat32_dir_entry *entries_start, uint32 entries_len, const char *name) +{ + struct fat32_dir_entry *entries; struct fat32_dir_entry *entry; char *entry_name; file.size = 0x00000000; file.start_cluster = fs.current_dir; + file.entry_index = -1; reset_seek(); - read_sectors = read(io_buf, 0x00000080); - if (read_sectors == 0x00000000) return -1; - - entries = (struct fat32_dir_entry *) io_buf; - entries_len = read_sectors * 0x0010; + entries = entries_start; while ((entry = next_entry(&entries, &entries_len, &entry_name)) != (void *) 0) { if (streq(entry_name, name)) { open_cluster((((uint32) entry->cluster_high) << 0x10) + entry->cluster_low, entry->size); + file.entry_index = (int32)(entry - entries_start); return 0; } } @@ -301,6 +341,295 @@ int16 open(const char *name) return -1; } +int16 unlink() +{ + int16 error; + uint32 entries_len; + + entries_len = load_dir((struct fat32_dir_entry *) io_buf, 0x00000800); + if (entries_len == 0) return -1; + + error = unlink_loaded((struct fat32_dir_entry *) io_buf); + if (error != 0) return error; + + return store_dir((struct fat32_dir_entry *) io_buf, entries_len); +} + +static int16 unlink_loaded(struct fat32_dir_entry *entries) +{ + int32 lfn_start; + int32 i; + + if (file.entry_index < 0) { + print_str("No file opened\r\n"); + return -1; + } + + if (entries[file.entry_index].attr & 0x10) { + print_str("Cannot unlink directory\r\n"); + return -1; + } + + lfn_start = file.entry_index; + for (i = file.entry_index - 1; i >= 0; --i) { + if (entries[i].attr == 0x0F) + lfn_start = i; + else + break; + } + + for (i = lfn_start; i <= file.entry_index; ++i) { + *((uint8 *) entries[i].name) = 0xe5; + } + + return 0; +} + +static uint16 lfn_entries_needed(const char *name) +{ + return (strlen(name) + 12) / 13; +} + +static uint8 check_slot(struct fat32_dir_entry *entries_start, uint32 total_entries, uint32 index, uint32 count) +{ + uint8 first_byte; + + if (index + count > total_entries) return 0; + + for (uint32 i = 0; i < count; ++i) { + first_byte = *((uint8 *) entries_start[index + i].name); + if (first_byte != 0xe5 && first_byte != 0x00) return 0; + } + + return 1; +} + +static int32 find_free_slot(struct fat32_dir_entry *entries_start, uint32 total_entries, uint32 count) +{ + uint32 run_start; + uint32 run_len; + uint8 first_byte; + + run_start = 0; + run_len = 0; + + for (uint32 i = 0; i < total_entries; ++i) { + first_byte = *((uint8 *) entries_start[i].name); + if (first_byte == 0xe5 || first_byte == 0x00) { + if (run_len == 0) run_start = i; + run_len += 1; + if (run_len >= count) return (int32) run_start; + } else { + run_len = 0; + } + } + + return -1; +} + +static void make_short_name(const char *name, char *short_name) +{ + uint16 i; + uint16 j; + const char *dot; + char c; + + for (i = 0; i < 11; ++i) short_name[i] = ' '; + + dot = (void *) 0; + for (i = 0; name[i] != 0x00; ++i) { + if (name[i] == '.') dot = &name[i]; + } + + j = 0; + for (i = 0; name[i] != 0x00 && j < 6; ++i) { + if (&name[i] == dot) break; + c = name[i]; + if (c >= 'a' && c <= 'z') c = 'A' + (c - 'a'); + short_name[j++] = c; + } + short_name[6] = '~'; + short_name[7] = '1'; + + if (dot) { + j = 8; + for (i = 1; dot[i] != 0x00 && j < 11; ++i) { + c = dot[i]; + if (c >= 'a' && c <= 'z') c = 'A' + (c - 'a'); + short_name[j++] = c; + } + } +} + +static uint8 lfn_checksum(const char *short_name) +{ + uint8 sum; + + sum = 0; + for (uint16 i = 0; i < 11; ++i) { + sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1) + (uint8) short_name[i]; + } + + return sum; +} + +static void write_lfn_entry(struct fat32_lfn_entry *entry, const char *name, uint16 name_len, uint8 seq, uint8 checksum) +{ + uint16 offset; + uint16 i; + uint16 ch; + + entry->seq = seq; + entry->attr = 0x0F; + entry->reserved = 0x00; + entry->checksum = checksum; + entry->zero = 0x0000; + + offset = ((seq & 0x3f) - 1) * 13; + + for (i = 0; i < 5; ++i) { + ch = (offset + i < name_len) ? (uint16)(uint8) name[offset + i] : (offset + i == name_len) ? 0x0000 : 0xFFFF; + entry->name1[i] = ch; + } + for (i = 0; i < 6; ++i) { + ch = (offset + 5 + i < name_len) ? (uint16)(uint8) name[offset + 5 + i] : (offset + 5 + i == name_len) ? 0x0000 : 0xFFFF; + entry->name2[i] = ch; + } + for (i = 0; i < 2; ++i) { + ch = (offset + 11 + i < name_len) ? (uint16)(uint8) name[offset + 11 + i] : (offset + 11 + i == name_len) ? 0x0000 : 0xFFFF; + entry->name3[i] = ch; + } +} + +static void write_short_entry(struct fat32_dir_entry *entry, const char *short_name, uint32 cluster, uint32 size) +{ + for (uint16 i = 0; i < 8; ++i) entry->name[i] = short_name[i]; + for (uint16 i = 0; i < 3; ++i) entry->ext[i] = short_name[8 + i]; + entry->attr = 0x20; + entry->reserved = 0x00; + entry->time_ts = 0x00; + entry->time = 0x0000; + entry->date = 0x0000; + entry->access_date = 0x0000; + entry->cluster_high = (uint16)(cluster >> 0x10); + entry->mod_time = 0x0000; + entry->mod_date = 0x0000; + entry->cluster_low = (uint16)(cluster & 0xffff); + entry->size = size; +} + +int16 link(const char *name) +{ + int16 error; + uint32 entries_len; + uint32 cluster; + uint32 size; + + cluster = file.start_cluster; + size = file.size; + + entries_len = load_dir((struct fat32_dir_entry *) io_buf, 0x00000800); + if (entries_len == 0) return -1; + + file.start_cluster = cluster; + file.size = size; + + error = link_loaded((struct fat32_dir_entry *) io_buf, entries_len, name); + if (error != 0) return error; + + return store_dir((struct fat32_dir_entry *) io_buf, entries_len); +} + +static int16 link_loaded(struct fat32_dir_entry *entries, uint32 entries_len, const char *name) +{ + uint16 lfn_count; + uint32 needed; + int32 slot; + + char short_name[11]; + uint8 checksum; + uint16 name_len; + + uint32 cluster; + uint32 size; + + cluster = file.start_cluster; + size = file.size; + + lfn_count = lfn_entries_needed(name); + needed = lfn_count + 1; + + slot = -1; + if (file.entry_index >= 0) { + int32 candidate; + candidate = file.entry_index - (int32) lfn_count; + if (candidate >= 0 && check_slot(entries, entries_len, (uint32) candidate, needed)) { + slot = candidate; + } + } + if (slot < 0) { + slot = find_free_slot(entries, entries_len, needed); + } + if (slot < 0) { + print_str("No free directory entries\r\n"); + return -1; + } + + make_short_name(name, short_name); + checksum = lfn_checksum(short_name); + name_len = strlen(name); + + for (uint16 i = 0; i < lfn_count; ++i) { + uint8 seq; + seq = (lfn_count - i); + if (i == 0) seq |= 0x40; + write_lfn_entry( + (struct fat32_lfn_entry *) &entries[slot + i], + name, name_len, seq, checksum + ); + } + + write_short_entry(&entries[slot + lfn_count], short_name, cluster, size); + + file.start_cluster = cluster; + file.size = size; + file.entry_index = slot + (int32) lfn_count; + + return 0; +} + +int16 rename(const char *name) +{ + int16 error; + uint32 entries_len; + uint32 cluster; + uint32 size; + + cluster = file.start_cluster; + size = file.size; + + entries_len = load_dir((struct fat32_dir_entry *) io_buf, 0x00000800); + if (entries_len == 0) return -1; + + file.start_cluster = cluster; + file.size = size; + + error = rename_loaded((struct fat32_dir_entry *) io_buf, entries_len, name); + if (error != 0) return error; + + return store_dir((struct fat32_dir_entry *) io_buf, entries_len); +} + +static int16 rename_loaded(struct fat32_dir_entry *entries, uint32 entries_len, const char *name) +{ + int16 error; + + error = unlink_loaded(entries); + if (error != 0) return error; + + return link_loaded(entries, entries_len, name); +} + int16 open_path(const char *path) { static char buf[0x0080]; @@ -328,11 +657,23 @@ int16 open_path(const char *path) } int16 chdir(const char *dir) +{ + uint32 entries_len; + + if (dir == (void *) 0) return chdir_loaded((void *) 0, 0, (void *) 0); + + entries_len = load_dir((struct fat32_dir_entry *) io_buf, 0x00000800); + if (entries_len == 0) return -1; + + return chdir_loaded((struct fat32_dir_entry *) io_buf, entries_len, dir); +} + +static int16 chdir_loaded(struct fat32_dir_entry *entries, uint32 entries_len, const char *dir) { int16 error; if (dir) { - error = open(dir); + error = open_loaded(entries, entries_len, dir); if (error != 0x0000) return error; fs.current_dir = file.start_cluster; @@ -344,6 +685,7 @@ int16 chdir(const char *dir) file.start_cluster = 0; file.current_cluster = 0; file.offset = 0; + file.entry_index = -1; return 0; } @@ -453,6 +795,38 @@ uint32 read(uint8 *buf, uint32 sectors) return sectors_read; } +uint32 write(const uint8 *buf, uint32 sectors) +{ + int16 error; + uint32 sectors_written; + uint32 remaining_sectors_in_cluster; + uint32 sectors_to_write; + uint32 disk_sector; + + if (file.start_cluster == 0x00000000) { + print_str("No file opened\r\n"); + return 0x00000000; + } + + sectors_written = 0; + + while (sectors_written < sectors && file.offset < fs.cluster_size) { + sectors_to_write = sectors - sectors_written; + remaining_sectors_in_cluster = fs.cluster_size - file.offset; + if (sectors_to_write > remaining_sectors_in_cluster) sectors_to_write = remaining_sectors_in_cluster; + + disk_sector = fs.data_start + (fs.cluster_size * (file.current_cluster - 2)) + file.offset; + error = esp_write(buf, sectors_to_write, disk_sector); + if (error != 0) return 0x00000000; + + seek(sectors_to_write); + sectors_written += sectors_to_write; + buf += sectors_to_write * 0x0200; + } + + return sectors_written; +} + uint32 get_size() { if (file.start_cluster == 0x00000000) { @@ -462,3 +836,10 @@ uint32 get_size() return file.size; } + +#ifdef NO_STATIC_INTERNALS +int32 get_entry_index() +{ + return file.entry_index; +} +#endif diff --git a/fat32.h b/fat32.h index fa9ca50..bfeae09 100644 --- a/fat32.h +++ b/fat32.h @@ -1,10 +1,18 @@ -int16 load_fat32(); -int16 open(const char *file); -void open_cluster(uint32 cluster, uint32 size); -int16 open_path(const char *path); -int16 chdir(const char *dir); -int16 for_each_dir_entry(void (*callback)(const char *entry_name, uint32 entry_cluster, uint32 entry_size)); -void reset_seek(); -int16 seek(uint32 sectors); -uint32 read(uint8 *buf, uint32 sectors); -uint32 get_size(); +#pragma once + +#include "prefix.h" + +int16 PREFIX(load_fat32)(); +int16 PREFIX(open)(const char *file); +void PREFIX(open_cluster)(uint32 cluster, uint32 size); +int16 PREFIX(open_path)(const char *path); +int16 PREFIX(chdir)(const char *dir); +int16 PREFIX(unlink)(); +int16 PREFIX(link)(const char *name); +int16 PREFIX(rename)(const char *name); +int16 PREFIX(for_each_dir_entry)(void (*callback)(const char *entry_name, uint32 entry_cluster, uint32 entry_size)); +void PREFIX(reset_seek)(); +int16 PREFIX(seek)(uint32 sectors); +uint32 PREFIX(read)(uint8 *buf, uint32 sectors); +uint32 PREFIX(write)(const uint8 *buf, uint32 sectors); +uint32 PREFIX(get_size)(); diff --git a/fat32_internal.h b/fat32_internal.h new file mode 100644 index 0000000..1a1e82d --- /dev/null +++ b/fat32_internal.h @@ -0,0 +1,7 @@ +#pragma once + +#include "prefix.h" + +#ifdef NO_STATIC_INTERNALS +int32 PREFIX(get_entry_index)(); +#endif diff --git a/gpt.c b/gpt.c index 4c999ee..1e40101 100644 --- a/gpt.c +++ b/gpt.c @@ -146,3 +146,21 @@ int16 esp_read(uint8 *buffer, uint16 sectors, uint32 sector) return disk_read(buffer, sectors, lba); } + +int16 esp_write(const uint8 *buffer, uint16 sectors, uint32 sector) +{ + uint32 lba; + + if (!esp_start) { + print_str("No partition loaded yet\r\n"); + return -1; + } + + lba = esp_start + sector; + if (lba > esp_end) { + print_str("Sector beyond partition end\r\n"); + return -1; + } + + return disk_write(buffer, sectors, lba); +} diff --git a/gpt.h b/gpt.h index d9a7f0c..d47a688 100644 --- a/gpt.h +++ b/gpt.h @@ -1,2 +1,7 @@ -int16 find_esp(); -int16 esp_read(uint8 *buffer, uint16 sectors, uint32 sector); +#pragma once + +#include "prefix.h" + +int16 PREFIX(find_esp)(); +int16 PREFIX(esp_read)(uint8 *buffer, uint16 sectors, uint32 sector); +int16 PREFIX(esp_write)(const uint8 *buffer, uint16 sectors, uint32 sector); diff --git a/io_buf.h b/io_buf.h index 88e43ab..5c3b748 100644 --- a/io_buf.h +++ b/io_buf.h @@ -1 +1,5 @@ -extern uint8 *io_buf; +#pragma once + +#include "prefix.h" + +extern uint8 *PREFIX(io_buf); diff --git a/lib.h b/lib.h index e7071e3..ea3be6d 100644 --- a/lib.h +++ b/lib.h @@ -1,10 +1,14 @@ -void flush(); -void print_str(const char *str); +#pragma once -uint8 memeq(const void *a, const void *b, uint16 len); -void memcpy(void *dst, const void *src, uint16 len); -uint16 strlen(const char *str); -uint8 streq(const char *a, const char *b); -void strcpy(char *dst, const char *src, uint16 len); -void print_hex_be(const uint8 *buf, uint16 len); -void print_hex_le(const uint8 *buf, uint16 len); +#include "prefix.h" + +void PREFIX(flush)(); +void PREFIX(print_str)(const char *str); + +uint8 PREFIX(memeq)(const void *a, const void *b, uint16 len); +void PREFIX(memcpy)(void *dst, const void *src, uint16 len); +uint16 PREFIX(strlen)(const char *str); +uint8 PREFIX(streq)(const char *a, const char *b); +void PREFIX(strcpy)(char *dst, const char *src, uint16 len); +void PREFIX(print_hex_be)(const uint8 *buf, uint16 len); +void PREFIX(print_hex_le)(const uint8 *buf, uint16 len); diff --git a/linux.h b/linux.h index 1d04f57..dd70a19 100644 --- a/linux.h +++ b/linux.h @@ -1 +1,5 @@ -int16 load_kernel(); +#pragma once + +#include "prefix.h" + +int16 PREFIX(load_kernel)(); diff --git a/loader.c b/loader.c index 8e9b301..f2baa75 100644 --- a/loader.c +++ b/loader.c @@ -1,5 +1,6 @@ #include "types.h" #include "loader.h" +#include "loader_internal.h" #include "lib.h" #include "fat32.h" #include "io_buf.h" @@ -106,7 +107,7 @@ static void dir_entry_callback_uki(const char *entry_name, uint32 entry_cluster, } } -static int16 cmp_alpha(const char *a, const char *b) +_static int16 cmp_alpha(const char *a, const char *b) { while (*a) { if (*a > *b) return 1; @@ -152,7 +153,7 @@ static uint8 is_version_char(char c) return 0; } -static int16 cmp_version(const char *a, const char *b) +_static int16 cmp_version(const char *a, const char *b) { uint16 int_a, int_b; diff --git a/loader.h b/loader.h index 55b7222..641ecc9 100644 --- a/loader.h +++ b/loader.h @@ -1,6 +1,10 @@ -int16 find_entry(); -char * get_kernel_path(); -char * get_initrd_path(); -char * get_cmdline(); -uint32 get_uki_cluster(); -uint32 get_uki_size(); +#pragma once + +#include "prefix.h" + +int16 PREFIX(find_entry)(); +char * PREFIX(get_kernel_path)(); +char * PREFIX(get_initrd_path)(); +char * PREFIX(get_cmdline)(); +uint32 PREFIX(get_uki_cluster)(); +uint32 PREFIX(get_uki_size)(); diff --git a/loader_internal.h b/loader_internal.h new file mode 100644 index 0000000..a15872d --- /dev/null +++ b/loader_internal.h @@ -0,0 +1,6 @@ +#pragma once + +#include "prefix.h" + +_static int16 PREFIX(cmp_alpha)(const char *a, const char *b); +_static int16 PREFIX(cmp_version)(const char *a, const char *b); diff --git a/make_test_fat32_disk.sh b/make_test_fat32_disk.sh new file mode 100755 index 0000000..818720f --- /dev/null +++ b/make_test_fat32_disk.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -eufo pipefail + +disk="$1"; shift + +truncate -s 0 "$disk" +truncate -s 64M "$disk" +sfdisk "$disk" << EOF +label: gpt +label-id: 01234567-abcd-0123-abcd-0123456789ab +start=2048, size=65536, type=uefi, uuid=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee, name=EFI +EOF + +mformat -i "$disk@@2048s" -T 65536 -c 2 -F -v EFI :: + +printf 'hello from testA' | mcopy -i "$disk@@2048s" - ::/testA.txt +printf 'unused0' | mcopy -i "$disk@@2048s" - ::/unused0 +printf 'unused1' | mcopy -i "$disk@@2048s" - ::/unused1 +printf 'hello from test_with_long_name_B' | mcopy -i "$disk@@2048s" - ::/test_with_long_name_B.txt +printf 'unused2' | mcopy -i "$disk@@2048s" - ::/unused2 + +mmd -i "$disk@@2048s" ::/dirC +printf 'unused3' | mcopy -i "$disk@@2048s" - ::/unused3 +printf 'hello from testC' | mcopy -i "$disk@@2048s" - ::/dirC/testC.txt +printf 'unused4' | mcopy -i "$disk@@2048s" - ::/dirC/unused4 + +mmd -i "$disk@@2048s" ::/directory_with_long_name_D +printf 'unused5' | mcopy -i "$disk@@2048s" - ::/unused5 +printf 'hello from testD' | mcopy -i "$disk@@2048s" - ::/directory_with_long_name_D/testD.txt +printf 'unused6' | mcopy -i "$disk@@2048s" - ::/directory_with_long_name_D/unused6 + +printf 'hello from testE' | mcopy -i "$disk@@2048s" - ::/testE.txt +printf 'unused7' | mcopy -i "$disk@@2048s" - ::/unused7 +printf 'hello from test_with_long_name_F' | mcopy -i "$disk@@2048s" - ::/test_with_long_name_F.txt +printf 'unused8' | mcopy -i "$disk@@2048s" - ::/unused8 +printf 'hello from testG' | mcopy -i "$disk@@2048s" - ::/testG.txt +printf 'unused9' | mcopy -i "$disk@@2048s" - ::/unused9 +printf 'hello from test_with_long_name_H' | mcopy -i "$disk@@2048s" - ::/test_with_long_name_H.txt +for i in {0..7}; do + printf 'buf%s' "$i" | mcopy -i "$disk@@2048s" - "::/buf$i" +done +printf 'unused10' | mcopy -i "$disk@@2048s" - ::/unused10 +for i in {0..7}; do + mdel -i "$disk@@2048s" "::/buf$i" +done + +mdir -i "$disk@@2048s" -/ :: diff --git a/prefix.h b/prefix.h new file mode 100644 index 0000000..2865aa9 --- /dev/null +++ b/prefix.h @@ -0,0 +1,11 @@ +#pragma once + +#ifndef PREFIX +#define PREFIX(name) name +#endif + +#ifdef NO_STATIC_INTERNALS +#define _static +#else +#define _static static +#endif diff --git a/setup_header.h b/setup_header.h index 5a52477..9033570 100644 --- a/setup_header.h +++ b/setup_header.h @@ -1,3 +1,5 @@ +#pragma once + struct setup_header { uint8 setup_sects; uint16 root_flags; diff --git a/test_fat32.c b/test_fat32.c new file mode 100644 index 0000000..c04d41f --- /dev/null +++ b/test_fat32.c @@ -0,0 +1,174 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PREFIX(name) bootloader_##name + +#include "types.h" +#include "lib.h" +#include "io_buf.h" +#include "gpt.h" +#include "fat32.h" +#include "fat32_internal.h" + +static uint8 *disk_map; + +void bootloader_halt() +{ + abort(); +} + +void bootloader_reset() +{ + exit(0); +} + +void bootloader_print_char(char c) +{ + if (c != 0) fputc(c, stderr); + else fflush(stderr); +} + +int16 bootloader_disk_read(uint8 *buffer, uint16 sectors, uint32 lba) +{ + uint32 len; + uint32 offset; + + if (sectors > 64) return -1; + + len = sectors * 0x0200; + offset = lba * 0x0200; + + memcpy(buffer, disk_map + offset, len); + return 0; +} + +int16 bootloader_disk_write(const uint8 *buffer, uint16 sectors, uint32 lba) +{ + uint32 len; + uint32 offset; + + if (sectors > 64) return -1; + + len = sectors * 0x0200; + offset = lba * 0x0200; + + memcpy(disk_map + offset, buffer, len); + return 0; +} + +static void check_file(const char *name, const char *expected_content) +{ + int16 error; + uint32 expected_size; + uint32 size; + uint32 sectors_needed; + static uint8 read_buf[0x1000]; + + expected_size = (uint32) strlen(expected_content); + + error = bootloader_open(name); + assert(error == 0); + + size = bootloader_get_size(); + assert(size == expected_size); + + sectors_needed = (size + 0x01ff) / 0x0200; + assert(sectors_needed <= sizeof(read_buf) / 0x0200); + + bootloader_reset_seek(); + assert(bootloader_read(read_buf, sectors_needed) == sectors_needed); + assert(memcmp(read_buf, expected_content, expected_size) == 0); +} + +int main(int argc, char **argv) +{ + int fd; + struct stat st; + int16 error; + int32 stored_entry_index; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 1; + } + + fd = open(argv[1], O_RDONLY); + assert(fd >= 0); + + assert(fstat(fd, &st) == 0); + + disk_map = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + assert(disk_map != MAP_FAILED); + close(fd); + + error = bootloader_find_esp(); + assert(error == 0); + + error = bootloader_load_fat32(); + assert(error == 0); + + printf("Testing open/read of short-name file...\n"); + check_file("testA.txt", "hello from testA"); + + printf("Testing open/read of long-name file...\n"); + check_file("test_with_long_name_B.txt", "hello from test_with_long_name_B"); + + printf("Testing chdir into short-name directory...\n"); + error = bootloader_chdir("dirC"); + assert(error == 0); + check_file("testC.txt", "hello from testC"); + + printf("Testing chdir to root and long-name directory...\n"); + error = bootloader_chdir(NULL); + assert(error == 0); + error = bootloader_chdir("directory_with_long_name_D"); + assert(error == 0); + check_file("testD.txt", "hello from testD"); + + printf("Testing rename of short-name file...\n"); + error = bootloader_chdir(NULL); + assert(error == 0); + error = bootloader_open("testE.txt"); + assert(error == 0); + stored_entry_index = bootloader_get_entry_index(); + error = bootloader_rename("testX.txt"); + assert(error == 0); + check_file("testX.txt", "hello from testE"); + assert(stored_entry_index == bootloader_get_entry_index()); + + printf("Testing rename of long-name file...\n"); + error = bootloader_open("test_with_long_name_F.txt"); + assert(error == 0); + stored_entry_index = bootloader_get_entry_index(); + error = bootloader_rename("test_with_long_name_X.txt"); + assert(error == 0); + check_file("test_with_long_name_X.txt", "hello from test_with_long_name_F"); + assert(stored_entry_index == bootloader_get_entry_index()); + + printf("Testing rename from short name to long name...\n"); + error = bootloader_open("testG.txt"); + assert(error == 0); + error = bootloader_rename("testG_now_with_a_long_name.txt"); + assert(error == 0); + check_file("testG_now_with_a_long_name.txt", "hello from testG"); + + printf("Testing rename from long name to even longer name...\n"); + error = bootloader_open("test_with_long_name_H.txt"); + assert(error == 0); + error = bootloader_rename("test_with_an_even_longer_name_H.txt"); + assert(error == 0); + check_file("test_with_an_even_longer_name_H.txt", "hello from test_with_long_name_H"); + + printf("All tests passed!\n"); + + munmap(disk_map, st.st_size); + return 0; +} diff --git a/test_loader.c b/test_loader.c index d6d5290..4ad2b7a 100644 --- a/test_loader.c +++ b/test_loader.c @@ -1,16 +1,25 @@ -#include "loader.c" - #include #include #include -void print_char(char c) +#define PREFIX(name) bootloader_##name + +#include "types.h" +#include "loader.h" +#include "loader_internal.h" + +void bootloader_print_char(char c) { if (c != 0) fputc(c, stdout); else fflush(stdout); } -int16 disk_read(uint8 *, uint16, uint32) +int16 bootloader_disk_read(uint8 *, uint16, uint32) +{ + return -1; +} + +int16 bootloader_disk_write(const uint8 *, uint16, uint32) { return -1; } @@ -20,96 +29,96 @@ int main() { printf("Testing cmp_alpha...\n"); - assert(cmp_alpha("abc", "abc") == 0); - assert(cmp_alpha("abc", "xyz") == -1); - assert(cmp_alpha("xyz", "abc") == 1); - assert(cmp_alpha("", "abc") == -1); - assert(cmp_alpha("abc", "abcdef") == -1); + assert(bootloader_cmp_alpha("abc", "abc") == 0); + assert(bootloader_cmp_alpha("abc", "xyz") == -1); + assert(bootloader_cmp_alpha("xyz", "abc") == 1); + assert(bootloader_cmp_alpha("", "abc") == -1); + assert(bootloader_cmp_alpha("abc", "abcdef") == -1); printf("All tests passed!\n"); printf("Testing cmp_version against spec examples...\n"); // 11 == 11 - assert(cmp_version("11", "11") == 0); + assert(bootloader_cmp_version("11", "11") == 0); // systemd-123 == systemd-123 - assert(cmp_version("systemd-123", "systemd-123") == 0); + assert(bootloader_cmp_version("systemd-123", "systemd-123") == 0); // bar-123 < foo-123 - assert(cmp_version("bar-123", "foo-123") == -1); - assert(cmp_version("foo-123", "bar-123") == 1); + assert(bootloader_cmp_version("bar-123", "foo-123") == -1); + assert(bootloader_cmp_version("foo-123", "bar-123") == 1); // 123a > 123 - assert(cmp_version("123", "123a") == -1); - assert(cmp_version("123a", "123") == 1); + assert(bootloader_cmp_version("123", "123a") == -1); + assert(bootloader_cmp_version("123a", "123") == 1); // 123.a > 123 - assert(cmp_version("123", "123.a") == -1); - assert(cmp_version("123.a", "123") == 1); + assert(bootloader_cmp_version("123", "123.a") == -1); + assert(bootloader_cmp_version("123.a", "123") == 1); // 123.a < 123.b - assert(cmp_version("123.a", "123.b") == -1); - assert(cmp_version("123.b", "123.a") == 1); + assert(bootloader_cmp_version("123.a", "123.b") == -1); + assert(bootloader_cmp_version("123.b", "123.a") == 1); // 123a > 123.a - assert(cmp_version("123.a", "123a") == -1); - assert(cmp_version("123a", "123.a") == 1); + assert(bootloader_cmp_version("123.a", "123a") == -1); + assert(bootloader_cmp_version("123a", "123.a") == 1); // 11α == 11β - assert(cmp_version("11α", "11β") == 0); + assert(bootloader_cmp_version("11α", "11β") == 0); // B < a - assert(cmp_version("B", "a") == -1); - assert(cmp_version("a", "B") == 1); + assert(bootloader_cmp_version("B", "a") == -1); + assert(bootloader_cmp_version("a", "B") == 1); // ’’ < 0 - assert(cmp_version("", "0") == -1); - assert(cmp_version("0", "") == 1); + assert(bootloader_cmp_version("", "0") == -1); + assert(bootloader_cmp_version("0", "") == 1); // 0. > 0 - assert(cmp_version("0", "0.") == -1); - assert(cmp_version("0.", "0") == 1); + assert(bootloader_cmp_version("0", "0.") == -1); + assert(bootloader_cmp_version("0.", "0") == 1); // 0.0 > 0 - assert(cmp_version("0", "0.0") == -1); - assert(cmp_version("0.0", "0") == 1); + assert(bootloader_cmp_version("0", "0.0") == -1); + assert(bootloader_cmp_version("0.0", "0") == 1); // 0 > ~ - assert(cmp_version("~", "0") == -1); - assert(cmp_version("0", "~") == 1); + assert(bootloader_cmp_version("~", "0") == -1); + assert(bootloader_cmp_version("0", "~") == 1); // ’’ > ~ - assert(cmp_version("~", "") == -1); - assert(cmp_version("", "~") == 1); + assert(bootloader_cmp_version("~", "") == -1); + assert(bootloader_cmp_version("", "~") == 1); // 1_ == 1 - assert(cmp_version("1_", "1") == 0); + assert(bootloader_cmp_version("1_", "1") == 0); // _1 == 1 - assert(cmp_version("_1", "1") == 0); + assert(bootloader_cmp_version("_1", "1") == 0); // 1_ < 1.2 - assert(cmp_version("1_", "1.2") == -1); - assert(cmp_version("1.2", "1_") == 1); + assert(bootloader_cmp_version("1_", "1.2") == -1); + assert(bootloader_cmp_version("1.2", "1_") == 1); // 1_2_3 > 1.3.3 - assert(cmp_version("1.3.3", "1_2_3") == -1); - assert(cmp_version("1_2_3", "1.3.3") == 1); + assert(bootloader_cmp_version("1.3.3", "1_2_3") == -1); + assert(bootloader_cmp_version("1_2_3", "1.3.3") == 1); // 1+ == 1 - assert(cmp_version("1+", "1") == 0); + assert(bootloader_cmp_version("1+", "1") == 0); // +1 == 1 - assert(cmp_version("+1", "1") == 0); + assert(bootloader_cmp_version("+1", "1") == 0); // 1+ < 1.2 - assert(cmp_version("1+", "1.2") == -1); - assert(cmp_version("1.2", "1+") == 1); + assert(bootloader_cmp_version("1+", "1.2") == -1); + assert(bootloader_cmp_version("1.2", "1+") == 1); // 1+2+3 > 1.3.3 - assert(cmp_version("1.3.3", "1+2+3") == -1); - assert(cmp_version("1+2+3", "1.3.3") == 1); + assert(bootloader_cmp_version("1.3.3", "1+2+3") == -1); + assert(bootloader_cmp_version("1+2+3", "1.3.3") == 1); printf("All tests passed!\n"); diff --git a/types.h b/types.h index bbfbb0c..2f7bc12 100644 --- a/types.h +++ b/types.h @@ -1,3 +1,5 @@ +#pragma once + typedef __INT8_TYPE__ int8; typedef __INT16_TYPE__ int16; typedef __INT32_TYPE__ int32;