Skip to content

Generate transient property signatures by probing systemd - #125

Open
peturingi wants to merge 3 commits into
systemd:mainfrom
peturingi:transient-property-signatures
Open

peturingi wants to merge 3 commits into
systemd:mainfrom
peturingi:transient-property-signatures

Conversation

@peturingi

Copy link
Copy Markdown

Depends on #124 (its commit is the first one here); the probe makes a few hundred thousand failing calls and runs out of file descriptors without it.

KNOWN_UNIT_SIGNATURES is kept by hand and has drifted from systemd:

  • Conditions and Asserts are listed with their read type a(sbbsi) instead of a(sbbs), so systemd rejects them with "Bad message".
  • 52 entries are properties systemd does not let you set at all: unit state copied from the getters (ActiveState, SubState, Can*, ...), reverse dependencies (WantedBy, RequiredBy, ...), removed names (StartLimitIntervalSec, Service) and a misspelled SmackLabelIPIn (systemd's is SmackLabelIPin).
  • 227 settable properties are missing.

systemd doesn't publish the signature it accepts when setting a property, and it often differs from the introspection XML: ExecStart is read as a(sasbttttuii) but set as a(sasb), TimersMonotonic is read as a(stt) but set as a(st), and properties like OnCalendar or MemoryMaxScale are write-only.

So this measures them. tools/probe_transient_properties.py sends one property at a time to StartTransientUnit on a throwaway system, with a zero value of each candidate signature, and classifies systemd's answer. systemd checks the name before reading the value and reads the value before validating it, so "unknown property", "wrong type" and "type accepted" are distinguishable. Candidate names and signatures come from the machine itself: the introspection XML, systemd --dump-configuration-items, and string literals in libsystemd-core. Checked against v257's src/core/dbus-*.c, it finds every streq(name, ...) setter plus the names systemd builds at runtime (Limit*, Exec*, dependency types). The single exception is JobTimeoutUSec, which no released systemd lets you set because of a missing return (fixed upstream in systemd/systemd@041e70d7f7).

I ran it in nspawn containers on systemd 255 (Ubuntu 24.04), 257 (Debian 13) and 259 (Ubuntu 26.04); the results are in tools/transient-properties/. No property changed signature between those versions. On 259 the cgroup v1 properties (CPUShares, MemoryLimit, BlockIO*, ...) are accepted but ignored, and the generated table notes that.

tools/generate_transient_properties.py turns the results into pystemd/systemd1/transient_properties.py. KNOWN_UNIT_SIGNATURES and signature_array keep their names and behaviour; the table is now the generated one plus pystemd's own conveniences (RuntimeMaxSec, ListenStream, ...), which stay hand-written but are tested to target real properties with the right signature. A unit test fails if the generated module is stale. CONTRIBUTING.md explains how to add a systemd version.

Unit and e2e tests (68) pass on all three systems. The new Conditions e2e test fails on main.

🤖 Generated with Claude Code

Pétur Ingi Egilsson and others added 2 commits October 18, 2026 05:37
When sd_bus_call failed, call_method raised before unref-ing the call
message and freeing the sd_bus_error. Any file descriptor appended to the
message (type "h") was leaked with it, so a long-running process making
failing calls eventually hit EMFILE. get_property never freed its error.

Release both in a finally block, and add an e2e test that makes 100
failing calls with an fd argument and checks the fd count stays flat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KNOWN_UNIT_SIGNATURES was kept by hand and had drifted: Conditions and
Asserts were listed with their read type a(sbbsi) instead of a(sbbs), so
systemd rejected them ("Bad message"); 52 entries are properties systemd
does not let you set at all (unit state such as ActiveState, reverse
dependencies, removed names, a misspelled SmackLabelIPIn); and over 200
settable properties were missing.

systemd does not publish the signatures it accepts when setting a
property, and they often differ from the introspection XML (ExecStart is
read as a(sasbttttuii) but set as a(sasb); some properties are
write-only). tools/probe_transient_properties.py measures them instead: it
sends each candidate name with each candidate signature to
StartTransientUnit on a throwaway system and classifies systemd's answer.
Candidates come from the machine itself (introspection XML, unit file
directives, string literals in libsystemd-core).

The results for systemd 255 (Ubuntu 24.04), 257 (Debian 13) and 259
(Ubuntu 26.04) are in tools/transient-properties/, and
tools/generate_transient_properties.py turns them into
pystemd/systemd1/transient_properties.py. No property changed signature
between those versions.

KNOWN_UNIT_SIGNATURES is now that table plus pystemd's own conveniences
(RuntimeMaxSec, ListenStream, ...), which are kept separate and tested to
target real properties with the right signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 18, 2026 03:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-version probing and D-Bus ownership changes warrant human validation, and the source-distribution test dependency remains unresolved.

1 open issue
What changed in this PR

Replaces pystemd’s hand-maintained transient property signatures with generated probe data, while preserving convenience conversions.

Changes:

  • Adds probing and generation tools with results from three systemd versions.
  • Corrects and expands property signatures, with regression and freshness checks.
  • Includes the D-Bus resource-leak fix from #124.
File Description
tools/​transient-properties/​systemd-259.json Records systemd 259 probe results.
tools/​transient-properties/​systemd-257.json Records systemd 257 probe results.
tools/​transient-properties/​systemd-255.json Records systemd 255 probe results.
tools/​probe_transient_properties.py Discovers accepted property signatures.
tools/​generate_transient_properties.py Generates and checks the signature table.
tests/​test_unit_signatures.py Checks signatures, conveniences, and freshness.
pystemd/​systemd1/​unit_signatures.py Combines generated signatures with conveniences.
pystemd/​systemd1/​transient_properties.py Provides the generated signature table.
pystemd/​dbuslib.pyx Releases resources on failed calls.
e2e/​test_transient_units.py Tests Conditions serialization.
e2e/​test_dbus.py Tests file-descriptor leak prevention.
CONTRIBUTING.md Documents probing and regeneration.
CHANGES.md Records signature and leak fixes.

Review effort: Balanced

Comment on lines +67 to +68
tool = Path(__file__).parent.parent / "tools/generate_transient_properties.py"
subprocess.run([sys.executable, str(tool), "--check"], check=True)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, confirmed: the sdist shipped tests/ but not tools/, so test_up_to_date couldn't find the generator. Fixed in 9f939c3 by adding both lines to MANIFEST.in. I built the sdist, unpacked it on Debian 13, and tests/test_unit_signatures.py now passes from it (6 passed).

tests/ is in the sdist, and test_unit_signatures checks the generated
module against tools/transient-properties/ by running
tools/generate_transient_properties.py, so both need to be shipped too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants