Skip to content

syslogd: cover the two windows forwarding still missed - #2417

Merged
widgetii merged 2 commits into
masterfrom
syslog-forward-gaps
Sep 14, 2026
Merged

syslogd: cover the two windows forwarding still missed#2417
widgetii merged 2 commits into
masterfrom
syslog-forward-gaps

Conversation

@widgetii

@widgetii widgetii commented Sep 14, 2026

Copy link
Copy Markdown
Member

Problem

#2415 gave a camera a way to send its log somewhere that outlives it. Two windows it does not reach, both found reviewing that change, and both the kind of moment the log is most worth having.

Failsafe mode started syslogd and not klogd. The rescue list was:

for s in S01syslogd S29pstore S38mdev S31hostname S40network S50dropbear; do

klogd is the only thing that pumps the kernel ring buffer into /dev/log, so the one boot mode built for post-mortem — reached after repeated failed boots — carried no kernel lines at all. No panic, no mmc timeouts, none of what usually explains why the boots failed. It goes directly after S01syslogd, which owns /dev/log.

sysupgrade stopped syslogd and said nothing first. A forwarded log therefore just stopped mid-flash, and a clean stop read exactly like a death — on the operation most likely to produce the latter. busybox emits no shutdown marker of its own on the remote path: it forwards each line unmodified and logs syslogd exiting only locally. So the breadcrumb has to be explicit, has to go before syslogd does, and needs a moment for the datagram to leave, because forwarding is UDP and nothing retries it. The image is not known yet — free_resources runs before the download — so it names what is about to be written and what it is being written over.

What review and hardware changed

Two reviews landed independently on the same defect: the breadcrumb claimed a flash before the download or checksum had happened, so a failed download left a collector holding a death notice for a camera that was never written to. It now says preparing to flash, and restore_resources() logs a retraction when the camera stays up. It also gained -s and user.warning, and its one-second datagram drain is gated on forwarding actually being enabled rather than charged to every camera.

Then the board found something neither review did, and it was my own mistake repeated one layer down. The first version of the failsafe fix started logging before the network — exactly the trap etc/default/syslogd documents, and exactly what this PR exists to fix. busybox drops rather than queues, and klogd's read consumes the ring buffer, so those lines are gone rather than delayed.

Measured on a gk7205v200, forwarding to a live collector:

kernel lines reaching the collector
syslogd started before the network 1 of 154
network + lease + settle first 157

Ordering alone was not enough: S40network's udhcpc forks to the background, so the reorder still raced the lease. And an address is not reachability — lines stamped 00:00:08 were still lost while 00:00:10 arrived — hence the bounded wait plus a short settle. Bounded, because failsafe has to come up where there is no DHCP server at all.

I tested and discarded two other explanations before landing on that one: burst loss (100/100 forwarded lines arrived) and unresolved ARP (100/100 after an ip neigh flush all).

Hardware tested on

Two boards, each for the half it can exercise.

  • gk7205v200 (gk7205v200_lite) — the family that ships openipc-failsafe. Flashed with a build of this branch (kernel + rootfs), then booted into real failsafe mode via the kernel command line, which is the second trigger rcS documents.
  • hi3516av300nightly-20260912-d87dae1, for the sysupgrade breadcrumb, forwarded to a live collector.

Evidence

Failsafe, on a real failsafe boot

Not the script invoked by hand: rcS dispatched it, on a camera running this branch.

# grep -o failsafe /proc/cmdline
failsafe

# majestic must be absent, or this is just a normal boot
majestic absent -> failsafe

# the change under test
klogd RUNNING

# kernel lines now in the log, where there would have been none
logread | grep -c "kern\."
154

# and the rest of the rescue list behaves
  syslogd    yes
  klogd      yes
  dropbear   yes
  majestic   no
  crond      no

The contrast, on the same board, with klogd stopped and then started in the order the new list uses:

# syslogd alone
kern.* lines in the log: 0
# then klogd
kern.* lines in the log: 330
# echo failsafe-klogd-probe > /dev/kmsg
Sep 14 07:47:09 gk7205v200-imx307 user.emerg kernel: failsafe-klogd-probe

The board was returned to a normal boot afterwards: no failsafe in /proc/cmdline, majestic running.

The sysupgrade breadcrumb, reaching a collector

# on the camera
sysupgrade: flashing now (kernel=1 rootfs=1 overlay=0) over nightly-20260912-d87dae1;
this log stops here until the camera comes back

# on the collector
2026-09-14T08:39:29.463042+01:00 10.216.128.39 notice sysupgrade  flashing now
(kernel=1 rootfs=1 overlay=0) over nightly-20260912-d87dae1; this log stops here
until the camera comes back

Checks that need no board

$ bash .github/scripts/test_sysupgrade.sh
All sysupgrade verification checks passed.

