// 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/`; 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 } }