Skip to content

Grab bag Sunday - #1602

Open
CatalyticArkun wants to merge 51 commits into
Developmentfrom
grab-bag-Sunday
Open

Grab bag Sunday#1602
CatalyticArkun wants to merge 51 commits into
Developmentfrom
grab-bag-Sunday

Conversation

@CatalyticArkun

Copy link
Copy Markdown
Contributor

https://klipy.com/gifs/dump-truck-dirt

Results of an automated Fable audit.
Issues are separated into commits for cherry picking. I haven't had time to device test all of these, but they're all host-verified with positive and negative unit tests.

Shell case globs treat the dot literally, so ra32.*/ra64.* required a
'ra32.' prefix and idlemon's bare ra32/ra64 process tokens fell through
to exit 1 - in-game idle poweroff never fired for RetroArch sessions.
Tracker: SPR-MED-050.
The unmount-failure branch called poweroff and fell through: poweroff
returns immediately, so fsck.fat ran against the still-mounted SD and
could corrupt the filesystem it was meant to repair. Exit the script
once the shutdown is requested. Tracker: SPR-HIGH-015.
start_pyui_message_writer waited forever for the realtime listener file.
If PyUI failed to launch, every caller hung - including save_poweroff's
display path and the forced sub-1% battery shutdown, which then never
powered off. Give up after 15s and let callers continue headless.
Tracker: SPR-HIGH-014.
A single glitched 0%/1% sample from the battery driver immediately
forced save_poweroff. Re-read after one second and only shut down when
the low reading is confirmed; unreadable confirm samples also skip the
forced path. Tracker: SPR-MED-051.
The -e check and the touch sat ~100ms apart (jq-heavy helpers run in
between), so a lid-close and a power-press landing together started two
sleep helpers: double suspend writes, two power-event readers, and both
instances re-writing volume state at wake. mkdir is the guard now -
create-or-fail in one syscall; the lock is a directory, removed with
rmdir on every exit path. Tracker: SPR-MED-049.
Fourteen bare 'echo N' progress markers from debugging were left in the
tmpfs stage2 script, including one after poweroff that can never run.
Tracker: SPR-MED-015.
All eight 'pgrep | xargs kill -9' pipelines ran kill with no arguments
when nothing matched, printing a usage error on every settings apply.
xargs -r skips the kill entirely on empty input. Tracker: SPR-MED-016.
Three defects in one setting path: the numeric coercion rewrote "Off"
to 4 before either Off check ran, so the warning could never be
disabled; morse_code_sos wrote to ${LED_PATH}/brightness on devices
whose LED_PATH is 'not applicable'; and the recovered-battery branch
only cleared the low_battery flag when a LED exists. Off is now checked
before coercion (and breaks the inner warning loop), LED writes are
guarded, and the flag clears on recovery regardless of LED hardware.
Tracker: SPR-MED-014.
The pidfile guard was check-then-write: two shutdown triggers arriving
together both passed the liveness check and ran the whole shutdown path
twice, and either instance's EXIT trap removed the shared pidfile out
from under the other. mkdir now takes the lock atomically, a dead
owner's lock is reclaimed via the pid stored inside it, and the trap
only removes a lock its own pid owns. Tracker: SPR-MED-053.
sleep_helper mutes with 'set_volume 0 false' on every sleep entry -
transient by design, so the wake path can restore .vol from SYSTEM_JSON.
The watchdog's inotify handler mirrored that 0 into SYSTEM_JSON before
suspend, so the restore read back 0 and every sleep/wake cycle on an
A133P device durably muted it. Skip mirroring while the sleep helper's
lock exists; the wake-time restore already carries the right value.
Tracker: SPR-HIGH-012.
Holding Vol-up while a Vol-down hold loop was still alive left both
background loops stepping the volume against each other. Each key-down
now cancels the opposite key's repeat loop too. Tracker: SPR-MED-048.
'kill $PID 2&> /dev/null' is a bash-ism this /bin/sh script parsed as
a BACKGROUNDED 'kill $PID 2' - signalling PID 2 and racing the very
loop it meant to stop, with the redirect applied to an empty command.
All four repeat-loop kills are now guarded for empty PIDs, quoted, and
use POSIX '2>/dev/null'. Tracker: SPR-MED-002.
mktemp defaults to /tmp (tmpfs) while SYSTEM_JSON lives on the SD card,
so the mv fell back to a copy+unlink and concurrent readers - PyUI's
0.2s config watcher, jq callers - could catch a truncated file. Stage
next to the target so the rename is atomic, and drop the temp if jq
fails. Tracker: SPR-MED-047.
The gate tested the bound method object, which is always truthy, so X
rebooted devices whose reboot_cmd() returns None - unlike the prompt
text five lines up, which called it correctly and told those users no
reboot option exists. Tracker: SPR-MED-060 (reboot-gate portion).
PyUiLogger.get_logger has no .error attribute, so both sites raised
AttributeError inside their except blocks instead of logging: hwclock
failures in device_common.sync_hw_clock and /etc/localtime updates in
trim_ui_device. Tracker: SPR-LOW-006 (logger-reference portion).
Both volume and power keys arrive on /dev/input/event0 on this device,
and two KeyWatcher instances each ran a polling thread over the same
node - competing reads double-fired power events. Every other device
pairs watchers on different nodes; here one watcher covers both roles.
Tracker: SPR-HIGH-017.
A killed or timed-out ffmpeg left a partial .__safe__.ogg that the
exists() short-circuit then returned forever, so one interrupted
conversion permanently broke that track's BGM. Zero-byte cache entries
are now deleted and reconverted, and a conversion that produced no
usable output reports failure instead of a path.
Tracker: SPR-MED-058 (cache-poisoning portion).
PyUiConfig, PyUiState, RomsListManager, and CollectionsManager all
truncated their target file and rewrote it in place, so cutting power
mid-save destroyed settings, favorites, recents, or collections. Each
writer now stages to a .tmp alongside the target, fsyncs, and
os.replace()s it - the same pattern UserConfig already used.
Tracker: SPR-MED-045.
The initial os.stat sat outside the guarded loop, so a watcher started
on a not-yet-existing config file died instantly and silently - and the
stop event every device implementation stores was never consulted, so
watchers could not be stopped. The initial stat is now guarded (the
file appearing later counts as a change), and the loop checks and waits
on the stop event, exiting promptly when it is set.
Tracker: SPR-MED-046.
MiyooGameList aborted the whole parse loop when one <game> element
lacked a <path> (AttributeError mid-loop dropped every later entry);
each entry is now parsed under its own guard and bad ones are skipped
with a log line. ActivityLog crashed outright sorting any event line
missing a numeric ts; those events are filtered before the sort, which
matches how _build_intervals already ignored them.
Tracker: SPR-LOW-003.
perform_action ended with an unconditional 'killall sendevent' that
landed milliseconds after kill_drastic backgrounded its >=0.2s
MENU+L1 save-and-exit combo pipeline - the combo never reached DraStic,
which neither saved nor exited on Game Switcher/Exit. The combo's PID
is now recorded and waited on before the cleanup killall.
Tracker: SPR-HIGH-013.
button_actions.sh is sourced by /bin/sh watchdogs where the bash-only
'[[ == *glob* ]]' test is invalid; the PORTS check now uses a case
pattern. Tracker: SPR-MED-017 (residual after the watchdog cleanup).
updater.py ran spruceBackup.sh without checking its result and rolled
straight into the deletion block that wipes the SD card; a timeout
raised out of the run() call uncaught. spruceBackup.sh compounded it by
re-testing $? in an elif (testing the previous [ ] instead of 7zr) and
always exiting 0. The backup status is captured once, real failures
propagate as the script's exit code, and the updater aborts before
touching any files when the backup did not succeed.
Tracker: SPR-HIGH-003.
asound-setup.sh accepts a HOME override and wrote $BASE_HOME/.asoundrc
without ensuring the directory exists; on a fresh home the redirect
failed and audio routing setup was silently skipped.
Tracker: SPR-MED-024.
'[!settings]*' is a character class, not an exclusion: it matched any
entry whose first character is outside {s,e,t,i,n,g}, so forget-wifi
spared unrelated entries and could miss saved networks entirely. Every
entry except the settings file is now removed explicitly.
Tracker: SPR-MED-022.
connect_services spun forever at 0.5s intervals waiting for a
connection, so with WiFi enabled but unreachable the service policy
never ran and the process parked indefinitely. Give up after 60s;
the periodic relaunch retries on its own schedule.
Tracker: SPR-MED-023.
pgrep returns one PID per line; with the stock daemon and spruce's own
instance both alive, the multi-line result became a single broken
/proc/<pid1>\n<pid2>/cmdline path and a malformed kill target. Each PID
is now inspected on its own: wrong-config instances are killed, and a
correctly-configured one is kept without a pointless restart.
Tracker: SPR-MED-021.
The display helpers interpolated raw text into printf-built JSON, so a
quote or backslash in a game, theme, or download name broke the message
and PyUI dropped or misrendered it. A json_escape helper (backslash,
quote, tab, CR, newline) now wraps every dynamic string; escaped
payloads round-trip through a strict JSON parser.
Tracker: SPR-MED-020.
The 0.5s poll loop forked a fresh sleep helper every pass while the lid
stayed closed - each one hitting the singleton guard, but still a new
process every half second for the whole closed period - and a helper
returning with the lid still closed (e.g. failed timeout poweroff) was
immediately relaunched with a fresh full timeout. The launch is now
edge-gated on open->closed, skips when a helper is already active, and
waits for the lid to reopen before re-arming. Tracker: SPR-MED-003.
send_cmd_to_ra created a fresh UDP socket per menu action and never
closed it - one leaked fd per save/load/quit for the life of the UI
process. The socket is now a context manager. Tracker: SPR-MED-026.
Every collection game was appended twice - once as a leftover generic
GridOrListEntry and once as the RomGridOrListEntry the menu actually
uses - so collections listed each game twice. Also bind the image path
into the entry's lambda by default argument: the old closure captured
the loop variable, giving every entry the last ROM's boxart.
Tracker: SPR-MED-027.
Both in-game menu popups walked a walrus loop that exits with
popup_selection = None when the view closes without a selection, then
dereferenced it - crashing the menu mid-game. A None selection now
behaves like pressing B (and unpauses RetroArch). The parent force-kill
branch in the listener also logged child.pid after the child loop, an
unbound/wrong reference when there are no children; it logs the parent
pid it actually kills. Tracker: SPR-MED-025.
Italian.json ended mid-value ('"primImgWidth": "Largh<EOF>'), so the
whole file failed to parse and Italian users got zero translations. The
partial entry is dropped and the object closed; the surviving 96 keys
parse and load again. Missing keys fall back to English in Language.label.
Tracker: SPR-MED-011.
Five layout.json files under Emu/NDS/resources/bg carried trailing
commas, which strict JSON parsers reject - any consumer using one
failed to load these screen layouts. Tracker: SPR-MED-012.
Translations had drifted 160+ keys behind English (181 for the repaired
Italian file), and Vietnamese carried seven keys English no longer has.
Every parseable language file is rebuilt in English key order: existing
translations kept, missing keys filled with the English string so the
UI shows text instead of nothing, stale keys dropped.
Tracker: SPR-LOW-008.
get_extlist lowercases entries, so pbp|PBP collapsed into a duplicate;
matching is case-insensitive already. Tracker: SPR-LOW-007.
Emu/ATOMISWAVE/config.json is active but no Roms/ATOMISWAVE folder
existed, so the system could never list or scan games. Matches the
NAOMI layout (Imgs/.gitkeep). Tracker: SPR-LOW-007.
Two RetroArch autoconfig files and the rg35xxsp key map carried CRLF
endings; on-device parsers read the stray carriage returns as part of
values. Tracker: SPR-LOW-007.
App/Credits declared led.png without shipping it and commented its
label out as '#label', so themeless launches drew no icon and no name;
Emu/ATOMISWAVE referenced atomiswave.png/_sel.png that did not exist.
Copies of the SPRUCE theme's own icons now live beside the configs like
every other app/emu, and the Credits label key is real again.
Tracker: SPR-MED-029.
An empty or fully-excluded game pool re-ran the selection loop without
end, pegging the CPU behind the please-wait screen. Thirty failed draws
now end with a clear message. Tracker: SPR-LOW-001.
eval on the composed command string executed any shell metacharacters
in a ROM file name; standard_launch.sh is now invoked directly with the
path as a quoted argument (the cmd file keeps its shell-line format for
the resume/kill consumers). get_rand_file also returned mid-loop with
IFS still set to newline, leaking it into the caller.
Tracker: SPR-MED-034.
If entering TMP_DIR failed, 'rm -r ./*' ran in whatever directory the
script started from. Tracker: SPR-MED-033.
PyUI launches these directly. deleteMacFiles.sh called log_message
without sourcing helperFunctions.sh and assumed python3 on PATH;
fetchPPSSPPauth.sh invoked the auth helper by repo-relative path, which
only worked from the payload root. Helpers are sourced, the device
Python resolver is used, and the path is absolute.
Tracker: SPR-MED-037.
command.txt comes from the GUI's stream selection; eval executed any
shell metacharacters in a host or app name. The line is now field-split
with globbing off and passed as arguments. Tracker: SPR-MED-035.
Inside the double-quoted preset heredoc-style strings, the bare ""
after 'alias0 = ' closed and reopened the shell string, so the written
.glslp carried an unquoted empty value instead of alias0 = "".
Tracker: SPR-MED-013.
The description used 'ReARMed' while the task key uses 'ReARMED', so
PyUI showed no description for that task. Tracker: SPR-MED-036.
Follow-up to the empty-pool escape: use a named constant and counter so
the bound is self-describing. Tracker: SPR-LOW-001.
audio_cleanup went through _send_cmd, whose _ensure_worker spawned a
brand-new worker - opening the audio device - purely so the cleanup
command could kill it. With no live worker the state is now reset
directly. Tracker: SPR-MED-004.
Two display_message_multiline lists were missing a comma, so adjacent
f-strings concatenated into 'Scale factor is 0.75Patching main assets'
on one line. Tracker: SPR-MED-032.
'[ -d ] || log && return 1' groups as '([ -d ] || log) && return 1':
when the backup folder existed the guard still returned 1 immediately,
so theme config restore never ran at all; when it was missing, only the
log-and-return path worked by accident. Now an explicit if.
Tracker: SPR-MED-005.
'7zr l | grep /mnt/SDCARD/' matched the archive's own pathname in the
listing header, so every queued archive passed the layout gate and was
extracted at / wherever its members pointed - a malicious or malformed
theme archive could write anywhere on the filesystem. The gate now
reads member paths from 7zr -slt and requires every entry to sit under
mnt/SDCARD/ (ancestor directory entries allowed); staged e2e tests
confirm wrong-layout archives are skipped and kept in the queue while
correct themes still install. Tracker: SPR-HIGH-010.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant