// 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 "?" } // " 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] } // " KiB / 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 m s", collapsing to "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 }