Skip to content

init scripts: match daemons by name, not by a stale pidfile - #2326

Merged
widgetii merged 2 commits into
masterfrom
initd-drop-pidfile-matching
Aug 29, 2026
Merged

init scripts: match daemons by name, not by a stale pidfile#2326
widgetii merged 2 commits into
masterfrom
initd-drop-pidfile-matching

Conversation

@widgetii

@widgetii widgetii commented Aug 29, 2026

Copy link
Copy Markdown
Member

Closes #2325. Follow-up to #2324, which fixed the same defect in S95majestic. Disjoint files, so the two can land in either order.

The defect

Given -p, busybox start-stop-daemon's do_procinit() reads only the pid that file holds and never scans /proc. -x is then just a filter on that single pid, so once the pidfile is stale the live daemon is invisible. Two consequences, both reproduced on a Hi3516EV300 (busybox 1.36.1):

Duplicate daemons. -S can't see the running instance, so it starts another — and -m overwrites the pidfile with the duplicate's pid, so the state is self-perpetuating.

Killing unrelated processes. Every stop() here passed -p with no -x, so it signalled whatever process now owned that pid:

# sleep 600 & echo $! > /var/run/<daemon>.pid
1260
# start-stop-daemon -K -q -p /var/run/<daemon>.pid
rc=0
# victim 1260: KILLED      # the daemon itself: still running

What changed

Nine scripts drop the pidfile and match on -x, which scans every process: S01syslogd, S49ntpd, S50mdnsd, S60crond, S60precision-time, S60siproxd, S89edge, S90matter, S97baresip. start becomes idempotent, stop polls until the daemon is genuinely gone before restart proceeds, and reload signals through the same matcher instead of killall.

S90matter also loses its kill -0 "$(cat "$PIDFILE")" guard, which was the same bug in longhand — it reports a reused pid as a live daemon.

Two things that would have broken a naive sweep

syslogd, ntpd and crond must match by name, not by absolute path. They are busybox applets, so /proc/PID/exe points at /bin/busybox and the majestic-style path form silently never matches — which would have spawned a second syslogd on every board, on every boot. Measured:

-x syslogd          -> MATCH (rc=0)
-x /sbin/syslogd    -> no match (rc=1)      # exe is /bin/busybox
-x crond            -> MATCH (rc=0)
-x /usr/sbin/crond  -> no match (rc=1)

Two scripts are deliberately NOT converted, each with a comment saying why, because name matching would be actively harmful:

  • S50dropbear — dropbear forks a child per SSH session, and every one shares the listener's name and executable. -x matching would make stop and restart kill every live SSH session, including the one running the command:

    -K -p PIDFILE -x dropbear  -> stopped dropbear (pid 607)        # listener only, correct
    -K -x dropbear             -> stopped dropbear (pid 908 607)    # 908 was my own session
    

    Its pidfile is also the trustworthy kind: there is no -b/-m in that script, so dropbear daemonises itself and writes the file, rather than start-stop-daemon guessing at it.

  • S96onvifserverDAEMON is httpd, a second busybox httpd living beside the WebUI's own (S50httpd). The two are told apart only by their arguments, which start-stop-daemon cannot match on, so the pidfile stays as the only thing that distinguishes them.

Both still gain an -x guard on -K, which removes the dangerous half — a reused pid can no longer be signalled by mistake:

-K -p <stale pidfile>              -> stopped process in pidfile (pid 1126)   # collateral damage
-K -p <stale pidfile> -x dropbear  -> no dropbear found; none killed          # guarded

Nothing outside these scripts reads any of the pidfiles. (siproxd.conf sets pid_file, but siproxd writes that itself — dropping -m removes a competing writer rather than a reader.)

Verification (Hi3516EV300 + IMX335, busybox 1.36.1, kernel 4.9.37)

Every changed file passes dash -n and sh -n on-device. The four daemons that actually run on this board were exercised live:

Check syslogd ntpd crond
stale pidfile + start → no duplicate PASS PASS PASS
stop → gone, pidfile cleared PASS PASS PASS
stop when already stopped → rc=0 PASS PASS PASS
start → running, argv[0] unchanged PASS PASS PASS
restart → exactly one daemon PASS PASS PASS

Plus: logger still reaches syslogd after a restart; dropbear's match set verified to be the listener alone; and a cold boot brings up syslogd, ntpd, crond, dropbear and majestic exactly once each, with argv[0] unchanged for all, the three converted pidfiles absent, dropbear's own pidfile present and correct, and :22, :80 and :554 listening.

The remaining six daemons (mdnsd, siproxd, ptp4l, n3n-edge, matter-server, baresip) are not enabled on this board, so the real daemons could not be run. Their init scripts were exercised on the camera instead, against a small static ARM ELF that waits on pause() and dies on the default SIGTERM, installed at each daemon's true install path. That gives a genuine /proc/PID/exe and argv[0], so the matching, duplicate-refusal and shutdown logic -- the only thing this PR changes -- runs exactly as it will in production. All six pass start, stale-pidfile-no-duplicate, restart-leaves-one, stop, and stop-when-already-stopped; reload delivers SIGHUP to the right pid.

The observed argv[0] confirms the intended split: mdnsd, siproxd, n3n-edge and baresip keep a bare argv[0], while ptp4l and matter-server keep the absolute argv[0] they already had. Nothing observable changes for either group.

What this does not prove is each daemon's own behaviour under restart -- whether siproxd re-binds cleanly, whether ptp4l re-acquires its grandmaster. Those paths are untouched here, but if per-daemon hardware evidence is wanted first, the six can be split into a separate PR and held until each package can be built and run on a board that enables it.

ONVIF: matching by argument, not by pidfile

Review caught that the -x httpd guard accepts any httpd, so a stale ONVIF pidfile whose pid the WebUI had reused could still stop the WebUI -- the very thing keeping the pidfile was meant to prevent. start-stop-daemon cannot match on arguments, so S96onvifserver now matches them itself, scanning for the httpd whose command line carries $HTTPD_CONFIG. That names our instance exactly and never the WebUI's, fixing both halves of the defect for this script too.

Verified with two real busybox httpd instances side by side on the camera: ONVIF starts alongside the WebUI, a second start is refused, and with the pidfile poisoned to name the WebUI's pid (1589), stop terminated the ONVIF instance (1616) and left the WebUI running.

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

Copy link
Copy Markdown

PR Summary by Qodo

Match init daemons independently of stale pidfiles

🐞 Bug fix ✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Match nine daemons through process scans instead of stale pidfiles.
• Make starts idempotent and block restarts until shutdown completes.
• Guard Dropbear and ONVIF pidfile stops against unrelated reused processes.
Diagram

graph TD
  A["Init action"] --> B{"Matching strategy"}
  B -->|nine daemons| C["Executable matcher"] --> D["Process scan"] --> E{"Requested action"}
  E -->|start| F["Idempotent start"]
  E -->|stop or restart| G["Signal and poll"]
  B -->|special cases| H["Guarded pidfile"]
Loading
High-Level Assessment

The daemon-specific strategy is appropriate for BusyBox-based SysV systems: executable matching removes stale-pid hazards with no new runtime dependency, while guarded pidfiles remain where process names cannot identify the correct instance. A shared helper or custom /proc argument scanner could reduce duplication or distinguish instances, but would add boot-time coupling and parsing complexity disproportionate to these small scripts.

Files changed (12) +296 / -152

Bug fix (12) +296 / -152
S01syslogdReplace syslogd pidfile matching with BusyBox applet-name discovery +28/-13

Replace syslogd pidfile matching with BusyBox applet-name discovery

• Starts and stops syslogd by its argv[0] name so stale pidfiles cannot hide or misidentify the process. Start is idempotent, stop polls for termination, and restart aborts when shutdown fails.

general/overlay/etc/init.d/S01syslogd

S49ntpdDiscover ntpd by BusyBox applet name instead of pidfile +28/-13

Discover ntpd by BusyBox applet name instead of pidfile

• Removes start-stop-daemon pidfile creation and matches ntpd by name, which works for the BusyBox applet. Adds idempotent start handling, bounded stop polling, and safe restart failure propagation.

general/overlay/etc/init.d/S49ntpd

S50dropbearGuard Dropbear listener shutdown with executable matching +10/-1

Guard Dropbear listener shutdown with executable matching

• Retains Dropbear's daemon-written pidfile to target only the SSH listener rather than active session children. Adds an executable filter to stop operations so a reused pid cannot kill an unrelated process.

general/overlay/etc/init.d/S50dropbear

S60crondMatch crond by BusyBox applet name without pidfiles +28/-13

Match crond by BusyBox applet name without pidfiles

• Switches crond lifecycle checks to name-based process scanning and removes start-stop-daemon pidfile management. Starts become idempotent, stops verify process exit, and failed stops prevent restart.

general/overlay/etc/init.d/S60crond

S97baresipMake baresip lifecycle independent of stale pidfiles +28/-14

Make baresip lifecycle independent of stale pidfiles

• Uses executable-name matching for start, stop, and liveness polling instead of a pidfile. Treats an existing process as successful and blocks restart or reload when shutdown does not complete.

general/package/baresip-openipc/files/S97baresip

S90matterReplace Matter pidfile checks with executable-based lifecycle detection +29/-28

Replace Matter pidfile checks with executable-based lifecycle detection

• Removes pidfile and kill -0 liveness checks, using matter-server executable matching for start, stop, and status. Adds idempotent starts, bounded shutdown polling, stale-file cleanup, and restart failure propagation.

general/package/matter/files/S90matter

S50mdnsdUse mdnsd process matching for lifecycle and reload signals +27/-14

Use mdnsd process matching for lifecycle and reload signals

• Drops pidfile-based management in favor of process-wide executable-name matching. Stop waits for termination, restart stops on failure, and reload sends SIGHUP through the same matcher instead of killall.

general/package/mdnsd-openipc/files/S50mdnsd

S89edgeManage n3n-edge through executable-name process scans +27/-14

Manage n3n-edge through executable-name process scans

• Removes generated pidfile matching from n3n-edge start and stop operations. Adds idempotent starts, verified shutdown before restart, and matcher-scoped SIGHUP reloads.

general/package/n3n-openipc/files/S89edge

S96onvifserverGuard ONVIF HTTPD pidfile stops with process-name matching +10/-1

Guard ONVIF HTTPD pidfile stops with process-name matching

• Keeps the pidfile because arguments are the only distinction from the WebUI's BusyBox httpd instance. Adds an httpd executable filter during stop so a stale pid cannot target an unrelated non-httpd process.

general/package/onvif-simple-server/files/S96onvifserver

S60precision-timeMatch ptp4l by absolute executable instead of pidfile +27/-13

Match ptp4l by absolute executable instead of pidfile

• Introduces a reusable ptp4l executable path and removes pidfile-based lifecycle matching. Start is idempotent, stop polls for actual termination, and restart aborts after a failed shutdown.

general/package/openipc-precision-time/files/S60precision-time

S60siproxdManage siproxd by process name and matcher-scoped signals +27/-14

Manage siproxd by process name and matcher-scoped signals

• Removes start-stop-daemon's competing pidfile writer and matches siproxd directly for lifecycle operations. Adds idempotent starts, verified stops, guarded restarts, and SIGHUP reload through start-stop-daemon.

general/package/siproxd-openipc/files/S60siproxd

S50mdnsdUse mdnsd process matching for lifecycle and reload signals +27/-14

Use mdnsd process matching for lifecycle and reload signals

