Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions doc/guide/sdbus.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ variant arrays.
- integer
* - variant
- v
- ``["type", value]``, e.g. ``["s", "active"]``
- ``["type", value]``, e.g. ``["s", "active"]``;
``a(ss)`` variants are also supported
* - array of basic type
- aX
- JSON array, e.g. ``[1, 2, 3]`` for ``ai``
Expand Down Expand Up @@ -118,10 +119,10 @@ extending ``message.c`` and registering the new signature in ``interface.c``.
* - Basic types (y, b, n, q, i, u, x, t, h, d, s, g, o)
- yes
- yes
* - Variant (v) with basic or simple array content
* - Variant (v) with basic or simple array content, or a(ss) content
- yes
- yes
* - Variant (v) with complex content
* - Variant (v) with other complex content
- null
- yes
* - Array of basic type (aX)
Expand All @@ -130,7 +131,7 @@ extending ``message.c`` and registering the new signature in ``interface.c``.
* - Property dictionary (a{sv})
- yes
- no
* - a(sv), a(sasb), a(ss)
* - a(sv), a(sasb)
- no
- yes
* - Arbitrary struct (...)
Expand Down Expand Up @@ -201,6 +202,74 @@ RPC Interface
Signal parameters as a JSON array.


Supported Methods
=================

The following ``(interface, member)`` pairs are registered in ``interface.c``
and may be called via ``sdbus.call``. Signatures use D-Bus notation; ``o``
is an object path, ``ay`` is a byte array.

.. list-table::
:header-rows: 1
:widths: 45 27 10 10

* - Interface
- Member
- In
- Out
* - org.freedesktop.systemd1.Manager
- Subscribe
-
-
* - org.freedesktop.systemd1.Manager
- Unsubscribe
-
-
* - org.freedesktop.systemd1.Manager
- ListUnitsByPatterns
- asas
- a(ssssssouso)
* - org.freedesktop.systemd1.Manager
- KillUnit
- ssi
-
* - org.freedesktop.systemd1.Manager
- StopUnit
- ss
- o
* - org.freedesktop.systemd1.Manager
- ResetFailedUnit
- s
-
* - org.freedesktop.systemd1.Manager
- StartTransientUnit
- ssa(sv)a(sa(sv))
- o
* - org.freedesktop.systemd1.Manager
- GetUnitByInvocationID
- ay
- o
* - org.freedesktop.DBus
- AddMatch
- s
-
* - org.freedesktop.DBus
- RemoveMatch
- s
-
* - org.freedesktop.DBus.Properties
- GetAll
- s
- a{sv}
* - org.freedesktop.DBus.Properties
- Get
- ss
- v
* - org.freedesktop.DBus.Properties
- PropertiesChanged (signal)
-
- sa{sv}as

Exploring the D-Bus Interface
=============================

Expand Down
6 changes: 6 additions & 0 deletions doc/man7/flux-environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,12 @@ MISCELLANEOUS
plugins from those paths, with plugins in the ``flux.cli.plugins``
namespace loaded last.

.. envvar:: FLUX_IMP_EXEC_HELPER

The IMP optionally takes its input from the output of an unprivileged
helper program when this variable is set to its path. It is set by the
job-exec system in the IMP environment at job launch.

.. _sub_command_environment:

SUB-COMMAND ENVIRONMENT
Expand Down
2 changes: 2 additions & 0 deletions doc/test/spell.en.pws
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,7 @@ ExecMainStatus
freedesktop
frominfo
GetAll
GetUnitByInvocationID
ioencode
KillUnit
libsdexec
Expand All @@ -1125,6 +1126,7 @@ LINEBUF
ListUnitsByPatterns
objpath
PropertiesChanged
RemoveMatch
ResetFailedUnit
sa
sdbus's
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ flux_SOURCES = \
builtin/shutdown.c \
builtin/lptest.c \
builtin/fsck.c \
builtin/cgroup.c
builtin/cgroup.c \
builtin/imp_exec_helper.c

nodist_flux_SOURCES = \
builtin-cmds.c
Expand Down Expand Up @@ -123,7 +124,6 @@ dist_fluxcmd_SCRIPTS = \
flux-cancel.py \
flux-watch.py \
flux-update.py \
flux-imp-exec-helper \
py-runner.py \
flux-hostlist.py \
flux-post-job-event.py \
Expand Down
235 changes: 235 additions & 0 deletions src/cmd/builtin/imp_exec_helper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/************************************************************\
* Copyright 2026 Lawrence Livermore National Security, LLC
* (c.f. AUTHORS, NOTICE.LLNS, COPYING)
*
* This file is part of the Flux resource manager framework.
* For details, see https://github.com/flux-framework.
*
* SPDX-License-Identifier: LGPL-3.0
\************************************************************/

/* imp_exec_helper.c - produce IMP exec helper JSON on stdout
*
* Usage: flux imp_exec_helper JOBID
*
* Fetch the signed jobspec (J) for JOBID and emit it as JSON to stdout
* for consumption by the IMP exec helper protocol (RFC 15).
*
* If INVOCATION_ID is set (i.e. we are running inside a systemd unit),
* also query the unit's DevicePolicy and DeviceAllow properties via
* sdbus and include an "options" object so the IMP can configure device
* containment.
*/

#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <jansson.h>

#include "ccan/str/str.h"
#include "ccan/str/hex/hex.h"
#include "builtin.h"

static const char *unit_interface = "org.freedesktop.systemd1.Service";
static const char *prop_interface = "org.freedesktop.DBus.Properties";

static struct optparse_option helper_opts[] = {
{ .name = "test-nojob", .key = 0, .has_arg = 0,
.usage = "Skip J lookup, emit options only (for testing)" },
OPTPARSE_TABLE_END,
};

/* Decode a 32-char hex invocation ID string to a JSON array of 16 bytes.
* The caller must json_decref() the result.
*/
static json_t *invocation_id_decode (const char *hex)
{
unsigned char data[16];
json_t *a;

if (!hex_decode (hex, strlen (hex), data, sizeof (data)))
log_msg_exit ("invalid INVOCATION_ID: %s", hex);
if (!(a = json_array ()))
log_err_exit ("error creating INVOCATION_ID byte array");
for (int i = 0; i < sizeof (data); i++) {
if (json_array_append_new (a, json_integer (data[i])) < 0)
log_err_exit ("error building INVOCATION_ID byte array");
}
return a;
}

/* Call GetUnitByInvocationID and return the decoded unit object path.
* The caller must free() the result.
*/
static char *get_unit_path (flux_t *h, const char *invocation_id)
{
json_t *bytes;
flux_future_t *f;
const char *path;
char *result;

bytes = invocation_id_decode (invocation_id);
if (!(f = flux_rpc_pack (h,
"sdbus.call",
FLUX_NODEID_ANY,
0,
"{s:s s:[o]}",
"member", "GetUnitByInvocationID",
"params", bytes))
|| flux_rpc_get_unpack (f, "{s:[s]}", "params", &path) < 0)
log_msg_exit ("GetUnitByInvocationID: %s", future_strerror (f, errno));
if (!(result = strdup (path)))
log_err_exit ("error duplicating unit path");
json_decref (bytes);
flux_future_destroy (f);
return result;
}

/* Add DevicePolicy, DeviceAllow to options if the IMP is running
* under systemd. INVOCATION_ID is set in the environment if so.
*/
static void options_add_device (json_t *options, flux_t *h)
{
const char *invocation_id;
char *unit_path;
flux_future_t *f;
flux_future_t *f2;
const char *vtype;
const char *policy;
json_t *allow;

if (!(invocation_id = getenv ("INVOCATION_ID")))
return;
unit_path = get_unit_path (h, invocation_id);

if (!(f = flux_rpc_pack (h,
"sdbus.call",
FLUX_NODEID_ANY,
0,
"{s:s s:s s:s s:[ss]}",
"path", unit_path,
"interface", prop_interface,
"member", "Get",
"params", unit_interface, "DevicePolicy"))
|| flux_rpc_get_unpack (f,
"{s:[[ss]]}",
"params", &vtype, &policy) < 0)
log_msg_exit ("Get DevicePolicy: %s", future_strerror (f, errno));

if (!(f2 = flux_rpc_pack (h,
"sdbus.call",
FLUX_NODEID_ANY,
0,
"{s:s s:s s:s s:[ss]}",
"path", unit_path,
"interface", prop_interface,
"member", "Get",
"params", unit_interface, "DeviceAllow"))
|| flux_rpc_get_unpack (f2,
"{s:[[so]]}",
"params", &vtype, &allow) < 0)
log_msg_exit ("Get DeviceAllow: %s", future_strerror (f2, errno));

if (json_object_set_new (options, "DevicePolicy", json_string (policy)) < 0
|| json_object_set (options, "DeviceAllow", allow) < 0)
log_msg_exit ("error adding device properties to options");

flux_future_destroy (f2);
flux_future_destroy (f);
free (unit_path);
}

/* Fetch the signed jobspec J for jobid.
* Caller must json_decref() the result.
*/
static json_t *J_from_jobid (flux_t *h, const char *jobid)
{
flux_jobid_t id;
flux_future_t *f;
const char *J;
json_t *result;

if (!jobid)
log_msg_exit ("Unable to determine jobid");
if (flux_job_id_parse (jobid, &id) < 0)
log_err_exit ("invalid jobid: %s", jobid);

if (!(f = flux_rpc_pack (h,
"job-info.lookup",
FLUX_NODEID_ANY,
0,
"{s:I s:[s] s:i}",
"id", id,
"keys", "J",
"flags", 0))
|| flux_rpc_get_unpack (f, "{s:s}", "J", &J) < 0)
log_err_exit ("error looking up J: %s", future_strerror (f, errno));
if (!(result = json_string (J)))
log_msg_exit ("error creating JSON string for J");
flux_future_destroy (f);
return result;
}

static int helper (optparse_t *p, int ac, char *av[])
{
int optindex;
const char *jobid;
flux_t *h;
json_t *imp_input;
json_t *options;

log_init ("imp_exec_helper");

optindex = optparse_option_index (p);

if (optparse_hasopt (p, "test-nojob") && optindex != ac)
log_msg_exit ("--test-nojob cannot be used with JOBID argument");
if (ac - optindex > 1) {
optparse_print_usage (p);
exit (1);
}
if (optindex < ac)
jobid = av[optindex];
else
jobid = getenv ("FLUX_JOB_ID");

h = builtin_get_flux_handle (p);

if (!(imp_input = json_object ()) || !(options = json_object ()))
log_msg_exit ("error creating json object");

if (!optparse_hasopt (p, "test-nojob")) {
if (json_object_set_new (imp_input, "J", J_from_jobid (h, jobid)) < 0)
log_msg_exit ("error adding J to imp input");
}

options_add_device (options, h);

if (json_object_size (options) > 0) {
if (json_object_set (imp_input, "options", options) < 0)
log_msg_exit ("error adding options to imp input");
}

if (json_dumpf (imp_input, stdout, JSON_COMPACT) < 0)
log_msg_exit ("error writing imp input object to stdout");

json_decref (options);
json_decref (imp_input);
return 0;
}

int subcommand_imp_exec_helper_register (optparse_t *p)
{
optparse_err_t e;
e = optparse_reg_subcommand (p,
"imp_exec_helper",
helper,
"[JOBID]",
"Emit IMP exec helper JSON for JOBID",
0,
helper_opts);
return (e == OPTPARSE_SUCCESS ? 0 : -1);
}

// vi: ts=4 sw=4 expandtab
Loading
Loading