ajhahn.de
← FlashOS
Flash 75 lines
// PID 1 ELF root.
//
// Built as a standalone aarch64-freestanding ET_EXEC (`pid1.elf`),
// staged into the initramfs at `/sbin/init`. The kernel's
// `kernel_process` locates that entry and hands its bytes to
// `prepare_move_to_user_elf` — the same ELF path the exec-elf /
// stackbomb / flibc test payloads already travel.
//
// The loader honours `e_entry` + `p_vaddr`, so section placement is the
// linker script's job (`tools/pid1_linker.ld`, single PT_LOAD at
// 0x100000) — no manual section pinning.

use "kernel_tests" as tests
use build_options

const OK = "[ \x1b[32mOK\x1b[0m ] "
const PID1_MSG [*:0]u8 = OK ++ "Reached target Userspace\n"
const LOGIN_PATH [*:0]u8 = "/bin/login"

// CI credential script. When the `ci-login-seed` build flag is set,
// PID-1 injects these bytes into the console RX ring before exec'ing
// /bin/login, so the real login path authenticates unattended under the boot
// watchdog (no interactive typist on QEMU, which feeds `</dev/null`). Must
// match an /etc/passwd + /etc/shadow account (see tools/gen_shadow.zig);
// `flash`/`flash` exercises the privilege drop to uid 1000. The flag is OFF
// by default and `zig build deploy` omits it, so a hardware boot does NOT
// seed — it stops at the `login:` prompt and demands a real password.
const LOGIN_SCRIPT []u8 = "flash\nflash\n"

// ELF entry. Naked: the loader zeroes x0..x30, sets SP = STACK_TOP
// (top stack page eagerly mapped) and jumps here. `bl pid1_main`
// gives `pid1_main` a normal prologue against that stack; the
// trailing `mov x8,#2 ; svc #0` (SYS_EXIT) is the fallback for an
// unexpected `pid1_main` return.
export fn _start() callconv(.naked) noreturn {
    asm volatile (
        \\bl pid1_main
        \\mov x8, #2
        \\svc #0
    )
}

export fn pid1_main() noreturn {
    tests.sys_writeConsole(PID1_MSG)
    // The in-kernel [TEST] harness is the boot-as-test path the QEMU
    // watchdog asserts (28 scenarios + 32 free-page checkpoints). Gated so
    // deploy/run boot clean straight to login; -Dboot-selftest=true
    // (CI/validation) runs it, the default skips it.
    if build_options.boot_selftest {
        result := tests.run_all()
        tests.print_tally(result.passed, result.total)
    }
    // Hand PID 1 to /bin/login. login authenticates against
    // /etc/shadow, drops privilege per /etc/passwd, then execs the user's
    // shell — fsh prints its homescreen line at REPL entry (the stable
    // `type 'help' for commands` tail), the boot-success marker the watchdog
    // waits for. To keep the unattended
    // QEMU boot reaching that marker (no interactive typist), inject the CI
    // credentials into the console RX ring first via SYS_CONSOLE_INJECT so
    // login's fd-0 reads drain them; inject-before-exec closes the empty-ring
    // race the way the retired in-harness fsh capstone did. Neither login nor
    // fsh emits sys_dump_free, so the free-page checkpoint count stays
    // deterministic. Gated on the ci-login-seed flag: a hardware deploy omits
    // it and the boot stops at the real `login:` prompt. sys_execve only
    // returns on failure — fall through to sys_exit then.
    if build_options.ci_login_seed {
        for b in LOGIN_SCRIPT {
            tests.sys_console_inject(b)
        }
    }
    const argv = [_:null]?[*:0]u8{ LOGIN_PATH }
    _ = tests.sys_execve(#intFromPtr(LOGIN_PATH), #intFromPtr(&argv))
    tests.sys_exit()
}