Markdown 383 lines
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/flashos_logo_dark.png">
<img src="assets/flashos_logo_light.png" alt="FlashOS" width="280">
</picture>
<h1>Changelog</h1>
<p><i>All notable changes to FlashOS, release by release.</i></p>
<p>
<a href="README.md"><b>README</b></a> ·
<a href="DOCUMENTATION.md"><b>Documentation</b></a> ·
<a href="SETUP.md"><b>Setup</b></a> ·
<a href="PORT.md"><b>Port</b></a> ·
<a href="VERSIONING.md"><b>Versioning</b></a> ·
<b>Changelog</b> ·
<a href="LICENSE.md"><b>License</b></a>
</p>
</div>
---
All notable changes to FlashOS are recorded in this file. The format is
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
(see [VERSIONING.md](VERSIONING.md)). Per-tag notes also appear on the
[releases page](https://github.com/ajhahnde/FlashOS/releases).
## [Unreleased]
## [v0.7.0] - 2026-06-18
### Added
- **`/bin/edit` — a full-screen text editor.** `edit <file>` slurps a file
into a heap-backed gap buffer, takes over the console with the alternate
screen + raw mode, and edits it in place: arrows / Home / End / PgUp / PgDn
navigate, printable keys insert, Backspace / Delete remove, Enter splits the
line, `ctrl-O` writes, `ctrl-W` searches forward from the cursor, and
`ctrl-X` exits (prompting to save a modified buffer). It is the **first real
consumer of the userland heap** — the `brk` / `sbrk` syscalls behind flibc's
bump `malloc`, which already existed — so it adds **no new syscall**
(`NR_SYSCALLS` holds) and **no kernel change**; it is pure userland. Save is
**unlink + create + write** rather than in-place, because the FAT32 backend's
`write` only grows `file_size` (there is no truncate): recreating the file
yields the correct, possibly smaller, size every time. The editing logic is
three new pure, host-tested flibc cores — `gapbuf.GapBuf` (storage),
`gapbuf.LineIndex` (lines + cursor motions), `gapbuf.Viewport` (scroll) —
plus a reused `grep_match.find` for search; the `keys` VT100 decoder gained
Delete / Home / End / PgUp / PgDn and the `ctrl-O/W/X` chords. Like
`/bin/less` it is interactive, so its edit loop is validated on real Pi
hardware and kept out of the CI boot script. Limits (deferred): one
logical line per screen row (horizontal scroll, no soft-wrap), no undo, tabs
shown as a single space, fixed 24×80 geometry.
## [v0.6.0] - 2026-06-18
### Added
- **FAT32 file create / unlink / rename and the `cp` / `mv` / `rm`
coreutils.** Three new syscalls complete the FAT32 write path:
`create` (slot 53) makes a new empty file and returns a writable fd —
the create-then-write half the flag-less `open` ABI lacked — `unlink`
(54) tombstones a file's directory entry and frees its cluster chain,
and `rename` (55) rewrites an 8.3 name in place within the same
directory. They are built on five new pure `fat32` primitives
(`findFreeDirSlot`, which extends a full directory by a cluster;
`writeDirEntry`; `markDeleted`; `freeChain`; `fsInfoOnFree`) and three
appended VFS vtable slots with EROFS defaults, so the read-only
initramfs and any future mount stay non-destructive. Three new
`/bin` coreutils consume them: `cp` (open + create + copy), `rm`
(unlink each argument), and `mv` (same-directory `rename`, with a
copy+unlink fallback across directories). Files only — `mkdir` /
`rmdir`, sub-directory writes, and cross-directory rename are deferred.
Created files are caller-owned (the permission metadata is a
per-session value that does not persist across reboot). On-device
source files use the 8.3-safe `.fl` extension (`.flash` does not fit an
8.3 short name); there is no LFN.
- **`/bin/grep`.** A literal-pattern line search — `grep [-i] PATTERN
[FILE...]`, reading stdin when given no file — over a pure, host-tested
substring matcher with optional ASCII case folding. No regex, no
new syscall.
- **`[TEST] fs-roundtrip` now exercises the full create/unlink/rename
lifecycle.** On a mounted (real-Pi) boot the scenario folds in a CRUD
leg — create, write, read back, rename, unlink, verify-gone — that
leaves the disk unchanged, so it adds no new scenario and the boot
contract stays at 30 EL0 scenarios / 34 checkpoints. FAT32 remains
Pi-only (QEMU's EMMC2 does not pass CMD8), so the logic is covered by
host tests and the lifecycle is validated on real Pi-4 hardware.
## [v0.5.0] - 2026-06-17
### Added
- **Hardware-monitoring syscalls and `/bin/cpuinfo`.** Four new
argument-free syscalls expose live system metrics to user space:
`mem_total` (slot 49, the allocatable pool size in pages), `uptime`
(50, seconds since boot from the architectural counter), `cpu_temp`
(51, SoC temperature in milli-degrees Celsius) and `cpu_freq` (52, the
ARM core clock in Hz). Temperature and clock are read over the VideoCore
mailbox (a new `TAG_GET_TEMPERATURE` property tag) and return `0` =
unknown on a board without the firmware (QEMU virt) or on a mailbox
timeout, which the tools render as `n/a` — never a fabricated value. Two
new coreutils join the existing `/bin/meminfo`: `/bin/cpuinfo` prints the
temperature and clock, and `/bin/uptime` prints the time since boot as
`Nm Ns`. `/bin/sysinfo` gains `mem` (used/total, the used figure in KiB
so a small footprint no longer floors to `0 MiB`), `uptime`, `temp` and
`freq` rows. Two in-kernel `[TEST]` scenarios (`hwmon-core`,
`hwmon-mailbox`) cover the new syscalls, moving the boot contract to 30
EL0 scenarios and 34 per-scenario checkpoints.
### Fixed
- **VideoCore mailbox reads no longer match a stale reply.** The doorbell
word the kernel posts to the property channel is identical on every call
(a fixed property-buffer address OR-ed with a fixed channel), so the
completion check could not tell a fresh reply from the leftover of the
previous transaction. Two back-to-back reads — as `cpuinfo` and
`sysinfo` issue for temperature then clock — let the second read consume
the first read's stale FIFO entry and parse the property buffer before
VideoCore had refreshed it, surfacing as the clock flapping between its
real value and `n/a`. `transact()` now drains the read FIFO before
posting and brackets the doorbell exchange with a `dsb sy` barrier, so
each read observes only its own reply; verified on real hardware with
three consecutive `cpuinfo` runs holding a stable `600 MHz`.
- **`/bin/login` now refuses to run as a non-root command.** Invoked from
an already-privilege-dropped shell, `login` would still authenticate the
entered credentials — the kernel verifier does not gate on the caller's
uid — and only then fail the privilege drop with a misleading `login:
cannot drop privilege`, a confusing half-success that could never grant a
higher uid since `setuid` is one-way. `login` now checks `geteuid()` at
entry and exits with `login: must be root`, leaving session minting to the
PID-1 supervisor. Switch users by logging out back to that supervisor,
then logging in as the other account.
## [v0.4.0] - 2026-06-13
### Changed
- **The OS-image source is now written in
[Flash](https://github.com/ajhahnde/Flash).** The kernel core, board
drivers, user space, and the in-kernel test harness were ported from
Zig to Flash — a self-hosted systems language that transpiles to Zig —
and now carry the `.flash` extension. `flashc` lowers them to Zig at
build time, so the kernel image is behaviourally identical to the
pre-port build: both boot watchdogs assert the same 28-scenario /
32-checkpoint contract and the same per-board free-page checkpoints,
with no re-capture across the port. See [PORT.md](PORT.md) for the full
lineage.
- **New build dependency: the Flash compiler (`flashc`).** Builds now
transpile the `.flash` modules through `flashc`, pinned by
`flash-toolchain.lock` and resolved via `-Dflashc=<path>` (default
`~/Flash/zig-out/bin/flashc-stage1`). Build it once from the pinned
commit — see [Setup §1](SETUP.md#1-host-toolchain). The AArch64
assembly, the tracing subsystem, the host build tooling, and two kernel
modules awaiting a compiler feature stay Zig (see
[PORT.md §4](PORT.md#4-what-stays-zig)).
## [v0.3.0] - 2026-06-07
### Added
- **Password masking at the login prompt.** `/bin/login` now echoes a
`*` per typed password character instead of suppressing echo, via a new
`CONSOLE_MODE_MASK` bit on `SYS_SET_CONSOLE_MODE`; typed secrets are
acknowledged without being shown. The mode is restored to the default
(kernel echo off) before the shell starts, so the mask never leaks into
the session.
- **Shared `console_ui` terminal-look module (`lib/console_ui/`).** One
freestanding source owns the status-tag taxonomy (`[ OK ]` / `[WARN]` /
`[FAIL]` …), the ANSI palette, and the line/stage/banner renderers,
compiled into both the kernel boot log and the userspace shell through a
caller-supplied sink — the whole system restyles from a single file.
Status tags tint only the inner word, systemd-style.
- **fsh homescreen banner.** The shell prints
`FlashOS [v<version>] by ajhahnde - type 'help' for commands` at REPL
entry, with the version single-sourced from `build.zig.zon` via
`build_options` (no version literal in code).
- **TAB completion in the shell.** `fsh` completes on TAB via a new
`readlineCompleting` line-editor path: the first token against `/bin`
plus the in-process built-ins, a later token as a filesystem path; the
buffer extends to the longest common prefix and a unique match appends a
trailing space or `/`. `help` now also enumerates `/bin`, so a new tool
advertises itself by existing.
- **`/bin/sysinfo`.** A print-and-exit coreutil rendering the FlashOS
version, the logged-in user, and the free-page count as aligned
key/value rows — the first consumer of the new screen-renderer module.
- **Shared screen-renderer + input seams.** New freestanding, host-tested
modules: `lib/console_ui/screen.zig` (alternate-screen, cursor, box
panels, key/value rows), `user_space/lib/flibc/keys.zig` (VT100 input
decoder), and `user_space/lib/flibc/completion.zig` (tab-completion
core). Allocator-free, with no kernel changes.
- **`reboot` and `logout` shell built-ins.** `fsh` gains `reboot`, which
resets the board through a new `SYS_REBOOT` syscall (slot 47) — PSCI
`SYSTEM_RESET` over the HVC conduit on QEMU `virt`, the BCM2711 watchdog
full-reset on Raspberry Pi 4 — and `logout`, a synonym for `exit` that
ends the session and returns to the `login:` prompt. Both join the
shell's TAB completion and `help` listing.
- **Command history and in-line cursor editing in the shell.** `fsh`
now decodes the VT100 arrow-key sequences in its line editor
(`flibc.readlineEdit`) instead of echoing them as literal characters:
Up/Down recall earlier commands from a per-session history ring, and
Left/Right move the cursor so a keystroke inserts or backspaces at the
cursor rather than only at the end of the line. History lives in a
fixed, caller-owned ring (no allocator) and needs no new syscall.
- **`-Dtest-filter` for the host-test step.** `zig build test
-Dtest-filter=<substr>` runs only host tests whose name contains the
substring, for faster focused iteration; the default runs the full
suite.
- **`/bin/less`.** A full-screen text pager — the first interactive
consumer of the screen-renderer + input seams. It takes over the
alternate screen, draws a titled panel, and scrolls a file with the
arrow keys, `j`/`k`, space/`b` (page), and `g`/`G` (ends), quitting on
`q` and restoring the shell view. Built on a new allocator-free,
host-tested pager core (`flibc.Pager`); no new syscall.
- **`/bin/clear`.** A terminal-clear coreutil — the smallest consumer of
the shared screen renderer. It emits the `console_ui.screen.clear`
sequence (cursor home + erase) and exits, wiping the current screen in
place; the escape bytes stay single-sourced in `console_ui` rather than
hardcoded in the tool.
- **Double-TAB candidate listing in the shell.** When TAB completion is
ambiguous with nothing left to insert, a second consecutive TAB lists
every matching command or path on a fresh line and redraws the prompt,
so the choices are visible without abandoning the typed line. Built on a
new pure, host-tested `completion.classify` helper; no new syscall.
- **`pwd` shell built-in.** `fsh` gains `pwd`, which prints the current
working directory through a new `SYS_GETCWD` syscall (slot 48) — the
readback half of the existing `cd` / `SYS_CHDIR` store, copying the
per-task `cwd` out of the kernel. It joins the shell's TAB completion
and `help` listing.
### Changed
- **`help` output restructured for readability.** The shell's `help` now
lists each built-in with a one-line description in aligned columns under
section headers (`Commands:` / `Run a program:` / `Programs in /bin:`),
replacing the previous single-line blob. The `/bin` listing is still
enumerated live, so new tools keep appearing automatically.
- **Boot-success marker moved to the fsh homescreen.** The QEMU watchdog
and the `picapture` helper now key on the stable
`type 'help' for commands` homescreen tail instead of the retired
`[ OK ] Reached target Shell` / `[ OK ] Authenticated` markers. The
kernel entropy announce is reworded from
`hwrng: fallback (timer mix, weak) ok` to `Initialized hwrng`. These
change the serial console output format (a breaking change to the boot
contract).
- **virt boot-watchdog free-page checkpoints.** They move to `0x3be46`
(per scenario) / `0x3be54` (boot baseline) because the larger `fsh`
(TAB completion, history, the restructured `help`), the new `/bin/sysinfo`
and `/bin/less` tools, and the `+strict-align` codegen grow the embedded
initramfs and kernel image; rpi4b is unchanged (its reserve calls are
no-ops).
### Removed
- **`[ OK ] Authenticated` login marker.** `/bin/login` no longer prints
a per-session auth marker; a blank line separates the password prompt
from the shell homescreen. Boot success is now the homescreen-marker
count alone.
### Fixed
- **`/bin/less` alignment fault on real hardware.** The pager's by-value
`Pager` return was vectorized into a misaligned 16-byte NEON store
(`stur q` at struct offset 40), which faulted under `SCTLR_EL1.A` on real
silicon (data abort, alignment fault) while passing QEMU's lenient TCG.
Instead of another per-site `align(16)` / volatile dodge, the freestanding
aarch64 build target now sets `+strict-align`, so LLVM never widens a copy
or a by-value return into an unaligned NEON store — closing the class for
the kernel and every userland tool at codegen. The virt boot-watchdog
checkpoints shift one page as a result (see Changed).
- **Line editing at the `login:` and password prompt.** `/bin/login` read
the username and password through a dumb byte loop that *appended* a
backspace byte instead of erasing it, so a single mistype was
uncorrectable and the attempt failed as "Login incorrect." Login now
drives flibc's line editor in echo-off mode: the username gets full
backspace editing, and the password reuses the same host-tested `step`
core with masked echo — one `*` per byte, rubbed out on backspace. No
kernel or syscall change.
## [v0.2.0] - 2026-06-06
### Added
- **FAT32 subdirectory path traversal.** Files below the mount root
(`/mnt/dir/file`, and deeper) now open. Previously the mount backend
encoded the whole mount-relative path as a single 8.3 name, so any
`/`-separated path was rejected and only files in the mount root were
reachable. The open hook now walks the path one component at a time,
descending into each subdirectory entry. Directory *listing*
(`readdir`) stays root-only for now — opening a known path works.
- **FAT32 write to an empty file.** Writing to a file whose directory
entry has no data cluster yet (`first_cluster == 0`, the on-disk shape
of a 0-byte file) now allocates its first cluster, links it, and
records it in the directory entry, instead of failing closed. The
write path now identifies the file by its directory-entry location
(stashed at open) rather than an ambiguous re-walk by first cluster, so
file growth works for subdirectory files too. Covered by host tests and
a Pi-only `[TEST] fs-empty-write` in-kernel scenario; the boot contract
moves to **28 in-kernel scenarios / 32 per-scenario checkpoints** (was
27 / 31). Create-if-missing for a *non-existent* path and crash-atomic
writes remain future work.
- **`-Dboot-selftest` build option (default off).** Gates the in-kernel
test harness: a normal `zig build run-virt` / `deploy` now boots
straight to the `login:` prompt with no test output, while CI and
validation builds pass `-Dboot-selftest=true` to run the full
in-kernel scenario suite. The EMMC2 smoke test and the free-page
checkpoint dump are gated the same way.
### Changed
- **Boot output restyled to systemd-style status lines.** The kernel,
init, login, and fsh now print `[ OK ]` / `[SKIP]` / `[WARN]` lines
instead of `[Debug]` noise. The two success markers were renamed —
`[Debug] login OK` → `[ OK ] Authenticated` and
`[Debug] fsh init OK` → `[ OK ] Reached target Shell` — so any log
parser keying on the old strings must be updated. The boot-contract
checkpoint and session counts are unchanged by the restyle.
- **Diagnostic output suppressed by default.** EMMC2 and USB bring-up
traces and hwrng chatter are now gated behind in-file flags
(`DIAG`, `TRACE_VERBOSE`) that default off, keeping the boot log clean.
## [v0.1.0] - 2026-06-05
First public release. FlashOS was developed privately before this
release, so v0.1.0 already ships a substantial feature set; the
highlights are below.
### Added
- **Bare-metal AArch64 kernel** for the Raspberry Pi 4B and QEMU
`-M virt`. Two-stage boot to EL1 (an EL3 armstub sets up the GIC and
`eret`s down on the Pi; `src/boot.S` drops straight from EL3 to EL1
under QEMU), then a four-level 4 KiB-page MMU brings up the identity
map, the linear-high kernel map, and demand-allocated user pages.
- **Scheduler and processes.** Priority round-robin with timer-driven
preemption, and the full `fork` / `exec` / `exit` / `wait` / `kill`
lifecycle over an indexed syscall table. Zombies are reaped, and the
scheduler stays leak-free across stress cycles.
- **ELF loader and a small userland libc** to run programs from user
space, with bounds checks on the segment ranges so a malformed binary
cannot map over the kernel or the stack guard.
- **Filesystem.** An initramfs plus a FAT32 backend, backed by the SD
card on the Pi and a disk image under QEMU. A write/verify roundtrip
test runs during boot.
- **Interactive shell (`fsh`)** over a unified file-descriptor ABI, with
pipes, console RX, and tracing. Memory pressure is handled gracefully.
- **USB-C gadget console.** The Pi enumerates as a USB CDC serial device,
so `fsh` runs over the same C-to-C cable that powers the board; no
separate serial adapter is required for normal use.
- **Logins.** A small identity/auth layer: a `login:` prompt,
PBKDF2-hashed passwords in a shadow file, and `passwd` to change them.
The accounts are build-time and public; the security model and its
limitations are documented.
- **Opt-in profiler** behind `-Dtrace`: samples the interrupted PC each
timer tick and prints a symbolized trace on the Mini-UART. Off by
default, zero footprint when off (the default image is byte-identical).
- **Tests.** An in-kernel `[TEST]` harness (27 EL0 scenarios / 31
checkpoints, each with a free-page baseline check) that runs on every
boot on both targets, plus a host-side `zig build test` suite (361
tests across 35 modules) for the pure-logic pieces. CI runs the boot
path on every push.
- **Dual-target build** — `-Dboard=rpi4b` / `-Dboard=virt` swaps the
per-board driver set, linker script, and boot quirks at comptime. Both
targets boot cleanly.
- **Kernel symbol table** generated from the linked ELF by a two-pass
build step, so panics and the profiler can print real names.
[Unreleased]: https://github.com/ajhahnde/FlashOS/compare/v0.7.0...HEAD
[v0.7.0]: https://github.com/ajhahnde/FlashOS/compare/v0.6.0...v0.7.0
[v0.6.0]: https://github.com/ajhahnde/FlashOS/compare/v0.5.0...v0.6.0
[v0.5.0]: https://github.com/ajhahnde/FlashOS/compare/v0.4.0...v0.5.0
[v0.4.0]: https://github.com/ajhahnde/FlashOS/compare/v0.3.0...v0.4.0
[v0.3.0]: https://github.com/ajhahnde/FlashOS/compare/v0.2.0...v0.3.0
[v0.2.0]: https://github.com/ajhahnde/FlashOS/compare/v0.1.0...v0.2.0
[v0.1.0]: https://github.com/ajhahnde/FlashOS/releases/tag/v0.1.0
---
[← Prev: Versioning](VERSIONING.md) · [Next: License →](LICENSE.md)
</content>