$ STRICT=1 bash .github/scripts/test_shell_parse.sh
checked 144 shell script(s)
all parsed clean under busybox ash

$ STRICT=1 bash .github/scripts/test_strip_shell_comments.sh
All strip-shell-comments checks passed.

$ python3 .github/scripts/ci-matrix.py --self-test
ci-matrix: self-test ok (99 boards, 136 packages, 56 cases)

Size

gk7205v200_lite is one of the tighter boards — the build of this branch printed headroom warning: rootfs.squashfs has 28KB left of 5120KB. Measured against origin/master, comment-stripped as the build ships them:

sysupgrade        +258 bytes
failsafe-rescue     +9 bytes
                  --------
                   267 bytes, against 28672 bytes of headroom

Note

Flashing for this test ran into a separate, pre-existing problem with sysupgrade -x, which leaves a camera with no shell, no SSH and no console until it reboots. That is unrelated to this change and is filed as #2416.

Scope

  • No kernel patches under general/package/all-patches/linux/ (those go to OpenIPC/linux)
  • No files specific to a single retail camera model (those go to OpenIPC/builder)
  • No probing or bring-up tooling (that goes to OpenIPC/ipctool)
  • Nothing under general/overlay/ or in a shared load_<vendor> script hardcodes a value specific to my board
  • Package sources come from an OpenIPC repository, and any version bump keeps at least the specificity of the pin it replaces (a new package should pin a full 40-character SHA)
  • No LD_PRELOAD, and no binaries that cannot be rebuilt from source
  • New code is selected by a defconfig, so CI actually builds it

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Cover failsafe and sysupgrade syslog forwarding gaps

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Include klogd in failsafe boots so forwarded logs retain kernel diagnostics.
• Emit a pre-flash sysupgrade marker before stopping forwarded logging.
• Delay syslogd shutdown briefly to improve marker delivery to collectors.
Diagram

graph TD
  F["Failsafe boot"] --> R["Rescue script"] --> S["syslogd"] --> C["Remote collector"]
  R --> K["klogd"] --> S
  B["Kernel ring"] --> K
  U["sysupgrade"] -->|logs marker| S
  U -->|delays then stops logging| W["Firmware flash"]
Loading
High-Level Assessment

The localized sequencing changes are the best fit: starting klogd beside syslogd restores missing diagnostics, and an explicit contextual marker handles BusyBox's lack of remote shutdown notification. Patching BusyBox or changing forwarding transport would be substantially broader and would not provide the upgrade-specific message.

Files changed (2) +22 / -1

Bug fix (2) +22 / -1
sysupgradeSend a final remote log marker before firmware flashing +16/-0

Send a final remote log marker before firmware flashing

• Logs the selected kernel, rootfs, and overlay actions plus the current build identifier before stopping syslogd. Adds a one-second delay so the UDP-forwarded marker can leave the device before logging shuts down.

general/overlay/usr/sbin/sysupgrade

failsafe-rescueStart klogd during failsafe recovery +6/-1

Start klogd during failsafe recovery

• Adds S02klogd immediately after S01syslogd in the minimal failsafe service list. This exposes kernel ring-buffer diagnostics through /dev/log during post-mortem recovery boots.

general/package/openipc-failsafe/files/failsafe-rescue

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 14, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Failed downloads look like begun flashes ✓ Resolved 🐞 Bug ≡ Correctness
Description
free_resources() emits “flashing now” and says logging stops until reboot before the firmware has
been downloaded or validated. When download or checksum validation fails, die() restores logging
without writing flash or rebooting, so remote collectors record an interrupted flash even though the
camera remains unchanged.
Code

general/overlay/usr/sbin/sysupgrade[R558-561]

+	logger -t sysupgrade "flashing now (kernel=${update_kernel:-0}" \
+		"rootfs=${update_rootfs:-0} overlay=${clear_overlay:-0}) over" \
+		"$(sed -n 's/^BUILD_ID=//p' /etc/os-release 2>/dev/null);" \
+		"this log stops here until the camera comes back"
Evidence
The added marker is emitted while free_resources() runs, but the sole main path calls
download_firmware afterward. Download and checksum failures invoke die(), whose pre-write branch
restores stopped services and exits, while actual flash writes occur substantially later in
flash_and_reboot().

