Flash 215 lines
// sysinfo — one-shot system summary for /bin/sysinfo.
//
// The first consumer of the console_ui screen-layer kv() renderer and a proof
// that FlashOS's full-screen navigation scaffold is wired end to end: a
// print-and-exit coreutil that lays the available system
// facts out as aligned key/value rows. It shows only what the kernel can answer
// today — the FlashOS version (build_options, single-sourced from build.zig.zon),
// the logged-in user (getuid -> /etc/passwd via the shared pwfile parser), the
// free-page count (sys_dump_free), and the hardware-monitoring metrics: memory
// use and uptime (board-independent), plus CPU temperature and clock from the
// VideoCore mailbox. temp / freq read 0 = unknown on a board without the
// mailbox (virt) and render "n/a" — sysinfo never fabricates a reading.
//
// Print-and-exit, so it needs neither the alt-screen buffer nor readKey (those
// serve the future live /bin/mon). Like meminfo it is kept out of the CI
// FSH_SCRIPT: the free-page value is non-deterministic and would break the
// baseline checkpoint count. Same coreutil recipe as ls / dmesg (flibc _start
// shim, flibc_mem, single R+X PT_LOAD, stack buffers only — rule 1).
use flibc
use pwfile
use console_ui
use build_options
link "flibc_start"
link "flibc_mem"
const PASSWD_MAX usize = 512
fn sink(bytes []u8) void {
_ = flibc.sys.write_fd(1, bytes.ptr, bytes.len)
}
export fn main(_ usize, _ argv) noreturn {
console_ui.banner(sink, "FlashOS system")
console_ui.screen.kv(sink, "version", build_options.version)
// user: getuid -> /etc/passwd via pwfile; the passwd slurp buffer is on
// this frame so the returned login-name slice stays valid for kv().
var pw_buf [PASSWD_MAX]u8 = undefined
console_ui.screen.kv(sink, "user", currentUser(&pw_buf))
// free: the live kernel free-page count, formatted into a stack buffer.
var num_buf [32]u8 = undefined
console_ui.screen.kv(sink, "free", freePages(&num_buf))
// hardware-monitoring rows. One scratch buffer reused across the four: kv
// consumes each value before the next formatter overwrites it. temp / freq
// print "n/a" when the syscall returns 0 (unknown).
var fmt_buf [48]u8 = undefined
console_ui.screen.kv(sink, "mem", memUsage(&fmt_buf))
console_ui.screen.kv(sink, "uptime", uptimeStr(&fmt_buf))
console_ui.screen.kv(sink, "temp", tempStr(&fmt_buf))
console_ui.screen.kv(sink, "freq", freqStr(&fmt_buf))
flibc.exit()
}
// Resolve the real uid's login name into a slice backed by `buf`. Returns "?"
// when the uid can't be read, /etc/passwd is unreadable, or the uid has no
// entry — the kv renderer wants a value and a numeric fallback would need a
// formatter the proof tool does not warrant.
fn currentUser(buf []mut u8) []u8 {
uid_raw := flibc.sys.getuid()
if uid_raw < 0 {
return "?"
}
uid := #as(u32, #intCast(uid_raw))
fd := flibc.sys.open("/etc/passwd")
if fd < 0 {
return "?"
}
var n usize = 0
while n < buf.len {
r := flibc.sys.read(fd, buf[n..].ptr, buf.len - n)
if r <= 0 {
break
}
n += #intCast(r)
}
_ = flibc.sys.close(fd)
if pwfile.lookupByUid(buf[0..n], uid) |entry| {
return entry.user
}
return "?"
}
// "<count> pages", the count formatted decimal into `buf`.
fn freePages(buf []mut u8) []u8 {
var i usize = u64dec(buf, flibc.sys.dump_free())
suffix := " pages"
for c in suffix {
buf[i] = c
i += 1
}
return buf[0..i]
}
// "<used> KiB / <total> MiB": the pages currently in use (mem_total -
// free) against the frozen allocatable pool. Used is rendered in KiB
// (<<2 — 4 KiB pages), not MiB: an idle system's live footprint is tens
// of pages, and a >>8 MiB conversion floors that to a meaningless 0.
// Total stays in MiB (the pool is GiB-scale). The two reads are separate
// syscalls, so a concurrent allocation could skew "used" by a page —
// harmless for a one-shot summary.
fn memUsage(buf []mut u8) []u8 {
total_pages := flibc.sys.mem_total()
free_pages := flibc.sys.dump_free()
var used_pages u64 = 0
if total_pages > free_pages {
used_pages = total_pages - free_pages
}
var i usize = u64dec(buf, used_pages << 2)
i += appendStr(buf[i..], " KiB / ")
i += u64dec(buf[i..], total_pages >> 8)
i += appendStr(buf[i..], " MiB")
return buf[0..i]
}
// Copy `s` into the front of `out`, returning the byte count.
fn appendStr(out []mut u8, s []u8) usize {
var i usize = 0
for c in s {
out[i] = c
i += 1
}
return i
}
// Seconds since boot, humanised: "<h>h <m>m <s>s", collapsing to "<s>s"
// under a minute. uptime() is monotonic across reads.
fn uptimeStr(buf []mut u8) []u8 {
secs := flibc.sys.uptime()
h := secs / 3600
m := (secs % 3600) / 60
s := secs % 60
var i usize = 0
if h > 0 {
i += u64dec(buf[i..], h)
buf[i] = 'h'
i += 1
buf[i] = ' '
i += 1
}
if h > 0 || m > 0 {
i += u64dec(buf[i..], m)
buf[i] = 'm'
i += 1
buf[i] = ' '
i += 1
}
i += u64dec(buf[i..], s)
buf[i] = 's'
i += 1
return buf[0..i]
}
// SoC temperature in whole degrees Celsius, or "n/a" when unknown (0 —
// virt's stub, or a mailbox timeout on real hardware). cpu_temp() is
// milli-degrees. ASCII "C" keeps every byte single-width on any console.
fn tempStr(buf []mut u8) []u8 {
milli := flibc.sys.cpu_temp()
if milli == 0 {
return "n/a"
}
var i usize = u64dec(buf, milli / 1000)
suffix := " C"
for c in suffix {
buf[i] = c
i += 1
}
return buf[0..i]
}
// ARM core clock in MHz, or "n/a" when unknown (0). cpu_freq() is Hz.
fn freqStr(buf []mut u8) []u8 {
hz := flibc.sys.cpu_freq()
if hz == 0 {
return "n/a"
}
var i usize = u64dec(buf, hz / 1_000_000)
suffix := " MHz"
for c in suffix {
buf[i] = c
i += 1
}
return buf[0..i]
}
// Write `v` as decimal ASCII into `out` (>= 20 bytes for the u64 max),
// returning the byte count.
fn u64dec(out []mut u8, v u64) usize {
if v == 0 {
out[0] = '0'
return 1
}
var tmp [20]u8 = undefined
var n usize = 0
var x u64 = v
while x != 0 {
tmp[n] = '0' + #as(u8, #intCast(x % 10))
n += 1
x /= 10
}
var i usize = 0
while i < n {
out[i] = tmp[n - 1 - i]
i += 1
}
return n
}