Files
2026-10-14 04:20:24 +02:00

13 KiB

Storage investigation

Observed problem

The previous PS5 host dynamically resolved statfs and getfsstat from libkernel_sys.sprx. Mount enumeration was reported as unavailable. An earlier Files-tab crash had made statvfs a suspected cause, but no crash dump proved that attribution. Neither failed exports nor successful compilation establish the behavior of the replacement on hardware.

System Explorer also called both storage functions during every render, including controller focus changes. It now keeps snapshots and refreshes them when entering a directory, after writing its test file, or with Refresh.

Community references

  • The public SDK filesystem wrappers implement getfsstat, statfs, and fstatfs using syscalls 395, 396, and 397. Its mount wrappers illustrate FreeBSD carry-flag error handling. Our integration only queries storage; it does not mount or unmount filesystems.
  • The SDK statfs header defines the versioned 472-byte ABI with 88-byte mount names. Our pinned SDK supplies this header. The removed prototype checked its ABI, but this did not make direct syscalls valid in a native title. No vendor file was changed.
  • The SDK mount-info example enumerates real filesystem records. This is a payload reference, not the API used by our native title.
  • Community SDK probes distinguish symbol resolution, mount-count probing, path access, and statfs calls. Resolving a symbol or opening a path does not prove mount enumeration.
  • PS5 Web Manager uses statvfs in a payload for storage reporting. A payload's libc and privilege context differ from our standalone title; copying that call would not establish title compatibility. The UI kit remains a reference for the native app host, not the source of storage statistics.

Confirmed native-title failure

Hardware logs captured two launches on 2026-10-05. Both displayed their first frame, then entered fs.readDir and fs.diskUsage. The kernel recorded:

PPRBUG-22859: the process pid=197 directly issued a syscall 396 at 0x400b37
exception: 0xa002030a (SYSTEM_ILLEGAL_FUNCTION_CALL)

The SDK payload syscall approach is therefore invalid in this native title. This is a fatal origin restriction, not an ordinary errno or a proven structure layout mismatch. Compile-time ABI checks cannot detect it. getfsstat was not reached; its direct implementation was removed too.

Current implementation and limits

PS5 diskUsage() opens the requested path with sceKernelOpen, calls the _fstatfs(int, struct statfs*) export from the title's libkernel, then closes the descriptor. Compile-time assertions check the pinned statfs layout.

The native-title precedent is ProsperoStore's storage probe, which declares the same underscored ABI and queries an opened descriptor. UnityOrbisBridge also resolves _fstatfs from libkernel and calls it with a descriptor. This is stronger evidence than a stub symbol alone, but does not replace a test of our artifact on the user's firmware and loader.

Unlike PS5Upload's privileged payload getmntinfo snapshot, mounts() discovers accessible filesystem records through _fstatfs. It probes sandbox roots, common storage paths, USB slots, and immediate children of / and /mnt. Queries stop at 96; output stops at 64 records. Records use the kernel's mount path, device, and filesystem type and are deduplicated by that tuple. Empty mount names are omitted. Inaccessible or deeper mounts can be absent. It neither mounts nor unmounts storage and does not fabricate entries from candidate paths.

PS5Upload's implementation was inspected locally in payload/src/runtime.c (mntinfo_snapshot, handle_fs_list_volumes) and payload/src/hw_info.c (storage_read_part). Its payload privilege and syscall origin differ from a standalone title; its libc wrappers cannot be transplanted blindly.

Directory listing continues to use sceKernelOpen, sceKernelGetdents, and sceKernelClose. The parser now validates the entire record header, record length, name length, terminator, and absence of embedded NULs or slashes before calling into the JavaScript listing builder. Truncated or malformed records produce EIO, and the descriptor closes on success and failure. The local buffer replaces shared static storage, avoiding callback reentrancy corruption. The metadata calls stat, lstat, and fstat are imported system functions, not locally emitted syscall instructions.

Capacity is block size times total blocks; available space is clamped to zero through total. Multiplication happens after conversion to JavaScript's double representation to avoid integer overflow. These are filesystem figures, not folder sizes or Settings storage categories.

EPERM and process access

The user tested the native-wrapper build and reported Operation not permitted for both mounts and disk usage. A running jailbreak does not automatically give every standalone title the filesystem permissions of a resident payload.

The missing startup step is visible in ProsperoStore's main: it requests filesystem access before querying storage. Its Lapy integration packages an exact-title one-shot helper. PS5Upload instead gives its own resident payload credentials and a console root, in runtime_apply_ucred_jailbreak.

Our opt-in filesystemAccess: "console" uses the pinned community client and upstream helper with the pinned SDK compatibility patch. System Explorer opts in. The request runs once before the render thread starts, prefers resident Lapy, otherwise sends the packaged helper to localhost ELF loader port 9021. The client proves write/read/ remove access in /data before accepting success. Root aliases are remapped to accessible sandbox mounts after the process-root change; data can fall back to the title's own /data/ps5-react/<TITLE_ID> directory.

The helper handles its target process's credentials and root references; the framework does not implement kernel offsets, credential writes, raw-pointer fallbacks, or system UI changes. Rejection or failed proof is logged and no alternate privilege mechanism is attempted. This changes the app's access context and still requires hardware qualification on the user's firmware.

Helper transport failure

The next hardware log reported filesystem access status=9 path=helper. Filesystem access was never established. The app bootstrap linked libSceNet but did not initialize it or allocate a network pool. The native socket shim in Kodi PS5 explicitly initializes both before creating sockets. We now do the same, own and release only our pool and successful initialization, and log each failed transport stage using sceNetErrnoLoc. This also distinguishes socket-option, connection, loader-stream and protocol-reply failures from missing permissions.

The requested ShadowMount reference confirms the execution-context difference: its startup sets the payload's credential auth ID; its mount snapshot uses bounded getfsstat(MNT_NOWAIT) calls; and its storage API converts real mount records into capacity JSON, filtering virtual filesystems. Those payload calls cannot simply replace our native-title calls. Its optional HTTP storage API is /api/v1/storage on port 10101 by default; that port was not reachable during this investigation, so it is not used as a fallback.

Validation

npm test exercises valid and malformed packed PS5 directory records, unaligned headers, mount deduplication and capacity bounds, byte-conversion boundaries, and real desktop storage bindings with missing-path errors. npm run build -- --app system-explorer checks PS5 compilation, ELF/SELF integrity, metadata, and ZIP contents. The PS5 ELF imports _fstatfs and the sceKernel* directory functions; the storage object has no syscall instruction.

The user subsequently confirmed the SDK v0.42 helper integration worked; see the exact artifacts. For future builds, open Files, inspect listing and capacity, open an accessible directory, press Refresh, and exit with Options. Record the exact artifact, firmware, and loader. A permission error is different from a crash; do not report successful compilation as working PS5 statistics. The original crash excerpt is stored locally in the ignored .build/system-explorer/diagnostics/storage-crash.txt.

The upstream reference documents helper qualification on firmware 6.02 and 12.70. That evidence does not establish our firmware 13.60 integration. The build checks exact-title helper metadata, one-shot mode and ELF hashes. Local protocol regression tests cover resident claims, partial transfers, rejection, preparation failure and corrupt access proofs.

Helper startup failure after network initialization

The next captured log confirms libSceNet initialization, pool creation, socket options and connection all succeeded. A separate payload.elf process then terminated with SIGILL at 0x200006a38; the app received EOF (recv=0) and reported access status 9. No successful access proof was returned.

Symbolizing the exact packaged helper places the fault at the SDK v0.40 payload_terminate trap when it cannot resolve its exit function. This identifies the failed helper runtime, not a successful elevation followed by a disk error. The crash address alone does not establish which earlier startup call failed.

Source inspection found a concrete firmware incompatibility: SDK v0.40's __kernel_init recognizes 13.00 and 13.20 but has no 13.60 case; its default returns -ENOSYS. SDK v0.42 explicitly includes 13.60. The helper now uses the same SDK as the application instead of the upstream builder's hardcoded v0.40. patches/lapy-sdk-042-attributes.patch migrates the helper to the SDK's 32-byte attribute API: save, compare and restore the complete array, preserving all other bytes when setting the existing access bit. The exact-title protocol, root-layout validation and rollback checks remain in place. This removes a source-confirmed incompatibility; successful elevation still requires hardware validation. No console upload or launch was performed by the build.

Sources: SDK v0.40 kernel initialization, SDK v0.40 termination, SDK v0.42 kernel and attributes API.

The user confirmed the SDK v0.42 replacement worked. This qualifies the exact application/helper pair recorded in HARDWARE.md; the earlier failures above remain historical evidence explaining the integration and its access contract.

Writable roots after elevation

A subsequent Game-store report described fs.writeFile permission failures on the same console where Orbit and Spectrum worked. The local Game-store build receipt enables console filesystem access and includes an exact-title PPSA99058 helper; this is not a missing manifest opt-in. Static inspection found that root selection checked only open(O_RDONLY | O_DIRECTORY). A root can pass that check and still deny the queue's file creation after elevation.

Data and temporary roots now pass exclusive write/read/remove probes before being retained. A failed data-root probe selects the title-owned /data path; temporary storage can use its tmp child. Directory creation and fallback proof failures log their path and errno. Probes never truncate existing files. Mocked native tests cover writable and read-only roots, fallback path mapping, probe collisions, failed readback, cleanup and rejected elevation. These tests establish the root-selection correction; they do not establish that it caused or resolved the reported console failure. The rebuilt Game-store still needs the startup access status, selected roots and a successful queue save recorded on hardware.