BFpilot v0.2.1 test build 4

This commit is contained in:
ItsBlurf committed 2026-06-12 19:59:24 +00:00
1 parent 1ee72b63fa
commit bc2c0e284f
34 files changed
+1036 -385

No files matched your search

+2
View File
@@ -4,6 +4,8 @@ gen/
*.a
*.so
*.elf
__pycache__/
diagnostics/*.json
# Local environment / editor noise
.env
+51 -7
View File
@@ -3,10 +3,12 @@
SHELL := bash
ifeq ($(strip $(PS5_PAYLOAD_SDK)),)
ifneq ($(filter ps5-diag,$(MAKECMDGOALS)),ps5-diag)
$(error PS5_PAYLOAD_SDK is required, e.g. export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk)
endif
else
include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
endif
HOST_UNAME := $(shell uname -s 2>/dev/null || echo unknown)
HOST_IS_WINDOWS := 0
@@ -39,6 +41,8 @@ HELLO_NOTIFY_BIN := tests/hello_notify.elf
INSTALLER_ENTER_PROBE_BIN := tests/installer_enter_probe.elf
INSTALLER_LINKONLY_APPINST_BIN := tests/installer_linkonly_appinst.elf
INSTALLER_RUNTIME_RESOLVE_APPINST_BIN := tests/installer_runtime_resolve_appinst.elf
INSTALLER_SYSMODULE_APPINST_BIN := tests/installer_sysmodule_appinst.elf
INSTALLER_WEBSRV_PATTERN_BIN := tests/installer_websrv_pattern.elf
WEB_SRCS := src/lite_main.c
WEB_SRCS += src/boot_marker.c
@@ -75,6 +79,12 @@ INSTALLER_LINKONLY_APPINST_SRCS += src/notify.c
INSTALLER_RUNTIME_RESOLVE_APPINST_SRCS := tests/installer_runtime_resolve_appinst.c
INSTALLER_RUNTIME_RESOLVE_APPINST_SRCS += src/boot_marker.c
INSTALLER_RUNTIME_RESOLVE_APPINST_SRCS += src/notify.c
INSTALLER_SYSMODULE_APPINST_SRCS := tests/installer_sysmodule_appinst.c
INSTALLER_SYSMODULE_APPINST_SRCS += src/boot_marker.c
INSTALLER_SYSMODULE_APPINST_SRCS += src/notify.c
INSTALLER_WEBSRV_PATTERN_SRCS := tests/installer_websrv_pattern.c
INSTALLER_WEBSRV_PATTERN_SRCS += src/boot_marker.c
INSTALLER_WEBSRV_PATTERN_SRCS += src/notify.c
ASSETS := $(wildcard assets/*)
GEN_SRCS := $(patsubst assets/%,gen/assets/%,$(ASSETS:=.c))
@@ -91,6 +101,8 @@ HELLO_NOTIFY_OBJS := $(patsubst %.c,build/hello-notify/%.o,$(HELLO_NOTIFY_SRCS))
INSTALLER_ENTER_PROBE_OBJS := $(patsubst %.c,build/installer-enter-probe/%.o,$(INSTALLER_ENTER_PROBE_SRCS))
INSTALLER_LINKONLY_APPINST_OBJS := $(patsubst %.c,build/installer-linkonly-appinst/%.o,$(INSTALLER_LINKONLY_APPINST_SRCS))
INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS := $(patsubst %.c,build/installer-runtime-resolve-appinst/%.o,$(INSTALLER_RUNTIME_RESOLVE_APPINST_SRCS))
INSTALLER_SYSMODULE_APPINST_OBJS := $(patsubst %.c,build/installer-sysmodule-appinst/%.o,$(INSTALLER_SYSMODULE_APPINST_SRCS))
INSTALLER_WEBSRV_PATTERN_OBJS := $(patsubst %.c,build/installer-websrv-pattern/%.o,$(INSTALLER_WEBSRV_PATTERN_SRCS))
COMMON_CFLAGS := -Os -Wall -Werror -Isrc
COMMON_CFLAGS += -ffunction-sections -fdata-sections -flto
@@ -151,6 +163,8 @@ INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS += -DBFPILOT_BUILD_MODE=\"installer-run
COMMON_LDFLAGS := -Wl,--gc-sections -flto
APPINST_LDLIBS := -lSceAppInstUtil
PRIVILEGED_APPINST_LDLIBS := -lkernel_sys -lSceSystemService
PRIVILEGED_APPINST_LDLIBS += -lSceUserService -lSceAppInstUtil
CC_CMD := "$(CC)"
STRIP_CMD := "$(STRIP)"
@@ -174,7 +188,10 @@ WINDOWS_BASE_SUFFIX += -l:crtend.o -l:crtn.o
WINDOWS_WEB_SUFFIX := -lkernel_web -lSceLibcInternal -lSceNet
WINDOWS_WEB_SUFFIX += -lc_stub_weak -lkernel_stub_weak
WINDOWS_WEB_SUFFIX += -l:crtend.o -l:crtn.o
WINDOWS_APPINST_SUFFIX := -lSceAppInstUtil $(WINDOWS_BASE_SUFFIX)
WINDOWS_APPINST_SUFFIX := -lkernel_sys -lSceSystemService
WINDOWS_APPINST_SUFFIX += -lSceUserService -lSceAppInstUtil
WINDOWS_APPINST_SUFFIX += -lSceLibcInternal -lc_stub_weak -lkernel_stub_weak
WINDOWS_APPINST_SUFFIX += -l:crtend.o -l:crtn.o
define run
bash -lc '$(RUN_ENV) && $(1)'
endef
@@ -184,7 +201,7 @@ $(1)
endef
endif
all: bfpilot debug full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst
all: bfpilot debug full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst installer-websrv-pattern
bfpilot: $(BFPILOT_BIN)
@@ -210,6 +227,10 @@ installer-linkonly-appinst: $(INSTALLER_LINKONLY_APPINST_BIN)
installer-runtime-resolve-appinst: $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN)
installer-sysmodule-appinst: $(INSTALLER_SYSMODULE_APPINST_BIN)
installer-websrv-pattern: $(INSTALLER_WEBSRV_PATTERN_BIN)
gen/assets:
$(call run,mkdir -p $@)
@@ -260,6 +281,14 @@ build/installer-runtime-resolve-appinst/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) -c $< -o $@)
build/installer-sysmodule-appinst/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) -c $< -o $@)
build/installer-websrv-pattern/%.o: %.c Makefile
$(call run,mkdir -p $(dir $@))
$(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) -c $< -o $@)
ifeq ($(HOST_IS_WINDOWS),1)
$(BFPILOT_BIN): $(BFPILOT_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(BFPILOT_OBJS) $(WINDOWS_WEB_SUFFIX))
@@ -306,6 +335,10 @@ $(INSTALLER_LINKONLY_APPINST_BIN): $(INSTALLER_LINKONLY_APPINST_OBJS)
$(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN): $(INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS) $(WINDOWS_BASE_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
$(INSTALLER_WEBSRV_PATTERN_BIN): $(INSTALLER_WEBSRV_PATTERN_OBJS)
$(call run,$(LD_CMD) -o $@ $(WINDOWS_LINK_PREFIX) $(INSTALLER_WEBSRV_PATTERN_OBJS) $(WINDOWS_APPINST_SUFFIX))
$(call run,$(STRIP_CMD) --strip-all $@)
else
$(BFPILOT_BIN): $(BFPILOT_OBJS)
$(call run,$(CC_CMD) $(BFPILOT_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(BFPILOT_OBJS))
@@ -322,7 +355,7 @@ $(FULL_BIN): $(LEGACY_FULL_OBJS) $(APP_ASSETS)
$(call run,$(STRIP_CMD) --strip-all $@)
$(LAUNCHER_INSTALLER_BIN): $(LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS)
$(call run,$(CC_CMD) $(LAUNCHER_INSTALLER_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(LAUNCHER_INSTALLER_OBJS) $(APPINST_LDLIBS))
$(call run,$(CC_CMD) $(LAUNCHER_INSTALLER_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(LAUNCHER_INSTALLER_OBJS) $(PRIVILEGED_APPINST_LDLIBS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(SAFE_LAUNCHER_INSTALLER_BIN): $(SAFE_LAUNCHER_INSTALLER_OBJS) $(APP_ASSETS)
@@ -352,10 +385,18 @@ $(INSTALLER_LINKONLY_APPINST_BIN): $(INSTALLER_LINKONLY_APPINST_OBJS)
$(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN): $(INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS)
$(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(INSTALLER_RUNTIME_RESOLVE_APPINST_OBJS))
$(call run,$(STRIP_CMD) --strip-all $@)
$(INSTALLER_SYSMODULE_APPINST_BIN): $(INSTALLER_SYSMODULE_APPINST_OBJS)
$(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(INSTALLER_SYSMODULE_APPINST_OBJS) -lSceSysmodule)
$(call run,$(STRIP_CMD) --strip-all $@)
$(INSTALLER_WEBSRV_PATTERN_BIN): $(INSTALLER_WEBSRV_PATTERN_OBJS)
$(call run,$(CC_CMD) $(INSTALLER_RUNTIME_RESOLVE_APPINST_CFLAGS) $(COMMON_LDFLAGS) -o $@ $(INSTALLER_WEBSRV_PATTERN_OBJS) -lkernel_sys -lSceSystemService -lSceUserService -lSceAppInstUtil)
$(call run,$(STRIP_CMD) --strip-all $@)
endif
inspect-imports: all
$(call run,bash scripts/inspect_imports.sh $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN))
$(call run,bash scripts/inspect_imports.sh $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN) $(INSTALLER_WEBSRV_PATTERN_BIN))
deploy-bfpilot: bfpilot
$(call run,$(DEPLOY_CMD) -h $(PS5_HOST) -p $(PS5_PORT) $(BFPILOT_BIN))
@@ -369,8 +410,11 @@ deploy-full: full
deploy-launcher-installer: launcher-installer
$(call run,$(DEPLOY_CMD) -h $(PS5_HOST) -p $(PS5_PORT) $(LAUNCHER_INSTALLER_BIN))
ps5-diag:
$(call run,$(PYTHON) scripts/ps5_diag.py)
clean:
$(call run,rm -rf $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) bfpilot-full-legacy.elf $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) bfpilot-core.elf build gen $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN) tests/hello_appinst.elf)
$(call run,rm -rf $(BFPILOT_BIN) $(DEBUG_BIN) $(FULL_BIN) bfpilot-full-legacy.elf $(LAUNCHER_INSTALLER_BIN) $(SAFE_LAUNCHER_INSTALLER_BIN) bfpilot-core.elf build gen $(HELLO_BOOT_BIN) $(HELLO_HTTP_BIN) $(HELLO_NOTIFY_BIN) $(INSTALLER_ENTER_PROBE_BIN) $(INSTALLER_LINKONLY_APPINST_BIN) $(INSTALLER_RUNTIME_RESOLVE_APPINST_BIN) $(INSTALLER_SYSMODULE_APPINST_BIN) $(INSTALLER_WEBSRV_PATTERN_BIN) tests/hello_appinst.elf)
.SECONDARY: $(GEN_SRCS)
.PHONY: all bfpilot debug full legacy-full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst inspect-imports clean deploy-bfpilot deploy-debug deploy-full deploy-launcher-installer
.PHONY: all bfpilot debug full legacy-full launcher-installer launcher-installer-safe hello-boot hello-http hello-notify installer-enter-probe installer-linkonly-appinst installer-runtime-resolve-appinst installer-sysmodule-appinst installer-websrv-pattern inspect-imports clean deploy-bfpilot deploy-debug deploy-full deploy-launcher-installer ps5-diag
+11 -11
View File
@@ -34,10 +34,10 @@ previous full build.
Use `bfpilot-launcher-installer.elf` only after `bfpilot.elf` works. It is a
separate optional payload whose only job is installing or refreshing the
home-screen tile. It intentionally direct-links AppInstUtil, matching the
websrv/Payload Manager/ftpsrv launcher-install pattern. If the loader rejects
that import, the installer can fail before `main()`. The file manager is still
unaffected.
home-screen tile. It uses the complete pattern proven by ps5 websrv: direct
`kernel_sys`, SystemService, UserService, and AppInstUtil imports, UserService
initialization, and temporary system authid during registration. It restores
the original authid before exit. The file manager is still unaffected.
Use `bfpilot-launcher-installer-safe.elf` only for diagnostics. It does not
direct-link AppInstUtil and reports whether AppInstUtil is already reachable
@@ -105,10 +105,9 @@ send bfpilot-launcher-installer.elf to <PS5_IP>:9021
check /data/BFpilot/launcher-installer.log
```
If `bfpilot-launcher-installer.elf` gives no notification and no log, send
`bfpilot-launcher-installer-safe.elf` next. If the safe installer reaches
`main()` but says AppInstUtil is unavailable, the direct installer is being
rejected before it can run.
If `bfpilot-launcher-installer.elf` gives no notification and no log, run the
probes below to distinguish ordinary entry, incomplete AppInst imports, and the
complete websrv dependency pattern.
If the launcher installer appears to do nothing, run the probes in this order:
@@ -116,6 +115,7 @@ If the launcher installer appears to do nothing, run the probes in this order:
tests/installer_enter_probe.elf
tests/installer_linkonly_appinst.elf
tests/installer_runtime_resolve_appinst.elf
tests/installer_websrv_pattern.elf
```
If `installer_linkonly_appinst.elf` does not create
@@ -190,9 +190,9 @@ make inspect-imports
It fails if `bfpilot.elf` or `bfpilot-debug.elf` contain AppInstUtil,
`sceAppInst`, `app_installer`, or `BFPL00001` installer fingerprints. Direct
`libSceAppInstUtil.sprx` import is allowed only for
`bfpilot-launcher-installer.elf` and
`tests/installer_linkonly_appinst.elf`.
`libSceAppInstUtil.sprx` import is allowed only for installer/probe targets.
The release installer must also import `kernel_sys`, SystemService, and
UserService; `make inspect-imports` enforces this composition.
## Project Layout
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Executable
BIN
View File
Binary file not shown.
Regular → Executable
View File
File mode changed.
+68
View File
@@ -0,0 +1,68 @@
# BFpilot Final Report
## Root Causes And Fixes
Copy performance was code-limited by 256 KiB buffers and a forced `fsync` after
every copied file. Copy/upload now use configurable 1 MiB buffers, copy no
longer forces per-file `fsync`, and transfer diagnostics report bytes,
elapsed-ms, MB/s, paths, device IDs, return state, and errno through job status,
upload responses, logs, and `/api/fs/transfer/stats`.
The tile failure was architectural. BFpilot linked AppInstUtil alone and omitted
the working-project service/privilege context. Live testing proved:
- AppInst-only direct import was rejected before `main()`.
- Safe runtime lookup returned `0xffffffff`.
- Runtime `dlopen` and Sysmodule loading stopped before returning.
- The exact ps5 websrv pattern reached `main()` and AppInst init returned `0`.
`bfpilot-launcher-installer.elf` now directly links `kernel_sys`,
SystemService, UserService, and AppInstUtil; initializes UserService; sets
authid `0x4801000000000013`; performs non-destructive staging/registration; and
restores its original authid before exit. Live `AppInstallTitleDir` returned
`0`, installing `BFPL00001` with `http://127.0.0.1:5905/`.
## Payload Choice
- `bfpilot.elf`: stable file manager only.
- `bfpilot-debug.elf`: file manager startup proof.
- `bfpilot-launcher-installer.elf`: proven isolated tile installer.
- `bfpilot-launcher-installer-safe.elf`: read-only AppInst availability probe.
- `bfpilot-full.elf`: experimental integrated dynamic path; not the recommended installer.
## Live Measurements
| Test | Result |
| --- | --- |
| 8 MiB upload | 3.46 MiB/s server, 2.331 s client |
| 8 MiB same-device copy | 133.33 MiB/s, 60 ms |
| 32 MiB upload | 3.92 MiB/s server, 8.183 s client |
| 32 MiB download | 5.58 MiB/s client |
| 32 MiB same-device copy | 179.78 MiB/s, 178 ms |
Internal copy is 34-46 times faster than upload. `/data` storage and PS5-side
copy are not the current upload bottleneck; the limiting path is LAN/client/HTTP
streaming. Browser folder upload remains sequential per file.
## Exact Commands
```sh
export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk
make clean all
make inspect-imports
python3 payload_sender.py 192.168.1.204 9021 bfpilot.elf
PS5_IP=192.168.1.204 BF_WEB_PORT=5905 make ps5-diag
python3 payload_sender.py 192.168.1.204 9021 bfpilot-launcher-installer.elf
PS5_IP=192.168.1.204 BF_WEB_PORT=5905 python3 scripts/ps5_diag.py --logs
```
## Remaining Limitations
- Results prove the repaired installer on the tested firmware/loader, not every
firmware in existence.
- Download still uses a 64 KiB userspace loop without `sendfile`.
- Recursive directory copy pre-scans for accurate progress.
- USB write throughput was not benchmarked.
- `bfpilot-full.elf` remains less compatible than the isolated installer.
+152
View File
@@ -0,0 +1,152 @@
# BFpilot Root-Cause Audit
Audit date: 2026-06-12
## Safety Finding
The previous `/api/diag` and `/api/fs/usb` implementations were not read-only.
They created, truncated, and deleted probe files under `/data/BFpilot`,
`/user/app`, and discovered mount paths. This violated the diagnostic safety
model. The probes are now removed. Write capability is reported as unknown and
the server advertises `diagReadOnly: true`.
Live writes were limited to BFpilot benchmark artifacts under
`/data/BFpilot/bench`, diagnostic logs under `/data/BFpilot`, and the explicitly
requested launcher staging directory `/user/app/BFPL00001`. No remote files
were deleted. The stable file manager remained available on port 5905.
## Architecture
- `src/lite_main.c`: boot marker, reload handoff, optional integrated launcher
path, then the HTTP listener.
- `src/websrv_lite.c`: threaded HTTP server and `/api/status`/`/api/diag`.
- `src/fs.c`: direct download streaming through `/fs/<path>`.
- `src/transfer.c`: list/stat/du, upload, PS5-side copy/move/delete, and the
single long-running job state.
- `assets/files.html`: browser UI. Copy and move call PS5-side API endpoints;
files do not round-trip through the browser.
- `src/app_installer.c`: dynamically resolved integrated installer used only by
`bfpilot-full.elf`.
- `src/launcher_installer_force_main.c`: direct AppInstUtil installer.
- `src/launcher_installer_main.c`: runtime-resolution diagnostic installer.
## Transfer Paths
### Upload
The browser sends one raw `XMLHttpRequest` body per file to
`POST /api/fs/upload`. There is no multipart parser and no chunked HTTP
support; `Content-Length` is required. Files are uploaded sequentially. The
server receives and writes each chunk directly to the final path. On an upload
failure, only the incomplete file created by that request is removed.
### Download
`src/fs.c` opens the requested file, sends a fixed `Content-Length`, then
streams it with a 64 KiB buffer. It does not use `sendfile`.
### Copy And Move
Copy is PS5-side. It performs a full recursive pre-scan for totals, copies to a
unique `.bfpilot-part-*` staging path, then renames the staging path into place.
Move first attempts `rename`; only `EXDEV` falls back to copy plus source
cleanup. Cancellation is checked between reads and recursive entries.
### Progress
There is one global file-operation job. Counters are atomic; strings and
timestamps use a short mutex. Browser polling runs every 500-700 ms in separate
HTTP client threads and does not hold the worker lock while transferring.
## Performance Root Causes
Confirmed from source:
- Copy and upload used fixed 256 KiB buffers.
- Every copied file ended with synchronous `fsync`, which can dominate copies
involving many files or slow USB media.
- Directory copy performs two recursive walks: pre-scan and copy.
- Upload is sequential per browser file, so many small files pay one HTTP
request/response cost each.
- Download uses a smaller 64 KiB buffer.
- There is no multipart parsing, per-chunk allocation, destination rescanning,
or global lock around transfer I/O.
Changes:
- Copy/upload buffer is now configurable through
`BFPILOT_TRANSFER_BUF_SIZE`, defaulting to 1 MiB.
- Removed per-file `fsync`; close and staged final rename remain checked.
- Added monotonic elapsed milliseconds, bytes read/written, average MB/s,
current/source/destination paths, source/destination `st_dev`, return state,
and errno.
- Added `/api/fs/transfer/stats` for the last upload and extended job status.
Live safe benchmark results are recorded in `docs/CODEX_FINAL_REPORT.md`.
Same-device internal copy reached 133-180 MiB/s while upload reached 3.46-3.92
MiB/s and download reached 5.58 MiB/s. This isolates the current throughput
limit to the LAN/client/HTTP streaming path rather than `/data` storage or the
PS5-side copy loop.
## Launcher Build Boundaries
| Target | Launcher behavior | AppInstUtil handling |
| --- | --- | --- |
| `bfpilot.elf` | none | no launcher source; no direct import |
| `bfpilot-debug.elf` | none | no launcher source; no direct import |
| `bfpilot-full.elf` | experimental integrated install | dynamic resolution via `sce_resolve.c` |
| `bfpilot-launcher-installer-safe.elf` | diagnostic/runtime path | runtime handle/symbol lookup |
| `bfpilot-launcher-installer.elf` | isolated installer | direct websrv-pattern imports: `kernel_sys`, SystemService, UserService, AppInstUtil |
| `tests/installer_linkonly_appinst.elf` | import probe | direct `-lSceAppInstUtil` |
| runtime/entry probes | diagnostics | no direct AppInstUtil import |
The tile metadata in `assets-app/param.json` points to
`http://127.0.0.1:5905/`.
The original direct installer linked only AppInstUtil. That ELF was rejected
before `main()`. Runtime handle lookup returned `0xffffffff`; both `dlopen` and
`sceSysmoduleLoadModuleInternal` stopped before returning. Comparing working
projects exposed the missing architecture:
- ps5-payload-dev/websrv links `kernel_sys`, SystemService, UserService, and
AppInstUtil together.
- It initializes UserService and changes the process authid to
`0x4801000000000013` before AppInst initialization.
- Elf Arsenal independently uses the same authid and documents it as required
for AppInst initialization on firmware 11.00+.
An exact non-installing websrv-pattern probe reached `main()` live and returned
zero from UserService init, authid setup, and AppInst init. The repaired
isolated installer then returned zero from AppInst init and
`AppInstallTitleDir`, registered `BFPL00001`, and restored the original authid.
This proves the root cause was BFpilot's incomplete direct dependency and
privilege setup, not tile metadata.
Launcher refresh previously uninstalled existing/legacy tiles and removed an
invalid app tree before proving registration would succeed. That made failure
recovery worse. Those removals are now skipped. Installers log the title ID,
deep link, embedded asset sizes, PNG signature, init/result codes, and staging
results.
## Build Baseline And Results
Initial local failures:
- `make clean`: failed because `PS5_PAYLOAD_SDK` was absent.
- Public SDK source checkout was not an installed SDK.
- Binary SDK initially failed because repository
`build-tools/llvm-config` lacked executable mode and host `ld.lld`/LLVM tool
names were unavailable.
After downloading the public binary SDK, making `build-tools/llvm-config`
executable, and supplying host LLVM tool aliases:
- baseline `make clean all`: PASS before behavior changes.
- final `make clean all`: PASS.
- final `make inspect-imports`: PASS.
- Python syntax checks: PASS.
- `make ps5-diag`: works without requiring the SDK and safely refuses the old
mutating `/api/diag`.
The workspace has an empty read-only `.git` mount, so changes are separated by
subsystem but actual commits could not be created.
+16 -15
View File
@@ -51,6 +51,9 @@ It is intentionally isolated from the file manager:
- It direct-links AppInstUtil, matching the launcher-install pattern used by
websrv, Payload Manager, and ftpsrv.
- It also links `kernel_sys`, SystemService, and UserService, initializes
UserService, temporarily sets authid `0x4801000000000013`, and restores the
original authid after registration.
- It logs to `/data/BFpilot/launcher-installer.log`.
- It cannot break the file manager because it is not part of `bfpilot.elf`.
@@ -78,11 +81,11 @@ The current probes separate three cases:
`kernel_dynlib_handle` returns `0xffffffff`, AppInstUtil is not reachable
through the safe runtime path.
On that combination, the safe runtime path cannot install. The force installer
uses the fragile direct AppInst import path because that is the pattern used by
working launcher installers. If the loader rejects that import before `main()`,
no code inside that ELF can recover. The file manager remains supported because
it avoids that import entirely.
The AppInst-only direct probe can fail even when the complete websrv pattern
works. `tests/installer_websrv_pattern.elf` reproduces the full dependency and
privilege setup without registering a title. On the tested live PS5 it reached
`main()` and returned zero from AppInst initialization; the repaired isolated
installer then registered the tile successfully.
## Patterns Checked
@@ -98,11 +101,10 @@ That pattern can work in environments where direct AppInst imports are accepted,
but it can also fail before `main()` on loaders that reject or cannot satisfy
the import. BFpilot keeps this pattern out of `bfpilot.elf`.
Some payloads also depend on a system-authid or other elevated runtime context
before AppInst init can succeed on newer firmware. BFpilot does not implement
authid changes, kernel patches, kstuff integration, forced unloads, package
installers, or DRM-related behavior. Those are outside the compatibility goal
for the standalone file manager.
Working payloads depend on a system authid before AppInst init can succeed on
newer firmware. BFpilot implements that only in the isolated launcher installer.
The stable file manager does not change authid and does not import launcher
services.
## Boot Marker
@@ -185,8 +187,7 @@ It fails if `bfpilot.elf` or `bfpilot-debug.elf` contain:
- `app_installer`
- `BFPL00001`
The only payloads allowed to directly import AppInstUtil are
`bfpilot-launcher-installer.elf` and
`tests/installer_linkonly_appinst.elf`. The link-only test exists specifically
to prove whether that loader/firmware rejects AppInstUtil-linked ELFs before
`main()`.
Only installer/probe payloads may directly import AppInstUtil. The release
installer must additionally import `kernel_sys`, SystemService, and UserService.
The link-only test intentionally proves that an incomplete AppInst-only
composition can be rejected before `main()`.
+120 -155
View File
@@ -1,212 +1,177 @@
# BFpilot Firmware Testing
Use this protocol on every firmware/loader combination. Test the file manager
first. Test launcher/AppInstUtil only after the file manager works.
Use this order on every firmware/loader combination. Do not skip ahead to the
launcher. Record the exact payload, endpoint, HTTP status/return code, expected
marker, actual result, and relevant source subsystem.
Record:
Remote diagnostics are read-only. Remote write tests require:
- Firmware version.
- Exploit/loader name and version.
- Payload name.
- Whether `/data/BFpilot/boot.log` received a new entry.
- Whether a notification appeared.
- Whether the HTTP server started.
- `/api/status` and `/api/diag` output when reachable.
- Relevant log files under `/data/BFpilot`.
```sh
export BF_ALLOW_PS5_WRITE=1
export BF_ALLOWED_REMOTE_ROOTS=/data/BFpilot/bench
```
## Stage A: Main File Manager
Never delete remote benchmark artifacts during diagnostics. They have unique
timestamped BFpilot-owned names and can be reviewed before any later cleanup.
1. Send `bfpilot.elf` to the loader on port `9021`.
2. Verify `BFpilot BOOT` notification if notifications are working.
3. Verify `/data/BFpilot/boot.log` has a new `bfpilot file-manager` entry.
4. Open `http://PS5_IP:5905/`.
5. Open `http://PS5_IP:5905/api/status`.
6. Open `http://PS5_IP:5905/api/diag`.
7. Browse `/data`.
8. Browse `/mnt/usb0` if an external drive is connected.
9. Upload a small text file to `/data/BFpilot-test`.
10. Copy, move, and delete that test file.
11. Save `/data/BFpilot/log.txt`.
## 1. Build Locally
Pass criteria:
```sh
export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk
make clean all
make inspect-imports
```
- Boot marker appears.
- Server starts on port `5905`.
- `/`, `/api/status`, `/api/diag`, `/fs`, and `/api/fs/*` are reachable.
- Basic file operations work in `/data/BFpilot-test`.
Expected: all ELFs build; import inspection reports compatibility checks passed.
## Stage B: Debug File Manager
Failure meaning: source/toolchain/import boundary failure. Do not inject.
Use this when Stage A appears to do nothing or when testing a new loader.
## 2. Stable File Manager
1. Send `bfpilot-debug.elf` to the loader on port `9021`.
2. Verify `/data/BFpilot/boot.log` has a new `bfpilot-debug debug` entry.
3. Open `http://PS5_IP:5905/api/status`.
4. Open `http://PS5_IP:5905/api/diag`.
5. Save `/data/BFpilot/log.txt`.
```sh
python3 payload_sender.py "$PS5_IP" "${BF_PAYLOAD_PORT:-9021}" bfpilot.elf
PS5_IP="$PS5_IP" BF_WEB_PORT="${BF_WEB_PORT:-5905}" make ps5-diag
```
Expected result:
Expected notification/log: `BFpilot BOOT`; a new `bfpilot file-manager` line in
`/data/BFpilot/boot.log`; server startup lines in `/data/BFpilot/log.txt`.
- If `boot.log` has no new entry, the payload likely failed before `main()` or
the loader rejected it.
- If `boot.log` has an entry but `/api/status` is unreachable, the failure is
after `main()` and before or during server startup.
Expected endpoints: `/api/status` and `/api/diag` return HTTP 200;
`/api/status` reports `mode=file-manager`, `port=5905`, and
`diagReadOnly=true`.
## Stage C: Alternate Port
Failure meaning:
Use this when another BFpilot instance may already be running on `5905`.
- No boot marker: loader rejected the ELF or failure occurred before `main()`.
- Boot marker but no status: failure after `main()` during startup/bind/listen.
- Status works but diagnostics fail: diagnostic route defect.
1. Send `bfpilot-debug.elf --port 5906` to the loader.
2. Open `http://PS5_IP:5906/api/status`.
3. Open `http://PS5_IP:5906/api/diag`.
4. Save `/data/BFpilot/log.txt`.
Next action: inject the debug payload only if this stage fails.
Pass criteria:
## 3. Debug File Manager
- The new payload can start on `5906` without touching the old server on `5905`.
```sh
python3 payload_sender.py "$PS5_IP" "${BF_PAYLOAD_PORT:-9021}" bfpilot-debug.elf
PS5_IP="$PS5_IP" BF_WEB_PORT="${BF_WEB_PORT:-5905}" make ps5-diag
```
## Stage D: Reinjection
Expected: `bfpilot-debug debug` boot marker and stronger startup notifications.
1. Start `bfpilot.elf`.
2. Reinject `bfpilot.elf` while idle.
3. Verify notification `BFpilot reload: old server detected`.
4. Verify the old listener stops cleanly and the new server starts.
5. Start a large copy or move.
6. Reinject `bfpilot.elf` during the active job.
7. Verify notification `BFpilot reload blocked`.
8. Verify the old job is not killed.
9. Save `/data/BFpilot/log.txt`.
Failure meaning: use `/data/BFpilot/boot.log`, `/data/BFpilot/log.txt`, and
`/data/BFpilot/crash.log` to locate the last checkpoint.
Pass criteria:
## 4. Safe Transfer Benchmark
- Idle reinjection performs a clean handoff.
- Active file jobs are not interrupted.
- Reload exits are logged and notified instead of appearing silent.
Run only after the stable file manager passes and writes are explicitly
authorized:
## Stage E: Launcher Installer
```sh
PS5_IP="$PS5_IP" BF_WEB_PORT="${BF_WEB_PORT:-5905}" \
BF_ALLOW_PS5_WRITE=1 \
BF_ALLOWED_REMOTE_ROOTS=/data/BFpilot/bench \
python3 scripts/ps5_diag.py --bench
```
Only run this after Stage A passes on the same firmware/loader.
Expected files: unique timestamped source/copy folders only under
`/data/BFpilot/bench`.
### Integrated Full Build
Expected results: upload response contains `elapsedMs`, `averageMBps`, and
`destinationDev`; copy job contains bytes read/written, elapsed ms, MB/s,
source/destination device IDs, and errno.
This is the first installer-capable build to try on firmware 11.6, because it
preserves the 0.2.0-style launcher flow that already installed there.
Failure meaning:
1. Send `bfpilot-full.elf` to the loader on port `9021`.
2. Watch for `BFpilot BOOT`, `BFpilot app`, and `BFpilot started`
notifications.
3. Open `http://PS5_IP:5905/`.
4. Save `/data/BFpilot/log.txt`.
5. Open `http://PS5_IP:5905/api/diag` if the web server starts.
6. Check whether the BFpilot tile appears or refreshes.
- Slow client upload and slow server upload metric: network/browser/receive or
storage path.
- Fast server upload metric but slow client elapsed time: browser/network.
- Slow same-device copy: copy/storage path.
- Slow only when device IDs differ: USB/cross-filesystem path.
Pass criteria:
Next action: save the generated diagnostics JSON. Do not benchmark user files.
- On firmware where AppInstUtil runtime resolution works, the tile installs or
refreshes.
- If launcher install fails, the web server still starts and logs the exact
AppInst resolution/init/install return codes.
## 5. Launcher Diagnostic Order
### Isolated Direct Installer
Only continue after stages 1-4 are stable.
Use this after Stage A if you want to test the direct AppInst import pattern
used by websrv/Payload Manager/ftpsrv. It may fail before `main()` if the loader
rejects AppInstUtil imports.
### 5.1 Safe Installer
1. Send `bfpilot-launcher-installer.elf` to the loader on port `9021`.
2. Watch for `BFpilot BOOT` and launcher installer notifications.
3. Check whether the BFpilot tile appears or refreshes.
4. Save `/data/BFpilot/launcher-installer.log`.
5. Save `/data/BFpilot/boot.log`.
Inject `bfpilot-launcher-installer-safe.elf`.
Launcher installer fields to record from the log:
Expected: boot marker and `/data/BFpilot/launcher-installer.log`.
- `entered main`.
- `AppInst init return code`.
- `/user/app/BFPL00001` mkdir return code.
- `param.json` and `icon0.png` write return code.
- `AppInstallTitleDir resolved`.
- `AppInstallTitleDir return code`.
- `AppInstallAll fallback return code`.
- `final result`.
Failure meaning: no marker means failure before `main()` unrelated to direct
AppInst import. A log showing AppInstUtil unavailable means runtime resolution
cannot install on this environment.
Pass criteria:
### 5.2 Entry Probe
- Launcher install may succeed or fail.
- Launcher failure does not affect `bfpilot.elf`.
- File manager remains testable through `http://PS5_IP:5905/`.
Inject `tests/installer_enter_probe.elf`.
If the direct installer gives no notification and no log:
Expected file: `/data/BFpilot/installer_enter_probe.txt`.
1. Send `bfpilot-launcher-installer-safe.elf`.
2. Save `/data/BFpilot/launcher-installer.log`.
3. If the safe installer reaches `main()` but logs
`kernel_dynlib_handle libSceAppInstUtil.sprx rc=0xffffffff`, AppInstUtil is
not available through the safe runtime path.
4. If `tests/installer_linkonly_appinst.elf` also produces no marker, direct
AppInst imports fail before `main()` on that loader/firmware.
Failure meaning: loader rejected even the safe installer-shaped probe.
In that case the direct installer is being rejected before it can run, while
the main file manager remains usable through `http://PS5_IP:5905/`.
### 5.3 Direct Import Probe
## Stage F: Minimal Probe Payloads
Inject `tests/installer_linkonly_appinst.elf`.
Use these to isolate loader, notification, HTTP, and AppInstUtil behavior.
Expected file: `/data/BFpilot/linkonly_appinst_entered.txt`.
### `tests/hello_boot.elf`
Failure meaning: absent marker after the entry probe passed means direct
AppInstUtil imports were rejected before `main()`.
1. Send `tests/hello_boot.elf`.
2. Verify boot marker notification.
3. Verify `/data/BFpilot/boot.log`.
4. Wait 10 seconds for `BFpilot BOOT still alive`.
### 5.4 Runtime Resolve Probe
### `tests/hello_http.elf`
Inject `tests/installer_runtime_resolve_appinst.elf`.
1. Send `tests/hello_http.elf`.
2. Open `http://PS5_IP:5906/api/status`.
Expected files: `/data/BFpilot/runtime_resolve_entered.txt` and
`/data/BFpilot/runtime_resolve_appinst.log`.
### `tests/hello_notify.elf`
Failure meaning: `kernel_dynlib_handle ... rc=0xffffffff` means AppInstUtil is
not reachable through the safe runtime path.
1. Send `tests/hello_notify.elf`.
2. Record notification result from sender output and `/data/BFpilot/boot.log`.
### 5.5 Exact Websrv-Pattern Probe
### `tests/installer_enter_probe.elf`
Build and inject `tests/installer_websrv_pattern.elf`:
1. Send `tests/installer_enter_probe.elf`.
2. Verify `/data/BFpilot/installer_enter_probe.txt` exists.
3. If it does not exist, the loader rejected even the safe installer-shaped
probe before `main()`.
```sh
make installer-websrv-pattern
python3 payload_sender.py "$PS5_IP" "${BF_PAYLOAD_PORT:-9021}" \
tests/installer_websrv_pattern.elf
```
### `tests/installer_linkonly_appinst.elf`
Expected files: `/data/BFpilot/websrv_pattern_entered.txt` and
`/data/BFpilot/websrv_pattern.log`. Expected return codes for UserService init,
authid setup, and AppInst init are all zero.
1. Send `tests/installer_linkonly_appinst.elf`.
2. Verify `/data/BFpilot/linkonly_appinst_entered.txt` exists.
3. If it does not exist, direct AppInstUtil linking is incompatible with that
loader/firmware and installer code must avoid direct AppInst imports.
Failure meaning: the loader/firmware does not support the complete dependency
and privilege composition used by the isolated installer. Do not attempt tile
registration.
### `tests/installer_runtime_resolve_appinst.elf`
### 5.6 Isolated Installer
1. Send `tests/installer_runtime_resolve_appinst.elf`.
2. Verify `/data/BFpilot/runtime_resolve_entered.txt` exists.
3. Save `/data/BFpilot/runtime_resolve_appinst.log`.
4. Use the log to see whether `kernel_dynlib_handle`, symbol lookup, and NID
resolve reached AppInstUtil.
Inject `bfpilot-launcher-installer.elf` only when the websrv-pattern probe
passes.
Interpretation:
Expected: boot marker, launcher log, metadata line showing title
`BFPL00001` and `http://127.0.0.1:5905/`, asset validation, AppInst init code,
staging codes, install code, and final result.
- `kernel_dynlib_handle ... rc=0xffffffff` means AppInstUtil is not already
loaded in this process.
- BFpilot intentionally skips `dlopen` for AppInstUtil after a prior test stops
before the `dlopen` result log, because that path is not safe enough for a
compatibility payload.
Failure meaning:
## Compatibility Table
- No boot marker: the complete direct-import set was rejected before `main()`.
- Init failed: AppInstUtil present but unusable in the current context.
- Authid setup failed: `kernel_sys` privilege operation unavailable.
- Asset/staging failed: path permissions or invalid/missing assets.
- Registration failed: AppInst install call rejected metadata/title directory.
- Installed but wrong URL: compare staged `param.json` with the logged deep link.
| firmware | exploit/loader | payload used | boot marker | server starts | file browse | upload | copy | move | delete | launcher install | notes |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 12.70 | | bfpilot.elf | | | | | | | | n/a | |
| 12.70 | | bfpilot-full.elf | | | | | | | | | |
| 12.70 | | bfpilot-launcher-installer.elf | | n/a | n/a | n/a | n/a | n/a | n/a | | |
| 11.60 | | bfpilot.elf | | | | | | | | n/a | |
| 11.60 | | bfpilot-full.elf | | | | | | | | | |
| 11.60 | | bfpilot-launcher-installer.elf | | n/a | n/a | n/a | n/a | n/a | n/a | | |
### 5.7 Experimental Full Build
Inject `bfpilot-full.elf` last.
Expected: launcher diagnostics plus a functioning web server even if launcher
registration fails.
Failure meaning: integrated dynamic path is incompatible with the environment.
Continue using `bfpilot.elf`; do not merge installer imports into it.
+29
View File
@@ -0,0 +1,29 @@
import socket
def send_payload(jar_path, host, port=50000):
with open(jar_path, 'rb') as f:
data = f.read()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
sock.sendall(data)
sock.close()
print(f"Sent {len(data)} bytes to {host}:{port}")
if __name__ == "__main__":
import sys
if len(sys.argv) == 3:
host = sys.argv[1]
file_path = sys.argv[2]
send_payload(file_path, host)
elif len(sys.argv) == 4:
host = sys.argv[1]
port = int(sys.argv[2])
file_path = sys.argv[3]
send_payload(file_path, host, port)
else:
print("Usage: python payload_sender.py <host> <file>")
print(" python payload_sender.py <host> <port> <file>")
print("Examples:")
print(" python payload_sender.py 192.168.1.100 helloworld.js")
print(" python payload_sender.py 192.168.1.100 50000 helloworld.js")
print(" python payload_sender.py 192.168.1.100 9020 payload.bin")
+14 -8
View File
@@ -58,7 +58,7 @@ check_no_direct_appinst_import() {
local needed
needed="$(needed_libraries "${file}" || true)"
if printf '%s\n' "${needed}" |
grep -E 'libSceAppInstUtil\.sprx|libSceSystemService\.sprx|libSceUserService\.sprx' >/dev/null; then
grep -E 'libSceAppInstUtil\.sprx|libSceSystemService\.sprx|libSceUserService\.sprx|libkernel_sys\.sprx' >/dev/null; then
echo
echo "inspect-imports: unsafe direct launcher import in ${file}" >&2
printf '%s\n' "${needed}" >&2
@@ -89,7 +89,7 @@ for file in "$@"; do
echo "-- undefined symbols --"
if [[ -n "${READELF}" ]]; then
"${READELF}" --dyn-symbols "${file}" | grep -E 'UND|Undefined' || true
"${READELF}" --dyn-syms "${file}" | grep -E 'UND|Undefined' || true
elif [[ -n "${NM}" ]]; then
"${NM}" -u "${file}" || true
else
@@ -108,6 +108,9 @@ for file in "$@"; do
tests/installer_linkonly_appinst.elf|./tests/installer_linkonly_appinst.elf)
continue
;;
tests/installer_websrv_pattern.elf|./tests/installer_websrv_pattern.elf)
continue
;;
esac
if [[ -f "${file}" ]]; then
check_no_direct_appinst_import "${file}"
@@ -137,12 +140,15 @@ if [[ -f tests/installer_linkonly_appinst.elf ]]; then
fi
if [[ -f bfpilot-launcher-installer.elf ]]; then
if ! needed_libraries bfpilot-launcher-installer.elf |
grep -q 'libSceAppInstUtil\.sprx'; then
echo
echo "inspect-imports: bfpilot-launcher-installer.elf must directly import libSceAppInstUtil.sprx" >&2
exit 1
fi
installer_needed="$(needed_libraries bfpilot-launcher-installer.elf || true)"
for required in libkernel_sys.sprx libSceSystemService.sprx \
libSceUserService.sprx libSceAppInstUtil.sprx; do
if ! printf '%s\n' "${installer_needed}" | grep -q "${required}"; then
echo
echo "inspect-imports: bfpilot-launcher-installer.elf missing ${required}" >&2
exit 1
fi
done
fi
echo
+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Collect BFpilot HTTP diagnostics without mutating the PS5."""
from __future__ import annotations
import datetime as dt
import json
import os
import pathlib
import sys
import time
import urllib.parse
import urllib.error
import urllib.request
def fetch_json(base: str, path: str) -> tuple[bool, object]:
url = base + path
try:
with urllib.request.urlopen(url, timeout=8) as response:
raw = response.read()
body = json.loads(raw)
if response.status != 200:
raise RuntimeError(f"HTTP {response.status}")
print(f"PASS GET {url} HTTP {response.status}")
return True, body
except (OSError, ValueError, urllib.error.URLError) as exc:
print(f"FAIL GET {url}: {exc}", file=sys.stderr)
return False, {"ok": False, "url": url, "error": str(exc)}
def fetch_logs(base: str, out_dir: pathlib.Path, stamp: str) -> dict[str, object]:
results: dict[str, object] = {}
for name in ("boot.log", "log.txt", "crash.log", "launcher-installer.log"):
url = base + "/fs/data/BFpilot/" + urllib.parse.quote(name)
try:
with urllib.request.urlopen(url, timeout=8) as response:
data = response.read()
path = out_dir / f"{stamp}-{name}"
path.write_bytes(data)
results[name] = {"ok": True, "bytes": len(data), "saved": str(path)}
print(f"PASS GET {url} HTTP {response.status}")
except urllib.error.HTTPError as exc:
results[name] = {"ok": False, "http": exc.code}
print(f"SKIP GET {url}: HTTP {exc.code}")
except OSError as exc:
results[name] = {"ok": False, "error": str(exc)}
print(f"FAIL GET {url}: {exc}", file=sys.stderr)
return results
def allowed_remote(path: str) -> bool:
roots = os.environ.get("BF_ALLOWED_REMOTE_ROOTS", "/data/BFpilot/bench")
return any(path == root.rstrip("/") or path.startswith(root.rstrip("/") + "/")
for root in roots.split(",") if root.startswith("/data/"))
def run_bench(base: str) -> tuple[bool, object]:
if os.environ.get("BF_ALLOW_PS5_WRITE", "0") != "1":
return False, {"ok": False, "skipped": True,
"reason": "BF_ALLOW_PS5_WRITE is not 1"}
stamp = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
root = "/data/BFpilot/bench"
source_dir = f"{root}/source-{stamp}"
copy_dir = f"{root}/copy-{stamp}"
filename = "bfpilot-bench-8m.bin"
if not allowed_remote(source_dir) or not allowed_remote(copy_dir):
return False, {"ok": False, "skipped": True,
"reason": "benchmark paths not in BF_ALLOWED_REMOTE_ROOTS"}
results: dict[str, object] = {
"sourceDir": source_dir,
"copyDir": copy_dir,
"cleanup": "not performed; diagnostics never delete remote files",
}
for path in (source_dir, copy_dir):
ok, body = fetch_json(base, "/api/fs/mkdir?path=" + urllib.parse.quote(path))
results[f"mkdir:{path}"] = body
if not ok:
return False, results
payload = bytes(8 * 1024 * 1024)
upload_url = (base + "/api/fs/upload?path=" + urllib.parse.quote(source_dir) +
"&filename=" + urllib.parse.quote(filename))
request = urllib.request.Request(upload_url, data=payload, method="POST")
try:
started = time.monotonic()
with urllib.request.urlopen(request, timeout=120) as response:
upload = json.loads(response.read())
results["upload"] = upload
results["clientUploadElapsedMs"] = round((time.monotonic() - started) * 1000)
print(f"PASS POST {upload_url} HTTP {response.status}")
except (OSError, ValueError, urllib.error.URLError) as exc:
results["upload"] = {"ok": False, "error": str(exc)}
print(f"FAIL POST {upload_url}: {exc}", file=sys.stderr)
return False, results
source = f"{source_dir}/{filename}"
copy_path = ("/api/fs/copy?src=" + urllib.parse.quote(source) +
"&dst=" + urllib.parse.quote(copy_dir))
ok, results["copyStart"] = fetch_json(base, copy_path)
if not ok:
return False, results
for _ in range(240):
ok, job = fetch_json(base, "/api/fs/job/status")
results["copyJob"] = job
if not ok or not isinstance(job, dict) or not job.get("busy"):
return bool(ok and isinstance(job, dict) and not job.get("error")), results
time.sleep(0.5)
results["copyJob"] = {"ok": False, "error": "timeout"}
return False, results
def main() -> int:
host = os.environ.get("PS5_IP", "192.168.1.204")
port = os.environ.get("BF_WEB_PORT", "5905")
allow_write = os.environ.get("BF_ALLOW_PS5_WRITE", "0") == "1"
base = f"http://{host}:{port}"
stamp = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
out_dir = pathlib.Path("diagnostics")
out_dir.mkdir(exist_ok=True)
status_ok, status = fetch_json(base, "/api/status")
diag_ok = False
if isinstance(status, dict) and status.get("diagReadOnly") is True:
diag_ok, diag = fetch_json(base, "/api/diag")
elif allow_write:
print("WARN /api/diag read-only capability absent; querying because BF_ALLOW_PS5_WRITE=1")
diag_ok, diag = fetch_json(base, "/api/diag")
else:
diag = {
"ok": False,
"skipped": True,
"reason": "server does not advertise diagReadOnly=true",
}
diag_ok = True
print("SKIP GET /api/diag: deployed server may create/delete probe files")
report = {
"timestampUtc": stamp,
"baseUrl": base,
"allowPs5Write": allow_write,
"status": status,
"diag": diag,
}
if "--bench" in sys.argv:
bench_ok, bench = run_bench(base)
report["bench"] = bench
else:
bench_ok = True
if "--logs" in sys.argv:
report["logs"] = fetch_logs(base, out_dir, stamp)
output = out_dir / f"ps5-diag-{stamp}.json"
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
print(f"saved {output}")
return 0 if status_ok and diag_ok and bench_ok else 1
if __name__ == "__main__":
raise SystemExit(main())
+56 -88
View File
@@ -3,7 +3,6 @@
*/
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
@@ -47,13 +46,11 @@ INCASSET(bfpilot_param_json, "assets-app/param.json");
INCASSET(bfpilot_icon0_png, "assets-app/icon0.png");
typedef int (*appinst_initialize_fn)(void);
typedef int (*appinst_uninstall_fn)(const char *);
typedef int (*app_install_title_dir_fn)(const char *, const char *, void *);
typedef int (*appinst_install_all_fn)(void *);
typedef struct appinst_api {
appinst_initialize_fn initialize;
appinst_uninstall_fn uninstall;
app_install_title_dir_fn install_title_dir;
appinst_install_all_fn install_all;
} appinst_api_t;
@@ -61,12 +58,17 @@ typedef struct appinst_api {
static const uint8_t g_install_marker[] = BFPILOT_INSTALL_MARKER;
static bfpilot_launcher_diag_t g_launcher_diag = {
.launcher_enabled = 1,
.appinst_init_rc = -1,
.launcher_attempted = 0,
.appinst_init_rc = BFPILOT_DIAG_SKIPPED,
.install_title_rc = BFPILOT_DIAG_SKIPPED,
.install_title_dir_resolved = 0,
.uninstall_resolved = 0,
.uninstall_rc = BFPILOT_DIAG_SKIPPED,
.install_all_resolved = 0,
.install_all_rc = BFPILOT_DIAG_SKIPPED,
.user_app_writable = -1,
.launcher_install_rc = -1,
.launcher_install_rc = BFPILOT_DIAG_SKIPPED,
.launcher_final_state = "not-attempted",
};
@@ -80,12 +82,17 @@ static void
reset_launcher_diag(void) {
bfpilot_launcher_diag_t diag = {
.launcher_enabled = 1,
.appinst_init_rc = -1,
.launcher_attempted = 1,
.appinst_init_rc = BFPILOT_DIAG_SKIPPED,
.install_title_rc = BFPILOT_DIAG_SKIPPED,
.install_title_dir_resolved = 0,
.uninstall_resolved = 0,
.uninstall_rc = BFPILOT_DIAG_SKIPPED,
.install_all_resolved = 0,
.install_all_rc = BFPILOT_DIAG_SKIPPED,
.user_app_writable = -1,
.launcher_install_rc = -1,
.launcher_install_rc = BFPILOT_DIAG_SKIPPED,
.launcher_final_state = "resolving-appinst",
};
g_launcher_diag = diag;
publish_launcher_diag();
@@ -111,16 +118,11 @@ resolve_appinst(appinst_api_t *api) {
bfpilot_log("launcher resolve Initialize %s rc=0x%08x",
api->initialize ? "ok" : "missing", rc);
rc = resolve_optional((void **)&api->uninstall,
"sceAppInstUtilAppUnInstall", NULL);
g_launcher_diag.uninstall_resolved = api->uninstall != NULL;
bfpilot_log("launcher resolve AppUnInstall %s rc=0x%08x",
api->uninstall ? "ok" : "missing", rc);
rc = resolve_optional((void **)&api->install_title_dir,
"sceAppInstUtilAppInstallTitleDir",
BFPILOT_NID_INSTALL_TITLE_DIR);
g_launcher_diag.install_title_dir_resolved = api->install_title_dir != NULL;
g_launcher_diag.title_dir_resolved = api->install_title_dir != NULL;
bfpilot_log("launcher resolve AppInstallTitleDir %s rc=0x%08x",
api->install_title_dir ? "ok" : "missing", rc);
@@ -134,25 +136,11 @@ resolve_appinst(appinst_api_t *api) {
}
static int
probe_dir_writable(const char *dir) {
char probe[256];
int n = snprintf(probe, sizeof(probe), "%s/.bfpilot_write_probe", dir);
if(n < 0 || (size_t)n >= sizeof(probe)) return 0;
int fd = open(probe, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if(fd < 0) return 0;
close(fd);
unlink(probe);
return 1;
}
static int
install_app(const appinst_api_t *api, const char *title_id, const char *dir) {
if(api->install_title_dir) {
int err = api->install_title_dir(title_id, dir, NULL);
g_launcher_diag.install_title_rc = err;
g_launcher_diag.launcher_install_rc = err;
publish_launcher_diag();
if(err == 0) return 0;
@@ -163,6 +151,7 @@ install_app(const appinst_api_t *api, const char *title_id, const char *dir) {
if(api->install_all) {
int err = api->install_all(NULL);
g_launcher_diag.install_all_rc = err;
g_launcher_diag.launcher_install_rc = err;
publish_launcher_diag();
bfpilot_log("launcher install AppInstallAll rc=0x%08x", err);
@@ -220,39 +209,6 @@ mkdir_if_needed(const char *path) {
}
static int
remove_tree(const char *path) {
struct stat st;
if(lstat(path, &st) != 0) return errno == ENOENT ? 0 : -1;
if(S_ISDIR(st.st_mode)) {
DIR *dir = opendir(path);
if(!dir) return -1;
struct dirent *ent;
while((ent = readdir(dir))) {
if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue;
char child[512];
int n = snprintf(child, sizeof(child), "%s/%s", path, ent->d_name);
if(n < 0 || (size_t)n >= sizeof(child)) {
closedir(dir);
errno = ENAMETOOLONG;
return -1;
}
if(remove_tree(child) != 0) {
closedir(dir);
return -1;
}
}
closedir(dir);
return rmdir(path);
}
return unlink(path);
}
static int
ensure_data_dir(void) {
if(mkdir_if_needed("/data") != 0) return -1;
@@ -273,13 +229,25 @@ bfpilot_install_app_if_needed(void) {
reset_launcher_diag();
resolve_appinst(&api);
g_launcher_diag.user_app_writable = probe_dir_writable(BFPILOT_APP_ROOT);
g_launcher_diag.user_app_writable = -1;
publish_launcher_diag();
bfpilot_log("launcher install %s writable=%d", BFPILOT_APP_ROOT,
g_launcher_diag.user_app_writable);
bfpilot_log("launcher install %s write probe skipped; installer is "
"non-destructive until staging", BFPILOT_APP_ROOT);
bfpilot_log("launcher metadata title_id=%s "
"deeplink=http://127.0.0.1:5905/ param_bytes=%lu "
"icon_bytes=%lu icon_png_magic=%s",
BFPILOT_APP_TITLE_ID, (unsigned long)bfpilot_param_json_size,
(unsigned long)bfpilot_icon0_png_size,
bfpilot_icon0_png_size >= 8 &&
bfpilot_icon0_png[0] == 0x89 && bfpilot_icon0_png[1] == 'P' &&
bfpilot_icon0_png[2] == 'N' && bfpilot_icon0_png[3] == 'G'
? "yes" : "no");
if(!api.initialize) {
g_launcher_diag.launcher_install_rc = -2;
snprintf(g_launcher_diag.launcher_final_state,
sizeof(g_launcher_diag.launcher_final_state),
"%s", "appinst-unavailable");
publish_launcher_diag();
bfpilot_log("launcher install sceAppInstUtilInitialize not resolved");
bfpilot_notify("BFpilot app failed", "AppInst init not resolved");
@@ -288,6 +256,9 @@ bfpilot_install_app_if_needed(void) {
if(!api.install_title_dir && !api.install_all) {
g_launcher_diag.launcher_install_rc = -2;
snprintf(g_launcher_diag.launcher_final_state,
sizeof(g_launcher_diag.launcher_final_state),
"%s", "install-symbols-unavailable");
publish_launcher_diag();
bfpilot_log("launcher install no AppInst install function resolved");
bfpilot_notify("BFpilot app failed", "AppInst install not resolved");
@@ -324,40 +295,24 @@ bfpilot_install_app_if_needed(void) {
bfpilot_log("launcher install sceAppInstUtilInitialize failed rc=0x%08x",
err);
g_launcher_diag.launcher_install_rc = err;
snprintf(g_launcher_diag.launcher_final_state,
sizeof(g_launcher_diag.launcher_final_state),
"%s", "appinst-init-failed");
publish_launcher_diag();
snprintf(msg, sizeof(msg), "AppInst init failed 0x%08x", err);
bfpilot_notify("BFpilot app failed", msg);
return -1;
}
if(api.uninstall) {
int uninstall_err = api.uninstall(BFPILOT_APP_TITLE_ID);
bfpilot_log("launcher install refresh BFpilot tile rc=0x%08x",
uninstall_err);
uninstall_err = api.uninstall(BFPILOT_INVALID_APP_TITLE_ID);
bfpilot_log("launcher install remove invalid BFpilot tile rc=0x%08x",
uninstall_err);
uninstall_err = api.uninstall(BFPILOT_LEGACY_APP_TITLE_ID);
bfpilot_log("launcher install remove BS5FileManager tile rc=0x%08x",
uninstall_err);
uninstall_err = api.uninstall(BFPILOT_OLD_LEGACY_APP_TITLE_ID);
bfpilot_log("launcher install remove old legacy tile rc=0x%08x",
uninstall_err);
} else {
bfpilot_log("launcher install AppUnInstall not resolved, skipping removes");
}
char invalid_dir[256];
snprintf(invalid_dir, sizeof(invalid_dir), BFPILOT_APP_ROOT "/%s",
BFPILOT_INVALID_APP_TITLE_ID);
if(remove_tree(invalid_dir) != 0) {
bfpilot_log("launcher install warning failed removing %s errno=%d",
invalid_dir, errno);
}
bfpilot_log("launcher existing/legacy tile removal skipped: "
"non-destructive refresh");
if(mkdir_if_needed(app_dir) != 0 || mkdir_if_needed(sce_sys_dir) != 0) {
bfpilot_log("launcher install mkdir failed errno=%d", errno);
g_launcher_diag.launcher_install_rc = -errno;
snprintf(g_launcher_diag.launcher_final_state,
sizeof(g_launcher_diag.launcher_final_state),
"%s", "staging-mkdir-failed");
publish_launcher_diag();
snprintf(msg, sizeof(msg), "mkdir failed errno %d", errno);
bfpilot_notify("BFpilot app failed", msg);
@@ -368,6 +323,9 @@ bfpilot_install_app_if_needed(void) {
bfpilot_log("launcher install failed writing %s errno=%d",
param_path, errno);
g_launcher_diag.launcher_install_rc = -errno;
snprintf(g_launcher_diag.launcher_final_state,
sizeof(g_launcher_diag.launcher_final_state),
"%s", "param-write-failed");
publish_launcher_diag();
bfpilot_notify("BFpilot app failed", "could not write param.json");
return -1;
@@ -377,6 +335,9 @@ bfpilot_install_app_if_needed(void) {
bfpilot_log("launcher install failed writing %s errno=%d",
icon_path, errno);
g_launcher_diag.launcher_install_rc = -errno;
snprintf(g_launcher_diag.launcher_final_state,
sizeof(g_launcher_diag.launcher_final_state),
"%s", "icon-write-failed");
publish_launcher_diag();
bfpilot_notify("BFpilot app failed", "could not write icon0.png");
return -1;
@@ -384,6 +345,10 @@ bfpilot_install_app_if_needed(void) {
err = install_app(&api, BFPILOT_APP_TITLE_ID, BFPILOT_APP_PARENT);
if(err) {
snprintf(g_launcher_diag.launcher_final_state,
sizeof(g_launcher_diag.launcher_final_state),
"%s", "registration-failed");
publish_launcher_diag();
bfpilot_log("launcher install install_app failed rc=0x%08x", err);
snprintf(msg, sizeof(msg), "register BFPL00001 failed 0x%08x", err);
bfpilot_notify("BFpilot app failed", msg);
@@ -401,5 +366,8 @@ bfpilot_install_app_if_needed(void) {
bfpilot_notify("BFpilot app ready",
"Tile BFPL00001 opens http://127.0.0.1:5905/");
snprintf(g_launcher_diag.launcher_final_state,
sizeof(g_launcher_diag.launcher_final_state), "%s", "installed");
publish_launcher_diag();
return 1;
}
+2 -22
View File
@@ -21,7 +21,6 @@
#define BFPILOT_HOMEBREW_DIR "/data/homebrew"
#define BFPILOT_LOG_PATH "/data/BFpilot/log.txt"
#define BFPILOT_CRASH_LOG_PATH "/data/BFpilot/crash.log"
#define BFPILOT_USER_APP_ROOT "/user/app"
#define BFPILOT_EXT0_ROOT "/mnt/ext0"
#define BFPILOT_EXT0_HOMEBREW "/mnt/ext0/homebrew"
@@ -386,24 +385,6 @@ probe_opendir(const char *path) {
}
static int
probe_write_file(const char *path) {
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if(fd < 0) {
bfpilot_diag_set_last_errno(errno);
return 0;
}
ssize_t wr = write(fd, "x", 1);
int close_rc = close(fd);
unlink(path);
if(wr != 1 || close_rc != 0) {
bfpilot_diag_set_last_errno(errno ? errno : EIO);
return 0;
}
return 1;
}
int
bfpilot_diag_can_stat_root(void) {
return probe_stat("/");
@@ -456,12 +437,11 @@ bfpilot_diag_can_opendir_ext0_homebrew(void) {
int
bfpilot_diag_can_write_data_bfpilot(void) {
ensure_diag_dir();
return probe_write_file(BFPILOT_DATA_DIR "/.write_probe");
return -1;
}
int
bfpilot_diag_can_write_user_app(void) {
return probe_write_file(BFPILOT_USER_APP_ROOT "/.bfpilot_write_probe");
return -1;
}
+79 -45
View File
@@ -30,9 +30,8 @@
#define BFPILOT_DATA_ROOT "/data"
#define BFPILOT_DATA_DIR "/data/BFpilot"
#define BFPILOT_LAUNCHER_LOG "/data/BFpilot/launcher-installer.log"
#define BFPILOT_APPINST_MODULE "libSceAppInstUtil.sprx"
#define BFPILOT_NID_INSTALL_TITLE_DIR "Wudg3Xe3heE"
#define BFPILOT_DIAG_SKIPPED (-2147483000)
#define BFPILOT_APPINST_AUTHID UINT64_C(0x4801000000000013)
#define INCASSET(name, file) \
__asm__(".section .rodata\n" \
@@ -49,11 +48,12 @@
INCASSET(bfpilot_param_json, "assets-app/param.json");
INCASSET(bfpilot_icon0_png, "assets-app/icon0.png");
typedef int (*app_install_title_dir_fn)(const char *, const char *, void *);
int sceUserServiceInitialize(void *);
void sceUserServiceTerminate(void);
int sceAppInstUtilInitialize(void);
int sceAppInstUtilAppInstallAll(void *);
int sceAppInstUtilAppUnInstall(const char *);
typedef int (*app_install_title_dir_fn)(const char *, const char *, void *);
static int
@@ -124,26 +124,6 @@ write_file(const char *path, const uint8_t *data, size_t size) {
}
static app_install_title_dir_fn
resolve_title_dir(void) {
uint32_t handle = 0;
installer_log("about to kernel_dynlib_handle %s", BFPILOT_APPINST_MODULE);
int handle_rc = kernel_dynlib_handle(-1, BFPILOT_APPINST_MODULE, &handle);
installer_log("kernel_dynlib_handle %s rc=0x%08x handle=0x%08x",
BFPILOT_APPINST_MODULE, handle_rc, handle);
if(handle_rc != 0) return NULL;
installer_log("about to kernel_dynlib_resolve AppInstallTitleDir nid=%s",
BFPILOT_NID_INSTALL_TITLE_DIR);
app_install_title_dir_fn fn =
(app_install_title_dir_fn)kernel_dynlib_resolve(
-1, handle, BFPILOT_NID_INSTALL_TITLE_DIR);
installer_log("kernel_dynlib_resolve AppInstallTitleDir addr=%p",
(void *)fn);
return fn;
}
int
main(void) {
bfpilot_boot_marker("bfpilot-launcher-installer", BFPILOT_BUILD_MODE);
@@ -154,19 +134,60 @@ main(void) {
char icon_path[256];
int title_dir_rc = BFPILOT_DIAG_SKIPPED;
int install_all_rc = BFPILOT_DIAG_SKIPPED;
int uninstall_rc = BFPILOT_DIAG_SKIPPED;
int final_rc = -1;
pid_t pid = getpid();
uint64_t original_authid = kernel_get_ucred_authid(pid);
int set_authid_rc;
int restore_authid_rc = BFPILOT_DIAG_SKIPPED;
int user_service_rc;
installer_log("entered main direct-appinst tag=%s build=%s",
installer_log("entered main websrv-pattern-appinst tag=%s build=%s",
VERSION_TAG, BUILD_VERSION);
bfpilot_notify("BFpilot launcher installer", "direct AppInst installer");
installer_log("launcher metadata title_id=%s deeplink=http://127.0.0.1:5905/ "
"param_bytes=%lu icon_bytes=%lu icon_png_magic=%s",
BFPILOT_APP_TITLE_ID, (unsigned long)bfpilot_param_json_size,
(unsigned long)bfpilot_icon0_png_size,
bfpilot_icon0_png_size >= 8 &&
bfpilot_icon0_png[0] == 0x89 && bfpilot_icon0_png[1] == 'P' &&
bfpilot_icon0_png[2] == 'N' && bfpilot_icon0_png[3] == 'G'
? "yes" : "no");
bfpilot_notify("BFpilot launcher installer", "websrv-pattern installer");
user_service_rc = sceUserServiceInitialize(NULL);
installer_log("sceUserServiceInitialize return code=0x%08x",
user_service_rc);
installer_log("privilege context original_authid=0x%016llx "
"target_authid=0x%016llx",
(unsigned long long)original_authid,
(unsigned long long)BFPILOT_APPINST_AUTHID);
set_authid_rc = kernel_set_ucred_authid(pid, BFPILOT_APPINST_AUTHID);
installer_log("kernel_set_ucred_authid target return code=0x%08x "
"current_authid=0x%016llx",
set_authid_rc,
(unsigned long long)kernel_get_ucred_authid(pid));
if(set_authid_rc != 0) {
installer_log("final result=privilege_setup_failed final_rc=%d "
"set_authid_rc=0x%08x", final_rc, set_authid_rc);
sceUserServiceTerminate();
bfpilot_notify("BFpilot launcher installer", "system authid setup failed");
return 1;
}
installer_log("about to sceAppInstUtilInitialize");
int appinst_init_rc = sceAppInstUtilInitialize();
installer_log("AppInst init return code=0x%08x", appinst_init_rc);
if(appinst_init_rc != 0) {
if(original_authid != 0) {
restore_authid_rc = kernel_set_ucred_authid(pid, original_authid);
installer_log("kernel_set_ucred_authid restore return code=0x%08x "
"current_authid=0x%016llx",
restore_authid_rc,
(unsigned long long)kernel_get_ucred_authid(pid));
}
installer_log("final result=failed_nonfatal final_rc=%d "
"appinst_init_rc=0x%08x", final_rc, appinst_init_rc);
"appinst_init_rc=0x%08x restore_authid_rc=0x%08x",
final_rc, appinst_init_rc, restore_authid_rc);
sceUserServiceTerminate();
bfpilot_notify("BFpilot launcher installer", "AppInst init failed");
return 1;
}
@@ -177,10 +198,7 @@ main(void) {
snprintf(param_path, sizeof(param_path), "%s/param.json", sce_sys_dir);
snprintf(icon_path, sizeof(icon_path), "%s/icon0.png", sce_sys_dir);
installer_log("about to sceAppInstUtilAppUnInstall %s",
BFPILOT_APP_TITLE_ID);
uninstall_rc = sceAppInstUtilAppUnInstall(BFPILOT_APP_TITLE_ID);
installer_log("uninstall return code=0x%08x", uninstall_rc);
installer_log("existing tile uninstall skipped: non-destructive refresh");
errno = 0;
int mkdir_app_rc = mkdir_if_needed(app_dir);
@@ -201,10 +219,14 @@ main(void) {
installer_log("%s write return code=%d errno=%d",
icon_path, write_icon_rc, errno);
app_install_title_dir_fn install_title_dir = resolve_title_dir();
installer_log("AppInstallTitleDir resolved=%s nid=%s",
install_title_dir ? "yes" : "no",
BFPILOT_NID_INSTALL_TITLE_DIR);
uint32_t appinst_handle = 0;
int handle_rc = kernel_dynlib_handle(-1, "libSceAppInstUtil.sprx",
&appinst_handle);
app_install_title_dir_fn install_title_dir = handle_rc == 0 ?
(app_install_title_dir_fn)kernel_dynlib_resolve(
-1, appinst_handle, "Wudg3Xe3heE") : NULL;
installer_log("AppInstallTitleDir resolved=%s handle_rc=0x%08x",
install_title_dir ? "yes" : "no", handle_rc);
if(mkdir_app_rc == 0 && mkdir_sce_sys_rc == 0 &&
write_param_rc == 0 && write_icon_rc == 0) {
@@ -233,15 +255,27 @@ main(void) {
final_rc = 0;
}
installer_log("final result=%s final_rc=%d appinst_init_rc=0x%08x "
"uninstall_rc=0x%08x mkdir_app_rc=%d mkdir_sce_sys_rc=%d "
"write_param_rc=%d write_icon_rc=%d title_dir_rc=0x%08x "
"install_all_rc=0x%08x",
final_rc == 0 ? "installed" : "failed_nonfatal", final_rc,
appinst_init_rc, uninstall_rc, mkdir_app_rc,
mkdir_sce_sys_rc, write_param_rc, write_icon_rc,
title_dir_rc, install_all_rc);
if(original_authid != 0) {
restore_authid_rc = kernel_set_ucred_authid(pid, original_authid);
installer_log("kernel_set_ucred_authid restore return code=0x%08x "
"current_authid=0x%016llx",
restore_authid_rc,
(unsigned long long)kernel_get_ucred_authid(pid));
}
installer_log("final result=%s final_rc=%d appinst_init_rc=0x%08x "
"user_service_rc=0x%08x "
"mkdir_app_rc=%d mkdir_sce_sys_rc=%d "
"write_param_rc=%d write_icon_rc=%d title_dir_rc=0x%08x "
"install_all_rc=0x%08x set_authid_rc=0x%08x "
"restore_authid_rc=0x%08x",
final_rc == 0 ? "installed" : "failed_nonfatal", final_rc,
appinst_init_rc, user_service_rc, mkdir_app_rc,
mkdir_sce_sys_rc, write_param_rc, write_icon_rc,
title_dir_rc, install_all_rc, set_authid_rc,
restore_authid_rc);
sceUserServiceTerminate();
if(final_rc == 0) {
bfpilot_notify("BFpilot launcher installer", "installed or refreshed");
return 0;
+9 -1
View File
@@ -18,6 +18,7 @@
#include "boot_marker.h"
#include "notify.h"
#include "sce_resolve.h"
#include "version.h"
#define BFPILOT_APP_TITLE_ID "BFPL00001"
@@ -138,7 +139,14 @@ main(void) {
const char *final_state = "unsupported";
installer_log("entered main tag=%s build=%s", VERSION_TAG, BUILD_VERSION);
installer_log("launcher metadata title_id=%s deeplink=http://127.0.0.1:5905/ "
"param_bytes=%lu icon_bytes=%lu icon_png_magic=%s",
BFPILOT_APP_TITLE_ID, (unsigned long)bfpilot_param_json_size,
(unsigned long)bfpilot_icon0_png_size,
bfpilot_icon0_png_size >= 8 &&
bfpilot_icon0_png[0] == 0x89 && bfpilot_icon0_png[1] == 'P' &&
bfpilot_icon0_png[2] == 'N' && bfpilot_icon0_png[3] == 'G'
? "yes" : "no");
installer_log("about to kernel_dynlib_handle %s", BFPILOT_APPINST_MODULE);
appinst_handle_rc = kernel_dynlib_handle(-1, BFPILOT_APPINST_MODULE,
&appinst_handle);
-1
View File
@@ -9,7 +9,6 @@
#include "sce_resolve.h"
int
sce_resolve_symbol(const char *module, const char *symbol, void **out) {
uint32_t handle = 0;
-1
View File
@@ -5,4 +5,3 @@
#pragma once
int sce_resolve_symbol(const char *module, const char *symbol, void **out);
+163 -27
View File
@@ -24,8 +24,12 @@
#include "websrv.h"
#define COPY_BUF_SIZE (256 * 1024)
#define UPLOAD_BUF_SIZE (256 * 1024)
#ifndef BFPILOT_TRANSFER_BUF_SIZE
#define BFPILOT_TRANSFER_BUF_SIZE (1024 * 1024)
#endif
#define COPY_BUF_SIZE BFPILOT_TRANSFER_BUF_SIZE
#define UPLOAD_BUF_SIZE BFPILOT_TRANSFER_BUF_SIZE
typedef struct json_buf {
@@ -192,11 +196,20 @@ struct job_state {
atomic_int total_files;
atomic_int done_files;
atomic_int failed_files;
atomic_long elapsed_ms;
atomic_long read_bytes;
atomic_long written_bytes;
atomic_int last_errno;
char current[512];
char source[512];
char destination[512];
char verb[16];
char error[256];
unsigned long source_dev;
unsigned long destination_dev;
time_t started_at;
time_t ended_at;
long started_ms;
};
@@ -204,6 +217,27 @@ static struct job_state g_job = {
.lock = PTHREAD_MUTEX_INITIALIZER,
};
struct upload_state {
pthread_mutex_t lock;
long elapsed_ms;
unsigned long bytes;
unsigned long destination_dev;
int error_code;
char path[512];
};
static struct upload_state g_upload = {
.lock = PTHREAD_MUTEX_INITIALIZER,
};
static long
monotonic_ms(void) {
struct timespec ts;
if(clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0;
return (long)ts.tv_sec * 1000L + (long)(ts.tv_nsec / 1000000L);
}
static void
job_set_current(const char *path) {
pthread_mutex_lock(&g_job.lock);
@@ -242,11 +276,20 @@ job_begin(const char *verb) {
atomic_store(&g_job.total_files, 0);
atomic_store(&g_job.done_files, 0);
atomic_store(&g_job.failed_files, 0);
atomic_store(&g_job.elapsed_ms, 0);
atomic_store(&g_job.read_bytes, 0);
atomic_store(&g_job.written_bytes, 0);
atomic_store(&g_job.last_errno, 0);
g_job.current[0] = 0;
g_job.source[0] = 0;
g_job.destination[0] = 0;
g_job.error[0] = 0;
g_job.source_dev = 0;
g_job.destination_dev = 0;
snprintf(g_job.verb, sizeof(g_job.verb), "%s", verb);
g_job.started_at = time(NULL);
g_job.ended_at = 0;
g_job.started_ms = monotonic_ms();
pthread_mutex_unlock(&g_job.lock);
return 1;
}
@@ -254,14 +297,28 @@ job_begin(const char *verb) {
static void
job_end(int rc, const char *err) {
int saved_errno = errno;
pthread_mutex_lock(&g_job.lock);
g_job.ended_at = time(NULL);
long ended_ms = monotonic_ms();
atomic_store(&g_job.elapsed_ms,
ended_ms > g_job.started_ms ? ended_ms - g_job.started_ms : 0);
atomic_store(&g_job.last_errno, rc == 0 ? 0 : saved_errno);
if(rc != 0 && err && !g_job.error[0]) {
snprintf(g_job.error, sizeof(g_job.error), "%s", err);
}
pthread_mutex_unlock(&g_job.lock);
bfpilot_log("transfer job end verb=%s rc=%d error=%s",
g_job.verb, rc, err ? err : "");
long elapsed_ms = atomic_load(&g_job.elapsed_ms);
long written = atomic_load(&g_job.written_bytes);
double mbps = elapsed_ms > 0 ?
((double)written * 1000.0 / (double)elapsed_ms) / (1024.0 * 1024.0) : 0.0;
bfpilot_log("transfer job end verb=%s rc=%d errno=%d bytes_read=%ld "
"bytes_written=%ld elapsed_ms=%ld average_mbps=%.2f "
"src_dev=%lu dst_dev=%lu current=%s error=%s",
g_job.verb, rc, atomic_load(&g_job.last_errno),
atomic_load(&g_job.read_bytes), written, elapsed_ms, mbps,
g_job.source_dev, g_job.destination_dev, g_job.current,
err ? err : "");
atomic_store(&g_job.busy, 0);
}
@@ -330,6 +387,7 @@ copy_file(const char *src, const char *dst) {
break;
}
if(r == 0) break;
atomic_fetch_add(&g_job.read_bytes, (long)r);
char *p = buf;
ssize_t left = r;
while(left > 0) {
@@ -348,6 +406,7 @@ copy_file(const char *src, const char *dst) {
p += w;
left -= w;
atomic_fetch_add(&g_job.copied_bytes, (long)w);
atomic_fetch_add(&g_job.written_bytes, (long)w);
}
if(rc != 0) break;
}
@@ -359,10 +418,6 @@ copy_file(const char *src, const char *dst) {
free(buf);
close(in);
if(rc == 0 && fsync(out) != 0 && errno != EINVAL) {
job_set_error("sync(%s): %s", dst, strerror(errno));
rc = -1;
}
if(close(out) != 0 && rc == 0) {
job_set_error("close(%s): %s", dst, strerror(errno));
rc = -1;
@@ -519,6 +574,11 @@ copy_worker(void *arg) {
free(a);
return NULL;
}
pthread_mutex_lock(&g_job.lock);
snprintf(g_job.source, sizeof(g_job.source), "%s", a->src);
snprintf(g_job.destination, sizeof(g_job.destination), "%s", a->dst);
g_job.source_dev = (unsigned long)src_st.st_dev;
pthread_mutex_unlock(&g_job.lock);
job_set_current("Scanning source");
size_walker(a->src, &files, &bytes, 0);
@@ -552,6 +612,11 @@ copy_worker(void *arg) {
return NULL;
}
}
if(stat(a->dst, &dst_st) == 0) {
pthread_mutex_lock(&g_job.lock);
g_job.destination_dev = (unsigned long)dst_st.st_dev;
pthread_mutex_unlock(&g_job.lock);
}
const char *base = path_basename(a->src);
if(strlen(a->dst) + strlen(base) + 2 >= sizeof(final_dst)) {
@@ -851,18 +916,6 @@ append_fs_place(json_buf_t *b, int *first, const char *path,
struct stat st;
if(stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) return 0;
char probe[128];
int n = snprintf(probe, sizeof(probe), "%s/.bfpilot_probe", path);
int writable = 0;
if(n > 0 && (size_t)n < sizeof(probe)) {
int fd = open(probe, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if(fd >= 0) {
writable = 1;
close(fd);
unlink(probe);
}
}
if(!*first && json_append(b, ",") != 0) return -1;
*first = 0;
@@ -870,7 +923,7 @@ append_fs_place(json_buf_t *b, int *first, const char *path,
json_string(b, path) != 0 ||
json_append(b, ",\"kind\":") != 0 ||
json_string(b, kind ? kind : "path") != 0 ||
json_appendf(b, ",\"writable\":%s}", writable ? "true" : "false") != 0) {
json_append(b, ",\"writable\":null,\"writeProbe\":\"skipped-read-only\"}") != 0) {
return -1;
}
return 0;
@@ -1075,15 +1128,26 @@ status_handler(const http_request_t *req) {
int tf = atomic_load(&g_job.total_files);
int df = atomic_load(&g_job.done_files);
int ff = atomic_load(&g_job.failed_files);
long elapsed_ms = atomic_load(&g_job.elapsed_ms);
long read_bytes = atomic_load(&g_job.read_bytes);
long written_bytes = atomic_load(&g_job.written_bytes);
int last_errno = atomic_load(&g_job.last_errno);
int cancel = atomic_load(&g_job.cancel);
char verb[16], current[512], err[256];
char verb[16], current[512], source[512], destination[512], err[256];
unsigned long source_dev, destination_dev;
long started_ms;
time_t started_at;
time_t ended_at;
pthread_mutex_lock(&g_job.lock);
snprintf(verb, sizeof(verb), "%s", g_job.verb);
snprintf(current, sizeof(current), "%s", g_job.current);
snprintf(source, sizeof(source), "%s", g_job.source);
snprintf(destination, sizeof(destination), "%s", g_job.destination);
snprintf(err, sizeof(err), "%s", g_job.error);
source_dev = g_job.source_dev;
destination_dev = g_job.destination_dev;
started_ms = g_job.started_ms;
started_at = g_job.started_at;
ended_at = g_job.ended_at;
pthread_mutex_unlock(&g_job.lock);
@@ -1097,6 +1161,10 @@ status_handler(const http_request_t *req) {
long eta = busy && speed > 0 && tb > cb
? (tb - cb + speed - 1) / speed
: 0;
if(busy) {
long now_ms = monotonic_ms();
elapsed_ms = now_ms > started_ms ? now_ms - started_ms : 0;
}
json_buf_t b = {0};
if(json_appendf(&b,
@@ -1107,14 +1175,53 @@ status_handler(const http_request_t *req) {
json_string(&b, current) != 0 ||
json_append(&b, ",\"error\":") != 0 ||
json_string(&b, err) != 0 ||
json_append(&b, ",\"source\":") != 0 ||
json_string(&b, source) != 0 ||
json_append(&b, ",\"destination\":") != 0 ||
json_string(&b, destination) != 0 ||
json_appendf(&b,
",\"totalBytes\":%ld,\"copiedBytes\":%ld,"
"\"totalFiles\":%d,\"doneFiles\":%d,\"failedFiles\":%d,"
"\"startedAt\":%ld,\"endedAt\":%ld,"
"\"elapsedSeconds\":%ld,\"speedBytesPerSec\":%ld,"
"\"etaSeconds\":%ld}",
"\"elapsedSeconds\":%ld,\"elapsedMs\":%ld,"
"\"bytesRead\":%ld,\"bytesWritten\":%ld,"
"\"sourceDev\":%lu,\"destinationDev\":%lu,\"errno\":%d,"
"\"speedBytesPerSec\":%ld,\"etaSeconds\":%ld}",
tb, cb, tf, df, ff, (long)started_at, (long)ended_at,
elapsed, speed, eta) != 0) {
elapsed, elapsed_ms, read_bytes, written_bytes, source_dev,
destination_dev, last_errno, speed, eta) != 0) {
free(b.data);
return -1;
}
return serve_owned(req, 200, b.data, b.len);
}
static int
transfer_stats_handler(const http_request_t *req) {
long elapsed_ms;
unsigned long bytes, destination_dev;
int error_code;
char path[512];
pthread_mutex_lock(&g_upload.lock);
elapsed_ms = g_upload.elapsed_ms;
bytes = g_upload.bytes;
destination_dev = g_upload.destination_dev;
error_code = g_upload.error_code;
snprintf(path, sizeof(path), "%s", g_upload.path);
pthread_mutex_unlock(&g_upload.lock);
json_buf_t b = {0};
double mbps = elapsed_ms > 0 ?
((double)bytes * 1000.0 / (double)elapsed_ms) / (1024.0 * 1024.0) : 0.0;
if(json_append(&b, "{\"ok\":true,\"bufferSize\":") != 0 ||
json_appendf(&b, "%u,\"lastUpload\":{\"path\":",
(unsigned int)UPLOAD_BUF_SIZE) != 0 ||
json_string(&b, path) != 0 ||
json_appendf(&b,
",\"bytes\":%lu,\"elapsedMs\":%ld,\"averageMBps\":%.2f,"
"\"destinationDev\":%lu,\"errno\":%d}}",
bytes, elapsed_ms, mbps, destination_dev, error_code) != 0) {
free(b.data);
return -1;
}
@@ -1143,6 +1250,7 @@ transfer_request(const http_request_t *req, const char *url) {
if(!strcmp(url, "/api/fs/mkdir")) return mkdir_handler(req);
if(!strcmp(url, "/api/fs/rename")) return rename_handler(req);
if(!strcmp(url, "/api/fs/job/status")) return status_handler(req);
if(!strcmp(url, "/api/fs/transfer/stats")) return transfer_stats_handler(req);
if(!strcmp(url, "/api/fs/job/cancel")) return cancel_handler(req);
return serve_error(req, 404, "no such endpoint");
}
@@ -1261,6 +1369,7 @@ transfer_upload_request(const http_request_t *req, const char *initial_data,
char err[200] = {0};
size_t bytes = 0;
size_t remaining = content_size;
long started_ms = monotonic_ms();
if(initial_size > remaining) initial_size = remaining;
if(initial_size > 0) {
@@ -1299,7 +1408,31 @@ transfer_upload_request(const http_request_t *req, const char *initial_data,
}
free(buf);
close(out);
int close_rc = close(out);
if(close_rc != 0 && !failed) {
failed = 1;
snprintf(err, sizeof(err), "close: %s", strerror(errno));
}
int saved_errno = failed ? (errno ? errno : EIO) : 0;
long ended_ms = monotonic_ms();
long elapsed_ms = ended_ms > started_ms ? ended_ms - started_ms : 0;
struct stat upload_st;
unsigned long destination_dev =
stat(dir, &upload_st) == 0 ? (unsigned long)upload_st.st_dev : 0;
pthread_mutex_lock(&g_upload.lock);
g_upload.elapsed_ms = elapsed_ms;
g_upload.bytes = (unsigned long)bytes;
g_upload.destination_dev = destination_dev;
g_upload.error_code = saved_errno;
snprintf(g_upload.path, sizeof(g_upload.path), "%s", final_path);
pthread_mutex_unlock(&g_upload.lock);
double mbps = elapsed_ms > 0 ?
((double)bytes * 1000.0 / (double)elapsed_ms) / (1024.0 * 1024.0) : 0.0;
bfpilot_log("upload end rc=%d errno=%d bytes_read=%lu bytes_written=%lu "
"elapsed_ms=%ld average_mbps=%.2f dst_dev=%lu path=%s",
failed ? -1 : 0, saved_errno, (unsigned long)bytes,
(unsigned long)bytes, elapsed_ms, mbps, destination_dev,
final_path);
if(failed) {
drain_body(req->fd, 0, remaining);
@@ -1310,7 +1443,10 @@ transfer_upload_request(const http_request_t *req, const char *initial_data,
json_buf_t b = {0};
if(json_append(&b, "{\"ok\":true,\"path\":") != 0 ||
json_string(&b, final_path) != 0 ||
json_appendf(&b, ",\"size\":%lu}", (unsigned long)bytes) != 0) {
json_appendf(&b,
",\"size\":%lu,\"elapsedMs\":%ld,\"averageMBps\":%.2f,"
"\"destinationDev\":%lu}",
(unsigned long)bytes, elapsed_ms, mbps, destination_dev) != 0) {
free(b.data);
return -1;
}
+5 -3
View File
@@ -349,6 +349,7 @@ status_request(const http_request_t *req) {
"\"pid\":%ld,"
"\"now\":%ld,"
"\"services\":[\"web\",\"file-manager\"],"
"\"diagReadOnly\":true,"
"\"launcherCompiled\":%s,"
"\"launcherDisabled\":%s,"
"\"port\":%u}",
@@ -412,6 +413,7 @@ diag_request(const http_request_t *req) {
"\"can_opendir_ext0_homebrew\":%s,"
"\"can_write_data_bfpilot\":%s,"
"\"can_write_user_app\":%s,"
"\"write_probe_policy\":\"skipped-read-only\","
"\"launcher_status\":\"%s\","
"\"last_errno\":%d,"
"\"checkpoint\":\"%s\","
@@ -451,7 +453,7 @@ diag_request(const http_request_t *req) {
"\"launcher_final_state\":\"%s\"},"
"\"notifications\":{\"mode\":\"optional-raw-debug\"},"
"\"routes\":[\"/\",\"/api/status\",\"/api/diag\","
"\"/fs\",\"/api/fs/*\"]}",
"\"/fs\",\"/api/fs/*\",\"/api/fs/transfer/stats\"]}",
VERSION_TAG, BUILD_VERSION, BFPILOT_BUILD_MODE,
(long)getpid(), (long)now,
bfpilot_diag_uptime(),
@@ -463,8 +465,8 @@ diag_request(const http_request_t *req) {
json_bool(can_opendir_usb0),
json_bool(can_opendir_ext0),
json_bool(can_opendir_ext0_homebrew),
json_bool(can_write_data),
json_bool(can_write_user_app),
json_tristate(can_write_data),
json_tristate(can_write_user_app),
launcher_status_json,
last_errno,
checkpoint_json,
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+46
View File
@@ -0,0 +1,46 @@
/*
* BFpilot probe - direct Sysmodule import, then load and resolve AppInstUtil.
*/
#include <stdint.h>
#include <ps5/kernel.h>
#include "boot_marker.h"
#include "notify.h"
#include "probe_common.h"
#define MARKER "/data/BFpilot/sysmodule_appinst_entered.txt"
#define LOG_PATH "/data/BFpilot/sysmodule_appinst.log"
#define APPINST_MODULE "libSceAppInstUtil.sprx"
#define APPINST_INTERNAL_ID 0x80000014U
int sceSysmoduleLoadModuleInternal(unsigned int);
int
main(void) {
int marker_rc = probe_write_marker(MARKER, "tests/installer_sysmodule_appinst");
bfpilot_boot_marker("tests/installer_sysmodule_appinst",
"installer-sysmodule-appinst");
probe_log(LOG_PATH, "entered main marker_rc=%d", marker_rc);
probe_log(LOG_PATH, "about to sceSysmoduleLoadModuleInternal id=0x%08x",
APPINST_INTERNAL_ID);
int load_rc = sceSysmoduleLoadModuleInternal(APPINST_INTERNAL_ID);
probe_log(LOG_PATH, "sceSysmoduleLoadModuleInternal rc=0x%08x", load_rc);
uint32_t handle = 0;
int handle_rc = kernel_dynlib_handle(-1, APPINST_MODULE, &handle);
probe_log(LOG_PATH, "kernel_dynlib_handle %s rc=0x%08x handle=0x%08x",
APPINST_MODULE, handle_rc, handle);
if(handle_rc == 0) {
intptr_t init = kernel_dynlib_dlsym(
-1, handle, "sceAppInstUtilInitialize");
intptr_t install_all = kernel_dynlib_dlsym(
-1, handle, "sceAppInstUtilAppInstallAll");
probe_log(LOG_PATH, "symbols initialize=%p install_all=%p",
(void *)init, (void *)install_all);
}
bfpilot_notify("BFpilot Sysmodule AppInst", "probe complete");
return handle_rc == 0 ? 0 : 1;
}
+53
View File
@@ -0,0 +1,53 @@
/*
* BFpilot probe - exact direct-import composition used by ps5 websrv.
*/
#include <stdint.h>
#include <unistd.h>
#include <ps5/kernel.h>
#include "boot_marker.h"
#include "notify.h"
#include "probe_common.h"
#define MARKER "/data/BFpilot/websrv_pattern_entered.txt"
#define LOG_PATH "/data/BFpilot/websrv_pattern.log"
#define APPINST_AUTHID UINT64_C(0x4801000000000013)
int sceUserServiceInitialize(void *);
void sceUserServiceTerminate(void);
int sceSystemServiceGetAppIdOfRunningBigApp(void);
int sceAppInstUtilInitialize(void);
int
main(void) {
int marker_rc = probe_write_marker(MARKER, "tests/installer_websrv_pattern");
bfpilot_boot_marker("tests/installer_websrv_pattern",
"installer-websrv-pattern");
probe_log(LOG_PATH, "entered main marker_rc=%d", marker_rc);
int user_rc = sceUserServiceInitialize(NULL);
probe_log(LOG_PATH, "sceUserServiceInitialize rc=0x%08x", user_rc);
pid_t pid = getpid();
uint64_t original_authid = kernel_get_ucred_authid(pid);
int authid_rc = kernel_set_ucred_authid(pid, APPINST_AUTHID);
probe_log(LOG_PATH, "set authid rc=0x%08x original=0x%016llx current=0x%016llx",
authid_rc, (unsigned long long)original_authid,
(unsigned long long)kernel_get_ucred_authid(pid));
int appinst_rc = sceAppInstUtilInitialize();
probe_log(LOG_PATH, "sceAppInstUtilInitialize rc=0x%08x", appinst_rc);
if(original_authid != 0) {
int restore_rc = kernel_set_ucred_authid(pid, original_authid);
probe_log(LOG_PATH, "restore authid rc=0x%08x current=0x%016llx",
restore_rc,
(unsigned long long)kernel_get_ucred_authid(pid));
}
sceUserServiceTerminate();
bfpilot_notify("BFpilot websrv-pattern probe", "probe complete");
return appinst_rc == 0 ? 0 : 1;
}
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -44,7 +44,7 @@ probe_write_marker(const char *path, const char *payload_name) {
if(n < 0) return -EINVAL;
if((size_t)n >= sizeof(line)) n = (int)sizeof(line) - 1;
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
if(fd < 0) return -errno;
ssize_t wr = write(fd, line, (size_t)n);