]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add `io.systemd.Manager.{PowerOff,Reboot,SoftReboot,Halt,Kexec}`
authorMichael Vogt <michael@amutable.com>
Fri, 20 Mar 2026 15:25:42 +0000 (16:25 +0100)
committerMichael Vogt <michael@amutable.com>
Sat, 21 Mar 2026 21:01:17 +0000 (22:01 +0100)
This adds the low-level io.systemd.Manager shutdown support. This
is (much) simpler than the logind one. It mimics dbus but uses
a shared helper for the simple cases.

Note that this is more restrictive than the dbus version. The
dbus version uses SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT) but
the varlink version uses varlink_check_privileged_peer(link).
This is mostly because I'm not sure how to do the equivalent
in a race-free way.

Thanks to Daan for suggesting this.

src/core/dbus-manager.c
src/core/varlink-manager.c
src/core/varlink-manager.h
src/core/varlink.c
src/shared/varlink-io.systemd.Manager.c

index 5e02d189072e2d7d244df4d2d7db399592f49a23..fec53341caecf30e1c276cf59c700ca9b268b6c9 100644 (file)
@@ -1690,6 +1690,7 @@ static int method_soft_reboot(sd_bus_message *message, void *userdata, sd_bus_er
                 return sd_bus_error_set(reterr_error, SD_BUS_ERROR_NOT_SUPPORTED,
                                         "Soft reboot is only supported by system manager.");
 
+        /* Keep the checks in sync with varlink-manager.c:vl_method_soft_reboot_manager() */
         r = mac_selinux_access_check(message, "reboot", reterr_error);
         if (r < 0)
                 return r;
index d00f7e5a248a7de17fa22924b0cb5447acf445ea..53db0a5a2e6fddd68055ee1ad40b1e4663ad9c85 100644 (file)
@@ -16,6 +16,7 @@
 #include "glyph-util.h"
 #include "json-util.h"
 #include "manager.h"
+#include "path-util.h"
 #include "pidref.h"
 #include "selinux-access.h"
 #include "set.h"
@@ -398,3 +399,84 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par
 
         return ret;
 }
+
+static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameters, ManagerObjective objective, const char *selinux_permission, bool can_do_root) {
+        Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link));
+        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+        _cleanup_free_ char *rt = NULL;
+        const char *root = NULL;
+        int r;
+
+        assert(link);
+        assert(parameters);
+
+        if (!MANAGER_IS_SYSTEM(m))
+                return sd_varlink_error(link, SD_VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, NULL);
+
+        if (can_do_root) {
+                static const sd_json_dispatch_field dispatch_table[] = {
+                        { "root", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
+                        {}
+                };
+
+                r = sd_varlink_dispatch(link, parameters, dispatch_table, &root);
+        } else
+                r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL);
+        if (r != 0)
+                return r;
+
+        r = mac_selinux_access_check_varlink(link, selinux_permission);
+        if (r < 0)
+                return r;
+
+        /* dbus uses SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT) in its checking. We cannot do the same
+         * because reading capabilities from /proc is racy (TOCTOU). So we use the stricter check
+         * TODO: figure out a way to check for CAP_SYS_BOOT */
+        r = varlink_check_privileged_peer(link);
+        if (r < 0)
+                return r;
+
+        if (!isempty(root)) {
+                if (!path_is_valid(root))
+                        return sd_varlink_error_invalid_parameter_name(link, "root");
+                if (!path_is_absolute(root))
+                        return sd_varlink_error_invalid_parameter_name(link, "root");
+
+                r = path_simplify_alloc(root, &rt);
+                if (r < 0)
+                        return r;
+        }
+
+        /* We need at least the pidref, otherwise there's nothing to log about. */
+        r = varlink_get_peer_pidref(link, &pidref);
+        if (r < 0)
+                log_debug_errno(r, "Failed to get peer pidref, ignoring: %m");
+        else
+                manager_log_caller(m, &pidref, manager_objective_to_string(objective));
+
+        if (can_do_root)
+                free_and_replace(m->switch_root, rt);
+        m->objective = objective;
+
+        return sd_varlink_reply(link, NULL);
+}
+
+int vl_method_poweroff_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return manager_do_set_objective(link, parameters, MANAGER_POWEROFF, "halt", /* can_do_root= */ false);
+}
+
+int vl_method_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return manager_do_set_objective(link, parameters, MANAGER_REBOOT, "reboot", /* can_do_root= */ false);
+}
+
+int vl_method_halt_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return manager_do_set_objective(link, parameters, MANAGER_HALT, "halt", /* can_do_root= */ false);
+}
+
+int vl_method_kexec_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return manager_do_set_objective(link, parameters, MANAGER_KEXEC, "reboot", /* can_do_root= */ false);
+}
+
+int vl_method_soft_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return manager_do_set_objective(link, parameters, MANAGER_SOFT_REBOOT, "reboot", /* can_do_root= */ true);
+}
index e5111eb58dc7a381806f3bda91c27d255ac1ceab..0e477e761b0d92cf3cd6625b75775ba27ef3d007 100644 (file)
@@ -9,3 +9,8 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd
 int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
 int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
 int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int vl_method_poweroff_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int vl_method_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int vl_method_halt_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int vl_method_kexec_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int vl_method_soft_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
index ec4f8abad95ae8da300e719f3bf6342d04462ef6..77b2d3bd8299761917962f93f2f3fbbff956a773 100644 (file)
@@ -390,6 +390,11 @@ int manager_setup_varlink_server(Manager *m) {
                         "io.systemd.Manager.Reexecute", vl_method_reexecute_manager,
                         "io.systemd.Manager.Reload", vl_method_reload_manager,
                         "io.systemd.Manager.EnqueueMarkedJobs", vl_method_enqueue_marked_jobs_manager,
+                        "io.systemd.Manager.PowerOff", vl_method_poweroff_manager,
+                        "io.systemd.Manager.Reboot", vl_method_reboot_manager,
+                        "io.systemd.Manager.Halt", vl_method_halt_manager,
+                        "io.systemd.Manager.KExec", vl_method_kexec_manager,
+                        "io.systemd.Manager.SoftReboot", vl_method_soft_reboot_manager,
                         "io.systemd.Unit.List", vl_method_list_units,
                         "io.systemd.Unit.SetProperties", vl_method_set_unit_properties,
                         "io.systemd.service.Ping", varlink_method_ping,
index cb304f229502964ab17721b0c52d767e759b3c5b..f33cab34b3de9148ff1ed8dcac7c1e9ba51ba07d 100644 (file)
@@ -193,6 +193,15 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
                 SD_VARLINK_FIELD_COMMENT("Job enqueue error message (on failure)"),
                 SD_VARLINK_DEFINE_OUTPUT(errorMessage, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
 
+static SD_VARLINK_DEFINE_METHOD(PowerOff);
+static SD_VARLINK_DEFINE_METHOD(Reboot);
+static SD_VARLINK_DEFINE_METHOD(Halt);
+static SD_VARLINK_DEFINE_METHOD(KExec);
+static SD_VARLINK_DEFINE_METHOD(
+                SoftReboot,
+                SD_VARLINK_FIELD_COMMENT("New root directory for the soft reboot"),
+                SD_VARLINK_DEFINE_INPUT(root, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
+
 static SD_VARLINK_DEFINE_ERROR(RateLimitReached);
 
 SD_VARLINK_DEFINE_INTERFACE(
@@ -205,6 +214,16 @@ SD_VARLINK_DEFINE_INTERFACE(
                 &vl_method_Reload,
                 SD_VARLINK_SYMBOL_COMMENT("Enqueue all marked jobs"),
                 &vl_method_EnqueueMarkedJobs,
+                SD_VARLINK_SYMBOL_COMMENT("Power off the system"),
+                &vl_method_PowerOff,
+                SD_VARLINK_SYMBOL_COMMENT("Reboot the system"),
+                &vl_method_Reboot,
+                SD_VARLINK_SYMBOL_COMMENT("Halt the system"),
+                &vl_method_Halt,
+                SD_VARLINK_SYMBOL_COMMENT("Reboot the system via kexec"),
+                &vl_method_KExec,
+                SD_VARLINK_SYMBOL_COMMENT("Soft-reboot the userspace"),
+                &vl_method_SoftReboot,
                 &vl_error_RateLimitReached,
                 &vl_type_ManagerContext,
                 &vl_type_ManagerRuntime,