• Drops pidfile-based management in favor of process-wide executable-name matching. Stop waits for termination, restart stops on failure, and reload sends SIGHUP through the same matcher instead of killall.

general/package/mdnsd-openipc/files/S50mdnsd

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

qodo-free-for-open-source-projects Bot commented Aug 29, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Six daemon changes untested 📘 Rule violation ☼ Reliability
Description
The PR changes lifecycle behavior for mdnsd, siproxd, ptp4l, n3n-edge, matter-server, and
baresip, but the description explicitly says these daemons were not exercised on hardware. This
directly fails the hardware-evidence requirement for behavior-changing code.
Code

general/package/baresip-openipc/files/S97baresip[18]

+	start-stop-daemon -b -S -q -x "$DAEMON" -- $DAEMON_ARGS
Evidence
PR Compliance ID 1 expressly treats a statement that behavior-changing code was not tested on
hardware as failure. The cited branch regions show newly changed start/stop behavior for the six
daemons, while the supplied PR description says those same six daemons were not enabled on the
tested board and were not exercised on hardware.

Rule 1: Hardware evidence is present and honest
general/package/mdnsd-openipc/files/S50mdnsd[22-43]
general/package/siproxd-openipc/files/S60siproxd[23-44]
general/package/openipc-precision-time/files/S60precision-time[24-45]
general/package/n3n-openipc/files/S89edge[23-44]
general/package/matter/files/S90matter[60-81]
general/package/baresip-openipc/files/S97baresip[18-39]

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

## Issue description
Six init-script lifecycle changes lack verification from real camera hardware.
## Issue Context
The PR description states that `mdnsd`, `siproxd`, `ptp4l`, `n3n-edge`, `matter-server`, and `baresip` were not exercised on hardware. Provide before/after command output demonstrating stale-PID startup behavior, shutdown verification, and restart behavior for each affected daemon on an applicable camera.
## Fix Focus Areas
- general/package/mdnsd-openipc/files/S50mdnsd[22-43]
- general/package/siproxd-openipc/files/S60siproxd[23-44]
- general/package/openipc-precision-time/files/S60precision-time[24-45]
- general/package/n3n-openipc/files/S89edge[23-44]
- general/package/matter/files/S90matter[60-81]
- general/package/baresip-openipc/files/S97baresip[18-39]

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



Remediation recommended

2. WebUI server remains killable ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new -x "$DAEMON" guard accepts any httpd, while the WebUI is a separate httpd instance and
is distinguished from ONVIF only by arguments. If a stale ONVIF pidfile PID has been reused by the
WebUI, stop kills the WebUI and deletes the ONVIF pidfile.
Code

general/package/onvif-simple-server/files/S96onvifserver[44]

+	start-stop-daemon -K -q -p "$PIDFILE" -x "$DAEMON"
Evidence
The changed guard checks only httpd, while both services run that daemon name and maintain
separate pidfiles. A WebUI PID in the stale ONVIF pidfile therefore satisfies the pidfile and name
conditions.

general/package/onvif-simple-server/files/S96onvifserver[7-18]
general/package/onvif-simple-server/files/S96onvifserver[42-47]
general/package/legacy/webui/files/init.d/S50httpd[3-6]
general/package/legacy/webui/files/init.d/S50httpd[21-25]

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

## Issue description
`-x httpd` cannot distinguish the ONVIF listener from the WebUI listener. A stale ONVIF pidfile whose PID is reused by the WebUI can therefore terminate the WebUI.
## Issue Context
Before signalling the PID from the retained ONVIF pidfile, verify through `/proc/<pid>/cmdline` that it is the instance launched with `/etc/httpd_onvif.conf` (and, where practical, the ONVIF port). Reject the stop rather than signalling a different `httpd`.
## Fix Focus Areas
- general/package/onvif-simple-server/files/S96onvifserver[34-44]

ⓘ 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 group findings by type and pick your Finding display, from Minimal to Full

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread general/package/baresip-openipc/files/S97baresip
Comment thread general/package/onvif-simple-server/files/S96onvifserver Outdated
Follow-up to the S95majestic fix. Given -p, busybox start-stop-daemon
inspects only the pid that file holds and never scans /proc, so the
pidfile is the sole liveness oracle -- and it goes stale on its own.
Measured on a Hi3516EV300 (busybox 1.36.1), a -S against a stale pidfile
starts a second copy of a daemon that is already running, and -m then
overwrites the file with the duplicate's pid. Worse, every stop() here
passed -p with no -x, so it signalled whatever process had since reused
that pid: pointed at an unrelated live process, it killed that process
and left the daemon running.

Nine scripts drop the pidfile and match on -x, which scans every
process: S01syslogd, S49ntpd, S50mdnsd, S60crond, S60precision-time,
S60siproxd, S89edge, S90matter and S97baresip. start is now idempotent,
stop polls until the daemon is really gone before restart proceeds, and
reload signals through the same matcher instead of killall.

syslogd, ntpd and crond match by name rather than by absolute path, and
that is deliberate: they are busybox applets, so /proc/PID/exe points at
/bin/busybox and a path never matches. Measured:

  -x syslogd        -> match
  -x /sbin/syslogd  -> no match

Two scripts are deliberately left on their pidfiles, with a comment
saying why, because name matching would be actively harmful:

  S50dropbear -- dropbear forks a child per SSH session sharing the
  listener's name and executable, so -x matching would make stop and
  restart kill every live session, including the one running the
  command. Measured: -K -x dropbear matched pids 908 and 607, where 607
  was the listener and 908 was the caller's own session. Its pidfile is
  also trustworthy, being written by dropbear itself rather than by -m.

  S96onvifserver -- DAEMON is "httpd", a second busybox httpd beside the
  WebUI's own (S50httpd), and the two differ only in their arguments,
  which start-stop-daemon cannot match on.

Both still gain an -x guard on -K, so a reused pid can no longer be
signalled by mistake.

Nothing outside these scripts reads any of the pidfiles.

Verified on a Hi3516EV300: for syslogd, ntpd and crond a stale pidfile
no longer spawns a duplicate, stop/start/restart each leave exactly one
daemon with argv[0] unchanged, syslogd still receives from logger, and a
cold boot brings up every daemon exactly once with :22, :80 and :554
listening.
Review catch: the -x "$DAEMON" guard added to -K accepts any httpd, and
the WebUI runs a second busybox httpd (S50httpd) that differs from this
one only in its arguments. A stale ONVIF pidfile whose pid the WebUI had
since reused would therefore stop the WebUI -- the very thing keeping
the pidfile was supposed to prevent.

start-stop-daemon cannot match on arguments, so match them here instead:
scan for the httpd whose command line carries $HTTPD_CONFIG. That names
our instance exactly and can never name the WebUI's, which fixes both
halves of the defect rather than only the collateral-damage half. start
skips when our instance is already up and drops any leftover pidfile
before -S, so a stale pid can no longer refuse the start either.

Also add the missing -q to S90matter's -S, which was letting busybox's
own "is already running" line print through the status message.

Verified on a Hi3516EV300 with two real busybox httpd instances running
side by side: ONVIF starts alongside the WebUI, a second start is
refused, and with the pidfile poisoned to name the WebUI's pid, stop
terminated the ONVIF instance and left the WebUI running.
@widgetii
widgetii force-pushed the initd-drop-pidfile-matching branch from fde08cc to 15bdf0d Compare August 29, 2026 07:50
@widgetii
widgetii merged commit 2d410d5 into master Aug 29, 2026
114 checks passed
@widgetii
widgetii deleted the initd-drop-pidfile-matching branch August 29, 2026 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

init scripts: start-stop-daemon -p can spawn duplicate daemons and kill unrelated processes

1 participant