diff --git a/.gitignore b/.gitignore index 39b76ba..b4d8392 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ bootloader_emu busybox busybox.tar.bz2 +counted_disk demo_uki.img demo.img dependencies.make diff --git a/Makefile b/Makefile index 0f89f74..58c790c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ MAKEFLAGS += --no-builtin-rules .SILENT: UNIT_TESTS := loader fat32 io_buf -.PHONY: all clean distclean test unit_tests qemu_test uki_test kexec_test emu_test $(UNIT_TESTS:%=run_test_%) +.PHONY: all clean distclean test unit_tests qemu_test uki_test kexec_test boot_count_test emu_test $(UNIT_TESTS:%=run_test_%) CC := gcc CC_X86 := x86_64-linux-gnu-gcc @@ -24,7 +24,7 @@ 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 +test: unit_tests emu_test qemu_test uki_test kexec_test boot_count_test unit_tests: $(UNIT_TESTS:%=run_test_%) @@ -63,6 +63,12 @@ kexec_test: kernel kexec.cpio grep -F 'kexec_core: Starting new kernel' < serial.log > /dev/null grep -F 'hello from the initrd' < serial.log > /dev/null +boot_count_test: counted_disk + echo 'running $< in qemu' + ./run_vm.sh -n '$<' | tee serial.log + grep -F 'Failed to load kernel' < serial.log > /dev/null + grep -F 'hello from the initrd' < serial.log > /dev/null + debug: disk bootloader.elf echo 'running $< in qemu in debug mode' ./debug.sh $^ @@ -114,6 +120,10 @@ disk: make_disk.sh mbr.bin bootloader.bin kernel initrd.cpio echo 'creating $@' ./$^ $@ +counted_disk: make_counted_disk.sh mbr.bin bootloader.bin kernel initrd.cpio + echo 'creating $@' + ./$^ $@ + demo: demo.img demo_uki.img demo.img: make_demo.sh mbr.bin bootloader.bin kernel busybox diff --git a/fat32.c b/fat32.c index d47d47d..ec2a031 100644 --- a/fat32.c +++ b/fat32.c @@ -311,9 +311,10 @@ static struct fat32_dir_entry * next_entry(struct fat32_dir_entry **entries, uin return (void *) 0; } -int16 for_each_dir_entry(void (*callback)(const char *entry_name, uint32 entry_cluster, uint32 entry_size)) +int16 for_each_dir_entry(void (*callback)(const char *entry_name, int32 entry_index, uint32 entry_cluster, uint32 entry_size)) { int16 error; + struct fat32_dir_entry *entries_start; struct fat32_dir_entry *entries; uint32 entries_len; struct fat32_dir_entry *entry; @@ -323,17 +324,83 @@ int16 for_each_dir_entry(void (*callback)(const char *entry_name, uint32 entry_c error = load_dir(); if (error != 0) return error; - entries = dir_cache.buf; - entries_len = dir_cache.num_entries; + entries_start = dir_cache.buf; + entries = entries_start; + entries_len = dir_cache.num_entries; 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); + callback(entry_name, (int32)(entry - entries_start), entry_cluster, entry->size); + } + + return 0; +} + +struct dir_entry_handle make_handle(int32 entry_index) +{ + struct dir_entry_handle h; + h.dir_cluster = fs.current_dir; + h.entry_index = entry_index; + return h; +} + +int16 open_handle(struct dir_entry_handle h) +{ + int16 error; + struct fat32_dir_entry *entry; + + fs.current_dir = h.dir_cluster; + + error = load_dir(); + if (error != 0) return error; + + if (h.entry_index < 0 || (uint32) h.entry_index >= dir_cache.num_entries) { + print_str("Handle entry_index out of range\r\n"); + return -1; } + entry = &dir_cache.buf[h.entry_index]; + + open_cluster((((uint32) entry->cluster_high) << 0x10) + entry->cluster_low, entry->size); + file.entry_index = h.entry_index; + return 0; } +int16 get_file_name(char *name_out, uint16 name_out_len) +{ + int16 error; + struct fat32_dir_entry *entries_start; + struct fat32_dir_entry *entries; + uint32 entries_len; + struct fat32_dir_entry *entry; + char *entry_name; + uint16 i; + + if (file.entry_index < 0) { + print_str("No file opened\r\n"); + return -1; + } + + error = load_dir(); + if (error != 0) return error; + + entries_start = dir_cache.buf; + entries = entries_start; + entries_len = dir_cache.num_entries; + + while ((entry = next_entry(&entries, &entries_len, &entry_name)) != (void *) 0) { + if ((int32)(entry - entries_start) == file.entry_index) { + for (i = 0; i + 1 < name_out_len && entry_name[i] != 0x00; ++i) name_out[i] = entry_name[i]; + name_out[i] = 0x00; + return 0; + } + } + + print_str("Entry index not found in directory\r\n"); + return -1; +} + void open_cluster(uint32 cluster, uint32 size) { file.size = size; diff --git a/fat32.h b/fat32.h index f3e0e68..8f44d66 100644 --- a/fat32.h +++ b/fat32.h @@ -2,6 +2,11 @@ #include "prefix.h" +struct dir_entry_handle { + uint32 dir_cluster; + int32 entry_index; +}; + int16 PREFIX(load_fat32)(void); int16 PREFIX(open)(const char *file); void PREFIX(open_cluster)(uint32 cluster, uint32 size); @@ -10,7 +15,10 @@ int16 PREFIX(chdir)(const char *dir); int16 PREFIX(unlink)(void); 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)); +int16 PREFIX(for_each_dir_entry)(void (*callback)(const char *entry_name, int32 entry_index, uint32 entry_cluster, uint32 entry_size)); +struct dir_entry_handle PREFIX(make_handle)(int32 entry_index); +int16 PREFIX(open_handle)(struct dir_entry_handle h); +int16 PREFIX(get_file_name)(char *name_out, uint16 name_out_len); void PREFIX(reset_seek)(void); int16 PREFIX(seek)(uint32 sectors); uint32 PREFIX(read)(uint8 *buf, uint32 sectors); diff --git a/hello.c b/hello.c index 4fd1f5e..8f6eeb8 100644 --- a/hello.c +++ b/hello.c @@ -1,7 +1,9 @@ #include +#include int main() { puts("hello from the initrd"); + reboot(RB_POWER_OFF); return 0; } diff --git a/loader.c b/loader.c index 47e221d..e6d05c0 100644 --- a/loader.c +++ b/loader.c @@ -16,10 +16,23 @@ struct entry { char options[0x0400]; uint32 uki_cluster; uint32 uki_size; + uint8 is_bad; + struct dir_entry_handle handle; }; static struct entry highest_entry = { }; +static struct { + uint32 cluster; + uint32 size; + int32 entry_index; + uint8 is_bad; +} config_entries[0x0010]; + +static uint16 num_config_entries; + +static uint8 find_counter(const char *filename, uint16 *plus_off, uint16 *left_len, uint16 *dash_off, uint16 *done_len, uint16 *suffix_off); + static void key_value(char **line, char **key, char **value) { char *ptr = *line; @@ -72,14 +85,7 @@ static void parse_entry(struct entry *entry, char *buf) } } -static struct { - uint32 cluster; - uint32 size; -} config_entries[0x0010]; - -static uint16 num_config_entries; - -static void dir_entry_callback(const char *entry_name, uint32 entry_cluster, uint32 entry_size) +static void dir_entry_callback(const char *entry_name, int32 entry_index, uint32 entry_cluster, uint32 entry_size) { uint16 name_len; @@ -87,13 +93,15 @@ static void dir_entry_callback(const char *entry_name, uint32 entry_cluster, uin if (streq(entry_name + name_len - 5, ".conf")) { if (num_config_entries > 0x0010) print_str("Too many config entries - ignoring excess ones\r\n"); - config_entries[num_config_entries].cluster = entry_cluster; - config_entries[num_config_entries].size = entry_size; + config_entries[num_config_entries].cluster = entry_cluster; + config_entries[num_config_entries].size = entry_size; + config_entries[num_config_entries].entry_index = entry_index; + config_entries[num_config_entries].is_bad = is_bad_entry(entry_name); ++num_config_entries; } } -static void dir_entry_callback_uki(const char *entry_name, uint32 entry_cluster, uint32 entry_size) +static void dir_entry_callback_uki(const char *entry_name, int32 entry_index, uint32 entry_cluster, uint32 entry_size) { uint16 name_len; @@ -101,8 +109,10 @@ static void dir_entry_callback_uki(const char *entry_name, uint32 entry_cluster, if (streq(entry_name + name_len - 4, ".efi")) { if (num_config_entries > 0x0010) print_str("Too many config entries - ignoring excess ones\r\n"); - config_entries[num_config_entries].cluster = entry_cluster; - config_entries[num_config_entries].size = entry_size; + config_entries[num_config_entries].cluster = entry_cluster; + config_entries[num_config_entries].size = entry_size; + config_entries[num_config_entries].entry_index = entry_index; + config_entries[num_config_entries].is_bad = is_bad_entry(entry_name); ++num_config_entries; } } @@ -262,6 +272,9 @@ static int16 cmp_entry(const struct entry *a, const struct entry *b) { int16 cmp; + if (a->is_bad && !b->is_bad) return -1; + if (!a->is_bad && b->is_bad) return 1; + if (a->uki_cluster != 0 && b->uki_cluster == 0) return 1; if (b->uki_cluster != 0 && a->uki_cluster == 0) return -1; @@ -277,6 +290,149 @@ static int16 cmp_entry(const struct entry *a, const struct entry *b) return cmp_version(a->version, b->version); } +static uint8 find_counter(const char *filename, uint16 *plus_off, uint16 *left_len, uint16 *dash_off, uint16 *done_len, uint16 *suffix_off) +{ + uint16 name_len; + uint16 suffix; + uint16 i; + uint16 plus; + uint16 dash; + uint16 llen; + uint16 dlen; + + name_len = strlen(filename); + + if (name_len >= 5 && streq(filename + name_len - 5, ".conf")) { + suffix = name_len - 5; + } else if (name_len >= 4 && streq(filename + name_len - 4, ".efi")) { + suffix = name_len - 4; + } else { + return 0; + } + + i = suffix; + dash = 0; + dlen = 0; + + while (i > 0 && filename[i - 1] >= '0' && filename[i - 1] <= '9') --i; + + if (i > 0 && filename[i - 1] == '-') { + dash = i - 1; + dlen = suffix - i; + if (dlen == 0) return 0; + + i = dash; + while (i > 0 && filename[i - 1] >= '0' && filename[i - 1] <= '9') --i; + } + + if (i == 0 || filename[i - 1] != '+') return 0; + + plus = i - 1; + if (dash) { + llen = dash - i; + } else { + llen = suffix - i; + } + if (llen == 0) return 0; + + if (plus_off) *plus_off = plus; + if (left_len) *left_len = llen; + if (dash_off) *dash_off = dash; + if (done_len) *done_len = dlen; + if (suffix_off) *suffix_off = suffix; + return 1; +} + +_static uint8 is_bad_entry(const char *filename) +{ + uint16 plus_off; + uint16 left_len; + uint16 i; + + if (!find_counter(filename, &plus_off, &left_len, (void *) 0, (void *) 0, (void *) 0)) return 0; + + for (i = 0; i < left_len; ++i) { + if (filename[plus_off + 1 + i] != '0') return 0; + } + return 1; +} + +static void write_uint16_padded(char *out, uint16 value, uint16 min_width, uint16 *written) +{ + char digits[6]; + uint16 nd; + uint16 i; + uint16 pad; + + if (value == 0) { + digits[0] = '0'; + nd = 1; + } else { + nd = 0; + while (value > 0) { + digits[nd++] = '0' + (value % 10); + value /= 10; + } + } + + pad = (nd < min_width) ? (min_width - nd) : 0; + + for (i = 0; i < pad; ++i) out[i] = '0'; + for (i = 0; i < nd; ++i) out[pad + i] = digits[nd - 1 - i]; + + *written = pad + nd; +} + +_static void tick_counters_str(const char *in, char *out, uint16 out_len) +{ + uint16 plus_off; + uint16 left_len; + uint16 dash_off; + uint16 done_len; + uint16 suffix_off; + uint16 in_len; + uint16 left_val; + uint16 done_val; + uint16 o; + uint16 i; + uint16 written; + const char *digits; + + if (!find_counter(in, &plus_off, &left_len, &dash_off, &done_len, &suffix_off)) { + for (i = 0; i + 1 < out_len && in[i] != 0x00; ++i) out[i] = in[i]; + out[i] = 0x00; + return; + } + + digits = in + plus_off + 1; + left_val = str_to_uint16(digits, (void *) 0); + if (left_val > 0) --left_val; + + done_val = 0; + if (dash_off) { + done_val = str_to_uint16(in + dash_off + 1, (void *) 0); + ++done_val; + } + + in_len = strlen(in); + o = 0; + + for (i = 0; i <= plus_off && o + 1 < out_len; ++i) out[o++] = in[i]; + + write_uint16_padded(out + o, left_val, left_len, &written); + o += written; + + if (dash_off) { + if (o + 1 < out_len) out[o++] = '-'; + write_uint16_padded(out + o, done_val, done_len, &written); + o += written; + } + + for (i = suffix_off; i < in_len && o + 1 < out_len; ++i) out[o++] = in[i]; + + out[o] = 0x00; +} + int16 find_entry_conf(void) { static struct entry entry; @@ -310,6 +466,8 @@ int16 find_entry_conf(void) } parse_entry(&entry, (char *) io_buf); + entry.is_bad = config_entries[i].is_bad; + entry.handle = make_handle(config_entries[i].entry_index); if (cmp_entry(&entry, &highest_entry) == 1) memcpy(&highest_entry, &entry, sizeof(struct entry)); } @@ -362,6 +520,8 @@ int16 find_entry_uki(void) entry.uki_cluster = config_entries[i].cluster; entry.uki_size = config_entries[i].size; strcpy(entry.version, version_str, sizeof(entry.version)); + entry.is_bad = config_entries[i].is_bad; + entry.handle = make_handle(config_entries[i].entry_index); if (cmp_entry(&entry, &highest_entry) == 1) memcpy(&highest_entry, &entry, sizeof(struct entry)); } @@ -369,6 +529,39 @@ int16 find_entry_uki(void) return 0; } +static int16 tick_counters(const struct entry *e) +{ + static char name_buf[0x0100]; + static char new_name[0x0100]; + int16 error; + + if (e->is_bad) return 0; + + error = open_handle(e->handle); + if (error != 0) { + print_str("tick_counters: open_handle failed\r\n"); + return error; + } + + error = get_file_name(name_buf, sizeof(name_buf)); + if (error != 0) { + print_str("tick_counters: get_file_name failed\r\n"); + return error; + } + + if (!find_counter(name_buf, (void *) 0, (void *) 0, (void *) 0, (void *) 0, (void *) 0)) return 0; + + tick_counters_str(name_buf, new_name, sizeof(new_name)); + + error = rename(new_name); + if (error != 0) { + print_str("tick_counters: rename failed\r\n"); + return error; + } + + return 0; +} + int16 find_entry(void) { int16 error; @@ -381,6 +574,8 @@ int16 find_entry(void) if (*highest_entry.linux == 0x00 && highest_entry.uki_cluster == 0x00) return -1; + tick_counters(&highest_entry); + print_str("Selected Boot Entry: "); if (highest_entry.uki_cluster) { print_str("UKI\r\n"); diff --git a/loader_internal.h b/loader_internal.h index a15872d..9b95211 100644 --- a/loader_internal.h +++ b/loader_internal.h @@ -4,3 +4,5 @@ _static int16 PREFIX(cmp_alpha)(const char *a, const char *b); _static int16 PREFIX(cmp_version)(const char *a, const char *b); +_static uint8 PREFIX(is_bad_entry)(const char *filename); +_static void PREFIX(tick_counters_str)(const char *in, char *out, uint16 out_len); diff --git a/main.c b/main.c index 469df41..b761112 100644 --- a/main.c +++ b/main.c @@ -35,7 +35,7 @@ void init() if (error != 0) { print_str("Failed to load kernel\r\n"); flush(); - halt(); + reset(); } flush(); diff --git a/make_counted_disk.sh b/make_counted_disk.sh new file mode 100755 index 0000000..7cc32da --- /dev/null +++ b/make_counted_disk.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -eufo pipefail + +mbr="$1"; shift +stage2="$1"; shift +kernel="$1"; shift +initrd="$1"; shift +disk="$1"; shift + +truncate -s 0 "$disk" +truncate -s 1G "$disk" +sfdisk "$disk" << EOF +label: gpt +label-id: 01234567-abcd-0123-abcd-0123456789ab +start=2048, size=1048576, type=uefi, uuid=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee, name=EFI +EOF + +dd if="$mbr" of="$disk" bs=446 count=1 conv=notrunc 2> /dev/null +dd if="$stage2" of="$disk" bs=512 count=32 seek=34 conv=notrunc 2> /dev/null + +mformat -i "$disk@@2048s" -T 1048576 -c 2 -F -v EFI :: +mmd -i "$disk@@2048s" ::/Linux +mcopy -i "$disk@@2048s" "$kernel" ::/Linux/kernel +mcopy -i "$disk@@2048s" "$initrd" ::/Linux/initrd_with_long_name +mmd -i "$disk@@2048s" ::/loader +mmd -i "$disk@@2048s" ::/loader/entries + +mcopy -i "$disk@@2048s" - ::/loader/entries/broken+3.conf << EOF +title Broken Entry +version 0.9 +linux /Linux/does_not_exist +initrd /Linux/initrd_with_long_name +options rdinit=/hello +EOF + +mcopy -i "$disk@@2048s" - ::/loader/entries/working.conf << EOF +title Working Entry +version 0.1 +linux /Linux/kernel +initrd /Linux/initrd_with_long_name +options rdinit=/hello +EOF + +mdir -i "$disk@@2048s" -/ :: diff --git a/run_vm.sh b/run_vm.sh index 3bbb7ed..6d7f908 100755 --- a/run_vm.sh +++ b/run_vm.sh @@ -31,5 +31,5 @@ done exec ./term_emu.py qemu-system-x86_64 \ -machine "$machine" -cpu qemu64 -accel tcg -m 1024 \ - -nodefaults -no-reboot -nographic -serial "$serial" \ + -nodefaults -nographic -serial "$serial" \ "${qemu_opts[@]}" diff --git a/test_loader.c b/test_loader.c index 4ad2b7a..586f7a0 100644 --- a/test_loader.c +++ b/test_loader.c @@ -1,6 +1,7 @@ #include #include #include +#include #define PREFIX(name) bootloader_##name @@ -27,6 +28,8 @@ int16 bootloader_disk_write(const uint8 *, uint16, uint32) int main() { + char buf[64]; + printf("Testing cmp_alpha...\n"); assert(bootloader_cmp_alpha("abc", "abc") == 0); @@ -122,5 +125,46 @@ int main() printf("All tests passed!\n"); + printf("Testing is_bad_entry...\n"); + + assert(bootloader_is_bad_entry("foo.conf") == 0); + assert(bootloader_is_bad_entry("foo+3.conf") == 0); + assert(bootloader_is_bad_entry("foo+0.conf") == 1); + assert(bootloader_is_bad_entry("foo+0-3.conf") == 1); + assert(bootloader_is_bad_entry("foo+00.efi") == 1); + assert(bootloader_is_bad_entry("foo+01.conf") == 0); + + printf("Testing tick_counters_str...\n"); + + // Plain filename — pass-through + bootloader_tick_counters_str("foo.conf", buf, sizeof(buf)); + assert(strcmp(buf, "foo.conf") == 0); + + // +N only, no -M materialized + bootloader_tick_counters_str("foo+3.conf", buf, sizeof(buf)); + assert(strcmp(buf, "foo+2.conf") == 0); + + // +N-M: decrement left, increment done, widths preserved + bootloader_tick_counters_str("foo+3-00.conf", buf, sizeof(buf)); + assert(strcmp(buf, "foo+2-01.conf") == 0); + + // TRIES_LEFT width preserved on decrement + bootloader_tick_counters_str("foo+10.conf", buf, sizeof(buf)); + assert(strcmp(buf, "foo+09.conf") == 0); + + // TRIES_DONE allowed to grow + bootloader_tick_counters_str("foo+9-99.conf", buf, sizeof(buf)); + assert(strcmp(buf, "foo+8-100.conf") == 0); + + // +0 stays at 0 (still bootable, but pointless — caller guards via is_bad) + bootloader_tick_counters_str("foo+1.conf", buf, sizeof(buf)); + assert(strcmp(buf, "foo+0.conf") == 0); + + // UKI (.efi) suffix + bootloader_tick_counters_str("uki+3-0.efi", buf, sizeof(buf)); + assert(strcmp(buf, "uki+2-1.efi") == 0); + + printf("All tests passed!\n"); + return 0; }