logind: implement maintenance time

Update frameworks that work automatically in the background
occasionally need to schedule reboots. Systemd-logind already
provides a nice mechanism to schedule shutdowns, send notfications
and block logins short before the time. Systemd has a framework for
calendar events, so we may conveniently use logind to define a
maintenance time for reboots.

The existing ScheduleShutdown DBus method in logind expects a usec_t
with an absolute time. Passing USEC_INFINITY as magic value now tells
logind to take the time from the configured maintenance time if set.

"shutdown -r" leverages that and uses the maintenance time
automatically if configured. The one minute default is still used if
nothing was specified.

Similarly the new 'auto' setting for the --when parameter of systemctl
uses the maintenance time if configured or a one minute timer like the
shutdown command.
This commit is contained in:
Ludwig Nussel
2024-06-20 14:37:42 +02:00
committed by Lennart Poettering
parent e262205eb7
commit 0e10c3d872
17 changed files with 141 additions and 16 deletions
+14
View File
@@ -393,6 +393,20 @@
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>DesignatedMaintenanceTime=</varname></term>
<listitem>
<para>
Specifies a default calendar event for scheduled shutdowns. So when using e.g. the command
<command>shutdown -r</command> to reboot the system without specifying a timeout, logind would
use the configured calendar event instead. For details about the syntax of calendar events, see
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
+12 -3
View File
@@ -253,9 +253,10 @@ node /org/freedesktop/login1 {
readonly b PreparingForShutdown = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b PreparingForSleep = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (st) ScheduledShutdown = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s DesignatedMaintenanceTime = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b Docked = ...;
readonly b LidClosed = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
@@ -295,6 +296,8 @@ node /org/freedesktop/login1 {
<!--property HandleHibernateKeyLongPress is not documented!-->
<!--property DesignatedMaintenanceTime is not documented!-->
<!--property StopIdleSessionUSec is not documented!-->
<!--Autogenerated cross-references for systemd.directives, do not edit-->
@@ -517,6 +520,8 @@ node /org/freedesktop/login1 {
<variablelist class="dbus-property" generated="True" extra-ref="ScheduledShutdown"/>
<variablelist class="dbus-property" generated="True" extra-ref="DesignatedMaintenanceTime"/>
<variablelist class="dbus-property" generated="True" extra-ref="Docked"/>
<variablelist class="dbus-property" generated="True" extra-ref="LidClosed"/>
@@ -688,7 +693,10 @@ node /org/freedesktop/login1 {
<literal>challenge</literal> is returned, the operation is available but only after authorization.</para>
<para><function>ScheduleShutdown()</function> schedules a shutdown operation <varname>type</varname> at
time <varname>usec</varname> in microseconds since the UNIX epoch. <varname>type</varname> can be one
time <varname>usec</varname> in microseconds since the UNIX epoch. Alternatively, if
<varname>usec</varname> <literal>UINT64_MAX</literal> and a maintenance window is
configured, <filename>systemd-logind</filename> will use the next time of the maintenance window
instead. <varname>type</varname> can be one
of <literal>poweroff</literal>, <literal>dry-poweroff</literal>, <literal>reboot</literal>,
<literal>dry-reboot</literal>, <literal>halt</literal>, and <literal>dry-halt</literal>. (The
<literal>dry-</literal> variants do not actually execute the shutdown action.)
@@ -1579,7 +1587,8 @@ node /org/freedesktop/login1/session/1 {
<function>CreateSessionWithPIDFD()</function> were added in version 255.</para>
<para><function>Sleep()</function>,
<function>CanSleep()</function>,
<varname>SleepOperation</varname>, and
<varname>SleepOperation</varname>,
<varname>DesignatedMaintenanceTime</varname>, and
<function>ListSessionsEx()</function> were added in version 256.</para>
</refsect2>
<refsect2>
+3 -1
View File
@@ -2862,7 +2862,9 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
which should adhere to the syntax documented in <citerefentry
project='man-pages'><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
section "PARSING TIMESTAMPS". Specially, if <literal>show</literal> is given, the currently scheduled
action will be shown, which can be canceled by passing an empty string or <literal>cancel</literal>.</para>
action will be shown, which can be canceled by passing an empty string or <literal>cancel</literal>.
<literal>auto</literal> will schedule the action according to maintenance window or one minute in
the future.</para>
<xi:include href="version-info.xml" xpointer="v254"/>
</listitem>
@@ -59,6 +59,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP),
SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_BUSY, EBUSY),
SD_BUS_ERROR_MAP(BUS_ERROR_NOT_YOUR_DEVICE, EPERM),
/* needs to be EOPNOTSUPP for proper handling in callers of logind_schedule_shutdown() */
SD_BUS_ERROR_MAP(BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, EOPNOTSUPP),
SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_NTP_SUPPORT, EOPNOTSUPP),
@@ -59,6 +59,8 @@
#define BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED "org.freedesktop.login1.SleepVerbNotSupported"
#define BUS_ERROR_SESSION_BUSY "org.freedesktop.login1.SessionBusy"
#define BUS_ERROR_NOT_YOUR_DEVICE "org.freedesktop.login1.NotYourDevice"
#define BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED \
"org.freedesktop.login1.DesignatedMaintenanceTimeNotScheduled"
#define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled"
#define BUS_ERROR_NO_NTP_SUPPORT "org.freedesktop.timedate1.NoNTPSupport"
+2
View File
@@ -77,6 +77,8 @@ void manager_reset_config(Manager *m) {
m->kill_exclude_users = strv_free(m->kill_exclude_users);
m->stop_idle_session_usec = USEC_INFINITY;
m->maintenance_time = calendar_spec_free(m->maintenance_time);
}
int manager_parse_config_file(Manager *m) {
+50 -1
View File
@@ -392,6 +392,31 @@ static int property_get_scheduled_shutdown(
return sd_bus_message_close_container(reply);
}
static int property_get_maintenance_time(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Manager *m = ASSERT_PTR(userdata);
_cleanup_free_ char *s = NULL;
int r;
assert(bus);
assert(reply);
if (m->maintenance_time) {
r = calendar_spec_to_string(m->maintenance_time, &s);
if (r < 0)
return log_error_errno(r, "Failed to format calendar specification: %m");
}
return sd_bus_message_append(reply, "s", s);
}
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_handle_action, handle_action, HandleAction);
static BUS_DEFINE_PROPERTY_GET(property_get_docked, "b", Manager, manager_is_docked_or_external_displays);
static BUS_DEFINE_PROPERTY_GET(property_get_lid_closed, "b", Manager, manager_is_lid_closed);
@@ -2331,6 +2356,8 @@ static void reset_scheduled_shutdown(Manager *m) {
}
(void) unlink(SHUTDOWN_SCHEDULE_FILE);
manager_send_changed(m, "ScheduledShutdown", NULL);
}
static int update_schedule_file(Manager *m) {
@@ -2565,6 +2592,25 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
if (r != 0)
return r;
if (elapse == USEC_INFINITY) {
if (m->maintenance_time) {
r = calendar_spec_next_usec(m->maintenance_time, now(CLOCK_REALTIME), &elapse);
if (r < 0) {
if (r == -ENOENT)
return sd_bus_error_set(error,
BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED,
"No upcoming maintenance window scheduled");
return sd_bus_error_setf(error,
BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED,
"Failed to determine next maintenace window");
}
log_info("Scheduled %s at maintenance window %s", type, FORMAT_TIMESTAMP(elapse));
} else
/* the good old shutdown command uses one minute by default */
elapse = usec_add(now(CLOCK_REALTIME), USEC_PER_MINUTE);
}
m->scheduled_shutdown_action = handle;
m->shutdown_dry_run = dry_run;
m->scheduled_shutdown_timeout = elapse;
@@ -2582,6 +2628,8 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
return r;
}
manager_send_changed(m, "ScheduledShutdown", NULL);
return sd_bus_reply_method_return(message, NULL);
}
@@ -3656,7 +3704,8 @@ static const sd_bus_vtable manager_vtable[] = {
SD_BUS_PROPERTY("IdleActionUSec", "t", NULL, offsetof(Manager, idle_action_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PreparingForShutdown", "b", property_get_preparing, 0, 0),
SD_BUS_PROPERTY("PreparingForSleep", "b", property_get_preparing, 0, 0),
SD_BUS_PROPERTY("ScheduledShutdown", "(st)", property_get_scheduled_shutdown, 0, 0),
SD_BUS_PROPERTY("ScheduledShutdown", "(st)", property_get_scheduled_shutdown, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("DesignatedMaintenanceTime", "s", property_get_maintenance_time, 0, 0),
SD_BUS_PROPERTY("Docked", "b", property_get_docked, 0, 0),
SD_BUS_PROPERTY("LidClosed", "b", property_get_lid_closed, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("OnExternalPower", "b", property_get_on_external_power, 0, 0),
+1
View File
@@ -50,5 +50,6 @@ Login.RuntimeDirectoryInodesMax, config_parse_iec_uint64, 0, offse
Login.RemoveIPC, config_parse_bool, 0, offsetof(Manager, remove_ipc)
Login.InhibitorsMax, config_parse_uint64, 0, offsetof(Manager, inhibitors_max)
Login.SessionsMax, config_parse_uint64, 0, offsetof(Manager, sessions_max)
Login.DesignatedMaintenanceTime, config_parse_calendar, 0, offsetof(Manager, maintenance_time)
Login.UserTasksMax, config_parse_compat_user_tasks_max, 0, 0
Login.StopIdleSessionSec, config_parse_sec_fix_0, 0, offsetof(Manager, stop_idle_session_usec)
+1
View File
@@ -50,3 +50,4 @@
#InhibitorsMax=8192
#SessionsMax=8192
#StopIdleSessionSec=infinity
#DesignatedMaintenanceTime=
+3
View File
@@ -8,6 +8,7 @@
#include "sd-device.h"
#include "sd-event.h"
#include "calendarspec.h"
#include "conf-parser.h"
#include "hashmap.h"
#include "list.h"
@@ -141,6 +142,8 @@ struct Manager {
char *efi_loader_entry_one_shot;
struct stat efi_loader_entry_one_shot_stat;
CalendarSpec *maintenance_time;
};
void manager_reset_config(Manager *m);
+36
View File
@@ -9,6 +9,7 @@
#include "alloc-util.h"
#include "chase.h"
#include "calendarspec.h"
#include "conf-files.h"
#include "conf-parser.h"
#include "constants.h"
@@ -1980,6 +1981,41 @@ int config_parse_unsigned_bounded(
return r;
}
int config_parse_calendar(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
CalendarSpec **cr = data;
_cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue)) {
*cr = calendar_spec_free(*cr);
return 0;
}
r = calendar_spec_from_string(rvalue, &c);
if (r < 0)
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse calendar specification, ignoring: %s", rvalue);
else
*cr = TAKE_PTR(c);
return 0;
}
DEFINE_CONFIG_PARSE(config_parse_percent, parse_percent, "Failed to parse percent value");
DEFINE_CONFIG_PARSE(config_parse_permyriad, parse_permyriad, "Failed to parse permyriad value");
DEFINE_CONFIG_PARSE_PTR(config_parse_sec_fix_0, parse_sec_fix_0, usec_t, "Failed to parse time value");
+1
View File
@@ -251,6 +251,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_permyriad);
CONFIG_PARSER_PROTOTYPE(config_parse_pid);
CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0);
CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
CONFIG_PARSER_PROTOTYPE(config_parse_calendar);
typedef enum Disabled {
DISABLED_CONFIGURATION,
+2 -1
View File
@@ -6,6 +6,7 @@
#include "pretty-print.h"
#include "reboot-util.h"
#include "systemctl-compat-shutdown.h"
#include "systemctl-logind.h"
#include "systemctl-sysv-compat.h"
#include "systemctl.h"
#include "terminal-util.h"
@@ -137,7 +138,7 @@ int shutdown_parse_argv(int argc, char *argv[]) {
return r;
}
} else
arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE;
arg_when = USEC_INFINITY; /* logind chooses on server side */
if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN)
/* No time argument for shutdown cancel */
+1 -1
View File
@@ -408,7 +408,7 @@ int logind_show_shutdown(void) {
else /* If we don't recognize the action string, we'll show it as-is */
pretty_action = action;
if (arg_action == ACTION_SYSTEMCTL)
if (IN_SET(arg_action, ACTION_SYSTEMCTL, ACTION_SYSTEMCTL_SHOW_SHUTDOWN))
log_info("%s scheduled for %s, use 'systemctl %s --when=cancel' to cancel.",
pretty_action,
FORMAT_TIMESTAMP_STYLE(elapse, arg_timestamp_style),
+1 -3
View File
@@ -203,10 +203,8 @@ int verb_start_special(int argc, char *argv[], void *userdata) {
case ACTION_SOFT_REBOOT:
if (arg_when == 0)
r = logind_reboot(a);
else if (arg_when != USEC_INFINITY)
else
r = logind_schedule_shutdown(a);
else /* arg_when == USEC_INFINITY */
r = logind_cancel_shutdown();
if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS))
/* The latter indicates that the requested operation requires auth,
* is not supported or already in progress, in which cases we ignore the error. */
+9 -6
View File
@@ -1023,15 +1023,17 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
case ARG_WHEN:
if (streq(optarg, "show")) {
r = logind_show_shutdown();
if (r < 0 && r != -ENODATA)
return r;
return 0;
arg_action = ACTION_SYSTEMCTL_SHOW_SHUTDOWN;
return 1;
}
if (STR_IN_SET(optarg, "", "cancel")) {
arg_when = USEC_INFINITY;
arg_action = ACTION_CANCEL_SHUTDOWN;
return 1;
}
if (streq(optarg, "auto")) {
arg_when = USEC_INFINITY; /* logind chooses on server side */
break;
}
@@ -1339,6 +1341,7 @@ static int run(int argc, char *argv[]) {
break;
case ACTION_SHOW_SHUTDOWN:
case ACTION_SYSTEMCTL_SHOW_SHUTDOWN:
r = logind_show_shutdown();
break;
+1
View File
@@ -35,6 +35,7 @@ enum action {
ACTION_RUNLEVEL,
ACTION_CANCEL_SHUTDOWN,
ACTION_SHOW_SHUTDOWN,
ACTION_SYSTEMCTL_SHOW_SHUTDOWN,
_ACTION_MAX,
_ACTION_INVALID = -EINVAL,
};