general/overlay/usr/sbin/sysupgrade[558-562]
general/overlay/usr/sbin/sysupgrade[1341-1345]
general/overlay/usr/sbin/sysupgrade[519-524]
general/overlay/usr/sbin/sysupgrade[98-110]
general/overlay/usr/sbin/sysupgrade[1139-1145]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The remote breadcrumb claims flashing has begun and logging will remain stopped until reboot, but it is emitted before download and validation. Recoverable download or checksum failures therefore leave a false record of an interrupted flash.
## Fix Focus Areas
- general/overlay/usr/sbin/sysupgrade[548-562]
## Recommended Fix
Change the breadcrumb to describe preparation rather than an active flash, and state that logging may resume if the operation fails before writing. Keep it before syslogd stops so remote collectors still receive the transition marker.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread general/overlay/usr/sbin/sysupgrade Outdated
#2415 gave a camera a way to send its log somewhere that outlives it. Two
windows it does not reach, both found reviewing that change, and both the kind
of moment the log is worth having.

Failsafe mode started syslogd and not klogd. klogd is the only thing that pumps
the kernel ring buffer into /dev/log, so the one boot mode built for
post-mortem -- reached after repeated failed boots -- carried no kernel lines at
all: no panic, no mmc timeouts, none of what usually explains why the boots
failed. It goes directly after S01syslogd, which owns /dev/log.

sysupgrade stopped syslogd and said nothing first. A forwarded log therefore
just stopped mid-flash, and a clean stop read exactly like a death -- on the
operation most likely to produce the latter. busybox emits no shutdown marker
of its own on the remote path: it forwards each line unmodified and logs
"syslogd exiting" only locally. So the breadcrumb has to be explicit, and has
to go before syslogd does, with a moment for the datagram to leave, because
forwarding is UDP and nothing retries it. The image is not known yet --
free_resources runs before the download -- so it names what is about to be
written and what it is being written over.

Tested on two boards, each for the half it can actually exercise.

gk7205v200 (gk7205v200_lite, the family that ships openipc-failsafe), running
the real failsafe-rescue script with klogd stopped beforehand:

  *** FAILSAFE: bringing up network + SSH only, services skipped ***
  Starting syslogd: OK (already running)
  Starting klogd: OK
  ...
  # echo failsafe-klogd-probe > /dev/kmsg
  Sep 14 07:47:09 gk7205v200-imx307 user.emerg kernel: failsafe-klogd-probe

A kernel message reaches syslog only once klogd is in the list. "Starting
dropbear: FAIL" and "bootcount-clear: not found" in that run are artifacts of
invoking the script by hand on a booted camera carrying an older local image,
not of this change.

hi3516av300 (nightly-20260912-d87dae1), the breadcrumb, forwarded to a
collector:

  # local
  sysupgrade: flashing now (kernel=1 rootfs=1 overlay=0) over
  nightly-20260912-d87dae1; this log stops here until the camera comes back

  # on the collector
  2026-09-14T08:39:29+01:00 10.216.128.39 notice sysupgrade  flashing now
  (kernel=1 rootfs=1 overlay=0) over nightly-20260912-d87dae1; this log stops
  here until the camera comes back

test_sysupgrade.sh, test_shell_parse.sh and test_strip_shell_comments.sh all
pass.
Two reviews landed on the same defect, and hardware found a third.

The breadcrumb claimed a flash that had not started. free_resources() runs
before check_sdcard, before the download and before any write, so a failed
download or a bad checksum left a collector holding "flashing now, logging
stops until the camera returns" for a camera that was never touched. It now
says "preparing to flash", and restore_resources() logs the retraction when the
camera stays up -- an abort or a deliberate --no_reboot -- so the two cases stop
looking identical. It also gained -s (the operator watching the transcript is
exactly who needs it) and user.warning (a collector filtering at >= warning
should not lose the one line explaining the silence), and the second it spends
letting a datagram out is now gated on forwarding actually being enabled,
rather than charged to every camera for a feature most have off.

Failsafe started logging before the network, which is the mistake this whole
change exists to fix, made again one layer down. etc/default/syslogd already
documents that busybox drops rather than queues while the network is down, and
klogd's read CONSUMES the ring buffer, so those lines are gone rather than
delayed. Measured on a gk7205v200: exactly one of 154 replayed kernel lines
reached the collector. The list is hand-written, so the order was free to fix --
S31hostname and S40network now precede S01syslogd and S02klogd, since syslogd
also samples the hostname once at startup.

And an address is not reachability. S40network's udhcpc forks to the
background, so the reordering alone still raced the lease. There is now a
bounded wait for an IPv4 address plus a short settle -- on that board lines
stamped 00:00:08 were still lost while 00:00:10 arrived, so something after the
lease is worth a couple of seconds. Bounded because failsafe must come up where
there is no DHCP server at all.

What that reliably buys is the local log: `logread` in failsafe now carries the
kernel, where it carried nothing, and every kernel message from then on is
forwarded. The replayed backlog gets its best chance, not a guarantee, and the
comment says so rather than claiming otherwise.

