ajhahn.de
← Flash
Flash 100 lines
// execvp — flibc's bare-name program resolver, ported to Flash from its
// hand-written Zig (the smallest real-code flibc leaf, after the re-export hub).
//
// Linux's execvp consults $PATH; FlashOS has no environment yet, so a single
// search prefix is hard-wired: a bare name `foo` resolves to `/bin/foo`. A name
// that already contains a slash (absolute `/usr/bin/foo` or relative
// `tools/foo`) skips the prefix and is handed to `sys.exec_path` verbatim — the
// kernel joins relative paths against the task's `cwd`.
//
// First port to exercise the driver-select grammar end to end: a comptime gate
// `const has_driver = builtin.cpu.arch == .aarch64 && …` selects between the two
// `struct { … }` arms of an `if`-expression (`const driver = if (has_driver)
// struct {…} else struct {…}`), so the host build picks the empty stub and the
// SVC-driving real `execvp` (its aarch64-freestanding `sys.exec_path` call) is
// never analysed off-target. It also lands the first sentinel-terminated *slice*
// type (`?[:0]mut u8`, the `resolve` return). The SVC drivers' bodies and the
// bottom-of-file host `test` blocks are dropped, as the prior leaves dropped
// theirs; the core lowers to Zig whose token stream matches the reference.

use builtin

const has_driver = builtin.cpu.arch == .aarch64 && builtin.target.os.tag == .freestanding

/// Maximum resolved path length the driver hands to sys.exec_path. Sized
/// to match the kernel's `cwd` budget (CWD_SIZE = 256) — the kernel can
/// already handle paths up to that ceiling, so widening here would only
/// invite a later kernel-side rejection.
pub const PATH_MAX usize = 256

const BIN_PREFIX = "/bin/"

/// Resolve a program name into an absolute (or already-slashed) path
/// laid out in `out`. Returns a sentinel-terminated slice into `out`
/// suitable for `sys.exec_path`. Rules:
///   * empty `name`           → null
///   * `name` contains '/'    → copy verbatim + NUL into `out` (lets
///                              the kernel handle absolute / relative
///                              resolution against `cwd`)
///   * bare `name`            → `/bin/` + name + NUL
///   * `out` too small for    → null (caller gets -1 rather than a
///     prefix + name + NUL      silently truncated binary path)
///
/// Pure: no syscalls, no allocator. Exercised in isolation by the host
/// suite — see the `test` blocks at the bottom of this file.
pub fn resolve(name []u8, out []mut u8) ?[:0]mut u8 {
    if name.len == 0 {
        return null
    }

    var has_slash = false
    for c in name {
        if c == '/' {
            has_slash = true
            break
        }
    }

    if has_slash {
        if name.len + 1 > out.len {
            return null
        }
        #memcpy(out[0..name.len], name)
        out[name.len] = 0
        return out[0..name.len :0]
    }

    total := BIN_PREFIX.len + name.len
    if total + 1 > out.len {
        return null
    }
    #memcpy(out[0..BIN_PREFIX.len], BIN_PREFIX)
    #memcpy(out[BIN_PREFIX.len..][0..name.len], name)
    out[total] = 0
    return out[0..total :0]
}

/// Resolve `name` (bare → `/bin/<name>`; slashed → verbatim) and exec
/// the result. Returns -1 on resolve failure (empty / oversize) or
/// whatever `sys.exec_path` returns; on success the syscall does not
/// return.
pub const execvp = driver.execvp

const driver = if (has_driver) struct {
    use "syscalls" as sys

    pub fn execvp(name cstr, argv argv) i32 {
        var path_buf [PATH_MAX]u8 = undefined
        var n usize = 0
        while name[n] != 0 {
            n += 1
        }
        resolved := resolve(name[0..n], &path_buf) orelse return -1
        return sys.exec_path(#ptrCast(resolved.ptr), argv)
    }
} else struct {
    pub fn execvp(_ cstr, _ argv) i32 {
        return -1
    }
}