Build system changes: - scripts/gen_initrd.sh packs all files from apps/ into a newc-format CPIO archive at build/isodir/boot/initrd.cpio. - CMakeLists.txt adds 'initrd' target as ISO dependency. GRUB loads the archive as a Multiboot2 module via 'module2 /boot/initrd.cpio'. - apps/README added as placeholder file for initial ramdisk content. Kernel changes: - kernel.c scans Multiboot2 tags for MULTIBOOT_TAG_TYPE_MODULE to find the initrd's physical address range, then passes it to cpio_init(). - cpio.c/h implements a parser for the SVR4/newc CPIO format: - cpio_init(): lists archive contents on startup - cpio_find(): look up a file by name (handles ./ prefix) - cpio_next(): iterate through all entries - cpio_count(): count files in archive - The initrd lives in identity-mapped physical memory, so no additional mapping is needed to access it. Verified in QEMU: GRUB loads the module at 0x0014A000, CPIO parser finds the README file (38 bytes). All existing functionality (Ring 3 processes, syscalls) continues to work.
180 lines
4.7 KiB
C
180 lines
4.7 KiB
C
/**
|
|
* @file cpio.c
|
|
* @brief CPIO newc archive parser implementation.
|
|
*
|
|
* Parses CPIO archives in the SVR4/newc format. The archive is expected
|
|
* to be loaded into memory by GRUB as a Multiboot2 module.
|
|
*/
|
|
|
|
#include "cpio.h"
|
|
#include <string.h>
|
|
|
|
/* Debug print helpers defined in kernel.c */
|
|
extern void offset_print(const char *str);
|
|
extern void print_hex(uint32_t val);
|
|
|
|
/** Pointer to the CPIO archive in memory. */
|
|
static const uint8_t *archive = NULL;
|
|
|
|
/** Size of the archive (0 if unknown). */
|
|
static uint32_t archive_len = 0;
|
|
|
|
/**
|
|
* Parse an N-character hexadecimal ASCII string to uint32_t.
|
|
*
|
|
* @param s Pointer to hex string.
|
|
* @param n Number of characters to parse.
|
|
* @return Parsed value.
|
|
*/
|
|
static uint32_t parse_hex(const char *s, int n) {
|
|
uint32_t val = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
char c = s[i];
|
|
uint32_t digit;
|
|
if (c >= '0' && c <= '9') {
|
|
digit = (uint32_t)(c - '0');
|
|
} else if (c >= 'a' && c <= 'f') {
|
|
digit = (uint32_t)(c - 'a' + 10);
|
|
} else if (c >= 'A' && c <= 'F') {
|
|
digit = (uint32_t)(c - 'A' + 10);
|
|
} else {
|
|
break;
|
|
}
|
|
val = (val << 4) | digit;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Round up to 4-byte boundary.
|
|
*/
|
|
static inline uint32_t align4(uint32_t v) {
|
|
return (v + 3) & ~3u;
|
|
}
|
|
|
|
/**
|
|
* Parse a CPIO entry at the given offset.
|
|
*
|
|
* @param offset Byte offset into the archive.
|
|
* @param entry Output entry information.
|
|
* @return Offset of the next entry, or 0 on error/end.
|
|
*/
|
|
static uint32_t parse_entry(uint32_t offset, cpio_entry_t *entry) {
|
|
if (!archive) return 0;
|
|
|
|
const cpio_newc_header_t *hdr = (const cpio_newc_header_t *)(archive + offset);
|
|
|
|
/* Verify magic */
|
|
if (memcmp(hdr->magic, CPIO_MAGIC, 6) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t namesize = parse_hex(hdr->namesize, 8);
|
|
uint32_t filesize = parse_hex(hdr->filesize, 8);
|
|
uint32_t mode = parse_hex(hdr->mode, 8);
|
|
|
|
/* Filename starts right after the header */
|
|
const char *name = (const char *)(archive + offset + CPIO_HEADER_SIZE);
|
|
|
|
/* Data starts after header + name, aligned to 4 bytes */
|
|
uint32_t data_offset = align4(offset + CPIO_HEADER_SIZE + namesize);
|
|
const void *data = archive + data_offset;
|
|
|
|
/* Next entry starts after data, aligned to 4 bytes */
|
|
uint32_t next_offset = align4(data_offset + filesize);
|
|
|
|
entry->name = name;
|
|
entry->namesize = namesize;
|
|
entry->data = data;
|
|
entry->datasize = filesize;
|
|
entry->mode = mode;
|
|
|
|
return next_offset;
|
|
}
|
|
|
|
void cpio_init(const void *archive_start, uint32_t archive_size) {
|
|
archive = (const uint8_t *)archive_start;
|
|
archive_len = archive_size;
|
|
|
|
offset_print(" CPIO: archive at ");
|
|
print_hex((uint32_t)archive_start);
|
|
offset_print(" CPIO: size = ");
|
|
print_hex(archive_size);
|
|
|
|
/* Count and list entries */
|
|
uint32_t count = 0;
|
|
uint32_t off = 0;
|
|
cpio_entry_t entry;
|
|
while (1) {
|
|
uint32_t next = parse_entry(off, &entry);
|
|
if (next == 0) break;
|
|
if (strcmp(entry.name, CPIO_TRAILER) == 0) break;
|
|
|
|
offset_print(" CPIO: [");
|
|
offset_print(entry.name);
|
|
offset_print("] size=");
|
|
print_hex(entry.datasize);
|
|
|
|
count++;
|
|
off = next;
|
|
}
|
|
|
|
offset_print(" CPIO: ");
|
|
print_hex(count);
|
|
offset_print(" CPIO: files found\n");
|
|
}
|
|
|
|
int cpio_find(const char *name, cpio_entry_t *entry) {
|
|
if (!archive) return -1;
|
|
|
|
uint32_t off = 0;
|
|
while (1) {
|
|
uint32_t next = parse_entry(off, entry);
|
|
if (next == 0) return -1;
|
|
if (strcmp(entry->name, CPIO_TRAILER) == 0) return -1;
|
|
|
|
/* Match by name. CPIO entries often have "./" prefix, try both. */
|
|
if (strcmp(entry->name, name) == 0) return 0;
|
|
|
|
/* Try matching without "./" prefix */
|
|
if (entry->name[0] == '.' && entry->name[1] == '/' &&
|
|
strcmp(entry->name + 2, name) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* Try matching with "./" prefix */
|
|
if (name[0] != '.' && entry->namesize > 2) {
|
|
/* Already handled above */
|
|
}
|
|
|
|
off = next;
|
|
}
|
|
}
|
|
|
|
int cpio_next(uint32_t *offset, cpio_entry_t *entry) {
|
|
if (!archive) return -1;
|
|
|
|
uint32_t next = parse_entry(*offset, entry);
|
|
if (next == 0) return -1;
|
|
if (strcmp(entry->name, CPIO_TRAILER) == 0) return -1;
|
|
|
|
*offset = next;
|
|
return 0;
|
|
}
|
|
|
|
uint32_t cpio_count(void) {
|
|
if (!archive) return 0;
|
|
|
|
uint32_t count = 0;
|
|
uint32_t off = 0;
|
|
cpio_entry_t entry;
|
|
while (1) {
|
|
uint32_t next = parse_entry(off, &entry);
|
|
if (next == 0) break;
|
|
if (strcmp(entry.name, CPIO_TRAILER) == 0) break;
|
|
count++;
|
|
off = next;
|
|
}
|
|
return count;
|
|
}
|