mirror of
https://github.com/phantomptr/ps5upload.git
synced 2026-10-19 02:20:02 -05:00
Files gets "Decompress to .exfat…" on a .ffpfsc again. The first build (pulled before 7.0.23) inflated the blocks in the helper with vendored zlib, and the helper died with SIGBUS within minutes of loading it; once the console froze. This one has no decoder: the helper mounts the container read-only the way ShadowMount+ mounts a .ffpfsc (LVD image type 9, read-only single-save flags, 4096-byte sectors, pfs with mkeymode=AC), so the kernel decodes it, and copies the file inside to <name>.part, then renames it in the same folder. The kernel lists that file rounded up to whole 64 KiB blocks; only the inode's real length is copied. The checks from the first build stay: the container's block table is read before the kernel sees it, a destination that exists and a drive without room for the image plus 1 GiB are refused, the size and exFAT boot sector are checked before the rename, and Stop removes the .part and the mount. One unpack runs at a time. The mount is not in the mount table the helper sees, so it is recorded on the /mnt tmpfs (owner pid, LVD unit, .part) and a helper that starts after one died mid-copy releases it; a record of a live instance is left alone. Measured on the FW 13.60 Phat: a 315 MB image (127 MB container) in 3.5 s and a 4.5 GB one (3.0 GB container) in 65 s, both byte-identical to the source; Stop, a takeover mid-copy and a stale record all left no mount, LVD unit or .part behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3cPwhp3UrzayBfyy9PDcN
175 lines
9.3 KiB
C
175 lines
9.3 KiB
C
#ifndef PS5UPLOAD2_RUNTIME_H
|
|
#define PS5UPLOAD2_RUNTIME_H
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <pthread.h>
|
|
|
|
#include "instance_verdict.h"
|
|
|
|
typedef struct {
|
|
uint64_t instance_id;
|
|
uint64_t takeover_nonce; /* random; names this instance in the takeover flag file (takeover_flag.h) */
|
|
/* Mutex guarding the counters below, which node.status reads while the management handlers
|
|
* (on AVA1 worker threads) update them. Keep critical sections short. */
|
|
pthread_mutex_t state_mtx;
|
|
int shutdown_requested;
|
|
int startup_reason;
|
|
/* How the PREVIOUS instance ended, classified once at startup before
|
|
* runtime_write_ownership overwrites the record. Holds a
|
|
* ps5upload2_prior_verdict_t. Reported by node.status so the client and
|
|
* the bug bundle can show it: an externally SIGKILLed predecessor is
|
|
* otherwise completely invisible. */
|
|
int prior_verdict;
|
|
int takeover_requested;
|
|
/* Set by runtime_try_takeover: a prior instance was serving AVA1 and was asked to hand over.
|
|
* Clear: nothing answered, so no prior was asked to exit and none will go by itself. */
|
|
int prior_asked;
|
|
uint64_t started_at_unix;
|
|
/* The prior instance's ownership record as read at startup, before the takeover made it
|
|
* unlink the file. The reap backs a fresh (possibly lost) read with it (review 010). */
|
|
int prior_rec_pid;
|
|
uint64_t prior_rec_started;
|
|
uint64_t command_count;
|
|
char ownership_path[256];
|
|
} runtime_state_t;
|
|
|
|
#define PS5UPLOAD2_STARTUP_FRESH 1
|
|
#define PS5UPLOAD2_STARTUP_TAKEOVER 2
|
|
|
|
int runtime_init(runtime_state_t *state);
|
|
int runtime_write_ownership(const runtime_state_t *state);
|
|
int runtime_clear_ownership(const runtime_state_t *state);
|
|
/* Best-effort SIGKILL of a previous instance that crashed and lingered (the
|
|
* cooperative takeover only handles a healthy old instance). Call AFTER
|
|
* runtime_try_takeover and BEFORE runtime_write_ownership. See runtime.c. */
|
|
void runtime_reap_prior_instance(runtime_state_t *state);
|
|
/* Classify how the previous instance ended and store it on `state`.
|
|
* MUST be called after runtime_init (which fills ownership_path) and
|
|
* BEFORE runtime_write_ownership overwrites the prior record. MUST also be
|
|
* called before any worker thread starts (mgmt thread, shutdown watchdog,
|
|
* etc.): it writes state->prior_verdict WITHOUT holding state_mtx, while
|
|
* handle_status_frame reads it under that mutex. That is only safe because
|
|
* today's one call site runs on the main thread long before any other
|
|
* thread that could read prior_verdict exists — a later call, or a second
|
|
* call from a worker thread, would race. This is a threading contract, not
|
|
* locking: no lock has been added here on purpose. */
|
|
void runtime_classify_prior_instance(runtime_state_t *state);
|
|
/* LAST RESORT. SIGKILL every process whose name carries our own
|
|
* "ps5upload" prefix, except this one. Returns how many were killed.
|
|
*
|
|
* Only ever called after BOTH the cooperative TAKEOVER_REQUEST handshake
|
|
* AND the pid-based reap have failed. The graceful path stays primary
|
|
* because it calls runtime_mark_active_transactions(..., "interrupted")
|
|
* first, which tears the journal down cleanly so upload resume survives;
|
|
* a SIGKILL skips all of that.
|
|
*
|
|
* Unlike the pid-based reap this does NOT need an ownership record, which
|
|
* is the case it exists for: a predecessor whose record was lost or
|
|
* overwritten is otherwise unreachable and the new payload just exits.
|
|
*
|
|
* Deliberately has NO boot-session guard, unlike runtime_reap_prior_instance.
|
|
* That guard exists there because a pid comes from a persisted file that
|
|
* survives reboots. Here every pid comes from a live KERN_PROC_PROC sysctl
|
|
* snapshot taken at call time — the live snapshot IS the boot-session proof,
|
|
* so there is nothing for a started_at/boottime check to add. See the
|
|
* comment at the top of the implementation for the full reasoning. */
|
|
int runtime_sweep_our_instances(void);
|
|
/* Arm a detached watchdog that force-`_exit()`s the process if the graceful
|
|
* shutdown wedges, so a stuck shutdown can't leave an orphan. Call once when
|
|
* shutdown begins (after runtime_wait_for_shutdown returns).
|
|
*
|
|
* `state` is used ONLY to clear the ownership record before the forced
|
|
* `_exit()` — a shutdown that wedges past this watchdog is a deliberate exit
|
|
* we caused, not an external kill, and the ownership record must not
|
|
* outlive it (see instance_verdict.h: a leftover record + dead pid is
|
|
* indistinguishable from `killed_externally` to the next instance). May be
|
|
* NULL to skip that step. */
|
|
void runtime_arm_shutdown_watchdog(const runtime_state_t *state, int exit_code);
|
|
int runtime_ensure_directories(void);
|
|
/* 2.2.52 Tier-1 staging sweep. Removes *.pkg files in
|
|
* PS5UPLOAD2_PKG_TEMP_DIR with mtime older than 24 h — orphans
|
|
* left by desktop crashes mid-install. Call once at payload init,
|
|
* after runtime_ensure_directories. Failure-tolerant: opendir/stat
|
|
* errors are silent so payload still starts even if the dir was
|
|
* never created (e.g. read-only /data, fresh PS5 startup). */
|
|
void runtime_sweep_stale_pkg_temp(void);
|
|
int runtime_try_takeover(runtime_state_t *state);
|
|
|
|
/* Asks this instance to exit (node.shutdown, the old shutdown frame, the takeover flag file). */
|
|
void runtime_request_shutdown(runtime_state_t *state, const char *why);
|
|
/* Blocks until something asks this instance to exit (node.shutdown, the takeover flag file). The
|
|
* AVA1 server runs on its own threads: the main thread has nothing else to serve. */
|
|
void runtime_wait_for_shutdown(runtime_state_t *state);
|
|
|
|
/* Pop a system toast on the PS5 UI (top-right corner). Defined in
|
|
* main.c. Used by mount/unmount/register/launch handlers in
|
|
* runtime.c so users see status on the PS5 even when the desktop
|
|
* client is closed. Empty/NULL message is a no-op. The kernel API
|
|
* caps message length at ~3 KiB internally, but in practice we
|
|
* format ~100-character strings. */
|
|
void pop_notification(const char *message);
|
|
|
|
/* (Re-)apply the full ucred jailbreak: uid/ruid/svuid=0, all-FF
|
|
* sceCaps, sceAttr=0x80000000, debugger authid, root vnode for
|
|
* rootdir + jaildir. Idempotent and safe to call multiple times.
|
|
* Sets `g_ucred_elevation_rc` to the aggregate result (0 = full
|
|
* elevation succeeded, non-zero = at least one kernel write
|
|
* failed — typically because kernel R/W isn't available yet).
|
|
* Called once at startup from main.c, and again lazily from
|
|
* shellui_rpc_init() if the prior elevation attempt failed —
|
|
* lets users load kstuff after the payload was already running
|
|
* and have sensors/launch start working without a reboot. */
|
|
void runtime_apply_ucred_jailbreak(void);
|
|
|
|
/* Installs the AVA1 management table (mgmt_rpc.h) over this runtime. 0, or -1. Call before
|
|
* the AVA1 server starts. */
|
|
int runtime_mgmt_install(runtime_state_t *state);
|
|
|
|
/* `volatile` matches the definition in main.c — without it, the
|
|
* compiler is allowed to cache reads across function calls into a
|
|
* register, which on a multi-thread frame dispatcher (where every
|
|
* connection thread re-runs runtime_apply_ucred_jailbreak() and
|
|
* mutates this) means a reader could observe a stale -1 long after
|
|
* elevation succeeded, or vice versa. The qualifier is the cheapest
|
|
* fix that preserves the existing int-shape extern across all
|
|
* call sites. */
|
|
extern volatile int g_ucred_elevation_rc;
|
|
|
|
/* Writable-roots allowlist check. Returns 1 if the path starts with an
|
|
* allowed root (/data, /user, /mnt/extN, /mnt/usbN, /mnt/ps5upload/<name>,
|
|
* /mnt/shadowmnt) and contains no ".."/"." components that escape it.
|
|
* Also resolves symlinks via realpath() and re-validates the canonical
|
|
* form. Used by every destructive FS handler and backup.c's restore
|
|
* path. NOTE: realpath() fails for not-yet-existing paths (write/mkdir
|
|
* targets) — in that case the lexical check result is returned as-is
|
|
* (there's no symlink to follow yet). */
|
|
int is_path_allowed(const char *p);
|
|
|
|
/* Mount a .ffpfsc read-only so the kernel decodes it (#453): at FS_MOUNT_BASE/.unpack/<tag>, the
|
|
* file inside readable at <mp>/<its name>. 0 with *unit_out set, or -1 with a cause token in err.
|
|
* `note` is a .part file the sweep removes if this helper dies before releasing. Always pair with
|
|
* fs_pfsc_release. fs_pfsc_sweep clears what a dead helper left mounted. */
|
|
int fs_pfsc_mount_ro(const char *image, const char *tag, const char *note, char *mp, size_t mp_cap, int *unit_out,
|
|
char *err, size_t err_cap);
|
|
void fs_pfsc_release(const char *mp, int unit);
|
|
void fs_pfsc_sweep(void);
|
|
|
|
/* The read half of the unsafe-read rule: `/system*` paths the hard-coded management handlers
|
|
* may read when the request asks for it. `is_path_allowed` OR this is the pair every
|
|
* unsafe read must satisfy — the same expression runtime.c uses. */
|
|
int is_safe_unsafe_read_path(const char *p);
|
|
|
|
/* The legacy frame number this thread is currently dispatching, or 0 when it is
|
|
* not inside a request. Thread-local and written only by its own thread, so
|
|
* the fatal-signal handler can read it without a lock.
|
|
*
|
|
* Why it exists: when the helper dies, the persisted stderr.log used to name
|
|
* the faulting call only if it was one of the fault-guarded hardware getters.
|
|
* A crash anywhere else left no trace of what was running. Recording the
|
|
* frame type turns "the helper disconnected" into "the helper died on
|
|
* SIGSEGV while serving frame 68" for EVERY request. */
|
|
extern __thread volatile unsigned int g_inflight_frame_type;
|
|
|
|
#endif
|