pax_global_header00006660000000000000000000000064151465560230014521gustar00rootroot0000000000000052 comment=cd0b569ec2787596f9faa71d0023c3e015d647d1 mini-init-asm-0.3.1+ds/000077500000000000000000000000001514655602300145775ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/.github/000077500000000000000000000000001514655602300161375ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/.github/workflows/000077500000000000000000000000001514655602300201745ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/.github/workflows/release.yml000066400000000000000000000133311514655602300223400ustar00rootroot00000000000000name: Build and Release on: push: branches: [main] tags: ["v*.*.*"] pull_request: branches: [main] workflow_dispatch: permissions: contents: read jobs: build-amd64: name: Build amd64 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y nasm make binutils - name: Build and test amd64 run: | timeout 5m make test-all strip build/mini-init-amd64 - name: Upload amd64 artifact uses: actions/upload-artifact@v4 with: name: mini-init-amd64-${{ github.sha }} path: build/mini-init-amd64 if-no-files-found: error retention-days: 7 build-arm64: name: Build arm64 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install cross-compile dependencies run: | sudo apt-get update sudo apt-get install -y nasm make binutils gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu qemu-user-static file - name: Build arm64 binary run: | timeout 5m make build-arm64 aarch64-linux-gnu-strip build/mini-init-arm64 - name: ARM64 fallback smoke (QEMU-user) run: | timeout 2m env ARM64_FALLBACK=1 EP_ARM64_FALLBACK=1 bash scripts/test_harness_arm64.sh build/mini-init-arm64 - name: Upload arm64 artifact uses: actions/upload-artifact@v4 with: name: mini-init-arm64-${{ github.sha }} path: build/mini-init-arm64 if-no-files-found: error retention-days: 7 test-arm64-native: name: Test arm64 (native runner) runs-on: ubuntu-24.04-arm timeout-minutes: 15 # Run on all PRs, pushes to main, and tags to ensure ARM64 compatibility # This validates the full epoll/signalfd path (not QEMU fallback) steps: - name: Checkout uses: actions/checkout@v4 - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y bash make binutils - name: Build and test arm64 run: | timeout 5m make DEBUG=0 build-arm64 timeout 2m bash scripts/test_harness.sh build/mini-init-arm64 timeout 2m bash scripts/test_ep_signals.sh build/mini-init-arm64 timeout 2m bash scripts/test_edge_cases.sh build/mini-init-arm64 timeout 2m bash scripts/test_exit_code_mapping.sh build/mini-init-arm64 timeout 2m bash scripts/test_restart.sh build/mini-init-arm64 timeout 2m bash scripts/test_diagnostics.sh build/mini-init-arm64 debian-package: name: Debian package build (lintian) runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install Debian packaging dependencies run: | sudo apt-get update sudo apt-get install -y \ binutils build-essential debhelper devscripts dpkg-dev fakeroot lintian make nasm - name: Prepare build tree (keep artifacts in workspace) run: | rm -rf ci/debian-build mkdir -p ci/debian-build/src tar \ --exclude='./.git' \ --exclude='./build' \ --exclude='./ci/debian-build' \ --exclude='./debian/.debhelper' \ --exclude='./debian/tmp' \ --exclude='./debian/mini-init-asm' \ --exclude='./debian/mini-init-asm-dbgsym' \ -cf - . | tar -C ci/debian-build/src -xf - - name: Build Debian package working-directory: ci/debian-build/src run: | timeout 10m dpkg-buildpackage -us -uc -b - name: Lintian (fail on warnings) working-directory: ci/debian-build/src run: | lintian --fail-on warning --display-info --tag-display-limit 0 ../*.deb - name: Upload Debian artifacts uses: actions/upload-artifact@v4 with: name: debian-packaging-${{ github.sha }} path: | ci/debian-build/*.deb ci/debian-build/*.ddeb ci/debian-build/*.buildinfo ci/debian-build/*.changes if-no-files-found: error retention-days: 7 release: name: Release needs: [build-amd64, build-arm64, test-arm64-native, debian-package] runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') permissions: contents: write steps: - name: Download amd64 artifact uses: actions/download-artifact@v4 with: name: mini-init-amd64-${{ github.sha }} path: release - name: Download arm64 artifact uses: actions/download-artifact@v4 with: name: mini-init-arm64-${{ github.sha }} path: release - name: Prepare release assets run: | cd release sha256sum mini-init-amd64 > mini-init-amd64.sha256 sha256sum mini-init-arm64 > mini-init-arm64.sha256 cat > RELEASE_NOTES.txt < # Changelog ## 0.3.1 - 2026-01-11 ### Fixed - Fix `debian/control` Architecture field: changed from `any` to `amd64 arm64` to prevent FTBFS on unsupported architectures. - Add explicit `make` to Build-Depends for reproducible builds across all environments. ### Improved - Implement VERSION single source of truth: version now controlled by `VERSION` file at repository root. - Auto-generate arch-specific version include files (`include/version_amd64.inc`, `include/version_arm64.inc`) during build. - Version string length now calculated dynamically (no hardcoded lengths). - Version bumps now require changing only 1 file instead of 4 separate locations. ### Tests/Docs - Add restart-on-crash test to `debian/tests/smoke`: validates `EP_RESTART_ENABLED` and `EP_MAX_RESTARTS` functionality. - Add PGID signal fan-out test to `debian/tests/smoke`: verifies signals reach both child and grandchild processes. - Autopkgtest now validates 5 critical behaviors (was 3). - Document that `EP_ARM64_FALLBACK` mode is NOT suitable for production (CI testing stub only). - List features NOT available in fallback mode: signal forwarding, graceful shutdown, restart, custom EP_SIGNALS. - Add restart configuration best practices: guidance on backoff delays and restart limits to prevent tight CPU loops. - Add Debian packaging guide section with build instructions, autopkgtest usage, lintian checks, and supported architectures. - Update man page date and version metadata to match VERSION file. - Document that ARM64 native tests run on every PR (validates full epoll/signalfd path). ## 0.3.0 - 2026-01-06 - Real-time signals: `EP_SIGNALS=RT*` now requires explicit `EP_SIGRTMIN`/`EP_SIGRTMAX` (avoids hardcoded SIGRTMIN/SIGRTMAX assumptions). - `EP_SIGNALS` now supports numeric signal tokens (`1..64`, excluding SIGKILL/SIGSTOP). - Logging and fd I/O: add `write_all` handling partial writes and `EINTR`; harden `signalfd`/`timerfd` reads. - ARM64 correctness: fix AArch64 call/return ABI issues (preserve `x30` where required), ensure `do_spawn` is instruction-aligned (avoid native SIGILL), and fix `EP_SIGNALS` parsing crash (token parsing no longer relies on a clobbered register). - Correctness: avoid forwarding signals after the main child has exited while restart backoff is pending (prevents accidental signaling of a reused PGID). - Debian readiness: add `mini-init-asm(1)` and an initial `debian/` packaging skeleton with autopkgtest and CI lintian build. ## 0.2.0 - 2025-12-13 ### Fixed - Fix critical PID1 hang: main-child exit could be missed during SIGCHLD storms (now reliably detected/reported on both amd64 and arm64). - Fix amd64 restart-mode stack safety when max restarts are reached. - Prevent `epoll` fd leakage into the exec’ed child (`epoll_create1(EPOLL_CLOEXEC)`). - Avoid SIGKILL escalation to a reused/nonexistent PGID (`kill(-pgid, 0)` probe before escalation). - Fix verbose logging writing a NUL byte in timestamps. ### Improved - More actionable verbose logs: signal number, grace seconds, restart backoff seconds, and restart count. - Harden `EP_SIGNALS` parsing: - Trim trailing whitespace per token. - Real-time signals are now bounded to the kernel max (`RT1..RT30`). - Numeric env vars are now parsed strictly as decimal digits; invalid/overflow values are ignored (warnings in verbose mode). - Timer-related seconds (grace/backoff) are clamped to fit signed 64-bit seconds. - `EP_EXIT_CODE_BASE` is now validated as `0..255` (out-of-range values are ignored; default applies). - ARM64/QEMU: remove `msub` usage in hot paths; in `EP_ARM64_FALLBACK=1` mode timestamps are omitted to reduce QEMU-user flakiness. ### Tests/Docs - Add an edge-case test covering “main child exits while many other children are reaped”. - Update docs to reflect RT signal bounds and ARM64 fallback timestamp behavior. - ARM64/QEMU: in fallback mode, smoke tests now exercise `--version` and the wait4-only path (helper exit propagation) instead of skipping entirely. - Clean up `scripts/*` quoting to avoid common ShellCheck warnings (SC2016/SC2086). mini-init-asm-0.3.1+ds/LICENSE000066400000000000000000000020511514655602300156020ustar00rootroot00000000000000MIT License Copyright (c) 2025 roots666 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mini-init-asm-0.3.1+ds/Makefile000066400000000000000000000074301514655602300162430ustar00rootroot00000000000000# Build configuration VERSION := $(shell cat VERSION 2>/dev/null || echo "unknown") BUILD_DIR ?= build INC_DIR := include TARGET_AMD64 ?= mini-init-amd64 TARGET_ARM64 ?= mini-init-arm64 AMD64_SRC_DIR := src/amd64 ARM64_SRC_DIR := src/arm64 NASM := nasm LD := ld DEBUG ?= 1 NASMFLAGS := -f elf64 -I$(INC_DIR)/ LDFLAGS := -nostdlib -z noexecstack -z relro -z now --build-id=sha1 ARM64_AS ?= $(shell command -v aarch64-linux-gnu-as 2>/dev/null || echo as) ARM64_LD ?= $(shell command -v aarch64-linux-gnu-ld 2>/dev/null || echo ld) ARM64_ASFLAGS ?= -I$(INC_DIR) ARM64_LDFLAGS ?= -nostdlib -z noexecstack -z relro -z now --build-id=sha1 ifeq ($(DEBUG),1) NASMFLAGS += -g -F dwarf ARM64_ASFLAGS += -g endif # Generate version include files for both architectures $(INC_DIR)/version_amd64.inc: VERSION @echo '; Auto-generated version file' > $@ @echo 'version_msg_str: db "mini-init-amd64 $(VERSION)", 10, 0' >> $@ @echo 'version_msg_len_val: equ $$ - version_msg_str - 1' >> $@ $(INC_DIR)/version_arm64.inc: VERSION @echo '// Auto-generated version file' > $@ @echo 'version_msg_arm64: .asciz "mini-init-arm64 $(VERSION)\n"' >> $@ @echo '.equ version_msg_arm64_len, . - version_msg_arm64 - 1' >> $@ AMD64_BUILD_DIR := $(BUILD_DIR)/amd64 ARM64_BUILD_DIR := $(BUILD_DIR)/arm64 AMD64_SRCS := main.asm signals.asm spawn.asm forward.asm wait.asm epoll.asm timer.asm log.asm time.asm AMD64_OBJS := $(addprefix $(AMD64_BUILD_DIR)/,$(AMD64_SRCS:.asm=.o)) ARM64_SRCS := main.S signals.S spawn.S forward.S wait.S epoll.S timer.S log.S time.S util.S ARM64_OBJS := $(addprefix $(ARM64_BUILD_DIR)/,$(ARM64_SRCS:.S=.o)) .PHONY: helper-arm64 ARM64_HELPERS_SRCS := helpers/exit42.S helpers/sleeper.S ARM64_HELPERS_OBJS := $(addprefix $(ARM64_BUILD_DIR)/,$(ARM64_HELPERS_SRCS:.S=.o)) ARM64_HELPERS_BINS := $(ARM64_BUILD_DIR)/helper-exit42 $(ARM64_BUILD_DIR)/helper-sleeper .PHONY: all all-amd64 build-arm64 clean test test-amd64 test-arm64 test-all all: all-amd64 all-amd64: $(BUILD_DIR)/$(TARGET_AMD64) build-arm64: $(BUILD_DIR)/$(TARGET_ARM64) $(ARM64_HELPERS_BINS) build-amd64: $(BUILD_DIR)/$(TARGET_AMD64) test: test-amd64 test-amd64: $(BUILD_DIR)/$(TARGET_AMD64) bash scripts/test_harness.sh $(BUILD_DIR)/$(TARGET_AMD64) test-arm64: $(BUILD_DIR)/$(TARGET_ARM64) bash scripts/test_harness_arm64.sh $(BUILD_DIR)/$(TARGET_ARM64) test-all: test-amd64 bash scripts/test_ep_signals.sh $(BUILD_DIR)/$(TARGET_AMD64) bash scripts/test_edge_cases.sh $(BUILD_DIR)/$(TARGET_AMD64) bash scripts/test_exit_code_mapping.sh $(BUILD_DIR)/$(TARGET_AMD64) bash scripts/test_restart.sh $(BUILD_DIR)/$(TARGET_AMD64) bash scripts/test_diagnostics.sh $(BUILD_DIR)/$(TARGET_AMD64) $(AMD64_BUILD_DIR): mkdir -p $@ $(ARM64_BUILD_DIR): mkdir -p $@ $(AMD64_BUILD_DIR)/%.o: $(AMD64_SRC_DIR)/%.asm $(INC_DIR)/*.inc $(INC_DIR)/version_amd64.inc | $(AMD64_BUILD_DIR) $(NASM) $(NASMFLAGS) $< -o $@ $(ARM64_BUILD_DIR)/%.o: $(ARM64_SRC_DIR)/%.S $(INC_DIR)/*.inc $(INC_DIR)/version_arm64.inc | $(ARM64_BUILD_DIR) $(ARM64_AS) $(ARM64_ASFLAGS) $< -o $@ # Helper objects $(ARM64_BUILD_DIR)/helpers/%.o: $(ARM64_SRC_DIR)/helpers/%.S $(INC_DIR)/*.inc | $(ARM64_BUILD_DIR) mkdir -p $(ARM64_BUILD_DIR)/helpers $(ARM64_AS) $(ARM64_ASFLAGS) -I$(INC_DIR) $< -o $@ $(BUILD_DIR)/$(TARGET_AMD64): $(AMD64_OBJS) $(LD) $(LDFLAGS) -o $@ $(AMD64_OBJS) $(BUILD_DIR)/$(TARGET_ARM64): $(ARM64_OBJS) $(ARM64_LD) $(ARM64_LDFLAGS) -o $@ $(ARM64_OBJS) # Helper binaries (linked standalone) $(ARM64_BUILD_DIR)/helper-exit42: $(ARM64_BUILD_DIR)/helpers/exit42.o $(ARM64_LD) $(ARM64_LDFLAGS) -o $@ $< $(ARM64_BUILD_DIR)/helper-sleeper: $(ARM64_BUILD_DIR)/helpers/sleeper.o $(ARM64_LD) $(ARM64_LDFLAGS) -o $@ $< clean: rm -rf $(BUILD_DIR) mini-init-asm-0.3.1+ds/README.md000066400000000000000000000532231514655602300160630ustar00rootroot00000000000000# mini-init-asm (PGID-mode) A tiny **PID 1** for containers, written in **x86-64 NASM** and **ARM64 GAS**. It spawns your target process as its *own process group*, forwards signals to the whole group, reaps zombies, and optionally restarts your app on crash. On exit, it returns your app's status code (with configurable signal→exit mapping). > **Architectures:** x86-64 Linux (native NASM build) and arm64/AArch64 (cross-build via GNU toolchain). --- ## TL;DR - **Problem:** many containers still run without a proper PID 1, which breaks signal handling and zombie reaping. - **Solution:** `mini-init-asm` is a **tiny, auditable init** that: - creates a **new session + process group** for your app; - forwards signals to the **entire group**; - reaps zombies (plus optional **subreaper** mode); - supports **graceful shutdown** with configurable timeout and `SIGKILL` escalation; - can **restart** the app on crashes (optional, env-driven); - is implemented in **pure assembly** using Linux syscalls only (no libc). It’s a parallel, assembly-focused alternative to tools like [Tini](https://github.com/krallin/tini): similar semantics for containers, but a different implementation style and feature focus. --- ## When to use mini-init-asm Use `mini-init-asm` if you: - Build **minimal or `FROM scratch` images**, and want a tiny, static PID 1. - Run **multi-process containers** and need robust **process-group** signal fan-out. - Want a **small, auditable** init written in pure assembly (good for learning & review). - Need simple **restart-on-crash** behavior without a full-blown supervisor. If you just want a battle-tested init with wide distro support, you probably still want [Tini](https://github.com/krallin/tini). This project is intentionally **small and opinionated**, and targets PGID-mode container entrypoints. --- ## mini-init-asm vs Tini This project is heavily inspired by the patterns popularized by [Tini](https://github.com/krallin/tini): spawn one child, forward signals, reap zombies. The difference is mainly in **implementation** and a few **runtime semantics**. ### High-level comparison | Aspect | **mini-init-asm** | **Tini** | | --------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | Language | x86-64 NASM + ARM64 GAS | C | | Architectures | amd64, arm64 | Many (amd64, arm, armhf, i386, etc. – see releases) | | Binary type | Static, **no libc**, pure Linux syscalls | Dynamic + static variants; depends on libc | | Default kill mode | Always kills **process group** (`kill(-pgid, sig)`) | By default kills **child only**, group-kill via `-g` / `TINI_KILL_PROCESS_GROUP` | | Session / PGID | Always creates **new session + PGID** for child | Optional group-kill mode; no hard “PGID-mode only” branding | | Signal handling | `signalfd(2)` + `epoll(7)` + `timerfd(2)` event loop | traditional signal handlers + wait / reaping loop | | Subreaper support | `EP_SUBREAPER=1` env (uses `PR_SET_CHILD_SUBREAPER`) | `-s` flag or `TINI_SUBREAPER` env | | Restart-on-crash | Yes, via `EP_RESTART_*` env vars (simple supervisor mode) | No (Tini intentionally does **not** supervise / restart children) | | Exit code mapping | `EP_EXIT_CODE_BASE` (base + signal number) | `-e` flags to remap specific exit codes to 0 | | Config surface | Mostly **env vars** + minimal flags (`-v`, `-V`) | CLI flags (`-v`, `-s`, `-g`, `-e`, `-p`, …) + env vars | | Ecosystem integration | Standalone binary, Dockerfile provided | Packaged in many distros; integrated into Docker via `--init` | | Size (qualitative) | Tiny static binary (pure asm, no libc; tens-of-KB range) | Tiny dynamic binary (~10KB), static version still <1MB (per upstream docs) | > The goal of `mini-init-asm` is **not** to replace Tini everywhere, but to offer: > > - a **PGID-first**, assembly-level implementation; > - an example of a full-featured container init in pure asm; > - a small init with **restart-mode** for simple setups. ### Feature matrix (plain PID1 vs Tini vs mini-init-asm) | Feature / Behavior | Plain app as PID 1 | Tini | mini-init-asm (this repo) | |-----------------------------|-----------------------------|-------------------------------|-------------------------------------------------| | Signal forwarding | Depends on app | Yes | Yes (group-wide) | | Zombie reaping | Depends on app | Yes | Yes | | Process-group kill | Depends on app | Optional (`-g` / env) | Always group-based | | Subreaper mode | No | Yes (`-s` / `TINI_SUBREAPER`) | Yes (`EP_SUBREAPER=1`) | | Restart on crash | Depends on app | No | Yes (`EP_RESTART_*` envs) | | Pure-syscall implementation | Rare | No (libc) | Yes | | Minimal config surface | N/A | CLI + env | Primarily env, very small CLI | ### Note (why choose this vs tini?) Most distros already provides well-established init helpers (notably `tini`) and Docker can enable one via `--init`. `mini-init-asm` is mainly useful when you specifically want: - A pure-syscall, libc-free implementation (e.g. for very small/static images or audit/learning goals). - Process-group forwarding as the default behavior (PGID-first “entrypoint init” semantics). - Optional restart-on-crash behavior (Tini intentionally does not supervise/restart children). If you just need a battle-tested, widely deployed init with broad architecture coverage and a long track record, prefer `tini`. --- ## Quick Start ### Prerequisites (Debian/Ubuntu) ```bash sudo apt-get update sudo apt-get install -y nasm make binutils ```` ### Build (x86-64) ```bash make ``` ### Example run (x86-64) ```bash ./build/mini-init-amd64 -- /bin/sh -c 'echo hello && sleep 5' ``` ### Cross-build (ARM64 / AArch64) Install a cross toolchain: ```bash sudo apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu make build-arm64 ``` ### Native build (ARM64 host) On an ARM64 machine, you can build natively: ```bash sudo apt-get install -y make binutils make build-arm64 ``` And you can run the regular test scripts against the ARM64 binary: ```bash bash scripts/test_harness.sh build/mini-init-arm64 bash scripts/test_ep_signals.sh build/mini-init-arm64 bash scripts/test_edge_cases.sh build/mini-init-arm64 bash scripts/test_exit_code_mapping.sh build/mini-init-arm64 bash scripts/test_restart.sh build/mini-init-arm64 bash scripts/test_diagnostics.sh build/mini-init-arm64 ``` ### Example run (ARM64 via QEMU on x86 host) Requires `qemu-user-static` to run an ARM64 binary on x86: ```bash # Recommended: ensures the second `--` reaches mini-init qemu-aarch64-static -- ./build/mini-init-arm64 -- /bin/sh -c 'echo hello && sleep 5' # Alternatively (works in some shells too): # qemu-aarch64-static ./build/mini-init-arm64 -- /bin/sh -c 'echo hello && sleep 5' ``` If you see a usage message like `usage: mini-init-arm64 ...`, the `--` delimiter was swallowed by QEMU. Use the extra `--` right after `qemu-aarch64-static`. > Note: QEMU user-mode emulation can be flaky for the full epoll/signalfd/timerfd loop on some hosts. > CI therefore uses `EP_ARM64_FALLBACK=1` for a minimal smoke test, and also runs native ARM64 tests > on GitHub-hosted ARM runners (subject to runner availability). For higher confidence, run tests on > real ARM64 hardware or use full-system emulation (qemu-system-aarch64) for integration tests. ### Graceful stop demo ```bash # Run and interrupt with Ctrl+C (TERM to group, grace window, optional KILL) ./build/mini-init-amd64 -- bash -c 'trap "echo got TERM; exit 0" TERM; sleep 1000' ``` --- ## Docker ### Single architecture (amd64) ```bash docker build -t mini-init-asm:dev -f docker/Dockerfile . docker run --rm -it mini-init-asm:dev -- /bin/sh -c 'sleep 1000' # In another terminal: docker kill --signal=TERM ``` ### Multi-architecture (amd64 + arm64) ```bash # Build for both platforms docker buildx build --platform linux/amd64,linux/arm64 \ -t mini-init-asm:latest -f docker/Dockerfile.multiarch . # Run on specific platform docker run --rm --platform linux/amd64 -it mini-init-asm:latest -- /bin/sh -c 'echo hello' docker run --rm --platform linux/arm64 -it mini-init-asm:latest -- /bin/sh -c 'echo hello' ``` --- ## Usage ```bash mini-init-{amd64|arm64} [--verbose|-v] [--version|-V] -- [args...] ``` ### Command-line options - `-v`, `--verbose` — enable verbose logging (timestamps, fds, signal events). - `-V`, `--version` — print version string and exit. ### Environment variables Numeric env vars are parsed as **non-negative decimal**. If a value is invalid/overflows, it is ignored (defaults apply); in verbose mode a warning is logged. Timer-related values (grace/backoff seconds) are clamped to fit in signed 64-bit seconds. - `EP_GRACE_SECONDS` Grace period (in seconds) from the *first* forwarded soft signal to `SIGKILL` escalation. Default: `10`. - `EP_SIGNALS` CSV of **additional** signal names to monitor/forward (case-sensitive). Supported tokens: - Named: `USR1,USR2,PIPE,WINCH,TTIN,TTOU,CONT,ALRM` - Numeric: decimal signal numbers `1..64` (SIGKILL and SIGSTOP are ignored) - Real-time: `RT1..RT30` (\=`SIGRTMIN+N`), only if `EP_SIGRTMIN` and `EP_SIGRTMAX` are set (see below) These **augment** the built-in set: `HUP,INT,QUIT,TERM,CHLD` plus default forwarding of `USR1,USR2,PIPE,WINCH,TTIN,TTOU,CONT,ALRM`. Unknown tokens are ignored with a warning. In verbose mode we only log “EP_SIGNALS parsed” if the variable is present (even if empty). - `EP_SIGRTMIN`, `EP_SIGRTMAX` Decimal runtime values for SIGRTMIN/SIGRTMAX in the target libc environment. Required to enable `RT*` tokens in `EP_SIGNALS`. This avoids hardcoding RT signal numbers (see `signal(7)`; SIGRTMIN can vary at runtime on glibc with threads). - `EP_SUBREAPER` If set to `1`, enables `PR_SET_CHILD_SUBREAPER` so that `mini-init-asm` adopts orphaned grandchildren. Useful when nested processes need proper reaping. `mini-init-asm` still exits when the main child exits (it does not wait indefinitely for adopted descendants). Default: disabled. - `EP_EXIT_CODE_BASE` Base value for mapping “killed by signal” to exit code: `exit_code = EP_EXIT_CODE_BASE + signal_number` (default base `128`, like shells). For example, `SIGKILL` (9) with base 200 → exit code 209. Valid range: `0..255` (out-of-range values are ignored; default applies). - `EP_RESTART_ENABLED` If set to `1`, enables **restart-on-crash**: when the child is killed by a signal (non-zero, non-normal exit), `mini-init-asm` restarts it. Restarts are **disabled** during graceful shutdown (after a soft signal like `TERM/INT/HUP/QUIT`). Default: disabled. - `EP_MAX_RESTARTS` Maximum number of restarts when `EP_RESTART_ENABLED=1`. Allows up to `N` restarts (`N+1` total runs: initial + N restarts). If the child crashes more than N times, `mini-init-asm` exits with the child’s code. Set to `0` for **unlimited** restarts. Default: `0`. - `EP_RESTART_BACKOFF_SECONDS` Delay before restarting a crashed child. Helps avoid tight restart loops. `0` = restart immediately. Default: `1`. - `EP_ARM64_FALLBACK` (ARM64/QEMU only) If set to `1`, ARM64 builds skip the epoll/signalfd path and use a simpler `wait4` loop. Intended as a workaround for QEMU user-mode flakiness in CI smoke tests. **WARNING:** This mode is a CI testing stub and is **NOT suitable for production use**. It does **NOT** provide: - Signal forwarding to child process group - Graceful shutdown with grace period escalation - Restart-on-crash functionality - Custom signal monitoring (EP_SIGNALS) Fallback mode only verifies basic spawn and exit code propagation. Use it **only** for CI smoke testing under QEMU user-mode emulation. For production, run the full binary on native ARM64 hardware or use full-system emulation. In fallback mode, verbose logs may omit timestamps to avoid QEMU-user emulation issues. Default: `0` (CI jobs typically set this). ### Examples ```bash # Default behavior: forward TERM/INT/HUP/QUIT to the group, wait 10s, then KILL if needed ./build/mini-init-amd64 -- ./your-app --flag # Verbose logs ./build/mini-init-amd64 -v -- ./your-app # Check version ./build/mini-init-amd64 --version # Custom grace period EP_GRACE_SECONDS=5 ./build/mini-init-amd64 -- ./your-app # Add USR1 forwarding EP_SIGNALS=USR1 ./build/mini-init-amd64 -- ./your-app # Add RT signals (RT1 = SIGRTMIN+1, RT5 = SIGRTMIN+5) # RT tokens are only enabled if you provide runtime SIGRTMIN/SIGRTMAX explicitly: EP_SIGRTMIN=34 EP_SIGRTMAX=64 EP_SIGNALS=RT1,RT5 ./build/mini-init-amd64 -- ./your-app # Enable subreaper mode (adopt orphaned grandchildren) EP_SUBREAPER=1 ./build/mini-init-amd64 -- ./your-app # Custom exit code base (SIGKILL will yield 200+9=209 instead of 128+9=137) EP_EXIT_CODE_BASE=200 ./build/mini-init-amd64 -- ./your-app # Restart on crash (up to 5 restarts, 2s backoff) EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=5 EP_RESTART_BACKOFF_SECONDS=2 \ ./build/mini-init-amd64 -- ./your-app # Unlimited restarts, no backoff EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=0 EP_RESTART_BACKOFF_SECONDS=0 \ ./build/mini-init-amd64 -- ./your-app ``` ### Best Practices #### Restart Configuration When enabling restart-on-crash (`EP_RESTART_ENABLED=1`): - **Always set a backoff delay** (`EP_RESTART_BACKOFF_SECONDS`) to prevent tight restart loops - Recommended minimum: `1` second (default) - For flaky apps: `5-10` seconds - **Set a restart limit** (`EP_MAX_RESTARTS`) to prevent infinite loops on persistent failures - Recommended: `3-5` restarts for transient errors - Use `0` (unlimited) only for long-running services with rare crashes **Example - Good configuration:** ```bash # Bounded restarts with backoff (recommended) EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=3 EP_RESTART_BACKOFF_SECONDS=5 \ ./build/mini-init-amd64 -- ./my-app ``` **Example - Dangerous configuration:** ```bash # Unlimited restarts with no delay (tight loop on immediate crash - avoid!) EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=0 EP_RESTART_BACKOFF_SECONDS=0 \ ./build/mini-init-amd64 -- ./my-app ``` ### Exit code semantics - Child exits normally → `mini-init-asm` returns the **child exit code**. - Child dies by signal → returns `EP_EXIT_CODE_BASE + signal_number` (default base: `128`, e.g. `SIGTERM` = 143). - Child is killed by `SIGKILL` after grace-period expiration → returns `EP_EXIT_CODE_BASE + 9`. --- ## How it works (epoll + signalfd + timerfd) High-level algorithm: 1. Block all relevant signals in PID 1. 2. Spawn the child under a **new session + process group** (PGID = child PID). 3. Create: - a `signalfd` for `HUP,INT,QUIT,TERM,CHLD` plus anything from `EP_SIGNALS`; - a `timerfd` for the grace window; - an `epoll` instance watching both. 4. Main loop: - Wait on `epoll_wait`. - On `signalfd` events: - For **soft signals** (`HUP/INT/QUIT/TERM`): - forward to **process group** via `kill(-pgid, sig)`; - arm the grace `timerfd` if this is the *first* soft signal. - On `SIGCHLD`: - reap children with `waitpid(-1, WNOHANG)`; - if the main child exited, propagate its exit code and terminate. - On timer expiration: - if the child is still alive, escalate to `SIGKILL` for the whole group. Key syscalls: `signalfd(2)`, `epoll(7)`, `timerfd_create(2)`, `timerfd_settime(2)`, `rt_sigprocmask(2)`, `wait4(2)`, `kill(2)`, `setsid(2)`, `setpgid(2)`. --- ## Repository layout ```text mini-init-asm/ ├─ README.md ├─ ROADMAP.md ├─ LICENSE ├─ Makefile ├─ include/ │ ├─ macros.inc # x86-64 syscall/log helpers │ ├─ macros_arm64.inc # arm64 syscall/log helpers │ ├─ syscalls_amd64.inc # syscall numbers for x86-64 │ └─ syscalls_aarch64.inc # syscall numbers for arm64 ├─ src/ │ ├─ amd64/ # NASM sources (x86-64 ABI) │ └─ arm64/ # AArch64 sources (arm64 ABI) ├─ scripts/ │ ├─ test_harness.sh # e2e tests │ └─ fixtures/ │ ├─ sleeper.sh │ └─ trap_exit0.sh └─ docker/ └─ Dockerfile # multi-stage: build -> scratch ``` --- ## Build system ```bash make # build/mini-init-amd64 make test # run e2e tests on x86-64 host make build-arm64 # build/mini-init-arm64 (requires aarch64-linux-gnu toolchain) make test-arm64 # run ARM64 smoke tests via QEMU (requires qemu-user-static) make clean ``` Both binaries are linked with `ld -nostdlib`. The code issues syscalls directly: - x86-64: `rax` + `rdi/rsi/rdx/r10/r8/r9` - arm64: `x8` + `x0`–`x5` Signal numbers and ABI differences are factored into the `include/syscalls_*.inc` files. --- ## Testing ### x86-64 (native) ```bash make test # Basic e2e tests make test-all # e2e + unit + edge cases # or directly: bash scripts/test_harness.sh build/mini-init-amd64 bash scripts/test_ep_signals.sh build/mini-init-amd64 bash scripts/test_edge_cases.sh build/mini-init-amd64 bash scripts/test_exit_code_mapping.sh build/mini-init-amd64 bash scripts/test_restart.sh build/mini-init-amd64 ``` ### ARM64 (via QEMU) ```bash sudo apt-get install -y qemu-user-static make test-arm64 # or: bash scripts/test_harness_arm64.sh build/mini-init-arm64 ``` ### ARM64 (native) On an ARM64 host you can run the regular test scripts directly: ```bash make build-arm64 bash scripts/test_harness.sh build/mini-init-arm64 bash scripts/test_ep_signals.sh build/mini-init-arm64 bash scripts/test_edge_cases.sh build/mini-init-arm64 bash scripts/test_exit_code_mapping.sh build/mini-init-arm64 bash scripts/test_restart.sh build/mini-init-arm64 bash scripts/test_diagnostics.sh build/mini-init-arm64 ``` ### Test suites 1. **Basic e2e tests** (`test_harness.sh`): - group-wide forwarding (TERM) and graceful exit; - escalation: app ignores TERM → KILL after grace window; - custom `EP_SIGNALS=USR1` and child reaction. 2. **EP_SIGNALS parser tests** (`test_ep_signals.sh`): - single/multiple token parsing; - unknown tokens (warnings); - whitespace handling; - empty / edge-case inputs. 3. **Edge-case integration tests** (`test_edge_cases.sh`): - rapid signal bursts; - orphaned process handling (`EP_SUBREAPER=1`); - mixed TERM/INT/HUP; - signals during grace period; - immediate child exit after signal. 4. **Exit code mapping tests** (`test_exit_code_mapping.sh`): - default base (128); - custom base; - normal exits (unaffected); - signal exits with custom base. 5. **Restart functionality tests** (`test_restart.sh`): - restart on crash (signal); - restart with backoff; - max-restart limit; - no restart on normal exit; - no restart after shutdown signal; - unlimited restarts (`EP_MAX_RESTARTS=0`). > **Note:** ARM64 tests run under QEMU user emulation and may differ slightly in timing or fail on some > hosts; for full determinism use native ARM64. --- ## Debian Packaging ### Package Information The Debian package `mini-init-asm` provides a unified binary: - **Installed at:** `/usr/bin/mini-init-asm` - **Architecture-specific:** Built for `amd64` and `arm64` only - **Statically linked:** No runtime dependencies (libc-free) ### Building the Debian Package ```bash # Install build dependencies sudo apt-get install debhelper-compat binutils nasm make # Build binary package dpkg-buildpackage -us -uc -b # Install locally sudo dpkg -i ../mini-init-asm_*.deb ``` ### Running Autopkgtest ```bash # After installing package autopkgtest . -- null # Or from source tree with schroot autopkgtest -B . -- schroot unstable-amd64 ``` ### Lintian Check ```bash lintian --fail-on warning --display-info ../mini-init-asm_*.deb ``` ### Supported Architectures Currently supported: **amd64**, **arm64** Other architectures are not supported due to the assembly implementation. --- ## Security notes - No privilege dropping, seccomp profiles, or capabilities tuning are implemented here. - Intended as a **small, auditable entrypoint** that you combine with higher-level policies (cgroups, seccomp, AppArmor/SELinux, etc.) at the orchestrator / image level. --- ## Credits - Inspired by years of using [Tini](https://github.com/krallin/tini) as a tiny init in containers. - Assembler style: NASM (SysV ABI) and GNU AS (AArch64). mini-init-asm-0.3.1+ds/ROADMAP.md000066400000000000000000000032661514655602300162130ustar00rootroot00000000000000# Roadmap This document tracks planned features and improvements for `mini-init-asm`. ## Recently completed - See CHANGELOG.md ## Short-term (Next Release) ### Possible next steps - Native ARM64 validation (real hardware or full-system QEMU) for the normal epoll/signalfd path in CI (not only fallback smoke). - Consider optional `EP_SUBREAPER_WAIT=1` (wait for adopted children after main child exit) and document tradeoffs. - Consider `EP_RESTART_ON_NONZERO_EXIT=1` (opt-in) if restart-on-crash should include nonzero normal exits. - Clamp `EP_MAX_RESTARTS` to a sane upper bound to avoid pathological loops. - Continue improving diagnostics while keeping pure-syscall design (e.g., log child PGID, kill/escalation decisions). ### Arm64 tests on linux - QEMU user-mode remains flaky for the full init loop on some hosts (hang/SIGILL under `qemu-aarch64-static`). - `EP_ARM64_FALLBACK=1` provides a wait4-only smoke test; `scripts/test_harness_arm64.sh` treats SIGILL/timeout as a non-fatal skip in fallback mode. - CI today: - always cross-builds arm64 and runs the QEMU-user fallback smoke; - additionally runs native ARM64 tests on GitHub-hosted ARM runners (subject to runner availability). - Next: add a higher-confidence arm64 CI lane (preferred order): 1) always-on native ARM64 runner, or 2) full-system emulation integration tests (`qemu-system-aarch64`), or 3) pin/upgrade QEMU-user and expand fallback coverage if full-loop remains unstable. --- ## Medium-term ### Enhanced Diagnostics - Add structured logging (JSON output option) - Performance metrics (signal delivery latency, grace period accuracy) - Health check endpoint (optional HTTP server on unix socket) ## Long-term --- mini-init-asm-0.3.1+ds/VERSION000066400000000000000000000000051514655602300156420ustar00rootroot000000000000000.3.1mini-init-asm-0.3.1+ds/docker/000077500000000000000000000000001514655602300160465ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/docker/Dockerfile000066400000000000000000000005261514655602300200430ustar00rootroot00000000000000# builder FROM debian:stable-slim AS builder RUN apt-get update && apt-get install -y --no-install-recommends nasm make binutils && rm -rf /var/lib/apt/lists/* WORKDIR /src COPY . . RUN make && strip build/mini-init-amd64 # runtime FROM scratch COPY --from=builder /src/build/mini-init-amd64 /mini-init-amd64 ENTRYPOINT ["/mini-init-amd64"] mini-init-asm-0.3.1+ds/docker/Dockerfile.multiarch000066400000000000000000000020431514655602300220260ustar00rootroot00000000000000# Multi-architecture Dockerfile for mini-init # Build with: docker buildx build --platform linux/amd64,linux/arm64 -t mini-init:latest -f docker/Dockerfile.multiarch . # Buildx will build separate images for each platform automatically # Builder stage FROM --platform=$BUILDPLATFORM debian:stable-slim AS builder ARG TARGETPLATFORM ARG BUILDPLATFORM RUN apt-get update && apt-get install -y --no-install-recommends \ nasm make binutils \ $(if [ "$TARGETPLATFORM" = "linux/arm64" ]; then echo "gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu"; fi) && \ rm -rf /var/lib/apt/lists/* WORKDIR /src COPY . . # Build for the target platform RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ make build-arm64 && \ aarch64-linux-gnu-strip build/mini-init-arm64 && \ cp build/mini-init-arm64 /mini-init; \ else \ make && \ strip build/mini-init-amd64 && \ cp build/mini-init-amd64 /mini-init; \ fi # Runtime stage FROM scratch COPY --from=builder /mini-init /mini-init ENTRYPOINT ["/mini-init"] mini-init-asm-0.3.1+ds/include/000077500000000000000000000000001514655602300162225ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/include/macros.inc000066400000000000000000000043561514655602300202110ustar00rootroot00000000000000; Common macros & helpers for Linux x86-64 syscalls (NASM) extern write_all %macro SYSCALL 1 mov rax, %1 syscall %endmacro ; rdi = fd, rsi = ptr, rdx = len %macro WRITE 3 mov rdi, %1 mov rsi, %2 mov rdx, %3 SYSCALL SYS_write %endmacro ; Exit with code in rdi %macro EXIT 1 mov rdi, %1 SYSCALL SYS_exit %endmacro ; Conditional stderr logger (requires extern g_verbose in the using module) ; usage: LOG msg_ptr, msg_len %macro LOG 2 push rdi push rsi push rdx cmp qword [rel g_verbose], 0 je %%done call get_timestamp_ptr mov rdi, 2 mov rsi, rax mov rdx, 18 call write_all mov rdi, 2 mov rsi, %1 mov rdx, %2 call write_all %%done: pop rdx pop rsi pop rdi %endmacro ; set bit for signal number in sigset (sig-1) ; in: rdi = &sigset (128 bytes), rsi = sig set_sig_bit: push rax push rcx push rdx mov eax, esi sub eax, 1 ; zero-based mov ecx, eax and ecx, 63 ; bit index shr eax, 6 ; qword index mov rdx, 1 mov cl, cl shl rdx, cl mov rcx, rax shl rcx, 3 ; *8 or [rdi + rcx], rdx pop rdx pop rcx pop rax ret ; parse decimal env var value (digits only) ; in: rsi = ptr to string (digits), out: rax = value parse_u64_dec: xor rax, rax .loop: mov bl, [rsi] cmp bl, 0 je .done cmp bl, '0' jb .done cmp bl, '9' ja .done imul rax, rax, 10 movzx rbx, bl sub rbx, '0' add rax, rbx inc rsi jmp .loop .done: ret ; strict parse decimal u64 (digits only, non-empty, full-string) ; in: rsi = ptr to NUL-terminated string ; out: rax = value (undefined if rdx=0), rdx = 1 if valid else 0 parse_u64_dec_checked: xor rax, rax xor r11, r11 ; ok=0 mov r8b, [rsi] test r8b, r8b je .bad .loop_checked: mov r8b, [rsi] test r8b, r8b je .ok cmp r8b, '0' jb .bad cmp r8b, '9' ja .bad mov r11, 1 mov rcx, 10 mul rcx ; rdx:rax = rax*10 test rdx, rdx jne .bad movzx rcx, r8b sub rcx, '0' add rax, rcx jc .bad inc rsi jmp .loop_checked .ok: mov rdx, r11 ret .bad: xor rdx, rdx ret mini-init-asm-0.3.1+ds/include/macros_arm64.inc000066400000000000000000000017161514655602300212170ustar00rootroot00000000000000// Common macros & helpers for Linux AArch64 syscalls .extern write_all .macro SYSCALL num mov x8, \num svc #0 .endm .macro WRITE fd, ptr, len mov x0, \fd mov x1, \ptr mov x2, \len SYSCALL SYS_write .endm .macro EXIT code mov x0, \code SYSCALL SYS_exit .endm .macro LOG msg_ptr, msg_len stp x0, x1, [sp, #-16]! stp x2, x3, [sp, #-16]! str x30, [sp, #-16]! adrp x3, g_verbose add x3, x3, :lo12:g_verbose ldr x3, [x3] cbz x3, .Llog_done\@ bl get_timestamp_ptr mov x1, x0 mov x0, #2 mov x2, #18 bl write_all mov x0, #2 adrp x1, \msg_ptr add x1, x1, :lo12:\msg_ptr mov x2, #\msg_len bl write_all // newline (keep stack 16-byte aligned) sub sp, sp, #16 mov w4, #'\n' strb w4, [sp] mov x0, #2 mov x1, sp mov x2, #1 bl write_all add sp, sp, #16 .Llog_done\@: ldr x30, [sp], #16 ldp x2, x3, [sp], #16 ldp x0, x1, [sp], #16 .endm mini-init-asm-0.3.1+ds/include/syscalls_aarch64.inc000066400000000000000000000033121514655602300220610ustar00rootroot00000000000000// Linux AArch64 syscall numbers (subset) .equ SYS_read, 63 .equ SYS_write, 64 .equ SYS_close, 57 .equ SYS_execve, 221 .equ SYS_clone, 220 .equ SYS_wait4, 260 .equ SYS_kill, 129 .equ SYS_setpgid, 154 .equ SYS_setsid, 157 .equ SYS_rt_sigprocmask, 135 .equ SYS_nanosleep, 101 .equ SYS_signalfd4, 74 .equ SYS_epoll_create1, 20 .equ SYS_epoll_ctl, 21 .equ SYS_epoll_pwait, 22 .equ SYS_timerfd_create, 85 .equ SYS_timerfd_settime, 86 .equ SYS_exit, 93 .equ SYS_exit_group, 94 .equ SYS_clock_gettime, 113 .equ SYS_prctl, 167 // epoll/op flags .equ EPOLL_CTL_ADD, 1 .equ EPOLLIN, 1 .equ EPOLL_CLOEXEC, 02000000 // sigprocmask how .equ SIG_BLOCK, 0 .equ SIG_UNBLOCK, 1 .equ SIG_SETMASK, 2 // signal numbers (same as generic) .equ SIGHUP, 1 .equ SIGINT, 2 .equ SIGQUIT, 3 .equ SIGILL, 4 .equ SIGTRAP, 5 .equ SIGABRT, 6 .equ SIGBUS, 7 .equ SIGFPE, 8 .equ SIGKILL, 9 .equ SIGUSR1, 10 .equ SIGSEGV, 11 .equ SIGUSR2, 12 .equ SIGPIPE, 13 .equ SIGALRM, 14 .equ SIGTERM, 15 .equ SIGCHLD, 17 .equ SIGCONT, 18 .equ SIGSTOP, 19 .equ SIGTSTP, 20 .equ SIGTTIN, 21 .equ SIGTTOU, 22 .equ SIGWINCH, 28 // NOTE: do not hardcode SIGRTMIN/SIGRTMAX (see signal(7)); they vary at runtime in libc. .equ KERNEL_SIGMAX, 64 // errno subset .equ ESRCH, 3 .equ EINTR, 4 .equ EAGAIN, 11 // timerfd/signalfd flags .equ TFD_CLOEXEC, 02000000 .equ TFD_NONBLOCK, 00004000 .equ SFD_CLOEXEC, 02000000 .equ SFD_NONBLOCK, 00004000 // clock ids .equ CLOCK_REALTIME, 0 .equ CLOCK_MONOTONIC, 1 // epoll helpers .equ EPOLL_EVENT_SIZE, 16 // prctl options .equ PR_SET_CHILD_SUBREAPER, 36 mini-init-asm-0.3.1+ds/include/syscalls_amd64.inc000066400000000000000000000034711514655602300215520ustar00rootroot00000000000000; Linux x86-64 syscall numbers (subset) %define SYS_read 0 %define SYS_write 1 %define SYS_close 3 %define SYS_execve 59 %define SYS_fork 57 %define SYS_wait4 61 %define SYS_kill 62 %define SYS_setpgid 109 %define SYS_setsid 112 %define SYS_rt_sigprocmask 14 %define SYS_nanosleep 35 %define SYS_signalfd4 289 %define SYS_epoll_create1 291 %define SYS_epoll_ctl 233 %define SYS_epoll_pwait 281 %define SYS_timerfd_create 283 %define SYS_timerfd_settime 286 %define SYS_clock_gettime 228 %define SYS_prctl 157 %define SYS_exit 60 ; epoll/op flags %define EPOLL_CTL_ADD 1 %define EPOLLIN 1 %define EPOLL_CLOEXEC 02000000o ; sigprocmask how %define SIG_BLOCK 0 %define SIG_UNBLOCK 1 %define SIG_SETMASK 2 ; common signals (x86/amd64 mapping) %define SIGHUP 1 %define SIGINT 2 %define SIGQUIT 3 %define SIGILL 4 %define SIGTRAP 5 %define SIGABRT 6 %define SIGBUS 7 %define SIGFPE 8 %define SIGKILL 9 %define SIGUSR1 10 %define SIGSEGV 11 %define SIGUSR2 12 %define SIGPIPE 13 %define SIGALRM 14 %define SIGTERM 15 %define SIGCHLD 17 %define SIGCONT 18 %define SIGSTOP 19 %define SIGTSTP 20 %define SIGTTIN 21 %define SIGTTOU 22 %define SIGWINCH 28 ; NOTE: do not hardcode SIGRTMIN/SIGRTMAX (see signal(7)); they vary at runtime in libc. %define KERNEL_SIGMAX 64 ; errno subset %define ESRCH 3 %define EINTR 4 %define EAGAIN 11 ; timerfd/signalfd flags (octal per manpages) %define TFD_CLOEXEC 02000000o %define TFD_NONBLOCK 00004000o %define SFD_CLOEXEC 02000000o %define SFD_NONBLOCK 00004000o ; clock ids %define CLOCK_REALTIME 0 %define CLOCK_MONOTONIC 1 ; epoll helpers %define EPOLL_EVENT_SIZE 16 ; prctl options %define PR_SET_CHILD_SUBREAPER 36 mini-init-asm-0.3.1+ds/include/version_amd64.inc000066400000000000000000000002021514655602300213670ustar00rootroot00000000000000; Auto-generated version file version_msg_str: db "mini-init-amd64 0.3.1", 10, 0 version_msg_len_val: equ $ - version_msg_str - 1 mini-init-asm-0.3.1+ds/include/version_arm64.inc000066400000000000000000000002101514655602300214040ustar00rootroot00000000000000// Auto-generated version file version_msg_arm64: .asciz "mini-init-arm64 0.3.1 " .equ version_msg_arm64_len, . - version_msg_arm64 - 1 mini-init-asm-0.3.1+ds/man/000077500000000000000000000000001514655602300153525ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/man/mini-init-asm.1000066400000000000000000000110471514655602300201120ustar00rootroot00000000000000.TH MINI-INIT-ASM 1 2026-01-11 "mini-init-asm 0.3.1" "User Commands" .SH NAME mini-init-asm \- tiny PID 1 init for containers (pure assembly) .SH SYNOPSIS .B mini-init-asm [\fB\-v\fR|\fB\-\-verbose\fR] [\fB\-V\fR|\fB\-\-version\fR] \fB\-\-\fR \fIcommand\fR [\fIargs...\fR] .SH DESCRIPTION .B mini-init-asm is a tiny init intended for container entrypoints. It spawns exactly one main child process in a new session and process group, forwards signals to the entire group, and reaps children. It optionally supports subreaper mode and a simple restart-on-crash loop. .PP Upstream builds currently produce architecture-named binaries (\fBmini-init-amd64\fR and \fBmini-init-arm64\fR). Debian packaging typically installs the active architecture binary as .BR mini-init-asm (1). .SH OPTIONS .TP .BR \-v ", " \-\-verbose Enable verbose diagnostics on stderr. .TP .BR \-V ", " \-\-version Print version and exit. .SH ENVIRONMENT All numeric environment variables are parsed as non-negative decimal integers. Invalid values are ignored (defaults apply). .TP .B EP_GRACE_SECONDS Grace period (seconds) after the first shutdown signal (TERM/INT/HUP/QUIT) before escalating to SIGKILL. Default: 10. .TP .B EP_SIGNALS Comma-separated list of additional signals to monitor and forward. Tokens are trimmed for surrounding spaces. .IP Supported named tokens: USR1, USR2, PIPE, WINCH, TTIN, TTOU, CONT, ALRM .IP Supported numeric tokens: decimal signal numbers 1..64 (SIGKILL and SIGSTOP are ignored). .IP Supported real-time tokens: RT1..RT30 (maps to \fISIGRTMIN\fR+N), only if \fBEP_SIGRTMIN\fR and \fBEP_SIGRTMAX\fR are set and valid. .IP Unknown tokens are ignored with a warning in verbose mode. .TP .B EP_SIGRTMIN Decimal runtime value of SIGRTMIN for the target libc environment. Required to enable RT* tokens in EP_SIGNALS. .TP .B EP_SIGRTMAX Decimal runtime value of SIGRTMAX for the target libc environment. Required to enable RT* tokens in EP_SIGNALS. .IP Rationale: per \fBsignal\fR(7), SIGRTMIN/SIGRTMAX can vary at runtime in some libcs (e.g. glibc with threads), so hard-coded RT numbering is unsafe. .TP .B EP_SUBREAPER If set to 1, enable \fBPR_SET_CHILD_SUBREAPER\fR so that \fBmini-init-asm\fR adopts orphaned descendants. The init still exits when the main child exits. Default: 0. .TP .B EP_EXIT_CODE_BASE Base exit code for signal deaths: \fIexit_code = base + signal_number\fR. Default: 128 (shell-compatible). Valid range: 0..255. .TP .B EP_RESTART_ENABLED If set to 1, restart the child only if it died due to a signal and shutdown has not started. Default: 0. .TP .B EP_MAX_RESTARTS Maximum number of restarts when restart is enabled. 0 means unlimited. Default: 0. .TP .B EP_RESTART_BACKOFF_SECONDS Seconds to wait before restarting after a crash. 0 restarts immediately. Default: 1. .TP .B EP_ARM64_FALLBACK ARM64/QEMU workaround: if set to 1, skip the epoll/signalfd loop and use a simpler wait4-only path. This mode primarily validates spawn and exit-code propagation. Default: 0. .SH SIGNAL FORWARDING The child is started as a new session leader and process group leader. When a signal is received, \fBmini-init-asm\fR forwards it to the entire child process group via \fBkill\fR(2) with a negative pid (\fIkill(-pgid, sig)\fR). .PP On the first shutdown signal (TERM/INT/HUP/QUIT), a grace timer is armed; when it expires and the child group is still present, SIGKILL is sent to the group. .SH EXIT STATUS .IP \[bu] 2 If the child exits normally, \fBmini-init-asm\fR exits with the child exit status. .IP \[bu] 2 If the child dies due to a signal, \fBmini-init-asm\fR exits with \fBEP_EXIT_CODE_BASE\fR + signal number. .IP \[bu] 2 If \fBmini-init-asm\fR escalates to SIGKILL after the grace period, it exits as if the child died to SIGKILL. .SH EXAMPLES .TP Run a command under the init: .IP .nf mini-init-asm \-\- /bin/sh \-c 'echo hello; sleep 5' .fi .TP Forward an additional numeric signal (SIGTRAP=5): .IP .nf EP_SIGNALS=5 mini-init-asm \-\- /bin/sh \-c 'trap "echo got TRAP; exit 0" TRAP; sleep 1000' .fi .TP Enable RT tokens (values must match the target libc runtime): .IP .nf EP_SIGRTMIN=34 EP_SIGRTMAX=64 EP_SIGNALS=RT1,RT5 mini-init-asm \-\- /bin/sh \-c 'sleep 1000' .fi .TP Restart-on-crash with backoff: .IP .nf EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=5 EP_RESTART_BACKOFF_SECONDS=2 \\ mini-init-asm \-\- ./your-app .fi .SH NOTES .IP \[bu] 2 To discover SIGRTMIN/SIGRTMAX inside an image, one option is: .IP .nf python3 \-c 'import signal; print(signal.SIGRTMIN, signal.SIGRTMAX)' .fi .SH SEE ALSO .BR signal (7), .BR signalfd (2), .BR epoll (7), .BR timerfd_create (2), .BR prctl (2), .BR kill (2), .BR wait4 (2) mini-init-asm-0.3.1+ds/scripts/000077500000000000000000000000001514655602300162665ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/scripts/fixtures/000077500000000000000000000000001514655602300201375ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/scripts/fixtures/sleeper.sh000066400000000000000000000001601514655602300221270ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail trap 'echo "got TERM"; exit 0' TERM INT HUP QUIT ( sleep 90 ) & sleep 90 mini-init-asm-0.3.1+ds/scripts/fixtures/trap_exit0.sh000066400000000000000000000001421514655602300225470ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail trap 'exit 0' TERM INT HUP QUIT while :; do sleep 5 done mini-init-asm-0.3.1+ds/scripts/test_diagnostics.sh000066400000000000000000000015601514655602300221720ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail BIN="${1:-./build/mini-init-amd64}" echo "[test] Diagnostics/verbose logging checks" tmp="$(mktemp)" tmp2="$(mktemp)" cleanup() { rm -f "$tmp" "$tmp2" } trap cleanup EXIT echo "[test] 1) Logs include signal and grace_seconds" EP_GRACE_SECONDS=2 "$BIN" -v -- /bin/bash scripts/fixtures/trap_exit0.sh 2>"$tmp" & pid=$! sleep 0.5 kill -TERM "$pid" set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 grep -q "DEBUG: signal=" "$tmp" grep -q "DEBUG: grace_seconds=" "$tmp" echo "[test] 2) Logs include restart_count on restart" set +e EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=1 EP_RESTART_BACKOFF_SECONDS=0 \ "$BIN" -v -- /bin/sh -c "kill -SEGV \$\$" 2>"$tmp2" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 139 grep -q "DEBUG: restart_count=" "$tmp2" echo "[test] Diagnostics tests passed" mini-init-asm-0.3.1+ds/scripts/test_edge_cases.sh000077500000000000000000000073061514655602300217540ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # Integration tests for edge cases: rapid signal bursts, orphaned processes BIN="${1:-./build/mini-init-amd64}" echo "[test] Edge case integration tests" # Test 1: Rapid signal bursts (send multiple TERM signals quickly) echo "[test] 1) Rapid signal bursts (TERM x5)" EP_GRACE_SECONDS=5 "$BIN" -v -- /bin/sh -c 'trap "exit 0" TERM; sleep 1000' & pid=$! sleep 0.5 # Send 5 TERM signals rapidly for _ in {1..5}; do kill -TERM "$pid" 2>/dev/null || true sleep 0.1 done set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 || { echo "FAIL: Rapid signal bursts not handled correctly" exit 1 } # Test 2: Orphaned process (child forks grandchild, parent exits) echo "[test] 2) Orphaned process handling" orphan_script=$( cat <<'EOF' # Fork a background process that will become orphaned (sleep 2; echo "orphan done"; exit 42) & orphan_pid=$! # Exit immediately, leaving the orphan exit 0 EOF ) EP_SUBREAPER=1 "$BIN" -v -- /bin/sh -c "$orphan_script" & init_pid=$! set +e wait "$init_pid" init_rc=$? set -e echo "[test] init rc=$init_rc" # Note: even in subreaper mode, this init exits when the main child exits; subreaper only affects adoption/reaping. test "$init_rc" -eq 0 || { echo "FAIL: Orphaned process not handled" exit 1 } # Test 3: Multiple rapid signals of different types echo "[test] 3) Mixed rapid signals (TERM, INT, HUP)" EP_GRACE_SECONDS=5 "$BIN" -v -- /bin/sh -c 'trap "exit 0" TERM INT HUP; sleep 1000' & pid=$! sleep 0.5 kill -TERM "$pid" 2>/dev/null || true sleep 0.1 kill -INT "$pid" 2>/dev/null || true sleep 0.1 kill -HUP "$pid" 2>/dev/null || true set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 || { echo "FAIL: Mixed rapid signals not handled" exit 1 } # Test 4: Signal during grace period (should not escalate) echo "[test] 4) Signal during grace period" EP_GRACE_SECONDS=3 "$BIN" -v -- /bin/sh -c 'trap "exit 0" TERM; sleep 1000' & pid=$! sleep 0.5 kill -TERM "$pid" 2>/dev/null || true sleep 1 # Send another TERM during grace period kill -TERM "$pid" 2>/dev/null || true set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 || { echo "FAIL: Signal during grace period not handled" exit 1 } # Test 5: Child exits immediately after signal (opportunistic reap) echo "[test] 5) Child exits immediately after signal" EP_GRACE_SECONDS=5 "$BIN" -v -- /bin/sh -c 'trap "exit 0" TERM; sleep 1000' & pid=$! sleep 0.5 kill -TERM "$pid" 2>/dev/null || true set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 || { echo "FAIL: Immediate exit after signal not handled" exit 1 } echo "[test] 6) Main-child exit amid many other zombies (reap correctness)" set +e zombies_script=$( cat <<'EOF' # Spawn many short-lived children that may still be zombies when the parent exits. i=0 while [ "$i" -lt 200 ]; do (exit 0) & i=$((i+1)) done exit 42 EOF ) EP_GRACE_SECONDS=5 timeout 5 "$BIN" -v -- /bin/sh -c "$zombies_script" >/dev/null 2>&1 wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 42 || { echo "FAIL: Expected init to exit 42 (main child), got $wait_rc" exit 1 } echo "[test] 7) Invalid numeric env values warn (verbose)" EP_GRACE_SECONDS=1x "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "invalid EP_GRACE_SECONDS" || { echo "FAIL: Expected warning for invalid EP_GRACE_SECONDS" exit 1 } echo "[test] 8) Overlarge grace seconds clamps (verbose)" EP_GRACE_SECONDS=9223372036854775808 "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "EP_GRACE_SECONDS too large; clamping" || { echo "FAIL: Expected clamp warning for overlarge EP_GRACE_SECONDS" exit 1 } echo "[test] All edge case tests passed" mini-init-asm-0.3.1+ds/scripts/test_ep_signals.sh000077500000000000000000000101541514655602300220110ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # Unit tests for EP_SIGNALS tokenizer/parser BIN="${1:-./build/mini-init-amd64}" echo "[test] EP_SIGNALS parser unit tests" # Test 1: Single valid token echo "[test] 1) Single token: USR1" EP_SIGNALS=USR1 "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "EP_SIGNALS parsed" || { echo "FAIL: EP_SIGNALS=USR1 not parsed" exit 1 } # Test 2: Multiple valid tokens echo "[test] 2) Multiple tokens: USR1,USR2,PIPE" EP_SIGNALS=USR1,USR2,PIPE "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "EP_SIGNALS parsed" || { echo "FAIL: Multiple tokens not parsed" exit 1 } # Test 3: Unknown token (should warn but continue) echo "[test] 3) Unknown token: INVALID" EP_SIGNALS=INVALID "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "Unknown EP_SIGNALS token" || { echo "FAIL: Unknown token not warned" exit 1 } # Test 4: Mixed valid and invalid tokens echo "[test] 4) Mixed tokens: USR1,INVALID,USR2" EP_SIGNALS=USR1,INVALID,USR2 "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "Unknown EP_SIGNALS token" || { echo "FAIL: Mixed tokens not handled" exit 1 } # Test 5: Empty EP_SIGNALS (should not error) echo "[test] 5) Empty EP_SIGNALS" EP_SIGNALS="" "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -vq "ERROR" || { echo "FAIL: Empty EP_SIGNALS caused error" exit 1 } # Test 6: All supported signals echo "[test] 6) All supported signals" output=$(EP_SIGNALS=USR1,USR2,PIPE,WINCH,TTIN,TTOU,CONT,ALRM "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1) if echo "$output" | grep -q "EP_SIGNALS parsed"; then : # Success else echo "FAIL: All signals not parsed" echo "Output was:" echo "$output" exit 1 fi # Test 7: Whitespace handling echo "[test] 7) Whitespace handling: ' USR1 , USR2 '" EP_SIGNALS=" USR1 , USR2 " "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "EP_SIGNALS parsed" || { echo "FAIL: Whitespace not handled" exit 1 } # Test 8: Very long token (should be ignored) echo "[test] 8) Very long token (should warn)" EP_SIGNALS=THISISAVERYLONGTOKENTHATEXCEEDSTHEBUFFER "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "Unknown EP_SIGNALS token" || { echo "FAIL: Long token not handled" exit 1 } # Test 9: Real-time signals (RT1, RT2, etc.) echo "[test] 9) RT tokens require explicit EP_SIGRTMIN/EP_SIGRTMAX (warns if missing)" EP_SIGNALS=RT1 "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "RT\\* EP_SIGNALS tokens require EP_SIGRTMIN" || { echo "FAIL: Expected warning for RT token without EP_SIGRTMIN/EP_SIGRTMAX" exit 1 } echo "[test] 9b) Real-time signals with explicit RT bounds: RT1,RT2,RT5" EP_SIGRTMIN=34 EP_SIGRTMAX=64 EP_SIGNALS=RT1,RT2,RT5 \ "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "EP_SIGNALS parsed" || { echo "FAIL: RT signals not parsed with EP_SIGRTMIN/EP_SIGRTMAX" exit 1 } # Test 10: Invalid RT signal (RT0, RT31, RT32) echo "[test] 10) Invalid RT signals: RT0,RT31,RT32 (should warn)" EP_SIGRTMIN=34 EP_SIGRTMAX=64 EP_SIGNALS=RT0,RT31,RT32 "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "Unknown EP_SIGNALS token" || { echo "FAIL: Invalid RT signals not handled" exit 1 } # Test 11: Mixed RT and regular signals echo "[test] 11) Mixed signals: USR1,RT1,RT5,USR2" EP_SIGRTMIN=34 EP_SIGRTMAX=64 EP_SIGNALS=USR1,RT1,RT5,USR2 "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "EP_SIGNALS parsed" || { echo "FAIL: Mixed RT and regular signals not parsed" exit 1 } echo "[test] 11b) Invalid RT suffix: RT5X (should warn)" EP_SIGRTMIN=34 EP_SIGRTMAX=64 EP_SIGNALS=RT5X "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -q "Unknown EP_SIGNALS token" || { echo "FAIL: Invalid RT suffix not warned" exit 1 } echo "[test] 12) No EP_SIGNALS should not log parse" output=$("$BIN" -v -- /bin/sh -c 'exit 0' 2>&1) if echo "$output" | grep -q "EP_SIGNALS parsed"; then echo "FAIL: EP_SIGNALS parsed log should not appear when unset" exit 1 fi echo "[test] 13) EP_SIGNALS parsed log appears once" count=$(EP_SIGNALS=USR1 "$BIN" -v -- /bin/sh -c 'exit 0' 2>&1 | grep -c "EP_SIGNALS parsed") if [ "$count" -ne 1 ]; then echo "FAIL: Expected one EP_SIGNALS parsed log, got $count" exit 1 fi echo "[test] All EP_SIGNALS parser tests passed" mini-init-asm-0.3.1+ds/scripts/test_exit_code_mapping.sh000077500000000000000000000046031514655602300233450ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # Tests for configurable signal-to-exit-code mapping BIN="${1:-./build/mini-init-amd64}" echo "[test] Exit code mapping tests" # Test 1: Default behavior (base=128) echo "[test] 1) Default exit code base (128)" EP_GRACE_SECONDS=1 "$BIN" -v -- /bin/sh -c 'trap "" TERM; sleep 99' & pid=$! sleep 0.5 kill -TERM "$pid" 2>/dev/null || true set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 137 || { echo "FAIL: Expected 137 (128+9), got $wait_rc" exit 1 } # Test 2: Custom exit code base echo "[test] 2) Custom exit code base (200)" EP_EXIT_CODE_BASE=200 EP_GRACE_SECONDS=1 "$BIN" -v -- /bin/sh -c 'trap "" TERM; sleep 99' & pid=$! sleep 0.5 kill -TERM "$pid" 2>/dev/null || true set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 209 || { echo "FAIL: Expected 209 (200+9), got $wait_rc" exit 1 } # Test 3: Normal exit (should not be affected by base) echo "[test] 3) Normal exit (unaffected by base)" EP_EXIT_CODE_BASE=200 "$BIN" -v -- /bin/sh -c 'exit 42' & pid=$! set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 42 || { echo "FAIL: Expected 42, got $wait_rc" exit 1 } # Test 4: Signal exit with custom base echo "[test] 4) Signal exit with custom base (TERM = 15)" EP_EXIT_CODE_BASE=100 "$BIN" -v -- /bin/sh -c 'trap "exit 0" TERM; sleep 1000' & pid=$! sleep 0.5 kill -TERM "$pid" 2>/dev/null || true set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 || { echo "FAIL: Expected 0 (graceful exit), got $wait_rc" exit 1 } echo "[test] 5) Invalid EP_EXIT_CODE_BASE keeps default (128)" EP_EXIT_CODE_BASE=200x EP_GRACE_SECONDS=1 "$BIN" -v -- /bin/sh -c 'trap "" TERM; sleep 99' & pid=$! sleep 0.5 kill -TERM "$pid" 2>/dev/null || true set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 137 || { echo "FAIL: Expected 137 (default base), got $wait_rc" exit 1 } echo "[test] 6) Out-of-range EP_EXIT_CODE_BASE keeps default (128)" EP_EXIT_CODE_BASE=999 EP_GRACE_SECONDS=1 "$BIN" -v -- /bin/sh -c 'trap "" TERM; sleep 99' & pid=$! sleep 0.5 kill -TERM "$pid" 2>/dev/null || true set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 137 || { echo "FAIL: Expected 137 (default base), got $wait_rc" exit 1 } echo "[test] All exit code mapping tests passed" mini-init-asm-0.3.1+ds/scripts/test_harness.sh000066400000000000000000000032311514655602300213230ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail BIN="${1:-./build/mini-init-amd64}" echo "[test] 1) Graceful termination" set -m EP_GRACE_SECONDS=5 "$BIN" -v -- /bin/bash scripts/fixtures/trap_exit0.sh & pid=$! sleep 1 if ! kill -0 "$pid" 2>/dev/null; then echo "[test] ERROR: init exited before TERM (pid=$pid)" wait "$pid" || true exit 1 fi kill -TERM "$pid" set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" # our fixture exits 0 on TERM test "$wait_rc" -eq 0 echo "[test] 2) Escalation after grace (app ignores TERM)" EP_GRACE_SECONDS=1 "$BIN" -v -- /bin/bash -c 'trap "" TERM INT HUP QUIT; while :; do sleep 5; done' & pid=$! sleep 1 if ! kill -0 "$pid" 2>/dev/null; then echo "[test] ERROR: init exited before TERM (pid=$pid)" wait "$pid" || true exit 1 fi kill -TERM "$pid" set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 137 echo "[test] OK (got 137)" echo "[test] 3) Forward custom EP_SIGNALS=USR1" EP_SIGNALS=USR1 "$BIN" -v -- /bin/bash -c 'trap "echo got USR1; exit 0" USR1; sleep 99' & pid=$! sleep 1 if ! kill -0 "$pid" 2>/dev/null; then echo "[test] ERROR: init exited before USR1 (pid=$pid)" wait "$pid" || true exit 1 fi kill -USR1 "$pid" set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 echo "[test] 4) Forward numeric EP_SIGNALS=5 (SIGTRAP)" EP_SIGNALS=5 "$BIN" -v -- /bin/sh -c 'trap "echo got TRAP; exit 0" TRAP; sleep 99' & pid=$! sleep 1 if ! kill -0 "$pid" 2>/dev/null; then echo "[test] ERROR: init exited before TRAP (pid=$pid)" wait "$pid" || true exit 1 fi kill -TRAP "$pid" set +e wait "$pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 mini-init-asm-0.3.1+ds/scripts/test_harness_arm64.sh000077500000000000000000000110731514655602300223420ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # Smoke test for ARM64 binary using QEMU user emulation # Requires: qemu-user-static or qemu-aarch64-static BIN="${1:-./build/mini-init-arm64}" QEMU="${QEMU:-qemu-aarch64-static}" ARM64_FALLBACK="${ARM64_FALLBACK:-${EP_ARM64_FALLBACK:-0}}" # Check if QEMU is available if ! command -v "$QEMU" &> /dev/null; then echo "[test] ERROR: $QEMU not found. Install qemu-user-static or qemu-aarch64-static" echo "[test] Debian/Ubuntu: sudo apt-get install -y qemu-user-static" exit 1 fi # Verify binary exists and is ARM64 if [ ! -f "$BIN" ]; then echo "[test] ERROR: Binary not found: $BIN" exit 1 fi if ! file "$BIN" | grep -q "ARM aarch64"; then echo "[test] ERROR: Binary is not ARM64: $BIN" file "$BIN" exit 1 fi echo "[test] ARM64 smoke test using $QEMU" echo "[test] Binary: $BIN" # Sanity: verify helpers exist and are ARM64, and QEMU can run them standalone if [ ! -f "build/arm64/helper-exit42" ] || [ ! -f "build/arm64/helper-sleeper" ]; then echo "[test] ERROR: Helper binaries not found. Run: make build-arm64" exit 1 fi if ! file "build/arm64/helper-exit42" | grep -q "ARM aarch64"; then echo "[test] ERROR: helper-exit42 is not ARM64" file "build/arm64/helper-exit42" exit 1 fi set +e "$QEMU" -- build/arm64/helper-exit42 helper_rc=$? set -e if [ "$helper_rc" -ne 42 ]; then echo "[test] ERROR: QEMU failed to run helper-exit42 (rc=$helper_rc)" exit 1 fi # Optional: fallback mode to skip flaky QEMU epoll/signalfd path if [ "$ARM64_FALLBACK" = "1" ]; then echo "[test] WARNING: EP_ARM64_FALLBACK=1 set; running wait4-only smoke under QEMU" echo "[test] 0) Version check" set +e timeout 5s "$QEMU" -- "$BIN" --version rc=$? set -e if [ "$rc" -ne 0 ]; then echo "[test] ERROR: --version failed under QEMU (rc=$rc)" exit 1 fi echo "[test] 1) Wait4-only path (helper-exit42)" out="$(mktemp)" err="$(mktemp)" # shellcheck disable=SC2317,SC2329 # Function invoked via trap cleanup_tmp() { rm -f "$out" "$err" } trap cleanup_tmp EXIT set +e timeout 10s env EP_ARM64_FALLBACK=1 "$QEMU" -- "$BIN" -v -- ./build/arm64/helper-exit42 >"$out" 2>"$err" rc=$? set -e if [ "$rc" -eq 42 ]; then cat "$out" cat "$err" >&2 echo "[test] OK (fallback smoke passed)" exit 0 fi # QEMU user-mode is known to be unstable for this binary; treat SIGILL/timeout as a non-fatal skip in fallback mode. if [ "$rc" -eq 132 ] || [ "$rc" -eq 124 ]; then echo "[test] WARNING: QEMU-user is unstable for mini-init-arm64 even in fallback mode (rc=$rc); skipping" cat "$out" cat "$err" >&2 exit 0 fi echo "[test] ERROR: Expected exit 42, got $rc" cat "$out" cat "$err" >&2 exit 1 fi # Test 1: Basic execution (help-like check) echo "[test] 1) Basic execution" set +e timeout 10s "$QEMU" -- "$BIN" -v -- ./build/arm64/helper-exit42 rc=$? set -e if [ "$rc" -eq 42 ]; then echo "[test] OK (got exit code 42)" else echo "[test] WARNING: Expected exit 42, got $rc" fi # Test 2: Graceful termination echo "[test] 2) Graceful termination" set -m EP_GRACE_SECONDS=5 timeout 15s "$QEMU" -- "$BIN" -v -- ./build/arm64/helper-sleeper & pid=$! sleep 1 # Check if process is still running before sending signal if kill -0 "$pid" 2>/dev/null; then kill -TERM "$pid" 2>/dev/null || true else echo "[test] WARNING: Process already exited before signal" fi set +e wait "$pid" 2>/dev/null || true wait_rc=$? set -e echo "[test] rc=$wait_rc" # Under QEMU without a shell, expect signal exit code (143 for TERM) or 137 on escalation if [ "$wait_rc" -eq 143 ] || [ "$wait_rc" -eq 137 ]; then echo "[test] OK (terminated by TERM/KILL as expected: $wait_rc)" else echo "[test] INFO: Got exit code $wait_rc (may vary under QEMU)" fi # Test 3: Escalation (simplified - may not work perfectly under QEMU) echo "[test] 3) Escalation check (may be flaky under QEMU)" EP_GRACE_SECONDS=1 timeout 10s "$QEMU" -- "$BIN" -v -- ./build/arm64/helper-sleeper & pid=$! sleep 1 # Check if process is still running before sending signal if kill -0 "$pid" 2>/dev/null; then kill -TERM "$pid" 2>/dev/null || true else echo "[test] WARNING: Process already exited before signal" fi set +e wait "$pid" 2>/dev/null || true wait_rc=$? set -e echo "[test] rc=$wait_rc" if [ "$wait_rc" -eq 137 ] || [ "$wait_rc" -eq 143 ]; then echo "[test] OK (got kill signal: $wait_rc)" else echo "[test] INFO: Got exit code $wait_rc (may vary under QEMU)" fi echo "[test] ARM64 smoke test completed" mini-init-asm-0.3.1+ds/scripts/test_restart.sh000077500000000000000000000142201514655602300213470ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # Integration tests for process restart functionality BIN="${1:-./build/mini-init-amd64}" echo "[test] Restart functionality tests" # Test 1: Basic restart on crash (signal) echo "[test] 1) Basic restart on crash (SIGSEGV)" # Use a file to track crashes across restarts rm -f "/tmp/test_restart_count" # Run with timeout wrapper restart_script_1=$( cat <<'EOF' crash_count=$(cat /tmp/test_restart_count 2>/dev/null || echo "0") crash_count=$((crash_count + 1)) echo "$crash_count" > /tmp/test_restart_count if [ "$crash_count" -ge 3 ]; then rm -f /tmp/test_restart_count exit 0 fi kill -SEGV $$ EOF ) set +e timeout 10 env EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=2 EP_RESTART_BACKOFF_SECONDS=0 \ "$BIN" -v -- /bin/bash -c "$restart_script_1" >/dev/null 2>&1 wait_rc=$? set -e # Check if restart count file was cleaned up (indicates success) if [ ! -f "/tmp/test_restart_count" ] && [ "$wait_rc" -eq 0 ]; then wait_rc=0 elif [ "$wait_rc" -eq 124 ]; then echo "FAIL: Process timed out" wait_rc=124 else wait_rc=1 fi rm -f "/tmp/test_restart_count" echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 || { echo "FAIL: Basic restart not working (rc=$wait_rc)" exit 1 } # Test 2: Restart with backoff echo "[test] 2) Restart with backoff delay" start_uptime="$(awk '{print $1}' /proc/uptime)" # Child will crash immediately, then restart after 2 second backoff, then crash again # With EP_MAX_RESTARTS=1, we allow 1 restart (2 total runs) # First crash happens immediately, then backoff wait (2s), then restart, then second crash EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=1 EP_RESTART_BACKOFF_SECONDS=2 \ "$BIN" -v -- /bin/sh -c "kill -SEGV \$\$" & init_pid=$! set +e wait "$init_pid" wait_rc=$? set -e end_uptime="$(awk '{print $1}' /proc/uptime)" elapsed="$(awk -v s="$start_uptime" -v e="$end_uptime" 'BEGIN{printf "%.3f", (e-s)}')" echo "[test] rc=$wait_rc, elapsed=${elapsed}s (monotonic)" # Should take ~2 seconds due to backoff (first crash -> backoff 2s -> restart -> second crash). # Use monotonic /proc/uptime and allow some scheduling jitter. awk -v s="$start_uptime" -v e="$end_uptime" 'BEGIN{exit !((e-s) >= 1.8)}' || { echo "FAIL: Backoff not working (elapsed=${elapsed}s, expected around 2s)" exit 1 } # Should exit with SIGSEGV code (139) after max restarts test "$wait_rc" -eq 139 || { echo "FAIL: Expected exit code 139 after max restarts, got $wait_rc" exit 1 } # Test 3: Max restarts limit echo "[test] 3) Max restarts limit" # Child will crash immediately, restart 2 times (max_restarts=2), then exit with SIGSEGV code EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=2 EP_RESTART_BACKOFF_SECONDS=0 \ "$BIN" -v -- /bin/sh -c "kill -SEGV \$\$" & init_pid=$! set +e wait "$init_pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" # Should exit with signal exit code (128 + 11 = 139 for SIGSEGV) after max restarts test "$wait_rc" -eq 139 || { echo "FAIL: Max restarts not enforced (rc=$wait_rc)" exit 1 } # Test 4: No restart on normal exit echo "[test] 4) No restart on normal exit" EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=10 EP_RESTART_BACKOFF_SECONDS=0 "$BIN" -v -- /bin/bash -c 'exit 42' & init_pid=$! set +e wait "$init_pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" # Should exit with child's exit code, not restart test "$wait_rc" -eq 42 || { echo "FAIL: Normal exit should not restart (rc=$wait_rc)" exit 1 } # Test 5: No restart when shutdown signal received echo "[test] 5) No restart when shutdown signal received" no_restart_shutdown_script=$( cat <<'EOF' trap "exit 0" TERM sleep 100 & sleep_pid=$! wait "$sleep_pid" 2>/dev/null || true EOF ) EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=10 EP_RESTART_BACKOFF_SECONDS=0 \ "$BIN" -v -- /bin/sh -c "$no_restart_shutdown_script" & init_pid=$! sleep 1 # Send TERM (shutdown signal) - should not restart kill -TERM "$init_pid" 2>/dev/null || true set +e wait "$init_pid" wait_rc=$? set -e echo "[test] rc=$wait_rc" # Should exit normally (0) because TERM was handled, not restart test "$wait_rc" -eq 0 || { echo "FAIL: Shutdown signal should prevent restart (rc=$wait_rc)" exit 1 } # Test 6: Unlimited restarts (EP_MAX_RESTARTS=0) echo "[test] 6) Unlimited restarts (EP_MAX_RESTARTS=0)" # Use a file to track crashes across restarts rm -f "/tmp/test_unlimited_count" # Run with timeout wrapper restart_script_6=$( cat <<'EOF' crash_count=$(cat /tmp/test_unlimited_count 2>/dev/null || echo "0") crash_count=$((crash_count + 1)) echo "$crash_count" > /tmp/test_unlimited_count if [ "$crash_count" -ge 3 ]; then rm -f /tmp/test_unlimited_count exit 0 fi kill -SEGV $$ EOF ) set +e timeout 10 env EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=0 EP_RESTART_BACKOFF_SECONDS=0 \ "$BIN" -v -- /bin/sh -c "$restart_script_6" >/dev/null 2>&1 wait_rc=$? set -e # Check if restart count file was cleaned up (indicates success) if [ ! -f "/tmp/test_unlimited_count" ] && [ "$wait_rc" -eq 0 ]; then wait_rc=0 elif [ "$wait_rc" -eq 124 ]; then echo "FAIL: Process timed out" wait_rc=124 else wait_rc=1 fi rm -f "/tmp/test_unlimited_count" echo "[test] rc=$wait_rc" test "$wait_rc" -eq 0 || { echo "FAIL: Unlimited restarts not working (rc=$wait_rc)" exit 1 } echo "[test] 7) Shutdown during restart backoff prevents restart (exits promptly)" tmp_backoff_log="$(mktemp)" trap 'rm -f "$tmp_backoff_log"' EXIT EP_RESTART_ENABLED=1 EP_MAX_RESTARTS=0 EP_RESTART_BACKOFF_SECONDS=5 EP_GRACE_SECONDS=5 \ "$BIN" -v -- /bin/sh -c "kill -SEGV \$\$" 2>"$tmp_backoff_log" & init_pid=$! sleep 0.5 kill -TERM "$init_pid" 2>/dev/null || true set +e timeout 3s bash -c "while kill -0 \"\$1\" 2>/dev/null; do sleep 0.1; done" _ "$init_pid" poll_rc=$? wait "$init_pid" wait_rc=$? set -e if [ "$poll_rc" -eq 124 ]; then echo "FAIL: Init did not exit promptly during backoff shutdown" exit 1 fi echo "[test] rc=$wait_rc" test "$wait_rc" -eq 139 || { echo "FAIL: Expected 139 (SIGSEGV mapped) after shutdown during backoff, got $wait_rc" exit 1 } grep -q "shutdown requested during restart backoff" "$tmp_backoff_log" || { echo "FAIL: Expected log indicating restart was cancelled during backoff" echo "--- stderr ---" cat "$tmp_backoff_log" >&2 exit 1 } echo "[test] All restart tests passed!" mini-init-asm-0.3.1+ds/src/000077500000000000000000000000001514655602300153665ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/src/amd64/000077500000000000000000000000001514655602300163015ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/src/amd64/epoll.asm000066400000000000000000000055441514655602300201260ustar00rootroot00000000000000; epoll helpers: create epoll fd, add FDs with EPOLLIN, wait for one event global epoll_create_fd global epoll_add_fd global epoll_wait_once %include "macros.inc" %include "syscalls_amd64.inc" extern g_verbose extern log_prefix_num extern get_timestamp_ptr extern get_timestamp_ptr section .bss align 8 epoll_events_buf: resb EPOLL_EVENT_SIZE * 4 section .text ; rax = epoll_create1(0) epoll_create_fd: mov rdi, EPOLL_CLOEXEC SYSCALL SYS_epoll_create1 test rax, rax js .ret ; log fd number push rax mov rdx, rax lea rdi, [rel log_epfd_created_prefix] mov rsi, log_epfd_created_prefix_len call log_prefix_num pop rax .ret: ret ; epoll_add_fd(epfd, fd) ; rdi=epfd, rsi=fd epoll_add_fd: ; prepare struct epoll_event { uint32_t events; uint64_t data; } (we use 16 bytes) sub rsp, EPOLL_EVENT_SIZE mov dword [rsp], EPOLLIN ; events mov dword [rsp+4], 0 ; padding mov qword [rsp+8], rsi ; data = fd mov rdx, rsi ; arg3: fd mov rsi, EPOLL_CTL_ADD ; arg2: op mov r10, rsp ; arg4: event pointer ; SYSCALL: epoll_ctl(epfd, op, fd, event) .ctl_retry: mov rax, SYS_epoll_ctl syscall test rax, rax jns .ctl_ok mov rbx, rax neg rbx cmp rbx, EINTR je .ctl_retry LOG log_epoll_ctl_err, log_epoll_ctl_err_len mov rax, -1 jmp .ctl_out .ctl_ok: ; log added fd number (from rdx) mov rdx, rdx lea rdi, [rel log_epoll_add_prefix] mov rsi, log_epoll_add_prefix_len call log_prefix_num xor rax, rax .ctl_out: add rsp, EPOLL_EVENT_SIZE ret ; epoll_wait_once(epfd) -> rax = ready_fd (from event.data) or <0 on error epoll_wait_once: ; prepare buffer lea rsi, [rel epoll_events_buf] mov rdx, 4 ; maxevents mov r10, -1 ; timeout = -1 (block) xor r8, r8 ; sigmask = NULL xor r9, r9 ; sigsetsize = 0 .wait_retry: mov rax, SYS_epoll_pwait syscall test rax, rax jns .count_ok mov rbx, rax neg rbx cmp rbx, EINTR je .wait_retry LOG log_epoll_wait_err, log_epoll_wait_err_len jmp .err .count_ok: ; rax = number of events cmp rax, 1 jl .err ; read data of first event lea rbx, [rel epoll_events_buf] mov rax, [rbx+8] ; data (u64) at offset 8 ret .err: mov rax, -1 ret section .rodata log_epfd_created_prefix: db "DEBUG: epoll fd created fd=", 0 log_epfd_created_prefix_len: equ $ - log_epfd_created_prefix - 1 log_epoll_add_prefix: db "DEBUG: added FD to epoll fd=", 0 log_epoll_add_prefix_len: equ $ - log_epoll_add_prefix - 1 log_epoll_ctl_err: db "ERROR: epoll_ctl failed", 10 log_epoll_ctl_err_len: equ $ - log_epoll_ctl_err log_epoll_wait_err: db "ERROR: epoll_pwait failed", 10 log_epoll_wait_err_len: equ $ - log_epoll_wait_err mini-init-asm-0.3.1+ds/src/amd64/forward.asm000066400000000000000000000005761514655602300204570ustar00rootroot00000000000000; forward signal to the child's process group using kill(-pgid, sig) global forward_signal_to_group %include "macros.inc" %include "syscalls_amd64.inc" section .text ; in: rdi = child_pgid, rsi = signo forward_signal_to_group: ; kill(-pgid, sig) mov rax, rdi neg rax mov rdi, rax mov rdx, rsi ; save sig in rdx mov rsi, rdx SYSCALL SYS_kill ret mini-init-asm-0.3.1+ds/src/amd64/log.asm000066400000000000000000000043711514655602300175710ustar00rootroot00000000000000; Logging helpers with decimal numbers ; ; log_prefix_num(prefix_ptr, prefix_len, value) ; rdi=prefix_ptr, rsi=prefix_len, rdx=value (u64) ; Writes: prefix, decimal(value), '\n' to stderr if g_verbose!=0 ; ; write_all(fd, buf, len) ; rdi=fd, rsi=buf, rdx=len ; Writes until all bytes are written or a non-EINTR error occurs. ; Returns rax=0 on success, or negative errno on error. ; global log_prefix_num global write_all extern g_verbose %include "syscalls_amd64.inc" section .text write_all: push rbx push r12 mov r12, rdi ; fd mov rbx, rdx ; remaining test rbx, rbx jz .ok .loop: mov rax, SYS_write mov rdi, r12 syscall cmp rax, 0 jg .wrote cmp rax, 0 je .err ; 0-byte write -> treat as error ; rax < 0 mov rcx, rax neg rcx cmp rcx, EINTR je .loop jmp .out ; return negative errno .wrote: sub rbx, rax add rsi, rax mov rdx, rbx test rbx, rbx jnz .loop .ok: xor rax, rax .out: pop r12 pop rbx ret .err: mov rax, -1 jmp .out log_prefix_num: push rbx push r12 push r13 push r14 push r15 ; check verbosity cmp qword [rel g_verbose], 0 je .out ; save args mov r14, rdi ; prefix_ptr mov r15, rsi ; prefix_len mov rbx, rdx ; value ; write prefix mov rdi, 2 mov rsi, r14 mov rdx, r15 call write_all ; convert rbx to decimal into stack buffer sub rsp, 32 lea r12, [rsp+32] ; end xor r13, r13 ; length mov rax, rbx test rax, rax jne .conv_loop ; zero special-case mov byte [r12-1], '0' lea r14, [r12-1] mov r13, 1 jmp .have_num .conv_loop: xor rdx, rdx mov r8, 10 div r8 ; rax=quot, rdx=rem add dl, '0' mov byte [r12-1], dl lea r12, [r12-1] inc r13 test rax, rax jne .conv_loop mov r14, r12 ; start ptr .have_num: ; write number mov rdi, 2 mov rsi, r14 mov rdx, r13 call write_all ; write newline mov rdi, 2 lea rsi, [rel ln_str] mov rdx, 1 call write_all add rsp, 32 .out: pop r15 pop r14 pop r13 pop r12 pop rbx ret section .rodata ln_str: db 10 mini-init-asm-0.3.1+ds/src/amd64/main.asm000066400000000000000000000557471514655602300177510ustar00rootroot00000000000000; _start: parse argv/env, spawn child in its own PGID, forward signals to group, ; reap children, graceful shutdown with escalation using timerfd + epoll. global _start global g_verbose global g_exit_code_base extern do_spawn extern forward_signal_to_group extern reap_children_nonblock extern extract_exit_code extern setup_signals_and_fd extern read_signalfd_once extern get_wait_status_ptr extern get_signalfd_fd extern epoll_create_fd extern epoll_add_fd extern epoll_wait_once extern create_grace_timerfd extern read_timerfd_tick extern get_timestamp_ptr extern log_prefix_num %include "macros.inc" %include "syscalls_amd64.inc" section .rodata usage_msg: db "usage: mini-init-amd64 [--verbose|-v] [--version|-V] -- [args...]", 10, 0 %include "version_amd64.inc" version_msg: equ version_msg_str version_msg_len: equ version_msg_len_val log_first_soft: db "DEBUG: first soft signal received", 10 log_first_soft_len: equ $ - log_first_soft log_escalate_kill: db "DEBUG: escalating to SIGKILL", 10 log_escalate_kill_len: equ $ - log_escalate_kill log_escalate_skip_dead_pgid: db "DEBUG: grace timer fired but PGID is gone; skipping SIGKILL", 10 log_escalate_skip_dead_pgid_len: equ $ - log_escalate_skip_dead_pgid log_sigchld_ok: db "DEBUG: SIGCHLD handled", 10 log_sigchld_ok_len: equ $ - log_sigchld_ok log_restart: db "DEBUG: restarting child", 10 log_restart_len: equ $ - log_restart log_max_restarts: db "DEBUG: max restarts reached, exiting", 10 log_max_restarts_len: equ $ - log_max_restarts log_backoff_wait: db "DEBUG: waiting for backoff before restart", 10 log_backoff_wait_len: equ $ - log_backoff_wait log_signal_prefix: db "DEBUG: signal=", 0 log_signal_prefix_len: equ $ - log_signal_prefix - 1 log_grace_secs_prefix: db "DEBUG: grace_seconds=", 0 log_grace_secs_prefix_len: equ $ - log_grace_secs_prefix - 1 log_restart_count_prefix: db "DEBUG: restart_count=", 0 log_restart_count_prefix_len: equ $ - log_restart_count_prefix - 1 log_restart_backoff_prefix: db "DEBUG: restart_backoff_seconds=", 0 log_restart_backoff_prefix_len: equ $ - log_restart_backoff_prefix - 1 log_debug_restart_enabled: db "DEBUG: restart_enabled=", 0 log_debug_restart_enabled_len: equ $ - log_debug_restart_enabled - 1 log_debug_wait_status: db "DEBUG: wait_status=", 0 log_debug_wait_status_len: equ $ - log_debug_wait_status - 1 log_debug_shutdown: db "DEBUG: shutdown=", 0 log_debug_shutdown_len: equ $ - log_debug_shutdown - 1 log_debug_env_check: db "DEBUG: checking env: ", 0 log_debug_env_check_len: equ $ - log_debug_env_check - 1 log_debug_value: db "DEBUG: value=", 0 log_debug_value_len: equ $ - log_debug_value - 1 log_newline: db 10 log_warn_bad_grace: db "WARN: invalid EP_GRACE_SECONDS; using default", 10 log_warn_bad_grace_len: equ $ - log_warn_bad_grace log_warn_clamp_grace: db "WARN: EP_GRACE_SECONDS too large; clamping", 10 log_warn_clamp_grace_len: equ $ - log_warn_clamp_grace log_warn_bad_exit_base: db "WARN: invalid EP_EXIT_CODE_BASE; using default", 10 log_warn_bad_exit_base_len: equ $ - log_warn_bad_exit_base log_warn_range_exit_base: db "WARN: EP_EXIT_CODE_BASE out of range (0..255); using default", 10 log_warn_range_exit_base_len: equ $ - log_warn_range_exit_base log_warn_bad_max_restarts: db "WARN: invalid EP_MAX_RESTARTS; using default", 10 log_warn_bad_max_restarts_len: equ $ - log_warn_bad_max_restarts log_warn_bad_backoff: db "WARN: invalid EP_RESTART_BACKOFF_SECONDS; using default", 10 log_warn_bad_backoff_len: equ $ - log_warn_bad_backoff log_warn_clamp_backoff: db "WARN: EP_RESTART_BACKOFF_SECONDS too large; clamping", 10 log_warn_clamp_backoff_len: equ $ - log_warn_clamp_backoff log_warn_grace_timer_unavailable: db "WARN: grace timer unavailable; escalating immediately to SIGKILL", 10 log_warn_grace_timer_unavailable_len: equ $ - log_warn_grace_timer_unavailable log_warn_backoff_timer_epoll_failed: db "WARN: backoff timer could not be added to epoll; restarting immediately", 10 log_warn_backoff_timer_epoll_failed_len: equ $ - log_warn_backoff_timer_epoll_failed log_info_child_exited_ignore_signal: db "DEBUG: child already exited; ignoring non-shutdown signal during restart backoff", 10 log_info_child_exited_ignore_signal_len: equ $ - log_info_child_exited_ignore_signal log_info_child_exited_cancel_restart: db "DEBUG: shutdown requested during restart backoff; exiting without restart", 10 log_info_child_exited_cancel_restart_len: equ $ - log_info_child_exited_cancel_restart section .bss align 8 g_verbose: resq 1 g_child_pid: resq 1 g_child_exited: resq 1 g_child_status: resq 1 g_grace_secs: resq 1 g_shutdown: resq 1 g_killed: resq 1 g_epfd: resq 1 g_sfd: resq 1 g_tfd: resq 1 g_argv_exec: resq 1 g_envp: resq 1 g_exit_code_base: resq 1 g_restart_enabled: resq 1 g_max_restarts: resq 1 g_restart_count: resq 1 g_restart_backoff: resq 1 g_backoff_tfd: resq 1 section .text ; tiny strcmp (nul-terminated); rdi=a, rsi=b; returns 1 if equal str_eq: push rax .loop: mov al, [rdi] cmp al, [rsi] jne .ne test al, al je .eq inc rdi inc rsi jmp .loop .eq: pop rax mov rax, 1 ret .ne: pop rax xor rax, rax ret ; simple prefix match: rdi = "NAME=", rsi = envstr; returns rax=ptr to value or 0 prefix_match: push rbx mov rbx, rsi mov rsi, rdi mov rdi, rbx .p_loop: mov al, [rsi] cmp al, 0 je .no cmp al, [rdi] jne .no cmp al, '=' je .value inc rsi inc rdi jmp .p_loop .value: inc rdi mov rax, rdi pop rbx ret .no: xor rax, rax pop rbx ret _start: ; stack: [argc][argv...][0][envp...][0] mov rbx, rsp mov rax, [rbx] ; argc mov r12, rax lea r13, [rbx + 8] ; argv ; find envp mov rcx, r12 lea r14, [r13 + rcx*8 + 8] ; points to the NULL after argv => envp ; defaults mov qword [g_verbose], 0 mov qword [g_grace_secs], 10 mov qword [g_exit_code_base], 128 mov qword [g_restart_enabled], 0 mov qword [g_max_restarts], 0 mov qword [g_restart_count], 0 mov qword [g_restart_backoff], 1 mov qword [g_backoff_tfd], 0 ; parse argv: look for -v/--verbose and -- delimiter mov r11, 1 ; i = 1 (use r11 to avoid clobbering by calls) mov r15, 0 ; exec index found? 0=no .parse_argv: cmp r11, r12 jge .argv_done mov rax, [r13 + r11*8] ; argv[i] mov rbx, rax ; check "--" lea rsi, [rel delim_str] mov rdi, rbx call str_eq cmp rax, 1 je .found_delim ; check "-v"/"--verbose" lea rsi, [rel v1_str] mov rdi, rbx call str_eq cmp rax, 1 je .set_v lea rsi, [rel v2_str] mov rdi, rbx call str_eq cmp rax, 1 je .set_v ; check "-V"/"--version" lea rsi, [rel ver1_str] mov rdi, rbx call str_eq cmp rax, 1 je .show_version lea rsi, [rel ver2_str] mov rdi, rbx call str_eq cmp rax, 1 je .show_version jmp .next_i .show_version: mov rdi, 1 mov rsi, version_msg mov rdx, version_msg_len WRITE 1, rsi, rdx EXIT 0 .set_v: mov qword [g_verbose], 1 jmp .next_i .found_delim: ; argv_exec = &argv[i+1] lea rax, [r13 + (r11+1)*8] mov [g_argv_exec], rax jmp .argv_done .next_i: inc r11 jmp .parse_argv .argv_done: cmp qword [g_argv_exec], 0 jne .have_cmd ; print usage and exit 2 mov rdi, 2 mov rsi, usage_msg mov rdx, 60 WRITE 2, rsi, rdx EXIT 2 .have_cmd: ; envp pointer mov [g_envp], r14 ; Parse EP_GRACE_SECONDS (optional) mov rbx, r14 .find_env: mov rax, [rbx] test rax, rax je .env_done ; compare prefix EP_GRACE_SECONDS= lea rdi, [rel ep_pref] mov rsi, rax call prefix_match test rax, rax jz .next_env ; rax points to value string mov rsi, rax call parse_u64_dec_checked test rdx, rdx jz .bad_grace test rax, rax js .clamp_grace mov [g_grace_secs], rax jmp .env_done .clamp_grace: mov rax, 0x7fffffffffffffff mov [g_grace_secs], rax LOG log_warn_clamp_grace, log_warn_clamp_grace_len jmp .env_done .bad_grace: ; ignore invalid, keep default LOG log_warn_bad_grace, log_warn_bad_grace_len jmp .env_done .next_env: add rbx, 8 jmp .find_env .env_done: ; Check EP_SUBREAPER and set PR_SET_CHILD_SUBREAPER if enabled mov rbx, r14 .find_subreaper: mov rax, [rbx] test rax, rax je .subreaper_done lea rdi, [rel ep_subreaper_pref] mov rsi, rax call prefix_match test rax, rax jz .next_subreaper_env ; Check if value is "1" mov al, [rax] cmp al, '1' jne .subreaper_done mov al, [rax+1] test al, al jnz .subreaper_done ; Set PR_SET_CHILD_SUBREAPER mov rdi, PR_SET_CHILD_SUBREAPER mov rsi, 1 xor rdx, rdx xor r10, r10 xor r8, r8 SYSCALL SYS_prctl jmp .subreaper_done .next_subreaper_env: add rbx, 8 jmp .find_subreaper .subreaper_done: ; Parse EP_EXIT_CODE_BASE (optional) mov rbx, r14 .find_exit_base: mov rax, [rbx] test rax, rax je .exit_base_done lea rdi, [rel ep_exit_base_pref] mov rsi, rax call prefix_match test rax, rax jz .next_exit_base_env ; rax points to value string mov rsi, rax call parse_u64_dec_checked test rdx, rdx jz .bad_exit_base cmp rax, 255 ja .range_exit_base mov [g_exit_code_base], rax jmp .exit_base_done .range_exit_base: LOG log_warn_range_exit_base, log_warn_range_exit_base_len jmp .exit_base_done .bad_exit_base: LOG log_warn_bad_exit_base, log_warn_bad_exit_base_len jmp .exit_base_done .next_exit_base_env: add rbx, 8 jmp .find_exit_base .exit_base_done: ; Parse EP_RESTART_ENABLED (optional) mov rbx, r14 .find_restart_enabled: mov rax, [rbx] test rax, rax je .restart_enabled_done lea rdi, [rel ep_restart_enabled_pref] mov rsi, rax call prefix_match test rax, rax jz .next_restart_enabled_env ; Debug: log when we find a match cmp qword [g_verbose], 1 jne .skip_match_debug push rax push rbx push rcx push rdi push rsi push rdx mov rdi, 2 lea rsi, [rel log_debug_env_check] mov rdx, log_debug_env_check_len SYSCALL SYS_write mov rdi, 2 mov rsi, [rbx] ; The env var string xor rdx, rdx .len_env: cmp byte [rsi+rdx], 0 je .len_env_done inc rdx cmp rdx, 64 jb .len_env .len_env_done: SYSCALL SYS_write mov rdi, 2 lea rsi, [rel log_newline] mov rdx, 1 SYSCALL SYS_write pop rdx pop rsi pop rdi pop rcx pop rbx pop rax .skip_match_debug: ; Check if value is "1" ; Save value pointer in rcx before we corrupt rax (rcx is not used in this section) mov rcx, rax ; Debug: log the value cmp qword [g_verbose], 1 jne .skip_value_debug push rax push rbx push rcx push rdi push rsi push rdx mov rsi, rcx ; value pointer call parse_u64_dec ; parse value to number mov rdx, rax lea rdi, [rel log_debug_value] mov rsi, log_debug_value_len call log_prefix_num pop rdx pop rsi pop rdi pop rcx pop rbx pop rax .skip_value_debug: mov al, [rcx] cmp al, '1' jne .next_restart_enabled_env ; Not "1", continue searching mov al, [rcx+1] test al, al jnz .next_restart_enabled_env ; Not null-terminated after "1", continue searching ; Found EP_RESTART_ENABLED=1, enable restart mov qword [g_restart_enabled], 1 LOG log_restart, log_restart_len ; Debug: log that we found it jmp .restart_enabled_done .next_restart_enabled_env: add rbx, 8 jmp .find_restart_enabled .restart_enabled_done: ; Parse EP_MAX_RESTARTS (optional) mov rbx, r14 .find_max_restarts: mov rax, [rbx] test rax, rax je .max_restarts_done lea rdi, [rel ep_max_restarts_pref] mov rsi, rax call prefix_match test rax, rax jz .next_max_restarts_env ; rax points to value string mov rsi, rax call parse_u64_dec_checked test rdx, rdx jz .bad_max_restarts mov [g_max_restarts], rax jmp .max_restarts_done .bad_max_restarts: LOG log_warn_bad_max_restarts, log_warn_bad_max_restarts_len jmp .max_restarts_done .next_max_restarts_env: add rbx, 8 jmp .find_max_restarts .max_restarts_done: ; Parse EP_RESTART_BACKOFF_SECONDS (optional) mov rbx, r14 .find_restart_backoff: mov rax, [rbx] test rax, rax je .restart_backoff_done lea rdi, [rel ep_restart_backoff_pref] mov rsi, rax call prefix_match test rax, rax jz .next_restart_backoff_env ; rax points to value string mov rsi, rax call parse_u64_dec_checked test rdx, rdx jz .bad_backoff test rax, rax js .clamp_backoff mov [g_restart_backoff], rax jmp .restart_backoff_done .clamp_backoff: mov rax, 0x7fffffffffffffff mov [g_restart_backoff], rax LOG log_warn_clamp_backoff, log_warn_clamp_backoff_len jmp .restart_backoff_done .bad_backoff: LOG log_warn_bad_backoff, log_warn_bad_backoff_len jmp .restart_backoff_done .next_restart_backoff_env: add rbx, 8 jmp .find_restart_backoff .restart_backoff_done: ; Setup signals + signalfd (pass envp for EP_SIGNALS) mov rdi, [g_envp] call setup_signals_and_fd ; signalfd fd call get_signalfd_fd mov [g_sfd], rax ; Create epoll and add signalfd call epoll_create_fd mov [g_epfd], rax mov rdi, rax mov rsi, [g_sfd] call epoll_add_fd cmp rax, 0 jge .epoll_ok ; Fatal: can't build event loop mov rdi, 111 EXIT rdi .epoll_ok: ; Spawn child mov rbx, [g_argv_exec] mov rsi, [g_envp] mov rdi, rbx call do_spawn mov [g_child_pid], rax .main_loop: ; Wait for either a signal event (sfd) or the grace timer (tfd) mov rdi, [g_epfd] call epoll_wait_once cmp rax, -1 je .main_loop mov rbx, rax ; ready fd ; signalfd ready? mov rax, [g_sfd] cmp rbx, rax jne .check_timer ; read signo call read_signalfd_once test rax, rax js .main_loop push rax ; save signo across log_prefix_num mov rdx, [rsp] lea rdi, [rel log_signal_prefix] mov rsi, log_signal_prefix_len call log_prefix_num pop rdx cmp rdx, SIGCHLD je .handle_chld ; If child already exited (e.g. crash + pending restart backoff), avoid ; forwarding signals to a potentially reused PGID. cmp qword [g_child_exited], 1 jne .child_alive mov rax, [g_backoff_tfd] test rax, rax jz .exit_child_status ; Backoff is active: only shutdown signals cancel the restart; other signals are ignored. cmp rdx, SIGTERM je .cancel_restart_exit cmp rdx, SIGINT je .cancel_restart_exit cmp rdx, SIGHUP je .cancel_restart_exit cmp rdx, SIGQUIT je .cancel_restart_exit LOG log_info_child_exited_ignore_signal, log_info_child_exited_ignore_signal_len jmp .main_loop .cancel_restart_exit: LOG log_info_child_exited_cancel_restart, log_info_child_exited_cancel_restart_len .exit_child_status: mov rax, [g_child_status] EXIT rax .child_alive: ; if not a "soft shutdown" signal, just forward and continue cmp rdx, SIGTERM je .soft_signal cmp rdx, SIGINT je .soft_signal cmp rdx, SIGHUP je .soft_signal cmp rdx, SIGQUIT je .soft_signal ; forward other signals (USR1,USR2,WINCH,CONT,ALRM,TTIN,TTOU,PIPE) without arming timer mov rdi, [g_child_pid] mov rsi, rdx call forward_signal_to_group jmp .main_loop .soft_signal: ; forward received soft signal to group mov rdi, [g_child_pid] mov rsi, rdx call forward_signal_to_group ; opportunistic reap: child may have exited immediately after soft signal mov rdi, [g_child_pid] call reap_children_nonblock test rax, rax jz .after_opportunistic ; compute exit code and exit call get_wait_status_ptr mov rdi, [rax] call extract_exit_code mov [g_child_status], rax mov qword [g_child_exited], 1 LOG log_sigchld_ok, log_sigchld_ok_len mov rax, [g_child_status] EXIT rax .after_opportunistic: ; If the main child already exited (e.g., crash + pending restart backoff), ; treat any shutdown signal as "stop now" and do not restart. cmp qword [g_child_exited], 1 jne .maybe_start_shutdown mov rax, [g_child_status] EXIT rax .maybe_start_shutdown: ; start shutdown on first soft signal cmp qword [g_shutdown], 1 je .main_loop LOG log_first_soft, log_first_soft_len mov rdx, [g_grace_secs] lea rdi, [rel log_grace_secs_prefix] mov rsi, log_grace_secs_prefix_len call log_prefix_num ; create timerfd for grace window mov rdi, [g_grace_secs] call create_grace_timerfd test rax, rax js .grace_timer_unavailable mov [g_tfd], rax ; add to epoll mov rdi, [g_epfd] mov rsi, [g_tfd] call epoll_add_fd cmp rax, 0 jge .grace_timer_added ; close timerfd and escalate immediately mov rdi, [g_tfd] SYSCALL SYS_close mov qword [g_tfd], 0 jmp .grace_timer_unavailable .grace_timer_added: mov qword [g_shutdown], 1 jmp .main_loop .grace_timer_unavailable: LOG log_warn_grace_timer_unavailable, log_warn_grace_timer_unavailable_len mov qword [g_shutdown], 1 mov rdi, [g_child_pid] mov rsi, SIGKILL call forward_signal_to_group mov qword [g_killed], 1 jmp .main_loop .check_timer: ; Check if this is the backoff timer mov rax, [g_backoff_tfd] test rax, rax jz .check_grace_timer cmp rbx, rax jne .check_grace_timer ; Backoff timer expired -> restart child mov rdi, rax call read_timerfd_tick ; Close backoff timerfd mov rdi, [g_backoff_tfd] SYSCALL SYS_close mov qword [g_backoff_tfd], 0 ; If shutdown began while we were waiting for backoff, do not restart. cmp qword [g_shutdown], 1 jne .do_backoff_restart mov rax, [g_child_status] EXIT rax .do_backoff_restart: ; Reset state for restart mov qword [g_child_exited], 0 mov qword [g_shutdown], 0 mov qword [g_killed], 0 ; Spawn new child mov rbx, [g_argv_exec] mov rsi, [g_envp] mov rdi, rbx call do_spawn mov [g_child_pid], rax ; Increment restart count mov rax, [g_restart_count] inc rax mov [g_restart_count], rax mov rdx, rax lea rdi, [rel log_restart_count_prefix] mov rsi, log_restart_count_prefix_len call log_prefix_num LOG log_restart, log_restart_len jmp .main_loop .check_grace_timer: ; timerfd event -> escalate SIGKILL if child not yet exited mov rax, [g_tfd] test rax, rax jz .main_loop cmp rbx, rax jne .main_loop ; drain timerfd mov rdi, [g_tfd] call read_timerfd_tick ; if child not exited yet -> kill -KILL cmp qword [g_child_exited], 1 je .main_loop ; avoid killing a reused PGID: if kill(-pgid, 0) reports ESRCH, skip escalation mov rax, [g_child_pid] neg rax mov rdi, rax xor rsi, rsi SYSCALL SYS_kill test rax, rax jns .do_escalate mov rbx, rax neg rbx cmp rbx, ESRCH jne .do_escalate LOG log_escalate_skip_dead_pgid, log_escalate_skip_dead_pgid_len jmp .main_loop .do_escalate: LOG log_escalate_kill, log_escalate_kill_len mov rdi, [g_child_pid] mov rsi, SIGKILL call forward_signal_to_group mov qword [g_killed], 1 jmp .main_loop .handle_chld: ; reap all mov rdi, [g_child_pid] call reap_children_nonblock test rax, rax jz .main_loop ; Get wait status call get_wait_status_ptr mov rdi, [rax] mov r12, rdi ; save raw wait status for restart logic/debug ; compute exit code call extract_exit_code mov [g_child_status], rax mov qword [g_child_exited], 1 ; Debug: log restart_enabled, shutdown, and wait_status mov rdx, [g_restart_enabled] lea rdi, [rel log_debug_restart_enabled] mov rsi, log_debug_restart_enabled_len call log_prefix_num mov rdx, [g_shutdown] lea rdi, [rel log_debug_shutdown] mov rsi, log_debug_shutdown_len call log_prefix_num mov rdx, r12 lea rdi, [rel log_debug_wait_status] mov rsi, log_debug_wait_status_len call log_prefix_num ; Check if we should restart (only if restart enabled, not in shutdown, and child was killed by signal) cmp qword [g_restart_enabled], 1 jne .no_restart cmp qword [g_shutdown], 1 je .no_restart ; Check if child was killed by signal (not normal exit) mov rax, r12 and rax, 0x7f cmp rax, 0 je .no_restart ; Normal exit, don't restart ; Check max restarts mov rax, [g_max_restarts] test rax, rax jz .check_restart_count ; 0 means unlimited mov rbx, [g_restart_count] cmp rbx, rax jge .max_restarts_reached .check_restart_count: ; Check if we need backoff mov rax, [g_restart_backoff] test rax, rax jz .restart_immediately mov r9, rax mov rdx, r9 lea rdi, [rel log_restart_backoff_prefix] mov rsi, log_restart_backoff_prefix_len call log_prefix_num ; Create backoff timerfd mov rdi, r9 call create_grace_timerfd mov [g_backoff_tfd], rax test rax, rax js .restart_immediately ; If timerfd creation failed, restart immediately ; Add backoff timerfd to epoll mov rdi, [g_epfd] mov rsi, [g_backoff_tfd] call epoll_add_fd cmp rax, 0 jge .backoff_added LOG log_warn_backoff_timer_epoll_failed, log_warn_backoff_timer_epoll_failed_len mov rdi, [g_backoff_tfd] SYSCALL SYS_close mov qword [g_backoff_tfd], 0 jmp .restart_immediately .backoff_added: LOG log_backoff_wait, log_backoff_wait_len jmp .main_loop .restart_immediately: ; Reset state for restart mov qword [g_child_exited], 0 mov qword [g_shutdown], 0 mov qword [g_killed], 0 ; Spawn new child mov rbx, [g_argv_exec] mov rsi, [g_envp] mov rdi, rbx call do_spawn mov [g_child_pid], rax ; Increment restart count mov rax, [g_restart_count] inc rax mov [g_restart_count], rax mov rdx, rax lea rdi, [rel log_restart_count_prefix] mov rsi, log_restart_count_prefix_len call log_prefix_num LOG log_restart, log_restart_len jmp .main_loop .max_restarts_reached: LOG log_max_restarts, log_max_restarts_len .no_restart: ; if we escalated to SIGKILL, force exit code (base+9) cmp qword [g_killed], 1 jne .no_force_kill_rc mov rax, [g_exit_code_base] add rax, 9 mov [g_child_status], rax .no_force_kill_rc: LOG log_sigchld_ok, log_sigchld_ok_len mov rax, [g_child_status] EXIT rax delim_str: db "--",0 v1_str: db "-v",0 v2_str: db "--verbose",0 ver1_str: db "-V",0 ver2_str: db "--version",0 ep_pref: db "EP_GRACE_SECONDS=",0 ep_subreaper_pref: db "EP_SUBREAPER=",0 ep_exit_base_pref: db "EP_EXIT_CODE_BASE=",0 ep_restart_enabled_pref: db "EP_RESTART_ENABLED=",0 ep_max_restarts_pref: db "EP_MAX_RESTARTS=",0 ep_restart_backoff_pref: db "EP_RESTART_BACKOFF_SECONDS=",0 mini-init-asm-0.3.1+ds/src/amd64/signals.asm000066400000000000000000000270711514655602300204520ustar00rootroot00000000000000; Block a set of signals and create signalfd for them ; Supports EP_SIGNALS=CSV to add extra names (USR1,USR2,PIPE,WINCH). global setup_signals_and_fd global read_signalfd_once global get_wait_status_ptr global get_signalfd_fd %include "macros.inc" %include "syscalls_amd64.inc" extern g_verbose extern get_timestamp_ptr extern log_prefix_num extern get_timestamp_ptr section .bss align 8 sigset: resb 128 ; kernel_sigset_t (x86-64: 64 signals) signalfd_buf: resb 128 ; struct signalfd_siginfo token_buf: resb 16 ; temp buffer for EP_SIGNALS tokens signalfd_fd: resq 1 rtmin_val: resq 1 rtmax_val: resq 1 rt_enabled: resq 1 section .text ; Returns rax = fd (>=0) or <0 on error ; rdi = envp setup_signals_and_fd: push r12 mov r12, rdi ; Build mask: HUP, INT, QUIT, TERM, CHLD lea rdi, [rel sigset] ; zero 128 bytes mov rcx, 16 xor rax, rax .zero: mov [rdi + rcx*8 - 8], rax loop .zero ; set default bits mov rsi, SIGHUP ; 1 call set_sig_bit mov rsi, SIGINT ; 2 call set_sig_bit mov rsi, SIGQUIT ; 3 call set_sig_bit mov rsi, SIGTERM ; 15 call set_sig_bit mov rsi, SIGCHLD ; 17 call set_sig_bit ; Add common forwards by default for robustness mov rsi, SIGUSR1 call set_sig_bit mov rsi, SIGUSR2 call set_sig_bit mov rsi, SIGPIPE call set_sig_bit mov rsi, SIGWINCH call set_sig_bit mov rsi, SIGTTIN call set_sig_bit mov rsi, SIGTTOU call set_sig_bit mov rsi, SIGCONT call set_sig_bit mov rsi, SIGALRM call set_sig_bit ; Parse optional EP_SIGRTMIN/EP_SIGRTMAX (required for RT* tokens) mov qword [rel rt_enabled], 0 mov qword [rel rtmin_val], 0 mov qword [rel rtmax_val], 0 xor r13, r13 ; found rtmin xor r15, r15 ; found rtmax mov rbx, r12 .find_sigrt_env: mov rax, [rbx] test rax, rax je .after_sigrt_env lea rdi, [rel ep_sigrtmin_pref] mov rsi, rax call prefix_match test rax, rax jz .chk_sigrtmax mov rsi, rax call parse_u64_dec_checked test rdx, rdx jz .next_sigrt_env mov [rel rtmin_val], rax mov r13, 1 jmp .next_sigrt_env .chk_sigrtmax: lea rdi, [rel ep_sigrtmax_pref] mov rsi, [rbx] call prefix_match test rax, rax jz .next_sigrt_env mov rsi, rax call parse_u64_dec_checked test rdx, rdx jz .next_sigrt_env mov [rel rtmax_val], rax mov r15, 1 .next_sigrt_env: add rbx, 8 jmp .find_sigrt_env .after_sigrt_env: cmp r13, 1 jne .sigrt_done cmp r15, 1 jne .sigrt_done mov rax, [rel rtmin_val] cmp rax, 1 jb .sigrt_done cmp rax, KERNEL_SIGMAX ja .sigrt_done mov rbx, [rel rtmax_val] cmp rbx, 1 jb .sigrt_done cmp rbx, KERNEL_SIGMAX ja .sigrt_done cmp rax, rbx jae .sigrt_done mov qword [rel rt_enabled], 1 .sigrt_done: ; Parse EP_SIGNALS=CSV (optional) mov rbx, r12 xor r14, r14 ; track if EP_SIGNALS parsed .find_env: mov rax, [rbx] test rax, rax je .after_env ; check prefix "EP_SIGNALS=" lea rdi, [rel ep_sigpref] mov rsi, rax call prefix_match test rax, rax jz .next_env mov rsi, rax ; pointer to value string mov r14, 1 LOG log_epsig_found, log_epsig_found_len .parse_csv: mov al, [rsi] cmp al, 0 je .after_env cmp al, ' ' je .skip_space mov r8, rsi ; token start .scan_token: mov al, [rsi] cmp al, 0 je .token_ready cmp al, ',' je .token_ready inc rsi jmp .scan_token .token_ready: mov r9, rsi ; token end (points to delimiter or NUL) mov rcx, r9 sub rcx, r8 ; token length ; trim trailing spaces .trim_end: cmp rcx, 0 jle .after_token mov al, [r8 + rcx - 1] cmp al, ' ' jne .trim_done dec rcx jmp .trim_end .trim_done: cmp rcx, 0 jle .after_token cmp rcx, 15 ; max token size (buffer 16 incl NUL) ja .unknown_tok lea rdi, [rel token_buf] mov rsi, r8 mov rdx, rcx ; keep length in rdx mov rax, rcx mov rcx, rax rep movsb mov byte [rdi], 0 ; rdi now at end -> write NUL mov rsi, r9 ; restore current pointer lea rbx, [rel token_buf] lea rdx, [rel tok_USR1] call token_eq mov rsi, r9 cmp rax, 1 jne .chk_usr2 mov rsi, SIGUSR1 lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_usr2: lea rdx, [rel tok_USR2] call token_eq mov rsi, r9 cmp rax, 1 jne .chk_pipe mov rsi, SIGUSR2 lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_pipe: lea rdx, [rel tok_PIPE] call token_eq mov rsi, r9 cmp rax, 1 jne .chk_winch mov rsi, SIGPIPE lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_winch: lea rdx, [rel tok_WINCH] call token_eq mov rsi, r9 cmp rax, 1 jne .chk_ttin mov rsi, SIGWINCH lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_ttin: lea rdx, [rel tok_TTIN] call token_eq mov rsi, r9 cmp rax, 1 jne .chk_ttou mov rsi, SIGTTIN lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_ttou: lea rdx, [rel tok_TTOU] call token_eq mov rsi, r9 cmp rax, 1 jne .chk_cont mov rsi, SIGTTOU lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_cont: lea rdx, [rel tok_CONT] call token_eq mov rsi, r9 cmp rax, 1 jne .chk_alarm mov rsi, SIGCONT lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_alarm: lea rdx, [rel tok_ALRM] call token_eq mov rsi, r9 cmp rax, 1 jne .chk_numeric mov rsi, SIGALRM lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_numeric: ; Numeric signal token (decimal) lea rbx, [rel token_buf] mov al, [rbx] cmp al, '0' jb .chk_rt cmp al, '9' ja .chk_rt lea rsi, [rbx] call parse_u64_dec_checked test rdx, rdx jz .unknown_tok cmp rax, 1 jb .unknown_tok cmp rax, KERNEL_SIGMAX ja .unknown_tok cmp rax, SIGKILL je .unknown_tok cmp rax, SIGSTOP je .unknown_tok mov rsi, rax lea rdi, [rel sigset] call set_sig_bit jmp .after_token .chk_rt: ; Check if token starts with "RT" (real-time signal) lea rbx, [rel token_buf] mov al, [rbx] cmp al, 'R' jne .unknown_tok mov al, [rbx+1] cmp al, 'T' jne .unknown_tok ; Require explicit runtime SIGRTMIN/SIGRTMAX cmp qword [rel rt_enabled], 1 jne .rt_disabled ; Parse number after "RT" lea rsi, [rbx+2] call parse_u64_dec_checked test rdx, rdx jz .unknown_tok ; Validate range: 1..(rtmax-rtmin) (rtmin+1..rtmax) cmp rax, 0 je .unknown_tok mov rcx, [rel rtmax_val] sub rcx, [rel rtmin_val] cmp rax, rcx ja .unknown_tok ; Calculate rtmin + number add rax, [rel rtmin_val] mov rsi, rax lea rdi, [rel sigset] call set_sig_bit jmp .after_token .rt_disabled: LOG log_rt_needs_env, log_rt_needs_env_len jmp .after_token .unknown_tok: LOG log_unknown_token, log_unknown_token_len .after_token: mov rsi, r9 cmp byte [rsi], 0 je .after_env inc rsi ; skip comma jmp .parse_csv .skip_space: inc rsi jmp .parse_csv .next_env: add rbx, 8 jmp .find_env .after_env: cmp r14, 0 je .after_env_no_log LOG log_epsig_done, log_epsig_done_len .after_env_no_log: ; Block them: rt_sigprocmask(SIG_BLOCK, &sigset, NULL, sizeof(k_sigset_t)) mov rdi, SIG_BLOCK lea rsi, [rel sigset] xor rdx, rdx mov r10, 8 ; sizeof(kernel sigset mask in bytes on x86-64 SYSCALL SYS_rt_sigprocmask ; signalfd4(-1, &sigset, 128, flags) mov rdi, -1 lea rsi, [rel sigset] mov rdx, 8 mov r10, SFD_CLOEXEC | SFD_NONBLOCK SYSCALL SYS_signalfd4 test rax, rax js .sfd_err mov [rel signalfd_fd], rax ; log signalfd fd number mov rdx, rax lea rdi, [rel log_sfd_prefix] mov rsi, log_sfd_prefix_len call log_prefix_num .ret: pop r12 ret .sfd_err: LOG log_sfd_create_err, log_sfd_create_err_len jmp .ret ; small helpers -------------------------------------------------------------- ; prefix_match: rdi = PATTERN "NAME=", rsi = envstr ; returns rax = ptr to value (after '=') or 0 prefix_match: push rbx mov rbx, rsi ; env string mov rsi, rdi ; pattern mov rdi, rbx .p_loop: mov al, [rsi] cmp al, 0 je .no cmp al, [rdi] jne .no cmp al, '=' je .value inc rsi inc rdi jmp .p_loop .value: inc rdi mov rax, rdi pop rbx ret .no: xor rax, rax pop rbx ret ; token_eq: compare zero-terminated token rbx with expected rdx ; returns rax=1 if equal, else 0 token_eq: push rbx push rcx push rdx mov rsi, rbx mov rdi, rdx .l1: mov al, [rsi] cmp al, [rdi] jne .ne cmp al, 0 je .eq inc rsi inc rdi jmp .l1 .eq: mov rax, 1 jmp .out .ne: xor rax, rax .out: pop rdx pop rcx pop rbx ret ; return signalfd fd get_signalfd_fd: mov rax, [rel signalfd_fd] ret ; Read one signalfd_siginfo event (after readiness) ; returns in rax = signo (int) or <0 on error read_signalfd_once: mov rdi, [rel signalfd_fd] lea rsi, [rel signalfd_buf] mov rdx, 128 .read_retry: SYSCALL SYS_read cmp rax, 128 je .ok test rax, rax js .read_err jmp .err .read_err: cmp rax, -EINTR je .read_retry cmp rax, -EAGAIN je .err jmp .err .ok: ; struct signalfd_siginfo starts with uint32_t ssi_signo mov eax, dword [rel signalfd_buf] movsx rax, eax ret .err: mov rax, -1 ret ; From wait.asm get_wait_status_ptr: extern wait_status lea rax, [rel wait_status] ret section .text ; token_eq_range: compare token [rbx..rsi) to zero-terminated expected at rdx ; returns rax=1 if equal, else 0 token_eq_range: push rbx push rcx push rdx mov r8, rbx ; start mov r9, rsi ; end mov r10, rdx ; expected .ter_loop: cmp r8, r9 jge .ter_after mov al, [r8] cmp al, [r10] jne .ter_no inc r8 inc r10 jmp .ter_loop .ter_after: cmp byte [r10], 0 jne .ter_no mov rax, 1 jmp .ter_out .ter_no: xor rax, rax .ter_out: pop rdx pop rcx pop rbx ret ep_sigpref: db "EP_SIGNALS=",0 ep_sigrtmin_pref: db "EP_SIGRTMIN=",0 ep_sigrtmax_pref: db "EP_SIGRTMAX=",0 tok_USR1: db "USR1",0 tok_USR2: db "USR2",0 tok_PIPE: db "PIPE",0 tok_WINCH: db "WINCH",0 tok_TTIN: db "TTIN",0 tok_TTOU: db "TTOU",0 tok_CONT: db "CONT",0 tok_ALRM: db "ALRM",0 section .rodata log_unknown_token: db "WARN: Unknown EP_SIGNALS token ignored", 10 log_unknown_token_len: equ $ - log_unknown_token log_sfd_prefix: db "DEBUG: signalfd created fd=", 0 log_sfd_prefix_len: equ $ - log_sfd_prefix - 1 log_epsig_found: db "DEBUG: parsing EP_SIGNALS", 10 log_epsig_found_len: equ $ - log_epsig_found log_epsig_done: db "DEBUG: EP_SIGNALS parsed", 10 log_epsig_done_len: equ $ - log_epsig_done log_tok_check: db "DEBUG: EP_SIGNALS token check", 10 log_tok_check_len: equ $ - log_tok_check log_sfd_create_err: db "ERROR: signalfd4 failed", 10 log_sfd_create_err_len: equ $ - log_sfd_create_err log_rt_needs_env: db "WARN: RT* EP_SIGNALS tokens require EP_SIGRTMIN and EP_SIGRTMAX; ignoring RT token", 10 log_rt_needs_env_len: equ $ - log_rt_needs_env mini-init-asm-0.3.1+ds/src/amd64/spawn.asm000066400000000000000000000025251514655602300201370ustar00rootroot00000000000000; fork -> child: setsid + setpgid(0,0) -> execve(argv_exec[0], argv_exec, envp) ; parent returns with rax=child_pid (>=1) or <0 on error global do_spawn %include "macros.inc" %include "syscalls_amd64.inc" section .text do_spawn: ; inputs: ; rdi = argv_exec (pointer to argv[exec]) ; rsi = envp (pointer to envp array) ; outputs: ; rax = child_pid (>=1) or negative errno push r12 mov r12, rdi ; save argv_exec push r13 mov r13, rsi ; save envp ; fork SYSCALL SYS_fork test rax, rax js .ret ; error jz .in_child ; rax==0 in child ; parent jmp .ret .in_child: ; unblock signals in the child so it can receive TERM/INT/etc. sub rsp, 8 mov qword [rsp], 0 mov rdi, SIG_SETMASK mov rsi, rsp ; empty mask -> unblock all xor rdx, rdx mov r10, 8 SYSCALL SYS_rt_sigprocmask add rsp, 8 ; become session leader SYSCALL SYS_setsid ; setpgid(0,0) -> pgid == pid xor rdi, rdi xor rsi, rsi SYSCALL SYS_setpgid ; execve(argv_exec[0], argv_exec, envp) mov rdi, [r12] ; filename mov rsi, r12 ; argv mov rdx, r13 ; envp SYSCALL SYS_execve ; execve failed -> exit(127) mov rdi, 127 SYSCALL SYS_exit .ret: pop r13 pop r12 ret mini-init-asm-0.3.1+ds/src/amd64/time.asm000066400000000000000000000027251514655602300177470ustar00rootroot00000000000000global get_timestamp_ptr extern g_verbose %include "syscalls_amd64.inc" %define CLOCK_REALTIME 0 section .bss time_buffer: resb 19 ; "SSSSSSSSSS.mmmmmm " section .text get_timestamp_ptr: cmp qword [rel g_verbose], 0 je .skip push rbx push r12 push r13 push r14 sub rsp, 16 mov rax, SYS_clock_gettime mov rdi, CLOCK_REALTIME mov rsi, rsp syscall mov rbx, [rsp] ; seconds mov r12, [rsp+8] ; nanoseconds add rsp, 16 ; convert ns to microseconds mov rax, r12 xor rdx, rdx mov r13, 1000 div r13 ; rax = microseconds mov r12, rax ; fill buffer with zeros lea r14, [rel time_buffer] mov rcx, 19/8 + 1 xor rax, rax .clr_loop: mov [r14+rcx*8-8], rax loop .clr_loop ; seconds -> 10 digits mov rcx, 10 lea rsi, [r14+9] mov rax, rbx .sec_loop: xor rdx, rdx mov r13, 10 div r13 add dl, '0' mov byte [rsi], dl dec rsi dec rcx jnz .sec_loop ; dot mov byte [r14+10], '.' ; microseconds -> 6 digits mov rcx, 6 lea rsi, [r14+16] mov rax, r12 .micro_loop: xor rdx, rdx mov r13, 10 div r13 add dl, '0' mov byte [rsi], dl dec rsi dec rcx jnz .micro_loop ; trailing space and null mov byte [r14+17], ' ' mov byte [r14+18], 0 mov rax, r14 pop r14 pop r13 pop r12 pop rbx ret .skip: lea rax, [rel time_buffer] mov byte [rax], 0 ret mini-init-asm-0.3.1+ds/src/amd64/timer.asm000066400000000000000000000051531514655602300201270ustar00rootroot00000000000000; timerfd helpers: create one-shot timer for grace period global create_grace_timerfd global read_timerfd_tick %include "macros.inc" %include "syscalls_amd64.inc" extern g_verbose extern log_prefix_num extern get_timestamp_ptr extern get_timestamp_ptr section .text ; create_grace_timerfd(seconds) -> rax = fd or <0 ; rdi=seconds (u64) create_grace_timerfd: push r12 push r13 mov r13, rdi ; save seconds ; debug: seconds == 0? test r13, r13 jnz .have_secs LOG log_tfd_zero, log_tfd_zero_len .have_secs: ; fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC|TFD_NONBLOCK) mov rdi, CLOCK_MONOTONIC mov rsi, TFD_CLOEXEC | TFD_NONBLOCK SYSCALL SYS_timerfd_create test rax, rax js .ret mov r12, rax ; fd ; log timerfd fd number mov rdx, r12 lea rdi, [rel log_tfd_prefix] mov rsi, log_tfd_prefix_len call log_prefix_num ; set itimerspec sub rsp, 32 ; __kernel_itimerspec (interval + value) ; it_interval = 0 mov qword [rsp], 0 mov qword [rsp+8], 0 ; it_value = seconds, 0 nsec mov [rsp+16], r13 mov qword [rsp+24], 0 ; timerfd_settime(fd, 0, &new_value, NULL) mov rdi, r12 xor rsi, rsi ; flags=0 (relative) mov rdx, rsp xor r10, r10 ; old_value=NULL .settime_retry: SYSCALL SYS_timerfd_settime test rax, rax jns .settime_ok mov rbx, rax neg rbx cmp rbx, EINTR je .settime_retry LOG log_tfd_set_err, log_tfd_set_err_len jmp .after_set .settime_ok: LOG log_tfd_armed, log_tfd_armed_len .after_set: add rsp, 32 mov rax, r12 .ret: pop r13 pop r12 ret ; read_timerfd_tick(fd): read u64 expirations to clear readiness ; rdi=fd, returns rax=0 on success or <0 read_timerfd_tick: sub rsp, 8 mov rsi, rsp mov rdx, 8 .read_retry: SYSCALL SYS_read cmp rax, 8 je .ok test rax, rax jns .short_or_eof cmp rax, -EINTR je .read_retry cmp rax, -EAGAIN je .ok ; already drained ; other error -> propagate jmp .out .short_or_eof: ; unexpected short read; treat as error mov rax, -1 jmp .out .ok: xor rax, rax .out: add rsp, 8 ret section .rodata log_tfd_prefix: db "DEBUG: timerfd created fd=", 0 log_tfd_prefix_len: equ $ - log_tfd_prefix - 1 log_tfd_armed: db "DEBUG: grace timer armed", 10 log_tfd_armed_len: equ $ - log_tfd_armed log_tfd_set_err: db "ERROR: timerfd_settime failed", 10 log_tfd_set_err_len: equ $ - log_tfd_set_err log_tfd_zero: db "WARN: timer armed with 0 seconds", 10 log_tfd_zero_len: equ $ - log_tfd_zero mini-init-asm-0.3.1+ds/src/amd64/wait.asm000066400000000000000000000032121514655602300177450ustar00rootroot00000000000000; Non-blocking reap + status decode helpers global reap_children_nonblock global extract_exit_code global wait_status extern g_exit_code_base %include "macros.inc" %include "syscalls_amd64.inc" section .bss align 8 wait_status: resq 1 section .text ; Loop wait4(-1, &status, WNOHANG, NULL) until no child. ; In: rdi = main_child_pid ; Out: rax = main_child_pid if reaped, else 0 ; Side-effect: if main child was reaped, stores its status in [wait_status] reap_children_nonblock: push rbx push r12 push r13 mov r12, rdi ; main pid xor rbx, rbx ; found pid (0/ pid) xor r13, r13 ; saved main status .again: mov rdi, -1 lea rsi, [rel wait_status] mov edx, 1 ; WNOHANG xor r10, r10 SYSCALL SYS_wait4 test rax, rax jg .got ; pid reaped cmp rax, 0 je .done ; no child ready ; rax < 0 -> error cmp rax, -EINTR je .again jmp .done .got: cmp rax, r12 jne .again mov rbx, rax mov r13, [rel wait_status] jmp .again .done: test rbx, rbx jz .out mov [rel wait_status], r13 .out: mov rax, rbx pop r13 pop r12 pop rbx ret ; Given a wait status in rdi, compute exit code to return: ; if signaled -> 128 + signo ; else -> exitstatus extract_exit_code: ; If WIFSIGNALED: (status & 0x7f) != 0 mov rax, rdi and rax, 0x7f cmp rax, 0 jne .signaled ; exited -> (status >> 8) & 0xff mov rax, rdi shr rax, 8 and rax, 0xff ret .signaled: ; g_exit_code_base + (status & 0x7f) mov rax, rdi and rax, 0x7f add rax, [rel g_exit_code_base] ret mini-init-asm-0.3.1+ds/src/arm64/000077500000000000000000000000001514655602300163175ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/src/arm64/epoll.S000066400000000000000000000056201514655602300175610ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .bss .align 3 epoll_events_buf: .skip EPOLL_EVENT_SIZE * 4 .text .align 2 .global epoll_create_fd .global epoll_add_fd .global epoll_wait_once .extern g_verbose .extern log_prefix_num .extern get_timestamp_ptr epoll_create_fd: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! mov x0, #EPOLL_CLOEXEC SYSCALL SYS_epoll_create1 cmp x0, #0 blt .ret mov x19, x0 mov x2, x19 adrp x0, log_epfd_prefix add x0, x0, :lo12:log_epfd_prefix mov x1, #log_epfd_prefix_len bl log_prefix_num mov x0, x19 .ret: ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret epoll_add_fd: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! LOG log_epoll_add_enter, log_epoll_add_enter_len mov x19, x0 // epfd mov x20, x1 // fd sub sp, sp, #EPOLL_EVENT_SIZE mov w2, #EPOLLIN str w2, [sp] mov w2, #0 str w2, [sp, #4] str x20, [sp, #8] mov x0, x19 mov x1, #EPOLL_CTL_ADD mov x2, x20 mov x3, sp .ctl_retry: SYSCALL SYS_epoll_ctl cmp x0, #0 bge .ctl_ok neg x4, x0 cmp x4, #4 // EINTR beq .ctl_retry LOG log_epoll_ctl_err, log_epoll_ctl_err_len mov x0, #-1 b .ctl_out .ctl_ok: mov x2, x20 adrp x0, log_epoll_add_prefix add x0, x0, :lo12:log_epoll_add_prefix mov x1, #log_epoll_add_prefix_len bl log_prefix_num mov x0, #0 .ctl_out: add sp, sp, #EPOLL_EVENT_SIZE ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 LOG log_epoll_add_exit, log_epoll_add_exit_len ret epoll_wait_once: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! adrp x1, epoll_events_buf add x1, x1, :lo12:epoll_events_buf mov x2, #4 mov x3, #-1 mov x4, #0 mov x5, #0 .wait_retry: SYSCALL SYS_epoll_pwait cmp x0, #0 bge .check_count neg x19, x0 cmp x19, #4 beq .wait_retry LOG log_epoll_wait_err, log_epoll_wait_err_len mov x0, #-1 b .wait_out .check_count: cmp x0, #1 blt .err ldr x0, [x1, #8] b .wait_out .err: mov x0, #-1 .wait_out: ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret .section .rodata log_epfd_prefix: .asciz "DEBUG: epoll fd created fd=" .equ log_epfd_prefix_len, . - log_epfd_prefix - 1 log_epoll_add_prefix: .asciz "DEBUG: added FD to epoll fd=" .equ log_epoll_add_prefix_len, . - log_epoll_add_prefix - 1 log_epoll_add_enter: .asciz "DEBUG: epoll_add_fd enter" .equ log_epoll_add_enter_len, . - log_epoll_add_enter - 1 log_epoll_add_exit: .asciz "DEBUG: epoll_add_fd exit" .equ log_epoll_add_exit_len, . - log_epoll_add_exit - 1 log_epoll_ctl_err: .asciz "ERROR: epoll_ctl failed" .equ log_epoll_ctl_err_len, . - log_epoll_ctl_err - 1 log_epoll_wait_err: .asciz "ERROR: epoll_pwait failed" .equ log_epoll_wait_err_len, . - log_epoll_wait_err - 1 mini-init-asm-0.3.1+ds/src/arm64/forward.S000066400000000000000000000003101514655602300201010ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .text .align 2 .global forward_signal_to_group forward_signal_to_group: neg x0, x0 // pid = -pgid SYSCALL SYS_kill ret mini-init-asm-0.3.1+ds/src/arm64/helpers/000077500000000000000000000000001514655602300177615ustar00rootroot00000000000000mini-init-asm-0.3.1+ds/src/arm64/helpers/exit42.S000066400000000000000000000002241514655602300212220ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .text .align 2 .global _start _start: // Exit with status 42 mov x0, #42 mov x8, #SYS_exit svc #0 mini-init-asm-0.3.1+ds/src/arm64/helpers/sleeper.S000066400000000000000000000011101514655602300215350ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .text .align 2 .global _start // Simple long-running process that sleeps in a loop // It will terminate with default action on TERM when run under mini-init _start: sub sp, sp, #16 // reserve space for struct timespec mov x3, #1000 // tv_sec = 1000 str x3, [sp] // store tv_sec (low 8 bytes) mov x3, #0 // tv_nsec = 0 str x3, [sp, #8] .sleep_loop: mov x0, sp // req mov x1, #0 // rem = NULL mov x8, #SYS_nanosleep svc #0 b .sleep_loop mini-init-asm-0.3.1+ds/src/arm64/log.S000066400000000000000000000025711514655602300172310ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .text .align 2 .global log_prefix_num .extern g_verbose log_prefix_num: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! stp x23, x24, [sp, #-16]! adrp x19, g_verbose add x19, x19, :lo12:g_verbose ldr x19, [x19] cbz x19, .out mov x20, x0 // prefix ptr mov x21, x1 // prefix len mov x22, x2 // value mov x0, #2 mov x1, x20 mov x2, x21 SYSCALL SYS_write sub sp, sp, #32 add x23, sp, #32 mov x24, #0 // length mov x25, x23 // current ptr mov x26, #10 mov x0, x22 cbnz x0, .conv_loop sub x25, x25, #1 mov w27, #'0' strb w27, [x25] mov x24, #1 b .have_num .conv_loop: udiv x28, x0, x26 mul x27, x28, x26 sub x27, x0, x27 add w27, w27, #'0' sub x25, x25, #1 strb w27, [x25] add x24, x24, #1 mov x0, x28 cbnz x0, .conv_loop .have_num: mov x0, #2 mov x1, x25 mov x2, x24 SYSCALL SYS_write mov x0, #2 adrp x1, ln_char add x1, x1, :lo12:ln_char mov x2, #1 SYSCALL SYS_write add sp, sp, #32 .out: ldp x23, x24, [sp], #16 ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret .section .rodata .align 2 ln_char: .byte 0x0a mini-init-asm-0.3.1+ds/src/arm64/main.S000066400000000000000000000662611514655602300174020ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .text .align 2 .global _start .global g_verbose .global g_exit_code_base .global g_arm64_fallback .extern do_spawn .extern forward_signal_to_group .extern reap_children_nonblock .extern extract_exit_code .extern setup_signals_and_fd .extern read_signalfd_once .extern get_wait_status_ptr .extern get_signalfd_fd .extern epoll_create_fd .extern epoll_add_fd .extern epoll_wait_once .extern create_grace_timerfd .extern read_timerfd_tick .extern log_prefix_num .extern get_timestamp_ptr .extern parse_u64_dec .extern parse_u64_dec_checked .extern set_sig_bit .section .rodata .align 3 usage_msg: .asciz "usage: mini-init-arm64 [--verbose|-v] [--version|-V] -- [args...]\n" .equ usage_msg_len, . - usage_msg - 1 .include "version_arm64.inc" log_first_soft: .asciz "DEBUG: first soft signal received" .equ log_first_soft_len, . - log_first_soft - 1 log_escalate_kill: .asciz "DEBUG: escalating to SIGKILL" .equ log_escalate_kill_len, . - log_escalate_kill - 1 log_escalate_skip_dead_pgid: .asciz "DEBUG: grace timer fired but PGID is gone; skipping SIGKILL" .equ log_escalate_skip_dead_pgid_len, . - log_escalate_skip_dead_pgid - 1 log_sigchld_ok: .asciz "DEBUG: SIGCHLD handled" .equ log_sigchld_ok_len, . - log_sigchld_ok - 1 log_restart: .asciz "DEBUG: restarting child" .equ log_restart_len, . - log_restart - 1 log_max_restarts: .asciz "DEBUG: max restarts reached, exiting" .equ log_max_restarts_len, . - log_max_restarts - 1 log_backoff_wait: .asciz "DEBUG: waiting for backoff before restart" .equ log_backoff_wait_len, . - log_backoff_wait - 1 log_signal_prefix: .asciz "DEBUG: signal=" .equ log_signal_prefix_len, . - log_signal_prefix - 1 log_grace_secs_prefix: .asciz "DEBUG: grace_seconds=" .equ log_grace_secs_prefix_len, . - log_grace_secs_prefix - 1 log_restart_count_prefix: .asciz "DEBUG: restart_count=" .equ log_restart_count_prefix_len, . - log_restart_count_prefix - 1 log_restart_backoff_prefix: .asciz "DEBUG: restart_backoff_seconds=" .equ log_restart_backoff_prefix_len, . - log_restart_backoff_prefix - 1 log_warn_bad_grace: .asciz "WARN: invalid EP_GRACE_SECONDS; using default" .equ log_warn_bad_grace_len, . - log_warn_bad_grace - 1 log_warn_clamp_grace: .asciz "WARN: EP_GRACE_SECONDS too large; clamping" .equ log_warn_clamp_grace_len, . - log_warn_clamp_grace - 1 log_warn_bad_exit_base: .asciz "WARN: invalid EP_EXIT_CODE_BASE; using default" .equ log_warn_bad_exit_base_len, . - log_warn_bad_exit_base - 1 log_warn_range_exit_base: .asciz "WARN: EP_EXIT_CODE_BASE out of range (0..255); using default" .equ log_warn_range_exit_base_len, . - log_warn_range_exit_base - 1 log_warn_bad_max_restarts: .asciz "WARN: invalid EP_MAX_RESTARTS; using default" .equ log_warn_bad_max_restarts_len, . - log_warn_bad_max_restarts - 1 log_warn_bad_backoff: .asciz "WARN: invalid EP_RESTART_BACKOFF_SECONDS; using default" .equ log_warn_bad_backoff_len, . - log_warn_bad_backoff - 1 log_warn_clamp_backoff: .asciz "WARN: EP_RESTART_BACKOFF_SECONDS too large; clamping" .equ log_warn_clamp_backoff_len, . - log_warn_clamp_backoff - 1 log_warn_grace_timer_unavailable: .asciz "WARN: grace timer unavailable; escalating immediately to SIGKILL" .equ log_warn_grace_timer_unavailable_len, . - log_warn_grace_timer_unavailable - 1 log_warn_backoff_timer_epoll_failed: .asciz "WARN: backoff timer could not be added to epoll; restarting immediately" .equ log_warn_backoff_timer_epoll_failed_len, . - log_warn_backoff_timer_epoll_failed - 1 log_info_child_exited_ignore_signal: .asciz "DEBUG: child already exited; ignoring non-shutdown signal during restart backoff" .equ log_info_child_exited_ignore_signal_len, . - log_info_child_exited_ignore_signal - 1 log_info_child_exited_cancel_restart: .asciz "DEBUG: shutdown requested during restart backoff; exiting without restart" .equ log_info_child_exited_cancel_restart_len, . - log_info_child_exited_cancel_restart - 1 log_after_sfd: .asciz "DEBUG: after setup_signals_and_fd" .equ log_after_sfd_len, . - log_after_sfd - 1 log_fallback: .asciz "DEBUG: using ARM64 fallback (wait4-only)" .equ log_fallback_len, . - log_fallback - 1 log_child_pid: .asciz "DEBUG: child pid=" .equ log_child_pid_len, . - log_child_pid - 1 log_epoll_add_result: .asciz "DEBUG: epoll_add_fd returned=" .equ log_epoll_add_result_len, . - log_epoll_add_result - 1 log_epoll_add_enter: .asciz "DEBUG: entering epoll_add_fd" .equ log_epoll_add_enter_len, . - log_epoll_add_enter - 1 log_epoll_add_exit: .asciz "DEBUG: exited epoll_add_fd" .equ log_epoll_add_exit_len, . - log_epoll_add_exit - 1 delim_str: .asciz "--" v1_str: .asciz "-v" v2_str: .asciz "--verbose" ver1_str: .asciz "-V" ver2_str: .asciz "--version" ep_pref: .asciz "EP_GRACE_SECONDS=" ep_subreaper_pref: .asciz "EP_SUBREAPER=" ep_exit_base_pref: .asciz "EP_EXIT_CODE_BASE=" ep_restart_enabled_pref: .asciz "EP_RESTART_ENABLED=" ep_max_restarts_pref: .asciz "EP_MAX_RESTARTS=" ep_restart_backoff_pref: .asciz "EP_RESTART_BACKOFF_SECONDS=" ep_arm64_fallback_pref: .asciz "EP_ARM64_FALLBACK=" .section .bss .align 3 g_verbose: .skip 8 g_child_pid: .skip 8 g_child_exited: .skip 8 g_child_status: .skip 8 g_grace_secs: .skip 8 g_shutdown: .skip 8 g_killed: .skip 8 g_epfd: .skip 8 g_sfd: .skip 8 g_tfd: .skip 8 g_argv_exec: .skip 8 g_envp: .skip 8 g_exit_code_base: .skip 8 g_restart_enabled: .skip 8 g_max_restarts: .skip 8 g_restart_count: .skip 8 g_restart_backoff: .skip 8 g_backoff_tfd: .skip 8 g_arm64_fallback: .skip 8 .text // int str_eq(const char *a, const char *b) str_eq: mov x2, x0 mov x3, x1 .str_loop: ldrb w4, [x2] ldrb w5, [x3] cmp w4, w5 bne .str_ne cbz w4, .str_eq add x2, x2, #1 add x3, x3, #1 b .str_loop .str_eq: mov x0, #1 ret .str_ne: mov x0, #0 ret // const char* prefix_match("NAME=", env) prefix_match: mov x2, x0 // pattern mov x3, x1 // env .pm_loop: ldrb w4, [x2] cbz w4, .pm_no ldrb w5, [x3] cmp w4, w5 bne .pm_no cmp w4, #'=' beq .pm_value add x2, x2, #1 add x3, x3, #1 b .pm_loop .pm_value: add x3, x3, #1 mov x0, x3 ret .pm_no: mov x0, #0 ret _start: // Linux AArch64 process entry: argc/argv/envp are on the initial stack // [sp] = argc (u64), [sp+8] = argv[0], ..., then NULL, then envp pointers... ldr x19, [sp] // argc add x20, sp, #8 // argv** lsl x22, x19, #3 // argc * 8 add x21, x20, x22 // &argv[argc] add x21, x21, #8 // envp** // defaults adrp x0, g_verbose add x0, x0, :lo12:g_verbose str xzr, [x0] // zero rest adrp x2, g_child_pid add x2, x2, :lo12:g_child_pid mov x3, x2 mov x4, #0 mov x5, #17 // count for global variables .zero_globals: str x4, [x3] add x3, x3, #8 subs x5, x5, #1 cbnz x5, .zero_globals adrp x0, g_grace_secs add x0, x0, :lo12:g_grace_secs mov x1, #10 str x1, [x0] adrp x0, g_exit_code_base add x0, x0, :lo12:g_exit_code_base mov x1, #128 str x1, [x0] adrp x0, g_arm64_fallback add x0, x0, :lo12:g_arm64_fallback mov x1, #0 str x1, [x0] adrp x0, g_restart_backoff add x0, x0, :lo12:g_restart_backoff mov x1, #1 str x1, [x0] // parse argv mov x22, #1 // i .parse_argv: cmp x22, x19 bge .argv_done lsl x23, x22, #3 add x24, x20, x23 ldr x25, [x24] // "--" adrp x0, delim_str add x0, x0, :lo12:delim_str mov x1, x25 bl str_eq cmp x0, #1 beq .found_delim // "-v" adrp x0, v1_str add x0, x0, :lo12:v1_str mov x1, x25 bl str_eq cmp x0, #1 beq .set_verbose // "--verbose" adrp x0, v2_str add x0, x0, :lo12:v2_str mov x1, x25 bl str_eq cmp x0, #1 beq .set_verbose // "-V" adrp x0, ver1_str add x0, x0, :lo12:ver1_str mov x1, x25 bl str_eq cmp x0, #1 beq .show_version // "--version" adrp x0, ver2_str add x0, x0, :lo12:ver2_str mov x1, x25 bl str_eq cmp x0, #1 beq .show_version add x22, x22, #1 b .parse_argv .show_version: mov x0, #1 adrp x1, version_msg_arm64 add x1, x1, :lo12:version_msg_arm64 mov x2, version_msg_arm64_len SYSCALL SYS_write mov x0, #0 SYSCALL SYS_exit .set_verbose: adrp x0, g_verbose add x0, x0, :lo12:g_verbose mov x1, #1 str x1, [x0] add x22, x22, #1 b .parse_argv .found_delim: add x23, x22, #1 lsl x23, x23, #3 add x23, x20, x23 adrp x0, g_argv_exec add x0, x0, :lo12:g_argv_exec str x23, [x0] b .argv_done .argv_done: adrp x0, g_argv_exec add x0, x0, :lo12:g_argv_exec ldr x1, [x0] cbnz x1, .have_cmd mov x0, #2 adrp x1, usage_msg add x1, x1, :lo12:usage_msg mov x2, #usage_msg_len SYSCALL SYS_write mov x0, #2 SYSCALL SYS_exit .have_cmd: // store envp adrp x0, g_envp add x0, x0, :lo12:g_envp str x21, [x0] // EP_GRACE_SECONDS mov x23, x21 .find_env: ldr x24, [x23] cbz x24, .env_done adrp x0, ep_pref add x0, x0, :lo12:ep_pref mov x1, x24 bl prefix_match cbz x0, .next_env mov x1, x0 bl parse_u64_dec_checked cbz x1, .bad_grace tbnz x0, #63, .clamp_grace adrp x2, g_grace_secs add x2, x2, :lo12:g_grace_secs str x0, [x2] b .env_done .clamp_grace: mov x0, #0x7fffffffffffffff adrp x2, g_grace_secs add x2, x2, :lo12:g_grace_secs str x0, [x2] LOG log_warn_clamp_grace, log_warn_clamp_grace_len b .env_done .bad_grace: LOG log_warn_bad_grace, log_warn_bad_grace_len b .next_env .next_env: add x23, x23, #8 b .find_env .env_done: // EP_SUBREAPER mov x23, x21 .find_subreaper: ldr x24, [x23] cbz x24, .subreaper_done adrp x0, ep_subreaper_pref add x0, x0, :lo12:ep_subreaper_pref mov x1, x24 bl prefix_match cbz x0, .next_subreaper_env // Check if value is "1" ldrb w1, [x0] cmp w1, #'1' bne .subreaper_done ldrb w1, [x0, #1] cbnz w1, .subreaper_done // Set PR_SET_CHILD_SUBREAPER mov x0, #PR_SET_CHILD_SUBREAPER mov x1, #1 mov x2, #0 mov x3, #0 mov x4, #0 SYSCALL SYS_prctl b .subreaper_done .next_subreaper_env: add x23, x23, #8 b .find_subreaper .subreaper_done: // EP_EXIT_CODE_BASE mov x23, x21 .find_exit_base: ldr x24, [x23] cbz x24, .exit_base_done adrp x0, ep_exit_base_pref add x0, x0, :lo12:ep_exit_base_pref mov x1, x24 bl prefix_match cbz x0, .next_exit_base_env mov x1, x0 bl parse_u64_dec_checked cbz x1, .bad_exit_base cmp x0, #255 bgt .range_exit_base adrp x2, g_exit_code_base add x2, x2, :lo12:g_exit_code_base str x0, [x2] b .exit_base_done .range_exit_base: LOG log_warn_range_exit_base, log_warn_range_exit_base_len b .exit_base_done .bad_exit_base: LOG log_warn_bad_exit_base, log_warn_bad_exit_base_len b .next_exit_base_env .next_exit_base_env: add x23, x23, #8 b .find_exit_base .exit_base_done: // EP_RESTART_ENABLED mov x23, x21 .find_restart_enabled: ldr x24, [x23] cbz x24, .restart_enabled_done adrp x0, ep_restart_enabled_pref add x0, x0, :lo12:ep_restart_enabled_pref mov x1, x24 bl prefix_match cbz x0, .next_restart_enabled_env // Check if value is "1" ldrb w1, [x0] cmp w1, #'1' bne .restart_enabled_done ldrb w1, [x0, #1] cbnz w1, .restart_enabled_done adrp x2, g_restart_enabled add x2, x2, :lo12:g_restart_enabled mov x3, #1 str x3, [x2] b .restart_enabled_done .next_restart_enabled_env: add x23, x23, #8 b .find_restart_enabled .restart_enabled_done: // EP_MAX_RESTARTS mov x23, x21 .find_max_restarts: ldr x24, [x23] cbz x24, .max_restarts_done adrp x0, ep_max_restarts_pref add x0, x0, :lo12:ep_max_restarts_pref mov x1, x24 bl prefix_match cbz x0, .next_max_restarts_env mov x1, x0 bl parse_u64_dec_checked cbz x1, .bad_max_restarts adrp x2, g_max_restarts add x2, x2, :lo12:g_max_restarts str x0, [x2] b .max_restarts_done .bad_max_restarts: LOG log_warn_bad_max_restarts, log_warn_bad_max_restarts_len b .next_max_restarts_env .next_max_restarts_env: add x23, x23, #8 b .find_max_restarts .max_restarts_done: // EP_RESTART_BACKOFF_SECONDS mov x23, x21 .find_restart_backoff: ldr x24, [x23] cbz x24, .restart_backoff_done adrp x0, ep_restart_backoff_pref add x0, x0, :lo12:ep_restart_backoff_pref mov x1, x24 bl prefix_match cbz x0, .next_restart_backoff_env mov x1, x0 bl parse_u64_dec_checked cbz x1, .bad_backoff tbnz x0, #63, .clamp_backoff adrp x2, g_restart_backoff add x2, x2, :lo12:g_restart_backoff str x0, [x2] b .restart_backoff_done .clamp_backoff: mov x0, #0x7fffffffffffffff adrp x2, g_restart_backoff add x2, x2, :lo12:g_restart_backoff str x0, [x2] LOG log_warn_clamp_backoff, log_warn_clamp_backoff_len b .restart_backoff_done .bad_backoff: LOG log_warn_bad_backoff, log_warn_bad_backoff_len b .next_restart_backoff_env .next_restart_backoff_env: add x23, x23, #8 b .find_restart_backoff .restart_backoff_done: // EP_ARM64_FALLBACK (wait4-only path for QEMU flakiness) mov x23, x21 .find_arm64_fallback: ldr x24, [x23] cbz x24, .arm64_fallback_done adrp x0, ep_arm64_fallback_pref add x0, x0, :lo12:ep_arm64_fallback_pref mov x1, x24 bl prefix_match cbz x0, .next_arm64_fallback_env ldrb w1, [x0] cmp w1, #'1' bne .arm64_fallback_done ldrb w1, [x0, #1] cbnz w1, .arm64_fallback_done adrp x2, g_arm64_fallback add x2, x2, :lo12:g_arm64_fallback mov x3, #1 str x3, [x2] b .arm64_fallback_done .next_arm64_fallback_env: add x23, x23, #8 b .find_arm64_fallback .arm64_fallback_done: // If fallback enabled, bypass epoll/signalfd and use wait4 only adrp x0, g_arm64_fallback add x0, x0, :lo12:g_arm64_fallback ldr x0, [x0] cbz x0, .normal_path LOG log_fallback, log_fallback_len // spawn child adrp x0, g_argv_exec add x0, x0, :lo12:g_argv_exec ldr x0, [x0] adrp x1, g_envp add x1, x1, :lo12:g_envp ldr x1, [x1] bl do_spawn // wait4(-1, &wait_status, 0, NULL) bl get_wait_status_ptr mov x1, x0 mov x0, #-1 mov x2, #0 mov x3, #0 SYSCALL SYS_wait4 bl get_wait_status_ptr ldr x0, [x0] bl extract_exit_code SYSCALL SYS_exit .normal_path: // setup signals mov x0, x21 bl setup_signals_and_fd LOG log_after_sfd, log_after_sfd_len adrp x1, g_sfd add x1, x1, :lo12:g_sfd str x0, [x1] // epoll create bl epoll_create_fd adrp x1, g_epfd add x1, x1, :lo12:g_epfd str x0, [x1] LOG log_epoll_add_enter, log_epoll_add_enter_len ldr x0, [x1] adrp x2, g_sfd add x2, x2, :lo12:g_sfd ldr x1, [x2] bl epoll_add_fd LOG log_epoll_add_exit, log_epoll_add_exit_len mov x22, x0 // save return value // Debug: epoll_add_fd result mov x2, x22 adrp x0, log_epoll_add_result add x0, x0, :lo12:log_epoll_add_result mov x1, #log_epoll_add_result_len bl log_prefix_num // If adding signalfd to epoll failed, fall back to blocking wait4 mov x0, x22 // restore return value cmp x0, #0 bge 1f // wait4(-1, &wait_status, 0, NULL) mov x0, #-1 bl get_wait_status_ptr mov x1, x0 mov x2, #0 mov x3, #0 SYSCALL SYS_wait4 bl get_wait_status_ptr ldr x0, [x0] bl extract_exit_code adrp x1, g_child_status add x1, x1, :lo12:g_child_status str x0, [x1] LOG log_sigchld_ok, log_sigchld_ok_len adrp x0, g_child_status add x0, x0, :lo12:g_child_status ldr x0, [x0] SYSCALL SYS_exit 1: // spawn child adrp x0, g_argv_exec add x0, x0, :lo12:g_argv_exec ldr x0, [x0] adrp x1, g_envp add x1, x1, :lo12:g_envp ldr x1, [x1] bl do_spawn adrp x2, g_child_pid add x2, x2, :lo12:g_child_pid str x0, [x2] // Debug: child pid mov x2, x0 adrp x0, log_child_pid add x0, x0, :lo12:log_child_pid mov x1, #log_child_pid_len bl log_prefix_num .main_loop: adrp x0, g_epfd add x0, x0, :lo12:g_epfd ldr x0, [x0] bl epoll_wait_once mov x1, #-1 cmp x0, x1 beq .main_loop mov x19, x0 // ready fd adrp x0, g_sfd add x0, x0, :lo12:g_sfd ldr x0, [x0] cmp x19, x0 bne .check_timer bl read_signalfd_once // On signalfd read errors (EAGAIN/EINTR), ignore and continue. tbnz x0, #63, .main_loop mov x20, x0 // signo mov x2, x20 adrp x0, log_signal_prefix add x0, x0, :lo12:log_signal_prefix mov x1, #log_signal_prefix_len bl log_prefix_num cmp x20, #SIGCHLD beq .handle_chld // If child already exited (e.g. crash + pending restart backoff), avoid // forwarding signals to a potentially reused PGID. adrp x0, g_child_exited add x0, x0, :lo12:g_child_exited ldr x0, [x0] cmp x0, #1 bne 1f adrp x1, g_backoff_tfd add x1, x1, :lo12:g_backoff_tfd ldr x1, [x1] cbz x1, 2f // Backoff is active: only shutdown signals cancel the restart; other signals are ignored. cmp x20, #SIGTERM beq 3f cmp x20, #SIGINT beq 3f cmp x20, #SIGHUP beq 3f cmp x20, #SIGQUIT beq 3f LOG log_info_child_exited_ignore_signal, log_info_child_exited_ignore_signal_len b .main_loop 3: LOG log_info_child_exited_cancel_restart, log_info_child_exited_cancel_restart_len 2: adrp x0, g_child_status add x0, x0, :lo12:g_child_status ldr x0, [x0] SYSCALL SYS_exit 1: // soft signals cmp x20, #SIGTERM beq .soft_signal cmp x20, #SIGINT beq .soft_signal cmp x20, #SIGHUP beq .soft_signal cmp x20, #SIGQUIT beq .soft_signal // forward others adrp x0, g_child_pid add x0, x0, :lo12:g_child_pid ldr x0, [x0] mov x1, x20 bl forward_signal_to_group b .main_loop .soft_signal: adrp x0, g_child_pid add x0, x0, :lo12:g_child_pid ldr x0, [x0] mov x1, x20 bl forward_signal_to_group adrp x0, g_child_pid add x0, x0, :lo12:g_child_pid ldr x0, [x0] bl reap_children_nonblock cbz x0, .after_opportunistic bl get_wait_status_ptr ldr x0, [x0] bl extract_exit_code adrp x1, g_child_status add x1, x1, :lo12:g_child_status str x0, [x1] adrp x1, g_child_exited add x1, x1, :lo12:g_child_exited mov x2, #1 str x2, [x1] LOG log_sigchld_ok, log_sigchld_ok_len adrp x0, g_child_status add x0, x0, :lo12:g_child_status ldr x0, [x0] SYSCALL SYS_exit .after_opportunistic: // If the main child already exited (e.g., crash + pending restart backoff), // treat any shutdown signal as "stop now" and do not restart. adrp x0, g_child_exited add x0, x0, :lo12:g_child_exited ldr x0, [x0] cmp x0, #1 bne .maybe_start_shutdown adrp x0, g_child_status add x0, x0, :lo12:g_child_status ldr x0, [x0] SYSCALL SYS_exit .maybe_start_shutdown: adrp x0, g_shutdown add x0, x0, :lo12:g_shutdown ldr x1, [x0] cmp x1, #1 beq .main_loop LOG log_first_soft, log_first_soft_len adrp x2, g_grace_secs add x2, x2, :lo12:g_grace_secs ldr x2, [x2] adrp x0, log_grace_secs_prefix add x0, x0, :lo12:log_grace_secs_prefix mov x1, #log_grace_secs_prefix_len mov x3, x2 mov x2, x3 bl log_prefix_num adrp x0, g_grace_secs add x0, x0, :lo12:g_grace_secs ldr x0, [x0] bl create_grace_timerfd // If timerfd creation failed, escalate immediately. tbnz x0, #63, .grace_timer_unavailable adrp x1, g_tfd add x1, x1, :lo12:g_tfd str x0, [x1] adrp x2, g_epfd add x2, x2, :lo12:g_epfd ldr x0, [x2] ldr x1, [x1] bl epoll_add_fd cmp x0, #0 bge 1f // epoll add failed -> close timerfd and escalate immediately adrp x0, g_tfd add x0, x0, :lo12:g_tfd ldr x0, [x0] SYSCALL SYS_close adrp x1, g_tfd add x1, x1, :lo12:g_tfd str xzr, [x1] b .grace_timer_unavailable 1: mov x1, #1 adrp x0, g_shutdown add x0, x0, :lo12:g_shutdown str x1, [x0] b .main_loop .grace_timer_unavailable: LOG log_warn_grace_timer_unavailable, log_warn_grace_timer_unavailable_len mov x1, #1 adrp x0, g_shutdown add x0, x0, :lo12:g_shutdown str x1, [x0] // escalate immediately to SIGKILL adrp x0, g_child_pid add x0, x0, :lo12:g_child_pid ldr x0, [x0] mov x1, #SIGKILL bl forward_signal_to_group mov x1, #1 adrp x0, g_killed add x0, x0, :lo12:g_killed str x1, [x0] b .main_loop .check_timer: // Check if this is the backoff timer adrp x0, g_backoff_tfd add x0, x0, :lo12:g_backoff_tfd ldr x0, [x0] cbz x0, .check_grace_timer cmp x19, x0 bne .check_grace_timer // Backoff timer expired -> restart child mov x0, x19 bl read_timerfd_tick // Close backoff timerfd adrp x0, g_backoff_tfd add x0, x0, :lo12:g_backoff_tfd ldr x0, [x0] SYSCALL SYS_close adrp x1, g_backoff_tfd add x1, x1, :lo12:g_backoff_tfd str xzr, [x1] // If shutdown began while we were waiting for backoff, do not restart. adrp x0, g_shutdown add x0, x0, :lo12:g_shutdown ldr x0, [x0] cmp x0, #1 bne 1f adrp x0, g_child_status add x0, x0, :lo12:g_child_status ldr x0, [x0] SYSCALL SYS_exit 1: // Reset state for restart adrp x0, g_child_exited add x0, x0, :lo12:g_child_exited str xzr, [x0] adrp x0, g_shutdown add x0, x0, :lo12:g_shutdown str xzr, [x0] adrp x0, g_killed add x0, x0, :lo12:g_killed str xzr, [x0] // Spawn new child adrp x0, g_argv_exec add x0, x0, :lo12:g_argv_exec ldr x0, [x0] adrp x1, g_envp add x1, x1, :lo12:g_envp ldr x1, [x1] bl do_spawn adrp x2, g_child_pid add x2, x2, :lo12:g_child_pid str x0, [x2] // Increment restart count adrp x0, g_restart_count add x0, x0, :lo12:g_restart_count ldr x1, [x0] add x1, x1, #1 str x1, [x0] mov x2, x1 adrp x0, log_restart_count_prefix add x0, x0, :lo12:log_restart_count_prefix mov x1, #log_restart_count_prefix_len bl log_prefix_num LOG log_restart, log_restart_len b .main_loop .check_grace_timer: // timerfd event -> escalate SIGKILL if child not yet exited adrp x0, g_tfd add x0, x0, :lo12:g_tfd ldr x0, [x0] cbz x0, .main_loop cmp x19, x0 bne .main_loop mov x0, x19 bl read_timerfd_tick adrp x0, g_child_exited add x0, x0, :lo12:g_child_exited ldr x0, [x0] cmp x0, #1 beq .main_loop // avoid killing a reused PGID: if kill(-pgid, 0) reports ESRCH, skip escalation adrp x0, g_child_pid add x0, x0, :lo12:g_child_pid ldr x0, [x0] neg x0, x0 mov x1, #0 SYSCALL SYS_kill cmp x0, #0 bge 1f neg x2, x0 cmp x2, #ESRCH bne 1f LOG log_escalate_skip_dead_pgid, log_escalate_skip_dead_pgid_len b .main_loop 1: LOG log_escalate_kill, log_escalate_kill_len adrp x0, g_child_pid add x0, x0, :lo12:g_child_pid ldr x0, [x0] mov x1, #SIGKILL bl forward_signal_to_group mov x1, #1 adrp x0, g_killed add x0, x0, :lo12:g_killed str x1, [x0] b .main_loop .handle_chld: adrp x0, g_child_pid add x0, x0, :lo12:g_child_pid ldr x0, [x0] bl reap_children_nonblock cbz x0, .main_loop bl get_wait_status_ptr ldr x0, [x0] bl extract_exit_code adrp x1, g_child_status add x1, x1, :lo12:g_child_status str x0, [x1] adrp x1, g_child_exited add x1, x1, :lo12:g_child_exited mov x2, #1 str x2, [x1] // Check if we should restart (only if restart enabled, not in shutdown, and child was killed by signal) adrp x0, g_restart_enabled add x0, x0, :lo12:g_restart_enabled ldr x0, [x0] cmp x0, #1 bne .no_restart adrp x0, g_shutdown add x0, x0, :lo12:g_shutdown ldr x0, [x0] cmp x0, #1 beq .no_restart // Check if child was killed by signal (not normal exit) bl get_wait_status_ptr ldr x0, [x0] and x0, x0, #0x7f cbz x0, .no_restart // Normal exit, don't restart // Check max restarts adrp x0, g_max_restarts add x0, x0, :lo12:g_max_restarts ldr x0, [x0] cbz x0, .check_restart_backoff // 0 means unlimited adrp x1, g_restart_count add x1, x1, :lo12:g_restart_count ldr x1, [x1] cmp x1, x0 bge .max_restarts_reached .check_restart_backoff: // Check if we need backoff adrp x0, g_restart_backoff add x0, x0, :lo12:g_restart_backoff ldr x0, [x0] cbz x0, .restart_immediately mov x3, x0 mov x2, x3 adrp x1, log_restart_backoff_prefix add x1, x1, :lo12:log_restart_backoff_prefix mov x0, x1 mov x1, #log_restart_backoff_prefix_len bl log_prefix_num // Create backoff timerfd mov x0, x3 bl create_grace_timerfd adrp x1, g_backoff_tfd add x1, x1, :lo12:g_backoff_tfd str x0, [x1] // Check if timerfd creation failed tbnz x0, #63, .restart_immediately // Negative means error // Add backoff timerfd to epoll adrp x2, g_epfd add x2, x2, :lo12:g_epfd ldr x0, [x2] ldr x1, [x1] bl epoll_add_fd cmp x0, #0 bge 1f LOG log_warn_backoff_timer_epoll_failed, log_warn_backoff_timer_epoll_failed_len // close backoff timerfd and restart immediately adrp x0, g_backoff_tfd add x0, x0, :lo12:g_backoff_tfd ldr x0, [x0] SYSCALL SYS_close adrp x1, g_backoff_tfd add x1, x1, :lo12:g_backoff_tfd str xzr, [x1] b .restart_immediately 1: LOG log_backoff_wait, log_backoff_wait_len b .main_loop .restart_immediately: // Reset state for restart adrp x0, g_child_exited add x0, x0, :lo12:g_child_exited str xzr, [x0] adrp x0, g_shutdown add x0, x0, :lo12:g_shutdown str xzr, [x0] adrp x0, g_killed add x0, x0, :lo12:g_killed str xzr, [x0] // Spawn new child adrp x0, g_argv_exec add x0, x0, :lo12:g_argv_exec ldr x0, [x0] adrp x1, g_envp add x1, x1, :lo12:g_envp ldr x1, [x1] bl do_spawn adrp x2, g_child_pid add x2, x2, :lo12:g_child_pid str x0, [x2] // Increment restart count adrp x0, g_restart_count add x0, x0, :lo12:g_restart_count ldr x1, [x0] add x1, x1, #1 str x1, [x0] mov x2, x1 adrp x0, log_restart_count_prefix add x0, x0, :lo12:log_restart_count_prefix mov x1, #log_restart_count_prefix_len bl log_prefix_num LOG log_restart, log_restart_len b .main_loop .max_restarts_reached: LOG log_max_restarts, log_max_restarts_len .no_restart: adrp x1, g_killed add x1, x1, :lo12:g_killed ldr x1, [x1] cmp x1, #1 bne .no_force_kill adrp x2, g_exit_code_base add x2, x2, :lo12:g_exit_code_base ldr x2, [x2] add x0, x2, #9 adrp x2, g_child_status add x2, x2, :lo12:g_child_status str x0, [x2] .no_force_kill: LOG log_sigchld_ok, log_sigchld_ok_len adrp x0, g_child_status add x0, x0, :lo12:g_child_status ldr x0, [x0] SYSCALL SYS_exit mini-init-asm-0.3.1+ds/src/arm64/signals.S000066400000000000000000000250451514655602300201110ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .bss .align 6 sigset: .skip 128 signalfd_buf: .skip 128 token_buf: .skip 16 signalfd_fd: .skip 8 rtmin_val: .skip 8 rtmax_val: .skip 8 rt_enabled: .skip 8 .text .align 2 .global setup_signals_and_fd .global read_signalfd_once .global get_signalfd_fd .extern g_verbose .extern log_prefix_num .extern get_timestamp_ptr .extern set_sig_bit .extern parse_u64_dec .extern parse_u64_dec_checked setup_signals_and_fd: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! mov x19, x0 // envp mov x25, #0 // track if EP_SIGNALS was provided adrp x20, sigset add x20, x20, :lo12:sigset mov x21, #0 mov x22, #0 .zero_loop: add x23, x20, x22 stp x21, x21, [x23] add x22, x22, #16 cmp x22, #128 blt .zero_loop mov x0, x20 mov x1, #SIGHUP bl set_sig_bit mov x0, x20 mov x1, #SIGINT bl set_sig_bit mov x0, x20 mov x1, #SIGQUIT bl set_sig_bit mov x0, x20 mov x1, #SIGTERM bl set_sig_bit mov x0, x20 mov x1, #SIGCHLD bl set_sig_bit // extra forwards mov x0, x20 mov x1, #SIGUSR1 bl set_sig_bit mov x0, x20 mov x1, #SIGUSR2 bl set_sig_bit mov x0, x20 mov x1, #SIGPIPE bl set_sig_bit mov x0, x20 mov x1, #SIGWINCH bl set_sig_bit mov x0, x20 mov x1, #SIGTTIN bl set_sig_bit mov x0, x20 mov x1, #SIGTTOU bl set_sig_bit mov x0, x20 mov x1, #SIGCONT bl set_sig_bit mov x0, x20 mov x1, #SIGALRM bl set_sig_bit // Parse optional EP_SIGRTMIN/EP_SIGRTMAX (required for RT* tokens) adrp x0, rt_enabled add x0, x0, :lo12:rt_enabled str xzr, [x0] adrp x0, rtmin_val add x0, x0, :lo12:rtmin_val str xzr, [x0] adrp x0, rtmax_val add x0, x0, :lo12:rtmax_val str xzr, [x0] mov x26, #0 // found rtmin mov x27, #0 // found rtmax mov x22, x19 .find_sigrt_env: ldr x23, [x22] cbz x23, .after_sigrt_env adrp x0, ep_sigrtmin_pref add x0, x0, :lo12:ep_sigrtmin_pref mov x1, x23 bl prefix_match cbz x0, .chk_sigrtmax mov x1, x0 bl parse_u64_dec_checked cbz x1, .next_sigrt_env adrp x2, rtmin_val add x2, x2, :lo12:rtmin_val str x0, [x2] mov x26, #1 b .next_sigrt_env .chk_sigrtmax: adrp x0, ep_sigrtmax_pref add x0, x0, :lo12:ep_sigrtmax_pref mov x1, x23 bl prefix_match cbz x0, .next_sigrt_env mov x1, x0 bl parse_u64_dec_checked cbz x1, .next_sigrt_env adrp x2, rtmax_val add x2, x2, :lo12:rtmax_val str x0, [x2] mov x27, #1 .next_sigrt_env: add x22, x22, #8 b .find_sigrt_env .after_sigrt_env: cmp x26, #1 bne .sigrt_done cmp x27, #1 bne .sigrt_done adrp x0, rtmin_val add x0, x0, :lo12:rtmin_val ldr x0, [x0] cmp x0, #1 blt .sigrt_done cmp x0, #KERNEL_SIGMAX bgt .sigrt_done adrp x1, rtmax_val add x1, x1, :lo12:rtmax_val ldr x1, [x1] cmp x1, #1 blt .sigrt_done cmp x1, #KERNEL_SIGMAX bgt .sigrt_done cmp x0, x1 bge .sigrt_done adrp x2, rt_enabled add x2, x2, :lo12:rt_enabled mov x3, #1 str x3, [x2] .sigrt_done: // Parse EP_SIGNALS (CSV) mov x22, x19 .find_env: ldr x23, [x22] cbz x23, .after_env adrp x0, ep_sigpref add x0, x0, :lo12:ep_sigpref mov x1, x23 bl prefix_match cbz x0, .next_env mov x25, #1 mov x1, x0 // value pointer .parse_csv: ldrb w2, [x1] cbz w2, .after_env cmp w2, #' ' beq .skip_space mov x3, x1 // token start .tok_scan: ldrb w2, [x1] cbz w2, .tok_done cmp w2, #',' beq .tok_done add x1, x1, #1 b .tok_scan .tok_done: mov x24, x1 // token end (delimiter or NUL) // trim trailing spaces mov x10, x24 .trim_end: cmp x10, x3 ble .trim_done ldrb w11, [x10, #-1] cmp w11, #' ' bne .trim_done sub x10, x10, #1 b .trim_end .trim_done: sub x5, x10, x3 // length cmp x5, #0 ble .after_token cmp x5, #15 bgt .unknown_tok adrp x6, token_buf add x6, x6, :lo12:token_buf mov x7, x5 mov x8, x3 .copy_loop: ldrb w9, [x8] strb w9, [x6] add x6, x6, #1 add x8, x8, #1 subs x7, x7, #1 bne .copy_loop mov w9, #0 strb w9, [x6] adrp x6, token_buf add x6, x6, :lo12:token_buf mov x0, x6 adrp x1, tok_USR1 add x1, x1, :lo12:tok_USR1 bl token_eq cmp x0, #1 beq .set_usr1 mov x0, x6 adrp x1, tok_USR2 add x1, x1, :lo12:tok_USR2 bl token_eq cmp x0, #1 beq .set_usr2 mov x0, x6 adrp x1, tok_PIPE add x1, x1, :lo12:tok_PIPE bl token_eq cmp x0, #1 beq .set_pipe mov x0, x6 adrp x1, tok_WINCH add x1, x1, :lo12:tok_WINCH bl token_eq cmp x0, #1 beq .set_winch mov x0, x6 adrp x1, tok_TTIN add x1, x1, :lo12:tok_TTIN bl token_eq cmp x0, #1 beq .set_ttin mov x0, x6 adrp x1, tok_TTOU add x1, x1, :lo12:tok_TTOU bl token_eq cmp x0, #1 beq .set_ttou mov x0, x6 adrp x1, tok_CONT add x1, x1, :lo12:tok_CONT bl token_eq cmp x0, #1 beq .set_cont mov x0, x6 adrp x1, tok_ALRM add x1, x1, :lo12:tok_ALRM bl token_eq cmp x0, #1 beq .set_alrm // Numeric signal token (decimal) ldrb w0, [x6] cmp w0, #'0' blt 1f cmp w0, #'9' bgt 1f mov x1, x6 bl parse_u64_dec_checked cbz x1, .unknown_tok cmp x0, #1 blt .unknown_tok cmp x0, #KERNEL_SIGMAX bgt .unknown_tok cmp x0, #SIGKILL beq .unknown_tok cmp x0, #SIGSTOP beq .unknown_tok mov x1, x0 mov x0, x20 bl set_sig_bit b .after_token 1: // Check if token starts with "RT" (real-time signal) ldrb w0, [x6] cmp w0, #'R' bne .unknown_tok ldrb w0, [x6, #1] cmp w0, #'T' bne .unknown_tok // Require explicit runtime SIGRTMIN/SIGRTMAX adrp x2, rt_enabled add x2, x2, :lo12:rt_enabled ldr x2, [x2] cbz x2, .rt_disabled // Parse number after "RT" add x1, x6, #2 bl parse_u64_dec_checked cbz x1, .unknown_tok // Validate range: 1..(rtmax-rtmin) (rtmin+1..rtmax) cmp x0, #0 beq .unknown_tok adrp x2, rtmax_val add x2, x2, :lo12:rtmax_val ldr x2, [x2] adrp x3, rtmin_val add x3, x3, :lo12:rtmin_val ldr x3, [x3] sub x2, x2, x3 cmp x0, x2 bgt .unknown_tok // Calculate rtmin + number adrp x2, rtmin_val add x2, x2, :lo12:rtmin_val ldr x2, [x2] add x1, x0, x2 mov x0, x20 bl set_sig_bit b .after_token .rt_disabled: LOG log_rt_needs_env, log_rt_needs_env_len b .after_token .unknown_tok: LOG log_unknown_token, log_unknown_token_len b .after_token .set_usr1: mov x0, x20 mov x1, #SIGUSR1 bl set_sig_bit b .after_token .set_usr2: mov x0, x20 mov x1, #SIGUSR2 bl set_sig_bit b .after_token .set_pipe: mov x0, x20 mov x1, #SIGPIPE bl set_sig_bit b .after_token .set_winch: mov x0, x20 mov x1, #SIGWINCH bl set_sig_bit b .after_token .set_ttin: mov x0, x20 mov x1, #SIGTTIN bl set_sig_bit b .after_token .set_ttou: mov x0, x20 mov x1, #SIGTTOU bl set_sig_bit b .after_token .set_cont: mov x0, x20 mov x1, #SIGCONT bl set_sig_bit b .after_token .set_alrm: mov x0, x20 mov x1, #SIGALRM bl set_sig_bit .after_token: mov x1, x24 ldrb w2, [x1] cbz w2, .after_env add x1, x1, #1 b .parse_csv .skip_space: add x1, x1, #1 b .parse_csv .next_env: add x22, x22, #8 b .find_env .after_env: cbz x25, .skip_epsig_log LOG log_epsig_done, log_epsig_done_len .skip_epsig_log: // Block signals mov x0, #SIG_BLOCK mov x1, x20 mov x2, #0 mov x3, #8 SYSCALL SYS_rt_sigprocmask // signalfd mov x0, #-1 mov x1, x20 mov x2, #8 mov x3, #SFD_CLOEXEC mov x4, #SFD_NONBLOCK orr x3, x3, x4 SYSCALL SYS_signalfd4 cmp x0, #0 blt .sfd_err mov x24, x0 adrp x1, signalfd_fd add x1, x1, :lo12:signalfd_fd str x24, [x1] mov x2, x24 adrp x0, log_sfd_prefix add x0, x0, :lo12:log_sfd_prefix mov x1, #log_sfd_prefix_len bl log_prefix_num mov x0, x24 b .ret .ret: ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret .sfd_err: LOG log_sfd_err, log_sfd_err_len mov x0, #-1 b .ret read_signalfd_once: adrp x0, signalfd_fd add x0, x0, :lo12:signalfd_fd ldr x0, [x0] adrp x1, signalfd_buf add x1, x1, :lo12:signalfd_buf mov x2, #128 1: SYSCALL SYS_read cmp x0, #128 beq 2f cmp x0, #0 blt .read_err b .read_err 2: adrp x1, signalfd_buf add x1, x1, :lo12:signalfd_buf ldr w0, [x1] ret .read_err: cmp x0, #-EINTR beq 1b mov x0, #-1 ret get_signalfd_fd: adrp x0, signalfd_fd add x0, x0, :lo12:signalfd_fd ldr x0, [x0] ret // token_eq(temp, const) // token_eq(temp, const) token_eq: mov x2, x0 mov x3, x1 .te_loop: ldrb w4, [x2] ldrb w5, [x3] cmp w4, w5 bne .te_ne cbz w4, .te_eq add x2, x2, #1 add x3, x3, #1 b .te_loop .te_eq: mov x0, #1 ret .te_ne: mov x0, #0 ret .text .align 2 prefix_match: mov x2, x0 mov x3, x1 .pm_loop: ldrb w4, [x2] cbz w4, .pm_no ldrb w5, [x3] cmp w4, w5 bne .pm_no cmp w4, #'=' beq .pm_value add x2, x2, #1 add x3, x3, #1 b .pm_loop .pm_value: add x3, x3, #1 mov x0, x3 ret .pm_no: mov x0, #0 ret .section .rodata log_sfd_prefix: .asciz "DEBUG: signalfd created fd=" .equ log_sfd_prefix_len, . - log_sfd_prefix - 1 log_sfd_err: .asciz "ERROR: signalfd4 failed" .equ log_sfd_err_len, . - log_sfd_err - 1 log_unknown_token: .asciz "WARN: Unknown EP_SIGNALS token ignored" .equ log_unknown_token_len, . - log_unknown_token - 1 log_rt_needs_env: .asciz "WARN: RT* EP_SIGNALS tokens require EP_SIGRTMIN and EP_SIGRTMAX; ignoring RT token" .equ log_rt_needs_env_len, . - log_rt_needs_env - 1 log_epsig_done: .asciz "DEBUG: EP_SIGNALS parsed" .equ log_epsig_done_len, . - log_epsig_done - 1 ep_sigpref: .asciz "EP_SIGNALS=" ep_sigrtmin_pref: .asciz "EP_SIGRTMIN=" ep_sigrtmax_pref: .asciz "EP_SIGRTMAX=" tok_USR1: .asciz "USR1" tok_USR2: .asciz "USR2" tok_PIPE: .asciz "PIPE" tok_WINCH: .asciz "WINCH" tok_TTIN: .asciz "TTIN" tok_TTOU: .asciz "TTOU" tok_CONT: .asciz "CONT" tok_ALRM: .asciz "ALRM" mini-init-asm-0.3.1+ds/src/arm64/spawn.S000066400000000000000000000034061514655602300175760ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .section .rodata .align 2 log_before_clone: .asciz "DEBUG: spawn before clone" .equ log_before_clone_len, . - log_before_clone - 1 log_after_clone_parent: .asciz "DEBUG: spawn clone parent" .equ log_after_clone_parent_len, . - log_after_clone_parent - 1 log_after_clone_child: .asciz "DEBUG: spawn clone child" .equ log_after_clone_child_len, . - log_after_clone_child - 1 .text .align 2 .global do_spawn .type do_spawn, %function do_spawn: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! mov x19, x0 // argv_exec mov x20, x1 // envp LOG log_before_clone, log_before_clone_len // clone(flags=SIGCHLD, child_stack=NULL to mimic fork semantics) mov x0, #SIGCHLD mov x1, #0 // child_stack = NULL -> kernel provides mov x2, #0 mov x3, #0 mov x4, #0 SYSCALL SYS_clone cmp x0, #0 bgt .parent beq .child b .out_restore .parent: LOG log_after_clone_parent, log_after_clone_parent_len // x0 already pid b .out_restore .child: LOG log_after_clone_child, log_after_clone_child_len // Unblock all signals in child (so TERM/INT etc are delivered) sub sp, sp, #16 mov x1, #0 str x1, [sp] mov x0, #SIG_SETMASK mov x1, sp // empty mask mov x2, #0 mov x3, #8 SYSCALL SYS_rt_sigprocmask add sp, sp, #16 mov x0, #0 SYSCALL SYS_setsid mov x0, #0 mov x1, #0 SYSCALL SYS_setpgid ldr x0, [x19] mov x1, x19 mov x2, x20 SYSCALL SYS_execve mov x0, #127 SYSCALL SYS_exit .out_restore: ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 .out: ret mini-init-asm-0.3.1+ds/src/arm64/time.S000066400000000000000000000045211514655602300174030ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .bss .align 4 time_buffer: .skip 19 .text .align 2 .global get_timestamp_ptr .extern g_verbose .extern g_arm64_fallback get_timestamp_ptr: // In EP_ARM64_FALLBACK mode, avoid udiv-heavy timestamp formatting (QEMU-user workaround). adrp x0, g_arm64_fallback add x0, x0, :lo12:g_arm64_fallback ldr x0, [x0] cbnz x0, .blank adrp x0, g_verbose add x0, x0, :lo12:g_verbose ldr x0, [x0] cbz x0, .empty stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! stp x23, x24, [sp, #-16]! sub sp, sp, #16 mov x8, SYS_clock_gettime mov x0, CLOCK_REALTIME mov x1, sp svc #0 ldr x19, [sp] // seconds ldr x20, [sp, #8] // nanoseconds add sp, sp, #16 // microseconds = nanoseconds / 1000 mov x0, x20 mov x1, #1000 udiv x21, x0, x1 adrp x22, time_buffer add x22, x22, :lo12:time_buffer // fill buffer with ASCII '0' mov x23, #0 mov x24, #'0' .fill_loop: cmp x23, #19 bge .digits strb w24, [x22, x23] add x23, x23, #1 b .fill_loop .digits: // seconds -> 10 digits mov x0, x19 mov x1, #10 add x3, x22, #9 mov x4, #10 .sec_loop: udiv x5, x0, x1 mul x6, x5, x1 sub x6, x0, x6 add x6, x6, #'0' strb w6, [x3] sub x3, x3, #1 mov x0, x5 subs x4, x4, #1 bne .sec_loop // dot mov w6, #'.' strb w6, [x22, #10] // microseconds -> 6 digits mov x0, x21 mov x1, #10 add x3, x22, #16 mov x4, #6 .micro_loop: udiv x5, x0, x1 mul x6, x5, x1 sub x6, x0, x6 add x6, x6, #'0' strb w6, [x3] sub x3, x3, #1 mov x0, x5 subs x4, x4, #1 bne .micro_loop // trailing space and NUL mov w6, #' ' strb w6, [x22, #17] mov w6, #0 strb w6, [x22, #18] mov x0, x22 ldp x23, x24, [sp], #16 ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret .empty: adrp x0, time_buffer add x0, x0, :lo12:time_buffer mov w1, #0 strb w1, [x0] ret .blank: adrp x0, time_buffer add x0, x0, :lo12:time_buffer mov x1, #0 mov w2, #' ' .blank_loop: cmp x1, #18 bge .blank_done strb w2, [x0, x1] add x1, x1, #1 b .blank_loop .blank_done: ret mini-init-asm-0.3.1+ds/src/arm64/timer.S000066400000000000000000000042201514655602300175610ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .text .align 2 .global create_grace_timerfd .global read_timerfd_tick .extern g_verbose .extern log_prefix_num .extern get_timestamp_ptr create_grace_timerfd: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! mov x19, x0 // seconds mov x0, CLOCK_MONOTONIC mov x1, #TFD_CLOEXEC mov x2, #TFD_NONBLOCK orr x1, x1, x2 SYSCALL SYS_timerfd_create cmp x0, #0 blt .ret // error mov x20, x0 // fd // log creation mov x2, x20 adrp x0, log_tfd_prefix add x0, x0, :lo12:log_tfd_prefix mov x1, #log_tfd_prefix_len bl log_prefix_num sub sp, sp, #32 mov x2, #0 str x2, [sp] // interval sec str x2, [sp, #8] // interval nsec str x19, [sp, #16] // value sec str x2, [sp, #24] // value nsec mov x0, x20 mov x1, #0 mov x2, sp mov x3, #0 .settime_retry: SYSCALL SYS_timerfd_settime cmp x0, #0 bge .settime_ok neg x4, x0 cmp x4, #4 // EINTR beq .settime_retry LOG log_tfd_err, log_tfd_err_len b .after_set .settime_ok: LOG log_tfd_armed, log_tfd_armed_len .after_set: add sp, sp, #32 mov x0, x20 .ret: ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret read_timerfd_tick: sub sp, sp, #8 mov x1, sp mov x2, #8 1: SYSCALL SYS_read cmp x0, #8 beq 2f cmp x0, #0 bgt 3f beq 4f neg x4, x0 cmp x4, #EINTR beq 1b cmp x4, #EAGAIN beq 2f // already drained b 5f 2: mov x0, #0 b 6f 3: // short read 4: // eof mov x0, #-1 b 6f 5: // keep negative errno in x0 6: add sp, sp, #8 ret .section .rodata log_tfd_prefix: .asciz "DEBUG: timerfd created fd=" .equ log_tfd_prefix_len, . - log_tfd_prefix - 1 log_tfd_armed: .asciz "DEBUG: grace timer armed" .equ log_tfd_armed_len, . - log_tfd_armed - 1 log_tfd_err: .asciz "ERROR: timerfd_settime failed" .equ log_tfd_err_len, . - log_tfd_err - 1 mini-init-asm-0.3.1+ds/src/arm64/util.S000066400000000000000000000046341514655602300174270ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .text .align 2 .global set_sig_bit .type set_sig_bit, %function set_sig_bit: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! sub x1, x1, #1 and x19, x1, #63 lsr x20, x1, #6 mov x21, #1 lsl x21, x21, x19 lsl x20, x20, #3 add x22, x0, x20 ldr x0, [x22] orr x0, x0, x21 str x0, [x22] ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret .global parse_u64_dec .type parse_u64_dec, %function parse_u64_dec: mov x0, #0 mov x2, #10 .p_loop: ldrb w3, [x1] cbz w3, .done cmp w3, #'0' blt .done cmp w3, #'9' bgt .done mul x0, x0, x2 sub w3, w3, #'0' add x0, x0, x3 add x1, x1, #1 b .p_loop .done: ret .global parse_u64_dec_checked .type parse_u64_dec_checked, %function // strict parse decimal u64 (digits only, non-empty, full-string) // in: x1 = ptr to NUL-terminated string // out: x0 = value (undefined if x1=0), x1 = 1 if valid else 0 parse_u64_dec_checked: mov x0, #0 mov x2, #10 mov x5, #0 // ok=0 ldrb w3, [x1] cbz w3, .bad .p_loop_checked: ldrb w3, [x1] cbz w3, .ok cmp w3, #'0' blt .bad cmp w3, #'9' bgt .bad mov x5, #1 umulh x4, x0, x2 mul x0, x0, x2 cbnz x4, .bad sub w3, w3, #'0' uxtw x3, w3 adds x0, x0, x3 bcs .bad add x1, x1, #1 b .p_loop_checked .ok: mov x1, x5 ret .bad: mov x1, #0 ret .global write_all .type write_all, %function // write_all(fd=x0, buf=x1, len=x2) -> x0=0 on success, negative errno on error // Retries on EINTR; handles partial writes. write_all: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! mov x19, x0 // fd mov x20, x1 // buf mov x21, x2 // remaining cbz x21, .write_all_ok .write_all_loop: mov x0, x19 mov x1, x20 mov x2, x21 SYSCALL SYS_write cmp x0, #0 bgt .write_all_wrote beq .write_all_err0 neg x4, x0 cmp x4, #EINTR beq .write_all_loop b .out .write_all_wrote: sub x21, x21, x0 add x20, x20, x0 cbnz x21, .write_all_loop .write_all_ok: mov x0, #0 .out: ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret .write_all_err0: mov x0, #-1 b .out mini-init-asm-0.3.1+ds/src/arm64/wait.S000066400000000000000000000027201514655602300174100ustar00rootroot00000000000000.include "syscalls_aarch64.inc" .include "macros_arm64.inc" .extern g_exit_code_base .bss .align 3 wait_status: .skip 8 .text .align 2 .global reap_children_nonblock .global extract_exit_code .global get_wait_status_ptr .global wait_status reap_children_nonblock: stp x29, x30, [sp, #-16]! mov x29, sp stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! mov x21, x0 // main pid mov x19, #0 // found pid mov x22, #0 // saved main status .again: mov x0, #-1 adrp x1, wait_status add x1, x1, :lo12:wait_status mov x2, #1 // WNOHANG mov x3, #0 SYSCALL SYS_wait4 cmp x0, #0 bgt .got beq .done_reap cmp x0, #-4 // -EINTR beq .again b .done_reap .got: cmp x0, x21 bne .again mov x19, x0 adrp x1, wait_status add x1, x1, :lo12:wait_status ldr x22, [x1] b .again .done_reap: cbz x19, .out adrp x1, wait_status add x1, x1, :lo12:wait_status str x22, [x1] .out: mov x0, x19 ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x29, x30, [sp], #16 ret get_wait_status_ptr: adrp x0, wait_status add x0, x0, :lo12:wait_status ret // Given status in x0 -> exit code extract_exit_code: and x1, x0, #0x7f cbz x1, .normal adrp x2, g_exit_code_base add x2, x2, :lo12:g_exit_code_base ldr x2, [x2] add x0, x1, x2 ret .normal: lsr x0, x0, #8 and x0, x0, #0xff ret