- Basic architecture recap
- The rpm-ostree daemon
There are two main contexts in which rpm-ostree is used: on compose servers to generate OSTree commits from RPMs, and on client systems, which consume these OSTree commits to provide transactional upgrades.
The core architecture document describes in details the process by which RPMs are converted into OSTrees on both the compose server and client systems in “hybrid mode”.
As described in the administration page, rpm-ostree is the CLI program used on rpm-ostree-based systems to e.g. upgrade, rebase, install packages, etc…
- to ensure serialization/locking of system mutations
- to make it easier for other clients to manage rpm-ostree-based systems (like Cockpit)
- to allow unprivileged users to make system mutations via polkit
The D-Bus API is defined here: https://github.com/coreos/rpm-ostree/blob/main/src/daemon/org.projectatomic.rpmostree1.xml
The rpm-ostree daemon runs as a systemd service which owns the
org.projectatomic.rpmostree1 name on the system D-Bus:
When a client wants to talk to the daemon, the D-Bus daemon starts up the systemd service if it’s not already running:
When a user types
rpm-ostree upgrade, at a high-level what happens is that it reaches out to the daemon (starting it if it’s not yet started) and calls the
Upgrade() D-Bus method. You can see its method signature in the XML definition file linked above.
On the client side, this happens in
rpmostree-builtin-upgrade.cxx where it calls
rpmostree_os_call_upgrade_sync function. This is an auto-generated function (via
gdbus-codegen) which in turns uses glib’s D-Bus bindings to do the actual D-Bus call.
On the daemon side, this call is handled in
rpmostreed-os.cxx) which creates a new
DeployTransaction object (via
rpmostreed_transaction_new_deploy). Transactions are how we ensure that only a single mutation happens at a time, and allow tracking the state of the daemon’s processing of the request.
Transaction objects implement their own separate D-Bus API over an abstract socket. The daemon returns to the client the address of the transaction’s socket and the client then connects to it as a D-Bus peer connection. (Notice how all the methods in the XML return a
Once the client is connected to the transaction, it calls its
Start() D-Bus method, which invokes the
_execute() function. In the case of
DeployTransaction, this is
The client then receives signals from the transaction to monitor the progress of the operation (for example, everytime a message must be printed, a
Message signal is emitted).
The same analogous process occurs for other operations. For example,
rpm-ostree kargs creates a
KernelArgTransaction object, which has its execution code in
Once a transaction has finished, it emits the
Finished signal, which tells the client to stop waiting for more updates and disconnect.
The daemon integrates with polkit, which is a framework for authorizing actions. For example, a local administrator running as its unprivileged uid should be able to call
How this works is that the daemon converts D-Bus method calls like
Upgrade() into polkit actions in
os_authorize_method. It then consults polkit to determine whether the client should be permitted to execute the requested actions.
We ship a base policy file which provide actions which should be allowed:
Some distros may enhance this policy by shipping rules which dynamically calculate authorization based on e.g. group membership. For example, in Fedora:
Once a D-Bus API is stable, its signature cannot be modified because it would break other D-Bus clients. This is why many methods have a catch-all
options parameter which allows for extending the API without changing its signature.
When adding new APIs, it’s OK to have separate arguments for each parameter considered essential to the operation, while keeping “auxiliary” parameters in an
options parameter (such as whether to reboot or not).