guests: add linux-aarch64 guest images and scripts

- initramfs.img, rootfs.img for AArch64 Linux guest
- mkdisk.py for disk image creation
- initramfs and rootfs directories
This commit is contained in:
Fábio Coutada 2026-07-15 15:32:04 +01:00
parent 4960a14096
commit 5ab1b438b4
7 changed files with 426 additions and 0 deletions

Binary file not shown.

View file

@ -0,0 +1,62 @@
#!/bin/bash
# Build a minimal initramfs for UniversalisOS Linux guest.
# Cross-compiles init.c for AArch64 and creates a cpio archive.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
OUTPUT_DIR="$SCRIPT_DIR/.."
INITRAMFS_DIR="$SCRIPT_DIR/rootfs"
# Cross-compiler
CROSS=aarch64-linux-gnu
CC=${CROSS}-gcc
OBJCOPY=${CROSS}-objcopy
STRIP=${CROSS}-strip
echo "=== Building minimal initramfs ==="
# Create rootfs directory structure
rm -rf "$INITRAMFS_DIR"
mkdir -p "$INITRAMFS_DIR"/{bin,dev,proc,sys,etc,sbin}
# Compile init.c as static binary
echo "Compiling init.c..."
${CC} -static -O2 -o "$INITRAMFS_DIR/init" "$SCRIPT_DIR/init.c"
${STRIP} "$INITRAMFS_DIR/init"
echo "init binary: $(file "$INITRAMFS_DIR/init")"
# Create a minimal /etc/init.d/rcS
cat > "$INITRAMFS_DIR/etc/init.d/rcS" << 'EOF'
#!/bin/sh
echo "Running rcS..."
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t tmpfs tmpfs /dev
exec /init
EOF
chmod +x "$INITRAMFS_DIR/etc/init.d/rcS"
# Create a minimal /etc/fstab
cat > "$INITRAMFS_DIR/etc/fstab" << 'EOF'
# <filesystem> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
EOF
# Create cpio archive
echo "Creating cpio archive..."
cd "$INITRAMFS_DIR"
find . -print0 | cpio --null -o --format=newc > "$OUTPUT_DIR/initramfs.cpio"
cd "$SCRIPT_DIR"
# Create gzip-compressed version
echo "Compressing..."
gzip -9 -f "$OUTPUT_DIR/initramfs.cpio"
mv "$OUTPUT_DIR/initramfs.cpio.gz" "$OUTPUT_DIR/initramfs.img"
echo "=== Initramfs built ==="
echo "Output: $OUTPUT_DIR/initramfs.img"
echo "Size: $(du -h "$OUTPUT_DIR/initramfs.img" | cut -f1)"
ls -la "$OUTPUT_DIR/initramfs.img"

View file

@ -0,0 +1,144 @@
/*
* Minimal /init for UniversalisOS initramfs.
*
* This is the first userspace program executed by Linux after mounting
* the rootfs. It demonstrates that the hypervisor can boot a real Linux
* kernel to userspace via virtio-blk.
*
* Features:
* - Mounts /proc, /sys, /dev (tmpfs)
* - Prints system information from /proc/cpuinfo
* - Runs a minimal shell loop (echo + wait)
* - Demonstrates the full hypervisor -> Linux -> userspace path
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/reboot.h>
#include <linux/reboot.h>
#define BANNER \
"\r\n" \
"============================================\r\n" \
" UniversalisOS Linux Guest Boot Successful!\r\n" \
"============================================\r\n" \
"\r\n"
static void mount_proc(void) {
mkdir("/proc", 0755);
if (mount("proc", "/proc", "proc", 0, NULL) == 0) {
printf("[init] /proc mounted\r\n");
} else {
printf("[init] /proc mount failed\r\n");
}
}
static void mount_sys(void) {
mkdir("/sys", 0755);
if (mount("sysfs", "/sys", "sysfs", 0, NULL) == 0) {
printf("[init] /sys mounted\r\n");
} else {
printf("[init] /sys mount failed\r\n");
}
}
static void mount_dev(void) {
mkdir("/dev", 0755);
if (mount("tmpfs", "/dev", "tmpfs", 0, "size=64M") == 0) {
printf("[init] /dev mounted (tmpfs)\r\n");
} else {
printf("[init] /dev mount failed\r\n");
}
}
static void print_cpuinfo(void) {
FILE *f = fopen("/proc/cpuinfo", "r");
if (f) {
char line[256];
printf("[init] CPU info:\r\n");
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "Processor", 9) == 0 ||
strncmp(line, "model name", 10) == 0 ||
strncmp(line, "CPU implementer", 14) == 0 ||
strncmp(line, "CPU variant", 10) == 0 ||
strncmp(line, "BogoMIPS", 8) == 0) {
printf(" %s", line);
}
}
fclose(f);
}
}
static void print_uptime(void) {
FILE *f = fopen("/proc/uptime", "r");
if (f) {
double uptime;
if (fscanf(f, "%lf", &uptime) == 1) {
printf("[init] System uptime: %.1f seconds\r\n", uptime);
}
fclose(f);
}
}
static void print_memory(void) {
FILE *f = fopen("/proc/meminfo", "r");
if (f) {
char line[256];
printf("[init] Memory info:\r\n");
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "MemTotal", 8) == 0 ||
strncmp(line, "MemFree", 7) == 0 ||
strncmp(line, "MemAvailable", 12) == 0) {
printf(" %s", line);
}
}
fclose(f);
}
}
int main(void) {
printf(BANNER);
printf("[init] UniversalisOS Linux Guest Init\r\n");
printf("[init] PID=%d\r\n", getpid());
/* Mount essential filesystems */
mount_dev();
mount_proc();
mount_sys();
/* Create basic device nodes */
mknod("/dev/console", 0600 | S_IFCHR, makedev(5, 1));
mknod("/dev/null", 0666 | S_IFCHR, makedev(1, 3));
mknod("/dev/zero", 0666 | S_IFCHR, makedev(1, 5));
/* Print system information */
print_cpuinfo();
print_uptime();
print_memory();
/* Print mount info */
FILE *f = fopen("/proc/mounts", "r");
if (f) {
char line[256];
printf("[init] Mounts:\r\n");
while (fgets(line, sizeof(line), f)) {
printf(" %s", line);
}
fclose(f);
}
printf("\r\n[init] System ready. Entering idle loop.\r\n");
printf("[init] To shutdown: echo 1 > /proc/sys/kernel/sysrq && echo o > /proc/sysrq-trigger\r\n");
/* Idle loop — in a real system this would run a shell or service manager */
while (1) {
sleep(10);
printf("[init] heartbeat\r\n");
}
return 0;
}

Binary file not shown.

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""
Generate a virtio-blk disk image for UniversalisOS Linux guest.
Creates a raw disk image with the UOSVBLK1 header format that
virtio-blk can detect and serve to the guest.
Usage:
python3 mkdisk.py <input_file> <output.img> [--size=SIZE]
The output image format:
Offset 0x000: "UOSVBLK1" magic (8 bytes)
Offset 0x008: data size in bytes (8 bytes, little-endian)
Offset 0x010: raw sector data (512-byte aligned)
"""
import sys
import os
import struct
MAGIC = b"UOSVBLK1"
SECTOR_SIZE = 512
def create_disk_image(input_path, output_path, target_size=None):
"""Create a virtio-blk disk image from an input file."""
# Read input file
with open(input_path, 'rb') as f:
data = f.read()
# Pad to sector boundary
if len(data) % SECTOR_SIZE != 0:
data += b'\x00' * (SECTOR_SIZE - (len(data) % SECTOR_SIZE))
# Pad to target size if specified
if target_size and target_size > len(data):
data += b'\x00' * (target_size - len(data))
# Create image with header
header = MAGIC + struct.pack('<Q', len(data)) # magic + size (LE 64-bit)
# Pad header to sector boundary
if len(header) % SECTOR_SIZE != 0:
header += b'\x00' * (SECTOR_SIZE - len(header))
image = header + data
# Write output
with open(output_path, 'wb') as f:
f.write(image)
print(f"Disk image created: {output_path}")
print(f" Input: {input_path} ({len(data)} bytes)")
print(f" Header: {len(header)} bytes")
print(f" Total: {len(image)} bytes ({len(image) // 1024} KB)")
print(f" Sectors: {len(image) // SECTOR_SIZE}")
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <input> <output.img> [--size=SIZE]")
print(f" input: input file (initramfs, rootfs, etc.)")
print(f" output: output disk image")
print(f" --size: target size in bytes (optional, pads with zeros)")
sys.exit(1)
input_path = sys.argv[1]
output_path = sys.argv[2]
target_size = None
for arg in sys.argv[3:]:
if arg.startswith('--size='):
target_size = int(arg.split('=')[1])
if not os.path.exists(input_path):
print(f"Error: input file not found: {input_path}")
sys.exit(1)
create_disk_image(input_path, output_path, target_size)
if __name__ == '__main__':
main()

Binary file not shown.

View file

@ -0,0 +1,140 @@
# UniversalisOS Linux Guest Rootfs Generation
#
# Targets:
# make minimal - Build minimal initramfs (init only, ~50KB)
# make busybox - Build busybox-based initramfs (~1MB)
# make full - Build full rootfs from yocto/buildroot (~50MB)
# make disk - Create virtio-blk disk image
# make all - Build minimal + disk image
# make clean - Remove all build artifacts
#
# Prerequisites:
# - aarch64-linux-gnu-gcc (cross-compiler)
# - cpio, gzip (for initramfs)
# - python3 (for mkdisk.py)
# Configuration
CROSS_COMPILE ?= aarch64-linux-gnu-
CC = $(CROSS_COMPILE)gcc
STRIP = $(CROSS_COMPILE)strip
# Directories
ROOTFS_DIR = $(CURDIR)
OUTPUT_DIR = $(ROOTFS_DIR)/output
BUILD_DIR = $(ROOTFS_DIR)/build
INITRAMFS_DIR = $(BUILD_DIR)/initramfs
# Output files
INITRAMFS_IMG = $(OUTPUT_DIR)/initramfs.img
DISK_IMG = $(OUTPUT_DIR)/rootfs.img
# Disk image size (256 MiB for full rootfs)
DISK_SIZE = 268435456
# Busybox configuration
BUSYBOX_VERSION = 1.36.1
BUSYBOX_URL = https://busybox.net/downloads/busybox-$(BUSYBOX_VERSION).tar.bz2
# ===== Minimal initramfs (init only) =====
.PHONY: minimal
minimal: $(INITRAMFS_IMG)
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(INITRAMFS_DIR): $(BUILD_DIR)
mkdir -p $(INITRAMFS_DIR)/{bin,dev,proc,sys,etc,sbin}
$(INITRAMFS_DIR)/init: $(INITRAMFS_DIR) $(ROOTFS_DIR)/initramfs/init.c
$(CC) -static -O2 -o $@ $(ROOTFS_DIR)/initramfs/init.c
$(STRIP) $@
$(INITRAMFS_DIR)/etc/init.d/rcS: $(INITRAMFS_DIR)
mkdir -p $(INITRAMFS_DIR)/etc/init.d
@echo '#!/bin/sh' > $@
@echo 'mount -t proc proc /proc' >> $@
@echo 'mount -t sysfs sysfs /sys' >> $@
@echo 'mount -t tmpfs tmpfs /dev' >> $@
@chmod +x $@
$(INITRAMFS_DIR)/etc/fstab: $(INITRAMFS_DIR)
@echo '# <filesystem> <mount> <type> <opts> <dump> <pass>' > $@
@echo 'proc /proc proc defaults 0 0' >> $@
@echo 'sysfs /sys sysfs defaults 0 0' >> $@
@echo 'tmpfs /dev tmpfs defaults 0 0' >> $@
$(INITRAMFS_IMG): $(INITRAMFS_DIR)/init $(INITRAMFS_DIR)/etc/init.d/rcS $(INITRAMFS_DIR)/etc/fstab
cd $(INITRAMFS_DIR) && find . -print0 | cpio --null -o --format=newc 2>/dev/null | gzip -9 > $@
@echo "Initramfs: $$(du -h $@ | cut -f1)"
# ===== Busybox initramfs =====
.PHONY: busybox
busybox: $(OUTPUT_DIR)/busybox-initramfs.img
$(BUILD_DIR)/busybox-$(BUSYBOX_VERSION): $(BUILD_DIR)
cd $(BUILD_DIR) && curl -L $(BUSYBOX_URL) | tar xj
cd $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION) && make ARCH=aarch64 CROSS_COMPILE=$(CROSS_COMPILE) defconfig
cd $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION) && sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
cd $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION) && make ARCH=aarch64 CROSS_COMPILE=$(CROSS_COMPILE) -j$$(nproc)
$(BUILD_DIR)/busybox-rootfs: $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION)
mkdir -p $@/{bin,dev,proc,sys,etc,sbin,tmp,root}
cp $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION)/busybox $@/bin/busybox
cd $@/bin && for cmd in sh ls cat echo mount mkdir mknod sleep reboot; do ln -sf busybox $$cmd; done
cd $@/sbin && ln -sf ../bin/busybox init
@echo '#!/bin/sh' > $@/etc/init.d/rcS
@echo 'mount -t proc proc /proc' >> $@/etc/init.d/rcS
@echo 'mount -t sysfs sysfs /sys' >> $@/etc/init.d/rcS
@echo 'mount -t tmpfs tmpfs /dev' >> $@/etc/init.d/rcS
@echo 'mount -t devpts devpts /dev/pts' >> $@/etc/init.d/rcS
@chmod +x $@/etc/init.d/rcS
$(OUTPUT_DIR)/busybox-initramfs.img: $(BUILD_DIR)/busybox-rootfs $(OUTPUT_DIR)
cd $(BUILD_DIR)/busybox-rootfs && find . -print0 | cpio --null -o --format=newc 2>/dev/null | gzip -9 > $@
@echo "Busybox initramfs: $$(du -h $@ | cut -f1)"
# ===== Full rootfs (yocto/buildroot placeholder) =====
.PHONY: full
full: $(OUTPUT_DIR)/full-rootfs.img
$(OUTPUT_DIR)/full-rootfs.img: $(OUTPUT_DIR)
@echo "=== Full rootfs generation ==="
@echo "This target requires yocto or buildroot to be configured."
@echo "To use:"
@echo " 1. Configure yocto/buildroot for aarch64"
@echo " 2. Build the rootfs"
@echo " 3. Copy the output to $(OUTPUT_DIR)/full-rootfs/"
@echo " 4. Run: make disk INPUT=$(OUTPUT_DIR)/full-rootfs/"
@echo ""
@echo "For now, using minimal initramfs as fallback."
$(MAKE) minimal
cp $(INITRAMFS_IMG) $@
# ===== Disk image generation =====
.PHONY: disk
disk: $(DISK_IMG)
$(DISK_IMG): $(INITRAMFS_IMG)
python3 ../mkdisk.py $(INITRAMFS_IMG) $@ --size=$(DISK_SIZE)
@echo "Disk image: $$(du -h $@ | cut -f1)"
# ===== All =====
.PHONY: all
all: minimal disk
@echo "=== Build complete ==="
@echo "Initramfs: $(INITRAMFS_IMG)"
@echo "Disk image: $(DISK_IMG)"
# ===== Clean =====
.PHONY: clean
clean:
rm -rf $(BUILD_DIR) $(OUTPUT_DIR)