mirror of
https://github.com/Klipper4FlashForge/firmware.git
synced 2026-10-22 05:12:16 -05:00
It is the one file a stock flash cannot undo, and replacing it is what left
owners with a printer that would not print. A stock package restores the other
two paths the mod points at itself -- its run.sh copies its own firmwareExe and
start.sh over them, and busybox `cp -f` unlinks a symlink rather than writing
through it. It carries no klipperDaemon at all: not in the software component,
not in its md5sum.list, no line in run.sh. So the link outlived the mod, and
stock's start.sh, whose last line is
/usr/prog/klipper/klipperDaemon start
ran our shim, whose `start` does nothing on purpose. The UI came up, klippy did
not, and flashing stock again changed nothing because there was nothing in it
to change.
The shim guarded that same line: klippy is an s6-rc longrun here, and stock's
klipperDaemon forks a second, unsupervised one that fights it for /dev/ttyS4
and /tmp/uds. But no caller is left on a modded machine. Our own start.sh
replaces stock's and asks s6-rc; FlashForge's firmwareExe execs
/usr/prog/klipper/start.sh and names no other script (`strings` on their
binary); Moonraker runs with `provider: none`, so a restart from Mainsail goes
over the API rather than through a service script; ff-startup and S70klipper
drove it once and neither exists now. What remains is a person typing
`klipperDaemon start` over ssh, who gets FlashForge's behaviour and a second
klippy -- s6-rc and s6-svc are the supported way, and that trade buys back a
printer that can be returned to stock.
test_install.py asks the machine the inverted question now: klipperDaemon is
not a link into $MODDIR, it exists, and it is FlashForge's own script.
Printers that already took an earlier release still carry the link and cannot
be repaired from here -- nothing of ours runs during a stock flash. The
package at Klipper4FlashForge/stock-recovery puts their copy back.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
88 lines
3.5 KiB
Python
88 lines
3.5 KiB
Python
"""A `[ -f $MODDIR/... ]` guard that can only ever be false.
|
|
|
|
THE BUG THIS EXISTS FOR. `installer/runFirmwareExe.sh` used to replace
|
|
FlashForge's `klipperDaemon`, and it guarded that work with
|
|
|
|
if [ -f $MODDIR/bin/klipperDaemon ] && [ -d /usr/prog/klipper ]; then
|
|
|
|
while the file shipped at `$MODDIR/prog/klipperDaemon` -- it had moved in
|
|
057a3a1 and the guard had not. The test was false on every printer and the
|
|
whole block was skipped IN SILENCE: no error, no log line, for release after
|
|
release. That block is gone; the class of bug it is named for is what this
|
|
file gates.
|
|
|
|
Nothing could have caught it. `sh -n` parses it, shellcheck likes it, and both
|
|
lanes stay green because the branch simply never runs. `test_no_undefined_names`
|
|
is the same idea for Python; this is the shell half.
|
|
|
|
WHY EXISTENCE GUARDS AND NOT EVERY $MODDIR PATH. Most `$MODDIR/...` references
|
|
are things a script CREATES or writes, and demanding those exist in a package
|
|
would be wrong. A `[ -f ]` / `[ -x ]` / `[ -d ]` is different: it is a claim
|
|
that something is already there, and the only two ways it can be true are that
|
|
a package ships it or that an earlier step wrote it. The second is the
|
|
allowlist below, and it is deliberately short so that adding to it is a
|
|
decision somebody makes rather than a way to silence this test.
|
|
"""
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from lib.paths import ROOT
|
|
|
|
pytestmark = pytest.mark.static
|
|
|
|
# Scripts that read $MODDIR expecting the payload to be installed.
|
|
SOURCES = sorted(
|
|
set(ROOT.glob("installer/*.sh"))
|
|
| set(ROOT.glob("pkgs/*/payload/prog/*"))
|
|
| set(ROOT.glob("pkgs/*/payload/bin/*.sh"))
|
|
| set(ROOT.glob("pkgs/*/payload/etc/s6-rc/source/*/run"))
|
|
)
|
|
|
|
# `[ -f $MODDIR/a/b ]`, `[ -x ${MODDIR}/a/b ]`. Only literal paths: anything
|
|
# with a variable or a glob in it cannot be resolved here and is skipped.
|
|
GUARD = re.compile(r"\[\s+-[fxdesr]\s+\$\{?MODDIR\}?/([A-Za-z0-9_.][A-Za-z0-9_./-]*)")
|
|
|
|
# Not shipped by any package, and correctly so.
|
|
ALLOWED = {
|
|
# Inside anvil-helixscreen's upstream tarball, which pkgs/helixscreen
|
|
# unpacks at build time -- so it is shipped, just not committed here.
|
|
"helixscreen/bin/helix-launcher.sh",
|
|
}
|
|
|
|
|
|
def _shipped():
|
|
"""Every path any recipe ships, relative to $MODDIR."""
|
|
out = set()
|
|
for payload in ROOT.glob("pkgs/*/payload"):
|
|
for p in payload.rglob("*"):
|
|
out.add(str(p.relative_to(payload)))
|
|
return out
|
|
|
|
|
|
def test_every_moddir_existence_guard_names_something_that_ships():
|
|
shipped = _shipped()
|
|
missing = []
|
|
for src in SOURCES:
|
|
try:
|
|
text = src.read_text(encoding="utf-8", errors="replace")
|
|
except OSError:
|
|
continue
|
|
for lineno, line in enumerate(text.splitlines(), 1):
|
|
for path in GUARD.findall(line):
|
|
path = path.rstrip("/")
|
|
if path in ALLOWED or path in shipped:
|
|
continue
|
|
missing.append(
|
|
"%s:%d $MODDIR/%s"
|
|
% (src.relative_to(ROOT), lineno, path))
|
|
|
|
assert not missing, (
|
|
"%d existence guard(s) name a $MODDIR path no recipe ships, so the "
|
|
"test is false on every printer and whatever it guards never runs -- "
|
|
"silently, which is how the klipperDaemon replacement was skipped for "
|
|
"several releases:\n %s\n"
|
|
"Either the path is wrong, or the file should be in a package, or it "
|
|
"is written at runtime and belongs in ALLOWED with a reason."
|
|
% (len(missing), "\n ".join(missing)))
|