Repository navigation
Conversation
A FIFO named like a config in a scanned directory blocked the whole stage run. ReadFile opens without O_NONBLOCK, and opening a FIFO for reading blocks in open(2) until a writer arrives, which on a boot is never: the stage runner parked in openat with wchan=wait_for_partner, kept its shutdown inhibitor, and the node could be neither reached nor rebooted until the file was deleted from recovery media. FromFile now opens with O_NONBLOCK and rejects anything the fstat says is not a regular file, which also covers a character device such as /dev/zero, where the read would never end either. O_NOFOLLOW is not set, so a symlink to a real config still works and one pointing at a FIFO is caught by the same fstat. The directory walk resolves the entry and skips a non-regular file with a warning rather than returning an error, so one planted path cannot cost the directory every config next to it. Fixes kairos-io/kairos#4865 Signed-off-by: Ettore Di Giacinto <mudler@kairos.io>
…parse The same invariant as the previous commit, for a file the walk can read but schema.Load cannot parse. Returning that error from the Walk callback ends the walk, so no file after the broken one is read, and prepareDAG then dropped the ops already collected from the files that did parse. One typo in one config therefore cancelled every other config in the directory, in lexicographic order or not. On Kairos that directory is /oem, so a single bad file silently left the node with no users, no passwords and no k3s config, and the node still booted. dirOps now names the file, logs it and keeps walking, and carries the error back alongside the ops that did load. prepareDAG returns the graph and that error together, runStage runs the graph and appends it, and Graph and Analyze keep showing the DAG instead of discarding it. A strict caller still sees the failure. Fixes kairos-io/kairos#5382 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ettore Di Giacinto <mudler@kairos.io>
|
Pushed Kept it here rather than in a second PR because it edits the same @mudler the design call worth a second opinion: the error is now reported and the configs that do parse still run, instead of failing the whole directory. That matches what kairos-agent already documents ("Cloud configs are being loaded and executed on a best-effort") and |
Fixes kairos-io/kairos#4865
Fixes kairos-io/kairos#5382
One unreadable or unparseable file in a scanned directory must not cost that directory every other config. Two causes, same invariant, so they ship together.
2. A config that will not parse ends the walk (kairos-io/kairos#5382)
dirOpsreturned theschema.Loaderror straight out of thevfs.Walkcallback. A non-nil return ends the walk, so no file after the broken one is read, andprepareDAGthen threw away the ops already collected from the files that did parse:So the directory was all-or-nothing. On Kairos that directory is
/oem, and a single typo in one file left the node with no users, no passwords and no k3s config, while still booting. kairos-agent logged an error without naming the file; immucore discarded it entirely.dirOpsnow names the file, logs it at error level and keeps walking, and carries the failure back next to the ops that did load.prepareDAGreturns the graph and that error together,runStageruns the graph and appends the error, andGraph/Analyzekeep showing the DAG instead of returning nil. A strict caller still fails on it.How this half was tested
CI on
590d8a8is green on all four legs, and theExecutor Suitewent from 19 to 21/21 specs, which is the two new ones. This box can no longer build yip offline (several modules renovate has since bumped are not in the local module cache), so what I could verify locally I verified by building the modified executor throughkairos-io/kairos(whose module graph is complete) with areplaceonto this worktree, and exercising the real code path:00-good.yaml+99-bad.yaml: before,00-good.yamldid not run at all; after, it runs and the error names99-bad.yaml.Graphover01_first/02_broken/03_last: returns a non-nil DAG with01_firstbefore03_last, plus the error.go test ./immucore/...green,agent/pkg/cloudinitandagent/pkg/configgreen.agent/pkg/utils(5SyncDataspecs, no rsync on the box) andsdk/sysextfail identically with upstream yip v1.26.4.1. A config that is not a regular file blocks the run (kairos-io/kairos#4865)
Summary
A FIFO named like a config in a scanned directory blocked the whole stage run.
ReadFileopens withoutO_NONBLOCK, and opening a FIFO for reading blocks inopen(2)until a writer arrives, which on a boot is never. On Kairos the stage runner parked inopenatwithwchan=wait_for_partner, kept its shutdown inhibitor, and the node could be neither reached nor rebooted until someone deleted the file from recovery media.FromFilenow opens withO_NONBLOCKand rejects anything thefstatsays is not a regular file. That also covers a character device such as/dev/zeronamed like a config, where the read would never end either.O_NOFOLLOWis deliberately not set: a symlink pointing at a real config is a layout yip supports, and one pointing at a FIFO is caught by the samefstat.dirOpsresolves each entry (the walk hands it theLstat) and skips a non-regular file with a warning rather than returning an error, so a single planted path cannot cost the directory every config next to it.The same guard landed on the Kairos side in kairos-io/kairos#4869, which fixed
kairos-agent config showandnotify. QA on that PR found the boot still hung, because immucore's initramfs stages and thecos-setup-*units read the same directories through yip rather than through the collector. This is that second half.Planting the file needs root-equivalent write access to a scanned path, so this is a robustness guard rather than a privilege boundary.
How it was tested
New specs in both packages, each running the call on its own goroutine with a 10s bound, because a regression here blocks forever and a plain call would hang the suite instead of failing it.
pkg/schema: a named pipe, a symlink to a named pipe, and a directory are all refused by name, a regular file and a symlink to a regular file still load.pkg/executor: a stage directory holding a FIFO (and one holding a symlink to a FIFO) still runs the config next to it, andRunreturns nil.Counts,
go test -count=1on this branch againstmasterat3c7db49in a throwaway worktree:pkg/schemapkg/executorThe 2 executor failures are
Get UsersandDeletes Users, which need root and fail identically onmaster.Also run with
-race, as CI does: no data races, same counts.Regression proof: disabling only the
dirOpsskip and keeping theFromFileguard makes both new executor specs fail, which is what makes the second hunk load-bearing rather than belt-and-braces. Before the fix the schema path did not fail, it blocked: a throwaway probe showedLoadstill parked after 5s on a FIFO.pkg/pluginsis 84 Passed / 31 Failed here and identically onmaster: those specs need root.