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
}
}