mouse/src
Diff
ea9ff564e7ff → e9727e0ca023
Containerfile
Mode 100644 → 100644; object f78e30d3af17 → e6f70c5a6dee
@@ -18,6 +18,7 @@
perl=5.42.2-r0 \
python3=3.14.5-r0 \
rustup=1.29.0-r0 \
+ rsync=3.4.3-r1 \
openssl-dev=3.5.7-r0 \
xz=5.8.3-r0
@@ -52,7 +53,11 @@
FROM toolchain AS rootfs-builder
-RUN apk add --no-cache byacc=20260126-r0
+# util-linux's Meson setup requires a program named bison even when its only
+# enabled target, agetty, does not use the parser generator. NetBSD curses does
+# require Berkeley Yacc, and Alpine makes the two packages conflict.
+RUN apk add --no-cache byacc=20260126-r0 \
+ && ln -s /usr/bin/byacc /usr/local/bin/bison
RUN MOUSE_SKIP_KERNEL=1 \
MOUSE_SOURCE_DIR=/opt/mouse-sources \
Makefile
Mode 100644 → 100644; object 3a8df4295e43 → 3848beab46aa
@@ -17,7 +17,7 @@ ./scripts/run-qemu.sh check: image - ./scripts/check-c2.sh + ./scripts/check-c3.sh audit: image @printf '%s\n' "static ELF audit passed during the container build"
PORTS.md
Mode → 100644; object → 83f3d597cc9c
@@ -1,0 +1,21 @@ +# OpenRC service contract for MOUSE ports + +A port may install an OpenRC service definition at `/etc/init.d/<name>`. +`<name>` must match `[a-z][a-z0-9_]*`, and exactly one definition may exist for +each name. The definition owns runtime dependencies, supervision, health, and +shutdown behaviour through normal `openrc-run` facilities. + +A port must not install persistent runlevel links, invoke or depend on +`rc-update`, edit `/etc/rc.conf`, or maintain another enablement database. The +administrator enables an optional ports service only with +`enable_<name>=YES` in `/etc/rc.conf`; `cheesed` validates that assignment +against the installed definition and creates disposable runlevel membership +under `/run` on the next boot. + +Services should run in the foreground under `supervise-daemon` when they need +process supervision. Runtime `service <name> start|stop|restart|reload|status` +actions never change next-boot enablement. + +The `ports/mouse-echo` payload is the acceptance example. It installs the +`mouse_echo` definition and executable without runlevel state; the base image's +explicit `enable_mouse_echo=YES` policy is solely responsible for starting it.
README.md
Mode 100644 → 100644; object 0d330f2b91e4 → b0840cb95ae6
@@ -1,6 +1,6 @@ # MOUSE source tree -This repository builds the MOUSE C2 boot image: Linux 6.18.35 from pinned +This repository builds the MOUSE C3 boot image: Linux 6.18.35 from pinned kernel.org source using MOUSE's own x86_64 configuration, a MOUSE-owned mini rootfs with ChimeraUtils and static OpenRC 0.63.3, and `cheesed` from the sibling repository as `/sbin/cheesed`. @@ -8,7 +8,9 @@ The mini rootfs is assembled from an explicit file list rather than inheriting another distribution's filesystem. The kernel, musl, NetBSD curses, `tcsh`, and the selected ChimeraUtils base commands are compiled locally from pinned source -archives. No shipped runtime artifact comes from Alpine. +archives. C3 adds a static util-linux `agetty` and non-PAM Shadow `login`, with +libxcrypt supplying the static password-hashing interface. No shipped runtime +artifact comes from Alpine. ## Build and boot @@ -41,12 +43,13 @@ verified through `sources.lock`. Before writing the initramfs, the build rejects any base ELF executable with an interpreter or dynamic `NEEDED` entry. -`make check` requires `expect`. It boots enabled and disabled policy variants -under QEMU, verifies strict `/etc/rc.conf` translation into disposable -runlevels, required-service ordering, `supervise-daemon` restart exhaustion and -administrative recovery, forced termination on the declared stop schedule, -runtime-only `service` operations, the absence of `rc-update`, and orderly -OpenRC shutdown before poweroff and reboot. +`make check` requires `expect`. It boots five policy and failure variants under +QEMU. The suite verifies the supervised serial login, root's `tcsh` login +environment, strict `/etc/rc.conf` translation into disposable runlevels, +enabled and disabled ports-service policy, missing dependencies and dependency +cycles without partial starts, required-service recovery, restart exhaustion, +forced termination, runtime-only `service` operations, and reverse-order +shutdown. To select a container frontend explicitly: @@ -64,7 +67,15 @@ ```text Cheesed to meet you! v0.1.0 -cheesed: C1 bootstrap complete; cheesed is PID 1 -cheesed: enabled optional services: mouse_optional +cheesed: C3 bootstrap complete; cheesed is PID 1 +cheesed: enabled optional services: mouse_echo cheesed: OpenRC default transition complete +mouse login: root (automatic login) +MOUSE 0.1.0 (x86_64) +mouse:~# ``` + +The checked-in development image uses `agetty --autologin root` so the QEMU +acceptance suite is deterministic. It still enters the conventional +`agetty` → `login` → `tcsh` chain; the root password remains locked in +`/etc/shadow`.
ports/mouse-echo/mouse-echo.c
Mode → 100644; object → 276254786f3b
@@ -1,0 +1,41 @@
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+static const char ready_path[] = "/run/mouse-echo.ready";
+static const char term_path[] = "/run/mouse-echo.term";
+
+static void write_marker(const char *path, const char *contents, size_t length)
+{
+ int descriptor = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+
+ if (descriptor == -1)
+ return;
+ (void)write(descriptor, contents, length);
+ (void)close(descriptor);
+}
+
+static void handle_term(int signal_number)
+{
+ static const char marker[] = "SIGTERM\n";
+
+ (void)signal_number;
+ write_marker(term_path, marker, sizeof(marker) - 1);
+}
+
+int main(void)
+{
+ static const char marker[] = "ready\n";
+ struct sigaction action = {
+ .sa_handler = handle_term,
+ };
+
+ if (sigemptyset(&action.sa_mask) == -1)
+ return 1;
+ if (sigaction(SIGTERM, &action, NULL) == -1)
+ return 1;
+ write_marker(ready_path, marker, sizeof(marker) - 1);
+
+ for (;;)
+ pause();
+}
ports/mouse-echo/rootfs/etc/init.d/mouse_echo
Mode → 100644; object → ecc57ff91178
@@ -1,0 +1,17 @@
+#!/sbin/openrc-run
+
+description="MOUSE example ports service"
+supervisor=supervise-daemon
+command=/usr/libexec/mouse-echo
+respawn_delay=0
+respawn_max=3
+respawn_period=10
+retry="TERM/1/KILL/1"
+
+depend() {
+ need console_login
+}
+
+start_pre() {
+ rm -f /run/mouse-echo.term
+}
rootfs/etc/csh.login
Mode 100644 → 100644; object 6d34b767b316 → 2920f948a439
@@ -1,8 +1,4 @@ umask 022 setenv SHELL /bin/tcsh -if ( -r /etc/motd ) then - cat /etc/motd -endif - cd
rootfs/etc/group
Mode 100644 → 100644; object 0b0aed46da25 → 421bb9eb2d66
@@ -1,2 +1,3 @@ root:x:0: +tty:x:5: uucp:x:14:
rootfs/etc/init.d/console_login
Mode → 100644; object → b72f9f217cd4
@@ -1,0 +1,14 @@
+#!/sbin/openrc-run
+
+description="MOUSE serial console login"
+supervisor=supervise-daemon
+command=/usr/bin/agetty
+command_args="--autologin root --login-program /usr/bin/login --noclear --keep-baud 115200,38400,9600 ttyS0 vt100"
+respawn_delay=1
+respawn_max=0
+retry="TERM/2/KILL/1"
+
+start_pre() {
+ checkpath --file --mode 0664 /run/utmp
+ checkpath --file --mode 0664 /var/log/wtmp
+}
rootfs/etc/init.d/mouse_optional
Mode 100644 → ; object 173b05646a96 →
@@ -1,25 +1,0 @@
-#!/sbin/openrc-run
-
-description="MOUSE optional supervised proof service"
-supervisor=supervise-daemon
-command=/usr/libexec/mouse-c2-daemon
-respawn_delay=0
-respawn_max=3
-respawn_period=10
-retry="TERM/1/KILL/1"
-
-depend() {
- need mouse_ready
-}
-
-start_post() {
- checkpath --file /run/mouse-optional.started
-}
-
-start_pre() {
- rm -f /run/mouse-c2-daemon.term
-}
-
-stop_post() {
- rm -f /run/mouse-optional.started
-}
rootfs/etc/init.d/mouse_ready
Mode 100644 → ; object d1ad77b846a4 →
@@ -1,15 +1,0 @@
-#!/sbin/openrc-run
-
-description="MOUSE required C1 proof service"
-
-start() {
- ebegin "Marking the MOUSE base ready"
- checkpath --file /run/mouse-ready.started
- eend $?
-}
-
-stop() {
- ebegin "Stopping the MOUSE readiness service"
- rm -f /run/mouse-ready.started
- eend $?
-}
rootfs/etc/login.defs
Mode → 100644; object → cf5da23ae2e6
@@ -1,0 +1,7 @@ +ENV_PATH PATH=/usr/bin +ENV_SUPATH PATH=/usr/bin +HUSHLOGIN_FILE .hushlogin +LOGIN_TIMEOUT 60 +MOTD_FILE /etc/motd +TTYGROUP tty +TTYPERM 0600
rootfs/etc/rc.conf
Mode 100644 → 100644; object e0086165deab → 627887674398
@@ -1,2 +1,2 @@ # Persistent MOUSE service policy belongs in this file. -enable_mouse_optional=YES +enable_mouse_echo=YES
rootfs/etc/securetty
Mode → 100644; object → 360e9251bc33
@@ -1,0 +1,1 @@ +ttyS0
rootfs/etc/shadow
Mode → 100644; object → e0ec2c3f45b6
@@ -1,0 +1,1 @@ +root:!:1:0:99999:7:::
rootfs/usr/lib/mouse/runlevels/default/console_login
Mode → 100644; object → cb88ee1b983c
@@ -1,0 +1,1 @@ +Required default-runlevel membership for the C3 console login service.
rootfs/usr/lib/mouse/runlevels/default/mouse_ready
Mode 100644 → ; object 63cade7eb2f2 →
@@ -1,1 +1,0 @@ -Required default-runlevel membership for the C1 readiness service.
scripts/build-image.sh
Mode 100755 → 100755; object 764b6f114e8c → f06cfb587e04
@@ -29,7 +29,8 @@
"$staging/run" \
"$staging/sys" \
"$staging/usr/bin" \
- "$staging/var"
+ "$staging/var/log" \
+ "$staging/var/mail"
chmod 0755 "$staging"
chmod 0700 "$staging/root"
install -d -m 1777 "$staging/tmp"
@@ -43,11 +44,13 @@
install -m 0755 "$cheesed_binary" "$staging/usr/bin/cheesed"
cp -R "$repo_dir/rootfs/." "$staging/"
+cp -R "$repo_dir/ports/mouse-echo/rootfs/." "$staging/"
find "$staging/usr/lib/mouse/runlevels" -name .keep -delete
ln -s mouse-release "$staging/etc/os-release"
chmod 0755 \
- "$staging/etc/init.d/mouse_optional" \
- "$staging/etc/init.d/mouse_ready"
+ "$staging/etc/init.d/console_login" \
+ "$staging/etc/init.d/mouse_echo"
+chmod 0600 "$staging/etc/shadow"
ln -s /run/openrc/runlevels/current "$staging/etc/runlevels"
"$script_dir/audit-static-base.sh" "$staging"
scripts/build-static-base.sh
Mode 100755 → 100755; object 34ee7674690d → e03c5dfdc99b
@@ -63,6 +63,7 @@
extract meson-1.9.1.tar.gz
extract ninja-1.13.1.tar.gz
extract pkgconf-2.5.1.tar.gz
+extract linux-6.18.35.tar.xz
extract musl-1.2.6.tar.gz
extract netbsd-curses-0.3.2.tar.gz
extract attr-2.5.2.tar.gz
@@ -72,6 +73,9 @@
extract chimerautils-15.0.3.tar.gz
extract libcap-2.77.tar.xz
extract openrc-0.63.3.tar.gz
+extract util-linux-2.42.2.tar.xz
+extract libxcrypt-4.5.2.tar.xz
+extract shadow-4.19.4.tar.xz
patch -d "$source_build_dir/tcsh-TCSH6_24_16" -p1 \
<"$repo_dir/patches/tcsh-gethost-native.patch"
@@ -116,6 +120,11 @@
make -s DESTDIR="$sysroot" install
)
+make -s -C "$source_build_dir/linux-6.18.35" \
+ ARCH=x86 \
+ headers_install \
+ INSTALL_HDR_PATH="$sysroot/usr"
+
install -d -m 0755 "$sysroot/usr/include/sys"
install -m 0644 "$source_dir/cdefs.h" "$sysroot/usr/include/sys/cdefs.h"
install -m 0644 "$source_dir/queue.h" "$sysroot/usr/include/sys/queue.h"
@@ -137,6 +146,55 @@
cc="$script_dir/mouse-cc"
cxx="$script_dir/mouse-cxx"
ranlib="$script_dir/mouse-ranlib"
+
+(
+ cd "$source_build_dir/libxcrypt-4.5.2"
+ CC="$cc" \
+ AR="$llvm_ar" \
+ RANLIB="$ranlib" \
+ ./configure \
+ --build="$build_triplet" \
+ --host=x86_64-linux-musl \
+ --prefix=/usr \
+ --disable-shared \
+ --enable-static \
+ --disable-obsolete-api \
+ --disable-symvers \
+ --enable-hashes=sha512crypt
+ make -s -j"$jobs"
+ make -s DESTDIR="$sysroot" install
+)
+
+(
+ cd "$source_build_dir/shadow-4.19.4"
+ CC="$cc" \
+ AR="$llvm_ar" \
+ RANLIB="$ranlib" \
+ PKG_CONFIG="$tools_dir/bin/pkgconf" \
+ PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig" \
+ PKG_CONFIG_SYSROOT_DIR="$sysroot" \
+ ./configure \
+ --build="$build_triplet" \
+ --host=x86_64-linux-musl \
+ --prefix=/usr \
+ --disable-shared \
+ --enable-static \
+ --disable-logind \
+ --disable-nls \
+ --disable-shadowgrp \
+ --without-audit \
+ --without-libpam \
+ --without-selinux \
+ --without-acl \
+ --without-attr \
+ --without-libbsd \
+ --without-skey \
+ --without-tcb \
+ --without-nscd
+ make -s -j"$jobs" -C lib
+ make -s -j"$jobs" -C src login
+ install -m 0755 src/login "$base_root/usr/bin/login"
+)
(
cd "$source_build_dir/netbsd-curses-0.3.2"
@@ -260,6 +318,23 @@
"default_library = 'static'" \
"prefer_static = true"
} >"$cross_file"
+
+util_linux_build="$source_build_dir/util-linux-2.42.2/build"
+PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
+PKG_CONFIG_SYSROOT_DIR="$sysroot" \
+ python3 "$meson_source" setup \
+ "$util_linux_build" \
+ "$source_build_dir/util-linux-2.42.2" \
+ --cross-file="$cross_file" \
+ --prefix=/usr \
+ --buildtype=release \
+ --default-library=static \
+ -Dauto_features=disabled \
+ -Dbuild-agetty=enabled
+PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
+PKG_CONFIG_SYSROOT_DIR="$sysroot" \
+ python3 "$meson_source" compile -C "$util_linux_build" agetty
+install -m 0755 "$util_linux_build/agetty" "$base_root/usr/bin/agetty"
(
cd "$source_build_dir/libcap-2.77"
@@ -442,8 +517,8 @@
install -d -m 0755 "$base_root/usr/libexec"
"$cc" -O2 -Wall -Wextra -Werror \
- "$repo_dir/support/mouse-c2-daemon.c" \
- -o "$base_root/usr/libexec/mouse-c2-daemon"
+ "$repo_dir/ports/mouse-echo/mouse-echo.c" \
+ -o "$base_root/usr/libexec/mouse-echo"
printf '%s\n' '#!/bin/sh' 'exec /usr/bin/tput clear' >"$base_root/usr/bin/clear"
chmod 0755 "$base_root/usr/bin/clear"
scripts/check-c3.exp
Mode → 100755; object → 0e183b5cb48c
@@ -1,0 +1,220 @@
+#!/usr/bin/expect -f
+
+if {$argc != 2} {
+ puts stderr "usage: check-c3.exp RUNNER MODE"
+ exit 2
+}
+
+set runner [file normalize [lindex $argv 0]]
+set mode [lindex $argv 1]
+if {$mode ni {enabled disabled recovery missing cycle}} {
+ puts stderr "invalid C3 check mode: $mode"
+ exit 2
+}
+set timeout 45
+
+proc fail {message} {
+ puts stderr "C3 check failed: $message"
+ exit 1
+}
+
+proc await_exact {value description} {
+ expect {
+ -exact $value {
+ return
+ }
+ timeout {
+ fail "timed out waiting for $description"
+ }
+ eof {
+ fail "QEMU exited while waiting for $description"
+ }
+ }
+}
+
+proc await_regexp {value description} {
+ expect {
+ -re $value {
+ return
+ }
+ timeout {
+ fail "timed out waiting for $description"
+ }
+ eof {
+ fail "QEMU exited while waiting for $description"
+ }
+ }
+}
+
+proc await_prompt {} {
+ send -- "\r"
+ await_exact "mouse:~# " "the tcsh prompt"
+}
+
+proc sendline {line} {
+ send -- $line
+ send -- "\r"
+}
+
+proc await_clean_exit {action} {
+ expect {
+ eof {
+ set result [wait]
+ set status [lindex $result 3]
+ if {$status != 0} {
+ fail "QEMU exited with status $status after $action"
+ }
+ }
+ timeout {
+ fail "QEMU did not exit after $action"
+ }
+ }
+}
+
+proc poweroff {} {
+ send -- "kill -TERM 1\r"
+ await_exact "cheesed: received poweroff request" "the poweroff request"
+ await_exact "cheesed: OpenRC shutdown transition complete" "the shutdown transition"
+ await_clean_exit "poweroff"
+}
+
+log_user 0
+spawn -noecho $runner
+await_exact "cheesed: C3 bootstrap complete; cheesed is PID 1" "PID 1 confirmation"
+
+if {$mode eq "recovery"} {
+ await_regexp {required service console_login is not started: status command exited with status [0-9]+; entering recovery console} "the required-service failure"
+ await_regexp {cheesed: started emergency shell as PID [0-9]+} "the recovery shell"
+ await_exact "mouse:~# " "the recovery prompt"
+ sendline {sh -c 'set -- `cat /proc/$PPID/stat`; cat /proc/$4/comm'}
+ await_exact "cheesed\r" "the recovery shell parent"
+ await_exact "mouse:~# " "the prompt after the recovery parent check"
+ poweroff
+ puts "C3 required-service recovery check passed"
+ exit 0
+}
+
+if {$mode eq "disabled"} {
+ await_exact "cheesed: enabled optional services: (none)" "disabled ports policy"
+} else {
+ await_exact "cheesed: enabled optional services: mouse_echo" "enabled ports policy"
+}
+if {$mode eq "missing"} {
+ await_exact "Service 'mouse_echo' needs non existent service 'absent_port_dependency'" "the missing-dependency diagnostic"
+}
+await_exact "Starting console_login" "the required console service"
+if {$mode eq "cycle"} {
+ await_exact "ERROR: cannot start cycle_peer as mouse_echo would not start" "the dependency-cycle diagnostic"
+}
+await_exact "mouse login: root (automatic login)" "the automatic root login"
+await_exact "MOUSE 0.1.0 (x86_64)" "the login banner"
+
+if {$mode eq "enabled"} {
+ await_exact "cheesed: OpenRC default transition complete" "the default transition"
+} elseif {$mode eq "disabled"} {
+ await_exact "cheesed: OpenRC default transition complete" "the default transition"
+} else {
+ await_exact "cheesed: OpenRC default transition complete" "the default transition after rejecting the optional service"
+ await_regexp {optional service mouse_echo failed: status command exited with status [0-9]+} "the optional-service failure"
+}
+await_prompt
+
+sendline {id}
+await_exact "uid=0(root) gid=0(root) groups=0(root)\r" "the root login identity"
+await_exact "mouse:~# " "the prompt after the identity check"
+sendline {echo $LOGNAME $USER $SHELL}
+await_exact "root root /bin/tcsh\r" "the login environment"
+await_exact "mouse:~# " "the prompt after the environment check"
+sendline {tty}
+await_exact "/dev/ttyS0\r" "the serial login tty"
+await_exact "mouse:~# " "the prompt after the tty check"
+sendline {sh -c 'set -- `cat /proc/$PPID/stat`; cat /proc/$4/comm'}
+await_exact "supervise-daemo\r" "the supervised login parent"
+await_exact "mouse:~# " "the prompt after the login parent check"
+sendline {sh -c 'test "$(cat /proc/1/comm)" = cheesed && ! command -v rc-update >/dev/null 2>&1'; echo BASE_$status}
+await_exact "BASE_0\r" "cheesed as PID 1 with no rc-update command"
+await_exact "mouse:~# " "the prompt after the base contract check"
+
+if {$mode eq "missing"} {
+ sendline {sh -c 'test ! -e /run/mouse-echo.unexpected'; echo ORDERED_$status}
+ await_exact "ORDERED_0\r" "no partial start after a missing dependency"
+ await_exact "mouse:~# " "the prompt after the missing-dependency check"
+ poweroff
+ puts "C3 missing-dependency check passed"
+ exit 0
+}
+
+if {$mode eq "cycle"} {
+ sendline {sh -c 'test ! -e /run/mouse-echo.unexpected && test ! -e /run/cycle-peer.unexpected'; echo ORDERED_$status}
+ await_exact "ORDERED_0\r" "no partial start after a dependency cycle"
+ await_exact "mouse:~# " "the prompt after the dependency-cycle check"
+ poweroff
+ puts "C3 dependency-cycle check passed"
+ exit 0
+}
+
+sendline {service console_login status}
+await_exact "status: started" "the required console service status"
+await_exact "mouse:~# " "the prompt after the console status"
+
+if {$mode eq "disabled"} {
+ sendline {sh -c 'test ! -e /run/openrc/runlevels/current/default/mouse_echo'; echo DISABLED_$status}
+ await_exact "DISABLED_0\r" "the absent ports runlevel member"
+ await_exact "mouse:~# " "the prompt after the disabled membership check"
+ sendline {sh -c 'cksum /etc/rc.conf > /run/rc.conf.cksum'; service mouse_echo start}
+ await_exact "Starting mouse_echo" "the runtime-only ports service start"
+ await_exact "mouse:~# " "the prompt after the runtime start"
+ sendline {sh -c 'test "$(cksum /etc/rc.conf)" = "$(cat /run/rc.conf.cksum)"'; echo POLICY_$status}
+ await_exact "POLICY_0\r" "unchanged persistent policy"
+ await_exact "mouse:~# " "the prompt after the persistent-policy check"
+ sendline {service mouse_echo stop}
+ await_exact "Stopping mouse_echo" "the runtime-only ports service stop"
+ await_exact "mouse:~# " "the prompt after the runtime stop"
+ poweroff
+ puts "C3 disabled ports-service check passed"
+ exit 0
+}
+
+sendline {sh -c 'test -L /run/openrc/runlevels/current/default/mouse_echo'; echo ENABLED_$status}
+await_exact "ENABLED_0\r" "the generated ports runlevel member"
+await_exact "mouse:~# " "the prompt after the enabled membership check"
+sendline {sh -c 'i=0; while [ "$i" -lt 4 ]; do child=`cat /run/openrc/options/mouse_echo/child_pid`; kill -KILL "$child"; i=$((i + 1)); sleep 1; done'; echo EXHAUST_$status}
+await_exact "EXHAUST_0\r" "four supervised ports-service crashes"
+await_exact "mouse:~# " "the prompt after exhausting respawns"
+sendline {service mouse_echo status; echo FAILED_STATUS_$status}
+await_exact "status: failed" "the failed ports-service status"
+await_exact "FAILED_STATUS_32\r" "the failed status exit code"
+await_exact "mouse:~# " "the prompt after failed status"
+sendline {service mouse_echo restart}
+await_exact "Starting mouse_echo" "administrative ports-service recovery"
+await_exact "mouse:~# " "the prompt after administrative recovery"
+sendline {set stubborn=`cat /run/openrc/options/mouse_echo/child_pid`; service mouse_echo stop}
+await_exact "Stopping mouse_echo" "the forced ports-service stop"
+await_exact "mouse:~# " "the prompt after the forced stop"
+sendline {sh -c 'test -f /run/mouse-echo.term'; echo TERM_SEEN_$status}
+await_exact "TERM_SEEN_0\r" "the ports daemon observing SIGTERM"
+await_exact "mouse:~# " "the prompt after the SIGTERM check"
+sendline {sh -c '! kill -0 "$1" 2>/dev/null' sh "$stubborn"; echo FORCED_GONE_$status}
+await_exact "FORCED_GONE_0\r" "SIGKILL escalation for the ports daemon"
+await_exact "mouse:~# " "the prompt after forced termination"
+sendline {service mouse_echo start}
+await_exact "Starting mouse_echo" "the final ports-service start"
+await_exact "mouse:~# " "the prompt after the final start"
+
+sendline {exit}
+await_exact "logout\r" "the first login session ending"
+await_exact "mouse login: root (automatic login)" "the respawned console login"
+await_exact "MOUSE 0.1.0 (x86_64)" "the respawned login banner"
+await_exact "mouse:~# " "the respawned tcsh prompt"
+sendline {sh -c 'for stat in /proc/[0-9]*/stat; do set -- $(cat "$stat"); test "$3" != Z || exit 1; done'; echo ZOMBIES_$status}
+await_exact "ZOMBIES_0\r" "the absence of zombie processes"
+await_exact "mouse:~# " "the prompt after the zombie check"
+
+sendline {kill -TERM 1}
+await_exact "cheesed: received poweroff request" "the poweroff request"
+await_exact "Stopping mouse_echo" "the ports service stopping first"
+await_exact "Stopping console_login" "the console service stopping after its dependent"
+await_exact "cheesed: OpenRC shutdown transition complete" "the shutdown transition"
+await_clean_exit "poweroff"
+
+puts "C3 enabled base-integration check passed"
scripts/check-c3.sh
Mode → 100755; object → 016204f88b15
@@ -1,0 +1,81 @@
+#!/bin/sh
+set -eu
+
+script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+repo_dir=$(dirname "$script_dir")
+build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
+image="$build_dir/mouse-initramfs.cpio.gz"
+
+for command in expect cpio gzip; do
+ if ! command -v "$command" >/dev/null 2>&1; then
+ printf '%s\n' "$command is required for the C3 integration check" >&2
+ exit 1
+ fi
+done
+
+if [ ! -f "$image" ]; then
+ printf '%s\n' "run 'make image' before checking MOUSE" >&2
+ exit 1
+fi
+
+temporary=$(mktemp -d "${TMPDIR:-/tmp}/mouse-c3-check.XXXXXX")
+trap 'rm -rf "$temporary"' EXIT HUP INT TERM
+base_root="$temporary/base"
+mkdir "$base_root"
+gzip -dc "$image" | (cd "$base_root" && cpio -id 2>/dev/null)
+
+pack_variant() {
+ name=$1
+ root=$2
+ output="$temporary/mouse-initramfs-$name.cpio.gz"
+ (
+ cd "$root"
+ find . -print |
+ LC_ALL=C sort |
+ cpio -o --format newc -R 0:0 2>/dev/null |
+ gzip -9
+ ) >"$output"
+ printf '%s\n' "$output"
+}
+
+run_variant() {
+ name=$1
+ root=$2
+ mode=$3
+ variant=$(pack_variant "$name" "$root")
+ MOUSE_INITRAMFS="$variant" \
+ "$script_dir/check-c3.exp" "$script_dir/run-qemu.sh" "$mode"
+}
+
+"$script_dir/check-c3.exp" "$script_dir/run-qemu.sh" enabled
+
+disabled_root="$temporary/disabled"
+cp -R "$base_root" "$disabled_root"
+printf '%s\n' \
+ '# Persistent MOUSE service policy belongs in this file.' \
+ 'enable_mouse_echo=NO' >"$disabled_root/etc/rc.conf"
+run_variant disabled "$disabled_root" disabled
+
+recovery_root="$temporary/recovery"
+cp -R "$base_root" "$recovery_root"
+install -m 0755 \
+ "$repo_dir/tests/fixtures/console_login_broken" \
+ "$recovery_root/etc/init.d/console_login"
+run_variant recovery "$recovery_root" recovery
+
+missing_root="$temporary/missing"
+cp -R "$base_root" "$missing_root"
+install -m 0755 \
+ "$repo_dir/tests/fixtures/mouse_echo_missing" \
+ "$missing_root/etc/init.d/mouse_echo"
+run_variant missing "$missing_root" missing
+
+cycle_root="$temporary/cycle"
+cp -R "$base_root" "$cycle_root"
+install -m 0755 \
+ "$repo_dir/tests/fixtures/mouse_echo_cycle" \
+ "$cycle_root/etc/init.d/mouse_echo"
+install -m 0755 \
+ "$repo_dir/tests/fixtures/cycle_peer" \
+ "$cycle_root/etc/init.d/cycle_peer"
+run_variant cycle "$cycle_root" cycle
sources.lock
Mode 100644 → 100644; object d178f4f1ef04 → 2920cd260135
@@ -16,3 +16,6 @@ 79721badcad1987dead9c3609eb4877ab9b58821c06bdacb824f2c8897c11f2a https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz pkgconf-2.5.1.tar.gz 897bc18b44afc26c70e78cead3dbb31e154acc24bee085a5a09079a88dbf6f52 https://mirrors.edge.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-2.77.tar.xz libcap-2.77.tar.xz f5bc2257f22ca7f0920a12698ad1e8f85a0f91a90e8e83106611514ea409b8ec https://github.com/OpenRC/openrc/archive/refs/tags/0.63.3.tar.gz openrc-0.63.3.tar.gz +03a05d3adf9602ef128f2da05b84b3205ce60c351e5737c0370f74000679ce8a https://www.kernel.org/pub/linux/utils/util-linux/v2.42/util-linux-2.42.2.tar.xz util-linux-2.42.2.tar.xz +71513a31c01a428bccd5367a32fd95f115d6dac50fb5b60c779d5c7942aec071 https://github.com/besser82/libxcrypt/releases/download/v4.5.2/libxcrypt-4.5.2.tar.xz libxcrypt-4.5.2.tar.xz +ce57a313e315a0a7cb04a8f50cc20753e994e487bbe9b78a2a824ca75cb486c0 https://github.com/shadow-maint/shadow/releases/download/4.19.4/shadow-4.19.4.tar.xz shadow-4.19.4.tar.xz
support/mouse-c2-daemon.c
Mode 100644 → ; object 81fea5db201d →
@@ -1,41 +1,0 @@
-#include <fcntl.h>
-#include <signal.h>
-#include <unistd.h>
-
-static const char ready_path[] = "/run/mouse-c2-daemon.ready";
-static const char term_path[] = "/run/mouse-c2-daemon.term";
-
-static void write_marker(const char *path, const char *contents, size_t length)
-{
- int descriptor = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
-
- if (descriptor == -1)
- return;
- (void)write(descriptor, contents, length);
- (void)close(descriptor);
-}
-
-static void handle_term(int signal_number)
-{
- static const char marker[] = "SIGTERM\n";
-
- (void)signal_number;
- write_marker(term_path, marker, sizeof(marker) - 1);
-}
-
-int main(void)
-{
- static const char marker[] = "ready\n";
- struct sigaction action = {
- .sa_handler = handle_term,
- };
-
- if (sigemptyset(&action.sa_mask) == -1)
- return 1;
- if (sigaction(SIGTERM, &action, NULL) == -1)
- return 1;
- write_marker(ready_path, marker, sizeof(marker) - 1);
-
- for (;;)
- pause();
-}
tests/fixtures/console_login_broken
Mode → 100644; object → c5fb7d794a02
@@ -1,0 +1,5 @@ +#!/sbin/openrc-run + +description="Broken required console fixture" +command=/usr/bin/missing-getty +
tests/fixtures/cycle_peer
Mode → 100644; object → a457b5655496
@@ -1,0 +1,12 @@
+#!/sbin/openrc-run
+
+description="Dependency cycle peer fixture"
+
+depend() {
+ need mouse_echo
+}
+
+start() {
+ checkpath --file /run/cycle-peer.unexpected
+}
+
tests/fixtures/mouse_echo_cycle
Mode → 100644; object → 45fc25eb057f
@@ -1,0 +1,12 @@
+#!/sbin/openrc-run
+
+description="Dependency cycle fixture"
+
+depend() {
+ need cycle_peer
+}
+
+start() {
+ checkpath --file /run/mouse-echo.unexpected
+}
+
tests/fixtures/mouse_echo_missing
Mode → 100644; object → 8cee1cc64853
@@ -1,0 +1,12 @@
+#!/sbin/openrc-run
+
+description="Missing dependency fixture"
+
+depend() {
+ need absent_port_dependency
+}
+
+start() {
+ checkpath --file /run/mouse-echo.unexpected
+}
+