SYSTEMD-SO...T.SERVICE(8)
๐ฆ systemd-soft-reboot.service โ userspace reboot operation ๐ฆ
okay bestie, welcome to the systemd-soft-reboot.service manual, section 8. it's giving "reboot but make it a vibe" and honestly? obsessed. ๐
๐ NAME ๐
systemd-soft-reboot.service โ Userspace reboot operation โจ
๐ธ SYNOPSIS ๐ธ
systemd-soft-reboot.service
โจ DESCRIPTION โจ
so like. systemd-soft-reboot.service is a system service that soft-reboot.target pulls in (it's the one doing the dragging, no cap), and its whole job is performing a userspace-only reboot operation. when it gets invoked, here's the sequence, bestie, follow along:
it sends the SIGTERM signal to any processes still left running (but โ important โ it does not wait for those processes to exit, she's not waiting around for anybody ๐), and follows up with SIGKILL. then, if the /run/nextroot/ directory exists โ and that could be a regular directory, a directory mount point, or a symlink to either one โ it switches the file system root over to it. after that it reexecutes the service manager off the (possibly now brand-new) root file system, which will enqueue a new boot transaction, exactly like a normal reboot would. periodt.
so why is this iconic? because this kind of userspace-only reboot lets you update or reset the entirety of userspace with minimal downtime. the way that it just... skips the boring parts? ๐ญ it does not transition through:
- The second phase of regular shutdown, as implemented by
systemd-shutdown(8). - The third phase of regular shutdown, i.e. the return to the initrd context.
- The hardware reboot operation.
- The firmware initialization.
- The boot loader initialization.
- The kernel initialization.
- The initrd initialization.
BUT โ and this is the part where we keep it real โ this form of reboot comes with drawbacks too:
- The OS update remains incomplete, because the kernel is not reset and just keeps running.
- Kernel settings (things like
/proc/sys/settings, a.k.a. "sysctl", or/sys/settings) are not reset.
these limitations may be addressed by various means, but those are outside the scope of this documentation. think kernel live-patching, and sufficiently comprehensive /etc/sysctl.d/ files. ๐
๐ฅ RESOURCE PASS-THROUGH ๐ฅ
okay this is the part where stuff survives the transition, and it's lowkey the coolest bit. various runtime OS resources can be passed from one system runtime to the next through the userspace reboot operation. specifically:
- File descriptors placed in the file descriptor store of services that remain active until the very end get passed to the next boot, where they land in the file descriptor store of the same unit. for this to work, units must declare
DefaultDependencies=no(and avoid a manualConflicts=shutdown.targetor anything like it) so they don't get terminated the usual way during the system shutdown operation. alternatively, useFileDescriptorStorePreserve=to let the file descriptor store remain pinned even when the unit is down. seesystemd.service(5)for all the details about the file descriptor store. ๐ซถ - Similar energy: file descriptors associated with
.socketunits stay open (and connectible!) as long as those units aren't stopped during the transition. (this is achieved byDefaultDependencies=no.) - The
/run/file system remains mounted and populated, and you can absolutely use it to pass state information between these userspace reboot cycles. ๐ - Service processes may keep running over the transition โ past soft-reboot and straight into the next session โ if they're placed in services that remain active until the very end of shutdown (again:
DefaultDependencies=no). they also have to be set up so they don't get killed by the aforementionedSIGTERMandSIGKILL, which you do withSurviveFinalKillSignal=yes, and they need to be configured to avoid being stopped on isolate viaIgnoreOnIsolate=yes. on top of that they have to be configured to be stopped on normal shutdown, reboot and maintenance mode. and finally, they have to be ordered afterbasic.targetso ordering on boot stays correct. note that if any new or custom units are used to isolate to, or that implement an equivalent shutdown functionality, they will also have to be configured manually for correct ordering and conflicting. for example:
[Unit]
Description=My Surviving Service
SurviveFinalKillSignal=yes
IgnoreOnIsolate=yes
DefaultDependencies=no
After=basic.target
Conflicts=reboot.target kexec.target poweroff.target halt.target rescue.target emergency.target
Before=shutdown.target rescue.target emergency.target
[Service]
Type=oneshot
ExecStart=sleep infinity
- On top of all that, templated units also need a configuration file for their slice, because by default they use a slice named after the non-templated part of the unit. for example, for a
foo@test.serviceinstance, asystem-foo.sliceunit may be added with the following content:
[Unit]
SurviveFinalKillSignal=yes
IgnoreOnIsolate=yes
DefaultDependencies=no
- File system mounts may remain mounted during the transition, and complex storage stays attached, if they're configured to remain until the very end of the shutdown process. (also achieved via
DefaultDependencies=no, and by avoidingConflicts=umount.target.) - If the unit publishes a service over D-Bus, the connection needs to be re-established after soft-reboot, because the D-Bus broker will be stopped and then started again. when you're using the
sd-bus(3)library, this can be achieved by adapting the following example. โจ
/* SPDX-License-Identifier: MIT-0 */
/* A D-Bus service that automatically reconnects when the system bus is
* restarted.
*
* Compile with 'cc sd_bus_service_reconnect.c $(pkg-config --libs --cflags libsystemd)'
*
* To allow the program to take ownership of the name 'org.freedesktop.ReconnectExample',
* add the following as /etc/dbus-1/system.d/org.freedesktop.ReconnectExample.conf
* and then reload the broker with 'systemctl reload dbus':
<?xml version="1.0"?> <!--*-nxml-*-->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="org.freedesktop.ReconnectExample"/>
<allow send_destination="org.freedesktop.ReconnectExample"/>
<allow receive_sender="org.freedesktop.ReconnectExample"/>
</policy>
<policy context="default">
<allow send_destination="org.freedesktop.ReconnectExample"/>
<allow receive_sender="org.freedesktop.ReconnectExample"/>
</policy>
</busconfig>
*
* To get the property via busctl:
*
* $ busctl --user get-property org.freedesktop.ReconnectExample \
* /org/freedesktop/ReconnectExample \
* org.freedesktop.ReconnectExample \
* Example
* s "example"
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#define _cleanup_(f) __attribute__((cleanup(f)))
static int log_error(int r, const char *str) {
fprintf(stderr, "%s failed: %s\n", str, strerror(-r));
return r;
}
typedef struct object {
const char *example;
sd_bus **bus;
sd_event **event;
} object;
static int property_get(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
object *o = userdata;
if (strcmp(property, "Example") == 0)
return sd_bus_message_append(reply, "s", o->example);
return sd_bus_error_setf(error,
SD_BUS_ERROR_UNKNOWN_PROPERTY,
"Unknown property '%s'",
property);
}
/* https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html */
static const sd_bus_vtable vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY(
"Example", "s",
property_get,
0,
SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END
};
static int setup(object *o);
static int on_disconnect(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) {
int r;
r = setup((object *)userdata);
if (r < 0) {
object *o = userdata;
r = sd_event_exit(*o->event, r);
if (r < 0)
return log_error(r, "sd_event_exit()");
}
return 1;
}
/* Ensure the event loop exits with a clear error if acquiring the well-known
* service name fails */
static int request_name_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
int r;
if (!sd_bus_message_is_method_error(m, NULL))
return 1;
const sd_bus_error *error = sd_bus_message_get_error(m);
if (sd_bus_error_has_names(error, SD_BUS_ERROR_TIMEOUT, SD_BUS_ERROR_NO_REPLY))
return 1; /* The bus is not available, try again later */
fprintf(stderr, "Failed to request name: %s\n", error->message);
object *o = userdata;
r = sd_event_exit(*o->event, -sd_bus_error_get_errno(error));
if (r < 0)
return log_error(r, "sd_event_exit()");
return 1;
}
static int setup(object *o) {
int r;
/* If we are reconnecting, then the bus object needs to be closed, detached
* from the event loop and recreated.
* https://www.freedesktop.org/software/systemd/man/sd_bus_detach_event.html
* https://www.freedesktop.org/software/systemd/man/sd_bus_close_unref.html
*/
if (*o->bus) {
r = sd_bus_detach_event(*o->bus);
if (r < 0)
return log_error(r, "sd_bus_detach_event()");
*o->bus = sd_bus_close_unref(*o->bus);
}
/* Set up a new bus object for the system bus, configure it to wait for D-Bus
* to be available instead of failing if it is not, and start it. All the
* following operations are asynchronous and will not block waiting for D-Bus
* to be available.
* https://www.freedesktop.org/software/systemd/man/sd_bus_new.html
* https://www.freedesktop.org/software/systemd/man/sd_bus_set_address.html
* https://www.freedesktop.org/software/systemd/man/sd_bus_set_bus_client.html
* https://www.freedesktop.org/software/systemd/man/sd_bus_negotiate_creds.html
* https://www.freedesktop.org/software/systemd/man/sd_bus_set_watch_bind.html
* https://www.freedesktop.org/software/systemd/man/sd_bus_set_connected_signal.html
* https://www.freedesktop.org/software/systemd/man/sd_bus_start.html
*/
r = sd_bus_new(o->bus);
if (r < 0)
return log_error(r, "sd_bus_new()");
r = sd_bus_set_address(*o->bus, "unix:path=/run/dbus/system_bus_socket");
if (r < 0)
return log_error(r, "sd_bus_set_address()");
r = sd_bus_set_bus_client(*o->bus, 1);
if (r < 0)
return log_error(r, "sd_bus_set_bus_client()");
r = sd_bus_negotiate_creds(*o->bus, 1, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS);
if (r < 0)
return log_error(r, "sd_bus_negotiate_creds()");
r = sd_bus_set_watch_bind(*o->bus, 1);
if (r < 0)
return log_error(r, "sd_bus_set_watch_bind()");
r = sd_bus_start(*o->bus);
if (r < 0)
return log_error(r, "sd_bus_start()");
/* Publish an interface on the bus, specifying our well-known object access
* path and public interface name.
* https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html
* https://dbus.freedesktop.org/doc/dbus-tutorial.html
*/
r = sd_bus_add_object_vtable(*o->bus,
NULL,
"/org/freedesktop/ReconnectExample",
"org.freedesktop.ReconnectExample",
vtable,
o);
if (r < 0)
return log_error(r, "sd_bus_add_object_vtable()");
/* By default, the service is only assigned an ephemeral name. Also add a
* well-known one, so that clients know whom to call. This needs to be
* asynchronous, as D-Bus might not be yet available. The callback will check
* whether the error is expected or not, in case it fails.
* https://www.freedesktop.org/software/systemd/man/sd_bus_request_name.html
*/
r = sd_bus_request_name_async(*o->bus,
NULL,
"org.freedesktop.ReconnectExample",
0,
request_name_callback,
o);
if (r < 0)
return log_error(r, "sd_bus_request_name_async()");
/* When D-Bus is disconnected this callback will be invoked, which will set up
* the connection again. This needs to be asynchronous, as D-Bus might not yet
* be available.
* https://www.freedesktop.org/software/systemd/man/sd_bus_match_signal_async.html
*/
r = sd_bus_match_signal_async(*o->bus,
NULL,
"org.freedesktop.DBus.Local",
NULL,
"org.freedesktop.DBus.Local",
"Disconnected",
on_disconnect,
NULL,
o);
if (r < 0)
return log_error(r, "sd_bus_match_signal_async()");
/* Attach the bus object to the event loop so that calls and signals are
* processed.
* https://www.freedesktop.org/software/systemd/man/sd_bus_attach_event.html
*/
r = sd_bus_attach_event(*o->bus, *o->event, 0);
if (r < 0)
return log_error(r, "sd_bus_attach_event()");
return 0;
}
int main(int argc, char **argv) {
/* The bus should be relinquished before the program terminates. The cleanup
* attribute allows us to do it nicely and cleanly whenever we exit the block.
*/
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
object o = {
.example = "example",
.bus = &bus,
.event = &event,
};
int r;
/* Create an event loop data structure, with default parameters.
* https://www.freedesktop.org/software/systemd/man/sd_event_default.html
*/
r = sd_event_default(&event);
if (r < 0)
return log_error(r, "sd_event_default()");
/* By default, the event loop will terminate when all sources have disappeared,
* so we have to keep it 'occupied'. Register signal handling to do so.
* https://www.freedesktop.org/software/systemd/man/sd_event_add_signal.html
*/
r = sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, NULL, NULL);
if (r < 0)
return log_error(r, "sd_event_add_signal(SIGINT)");
r = sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, NULL, NULL);
if (r < 0)
return log_error(r, "sd_event_add_signal(SIGTERM)");
r = setup(&o);
if (r < 0)
return EXIT_FAILURE;
/* Enter the main loop, it will exit only on sigint/sigterm.
* https://www.freedesktop.org/software/systemd/man/sd_event_loop.html
*/
r = sd_event_loop(event);
if (r < 0)
return log_error(r, "sd_event_loop()");
/* https://www.freedesktop.org/software/systemd/man/sd_bus_release_name.html */
r = sd_bus_release_name(bus, "org.freedesktop.ReconnectExample");
if (r < 0)
return log_error(r, "sd_bus_release_name()");
return 0;
}
okay bestie, real talk time ๐ Even though yeeting resources from one soft reboot cycle into the next is technically possible this way, we are begging you to use this sparingly. Like, sparingly-sparingly. Because it makes your system more fragile, periodt โ resources from different versions of the OS and different versions of your apps can end up mixed together with unforeseen consequences ๐ญ.
And specifically? Please, please don't let processes survive the soft reboot. Because that literally means your code updates will necessarily be incomplete, and processes typically pin a bunch of other resources (like the file system they're backed by), which just balloons your memory usage โ since two versions of the OS/application/file system might all be chillin' in memory at the same time ๐.
If you do want to leave processes running across a soft-reboot, you have to disconnect that service comprehensively from the rest of the OS โ as in, minimize IPC and reduce resource sharing with the rest of the OS. One possible mechanism for this is the concept of Portable Services[1], but make sure no resource from the host's OS filesystems is pinned via BindPaths= or similar unit settings โ otherwise the old, originating filesystem stays mounted for as long as the unit is running. not the vibe, bestie.
โจ NOTES โจ
- Heads up: because
systemd-shutdown(8)is not executed, the executables in/usr/lib/systemd/system-shutdown/are not executed either ๐ซถ systemd-soft-reboot.service(and its related units) should never be executed directly, okay? Like, never ever. Instead, you trigger system shutdown with a command such assystemctl soft-reboot๐- And if a new root file system has been set up on
"/run/nextroot/", then a soft-reboot gets performed when therebootcommand is invoked ๐
๐ซ SEE ALSO ๐ซ
systemd(1), systemctl(1), systemd.special(7), systemd-poweroff.service(8), systemd-suspend.service(8), bootup(7)
๐ NOTES ๐
- Portable Services โ
https://systemd.io/PORTABLE_SERVICES
and that's the whole page, bestie!! ๐ This was systemd 262.1's SYSTEMD-SO...T.SERVICE(8) โ go forth, soft-reboot responsibly, and remember: resources are for sharing, but like, not too much ๐โจ๐ฆ
๐ฆ where this page comes from
- arch rolling: systemd 262.1-1 ยท LGPL-2.1-or-later AND CC0-1.0 AND GPL-2.0-or-later AND MIT-0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms