add logging and other things
This commit is contained in:
parent
a4650fd589
commit
067cadbe0f
10 changed files with 870 additions and 98 deletions
116
README.md
116
README.md
|
|
@ -9,18 +9,19 @@ format.
|
|||
- `.rc` config parser (services + on-trigger action blocks)
|
||||
- Supervisor event loop built on `signalfd` + `epoll`
|
||||
- service spawn / reap / respawn (per-service restart policy)
|
||||
- crash-window rate limiting (`crash-threshold`/`crash-window` cap crash
|
||||
restarts within a rolling window; a throttled service needs `bctl start`)
|
||||
- action commands: `start`, `stop`, `restart`, `exec`, `mkdir`, `chmod`,
|
||||
`chown`, `setenv`, `write`, `symlink`, `mount`, `log`
|
||||
`chown`, `setenv`, `write`, `symlink`, `mount`, `switch_root`, `log`
|
||||
- ordered `reboot`/`poweroff` shutdown (graceful stop, unmount, reboot)
|
||||
- logger with a ring buffer that flushes to the console once available
|
||||
- first/second stage boot via initramfs + `switch_root`
|
||||
- dependency ordering (`depends = NAME`) with cycle detection
|
||||
- property triggers (`on property:K=V`) + `setprop`/`getprop`
|
||||
- per-service logs (`logfile = PATH`) capturing stdout+stderr
|
||||
|
||||
roadmap:
|
||||
|
||||
- `SIGCHLD` crash-window limiting (rate-limited restarts; `crash-threshold`/
|
||||
`crash-window` are parsed but not yet enforced by the reaper)
|
||||
- dependency ordering between services
|
||||
- property triggers (`property:<k>=<v>`) and `setprop`/`getprop`
|
||||
- per-service logging to files
|
||||
- `reboot`/`poweroff` path with ordered unmount
|
||||
- readiness/socket activation
|
||||
|
||||
## building
|
||||
|
|
@ -66,15 +67,32 @@ service NAME /path/to/exe [args...]
|
|||
oneshot # run once and exit, never respawn
|
||||
disabled # not started by the boot sequence
|
||||
console # bind stdio to /dev/console
|
||||
logfile = PATH # redirect stdout+stderr to PATH (append)
|
||||
class = NAME # grouping (default "default")
|
||||
respawn = never|on-failure|always # restart policy (default always)
|
||||
crash-threshold = N # restarts allowed per window
|
||||
crash-window = SECS
|
||||
depends = NAME [NAME...] # start these (transitively) first; cycle-checked
|
||||
seclabel = CONTEXT # SELinux exec context (--selinux build)
|
||||
setenv = K=V # extra environment (repeatable)
|
||||
cwd = /path
|
||||
```
|
||||
|
||||
`depends = NAME [NAME...]` (repeatable, like `group`) gives dependency
|
||||
ordering: `start app` (from a trigger or `bctl start`) starts every transitive
|
||||
dependency first — in declared order — before the service itself. Shared and
|
||||
already-running dependencies are started once; a self- or mutual-cycle is
|
||||
reported and the start refused. This is *ordering* only (dependencies are
|
||||
spawned before dependents); waiting for a dependency to signal readiness is a
|
||||
separate, later feature.
|
||||
|
||||
`logfile = PATH` captures the service's `stdout`+`stderr` to `PATH`, appending
|
||||
across restarts, instead of the console. The parent directory must already
|
||||
exist (make it with `mkdir` in `early-init`). The file is opened as root
|
||||
*before* the privilege drop, so a service running as an unprivileged user can
|
||||
still append to a root-owned log. `logfile` and `console` are alternatives;
|
||||
`logfile` wins when both are set.
|
||||
|
||||
### actions
|
||||
|
||||
```rc
|
||||
|
|
@ -88,16 +106,36 @@ on TRIGGER
|
|||
write PATH CONTENT
|
||||
symlink TARGET LINK
|
||||
mount SOURCE TARGET FSTYPE
|
||||
switch_root NEW_ROOT INIT [ARGS...]
|
||||
setprop KEY VALUE | getprop KEY
|
||||
log message
|
||||
```
|
||||
|
||||
boot triggers fire in order: `early-init`, `init`, `boot`. `shutdown` triggers
|
||||
fire when the system is winding down. property/`service-*` triggers are on the
|
||||
roadmap.
|
||||
fire when the system is winding down. `service-*` triggers are on the roadmap.
|
||||
|
||||
Services run as `root` by default; `user`/`group` trigger a full privilege
|
||||
drop (supplementary groups, then gid, then uid) before exec.
|
||||
|
||||
### property triggers
|
||||
|
||||
An `on property:KEY=VALUE` action fires whenever `setprop KEY VALUE` is run
|
||||
(by an action, or via the control socket). It is the Android-style way to wake
|
||||
a later step once an earlier one signals a condition:
|
||||
|
||||
```rc
|
||||
on boot
|
||||
setprop net.up 1
|
||||
|
||||
on property:net.up=1
|
||||
start webserver
|
||||
```
|
||||
|
||||
Properties live in a small supervisor store and are addressable from actions
|
||||
(`setprop`/`getprop`) and from `bctl setprop KEY VALUE` / `bctl getprop KEY`.
|
||||
A `setprop` fires *all* matching `property:KEY=VALUE` actions; a nested
|
||||
`setprop`-in-trigger storm is capped to avoid infinite recursion.
|
||||
|
||||
### imports
|
||||
|
||||
Configs can be split across files with `@import PATH` (column 0, before any
|
||||
|
|
@ -113,6 +151,64 @@ self/cyclic imports are reported as errors. Imported files may import other
|
|||
files and define services and actions like any other rc. `reload` re-parses
|
||||
the whole import tree, so imported changes take effect on `bctl reload`.
|
||||
|
||||
## first/second stage boot (initramfs)
|
||||
|
||||
bajia supports the classic two-stage boot: a minimal initramfs runs it as
|
||||
PID 1, and once the real root is mounted it `switch_root`es onto that root and
|
||||
hands off to the real init (usually this same binary). The two stages are just
|
||||
two different `.rc` configs; the kernel boots into the first-stage config, and
|
||||
its `switch_root` line re-execs the second-stage binary with the full config.
|
||||
|
||||
```
|
||||
# first stage - etc/initramfs.rc (loaded by the kernel into the initramfs)
|
||||
on early-init
|
||||
mount proc /proc proc
|
||||
mount sysfs /sys sysfs
|
||||
mount devtmpfs /dev devtmpfs
|
||||
|
||||
on init
|
||||
mkdir /mnt/root 0755
|
||||
mount /dev/sda1 /mnt/root ext4 # real root filesystem
|
||||
mount /mnt/root/boot /mnt/root/boot # etc., as needed
|
||||
|
||||
# hand control to the real init on the new root (still PID 1)
|
||||
on boot
|
||||
switch_root /mnt/root /sbin/init /etc/bajia/init.rc
|
||||
```
|
||||
|
||||
The `switch_root NEW_ROOT INIT [ARGS...]` command:
|
||||
|
||||
1. bind-mounts `NEW_ROOT` onto itself so it is a proper mount point,
|
||||
2. moves `/dev`, `/proc`, `/sys` into the new root,
|
||||
3. `chdir`s there, calls `pivot_root` (stashing the initramfs root at
|
||||
`/initrd`) and detaches that root to reclaim its backing RAM,
|
||||
4. re-execs `INIT` with `ARGS` as PID 1 (usually bajia again, i.e. a second
|
||||
invocation that reads the real config and runs the full `boot` services).
|
||||
|
||||
`switch_root` only succeeds on a real mount point and refuses to pivot onto
|
||||
`/`; it never returns on success. It is meant to be the last command of the
|
||||
first-stage `boot` trigger.
|
||||
|
||||
### testing it in a VM
|
||||
|
||||
`tools/run_vm.py` has a `--two-stage` mode that boots the whole chain in QEMU:
|
||||
a minimal stage-1 initramfs (bajia as `/init` + a generated first-stage rc that
|
||||
mounts a real root and `switch_root`s onto it) and a stage-2 writable ext4 root
|
||||
disk (bajia at `/sbin/init` + the full config). It needs a static bajia, a
|
||||
static busybox, and a kernel with virtio-blk built in (stock distro kernels
|
||||
qualify):
|
||||
|
||||
```sh
|
||||
python3 tools/run_vm.py --two-stage --busybox /path/to/busybox-static \
|
||||
--root-dev /dev/vda --nographic
|
||||
```
|
||||
|
||||
You should see two `Welcome to bajia 0.1` banners (one per stage) and end up at
|
||||
a `bajia login:` prompt on the second-stage root. `--config my-init.rc` selects
|
||||
the second-stage config; `--root-dev`/`--root-fstype` (default `/dev/vda`/ext4)
|
||||
and `--root-size` (default 128 MiB) tune the real root disk. Without
|
||||
`--two-stage`, the tool keeps its original single-root initramfs behaviour.
|
||||
|
||||
## control
|
||||
|
||||
A running init listens on an abstract unix socket (`@bajia`). The bundled
|
||||
|
|
@ -124,6 +220,8 @@ bctl start NAME # start a service
|
|||
bctl stop NAME # graceful stop (SIGTERM)
|
||||
bctl restart NAME # restart a service
|
||||
bctl trigger EVENT # fire an action trigger
|
||||
bctl setprop KEY VALUE # set a property; fires matching on property: triggers
|
||||
bctl getprop KEY # print a property's value
|
||||
bctl reload # re-parse init.rc and reconcile services
|
||||
bctl shutdown [poweroff|reboot]
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue