Repository navigation
reboot: Refuse to reboot while a shutdown inhibitor blocks it - #9
Draft
cgwalters-bot wants to merge 2 commits into
Draft
cgwalters-bot wants to merge 2 commits into
cgwalters-bot wants to merge 2 commits into
Conversation
cgwalters-bot
force-pushed
the
bot/reboot-check-inhibitors
branch
from
September 24, 2026 07:28
9b54ac7 to
379e389
Compare
cgwalters-bot
force-pushed
the
bot/reboot-check-inhibitors
branch
from
September 24, 2026 10:02
379e389 to
3c679b3
Compare
We run `systemctl reboot` via `systemd-run` so it's outside our mount namespace, but without --wait we only learn whether the transient unit was started, not whether the reboot was accepted. If logind refuses it, bootc then parks forever waiting for a SIGTERM that never comes, and e.g. `bootc upgrade --apply` from bootc-fetch-apply-updates.service would just hang instead of failing. One way to get there is a block mode shutdown inhibitor: since systemd 257, logind rejects reboot requests from root too while one is held, unless the caller explicitly asks to skip inhibitors. On CentOS Stream 10 with such a lock held, `systemd-run -- systemctl reboot` exits 0 while the system stays up, so bootc would wait forever. With --wait and --pipe, the exit status and systemctl's error message come back to us. --collect avoids leaving a failed transient unit behind in that case. There is a small race with --wait: once the reboot is accepted, shutdown can stop the transient unit or kill systemd-run before it reports success, and bootc would then print an error and exit while the system goes down instead of parking until SIGTERM. The window is short since `systemctl reboot` returns as soon as the job is queued, and the reboot happens either way; only the exit status of the bootc process being shut down is affected. Generated-by: AI
Something that takes a block mode shutdown inhibitor lock (a long-running job, a package manager, etc.) is explicitly asking not to be rebooted underneath it. But systemctl only checks inhibitors itself for interactive invocations, and bootc runs it as root from a systemd-run transient unit. Since systemd 257 logind refuses such reboots from root anyway, but older systems (e.g. CentOS Stream 9 with 252) don't. Do what rpm-ostree does (coreos/rpm-ostree#2862): before rebooting, ask logind for its inhibitors and refuse if one blocks shutdown, naming who holds it. We deliberately don't use `systemctl reboot --check-inhibitors=yes`: that also refuses while any other user is logged in, which includes the administrator's own SSH session or a stray tmux, and would break both `sudo bootc upgrade --apply` and automatic updates on many systems. We also deliberately differ from rpm-ostree, which fails closed, when logind can't be queried (e.g. it isn't running): bootc warns and reboots anyway. Otherwise every `--apply` and the update timer would fail on such systems, while where logind does run, systemd 257 and newer already enforce block locks themselves. This is a behavior change for bootc-fetch-apply-updates.service: while such a lock is held it now fails instead of rebooting, on any systemd version. The update stays staged and is applied at the next reboot or retried on the next timer run; `systemctl reboot --check-inhibitors=no` reboots anyway. The composefs `systemctl soft-reboot` path is left alone, since logind 257+ already enforces shutdown inhibitors for it. Related: bootc-dev#1047 Generated-by: AI
cgwalters-bot
force-pushed
the
bot/reboot-check-inhibitors
branch
from
September 29, 2026 18:07
3c679b3 to
6fb9c7c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When bootc reboots (
upgrade --apply,switch --apply,rollback --apply, and so bootc-fetch-apply-updates.service), it runssystemctl rebootas root and not on a tty. In that case systemctl doesn't check inhibitors itself. logind in systemd 257 and later refuses such a reboot while ablockshutdown inhibitor is held, but older systems such as CentOS Stream 9 (systemd 252) just reboot. Anything holding such a lock is explicitly asking not to be rebooted underneath it.This PR does what rpm-ostree does (coreos/rpm-ostree#2862). Before rebooting, bootc asks logind for its inhibitors (
ListInhibitorsviabusctl --json=short). It refuses when a lock has modeblockand itswhatincludesshutdown, and the error names who holds the lock.delay,block-weakand sleep/idle-only locks don't count. The error suggestssystemctl reboot --check-inhibitors=no, and says pending changes take effect on the next boot. That wording also covers rollback and the composefs paths, not just a staged update. The man pages and--applyhelp text describe this.If logind can't be queried (e.g. it isn't running), bootc prints a warning and reboots anyway. That deliberately differs from rpm-ostree, which fails closed. Failing closed would break every
--apply, and the update timer, on such systems. Where logind does run, systemd 257 and newer already enforce block locks themselves.An earlier version of this PR passed
systemctl reboot --check-inhibitors=yesinstead. That also refuses while any non-root user is logged in, including the administrator's own SSH session or a stray tmux. That would breaksudo bootc upgrade --applyand automatic updates on many systems, so this version doesn't use it.This builds on #13 (
reboot: Propagate a refused reboot as an error). With that change, a reboot refused by logind shows up as an error instead of a hang. The composefssystemctl soft-rebootpath is left alone, because logind 257+ already enforces shutdown inhibitors for it.Behavior change: on any systemd version, bootc-fetch-apply-updates.service now fails instead of rebooting while such a lock is held. The update is applied at the next reboot, or retried on the next timer run.
Related: bootc-dev#1047. The issue asks for
--check-inhibitors=yes, which this deliberately doesn't use (see above).Testing
Rebased onto main b06e0b0. The conflicts were context only: main now documents
--soft-rebootin the--applyhelp and moved the soft reboot text out of bootc-upgrade(8), so the inhibitor sentences sit next to the new text.I tested at 6fb9c7c on a 16-core devspace (cgwalters-devspace-36601734885, CentOS Stream 10 base). The branch was built in its own checkout, with its own cargo target, and the built image's bootc RPM is
g6fb9c7c017.just validatepassed.just unit-testspassed: 467 tests, including two new table-driven tests.reboot::tests::test_blocking_inhibitorschecks which locks count: block/shutdown in a combinedwhatcounts, while delay, block-weak and sleep-only locks don't. Its input is in the JSON format thatbusctl --json=shortprints on systemd 257.test_blocking_inhibitors_malformedcovers empty input, non-JSON, a missing or wrong-shapedata, too few fields, and wrongly typed fields.just test-tmt plan-24-image-upgrade-reboot(centos-bootc:stream10) passed. Withsystemd-inhibit --what=shutdown --mode=block --who=bootc-testheld,bootc switch --applyfailed withReboot blocked by shutdown inhibitor: "bootc-test" (PID 1659, UID 0): testing. Pending changes take effect on the next boot; use systemctl reboot --check-inhibitors=no to reboot anyway.Once the lock was released,--applyrebooted into the new image. That run logged in over SSH as root, so this doesn't show the logged-in-user case directly. The bootc side doesn't look at sessions at all.matchon the query result. The query and parse failures it handles are covered by the malformed-input test.Generated-by: https://github.com/cgwalters/#llms
Review draft in cgwalters-forge, not upstream yet. This section is removed when the PR is opened upstream.
bootc-dev/bootc, basemainPVTI_lADOE9oHIs4BlJLczg9kENUTo review:
/promoteon a line of its own, to open it upstream, ready for review. Either covers only the commits pushed so far.Signed-off-by: Colin Walters <walters@verbum.org>to the commits lacking it (the bot's and yours; anyone else's only if you ask), with you as committer./draftline (in the same comment or before) to open it upstream as a draft (/readyundoes that).