.. _libsdexec:
######################
Systemd Client Library
######################
.. default-domain:: c
libsdexec (``src/common/libsdexec/``) is a C library that abstracts the D-Bus
interface to systemd. It translates libsubprocess-style command objects into
``StartTransientUnit`` D-Bus calls, manages I/O channels, and tracks unit
state.
Unit States
===========
Unit state is tracked with two enums that map to the ``ActiveState``
and ``SubState`` D-Bus properties on ``org.freedesktop.systemd1.Unit``.
.. c:enum:: sdexec_state_t
.. c:enumerator:: STATE_UNKNOWN
State string was not recognized.
.. c:enumerator:: STATE_INACTIVE
Unit is stopped (ActiveState = "inactive").
.. c:enumerator:: STATE_ACTIVATING
Unit is starting (ActiveState = "activating").
.. c:enumerator:: STATE_ACTIVE
Unit is running (ActiveState = "active").
.. c:enumerator:: STATE_DEACTIVATING
Unit is stopping (ActiveState = "deactivating").
.. c:enumerator:: STATE_FAILED
Unit failed to start or was killed (ActiveState = "failed").
.. c:enum:: sdexec_substate_t
.. c:enumerator:: SUBSTATE_UNKNOWN
Substate string was not recognized.
.. c:enumerator:: SUBSTATE_DEAD
No processes running.
.. c:enumerator:: SUBSTATE_START
Start sequence in progress.
.. c:enumerator:: SUBSTATE_RUNNING
Main process is running.
.. c:enumerator:: SUBSTATE_EXITED
Main process has exited; unit cleanup may be pending.
.. c:enumerator:: SUBSTATE_FAILED
Unit stopped due to an error.
.. c:function:: const char *sdexec_statetostr(sdexec_state_t state)
const char *sdexec_substatetostr(sdexec_substate_t substate)
sdexec_state_t sdexec_strtostate(const char *s)
sdexec_substate_t sdexec_strtosubstate(const char *s)
Convert between :c:enum:`sdexec_state_t` / :c:enum:`sdexec_substate_t`
and their string representations. Conversion from an unknown string
returns the ``_UNKNOWN`` enumerator.
Unit Object
===========
A ``struct unit`` accumulates D-Bus property updates for a single transient
unit and exposes typed accessors. It is the primary object passed between
the property and lifecycle layers.
.. c:function:: struct unit *sdexec_unit_create(const char *name)
Allocate a unit object for the unit named *name*. Returns NULL on
allocation failure.
.. c:function:: void sdexec_unit_destroy(struct unit *unit)
Free *unit* and all associated resources.
.. c:function:: bool sdexec_unit_update(struct unit *unit, json_t *property_dict)
Apply a property dictionary (from :c:func:`sdexec_property_changed_dict`
or :c:func:`sdexec_property_get_all_dict`) to *unit*. Returns true
if any tracked field changed, false if the update was a no-op.
.. c:function:: bool sdexec_unit_update_frominfo(struct unit *unit, struct unit_info *info)
Like :c:func:`sdexec_unit_update` but applies data from a
:c:struct:`unit_info` returned by :c:func:`sdexec_list_units_next`.
.. c:function:: void *sdexec_unit_aux_get(struct unit *unit, const char *name)
int sdexec_unit_aux_set(struct unit *unit, const char *name, void *aux, flux_free_f destroy)
Attach or retrieve arbitrary caller data keyed by *name*.
:c:func:`sdexec_unit_aux_set` returns 0 on success, -1 on error.
.. c:function:: sdexec_state_t sdexec_unit_state(struct unit *unit)
sdexec_substate_t sdexec_unit_substate(struct unit *unit)
pid_t sdexec_unit_pid(struct unit *unit)
const char *sdexec_unit_path(struct unit *unit)
const char *sdexec_unit_name(struct unit *unit)
Accessors for current state, substate, main PID, D-Bus object path, and
unit name.
.. c:function:: int sdexec_unit_wait_status(struct unit *unit)
Returns the :linux:man2:`wait` status of the main process if
:c:func:`sdexec_unit_has_finished` returns true, otherwise -1.
.. c:function:: int sdexec_unit_systemd_error(struct unit *unit)
Returns the systemd error code if :c:func:`sdexec_unit_has_failed`
returns true, otherwise -1.
.. c:function:: bool sdexec_unit_has_started(struct unit *unit)
bool sdexec_unit_has_finished(struct unit *unit)
bool sdexec_unit_has_failed(struct unit *unit)
Lifecycle predicates. ``has_started`` becomes true when the unit reaches
ACTIVE/RUNNING and ``ExecMainPID`` is set. ``has_finished`` becomes true
when ``ExecMainCode`` is available. ``has_failed`` becomes true when the
unit reaches FAILED state.
Starting Units
==============
.. c:function:: flux_future_t *sdexec_start_transient_unit(flux_t *h, uint32_t rank, const char *mode, json_t *cmd, int stdin_fd, int stdout_fd, int stderr_fd, flux_error_t *error)
Send a ``StartTransientUnit`` D-Bus call via ``sdbus.call`` on *rank*.
*cmd* is a libsubprocess command object; the unit name must be set as the
``SDEXEC_NAME`` command option (with ``.service`` suffix).
*stdin_fd*, *stdout_fd*, and *stderr_fd* are file descriptors to pass to
the new unit as its stdio streams. Pass -1 to have systemd manage a
stream itself. The caller may close its copies after the future is first
fulfilled.
Command options with the ``SDEXEC_PROP_`` prefix are translated to service
unit properties. The following are given special treatment; all others
are passed as strings:
.. list-table::
:header-rows: 1
* - Option (after SDEXEC_PROP\_)
- D-Bus type
- Value format
* - MemoryHigh, MemoryMax, MemoryLow, MemoryMin
- t (uint64)
- "infinity", "98%", or quantity with K/M/G/T suffix
* - AllowedCPUs, AllowedMemoryNodes
- ay (byte array)
- Flux idset notation, e.g. "0,2-4,7"
* - DeviceAllow
- a(ss)
- Comma-separated "specifier perms" pairs,
e.g. "/dev/nvidiactl rw,/dev/nvidia0 rw"
* - DevicePolicy
- s (string)
- "auto", "closed", or "strict"
See :linux:man5:`systemd.resource-control` for property semantics.
.. note::
``DeviceAllow`` and ``DevicePolicy`` are accepted by
``StartTransientUnit`` but are not enforced in the Flux user
systemd instance. Systemd implements device containment via eBPF
``BPF_CGROUP_DEVICE`` programs, which require ``CAP_BPF`` or
``CAP_SYS_ADMIN`` — capabilities the unprivileged Flux user
instance does not hold and that cannot be delegated via cgroup
ownership. Per-job device containment will be implemented through
the IMP until systemd makes device restriction delegatable without
elevated privileges.
Returns a :type:`flux_future_t`. On error, returns NULL with
*error* set if non-NULL.
.. c:function:: int sdexec_start_transient_unit_get(flux_future_t *f, const char **job)
Extract the job object path from a fulfilled
:c:func:`sdexec_start_transient_unit` future. The path is valid for
the lifetime of *f*. Returns 0 on success, -1 with ``errno`` set on
error.
Stopping Units
==============
.. c:function:: flux_future_t *sdexec_stop_unit(flux_t *h, uint32_t rank, const char *name, const char *mode)
Send a ``StopUnit`` D-Bus call for the unit *name* on *rank*. *mode* is
typically ``"fail"``; see the `systemd D-Bus API
`_ for other
values. Returns a :type:`flux_future_t`.
.. c:function:: flux_future_t *sdexec_reset_failed_unit(flux_t *h, uint32_t rank, const char *name)
Send a ``ResetFailedUnit`` D-Bus call to clear the failed state of unit
*name* on *rank*.
.. c:function:: flux_future_t *sdexec_kill_unit(flux_t *h, uint32_t rank, const char *name, const char *who, int signum)
Send a ``KillUnit`` D-Bus call to deliver *signum* to the processes in
unit *name* on *rank*. *who* selects the target: ``"main"`` for the main
process, ``"control"`` for the control process, or ``"all"`` for both.
Properties
==========
.. c:function:: flux_future_t *sdexec_property_get(flux_t *h, const char *service, uint32_t rank, const char *path, const char *name)
Issue a ``Get`` call on the D-Bus Properties interface at object path
*path* via *service* (typically ``"sdbus"``). Use
:c:func:`sdexec_property_get_unpack` to extract the result.
.. c:function:: int sdexec_property_get_unpack(flux_future_t *f, const char *fmt, ...)
Unpack the variant value from a fulfilled :c:func:`sdexec_property_get`
future. *fmt* is a Jansson-style unpack format string applied to the
unwrapped value. Returns 0 on success, -1 on error.
.. c:function:: flux_future_t *sdexec_property_get_all(flux_t *h, const char *service, uint32_t rank, const char *path)
Issue a ``GetAll`` call on the D-Bus Properties interface at *path*.
Use :c:func:`sdexec_property_get_all_dict` to access the result.
.. c:function:: json_t *sdexec_property_get_all_dict(flux_future_t *f)
Return the property dictionary from a fulfilled
:c:func:`sdexec_property_get_all` future. The dict is valid for the
lifetime of *f* and can be queried with :c:func:`sdexec_property_dict_unpack`
or passed to :c:func:`sdexec_unit_update`.
.. c:function:: flux_future_t *sdexec_property_changed(flux_t *h, const char *service, uint32_t rank, const char *path)
Subscribe to ``PropertiesChanged`` signals for the unit at *path*. Each
fulfillment of the returned streaming future represents one signal. Pass
*path* = NULL to receive signals for all paths and use
:c:func:`sdexec_property_changed_path` to filter.
.. c:function:: json_t *sdexec_property_changed_dict(flux_future_t *f)
const char *sdexec_property_changed_path(flux_future_t *f)
Extract the property dictionary or object path from the current
fulfillment of a :c:func:`sdexec_property_changed` future. Both are
valid for the lifetime of the current fulfillment. The dict can be passed
directly to :c:func:`sdexec_unit_update`.
.. c:function:: int sdexec_property_dict_unpack(json_t *dict, const char *name, const char *fmt, ...)
Look up property *name* in a property dictionary and unpack its variant
value using the Jansson format string *fmt*. Returns 0 on success, -1
if the property is absent or the type does not match.
I/O Channels
============
.. c:macro:: CHANNEL_LINEBUF
Flag for :c:func:`sdexec_channel_create_output`: buffer output and deliver
it line-by-line rather than in arbitrary chunks.
.. c:type:: void (*channel_output_f)(struct channel *ch, json_t *io, void *arg)
Output callback type. *io* is an ioencode-formatted JSON object
containing the stream name, broker rank, and data (or EOF flag).
.. c:type:: void (*channel_error_f)(struct channel *ch, flux_error_t *error, void *arg)
Error callback type. Called on a read error before the output callback
delivers EOF.
.. c:function:: struct channel *sdexec_channel_create_output(flux_t *h, const char *name, size_t bufsize, int flags, channel_output_f output_cb, channel_error_f error_cb, void *arg)
Create a channel for output from the systemd unit (stdout or stderr).
The internal fd watcher is not started until
:c:func:`sdexec_channel_start_output` is called. Set
:c:macro:`CHANNEL_LINEBUF` in *flags* to enable line buffering.
.. c:function:: struct channel *sdexec_channel_create_input(flux_t *h, const char *name)
Create a channel for input to the systemd unit (stdin). Write to it
using :c:func:`sdexec_channel_write`.
.. c:function:: int sdexec_channel_write(struct channel *ch, json_t *io)
Write ioencode-formatted data to an input channel. The rank and
stream name fields of *io* are ignored. This may block if the socket
buffer is full. Returns 0 on success, -1 on error.
.. c:function:: int sdexec_channel_get_fd(struct channel *ch)
const char *sdexec_channel_get_name(struct channel *ch)
Return the file descriptor for the systemd end of the socketpair, or the
channel name. :c:func:`sdexec_channel_get_fd` returns -1 if the fd
has been closed or *ch* is NULL.
.. c:function:: void sdexec_channel_close_fd(struct channel *ch)
Close the systemd-side fd. Call this after systemd has duped it — the
response handler for ``StartTransientUnit`` is the right place.
.. c:function:: void sdexec_channel_start_output(struct channel *ch)
Arm the reactor watcher for an output channel. Data arriving after this
call triggers *output_cb*.
.. c:function:: void sdexec_channel_destroy(struct channel *ch)
Destroy *ch* and free all associated resources.
Unit List
=========
.. c:struct:: unit_info
Snapshot of a unit entry returned by ``ListUnitsByPatterns``.
.. c:member:: const char *name
Unit name (e.g. ``flux-job-123-abc.service``).
.. c:member:: const char *description
Unit description string.
.. c:member:: const char *load_state
Load state ("loaded", "not-found", etc.).
.. c:member:: const char *active_state
Active state string; see :c:enum:`sdexec_state_t`.
.. c:member:: const char *sub_state
Sub-state string; see :c:enum:`sdexec_substate_t`.
.. c:member:: const char *path
D-Bus object path for the unit.
.. c:member:: json_int_t job_id
ID of a queued systemd job for this unit, or 0 if none.
.. c:member:: const char *job_type
Type string of the queued job ("start", "stop", etc.).
.. c:member:: const char *job_path
D-Bus object path of the queued job.
.. c:function:: flux_future_t *sdexec_list_units(flux_t *h, const char *service, uint32_t rank, const char *pattern)
Issue a ``ListUnitsByPatterns`` D-Bus call via *service* on *rank*.
*pattern* is a glob matched against unit names; pass ``"*"`` for all units.
Iterate results with :c:func:`sdexec_list_units_next`.
.. c:function:: bool sdexec_list_units_next(flux_future_t *f, struct unit_info *info)
Fill *info* with the next unit entry from a fulfilled
:c:func:`sdexec_list_units` future. Returns true on success,
false when the list is exhausted. Pointers in *info* are valid
until the next call or until *f* is destroyed.
************
Object Paths
************
systemd maps unit names to D-Bus object paths by appending an encoded form of
the unit name to ``/org/freedesktop/systemd1/unit/``. Any character that is
not alphanumeric or underscore is replaced with ``_XX`` where XX is the
lowercase hex byte value. libsdexec handles this via :linux:man3:`sd_bus_path_encode` and
:linux:man3:`sd_bus_path_decode` in ``objpath.c``.
.. note::
This encoding is a systemd convention, not a D-Bus standard. Its presence
in sdbus's object-path type handler means sdbus has inadvertently absorbed
systemd-specific knowledge that ideally would live only here.
.. list-table::
:header-rows: 1
* - Unit name
- D-Bus object path
* - dbus.service
- /org/freedesktop/systemd1/unit/dbus_2eservice
* - flux-broker.service
- /org/freedesktop/systemd1/unit/flux_2dbroker_2eservice
* - flux-job-f23abc.service
- /org/freedesktop/systemd1/unit/flux_2djob_2df23abc_2eservice
* - user\@1000.service
- /org/freedesktop/systemd1/unit/user_401000_2eservice
*************************
D-Bus Interface Reference
*************************
The following D-Bus interfaces and members are used by the systemd integration.
See the `org.freedesktop.systemd1 man page
`_
for the full interface specification.
org.freedesktop.systemd1.Manager
=================================
.. list-table::
:header-rows: 1
* - Method
- In
- Out
- Notes
* - StartTransientUnit
- ssa(sv)a(sa(sv))
- o
- Launches a transient unit; returns the job object path
* - StopUnit
- ss
- o
- Stops a unit; mode is typically "fail"
* - KillUnit
- ssi
-
- Sends a signal to unit processes; *who* is "main", "control", or "all"
* - ResetFailedUnit
- s
-
- Clears a failed unit so it can be restarted
* - ListUnitsByPatterns
- asas
- a(ssssssouso)
- Lists units matching state/name patterns (used in tests)
org.freedesktop.systemd1.Service
=================================
Properties polled via GetAll or received via PropertiesChanged:
.. list-table::
:header-rows: 1
* - Property
- D-Bus type
- Notes
* - ExecMainPID
- u
- Main process PID; available once unit is active
* - ExecMainCode
- i
- Exit code (CLD_* constant)
* - ExecMainStatus
- i
- Raw wait status
* - ActiveState
- s
- One of: inactive, activating, active, deactivating, failed
* - SubState
- s
- One of: dead, start, running, exited, failed
* - Result
- s
- One of: done, exited, killed, timeout, etc.
org.freedesktop.DBus.Properties
================================
.. list-table::
:header-rows: 1
* - Method / Signal
- In
- Out
- Notes
* - Get
- ss
- v
- Fetch a single property by interface and name
* - GetAll
- s
- a{sv}
- Fetch all properties as a string→variant dictionary
* - PropertiesChanged (signal)
-
-
- Emitted on property changes; sdexec subscribes per-unit
org.freedesktop.DBus
=====================
.. list-table::
:header-rows: 1
* - Method
- Notes
* - Subscribe
- Request signal delivery to this connection
* - AddMatch
- Add a match rule to filter incoming signals
******************
External Resources
******************
- :linux:man5:`systemd.resource-control`
- :linux:man5:`systemd.service`