updated Documentation for 1.0.0 release

Refresh the install and build guides for the Setup PKG workflow, and remove the obsolete evict helper and its references.
This commit is contained in:
CyberMask367 committed 2026-10-15 23:18:55 +01:00
1 parent 55d8df0c28
commit 4c3dbbd141
7 files changed
+235 -240

No files matched your search

+97 -36
View File
@@ -86,45 +86,62 @@ Details: [`docs/DAEMON.md`](docs/DAEMON.md) · full index at [`docs/`](docs/)
## Install (5 minutes)
> ⚠️ **No PKG — beta is `.elf` only.** Grab the latest test build
> (`orbisrpc_test-build-*.elf`) from the Discord
> (**[discord.gg/BWEyfcT7ZQ](https://discord.gg/BWEyfcT7ZQ)**). This is still
> a test build, not a full release — expect bugs, report them in
> `testers-chat`.
> ✅ **Released as 1.0.0.** Grab `orbisRPC-1.0.0.pkg` from
> [releases](https://github.com/SirHumza/orbisRPC/releases/latest).
1. Before launching, delete the `/data/orbisRPC` folder from your PS4 if
you have one there from an earlier build.
2. Put the `.elf` on the console at `/data/payloads/` (FTP it over, or
USB) — that's where GoldHEN looks for payloads.
3. Launch it from the payload list. First run creates the config file,
then says there's no token yet.
4. Open `/data/orbisRPC/config.json` and add your account token in the
`"token": "SET_ME"` slot. Don't know where the token comes from?
Follow [docs/TOKEN_SETUP.md](docs/TOKEN_SETUP.md) — wrong tokens are
1. Install the PKG on the PS4.
2. Open **orbisRPC Setup** from the home screen.
3. Pick **INSTALL / UPDATE**. It copies the payload into
`/data/payloads/`, writes your config, and offers to add orbisRPC to the
GoldHEN auto-run queue so it starts on every boot. It also offers to add a
Sony TMDB exception to [nanoDNS](https://github.com/peterrsr/nanoDNS) if
you run it — skip either prompt if you don't want it.
4. Enter your Discord token when asked. Don't know where the token comes
from? Follow [docs/TOKEN_SETUP.md](docs/TOKEN_SETUP.md) — wrong tokens are
the #1 install failure.
5. After setting the token, re-run the payload from `/data/payloads`.
Launch a game and watch Discord. Give feedback in `testers-chat`.
5. Restart the console (or re-jailbreak). Launch a game and watch Discord.
After a reboot just re-jailbreak and launch the payload from
`/data/payloads` again.
After that the daemon starts on boot. The app's **UNINSTALL** entry removes
the payload and your configs again.
**Upgrading test builds:** as usual, delete the `/data/orbisRPC` folder
before using a new test build — and you'll have to re-add your token in
the config.
<details>
<summary>Installing the payload by hand instead</summary>
**Auto-run (not recommended on test builds):** you can drop the `.elf` in
`/data/payloads` and add it to the autorun queue in GoldHEN settings, but
test builds change fast — stick to manual runs for now.
You can skip the PKG and run the daemon yourself:
**Official release:** will ship a PKG installer that sets everything up
for you — including autorun and the nanoDNS exception fix.
1. Delete the `/data/orbisRPC` folder from your PS4 if you have one from an
earlier build.
2. Put `orbisRPC.elf` on the console at `/data/payloads/` (FTP it over, or
USB) — that's where GoldHEN looks for payloads.
3. Launch it from the payload list. First run creates the config file, then
says there's no token yet.
4. Open `/data/orbisRPC/config.json` and put your account token in the
`"token": "SET_ME"` slot.
5. Re-run the payload from `/data/payloads`. Launch a game and watch Discord.
The installer is the better path — it does the token, the auto-run entry and
the nanoDNS exception for you — but the manual route works, and it's what you
want if you'd rather not install an app.
To start on boot by hand, add `orbisRPC.elf` to the auto-run queue in GoldHEN
settings.
</details>
✅ Firmware: confirmed working on 9.00 through 13.52 but older fimwares should work.
### What's in the latest test build (0.83)
### What's new in 1.0.0
- Added a community custom list on github to resolve for apps or homebrew that are not on pkg-zone (everyone can contribute to the list).
- dropped the payload bin validation path.
- **orbisRPC Setup** — a real installer app with install/update, settings and
uninstall. Handles the token, GoldHEN auto-run, and the nanoDNS TMDB
exception.
- **In-app update check** — both the app and the daemon notice a newer
release and tell you. Neither replaces the payload on its own; the installer
is what does that.
- **Community title list** — [`config/custom.json`](config/custom.json)
resolves apps and homebrew pkg-zone doesn't carry. Anyone can contribute by
opening a pull request.
- Dropped the payload `.bin` validation path.
## What you'll see
@@ -231,12 +248,38 @@ nanoDNS — then reboot. (This is the step most people miss.)
## Building
Two toolchains are involved. Point them at yours once per shell:
```bash
./scripts/build_sdk_from_source.sh # SDK from git (REQUIRED - see note)
PS4_SDK_SRC=~/ps4-payload-sdk-src \
PS4_PAYLOAD_SDK=~/ps4-payload-sdk ./scripts/build_sdk.sh # daemon payload
make -f installer/Makefile # Setup PKG (OpenOrbis toolchain, llvmshim)
make -C tests test # host unit tests
export OO_PS4_TOOLCHAIN="$HOME/OpenOrbis-PS4-Toolchain" # installer app
export PS4_PAYLOAD_SDK="$HOME/ps4-payload-sdk" # daemon payload
export PS4_SDK_SRC="$HOME/ps4-payload-sdk-src" # 13.52 firmware gate
```
Build the PS4 SDK from git once, before anything else:
```bash
./scripts/build_sdk_from_source.sh
```
Then, from the repo root:
| Command | Builds | Output |
|---|---|---|
| `make all` | payload → stage into the app → package | `output/orbisRPC-1.0.0.elf` + `output/orbisRPC-1.0.0.pkg` |
| `make payload` | daemon payload only | `output/orbisRPC-1.0.0.elf` |
| `make installer` | Setup PKG only | `output/orbisRPC-1.0.0.pkg` |
| `make elf` | compile + link the app, no packaging | — |
| `make gfx` | artwork only (PNG → BMP) | — |
| `make clean` | remove build output | — |
`make all` stages the freshly built payload into `installer/assets/` before
packaging, so the PKG never ships a stale ELF.
Host-side checks, no PS4 toolchain needed:
```bash
make -C tests test # unit tests
make -C tests asan # ASan/UBSan
python3 tests/e2e_consumer.py # contracts + linkage gate
```
@@ -244,11 +287,29 @@ python3 tests/e2e_consumer.py # contracts + linkage gate
⚠️ **Build the SDK from git, not the release ZIP.** The SDK's CRT refuses to
start on firmware it does not list, and terminates the payload before `main()`
with no log line. The released SDK (v0.9) lists 13.50 but not 13.52, so a
ZIP-built payload cannot boot on 13.52. See
[`docs/research/sdk-13.52.md`](docs/research/sdk-13.52.md).
ZIP-built payload cannot boot on 13.52. `PS4_SDK_SRC` is what enables the
build-time check for this; without it the build still succeeds and dies at
runtime. See [`docs/research/sdk-13.52.md`](docs/research/sdk-13.52.md).
Installer app internals: [`docs/INSTALLER.md`](docs/INSTALLER.md).
Build/test/CI details: [`docs/BUILDING.md`](docs/BUILDING.md).
### Adding a title to the community list
Names for apps pkg-zone doesn't carry live in
[`config/custom.json`](config/custom.json). Add one with:
```bash
python3 scripts/add_custom_title.py # asks for TITLEID, name, cover URL
python3 scripts/add_custom_title.py --list
python3 scripts/add_custom_title.py --remove ORPC00042
```
Then open a pull request. **The file must stay compact** — `"id":"ORPC00042"`,
no space after the colon. The payload scans for that exact byte run, so
pretty-printing the file makes every lookup miss silently. The script writes it
correctly for you.
## Developing
- CI runs host tests, ASan, contract checks, the SDK payload build with a
+41 -5
View File
@@ -2,23 +2,59 @@
## Requirements
- macOS or Linux with `clang-18`, `lld-18`, `llvm-18`.
- Linux or macOS with `clang-18`, `lld-18`, `llvm-18`.
- The [OpenOrbis PS4 toolchain](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain)
for the Setup app — `OO_PS4_TOOLCHAIN`.
- The ps4-payload-sdk **built from git** — never the release ZIP. The
SDK's CRT gates startup on a per-firmware table (`crt/patch.c`), and the
v0.9 ZIP predates the 13.52 offsets, so a ZIP-built payload dies before
`main()` with no log line. Full story:
[research/sdk-13.52.md](research/sdk-13.52.md).
```bash
export OO_PS4_TOOLCHAIN="$HOME/OpenOrbis-PS4-Toolchain"
export PS4_PAYLOAD_SDK="$HOME/ps4-payload-sdk"
export PS4_SDK_SRC="$HOME/ps4-payload-sdk-src"
./scripts/build_sdk_from_source.sh # once
```
## Root `Makefile`
| Target | Builds | Output |
|---|---|---|
| `make all` | payload → stage into `installer/assets/` → package | `output/orbisRPC-<version>.elf` + `output/orbisRPC-<version>.pkg` |
| `make payload` | daemon payload only | `output/orbisRPC-<version>.elf` |
| `make installer` | Setup PKG only | `output/orbisRPC-<version>.pkg` |
| `make pkg` | stage + package, no payload build | — |
| `make elf` | app compile + link, no packaging | — |
| `make gfx` | `installer/artwork/*.png` → `assets/images/*.bmp` | — |
| `make clean` | remove build output, including `output/` | — |
`payload` needs the ps4 SDK; `installer` and everything else need
`OO_PS4_TOOLCHAIN`. `all` needs both, so it is the one target that requires
the full environment.
The payload name inside the package is derived from `#define PAYLOAD_NAME` in
`installer/paths.h`, and the same string is what `pkg.gp4` lists — so the
staged copy, the packaged file, and the console destination cannot drift apart.
Staging fails loudly if any other `.elf` is sitting in `installer/assets/`,
because `pkg.gp4` is checked in and a stale entry there would ship on every
future build.
App internals: [INSTALLER.md](INSTALLER.md).
## Scripts (`scripts/`)
| Script | What |
|---|---|
| `build_sdk_from_source.sh` | Clones `ps4-payload-dev/sdk`, pins the ref, refuses unless `crt/patch.c` has the firmware cases, `make DESTDIR=… clean install`. Run once. |
| `build_sdk.sh` | Compiles the daemon (`orbisrpc/*.c` + vendored sqlite + mbedTLS) and links `build-sdk/orbisrpc_sdk.elf` with daemon-world libs only. Needs `PS4_PAYLOAD_SDK` (and `PS4_SDK_SRC` for the firmware gate). |
| `build_evict.sh` | Builds `tools/evict.c` → `build-sdk/evict.elf`. Hot-swap helper: stops a running daemon without rebooting. Not needed for the reboot-based tester flow. |
| `build.sh` | Legacy OpenOrbis flavor. Not the shipping path. |
| `build_sdk.sh` | Compiles the daemon (`orbisrpc/*.c` + vendored sqlite + mbedTLS) and links `build-sdk/orbisrpc_sdk.elf` with daemon-world libs only. Needs `PS4_PAYLOAD_SDK` (and `PS4_SDK_SRC` for the firmware gate). Called by `make payload`. |
| `build_pkg.sh` | Wrapper that exports `OO_PS4_TOOLCHAIN` and runs `make`. |
| `gfx2bmp.py` | Artwork PNG → BMP, used by `make gfx`. |
| `gen_icon_header.py` | Regenerates `orbisrpc/icon_embedded.h` from the notification icon. |
| `add_custom_title.py` | Adds/removes entries in `config/custom.json`. |
| `build.sh` | Legacy OpenOrbis flavor of the payload build. Not the shipping path. |
| `make_ca_bundle.py` | Regenerates the curated CA bundle the daemon pins TLS against. |
| `make_manifest.py` | Update-manifest tooling for the signed self-update path. |
## The one rule
+92 -63
View File
@@ -1,80 +1,109 @@
# Installer (Setup PKG, `ORPC00001`)
# Installer (orbisRPC Setup, `ORPC00001`)
> Status: design + code exist in `installer/`, but **no PKG ships with
> test builds** — testers install the `.elf` directly (see repo README).
> The PKG arrives with the official release and will also set up autorun
> and the nanoDNS exception.
The PS4 side ships as one app: **orbisRPC Setup**. It installs the daemon
payload, holds the settings UI, updates, and uninstalls. Title ID `ORPC00001`,
content ID `IV0000-ORPC00001_00-ORBISRPCSETUP000`.
## What it does
Source lives in [`installer/`](../installer), built by the root
[`Makefile`](../Makefile).
One linear flow, forward-only (every No skips ahead, nothing loops back):
confirm → evict old daemon (via `sceKernelLoadStartModule`) → copy
`evict.elf` + `orbisrpc.bin` to `/data/payloads/` (mkdir -p +
byte-count + FNV hash read-back) → pre-saved config with token
(skips entry when valid) → done.
Read-only status screen available via decline.
## The menu
There is no WiFi check. The installer runs sandboxed, so its socket probe
measures the app's network stack, not the payload's — and a FAILED line
reads like a verdict on Discord itself. The daemon reports real reachability
in its own log once started.
| Entry | What it does |
|---|---|
| **INSTALL / UPDATE** | Installs or updates the daemon payload and config |
| **SETTINGS** | Presence text, toggles, preferences — writes `config.json` |
| **UNINSTALL** | Removes the payload and the configs from the console |
The installer never boots anything directly. Starting the daemon is
the `/data/payloads/` directory — pick `evict.elf` first
(removes old orbisrpc instance), then pick `orbisrpc.bin`.
No loopback ports, no injection, no boot proof to go wrong.
## What it writes
All paths from [`installer/paths.h`](../installer/paths.h). The payload name is
`orbisRPC.elf` everywhere — packaged as `assets/orbisRPC.elf`, installed to
`/data/payloads/orbisRPC.elf`.
| Path | What |
|---|---|
| `/data/payloads/orbisRPC.elf` | the daemon payload |
| `/data/payloads/orbisRPC.elf.png` | icon for the GoldHEN launch row |
| `/data/orbisRPC/config.json` | config, including the Discord token |
| `/data/orbisRPC/version` | installed version marker |
| `/data/orbisRPC/install.log` | install-step log |
| `/data/orbisRPC/net.log` | connectivity log |
| `/data/orbisRPC/icon.png` | icon |
| `/data/GoldHEN/payloads.ini` | auto-run queue entry |
| `/data/nanodns/nanodns.ini` | optional Sony TMDB exception |
| `/data/temp/` | staging for downloads (`orbisRPC.elf`, `config.json`) |
## Install flow
```
confirm → sceKernelLoadStartModule(evict.elf) — kills old daemon
→ copy evict.elf to /data/payloads/evict.elf
→ copy orbisrpc.bin to /data/payloads/orbisrpc.bin
→ save config.json with Discord token (pre-populated)
→ (token entry skipped if already valid)
→ done
already installed? -> yes: report and stop (uninstall first)
\-> no:
GitHub release newer than local? -> yes: download payload + config to /data/temp/
preflight: /app0/assets/orbisRPC.elf present? -> no: stop with "missing its payload"
copy payload -> /data/payloads/orbisRPC.elf
copy icon -> /data/payloads/orbisRPC.elf.png
write config -> /data/orbisRPC/config.json (atomic: tmp + fsync + rename)
write version -> /data/orbisRPC/version
ask: GoldHEN present? -> add /user/data/payloads entry + payloads.ini
ask: nanoDNS present? -> add tmdb.np.dl.playstation.net exception
restart prompt if an update replaced a running payload
```
The `evict.elf` is launched via `sceKernelLoadStartModule` during
install — it reads `daemon.lock`, kills the running daemon, exits.
`evict.elf` stays in `/data/payloads/` for future manual use.
## Navigation law
No branch ever returns to start. Cancel/skip always moves forward. The only
exits are Done, explicit close, or a payload write failure.
Buttons: X = enter, O = back. The confirm dialog uses `YESNO` focused on
Yes — the OpenOrbis default `YESNO_FOCUS_NO` puts the console's confirm
button on No, which inverts the whole wizard.
Both the GoldHEN and nanoDNS prompts are **opt-in**, **idempotent** (an entry
that is already present reports "Nothing was changed"), and **skipped silently**
when that tool isn't installed. Neither is required — the daemon works without
them.
## Console facts encoded
- Splash is hidden first thing in `main()` so module loading in
`ui_init()` happens with the splash already gone — no visible delay
between splash hide and first dialog.
- Dialog/IME/IME-backend sysmodules + internal SYSTEM/USER/COMMON/PAD
modules load before use (order matters — `sceCommonDialogInitialize()`
precedes external module loads; PAD is unloaded on exit).
- `stat()`/`fstat()` lie about sizes inside the sandbox (observed 4096/4160
for a 2071552-byte file), so install proof is byte count + FNV hash on
read-back, and status uses open-existence, never stat.
- Config writes are atomic (tmp+fsync+rename) with read-back proof.
- IME wait is bounded; asset key charset validated (bad keys blank the
activity).
- `evict.elf` is loaded via `sceKernelLoadStartModule` because `kill()`
is blocked by the sandbox (EPERM).
## Auto-start
GoldHEN AutoRun: enable it for `orbisrpc` once and the daemon
starts on every jailbreak. The queue is menu-managed; the installer
shows where, it can't write the queue itself.
- `stat()`/`fstat()` lie about sizes inside the sandbox (observed 4096/4160 for
a 2 MB file), so install proof is byte count + FNV hash on read-back, and
existence checks use `open()`, never `stat()`.
- Config writes are atomic (tmp + fsync + rename) with read-back proof.
- The splash is hidden first thing in `main()` so sysmodule loading happens with
no visible gap before the first dialog.
- Dialog / IME / IME-backend sysmodules and the internal SYSTEM, USER, COMMON
and PAD modules load before use. Order matters:
`sceCommonDialogInitialize()` precedes the external module loads. PAD is
unloaded on exit.
- Buttons: X = enter, O = back. The confirm dialog uses `YESNO` focused on Yes —
OpenOrbis's `YESNO_FOCUS_NO` default puts the console's confirm button on No,
which inverts the whole wizard.
- No branch returns to start; cancel and skip always move forward.
- `updatecheck` hits the GitHub releases API. It reports a newer version and
downloads to `/data/temp/`; the daemon separately only *notifies* and never
replaces itself.
## Building
`make -f installer/Makefile` (`OO_PS4_TOOLCHAIN`, llvmshim). Staged assets:
`daemon.elf`, `evict.elf`, `config.json`. The PKG ships all three —
the installer copies both payloads, launches evict.elf to kill the
old daemon, and pre-saves the config token.
Output: `IV0000-ORPC00001_00-ORBISRPCSETUP000.pkg`.
```bash
export OO_PS4_TOOLCHAIN="$HOME/OpenOrbis-PS4-Toolchain"
make installer # or `make all` for payload + PKG
```
| Target | Output |
|---|---|
| `make all` | `output/orbisRPC-<version>.elf` + `output/orbisRPC-<version>.pkg` |
| `make installer` | `output/orbisRPC-<version>.pkg` |
| `make elf` | compile + link only, no packaging |
| `make gfx` | `installer/artwork/*.png` → `installer/assets/images/*.bmp` |
The version comes from `VERSION` at the top of the [`Makefile`](../Makefile),
and is stamped into the output filenames.
`make installer` stages `build-sdk/orbisrpc_sdk.elf` into
`installer/assets/orbisRPC.elf` first — run `make payload` (or `make all`) at
least once beforehand, or staging fails with a clear error. Staging refuses to
run if any *other* `.elf` is sitting in `installer/assets/`, because `pkg.gp4`
is a checked-in file and a stray entry there would ship forever.
Artwork: PNGs live in `installer/artwork/`, converted to BMP by
`scripts/gfx2bmp.py`. `make gfx` is enough after changing one.
## Uninstall
The **UNINSTALL** entry removes the payload and its icon from
`/data/payloads/`, the GoldHEN auto-run entry, the nanoDNS exception, and
`/data/orbisRPC/`. It leaves the app itself installed — remove the PKG
afterwards if you want it gone.
+2 -3
View File
@@ -25,7 +25,6 @@ libkernel.so). Running on-console at 192.168.1.136 via elfldr:9021.
gate, safe mode, signed all-or-nothing staging, boot rollback.
- `cfg.c`/`lock.c`/`timesync.c` — schema'd fallible config, sysctl-liveness
single instance, SNTP wall clock for timer ms.
- `tools/evict.c` — SIGTERM-then-SIGKILL deploy rotation for the locked daemon.
## State machine
@@ -50,7 +49,7 @@ Sandbox param.sfo source, details-ID duplication, user `titles`
overrides, `home_art`, shipped logo default, app.db SQLite names,
self-learning map, presence-builder test seam, small badge, strncpy NUL
hardening, home_art validation, 4004 survival, cfg_save return,
evict.elf deploy tool, CI ASan+e2e jobs.
CI ASan+e2e jobs.
## Not fixed (external / out of scope)
@@ -70,7 +69,7 @@ evict.elf deploy tool, CI ASan+e2e jobs.
## Hardware matrix (proven)
Gateway ready, TLS-ECDHE suite, game detection + ticking timer post-SNTP,
mp: art serving (phone), duplicate-ID gone, evict clean-stop rotations,
mp: art serving (phone), duplicate-ID gone,
reboot recovery. Pending eyes: appdb name flip, badge render, logo tile.
## Readiness
+3 -4
View File
@@ -89,7 +89,6 @@ process table and now use `procwalk.c`, which keeps the self-describing
position:
- `lock.c` — single-instance guard
- `tools/evict.c` — stop a running daemon before replacing it
Measured, old logic vs `procwalk.c`:
@@ -99,9 +98,9 @@ Measured, old logic vs `procwalk.c`:
| record 736 B, name @611 | **0** | 1 |
| record 520 B, name @120 | **0** | 1 |
A hit is corroboration, not proof: records are zero-padded, so both callers
reject `pid <= 0` before searching, and their failure biases are opposite
(`lock.c` assumes a peer on an unreadable table; `evict.c` refuses to kill).
A hit is corroboration, not proof: records are zero-padded, so the caller
rejects `pid <= 0` before searching, and its failure bias is to assume a peer
holds the lock (an unreadable table means stand down, not that the peer died).
## Cost of the change
-24
View File
@@ -1,24 +0,0 @@
#!/bin/sh
# build_evict.sh - evict.elf via ps4-payload-sdk (daemon-world linkage only).
# Usage: PS4_PAYLOAD_SDK=/path ./scripts/build_evict.sh
set -e
SDK="${PS4_PAYLOAD_SDK:-$HOME/ps4-payload-sdk/ps4-payload-sdk}"
CC="$SDK/bin/orbis-clang"
export PS4_PAYLOAD_SDK="$SDK"
export PATH="$HOME/llvmshim:$PATH"
OUT="build-sdk"
mkdir -p "$OUT"
if [ ! -x "$CC" ]; then
echo "FAIL: $CC not found. Build the SDK from git first:"
echo " ./scripts/build_sdk_from_source.sh"
exit 1
fi
echo "=== evict (SDK) ==="
# evict.c shares orbisrpc/procwalk.c with the daemon so the kinfo_proc record
# walk stays offset-free in one place. A private second copy in evict.c is
# exactly how the 9.00-only +447/+72 offsets survived there in the first place.
"$CC" -O2 -Wall -DORBISRPC_SDK_PAYLOAD -Iorbisrpc -c -o "$OUT/procwalk_evict.o" orbisrpc/procwalk.c \
|| { echo "FAIL: procwalk"; exit 1; }
"$CC" -O2 -Wall -DORBISRPC_SDK_PAYLOAD -Iorbisrpc -o "$OUT/evict.elf" \
tools/evict.c "$OUT/procwalk_evict.o" || { echo "FAIL: evict"; exit 1; }
ls -la "$OUT/evict.elf"
-105
View File
@@ -1,105 +0,0 @@
/* evict.c - stop a running orbisRPC daemon so a fresh payload can take over.
* Reads /data/orbisRPC/daemon.lock, verifies the holder is a live "Payload"
* process via the same offset-free sysctl walk as lock.c/procwalk.c, then
* SIGTERM (clean stop: banks session, clears presence, releases lock) with a
* SIGKILL fallback. Refuses to signal anything it cannot confirm is a live
* Payload peer.
* Build: PS4_PAYLOAD_SDK=... ./scripts/build_evict.sh -> build-sdk/evict.elf
* Run: send via elfldr:9021 or GoldHEN BinLoader:9020, then send the fresh
* orbisrpc_sdk.elf the same way.
* Result: /data/orbisRPC/evict.txt (+ klog). Takes up to ~34 s: 24 x 1 s
* waiting for a clean stop before the SIGKILL fallback. */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include "../orbisrpc/procwalk.h"
#define LOCK_PATH "/data/orbisRPC/daemon.lock"
#define RESULT_PATH "/data/orbisRPC/evict.txt"
static void report(const char *msg){
printf("evict: %s\n", msg);
int fd = open(RESULT_PATH, O_CREAT|O_TRUNC|O_WRONLY, 0644);
if(fd >= 0){ (void)write(fd, msg, strlen(msg)); (void)write(fd, "\n", 1); close(fd); }
}
/* 1 = pid is a live "Payload" process, 0 otherwise. Mirrors lock.c.
*
* This used to read the name at byte 447 and the pid at byte 72 of each
* kinfo_proc record. Those are 9.00 offsets; on newer firmware they address
* the wrong bytes, so every live payload looked dead and evict would delete
* the lock and report STALE while the daemon kept running -- leaving exactly
* the two-daemon pileup it exists to prevent.
*
* Narrow by exact process name first, then search the record for the pid.
* Unlike lock.c the bias is the other way here: this gates a kill(), so an
* unreadable table must report "not live" and let the caller do nothing. */
static int payload_live(int pid){
if(pid <= 0 || pid == (int)getpid()) return 0;
int mib[4] = { 1, 14, 8, 0 };
size_t sz = 0;
if(sysctl(mib, 4, NULL, &sz, NULL, 0) != 0) return 0;
static unsigned char buf[256*1024];
if(sz > sizeof buf) return 0; /* unreadable table: fail closed, do not kill */
if(sysctl(mib, 4, buf, &sz, NULL, 0) != 0) return 0;
const unsigned char *rec = NULL;
size_t recsz = 0;
int rc = procwalk_find_comm(buf, sz, "Payload", &rec, &recsz);
if(rc != 0) return 0; /* malformed, or no Payload process at all */
return procwalk_record_has_i32(rec, recsz, pid);
}
int main(void){
char msg[128];
int fd = open(LOCK_PATH, O_RDONLY);
if(fd < 0){ report("NO_LOCK nothing running"); return 0; }
char b[32]; ssize_t n = read(fd, b, sizeof b - 1); close(fd);
if(n <= 0){ report("LOCK_UNREADABLE"); return 1; }
b[n] = 0;
int pid = 0;
if(sscanf(b, "%d", &pid) != 1 || pid <= 0){
remove(LOCK_PATH);
report("LOCK_GARBAGE removed");
return 0;
}
if(!payload_live(pid)){
remove(LOCK_PATH);
snprintf(msg, sizeof msg, "STALE holder=%d not a live Payload; lock removed", pid);
report(msg);
return 0;
}
if(kill(pid, SIGTERM) != 0 && errno == ESRCH){
remove(LOCK_PATH);
report("HOLDER_GONE lock removed");
return 0;
}
/* Grace period: clean stop banks session + releases lock. */
for(int i = 0; i < 24; i++){
sleep(1);
if(!payload_live(pid)){
remove(LOCK_PATH);
snprintf(msg, sizeof msg, "EVICTED holder=%d clean stop", pid);
report(msg);
return 0;
}
}
/* Wedged (e.g. stuck in blocking connect): SIGKILL fallback. */
(void)kill(pid, SIGKILL);
for(int i = 0; i < 10; i++){
sleep(1);
if(!payload_live(pid)){
remove(LOCK_PATH);
snprintf(msg, sizeof msg, "EVICTED holder=%d SIGKILL fallback", pid);
report(msg);
return 0;
}
}
snprintf(msg, sizeof msg, "STUCK holder=%d still alive; reboot console", pid);
report(msg);
return 2;
}