Systemd Client Library

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.

enum sdexec_state_t
enumerator STATE_UNKNOWN

State string was not recognized.

enumerator STATE_INACTIVE

Unit is stopped (ActiveState = "inactive").

enumerator STATE_ACTIVATING

Unit is starting (ActiveState = "activating").

enumerator STATE_ACTIVE

Unit is running (ActiveState = "active").

enumerator STATE_DEACTIVATING

Unit is stopping (ActiveState = "deactivating").

enumerator STATE_FAILED

Unit failed to start or was killed (ActiveState = "failed").

enum sdexec_substate_t
enumerator SUBSTATE_UNKNOWN

Substate string was not recognized.

enumerator SUBSTATE_DEAD

No processes running.

enumerator SUBSTATE_START

Start sequence in progress.

enumerator SUBSTATE_RUNNING

Main process is running.

enumerator SUBSTATE_EXITED

Main process has exited; unit cleanup may be pending.

enumerator SUBSTATE_FAILED

Unit stopped due to an error.

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 sdexec_state_t / 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.

struct unit *sdexec_unit_create(const char *name)

Allocate a unit object for the unit named name. Returns NULL on allocation failure.

void sdexec_unit_destroy(struct unit *unit)

Free unit and all associated resources.

bool sdexec_unit_update(struct unit *unit, json_t *property_dict)

Apply a property dictionary (from sdexec_property_changed_dict() or sdexec_property_get_all_dict()) to unit. Returns true if any tracked field changed, false if the update was a no-op.

bool sdexec_unit_update_frominfo(struct unit *unit, struct unit_info *info)

Like sdexec_unit_update() but applies data from a unit_info returned by sdexec_list_units_next().

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. sdexec_unit_aux_set() returns 0 on success, -1 on error.

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.

int sdexec_unit_wait_status(struct unit *unit)

Returns the wait(2) status of the main process if sdexec_unit_has_finished() returns true, otherwise -1.

int sdexec_unit_systemd_error(struct unit *unit)

Returns the systemd error code if sdexec_unit_has_failed() returns true, otherwise -1.

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

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:

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 systemd.resource-control(5) 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 flux_future_t. On error, returns NULL with error set if non-NULL.

int sdexec_start_transient_unit_get(flux_future_t *f, const char **job)

Extract the job object path from a fulfilled 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

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 flux_future_t.

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.

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

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 sdexec_property_get_unpack() to extract the result.

int sdexec_property_get_unpack(flux_future_t *f, const char *fmt, ...)

Unpack the variant value from a fulfilled sdexec_property_get() future. fmt is a Jansson-style unpack format string applied to the unwrapped value. Returns 0 on success, -1 on error.

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 sdexec_property_get_all_dict() to access the result.

json_t *sdexec_property_get_all_dict(flux_future_t *f)

Return the property dictionary from a fulfilled sdexec_property_get_all() future. The dict is valid for the lifetime of f and can be queried with sdexec_property_dict_unpack() or passed to sdexec_unit_update().

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 sdexec_property_changed_path() to filter.

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 sdexec_property_changed() future. Both are valid for the lifetime of the current fulfillment. The dict can be passed directly to sdexec_unit_update().

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

CHANNEL_LINEBUF

Flag for sdexec_channel_create_output(): buffer output and deliver it line-by-line rather than in arbitrary chunks.

typedef 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).

typedef 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.

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 sdexec_channel_start_output() is called. Set CHANNEL_LINEBUF in flags to enable line buffering.

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 sdexec_channel_write().

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.

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. sdexec_channel_get_fd() returns -1 if the fd has been closed or ch is NULL.

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.

void sdexec_channel_start_output(struct channel *ch)

Arm the reactor watcher for an output channel. Data arriving after this call triggers output_cb.

void sdexec_channel_destroy(struct channel *ch)

Destroy ch and free all associated resources.

Unit List

struct unit_info

Snapshot of a unit entry returned by ListUnitsByPatterns.

const char *name

Unit name (e.g. flux-job-123-abc.service).

const char *description

Unit description string.

const char *load_state

Load state ("loaded", "not-found", etc.).

const char *active_state

Active state string; see sdexec_state_t.

const char *sub_state

Sub-state string; see sdexec_substate_t.

const char *path

D-Bus object path for the unit.

json_int_t job_id

ID of a queued systemd job for this unit, or 0 if none.

const char *job_type

Type string of the queued job ("start", "stop", etc.).

const char *job_path

D-Bus object path of the queued job.

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 sdexec_list_units_next().

bool sdexec_list_units_next(flux_future_t *f, struct unit_info *info)

Fill info with the next unit entry from a fulfilled 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 sd_bus_path_encode(3) and sd_bus_path_decode(3) 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.

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

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:

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

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

Method

Notes

Subscribe

Request signal delivery to this connection

AddMatch

Add a match rule to filter incoming signals

External Resources