Smaller things from the same pass: a /dev/log readiness wait between syslogd and
klogd, because start-stop-daemon -b returns before the socket exists and a
consuming read cannot be retried; get_build_id() beside get_system_build()
instead of a third spelling of the same os-release read; and the claim that
failsafe now carries "no panic" removed, because klogd replays this boot's ring
buffer and a boot that reached failsafe has no crash in it -- the oops is in
/sys/fs/pstore, which this mode deliberately leaves intact.

test_sysupgrade.sh gains three assertions, including that the breadcrumb
precedes the syslogd stop. That ordering is the whole mechanism and nothing else
in the suite protected it; the check was confirmed to fail against a
deliberately reordered copy.
@widgetii
widgetii merged commit 9b67eea into master Sep 14, 2026
117 checks passed
@widgetii
widgetii deleted the syslog-forward-gaps branch September 14, 2026 11:59
widgetii added a commit that referenced this pull request Sep 14, 2026
…rn (#2419)

Two loose ends from #2417, both of them a camera left in a state nobody can
see from outside.

check_sdcard kept everything it had been given. It runs after create_lock and
free_resources, and a recovery file on the card exited straight out of it, so
the lock stayed in /tmp and every later run answered "Another sysupgrade
process is already running!" until a reboot cleared it; syslogd, klogd, ntpd
and crond stayed stopped; and majestic stayed gutted by the SIGQUIT
free_resources sends. Since #2417 it also left a collector holding "logging
stops here until this camera returns" for a camera that was never touched.
Nothing is written at that point, so die() already does the right thing --
no-reboot branch, services restarted, lock dropped. Use it.

Measured on an hi3516av300 (nightly-20260913-910e724), all three stoplist
files on the card, kernel path deliberately nonexistent so nothing could be
written even if the check were skipped. Before: syslogd, klogd, ntpd and crond
all down, lock leaked, and the next run refusing outright. After: all four
back, lock gone, and the #2417 retraction sent -- "still here on
nightly-20260913-910e724", the first time that line has been confirmed on
hardware rather than in the suite.

majestic reports "up" either way, and that is the trap: pidof finds it because
SIGQUIT is majestic's "release the SDK and keep serving" signal. In the broken
state a snapshot returns HTTP 200 with 0 bytes and the log says "stop_sdk:
Stop sdk Ok!". The camera looks alive and has no video.

The second half is the other end of that sentence. free_resources tells a
collector the log stops "until this camera returns"; nothing ever said it had.
S41bootmsg logs "this camera is up on <BUILD_ID>" once the network can carry
it. The build id is the payload: it is the only word from the camera itself
that the flash took, or that the bootloader fell back to what it had.

Gated on SYSLOG_REMOTE, so a camera that forwards nothing sources a file it
does not have and exits -- 672 bytes once comments are stripped.
Backgrounded, so the boot pays nothing: the script returns in 0.00s and the
waiting happens in a subshell, which matters because it can wait a good while
and blocking would put that in front of majestic and the video behind it.

What it waits FOR took two corrections, both from the same mistake of
assuming a signal meant what it looked like.

A two-second settle after the address -- failsafe-rescue's -- is not enough:
the marker went out at 13:28:10 and the first line the collector received that
boot was majestic's at 13:28:11. It missed by one second, and forwarding is
UDP with nothing to retry it. It now sends one ICMP packet at the collector
first, which resolves the ARP entry the datagram needs instead of guessing how
long that takes.

And an IP is not a hostname. Every measurement above was taken against a
literal IP; busybox resolves a name once, when syslogd starts at S01 before
the network, and retries only every 120 s -- etc/default/syslogd has said so
since #2415 and tells people to prefer an IP for exactly this reason. So on a
name the marker was dropped outright: reproduced on the same board, present
locally at 12:49:55 and absent at the collector, while 48 other lines from
that window arrived once the daemon had re-resolved. The marker now waits that
window out on a name. Late and delivered beats punctual and dropped, and the
build id is no less true two minutes on.

Not by restarting syslogd to force a re-resolve, which is the obvious repair:
its buffer is in RAM and dies with it, so that trades the whole local record
of the boot -- the copy that still exists when forwarding does not -- for one
forwarded line.

Delivered on both destination forms, same board and collector:

  2026-09-14T13:33:52+01:00 10.216.128.39 warning boot  this camera is up on nightly-20260913-910e724
  2026-09-14T13:34:49+01:00 10.216.128.39 warning boot  this camera is up on nightly-20260913-910e724
  2026-09-14T13:55:39+01:00 10.216.128.39 warning boot  this camera is up on nightly-20260913-910e724

test_sysupgrade.sh gains four behaviour checks and six drift checks, and the
mount/umount stubs are now stateful so check_sdcard's unmount loop can be
driven at all. The two assertions that encode the abort fix were confirmed to
fail against the pre-fix script while the two unrelated ones stayed green, so
they pin the fix rather than the weather.
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