From 84c01612de805d88875d4d91cfcf73cf10f99447 Mon Sep 17 00:00:00 2001 From: Michal Sekletar Date: Mon, 30 Oct 2023 12:08:59 +0100 Subject: [PATCH 1/2] core/manager: add dbus API to create auxiliary scope from running service This commit introduces new D-Bus API, StartAuxiliaryScope(). It may be used by services as part of the restart procedure. Service sends an array of PID file descriptors corresponding to processes that are part of the service and must continue running also after service restarts, i.e. they haven't finished the job why they were spawned in the first place (e.g. long running video transcoding job). Systemd creates new scope unit for these processes and migrates them into it. Cgroup properties of scope are copied from the service so it retains same cgroup settings and limits as service had. --- man/org.freedesktop.systemd1.xml | 15 ++ src/core/cgroup.c | 308 +++++++++++++++++++++++++++++++ src/core/cgroup.h | 14 ++ src/core/dbus-manager.c | 179 ++++++++++++++++++ 4 files changed, 516 insertions(+) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index f6d327dfd3e..1bdff502ca3 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -277,6 +277,11 @@ node /org/freedesktop/systemd1 { GetDynamicUsers(out a(us) users); DumpUnitFileDescriptorStore(in s name, out a(suuutuusu) entries); + StartAuxiliaryScope(in s name, + in ah pidfds, + in t flags, + in a(sv) properties, + out o job); signals: UnitNew(s id, o unit); @@ -990,6 +995,8 @@ node /org/freedesktop/systemd1 { + + @@ -1567,6 +1574,13 @@ node /org/freedesktop/systemd1 { file descriptors currently in the file descriptor store of the specified unit. This call is equivalent to DumpFileDescriptorStore() on the org.freedesktop.systemd1.Service. For further details, see below. + + StartAuxiliaryScope() creates a new scope unit from a service where calling + process resides. Set of processes that will be migrated to newly created scope is passed in as an array + of pidfds. This is useful for creating auxiliary scopes that should contain worker processes and their lifecycle + shouldn't be bound to a lifecycle of the service, e.g. they should continue running after the restart + of the service. Note that the main PID of the service can not be migrated to an auxiliary scope. + Also, flags argument must be 0 and is reserved for future extensions. @@ -11826,6 +11840,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ QueueSignalUnit(), SoftReboot(), and DumpUnitFileDescriptorStore() were added in version 254. + StartAuxiliaryScope() was added in version 256. Unit Objects diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 5e66ef76b55..311a4197aa1 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -33,6 +33,7 @@ #include "process-util.h" #include "procfs-util.h" #include "restrict-ifaces.h" +#include "set.h" #include "special.h" #include "stdio-util.h" #include "string-table.h" @@ -189,6 +190,313 @@ void cgroup_context_init(CGroupContext *c) { }; } +int cgroup_context_add_io_device_weight_dup(CGroupContext *c, CGroupIODeviceWeight *w) { + _cleanup_free_ CGroupIODeviceWeight *n = NULL; + + assert(c); + assert(w); + + n = new0(CGroupIODeviceWeight, 1); + if (!n) + return -ENOMEM; + + n->path = strdup(w->path); + if (!n->path) + return -ENOMEM; + n->weight = w->weight; + + LIST_PREPEND(device_weights, c->io_device_weights, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_io_device_limit_dup(CGroupContext *c, CGroupIODeviceLimit *l) { + _cleanup_free_ CGroupIODeviceLimit *n = NULL; + + assert(c); + assert(l); + + n = new0(CGroupIODeviceLimit, 1); + if (!l) + return -ENOMEM; + + n->path = strdup(l->path); + if (!n->path) + return -ENOMEM; + + for (CGroupIOLimitType type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) + n->limits[type] = l->limits[type]; + + LIST_PREPEND(device_limits, c->io_device_limits, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_io_device_latency_dup(CGroupContext *c, CGroupIODeviceLatency *l) { + _cleanup_free_ CGroupIODeviceLatency *n = NULL; + + assert(c); + assert(l); + + n = new0(CGroupIODeviceLatency, 1); + if (!n) + return -ENOMEM; + + n->path = strdup(l->path); + if (!n->path) + return -ENOMEM; + + n->target_usec = l->target_usec; + + LIST_PREPEND(device_latencies, c->io_device_latencies, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, CGroupBlockIODeviceWeight *w) { + _cleanup_free_ CGroupBlockIODeviceWeight *n = NULL; + + assert(c); + assert(w); + + n = new0(CGroupBlockIODeviceWeight, 1); + if (!n) + return -ENOMEM; + + n->path = strdup(w->path); + if (!n->path) + return -ENOMEM; + + n->weight = w->weight; + + LIST_PREPEND(device_weights, c->blockio_device_weights, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) { + _cleanup_free_ CGroupBlockIODeviceBandwidth *n = NULL; + + assert(c); + assert(b); + + n = new0(CGroupBlockIODeviceBandwidth, 1); + if (!n) + return -ENOMEM; + + *n = (CGroupBlockIODeviceBandwidth) { + .rbps = b->rbps, + .wbps = b->wbps, + }; + + LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_device_allow_dup(CGroupContext *c, CGroupDeviceAllow *a) { + _cleanup_free_ CGroupDeviceAllow *n = NULL; + + assert(c); + assert(a); + + n = new0(CGroupDeviceAllow, 1); + if (!n) + return -ENOMEM; + + n->path = strdup(a->path); + if (!n->path) + return -ENOMEM; + + n->permissions = a->permissions; + + LIST_PREPEND(device_allow, c->device_allow, TAKE_PTR(n)); + return 0; +} + +static int cgroup_context_add_socket_bind_item_dup(CGroupContext *c, CGroupSocketBindItem *i, CGroupSocketBindItem *h) { + _cleanup_free_ CGroupSocketBindItem *n = NULL; + + assert(c); + assert(i); + + n = new0(CGroupSocketBindItem, 1); + if (!n) + return -ENOMEM; + + *n = (CGroupSocketBindItem) { + .address_family = i->address_family, + .ip_protocol = i->ip_protocol, + .nr_ports = i->nr_ports, + .port_min = i->port_min, + }; + + LIST_PREPEND(socket_bind_items, h, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, CGroupSocketBindItem *i) { + return cgroup_context_add_socket_bind_item_dup(c, i, c->socket_bind_allow); +} + +int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, CGroupSocketBindItem *i) { + return cgroup_context_add_socket_bind_item_dup(c, i, c->socket_bind_deny); +} + +int cgroup_context_copy(CGroupContext *dst, const CGroupContext *src) { + struct in_addr_prefix *i; + char *iface; + int r; + + assert(src); + assert(dst); + + dst->cpu_accounting = src->cpu_accounting; + dst->io_accounting = src->io_accounting; + dst->blockio_accounting = src->blockio_accounting; + dst->memory_accounting = src->memory_accounting; + dst->tasks_accounting = src->tasks_accounting; + dst->ip_accounting = src->ip_accounting; + + dst->memory_oom_group = dst->memory_oom_group; + + dst->cpu_weight = src->cpu_weight; + dst->startup_cpu_weight = src->startup_cpu_weight; + dst->cpu_quota_per_sec_usec = src->cpu_quota_per_sec_usec; + dst->cpu_quota_period_usec = src->cpu_quota_period_usec; + + dst->cpuset_cpus = src->cpuset_cpus; + dst->startup_cpuset_cpus = src->startup_cpuset_cpus; + dst->cpuset_mems = src->cpuset_mems; + dst->startup_cpuset_mems = src->startup_cpuset_mems; + + dst->io_weight = src->io_weight; + dst->startup_io_weight = src->startup_io_weight; + + LIST_FOREACH_BACKWARDS(device_weights, w, LIST_FIND_TAIL(device_weights, src->io_device_weights)) { + r = cgroup_context_add_io_device_weight_dup(dst, w); + if (r < 0) + return r; + } + + LIST_FOREACH_BACKWARDS(device_limits, l, LIST_FIND_TAIL(device_limits, src->io_device_limits)) { + r = cgroup_context_add_io_device_limit_dup(dst, l); + if (r < 0) + return r; + } + + LIST_FOREACH_BACKWARDS(device_latencies, l, LIST_FIND_TAIL(device_latencies, src->io_device_latencies)) { + r = cgroup_context_add_io_device_latency_dup(dst, l); + if (r < 0) + return r; + } + + dst->default_memory_min = src->default_memory_min; + dst->default_memory_low = src->default_memory_low; + dst->default_startup_memory_low = src->default_startup_memory_low; + dst->memory_min = src->memory_min; + dst->memory_low = src->memory_low; + dst->startup_memory_low = src->startup_memory_low; + dst->memory_high = src->memory_high; + dst->startup_memory_high = src->startup_memory_high; + dst->memory_max = src->memory_max; + dst->startup_memory_max = src->startup_memory_max; + dst->memory_swap_max = src->memory_swap_max; + dst->startup_memory_swap_max = src->startup_memory_swap_max; + dst->memory_zswap_max = src->memory_zswap_max; + dst->startup_memory_zswap_max = src->startup_memory_zswap_max; + + dst->default_memory_min_set = src->default_memory_min_set; + dst->default_memory_low_set = src->default_memory_low_set; + dst->default_startup_memory_low_set = src->default_startup_memory_low_set; + dst->memory_min_set = src->memory_min_set; + dst->memory_low_set = src->memory_low_set; + dst->startup_memory_low_set = src->startup_memory_low_set; + dst->startup_memory_high_set = src->startup_memory_high_set; + dst->startup_memory_max_set = src->startup_memory_max_set; + dst->startup_memory_swap_max_set = src->startup_memory_swap_max_set; + dst->startup_memory_zswap_max_set = src->startup_memory_zswap_max_set; + + SET_FOREACH(i, src->ip_address_allow) { + r = in_addr_prefix_add(&dst->ip_address_allow, i); + if (r < 0) + return r; + } + + SET_FOREACH(i, src->ip_address_deny) { + r = in_addr_prefix_add(&dst->ip_address_deny, i); + if (r < 0) + return r; + } + + dst->ip_address_allow_reduced = src->ip_address_allow_reduced; + dst->ip_address_deny_reduced = src->ip_address_deny_reduced; + + if (!strv_isempty(src->ip_filters_ingress)) { + dst->ip_filters_ingress = strv_copy(src->ip_filters_ingress); + if (!dst->ip_filters_ingress) + return -ENOMEM; + } + + if (!strv_isempty(src->ip_filters_egress)) { + dst->ip_filters_egress = strv_copy(src->ip_filters_egress); + if (!dst->ip_filters_egress) + return -ENOMEM; + } + + LIST_FOREACH_BACKWARDS(programs, l, LIST_FIND_TAIL(programs, src->bpf_foreign_programs)) { + r = cgroup_context_add_bpf_foreign_program_dup(dst, l); + if (r < 0) + return r; + } + + SET_FOREACH(iface, src->restrict_network_interfaces) { + r = set_put_strdup(&dst->restrict_network_interfaces, iface); + if (r < 0) + return r; + } + dst->restrict_network_interfaces_is_allow_list = src->restrict_network_interfaces_is_allow_list; + + dst->cpu_shares = src->cpu_shares; + dst->startup_cpu_shares = src->startup_cpu_shares; + + dst->blockio_weight = src->blockio_weight; + dst->startup_blockio_weight = src->startup_blockio_weight; + + LIST_FOREACH_BACKWARDS(device_weights, l, LIST_FIND_TAIL(device_weights, src->blockio_device_weights)) { + r = cgroup_context_add_block_io_device_weight_dup(dst, l); + if (r < 0) + return r; + } + + LIST_FOREACH_BACKWARDS(device_bandwidths, l, LIST_FIND_TAIL(device_bandwidths, src->blockio_device_bandwidths)) { + r = cgroup_context_add_block_io_device_bandwidth_dup(dst, l); + if (r < 0) + return r; + } + + dst->memory_limit = src->memory_limit; + + dst->device_policy = src->device_policy; + LIST_FOREACH_BACKWARDS(device_allow, l, LIST_FIND_TAIL(device_allow, src->device_allow)) { + r = cgroup_context_add_device_allow_dup(dst, l); + if (r < 0) + return r; + } + + LIST_FOREACH_BACKWARDS(socket_bind_items, l, LIST_FIND_TAIL(socket_bind_items, src->socket_bind_allow)) { + r = cgroup_context_add_socket_bind_item_allow_dup(dst, l); + if (r < 0) + return r; + + } + + LIST_FOREACH_BACKWARDS(socket_bind_items, l, LIST_FIND_TAIL(socket_bind_items, src->socket_bind_deny)) { + r = cgroup_context_add_socket_bind_item_deny_dup(dst, l); + if (r < 0) + return r; + } + + dst->tasks_max = src->tasks_max; + + return 0; +} + void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) { assert(c); assert(a); diff --git a/src/core/cgroup.h b/src/core/cgroup.h index f1b674b4b74..ecd6759983f 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -129,6 +129,7 @@ typedef enum CGroupPressureWatch { _CGROUP_PRESSURE_WATCH_INVALID = -EINVAL, } CGroupPressureWatch; +/* When adding members make sure to update cgroup_context_copy() accordingly */ struct CGroupContext { bool cpu_accounting; bool io_accounting; @@ -285,6 +286,7 @@ uint64_t cgroup_context_cpu_weight(CGroupContext *c, ManagerState state); usec_t cgroup_cpu_adjust_period(usec_t period, usec_t quota, usec_t resolution, usec_t max_period); void cgroup_context_init(CGroupContext *c); +int cgroup_context_copy(CGroupContext *dst, const CGroupContext *src); void cgroup_context_done(CGroupContext *c); void cgroup_context_dump(Unit *u, FILE* f, const char *prefix); void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f); @@ -309,6 +311,18 @@ static inline bool cgroup_context_want_memory_pressure(const CGroupContext *c) { int cgroup_context_add_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p); int cgroup_context_add_or_update_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p); int cgroup_context_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *path); +int cgroup_context_add_io_device_limit_dup(CGroupContext *c, CGroupIODeviceLimit *l); +int cgroup_context_add_io_device_weight_dup(CGroupContext *c, CGroupIODeviceWeight *w); +int cgroup_context_add_io_device_latency_dup(CGroupContext *c, CGroupIODeviceLatency *l); +int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, CGroupBlockIODeviceWeight *w); +int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, CGroupBlockIODeviceBandwidth *b); +int cgroup_context_add_device_allow_dup(CGroupContext *c, CGroupDeviceAllow *a); +int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, CGroupSocketBindItem *i); +int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, CGroupSocketBindItem *i); + +static inline int cgroup_context_add_bpf_foreign_program_dup(CGroupContext *c, CGroupBPFForeignProgram *p) { + return cgroup_context_add_bpf_foreign_program(c, p->attach_type, p->bpffs_path); +} void unit_modify_nft_set(Unit *u, bool add); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 745f5cc17c6..a62133a4c21 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -2933,6 +2933,180 @@ static int method_dump_unit_descriptor_store(sd_bus_message *message, void *user return method_generic_unit_operation(message, userdata, error, bus_service_method_dump_file_descriptor_store, 0); } +static int aux_scope_from_message(Manager *m, sd_bus_message *message, Unit **ret_scope, sd_bus_error *error) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + _cleanup_free_ PidRef *pidrefs = NULL; + const char *name; + Unit *from, *scope; + PidRef *main_pid; + CGroupContext *cc; + size_t n_pids = 0; + uint64_t flags; + pid_t pid; + int r; + + assert(ret_scope); + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + from = manager_get_unit_by_pid(m, pid); + if (!from) + return sd_bus_error_set(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); + + if (!IN_SET(from->type, UNIT_SERVICE, UNIT_SCOPE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Starting auxiliary scope is supported only for service and scope units, refusing."); + + if (!unit_name_is_valid(from->id, UNIT_NAME_PLAIN)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Auxiliary scope can be started only for non-template service units and scope units, refusing."); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!unit_name_is_valid(name, UNIT_NAME_PLAIN)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid name \"%s\" for auxiliary scope.", name); + + if (unit_name_to_type(name) != UNIT_SCOPE) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Name \"%s\" of auxiliary scope doesn't have .scope suffix.", name); + + main_pid = unit_main_pid(from); + + r = sd_bus_message_enter_container(message, 'a', "h"); + if (r < 0) + return r; + + for (;;) { + _cleanup_(pidref_done) PidRef p = PIDREF_NULL; + Unit *unit; + int fd; + + r = sd_bus_message_read(message, "h", &fd); + if (r < 0) + return r; + if (r == 0) + break; + + r = pidref_set_pidfd(&p, fd); + if (r < 0) { + log_unit_warning_errno(from, r, "Failed to create process reference from PIDFD, ignoring: %m"); + continue; + } + + unit = manager_get_unit_by_pidref(m, &p); + if (!unit) { + log_unit_warning_errno(from, SYNTHETIC_ERRNO(ENOENT), "Failed to get unit from PIDFD, ingoring: %m"); + continue; + } + + if (!streq(unit->id, from->id)) { + log_unit_warning(from, "PID " PID_FMT " is not running in the same service as the calling process, ignoring.", p.pid); + continue; + } + + if (pidref_equal(main_pid, &p)) { + log_unit_warning(from, "Main PID cannot be migrated into auxiliary scope, ignoring."); + continue; + } + + if (!GREEDY_REALLOC(pidrefs, n_pids+1)) + return -ENOMEM; + + pidrefs[n_pids++] = TAKE_PIDREF(p); + } + + if (n_pids == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No processes can be migrated to auxiliary scope."); + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "t", &flags); + if (r < 0) + return r; + + if (flags != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero."); + + r = manager_load_unit(m, name, NULL, error, &scope); + if (r < 0) + return r; + + if (!unit_is_pristine(scope)) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, + "Unit %s was already loaded or has a fragment file.", name); + + r = unit_set_slice(scope, UNIT_GET_SLICE(from)); + if (r < 0) + return r; + + cc = unit_get_cgroup_context(scope); + + r = cgroup_context_copy(cc, unit_get_cgroup_context(from)); + if (r < 0) + return r; + + r = unit_make_transient(scope); + if (r < 0) + return r; + + r = bus_unit_set_properties(scope, message, UNIT_RUNTIME, true, error); + if (r < 0) + return r; + + FOREACH_ARRAY(p, pidrefs, n_pids) { + r = unit_pid_attachable(scope, p, error); + if (r < 0) + return r; + + r = unit_watch_pidref(scope, p, /* exclusive= */ false); + if (r < 0 && r != -EEXIST) + return r; + } + + /* Now load the missing bits of the unit we just created */ + unit_add_to_load_queue(scope); + manager_dispatch_load_queue(m); + + *ret_scope = TAKE_PTR(scope); + + return 1; +} + +static int method_start_aux_scope(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = ASSERT_PTR(userdata); + Unit *u = NULL; /* avoid false maybe-uninitialized warning */ + int r; + + assert(message); + + r = mac_selinux_access_check(message, "start", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = aux_scope_from_message(m, message, &u, error); + if (r < 0) + return r; + + return bus_unit_queue_job(message, u, JOB_START, JOB_REPLACE, 0, error); +} + const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_VTABLE_START(0), @@ -3491,6 +3665,11 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_RESULT("a(suuutuusu)", entries), method_dump_unit_descriptor_store, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("StartAuxiliaryScope", + SD_BUS_ARGS("s", name, "ah", pidfds, "t", flags, "a(sv)", properties), + SD_BUS_RESULT("o", job), + method_start_aux_scope, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL_WITH_ARGS("UnitNew", SD_BUS_ARGS("s", id, "o", unit), From fd7fd59b6dc81a0a0e3f5e40e085c51c16fa7eec Mon Sep 17 00:00:00 2001 From: Michal Sekletar Date: Mon, 30 Oct 2023 12:12:28 +0100 Subject: [PATCH 2/2] tests: add test for StartAuxiliaryScope() --- src/test/meson.build | 4 + src/test/test-aux-scope.c | 160 +++++++++++++++++++++++++++ test/units/testsuite-07.aux-scope.sh | 34 ++++++ 3 files changed, 198 insertions(+) create mode 100644 src/test/test-aux-scope.c create mode 100755 test/units/testsuite-07.aux-scope.sh diff --git a/src/test/meson.build b/src/test/meson.build index aec125d483a..1230bafab98 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -600,4 +600,8 @@ executables += [ libudev_basic, ], }, + test_template + { + 'sources' : files('test-aux-scope.c'), + 'type' : 'manual', + }, ] diff --git a/src/test/test-aux-scope.c b/src/test/test-aux-scope.c new file mode 100644 index 00000000000..175757b1c18 --- /dev/null +++ b/src/test/test-aux-scope.c @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "sd-event.h" + +#include "bus-error.h" +#include "bus-message.h" +#include "bus-wait-for-jobs.h" +#include "fd-util.h" +#include "log.h" +#include "missing_syscall.h" +#include "process-util.h" +#include "tests.h" + +static int on_sigusr1(sd_event_source *s, const struct signalfd_siginfo *ssi, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + PidRef *pids = (PidRef *) userdata; + const char *job; + int r; + + assert(pids); + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to acquire bus: %m"); + + r = sd_bus_message_new_method_call(bus, &message, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartAuxiliaryScope"); + if (r < 0) + return log_error_errno(r, "Failed to create bus message: %m"); + + r = sd_bus_message_append_basic(message, 's', "test-aux-scope.scope"); + if (r < 0) + return log_error_errno(r, "Failed to attach scope name: %m"); + + r = sd_bus_message_open_container(message, 'a', "h"); + if (r < 0) + return log_error_errno(r, "Failed to create array of FDs: %m"); + + for (size_t i = 0; i < 10; i++) { + r = sd_bus_message_append_basic(message, 'h', &pids[i].fd); + if (r < 0) + return log_error_errno(r, "Failed to append PIDFD to message: %m"); + } + + r = sd_bus_message_close_container(message); + if (r < 0) + return log_error_errno(r, "Failed to close container: %m"); + + r = sd_bus_message_append(message, "ta(sv)", UINT64_C(0), 1, "Description", "s", "Test auxiliary scope"); + if (r < 0) + return log_error_errno(r, "Failed to append unit properties: %m"); + + r = sd_bus_call(bus, message, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start auxiliary scope: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &job); + if (r < 0) + return log_error_errno(r, "Failed to read reply: %m"); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + + r = bus_wait_for_jobs_one(w, job, false, NULL); + if (r < 0) + return r; + + return 0; +} + +static void destroy_pidrefs(PidRef *pids, size_t npids) { + assert(pids || npids == 0); + + for (size_t i = 0; i < npids; i++) + pidref_done(&pids[i]); + + free(pids); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + PidRef *pids = NULL; + size_t npids = 0; + int r, fd; + + CLEANUP_ARRAY(pids, npids, destroy_pidrefs); + + test_setup_logging(LOG_INFO); + + fd = pidfd_open(getpid_cached(), 0); + if (fd < 0 && (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno))) + return log_tests_skipped("pidfds are not available"); + else if (fd < 0) { + log_error_errno(errno, "pidfd_open() failed: %m"); + return EXIT_FAILURE; + } + safe_close(fd); + + r = sd_event_new(&event); + if (r < 0) { + log_error_errno(r, "Failed to allocate event loop: %m"); + return EXIT_FAILURE; + } + + npids = 10; + pids = new0(PidRef, npids); + assert(pids); + + r = sd_event_add_signal(event, NULL, SIGUSR1|SD_EVENT_SIGNAL_PROCMASK, on_sigusr1, pids); + if (r < 0) { + log_error_errno(r, "Failed to setup SIGUSR1 signal handling: %m"); + return EXIT_FAILURE; + } + + for (size_t i = 0; i < npids; i++) { + PidRef pidref = PIDREF_NULL; + pid_t pid; + + r = safe_fork("(worker)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS, &pid); + if (r < 0) { + log_error_errno(r, "Failed to fork(): %m"); + return EXIT_FAILURE; + } + + if (r == 0) { + /* Worker */ + sleep(3600); + _exit(EXIT_SUCCESS); + } + + r = pidref_set_pid(&pidref, pid); + if (r < 0) { + log_error_errno(r, "Failed to initialize PID ref: %m"); + return EXIT_FAILURE; + } + + assert_se(pidref.pid == pid); + assert_se(pidref.fd != -EBADF); + + pids[i] = TAKE_PIDREF(pidref); + } + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return 0; +} diff --git a/test/units/testsuite-07.aux-scope.sh b/test/units/testsuite-07.aux-scope.sh new file mode 100755 index 00000000000..4e46e1bb7e3 --- /dev/null +++ b/test/units/testsuite-07.aux-scope.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +export SYSTEMD_PAGER=cat + +if ! grep -q pidfd_open /proc/kallsyms; then + echo "pidfds not available, skipping the test..." + exit 0 +fi + +systemd-run --unit test-aux-scope.service \ + -p Slice=aux.slice -p Type=exec -p TasksMax=99 -p CPUWeight=199 -p IPAccounting=yes \ + /usr/lib/systemd/tests/unit-tests/manual/test-aux-scope +kill -s USR1 "$(systemctl show --value --property MainPID test-aux-scope.service)" + +sleep 1 + +systemctl status test-aux-scope.service +# shellcheck disable=SC2009 +test "$(ps -eo pid,unit | grep -c test-aux-scope.service)" = 1 + +systemctl status test-aux-scope.scope +# shellcheck disable=SC2009 +test "$(ps -eo pid,unit | grep -c test-aux-scope.scope)" = 10 + +test "$(systemctl show -p Slice --value test-aux-scope.scope)" = aux.slice +test "$(systemctl show -p TasksMax --value test-aux-scope.scope)" = 99 +test "$(systemctl show -p CPUWeight --value test-aux-scope.scope)" = 199 +test "$(systemctl show -p IPAccounting --value test-aux-scope.scope)" = yes + +systemctl stop test-aux-scope.scope +systemctl stop test-aux-scope.service