init
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
# Apple Container requires this file; the wrapper already cleans the context.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
/build/
|
||||||
|
/base/cheesed/target/
|
||||||
|
.DS_Store
|
||||||
+301
@@ -0,0 +1,301 @@
|
|||||||
|
ARG MOUSE_ROOTFS_STAGE=rootfs-builder
|
||||||
|
|
||||||
|
FROM alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS toolchain
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
bc=1.08.2-r1 \
|
||||||
|
build-base=0.5-r4 \
|
||||||
|
clang22=22.1.3-r2 \
|
||||||
|
cmake=4.2.3-r0 \
|
||||||
|
curl=8.21.0-r0 \
|
||||||
|
diffutils=3.12-r0 \
|
||||||
|
elfutils-dev=0.195-r0 \
|
||||||
|
file=5.47-r2 \
|
||||||
|
findutils=4.10.0-r1 \
|
||||||
|
gawk=5.3.2-r2 \
|
||||||
|
lld22=22.1.3-r0 \
|
||||||
|
linux-headers=7.0.0-r1 \
|
||||||
|
llvm=22-r0 \
|
||||||
|
patch=2.8-r0 \
|
||||||
|
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
|
||||||
|
|
||||||
|
RUN apk add --no-cache flex=2.6.4-r8
|
||||||
|
|
||||||
|
ENV RUSTUP_HOME=/opt/rustup
|
||||||
|
ENV CARGO_HOME=/opt/cargo
|
||||||
|
ENV PATH=/opt/cargo/bin:/usr/lib/llvm22/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
RUN rustup-init -y \
|
||||||
|
--profile minimal \
|
||||||
|
--default-toolchain 1.94.0 \
|
||||||
|
--target x86_64-unknown-linux-musl \
|
||||||
|
&& chmod -R a+rX /opt/rustup /opt/cargo
|
||||||
|
|
||||||
|
WORKDIR /work/mouse-src
|
||||||
|
ARG MOUSE_BUILD_JOBS=8
|
||||||
|
ENV MOUSE_BUILD_JOBS=${MOUSE_BUILD_JOBS}
|
||||||
|
|
||||||
|
FROM scratch AS toolchain-sources
|
||||||
|
COPY mouse-sources/toolchain/ /opt/mouse-sources/
|
||||||
|
|
||||||
|
FROM scratch AS kernel-sources
|
||||||
|
COPY mouse-sources/kernel/ /opt/mouse-sources/
|
||||||
|
|
||||||
|
FROM scratch AS firmware-sources
|
||||||
|
COPY mouse-sources/firmware/ /opt/mouse-sources/
|
||||||
|
|
||||||
|
FROM scratch AS facilities-sources
|
||||||
|
COPY mouse-sources/facilities/ /opt/mouse-sources/
|
||||||
|
|
||||||
|
FROM scratch AS storage-sources
|
||||||
|
COPY mouse-sources/storage/ /opt/mouse-sources/
|
||||||
|
|
||||||
|
FROM scratch AS pkgsrc-sources
|
||||||
|
COPY mouse-sources/pkgsrc/ /opt/mouse-sources/
|
||||||
|
|
||||||
|
FROM scratch AS software-sources
|
||||||
|
COPY mouse-sources/software/ /opt/mouse-sources/
|
||||||
|
|
||||||
|
FROM --platform=linux/amd64 alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS pkgsrc-seed-tools
|
||||||
|
|
||||||
|
FROM toolchain AS kernel-builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache bison=3.8.2-r3
|
||||||
|
|
||||||
|
COPY --from=kernel-sources /opt/mouse-sources/ /opt/mouse-sources/
|
||||||
|
COPY mouse-src/base/kernel/ /work/mouse-src/base/kernel/
|
||||||
|
COPY mouse-src/scripts/build-kernel.sh /work/mouse-src/scripts/build-kernel.sh
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
/work/mouse-src/scripts/build-kernel.sh
|
||||||
|
|
||||||
|
FROM kernel-builder AS kernel-test-builder
|
||||||
|
|
||||||
|
COPY mouse-src/tests/fixtures/kernel-module/ /work/mouse-src/tests/fixtures/kernel-module/
|
||||||
|
COPY mouse-src/scripts/build-kernel-test-module.sh /work/mouse-src/scripts/build-kernel-test-module.sh
|
||||||
|
|
||||||
|
RUN /work/mouse-src/scripts/build-kernel-test-module.sh
|
||||||
|
|
||||||
|
# Keep the resource-heavy kernel and LLVM builds from running concurrently.
|
||||||
|
# The constant marker adds an ordering edge without passing kernel artifacts
|
||||||
|
# into the static-base cache key.
|
||||||
|
FROM kernel-builder AS kernel-ready
|
||||||
|
|
||||||
|
RUN install -m 0444 /dev/null /kernel-ready
|
||||||
|
|
||||||
|
FROM toolchain AS static-base-bootstrap
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
bison=3.8.2-r3 \
|
||||||
|
byacc=20260126-r0
|
||||||
|
|
||||||
|
COPY --from=kernel-ready /kernel-ready /tmp/kernel-ready
|
||||||
|
|
||||||
|
COPY --from=toolchain-sources /opt/mouse-sources/ /opt/mouse-sources/
|
||||||
|
COPY --from=kernel-sources /opt/mouse-sources/ /opt/mouse-sources/
|
||||||
|
COPY --from=software-sources /opt/mouse-sources/ /opt/mouse-sources/
|
||||||
|
COPY mouse-src/base/support/cross-compiler-driver /work/mouse-src/base/support/cross-compiler-driver
|
||||||
|
COPY mouse-src/base/support/rust-eh-personality.c /work/mouse-src/base/support/rust-eh-personality.c
|
||||||
|
COPY mouse-src/scripts/configure-cross-toolchain.sh /work/mouse-src/scripts/configure-cross-toolchain.sh
|
||||||
|
COPY mouse-src/scripts/static-base/common.sh /work/mouse-src/scripts/static-base/common.sh
|
||||||
|
COPY mouse-src/scripts/static-base/bootstrap.sh /work/mouse-src/scripts/static-base/bootstrap.sh
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
sh /work/mouse-src/scripts/static-base/bootstrap.sh
|
||||||
|
|
||||||
|
FROM static-base-bootstrap AS static-base-libraries
|
||||||
|
|
||||||
|
COPY mouse-src/scripts/static-base/libraries-and-accounts.sh /work/mouse-src/scripts/static-base/libraries-and-accounts.sh
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
sh /work/mouse-src/scripts/static-base/libraries-and-accounts.sh
|
||||||
|
|
||||||
|
FROM static-base-libraries AS static-base-toolchain
|
||||||
|
|
||||||
|
COPY mouse-src/scripts/static-base/native-toolchain.sh /work/mouse-src/scripts/static-base/native-toolchain.sh
|
||||||
|
|
||||||
|
RUN MOUSE_NATIVE_TOOLCHAIN_PHASE=llvm \
|
||||||
|
MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
sh /work/mouse-src/scripts/static-base/native-toolchain.sh
|
||||||
|
|
||||||
|
RUN MOUSE_NATIVE_TOOLCHAIN_PHASE=package-tools \
|
||||||
|
MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
sh /work/mouse-src/scripts/static-base/native-toolchain.sh
|
||||||
|
|
||||||
|
COPY mouse-src/scripts/static-base/dynamic-runtime.sh /work/mouse-src/scripts/static-base/dynamic-runtime.sh
|
||||||
|
|
||||||
|
RUN sh /work/mouse-src/scripts/static-base/dynamic-runtime.sh
|
||||||
|
|
||||||
|
FROM static-base-toolchain AS static-base-system
|
||||||
|
|
||||||
|
COPY mouse-src/base/patches/chimerautils-native-cdefs.patch /work/mouse-src/base/patches/chimerautils-native-cdefs.patch
|
||||||
|
COPY mouse-src/base/patches/chimerautils-scanflike.patch /work/mouse-src/base/patches/chimerautils-scanflike.patch
|
||||||
|
COPY mouse-src/base/patches/tcsh-gethost-native.patch /work/mouse-src/base/patches/tcsh-gethost-native.patch
|
||||||
|
COPY mouse-src/scripts/static-base/system-services.sh /work/mouse-src/scripts/static-base/system-services.sh
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
sh /work/mouse-src/scripts/static-base/system-services.sh
|
||||||
|
|
||||||
|
FROM static-base-system AS static-base-facilities
|
||||||
|
|
||||||
|
COPY --from=facilities-sources /opt/mouse-sources/ /opt/mouse-sources/
|
||||||
|
COPY mouse-src/base/patches/nftables-posix-configure.patch /work/mouse-src/base/patches/nftables-posix-configure.patch
|
||||||
|
COPY mouse-src/scripts/static-base/facilities.sh /work/mouse-src/scripts/static-base/facilities.sh
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
sh /work/mouse-src/scripts/static-base/facilities.sh
|
||||||
|
|
||||||
|
FROM static-base-facilities AS static-base-storage
|
||||||
|
|
||||||
|
COPY --from=storage-sources /opt/mouse-sources/ /opt/mouse-sources/
|
||||||
|
COPY mouse-src/scripts/static-base/storage.sh /work/mouse-src/scripts/static-base/storage.sh
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
sh /work/mouse-src/scripts/static-base/storage.sh
|
||||||
|
|
||||||
|
FROM static-base-storage AS static-base-builder
|
||||||
|
|
||||||
|
COPY mouse-src/scripts/static-base/finish.sh /work/mouse-src/scripts/static-base/finish.sh
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
sh /work/mouse-src/scripts/static-base/finish.sh
|
||||||
|
|
||||||
|
FROM --platform=linux/amd64 scratch AS pkgsrc-seed-builder
|
||||||
|
|
||||||
|
ARG MOUSE_BUILD_JOBS=8
|
||||||
|
|
||||||
|
COPY --from=static-base-builder /work/mouse-src/build/static/rootfs /
|
||||||
|
COPY --from=pkgsrc-sources /opt/mouse-sources/pkgsrc-2026Q2.tar.gz /usr/share/mouse/pkgsrc-2026Q2.tar.gz
|
||||||
|
COPY mouse-src/base/rootfs/etc/group /etc/group
|
||||||
|
COPY mouse-src/base/rootfs/etc/mouse-release /etc/mouse-release
|
||||||
|
COPY mouse-src/base/rootfs/etc/passwd /etc/passwd
|
||||||
|
COPY mouse-src/base/patches/pkgsrc-bootstrap-mouse.patch /usr/share/mouse/pkgsrc-bootstrap-mouse.patch
|
||||||
|
COPY mouse-src/scripts/build-pkgsrc-seed.sh /usr/libexec/mouse-build-pkgsrc-seed
|
||||||
|
COPY --from=pkgsrc-seed-tools /bin/busybox /usr/libexec/mouse-build-busybox
|
||||||
|
|
||||||
|
RUN MOUSE_BUILD_JOBS="$MOUSE_BUILD_JOBS" \
|
||||||
|
sh /usr/libexec/mouse-build-pkgsrc-seed
|
||||||
|
|
||||||
|
FROM toolchain AS btrfs-image-builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
mtools=4.0.49-r0 \
|
||||||
|
pkgconf=2.5.1-r0 \
|
||||||
|
sfdisk=2.42.1-r0 \
|
||||||
|
util-linux-dev=2.42.1-r0 \
|
||||||
|
zlib-dev=1.3.2-r0 \
|
||||||
|
zstd-dev=1.5.7-r2
|
||||||
|
|
||||||
|
COPY --from=storage-sources /opt/mouse-sources/btrfs-progs-v7.0.tar.xz /opt/
|
||||||
|
COPY --from=storage-sources /opt/mouse-sources/limine-binary-12.3.2.tar.xz /opt/mouse-sources/limine-binary-12.3.2.tar.xz
|
||||||
|
|
||||||
|
RUN tar -C /work -xf /opt/btrfs-progs-v7.0.tar.xz \
|
||||||
|
&& cd /work/btrfs-progs-v7.0 \
|
||||||
|
&& ./configure \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-backtrace \
|
||||||
|
--disable-documentation \
|
||||||
|
--disable-convert \
|
||||||
|
--disable-lzo \
|
||||||
|
--disable-libudev \
|
||||||
|
--disable-python \
|
||||||
|
--disable-zoned \
|
||||||
|
--with-crypto=builtin \
|
||||||
|
&& make \
|
||||||
|
-j"$MOUSE_BUILD_JOBS" \
|
||||||
|
mkfs.btrfs \
|
||||||
|
&& install -m 0755 \
|
||||||
|
/work/btrfs-progs-v7.0/mkfs.btrfs \
|
||||||
|
/opt/mkfs.btrfs
|
||||||
|
|
||||||
|
FROM static-base-builder AS image-commands-builder
|
||||||
|
|
||||||
|
COPY mouse-src/base/support/mouse-bootenv /work/mouse-src/base/support/mouse-bootenv
|
||||||
|
COPY mouse-src/scripts/audit-static-base.sh /work/mouse-src/scripts/audit-static-base.sh
|
||||||
|
COPY mouse-src/scripts/build-image-commands.sh /work/mouse-src/scripts/build-image-commands.sh
|
||||||
|
|
||||||
|
RUN /work/mouse-src/scripts/build-image-commands.sh \
|
||||||
|
&& /work/mouse-src/scripts/audit-static-base.sh /work/mouse-src/build/static/rootfs
|
||||||
|
|
||||||
|
FROM image-commands-builder AS test-fixtures-builder
|
||||||
|
|
||||||
|
COPY mouse-src/tests/fixtures/mouse-echo/mouse-echo.c /work/mouse-src/tests/fixtures/mouse-echo/mouse-echo.c
|
||||||
|
|
||||||
|
RUN /work/mouse-src/build/static/cross-tools/bin/x86_64-mouse-linux-musl-cc \
|
||||||
|
-O2 -Wall -Wextra -Werror \
|
||||||
|
/work/mouse-src/tests/fixtures/mouse-echo/mouse-echo.c \
|
||||||
|
-o /opt/mouse-echo
|
||||||
|
|
||||||
|
FROM image-commands-builder AS cheesed-builder
|
||||||
|
|
||||||
|
COPY mouse-src/base/cheesed/ /work/mouse-src/base/cheesed/
|
||||||
|
|
||||||
|
RUN CHEESED_TARGET=x86_64-unknown-linux-musl \
|
||||||
|
/work/mouse-src/base/cheesed/scripts/build-static.sh \
|
||||||
|
&& install -m 0755 \
|
||||||
|
/work/mouse-src/base/cheesed/target/x86_64-unknown-linux-musl/release/cheesed \
|
||||||
|
/opt/cheesed
|
||||||
|
|
||||||
|
FROM btrfs-image-builder AS rootfs-common
|
||||||
|
|
||||||
|
COPY --from=cheesed-builder /work/mouse-src/build/static/rootfs /work/mouse-src/build/static/rootfs
|
||||||
|
COPY --from=cheesed-builder /opt/cheesed /opt/cheesed
|
||||||
|
COPY --from=kernel-builder /work/mouse-src/build/vmlinuz-virt /work/mouse-src/build/vmlinuz-virt
|
||||||
|
COPY --from=pkgsrc-seed-builder /usr/local /opt/mouse-pkgsrc-seed/usr/local
|
||||||
|
COPY --from=pkgsrc-seed-builder /var/db/pkg /opt/mouse-pkgsrc-seed/var/db/pkg
|
||||||
|
COPY mouse-src/base/rootfs/ /work/mouse-src/base/rootfs/
|
||||||
|
COPY mouse-src/base/patches/pkgsrc-rc.subr-cgroup.patch /work/mouse-src/base/patches/pkgsrc-rc.subr-cgroup.patch
|
||||||
|
COPY mouse-src/scripts/audit-static-base.sh /work/mouse-src/scripts/audit-static-base.sh
|
||||||
|
COPY mouse-src/scripts/build-image.sh /work/mouse-src/scripts/build-image.sh
|
||||||
|
COPY mouse-src/scripts/build-boot-disk.sh /work/mouse-src/scripts/build-boot-disk.sh
|
||||||
|
COPY mouse-src/base/limine.conf /work/mouse-src/base/limine.conf
|
||||||
|
|
||||||
|
FROM rootfs-common AS rootfs-builder
|
||||||
|
|
||||||
|
COPY --from=kernel-builder /work/mouse-src/build/kernel/modules /opt/mouse-kernel-modules
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
MOUSE_CHEESED_BINARY=/opt/cheesed \
|
||||||
|
MOUSE_BTRFS_IMAGE_TOOL=/opt/mkfs.btrfs \
|
||||||
|
MOUSE_PKGSRC_SEED=/opt/mouse-pkgsrc-seed \
|
||||||
|
MOUSE_KERNEL_MODULES=/opt/mouse-kernel-modules \
|
||||||
|
/work/mouse-src/scripts/build-image.sh
|
||||||
|
|
||||||
|
FROM rootfs-common AS rootfs-test-builder
|
||||||
|
|
||||||
|
COPY --from=kernel-test-builder /work/mouse-src/build/kernel/test-modules /opt/mouse-kernel-modules
|
||||||
|
COPY --from=test-fixtures-builder /opt/mouse-echo /opt/mouse-echo
|
||||||
|
COPY mouse-src/tests/fixtures/ /work/mouse-src/tests/fixtures/
|
||||||
|
|
||||||
|
RUN MOUSE_SOURCE_DIR=/opt/mouse-sources \
|
||||||
|
MOUSE_CHEESED_BINARY=/opt/cheesed \
|
||||||
|
MOUSE_BTRFS_IMAGE_TOOL=/opt/mkfs.btrfs \
|
||||||
|
MOUSE_PKGSRC_SEED=/opt/mouse-pkgsrc-seed \
|
||||||
|
MOUSE_KERNEL_MODULES=/opt/mouse-kernel-modules \
|
||||||
|
MOUSE_MOUSE_ECHO_BINARY=/opt/mouse-echo \
|
||||||
|
MOUSE_INCLUDE_TEST_FIXTURES=1 \
|
||||||
|
/work/mouse-src/scripts/build-image.sh
|
||||||
|
|
||||||
|
FROM ${MOUSE_ROOTFS_STAGE} AS selected-rootfs
|
||||||
|
|
||||||
|
FROM toolchain AS artifact-packer
|
||||||
|
|
||||||
|
COPY --from=selected-rootfs /work/mouse-src/build/mouse-disk.img /
|
||||||
|
COPY --from=firmware-sources /opt/mouse-sources/ovmf-0.0.202605-r0.apk /opt/
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/ovmf \
|
||||||
|
&& tar -xf /opt/ovmf-0.0.202605-r0.apk \
|
||||||
|
-C /opt/ovmf \
|
||||||
|
usr/share/OVMF/OVMF_CODE.fd \
|
||||||
|
&& gzip -1 /mouse-disk.img
|
||||||
|
|
||||||
|
FROM scratch AS artifacts
|
||||||
|
|
||||||
|
COPY --from=kernel-builder /work/mouse-src/build/vmlinuz-virt /
|
||||||
|
COPY --from=artifact-packer /mouse-disk.img.gz /
|
||||||
|
COPY --from=artifact-packer /opt/ovmf/usr/share/OVMF/OVMF_CODE.fd /ovmf-code.fd
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
.PHONY: all check check-locks clean fetch image run
|
||||||
|
|
||||||
|
MOUSE_BUILDER_CPUS ?= 8
|
||||||
|
MOUSE_BUILDER_MEMORY ?= 8G
|
||||||
|
MOUSE_BUILD_JOBS ?= 8
|
||||||
|
MOUSE_BOOT_MODE ?= firmware
|
||||||
|
MOUSE_NETWORK ?= 1
|
||||||
|
|
||||||
|
BUILD_ENV = \
|
||||||
|
MOUSE_BUILDER_CPUS="$(MOUSE_BUILDER_CPUS)" \
|
||||||
|
MOUSE_BUILDER_MEMORY="$(MOUSE_BUILDER_MEMORY)" \
|
||||||
|
MOUSE_BUILD_JOBS="$(MOUSE_BUILD_JOBS)"
|
||||||
|
|
||||||
|
CHECKS = boot firmware world service-integration storage btrfs network \
|
||||||
|
multi-user toolchain pkgsrc
|
||||||
|
|
||||||
|
all: image
|
||||||
|
|
||||||
|
check-locks:
|
||||||
|
./scripts/check-source-locks.sh
|
||||||
|
|
||||||
|
fetch: check-locks
|
||||||
|
./scripts/fetch-sources.sh
|
||||||
|
|
||||||
|
image: check-locks
|
||||||
|
$(BUILD_ENV) ./scripts/build-in-container.sh
|
||||||
|
|
||||||
|
run:
|
||||||
|
MOUSE_BOOT_MODE="$(MOUSE_BOOT_MODE)" \
|
||||||
|
MOUSE_NETWORK="$(MOUSE_NETWORK)" \
|
||||||
|
./scripts/run-qemu.sh
|
||||||
|
|
||||||
|
check: check-locks
|
||||||
|
$(BUILD_ENV) MOUSE_INCLUDE_TEST_FIXTURES=1 \
|
||||||
|
./scripts/build-in-container.sh
|
||||||
|
@set -e; for check in $(CHECKS); do ./tests/acceptance/$$check.sh; done
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f build/vmlinuz-virt \
|
||||||
|
build/mouse-disk.img build/ovmf-code.fd \
|
||||||
|
build/mouse-rootfs.btrfs
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# MOUSE
|
||||||
|
|
||||||
|
MOUSE is the **Monolithic Opinionated Unix-like System Environment**: a small
|
||||||
|
Linux system with a versioned base, pkgsrc for third-party software, and
|
||||||
|
`cheesed` as PID 1.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
The build needs Apple Container, Docker, or Podman, plus QEMU and Expect for
|
||||||
|
the runtime checks.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make image
|
||||||
|
make run
|
||||||
|
make check
|
||||||
|
```
|
||||||
|
|
||||||
|
Artifacts and downloaded sources are kept in `build/`. The default builder has
|
||||||
|
8 CPUs, 8 GB of memory, and 8 build jobs; override those with
|
||||||
|
`MOUSE_BUILDER_CPUS`, `MOUSE_BUILDER_MEMORY`, and `MOUSE_BUILD_JOBS`.
|
||||||
|
`MOUSE_NETWORK=0 make run` disables QEMU networking.
|
||||||
|
|
||||||
|
Development images contain `root` and `toor`, both with the password
|
||||||
|
`cheddar`. `root` uses `/bin/sh` for emergency administration; `toor` uses
|
||||||
|
`/bin/tcsh` for ordinary administration. Change both passwords before exposing
|
||||||
|
an image to a network.
|
||||||
|
|
||||||
|
## System
|
||||||
|
|
||||||
|
The base owns `/bin`, `/sbin`, `/usr/bin`, and `/usr/sbin`; pkgsrc owns
|
||||||
|
`/usr/local`. `/var`, `/home`, `/srv`, and `/usr/local` are shared by both root
|
||||||
|
environments, `/etc` follows the selected root, and `/run` is rebuilt at boot.
|
||||||
|
The fixed `/usr/local` package prefix keeps pkgsrc out of base-owned paths.
|
||||||
|
|
||||||
|
Limine boots the default or alternate root from the same disk.
|
||||||
|
`mouse-bootenv prepare` snapshots the running root into the inactive one;
|
||||||
|
`activate` selects it and `rollback` selects the other root. Automated base
|
||||||
|
updates and configuration merging are not implemented yet.
|
||||||
|
|
||||||
|
`cheesed` mounts early filesystems, reaps children, supervises services in
|
||||||
|
cgroups, and handles shutdown. It identifies itself as
|
||||||
|
`Cheesed to meet you! v0.1.0`. Service definitions live in `/etc/rc.d` and
|
||||||
|
`/usr/local/etc/rc.d`; dependency cycles, missing facilities, and duplicate
|
||||||
|
names are rejected.
|
||||||
|
|
||||||
|
Persistent service policy lives only in `/etc/rc.conf`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
enable_network=YES
|
||||||
|
enable_cron=YES
|
||||||
|
enable_ntpd=YES
|
||||||
|
```
|
||||||
|
|
||||||
|
Assignments are exactly `enable_NAME=YES` or `enable_NAME=NO`; shell expansion,
|
||||||
|
includes, and other keys are invalid. `/sbin/service` controls the running
|
||||||
|
system without editing boot policy.
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
|
||||||
|
Images contain the pinned pkgsrc 2026Q2 tree in `/usr/local/src/pkgsrc`, with
|
||||||
|
`bmake` and the package tools under `/usr/local`. Packages use
|
||||||
|
`/usr/local/etc` for configuration, `/var/db/pkg` for records, and
|
||||||
|
`/var/cache/pkgsrc` for distfiles and binary packages. This state survives a
|
||||||
|
base rollback.
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
`make check` builds the assembled disk with test fixtures, then boots it under
|
||||||
|
QEMU. It covers BIOS and UEFI boot, both Btrfs roots, services and cgroup
|
||||||
|
cleanup, users, networking, storage tools, native C and C++ compilation, pkgsrc
|
||||||
|
installation, and persistence across reboots. The build also rejects changed
|
||||||
|
source checksums and dynamic base executables.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[target.x86_64-unknown-linux-musl]
|
||||||
|
linker = "rust-lld"
|
||||||
Generated
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cheesed"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "cheesed"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.85"
|
||||||
|
description = "MOUSE PID 1 and service supervisor"
|
||||||
|
license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "=0.2.189"
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unsafe_op_in_unsafe_fn = "deny"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
all = "deny"
|
||||||
|
pedantic = "deny"
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
target=${CHEESED_TARGET:-x86_64-unknown-linux-musl}
|
||||||
|
|
||||||
|
if ! rustup target list --installed | grep -qx "$target"; then
|
||||||
|
printf '%s\n' "missing Rust target: $target" >&2
|
||||||
|
printf '%s\n' "install it with: rustup target add $target" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$repo_dir"
|
||||||
|
cargo build --locked --release --target "$target"
|
||||||
|
)
|
||||||
|
binary="$repo_dir/target/$target/release/cheesed"
|
||||||
|
|
||||||
|
if ! file "$binary" | grep -Eq 'statically linked|static-pie linked'; then
|
||||||
|
printf '%s\n' "release binary is not statically linked: $binary" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$binary"
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
//! Core process model for the MOUSE init and service supervisor.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", test))]
|
||||||
|
mod policy;
|
||||||
|
|
||||||
|
/// The first message emitted after PID 1 bootstrap succeeds.
|
||||||
|
pub const STARTUP_BANNER: &str = concat!("Cheesed to meet you! v", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
|
/// The terminal system action requested from PID 1.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum ShutdownAction {
|
||||||
|
Reboot,
|
||||||
|
Poweroff,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ShutdownAction {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Reboot => formatter.write_str("reboot"),
|
||||||
|
Self::Poweroff => formatter.write_str("poweroff"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps the two shutdown signals accepted by PID 1.
|
||||||
|
#[must_use]
|
||||||
|
pub fn shutdown_action(signal: i32) -> Option<ShutdownAction> {
|
||||||
|
match signal {
|
||||||
|
libc::SIGINT => Some(ShutdownAction::Reboot),
|
||||||
|
libc::SIGTERM => Some(ShutdownAction::Poweroff),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod linux;
|
||||||
|
|
||||||
|
/// Runs `cheesed` in its normal PID 1 mode.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if invoked outside PID 1. Once PID 1 initialization starts,
|
||||||
|
/// failures are logged and kept alive rather than returned.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn run() -> Result<(), String> {
|
||||||
|
linux::run()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rejects normal init mode on non-Linux development hosts.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Always returns an error because PID 1 mode requires Linux.
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
pub fn run() -> Result<(), String> {
|
||||||
|
Err("cheesed PID 1 mode is supported only on Linux".to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{STARTUP_BANNER, ShutdownAction, shutdown_action};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn startup_banner_includes_the_package_version() {
|
||||||
|
assert_eq!(STARTUP_BANNER, "Cheesed to meet you! v0.1.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sigint_requests_reboot() {
|
||||||
|
assert_eq!(shutdown_action(libc::SIGINT), Some(ShutdownAction::Reboot));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sigterm_requests_poweroff() {
|
||||||
|
assert_eq!(
|
||||||
|
shutdown_action(libc::SIGTERM),
|
||||||
|
Some(ShutdownAction::Poweroff)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unrelated_signal_is_not_a_shutdown_request() {
|
||||||
|
assert_eq!(shutdown_action(libc::SIGCHLD), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
mod boot;
|
||||||
|
mod process;
|
||||||
|
mod services;
|
||||||
|
|
||||||
|
use self::boot::{configure_hostname, mount_early_filesystems, mount_mouse_storage};
|
||||||
|
use self::process::{
|
||||||
|
child_exited, install_signal_handlers, perform_shutdown, reap_children, spawn_emergency_shell,
|
||||||
|
take_shutdown_action,
|
||||||
|
};
|
||||||
|
use self::services::{ServiceManager, boot_services};
|
||||||
|
use crate::STARTUP_BANNER;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const CONSOLE_PATH: &str = "/dev/console";
|
||||||
|
const POLL_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
const SHELL_RESTART_DELAY: Duration = Duration::from_secs(1);
|
||||||
|
|
||||||
|
pub(crate) fn run() -> Result<(), String> {
|
||||||
|
// SAFETY: getpid has no preconditions and does not mutate memory.
|
||||||
|
if unsafe { libc::getpid() } != 1 {
|
||||||
|
return Err("normal mode must run as PID 1".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
run_pid1()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_pid1() -> ! {
|
||||||
|
let mut console = loop {
|
||||||
|
match Console::open() {
|
||||||
|
Ok(console) => break console,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("cheesed: cannot open {CONSOLE_PATH} for early boot logging: {error}");
|
||||||
|
thread::sleep(SHELL_RESTART_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: cheesed is single-threaded here and passes a conventional process umask.
|
||||||
|
unsafe {
|
||||||
|
libc::umask(0o022);
|
||||||
|
}
|
||||||
|
if let Err(error) = std::env::set_current_dir("/") {
|
||||||
|
remain_alive_after_fatal_error(&mut console, &format!("cannot chdir to /: {error}"));
|
||||||
|
}
|
||||||
|
if let Err(error) = install_signal_handlers() {
|
||||||
|
remain_alive_after_fatal_error(
|
||||||
|
&mut console,
|
||||||
|
&format!("cannot install signal handlers: {error}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Err(error) = mount_early_filesystems() {
|
||||||
|
remain_alive_after_fatal_error(
|
||||||
|
&mut console,
|
||||||
|
&format!("cannot mount early filesystems: {error}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Err(error) = mount_mouse_storage() {
|
||||||
|
remain_alive_after_fatal_error(
|
||||||
|
&mut console,
|
||||||
|
&format!("cannot mount MOUSE storage: {error}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Err(error) = configure_hostname() {
|
||||||
|
remain_alive_after_fatal_error(
|
||||||
|
&mut console,
|
||||||
|
&format!("cannot configure hostname: {error}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.write_line(STARTUP_BANNER);
|
||||||
|
console.log("base bootstrap complete; cheesed is PID 1");
|
||||||
|
let mut services = None;
|
||||||
|
let emergency = match boot_services(&mut console) {
|
||||||
|
Ok(manager) => {
|
||||||
|
services = Some(manager);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
console.log(&format!("{error}; entering emergency console"));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let shell_pid = if emergency {
|
||||||
|
match spawn_emergency_shell(&mut console) {
|
||||||
|
Ok(pid) => Some(pid),
|
||||||
|
Err(error) => {
|
||||||
|
console.log(&error);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
steady_state(&mut console, services, emergency, shell_pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn steady_state(
|
||||||
|
console: &mut Console,
|
||||||
|
mut services: Option<ServiceManager>,
|
||||||
|
emergency: bool,
|
||||||
|
mut shell_pid: Option<libc::pid_t>,
|
||||||
|
) -> ! {
|
||||||
|
loop {
|
||||||
|
if child_exited() {
|
||||||
|
let shell_exited = reap_children(console, shell_pid);
|
||||||
|
if shell_exited {
|
||||||
|
shell_pid = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(manager) = services.as_mut() {
|
||||||
|
manager.poll(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(action) = take_shutdown_action() {
|
||||||
|
perform_shutdown(console, shell_pid, services.as_mut(), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if emergency && shell_pid.is_none() {
|
||||||
|
console.log("emergency shell exited; restarting it in one second");
|
||||||
|
thread::sleep(SHELL_RESTART_DELAY);
|
||||||
|
shell_pid = Some(match spawn_emergency_shell(console) {
|
||||||
|
Ok(pid) => pid,
|
||||||
|
Err(error) => {
|
||||||
|
console.log(&format!("cannot restart emergency shell: {error}"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(POLL_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remain_alive_after_fatal_error(console: &mut Console, error: &str) -> ! {
|
||||||
|
console.log(error);
|
||||||
|
console.log("unrecoverable bootstrap failure; PID 1 will remain alive");
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_secs(60));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Console {
|
||||||
|
file: File,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Console {
|
||||||
|
fn open() -> io::Result<Self> {
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(CONSOLE_PATH)?;
|
||||||
|
Ok(Self { file })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&mut self, message: &str) {
|
||||||
|
self.write_line(&format!("cheesed: {message}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_line(&mut self, message: &str) {
|
||||||
|
let _ = writeln!(self.file, "{message}");
|
||||||
|
let _ = self.file.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_file(&self) -> io::Result<File> {
|
||||||
|
self.file.try_clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
const HOSTNAME_PATH: &str = "/etc/hostname";
|
||||||
|
const CGROUP_ROOT: &str = "/sys/fs/cgroup";
|
||||||
|
const MOUSE_ROOT_PARTUUID: &str = "4d4f5553-4500-4000-8000-000000000002";
|
||||||
|
const BTRFS_SUPER_MAGIC: u64 = 0x9123_683e;
|
||||||
|
|
||||||
|
pub(super) fn mount_early_filesystems() -> io::Result<()> {
|
||||||
|
mount_filesystem(c"devtmpfs".as_ptr(), c"/dev".as_ptr(), c"devtmpfs".as_ptr())?;
|
||||||
|
mount_filesystem(c"proc".as_ptr(), c"/proc".as_ptr(), c"proc".as_ptr())?;
|
||||||
|
mount_filesystem(c"sysfs".as_ptr(), c"/sys".as_ptr(), c"sysfs".as_ptr())?;
|
||||||
|
std::fs::create_dir_all(CGROUP_ROOT)?;
|
||||||
|
mount_filesystem(
|
||||||
|
c"cgroup2".as_ptr(),
|
||||||
|
c"/sys/fs/cgroup".as_ptr(),
|
||||||
|
c"cgroup2".as_ptr(),
|
||||||
|
)?;
|
||||||
|
mount_filesystem(c"tmpfs".as_ptr(), c"/run".as_ptr(), c"tmpfs".as_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_filesystem(
|
||||||
|
source: *const libc::c_char,
|
||||||
|
target: *const libc::c_char,
|
||||||
|
filesystem_type: *const libc::c_char,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
// SAFETY: all pointers are valid, static, NUL-terminated C strings; no data
|
||||||
|
// argument is required for these pseudo-filesystems.
|
||||||
|
if unsafe {
|
||||||
|
libc::mount(
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
filesystem_type,
|
||||||
|
0,
|
||||||
|
std::ptr::null::<libc::c_void>(),
|
||||||
|
)
|
||||||
|
} == -1
|
||||||
|
{
|
||||||
|
let error = io::Error::last_os_error();
|
||||||
|
if error.raw_os_error() != Some(libc::EBUSY) {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mount_mouse_storage() -> io::Result<()> {
|
||||||
|
let device = match mouse_root_device() {
|
||||||
|
Ok(device) => device,
|
||||||
|
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let device = std::ffi::CString::new(device.as_os_str().as_bytes())
|
||||||
|
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||||
|
|
||||||
|
std::fs::create_dir_all("/run/mouse-root")?;
|
||||||
|
mount_btrfs(&device, c"/run/mouse-root", c"subvolid=5")?;
|
||||||
|
|
||||||
|
if root_is_btrfs()? {
|
||||||
|
for (target, subvolume) in [
|
||||||
|
(c"/usr/local", c"subvol=usr-local"),
|
||||||
|
(c"/var", c"subvol=var"),
|
||||||
|
(c"/home", c"subvol=home"),
|
||||||
|
(c"/srv", c"subvol=srv"),
|
||||||
|
] {
|
||||||
|
mount_btrfs(&device, target, subvolume)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_root_device() -> io::Result<PathBuf> {
|
||||||
|
let expected = format!("PARTUUID={MOUSE_ROOT_PARTUUID}");
|
||||||
|
for entry in std::fs::read_dir("/sys/class/block")? {
|
||||||
|
let entry = entry?;
|
||||||
|
let Ok(uevent) = std::fs::read_to_string(entry.path().join("uevent")) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if uevent.lines().any(|line| line == expected) {
|
||||||
|
let device = Path::new("/dev").join(entry.file_name());
|
||||||
|
if device.exists() {
|
||||||
|
return Ok(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::NotFound,
|
||||||
|
format!("MOUSE root partition {MOUSE_ROOT_PARTUUID} is not present"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_btrfs(
|
||||||
|
device: &std::ffi::CStr,
|
||||||
|
target: &std::ffi::CStr,
|
||||||
|
options: &std::ffi::CStr,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
// SAFETY: all pointers reference NUL-terminated strings for the duration
|
||||||
|
// of the call. The btrfs device and subvolume layout are fixed MOUSE image
|
||||||
|
// contracts rather than discovered mount policy.
|
||||||
|
if unsafe {
|
||||||
|
libc::mount(
|
||||||
|
device.as_ptr(),
|
||||||
|
target.as_ptr(),
|
||||||
|
c"btrfs".as_ptr(),
|
||||||
|
0,
|
||||||
|
options.as_ptr().cast::<libc::c_void>(),
|
||||||
|
)
|
||||||
|
} == -1
|
||||||
|
{
|
||||||
|
let error = io::Error::last_os_error();
|
||||||
|
if error.raw_os_error() != Some(libc::EBUSY) {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root_is_btrfs() -> io::Result<bool> {
|
||||||
|
// SAFETY: status points to writable storage and the path is a static,
|
||||||
|
// NUL-terminated string.
|
||||||
|
let mut status: libc::statfs = unsafe { std::mem::zeroed() };
|
||||||
|
if unsafe { libc::statfs(c"/".as_ptr(), &raw mut status) } == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
Ok(status.f_type as u64 == BTRFS_SUPER_MAGIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn configure_hostname() -> io::Result<()> {
|
||||||
|
let hostname = std::fs::read_to_string(HOSTNAME_PATH)?;
|
||||||
|
let hostname = hostname.trim();
|
||||||
|
if hostname.is_empty() || hostname.as_bytes().contains(&0) {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!("{HOSTNAME_PATH} must contain a non-empty hostname"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: hostname points to readable bytes for the supplied length, and
|
||||||
|
// sethostname does not require a terminating NUL.
|
||||||
|
if unsafe { libc::sethostname(hostname.as_ptr().cast::<libc::c_char>(), hostname.len()) } == -1
|
||||||
|
{
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
use super::services::ServiceManager;
|
||||||
|
use super::{Console, POLL_INTERVAL};
|
||||||
|
use crate::{ShutdownAction, shutdown_action};
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io;
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
use std::os::unix::process::{CommandExt, ExitStatusExt};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Command, ExitStatus, Stdio};
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
const EMERGENCY_SHELL: &str = "/bin/tcsh";
|
||||||
|
pub(super) const TERMINATION_GRACE: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
|
static CHILD_EXITED: AtomicBool = AtomicBool::new(false);
|
||||||
|
static SHUTDOWN_SIGNAL: AtomicI32 = AtomicI32::new(0);
|
||||||
|
|
||||||
|
extern "C" fn handle_signal(signal: libc::c_int) {
|
||||||
|
if signal == libc::SIGCHLD {
|
||||||
|
CHILD_EXITED.store(true, Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
let _ = SHUTDOWN_SIGNAL.compare_exchange(0, signal, Ordering::Relaxed, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn child_exited() -> bool {
|
||||||
|
CHILD_EXITED.swap(false, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn install_signal_handlers() -> io::Result<()> {
|
||||||
|
for signal in [libc::SIGCHLD, libc::SIGINT, libc::SIGTERM] {
|
||||||
|
// SAFETY: zero is a valid initial representation for sigaction before its
|
||||||
|
// fields and mask are initialized below.
|
||||||
|
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
|
||||||
|
action.sa_sigaction = handle_signal as *const () as usize;
|
||||||
|
action.sa_flags = libc::SA_RESTART;
|
||||||
|
if signal == libc::SIGCHLD {
|
||||||
|
action.sa_flags |= libc::SA_NOCLDSTOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: action owns a valid sigset_t and the signal number is known.
|
||||||
|
if unsafe { libc::sigemptyset(&raw mut action.sa_mask) } == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
// SAFETY: action remains alive for the call and the old action is not needed.
|
||||||
|
if unsafe { libc::sigaction(signal, &raw const action, std::ptr::null_mut()) } == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) enum CommandResult {
|
||||||
|
Exited(ExitStatus),
|
||||||
|
Shutdown(ShutdownAction),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn command_with_console(
|
||||||
|
program: &str,
|
||||||
|
arguments: &[&str],
|
||||||
|
console: &mut Console,
|
||||||
|
timeout: Duration,
|
||||||
|
service_cgroup: Option<&Path>,
|
||||||
|
) -> Result<CommandResult, String> {
|
||||||
|
let stdin = console
|
||||||
|
.clone_file()
|
||||||
|
.map_err(|error| format!("cannot duplicate console input: {error}"))?;
|
||||||
|
let stdout = console
|
||||||
|
.clone_file()
|
||||||
|
.map_err(|error| format!("cannot duplicate console output: {error}"))?;
|
||||||
|
let stderr = console
|
||||||
|
.clone_file()
|
||||||
|
.map_err(|error| format!("cannot duplicate console error output: {error}"))?;
|
||||||
|
let mut command = Command::new(program);
|
||||||
|
command
|
||||||
|
.args(arguments)
|
||||||
|
.env("PATH", "/sbin:/bin:/usr/sbin:/usr/bin")
|
||||||
|
.stdin(Stdio::from(stdin))
|
||||||
|
.stdout(Stdio::from(stdout))
|
||||||
|
.stderr(Stdio::from(stderr));
|
||||||
|
let cgroup_procs = service_cgroup
|
||||||
|
.map(|cgroup| {
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.open(cgroup.join("cgroup.procs"))
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
.map_err(|error| format!("cannot open service cgroup: {error}"))?;
|
||||||
|
if let Some(cgroup) = service_cgroup {
|
||||||
|
command.env("MOUSE_SERVICE_CGROUP", cgroup);
|
||||||
|
command.env("MOUSE_SERVICE_PIDS", cgroup_pids(cgroup)?);
|
||||||
|
}
|
||||||
|
let cgroup_fd = cgroup_procs.as_ref().map(AsRawFd::as_raw_fd);
|
||||||
|
// SAFETY: this closure runs between fork and exec, and setpgid is
|
||||||
|
// async-signal-safe. A separate group lets PID 1 terminate a whole
|
||||||
|
// transition, including service-script descendants.
|
||||||
|
unsafe {
|
||||||
|
command.pre_exec(move || {
|
||||||
|
if libc::setpgid(0, 0) == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
if let Some(fd) = cgroup_fd {
|
||||||
|
let pid = b"0\n";
|
||||||
|
if libc::write(fd, pid.as_ptr().cast(), pid.len()) == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let child = command
|
||||||
|
.spawn()
|
||||||
|
.map_err(|error| format!("cannot run {program}: {error}"))?;
|
||||||
|
let pid = i32::try_from(child.id()).map_err(|_| "child PID does not fit pid_t".to_owned())?;
|
||||||
|
let deadline = Instant::now() + timeout;
|
||||||
|
loop {
|
||||||
|
if let Some(status) = reap_during_command(console, pid)? {
|
||||||
|
return Ok(CommandResult::Exited(status));
|
||||||
|
}
|
||||||
|
if let Some(action) = take_shutdown_action() {
|
||||||
|
terminate_process_group(pid, program, console);
|
||||||
|
return Ok(CommandResult::Shutdown(action));
|
||||||
|
}
|
||||||
|
if Instant::now() >= deadline {
|
||||||
|
terminate_process_group(pid, program, console);
|
||||||
|
return Err(format!(
|
||||||
|
"{program} {} timed out after {} seconds",
|
||||||
|
arguments.join(" "),
|
||||||
|
timeout.as_secs()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
thread::sleep(POLL_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cgroup_pids(cgroup: &Path) -> Result<String, String> {
|
||||||
|
let contents = std::fs::read_to_string(cgroup.join("cgroup.procs"))
|
||||||
|
.map_err(|error| format!("cannot read {}: {error}", cgroup.display()))?;
|
||||||
|
let mut pids = Vec::new();
|
||||||
|
for pid in contents.lines() {
|
||||||
|
if pid.is_empty() || !pid.bytes().all(|byte| byte.is_ascii_digit()) {
|
||||||
|
return Err(format!("invalid PID in {}", cgroup.display()));
|
||||||
|
}
|
||||||
|
pids.push(pid);
|
||||||
|
}
|
||||||
|
Ok(pids.join(" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn restore_shutdown_action(action: ShutdownAction) {
|
||||||
|
SHUTDOWN_SIGNAL.store(
|
||||||
|
match action {
|
||||||
|
ShutdownAction::Reboot => libc::SIGINT,
|
||||||
|
ShutdownAction::Poweroff => libc::SIGTERM,
|
||||||
|
},
|
||||||
|
Ordering::Relaxed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn take_shutdown_action() -> Option<ShutdownAction> {
|
||||||
|
shutdown_action(SHUTDOWN_SIGNAL.swap(0, Ordering::Relaxed))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reap_during_command(
|
||||||
|
console: &mut Console,
|
||||||
|
command_pid: libc::pid_t,
|
||||||
|
) -> Result<Option<ExitStatus>, String> {
|
||||||
|
CHILD_EXITED.store(false, Ordering::Relaxed);
|
||||||
|
loop {
|
||||||
|
let mut status = 0;
|
||||||
|
// SAFETY: status is writable and WNOHANG makes the call nonblocking.
|
||||||
|
let pid = unsafe { libc::waitpid(-1, &raw mut status, libc::WNOHANG) };
|
||||||
|
match pid {
|
||||||
|
value if value == command_pid => {
|
||||||
|
return Ok(Some(ExitStatus::from_raw(status)));
|
||||||
|
}
|
||||||
|
value if value > 0 => {
|
||||||
|
console.log(&format!(
|
||||||
|
"reaped orphan PID {value} ({})",
|
||||||
|
describe_wait_status(status)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
0 => return Ok(None),
|
||||||
|
_ => {
|
||||||
|
let error = io::Error::last_os_error();
|
||||||
|
return if error.raw_os_error() == Some(libc::ECHILD) {
|
||||||
|
Err("transition child disappeared without an exit status".to_owned())
|
||||||
|
} else {
|
||||||
|
Err(format!("waitpid failed during transition: {error}"))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terminate_process_group(pid: libc::pid_t, program: &str, console: &mut Console) {
|
||||||
|
// SAFETY: negative pid addresses the process group created before exec.
|
||||||
|
let term_result = unsafe { libc::kill(-pid, libc::SIGTERM) };
|
||||||
|
if term_result == -1 && io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH) {
|
||||||
|
console.log(&format!("cannot terminate {program} process group {pid}"));
|
||||||
|
}
|
||||||
|
let deadline = Instant::now() + TERMINATION_GRACE;
|
||||||
|
while Instant::now() < deadline {
|
||||||
|
match reap_during_command(console, pid) {
|
||||||
|
Ok(Some(_)) | Err(_) => return,
|
||||||
|
Ok(None) => thread::sleep(POLL_INTERVAL),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SAFETY: negative pid addresses the same known process group.
|
||||||
|
unsafe {
|
||||||
|
libc::kill(-pid, libc::SIGKILL);
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
match reap_during_command(console, pid) {
|
||||||
|
Ok(Some(_)) | Err(_) => return,
|
||||||
|
Ok(None) => thread::sleep(POLL_INTERVAL),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn describe_exit_status(status: ExitStatus) -> String {
|
||||||
|
status
|
||||||
|
.code()
|
||||||
|
.map_or_else(|| "a signal".to_owned(), |code| format!("status {code}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn spawn_emergency_shell(console: &mut Console) -> Result<libc::pid_t, String> {
|
||||||
|
let stdin = console
|
||||||
|
.clone_file()
|
||||||
|
.map_err(|error| format!("cannot duplicate console input: {error}"))?;
|
||||||
|
let stdout = console
|
||||||
|
.clone_file()
|
||||||
|
.map_err(|error| format!("cannot duplicate console output: {error}"))?;
|
||||||
|
let stderr = console
|
||||||
|
.clone_file()
|
||||||
|
.map_err(|error| format!("cannot duplicate console error output: {error}"))?;
|
||||||
|
|
||||||
|
let mut command = Command::new(EMERGENCY_SHELL);
|
||||||
|
command
|
||||||
|
.arg("-l")
|
||||||
|
.env_clear()
|
||||||
|
.env("HOME", "/root")
|
||||||
|
.env("LOGNAME", "root")
|
||||||
|
.env("PATH", "/sbin:/bin:/usr/sbin:/usr/bin")
|
||||||
|
.env("SHELL", EMERGENCY_SHELL)
|
||||||
|
.env("TERM", "linux")
|
||||||
|
.env("USER", "root")
|
||||||
|
.stdin(Stdio::from(stdin))
|
||||||
|
.stdout(Stdio::from(stdout))
|
||||||
|
.stderr(Stdio::from(stderr));
|
||||||
|
|
||||||
|
// SAFETY: this closure runs after fork and before exec in the single-threaded
|
||||||
|
// child. It invokes only async-signal-safe system calls and reports failures
|
||||||
|
// through a preallocated io::Error.
|
||||||
|
unsafe {
|
||||||
|
command.pre_exec(|| {
|
||||||
|
if libc::setsid() == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
if libc::ioctl(libc::STDIN_FILENO, libc::TIOCSCTTY, 0) == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let child = command
|
||||||
|
.spawn()
|
||||||
|
.map_err(|error| format!("cannot start {EMERGENCY_SHELL}: {error}"))?;
|
||||||
|
|
||||||
|
let pid = i32::try_from(child.id()).map_err(|_| "child PID does not fit pid_t".to_owned())?;
|
||||||
|
console.log(&format!("started emergency shell as PID {pid}"));
|
||||||
|
Ok(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn reap_children(console: &mut Console, shell_pid: Option<libc::pid_t>) -> bool {
|
||||||
|
let mut shell_exited = false;
|
||||||
|
loop {
|
||||||
|
let mut status = 0;
|
||||||
|
// SAFETY: status points to writable storage and WNOHANG makes this nonblocking.
|
||||||
|
let pid = unsafe { libc::waitpid(-1, &raw mut status, libc::WNOHANG) };
|
||||||
|
match pid {
|
||||||
|
value if value > 0 => {
|
||||||
|
let description = describe_wait_status(status);
|
||||||
|
if Some(value) == shell_pid {
|
||||||
|
console.log(&format!("emergency shell PID {value} {description}"));
|
||||||
|
shell_exited = true;
|
||||||
|
} else {
|
||||||
|
console.log(&format!("reaped orphan PID {value} ({description})"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0 => break,
|
||||||
|
_ => {
|
||||||
|
let error = io::Error::last_os_error();
|
||||||
|
if error.raw_os_error() != Some(libc::ECHILD) {
|
||||||
|
console.log(&format!("waitpid failed: {error}"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shell_exited
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe_wait_status(status: libc::c_int) -> String {
|
||||||
|
if libc::WIFEXITED(status) {
|
||||||
|
format!("exited with status {}", libc::WEXITSTATUS(status))
|
||||||
|
} else if libc::WIFSIGNALED(status) {
|
||||||
|
format!("was killed by signal {}", libc::WTERMSIG(status))
|
||||||
|
} else {
|
||||||
|
format!("changed state with wait status {status}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn perform_shutdown(
|
||||||
|
console: &mut Console,
|
||||||
|
shell_pid: Option<libc::pid_t>,
|
||||||
|
services: Option<&mut ServiceManager>,
|
||||||
|
action: ShutdownAction,
|
||||||
|
) -> ! {
|
||||||
|
console.log(&format!("received {action} request"));
|
||||||
|
stop_shell(console, shell_pid);
|
||||||
|
if let Some(manager) = services {
|
||||||
|
manager.stop_all(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: sync has no preconditions and is required before the terminal reboot call.
|
||||||
|
unsafe {
|
||||||
|
libc::sync();
|
||||||
|
}
|
||||||
|
unmount_mouse_storage(console);
|
||||||
|
remount_root_read_only(console);
|
||||||
|
|
||||||
|
let command = match action {
|
||||||
|
ShutdownAction::Reboot => libc::RB_AUTOBOOT,
|
||||||
|
ShutdownAction::Poweroff => libc::RB_POWER_OFF,
|
||||||
|
};
|
||||||
|
// SAFETY: cheesed is PID 1 and passes one of Linux's defined reboot commands.
|
||||||
|
if unsafe { libc::reboot(command) } == -1 {
|
||||||
|
console.log(&format!(
|
||||||
|
"{action} syscall failed: {}; PID 1 will remain alive",
|
||||||
|
io::Error::last_os_error()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_secs(60));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmount_mouse_storage(console: &mut Console) {
|
||||||
|
for target in [
|
||||||
|
c"/srv",
|
||||||
|
c"/home",
|
||||||
|
c"/var",
|
||||||
|
c"/usr/local",
|
||||||
|
c"/run/mouse-root",
|
||||||
|
] {
|
||||||
|
// SAFETY: target is a static NUL-terminated mount path and no flags
|
||||||
|
// request lazy or forced unmounting.
|
||||||
|
if unsafe { libc::umount2(target.as_ptr(), 0) } == -1 {
|
||||||
|
let error = io::Error::last_os_error();
|
||||||
|
if !matches!(error.raw_os_error(), Some(libc::EINVAL | libc::ENOENT)) {
|
||||||
|
console.log(&format!(
|
||||||
|
"cannot unmount {}: {error}",
|
||||||
|
target.to_string_lossy()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remount_root_read_only(console: &mut Console) {
|
||||||
|
// SAFETY: all pointers are either null or point to static NUL-terminated
|
||||||
|
// strings. MS_REMOUNT changes the existing root mount rather than selecting
|
||||||
|
// or discovering storage.
|
||||||
|
let result = unsafe {
|
||||||
|
libc::mount(
|
||||||
|
std::ptr::null(),
|
||||||
|
c"/".as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
libc::MS_REMOUNT | libc::MS_RDONLY,
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if result == -1 {
|
||||||
|
console.log(&format!(
|
||||||
|
"cannot remount root filesystem read-only: {}",
|
||||||
|
io::Error::last_os_error()
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
console.log("root filesystem remounted read-only");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_shell(console: &mut Console, shell_pid: Option<libc::pid_t>) {
|
||||||
|
let Some(pid) = shell_pid else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: pid is a direct child PID and SIGTERM is a valid signal.
|
||||||
|
if unsafe { libc::kill(pid, libc::SIGTERM) } == -1 {
|
||||||
|
let error = io::Error::last_os_error();
|
||||||
|
if error.raw_os_error() != Some(libc::ESRCH) {
|
||||||
|
console.log(&format!("cannot stop emergency shell PID {pid}: {error}"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let deadline = Instant::now() + TERMINATION_GRACE;
|
||||||
|
while Instant::now() < deadline {
|
||||||
|
if wait_for_pid(pid) {
|
||||||
|
console.log(&format!("emergency shell PID {pid} stopped"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thread::sleep(POLL_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(&format!(
|
||||||
|
"emergency shell PID {pid} exceeded shutdown timeout; sending SIGKILL"
|
||||||
|
));
|
||||||
|
// SAFETY: pid is the known shell PID and SIGKILL is a valid signal.
|
||||||
|
unsafe {
|
||||||
|
libc::kill(pid, libc::SIGKILL);
|
||||||
|
}
|
||||||
|
let _ = wait_for_pid(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_for_pid(pid: libc::pid_t) -> bool {
|
||||||
|
let mut status = 0;
|
||||||
|
// SAFETY: status points to writable storage, pid is a known child, and WNOHANG
|
||||||
|
// makes the check nonblocking.
|
||||||
|
let result = unsafe { libc::waitpid(pid, &raw mut status, libc::WNOHANG) };
|
||||||
|
result == pid
|
||||||
|
|| (result == -1 && io::Error::last_os_error().raw_os_error() == Some(libc::ECHILD))
|
||||||
|
}
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
use super::process::{
|
||||||
|
CommandResult, TERMINATION_GRACE, command_with_console, describe_exit_status,
|
||||||
|
restore_shutdown_action,
|
||||||
|
};
|
||||||
|
use super::{Console, POLL_INTERVAL};
|
||||||
|
use crate::policy::{ServiceCatalog, parse_rc_conf};
|
||||||
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
|
use std::io::{self, BufRead, BufReader, Write};
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::os::unix::net::UnixListener;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
const RC_CONF_PATH: &str = "/etc/rc.conf";
|
||||||
|
const SYSTEM_SERVICE_DEFINITIONS: &str = "/etc/rc.d";
|
||||||
|
const LOCAL_SERVICE_DEFINITIONS: &str = "/usr/local/etc/rc.d";
|
||||||
|
const REQUIRED_SERVICES: &str = "/usr/lib/mouse/required-services";
|
||||||
|
const SERVICE_CGROUP_ROOT: &str = "/sys/fs/cgroup/mouse.services";
|
||||||
|
const CONTROL_SOCKET: &str = "/run/cheesed.sock";
|
||||||
|
const SERVICE_TRANSITION_TIMEOUT: Duration = Duration::from_secs(120);
|
||||||
|
const SERVICE_STOP_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
const RESTART_WINDOW: Duration = Duration::from_secs(10);
|
||||||
|
const RESTART_DELAY: Duration = Duration::from_secs(1);
|
||||||
|
const RESTART_LIMIT: usize = 3;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum ServiceState {
|
||||||
|
Stopped,
|
||||||
|
Started,
|
||||||
|
Running,
|
||||||
|
RestartPending,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceState {
|
||||||
|
fn description(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Stopped => "stopped",
|
||||||
|
Self::Started => "started",
|
||||||
|
Self::Running => "running",
|
||||||
|
Self::RestartPending => "restarting",
|
||||||
|
Self::Failed => "failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ManagedService {
|
||||||
|
state: ServiceState,
|
||||||
|
restart_times: VecDeque<Instant>,
|
||||||
|
restart_at: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ManagedService {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
state: ServiceState::Stopped,
|
||||||
|
restart_times: VecDeque::new(),
|
||||||
|
restart_at: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct ServiceManager {
|
||||||
|
catalog: ServiceCatalog,
|
||||||
|
services: BTreeMap<String, ManagedService>,
|
||||||
|
started_order: Vec<String>,
|
||||||
|
listener: UnixListener,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn boot_services(console: &mut Console) -> Result<ServiceManager, String> {
|
||||||
|
let catalog = ServiceCatalog::discover(
|
||||||
|
Path::new(SYSTEM_SERVICE_DEFINITIONS),
|
||||||
|
Path::new(LOCAL_SERVICE_DEFINITIONS),
|
||||||
|
Path::new(REQUIRED_SERVICES),
|
||||||
|
)
|
||||||
|
.map_err(|error| format!("cannot discover services: {error}"))?;
|
||||||
|
let contents = std::fs::read_to_string(RC_CONF_PATH)
|
||||||
|
.map_err(|error| format!("cannot read {RC_CONF_PATH}: {error}"))?;
|
||||||
|
let policy = parse_rc_conf(&contents, &catalog)
|
||||||
|
.map_err(|error| format!("invalid service policy: {error}"))?;
|
||||||
|
let enabled = policy.enabled().collect::<Vec<_>>();
|
||||||
|
if enabled.is_empty() {
|
||||||
|
console.log("enabled optional services: (none)");
|
||||||
|
} else {
|
||||||
|
console.log(&format!(
|
||||||
|
"enabled optional services: {}",
|
||||||
|
enabled.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let order = catalog
|
||||||
|
.start_order(&policy)
|
||||||
|
.map_err(|error| format!("cannot order services: {error}"))?;
|
||||||
|
let mut manager = ServiceManager::new(catalog)?;
|
||||||
|
for service in order {
|
||||||
|
console.log(&format!("starting service {service}"));
|
||||||
|
if let Err(error) = manager.start_service(&service, console, false) {
|
||||||
|
if manager.catalog.is_required(&service) {
|
||||||
|
manager.stop_all(console);
|
||||||
|
return Err(format!("required service {service} failed: {error}"));
|
||||||
|
}
|
||||||
|
console.log(&format!("optional service {service} failed: {error}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("service graph started; cgroup supervision active");
|
||||||
|
Ok(manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceManager {
|
||||||
|
fn new(catalog: ServiceCatalog) -> Result<Self, String> {
|
||||||
|
std::fs::create_dir_all(SERVICE_CGROUP_ROOT)
|
||||||
|
.map_err(|error| format!("cannot create service cgroup root: {error}"))?;
|
||||||
|
let _ = std::fs::remove_file(CONTROL_SOCKET);
|
||||||
|
let listener = UnixListener::bind(CONTROL_SOCKET)
|
||||||
|
.map_err(|error| format!("cannot bind {CONTROL_SOCKET}: {error}"))?;
|
||||||
|
listener
|
||||||
|
.set_nonblocking(true)
|
||||||
|
.map_err(|error| format!("cannot make {CONTROL_SOCKET} nonblocking: {error}"))?;
|
||||||
|
std::fs::set_permissions(CONTROL_SOCKET, std::fs::Permissions::from_mode(0o600))
|
||||||
|
.map_err(|error| format!("cannot protect {CONTROL_SOCKET}: {error}"))?;
|
||||||
|
Ok(Self {
|
||||||
|
catalog,
|
||||||
|
services: BTreeMap::new(),
|
||||||
|
started_order: Vec::new(),
|
||||||
|
listener,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn service(&mut self, name: &str) -> &mut ManagedService {
|
||||||
|
self.services.entry(name.to_owned()).or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_service(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
console: &mut Console,
|
||||||
|
clear_history: bool,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let definition = self
|
||||||
|
.catalog
|
||||||
|
.definition(name)
|
||||||
|
.ok_or_else(|| format!("unknown service {name}"))?;
|
||||||
|
if matches!(
|
||||||
|
self.services.get(name).map(|service| service.state),
|
||||||
|
Some(ServiceState::Started | ServiceState::Running)
|
||||||
|
) {
|
||||||
|
return Ok(format!("{name} is already started"));
|
||||||
|
}
|
||||||
|
let script = definition.path().to_path_buf();
|
||||||
|
let cgroup = service_cgroup(name)?;
|
||||||
|
std::fs::create_dir_all(&cgroup)
|
||||||
|
.map_err(|error| format!("cannot create cgroup for {name}: {error}"))?;
|
||||||
|
kill_cgroup(&cgroup, console);
|
||||||
|
let result = command_with_console(
|
||||||
|
script
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| format!("service path for {name} is not UTF-8"))?,
|
||||||
|
&["start"],
|
||||||
|
console,
|
||||||
|
SERVICE_TRANSITION_TIMEOUT,
|
||||||
|
Some(&cgroup),
|
||||||
|
)?;
|
||||||
|
let status = match result {
|
||||||
|
CommandResult::Exited(status) => status,
|
||||||
|
CommandResult::Shutdown(action) => {
|
||||||
|
restore_shutdown_action(action);
|
||||||
|
return Err(format!("start of {name} interrupted by {action} request"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !status.success() {
|
||||||
|
kill_cgroup(&cgroup, console);
|
||||||
|
self.service(name).state = ServiceState::Failed;
|
||||||
|
return Err(format!(
|
||||||
|
"start command exited with {}",
|
||||||
|
describe_exit_status(status)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let state = if cgroup_populated(&cgroup)? {
|
||||||
|
ServiceState::Running
|
||||||
|
} else {
|
||||||
|
ServiceState::Started
|
||||||
|
};
|
||||||
|
let service = self.service(name);
|
||||||
|
service.state = state;
|
||||||
|
service.restart_at = None;
|
||||||
|
if clear_history {
|
||||||
|
service.restart_times.clear();
|
||||||
|
}
|
||||||
|
if !self.started_order.iter().any(|started| started == name) {
|
||||||
|
self.started_order.push(name.to_owned());
|
||||||
|
}
|
||||||
|
Ok(format!("{name} {}", state.description()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_service(&mut self, name: &str, console: &mut Console) -> Result<String, String> {
|
||||||
|
let definition = self
|
||||||
|
.catalog
|
||||||
|
.definition(name)
|
||||||
|
.ok_or_else(|| format!("unknown service {name}"))?;
|
||||||
|
let current = self
|
||||||
|
.services
|
||||||
|
.get(name)
|
||||||
|
.map_or(ServiceState::Stopped, |s| s.state);
|
||||||
|
if current == ServiceState::Stopped {
|
||||||
|
return Ok(format!("{name} is already stopped"));
|
||||||
|
}
|
||||||
|
let script = definition.path().to_path_buf();
|
||||||
|
let cgroup = service_cgroup(name)?;
|
||||||
|
let result = command_with_console(
|
||||||
|
script
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| format!("service path for {name} is not UTF-8"))?,
|
||||||
|
&["stop"],
|
||||||
|
console,
|
||||||
|
SERVICE_STOP_TIMEOUT,
|
||||||
|
Some(&cgroup),
|
||||||
|
);
|
||||||
|
let (command_error, allow_grace) = match result {
|
||||||
|
Ok(CommandResult::Exited(status)) if status.success() => (None, true),
|
||||||
|
Ok(CommandResult::Exited(status)) => (
|
||||||
|
Some(format!(
|
||||||
|
"stop command exited with {}",
|
||||||
|
describe_exit_status(status)
|
||||||
|
)),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
Ok(CommandResult::Shutdown(action)) => {
|
||||||
|
restore_shutdown_action(action);
|
||||||
|
(Some(format!("stop interrupted by {action} request")), false)
|
||||||
|
}
|
||||||
|
Err(error) => (Some(error), false),
|
||||||
|
};
|
||||||
|
if allow_grace {
|
||||||
|
wait_for_empty_cgroup(&cgroup, TERMINATION_GRACE);
|
||||||
|
}
|
||||||
|
kill_cgroup(&cgroup, console);
|
||||||
|
let service = self.service(name);
|
||||||
|
service.state = ServiceState::Stopped;
|
||||||
|
service.restart_at = None;
|
||||||
|
if let Some(error) = command_error {
|
||||||
|
Err(error)
|
||||||
|
} else {
|
||||||
|
Ok(format!("{name} stopped"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_action(
|
||||||
|
&mut self,
|
||||||
|
action: &str,
|
||||||
|
name: &str,
|
||||||
|
console: &mut Console,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
if self.catalog.definition(name).is_none() {
|
||||||
|
let refreshed = ServiceCatalog::discover(
|
||||||
|
Path::new(SYSTEM_SERVICE_DEFINITIONS),
|
||||||
|
Path::new(LOCAL_SERVICE_DEFINITIONS),
|
||||||
|
Path::new(REQUIRED_SERVICES),
|
||||||
|
)
|
||||||
|
.map_err(|error| format!("cannot refresh service definitions: {error}"))?;
|
||||||
|
if refreshed.definition(name).is_none() {
|
||||||
|
return Err(format!("unknown service {name}"));
|
||||||
|
}
|
||||||
|
self.catalog = refreshed;
|
||||||
|
}
|
||||||
|
match action {
|
||||||
|
"start" => self.start_service(name, console, true),
|
||||||
|
"stop" => self.stop_service(name, console),
|
||||||
|
"restart" => {
|
||||||
|
let _ = self.stop_service(name, console);
|
||||||
|
self.start_service(name, console, true)
|
||||||
|
}
|
||||||
|
"reload" => {
|
||||||
|
let definition = self
|
||||||
|
.catalog
|
||||||
|
.definition(name)
|
||||||
|
.ok_or_else(|| format!("unknown service {name}"))?;
|
||||||
|
let script = definition.path().to_path_buf();
|
||||||
|
let cgroup = service_cgroup(name)?;
|
||||||
|
match command_with_console(
|
||||||
|
script
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| format!("service path for {name} is not UTF-8"))?,
|
||||||
|
&["reload"],
|
||||||
|
console,
|
||||||
|
SERVICE_TRANSITION_TIMEOUT,
|
||||||
|
Some(&cgroup),
|
||||||
|
)? {
|
||||||
|
CommandResult::Exited(status) if status.success() => {
|
||||||
|
Ok(format!("{name} reloaded"))
|
||||||
|
}
|
||||||
|
CommandResult::Exited(status) => Err(format!(
|
||||||
|
"reload command exited with {}",
|
||||||
|
describe_exit_status(status)
|
||||||
|
)),
|
||||||
|
CommandResult::Shutdown(action) => {
|
||||||
|
restore_shutdown_action(action);
|
||||||
|
Err(format!("reload interrupted by {action} request"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status" => {
|
||||||
|
let state = self
|
||||||
|
.services
|
||||||
|
.get(name)
|
||||||
|
.map_or(ServiceState::Stopped, |service| service.state);
|
||||||
|
if matches!(state, ServiceState::Failed | ServiceState::Stopped) {
|
||||||
|
Err(format!("{name} {}", state.description()))
|
||||||
|
} else {
|
||||||
|
Ok(format!("{name} {}", state.description()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(format!("unsupported service action {action}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn poll(&mut self, console: &mut Console) {
|
||||||
|
loop {
|
||||||
|
match self.listener.accept() {
|
||||||
|
Ok((mut stream, _)) => {
|
||||||
|
let result = (|| {
|
||||||
|
stream.set_read_timeout(Some(Duration::from_secs(2)))?;
|
||||||
|
let mut request = String::new();
|
||||||
|
BufReader::new(stream.try_clone()?).read_line(&mut request)?;
|
||||||
|
if request.len() > 256 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"service request is too long",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let fields = request.split_ascii_whitespace().collect::<Vec<_>>();
|
||||||
|
if fields.len() != 2 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"expected ACTION SERVICE",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let response = match self.run_action(fields[0], fields[1], console) {
|
||||||
|
Ok(message) => format!("OK {message}\n"),
|
||||||
|
Err(error) => format!("ERR {error}\n"),
|
||||||
|
};
|
||||||
|
stream.write_all(response.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
})();
|
||||||
|
if let Err(error) = result {
|
||||||
|
let _ = writeln!(stream, "ERR {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) if error.kind() == io::ErrorKind::WouldBlock => break,
|
||||||
|
Err(error) => {
|
||||||
|
console.log(&format!("service control socket failed: {error}"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let names = self.services.keys().cloned().collect::<Vec<_>>();
|
||||||
|
let mut restart = Vec::new();
|
||||||
|
for name in names {
|
||||||
|
let cgroup = match service_cgroup(&name) {
|
||||||
|
Ok(path) => path,
|
||||||
|
Err(error) => {
|
||||||
|
console.log(&error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let populated = cgroup_populated(&cgroup).unwrap_or(false);
|
||||||
|
let service = self.service(&name);
|
||||||
|
if service.state == ServiceState::Running && !populated {
|
||||||
|
while service
|
||||||
|
.restart_times
|
||||||
|
.front()
|
||||||
|
.is_some_and(|instant| now.duration_since(*instant) > RESTART_WINDOW)
|
||||||
|
{
|
||||||
|
service.restart_times.pop_front();
|
||||||
|
}
|
||||||
|
if service.restart_times.len() >= RESTART_LIMIT {
|
||||||
|
service.state = ServiceState::Failed;
|
||||||
|
console.log(&format!(
|
||||||
|
"service {name} exhausted {RESTART_LIMIT} restarts in {} seconds",
|
||||||
|
RESTART_WINDOW.as_secs()
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
service.restart_times.push_back(now);
|
||||||
|
service.restart_at = Some(now + RESTART_DELAY);
|
||||||
|
service.state = ServiceState::RestartPending;
|
||||||
|
console.log(&format!("service {name} exited; restart scheduled"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if service.state == ServiceState::RestartPending
|
||||||
|
&& service.restart_at.is_some_and(|deadline| now >= deadline)
|
||||||
|
{
|
||||||
|
restart.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name in restart {
|
||||||
|
match self.start_service(&name, console, false) {
|
||||||
|
Ok(message) => console.log(&message),
|
||||||
|
Err(error) => {
|
||||||
|
self.service(&name).state = ServiceState::Failed;
|
||||||
|
console.log(&format!("service {name} restart failed: {error}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn stop_all(&mut self, console: &mut Console) {
|
||||||
|
for name in self.started_order.clone().into_iter().rev() {
|
||||||
|
console.log(&format!("stopping service {name}"));
|
||||||
|
if let Err(error) = self.stop_service(&name, console) {
|
||||||
|
console.log(&format!("service {name} stop failed: {error}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn service_cgroup(name: &str) -> Result<std::path::PathBuf, String> {
|
||||||
|
if name.is_empty()
|
||||||
|
|| !name
|
||||||
|
.bytes()
|
||||||
|
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.'))
|
||||||
|
{
|
||||||
|
return Err(format!("invalid service name {name:?}"));
|
||||||
|
}
|
||||||
|
Ok(Path::new(SERVICE_CGROUP_ROOT).join(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cgroup_populated(cgroup: &Path) -> Result<bool, String> {
|
||||||
|
let contents = std::fs::read_to_string(cgroup.join("cgroup.events"))
|
||||||
|
.map_err(|error| format!("cannot read {}: {error}", cgroup.display()))?;
|
||||||
|
Ok(contents
|
||||||
|
.lines()
|
||||||
|
.any(|line| line.split_ascii_whitespace().eq(["populated", "1"])))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_for_empty_cgroup(cgroup: &Path, timeout: Duration) {
|
||||||
|
let deadline = Instant::now() + timeout;
|
||||||
|
while Instant::now() < deadline {
|
||||||
|
if !cgroup_populated(cgroup).unwrap_or(false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thread::sleep(POLL_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kill_cgroup(cgroup: &Path, console: &mut Console) {
|
||||||
|
if !cgroup_populated(cgroup).unwrap_or(false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Err(error) = std::fs::write(cgroup.join("cgroup.kill"), b"1\n") {
|
||||||
|
console.log(&format!(
|
||||||
|
"cannot kill remaining processes in {}: {error}",
|
||||||
|
cgroup.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
wait_for_empty_cgroup(cgroup, TERMINATION_GRACE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
use std::io::{BufRead, BufReader, Write};
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
const CONTROL_SOCKET: &str = "/run/cheesed.sock";
|
||||||
|
const SERVICE_USAGE: &str = "usage: service NAME {start|stop|restart|reload|status}";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut arguments = std::env::args();
|
||||||
|
let executable = arguments.next().unwrap_or_else(|| "cheesed".to_owned());
|
||||||
|
let name = Path::new(&executable)
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.unwrap_or("cheesed");
|
||||||
|
let arguments = arguments.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let result = match name {
|
||||||
|
"cheesed" => cheesed::run(),
|
||||||
|
"service" => run_service(&arguments),
|
||||||
|
"poweroff" | "reboot" | "shutdown" => run_shutdown(name, &arguments),
|
||||||
|
_ => Err(format!("unknown cheesed invocation name: {name}")),
|
||||||
|
};
|
||||||
|
if let Err(error) = result {
|
||||||
|
eprintln!("{name}: {error}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_service(arguments: &[String]) -> Result<(), String> {
|
||||||
|
let [name, action] = arguments else {
|
||||||
|
return Err(SERVICE_USAGE.to_owned());
|
||||||
|
};
|
||||||
|
if !matches!(
|
||||||
|
action.as_str(),
|
||||||
|
"start" | "stop" | "restart" | "reload" | "status"
|
||||||
|
) {
|
||||||
|
return Err(SERVICE_USAGE.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stream = UnixStream::connect(CONTROL_SOCKET)
|
||||||
|
.map_err(|error| format!("cannot connect to cheesed: {error}"))?;
|
||||||
|
writeln!(stream, "{action} {name}").map_err(|error| format!("cannot send request: {error}"))?;
|
||||||
|
stream
|
||||||
|
.shutdown(std::net::Shutdown::Write)
|
||||||
|
.map_err(|error| format!("cannot finish request: {error}"))?;
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
BufReader::new(stream)
|
||||||
|
.read_line(&mut response)
|
||||||
|
.map_err(|error| format!("cannot read response: {error}"))?;
|
||||||
|
let response = response.trim_end();
|
||||||
|
if let Some(message) = response.strip_prefix("OK ") {
|
||||||
|
println!("{message}");
|
||||||
|
Ok(())
|
||||||
|
} else if let Some(message) = response.strip_prefix("ERR ") {
|
||||||
|
Err(message.to_owned())
|
||||||
|
} else {
|
||||||
|
Err("invalid response from cheesed".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_shutdown(name: &str, arguments: &[String]) -> Result<(), String> {
|
||||||
|
let signal = shutdown_signal(name, arguments)?;
|
||||||
|
// SAFETY: PID 1 and the selected signal are fixed by the validated command name.
|
||||||
|
if unsafe { libc::kill(1, signal) } == -1 {
|
||||||
|
return Err(format!(
|
||||||
|
"cannot signal cheesed: {}",
|
||||||
|
std::io::Error::last_os_error()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shutdown_signal(name: &str, arguments: &[String]) -> Result<libc::c_int, String> {
|
||||||
|
match (name, arguments) {
|
||||||
|
("poweroff" | "shutdown", []) => Ok(libc::SIGTERM),
|
||||||
|
("reboot", []) => Ok(libc::SIGINT),
|
||||||
|
("shutdown", [flag]) if matches!(flag.as_str(), "-h" | "-p") => Ok(libc::SIGTERM),
|
||||||
|
("shutdown", [argument]) if argument == "now" => Ok(libc::SIGTERM),
|
||||||
|
("shutdown", [flag, argument])
|
||||||
|
if matches!(flag.as_str(), "-h" | "-p") && argument == "now" =>
|
||||||
|
{
|
||||||
|
Ok(libc::SIGTERM)
|
||||||
|
}
|
||||||
|
("shutdown", [flag]) if flag == "-r" => Ok(libc::SIGINT),
|
||||||
|
("shutdown", [flag, argument]) if flag == "-r" && argument == "now" => Ok(libc::SIGINT),
|
||||||
|
("shutdown", _) => Err("usage: shutdown [-h|-p|-r] [now]".to_owned()),
|
||||||
|
_ => Err(format!("usage: {name}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::shutdown_signal;
|
||||||
|
|
||||||
|
fn arguments(values: &[&str]) -> Vec<String> {
|
||||||
|
values.iter().map(ToString::to_string).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multicall_shutdown_names_select_the_pid1_signal() {
|
||||||
|
assert_eq!(shutdown_signal("poweroff", &[]), Ok(libc::SIGTERM));
|
||||||
|
assert_eq!(shutdown_signal("reboot", &[]), Ok(libc::SIGINT));
|
||||||
|
assert_eq!(
|
||||||
|
shutdown_signal("shutdown", &arguments(&["-r", "now"])),
|
||||||
|
Ok(libc::SIGINT)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
shutdown_signal("shutdown", &arguments(&["-p", "now"])),
|
||||||
|
Ok(libc::SIGTERM)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multicall_shutdown_rejects_invalid_arguments() {
|
||||||
|
assert!(shutdown_signal("poweroff", &arguments(&["now"])).is_err());
|
||||||
|
assert!(shutdown_signal("shutdown", &arguments(&["later"])).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,568 @@
|
|||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::fmt;
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct RcPolicy {
|
||||||
|
enabled: BTreeSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RcPolicy {
|
||||||
|
pub(crate) fn enabled(&self) -> impl Iterator<Item = &str> {
|
||||||
|
self.enabled.iter().map(String::as_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ServiceDefinition {
|
||||||
|
path: PathBuf,
|
||||||
|
provides: BTreeSet<String>,
|
||||||
|
requires: BTreeSet<String>,
|
||||||
|
before: BTreeSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceDefinition {
|
||||||
|
pub(crate) fn path(&self) -> &Path {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ServiceCatalog {
|
||||||
|
definitions: BTreeMap<String, ServiceDefinition>,
|
||||||
|
required: BTreeSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceCatalog {
|
||||||
|
pub(crate) fn discover(
|
||||||
|
system_definitions: &Path,
|
||||||
|
local_definitions: &Path,
|
||||||
|
required_services: &Path,
|
||||||
|
) -> Result<Self, PolicyError> {
|
||||||
|
let mut definitions = read_service_definitions(system_definitions, false)?;
|
||||||
|
for (name, definition) in read_service_definitions(local_definitions, true)? {
|
||||||
|
if let Some(system_definition) = definitions.insert(name.clone(), definition) {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"duplicate service definition {name}: {} and {}",
|
||||||
|
system_definition.path.display(),
|
||||||
|
definitions[&name].path.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let required = read_required_services(required_services)?;
|
||||||
|
for service in &required {
|
||||||
|
if !definitions.contains_key(service) {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"required service {service} has no definition"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
definitions,
|
||||||
|
required,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn definition(&self, name: &str) -> Option<&ServiceDefinition> {
|
||||||
|
self.definitions.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_required(&self, name: &str) -> bool {
|
||||||
|
self.required.contains(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn start_order(&self, policy: &RcPolicy) -> Result<Vec<String>, PolicyError> {
|
||||||
|
let selected = self
|
||||||
|
.required
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(policy.enabled.iter().cloned())
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
self.order_selected(&selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn order_selected(&self, selected: &BTreeSet<String>) -> Result<Vec<String>, PolicyError> {
|
||||||
|
let mut all_providers = BTreeMap::<String, BTreeSet<String>>::new();
|
||||||
|
for (name, definition) in &self.definitions {
|
||||||
|
for token in &definition.provides {
|
||||||
|
all_providers
|
||||||
|
.entry(token.clone())
|
||||||
|
.or_default()
|
||||||
|
.insert(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut outgoing = selected
|
||||||
|
.iter()
|
||||||
|
.map(|name| (name.clone(), BTreeSet::new()))
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
let mut indegree = selected
|
||||||
|
.iter()
|
||||||
|
.map(|name| (name.clone(), 0_usize))
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
|
||||||
|
for name in selected {
|
||||||
|
let definition = &self.definitions[name];
|
||||||
|
for token in &definition.requires {
|
||||||
|
let Some(providers) = all_providers.get(token) else {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"service {name} requires missing facility {token}"
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
let selected_providers = providers
|
||||||
|
.intersection(selected)
|
||||||
|
.filter(|provider| *provider != name)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if selected_providers.is_empty() && !providers.contains(name) {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"service {name} requires disabled facility {token}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
for provider in selected_providers {
|
||||||
|
add_edge(&mut outgoing, &mut indegree, &provider, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for token in &definition.before {
|
||||||
|
if let Some(providers) = all_providers.get(token) {
|
||||||
|
for target in providers.intersection(selected) {
|
||||||
|
if target != name {
|
||||||
|
add_edge(&mut outgoing, &mut indegree, name, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ready = indegree
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(name, degree)| (*degree == 0).then_some(name.clone()))
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
let mut order = Vec::with_capacity(selected.len());
|
||||||
|
while let Some(name) = ready.pop_first() {
|
||||||
|
order.push(name.clone());
|
||||||
|
for dependent in outgoing[&name].clone() {
|
||||||
|
let degree = indegree
|
||||||
|
.get_mut(&dependent)
|
||||||
|
.expect("dependency graph contains every selected service");
|
||||||
|
*degree -= 1;
|
||||||
|
if *degree == 0 {
|
||||||
|
ready.insert(dependent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.len() != selected.len() {
|
||||||
|
let cycle = indegree
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(name, degree)| (degree != 0).then_some(name))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"service dependency cycle contains: {cycle}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_edge(
|
||||||
|
outgoing: &mut BTreeMap<String, BTreeSet<String>>,
|
||||||
|
indegree: &mut BTreeMap<String, usize>,
|
||||||
|
from: &str,
|
||||||
|
to: &str,
|
||||||
|
) {
|
||||||
|
if outgoing
|
||||||
|
.get_mut(from)
|
||||||
|
.expect("edge source is selected")
|
||||||
|
.insert(to.to_owned())
|
||||||
|
{
|
||||||
|
*indegree.get_mut(to).expect("edge target is selected") += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct PolicyError {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PolicyError {
|
||||||
|
fn new(message: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
message: message.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PolicyError {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
formatter.write_str(&self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for PolicyError {
|
||||||
|
fn from(error: io::Error) -> Self {
|
||||||
|
Self::new(error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_rc_conf(
|
||||||
|
contents: &str,
|
||||||
|
catalog: &ServiceCatalog,
|
||||||
|
) -> Result<RcPolicy, PolicyError> {
|
||||||
|
let mut assignments = BTreeMap::new();
|
||||||
|
|
||||||
|
for (index, line) in contents.lines().enumerate() {
|
||||||
|
let line_number = index + 1;
|
||||||
|
if line.trim().is_empty() || line.trim_start().starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((key, value)) = line.split_once('=') else {
|
||||||
|
return Err(line_error(
|
||||||
|
line_number,
|
||||||
|
"expected enable_name=YES or enable_name=NO",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if key.contains(char::is_whitespace)
|
||||||
|
|| value.contains(char::is_whitespace)
|
||||||
|
|| line.matches('=').count() != 1
|
||||||
|
{
|
||||||
|
return Err(line_error(
|
||||||
|
line_number,
|
||||||
|
"whitespace, quoting, comments, and extra assignments are not allowed",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(name) = key.strip_prefix("enable_") else {
|
||||||
|
return Err(line_error(line_number, "unknown rc.conf key"));
|
||||||
|
};
|
||||||
|
if !valid_service_name(name) {
|
||||||
|
return Err(line_error(line_number, "invalid service name"));
|
||||||
|
}
|
||||||
|
let enabled = match value {
|
||||||
|
"YES" => true,
|
||||||
|
"NO" => false,
|
||||||
|
_ => {
|
||||||
|
return Err(line_error(
|
||||||
|
line_number,
|
||||||
|
"enablement value must be exactly YES or NO",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !catalog.definitions.contains_key(name) {
|
||||||
|
return Err(line_error(
|
||||||
|
line_number,
|
||||||
|
format!("service {name} has no installed definition"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if catalog.required.contains(name) {
|
||||||
|
return Err(line_error(
|
||||||
|
line_number,
|
||||||
|
format!("required service {name} cannot be enabled or disabled"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if assignments.insert(name.to_owned(), enabled).is_some() {
|
||||||
|
return Err(line_error(
|
||||||
|
line_number,
|
||||||
|
format!("duplicate assignment for service {name}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RcPolicy {
|
||||||
|
enabled: assignments
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(name, enabled)| enabled.then_some(name))
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_service_definitions(
|
||||||
|
directory: &Path,
|
||||||
|
optional: bool,
|
||||||
|
) -> Result<BTreeMap<String, ServiceDefinition>, PolicyError> {
|
||||||
|
let entries = match fs::read_dir(directory) {
|
||||||
|
Ok(entries) => entries,
|
||||||
|
Err(error) if optional && error.kind() == io::ErrorKind::NotFound => {
|
||||||
|
return Ok(BTreeMap::new());
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"cannot read {}: {error}",
|
||||||
|
directory.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut definitions = BTreeMap::new();
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry?;
|
||||||
|
if !entry.file_type()?.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = entry
|
||||||
|
.file_name()
|
||||||
|
.into_string()
|
||||||
|
.map_err(|_| PolicyError::new("service definition name is not UTF-8"))?;
|
||||||
|
if !valid_service_name(&name) {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"service definition {name:?} is not a valid MOUSE service name"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let path = entry.path();
|
||||||
|
let mut definition = parse_service_definition(&path)?;
|
||||||
|
definition.provides.insert(name.clone());
|
||||||
|
definitions.insert(name, definition);
|
||||||
|
}
|
||||||
|
Ok(definitions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_service_definition(path: &Path) -> Result<ServiceDefinition, PolicyError> {
|
||||||
|
let contents = fs::read_to_string(path).map_err(|error| {
|
||||||
|
PolicyError::new(format!(
|
||||||
|
"cannot read service definition {}: {error}",
|
||||||
|
path.display()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let mut provides = BTreeSet::new();
|
||||||
|
let mut requires = BTreeSet::new();
|
||||||
|
let mut before = BTreeSet::new();
|
||||||
|
for line in contents.lines() {
|
||||||
|
let line = line.trim_start();
|
||||||
|
let Some(comment) = line.strip_prefix('#') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let comment = comment.trim_start();
|
||||||
|
for (label, destination) in [
|
||||||
|
("PROVIDE:", &mut provides),
|
||||||
|
("REQUIRE:", &mut requires),
|
||||||
|
("BEFORE:", &mut before),
|
||||||
|
] {
|
||||||
|
if let Some(tokens) = comment.strip_prefix(label) {
|
||||||
|
for token in tokens.split_ascii_whitespace() {
|
||||||
|
if !valid_facility_name(token) {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"{} contains invalid {label} facility {token:?}",
|
||||||
|
path.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
destination.insert(token.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ServiceDefinition {
|
||||||
|
path: path.to_owned(),
|
||||||
|
provides,
|
||||||
|
requires,
|
||||||
|
before,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_required_services(path: &Path) -> Result<BTreeSet<String>, PolicyError> {
|
||||||
|
let contents = fs::read_to_string(path).map_err(|error| {
|
||||||
|
PolicyError::new(format!(
|
||||||
|
"cannot read required service list {}: {error}",
|
||||||
|
path.display()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let mut required = BTreeSet::new();
|
||||||
|
for (index, line) in contents.lines().enumerate() {
|
||||||
|
let name = line.trim();
|
||||||
|
if name.is_empty() || name.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !valid_service_name(name) {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"{}:{}: invalid required service name {name:?}",
|
||||||
|
path.display(),
|
||||||
|
index + 1
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if !required.insert(name.to_owned()) {
|
||||||
|
return Err(PolicyError::new(format!(
|
||||||
|
"{}:{}: duplicate required service {name}",
|
||||||
|
path.display(),
|
||||||
|
index + 1
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(required)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_service_name(name: &str) -> bool {
|
||||||
|
let mut bytes = name.bytes();
|
||||||
|
matches!(bytes.next(), Some(b'a'..=b'z'))
|
||||||
|
&& bytes.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_facility_name(name: &str) -> bool {
|
||||||
|
!name.is_empty()
|
||||||
|
&& name
|
||||||
|
.bytes()
|
||||||
|
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_error(line_number: usize, message: impl fmt::Display) -> PolicyError {
|
||||||
|
PolicyError::new(format!("/etc/rc.conf:{line_number}: {message}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{RcPolicy, ServiceCatalog, parse_rc_conf};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
struct TestLayout {
|
||||||
|
root: PathBuf,
|
||||||
|
system_definitions: PathBuf,
|
||||||
|
local_definitions: PathBuf,
|
||||||
|
required_services: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestLayout {
|
||||||
|
fn new() -> Self {
|
||||||
|
let serial = NEXT_TEMP.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let root = std::env::temp_dir()
|
||||||
|
.join(format!("cheesed-policy-{}-{serial}", std::process::id()));
|
||||||
|
let system_definitions = root.join("etc/rc.d");
|
||||||
|
let local_definitions = root.join("usr/local/etc/rc.d");
|
||||||
|
let required_services = root.join("required-services");
|
||||||
|
fs::create_dir_all(&system_definitions).unwrap();
|
||||||
|
fs::create_dir_all(&local_definitions).unwrap();
|
||||||
|
fs::write(
|
||||||
|
system_definitions.join("required"),
|
||||||
|
"#!/bin/sh\n# PROVIDE: REQUIRED\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
fs::write(
|
||||||
|
system_definitions.join("optional"),
|
||||||
|
"#!/bin/sh\n# PROVIDE: OPTIONAL\n# REQUIRE: REQUIRED\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
fs::write(
|
||||||
|
local_definitions.join("ported"),
|
||||||
|
"#!/bin/sh\n# PROVIDE: PORTED\n# REQUIRE: OPTIONAL\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
fs::write(&required_services, "required\n").unwrap();
|
||||||
|
Self {
|
||||||
|
root,
|
||||||
|
system_definitions,
|
||||||
|
local_definitions,
|
||||||
|
required_services,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn catalog(&self) -> ServiceCatalog {
|
||||||
|
ServiceCatalog::discover(
|
||||||
|
&self.system_definitions,
|
||||||
|
&self.local_definitions,
|
||||||
|
&self.required_services,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestLayout {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
fs::remove_dir_all(&self.root).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(policy: &RcPolicy) -> BTreeSet<&str> {
|
||||||
|
policy.enabled().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_comments_empty_lines_and_strict_assignments() {
|
||||||
|
let layout = TestLayout::new();
|
||||||
|
let policy = parse_rc_conf(
|
||||||
|
"\n # comment\nenable_optional=YES\nenable_ported=YES\n",
|
||||||
|
&layout.catalog(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(enabled(&policy), BTreeSet::from(["optional", "ported"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_shell_ambiguous_unknown_and_required_assignments() {
|
||||||
|
let layout = TestLayout::new();
|
||||||
|
let catalog = layout.catalog();
|
||||||
|
for invalid in [
|
||||||
|
"enable_optional =YES",
|
||||||
|
"enable_optional=\"YES\"",
|
||||||
|
"enable_optional=YES # comment",
|
||||||
|
"enable_optional=$(hostname)",
|
||||||
|
"enable_optional=YES=NO",
|
||||||
|
"rc_parallel=YES",
|
||||||
|
"enable_missing=NO",
|
||||||
|
"enable_required=YES",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
parse_rc_conf(invalid, &catalog).is_err(),
|
||||||
|
"accepted {invalid:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn orders_selected_services_from_rcorder_headers() {
|
||||||
|
let layout = TestLayout::new();
|
||||||
|
let catalog = layout.catalog();
|
||||||
|
let policy = parse_rc_conf("enable_optional=YES\nenable_ported=YES\n", &catalog).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
catalog.start_order(&policy).unwrap(),
|
||||||
|
["required", "optional", "ported"]
|
||||||
|
);
|
||||||
|
assert!(catalog.is_required("required"));
|
||||||
|
assert_eq!(
|
||||||
|
catalog.definition("ported").unwrap().path(),
|
||||||
|
layout.local_definitions.join("ported")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_disabled_missing_and_cyclic_dependencies() {
|
||||||
|
let layout = TestLayout::new();
|
||||||
|
let catalog = layout.catalog();
|
||||||
|
let disabled = parse_rc_conf("enable_ported=YES\n", &catalog).unwrap();
|
||||||
|
assert!(catalog.start_order(&disabled).is_err());
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
layout.system_definitions.join("required"),
|
||||||
|
"#!/bin/sh\n# PROVIDE: REQUIRED\n# REQUIRE: OPTIONAL\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let catalog = layout.catalog();
|
||||||
|
let cyclic = parse_rc_conf("enable_optional=YES\n", &catalog).unwrap();
|
||||||
|
assert!(catalog.start_order(&cyclic).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_duplicate_system_and_local_service_definitions() {
|
||||||
|
let layout = TestLayout::new();
|
||||||
|
fs::write(layout.local_definitions.join("optional"), "#!/bin/sh\n").unwrap();
|
||||||
|
let error = ServiceCatalog::discover(
|
||||||
|
&layout.system_definitions,
|
||||||
|
&layout.local_definitions,
|
||||||
|
&layout.required_services,
|
||||||
|
)
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(
|
||||||
|
error
|
||||||
|
.to_string()
|
||||||
|
.contains("duplicate service definition optional")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
CONFIG_LOCALVERSION="-mouse"
|
||||||
|
CONFIG_DEFAULT_HOSTNAME="mouse"
|
||||||
|
|
||||||
|
CONFIG_64BIT=y
|
||||||
|
CONFIG_X86_64=y
|
||||||
|
CONFIG_SMP=y
|
||||||
|
CONFIG_HYPERVISOR_GUEST=y
|
||||||
|
CONFIG_PARAVIRT=y
|
||||||
|
CONFIG_ACPI=y
|
||||||
|
CONFIG_EFI=y
|
||||||
|
CONFIG_EFI_STUB=y
|
||||||
|
|
||||||
|
CONFIG_MULTIUSER=y
|
||||||
|
CONFIG_PRINTK=y
|
||||||
|
CONFIG_BUG=y
|
||||||
|
CONFIG_FUTEX=y
|
||||||
|
CONFIG_EPOLL=y
|
||||||
|
CONFIG_SIGNALFD=y
|
||||||
|
CONFIG_TIMERFD=y
|
||||||
|
CONFIG_EVENTFD=y
|
||||||
|
CONFIG_BINFMT_ELF=y
|
||||||
|
CONFIG_BINFMT_SCRIPT=y
|
||||||
|
CONFIG_FILE_LOCKING=y
|
||||||
|
CONFIG_SWAP=y
|
||||||
|
CONFIG_CGROUPS=y
|
||||||
|
CONFIG_KALLSYMS=y
|
||||||
|
CONFIG_MODULES=y
|
||||||
|
CONFIG_MODULE_UNLOAD=y
|
||||||
|
CONFIG_MODVERSIONS=y
|
||||||
|
CONFIG_BASIC_MODVERSIONS=y
|
||||||
|
|
||||||
|
CONFIG_BLOCK=y
|
||||||
|
CONFIG_BLK_DEV=y
|
||||||
|
CONFIG_EFI_PARTITION=y
|
||||||
|
CONFIG_DEVTMPFS=y
|
||||||
|
CONFIG_DEVTMPFS_MOUNT=y
|
||||||
|
CONFIG_FW_LOADER=y
|
||||||
|
CONFIG_PROC_FS=y
|
||||||
|
CONFIG_PROC_SYSCTL=y
|
||||||
|
CONFIG_SYSFS=y
|
||||||
|
CONFIG_TMPFS=y
|
||||||
|
CONFIG_TMPFS_POSIX_ACL=y
|
||||||
|
|
||||||
|
CONFIG_NET=y
|
||||||
|
CONFIG_PACKET=y
|
||||||
|
CONFIG_PACKET_DIAG=y
|
||||||
|
CONFIG_UNIX=y
|
||||||
|
CONFIG_UNIX_DIAG=y
|
||||||
|
CONFIG_INET=y
|
||||||
|
CONFIG_INET_DIAG=y
|
||||||
|
CONFIG_INET_TCP_DIAG=y
|
||||||
|
CONFIG_INET_UDP_DIAG=y
|
||||||
|
CONFIG_IPV6=y
|
||||||
|
CONFIG_NETLINK_DIAG=y
|
||||||
|
CONFIG_NAMESPACES=y
|
||||||
|
CONFIG_NET_NS=y
|
||||||
|
CONFIG_NETFILTER=y
|
||||||
|
CONFIG_NETFILTER_ADVANCED=y
|
||||||
|
CONFIG_NF_CONNTRACK=y
|
||||||
|
CONFIG_NF_NAT=y
|
||||||
|
CONFIG_NF_TABLES=y
|
||||||
|
CONFIG_NF_TABLES_INET=y
|
||||||
|
CONFIG_NF_TABLES_NETDEV=y
|
||||||
|
CONFIG_NFT_CT=y
|
||||||
|
CONFIG_NFT_LOG=y
|
||||||
|
CONFIG_NFT_LIMIT=y
|
||||||
|
CONFIG_NFT_MASQ=y
|
||||||
|
CONFIG_NFT_REDIR=y
|
||||||
|
CONFIG_NFT_NAT=y
|
||||||
|
CONFIG_NFT_REJECT=y
|
||||||
|
CONFIG_NFT_REJECT_INET=y
|
||||||
|
CONFIG_NET_SCHED=y
|
||||||
|
CONFIG_NET_SCH_HTB=y
|
||||||
|
CONFIG_NET_SCH_FQ_CODEL=y
|
||||||
|
CONFIG_NET_CLS_BASIC=y
|
||||||
|
CONFIG_NET_CLS_U32=y
|
||||||
|
CONFIG_NETDEVICES=y
|
||||||
|
CONFIG_NET_CORE=y
|
||||||
|
CONFIG_ETHERNET=y
|
||||||
|
CONFIG_DUMMY=y
|
||||||
|
CONFIG_BONDING=y
|
||||||
|
CONFIG_TUN=y
|
||||||
|
CONFIG_VETH=y
|
||||||
|
CONFIG_VLAN_8021Q=y
|
||||||
|
CONFIG_BRIDGE=y
|
||||||
|
CONFIG_BRIDGE_VLAN_FILTERING=y
|
||||||
|
|
||||||
|
CONFIG_PCI=y
|
||||||
|
CONFIG_PCI_MSI=y
|
||||||
|
CONFIG_VIRTIO_MENU=y
|
||||||
|
CONFIG_VIRTIO=y
|
||||||
|
CONFIG_VIRTIO_PCI=y
|
||||||
|
CONFIG_VIRTIO_BLK=y
|
||||||
|
CONFIG_VIRTIO_NET=y
|
||||||
|
CONFIG_NET_VENDOR_INTEL=y
|
||||||
|
CONFIG_E1000=y
|
||||||
|
CONFIG_E1000E=y
|
||||||
|
|
||||||
|
CONFIG_SCSI=y
|
||||||
|
CONFIG_BLK_DEV_SD=y
|
||||||
|
CONFIG_ATA=y
|
||||||
|
CONFIG_SATA_AHCI=y
|
||||||
|
CONFIG_BLK_DEV_NVME=y
|
||||||
|
|
||||||
|
CONFIG_TTY=y
|
||||||
|
CONFIG_UNIX98_PTYS=y
|
||||||
|
CONFIG_SERIAL_8250=y
|
||||||
|
CONFIG_SERIAL_8250_CONSOLE=y
|
||||||
|
CONFIG_VT=y
|
||||||
|
CONFIG_VT_CONSOLE=y
|
||||||
|
CONFIG_VGA_CONSOLE=y
|
||||||
|
CONFIG_INPUT=y
|
||||||
|
CONFIG_INPUT_KEYBOARD=y
|
||||||
|
CONFIG_KEYBOARD_ATKBD=y
|
||||||
|
CONFIG_SERIO=y
|
||||||
|
CONFIG_SERIO_I8042=y
|
||||||
|
CONFIG_FB=y
|
||||||
|
CONFIG_FB_EFI=y
|
||||||
|
CONFIG_FRAMEBUFFER_CONSOLE=y
|
||||||
|
|
||||||
|
CONFIG_BTRFS_FS=y
|
||||||
|
CONFIG_BTRFS_FS_POSIX_ACL=y
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
timeout: 3
|
||||||
|
default_entry: 1
|
||||||
|
serial: yes
|
||||||
|
serial_baudrate: 115200
|
||||||
|
graphics: yes
|
||||||
|
interface_branding: MOUSE
|
||||||
|
|
||||||
|
/MOUSE
|
||||||
|
protocol: linux
|
||||||
|
path: boot():/vmlinuz-mouse
|
||||||
|
cmdline: console=tty0 console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed panic=-1
|
||||||
|
|
||||||
|
/MOUSE alternate
|
||||||
|
protocol: linux
|
||||||
|
path: boot():/vmlinuz-mouse
|
||||||
|
cmdline: console=tty0 console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rootflags=subvol=ROOT/alternate rw rootwait init=/sbin/cheesed panic=-1
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
diff --git a/include/sys/cdefs.h b/include/sys/cdefs.h
|
||||||
|
index 2dac3f9..aa16c50 100644
|
||||||
|
--- a/include/sys/cdefs.h
|
||||||
|
+++ b/include/sys/cdefs.h
|
||||||
|
@@ -28,6 +28,10 @@
|
||||||
|
#ifndef SYS_CDEFS_H
|
||||||
|
#define SYS_CDEFS_H
|
||||||
|
|
||||||
|
+#ifdef __APPLE__
|
||||||
|
+#include_next <sys/cdefs.h>
|
||||||
|
+#else
|
||||||
|
+
|
||||||
|
#include <features.h>
|
||||||
|
|
||||||
|
#ifdef __GLIBC__
|
||||||
|
@@ -53,4 +57,6 @@
|
||||||
|
|
||||||
|
#define __unreachable() __builtin_unreachable()
|
||||||
|
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
#endif
|
||||||
|
diff --git a/include/string.h b/include/string.h
|
||||||
|
index 68108f6..9707bcd 100644
|
||||||
|
--- a/include/string.h
|
||||||
|
+++ b/include/string.h
|
||||||
|
@@ -38,7 +38,9 @@
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
+#ifndef __APPLE__
|
||||||
|
void strmode(mode_t, char *);
|
||||||
|
+#endif
|
||||||
|
#ifndef HAVE_STRLCPY
|
||||||
|
size_t strlcpy(char *d, const char *s, size_t n);
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
diff --git a/include/sys/cdefs.h b/include/sys/cdefs.h
|
||||||
|
index aa16c50..4602b26 100644
|
||||||
|
--- a/include/sys/cdefs.h
|
||||||
|
+++ b/include/sys/cdefs.h
|
||||||
|
@@ -53,6 +53,7 @@
|
||||||
|
#define __dead2
|
||||||
|
#define __printf0like(x, y)
|
||||||
|
#define __printflike(x, y) __attribute__((format(printf, x, y)))
|
||||||
|
+#define __scanflike(x, y) __attribute__((format(scanf, x, y)))
|
||||||
|
#define __DECONST(a, v) ((a)(v))
|
||||||
|
#define __unused __attribute__((unused))
|
||||||
|
#define __unreachable() __builtin_unreachable()
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
diff --git a/configure b/configure
|
||||||
|
--- a/configure
|
||||||
|
+++ b/configure
|
||||||
|
@@ -17940,8 +17940,10 @@ $config_commands
|
||||||
|
echo " ${STABLE_RELEASE}"
|
||||||
|
echo "};"
|
||||||
|
echo "static char nftbuildstamp[] = {"
|
||||||
|
- for ((i = 56; i >= 0; i-= 8)); do
|
||||||
|
+ i=56
|
||||||
|
+ while test "$i" -ge 0; do
|
||||||
|
echo " ((uint64_t)MAKE_STAMP >> $i) & 0xff,"
|
||||||
|
+ i=$((i - 8))
|
||||||
|
done
|
||||||
|
echo "};"
|
||||||
|
) >nftversion.h
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
--- a/bootstrap/bootstrap
|
||||||
|
+++ b/bootstrap/bootstrap
|
||||||
|
@@ -683,7 +683,9 @@ Linux)
|
||||||
|
fi
|
||||||
|
# Debian/Ubuntu's awk is mawk, and mawk does not understand
|
||||||
|
# some regexps used in pkgsrc/mk.
|
||||||
|
- if [ -f /etc/debian_version ]; then
|
||||||
|
+ if [ -f /etc/mouse-release ]; then
|
||||||
|
+ bootstrap_awk=/usr/bin/awk
|
||||||
|
+ elif [ -f /etc/debian_version ]; then
|
||||||
|
need_awk=yes
|
||||||
|
# Arch uses gawk 5 that breaks some regexps. It doesn't provide pax
|
||||||
|
# anymore.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
--- rc.subr.orig
|
||||||
|
+++ rc.subr
|
||||||
|
@@ -266,6 +266,14 @@
|
||||||
|
_procname=$1
|
||||||
|
_interpreter=$2
|
||||||
|
_psargs=$3
|
||||||
|
|
||||||
|
+ # cheesed places the rc script and every process it starts in one
|
||||||
|
+ # service cgroup. That cgroup is the process database on MOUSE, so rc.d
|
||||||
|
+ # scripts do not need ps or package-specific pidfile interpretation.
|
||||||
|
+ if [ "${MOUSE_SERVICE_PIDS+x}" = x ]; then
|
||||||
|
+ ${_RCCMD_echo} -n "$MOUSE_SERVICE_PIDS"
|
||||||
|
+ return
|
||||||
|
+ fi
|
||||||
|
+
|
||||||
|
_pref=
|
||||||
|
if [ $_interpreter != "." ]; then # an interpreted script
|
||||||
|
@@ -548,7 +556,8 @@
|
||||||
|
# checkyesno ${rcvar}
|
||||||
|
# and return if that failed
|
||||||
|
#
|
||||||
|
- if [ -n "${rcvar}" -a "$rc_arg" != "rcvar" ]; then
|
||||||
|
+ if [ -z "${MOUSE_SERVICE_CGROUP:-}" ] &&
|
||||||
|
+ [ -n "${rcvar}" -a "$rc_arg" != "rcvar" ]; then
|
||||||
|
if ! checkyesno ${rcvar}; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
--- a/gethost.c
|
||||||
|
+++ b/gethost.c
|
||||||
|
@@ -29,7 +29,12 @@
|
||||||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
-#include "sh.h"
|
||||||
|
+#include <assert.h>
|
||||||
|
+#include <ctype.h>
|
||||||
|
+#include <stddef.h>
|
||||||
|
+#include <stdio.h>
|
||||||
|
+#include <stdlib.h>
|
||||||
|
+#include <string.h>
|
||||||
|
|
||||||
|
#ifdef SCO
|
||||||
|
# define perror __perror
|
||||||
|
@@ -45,8 +50,7 @@
|
||||||
|
# undef system
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-#include <ctype.h>
|
||||||
|
-#include <assert.h>
|
||||||
|
+#define INBUFSIZE 8192
|
||||||
|
|
||||||
|
#define ISSPACE(p) (isspace((unsigned char) (p)) && (p) != '\n')
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
SHELL=/bin/sh
|
||||||
|
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
|
||||||
|
|
||||||
|
# minute hour day month weekday user command
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
set path = ( /sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin )
|
||||||
|
|
||||||
|
if ( $?prompt ) then
|
||||||
|
set prompt = "mouse:%~%# "
|
||||||
|
endif
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
umask 022
|
||||||
|
setenv SHELL /bin/tcsh
|
||||||
|
|
||||||
|
cd
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
GROUP=100
|
||||||
|
HOME=/home
|
||||||
|
INACTIVE=-1
|
||||||
|
EXPIRE=
|
||||||
|
SHELL=/bin/tcsh
|
||||||
|
SKEL=/etc/skel
|
||||||
|
CREATE_MAIL_SPOOL=no
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Package-owned kernel modules live on persistent /usr/local.
|
||||||
|
external * /usr/local/lib/modules
|
||||||
|
search external built-in
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# The base image manages one wired interface.
|
||||||
|
allowinterfaces eth0
|
||||||
|
interface eth0
|
||||||
|
metric 100
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
root:x:0:root,toor
|
||||||
|
tty:x:5:
|
||||||
|
uucp:x:14:
|
||||||
|
users:x:100:
|
||||||
|
dhcpcd:x:101:
|
||||||
|
_ntp:x:102:
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
mouse
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
127.0.0.1 localhost mouse
|
||||||
|
::1 localhost mouse
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
ENV_PATH PATH=/bin:/usr/bin:/usr/local/bin
|
||||||
|
ENV_SUPATH PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
||||||
|
MAIL_DIR /var/mail
|
||||||
|
HUSHLOGIN_FILE .hushlogin
|
||||||
|
LOGIN_TIMEOUT 60
|
||||||
|
MOTD_FILE /etc/motd
|
||||||
|
PASS_MAX_DAYS 99999
|
||||||
|
PASS_MIN_DAYS 0
|
||||||
|
PASS_WARN_AGE 7
|
||||||
|
TTYGROUP tty
|
||||||
|
TTYPERM 0600
|
||||||
|
UID_MIN 1000
|
||||||
|
UID_MAX 60000
|
||||||
|
GID_MIN 1000
|
||||||
|
GID_MAX 60000
|
||||||
|
CREATE_HOME yes
|
||||||
|
USERGROUPS_ENAB yes
|
||||||
|
ENCRYPT_METHOD SHA512
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Load the exact module matching a kernel device alias. Device nodes themselves
|
||||||
|
# are supplied by devtmpfs and mdevd applies conservative default ownership.
|
||||||
|
$MODALIAS=.* root:root 0600 @modprobe -qb "$MODALIAS"
|
||||||
|
null root:root 0666
|
||||||
|
zero root:root 0666
|
||||||
|
full root:root 0666
|
||||||
|
random root:root 0444
|
||||||
|
urandom root:root 0444
|
||||||
|
console root:tty 0600
|
||||||
|
tty root:tty 0666
|
||||||
|
tty[0-9]* root:tty 0660
|
||||||
|
ttyS[0-9]* root:uucp 0660
|
||||||
|
.* root:root 0600
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
MOUSE 0.1.0 (x86_64)
|
||||||
|
Monolithic Opinionated Unix-like System Environment
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
NAME=MOUSE
|
||||||
|
ID=mouse
|
||||||
|
PRETTY_NAME="MOUSE 0.1.0"
|
||||||
|
VERSION="0.1.0"
|
||||||
|
VERSION_ID=0.1.0
|
||||||
|
ARCHITECTURE=x86_64
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
servers pool.ntp.org
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
root:x:0:0:root:/root:/bin/sh
|
||||||
|
toor:x:0:0:BSD-style root account:/root:/bin/tcsh
|
||||||
|
dhcpcd:x:101:101:dhcpcd privilege separation:/var/empty:/sbin/nologin
|
||||||
|
_ntp:x:102:102:OpenNTPD privilege separation:/var/empty:/sbin/nologin
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Persistent MOUSE service policy belongs in this file.
|
||||||
|
enable_network=YES
|
||||||
|
enable_cron=YES
|
||||||
|
enable_ntpd=YES
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: console_login
|
||||||
|
# REQUIRE: LOGIN
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=console_login
|
||||||
|
start_precmd=console_login_prestart
|
||||||
|
start_cmd=console_login_start
|
||||||
|
stop_cmd=:
|
||||||
|
|
||||||
|
autologin=
|
||||||
|
if grep -qw mouse.autologin=1 /proc/cmdline; then
|
||||||
|
autologin="--autologin root"
|
||||||
|
fi
|
||||||
|
|
||||||
|
console_login_prestart()
|
||||||
|
{
|
||||||
|
[ -e /run/utmp ] || : > /run/utmp
|
||||||
|
[ -e /var/log/wtmp ] || : > /var/log/wtmp
|
||||||
|
chmod 0664 /run/utmp /var/log/wtmp
|
||||||
|
}
|
||||||
|
|
||||||
|
console_login_start()
|
||||||
|
{
|
||||||
|
/usr/bin/setsid -f /sbin/agetty $autologin \
|
||||||
|
--login-program /bin/login --noclear --keep-baud \
|
||||||
|
115200,38400,9600 ttyS0 vt100 &
|
||||||
|
/usr/bin/setsid -f /sbin/agetty $autologin \
|
||||||
|
--login-program /bin/login --noclear tty1 linux &
|
||||||
|
}
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: cron
|
||||||
|
# REQUIRE: DAEMON
|
||||||
|
# BEFORE: LOGIN
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=cron
|
||||||
|
command=/usr/sbin/cron
|
||||||
|
command_args="-n -s &"
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: DAEMON
|
||||||
|
# REQUIRE: machine_identity
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=daemon
|
||||||
|
start_cmd=:
|
||||||
|
stop_cmd=:
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: LOGIN
|
||||||
|
# REQUIRE: DAEMON
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=login
|
||||||
|
start_cmd=:
|
||||||
|
stop_cmd=:
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: LOOPBACK
|
||||||
|
# BEFORE: DAEMON
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=loopback
|
||||||
|
|
||||||
|
loopback_start()
|
||||||
|
{
|
||||||
|
/sbin/ip link set lo up
|
||||||
|
/sbin/ip address replace 127.0.0.1/8 dev lo
|
||||||
|
/sbin/ip -6 address replace ::1/128 dev lo
|
||||||
|
}
|
||||||
|
|
||||||
|
start_cmd=loopback_start
|
||||||
|
stop_cmd=:
|
||||||
|
status_cmd=:
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: machine_identity
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=machine_identity
|
||||||
|
start_cmd=machine_identity_start
|
||||||
|
stop_cmd=:
|
||||||
|
|
||||||
|
machine_identity_start()
|
||||||
|
{
|
||||||
|
if [ ! -s /etc/machine-id ]; then
|
||||||
|
sed 's/-//g' /proc/sys/kernel/random/uuid > /etc/machine-id.new ||
|
||||||
|
return 1
|
||||||
|
chmod 0444 /etc/machine-id.new || return 1
|
||||||
|
mv /etc/machine-id.new /etc/machine-id || return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: mdevd DEVFS
|
||||||
|
# BEFORE: machine_identity
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=mdevd
|
||||||
|
command=/sbin/mdevd
|
||||||
|
command_args="-C &"
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: network NETWORKING
|
||||||
|
# REQUIRE: DAEMON
|
||||||
|
# BEFORE: LOGIN
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=network
|
||||||
|
command=/sbin/dhcpcd
|
||||||
|
command_args="-4 -b -q -c /usr/libexec/mouse-dhcpcd-hook"
|
||||||
|
start_precmd=network_prestart
|
||||||
|
start_postcmd=network_poststart
|
||||||
|
stop_postcmd=network_poststop
|
||||||
|
|
||||||
|
network_prestart()
|
||||||
|
{
|
||||||
|
rm -f /run/mouse-network.ready
|
||||||
|
}
|
||||||
|
|
||||||
|
network_poststart()
|
||||||
|
{
|
||||||
|
waited=0
|
||||||
|
while [ ! -f /run/mouse-network.ready ]; do
|
||||||
|
if [ "$waited" -ge 20 ]; then
|
||||||
|
echo "timed out waiting for an IPv4 DHCP lease" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
network_poststop()
|
||||||
|
{
|
||||||
|
rm -f /run/mouse-network.ready
|
||||||
|
}
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: ntpd TIMESYNC
|
||||||
|
# REQUIRE: DAEMON
|
||||||
|
# BEFORE: LOGIN
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=ntpd
|
||||||
|
command=/usr/sbin/ntpd
|
||||||
|
command_args="-d &"
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: SERVERS
|
||||||
|
# REQUIRE: DAEMON
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=servers
|
||||||
|
start_cmd=:
|
||||||
|
stop_cmd=:
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# PROVIDE: syslogd LOGGER
|
||||||
|
# REQUIRE: machine_identity
|
||||||
|
# BEFORE: DAEMON
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=syslogd
|
||||||
|
command=/usr/sbin/syslogd
|
||||||
|
command_args="-F -ss -f /etc/syslog.conf &"
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ttyS0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ntp 123/udp
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
root:$6$mouse$LofYSsSObtmx/zmS97A.TFONKSQSATXE4bD6dY1H7JSBSayuyAuc4Qwpm7JdfW9lUnri9DrQfUKGiLTNKA3v81:1:0:99999:7:::
|
||||||
|
toor:$6$mouse$LofYSsSObtmx/zmS97A.TFONKSQSATXE4bD6dY1H7JSBSayuyAuc4Qwpm7JdfW9lUnri9DrQfUKGiLTNKA3v81:1:0:99999:7:::
|
||||||
|
dhcpcd:!:1:0:99999:7:::
|
||||||
|
_ntp:!:1:0:99999:7:::
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/bin/sh
|
||||||
|
/bin/tcsh
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Keep the base logger local. Remote collection is an explicit package policy.
|
||||||
|
secure_mode 2
|
||||||
|
rotate_size 1M
|
||||||
|
rotate_count 5
|
||||||
|
|
||||||
|
auth,authpriv.* /var/log/auth.log
|
||||||
|
cron.* -/var/log/cron.log
|
||||||
|
kern.* -/var/log/kern.log
|
||||||
|
*.*;auth,authpriv.none -/var/log/messages
|
||||||
|
*.=emerg *
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Login defaults for the POSIX root account.
|
||||||
|
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
PS1='mouse:~# '
|
||||||
|
export PS1
|
||||||
|
|
||||||
|
umask 022
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
machine_identity
|
||||||
|
loopback
|
||||||
|
mdevd
|
||||||
|
syslogd
|
||||||
|
daemon
|
||||||
|
login
|
||||||
|
servers
|
||||||
|
console_login
|
||||||
Executable
+22
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
case "${reason:-}" in
|
||||||
|
BOUND|REBIND|REBOOT|RENEW)
|
||||||
|
temporary=/run/resolv.conf.new
|
||||||
|
: >"$temporary"
|
||||||
|
for server in ${new_domain_name_servers:-}; do
|
||||||
|
printf 'nameserver %s\n' "$server" >>"$temporary"
|
||||||
|
done
|
||||||
|
if [ ! -s "$temporary" ]; then
|
||||||
|
rm -f "$temporary"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
chmod 0644 "$temporary"
|
||||||
|
mv "$temporary" /etc/resolv.conf
|
||||||
|
printf '%s\n' "${new_ip_address:-unknown}" > /run/mouse-network.ready
|
||||||
|
;;
|
||||||
|
EXPIRE|FAIL|NAK|RELEASE|STOP|STOPPED)
|
||||||
|
rm -f /run/mouse-network.ready
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+137
@@ -0,0 +1,137 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
toolchain_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
config="$toolchain_dir/compiler.conf"
|
||||||
|
|
||||||
|
if [ ! -r "$config" ]; then
|
||||||
|
printf '%s\n' "missing cross-compiler configuration: $config" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
IFS= read -r c_compiler
|
||||||
|
IFS= read -r cxx_compiler
|
||||||
|
IFS= read -r sysroot
|
||||||
|
IFS= read -r linker
|
||||||
|
IFS= read -r builtins
|
||||||
|
IFS= read -r rust_eh_personality
|
||||||
|
} <"$config"
|
||||||
|
|
||||||
|
cxx_runtime=no
|
||||||
|
case "${0##*/}" in
|
||||||
|
*-cc)
|
||||||
|
compiler=$c_compiler
|
||||||
|
;;
|
||||||
|
*-bootstrap-c++)
|
||||||
|
compiler=$cxx_compiler
|
||||||
|
;;
|
||||||
|
*-c++)
|
||||||
|
compiler=$cxx_compiler
|
||||||
|
cxx_runtime=yes
|
||||||
|
set -- \
|
||||||
|
-nostdinc++ \
|
||||||
|
-isystem "$sysroot/usr/include/c++/v1" \
|
||||||
|
"$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "cross compiler invoked with an unknown name: ${0##*/}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
link=executable
|
||||||
|
caller_startup=no
|
||||||
|
for argument do
|
||||||
|
case "$argument" in
|
||||||
|
-c|-E|-M|-MM|-S|-fsyntax-only)
|
||||||
|
link=compile
|
||||||
|
;;
|
||||||
|
-shared)
|
||||||
|
link=shared
|
||||||
|
;;
|
||||||
|
-r)
|
||||||
|
link=relocatable
|
||||||
|
;;
|
||||||
|
-nostartfiles)
|
||||||
|
caller_startup=yes
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$link" = compile ]; then
|
||||||
|
exec "$compiler" \
|
||||||
|
--target=x86_64-unknown-linux-musl \
|
||||||
|
--sysroot="$sysroot" \
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Shared and relocatable links still need the target linker, but Clang should
|
||||||
|
# select their startup objects and libraries because they are not base-system
|
||||||
|
# executables and must not inherit the static base link recipe below.
|
||||||
|
if [ "$link" = shared ] || [ "$link" = relocatable ]; then
|
||||||
|
exec "$compiler" \
|
||||||
|
--target=x86_64-unknown-linux-musl \
|
||||||
|
--sysroot="$sysroot" \
|
||||||
|
--ld-path="$linker" \
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rust's self-contained musl target passes its own CRT objects together with
|
||||||
|
# -nostartfiles, so the caller supplies everything needed for startup.
|
||||||
|
if [ "$caller_startup" = yes ]; then
|
||||||
|
exec "$compiler" \
|
||||||
|
--target=x86_64-unknown-linux-musl \
|
||||||
|
--sysroot="$sysroot" \
|
||||||
|
--ld-path="$linker" \
|
||||||
|
-nostdlib \
|
||||||
|
-static \
|
||||||
|
-L"$sysroot/usr/lib" \
|
||||||
|
"$@" \
|
||||||
|
-Wl,--start-group \
|
||||||
|
${MOUSE_CROSS_EXTRA_STATIC_LIBS:-} \
|
||||||
|
"$builtins" \
|
||||||
|
"$sysroot/usr/lib/libc.a" \
|
||||||
|
-Wl,--end-group
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$cxx_runtime" = yes ]; then
|
||||||
|
exec "$compiler" \
|
||||||
|
--target=x86_64-unknown-linux-musl \
|
||||||
|
--sysroot="$sysroot" \
|
||||||
|
--ld-path="$linker" \
|
||||||
|
-nostdlib \
|
||||||
|
-static \
|
||||||
|
"$sysroot/usr/lib/crt1.o" \
|
||||||
|
"$sysroot/usr/lib/crti.o" \
|
||||||
|
-L"$sysroot/usr/lib" \
|
||||||
|
"$@" \
|
||||||
|
-Wl,--start-group \
|
||||||
|
${MOUSE_CROSS_EXTRA_STATIC_LIBS:-} \
|
||||||
|
"$sysroot/usr/lib/libc++.a" \
|
||||||
|
"$sysroot/usr/lib/libc++abi.a" \
|
||||||
|
"$sysroot/usr/lib/libunwind.a" \
|
||||||
|
"$builtins" \
|
||||||
|
"$rust_eh_personality" \
|
||||||
|
"$sysroot/usr/lib/libc.a" \
|
||||||
|
-Wl,--end-group \
|
||||||
|
"$sysroot/usr/lib/crtn.o"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$compiler" \
|
||||||
|
--target=x86_64-unknown-linux-musl \
|
||||||
|
--sysroot="$sysroot" \
|
||||||
|
--ld-path="$linker" \
|
||||||
|
-nostdlib \
|
||||||
|
-static \
|
||||||
|
"$sysroot/usr/lib/crt1.o" \
|
||||||
|
"$sysroot/usr/lib/crti.o" \
|
||||||
|
-L"$sysroot/usr/lib" \
|
||||||
|
"$@" \
|
||||||
|
-Wl,--start-group \
|
||||||
|
${MOUSE_CROSS_EXTRA_STATIC_LIBS:-} \
|
||||||
|
"$builtins" \
|
||||||
|
"$rust_eh_personality" \
|
||||||
|
"$sysroot/usr/lib/libc.a" \
|
||||||
|
-Wl,--end-group \
|
||||||
|
"$sysroot/usr/lib/crtn.o"
|
||||||
Executable
+88
@@ -0,0 +1,88 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
root=/run/mouse-root
|
||||||
|
active_file=/etc/mouse-boot-environment
|
||||||
|
btrfs=/sbin/btrfs
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf '%s\n' "mouse-bootenv: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
read_environment() {
|
||||||
|
[ -f "$active_file" ] || fail "missing $active_file"
|
||||||
|
environment=$(cat "$active_file")
|
||||||
|
case "$environment" in
|
||||||
|
default|alternate) ;;
|
||||||
|
*) fail "invalid boot environment: $environment" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
other_environment() {
|
||||||
|
case "$1" in
|
||||||
|
default) printf '%s\n' alternate ;;
|
||||||
|
alternate) printf '%s\n' default ;;
|
||||||
|
*) fail "invalid boot environment: $1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
require_storage() {
|
||||||
|
[ -d "$root/ROOT/default" ] || fail "MOUSE btrfs root is not mounted"
|
||||||
|
[ -d "$root/ROOT/alternate" ] || fail "alternate boot environment is missing"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_default() {
|
||||||
|
target_path=$1
|
||||||
|
target_id=$("$btrfs" inspect-internal rootid "$target_path")
|
||||||
|
case "$target_id" in
|
||||||
|
''|*[!0-9]*) fail "could not resolve the subvolume ID for $target_path" ;;
|
||||||
|
esac
|
||||||
|
"$btrfs" subvolume set-default "$target_id" "$root"
|
||||||
|
}
|
||||||
|
|
||||||
|
activate() {
|
||||||
|
target=$1
|
||||||
|
case "$target" in
|
||||||
|
default|alternate) ;;
|
||||||
|
*) fail "invalid boot environment: $target" ;;
|
||||||
|
esac
|
||||||
|
[ "$(cat "$root/ROOT/$target/etc/mouse-boot-environment")" = "$target" ] ||
|
||||||
|
fail "$target has an invalid environment marker"
|
||||||
|
set_default "$root/ROOT/$target"
|
||||||
|
sync
|
||||||
|
printf '%s\n' "$target will boot next"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_environment
|
||||||
|
require_storage
|
||||||
|
|
||||||
|
case "${1:-status}" in
|
||||||
|
status)
|
||||||
|
printf '%s\n' "running: $environment"
|
||||||
|
"$btrfs" subvolume get-default "$root"
|
||||||
|
;;
|
||||||
|
prepare)
|
||||||
|
inactive=$(other_environment "$environment")
|
||||||
|
set_default "$root/ROOT/$environment"
|
||||||
|
"$btrfs" subvolume delete --commit-after "$root/ROOT/$inactive"
|
||||||
|
"$btrfs" subvolume snapshot \
|
||||||
|
"$root/ROOT/$environment" \
|
||||||
|
"$root/ROOT/$inactive"
|
||||||
|
printf '%s\n' "$inactive" \
|
||||||
|
>"$root/ROOT/$inactive/etc/mouse-boot-environment"
|
||||||
|
sync
|
||||||
|
printf '%s\n' "$inactive is ready for an update"
|
||||||
|
;;
|
||||||
|
activate)
|
||||||
|
[ "$#" -eq 2 ] || fail "usage: mouse-bootenv activate default|alternate"
|
||||||
|
activate "$2"
|
||||||
|
;;
|
||||||
|
rollback)
|
||||||
|
[ "$#" -eq 1 ] || fail "usage: mouse-bootenv rollback"
|
||||||
|
activate "$(other_environment "$environment")"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "usage: mouse-bootenv [status|prepare|activate NAME|rollback]"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Rust's compiler-builtins archive carries an unused unwind reference on
|
||||||
|
* x86_64. Base C programs do not unwind through Rust, but lld still requires
|
||||||
|
* the personality symbol when it extracts the relevant builtins object.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
rust_eh_personality(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# SHA-256 URL filename
|
||||||
|
20a0b0a2bb2525fa53c7eee9beb854b4c9cf172eabb209af7020743547bfe9fb https://www.greenwoodsoftware.com/less/less-704.tar.gz less-704.tar.gz
|
||||||
|
8bf0d570f01e70a6e124884088870cbed7537f36328d512909eb10cd53179d9c https://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz mandoc-1.14.6.tar.gz
|
||||||
|
45672fec165cb4cc1358a2d76b5d57d22876dcb97ab169427ac385cbe1d5597a https://astron.com/pub/file/file-5.47.tar.gz file-5.47.tar.gz
|
||||||
|
01a7b881bd220bfdf615f97b8718f80bdfd3f6add385b993dcf6efd14e8c0ac6 https://ftp.gnu.org/gnu/gzip/gzip-1.14.tar.xz gzip-1.14.tar.xz
|
||||||
|
ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269 https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz bzip2-1.0.8.tar.gz
|
||||||
|
33bf69c0d6c698e83a68f77e6c1f465778e418ca0b3d59860d3ab446f4ac99a6 https://github.com/tukaani-project/xz/releases/download/v5.8.3/xz-5.8.3.tar.bz2 xz-5.8.3.tar.bz2
|
||||||
|
fd9fa1b95809417157ca83dd72957e3261bdbce896353cb936f80af0b33a4b5c https://www.kernel.org/pub/linux/utils/net/iproute2/iproute2-7.1.0.tar.xz iproute2-7.1.0.tar.xz
|
||||||
|
274b9b919ef3152bfb3da3a13c950dd60d6e2bcd54230ffeca298d03b40d0525 https://www.netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 libmnl-1.0.5.tar.bz2
|
||||||
|
607da28dba66fbdeccf8ef1395dded9077e8d19f2995f9a4d45a9c2f0bcffba8 https://www.netfilter.org/projects/libnftnl/files/libnftnl-1.3.1.tar.xz libnftnl-1.3.1.tar.xz
|
||||||
|
372931bda8556b310636a2f9020adc710f9bab66f47efe0ce90bff800ac2530c https://www.netfilter.org/projects/nftables/files/nftables-1.1.6.tar.xz nftables-1.1.6.tar.xz
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# SHA-256 URL filename
|
||||||
|
abbfe6ee4a2b2d19a3a7c140c253015deed098d9731a0326db6465740d747846 https://dl-cdn.alpinelinux.org/alpine/edge/community/x86_64/ovmf-0.0.202605-r0.apk ovmf-0.0.202605-r0.apk
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# SHA-256 URL filename
|
||||||
|
f78602932219125e211c5f5bfd84edcfd4ec5ce88fc944f8248413f665bef236 https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.35.tar.xz linux-6.18.35.tar.xz
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# sha256 url filename
|
||||||
|
d8c008b6c786aeb9c7d5260d3fc8d14b15fed32f339be620fe7226f08061fc72 https://cdn.netbsd.org/pub/pkgsrc/pkgsrc-2026Q2/pkgsrc-2026Q2.tar.gz pkgsrc-2026Q2.tar.gz
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# SHA-256 URL filename
|
||||||
|
d585fd3b613c66151fc3249e8ed44f77020cb5e6c1e635a616d3f9f82460512a https://musl.libc.org/releases/musl-1.2.6.tar.gz musl-1.2.6.tar.gz
|
||||||
|
bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16 https://zlib.net/zlib-1.3.2.tar.gz zlib-1.3.2.tar.gz
|
||||||
|
d3a8ba457ae25c27c84fd2830a2efdcc5b1d40bf585d4eb0d35f47e99e5d4774 https://github.com/libarchive/libarchive/releases/download/v3.8.7/libarchive-3.8.7.tar.xz libarchive-3.8.7.tar.xz
|
||||||
|
9d3ebd651e5f70b87b1327b01cbd7e0c01a0f036b4c1371f653b7704b11daf23 https://github.com/sabotage-linux/netbsd-curses/archive/refs/tags/v0.3.2.tar.gz netbsd-curses-0.3.2.tar.gz
|
||||||
|
39bf67452fa41d0948c2197601053f48b3d78a029389734332a6309a680c6c87 https://download-mirror.savannah.gnu.org/releases/attr/attr-2.5.2.tar.gz attr-2.5.2.tar.gz
|
||||||
|
5f2bdbad629707aa7d85c623f994aa8a1d2dec55a73de5205bac0bf6058a2f7c https://download-mirror.savannah.gnu.org/releases/acl/acl-2.3.2.tar.gz acl-2.3.2.tar.gz
|
||||||
|
d12249ffad3ef04b160e6419adf1bbe7e593a60bb23f0a0a077fa780b214934a https://github.com/Juniper/libxo/releases/download/1.7.5/libxo-1.7.5.tar.gz libxo-1.7.5.tar.gz
|
||||||
|
aa8b6eeca8b93db659a7ebf421a7089b3f7f8457deba34170cd146b92fe60546 https://github.com/tcsh-org/tcsh/archive/refs/tags/TCSH6_24_16.tar.gz tcsh-6.24.16.tar.gz
|
||||||
|
183ad94242316cad014b54668e0b117ba4d8f6978b1229cde3fc5cd913020751 https://github.com/chimera-linux/chimerautils/archive/refs/tags/v15.0.3.tar.gz chimerautils-15.0.3.tar.gz
|
||||||
|
a8c0d28a529ca480f9f36cf5792e2cd21984552a3c8e4aa11a24aa31aeac98e8 https://github.com/openssl/openssl/releases/download/openssl-3.5.7/openssl-3.5.7.tar.gz openssl-3.5.7.tar.gz
|
||||||
|
3ff344e30b9b1ed2971044eabb438a08f2e2245ddb5f8ab1a3ad8b63ab4eaf91 https://curl.se/ca/cacert-2026-07-16.pem cacert-2026-07-16.pem
|
||||||
|
b035c1aeaf82d6988e0feb9be8ac80e1825227ff01d0c7a92e4356f423db0b62 https://raw.githubusercontent.com/chimera-linux/cports/1e8911eb1bb9dc94a6734686b8327206d5ac89e0/main/musl-bsd-headers/files/cdefs.h cdefs.h
|
||||||
|
3659cd137c320991a78413dd370a92fd18e0a8bc36d017d554f08677a37d7d5a https://raw.githubusercontent.com/chimera-linux/cports/1e8911eb1bb9dc94a6734686b8327206d5ac89e0/main/musl-bsd-headers/files/queue.h queue.h
|
||||||
|
e1e498a79bf160a5766fa560f2b07b206fe89fe21a62600c77d72e00a6992f92 https://raw.githubusercontent.com/chimera-linux/cports/1e8911eb1bb9dc94a6734686b8327206d5ac89e0/main/musl-bsd-headers/files/tree.h tree.h
|
||||||
|
a3c6ac4e3a3f7407760f87810e6937a213934bd8d2d7b9ea477489206d80742c https://raw.githubusercontent.com/chimera-linux/cports/1e8911eb1bb9dc94a6734686b8327206d5ac89e0/main/musl-bsd-headers/files/error.h error.h
|
||||||
|
febaa8f7c1916521c53eb5fd11c0641b5eb4741c2c6e9b42c288ed62d9e4fd2c https://github.com/mesonbuild/meson/archive/refs/tags/1.9.1.tar.gz meson-1.9.1.tar.gz
|
||||||
|
f0055ad0369bf2e372955ba55128d000cfcc21777057806015b45e4accbebf23 https://github.com/ninja-build/ninja/archive/refs/tags/v1.13.1.tar.gz ninja-1.13.1.tar.gz
|
||||||
|
79721badcad1987dead9c3609eb4877ab9b58821c06bdacb824f2c8897c11f2a https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz pkgconf-2.5.1.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
|
||||||
|
48a89ecf49927ed60b42b73edc07a63f6198d2c47a848f723a495c5cd66e220f https://github.com/NetworkConfiguration/dhcpcd/archive/8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4.tar.gz dhcpcd-8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4.tar.gz
|
||||||
|
dd16fb1d67bfab79a72f5e8390735c49e3e8e70b4945a15ab1f81ddb78658fb3 https://ftp.gnu.org/gnu/make/make-4.4.1.tar.gz make-4.4.1.tar.gz
|
||||||
|
f1da374a15ba7605cf378347f96bc8b678d3d7c0765269c8242cfe5b0789c571 https://github.com/cronie-crond/cronie/releases/download/cronie-1.7.2/cronie-1.7.2.tar.gz cronie-1.7.2.tar.gz
|
||||||
|
091eeb3f4e358e28c3ab2ea58f93d7a0b5758a20d7c8a0418e162e9b2c27addc https://cdn.openbsd.org/pub/OpenBSD/OpenNTPD/openntpd-7.9p1.tar.gz openntpd-7.9p1.tar.gz
|
||||||
|
c2e6d193cc78f84cd6ddb72aaf6d5c6a9162f0470e5992092057f5ff518562fa https://sourceforge.net/projects/procps-ng/files/Production/procps-ng-4.0.5.tar.xz/download procps-ng-4.0.5.tar.xz
|
||||||
|
19e680c9eef8c079da4da37040b5f5453763205b4edfb1e2c114de77908927e4 https://github.com/iputils/iputils/archive/refs/tags/20250605.tar.gz iputils-20250605.tar.gz
|
||||||
|
5a5d5073070cc7e0c7a7a3c6ec2a0e1780850c8b47b3e3892226b93ffcb9cb54 https://mirrors.edge.kernel.org/pub/linux/utils/kernel/kmod/kmod-34.2.tar.xz kmod-34.2.tar.xz
|
||||||
|
f9c905e74935c6fe911c7e344e3e89d5fbd2014c1a04650b524b15ce9b5635d1 https://skarnet.org/software/skalibs/skalibs-2.15.1.0.tar.gz skalibs-2.15.1.0.tar.gz
|
||||||
|
ce1ae0149b6a57a34f608218fd6181aa6aa68135cac2f4d931b5b417b072e244 https://skarnet.org/software/mdevd/mdevd-0.1.8.2.tar.gz mdevd-0.1.8.2.tar.gz
|
||||||
|
bc410ca64551a11fac6518b418fb6b8afbd888a70af2c5eb353334a706727bca https://github.com/troglobit/sysklogd/releases/download/v2.7.2/sysklogd-2.7.2.tar.gz sysklogd-2.7.2.tar.gz
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# SHA-256 URL filename
|
||||||
|
c286d6876cbcd72327a0b417e4cfd280353ec23e37b549fdbcd7800a832d9a99 https://www.kernel.org/pub/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v7.0.tar.xz btrfs-progs-v7.0.tar.xz
|
||||||
|
37d7284556b20954e56e1ca85b80226768902e2edabd3b649e9e72c0c9012ee3 https://github.com/facebook/zstd/archive/refs/tags/v1.5.7.tar.gz zstd-1.5.7.tar.gz
|
||||||
|
26e53e04d562e91403018997afdb5d8c4448a580a3db433d0bccc9b0f02ac134 https://github.com/Limine-Bootloader/Limine/releases/download/v12.3.2/limine-binary.tar.xz limine-binary-12.3.2.tar.xz
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# SHA-256 URL filename
|
||||||
|
2488c33a959eafba1c44f253e5bbe7ac958eb53fa626298a3a5f4b87373767cd https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.3/llvm-project-22.1.3.src.tar.xz llvm-project-22.1.3.src.tar.xz
|
||||||
Executable
+154
@@ -0,0 +1,154 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "$#" -ne 1 ]; then
|
||||||
|
printf '%s\n' "usage: audit-static-base.sh ROOTFS" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
rootfs=$1
|
||||||
|
failed=0
|
||||||
|
count=0
|
||||||
|
|
||||||
|
for directory in bin sbin usr/bin usr/sbin; do
|
||||||
|
if [ ! -d "$rootfs/$directory" ] || [ -L "$rootfs/$directory" ]; then
|
||||||
|
printf '%s\n' "FHS command directory must be a real directory: /$directory" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for executable in \
|
||||||
|
/bin/chgrp /bin/login /bin/sh /bin/su /bin/tcsh \
|
||||||
|
/sbin/agetty /sbin/blkid /sbin/bridge /sbin/btrfs /sbin/dhcpcd /sbin/dmesg \
|
||||||
|
/sbin/depmod /sbin/insmod /sbin/kmod /sbin/lsmod /sbin/mdevd \
|
||||||
|
/sbin/mdevd-coldplug /sbin/mkfs.btrfs /sbin/modinfo /sbin/modprobe \
|
||||||
|
/sbin/mount /sbin/fdisk /sbin/ip /sbin/losetup /sbin/nft /sbin/rmmod \
|
||||||
|
/sbin/sfdisk /sbin/ss /sbin/swapoff /sbin/swapon /sbin/sysctl /sbin/tc \
|
||||||
|
/sbin/umount /sbin/wipefs \
|
||||||
|
/usr/bin/setsid /usr/sbin/mouse-bootenv \
|
||||||
|
/usr/bin/awk /usr/bin/basename /usr/bin/cmp /usr/bin/cut \
|
||||||
|
/usr/bin/diff /usr/bin/dirname /usr/bin/fetch /usr/bin/groups /usr/bin/ldd \
|
||||||
|
/usr/bin/install /usr/bin/m4 /usr/bin/nc /usr/bin/patch \
|
||||||
|
/usr/bin/sort /usr/bin/test /usr/bin/tr /usr/bin/whoami \
|
||||||
|
/usr/bin/xargs \
|
||||||
|
/usr/bin/apropos /usr/bin/bzip2 /usr/bin/crontab \
|
||||||
|
/usr/bin/file /usr/bin/findmnt /usr/bin/flock /usr/bin/free \
|
||||||
|
/usr/bin/gzip /usr/bin/less /usr/bin/lsblk /usr/bin/man /usr/bin/mandoc \
|
||||||
|
/usr/bin/nvi /usr/bin/openssl /usr/bin/passwd /usr/bin/pgrep \
|
||||||
|
/usr/bin/pkill /usr/bin/readlink /usr/bin/sha256 /usr/bin/tar \
|
||||||
|
/usr/bin/top /usr/bin/tput /usr/bin/unzip \
|
||||||
|
/usr/bin/uptime /usr/bin/vmstat /usr/bin/watch /usr/bin/xz /usr/bin/zstd \
|
||||||
|
/bin/ping /bin/ps /usr/bin/vi /usr/sbin/makewhatis \
|
||||||
|
/usr/sbin/cron /usr/sbin/groupadd /usr/sbin/ntpd /usr/sbin/syslogd \
|
||||||
|
/usr/sbin/useradd
|
||||||
|
do
|
||||||
|
if [ ! -x "$rootfs$executable" ]; then
|
||||||
|
printf '%s\n' "missing executable at its FHS path: $executable" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -f "$rootfs/etc/ssl/cert.pem" ]; then
|
||||||
|
printf '%s\n' "missing TLS trust bundle: /etc/ssl/cert.pem" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$rootfs/usr/share/man/mandoc.db" ]; then
|
||||||
|
printf '%s\n' "missing World manual-page database" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$rootfs/etc/mouse-release" ]; then
|
||||||
|
for executable in /sbin/cheesed /sbin/poweroff /sbin/reboot /sbin/service /sbin/shutdown; do
|
||||||
|
if [ ! -x "$rootfs$executable" ]; then
|
||||||
|
printf '%s\n' "missing executable at its FHS path: $executable" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$rootfs/usr/share/mouse/pkgsrc.version" ]; then
|
||||||
|
if [ "$(cat "$rootfs/usr/share/mouse/pkgsrc.version")" != 2026Q2 ]; then
|
||||||
|
printf '%s\n' "unexpected pkgsrc version" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$rootfs/usr/bin/cc" ]; then
|
||||||
|
for tool in \
|
||||||
|
/usr/bin/ar /usr/bin/c++ /usr/bin/clang /usr/bin/clang++ \
|
||||||
|
/usr/bin/gmake /usr/bin/ld.lld /usr/bin/make /usr/bin/objdump \
|
||||||
|
/usr/bin/pkgconf /usr/bin/readelf
|
||||||
|
do
|
||||||
|
if [ ! -x "$rootfs$tool" ]; then
|
||||||
|
printf '%s\n' "incomplete native toolchain: $tool" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for development_file in \
|
||||||
|
/usr/include/stdio.h /usr/include/c++/v1/filesystem \
|
||||||
|
/usr/include/openssl/ssl.h /usr/include/zlib.h \
|
||||||
|
/lib/ld-musl-x86_64.so.1 /usr/lib/libc.so \
|
||||||
|
/usr/lib/crt1.o /usr/lib/Scrt1.o /usr/lib/crtbegin.o \
|
||||||
|
/usr/lib/crtbeginS.o /usr/lib/crtendS.o \
|
||||||
|
/usr/lib/libc.a /usr/lib/libc++.a /usr/lib/libc++.so \
|
||||||
|
/usr/lib/libc++abi.so /usr/lib/libunwind.so \
|
||||||
|
/usr/lib/libc++abi.a /usr/lib/libcrypto.a /usr/lib/libssl.a \
|
||||||
|
/usr/lib/libunwind.a /usr/lib/libz.a
|
||||||
|
do
|
||||||
|
if [ ! -f "$rootfs$development_file" ]; then
|
||||||
|
printf '%s\n' "incomplete development sysroot: $development_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$rootfs/usr/share/mk" ] || [ -e "$rootfs/usr/bin/bmake" ]; then
|
||||||
|
printf '%s\n' "base image must leave BSD make to pkgsrc under /usr/local" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -e "$rootfs/usr/bin/make" ]; then
|
||||||
|
if [ ! -L "$rootfs/usr/bin/gmake" ] || \
|
||||||
|
[ "$(readlink "$rootfs/usr/bin/gmake")" != make ]; then
|
||||||
|
printf '%s\n' "/usr/bin/gmake must alias the base GNU make" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$rootfs/usr/bin/rc-update" ] ||
|
||||||
|
[ -e "$rootfs/sbin/rc-update" ]; then
|
||||||
|
printf '%s\n' "forbidden persistent-policy tool in base image: rc-update" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r executable; do
|
||||||
|
description=$(file "$executable")
|
||||||
|
case "$description" in
|
||||||
|
*ELF*)
|
||||||
|
if objdump -p "$executable" |
|
||||||
|
grep -Eq '^[[:space:]]*SONAME[[:space:]]'; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
count=$((count + 1))
|
||||||
|
if objdump -p "$executable" |
|
||||||
|
grep -Eq '^[[:space:]]*(INTERP|NEEDED)[[:space:]]'; then
|
||||||
|
printf '%s\n' "dynamic base executable: $executable" >&2
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <<EOF
|
||||||
|
$(find "$rootfs" -path "$rootfs/usr/local" -prune -o \
|
||||||
|
-type f -perm -0100 -print | LC_ALL=C sort)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$count" -eq 0 ]; then
|
||||||
|
printf '%s\n' "no ELF executables found under $rootfs" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$failed" -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "static ELF audit passed: $count executables"
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
source_dir=${MOUSE_SOURCE_DIR:-"$build_dir/sources"}
|
||||||
|
rootfs_image=${MOUSE_ROOTFS_IMAGE:-"$build_dir/mouse-rootfs.btrfs"}
|
||||||
|
kernel_image=${MOUSE_KERNEL_IMAGE:-"$build_dir/vmlinuz-virt"}
|
||||||
|
disk_image=${MOUSE_DISK_IMAGE_OUTPUT:-"$build_dir/mouse-disk.img"}
|
||||||
|
limine_archive=${MOUSE_LIMINE_ARCHIVE:-"$source_dir/limine-binary-12.3.2.tar.xz"}
|
||||||
|
limine_config=${MOUSE_LIMINE_CONFIG:-"$repo_dir/base/limine.conf"}
|
||||||
|
|
||||||
|
for command in cc make mcopy mformat mmd sfdisk; do
|
||||||
|
if ! command -v "$command" >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "$command is required to build the boot disk" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for input in "$rootfs_image" "$kernel_image" \
|
||||||
|
"$limine_archive" "$limine_config"; do
|
||||||
|
if [ ! -f "$input" ]; then
|
||||||
|
printf '%s\n' "missing boot-disk input: $input" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rootfs_bytes=$(stat -c %s "$rootfs_image")
|
||||||
|
|
||||||
|
sector_size=512
|
||||||
|
boot_start=2048
|
||||||
|
boot_sectors=262144
|
||||||
|
root_start=$((boot_start + boot_sectors))
|
||||||
|
root_sectors=$(((rootfs_bytes + sector_size - 1) / sector_size))
|
||||||
|
disk_sectors=$((root_start + root_sectors + 2048))
|
||||||
|
|
||||||
|
working=$(mktemp -d "${TMPDIR:-/tmp}/mouse-boot-disk.XXXXXX")
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$working"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
tar -xf "$limine_archive" -C "$working"
|
||||||
|
limine_source="$working/limine-binary"
|
||||||
|
make -s -C "$limine_source"
|
||||||
|
|
||||||
|
boot_image="$working/mouse-boot.fat"
|
||||||
|
truncate -s $((boot_sectors * sector_size)) "$boot_image"
|
||||||
|
mformat -i "$boot_image" -F -v MOUSE_BOOT ::
|
||||||
|
mmd -i "$boot_image" ::/EFI ::/EFI/BOOT
|
||||||
|
mcopy -i "$boot_image" \
|
||||||
|
"$limine_source/BOOTX64.EFI" ::/EFI/BOOT/BOOTX64.EFI
|
||||||
|
mcopy -i "$boot_image" \
|
||||||
|
"$limine_source/limine-bios.sys" ::/limine-bios.sys
|
||||||
|
mcopy -i "$boot_image" "$limine_config" ::/limine.conf
|
||||||
|
mcopy -i "$boot_image" "$kernel_image" ::/vmlinuz-mouse
|
||||||
|
|
||||||
|
rm -f "$disk_image"
|
||||||
|
truncate -s $((disk_sectors * sector_size)) "$disk_image"
|
||||||
|
sfdisk --quiet "$disk_image" <<EOF
|
||||||
|
label: gpt
|
||||||
|
label-id: 4d4f5553-4500-4000-8000-000000000000
|
||||||
|
unit: sectors
|
||||||
|
|
||||||
|
start=34, size=$((boot_start - 34)), type=21686148-6449-6e6f-744e-656564454649, uuid=4d4f5553-4500-4000-8000-000000000003, name="MOUSE_BIOS"
|
||||||
|
start=$boot_start, size=$boot_sectors, type=c12a7328-f81f-11d2-ba4b-00a0c93ec93b, uuid=4d4f5553-4500-4000-8000-000000000001, name="MOUSE_BOOT"
|
||||||
|
start=$root_start, size=$root_sectors, type=0fc63daf-8483-4772-8e79-3d69d8477de4, uuid=4d4f5553-4500-4000-8000-000000000002, name="MOUSE_ROOT"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
dd if="$boot_image" of="$disk_image" bs="$sector_size" \
|
||||||
|
seek="$boot_start" conv=notrunc status=none
|
||||||
|
dd if="$rootfs_image" of="$disk_image" bs="$sector_size" \
|
||||||
|
seek="$root_start" conv=notrunc status=none
|
||||||
|
"$limine_source/limine" bios-install "$disk_image"
|
||||||
|
|
||||||
|
printf '%s\n' "built $disk_image with Limine BIOS and UEFI boot support"
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
static_dir="$build_dir/static"
|
||||||
|
base_root="$static_dir/rootfs"
|
||||||
|
cc="$static_dir/cross-tools/bin/x86_64-mouse-linux-musl-cc"
|
||||||
|
|
||||||
|
for requirement in \
|
||||||
|
"$cc" \
|
||||||
|
"$static_dir/sysroot/usr/lib/libc.a"
|
||||||
|
do
|
||||||
|
if [ ! -x "$requirement" ] && [ ! -f "$requirement" ]; then
|
||||||
|
printf '%s\n' "missing static-base input: $requirement" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
install -m 0755 \
|
||||||
|
"$repo_dir/base/support/mouse-bootenv" \
|
||||||
|
"$base_root/usr/sbin/mouse-bootenv"
|
||||||
|
ln -sfn ../usr/lib/libc.so "$base_root/lib/ld-musl-x86_64.so.1"
|
||||||
Executable
+211
@@ -0,0 +1,211 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
cheesed_dir="$repo_dir/base/cheesed"
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
target=${CHEESED_TARGET:-x86_64-unknown-linux-musl}
|
||||||
|
test_fixtures=${MOUSE_INCLUDE_TEST_FIXTURES:-0}
|
||||||
|
kernel_modules=${MOUSE_KERNEL_MODULES:-"$build_dir/kernel/modules"}
|
||||||
|
staging=
|
||||||
|
image_staging=
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
for directory in "$staging" "$image_staging"; do
|
||||||
|
if [ -n "$directory" ] && [ -d "$directory" ]; then
|
||||||
|
rm -rf "$directory"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
case "$test_fixtures" in
|
||||||
|
0|1) ;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "MOUSE_INCLUDE_TEST_FIXTURES must be 0 or 1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
base_root="$build_dir/static/rootfs"
|
||||||
|
pkgsrc_seed=${MOUSE_PKGSRC_SEED:-"$build_dir/pkgsrc-seed"}
|
||||||
|
for input in "$base_root" "$build_dir/vmlinuz-virt"; do
|
||||||
|
if [ ! -e "$input" ]; then
|
||||||
|
printf '%s\n' "missing prepared image input: $input" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for seed_directory in "$pkgsrc_seed/usr/local" "$pkgsrc_seed/var/db/pkg"; do
|
||||||
|
if [ ! -d "$seed_directory" ]; then
|
||||||
|
printf '%s\n' "missing pkgsrc seed directory: $seed_directory" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
btrfs_image_tool=${MOUSE_BTRFS_IMAGE_TOOL:-"$base_root/sbin/mkfs.btrfs"}
|
||||||
|
if [ ! -x "$btrfs_image_tool" ]; then
|
||||||
|
printf '%s\n' "missing btrfs image tool: $btrfs_image_tool" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -n "${MOUSE_CHEESED_BINARY:-}" ]; then
|
||||||
|
cheesed_binary=$MOUSE_CHEESED_BINARY
|
||||||
|
else
|
||||||
|
cheesed_binary=$(CHEESED_TARGET="$target" "$cheesed_dir/scripts/build-static.sh")
|
||||||
|
fi
|
||||||
|
staging=$(mktemp -d "${TMPDIR:-/tmp}/mouse-rootfs.XXXXXX")
|
||||||
|
image_staging=$(mktemp -d "${TMPDIR:-/tmp}/mouse-btrfs.XXXXXX")
|
||||||
|
|
||||||
|
install -d -m 0755 \
|
||||||
|
"$staging/dev" \
|
||||||
|
"$staging/bin" \
|
||||||
|
"$staging/etc" \
|
||||||
|
"$staging/etc/default" \
|
||||||
|
"$staging/etc/rc.d" \
|
||||||
|
"$staging/etc/skel" \
|
||||||
|
"$staging/home" \
|
||||||
|
"$staging/media" \
|
||||||
|
"$staging/mnt" \
|
||||||
|
"$staging/proc" \
|
||||||
|
"$staging/root" \
|
||||||
|
"$staging/run" \
|
||||||
|
"$staging/sbin" \
|
||||||
|
"$staging/srv" \
|
||||||
|
"$staging/sys" \
|
||||||
|
"$staging/usr/bin" \
|
||||||
|
"$staging/usr/local/etc/rc.d" \
|
||||||
|
"$staging/usr/lib/mouse" \
|
||||||
|
"$staging/usr/sbin" \
|
||||||
|
"$staging/var/cache/pkgsrc/distfiles" \
|
||||||
|
"$staging/var/cache/pkgsrc/packages" \
|
||||||
|
"$staging/var/db" \
|
||||||
|
"$staging/var/lib/dhcpcd" \
|
||||||
|
"$staging/var/empty" \
|
||||||
|
"$staging/var/log" \
|
||||||
|
"$staging/var/mail" \
|
||||||
|
"$staging/var/spool/cron"
|
||||||
|
chmod 0755 "$staging"
|
||||||
|
chmod 0700 "$staging/root"
|
||||||
|
chmod 0755 "$staging/var/empty"
|
||||||
|
chmod 0700 "$staging/var/spool/cron"
|
||||||
|
install -d -m 1777 \
|
||||||
|
"$staging/tmp" \
|
||||||
|
"$staging/var/tmp" \
|
||||||
|
"$staging/var/tmp/vi.recover"
|
||||||
|
|
||||||
|
ln -s /run "$staging/var/run"
|
||||||
|
|
||||||
|
cp -R "$base_root/." "$staging/"
|
||||||
|
cp -R "$pkgsrc_seed/usr/local/." "$staging/usr/local/"
|
||||||
|
cp -R "$pkgsrc_seed/var/db/pkg" "$staging/var/db/"
|
||||||
|
if [ ! -d "$kernel_modules/lib/modules" ]; then
|
||||||
|
printf '%s\n' "missing kernel module tree: $kernel_modules/lib/modules" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp -R "$kernel_modules/lib/." "$staging/lib/"
|
||||||
|
if [ "$test_fixtures" -eq 1 ] && [ -d "$kernel_modules/usr/local" ]; then
|
||||||
|
cp -R "$kernel_modules/usr/local/." "$staging/usr/local/"
|
||||||
|
fi
|
||||||
|
install -m 0755 "$cheesed_binary" "$staging/sbin/cheesed"
|
||||||
|
for command in service shutdown poweroff reboot; do
|
||||||
|
ln -s cheesed "$staging/sbin/$command"
|
||||||
|
done
|
||||||
|
|
||||||
|
cp -R "$repo_dir/base/rootfs/." "$staging/"
|
||||||
|
if [ "$test_fixtures" -eq 1 ]; then
|
||||||
|
cp -R "$repo_dir/tests/fixtures/mouse-echo/rootfs/." "$staging/"
|
||||||
|
install -m 0755 \
|
||||||
|
"${MOUSE_MOUSE_ECHO_BINARY:?missing test mouse-echo binary}" \
|
||||||
|
"$staging/usr/libexec/mouse-echo"
|
||||||
|
printf '%s\n' "enable_mouse_echo=YES" >>"$staging/etc/rc.conf"
|
||||||
|
fi
|
||||||
|
install -d -m 0755 "$staging/usr/share/mouse"
|
||||||
|
if [ "$test_fixtures" -eq 1 ]; then
|
||||||
|
install -d -m 0755 "$staging/usr/share/mouse/pkgsrc-fixture"
|
||||||
|
cp -R "$repo_dir/tests/fixtures/pkgsrc/local" \
|
||||||
|
"$staging/usr/share/mouse/pkgsrc-fixture/"
|
||||||
|
fi
|
||||||
|
printf '%s\n' 2026Q2 >"$staging/usr/share/mouse/pkgsrc.version"
|
||||||
|
rc_subr_source="$staging/usr/local/src/pkgsrc/pkgtools/rc.subr/files/rc.subr"
|
||||||
|
[ -f "$rc_subr_source" ] || {
|
||||||
|
printf '%s\n' "pkgsrc seed lacks rc.subr source" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
sed 's|@SYSCONFBASE@|/usr/local/etc|g' "$rc_subr_source" \
|
||||||
|
>"$staging/etc/rc.subr"
|
||||||
|
patch "$staging/etc/rc.subr" \
|
||||||
|
<"$repo_dir/base/patches/pkgsrc-rc.subr-cgroup.patch"
|
||||||
|
chmod 0444 "$staging/etc/rc.subr"
|
||||||
|
ln -s mouse-release "$staging/etc/os-release"
|
||||||
|
chmod 0755 \
|
||||||
|
"$staging/etc/rc.d/console_login" \
|
||||||
|
"$staging/etc/rc.d/cron" \
|
||||||
|
"$staging/etc/rc.d/daemon" \
|
||||||
|
"$staging/etc/rc.d/login" \
|
||||||
|
"$staging/etc/rc.d/loopback" \
|
||||||
|
"$staging/etc/rc.d/machine_identity" \
|
||||||
|
"$staging/etc/rc.d/mdevd" \
|
||||||
|
"$staging/etc/rc.d/network" \
|
||||||
|
"$staging/etc/rc.d/ntpd" \
|
||||||
|
"$staging/etc/rc.d/servers" \
|
||||||
|
"$staging/etc/rc.d/syslogd" \
|
||||||
|
"$staging/usr/libexec/mouse-dhcpcd-hook"
|
||||||
|
if [ "$test_fixtures" -eq 1 ]; then
|
||||||
|
chmod 0755 "$staging/etc/rc.d/mouse_echo"
|
||||||
|
fi
|
||||||
|
chmod 0600 "$staging/etc/shadow"
|
||||||
|
|
||||||
|
chmod 0444 "$staging/usr/share/mouse/pkgsrc.version"
|
||||||
|
|
||||||
|
"$script_dir/audit-static-base.sh" "$staging"
|
||||||
|
|
||||||
|
mkdir -p "$build_dir"
|
||||||
|
btrfs_image="$build_dir/mouse-rootfs.btrfs"
|
||||||
|
rootfs_size=${MOUSE_ROOTFS_SIZE:-4G}
|
||||||
|
install -d -m 0755 \
|
||||||
|
"$image_staging/ROOT/default" \
|
||||||
|
"$image_staging/ROOT/alternate" \
|
||||||
|
"$image_staging/usr-local" \
|
||||||
|
"$image_staging/var" \
|
||||||
|
"$image_staging/home" \
|
||||||
|
"$image_staging/srv"
|
||||||
|
|
||||||
|
cp -R "$staging/." "$image_staging/ROOT/default/"
|
||||||
|
cp -R "$staging/." "$image_staging/ROOT/alternate/"
|
||||||
|
printf '%s\n' default \
|
||||||
|
>"$image_staging/ROOT/default/etc/mouse-boot-environment"
|
||||||
|
printf '%s\n' alternate \
|
||||||
|
>"$image_staging/ROOT/alternate/etc/mouse-boot-environment"
|
||||||
|
|
||||||
|
for persistent in \
|
||||||
|
usr/local:usr-local \
|
||||||
|
var:var \
|
||||||
|
home:home \
|
||||||
|
srv:srv
|
||||||
|
do
|
||||||
|
root_path=${persistent%%:*}
|
||||||
|
subvolume=${persistent#*:}
|
||||||
|
cp -R "$staging/$root_path/." "$image_staging/$subvolume/"
|
||||||
|
for environment in default alternate; do
|
||||||
|
rm -rf "$image_staging/ROOT/$environment/$root_path"
|
||||||
|
install -d -m 0755 \
|
||||||
|
"$image_staging/ROOT/$environment/$root_path"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f "$btrfs_image"
|
||||||
|
truncate -s "$rootfs_size" "$btrfs_image"
|
||||||
|
"$btrfs_image_tool" \
|
||||||
|
--force \
|
||||||
|
--label MOUSE_ROOT \
|
||||||
|
--uuid 4d4f5553-4500-4000-8000-00000000000b \
|
||||||
|
--rootdir "$image_staging" \
|
||||||
|
--subvol default:ROOT/default \
|
||||||
|
--subvol rw:ROOT/alternate \
|
||||||
|
--subvol rw:usr-local \
|
||||||
|
--subvol rw:var \
|
||||||
|
--subvol rw:home \
|
||||||
|
--subvol rw:srv \
|
||||||
|
"$btrfs_image"
|
||||||
|
|
||||||
|
printf '%s\n' "built $build_dir/vmlinuz-virt"
|
||||||
|
printf '%s\n' "built $btrfs_image"
|
||||||
|
sh "$script_dir/build-boot-disk.sh"
|
||||||
Executable
+191
@@ -0,0 +1,191 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
output_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
test_fixtures=${MOUSE_INCLUDE_TEST_FIXTURES:-0}
|
||||||
|
builder_cpus=${MOUSE_BUILDER_CPUS:-8}
|
||||||
|
builder_memory=${MOUSE_BUILDER_MEMORY:-8G}
|
||||||
|
build_jobs=${MOUSE_BUILD_JOBS:-8}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf '%s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_positive() {
|
||||||
|
case "$1" in
|
||||||
|
''|*[!0-9]*|0) die "$2 must be a positive integer" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
memory_mib() {
|
||||||
|
case "$1" in
|
||||||
|
*[Kk]) value=${1%?}; divisor=1024 ;;
|
||||||
|
*[Mm]) value=${1%?}; divisor=1 ;;
|
||||||
|
*[Gg]) value=${1%?}; divisor=0 ;;
|
||||||
|
*[Tt]) value=${1%?}; divisor=-1 ;;
|
||||||
|
*) value=$1; divisor=1048576 ;;
|
||||||
|
esac
|
||||||
|
require_positive "$value" MOUSE_BUILDER_MEMORY
|
||||||
|
case "$divisor" in
|
||||||
|
0) printf '%s\n' "$((value * 1024))" ;;
|
||||||
|
-1) printf '%s\n' "$((value * 1024 * 1024))" ;;
|
||||||
|
1) printf '%s\n' "$value" ;;
|
||||||
|
*) printf '%s\n' "$(((value + divisor - 1) / divisor))" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$test_fixtures" in
|
||||||
|
0|1) ;;
|
||||||
|
*) die "MOUSE_INCLUDE_TEST_FIXTURES must be 0 or 1" ;;
|
||||||
|
esac
|
||||||
|
case "$test_fixtures" in
|
||||||
|
0) rootfs_stage=rootfs-builder ;;
|
||||||
|
1) rootfs_stage=rootfs-test-builder ;;
|
||||||
|
esac
|
||||||
|
require_positive "$builder_cpus" MOUSE_BUILDER_CPUS
|
||||||
|
require_positive "$build_jobs" MOUSE_BUILD_JOBS
|
||||||
|
[ -n "$builder_memory" ] || die "MOUSE_BUILDER_MEMORY must not be empty"
|
||||||
|
|
||||||
|
if [ -n "${MOUSE_BUILDER_PLATFORM:-}" ]; then
|
||||||
|
builder_platform=$MOUSE_BUILDER_PLATFORM
|
||||||
|
else
|
||||||
|
case "$(uname -m)" in
|
||||||
|
arm64|aarch64) builder_platform=linux/arm64 ;;
|
||||||
|
x86_64|amd64) builder_platform=linux/amd64 ;;
|
||||||
|
*) die "unsupported container host architecture: $(uname -m)" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${CONTAINER_CLI:-}" ]; then
|
||||||
|
container_cli=$CONTAINER_CLI
|
||||||
|
elif command -v container >/dev/null 2>&1; then
|
||||||
|
container_cli=$(command -v container)
|
||||||
|
elif [ -x /usr/local/bin/container ]; then
|
||||||
|
container_cli=/usr/local/bin/container
|
||||||
|
elif command -v docker >/dev/null 2>&1; then
|
||||||
|
container_cli=$(command -v docker)
|
||||||
|
elif command -v podman >/dev/null 2>&1; then
|
||||||
|
container_cli=$(command -v podman)
|
||||||
|
else
|
||||||
|
die "no supported container CLI found (container, docker, or podman)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
container_frontend=$(basename "$container_cli")
|
||||||
|
case "$container_frontend" in
|
||||||
|
container)
|
||||||
|
"$container_cli" system start >/dev/null
|
||||||
|
builder_resources=$("$container_cli" builder status 2>/dev/null |
|
||||||
|
awk 'NR == 2 { print $5, $6 }')
|
||||||
|
if [ -n "$builder_resources" ]; then
|
||||||
|
set -- $builder_resources
|
||||||
|
actual_cpus=$1
|
||||||
|
actual_memory_mib=$2
|
||||||
|
expected_memory_mib=$(memory_mib "$builder_memory")
|
||||||
|
if [ "$actual_cpus" != "$builder_cpus" ] ||
|
||||||
|
[ "$actual_memory_mib" != "$expected_memory_mib" ]; then
|
||||||
|
die "Apple Container builder has $actual_cpus CPUs and ${actual_memory_mib} MB; expected $builder_cpus CPUs and ${expected_memory_mib} MB. Recreate it with: container builder stop; container builder delete; container builder start --cpus $builder_cpus --memory $builder_memory (deleting it discards the build cache)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
docker|podman) ;;
|
||||||
|
*) die "unsupported container CLI: $container_frontend" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
"$repo_dir/scripts/check-source-locks.sh"
|
||||||
|
"$repo_dir/scripts/fetch-sources.sh"
|
||||||
|
|
||||||
|
mkdir -p "$output_dir"
|
||||||
|
build_context=$(mktemp -d "$output_dir/.container-context.XXXXXX")
|
||||||
|
build_context=$(CDPATH= cd -- "$build_context" && pwd)
|
||||||
|
export_dir=$(mktemp -d "$output_dir/.container-output.XXXXXX")
|
||||||
|
export_dir=$(CDPATH= cd -- "$export_dir" && pwd)
|
||||||
|
artifact_temp=
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
[ -z "$artifact_temp" ] || rm -f "$artifact_temp"
|
||||||
|
rm -rf "$build_context" "$export_dir"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$repo_dir/.."
|
||||||
|
tar -cf - \
|
||||||
|
--exclude='mouse-src/.git/*' \
|
||||||
|
--exclude='mouse-src/build/*' \
|
||||||
|
--exclude='mouse-src/Containerfile' \
|
||||||
|
--exclude='mouse-src/.containerignore' \
|
||||||
|
--exclude='mouse-src/scripts/build-in-container.sh' \
|
||||||
|
--exclude='mouse-src/base/cheesed/target/*' \
|
||||||
|
mouse-src
|
||||||
|
) | tar -xf - -C "$build_context"
|
||||||
|
|
||||||
|
cp "$repo_dir/Containerfile" "$build_context/Dockerfile"
|
||||||
|
cp "$repo_dir/.containerignore" "$build_context/.dockerignore"
|
||||||
|
|
||||||
|
source_dir=${MOUSE_SOURCE_DIR:-"$output_dir/sources"}
|
||||||
|
for domain in toolchain kernel firmware facilities storage pkgsrc software; do
|
||||||
|
domain_dir="$build_context/mouse-sources/$domain"
|
||||||
|
mkdir -p "$domain_dir"
|
||||||
|
while read -r checksum url filename extra; do
|
||||||
|
case "$checksum" in
|
||||||
|
''|\#*) continue ;;
|
||||||
|
esac
|
||||||
|
install -m 0444 "$source_dir/$filename" "$domain_dir/$filename"
|
||||||
|
done <"$repo_dir/release/$domain.lock"
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$build_context"
|
||||||
|
set -- build
|
||||||
|
case "$container_frontend" in
|
||||||
|
container)
|
||||||
|
set -- "$@" --cpus "$builder_cpus" --memory "$builder_memory"
|
||||||
|
;;
|
||||||
|
docker|podman)
|
||||||
|
set -- "$@" \
|
||||||
|
--cpu-period 100000 \
|
||||||
|
--cpu-quota "$((builder_cpus * 100000))" \
|
||||||
|
--memory "$builder_memory"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
set -- "$@" \
|
||||||
|
--platform "$builder_platform" \
|
||||||
|
--target artifacts \
|
||||||
|
--build-arg "MOUSE_BUILD_JOBS=$build_jobs" \
|
||||||
|
--build-arg "MOUSE_ROOTFS_STAGE=$rootfs_stage" \
|
||||||
|
--output "type=local,dest=$export_dir" \
|
||||||
|
.
|
||||||
|
"$container_cli" "$@"
|
||||||
|
)
|
||||||
|
|
||||||
|
artifacts_dir=$export_dir
|
||||||
|
if [ ! -f "$artifacts_dir/vmlinuz-virt" ]; then
|
||||||
|
platform_dir=$(printf '%s\n' "$builder_platform" | tr / _)
|
||||||
|
artifacts_dir="$export_dir/out.tar/$platform_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for artifact in vmlinuz-virt ovmf-code.fd; do
|
||||||
|
[ -f "$artifacts_dir/$artifact" ] || \
|
||||||
|
die "container build did not export $artifact"
|
||||||
|
install -m 0644 "$artifacts_dir/$artifact" "$output_dir/$artifact"
|
||||||
|
done
|
||||||
|
|
||||||
|
artifact=mouse-disk.img
|
||||||
|
[ -f "$artifacts_dir/$artifact.gz" ] || \
|
||||||
|
die "container build did not export $artifact.gz"
|
||||||
|
artifact_temp="$output_dir/.$artifact.tmp"
|
||||||
|
gzip -dc "$artifacts_dir/$artifact.gz" >"$artifact_temp"
|
||||||
|
chmod 0644 "$artifact_temp"
|
||||||
|
mv -f "$artifact_temp" "$output_dir/$artifact"
|
||||||
|
artifact_temp=
|
||||||
|
|
||||||
|
for artifact in \
|
||||||
|
vmlinuz-virt \
|
||||||
|
ovmf-code.fd \
|
||||||
|
mouse-disk.img
|
||||||
|
do
|
||||||
|
printf '%s\n' "exported $output_dir/$artifact"
|
||||||
|
done
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
kernel_version=$(cat "$build_dir/kernel/release")
|
||||||
|
kernel_source="$build_dir/kernel/linux-6.18.35"
|
||||||
|
kernel_output="$build_dir/kernel/output"
|
||||||
|
module_output="$build_dir/kernel/module-test"
|
||||||
|
module_root="$build_dir/kernel/test-modules"
|
||||||
|
|
||||||
|
rm -rf "$module_output" "$module_root"
|
||||||
|
mkdir -p "$module_output"
|
||||||
|
cp "$repo_dir/tests/fixtures/kernel-module/mouse_test.c" \
|
||||||
|
"$repo_dir/tests/fixtures/kernel-module/Makefile" \
|
||||||
|
"$module_output/"
|
||||||
|
make -s -C "$kernel_source" \
|
||||||
|
O="$kernel_output" \
|
||||||
|
ARCH=x86_64 \
|
||||||
|
LLVM=1 \
|
||||||
|
M="$module_output" \
|
||||||
|
modules
|
||||||
|
|
||||||
|
cp -R "$build_dir/kernel/modules" "$module_root"
|
||||||
|
install -d -m 0755 \
|
||||||
|
"$module_root/usr/local/lib/modules/$kernel_version/extra"
|
||||||
|
install -m 0644 "$module_output/mouse_test.ko" \
|
||||||
|
"$module_root/usr/local/lib/modules/$kernel_version/extra/mouse_test.ko"
|
||||||
Executable
+80
@@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
source_dir=${MOUSE_SOURCE_DIR:-"$build_dir/sources"}
|
||||||
|
kernel_version=6.18.35
|
||||||
|
kernel_archive="$source_dir/linux-$kernel_version.tar.xz"
|
||||||
|
kernel_source="$build_dir/kernel/linux-$kernel_version"
|
||||||
|
kernel_output="$build_dir/kernel/output"
|
||||||
|
kernel_config="$repo_dir/base/kernel/x86_64.config"
|
||||||
|
jobs=${MOUSE_BUILD_JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1\n')}
|
||||||
|
|
||||||
|
if [ ! -f "$kernel_archive" ]; then
|
||||||
|
printf '%s\n' "missing $kernel_archive; run scripts/fetch-sources.sh first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$kernel_config" ]; then
|
||||||
|
printf '%s\n' "missing MOUSE kernel config: $kernel_config" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$build_dir/kernel"
|
||||||
|
rm -rf "$kernel_source" "$kernel_output"
|
||||||
|
tar -C "$build_dir/kernel" -xf "$kernel_archive"
|
||||||
|
mkdir -p "$kernel_output"
|
||||||
|
|
||||||
|
make -C "$kernel_source" \
|
||||||
|
O="$kernel_output" \
|
||||||
|
ARCH=x86_64 \
|
||||||
|
LLVM=1 \
|
||||||
|
KCONFIG_ALLCONFIG="$kernel_config" \
|
||||||
|
allnoconfig
|
||||||
|
|
||||||
|
while IFS= read -r requested || [ -n "$requested" ]; do
|
||||||
|
case "$requested" in
|
||||||
|
CONFIG_*=*) ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
if ! grep -Fqx "$requested" "$kernel_output/.config"; then
|
||||||
|
printf '%s\n' \
|
||||||
|
"kernel did not enable requested setting: $requested" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done <"$kernel_config"
|
||||||
|
|
||||||
|
make -C "$kernel_source" \
|
||||||
|
O="$kernel_output" \
|
||||||
|
ARCH=x86_64 \
|
||||||
|
LLVM=1 \
|
||||||
|
KBUILD_BUILD_USER=mouse \
|
||||||
|
KBUILD_BUILD_HOST=mouse \
|
||||||
|
-j"$jobs" \
|
||||||
|
bzImage modules
|
||||||
|
|
||||||
|
kernel_release=$(make -s -C "$kernel_source" \
|
||||||
|
O="$kernel_output" \
|
||||||
|
ARCH=x86_64 \
|
||||||
|
LLVM=1 \
|
||||||
|
kernelrelease)
|
||||||
|
module_root="$build_dir/kernel/modules"
|
||||||
|
rm -rf "$module_root"
|
||||||
|
make -s -C "$kernel_source" \
|
||||||
|
O="$kernel_output" \
|
||||||
|
ARCH=x86_64 \
|
||||||
|
LLVM=1 \
|
||||||
|
INSTALL_MOD_PATH="$module_root" \
|
||||||
|
modules_install
|
||||||
|
|
||||||
|
printf '%s\n' "$kernel_release" >"$build_dir/kernel/release"
|
||||||
|
install -m 0644 "$kernel_output/.config" \
|
||||||
|
"$module_root/lib/modules/$kernel_release/config"
|
||||||
|
install -m 0644 "$kernel_output/Module.symvers" \
|
||||||
|
"$module_root/lib/modules/$kernel_release/Module.symvers"
|
||||||
|
install -m 0644 \
|
||||||
|
"$kernel_output/arch/x86/boot/bzImage" \
|
||||||
|
"$build_dir/vmlinuz-virt"
|
||||||
|
printf '%s\n' \
|
||||||
|
"built Linux $kernel_release with built-in btrfs and loadable-module support"
|
||||||
Executable
+89
@@ -0,0 +1,89 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
archive=${MOUSE_PKGSRC_ARCHIVE:-/usr/share/mouse/pkgsrc-2026Q2.tar.gz}
|
||||||
|
patch_file=${MOUSE_PKGSRC_PATCH:-/usr/share/mouse/pkgsrc-bootstrap-mouse.patch}
|
||||||
|
source_parent=/usr/local/src
|
||||||
|
pkgsrc=$source_parent/pkgsrc
|
||||||
|
mk_conf=/usr/local/etc/mk.conf
|
||||||
|
build_busybox=/usr/libexec/mouse-build-busybox
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf '%s\n' "build-pkgsrc-seed: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$(id -u)" -eq 0 ] || fail "must run as root"
|
||||||
|
[ -f "$archive" ] || fail "missing pinned pkgsrc archive: $archive"
|
||||||
|
[ -f "$patch_file" ] || fail "missing MOUSE pkgsrc bootstrap patch: $patch_file"
|
||||||
|
[ -x "$build_busybox" ] || fail "missing build-only copy helper"
|
||||||
|
[ ! -e /usr/local/bin/bmake ] || fail "pkgsrc seed root is not empty"
|
||||||
|
[ ! -e /var/db/pkg ] || fail "pkgsrc package database already exists"
|
||||||
|
|
||||||
|
mkdir -p \
|
||||||
|
"$source_parent" \
|
||||||
|
/usr/local/etc/rc.d \
|
||||||
|
/tmp \
|
||||||
|
/var/cache/pkgsrc/distfiles \
|
||||||
|
/var/cache/pkgsrc/packages
|
||||||
|
chmod 1777 /tmp
|
||||||
|
tar -xzf "$archive" -C "$source_parent"
|
||||||
|
[ -x "$pkgsrc/bootstrap/bootstrap" ] || fail "archive did not produce a pkgsrc tree"
|
||||||
|
[ -f "$pkgsrc/mk/init/bsd.init.mk" ] || fail "pkgsrc tree lacks init-system support"
|
||||||
|
patch -d "$pkgsrc" -p1 <"$patch_file"
|
||||||
|
grep -Fq 'bootstrap_awk=/usr/bin/awk' "$pkgsrc/bootstrap/bootstrap" ||
|
||||||
|
fail "pkgsrc tree lacks the MOUSE World awk policy"
|
||||||
|
|
||||||
|
# Apple Rosetta reports ENOSYS for the copy operation used by MOUSE's static
|
||||||
|
# cp. Use an amd64 build-only applet while pkgsrc populates its work directory;
|
||||||
|
# only /usr/local and /var/db/pkg leave this stage.
|
||||||
|
mv /bin/cp /bin/cp.mouse
|
||||||
|
ln -s "$build_busybox" /bin/cp
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$pkgsrc/bootstrap"
|
||||||
|
env \
|
||||||
|
AWK=/usr/bin/awk \
|
||||||
|
CC=/usr/bin/cc \
|
||||||
|
CXX=/usr/bin/c++ \
|
||||||
|
SH=/bin/sh \
|
||||||
|
./bootstrap \
|
||||||
|
--compiler clang \
|
||||||
|
--make-jobs "${MOUSE_BUILD_JOBS:-1}" \
|
||||||
|
--pkgdbdir /var/db/pkg \
|
||||||
|
--prefer-pkgsrc yes \
|
||||||
|
--prefix /usr/local \
|
||||||
|
--sysconfbase /usr/local/etc \
|
||||||
|
--sysconfdir /usr/local/etc \
|
||||||
|
--varbase /var
|
||||||
|
)
|
||||||
|
|
||||||
|
rm /bin/cp
|
||||||
|
mv /bin/cp.mouse /bin/cp
|
||||||
|
rm "$build_busybox"
|
||||||
|
|
||||||
|
cat >>"$mk_conf" <<'EOF'
|
||||||
|
|
||||||
|
# MOUSE pkgsrc policy
|
||||||
|
PKGSRC_COMPILER= clang
|
||||||
|
LOCALBASE= /usr/local
|
||||||
|
PKG_SYSCONFBASE= /usr/local/etc
|
||||||
|
PKG_DBDIR= /var/db/pkg
|
||||||
|
VARBASE= /var
|
||||||
|
DISTDIR= /var/cache/pkgsrc/distfiles
|
||||||
|
PACKAGES= /var/cache/pkgsrc/packages
|
||||||
|
INIT_SYSTEM= rc.d
|
||||||
|
RCD_SCRIPTS_DIR= /usr/local/etc/rc.d
|
||||||
|
PKG_RCD_SCRIPTS= YES
|
||||||
|
PREFER_PKGSRC= yes
|
||||||
|
EOF
|
||||||
|
|
||||||
|
ln -s /etc/rc.subr /usr/local/etc/rc.subr
|
||||||
|
rm -rf "$pkgsrc/bootstrap/work"
|
||||||
|
|
||||||
|
[ -x /usr/local/bin/bmake ] || fail "bootstrap did not install bmake"
|
||||||
|
[ -x /usr/local/sbin/pkg_info ] || fail "bootstrap did not install pkg_info"
|
||||||
|
[ -d /var/db/pkg ] || fail "bootstrap did not create the package database"
|
||||||
|
grep -Eq '^TOOLS_PLATFORM\.awk\?=[[:space:]]*/usr/bin/awk$' "$mk_conf" ||
|
||||||
|
fail "bootstrap did not preserve the World awk policy"
|
||||||
|
printf '%s\n' "built pkgsrc 2026Q2 seed"
|
||||||
Executable
+197
@@ -0,0 +1,197 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
lock_dir="$repo_dir/release"
|
||||||
|
temporary=$(mktemp -d "${TMPDIR:-/tmp}/mouse-source-lock-check.XXXXXX")
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$temporary"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
MOUSE_VALIDATE_ONLY=1 "$script_dir/fetch-sources.sh"
|
||||||
|
|
||||||
|
awk 'NF && $1 !~ /^#/ { print $3 }' \
|
||||||
|
"$lock_dir/toolchain.lock" \
|
||||||
|
"$lock_dir/kernel.lock" \
|
||||||
|
"$lock_dir/facilities.lock" \
|
||||||
|
"$lock_dir/storage.lock" \
|
||||||
|
"$lock_dir/software.lock" |
|
||||||
|
LC_ALL=C sort -u >"$temporary/locked"
|
||||||
|
|
||||||
|
awk '$1 == "extract" { print $2 }' "$script_dir/static-base"/*.sh |
|
||||||
|
LC_ALL=C sort -u >"$temporary/referenced-archives"
|
||||||
|
sed -n 's/.*"$source_dir\/\([^"$]*\)".*/\1/p' \
|
||||||
|
"$script_dir/static-base"/*.sh \
|
||||||
|
"$script_dir/build-boot-disk.sh" |
|
||||||
|
LC_ALL=C sort -u >"$temporary/referenced-files"
|
||||||
|
cat "$temporary/referenced-archives" "$temporary/referenced-files" |
|
||||||
|
LC_ALL=C sort -u >"$temporary/referenced"
|
||||||
|
|
||||||
|
if ! diff -u "$temporary/locked" "$temporary/referenced"; then
|
||||||
|
printf '%s\n' "source locks do not match static-base component inputs" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
toolchain_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
|
||||||
|
"$lock_dir/toolchain.lock")
|
||||||
|
kernel_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
|
||||||
|
"$lock_dir/kernel.lock")
|
||||||
|
if [ "$toolchain_count" -ne 1 ] || \
|
||||||
|
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/toolchain.lock" |
|
||||||
|
grep -Eq '^llvm-project-[^/]+$'
|
||||||
|
then
|
||||||
|
printf '%s\n' "toolchain.lock must contain exactly the LLVM source" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$kernel_count" -ne 1 ] || \
|
||||||
|
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/kernel.lock" |
|
||||||
|
grep -Eq '^linux-[^/]+$'
|
||||||
|
then
|
||||||
|
printf '%s\n' "kernel.lock must contain exactly the Linux source" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
pkgsrc_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
|
||||||
|
"$lock_dir/pkgsrc.lock")
|
||||||
|
if [ "$pkgsrc_count" -ne 1 ] || \
|
||||||
|
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/pkgsrc.lock" |
|
||||||
|
grep -Eq '^pkgsrc-[0-9]{4}Q[1-4]\.tar\.gz$'
|
||||||
|
then
|
||||||
|
printf '%s\n' "pkgsrc.lock must contain exactly one stable pkgsrc archive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
firmware_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
|
||||||
|
"$lock_dir/firmware.lock")
|
||||||
|
if [ "$firmware_count" -ne 1 ] || \
|
||||||
|
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/firmware.lock" |
|
||||||
|
grep -Eq '^ovmf-[^/]+\.apk$'
|
||||||
|
then
|
||||||
|
printf '%s\n' "firmware.lock must contain exactly one OVMF package" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
storage_count=$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' \
|
||||||
|
"$lock_dir/storage.lock")
|
||||||
|
if [ "$storage_count" -lt 1 ] || \
|
||||||
|
! awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/storage.lock" |
|
||||||
|
grep -Eq '^btrfs-progs-[^/]+$'
|
||||||
|
then
|
||||||
|
printf '%s\n' "storage.lock must contain the btrfs-progs source" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/software.lock" |
|
||||||
|
grep -Eq '^(llvm-project|linux|btrfs-progs|less|mandoc|iproute2|nftables)-'
|
||||||
|
then
|
||||||
|
printf '%s\n' "software.lock contains a source owned by another cache domain" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if awk 'NF && $1 !~ /^#/ { print $3 }' "$lock_dir/facilities.lock" |
|
||||||
|
grep -Eq '^(llvm-project|linux|btrfs-progs)-'
|
||||||
|
then
|
||||||
|
printf '%s\n' "facilities.lock contains a kernel, toolchain, or storage source" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk '
|
||||||
|
/^FROM toolchain AS kernel-builder$/ { in_kernel = 1; next }
|
||||||
|
/^FROM / && in_kernel { exit }
|
||||||
|
in_kernel { print }
|
||||||
|
' "$repo_dir/Containerfile" >"$temporary/kernel-stage"
|
||||||
|
|
||||||
|
if ! grep -Fq 'COPY --from=kernel-sources ' "$temporary/kernel-stage"; then
|
||||||
|
printf '%s\n' "kernel-builder does not import kernel-sources" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if grep -Eq 'COPY --from=(toolchain|storage|software)-sources ' \
|
||||||
|
"$temporary/kernel-stage"
|
||||||
|
then
|
||||||
|
printf '%s\n' "kernel-builder imports an unrelated source domain" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if grep -Fq 'tests/fixtures' "$temporary/kernel-stage"; then
|
||||||
|
printf '%s\n' "production kernel stage imports test fixtures" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk '
|
||||||
|
/^FROM kernel-builder AS kernel-ready$/ { in_marker = 1; next }
|
||||||
|
/^FROM / && in_marker { exit }
|
||||||
|
in_marker { print }
|
||||||
|
' "$repo_dir/Containerfile" >"$temporary/kernel-ready-stage"
|
||||||
|
|
||||||
|
if ! grep -Fq 'RUN install -m 0444 /dev/null /kernel-ready' \
|
||||||
|
"$temporary/kernel-ready-stage"
|
||||||
|
then
|
||||||
|
printf '%s\n' "kernel-ready does not create its completion marker" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for stage_script in \
|
||||||
|
static-base-bootstrap:bootstrap.sh \
|
||||||
|
static-base-libraries:libraries-and-accounts.sh \
|
||||||
|
static-base-system:system-services.sh \
|
||||||
|
static-base-facilities:facilities.sh \
|
||||||
|
static-base-toolchain:native-toolchain.sh \
|
||||||
|
static-base-storage:storage.sh \
|
||||||
|
static-base-builder:finish.sh
|
||||||
|
do
|
||||||
|
stage=${stage_script%%:*}
|
||||||
|
component=${stage_script#*:}
|
||||||
|
awk -v heading="AS $stage" '
|
||||||
|
/^FROM / {
|
||||||
|
if (active) exit
|
||||||
|
active = index($0, heading) != 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
active { print }
|
||||||
|
' "$repo_dir/Containerfile" >"$temporary/$stage"
|
||||||
|
if ! grep -Fq \
|
||||||
|
"COPY mouse-src/scripts/static-base/$component " \
|
||||||
|
"$temporary/$stage"
|
||||||
|
then
|
||||||
|
printf '%s\n' \
|
||||||
|
"$stage does not own static-base component $component" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$(grep -Fc \
|
||||||
|
"COPY mouse-src/scripts/static-base/$component " \
|
||||||
|
"$repo_dir/Containerfile")" -ne 1 ]
|
||||||
|
then
|
||||||
|
printf '%s\n' \
|
||||||
|
"$component must enter exactly one container cache stage" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! grep -Fq \
|
||||||
|
'COPY --from=kernel-ready /kernel-ready /tmp/kernel-ready' \
|
||||||
|
"$temporary/static-base-bootstrap"
|
||||||
|
then
|
||||||
|
printf '%s\n' "static-base build does not wait for kernel completion" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for production_stage in image-commands-builder rootfs-builder; do
|
||||||
|
awk -v heading="AS $production_stage" '
|
||||||
|
/^FROM / {
|
||||||
|
if (active) exit
|
||||||
|
active = index($0, heading) != 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
active { print }
|
||||||
|
' "$repo_dir/Containerfile" >"$temporary/$production_stage"
|
||||||
|
if grep -Fq 'tests/fixtures' "$temporary/$production_stage"; then
|
||||||
|
printf '%s\n' "$production_stage imports test fixtures" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for test_stage in kernel-test-builder test-fixtures-builder rootfs-test-builder; do
|
||||||
|
if ! grep -Fq "AS $test_stage" "$repo_dir/Containerfile"; then
|
||||||
|
printf '%s\n' "missing isolated test stage: $test_stage" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "source lock and cache-boundary checks passed"
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "$#" -ne 9 ]; then
|
||||||
|
printf '%s\n' \
|
||||||
|
"usage: configure-cross-toolchain.sh DEST SYSROOT CLANG CLANGXX LLD BUILTINS RUST_EH_PERSONALITY LLVM_AR LLVM_STRIP" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
destination=$1
|
||||||
|
sysroot=$2
|
||||||
|
clang=$3
|
||||||
|
clangxx=$4
|
||||||
|
lld=$5
|
||||||
|
builtins=$6
|
||||||
|
rust_eh_personality=$7
|
||||||
|
llvm_ar=$8
|
||||||
|
llvm_strip=$9
|
||||||
|
target=x86_64-mouse-linux-musl
|
||||||
|
|
||||||
|
for requirement in \
|
||||||
|
"$clang" "$clangxx" "$lld" "$builtins" "$rust_eh_personality" "$llvm_ar" "$llvm_strip"
|
||||||
|
do
|
||||||
|
if [ ! -f "$requirement" ]; then
|
||||||
|
printf '%s\n' "missing cross-toolchain input: $requirement" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
install -d -m 0755 "$destination/bin"
|
||||||
|
install -m 0755 \
|
||||||
|
"$repo_dir/base/support/cross-compiler-driver" \
|
||||||
|
"$destination/bin/compiler-driver"
|
||||||
|
|
||||||
|
printf '%s\n' \
|
||||||
|
"$clang" \
|
||||||
|
"$clangxx" \
|
||||||
|
"$sysroot" \
|
||||||
|
"$lld" \
|
||||||
|
"$builtins" \
|
||||||
|
"$rust_eh_personality" \
|
||||||
|
>"$destination/compiler.conf"
|
||||||
|
|
||||||
|
ln -sf compiler-driver "$destination/bin/$target-cc"
|
||||||
|
ln -sf compiler-driver "$destination/bin/$target-c++"
|
||||||
|
ln -sf compiler-driver "$destination/bin/$target-bootstrap-c++"
|
||||||
|
ln -sf "$llvm_ar" "$destination/bin/$target-ar"
|
||||||
|
ln -sf "$llvm_ar" "$destination/bin/$target-ranlib"
|
||||||
|
ln -sf "$llvm_strip" "$destination/bin/$target-strip"
|
||||||
Executable
+114
@@ -0,0 +1,114 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
source_dir=${MOUSE_SOURCE_DIR:-"$build_dir/sources"}
|
||||||
|
lock_dir="$repo_dir/release"
|
||||||
|
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
|
set -- \
|
||||||
|
"$lock_dir/toolchain.lock" \
|
||||||
|
"$lock_dir/kernel.lock" \
|
||||||
|
"$lock_dir/firmware.lock" \
|
||||||
|
"$lock_dir/facilities.lock" \
|
||||||
|
"$lock_dir/pkgsrc.lock" \
|
||||||
|
"$lock_dir/storage.lock" \
|
||||||
|
"$lock_dir/software.lock"
|
||||||
|
fi
|
||||||
|
|
||||||
|
seen=$(mktemp "${TMPDIR:-/tmp}/mouse-source-locks.XXXXXX")
|
||||||
|
cleanup() {
|
||||||
|
rm -f "$seen"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
for lock_file in "$@"; do
|
||||||
|
if [ ! -f "$lock_file" ]; then
|
||||||
|
printf '%s\n' "missing source lock: $lock_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
line_number=0
|
||||||
|
while read -r expected url filename extra; do
|
||||||
|
line_number=$((line_number + 1))
|
||||||
|
case "$expected" in
|
||||||
|
""|\#*) continue ;;
|
||||||
|
esac
|
||||||
|
if [ -n "${extra:-}" ]; then
|
||||||
|
printf '%s\n' "$lock_file:$line_number: expected three fields" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "${#expected}" -ne 64 ]; then
|
||||||
|
printf '%s\n' "$lock_file:$line_number: SHA-256 must have 64 characters" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
case "$expected" in
|
||||||
|
*[!0-9a-f]*)
|
||||||
|
printf '%s\n' "$lock_file:$line_number: SHA-256 must be lowercase hexadecimal" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$url" in
|
||||||
|
https://*) ;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "$lock_file:$line_number: source URL must use HTTPS" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$filename" in
|
||||||
|
""|.|..|*/*)
|
||||||
|
printf '%s\n' "$lock_file:$line_number: invalid source filename: $filename" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if grep -Fqx "$filename" "$seen"; then
|
||||||
|
printf '%s\n' "$lock_file:$line_number: duplicate source filename: $filename" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$filename" >>"$seen"
|
||||||
|
done <"$lock_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${MOUSE_VALIDATE_ONLY:-0}" = 1 ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$source_dir"
|
||||||
|
|
||||||
|
for lock_file in "$@"; do
|
||||||
|
while read -r expected url filename extra; do
|
||||||
|
case "$expected" in
|
||||||
|
""|\#*) continue ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
destination="$source_dir/$filename"
|
||||||
|
if [ ! -f "$destination" ]; then
|
||||||
|
temporary="$destination.part"
|
||||||
|
printf '%s\n' "fetching $filename"
|
||||||
|
curl \
|
||||||
|
--fail \
|
||||||
|
--location \
|
||||||
|
--proto '=https' \
|
||||||
|
--tlsv1.2 \
|
||||||
|
--connect-timeout 20 \
|
||||||
|
--retry 4 \
|
||||||
|
--retry-all-errors \
|
||||||
|
"$url" \
|
||||||
|
-o "$temporary"
|
||||||
|
mv "$temporary" "$destination"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
actual=$(sha256sum "$destination" | awk '{print $1}')
|
||||||
|
else
|
||||||
|
actual=$(shasum -a 256 "$destination" | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
if [ "$actual" != "$expected" ]; then
|
||||||
|
printf '%s\n' "checksum mismatch: $filename" >&2
|
||||||
|
printf '%s\n' "expected: $expected" >&2
|
||||||
|
printf '%s\n' "actual: $actual" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done <"$lock_file"
|
||||||
|
done
|
||||||
Executable
+152
@@ -0,0 +1,152 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
boot_mode=${MOUSE_BOOT_MODE:-firmware}
|
||||||
|
kernel_args=${MOUSE_KERNEL_ARGS:-}
|
||||||
|
disk_image=${MOUSE_DISK_IMAGE:-"$build_dir/mouse-disk.img"}
|
||||||
|
disk_bus=${MOUSE_DISK_BUS:-virtio}
|
||||||
|
firmware=${MOUSE_FIRMWARE:-bios}
|
||||||
|
uefi_code=${MOUSE_UEFI_CODE:-"$build_dir/ovmf-code.fd"}
|
||||||
|
network=${MOUSE_NETWORK:-0}
|
||||||
|
network_backend=${MOUSE_NETWORK_BACKEND:-user}
|
||||||
|
network_device=${MOUSE_NETWORK_DEVICE:-virtio}
|
||||||
|
memory=${MOUSE_MEMORY:-512M}
|
||||||
|
|
||||||
|
if [ -n "${QEMU_SYSTEM_X86_64:-}" ]; then
|
||||||
|
qemu=$QEMU_SYSTEM_X86_64
|
||||||
|
else
|
||||||
|
qemu=qemu-system-x86_64
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$qemu" in
|
||||||
|
*/*)
|
||||||
|
if [ ! -x "$qemu" ]; then
|
||||||
|
printf '%s\n' "$qemu is not executable" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if ! command -v "$qemu" >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "qemu-system-x86_64 is required to boot the image" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ ! -f "$build_dir/vmlinuz-virt" ]; then
|
||||||
|
printf '%s\n' "run 'make image' before booting MOUSE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
disk_drive="file=$disk_image,format=raw,if=none,id=mouse-root"
|
||||||
|
disk_controller=
|
||||||
|
case "$disk_bus" in
|
||||||
|
virtio)
|
||||||
|
disk_device="virtio-blk-pci,drive=mouse-root,disable-legacy=on"
|
||||||
|
;;
|
||||||
|
sata)
|
||||||
|
disk_controller="ich9-ahci,id=mouse-ahci"
|
||||||
|
disk_device="ide-hd,drive=mouse-root,bus=mouse-ahci.0"
|
||||||
|
;;
|
||||||
|
nvme)
|
||||||
|
disk_device="nvme,drive=mouse-root,serial=MOUSE0001"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "invalid MOUSE_DISK_BUS: $disk_bus" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
set -- "$qemu" \
|
||||||
|
-machine q35,accel=tcg \
|
||||||
|
-cpu max \
|
||||||
|
-m "$memory" \
|
||||||
|
-no-reboot \
|
||||||
|
-display none \
|
||||||
|
-nographic \
|
||||||
|
-nic none
|
||||||
|
|
||||||
|
case "$firmware" in
|
||||||
|
bios) ;;
|
||||||
|
uefi)
|
||||||
|
if [ ! -f "$uefi_code" ]; then
|
||||||
|
printf '%s\n' "missing pinned UEFI firmware: $uefi_code" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
set -- "$@" \
|
||||||
|
-drive "if=pflash,format=raw,readonly=on,file=$uefi_code"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "invalid MOUSE_FIRMWARE: $firmware" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$boot_mode" in
|
||||||
|
firmware)
|
||||||
|
if [ ! -f "$disk_image" ]; then
|
||||||
|
printf '%s\n' "missing boot disk: $disk_image" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
set -- "$@" -drive "$disk_drive"
|
||||||
|
if [ -n "$disk_controller" ]; then
|
||||||
|
set -- "$@" -device "$disk_controller"
|
||||||
|
fi
|
||||||
|
set -- "$@" -device "$disk_device"
|
||||||
|
;;
|
||||||
|
disk)
|
||||||
|
if [ ! -f "$disk_image" ]; then
|
||||||
|
printf '%s\n' "missing root disk: $disk_image" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
kernel_args=${kernel_args:-"console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed panic=-1"}
|
||||||
|
set -- "$@" \
|
||||||
|
-kernel "$build_dir/vmlinuz-virt" \
|
||||||
|
-drive "$disk_drive"
|
||||||
|
if [ -n "$disk_controller" ]; then
|
||||||
|
set -- "$@" -device "$disk_controller"
|
||||||
|
fi
|
||||||
|
set -- "$@" -device "$disk_device"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "invalid MOUSE_BOOT_MODE: $boot_mode" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$network" in
|
||||||
|
0) ;;
|
||||||
|
1)
|
||||||
|
if ! "$qemu" -netdev help 2>&1 |
|
||||||
|
grep -Fxq "$network_backend"; then
|
||||||
|
printf '%s\n' \
|
||||||
|
"QEMU does not provide the requested network backend: $network_backend" \
|
||||||
|
"install a QEMU build with that backend or set MOUSE_NETWORK_BACKEND" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
case "$network_device" in
|
||||||
|
virtio) nic_device="virtio-net-pci,netdev=mouse-net,disable-legacy=on" ;;
|
||||||
|
e1000) nic_device="e1000,netdev=mouse-net" ;;
|
||||||
|
e1000e) nic_device="e1000e,netdev=mouse-net" ;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "invalid MOUSE_NETWORK_DEVICE: $network_device" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
set -- "$@" \
|
||||||
|
-netdev "$network_backend,id=mouse-net" \
|
||||||
|
-device "$nic_device"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "invalid MOUSE_NETWORK: $network" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$boot_mode" = disk ]; then
|
||||||
|
set -- "$@" -append "$kernel_args"
|
||||||
|
fi
|
||||||
|
exec "$@"
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$component_dir/common.sh"
|
||||||
|
|
||||||
|
rm -rf "$static_dir"
|
||||||
|
install -d -m 0755 \
|
||||||
|
"$source_build_dir" \
|
||||||
|
"$sysroot" \
|
||||||
|
"$base_root/bin" \
|
||||||
|
"$base_root/sbin" \
|
||||||
|
"$base_root/usr/bin" \
|
||||||
|
"$base_root/usr/sbin" \
|
||||||
|
"$tools_dir/bin"
|
||||||
|
|
||||||
|
# llvm-ar selects ranlib mode from argv[0]. This gives build systems a real
|
||||||
|
# ranlib frontend even on the Rust toolchain for macOS, which ships only the
|
||||||
|
# underlying multi-call binary.
|
||||||
|
ln -s "$llvm_ar" "$tools_dir/bin/llvm-ranlib"
|
||||||
|
llvm_ranlib="$tools_dir/bin/llvm-ranlib"
|
||||||
|
|
||||||
|
extract() {
|
||||||
|
tar -xf "$source_dir/$1" -C "$source_build_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 zlib-1.3.2.tar.gz
|
||||||
|
extract libarchive-3.8.7.tar.xz
|
||||||
|
extract llvm-project-22.1.3.src.tar.xz
|
||||||
|
extract netbsd-curses-0.3.2.tar.gz
|
||||||
|
extract attr-2.5.2.tar.gz
|
||||||
|
extract acl-2.3.2.tar.gz
|
||||||
|
extract libxo-1.7.5.tar.gz
|
||||||
|
extract tcsh-6.24.16.tar.gz
|
||||||
|
extract chimerautils-15.0.3.tar.gz
|
||||||
|
extract openssl-3.5.7.tar.gz
|
||||||
|
extract util-linux-2.42.2.tar.xz
|
||||||
|
extract libxcrypt-4.5.2.tar.xz
|
||||||
|
extract shadow-4.19.4.tar.xz
|
||||||
|
extract dhcpcd-8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4.tar.gz
|
||||||
|
extract make-4.4.1.tar.gz
|
||||||
|
extract cronie-1.7.2.tar.gz
|
||||||
|
extract openntpd-7.9p1.tar.gz
|
||||||
|
extract procps-ng-4.0.5.tar.xz
|
||||||
|
extract iputils-20250605.tar.gz
|
||||||
|
extract kmod-34.2.tar.xz
|
||||||
|
extract skalibs-2.15.1.0.tar.gz
|
||||||
|
extract mdevd-0.1.8.2.tar.gz
|
||||||
|
extract sysklogd-2.7.2.tar.gz
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/ninja-1.13.1"
|
||||||
|
python3 configure.py --bootstrap
|
||||||
|
install -m 0755 ninja "$tools_dir/bin/ninja"
|
||||||
|
)
|
||||||
|
|
||||||
|
meson_source="$source_build_dir/meson-1.9.1/meson.py"
|
||||||
|
PATH="$tools_dir/bin:$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
python3 "$meson_source" setup \
|
||||||
|
"$source_build_dir/pkgconf-pkgconf-2.5.1/build" \
|
||||||
|
"$source_build_dir/pkgconf-pkgconf-2.5.1" \
|
||||||
|
--prefix="$tools_dir" \
|
||||||
|
--buildtype=release \
|
||||||
|
--default-library=static
|
||||||
|
python3 "$meson_source" compile -C "$source_build_dir/pkgconf-pkgconf-2.5.1/build"
|
||||||
|
python3 "$meson_source" install -C "$source_build_dir/pkgconf-pkgconf-2.5.1/build"
|
||||||
|
|
||||||
|
musl_cc="$clang --target=x86_64-unknown-linux-musl --ld-path=$lld"
|
||||||
|
musl_mulxc3="$static_dir/musl-mulxc3.o"
|
||||||
|
"$clang" \
|
||||||
|
--target=x86_64-unknown-linux-musl \
|
||||||
|
--sysroot="$sysroot" \
|
||||||
|
-fPIC \
|
||||||
|
-I"$source_build_dir/llvm-project-22.1.3.src/compiler-rt/lib/builtins" \
|
||||||
|
-c "$source_build_dir/llvm-project-22.1.3.src/compiler-rt/lib/builtins/mulxc3.c" \
|
||||||
|
-o "$musl_mulxc3"
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/musl-1.2.6"
|
||||||
|
CC="$musl_cc" \
|
||||||
|
AR="$llvm_ar" \
|
||||||
|
RANLIB="$llvm_ranlib" \
|
||||||
|
./configure \
|
||||||
|
--target=x86_64 \
|
||||||
|
--prefix=/usr \
|
||||||
|
--syslibdir=/lib
|
||||||
|
make -s -j"$jobs" LIBCC="$builtins $musl_mulxc3"
|
||||||
|
make -s DESTDIR="$sysroot" LIBCC="$builtins $musl_mulxc3" 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"
|
||||||
|
install -m 0644 "$source_dir/tree.h" "$sysroot/usr/include/sys/tree.h"
|
||||||
|
install -m 0644 "$source_dir/error.h" "$sysroot/usr/include/error.h"
|
||||||
|
|
||||||
|
rust_eh_personality="$static_dir/rust-eh-personality.o"
|
||||||
|
"$clang" \
|
||||||
|
--target=x86_64-unknown-linux-musl \
|
||||||
|
--sysroot="$sysroot" \
|
||||||
|
-c "$repo_dir/base/support/rust-eh-personality.c" \
|
||||||
|
-o "$rust_eh_personality"
|
||||||
|
|
||||||
|
"$script_dir/configure-cross-toolchain.sh" \
|
||||||
|
"$cross_tools_dir" \
|
||||||
|
"$sysroot" \
|
||||||
|
"$clang" \
|
||||||
|
"$clangxx" \
|
||||||
|
"$lld" \
|
||||||
|
"$builtins" \
|
||||||
|
"$rust_eh_personality" \
|
||||||
|
"$llvm_ar" \
|
||||||
|
"$llvm_strip"
|
||||||
|
|
||||||
|
target=x86_64-mouse-linux-musl
|
||||||
|
cc="$cross_tools_dir/bin/$target-cc"
|
||||||
|
cxx="$cross_tools_dir/bin/$target-c++"
|
||||||
|
bootstrap_cxx="$cross_tools_dir/bin/$target-bootstrap-c++"
|
||||||
|
ar="$cross_tools_dir/bin/$target-ar"
|
||||||
|
ranlib="$cross_tools_dir/bin/$target-ranlib"
|
||||||
|
strip="$cross_tools_dir/bin/$target-strip"
|
||||||
|
|
||||||
|
llvm_runtimes_build="$source_build_dir/llvm-runtimes-build"
|
||||||
|
cmake \
|
||||||
|
-G Ninja \
|
||||||
|
-S "$source_build_dir/llvm-project-22.1.3.src/runtimes" \
|
||||||
|
-B "$llvm_runtimes_build" \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
-DCMAKE_C_COMPILER="$cc" \
|
||||||
|
-DCMAKE_C_COMPILER_TARGET=x86_64-unknown-linux-musl \
|
||||||
|
-DCMAKE_CXX_COMPILER="$bootstrap_cxx" \
|
||||||
|
-DCMAKE_CXX_COMPILER_TARGET=x86_64-unknown-linux-musl \
|
||||||
|
-DCMAKE_AR="$ar" \
|
||||||
|
-DCMAKE_RANLIB="$ranlib" \
|
||||||
|
-DCMAKE_SYSTEM_NAME=Linux \
|
||||||
|
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
|
||||||
|
-DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \
|
||||||
|
-DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF \
|
||||||
|
-DCOMPILER_RT_BUILD_BUILTINS=ON \
|
||||||
|
-DCOMPILER_RT_BUILD_CRT=ON \
|
||||||
|
-DCOMPILER_RT_BUILD_GWP_ASAN=OFF \
|
||||||
|
-DCOMPILER_RT_BUILD_LIBFUZZER=OFF \
|
||||||
|
-DCOMPILER_RT_BUILD_MEMPROF=OFF \
|
||||||
|
-DCOMPILER_RT_BUILD_ORC=OFF \
|
||||||
|
-DCOMPILER_RT_BUILD_PROFILE=OFF \
|
||||||
|
-DCOMPILER_RT_BUILD_SANITIZERS=OFF \
|
||||||
|
-DCOMPILER_RT_BUILD_XRAY=OFF \
|
||||||
|
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON \
|
||||||
|
-DLIBCXX_ENABLE_SHARED=OFF \
|
||||||
|
-DLIBCXX_ENABLE_STATIC=ON \
|
||||||
|
-DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=OFF \
|
||||||
|
-DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF \
|
||||||
|
-DLIBCXX_INCLUDE_BENCHMARKS=OFF \
|
||||||
|
-DLIBCXX_INCLUDE_TESTS=OFF \
|
||||||
|
-DLIBCXX_HAS_MUSL_LIBC=ON \
|
||||||
|
-DLIBCXXABI_ENABLE_SHARED=OFF \
|
||||||
|
-DLIBCXXABI_ENABLE_STATIC=ON \
|
||||||
|
-DLIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL=OFF \
|
||||||
|
-DLIBCXXABI_INCLUDE_TESTS=OFF \
|
||||||
|
-DLIBCXXABI_USE_LLVM_UNWINDER=ON \
|
||||||
|
-DLIBUNWIND_ENABLE_SHARED=OFF \
|
||||||
|
-DLIBUNWIND_ENABLE_STATIC=ON \
|
||||||
|
-DLIBUNWIND_INCLUDE_TESTS=OFF
|
||||||
|
cmake --build "$llvm_runtimes_build" -j "$jobs"
|
||||||
|
DESTDIR="$sysroot" cmake --install "$llvm_runtimes_build"
|
||||||
|
|
||||||
|
clang_runtime_dir="$sysroot/usr/lib/clang/22/lib/x86_64-unknown-linux-musl"
|
||||||
|
install -d -m 0755 "$clang_runtime_dir"
|
||||||
|
install -m 0644 \
|
||||||
|
"$sysroot/usr/lib/linux/libclang_rt.builtins-x86_64.a" \
|
||||||
|
"$clang_runtime_dir/libclang_rt.builtins.a"
|
||||||
|
for runtime_object in \
|
||||||
|
clang_rt.crtbegin-x86_64.o \
|
||||||
|
clang_rt.crtend-x86_64.o
|
||||||
|
do
|
||||||
|
install -m 0644 \
|
||||||
|
"$sysroot/usr/lib/linux/$runtime_object" \
|
||||||
|
"$clang_runtime_dir/$runtime_object"
|
||||||
|
done
|
||||||
|
install -m 0644 \
|
||||||
|
"$sysroot/usr/lib/linux/clang_rt.crtbegin-x86_64.o" \
|
||||||
|
"$sysroot/usr/lib/crtbeginT.o"
|
||||||
|
install -m 0644 \
|
||||||
|
"$sysroot/usr/lib/linux/clang_rt.crtend-x86_64.o" \
|
||||||
|
"$sysroot/usr/lib/crtend.o"
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '%s\n' \
|
||||||
|
'[binaries]' \
|
||||||
|
"c = '$cc'" \
|
||||||
|
"cpp = '$cxx'" \
|
||||||
|
"ar = '$ar'" \
|
||||||
|
"strip = '$strip'" \
|
||||||
|
"pkg-config = '$tools_dir/bin/pkgconf'" \
|
||||||
|
'' \
|
||||||
|
'[host_machine]' \
|
||||||
|
"system = 'linux'" \
|
||||||
|
"cpu_family = 'x86_64'" \
|
||||||
|
"cpu = 'x86_64'" \
|
||||||
|
"endian = 'little'" \
|
||||||
|
'' \
|
||||||
|
'[built-in options]' \
|
||||||
|
"c_args = ['-O2']" \
|
||||||
|
"cpp_args = ['-O2']" \
|
||||||
|
"c_link_args = ['-static']" \
|
||||||
|
"cpp_link_args = ['-static']" \
|
||||||
|
"default_library = 'static'" \
|
||||||
|
"prefer_static = true"
|
||||||
|
} >"$cross_file"
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
script_dir=$(dirname "$component_dir")
|
||||||
|
repo_dir=$(dirname "$script_dir")
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
source_dir=${MOUSE_SOURCE_DIR:-"$build_dir/sources"}
|
||||||
|
static_dir="$build_dir/static"
|
||||||
|
source_build_dir="$static_dir/src"
|
||||||
|
sysroot="$static_dir/sysroot"
|
||||||
|
base_root="$static_dir/rootfs"
|
||||||
|
tools_dir="$static_dir/tools"
|
||||||
|
cross_tools_dir="$static_dir/cross-tools"
|
||||||
|
jobs=${MOUSE_BUILD_JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '2')}
|
||||||
|
|
||||||
|
case "$static_dir" in
|
||||||
|
"$repo_dir"/build/static) ;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "refusing to clear unexpected static build path: $static_dir" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
target_libdir=$(rustc --print target-libdir --target x86_64-unknown-linux-musl)
|
||||||
|
builtins=$(find "$target_libdir" -maxdepth 1 -name 'libcompiler_builtins-*.rlib' -print | head -1)
|
||||||
|
|
||||||
|
clang=$(command -v clang)
|
||||||
|
clangxx=$(command -v clang++)
|
||||||
|
lld=$(command -v ld.lld)
|
||||||
|
llvm_ar=$(command -v llvm-ar)
|
||||||
|
llvm_strip=$(command -v llvm-strip)
|
||||||
|
build_triplet=$(cc -dumpmachine)
|
||||||
|
native_cc=cc
|
||||||
|
|
||||||
|
for requirement in \
|
||||||
|
"$clang" "$clangxx" "$lld" "$builtins" "$llvm_ar" "$llvm_strip"
|
||||||
|
do
|
||||||
|
if [ ! -f "$requirement" ]; then
|
||||||
|
printf '%s\n' "missing LLVM/Rust cross-build component: $requirement" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
llvm_ranlib="$tools_dir/bin/llvm-ranlib"
|
||||||
|
meson_source="$source_build_dir/meson-1.9.1/meson.py"
|
||||||
|
PATH="$tools_dir/bin:$PATH"
|
||||||
|
export PATH
|
||||||
|
target=x86_64-mouse-linux-musl
|
||||||
|
cc="$cross_tools_dir/bin/$target-cc"
|
||||||
|
cxx="$cross_tools_dir/bin/$target-c++"
|
||||||
|
bootstrap_cxx="$cross_tools_dir/bin/$target-bootstrap-c++"
|
||||||
|
ar="$cross_tools_dir/bin/$target-ar"
|
||||||
|
ranlib="$cross_tools_dir/bin/$target-ranlib"
|
||||||
|
strip="$cross_tools_dir/bin/$target-strip"
|
||||||
|
cross_file="$static_dir/chimerautils.cross"
|
||||||
Executable
+125
@@ -0,0 +1,125 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$component_dir/common.sh"
|
||||||
|
|
||||||
|
for runtime_file in \
|
||||||
|
/lib/ld-musl-x86_64.so.1 \
|
||||||
|
/usr/lib/libc.so \
|
||||||
|
/usr/lib/crt1.o \
|
||||||
|
/usr/lib/Scrt1.o \
|
||||||
|
/usr/lib/crti.o \
|
||||||
|
/usr/lib/crtn.o \
|
||||||
|
/usr/lib/crtbeginT.o \
|
||||||
|
/usr/lib/crtend.o
|
||||||
|
do
|
||||||
|
if [ ! -f "$base_root$runtime_file" ]; then
|
||||||
|
printf '%s\n' "missing dynamic runtime file: $runtime_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for root in "$sysroot" "$base_root"; do
|
||||||
|
ln -sf crtbeginT.o "$root/usr/lib/crtbegin.o"
|
||||||
|
ln -sf crtbeginT.o "$root/usr/lib/crtbeginS.o"
|
||||||
|
ln -sf crtend.o "$root/usr/lib/crtendS.o"
|
||||||
|
done
|
||||||
|
|
||||||
|
make_dynamic_compiler() {
|
||||||
|
wrapper=$1
|
||||||
|
compiler=$2
|
||||||
|
cat >"$wrapper" <<EOF
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
shared=no
|
||||||
|
for argument do
|
||||||
|
if [ "\$argument" = -shared ]; then
|
||||||
|
shared=yes
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "\$shared" = no ]; then
|
||||||
|
exec "$compiler" "\$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$compiler" \
|
||||||
|
-nostdlib \
|
||||||
|
"$sysroot/usr/lib/crti.o" \
|
||||||
|
"$sysroot/usr/lib/crtbeginS.o" \
|
||||||
|
-L"$sysroot/usr/lib" \
|
||||||
|
"\$@" \
|
||||||
|
"$dynamic_builtins" \
|
||||||
|
-lc \
|
||||||
|
"$sysroot/usr/lib/crtendS.o" \
|
||||||
|
"$sysroot/usr/lib/crtn.o"
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$wrapper"
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic_cc="$cross_tools_dir/bin/x86_64-mouse-linux-musl-dynamic-cc"
|
||||||
|
dynamic_cxx="$cross_tools_dir/bin/x86_64-mouse-linux-musl-dynamic-c++"
|
||||||
|
dynamic_builtins="$sysroot/usr/lib/clang/22/lib/x86_64-unknown-linux-musl/libclang_rt.builtins.a"
|
||||||
|
if [ ! -f "$dynamic_builtins" ]; then
|
||||||
|
printf '%s\n' "missing compiler-rt builtins: $dynamic_builtins" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
make_dynamic_compiler "$dynamic_cc" "$cc"
|
||||||
|
make_dynamic_compiler "$dynamic_cxx" "$bootstrap_cxx"
|
||||||
|
|
||||||
|
shared_runtimes_build="$source_build_dir/llvm-shared-runtimes-build"
|
||||||
|
cmake \
|
||||||
|
-G Ninja \
|
||||||
|
-S "$source_build_dir/llvm-project-22.1.3.src/runtimes" \
|
||||||
|
-B "$shared_runtimes_build" \
|
||||||
|
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
-DCMAKE_C_COMPILER="$dynamic_cc" \
|
||||||
|
-DCMAKE_C_COMPILER_TARGET=x86_64-unknown-linux-musl \
|
||||||
|
-DCMAKE_CXX_COMPILER="$dynamic_cxx" \
|
||||||
|
-DCMAKE_CXX_COMPILER_TARGET=x86_64-unknown-linux-musl \
|
||||||
|
-DCMAKE_AR="$ar" \
|
||||||
|
-DCMAKE_RANLIB="$ranlib" \
|
||||||
|
-DCMAKE_SYSTEM_NAME=Linux \
|
||||||
|
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
|
||||||
|
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \
|
||||||
|
-DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF \
|
||||||
|
-DLIBCXX_ENABLE_SHARED=ON \
|
||||||
|
-DLIBCXX_ENABLE_STATIC=OFF \
|
||||||
|
-DLIBCXX_HAS_MUSL_LIBC=ON \
|
||||||
|
-DLIBCXX_HAS_ATOMIC_LIB=NO \
|
||||||
|
-DLIBCXX_INCLUDE_BENCHMARKS=OFF \
|
||||||
|
-DLIBCXX_INCLUDE_TESTS=OFF \
|
||||||
|
-DLIBCXX_USE_COMPILER_RT=ON \
|
||||||
|
-DLIBCXXABI_ENABLE_SHARED=ON \
|
||||||
|
-DLIBCXXABI_ENABLE_STATIC=OFF \
|
||||||
|
-DLIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL=OFF \
|
||||||
|
-DLIBCXXABI_INCLUDE_TESTS=OFF \
|
||||||
|
-DLIBCXXABI_USE_COMPILER_RT=ON \
|
||||||
|
-DLIBCXXABI_USE_LLVM_UNWINDER=ON \
|
||||||
|
-DLIBUNWIND_ENABLE_SHARED=ON \
|
||||||
|
-DLIBUNWIND_ENABLE_STATIC=OFF \
|
||||||
|
-DLIBUNWIND_INCLUDE_TESTS=OFF \
|
||||||
|
-DLIBUNWIND_USE_COMPILER_RT=ON
|
||||||
|
cmake --build "$shared_runtimes_build" -j "$jobs"
|
||||||
|
DESTDIR="$sysroot" cmake --install "$shared_runtimes_build"
|
||||||
|
cp -R "$sysroot/usr/lib/." "$base_root/usr/lib/"
|
||||||
|
|
||||||
|
cat >"$base_root/usr/bin/clang.cfg" <<'EOF'
|
||||||
|
--target=x86_64-unknown-linux-musl
|
||||||
|
--sysroot=/
|
||||||
|
--ld-path=/usr/bin/ld.lld
|
||||||
|
--rtlib=compiler-rt
|
||||||
|
--unwindlib=libunwind
|
||||||
|
EOF
|
||||||
|
cat >"$base_root/usr/bin/clang++.cfg" <<'EOF'
|
||||||
|
--target=x86_64-unknown-linux-musl
|
||||||
|
--sysroot=/
|
||||||
|
--ld-path=/usr/bin/ld.lld
|
||||||
|
-stdlib=libc++
|
||||||
|
--rtlib=compiler-rt
|
||||||
|
--unwindlib=libunwind
|
||||||
|
-lc++abi
|
||||||
|
EOF
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$component_dir/common.sh"
|
||||||
|
|
||||||
|
extract() {
|
||||||
|
tar -xf "$source_dir/$1" -C "$source_build_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract less-704.tar.gz
|
||||||
|
extract mandoc-1.14.6.tar.gz
|
||||||
|
extract file-5.47.tar.gz
|
||||||
|
extract gzip-1.14.tar.xz
|
||||||
|
extract bzip2-1.0.8.tar.gz
|
||||||
|
extract xz-5.8.3.tar.bz2
|
||||||
|
extract iproute2-7.1.0.tar.xz
|
||||||
|
extract libmnl-1.0.5.tar.bz2
|
||||||
|
extract libnftnl-1.3.1.tar.xz
|
||||||
|
extract nftables-1.1.6.tar.xz
|
||||||
|
|
||||||
|
pkgconfig_path="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig"
|
||||||
|
|
||||||
|
install_man_page() {
|
||||||
|
page=$1
|
||||||
|
name=${2:-${page##*/}}
|
||||||
|
section=${name##*.}
|
||||||
|
install -d -m 0755 "$base_root/usr/share/man/man$section"
|
||||||
|
install -m 0644 "$page" "$base_root/usr/share/man/man$section/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_command_man_page() {
|
||||||
|
tree=$1
|
||||||
|
command=$2
|
||||||
|
page=$(find "$tree" -type f -name "$command.[1-9]" -print | head -1)
|
||||||
|
if [ -n "$page" ]; then
|
||||||
|
install_man_page "$page"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/less-704"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
LIBS="-lcurses -lterminfo" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--sysconfdir=/etc \
|
||||||
|
--with-regex=posix
|
||||||
|
make -s -j"$jobs" less lesskey lessecho
|
||||||
|
install -m 0755 less lesskey lessecho "$base_root/usr/bin/"
|
||||||
|
)
|
||||||
|
install_man_page "$source_build_dir/less-704/less.nro" less.1
|
||||||
|
install_man_page "$source_build_dir/less-704/lesskey.nro" lesskey.1
|
||||||
|
install_man_page "$source_build_dir/less-704/lessecho.nro" lessecho.1
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/bzip2-1.0.8"
|
||||||
|
make -s -j"$jobs" \
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
CFLAGS="-O2" \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
bzip2 bzip2recover libbz2.a
|
||||||
|
install -m 0644 bzlib.h "$sysroot/usr/include/bzlib.h"
|
||||||
|
install -m 0644 libbz2.a "$sysroot/usr/lib/libbz2.a"
|
||||||
|
install -m 0755 bzip2 bzip2recover "$base_root/usr/bin/"
|
||||||
|
)
|
||||||
|
ln -s bzip2 "$base_root/usr/bin/bunzip2"
|
||||||
|
ln -s bzip2 "$base_root/usr/bin/bzcat"
|
||||||
|
for page in bzip2.1 bzgrep.1 bzmore.1 bzdiff.1; do
|
||||||
|
install_man_page "$source_build_dir/bzip2-1.0.8/$page"
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/xz-5.8.3"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--disable-nls \
|
||||||
|
--disable-doc \
|
||||||
|
--disable-xzdec \
|
||||||
|
--disable-lzmadec \
|
||||||
|
--disable-lzmainfo
|
||||||
|
make -s -j"$jobs" -C src/liblzma
|
||||||
|
make -s -C src/liblzma DESTDIR="$sysroot" install
|
||||||
|
make -s -j"$jobs" -C src/xz xz
|
||||||
|
install -m 0755 src/xz/xz "$base_root/usr/bin/xz"
|
||||||
|
)
|
||||||
|
for command in unxz xzcat lzma unlzma lzcat; do
|
||||||
|
ln -s xz "$base_root/usr/bin/$command"
|
||||||
|
done
|
||||||
|
for page in xz.1; do
|
||||||
|
install_man_page "$source_build_dir/xz-5.8.3/src/xz/$page"
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/gzip-1.14"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
gl_cv_func_getcwd_path_max=yes \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-nls
|
||||||
|
make -s -j"$jobs"
|
||||||
|
install -m 0755 gzip "$base_root/usr/bin/gzip"
|
||||||
|
for script in gunzip zcat zcmp zdiff zgrep zless zmore; do
|
||||||
|
install -m 0755 "$script" "$base_root/usr/bin/$script"
|
||||||
|
done
|
||||||
|
)
|
||||||
|
for page in gzip.1 gunzip.1 zcat.1 zcmp.1 zdiff.1 zgrep.1 zless.1 zmore.1; do
|
||||||
|
install_man_page "$source_build_dir/gzip-1.14/$page"
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/file-5.47"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
PKG_CONFIG="$tools_dir/bin/pkgconf" \
|
||||||
|
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--disable-zstdlib \
|
||||||
|
--disable-lzlib \
|
||||||
|
--disable-libseccomp
|
||||||
|
make -s -C src magic.h
|
||||||
|
make -s -j"$jobs" -C src file
|
||||||
|
make -s -C magic magic.mgc
|
||||||
|
install -m 0755 src/file "$base_root/usr/bin/file"
|
||||||
|
install -d -m 0755 "$base_root/usr/share/misc"
|
||||||
|
install -m 0644 magic/magic.mgc "$base_root/usr/share/misc/magic.mgc"
|
||||||
|
)
|
||||||
|
install_man_page "$source_build_dir/file-5.47/doc/file.man" file.1
|
||||||
|
install_man_page "$source_build_dir/file-5.47/doc/magic.man" magic.5
|
||||||
|
|
||||||
|
unzip_source="$source_build_dir/libarchive-unzip-3.8.7"
|
||||||
|
install -d -m 0755 "$unzip_source"
|
||||||
|
tar -xf "$source_dir/libarchive-3.8.7.tar.xz" \
|
||||||
|
-C "$unzip_source" \
|
||||||
|
--strip-components=1
|
||||||
|
(
|
||||||
|
cd "$unzip_source"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
PKG_CONFIG="$tools_dir/bin/pkgconf" \
|
||||||
|
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--disable-bsdtar \
|
||||||
|
--disable-bsdcat \
|
||||||
|
--disable-bsdcpio \
|
||||||
|
--enable-bsdunzip=static \
|
||||||
|
--disable-acl \
|
||||||
|
--disable-xattr \
|
||||||
|
--without-libb2 \
|
||||||
|
--without-iconv \
|
||||||
|
--without-lz4 \
|
||||||
|
--without-zstd \
|
||||||
|
--without-xml2 \
|
||||||
|
--without-expat \
|
||||||
|
--without-openssl \
|
||||||
|
--without-cng
|
||||||
|
make -s -j"$jobs" bsdunzip
|
||||||
|
install -m 0755 bsdunzip "$base_root/usr/bin/unzip"
|
||||||
|
)
|
||||||
|
install_man_page "$unzip_source/unzip/bsdunzip.1" unzip.1
|
||||||
|
|
||||||
|
mandoc_source="$source_build_dir/mandoc-1.14.6"
|
||||||
|
cat >"$mandoc_source/configure.local" <<EOF
|
||||||
|
CC="$cc"
|
||||||
|
AR="$ar"
|
||||||
|
CFLAGS="-O2"
|
||||||
|
LDFLAGS="-static"
|
||||||
|
STATIC="-static"
|
||||||
|
PREFIX="/usr"
|
||||||
|
BINDIR="/usr/bin"
|
||||||
|
SBINDIR="/usr/sbin"
|
||||||
|
MANDIR="/usr/share/man"
|
||||||
|
MANPATH_BASE="/usr/share/man:/usr/local/man"
|
||||||
|
MANPATH_DEFAULT="/usr/share/man:/usr/local/man"
|
||||||
|
OSENUM=MANDOC_OS_OTHER
|
||||||
|
OSNAME="MOUSE"
|
||||||
|
BINM_PAGER="less"
|
||||||
|
LN="ln -sf"
|
||||||
|
EOF
|
||||||
|
(
|
||||||
|
cd "$mandoc_source"
|
||||||
|
./configure
|
||||||
|
make -s -j"$jobs"
|
||||||
|
make -s DESTDIR="$base_root" base-install
|
||||||
|
)
|
||||||
|
|
||||||
|
iproute_source="$source_build_dir/iproute2-7.1.0"
|
||||||
|
(
|
||||||
|
cd "$iproute_source"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
YACC=byacc \
|
||||||
|
PKG_CONFIG="$tools_dir/bin/pkgconf" \
|
||||||
|
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
sh ./configure
|
||||||
|
# Enter component directories through the top-level makefile. It defines
|
||||||
|
# and exports iproute2's path constants, feature macros, and UAPI includes;
|
||||||
|
# invoking the sub-makes directly silently drops them.
|
||||||
|
make -s -j"$jobs" \
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
YACC=byacc \
|
||||||
|
SHARED_LIBS=n \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
SUBDIRS=lib
|
||||||
|
make -s -j"$jobs" \
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
YACC=byacc \
|
||||||
|
SHARED_LIBS=n \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
SUBDIRS="ip tc bridge misc"
|
||||||
|
for target in ip/ip tc/tc bridge/bridge misc/ss; do
|
||||||
|
install -m 0755 "$target" "$base_root/sbin/${target##*/}"
|
||||||
|
done
|
||||||
|
)
|
||||||
|
for command in ip tc bridge ss; do
|
||||||
|
install_command_man_page "$iproute_source/man" "$command"
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/libmnl-1.0.5"
|
||||||
|
CC="$cc" AR="$ar" RANLIB="$ranlib" ./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static
|
||||||
|
make -s -j"$jobs"
|
||||||
|
make -s DESTDIR="$sysroot" install
|
||||||
|
)
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/libnftnl-1.3.1"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
PKG_CONFIG="$tools_dir/bin/pkgconf" \
|
||||||
|
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static
|
||||||
|
make -s -j"$jobs"
|
||||||
|
make -s DESTDIR="$sysroot" install
|
||||||
|
)
|
||||||
|
# These are static-only cross-build dependencies. Libtool archives retain the
|
||||||
|
# target's /usr/lib prefix and make nftables' host libtool look outside the
|
||||||
|
# sysroot; the real .a archives and pkg-config metadata are sufficient.
|
||||||
|
rm -f "$sysroot/usr/lib/libmnl.la" "$sysroot/usr/lib/libnftnl.la"
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/nftables-1.1.6"
|
||||||
|
patch -p1 <"$repo_dir/base/patches/nftables-posix-configure.patch"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
YACC=byacc \
|
||||||
|
PKG_CONFIG="$tools_dir/bin/pkgconf" \
|
||||||
|
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--sbindir=/sbin \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--disable-man-doc \
|
||||||
|
--with-mini-gmp \
|
||||||
|
--without-cli \
|
||||||
|
--without-xtables \
|
||||||
|
--without-json \
|
||||||
|
--without-unitdir
|
||||||
|
make -s -j"$jobs" src/nft
|
||||||
|
install -m 0755 src/nft "$base_root/sbin/nft"
|
||||||
|
)
|
||||||
|
install_man_page "$source_build_dir/nftables-1.1.6/doc/nft.8"
|
||||||
|
|
||||||
|
for command in \
|
||||||
|
blkid dmesg fdisk findmnt flock losetup lsblk mount sfdisk swapoff swapon umount wipefs
|
||||||
|
do
|
||||||
|
install_command_man_page "$source_build_dir/util-linux-2.42.2" "$command"
|
||||||
|
done
|
||||||
|
for command in free pgrep pkill ps sysctl top uptime vmstat watch; do
|
||||||
|
install_command_man_page "$source_build_dir/procps-ng-4.0.5" "$command"
|
||||||
|
done
|
||||||
|
install_command_man_page "$source_build_dir/iputils-20250605" ping
|
||||||
|
|
||||||
|
while IFS= read -r page; do
|
||||||
|
filename=${page##*/}
|
||||||
|
command=${filename%.*}
|
||||||
|
if [ -x "$base_root/bin/$command" ] ||
|
||||||
|
[ -x "$base_root/sbin/$command" ] ||
|
||||||
|
[ -x "$base_root/usr/bin/$command" ] ||
|
||||||
|
[ -x "$base_root/usr/sbin/$command" ]
|
||||||
|
then
|
||||||
|
install_man_page "$page"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
$(find "$source_build_dir/chimerautils-15.0.3" -type f -name '*.[1-9]' -print | LC_ALL=C sort)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
host_mandoc_source="$static_dir/host-mandoc-1.14.6"
|
||||||
|
install -d -m 0755 "$host_mandoc_source"
|
||||||
|
tar -xf "$source_dir/mandoc-1.14.6.tar.gz" \
|
||||||
|
-C "$host_mandoc_source" \
|
||||||
|
--strip-components=1
|
||||||
|
cat >"$host_mandoc_source/configure.local" <<EOF
|
||||||
|
CC="$native_cc"
|
||||||
|
PREFIX="$tools_dir/mandoc-host"
|
||||||
|
EOF
|
||||||
|
(
|
||||||
|
cd "$host_mandoc_source"
|
||||||
|
./configure
|
||||||
|
make -s -j"$jobs" mandoc
|
||||||
|
install -d -m 0755 "$tools_dir/mandoc-host"
|
||||||
|
install -m 0755 mandoc "$tools_dir/mandoc-host/makewhatis"
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$component_dir/common.sh"
|
||||||
|
|
||||||
|
printf '%s\n' '#!/bin/sh' 'exec /usr/bin/tput clear' >"$base_root/usr/bin/clear"
|
||||||
|
chmod 0755 "$base_root/usr/bin/clear"
|
||||||
|
printf '%s\n' "$base_root"
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$component_dir/common.sh"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/zlib-1.3.2"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
./configure \
|
||||||
|
--prefix=/usr \
|
||||||
|
--static
|
||||||
|
make -s -j"$jobs"
|
||||||
|
make -s DESTDIR="$sysroot" install
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/libarchive-3.8.7"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
CPPFLAGS="-I$sysroot/usr/include" \
|
||||||
|
LDFLAGS="-L$sysroot/usr/lib" \
|
||||||
|
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 \
|
||||||
|
--enable-bsdtar=static \
|
||||||
|
--disable-bsdcat \
|
||||||
|
--disable-bsdcpio \
|
||||||
|
--disable-bsdunzip \
|
||||||
|
--disable-acl \
|
||||||
|
--disable-xattr \
|
||||||
|
--without-bz2lib \
|
||||||
|
--without-libb2 \
|
||||||
|
--without-iconv \
|
||||||
|
--without-lz4 \
|
||||||
|
--without-lzma \
|
||||||
|
--without-zstd \
|
||||||
|
--without-xml2 \
|
||||||
|
--without-expat \
|
||||||
|
--without-openssl \
|
||||||
|
--without-cng
|
||||||
|
make -s -j"$jobs" bsdtar
|
||||||
|
install -m 0755 bsdtar "$base_root/usr/bin/tar"
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/openssl-3.5.7"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
./Configure \
|
||||||
|
linux-x86_64 \
|
||||||
|
--prefix=/usr \
|
||||||
|
--libdir=lib \
|
||||||
|
--openssldir=/etc/ssl \
|
||||||
|
no-afalgeng \
|
||||||
|
no-apps \
|
||||||
|
no-docs \
|
||||||
|
no-dso \
|
||||||
|
no-module \
|
||||||
|
no-secure-memory \
|
||||||
|
no-shared \
|
||||||
|
no-tests
|
||||||
|
make -s -j"$jobs" build_libs
|
||||||
|
make -s DESTDIR="$sysroot" install_sw
|
||||||
|
)
|
||||||
|
|
||||||
|
install -d -m 0755 "$base_root/etc/ssl"
|
||||||
|
install -m 0644 \
|
||||||
|
"$source_dir/cacert-2026-07-16.pem" \
|
||||||
|
"$base_root/etc/ssl/cert.pem"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/libxcrypt-4.5.2"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$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="$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
|
||||||
|
shadow_commands="
|
||||||
|
groupadd groupdel groupmod login passwd su
|
||||||
|
useradd userdel usermod
|
||||||
|
"
|
||||||
|
MOUSE_CROSS_EXTRA_STATIC_LIBS="$sysroot/usr/lib/libcrypt.a" \
|
||||||
|
make -s -j"$jobs" -C src $shadow_commands
|
||||||
|
for command in $shadow_commands; do
|
||||||
|
mode=0755
|
||||||
|
destination="$base_root/usr/sbin"
|
||||||
|
case "$command" in
|
||||||
|
login) destination="$base_root/bin" ;;
|
||||||
|
passwd)
|
||||||
|
mode=4755
|
||||||
|
destination="$base_root/usr/bin"
|
||||||
|
;;
|
||||||
|
su)
|
||||||
|
mode=4755
|
||||||
|
destination="$base_root/bin"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
install -m "$mode" "src/$command" "$destination/$command"
|
||||||
|
done
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/dhcpcd-8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
./configure \
|
||||||
|
--os=linux \
|
||||||
|
--target=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--sbindir=/usr/bin \
|
||||||
|
--sysconfdir=/etc \
|
||||||
|
--libexecdir=/usr/libexec \
|
||||||
|
--dbdir=/var/lib/dhcpcd \
|
||||||
|
--rundir=/run \
|
||||||
|
--enable-static \
|
||||||
|
--disable-ipv6 \
|
||||||
|
--disable-auth \
|
||||||
|
--enable-privsep \
|
||||||
|
--privsepuser=dhcpcd \
|
||||||
|
--disable-ntp \
|
||||||
|
--without-udev \
|
||||||
|
--without-libpcap \
|
||||||
|
--without-openssl \
|
||||||
|
--with-hooks=
|
||||||
|
make -s -j"$jobs" -C src
|
||||||
|
install -m 0755 src/dhcpcd "$base_root/sbin/dhcpcd"
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/netbsd-curses-0.3.2"
|
||||||
|
make -s -f GNUmakefile -j"$jobs" \
|
||||||
|
CC="$cc" \
|
||||||
|
HOSTCC="$native_cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
CFLAGS="-O2" \
|
||||||
|
CPPFLAGS="-I. -I./libterminfo -DTERMINFO_COMPILE -DTERMINFO_DB -DTERMINFO_COMPAT" \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
PREFIX=/usr \
|
||||||
|
all-static
|
||||||
|
make -s -f GNUmakefile \
|
||||||
|
CC="$cc" \
|
||||||
|
HOSTCC="$native_cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
CFLAGS="-O2" \
|
||||||
|
CPPFLAGS="-I. -I./libterminfo -DTERMINFO_COMPILE -DTERMINFO_DB -DTERMINFO_COMPAT" \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
PREFIX=/usr \
|
||||||
|
DESTDIR="$sysroot" \
|
||||||
|
install-static
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
build_autoconf_library() {
|
||||||
|
source_name=$1
|
||||||
|
build_target=$2
|
||||||
|
shift 2
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/$source_name"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$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 \
|
||||||
|
"$@"
|
||||||
|
make -s -j"$jobs" "$build_target"
|
||||||
|
make -s DESTDIR="$sysroot" \
|
||||||
|
install-libLTLIBRARIES \
|
||||||
|
install-data-local \
|
||||||
|
install-pkgconfDATA
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
build_autoconf_library attr-2.5.2 libattr.la
|
||||||
|
build_autoconf_library acl-2.3.2 libacl.la
|
||||||
|
|
||||||
|
# The projects' install-time EXPORT substitution uses a GNU sed word-boundary
|
||||||
|
# extension which macOS sed accepts but does not apply. Normalize the installed
|
||||||
|
# public declarations for consumers of the cross-built static libraries.
|
||||||
|
find "$sysroot/usr/include/attr" "$sysroot/usr/include/acl" \
|
||||||
|
"$sysroot/usr/include/sys/acl.h" \
|
||||||
|
-type f -exec perl -pi -e 's/\bEXPORT\b/extern/g' {} +
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/libxo-1.7.5"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
ac_cv_func_malloc_0_nonnull=yes \
|
||||||
|
ac_cv_func_realloc_0_nonnull=yes \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--disable-libxo-options \
|
||||||
|
--disable-gettext \
|
||||||
|
--enable-text-only
|
||||||
|
make -s -C libxo -j"$jobs" libxo.la
|
||||||
|
make -s -C libxo DESTDIR="$sysroot" \
|
||||||
|
install-libLTLIBRARIES \
|
||||||
|
install-libxoincHEADERS
|
||||||
|
make -s DESTDIR="$sysroot" install-pkgconfigDATA
|
||||||
|
)
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$component_dir/common.sh"
|
||||||
|
|
||||||
|
build_llvm() {
|
||||||
|
llvm_host_build="$source_build_dir/llvm-host-tools-build"
|
||||||
|
cmake \
|
||||||
|
-G Ninja \
|
||||||
|
-S "$source_build_dir/llvm-project-22.1.3.src/llvm" \
|
||||||
|
-B "$llvm_host_build" \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_C_COMPILER="$native_cc" \
|
||||||
|
-DCMAKE_CXX_COMPILER=c++ \
|
||||||
|
-DLLVM_ENABLE_PROJECTS=clang \
|
||||||
|
-DLLVM_TARGETS_TO_BUILD=X86 \
|
||||||
|
-DLLVM_INCLUDE_BENCHMARKS=OFF \
|
||||||
|
-DLLVM_INCLUDE_EXAMPLES=OFF \
|
||||||
|
-DLLVM_INCLUDE_TESTS=OFF \
|
||||||
|
-DCLANG_INCLUDE_TESTS=OFF
|
||||||
|
cmake --build "$llvm_host_build" -j "$jobs" \
|
||||||
|
--target llvm-tblgen clang-tblgen
|
||||||
|
|
||||||
|
llvm_target_build="$source_build_dir/llvm-target-build"
|
||||||
|
llvm_target_install="$static_dir/llvm-target-install"
|
||||||
|
cmake \
|
||||||
|
-G Ninja \
|
||||||
|
-S "$source_build_dir/llvm-project-22.1.3.src/llvm" \
|
||||||
|
-B "$llvm_target_build" \
|
||||||
|
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
-DCMAKE_C_COMPILER="$cc" \
|
||||||
|
-DCMAKE_CXX_COMPILER="$cxx" \
|
||||||
|
-DCMAKE_AR="$ar" \
|
||||||
|
-DCMAKE_RANLIB="$ranlib" \
|
||||||
|
-DCMAKE_SYSTEM_NAME=Linux \
|
||||||
|
-DCMAKE_SYSTEM_PROCESSOR=x86_64 \
|
||||||
|
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
|
||||||
|
-DLLVM_TABLEGEN="$llvm_host_build/bin/llvm-tblgen" \
|
||||||
|
-DCLANG_TABLEGEN="$llvm_host_build/bin/clang-tblgen" \
|
||||||
|
-DLLVM_ENABLE_PROJECTS="clang;lld" \
|
||||||
|
-DLLVM_TARGETS_TO_BUILD=X86 \
|
||||||
|
-DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-unknown-linux-musl \
|
||||||
|
-DLLVM_HOST_TRIPLE=x86_64-unknown-linux-musl \
|
||||||
|
-DLLVM_ENABLE_BINDINGS=OFF \
|
||||||
|
-DLLVM_ENABLE_FFI=OFF \
|
||||||
|
-DLLVM_ENABLE_LIBEDIT=OFF \
|
||||||
|
-DLLVM_ENABLE_LIBXML2=OFF \
|
||||||
|
-DLLVM_ENABLE_PIC=OFF \
|
||||||
|
-DLLVM_ENABLE_TERMINFO=OFF \
|
||||||
|
-DLLVM_ENABLE_ZLIB=OFF \
|
||||||
|
-DLLVM_ENABLE_ZSTD=OFF \
|
||||||
|
-DLLVM_INCLUDE_BENCHMARKS=OFF \
|
||||||
|
-DLLVM_INCLUDE_EXAMPLES=OFF \
|
||||||
|
-DLLVM_INCLUDE_TESTS=OFF \
|
||||||
|
-DLLVM_INCLUDE_UTILS=ON \
|
||||||
|
-DCLANG_ENABLE_ARCMT=OFF \
|
||||||
|
-DCLANG_ENABLE_STATIC_ANALYZER=OFF \
|
||||||
|
-DCLANG_INCLUDE_TESTS=OFF \
|
||||||
|
-DLLVM_DISTRIBUTION_COMPONENTS="clang;clang-resource-headers;lld;llvm-ar;llvm-nm;llvm-objdump;llvm-readelf;llvm-size;llvm-strings;llvm-strip"
|
||||||
|
DESTDIR="$llvm_target_install" \
|
||||||
|
cmake --build "$llvm_target_build" -j "$jobs" \
|
||||||
|
--target install-distribution
|
||||||
|
|
||||||
|
cp -R "$sysroot/usr/include" "$base_root/usr/"
|
||||||
|
cp -R "$sysroot/usr/lib/." "$base_root/usr/lib/"
|
||||||
|
cp -R "$sysroot/lib" "$base_root/"
|
||||||
|
cp -R "$llvm_target_install/usr/." "$base_root/usr/"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_package_tools() {
|
||||||
|
pkgconf_target_build="$source_build_dir/pkgconf-pkgconf-2.5.1/build-target"
|
||||||
|
python3 "$meson_source" setup \
|
||||||
|
"$pkgconf_target_build" \
|
||||||
|
"$source_build_dir/pkgconf-pkgconf-2.5.1" \
|
||||||
|
--cross-file="$cross_file" \
|
||||||
|
--prefix=/usr \
|
||||||
|
--buildtype=release \
|
||||||
|
--default-library=static
|
||||||
|
python3 "$meson_source" compile -C "$pkgconf_target_build"
|
||||||
|
DESTDIR="$base_root" \
|
||||||
|
python3 "$meson_source" install -C "$pkgconf_target_build"
|
||||||
|
ln -s pkgconf "$base_root/usr/bin/pkg-config"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/make-4.4.1"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-load \
|
||||||
|
--disable-nls \
|
||||||
|
--without-guile
|
||||||
|
make -j"$jobs"
|
||||||
|
make DESTDIR="$base_root" install
|
||||||
|
)
|
||||||
|
ln -s make "$base_root/usr/bin/gmake"
|
||||||
|
|
||||||
|
ln -s clang "$base_root/usr/bin/cc"
|
||||||
|
ln -s clang++ "$base_root/usr/bin/c++"
|
||||||
|
ln -s ld.lld "$base_root/usr/bin/ld"
|
||||||
|
ln -s llvm-ar "$base_root/usr/bin/ar"
|
||||||
|
ln -s llvm-nm "$base_root/usr/bin/nm"
|
||||||
|
ln -s llvm-objdump "$base_root/usr/bin/objdump"
|
||||||
|
ln -s llvm-readelf "$base_root/usr/bin/readelf"
|
||||||
|
ln -s llvm-ar "$base_root/usr/bin/llvm-ranlib"
|
||||||
|
cat >"$base_root/usr/bin/ranlib" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# BSD ranlib accepts -t to refresh an existing archive index. LLVM's ranlib
|
||||||
|
# does not expose that option, but rebuilding the index is equivalent for the
|
||||||
|
# package build that requested it.
|
||||||
|
if [ "${1-}" = "-t" ]; then
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec /usr/bin/llvm-ranlib "$@"
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$base_root/usr/bin/ranlib"
|
||||||
|
ln -s llvm-strip "$base_root/usr/bin/strip"
|
||||||
|
ln -s ../../lib/ld-musl-x86_64.so.1 "$base_root/usr/bin/ldd"
|
||||||
|
|
||||||
|
cat >"$base_root/usr/bin/clang.cfg" <<'EOF'
|
||||||
|
--target=x86_64-unknown-linux-musl
|
||||||
|
--sysroot=/
|
||||||
|
--ld-path=/usr/bin/ld.lld
|
||||||
|
--rtlib=compiler-rt
|
||||||
|
--unwindlib=libunwind
|
||||||
|
-static
|
||||||
|
EOF
|
||||||
|
cat >"$base_root/usr/bin/clang++.cfg" <<'EOF'
|
||||||
|
--target=x86_64-unknown-linux-musl
|
||||||
|
--sysroot=/
|
||||||
|
--ld-path=/usr/bin/ld.lld
|
||||||
|
-stdlib=libc++
|
||||||
|
--rtlib=compiler-rt
|
||||||
|
--unwindlib=libunwind
|
||||||
|
-lc++abi
|
||||||
|
-static
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${MOUSE_NATIVE_TOOLCHAIN_PHASE:-all}" in
|
||||||
|
llvm) build_llvm ;;
|
||||||
|
package-tools) build_package_tools ;;
|
||||||
|
all)
|
||||||
|
build_llvm
|
||||||
|
build_package_tools
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "invalid MOUSE_NATIVE_TOOLCHAIN_PHASE" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$component_dir/common.sh"
|
||||||
|
|
||||||
|
extract() {
|
||||||
|
tar -xf "$source_dir/$1" -C "$source_build_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract btrfs-progs-v7.0.tar.xz
|
||||||
|
extract zstd-1.5.7.tar.gz
|
||||||
|
|
||||||
|
install -d -m 0755 \
|
||||||
|
"$sysroot/usr/include" \
|
||||||
|
"$sysroot/usr/lib" \
|
||||||
|
"$sysroot/usr/lib/pkgconfig"
|
||||||
|
|
||||||
|
make -s -C "$source_build_dir/zstd-1.5.7/lib" -j"$jobs" \
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
libzstd.a
|
||||||
|
make -s -C "$source_build_dir/zstd-1.5.7/lib" \
|
||||||
|
PREFIX=/usr \
|
||||||
|
LIBDIR=/usr/lib \
|
||||||
|
PKGCONFIGDIR=/usr/lib/pkgconfig \
|
||||||
|
DESTDIR="$sysroot" \
|
||||||
|
install-static install-includes install-pc
|
||||||
|
|
||||||
|
make -s -C "$source_build_dir/zstd-1.5.7/programs" -j"$jobs" \
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
ZSTD_LEGACY_SUPPORT=0 \
|
||||||
|
ZSTD_MULTITHREAD=0 \
|
||||||
|
zstd-release
|
||||||
|
install -m 0755 \
|
||||||
|
"$source_build_dir/zstd-1.5.7/programs/zstd" \
|
||||||
|
"$base_root/usr/bin/zstd"
|
||||||
|
ln -s zstd "$base_root/usr/bin/unzstd"
|
||||||
|
ln -s zstd "$base_root/usr/bin/zstdcat"
|
||||||
|
install -d -m 0755 "$base_root/usr/share/man/man1"
|
||||||
|
install -m 0644 \
|
||||||
|
"$source_build_dir/zstd-1.5.7/programs/zstd.1" \
|
||||||
|
"$base_root/usr/share/man/man1/zstd.1"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/util-linux-2.42.2"
|
||||||
|
CC="$cc" \
|
||||||
|
CXX="$cxx" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-all-programs \
|
||||||
|
--enable-libblkid \
|
||||||
|
--enable-libuuid \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static
|
||||||
|
make -s -j"$jobs"
|
||||||
|
make -s DESTDIR="$sysroot" install
|
||||||
|
)
|
||||||
|
|
||||||
|
btrfs_source="$source_build_dir/btrfs-progs-v7.0"
|
||||||
|
pkgconfig_path="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig"
|
||||||
|
compiler_rt="$sysroot/usr/lib/clang/22/lib/x86_64-unknown-linux-musl/libclang_rt.builtins.a"
|
||||||
|
(
|
||||||
|
cd "$btrfs_source"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
CFLAGS="-O2" \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
ac_cv_func_malloc_0_nonnull=yes \
|
||||||
|
ac_cv_func_realloc_0_nonnull=yes \
|
||||||
|
PKG_CONFIG="$tools_dir/bin/pkgconf" \
|
||||||
|
PKG_CONFIG_ALL_STATIC=1 \
|
||||||
|
PKG_CONFIG_LIBDIR="$pkgconfig_path" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-backtrace \
|
||||||
|
--disable-documentation \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--disable-convert \
|
||||||
|
--disable-lzo \
|
||||||
|
--disable-libudev \
|
||||||
|
--disable-python \
|
||||||
|
--disable-zoned \
|
||||||
|
--with-crypto=builtin
|
||||||
|
MOUSE_CROSS_EXTRA_STATIC_LIBS="$compiler_rt" \
|
||||||
|
make -s -j"$jobs" btrfs.static mkfs.btrfs.static
|
||||||
|
"$strip" btrfs.static mkfs.btrfs.static
|
||||||
|
install -m 0755 btrfs.static "$base_root/sbin/btrfs"
|
||||||
|
install -m 0755 mkfs.btrfs.static "$base_root/sbin/mkfs.btrfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
ln -s btrfs "$base_root/sbin/btrfsck"
|
||||||
|
|
||||||
|
"$tools_dir/mandoc-host/makewhatis" "$base_root/usr/share/man"
|
||||||
@@ -0,0 +1,489 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
component_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$component_dir/common.sh"
|
||||||
|
|
||||||
|
patch -d "$source_build_dir/tcsh-TCSH6_24_16" -p1 \
|
||||||
|
<"$repo_dir/base/patches/tcsh-gethost-native.patch"
|
||||||
|
patch -d "$source_build_dir/chimerautils-15.0.3" -p1 \
|
||||||
|
<"$repo_dir/base/patches/chimerautils-native-cdefs.patch"
|
||||||
|
patch -d "$source_build_dir/chimerautils-15.0.3" -p1 \
|
||||||
|
<"$repo_dir/base/patches/chimerautils-scanflike.patch"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/tcsh-TCSH6_24_16"
|
||||||
|
CC="$cc" \
|
||||||
|
LIBS="-lcurses -lterminfo" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--bindir=/bin \
|
||||||
|
--disable-nls-catalogs
|
||||||
|
make -j"$jobs"
|
||||||
|
install -m 0755 tcsh "$base_root/bin/tcsh"
|
||||||
|
)
|
||||||
|
|
||||||
|
util_linux_build="$source_build_dir/util-linux-build"
|
||||||
|
install -d -m 0755 "$util_linux_build"
|
||||||
|
(
|
||||||
|
cd "$util_linux_build"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
CFLAGS="-O2" \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
PKG_CONFIG="$tools_dir/bin/pkgconf" \
|
||||||
|
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
"$source_build_dir/util-linux-2.42.2/configure" \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--disable-nls \
|
||||||
|
--disable-all-programs \
|
||||||
|
--enable-libuuid \
|
||||||
|
--enable-libblkid \
|
||||||
|
--enable-libmount \
|
||||||
|
--enable-libsmartcols \
|
||||||
|
--enable-libfdisk \
|
||||||
|
--enable-agetty \
|
||||||
|
--enable-nologin \
|
||||||
|
--enable-setsid \
|
||||||
|
--enable-mount \
|
||||||
|
--enable-blkid \
|
||||||
|
--enable-fdisks \
|
||||||
|
--enable-losetup \
|
||||||
|
--enable-wipefs \
|
||||||
|
--enable-swapon \
|
||||||
|
--enable-lsblk \
|
||||||
|
--enable-findmnt \
|
||||||
|
--enable-flock \
|
||||||
|
--enable-dmesg \
|
||||||
|
--enable-static-programs=blkid,fdisk,losetup,mount,sfdisk,umount
|
||||||
|
make -s -j"$jobs" \
|
||||||
|
agetty nologin setsid mount umount blkid fdisk sfdisk losetup \
|
||||||
|
wipefs swapon swapoff lsblk findmnt flock dmesg
|
||||||
|
)
|
||||||
|
install_util_linux() {
|
||||||
|
command=$1
|
||||||
|
destination=$2
|
||||||
|
binary=$(find "$util_linux_build" -type f -name "$command" -perm -0100 -print | head -1)
|
||||||
|
if [ -z "$binary" ]; then
|
||||||
|
printf '%s\n' "missing util-linux build output: $command" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
install -m 0755 "$binary" "$destination"
|
||||||
|
}
|
||||||
|
install_util_linux agetty "$base_root/sbin/agetty"
|
||||||
|
install_util_linux nologin "$base_root/sbin/nologin"
|
||||||
|
install_util_linux setsid "$base_root/usr/bin/setsid"
|
||||||
|
install_util_linux mount "$base_root/sbin/mount"
|
||||||
|
install_util_linux umount "$base_root/sbin/umount"
|
||||||
|
install_util_linux blkid "$base_root/sbin/blkid"
|
||||||
|
install_util_linux fdisk "$base_root/sbin/fdisk"
|
||||||
|
install_util_linux sfdisk "$base_root/sbin/sfdisk"
|
||||||
|
install_util_linux losetup "$base_root/sbin/losetup"
|
||||||
|
install_util_linux wipefs "$base_root/sbin/wipefs"
|
||||||
|
install_util_linux swapon "$base_root/sbin/swapon"
|
||||||
|
install_util_linux swapoff "$base_root/sbin/swapoff"
|
||||||
|
install_util_linux lsblk "$base_root/usr/bin/lsblk"
|
||||||
|
install_util_linux findmnt "$base_root/usr/bin/findmnt"
|
||||||
|
install_util_linux flock "$base_root/usr/bin/flock"
|
||||||
|
install_util_linux dmesg "$base_root/sbin/dmesg"
|
||||||
|
|
||||||
|
chimerautils_build="$source_build_dir/chimerautils-15.0.3/build"
|
||||||
|
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
python3 "$meson_source" setup \
|
||||||
|
"$chimerautils_build" \
|
||||||
|
"$source_build_dir/chimerautils-15.0.3" \
|
||||||
|
--cross-file="$cross_file" \
|
||||||
|
--prefix=/usr \
|
||||||
|
--buildtype=release \
|
||||||
|
-Dopenssl=enabled \
|
||||||
|
-Dlibedit=disabled \
|
||||||
|
-Dzlib=disabled \
|
||||||
|
-Dlzma=disabled \
|
||||||
|
-Dbzip2=disabled \
|
||||||
|
-Dzstd=disabled \
|
||||||
|
-Dpam=disabled \
|
||||||
|
-Dselinux=disabled \
|
||||||
|
-Dchimera_realpath=disabled
|
||||||
|
|
||||||
|
commands="
|
||||||
|
awk basename cat chmod chown chroot cksum cmp col colrm column comm
|
||||||
|
cp csplit cut date dd df diff diff3 dirname du echo env expand expr false
|
||||||
|
ed fetch find fmt fold gencat getopt grep head hexdump hostname id join kill
|
||||||
|
ln logger logname ls m4 md5 mesg mkdir mkfifo mknod mktemp mv nc nice nl nohup
|
||||||
|
nproc paste patch pathchk pr printenv printf pwd realpath renice rev rm rmdir script
|
||||||
|
sdiff sed seq sh sleep sort split stat stty sync tail tee time timeout touch tr true
|
||||||
|
truncate tsort tty ul uname unexpand uniq users vis wc whereis which who xargs yes
|
||||||
|
vi
|
||||||
|
"
|
||||||
|
|
||||||
|
targets="
|
||||||
|
src.freebsd/awk/awk
|
||||||
|
src.freebsd/coreutils/basename/basename
|
||||||
|
src.freebsd/coreutils/cat/cat
|
||||||
|
src.freebsd/coreutils/chmod/chmod
|
||||||
|
src.freebsd/coreutils/chown/chown
|
||||||
|
src.freebsd/coreutils/chroot/chroot
|
||||||
|
src.freebsd/coreutils/cksum/cksum
|
||||||
|
src.freebsd/diffutils/cmp/cmp
|
||||||
|
src.freebsd/miscutils/col/col
|
||||||
|
src.freebsd/miscutils/colrm/colrm
|
||||||
|
src.freebsd/miscutils/column/column
|
||||||
|
src.freebsd/coreutils/comm/comm
|
||||||
|
src.freebsd/coreutils/cp/cp
|
||||||
|
src.freebsd/coreutils/csplit/csplit
|
||||||
|
src.freebsd/coreutils/cut/cut
|
||||||
|
src.freebsd/coreutils/date/date
|
||||||
|
src.freebsd/coreutils/dd/dd
|
||||||
|
src.freebsd/coreutils/df/df
|
||||||
|
src.freebsd/diffutils/diff/diff
|
||||||
|
src.freebsd/diffutils/diff3/diff3
|
||||||
|
src.freebsd/coreutils/dirname/dirname
|
||||||
|
src.freebsd/coreutils/du/du
|
||||||
|
src.freebsd/ed/ed
|
||||||
|
src.freebsd/coreutils/echo/echo
|
||||||
|
src.freebsd/coreutils/env/env
|
||||||
|
src.freebsd/coreutils/expand/expand
|
||||||
|
src.freebsd/coreutils/expr/expr
|
||||||
|
src.freebsd/coreutils/false/false
|
||||||
|
src.freebsd/fetch/fetch
|
||||||
|
src.freebsd/findutils/find/find
|
||||||
|
src.freebsd/coreutils/fmt/fmt
|
||||||
|
src.freebsd/coreutils/fold/fold
|
||||||
|
src.freebsd/gencat/gencat
|
||||||
|
src.freebsd/miscutils/getopt/getopt
|
||||||
|
src.freebsd/grep/grep
|
||||||
|
src.freebsd/coreutils/head/head
|
||||||
|
src.freebsd/miscutils/hexdump/hexdump
|
||||||
|
src.freebsd/coreutils/hostname/hostname
|
||||||
|
src.freebsd/coreutils/id/id
|
||||||
|
src.freebsd/coreutils/join/join
|
||||||
|
src.freebsd/miscutils/kill/kill
|
||||||
|
src.freebsd/coreutils/ln/ln
|
||||||
|
src.freebsd/miscutils/logger/logger
|
||||||
|
src.freebsd/coreutils/logname/logname
|
||||||
|
src.freebsd/coreutils/ls/ls
|
||||||
|
src.freebsd/m4/m4
|
||||||
|
src.freebsd/coreutils/md5/md5
|
||||||
|
src.freebsd/miscutils/mesg/mesg
|
||||||
|
src.freebsd/coreutils/mkdir/mkdir
|
||||||
|
src.freebsd/coreutils/mkfifo/mkfifo
|
||||||
|
src.freebsd/coreutils/mknod/mknod
|
||||||
|
src.freebsd/coreutils/mktemp/mktemp
|
||||||
|
src.freebsd/coreutils/mv/mv
|
||||||
|
src.freebsd/netcat/nc
|
||||||
|
src.freebsd/coreutils/nice/nice
|
||||||
|
src.freebsd/coreutils/nl/nl
|
||||||
|
src.freebsd/coreutils/nohup/nohup
|
||||||
|
src.freebsd/coreutils/nproc/nproc
|
||||||
|
src.freebsd/nvi/vi
|
||||||
|
src.freebsd/coreutils/paste/paste
|
||||||
|
src.freebsd/patch/patch
|
||||||
|
src.freebsd/coreutils/pathchk/pathchk
|
||||||
|
src.freebsd/coreutils/pr/pr
|
||||||
|
src.freebsd/coreutils/printenv/printenv
|
||||||
|
src.freebsd/coreutils/printf/printf
|
||||||
|
src.freebsd/coreutils/pwd/pwd
|
||||||
|
src.freebsd/coreutils/realpath/realpath
|
||||||
|
src.freebsd/miscutils/renice/renice
|
||||||
|
src.freebsd/miscutils/rev/rev
|
||||||
|
src.freebsd/coreutils/rm/rm
|
||||||
|
src.freebsd/coreutils/rmdir/rmdir
|
||||||
|
src.freebsd/miscutils/script/script
|
||||||
|
src.freebsd/diffutils/sdiff/sdiff
|
||||||
|
src.freebsd/sed/sed
|
||||||
|
src.freebsd/coreutils/seq/seq
|
||||||
|
src.freebsd/sh/sh
|
||||||
|
src.freebsd/coreutils/sleep/sleep
|
||||||
|
src.freebsd/coreutils/sort/sort
|
||||||
|
src.freebsd/coreutils/split/split
|
||||||
|
src.freebsd/coreutils/stat/stat
|
||||||
|
src.freebsd/coreutils/stty/stty
|
||||||
|
src.freebsd/coreutils/sync/sync
|
||||||
|
src.freebsd/coreutils/tail/tail
|
||||||
|
src.freebsd/coreutils/tee/tee
|
||||||
|
src.freebsd/miscutils/time/time
|
||||||
|
src.freebsd/coreutils/timeout/timeout
|
||||||
|
src.freebsd/coreutils/touch/touch
|
||||||
|
src.freebsd/coreutils/tr/tr
|
||||||
|
src.freebsd/coreutils/true/true
|
||||||
|
src.freebsd/coreutils/truncate/truncate
|
||||||
|
src.freebsd/coreutils/tsort/tsort
|
||||||
|
src.freebsd/coreutils/tty/tty
|
||||||
|
src.freebsd/miscutils/ul/ul
|
||||||
|
src.freebsd/coreutils/uname/uname
|
||||||
|
src.freebsd/coreutils/unexpand/unexpand
|
||||||
|
src.freebsd/coreutils/uniq/uniq
|
||||||
|
src.freebsd/coreutils/users/users
|
||||||
|
src.freebsd/vis/vis
|
||||||
|
src.freebsd/coreutils/wc/wc
|
||||||
|
src.freebsd/miscutils/whereis/whereis
|
||||||
|
src.freebsd/which/which
|
||||||
|
src.freebsd/coreutils/who/who
|
||||||
|
src.freebsd/findutils/xargs/xargs
|
||||||
|
src.freebsd/coreutils/yes/yes
|
||||||
|
src.freebsd/coreutils/xinstall/xinstall
|
||||||
|
src.freebsd/coreutils/test/xtest
|
||||||
|
"
|
||||||
|
|
||||||
|
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
python3 "$meson_source" compile -C "$chimerautils_build" $targets
|
||||||
|
|
||||||
|
for command in $commands; do
|
||||||
|
binary=$(find "$chimerautils_build" -type f -name "$command" -perm -0100 -print | head -1)
|
||||||
|
if [ -z "$binary" ]; then
|
||||||
|
printf '%s\n' "missing ChimeraUtils build output: $command" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
destination="$base_root/usr/bin"
|
||||||
|
case "$command" in
|
||||||
|
cat|chmod|chown|cp|date|dd|df|echo|hostname|kill|ln|ls|mkdir|mv|pwd|rm|rmdir|sed|sh|sleep|stty|sync|touch|uname)
|
||||||
|
destination="$base_root/bin"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
install -m 0755 "$binary" "$destination/$command"
|
||||||
|
done
|
||||||
|
|
||||||
|
ln -s chown "$base_root/bin/chgrp"
|
||||||
|
ln -s vi "$base_root/usr/bin/nvi"
|
||||||
|
ln -s vi "$base_root/usr/bin/view"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/procps-ng-4.0.5"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
ac_cv_func_malloc_0_nonnull=yes \
|
||||||
|
ac_cv_func_realloc_0_nonnull=yes \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
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-nls \
|
||||||
|
--disable-pidof \
|
||||||
|
--disable-pidwait \
|
||||||
|
--disable-kill \
|
||||||
|
--disable-w \
|
||||||
|
--disable-numa \
|
||||||
|
--without-systemd \
|
||||||
|
--without-elogind
|
||||||
|
make -s -j"$jobs" \
|
||||||
|
src/ps/pscommand \
|
||||||
|
src/pgrep \
|
||||||
|
src/pkill \
|
||||||
|
src/top/top \
|
||||||
|
src/free \
|
||||||
|
src/uptime \
|
||||||
|
src/vmstat \
|
||||||
|
src/watch \
|
||||||
|
src/sysctl
|
||||||
|
install -m 0755 src/ps/pscommand "$base_root/bin/ps"
|
||||||
|
install -m 0755 src/pgrep "$base_root/usr/bin/pgrep"
|
||||||
|
install -m 0755 src/pkill "$base_root/usr/bin/pkill"
|
||||||
|
install -m 0755 src/top/top "$base_root/usr/bin/top"
|
||||||
|
install -m 0755 src/free "$base_root/usr/bin/free"
|
||||||
|
install -m 0755 src/uptime "$base_root/usr/bin/uptime"
|
||||||
|
install -m 0755 src/vmstat "$base_root/usr/bin/vmstat"
|
||||||
|
install -m 0755 src/watch "$base_root/usr/bin/watch"
|
||||||
|
install -m 0755 src/sysctl "$base_root/sbin/sysctl"
|
||||||
|
)
|
||||||
|
|
||||||
|
iputils_build="$source_build_dir/iputils-20250605/build"
|
||||||
|
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
python3 "$meson_source" setup \
|
||||||
|
"$iputils_build" \
|
||||||
|
"$source_build_dir/iputils-20250605" \
|
||||||
|
--cross-file="$cross_file" \
|
||||||
|
--prefix=/usr \
|
||||||
|
--buildtype=release \
|
||||||
|
--default-library=static \
|
||||||
|
-DUSE_CAP=false \
|
||||||
|
-DUSE_IDN=false \
|
||||||
|
-DUSE_GETTEXT=false \
|
||||||
|
-DBUILD_ARPING=false \
|
||||||
|
-DBUILD_CLOCKDIFF=false \
|
||||||
|
-DBUILD_TRACEPATH=false \
|
||||||
|
-DBUILD_MANS=false \
|
||||||
|
-DSKIP_TESTS=true
|
||||||
|
python3 "$meson_source" compile -C "$iputils_build" ping
|
||||||
|
install -m 4755 "$iputils_build/ping/ping" "$base_root/bin/ping"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/cronie-1.7.2"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--sysconfdir=/etc \
|
||||||
|
--localstatedir=/var \
|
||||||
|
--runstatedir=/run \
|
||||||
|
--disable-anacron \
|
||||||
|
--without-pam \
|
||||||
|
--without-selinux \
|
||||||
|
--without-audit
|
||||||
|
make -s -j"$jobs"
|
||||||
|
install -m 0755 src/crond "$base_root/usr/sbin/cron"
|
||||||
|
install -m 4755 src/crontab "$base_root/usr/bin/crontab"
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/openntpd-7.9p1"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
YACC=byacc \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--sysconfdir=/etc \
|
||||||
|
--localstatedir=/var \
|
||||||
|
--runstatedir=/run \
|
||||||
|
--with-privsep-user=_ntp \
|
||||||
|
--disable-https-constraint \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static
|
||||||
|
make -s -j"$jobs" -C compat
|
||||||
|
make -s -j"$jobs" -C src ntpd
|
||||||
|
install -m 0755 src/ntpd "$base_root/usr/sbin/ntpd"
|
||||||
|
ln -s ntpd "$base_root/usr/sbin/ntpctl"
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/sysklogd-2.7.2"
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
LDFLAGS="-static" \
|
||||||
|
PKG_CONFIG="$tools_dir/bin/pkgconf" \
|
||||||
|
./configure \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--host=x86_64-linux-musl \
|
||||||
|
--prefix=/usr \
|
||||||
|
--sysconfdir=/etc \
|
||||||
|
--runstatedir=/run \
|
||||||
|
--disable-shared \
|
||||||
|
--enable-static \
|
||||||
|
--without-logger \
|
||||||
|
--with-systemd=no
|
||||||
|
make -s -j"$jobs" -C src syslogd
|
||||||
|
install -m 0755 src/syslogd "$base_root/usr/sbin/syslogd"
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/skalibs-2.15.1.0"
|
||||||
|
CC="$cc" AR="$ar" RANLIB="$ranlib" ./configure \
|
||||||
|
--prefix=/usr \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--target=x86_64-linux-musl \
|
||||||
|
--with-include="$sysroot/usr/include" \
|
||||||
|
--with-lib="$sysroot/usr/lib" \
|
||||||
|
--with-sysdep-devurandom=yes \
|
||||||
|
--with-sysdep-posixspawnearlyreturn=no \
|
||||||
|
--with-sysdep-procselfexe=/proc/self/exe \
|
||||||
|
--with-sysdep-selectinfinite=yes \
|
||||||
|
--disable-shared
|
||||||
|
make -s -j"$jobs" CC="$cc" AR="$ar" RANLIB="$ranlib"
|
||||||
|
make -s DESTDIR="$sysroot" install
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/mdevd-0.1.8.2"
|
||||||
|
CC="$cc" AR="$ar" RANLIB="$ranlib" ./configure \
|
||||||
|
--prefix=/usr \
|
||||||
|
--build="$build_triplet" \
|
||||||
|
--target=x86_64-linux-musl \
|
||||||
|
--with-sysdeps="$sysroot/usr/lib/skalibs/sysdeps" \
|
||||||
|
--with-include="$sysroot/usr/include" \
|
||||||
|
--with-lib="$sysroot/usr/lib" \
|
||||||
|
--enable-static-libc \
|
||||||
|
--disable-shared
|
||||||
|
make -s -j"$jobs" CC="$cc" AR="$ar" RANLIB="$ranlib"
|
||||||
|
install -m 0755 mdevd "$base_root/sbin/mdevd"
|
||||||
|
install -m 0755 mdevd-coldplug "$base_root/sbin/mdevd-coldplug"
|
||||||
|
)
|
||||||
|
|
||||||
|
kmod_build="$source_build_dir/kmod-34.2/build"
|
||||||
|
PKG_CONFIG_LIBDIR="$sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig" \
|
||||||
|
PKG_CONFIG_SYSROOT_DIR="$sysroot" \
|
||||||
|
python3 "$meson_source" setup \
|
||||||
|
"$kmod_build" \
|
||||||
|
"$source_build_dir/kmod-34.2" \
|
||||||
|
--cross-file="$cross_file" \
|
||||||
|
--prefix=/usr \
|
||||||
|
--buildtype=release \
|
||||||
|
--default-library=static \
|
||||||
|
-Dzstd=disabled \
|
||||||
|
-Dxz=disabled \
|
||||||
|
-Dzlib=disabled \
|
||||||
|
-Dopenssl=disabled \
|
||||||
|
-Dmanpages=false \
|
||||||
|
-Ddocs=false \
|
||||||
|
-Dbuild-tests=false
|
||||||
|
python3 "$meson_source" compile -C "$kmod_build" kmod:executable
|
||||||
|
install -m 0755 "$kmod_build/kmod" "$base_root/sbin/kmod"
|
||||||
|
for command in depmod insmod lsmod modinfo modprobe rmmod; do
|
||||||
|
ln -s kmod "$base_root/sbin/$command"
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$source_build_dir/openssl-3.5.7"
|
||||||
|
make -s distclean
|
||||||
|
CC="$cc" \
|
||||||
|
AR="$ar" \
|
||||||
|
RANLIB="$ranlib" \
|
||||||
|
./Configure \
|
||||||
|
linux-x86_64 \
|
||||||
|
--prefix=/usr \
|
||||||
|
--libdir=lib \
|
||||||
|
--openssldir=/etc/ssl \
|
||||||
|
no-afalgeng \
|
||||||
|
no-docs \
|
||||||
|
no-dso \
|
||||||
|
no-module \
|
||||||
|
no-secure-memory \
|
||||||
|
no-shared \
|
||||||
|
no-tests
|
||||||
|
make -s -j"$jobs" build_generated
|
||||||
|
make -s depend
|
||||||
|
make -s -j"$jobs" apps/openssl
|
||||||
|
install -m 0755 apps/openssl "$base_root/usr/bin/openssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
install -m 0755 \
|
||||||
|
"$chimerautils_build/src.freebsd/coreutils/xinstall/xinstall" \
|
||||||
|
"$base_root/usr/bin/install"
|
||||||
|
install -m 0755 \
|
||||||
|
"$chimerautils_build/src.freebsd/coreutils/test/xtest" \
|
||||||
|
"$base_root/usr/bin/test"
|
||||||
|
|
||||||
|
ln -s test "$base_root/usr/bin/["
|
||||||
|
ln -s install "$base_root/usr/bin/binstall"
|
||||||
|
ln -s id "$base_root/usr/bin/groups"
|
||||||
|
ln -s id "$base_root/usr/bin/whoami"
|
||||||
|
ln -s md5 "$base_root/usr/bin/sha256"
|
||||||
|
ln -s stat "$base_root/usr/bin/readlink"
|
||||||
|
|
||||||
|
install -m 0755 "$sysroot/usr/bin/tput" "$base_root/usr/bin/tput"
|
||||||
|
ln -s tcsh "$base_root/bin/csh"
|
||||||
Executable
+61
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/expect -f
|
||||||
|
|
||||||
|
if {$argc != 1} {
|
||||||
|
puts stderr "usage: boot.exp RUNNER"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
set runner [file normalize [lindex $argv 0]]
|
||||||
|
set timeout 30
|
||||||
|
set acceptance_name "boot"
|
||||||
|
source [file join [file dirname [info script]] harness.exp]
|
||||||
|
|
||||||
|
proc boot_mouse {runner} {
|
||||||
|
global spawn_id
|
||||||
|
spawn -noecho $runner
|
||||||
|
await_exact "Cheesed to meet you! v0.1.0" "the cheesed startup banner"
|
||||||
|
await_exact "cheesed: base bootstrap complete; cheesed is PID 1" "PID 1 confirmation"
|
||||||
|
await_exact "MOUSE 0.1.0 (x86_64)" "the MOUSE login banner"
|
||||||
|
await_exact "mouse:~# " "the root shell prompt"
|
||||||
|
}
|
||||||
|
|
||||||
|
boot_mouse $runner
|
||||||
|
|
||||||
|
send -- "hostname\r"
|
||||||
|
await_exact "mouse\r" "the configured hostname"
|
||||||
|
await_exact "mouse:~# " "the prompt after hostname"
|
||||||
|
|
||||||
|
send -- "cat /proc/1/comm\r"
|
||||||
|
await_exact "cheesed\r" "cheesed in /proc/1"
|
||||||
|
await_exact "mouse:~# " "the prompt after the PID 1 check"
|
||||||
|
|
||||||
|
send -- "sh -c 'test -c /dev/null'; echo DEV_STATUS_\$?\r"
|
||||||
|
await_exact "DEV_STATUS_0\r" "a mounted devtmpfs"
|
||||||
|
await_exact "mouse:~# " "the prompt after the devtmpfs check"
|
||||||
|
|
||||||
|
send -- "clear; echo CLEAR_STATUS_\$?\r"
|
||||||
|
await_exact "CLEAR_STATUS_0\r" "a successful clear command"
|
||||||
|
await_exact "mouse:~# " "the prompt after clear"
|
||||||
|
|
||||||
|
send -- {sh -c 'i=0; while [ "$i" -lt 8 ]; do (sleep 1) & i=$((i + 1)); done'}
|
||||||
|
send -- "\r"
|
||||||
|
await_exact "mouse:~# " "the prompt after starting orphan probes"
|
||||||
|
await_exact "cheesed: reaped orphan PID " "PID 1 orphan reaping"
|
||||||
|
|
||||||
|
send -- "sleep 2\r"
|
||||||
|
await_exact "mouse:~# " "all orphan probes to exit"
|
||||||
|
send -- {sh -c 'if grep -l ") Z " /proc/[0-9]*/stat >/dev/null; then hostname; else cat /proc/1/comm; fi'}
|
||||||
|
send -- "\r"
|
||||||
|
await_exact "cheesed\r" "a zombie-free process table"
|
||||||
|
await_exact "mouse:~# " "the prompt after the zombie check"
|
||||||
|
|
||||||
|
send -- "kill -TERM 1\r"
|
||||||
|
await_exact "cheesed: received poweroff request" "the poweroff request"
|
||||||
|
await_clean_exit "poweroff"
|
||||||
|
|
||||||
|
boot_mouse $runner
|
||||||
|
send -- "kill -INT 1\r"
|
||||||
|
await_exact "cheesed: received reboot request" "the reboot request"
|
||||||
|
await_clean_exit "reboot"
|
||||||
|
|
||||||
|
puts "boot acceptance tests passed"
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$acceptance_dir/harness.sh"
|
||||||
|
acceptance_init
|
||||||
|
acceptance_require_commands "the boot acceptance test" expect
|
||||||
|
acceptance_copy_disk boot
|
||||||
|
|
||||||
|
MOUSE_KERNEL_ARGS="console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed mouse.autologin=1 panic=-1 quiet loglevel=3"
|
||||||
|
MOUSE_BOOT_MODE=disk
|
||||||
|
MOUSE_DISK_IMAGE="$test_disk"
|
||||||
|
MOUSE_NETWORK=0
|
||||||
|
export MOUSE_KERNEL_ARGS
|
||||||
|
export MOUSE_BOOT_MODE
|
||||||
|
export MOUSE_DISK_IMAGE
|
||||||
|
export MOUSE_NETWORK
|
||||||
|
|
||||||
|
exec expect "$acceptance_dir/boot.exp" "$runner"
|
||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/expect -f
|
||||||
|
|
||||||
|
if {$argc != 3} {
|
||||||
|
puts stderr "usage: btrfs.exp RUNNER DISK MODE"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
set runner [file normalize [lindex $argv 0]]
|
||||||
|
set disk [file normalize [lindex $argv 1]]
|
||||||
|
set mode [lindex $argv 2]
|
||||||
|
if {$mode ni {first alternate rollback powerloss-stage powerloss-verify}} {
|
||||||
|
puts stderr "invalid btrfs mode: $mode"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
set timeout 180
|
||||||
|
set acceptance_name "btrfs"
|
||||||
|
source [file join [file dirname [info script]] harness.exp]
|
||||||
|
|
||||||
|
acceptance_log
|
||||||
|
set env(MOUSE_DISK_IMAGE) $disk
|
||||||
|
set env(MOUSE_MEMORY) 1G
|
||||||
|
|
||||||
|
set env(MOUSE_BOOT_MODE) disk
|
||||||
|
set env(MOUSE_KERNEL_ARGS) "console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed mouse.autologin=1 panic=-1"
|
||||||
|
spawn -noecho $runner
|
||||||
|
|
||||||
|
await_exact "cheesed: base bootstrap complete; cheesed is PID 1" "PID 1 confirmation"
|
||||||
|
await_exact "mouse login: root (automatic login)" "the serial login"
|
||||||
|
await_prompt
|
||||||
|
sendline {sh -c "grep -Eq '^[^ ]+ / btrfs ' /proc/mounts && grep -Eq '^[^ ]+ /usr/local btrfs ' /proc/mounts && grep -Eq '^[^ ]+ /var btrfs ' /proc/mounts && grep -Eq '^[^ ]+ /home btrfs ' /proc/mounts && grep -Eq '^[^ ]+ /srv btrfs ' /proc/mounts"; echo MOUNTS_$?}
|
||||||
|
await_exact "MOUNTS_0\r" "the btrfs root and persistent subvolume mounts"
|
||||||
|
await_exact "mouse:~# " "the prompt after mount verification"
|
||||||
|
sendline {btrfs version; echo VERSION_$?}
|
||||||
|
await_exact "btrfs-progs v7.0\r" "the pinned btrfs userspace version"
|
||||||
|
await_exact "VERSION_0\r" "the btrfs version status"
|
||||||
|
await_exact "mouse:~# " "the prompt after version"
|
||||||
|
|
||||||
|
if {$mode in {first powerloss-stage}} {
|
||||||
|
sendline {sh -c 'test "$(cat /etc/mouse-boot-environment)" = default && mkdir -p /var/lib/mouse /home/mouse-state /usr/local/share/mouse /srv/mouse && echo base-default >/etc/bootenv-state && echo var-shared >/var/lib/mouse/state && echo home-shared >/home/mouse-state/value && echo local-shared >/usr/local/share/mouse/state && echo srv-shared >/srv/mouse/state'; echo WRITE_$?}
|
||||||
|
await_exact "WRITE_0\r" "boot-environment and persistent state creation"
|
||||||
|
await_exact "mouse:~# " "the prompt after state creation"
|
||||||
|
sendline {mouse-bootenv prepare; echo PREPARE_$?}
|
||||||
|
await_exact "alternate is ready for an update\r" "the inactive snapshot preparation"
|
||||||
|
await_exact "PREPARE_0\r" "the preparation status"
|
||||||
|
await_exact "mouse:~# " "the prompt after preparation"
|
||||||
|
sendline {sh -c 'echo alternate-updated >/run/mouse-root/ROOT/alternate/etc/bootenv-state'; mouse-bootenv activate alternate; echo ACTIVATE_$?}
|
||||||
|
await_exact "alternate will boot next\r" "the alternate activation"
|
||||||
|
await_exact "ACTIVATE_0\r" "the activation status"
|
||||||
|
if {$mode eq "powerloss-stage"} {
|
||||||
|
exec kill -KILL [exp_pid]
|
||||||
|
expect eof
|
||||||
|
catch wait
|
||||||
|
puts "power-loss interruption staged"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
} elseif {$mode in {alternate powerloss-verify}} {
|
||||||
|
sendline {sh -c 'test "$(cat /etc/mouse-boot-environment)" = alternate && test "$(cat /etc/bootenv-state)" = alternate-updated && test "$(cat /var/lib/mouse/state)" = var-shared && test "$(cat /home/mouse-state/value)" = home-shared && test "$(cat /usr/local/share/mouse/state)" = local-shared && test "$(cat /srv/mouse/state)" = srv-shared'; echo ALTERNATE_$?}
|
||||||
|
await_exact "ALTERNATE_0\r" "the alternate root and shared persistent state"
|
||||||
|
if {$mode eq "alternate"} {
|
||||||
|
await_exact "mouse:~# " "the prompt after alternate verification"
|
||||||
|
sendline {mouse-bootenv rollback; echo ROLLBACK_$?}
|
||||||
|
await_exact "default will boot next\r" "the default rollback activation"
|
||||||
|
await_exact "ROLLBACK_0\r" "the rollback status"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendline {sh -c 'test "$(cat /etc/mouse-boot-environment)" = default && test "$(cat /etc/bootenv-state)" = base-default && test "$(cat /var/lib/mouse/state)" = var-shared && test "$(cat /home/mouse-state/value)" = home-shared && test "$(cat /usr/local/share/mouse/state)" = local-shared && test "$(cat /srv/mouse/state)" = srv-shared'; echo DEFAULT_$?}
|
||||||
|
await_exact "DEFAULT_0\r" "the rolled-back root and shared persistent state"
|
||||||
|
}
|
||||||
|
await_exact "mouse:~# " "the prompt before poweroff"
|
||||||
|
poweroff
|
||||||
|
puts "$mode btrfs acceptance passed"
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$acceptance_dir/harness.sh"
|
||||||
|
acceptance_init
|
||||||
|
acceptance_require_commands "the btrfs acceptance test" expect
|
||||||
|
acceptance_copy_disk btrfs
|
||||||
|
|
||||||
|
"$acceptance_dir/btrfs.exp" "$runner" "$test_disk" first
|
||||||
|
"$acceptance_dir/btrfs.exp" "$runner" "$test_disk" alternate
|
||||||
|
"$acceptance_dir/btrfs.exp" "$runner" "$test_disk" rollback
|
||||||
|
|
||||||
|
powerloss_disk="$temporary/mouse-disk-powerloss.img"
|
||||||
|
acceptance_clone_disk "$build_dir/mouse-disk.img" "$powerloss_disk"
|
||||||
|
"$acceptance_dir/btrfs.exp" "$runner" "$powerloss_disk" powerloss-stage
|
||||||
|
"$acceptance_dir/btrfs.exp" "$runner" "$powerloss_disk" powerloss-verify
|
||||||
|
|
||||||
|
printf '%s\n' \
|
||||||
|
"btrfs root, boot-environment, and power-loss acceptance tests passed"
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/expect -f
|
||||||
|
|
||||||
|
if {$argc != 3} {
|
||||||
|
puts stderr "usage: firmware.exp RUNNER LABEL CHECK"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
set runner [file normalize [lindex $argv 0]]
|
||||||
|
set label [lindex $argv 1]
|
||||||
|
set check [lindex $argv 2]
|
||||||
|
set timeout 90
|
||||||
|
set acceptance_name "firmware-$check"
|
||||||
|
source [file join [file dirname [info script]] harness.exp]
|
||||||
|
|
||||||
|
acceptance_log
|
||||||
|
spawn -noecho $runner
|
||||||
|
await_exact "Cheesed to meet you! v0.1.0" "MOUSE through the Limine firmware path"
|
||||||
|
await_exact "mouse login: " "the authenticated firmware-boot login"
|
||||||
|
sendline {root}
|
||||||
|
await_exact "Password: " "the root password prompt"
|
||||||
|
sendline {cheddar}
|
||||||
|
await_exact "mouse:~# " "the root shell"
|
||||||
|
switch -- $check {
|
||||||
|
raw-virtio {
|
||||||
|
set check_command {[ -e /sys/block/vda ]}
|
||||||
|
}
|
||||||
|
raw-sata {
|
||||||
|
set check_command {[ -e /sys/block/sda ]}
|
||||||
|
}
|
||||||
|
raw-uefi-sata {
|
||||||
|
set check_command {[ -d /sys/firmware/efi ] && [ -e /sys/block/sda ]}
|
||||||
|
}
|
||||||
|
raw-uefi-nvme {
|
||||||
|
set check_command {[ -d /sys/firmware/efi ] && [ -e /sys/block/nvme0n1 ]}
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
puts stderr "unknown firmware check: $check"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendline "sh -c 'grep -q \"root=PARTUUID=4d4f5553-4500-4000-8000-000000000002\" /proc/cmdline && grep -Eq \"^\[^ \]+ / btrfs \" /proc/mounts && $check_command'; echo FIRMWARE_\$?"
|
||||||
|
await_exact "FIRMWARE_0\r" "the GPT, Limine, PARTUUID, and Btrfs boot contract"
|
||||||
|
await_exact "mouse:~# " "the prompt after firmware verification"
|
||||||
|
poweroff
|
||||||
|
|
||||||
|
puts "$label acceptance passed"
|
||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$acceptance_dir/harness.sh"
|
||||||
|
acceptance_init
|
||||||
|
acceptance_require_commands "the firmware acceptance test" expect
|
||||||
|
acceptance_require_file \
|
||||||
|
"$build_dir/mouse-disk.img" \
|
||||||
|
"run 'make check' before checking BIOS firmware"
|
||||||
|
acceptance_require_file \
|
||||||
|
"$build_dir/ovmf-code.fd" \
|
||||||
|
"run 'make check' before checking UEFI firmware"
|
||||||
|
acceptance_make_temporary firmware
|
||||||
|
|
||||||
|
raw_disk="$temporary/mouse-disk.img"
|
||||||
|
acceptance_clone_disk "$build_dir/mouse-disk.img" "$raw_disk"
|
||||||
|
|
||||||
|
run_firmware()
|
||||||
|
{
|
||||||
|
label=$1
|
||||||
|
check=$2
|
||||||
|
disk=$3
|
||||||
|
bus=$4
|
||||||
|
firmware=$5
|
||||||
|
|
||||||
|
MOUSE_BOOT_MODE=firmware \
|
||||||
|
MOUSE_DISK_IMAGE="$disk" \
|
||||||
|
MOUSE_DISK_BUS="$bus" \
|
||||||
|
MOUSE_FIRMWARE="$firmware" \
|
||||||
|
MOUSE_UEFI_CODE="$build_dir/ovmf-code.fd" \
|
||||||
|
MOUSE_MEMORY="${MOUSE_MEMORY:-1G}" \
|
||||||
|
expect "$acceptance_dir/firmware.exp" "$runner" "$label" "$check"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_firmware "BIOS raw VirtIO" raw-virtio \
|
||||||
|
"$raw_disk" virtio bios
|
||||||
|
run_firmware "BIOS raw AHCI" raw-sata \
|
||||||
|
"$raw_disk" sata bios
|
||||||
|
run_firmware "UEFI raw AHCI" raw-uefi-sata \
|
||||||
|
"$raw_disk" sata uefi
|
||||||
|
run_firmware "UEFI raw NVMe" raw-uefi-nvme \
|
||||||
|
"$raw_disk" nvme uefi
|
||||||
|
|
||||||
|
printf '%s\n' "BIOS and UEFI firmware acceptance passed"
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
if {![info exists acceptance_name]} {
|
||||||
|
puts stderr "acceptance harness requires acceptance_name"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
proc fail {message} {
|
||||||
|
global acceptance_name
|
||||||
|
puts stderr "$acceptance_name acceptance 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 {value description} {
|
||||||
|
await_exact $value $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 sendline {line} {
|
||||||
|
send -- $line
|
||||||
|
send -- "\r"
|
||||||
|
}
|
||||||
|
|
||||||
|
proc prompt {} {
|
||||||
|
await_exact "mouse:~# " "the root prompt"
|
||||||
|
}
|
||||||
|
|
||||||
|
proc await_prompt {} {
|
||||||
|
send -- "\r"
|
||||||
|
await_exact "mouse:~# " "the root prompt"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {} {
|
||||||
|
sendline {poweroff}
|
||||||
|
await_exact "cheesed: received poweroff request" "the poweroff request"
|
||||||
|
await_exact "cheesed: root filesystem remounted read-only" "the read-only root remount"
|
||||||
|
await_clean_exit "poweroff"
|
||||||
|
}
|
||||||
|
|
||||||
|
proc acceptance_log {} {
|
||||||
|
global env
|
||||||
|
if {[info exists env(MOUSE_EXPECT_LOG)]} {
|
||||||
|
log_user 1
|
||||||
|
} else {
|
||||||
|
log_user 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
acceptance_init() {
|
||||||
|
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(CDPATH= cd -- "$acceptance_dir/../.." && pwd)
|
||||||
|
runner="$repo_dir/scripts/run-qemu.sh"
|
||||||
|
build_dir=${MOUSE_BUILD_DIR:-"$repo_dir/build"}
|
||||||
|
temporary=
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptance_require_commands() {
|
||||||
|
description=$1
|
||||||
|
shift
|
||||||
|
for command in "$@"; do
|
||||||
|
if ! command -v "$command" >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "$command is required for $description" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptance_require_file() {
|
||||||
|
path=$1
|
||||||
|
instruction=$2
|
||||||
|
if [ ! -f "$path" ]; then
|
||||||
|
printf '%s\n' "$instruction" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptance_cleanup_extra() {
|
||||||
|
:
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptance_cleanup() {
|
||||||
|
acceptance_cleanup_extra
|
||||||
|
if [ -n "$temporary" ] && [ -d "$temporary" ]; then
|
||||||
|
rm -rf "$temporary"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptance_make_temporary() {
|
||||||
|
name=$1
|
||||||
|
temporary=$(mktemp -d "${TMPDIR:-/tmp}/mouse-$name.XXXXXX")
|
||||||
|
trap acceptance_cleanup EXIT HUP INT TERM
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptance_copy_disk() {
|
||||||
|
name=$1
|
||||||
|
description=${2:-$1}
|
||||||
|
base_disk="$build_dir/mouse-disk.img"
|
||||||
|
acceptance_require_file \
|
||||||
|
"$base_disk" \
|
||||||
|
"run 'make check' before checking $description"
|
||||||
|
acceptance_make_temporary "$name"
|
||||||
|
test_disk="$temporary/mouse-disk.img"
|
||||||
|
acceptance_clone_disk "$base_disk" "$test_disk"
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptance_clone_disk() {
|
||||||
|
source=$1
|
||||||
|
destination=$2
|
||||||
|
if [ "$(uname -s)" = Darwin ] && cp -c "$source" "$destination" 2>/dev/null; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
cp "$source" "$destination"
|
||||||
|
}
|
||||||
Executable
+99
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/expect -f
|
||||||
|
|
||||||
|
if {$argc != 3} {
|
||||||
|
puts stderr "usage: multi-user.exp RUNNER DISK MODE"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
set runner [file normalize [lindex $argv 0]]
|
||||||
|
set disk [file normalize [lindex $argv 1]]
|
||||||
|
set mode [lindex $argv 2]
|
||||||
|
if {$mode ni {provision login verify}} {
|
||||||
|
puts stderr "invalid multi-user mode: $mode"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
set timeout 75
|
||||||
|
set acceptance_name "multi-user"
|
||||||
|
source [file join [file dirname [info script]] harness.exp]
|
||||||
|
|
||||||
|
acceptance_log
|
||||||
|
set env(MOUSE_BOOT_MODE) disk
|
||||||
|
set env(MOUSE_DISK_IMAGE) $disk
|
||||||
|
set env(MOUSE_NETWORK) 0
|
||||||
|
if {$mode eq "provision"} {
|
||||||
|
set env(MOUSE_KERNEL_ARGS) "console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed mouse.autologin=1 panic=-1 quiet loglevel=3"
|
||||||
|
} else {
|
||||||
|
set env(MOUSE_KERNEL_ARGS) "console=ttyS0 root=PARTUUID=4d4f5553-4500-4000-8000-000000000002 rootfstype=btrfs rw rootwait init=/sbin/cheesed panic=-1 quiet loglevel=3"
|
||||||
|
}
|
||||||
|
spawn -noecho $runner
|
||||||
|
|
||||||
|
if {$mode eq "provision"} {
|
||||||
|
await "mouse login: root (automatic login)" "the provisioning login"
|
||||||
|
await "mouse:~# " "the provisioning shell"
|
||||||
|
sendline {groupadd -R /run/mouse-root/ROOT/default operators; useradd -R /run/mouse-root/ROOT/default -M -g operators -s /bin/tcsh alice; mkdir /run/mouse-root/home/alice; chown 1000:1000 /run/mouse-root/home/alice; echo CREATE_$status}
|
||||||
|
await "CREATE_0\r" "account creation"
|
||||||
|
await "mouse:~# " "the provisioning prompt"
|
||||||
|
sendline {sh -c 'grep -q "^root:x:0:0:root:/root:/bin/sh$" /run/mouse-root/ROOT/default/etc/passwd && grep -q "^toor:x:0:0:BSD-style root account:/root:/bin/tcsh$" /run/mouse-root/ROOT/default/etc/passwd'; echo ADMIN_$status}
|
||||||
|
await "ADMIN_0\r" "the BSD-style administrator accounts"
|
||||||
|
await "mouse:~# " "the provisioning prompt"
|
||||||
|
sendline {sh -c 'usermod -R /run/mouse-root/ROOT/default -p "$(head -n 1 /run/mouse-root/ROOT/default/etc/shadow | cut -d: -f2)" alice'; echo PASSWORD_$status}
|
||||||
|
await "PASSWORD_0\r" "the alternate-root password setup"
|
||||||
|
await "mouse:~# " "the provisioning prompt"
|
||||||
|
sendline {sh -c 'test "$(grep "^alice:" /run/mouse-root/ROOT/default/etc/shadow | cut -d: -f2)" = "$(head -n 1 /run/mouse-root/ROOT/default/etc/shadow | cut -d: -f2)" && grep -q "^alice:x:1000:1000::/home/alice:/bin/tcsh$" /run/mouse-root/ROOT/default/etc/passwd'; echo ACCOUNT_$status}
|
||||||
|
await "ACCOUNT_0\r" "the on-disk login account"
|
||||||
|
await "mouse:~# " "the provisioning prompt"
|
||||||
|
sendline {echo enable_mouse_echo=YES >/run/mouse-root/ROOT/default/etc/rc.conf; echo enable_network=NO >>/run/mouse-root/ROOT/default/etc/rc.conf; echo POLICY_$status}
|
||||||
|
await "POLICY_0\r" "the offline test policy"
|
||||||
|
await "mouse:~# " "the provisioning prompt"
|
||||||
|
poweroff
|
||||||
|
puts "account provisioning acceptance passed"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
await "mouse login: " "the authenticated login prompt"
|
||||||
|
set timeout 2
|
||||||
|
expect {
|
||||||
|
-exact "root (automatic login)" { fail "normal disk enabled root autologin" }
|
||||||
|
timeout {}
|
||||||
|
}
|
||||||
|
set timeout 75
|
||||||
|
sendline {alice}
|
||||||
|
await "Password: " "the password prompt"
|
||||||
|
if {$mode eq "login"} {
|
||||||
|
sendline {wrong-password}
|
||||||
|
await "Login incorrect" "failed authentication rejection"
|
||||||
|
await "mouse login: " "the retry login prompt"
|
||||||
|
sendline {alice}
|
||||||
|
await "Password: " "the second password prompt"
|
||||||
|
}
|
||||||
|
sendline {cheddar}
|
||||||
|
await "mouse:~> " "the ordinary user shell"
|
||||||
|
sendline {id}
|
||||||
|
await "uid=1000(alice) gid=1000(operators) groups=1000(operators)\r" "the ordinary identity"
|
||||||
|
await "mouse:~> " "the user prompt"
|
||||||
|
sendline {sh -c 'test "$HOME" = /home/alice && test "$SHELL" = /bin/tcsh && test -O /home/alice && ! touch /usr/alice-must-not-write'; echo USER_$status}
|
||||||
|
await "USER_0\r" "home ownership and base protection"
|
||||||
|
await "mouse:~> " "the user prompt"
|
||||||
|
sendline {su - root}
|
||||||
|
await "Password: " "the explicit elevation password"
|
||||||
|
sendline {cheddar}
|
||||||
|
await "mouse:~# " "the elevated root shell"
|
||||||
|
sendline {id}
|
||||||
|
await "uid=0(root) gid=0(root) groups=0(root)\r" "authorised root elevation"
|
||||||
|
await "mouse:~# " "the root prompt"
|
||||||
|
sendline {sh -c 'test -d /bin && test -d /sbin && test -d /usr/bin && test -d /usr/sbin && test -x /bin/ln && test -x /sbin/cheesed && test -x /usr/bin/passwd && test -x /usr/sbin/useradd'; echo FHS_$?}
|
||||||
|
await "FHS_0\r" "the FHS command-directory split"
|
||||||
|
await "mouse:~# " "the root prompt"
|
||||||
|
sendline {sh -c 'rm -f /tmp/ln-source /tmp/ln-target; echo mouse >/tmp/ln-source; ln /tmp/ln-source /tmp/ln-target; test "$(cat /tmp/ln-target)" = mouse'; echo LN_$?}
|
||||||
|
await "LN_0\r" "the statically linked ln command"
|
||||||
|
await "mouse:~# " "the root prompt"
|
||||||
|
sendline {exit}
|
||||||
|
await "mouse:~> " "the ordinary user shell after leaving root"
|
||||||
|
sendline {su - toor}
|
||||||
|
await "Password: " "the toor password prompt"
|
||||||
|
sendline {cheddar}
|
||||||
|
await "mouse:~# " "the toor shell"
|
||||||
|
sendline {echo $LOGNAME $SHELL}
|
||||||
|
await "toor /bin/tcsh\r" "the toor login and friendly shell"
|
||||||
|
await "mouse:~# " "the toor prompt"
|
||||||
|
poweroff
|
||||||
|
puts "$mode authenticated multi-user acceptance passed"
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
acceptance_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
. "$acceptance_dir/harness.sh"
|
||||||
|
acceptance_init
|
||||||
|
acceptance_require_commands "the multi-user acceptance test" expect
|
||||||
|
acceptance_copy_disk multi-user "multi-user operation"
|
||||||
|
|
||||||
|
"$acceptance_dir/multi-user.exp" "$runner" "$test_disk" provision
|
||||||
|
"$acceptance_dir/multi-user.exp" "$runner" "$test_disk" login
|
||||||
|
"$acceptance_dir/multi-user.exp" "$runner" "$test_disk" verify
|
||||||
|
printf '%s\n' "multi-user acceptance tests passed"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user