Compare commits

...

6 Commits

Author SHA1 Message Date
Ivan Kruglov 1dc7dd847b TEST-13-NSPAWN.machinectl.sh: resolve race condition
I encountered this race condition while working on TEST-13-NSPAWN.varlinkctl.sh.
The long-running machine's init script sometimes does not have time to start and
register signals. As result, occasiounally failed tests.
2024-09-18 14:14:50 +02:00
Ivan Kruglov 76ad48f9a5 varlinkctl: tests for io.systemd.Machine.List, io.systemd.Machine.Get, io.systemd.Machine.GetByPID, io.systemd.Machine.Kill 2024-09-18 14:14:50 +02:00
Ivan Kruglov 95adf8a563 varlinkctl: io.systemd.Machine.Kill 2024-09-18 14:10:38 +02:00
Ivan Kruglov 4fb0dabd92 varlinkctl: io.systemd.Machine.Unregister & io.systemd.Machine.Terminate 2024-09-17 14:00:25 +02:00
Ivan Kruglov c0e92dec76 varlinkctl: io.systemd.Machine.GetByPID 2024-09-17 14:00:22 +02:00
Ivan Kruglov c3065654c8 varlinkctl: io.systemd.Machine.Get 2024-09-17 14:00:03 +02:00
6 changed files with 413 additions and 31 deletions

View File

@ -14,6 +14,7 @@
#include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "signal-util.h"
#include "socket-util.h"
#include "string-util.h"
#include "varlink-util.h"
@ -188,3 +189,127 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink
return sd_varlink_reply(link, NULL);
}
int lookup_machine_by_name(sd_varlink *link, Manager *manager, const char *machine_name, Machine **ret_machine) {
assert(ret_machine);
if (!machine_name)
return sd_varlink_error_invalid_parameter_name(link, "name");
if (!hostname_is_valid(machine_name, /* flags= */ VALID_HOSTNAME_DOT_HOST))
return sd_varlink_error_invalid_parameter_name(link, "name");
Machine *machine = hashmap_get(manager->machines, machine_name);
if (!machine)
return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL);
*ret_machine = machine;
return 0;
}
int vl_method_unregister_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Machine *machine = ASSERT_PTR(userdata);
Manager *manager = machine->manager;
assert(manager);
int r;
r = varlink_verify_polkit_async(
link,
manager->bus,
"org.freedesktop.machine1.manage-machines",
(const char**) STRV_MAKE("name", machine->name,
"verb", "unregister"),
&manager->polkit_registry);
if (r <= 0)
return r;
r = machine_finalize(machine);
if (r < 0)
return r;
return sd_varlink_reply(link, NULL);
}
int vl_method_terminate_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Machine *machine = ASSERT_PTR(userdata);
Manager *manager = machine->manager;
assert(manager);
int r;
r = varlink_verify_polkit_async(
link,
manager->bus,
"org.freedesktop.machine1.manage-machines",
(const char**) STRV_MAKE("name", machine->name,
"verb", "terminate"),
&manager->polkit_registry);
if (r <= 0)
return r;
r = machine_stop(machine);
if (r < 0)
return r;
return sd_varlink_reply(link, NULL);
}
int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
const char *machine_name = NULL;
const char *swho = NULL;
KillWhom whom;
int32_t signo;
const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, PTR_TO_SIZE(&machine_name), SD_JSON_MANDATORY },
{ "who", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, PTR_TO_SIZE(&swho), 0 },
{ "signal", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int32, PTR_TO_SIZE(&signo), SD_JSON_MANDATORY },
VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
int r;
assert(parameters);
r = sd_varlink_dispatch(link, parameters, dispatch_table, 0);
if (r != 0)
return r;
Machine *machine = NULL;
r = lookup_machine_by_name(link, manager, machine_name, &machine);
if (r != 0)
return r;
assert(machine);
if (isempty(swho)) {
whom = KILL_ALL;
} else {
whom = kill_whom_from_string(swho);
if (whom < 0)
return sd_varlink_error_invalid_parameter_name(link, "who");
}
if (!SIGNAL_VALID(signo))
return sd_varlink_error_invalid_parameter_name(link, "signal");
r = varlink_verify_polkit_async(
link,
manager->bus,
"org.freedesktop.machine1.manage-machines",
(const char**) STRV_MAKE("name", machine->name,
"verb", "kill"),
&manager->polkit_registry);
if (r <= 0)
return r;
r = machine_kill(machine, whom, signo);
if (r < 0)
return r;
return sd_varlink_reply(link, NULL);
}

View File

