Skip to content

pins_dir: a read-only pin layer owned by packages (#42) - #97

Merged
linuxnow merged 4 commits into
mainfrom
fix/42-pins-d
Oct 19, 2026
Merged

linuxnow merged 4 commits into
mainfrom
fix/42-pins-d

Conversation

@linuxnow

Copy link
Copy Markdown
Contributor

Refs #42. Built on #85 (fix/71-verdict-refused): it needs PIN_REFUSED (-516). Until #85 is merged its commit shows in this diff too. Merge #85 first.

What: pins that ship with packages get a read-only layer under the host's pins. At start the daemon reads every *.pins file of the new key pins_dir (default /usr/share/plugin-hostd/pins.d; empty is none) in byte order of the names, in the pin_set line format. The pin store, pin_file and pin_set stay as they are and make up the host's layer.

Rules, with the decisions of 2026-10-18:

  • A pins.d pin with verdict=refused always holds. The store, the pin_file, pin_set and pin_accept never override it. pin_set and pin_accept of its uri answer -516. A store or pin_file line for it is kept but has no effect, and stderr says so at start. pin_clear does not remove it.
  • Any other pins.d pin is under the host's layer. A host pin of the same uri holds over it. A change of the files gives pin_changed until pin_accept records the new files in the store. pin_clear makes the package pin hold again. First-use does not learn a plugin that has a package pin.
  • Two pins.d pins of one uri: a refusal wins, otherwise the file last by name wins. Two lines in one file follow the same rule.
  • A bad or overlong line, or a file that cannot be read, is skipped and named on stderr. A missing or empty directory holds no pins. Hidden files and names that do not end in .pins are not read.
  • pin_file stays a separate option.

Docs come first in their own commit (README pin section, manual). The key list, verb table, error table and protocol JSON are regenerated from protocol.h. Packaging: make install creates the directory, the RPM owns it with %dir, the DEB through plugin-hostd.dirs.

How tested (daemon tests over the fake worker, each asserts the reply an add gets):

  • a_pins_d_pin_admits_its_plugin_and_of_two_files_the_last_by_name_wins: files are written in reverse name order. The later file holds in both directions, and the later line holds within one file. .rpmnew, README and hidden files are ignored. First-use learns nothing for a package pin.
  • a_pins_d_refusal_wins_over_any_other_pins_d_pin_and_over_the_host: the refusal wins as the earlier file, the later file and the earlier line. It wins over the store alone, a store plus pin_file, pin_set (-516), pin_accept (-516) and pin_clear one/all. Positive control: another plugin in the same file runs.
  • the_host_holds_over_a_pins_d_pin_that_is_not_a_refusal: pin_changed, then pin_accept, then the store holds across a restart. pin_clear makes the package pin hold again. pin_set over a package pin.
  • a_bad_pins_d_line_or_file_is_skipped_and_the_rest_is_read: a malformed line, a 9 KB line that would parse as a refusal, and a directory named x.pins. The daemon starts and the good line holds.
  • an_empty_or_missing_pins_dir_holds_no_pins: missing, empty and unset all answer -508, with no error. First-use still learns.

Sabotage: tests/sabotage.py gets one arm per rule: host overrides the refusal, pin_set overrides it, pin_accept overrides it, the later pin wins over a refusal, reverse name order, no .pins suffix filter, hidden file read, package over host, a bad line is fatal, pins_dir not read. I ran those arms and #85's arm locally: all 11 red when broken, green when restored.

Local checks (Fedora host): test-daemon, test-format, test-pin, check-generated, test-perturbation, check-readme, check-no-pins, test-mod-host-json all pass. Two exceptions:

Owed outside this PR: the release that carries it (a minor bump, the train's), and the producer side in omx-plugins.

verdict=refused was parsed, stored and then ignored. A plugin whose files failed qualification was added like
any other. pins_check now refuses such a plugin with the new code PHD_ERR_PIN_REFUSED (-516), after the
files have been checked against the pin: the verdict applies only to the pinned files, so files that
differ still answer -509 first. Because the add and the replay share pins_check, an instance whose pin turns
refused while it runs is refused at its next replay (instance_replay_refused with -516). The code is declared
in protocol.h, and the README and protocol/plugin-hostd.json are regenerated from it.

The test fails on main (the add answers resp 0). It covers pin_set, a pin_file under first-use (the
plugin is not learned into the store), the replay, and changed files. A sabotage arm removes the check.

Closes #71
…'s (#42)

Every *.pins file of pins_dir (/usr/share/plugin-hostd/pins.d by default)
is read at start in byte order of its name. A pin there with
verdict=refused always holds: the store, the pin_file, pin_set and
pin_accept never override it, and pin_set and pin_accept of its uri answer
PIN_REFUSED (-516). Any other package pin is under the host's pins. Of two
package pins of one uri a refusal wins, otherwise the later file by name.
A bad line or an unreadable file is skipped and named on stderr; a missing
or empty directory holds no pins.
@linuxnow
linuxnow merged commit e1b31b8 into main Oct 19, 2026
35 checks passed
@linuxnow
linuxnow deleted the fix/42-pins-d branch October 19, 2026 00:12
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