diff --git a/.gitignore b/.gitignore index 81f17d2e3..f8c7df7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ /common/lib/stb_image.h /common/cc-runtime.s2.c /cc-runtime +/common/compress/pdgzip.c +/common/compress/pdgzip.h +/pdgzip /libfdt /edk2-ovmf /bochsout.txt diff --git a/3RDPARTY.md b/3RDPARTY.md index 4a255511c..cdcb17eb4 100644 --- a/3RDPARTY.md +++ b/3RDPARTY.md @@ -58,6 +58,9 @@ used for wallpaper image loading. - [libfdt](https://github.com/osdev0/libfdt) (BSD-2-Clause) is used for manipulating Flat Device Trees. +- [pdgzip](https://github.com/iczelia/pdgzip) (0BSD) is used to provide the +transparent gzip decompression layer for loaded files. + Note that some of these projects, or parts of them, are provided under dual-licensing, in which case, in the above list, the only license mentioned is the one chosen by the Limine developers. Refer to each individual project's diff --git a/GNUmakefile.in b/GNUmakefile.in index fd0c24aa3..2e19b9f34 100644 --- a/GNUmakefile.in +++ b/GNUmakefile.in @@ -209,8 +209,8 @@ $(call MKESCAPE,$(BINDIR))/limine-uefi-cd.bin: $(if $(BUILD_UEFI_IA32),$(call MK ifneq ($(BUILD_UEFI_CD),no) $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' rm -f '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' - dd if=/dev/zero of='$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' bs=512 count=5760 2>/dev/null - mformat -i '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' -f 2880 -N 12345678 :: + dd if=/dev/zero of='$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' bs=512 count=32768 2>/dev/null + mformat -i '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' -h 64 -t 32 -s 16 -N 12345678 :: LIMINE_UEFI_CD_TMP="$$(mktemp -d)"; \ mkdir -p "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT; \ cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \ diff --git a/bootstrap b/bootstrap index 990a3052a..442dc5ceb 100755 --- a/bootstrap +++ b/bootstrap @@ -81,6 +81,13 @@ if ! test -f version; then dae79833b57a01b9fd3e359ee31def69f5ae899b cp cc-runtime/src/cc-runtime.c common/cc-runtime.s2.c + clone_repo_commit \ + https://github.com/iczelia/pdgzip.git \ + pdgzip \ + 16c41d9af067c4185c136622c58ad4188609a3d1 + cp pdgzip/pdgzip.c common/compress/pdgzip.c + cp pdgzip/pdgzip.h common/compress/pdgzip.h + clone_repo_commit \ https://github.com/Limine-Bootloader/limine-protocol.git \ limine-protocol \ diff --git a/common/compress/gzip.c b/common/compress/gzip.c new file mode 100644 index 000000000..345a91bb9 --- /dev/null +++ b/common/compress/gzip.c @@ -0,0 +1,146 @@ +/* Limine glue around pdgzip: transparent gzip decompression layer over a + * file_handle. The underlying decoder lives in common/compress/pdgzip.c + * (imported by ./bootstrap from the upstream iczelia/pdgzip repo); this + * file only wires pdgzip's streaming read-callback API into Limine's + * file_handle abstraction and adds support for random-access reads via + * rewind-and-skip. + * + * Copyright (C) 2019-2026 Mintsuki and contributors. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gzip_handle { + struct file_handle * source; /* compressed file (owned) */ + pdgzip_t * gz; /* decoder backed by `scratch` */ + void * scratch; /* pdgzip scratch buffer */ + size_t scratch_sz; + uint64_t src_pos; /* next byte to pull from `source` */ + uint64_t dec_pos; /* current decompressed stream offset */ +}; + +/* pdgzip read callback: pull up to `len` bytes from the compressed source + starting at gh->src_pos. A short read (including zero) signals EOF to + the decoder, which is correct at the end of the file. */ +static size_t gz_source_read(void * user, void * buf, size_t len) { + struct gzip_handle * gh = user; + uint64_t avail = gh->source->size - gh->src_pos; + if ((uint64_t)len > avail) len = (size_t)avail; + if (len == 0) return 0; + fread(gh->source, buf, gh->src_pos, len); + gh->src_pos += len; + return len; +} + +/* (Re)initialize the decoder for a fresh pass over the compressed stream. + pdgzip_init zeroes its own scratch, so we only need to reset our own + bookkeeping. */ +static void gz_reset(struct gzip_handle * gh) { + pdgzip_cfg_t cfg = { .read = gz_source_read, .user = gh, .concat = 0 }; + gh->src_pos = 0; + gh->dec_pos = 0; + gh->gz = pdgzip_init(gh->scratch, &cfg); +} + +static uint64_t gzip_read(struct file_handle * file, void * buf, uint64_t loc, uint64_t count) { + struct gzip_handle * gh = file->fd; + /* Rewind on backward seeks. */ + if (loc < gh->dec_pos) gz_reset(gh); + /* Skip forward to reach the requested offset. EOS during seek means + the requested location is past end-of-stream - return 0 bytes. */ + while (gh->dec_pos < loc) { + uint8_t discard[4096]; + uint64_t gap = loc - gh->dec_pos; + size_t chunk = gap > sizeof(discard) ? sizeof(discard) : (size_t)gap; + int64_t n = pdgzip_read(gh->gz, discard, chunk); + if (n < 0) panic(false, "gzip: decompression error during seek"); + if (n == 0) return 0; + gh->dec_pos += (uint64_t)n; + } + /* Decompress the requested data. */ + uint8_t * dst = buf; + uint64_t remaining = count; + while (remaining > 0) { + size_t chunk = remaining > 65536 ? 65536 : (size_t)remaining; + int64_t n = pdgzip_read(gh->gz, dst, chunk); + if (n < 0) panic(false, "gzip: decompression error"); + if (n == 0) break; + dst += n; + remaining -= (uint64_t)n; + gh->dec_pos += (uint64_t)n; + } + return count - remaining; +} + +static void gzip_close(struct file_handle * file) { + struct gzip_handle * gh = file->fd; + fclose(gh->source); + pmm_free(gh->scratch, gh->scratch_sz); + pmm_free(gh, sizeof(struct gzip_handle)); +} + +bool gzip_check(struct file_handle * fd) { + if (fd->size < 18) return false; + uint8_t magic[2]; fread(fd, magic, 0, 2); + return magic[0] == 0x1F && magic[1] == 0x8B; +} + +struct file_handle * gzip_open(struct file_handle * compressed) { + /* The decompressed size is not known up front. The 4-byte ISIZE trailer + is unreliable (modulo 2^32, spec defect) and callers must instead + drain until gzip_read returns 0 bytes (EOS). Advertise an unknown + size via UINT64_MAX. */ + struct gzip_handle * gh = ext_mem_alloc(sizeof(struct gzip_handle)); + gh->source = compressed; + gh->scratch_sz = pdgzip_state_size(); + gh->scratch = ext_mem_alloc(gh->scratch_sz); + gz_reset(gh); + /* Depends on ext_mem_alloc returning zeroed memory. */ + struct file_handle * ret = ext_mem_alloc(sizeof(struct file_handle)); + ret->fd = gh; + ret->read = (void *) gzip_read; + ret->close = (void *) gzip_close; + ret->size = UINT64_MAX; + ret->vol = compressed->vol; + if (compressed->path != NULL && compressed->path_len > 0) { + ret->path = ext_mem_alloc(compressed->path_len); + memcpy(ret->path, compressed->path, compressed->path_len); + ret->path_len = compressed->path_len; + } +#if defined (UEFI) + ret->efi_part_handle = compressed->efi_part_handle; +#endif + ret->pxe = compressed->pxe; + ret->pxe_ip = compressed->pxe_ip; + ret->pxe_port = compressed->pxe_port; + return ret; +} diff --git a/common/compress/gzip.h b/common/compress/gzip.h new file mode 100644 index 000000000..67b0a2302 --- /dev/null +++ b/common/compress/gzip.h @@ -0,0 +1,53 @@ +/* embeddable gzip decoder: Copyright (C) 2026 Kamila Szewczyk + * limine: Copyright (C) 2019-2026 Mintsuki and contributors. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef COMPRESS__GZIP_H__ +#define COMPRESS__GZIP_H__ + +#include + +/* Check if a file handle points to gzip-compressed data (0x1F 0x8B magic). */ +bool gzip_check(struct file_handle * fd); + +/* Wrap a gzip-compressed file handle in a decompressing layer. + * + * Returns a new file_handle whose read callback transparently + * decompresses the data. The returned handle takes ownership of + * `compressed` and will close it when itself is closed. + * + * WARNING: Due to a Gzip format deficiency, ->size of the resulting + * file_handle is only an approximation (i.e., it is not correct for + * files larger than 4 GiB and doesn't necessarily have to reflect + * the genuine decompressed size at all in adversarial circumstances). + * + * The real decompressed size can only be authoritatively obtained by + * fully decompressing the file. + * + * Supports very fast sequential reads and random-access reads (with + * an implicit rewind + skip penalty inherent to the gzip format). + */ +struct file_handle * gzip_open(struct file_handle * compressed); + +#endif diff --git a/common/crypt/blake2b.c b/common/crypt/blake2b.c index b62183f36..f52637a6c 100644 --- a/common/crypt/blake2b.c +++ b/common/crypt/blake2b.c @@ -6,6 +6,8 @@ #include #include #include +#include +#include #define BLAKE2B_BLOCK_BYTES 128 #define BLAKE2B_KEY_BYTES 64 @@ -218,3 +220,73 @@ void blake2b(void *out, const void *in, size_t in_len) { blake2b_update(&state, in, in_len); blake2b_final(&state, out); } + +/* Streaming filter: wraps a source file_handle and hashes bytes as + they are read sequentially. The hash is finalized and compared via + blake2b_check_hash(). Non-sequential reads panic -- the filter is + meant to sit underneath the gzip bitreader or uri_open's drain loop, + both of which advance monotonically. */ +struct blake2b_handle { + struct file_handle *source; + struct blake2b_state state; + uint64_t pos; + bool finalized; + uint8_t digest[BLAKE2B_OUT_BYTES]; +}; + +static uint64_t blake2b_read(struct file_handle *fh, void *buf, uint64_t loc, uint64_t count) { + struct blake2b_handle *h = fh->fd; + if (loc != h->pos) { + panic(false, "blake2b filter: non-sequential read (pos=%x, loc=%x)", + (uint64_t)h->pos, loc); + } + uint64_t got = fread(h->source, buf, loc, count); + blake2b_update(&h->state, buf, got); + h->pos += got; + return got; +} + +static void blake2b_close(struct file_handle *fh) { + struct blake2b_handle *h = fh->fd; + fclose(h->source); + pmm_free(h, sizeof(struct blake2b_handle)); +} + +struct file_handle *blake2b_open(struct file_handle *source) { + struct blake2b_handle *h = ext_mem_alloc(sizeof(struct blake2b_handle)); + blake2b_init(&h->state); + h->source = source; + h->pos = 0; + h->finalized = false; + + struct file_handle *ret = ext_mem_alloc(sizeof(struct file_handle)); + ret->fd = h; + ret->read = (void *)blake2b_read; + ret->close = (void *)blake2b_close; + ret->size = source->size; + ret->vol = source->vol; + if (source->path != NULL && source->path_len > 0) { + ret->path = ext_mem_alloc(source->path_len); + memcpy(ret->path, source->path, source->path_len); + ret->path_len = source->path_len; + } +#if defined (UEFI) + ret->efi_part_handle = source->efi_part_handle; +#endif + ret->pxe = source->pxe; + ret->pxe_ip = source->pxe_ip; + ret->pxe_port = source->pxe_port; + return ret; +} + +bool blake2b_check_hash(struct file_handle *fh, void *reference_hash) { + if (fh->read != (void *)blake2b_read) { + panic(false, "blake2b_check_hash: not a blake2b filter handle"); + } + struct blake2b_handle *h = fh->fd; + if (!h->finalized) { + blake2b_final(&h->state, h->digest); + h->finalized = true; + } + return memcmp(h->digest, reference_hash, BLAKE2B_OUT_BYTES) == 0; +} diff --git a/common/crypt/blake2b.h b/common/crypt/blake2b.h index 313a7c63c..bcee067fe 100644 --- a/common/crypt/blake2b.h +++ b/common/crypt/blake2b.h @@ -7,4 +7,8 @@ void blake2b(void *out, const void *in, size_t in_len); +struct file_handle; +struct file_handle * blake2b_open(struct file_handle * source); +bool blake2b_check_hash(struct file_handle *fd, void* reference_hash); + #endif diff --git a/common/fs/fat32.s2.c b/common/fs/fat32.s2.c index 00d38f936..956aa43fc 100644 --- a/common/fs/fat32.s2.c +++ b/common/fs/fat32.s2.c @@ -605,7 +605,7 @@ char *fat32_get_label(struct volume *part) { return context.label; } -static void fat32_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count); +static uint64_t fat32_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count); static void fat32_close(struct file_handle *file); struct file_handle *fat32_open(struct volume *part, const char *path) { @@ -622,7 +622,7 @@ struct file_handle *fat32_open(struct volume *part, const char *path) { unsigned int current_index = 0; char current_part[FAT32_LFN_MAX_FILENAME_LENGTH]; - // skip trailing slashes + // skip leading slashes while (path[current_index] == '/') { current_index++; } @@ -719,11 +719,12 @@ struct file_handle *fat32_open(struct volume *part, const char *path) { } } -static void fat32_read(struct file_handle *file, void *buf, uint64_t loc, uint64_t count) { +static uint64_t fat32_read(struct file_handle *file, void *buf, uint64_t loc, uint64_t count) { struct fat32_file_handle *f = file->fd; if (!read_cluster_chain(&f->context, f->cluster_chain, f->chain_len, buf, loc, count)) { panic(false, "fat32: cluster chain read failed (corrupted filesystem?)"); } + return count; } static void fat32_close(struct file_handle *file) { diff --git a/common/fs/file.h b/common/fs/file.h index 86474bd0b..800169246 100644 --- a/common/fs/file.h +++ b/common/fs/file.h @@ -17,13 +17,15 @@ char *fs_get_label(struct volume *part); struct file_handle { bool is_memfile; bool readall; + bool is_high_mem; struct volume *vol; char *path; size_t path_len; void *fd; - void (*read)(void *fd, void *buf, uint64_t loc, uint64_t count); + uint64_t (*read)(void *fd, void *buf, uint64_t loc, uint64_t count); void (*close)(void *fd); uint64_t size; + uint64_t load_addr_64; #if defined (UEFI) EFI_HANDLE efi_part_handle; #endif @@ -33,13 +35,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); +uint64_t fread(struct file_handle *fd, void *buf, uint64_t loc, uint64_t count); void fclose(struct file_handle *fd); -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__) - , void (*memcpy_to_64)(uint64_t dst, void *src, size_t count) -#endif -); #endif diff --git a/common/fs/file.s2.c b/common/fs/file.s2.c index 5cde4663e..ad133c3dd 100644 --- a/common/fs/file.s2.c +++ b/common/fs/file.s2.c @@ -81,104 +81,14 @@ void fclose(struct file_handle *fd) { pmm_free(fd, sizeof(struct file_handle)); } -void fread(struct file_handle *fd, void *buf, uint64_t loc, uint64_t count) { +uint64_t fread(struct file_handle *fd, void *buf, uint64_t loc, uint64_t count) { if (fd->is_memfile) { if (loc >= fd->size || count > fd->size - loc) { panic(false, "fread: attempted out of bounds read"); } memcpy(buf, fd->fd + loc, count); + return count; } else { - fd->read(fd, buf, loc, count); - } -} - -void *freadall(struct file_handle *fd, uint32_t type) { - return freadall_mode(fd, type, false -#if defined (__i386__) - , NULL -#endif - ); -} - -void *freadall_mode(struct file_handle *fd, uint32_t type, bool allow_high_allocs -#if defined (__i386__) - , void (*memcpy_to_64)(uint64_t dst, void *src, size_t count) -#endif -) { -#if defined (__i386__) - static uint64_t high_ret; - - if (memcpy_to_64 == NULL) { - allow_high_allocs = false; - } -#endif - - if (fd->is_memfile) { - if (fd->readall) { -#if defined (__i386__) - if (allow_high_allocs == true) { - high_ret = (uintptr_t)fd->fd; - return &high_ret; - } -#endif - return fd->fd; - } -#if defined (UEFI) && defined (__x86_64__) - if (!allow_high_allocs && (uintptr_t)fd->fd >= 0x100000000) { - void *newptr = ext_mem_alloc_type(fd->size, type); - memcpy(newptr, fd->fd, fd->size); - pmm_free(fd->fd, fd->size); - fd->fd = newptr; - } else { -#endif - memmap_alloc_range((uint64_t)(size_t)fd->fd, ALIGN_UP(fd->size, 4096, panic(false, "Alignment overflow")), type, 0, true, false, false); -#if defined (UEFI) && defined (__x86_64__) - } -#endif - fd->readall = true; -#if defined (__i386__) - if (allow_high_allocs == true) { - high_ret = (uintptr_t)fd->fd; - return &high_ret; - } -#endif - return fd->fd; - } else { - void *ret = ext_mem_alloc_type_aligned_mode(fd->size, type, 4096, allow_high_allocs); -#if defined (__i386__) - if (allow_high_allocs == true) { - high_ret = *(uint64_t *)ret; - if (high_ret < 0x100000000) { - ret = (void *)(uintptr_t)high_ret; - goto low_ret; - } - void *pool = ext_mem_alloc(0x100000); - for (uint64_t i = 0; i < fd->size; i += 0x100000) { - size_t count; - if (fd->size - i < 0x100000) { - count = fd->size - i; - } else { - count = 0x100000; - } - fd->read(fd, pool, i, count); - memcpy_to_64(high_ret + i, pool, count); - } - pmm_free(pool, 0x100000); - fd->close(fd); - return &high_ret; - } -low_ret: -#endif - fd->read(fd, ret, 0, fd->size); - fd->close(fd); - fd->fd = ret; - fd->readall = true; - fd->is_memfile = true; -#if defined (__i386__) - if (allow_high_allocs == true) { - return &high_ret; - } -#endif - return ret; + return fd->read(fd, buf, loc, count); } } diff --git a/common/fs/iso9660.s2.c b/common/fs/iso9660.s2.c index 2800f7082..1c1296a3d 100644 --- a/common/fs/iso9660.s2.c +++ b/common/fs/iso9660.s2.c @@ -338,7 +338,7 @@ static struct iso9660_directory_entry *iso9660_find(void *buffer, uint32_t size, return NULL; } -static void iso9660_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count); +static uint64_t iso9660_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count); static void iso9660_close(struct file_handle *file); struct file_handle *iso9660_open(struct volume *vol, const char *path) { @@ -518,7 +518,8 @@ setup_handle:; return handle; } -static void iso9660_read(struct file_handle *file, void *buf, uint64_t loc, uint64_t count) { +static uint64_t iso9660_read(struct file_handle *file, void *buf, uint64_t loc, uint64_t count) { + uint64_t requested = count; struct iso9660_file_handle *f = file->fd; // Find which extent 'loc' falls into and read across extents as needed @@ -550,6 +551,7 @@ static void iso9660_read(struct file_handle *file, void *buf, uint64_t loc, uint if (count > 0) { panic(false, "iso9660: read beyond end of file"); } + return requested; } static void iso9660_close(struct file_handle *file) { diff --git a/common/lib/gterm.c b/common/lib/gterm.c index cdc37b7bd..c77d2dc60 100644 --- a/common/lib/gterm.c +++ b/common/lib/gterm.c @@ -588,7 +588,11 @@ static void gterm_parse_config(char *config, struct gterm_config *cfg) { print("Wallpaper skipped: Secure Boot is active and no hash is associated.\n"); } else { struct file_handle *bg_file; - if ((bg_file = uri_open(background_path)) != NULL) { + if ((bg_file = uri_open(background_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false +#if defined (__i386__) + , NULL, NULL +#endif + )) != NULL) { background = image_open(bg_file); fclose(bg_file); } @@ -663,7 +667,11 @@ static void gterm_parse_config(char *config, struct gterm_config *cfg) { goto config_no_load_font; } struct file_handle *f; - if ((f = uri_open(menu_font)) == NULL) { + if ((f = uri_open(menu_font, MEMMAP_BOOTLOADER_RECLAIMABLE, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) { print("menu: Could not open font file.\n"); } else { if (cfg->font_size > f->size) { diff --git a/common/lib/misc.c b/common/lib/misc.c index 45cfdbb86..2c850a7b1 100644 --- a/common/lib/misc.c +++ b/common/lib/misc.c @@ -135,10 +135,14 @@ void *get_device_tree_blob(const char *config, size_t extra_size) { } if (dtb_path != NULL) { struct file_handle *dtb_file; - if ((dtb_file = uri_open(dtb_path)) == NULL) + if ((dtb_file = uri_open(dtb_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(soft_panic, "dtb: Failed to open device tree blob with path `%#`. Is the path correct?", dtb_path); - dtb = freadall(dtb_file, MEMMAP_BOOTLOADER_RECLAIMABLE); + dtb = dtb_file->fd; size = dtb_file->size; fclose(dtb_file); printv("dtb: loaded dtb at %p from file `%#`\n", dtb, dtb_path); diff --git a/common/lib/uri.c b/common/lib/uri.c index 7f8687915..2850bf7e1 100644 --- a/common/lib/uri.c +++ b/common/lib/uri.c @@ -12,6 +12,7 @@ #include #include #include +#include // A URI takes the form of: resource(root):/path#hash // The following function splits up a URI into its components. @@ -222,8 +223,52 @@ static struct file_handle *uri_boot_dispatch(char *s_part, char *path) { return fopen(volume, path); } -struct file_handle *uri_open(char *uri) { - struct file_handle *ret; +// Release a range of memory previously reserved with memmap_alloc_range. +// Works for both low and high addresses, unlike pmm_free which truncates +// on 32-bit builds. +static void uri_release_range(uint64_t addr, uint64_t count) { + count = ALIGN_UP(count, 4096, panic(false, "uri: alignment overflow")); + memmap_alloc_range(addr, count, MEMMAP_USABLE, 0, false, false, true); +} + +// Allocate `count` bytes via ext_mem_alloc_type_aligned_mode and return +// the physical address in *out_addr. When allow_high_mem is true on i386 +// and the allocator landed above 4 GiB, *out_low is set to NULL and the +// 64-bit address is stored in *out_addr. Otherwise *out_low points at the +// allocation and *out_addr == (uintptr_t)*out_low. +static void uri_alloc(uint64_t count, uint32_t type, bool allow_high_mem, + void **out_low, uint64_t *out_addr) { + void *ret = ext_mem_alloc_type_aligned_mode(count, type, 4096, allow_high_mem); +#if defined (__i386__) + if (allow_high_mem) { + uint64_t addr = *(uint64_t *)ret; + if (addr >= 0x100000000) { + *out_low = NULL; + *out_addr = addr; + return; + } + ret = (void *)(uintptr_t)addr; + } +#else + (void)allow_high_mem; +#endif + *out_low = ret; + *out_addr = (uintptr_t)ret; +} + +struct file_handle *uri_open(char *uri, uint32_t type, bool allow_high_mem +#if defined (__i386__) + , void (*memcpy_to_64)(uint64_t dst, void *src, size_t count) + , void (*memcpy_from_64)(void *dst, uint64_t src, size_t count) +#endif +) { +#if defined (__i386__) + if (memcpy_to_64 == NULL || memcpy_from_64 == NULL) { + allow_high_mem = false; + } +#endif + + struct file_handle *raw; char *resource = NULL, *root = NULL, *path = NULL, *hash = NULL; if (!uri_resolve(uri, &resource, &root, &path, &hash)) { @@ -234,43 +279,199 @@ struct file_handle *uri_open(char *uri) { panic(true, "No resource specified for URI `%#`.", uri); } + bool gz_compressed = *resource == '$'; + if (gz_compressed) { + resource++; + } + if (!strcmp(resource, "hdd")) { - ret = uri_hdd_dispatch(root, path); + raw = uri_hdd_dispatch(root, path); } else if (!strcmp(resource, "odd")) { - ret = uri_odd_dispatch(root, path); + raw = uri_odd_dispatch(root, path); } else if (!strcmp(resource, "boot")) { - ret = uri_boot_dispatch(root, path); + raw = uri_boot_dispatch(root, path); } else if (!strcmp(resource, "guid")) { - ret = uri_guid_dispatch(root, path); + raw = uri_guid_dispatch(root, path); } else if (!strcmp(resource, "uuid")) { - ret = uri_guid_dispatch(root, path); + raw = uri_guid_dispatch(root, path); } else if (!strcmp(resource, "fslabel")) { - ret = uri_fslabel_dispatch(root, path); + raw = uri_fslabel_dispatch(root, path); } else if (!strcmp(resource, "tftp")) { - ret = uri_tftp_dispatch(root, path); + raw = uri_tftp_dispatch(root, path); } else { panic(true, "Resource `%s` not valid.", resource); } - if (secure_boot_active && hash == NULL && ret != NULL) { - panic(true, "Secure Boot is active and URI `%#` has no associated hash!", uri); + if (raw == NULL) { + return NULL; } - if (hash != NULL && ret != NULL) { - uint8_t out_buf[BLAKE2B_OUT_BYTES]; -#if defined (UEFI) && defined (__x86_64__) - void *file_buf = freadall_mode(ret, MEMMAP_BOOTLOADER_RECLAIMABLE, true); -#else - void *file_buf = freadall(ret, MEMMAP_BOOTLOADER_RECLAIMABLE); -#endif - blake2b(out_buf, file_buf, ret->size); - uint8_t hash_buf[BLAKE2B_OUT_BYTES]; + if (secure_boot_active && hash == NULL) { + panic(true, "Secure Boot is active and URI `%#` has no associated hash!", uri); + } + uint8_t hash_buf[BLAKE2B_OUT_BYTES]; + if (hash != NULL) { for (size_t i = 0; i < sizeof(hash_buf); i++) { hash_buf[i] = digit_to_int(hash[i * 2]) << 4 | digit_to_int(hash[i * 2 + 1]); } + } - if (memcmp(hash_buf, out_buf, sizeof(out_buf)) != 0) { + // Snapshot metadata from raw before the close cascade frees its buffers. + struct volume *raw_vol = raw->vol; + size_t raw_path_len = raw->path_len; + char *raw_path_copy = NULL; + if (raw->path != NULL && raw_path_len > 0) { + raw_path_copy = ext_mem_alloc(raw_path_len); + memcpy(raw_path_copy, raw->path, raw_path_len); + } +#if defined (UEFI) + EFI_HANDLE raw_efi_part = raw->efi_part_handle; +#endif + bool raw_pxe = raw->pxe; + uint32_t raw_pxe_ip = raw->pxe_ip; + uint16_t raw_pxe_port = raw->pxe_port; + + // Build the filter chain: raw -> blake2b -> gzip. blake2b hashes on-disk + // (compressed) bytes. + struct file_handle *top = raw; + struct file_handle *hash_fh = NULL; + if (hash != NULL) { + hash_fh = blake2b_open(top); + top = hash_fh; + } + if (gz_compressed) { + top = gzip_open(top); + } + + // Drain the stream into a final allocation. + void *buf_low = NULL; + uint64_t buf_addr = 0; + uint64_t buf_cap = 0; + uint64_t buf_len = 0; + bool is_high = false; + + if (!gz_compressed) { + // Size is authoritative. Single up-front allocation, one copy. + uint64_t sz = top->size; + uri_alloc(sz, type, allow_high_mem, &buf_low, &buf_addr); + is_high = (buf_low == NULL); + +#if defined (__i386__) + if (is_high) { + // 1 MiB bounce loop, same as the old freadall_mode high path. + void *pool = ext_mem_alloc(0x100000); + for (uint64_t i = 0; i < sz; i += 0x100000) { + size_t chunk = sz - i < 0x100000 ? (size_t)(sz - i) : 0x100000; + uint64_t got = top->read(top, pool, i, chunk); + if (got != chunk) { + panic(false, "uri: short read from non-gzip stream"); + } + memcpy_to_64(buf_addr + i, pool, chunk); + } + pmm_free(pool, 0x100000); + } else +#endif + { + // In-place fill. + if (sz > 0) { + uint64_t got = top->read(top, buf_low, 0, sz); + if (got != sz) { + panic(false, "uri: short read from non-gzip stream"); + } + } + } + buf_len = sz; + } else { + // Size is unknown (UINT64_MAX from gzip_open). Stretchy vector. + // Initial capacity: 1 MiB, doubles on exhaustion. + buf_cap = 0x100000; + uri_alloc(buf_cap, type, allow_high_mem, &buf_low, &buf_addr); + is_high = (buf_low == NULL); + +#if defined (__i386__) + // High-path uses a 1 MiB bounce pool for both the read side and + // the grow-copy; reused across iterations. + void *pool = is_high ? ext_mem_alloc(0x100000) : NULL; +#endif + + for (;;) { + if (buf_len == buf_cap) { + // Grow: new capacity = 2x (capped to prevent absurd jumps). + uint64_t new_cap = buf_cap * 2; + void *new_low = NULL; + uint64_t new_addr = 0; + uri_alloc(new_cap, type, allow_high_mem, &new_low, &new_addr); + bool new_is_high = (new_low == NULL); + +#if defined (__i386__) + if (is_high && new_is_high) { + // 64-to-64: bounce via low pool in 1 MiB strides. + for (uint64_t off = 0; off < buf_len; off += 0x100000) { + size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000; + memcpy_from_64(pool, buf_addr + off, chunk); + memcpy_to_64(new_addr + off, pool, chunk); + } + } else if (is_high && !new_is_high) { + // Shouldn't happen: once we landed high we ask for high. + // Keep a defensive path: bounce via pool, then memcpy. + for (uint64_t off = 0; off < buf_len; off += 0x100000) { + size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000; + memcpy_from_64(pool, buf_addr + off, chunk); + memcpy((uint8_t *)new_low + off, pool, chunk); + } + } else if (!is_high && new_is_high) { + for (uint64_t off = 0; off < buf_len; off += 0x100000) { + size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000; + memcpy_to_64(new_addr + off, (uint8_t *)buf_low + off, chunk); + } + } else +#endif + { + (void)new_is_high; /* Silence unused warning on non-i386. */ + memcpy(new_low, buf_low, buf_len); + } + + // Release the old allocation. + uri_release_range(buf_addr, buf_cap); + + buf_low = new_low; + buf_addr = new_addr; + buf_cap = new_cap; +#if defined (__i386__) + if (is_high != new_is_high && new_is_high && pool == NULL) { + pool = ext_mem_alloc(0x100000); + } + is_high = new_is_high; +#endif + } + + uint64_t want = buf_cap - buf_len; + if (want > 65536) want = 65536; + + uint64_t got; +#if defined (__i386__) + if (is_high) { + got = top->read(top, pool, buf_len, want); + if (got > 0) memcpy_to_64(buf_addr + buf_len, pool, got); + } else +#endif + { + got = top->read(top, (uint8_t *)buf_low + buf_len, buf_len, want); + } + if (got == 0) break; + buf_len += got; + } + +#if defined (__i386__) + if (pool != NULL) pmm_free(pool, 0x100000); +#endif + } + + // Finalize hash check now that all compressed bytes have flowed through + // the filter. + if (hash_fh != NULL) { + if (!blake2b_check_hash(hash_fh, hash_buf)) { if (hash_mismatch_panic) { panic(true, "Blake2b hash for URI `%#` does not match!", uri); } else { @@ -286,5 +487,27 @@ struct file_handle *uri_open(char *uri) { } } - return ret; + // Close the filter chain. fclose cascades. + fclose(top); + + // Build the returned memfile. Fresh allocation so we never mutate any + // closed filter handle's state. + struct file_handle *out = ext_mem_alloc(sizeof(struct file_handle)); + out->is_memfile = true; + out->readall = true; + out->is_high_mem = is_high; + out->fd = is_high ? NULL : buf_low; + out->load_addr_64 = buf_addr; + out->size = buf_len; + out->vol = raw_vol; + out->path = raw_path_copy; + out->path_len = raw_path_copy != NULL ? raw_path_len : 0; +#if defined (UEFI) + out->efi_part_handle = raw_efi_part; +#endif + out->pxe = raw_pxe; + out->pxe_ip = raw_pxe_ip; + out->pxe_port = raw_pxe_port; + + return out; } diff --git a/common/lib/uri.h b/common/lib/uri.h index d15a5494b..107a42927 100644 --- a/common/lib/uri.h +++ b/common/lib/uri.h @@ -5,6 +5,22 @@ #include bool uri_resolve(char *uri, char **resource, char **root, char **path, char **hash); -struct file_handle *uri_open(char *uri); + +// uri_open resolves the URI, verifies the blake2b hash (if present) and +// gzip-decodes (if the resource is prefixed with `$`) in a single streaming +// pass, and returns a memfile (is_memfile=true, readall=true) whose payload +// has been placed in memory of the requested `type`. +// +// When `allow_high_mem` is true and the target architecture is i386, the +// buffer may end up above 4 GiB; in that case the returned handle has +// is_high_mem=true, fd=NULL, and load_addr_64 holding the physical address. +// Otherwise load_addr_64 == (uintptr_t)fd. On i386 the two memcpy callbacks +// are used only when allow_high_mem is true; pass NULL otherwise. +struct file_handle *uri_open(char *uri, uint32_t type, bool allow_high_mem +#if defined (__i386__) + , void (*memcpy_to_64)(uint64_t dst, void *src, size_t count) + , void (*memcpy_from_64)(void *dst, uint64_t src, size_t count) +#endif +); #endif diff --git a/common/protos/chainload.c b/common/protos/chainload.c index dfd20e5eb..b92a5b60a 100644 --- a/common/protos/chainload.c +++ b/common/protos/chainload.c @@ -272,7 +272,11 @@ noreturn void chainload(char *config, char *cmdline) { secure_boot_active = false; struct file_handle *image; - if ((image = uri_open(image_path)) == NULL) + if ((image = uri_open(image_path, MEMMAP_RESERVED, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(true, "efi: Failed to open image with path `%s`. Is the path correct?", image_path); secure_boot_active = saved_secure_boot_active; @@ -281,7 +285,7 @@ noreturn void chainload(char *config, char *cmdline) { EFI_HANDLE efi_part_handle = image->efi_part_handle; - void *ptr = freadall(image, MEMMAP_RESERVED); + void *ptr = image->fd; size_t image_size = image->size; memmap_alloc_range_in(untouched_memmap, &untouched_memmap_entries, diff --git a/common/protos/limine.c b/common/protos/limine.c index aced5f8d2..0bd53bc16 100644 --- a/common/protos/limine.c +++ b/common/protos/limine.c @@ -143,9 +143,9 @@ static pagemap_t build_identity_map(void) { return pagemap; } -void limine_memcpy_to_64_asm(int paging_mode, void *pagemap, uint64_t dst, void *src, size_t count); +void limine_memcpy_64_asm(int paging_mode, void *pagemap, uint64_t dst, uint64_t src, size_t count); -static void limine_memcpy_to_64(uint64_t dst, void *src, size_t count) { +static void limine_ensure_identity_map(pagemap_t *out) { static bool identity_map_ready = false; static pagemap_t identity_map; @@ -153,8 +153,19 @@ static void limine_memcpy_to_64(uint64_t dst, void *src, size_t count) { identity_map = build_identity_map(); identity_map_ready = true; } + *out = identity_map; +} + +static void limine_memcpy_to_64(uint64_t dst, void *src, size_t count) { + pagemap_t identity_map; + limine_ensure_identity_map(&identity_map); + limine_memcpy_64_asm(paging_mode, identity_map.top_level, dst, (uint64_t)(uintptr_t)src, count); +} - limine_memcpy_to_64_asm(paging_mode, identity_map.top_level, dst, src, count); +static void limine_memcpy_from_64(void *dst, uint64_t src, size_t count) { + pagemap_t identity_map; + limine_ensure_identity_map(&identity_map); + limine_memcpy_64_asm(paging_mode, identity_map.top_level, (uint64_t)(uintptr_t)dst, src, count); } #endif @@ -367,7 +378,7 @@ static uint64_t reported_addr_64(uint64_t addr) { get_phys_addr__r; \ }) -static struct limine_file get_file(struct file_handle *file, char *cmdline, bool kernel) { +static struct limine_file get_file(struct file_handle *file, char *cmdline) { struct limine_file ret = {0}; if (file->pxe) { @@ -405,15 +416,14 @@ static struct limine_file get_file(struct file_handle *file, char *cmdline, bool ret.path = reported_addr(path); - void *freadall_ret = freadall_mode(file, MEMMAP_KERNEL_AND_MODULES, !kernel #if defined (__i386__) - , limine_memcpy_to_64 -#endif - ); -#if defined (__i386__) - ret.address = kernel ? reported_addr(freadall_ret) : reported_addr_64(*(uint64_t *)freadall_ret); + if (file->is_high_mem) { + ret.address = reported_addr_64(file->load_addr_64); + } else { + ret.address = reported_addr(file->fd); + } #else - ret.address = reported_addr(freadall_ret); + ret.address = reported_addr(file->fd); #endif ret.size = file->size; @@ -472,7 +482,11 @@ noreturn void limine_load(char *config, char *cmdline) { print("limine: Loading executable `%#`...\n", kernel_path); struct file_handle *kernel_file; - if ((kernel_file = uri_open(kernel_path)) == NULL) + if ((kernel_file = uri_open(kernel_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(true, "limine: Failed to open executable with path `%#`. Is the path correct?", kernel_path); char *k_path_copy = ext_mem_alloc(strlen(kernel_path) + 1); @@ -495,7 +509,7 @@ noreturn void limine_load(char *config, char *cmdline) { k_path[i] = 0; } - uint8_t *kernel = freadall(kernel_file, MEMMAP_BOOTLOADER_RECLAIMABLE); + uint8_t *kernel = kernel_file->fd; char *kaslr_s = config_get_value(config, 0, "KASLR"); bool kaslr = false; @@ -929,7 +943,7 @@ FEAT_END #endif struct limine_file *kf = ext_mem_alloc(sizeof(struct limine_file)); - *kf = get_file(kernel_file, cmdline, true); + *kf = get_file(kernel_file, cmdline); fclose(kernel_file); // Entry point feature @@ -1229,23 +1243,24 @@ FEAT_START module_path = (char *)get_phys_addr(internal_module->path); module_cmdline = (char *)get_phys_addr(internal_module->string); - if (internal_module->flags & LIMINE_INTERNAL_MODULE_COMPRESSED) { - panic(true, "limine: Compressed internal modules no longer supported"); - } - + bool module_compressed = internal_module->flags & LIMINE_INTERNAL_MODULE_COMPRESSED; + // Validate path length to prevent buffer overflow size_t k_resource_len = strlen(k_resource); size_t k_root_len = strlen(k_root); size_t module_path_len = strlen(module_path); size_t k_path_len = strlen(k_path); - // Format: k_resource + "(" + k_root + "):" + k_path + "/" + module_path + null - size_t total_len = k_resource_len + 1 + k_root_len + 2 + k_path_len + 1 + module_path_len + 1; + // Format: ["$"] + k_resource + "(" + k_root + "):" + k_path + "/" + module_path + null + size_t total_len = (module_compressed ? 1 : 0) + k_resource_len + 1 + k_root_len + 2 + k_path_len + 1 + module_path_len + 1; if (total_len > 1024) { panic(true, "limine: Internal module path too long"); } char *module_path_abs = ext_mem_alloc(1024); char *module_path_abs_p = module_path_abs; + if (module_compressed) { + *module_path_abs_p++ = '$'; + } memcpy(module_path_abs_p, k_resource, k_resource_len); module_path_abs_p += k_resource_len; *module_path_abs_p++ = '('; @@ -1284,7 +1299,11 @@ FEAT_START print("limine: Loading module `%#`...\n", module_path); struct file_handle *f; - if ((f = uri_open(module_path)) == NULL) { + if ((f = uri_open(module_path, MEMMAP_KERNEL_AND_MODULES, true +#if defined (__i386__) + , limine_memcpy_to_64, limine_memcpy_from_64 +#endif + )) == NULL) { if (module_required) { panic(true, "limine: Failed to open module with path `%#`. Is the path correct?", module_path); } @@ -1299,7 +1318,7 @@ FEAT_START } struct limine_file *l = &modules[final_module_count++]; - *l = get_file(f, module_cmdline, false); + *l = get_file(f, module_cmdline); fclose(f); } diff --git a/common/protos/limine_asm.asm_ia32 b/common/protos/limine_asm.asm_ia32 index a83cc9d54..b7bb456ba 100644 --- a/common/protos/limine_asm.asm_ia32 +++ b/common/protos/limine_asm.asm_ia32 @@ -50,10 +50,14 @@ old_pagemap: section .text -; void limine_memcpy_to_64_asm(int paging_mode, void *pagemap, uint64_t dst, void *src, size_t count); - -global limine_memcpy_to_64_asm -limine_memcpy_to_64_asm: +; void limine_memcpy_64_asm(int paging_mode, void *pagemap, uint64_t dst, uint64_t src, size_t count); +; +; Both endpoints are 64-bit-wide. Low-memory callers cast pointers +; through (uint64_t)(uintptr_t). Identity mapping makes low addresses +; valid as 64-bit virtual addresses inside the long-mode copy. + +global limine_memcpy_64_asm +limine_memcpy_64_asm: push ebp mov ebp, esp @@ -177,8 +181,8 @@ bits 64 mov ss, eax mov rdi, qword [rbp+16] - mov esi, dword [rbp+24] - mov ecx, dword [rbp+28] + mov rsi, qword [rbp+24] + mov ecx, dword [rbp+32] rep movsb diff --git a/common/protos/linux_risc.c b/common/protos/linux_risc.c index 702f76925..6e6285a4f 100644 --- a/common/protos/linux_risc.c +++ b/common/protos/linux_risc.c @@ -133,7 +133,7 @@ static void load_module(struct boot_param *p, char *config) { print("linux: Loading module `%#`...\n", module_path); - struct file_handle *module_file = uri_open(module_path); + struct file_handle *module_file = uri_open(module_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false); if (!module_file) { panic(true, "linux: failed to open module `%s`. Is the path correct?", module_path); } @@ -481,7 +481,7 @@ noreturn void linux_load(char *config, char *cmdline) { print("linux: Loading kernel `%#`...\n", kernel_path); - if ((kernel_file = uri_open(kernel_path)) == NULL) { + if ((kernel_file = uri_open(kernel_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false)) == NULL) { panic(true, "linux: failed to open kernel `%s`. Is the path correct?", kernel_path); } diff --git a/common/protos/linux_x86.c b/common/protos/linux_x86.c index adc64c109..8960e196b 100644 --- a/common/protos/linux_x86.c +++ b/common/protos/linux_x86.c @@ -302,7 +302,11 @@ noreturn void linux_load(char *config, char *cmdline) { print("linux: Loading kernel `%#`...\n", kernel_path); - if ((kernel_file = uri_open(kernel_path)) == NULL) + if ((kernel_file = uri_open(kernel_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(true, "linux: Failed to open kernel with path `%#`. Is the path correct?", kernel_path); // Minimum size check: need at least 0x206 bytes for signature at 0x202 @@ -439,7 +443,11 @@ noreturn void linux_load(char *config, char *cmdline) { print("linux: Loading module `%#`...\n", module_path); struct file_handle *module; - if ((module = uri_open(module_path)) == NULL) + if ((module = uri_open(module_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(true, "linux: Failed to open module with path `%s`. Is the path correct?", module_path); size_of_all_modules = CHECKED_ADD(size_of_all_modules, module->size, diff --git a/common/protos/multiboot1.c b/common/protos/multiboot1.c index 08d95f565..589a2ada9 100644 --- a/common/protos/multiboot1.c +++ b/common/protos/multiboot1.c @@ -65,10 +65,14 @@ noreturn void multiboot1_load(char *config, char *cmdline) { print("multiboot1: Loading executable `%#`...\n", kernel_path); - if ((kernel_file = uri_open(kernel_path)) == NULL) + if ((kernel_file = uri_open(kernel_path, MEMMAP_KERNEL_AND_MODULES, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(true, "multiboot1: Failed to open executable with path `%#`. Is the path correct?", kernel_path); - uint8_t *kernel = freadall(kernel_file, MEMMAP_KERNEL_AND_MODULES); + uint8_t *kernel = kernel_file->fd; size_t kernel_file_size = kernel_file->size; @@ -321,7 +325,11 @@ noreturn void multiboot1_load(char *config, char *cmdline) { print("multiboot1: Loading module `%#`...\n", module_path); struct file_handle *f; - if ((f = uri_open(module_path)) == NULL) + if ((f = uri_open(module_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(true, "multiboot1: Failed to open module with path `%#`. Is the path correct?", module_path); char *module_cmdline = conf_tuple.value2; @@ -331,7 +339,7 @@ noreturn void multiboot1_load(char *config, char *cmdline) { char *lowmem_modstr = mb1_info_alloc(&mb1_info_raw, strlen(module_cmdline) + 1); strcpy(lowmem_modstr, module_cmdline); - void *module_addr = freadall(f, MEMMAP_BOOTLOADER_RECLAIMABLE); + void *module_addr = f->fd; uint64_t module_target = (uint64_t)-1; /* no target preference, use top */ if (!elsewhere_append(true /* flexible target */, diff --git a/common/protos/multiboot2.c b/common/protos/multiboot2.c index b7ec894a7..a41fe9480 100644 --- a/common/protos/multiboot2.c +++ b/common/protos/multiboot2.c @@ -85,10 +85,14 @@ noreturn void multiboot2_load(char *config, char* cmdline) { print("multiboot2: Loading executable `%#`...\n", kernel_path); - if ((kernel_file = uri_open(kernel_path)) == NULL) + if ((kernel_file = uri_open(kernel_path, MEMMAP_KERNEL_AND_MODULES, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(true, "multiboot2: Failed to open executable with path `%#`. Is the path correct?", kernel_path); - uint8_t *kernel = freadall(kernel_file, MEMMAP_KERNEL_AND_MODULES); + uint8_t *kernel = kernel_file->fd; size_t kernel_file_size = kernel_file->size; @@ -627,7 +631,11 @@ noreturn void multiboot2_load(char *config, char* cmdline) { print("multiboot2: Loading module `%#`...\n", module_path); struct file_handle *f; - if ((f = uri_open(module_path)) == NULL) + if ((f = uri_open(module_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false +#if defined (__i386__) + , NULL, NULL +#endif + )) == NULL) panic(true, "multiboot2: Failed to open module with path `%#`. Is the path correct?", module_path); // Module commandline can be null, so we guard against that and make the @@ -635,7 +643,7 @@ noreturn void multiboot2_load(char *config, char* cmdline) { char *module_cmdline = conf_tuple.value2; if (!module_cmdline) module_cmdline = ""; - void *module_addr = freadall(f, MEMMAP_BOOTLOADER_RECLAIMABLE); + void *module_addr = f->fd; uint64_t module_target = (uint64_t)-1; if (!elsewhere_append(true /* flexible target */, diff --git a/contrib/test_gzip_qemu.sh b/contrib/test_gzip_qemu.sh new file mode 100755 index 000000000..4cc24a7a3 --- /dev/null +++ b/contrib/test_gzip_qemu.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# Mechanically test gzip decompression end-to-end in QEMU. +# Boots the Limine test kernel via UEFI with both a plain file and +# its .gz counterpart as internal modules. The test kernel loads +# the compressed module (decompressed by the bootloader) and compares +# it byte-for-byte against the plain copy. +# Usage: bash contrib/test_gzip_qemu.sh +# Requires: qemu-system-x86_64, mtools, gzip +set -euo pipefail +cd "$(dirname "$0")/.." +TIMEOUT="${QEMU_TIMEOUT:-20}" +if ! command -v gzip >/dev/null 2>&1; then + echo "where's your gzip?" + exit 1 +fi +TEST_CFLAGS="-DENABLE_QEMU_SHUTDOWN -DENABLE_GZIP_TEST" +make limine-bios limine-uefi-x86-64 2>&1 | tail -1 +make edk2-ovmf 2>&1 | tail -1 +make -C test -f test.mk ARCH=x86 EXTRA_CFLAGS="$TEST_CFLAGS" test.elf 2>&1 | tail -1 +IMG=test_uefi.img +rm -f "$IMG" +mformat -i "$IMG" -C -F -T 131072 :: 2>/dev/null +mmd -i "$IMG" ::/boot ::/EFI ::/EFI/BOOT 2>/dev/null +mcopy -i "$IMG" bin/BOOTX64.EFI ::/EFI/BOOT/ +mcopy -i "$IMG" bin/limine-bios.sys ::/boot/ +mcopy -i "$IMG" test/test.elf ::/boot/ +mcopy -i "$IMG" test/bg.jpg ::/boot/ +mcopy -i "$IMG" test/limine.conf ::/boot/ +GZ_TMP=$(mktemp) +gzip -c test/limine.conf > "$GZ_TMP" +mcopy -i "$IMG" "$GZ_TMP" ::/boot/limine.conf.gz +rm -f "$GZ_TMP" +QEMU_LOG=$(mktemp) +trap 'rm -f "$QEMU_LOG" "$IMG"' EXIT +timeout "$TIMEOUT" \ + qemu-system-x86_64 \ + -display none \ + -m 512M -M q35 \ + -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-x86_64.fd,readonly=on \ + -net none -smp 4 \ + -drive format=raw,file="$IMG" \ + -debugcon file:"$QEMU_LOG" \ + || true # timeout exits 124 +if grep -q 'gzip: pass' "$QEMU_LOG"; then + grep 'gzip:' "$QEMU_LOG" + echo "pass: gzip decompression verified in QEMU" + exit 0 +elif grep -q 'gzip: FAIL' "$QEMU_LOG"; then + grep 'gzip:' "$QEMU_LOG" + echo "fail" + exit 1 +else + echo "fail: gzip test marker not found in QEMU output" + echo "last 20 lines of log:" + tail -20 "$QEMU_LOG" + exit 1 +fi diff --git a/test/limine.c b/test/limine.c index b0b11e794..52ed23ab0 100644 --- a/test/limine.c +++ b/test/limine.c @@ -6,6 +6,14 @@ #include #include +int memcmp(const void *, const void *, size_t); + +#ifdef ENABLE_QEMU_SHUTDOWN +static inline void outw(uint16_t port, uint16_t value) { + __asm volatile ("outw %%ax, %1" : : "a" (value), "Nd" (port) : "memory"); +} +#endif + __attribute__((section(".limine_requests"))) static volatile uint64_t limine_base_revision[] = LIMINE_BASE_REVISION(6); @@ -77,12 +85,25 @@ struct limine_internal_module internal_module2 = { struct limine_internal_module internal_module3 = { .path = "./limine.conf", .string = "Third internal module" + /* gzip test depends on this name to find + the original to compare against. */ }; +#ifdef ENABLE_GZIP_TEST +struct limine_internal_module internal_module4 = { + .path = "./limine.conf.gz", + .string = "gzip-compressed limine.conf", + .flags = LIMINE_INTERNAL_MODULE_COMPRESSED +}; +#endif + struct limine_internal_module *internal_modules[] = { &internal_module1, &internal_module2, - &internal_module3 + &internal_module3, +#ifdef ENABLE_GZIP_TEST + &internal_module4, +#endif }; __attribute__((section(".limine_requests"))) @@ -90,7 +111,7 @@ static volatile struct limine_module_request module_request = { .id = LIMINE_MODULE_REQUEST_ID, .revision = 1, .response = NULL, - .internal_module_count = 3, + .internal_module_count = sizeof(internal_modules) / sizeof(internal_modules[0]), .internal_modules = internal_modules }; @@ -528,6 +549,38 @@ FEAT_START e9_printf("---"); print_file(f); } + +#ifdef ENABLE_GZIP_TEST + /* Gzip decompression test: compare internal_module3 (plain limine.conf) + against internal_module4 (limine.conf.gz, decompressed by bootloader). */ + { + struct limine_file *plain = NULL, *decompressed = NULL; + for (size_t i = 0; i < module_response->module_count; i++) { + struct limine_file *f = module_response->modules[i]; + if (f->string != NULL) { + /* Match by the module string we assigned. */ + bool is_third = f->string[0] == 'T' && f->string[1] == 'h' + && f->string[2] == 'i' && f->string[3] == 'r' + && f->string[4] == 'd'; + bool is_gz = f->string[0] == 'g' && f->string[1] == 'z'; + if (is_third) plain = f; + if (is_gz) decompressed = f; + } + } + if (plain == NULL) { + e9_printf("gzip: FAIL (plain module not found)"); + } else if (decompressed == NULL) { + e9_printf("gzip: FAIL (decompressed module not found)"); + } else if (plain->size != decompressed->size) { + e9_printf("gzip: FAIL (size mismatch: plain=%x, decompressed=%x)", + plain->size, decompressed->size); + } else if (memcmp(plain->address, decompressed->address, plain->size) != 0) { + e9_printf("gzip: FAIL (content mismatch, size=%x)", plain->size); + } else { + e9_printf("gzip: pass (size=%x)", plain->size); + } + } +#endif FEAT_END FEAT_START @@ -701,5 +754,8 @@ FEAT_START e9_printf("Exec time: %d usec", perf_response->exec_usec); FEAT_END +#ifdef ENABLE_QEMU_SHUTDOWN + outw(0x604, 0x2000); /* QEMU-specific shutdown, used by automated tests. */ +#endif for (;;); } diff --git a/test/test.mk b/test/test.mk index beea2001a..48073e418 100644 --- a/test/test.mk +++ b/test/test.mk @@ -71,7 +71,8 @@ override CFLAGS += \ -I../limine-protocol/include \ -I../flanterm/src \ -isystem ../freestnd-c-hdrs/include \ - -D_LIMINE_PROTO + -D_LIMINE_PROTO \ + $(EXTRA_CFLAGS) ifeq ($(ARCH),x86) override CFLAGS += \