@ -2,5 +2,12 @@
#pragma once
#include "sd-varlink.h"
#include "machine.h"
int lookup_machine_by_name(sd_varlink *link, Manager *manager, const char *machine_name, Machine **ret_machine);
typedef int (*vl_method_handler_t) (sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) ;
int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
int vl_method_unregister_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
int vl_method_terminate_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);

View File

@ -1,13 +1,17 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-varlink.h"
#include "varlink-internal.h"
#include "bus-polkit.h"
#include "format-util.h"
#include "hostname-util.h"
#include "json-util.h"
#include "machine-varlink.h"
#include "machined-varlink.h"
#include "mkdir.h"
#include "process-util.h"
#include "cgroup-util.h"
#include "socket-util.h"
#include "user-util.h"
#include "varlink-io.systemd.Machine.h"
@ -433,13 +437,12 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl
return r;
if (mn) {
if (!hostname_is_valid(mn, /* flags= */ VALID_HOSTNAME_DOT_HOST))
return sd_varlink_error_invalid_parameter_name(link, "name");
Machine *machine = hashmap_get(m->machines, mn);
if (!machine)
return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL);
Machine *machine = NULL;
r = lookup_machine_by_name(link, m, mn, &machine);
if (r != 0)
return r;
assert(machine);
return list_machine_one(link, machine, /* more= */ false);
}
@ -463,6 +466,110 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl
return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL);
}
static int vl_method_get(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
const char *mn = NULL;
const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, PTR_TO_SIZE(&mn), SD_JSON_MANDATORY },
{}
};
int r;
assert(parameters);
r = sd_varlink_dispatch(link, parameters, dispatch_table, 0);
if (r != 0)
return r;
Machine *machine = NULL;
r = lookup_machine_by_name(link, m, mn, &machine);
if (r != 0)
return r;
assert(machine);
return list_machine_one(link, machine, /* more= */ false);
}
static int vl_method_get_by_pid(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
int r;
pid_t pid = 0;
assert_cc(sizeof(pid_t) == sizeof(uint32_t));
assert(parameters);
const sd_json_dispatch_field dispatch_table[] = {
{ "pid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, PTR_TO_SIZE(&pid), SD_JSON_MANDATORY },
{}
};
r = sd_varlink_dispatch(link, parameters, dispatch_table, 0);
if (r != 0)
return r;
if (pid == 0) {
int pidfd = sd_varlink_get_peer_pidfd(link);
if (pidfd < 0)
return pidfd;
r = pidfd_get_pid(pidfd, &pid);
if (r < 0)
return varlink_log_errno(link, r, "Failed to acquire pid of peer: %m");
}
if (pid <= 0)
return sd_varlink_error_invalid_parameter_name(link, "pid");
Machine *machine = hashmap_get(m->machine_leaders, PID_TO_PTR(pid));
if (!machine) {
_cleanup_free_ char *unit = NULL;
r = cg_pid_get_unit(pid, &unit);
if (r >= 0)
machine = hashmap_get(m->machine_units, unit);
}
if (!machine)
return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL);
return list_machine_one(link, machine, /* more= */ false);
}
static int lookup_machine_and_call_method(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata, vl_method_handler_t method) {
Manager *manager = ASSERT_PTR(userdata);
const char *machine_name = NULL;
const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, PTR_TO_SIZE(&machine_name), SD_JSON_MANDATORY },
VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
int r;
assert(parameters);
r = sd_varlink_dispatch(link, parameters, dispatch_table, 0);
if (r != 0)
return r;
Machine *machine = NULL;
r = lookup_machine_by_name(link, manager, machine_name, &machine);
if (r != 0)
return r;
assert(machine);
return method(link, parameters, flags, machine);
}
static int vl_method_unregister(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return lookup_machine_and_call_method(link, parameters, flags, userdata, vl_method_unregister_internal);
}
static int vl_method_terminate(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return lookup_machine_and_call_method(link, parameters, flags, userdata, vl_method_terminate_internal);
}
static int manager_varlink_init_userdb(Manager *m) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
int r;
@ -525,8 +632,13 @@ static int manager_varlink_init_machine(Manager *m) {
r = sd_varlink_server_bind_method_many(
s,
"io.systemd.Machine.Register", vl_method_register,
"io.systemd.Machine.List", vl_method_list);
"io.systemd.Machine.Register", vl_method_register,
"io.systemd.Machine.Unregister", vl_method_unregister,
"io.systemd.Machine.Terminate", vl_method_terminate,
"io.systemd.Machine.Kill", vl_method_kill,
"io.systemd.Machine.List", vl_method_list,
"io.systemd.Machine.Get", vl_method_get,
"io.systemd.Machine.GetByPID", vl_method_get_by_pid);
if (r < 0)
return log_error_errno(r, "Failed to register varlink methods: %m");

View File

@ -28,30 +28,64 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_FIELD_COMMENT("Timestamp in µs in the CLOCK_MONOTONIC clock"),
SD_VARLINK_DEFINE_FIELD(monotonic, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
#define SD_VARLINK_MACHINE_OUTPUT_FIELDS \
SD_VARLINK_FIELD_COMMENT("Name of the machine"), \
SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), \
SD_VARLINK_FIELD_COMMENT("128bit ID identifying this machine, formatted in hexadecimal"), \
SD_VARLINK_DEFINE_OUTPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \
SD_VARLINK_FIELD_COMMENT("Name of the software that registered this machine"), \
SD_VARLINK_DEFINE_OUTPUT(service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \
SD_VARLINK_FIELD_COMMENT("The class of this machine"), \
SD_VARLINK_DEFINE_OUTPUT(class, SD_VARLINK_STRING, 0), \
SD_VARLINK_FIELD_COMMENT("Leader process PID of this machine"), \
SD_VARLINK_DEFINE_OUTPUT(leader, SD_VARLINK_INT, SD_VARLINK_NULLABLE), \
SD_VARLINK_FIELD_COMMENT("Root directory of this machine, if known, relative to host file system"), \
SD_VARLINK_DEFINE_OUTPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \
SD_VARLINK_FIELD_COMMENT("The service manager unit this machine resides in"), \
SD_VARLINK_DEFINE_OUTPUT(unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \
SD_VARLINK_FIELD_COMMENT("Timestamp when the machine was activated"), \
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(timestamp, Timestamp, SD_VARLINK_NULLABLE), \
SD_VARLINK_FIELD_COMMENT("AF_VSOCK CID of the machine if known and applicable"), \
SD_VARLINK_DEFINE_OUTPUT(vSockCid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), \
SD_VARLINK_FIELD_COMMENT("SSH address to connect to"), \
SD_VARLINK_DEFINE_OUTPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)
static SD_VARLINK_DEFINE_METHOD(
Unregister,
SD_VARLINK_FIELD_COMMENT("The name of a machine to unregister."),
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0));
static SD_VARLINK_DEFINE_METHOD(
Terminate,
SD_VARLINK_FIELD_COMMENT("The name of a machine to terminate."),
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0));
static SD_VARLINK_DEFINE_METHOD(
Kill,
SD_VARLINK_FIELD_COMMENT("The name of a machine to sends signal to"),
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("Who should receive the signal"),
SD_VARLINK_DEFINE_INPUT(who, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Signal to send"),
SD_VARLINK_DEFINE_INPUT(signal, SD_VARLINK_INT, 0));
static SD_VARLINK_DEFINE_METHOD(
List,
SD_VARLINK_FIELD_COMMENT("If non-null the name of a running machine to report details on. If null/unspecified enumerates all running machines."),
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Name of the machine"),
SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("128bit ID identifying this machine, formatted in hexadecimal"),
SD_VARLINK_DEFINE_OUTPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Name of the software that registered this machine"),
SD_VARLINK_DEFINE_OUTPUT(service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The class of this machine"),
SD_VARLINK_DEFINE_OUTPUT(class, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("Leader process PID of this machine"),
SD_VARLINK_DEFINE_OUTPUT(leader, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Root directory of this machine, if known, relative to host file system"),
SD_VARLINK_DEFINE_OUTPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The service manager unit this machine resides in"),
SD_VARLINK_DEFINE_OUTPUT(unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Timestamp when the machine was activated"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(timestamp, Timestamp, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("AF_VSOCK CID of the machine if known and applicable"),
SD_VARLINK_DEFINE_OUTPUT(vSockCid, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("SSH address to connect to"),
SD_VARLINK_DEFINE_OUTPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
SD_VARLINK_MACHINE_OUTPUT_FIELDS);
static SD_VARLINK_DEFINE_METHOD(
Get,
SD_VARLINK_FIELD_COMMENT("The name of a running machine to report details on."),
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0),
SD_VARLINK_MACHINE_OUTPUT_FIELDS);
static SD_VARLINK_DEFINE_METHOD(
GetByPID,
SD_VARLINK_FIELD_COMMENT("The PID of a running machine to report details on."),
SD_VARLINK_DEFINE_INPUT(pid, SD_VARLINK_INT, 0),
SD_VARLINK_MACHINE_OUTPUT_FIELDS);
static SD_VARLINK_DEFINE_ERROR(NoSuchMachine);
static SD_VARLINK_DEFINE_ERROR(MachineExists);
@ -62,8 +96,17 @@ SD_VARLINK_DEFINE_INTERFACE(
SD_VARLINK_SYMBOL_COMMENT("A timestamp object consisting of both CLOCK_REALTIME and CLOCK_MONOTONIC timestamps"),
&vl_type_Timestamp,
&vl_method_Register,
&vl_method_Unregister,
SD_VARLINK_SYMBOL_COMMENT("Terminate virtual machine, killing its processes"),
&vl_method_Terminate,
SD_VARLINK_SYMBOL_COMMENT("Send a UNIX signal to the machine's processes"),
&vl_method_Kill,
SD_VARLINK_SYMBOL_COMMENT("List running machines"),
&vl_method_List,
SD_VARLINK_SYMBOL_COMMENT("Get running machine"),
&vl_method_Get,
SD_VARLINK_SYMBOL_COMMENT("Get running machine by PID"),
&vl_method_GetByPID,
SD_VARLINK_SYMBOL_COMMENT("No matching machine currently running"),
&vl_error_NoSuchMachine,
&vl_error_MachineExists);

View File

@ -47,12 +47,21 @@ trap 'kill $PID' EXIT
# We need to wait for the sleep process asynchronously in order to allow
# bash to process signals
sleep infinity &
# notify that the process is ready
touch /ready
PID=$!
while :; do
wait || :
done
EOF
rm -f /var/lib/machines/long-running/ready
machinectl start long-running
# !!!! DO NOT REMOVE THIS TEST
# The test makes sure that the long-running's init script has enough time to start and registered signal traps
timeout 10 bash -c "until test -e /var/lib/machines/long-running/ready; do sleep .5; done"
machinectl
machinectl --no-pager --help
@ -222,6 +231,3 @@ done
(! machinectl read-only container1 "")
(! machinectl read-only container1 foo)
(! machinectl read-only container1 -- -1)
varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{}'
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name":".host"}'

View File

@ -0,0 +1,89 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
export PAGER=
at_exit() {
machinectl status long-running >/dev/null && machinectl kill --signal=KILL long-running
mountpoint -q /var/lib/machines && timeout 10 sh -c "until umount /var/lib/machines; do sleep .5; done"
}
trap at_exit EXIT
systemctl service-log-level systemd-machined debug
systemctl service-log-level systemd-importd debug
# Mount temporary directory over /var/lib/machines to not pollute the image
mkdir -p /var/lib/machines
mount --bind "$(mktemp --tmpdir=/var/tmp -d)" /var/lib/machines
# Create one "long running" container with some basic signal handling
create_dummy_container /var/lib/machines/long-running
cat >/var/lib/machines/long-running/sbin/init <<\EOF
#!/usr/bin/bash
PID=0
trap "touch /trap" TRAP
trap 'kill $PID' EXIT
# We need to wait for the sleep process asynchronously in order to allow
# bash to process signals
sleep infinity &
# notify that the process is ready
touch /ready
PID=$!
while :; do
wait || :
done
EOF
machine_start() {
machinectl status long-running >/dev/null && return 0 || true
rm -f /var/lib/machines/long-running/ready
# sometime `machinectl start` returns 1 and then do a success
machinectl start long-running || machinectl start long-running
# !!!! DO NOT REMOVE THIS TEST
# The test makes sure that the long-running's init script has enough time to start and registered signal traps
timeout 30 bash -c "until test -e /var/lib/machines/long-running/ready; do sleep .5; done"
}
machine_start
# test io.systemd.Machine.List
varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{}' | grep 'long-running'
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name":"long-running"}'
# test io.systemd.Machine.Get
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Get '{"name":"long-running"}'
# test io.systemd.Machine.GetByPID
pid=$(varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Get '{"name":"long-running"}' | jq .leader)
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Get '{"name":"long-running"}' > /tmp/expected
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.GetByPID '{"pid":'$pid'}' > /tmp/got
diff -u /tmp/expected /tmp/got
# test io.systemd.Machine.Kill
# sending TRAP signal
rm -f /var/lib/machines/long-running/trap
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Kill '{"name":"long-running", "who": "leader", "signal": 5}'
timeout 30 bash -c "until test -e /var/lib/machines/long-running/trap; do sleep .5; done"
# sending KILL signal
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Kill '{"name":"long-running", "signal": 9}'
timeout 30 bash -c "while varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Get '{\"name\":\"long-running\"}'; do sleep 0.5; done"
# test io.systemd.Machine.Terminate
machine_start
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Terminate '{"name":"long-running"}'
timeout 120 bash -c "while varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Get '{\"name\":\"long-running\"}'; do sleep 0.5; done"