Skip to content

Preview snapshot permanently fails when a stale /tmp/led_matrix_preview.png.tmp is owned by the other service's user (fs.protected_regular blocks even root) #528

Description

@ChuckBuilds

Summary

The display preview snapshot writes through a fixed temp path, /tmp/led_matrix_preview.png.tmp. /tmp is world-writable and sticky, and the display service (ledmatrix, User=root) and the web service (ledmatrix-web, User=hdpi) are different users. Once a stale .tmp owned by the other user is left behind, fs.protected_regular makes every subsequent write fail with EACCESeven for root — and nothing ever clears it.

On this rig the preview has been dead for 22.9 hours. The display itself is running fine.

Symptoms

$ journalctl -u ledmatrix | grep Snapshot
17:18:18 WARNING src.display_manager - Snapshot write failing (web preview/health mirror is stale): [Errno 13] Permission denied: '/tmp/led_matrix_preview.png.tmp'
17:23:18 WARNING (same)
17:28:18 WARNING (same)
...every 5 minutes, indefinitely
$ ls -la /tmp/led_matrix_preview*
-rw-rw-r-- 1 root root 10723 Sep 3 18:53 /tmp/led_matrix_preview.png
-rw-rw-r-- 1 hdpi hdpi 92 Sep 4 16:32 /tmp/led_matrix_preview.png.tmp <-- stale, wrong owner
-rw-r--r-- 1 hdpi hdpi 0 Sep 4 17:48 /tmp/led_matrix_preview_viewer
$ ls -ld /tmp
drwxrwxrwt 20 root root 2260 Sep 4 17:48 /tmp

/api/v3/health consequently reports the hardware as stale on a perfectly healthy rig:

"hardware": { "snapshot_age_seconds": 82484.0, "status": "stale" }

Root cause — it is not ordinary permissions

Root normally bypasses DAC, so EACCES for root looks impossible. It's fs.protected_regular, which refuses an O_CREAT open of an existing file in a world-writable sticky directory when the file's owner is neither the opener nor the directory owner. Demonstrated directly on the rig:

$ sudo python3 -c "open('/tmp/led_matrix_preview.png.tmp','wb').write(b'x')"
root FAILED: [Errno 13] Permission denied: '/tmp/led_matrix_preview.png.tmp'

So the atomic write-then-rename is safe against torn reads but not against a cross-user stale temp file in a shared sticky directory.

Why it never recovers

display_manager catches the error, logs it (throttled to every 5 minutes) and carries on. Nothing removes or works around the offending file, so the only fix is a human running rm /tmp/led_matrix_preview.png.tmp. The comment at display_manager.py:227 says the failure "is never silent" — true, but "never silent" and "never recovers" are different things, and the log line doesn't say what to delete.

Knock-on effect on the health check

src/common/snapshot_policy.py is explicit that the file's mtime is a liveness proxy:

"The health check ... uses the file's AGE as a liveness proxy: age >= 60s reads as degraded."
"TOUCH_INTERVAL must stay well under it."

TOUCH_INTERVAL is 20s and IDLE_INTERVAL is 30s, so the policy is sound — but both the WRITE and the TOUCH go through the same blocked path, so the liveness proxy inverts: a healthy, rendering display reports stale. Anyone using /api/v3/health for monitoring gets a permanent false alarm.

Suggested fix

Any one of these closes it; the first two are the smallest:

  1. Unique temp nametempfile.mkstemp(dir="/tmp", prefix="led_matrix_preview.", suffix=".png") then os.replace(). A stale file from another user can never collide, because the name is never reused.
  2. Unlink-on-EACCES retry — try to remove the temp path and retry once before giving up. Note this fails too under protected_regular if the directory isn't owned by the caller, so (1) is more robust.
  3. Move the mirror out of /tmp — a service-owned directory such as /var/cache/ledmatrix/ (already used for other state) has neither the sticky-bit hazard nor the cross-user ambiguity.

Worth also including the offending path in the warning so the manual fix is obvious:

Snapshot write failing (web preview/health mirror is stale): ...
-> a stale /tmp/led_matrix_preview.png.tmp owned by another user blocks this; remove it

Environment

LEDMatrix v3.3.0-4-g0730d952, Raspberry Pi (Debian trixie, kernel 6.18.34), 256x64 panel.
ledmatrix.service runs as root, ledmatrix-web.service as hdpi. Found while validating plugins — I tried to capture the panel and found the preview 23 hours old.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions