]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
logind: implement maintenance time
authorLudwig Nussel <ludwig.nussel@suse.de>
Mon, 6 May 2024 13:55:16 +0000 (15:55 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 20 Jun 2024 12:37:42 +0000 (14:37 +0200)
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.

17 files changed:
man/logind.conf.xml
man/org.freedesktop.login1.xml
man/systemctl.xml
src/libsystemd/sd-bus/bus-common-errors.c
src/libsystemd/sd-bus/bus-common-errors.h
src/login/logind-core.c
src/login/logind-dbus.c
src/login/logind-gperf.gperf
src/login/logind.conf.in
src/login/logind.h
src/shared/conf-parser.c
src/shared/conf-parser.h
src/systemctl/systemctl-compat-shutdown.c
src/systemctl/systemctl-logind.c
src/systemctl/systemctl-start-special.c
src/systemctl/systemctl.c
src/systemctl/systemctl.h

index c52431fd413505a53786c5a0fd20c5ba3ac18dd7..9ec6cb1c400d3470f674ee3928d1df0a9932a9a6 100644 (file)
 
         <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>
 
index d9b9b0e1b3b5903f27ddb325fad6ca70f5ef8779..ad9b00e8eb0cd334a9164a45acd21b439e69bc64 100644 (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>
index 70fd91f45a656ae2240b8dbb7dba8c78dbbd76b1..768a30627fdcde35dc5bc037eeb89fb6abb21f47 100644 (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>
index de12ec5e9eb382d01d38fe8248ccac723a6a2d83..a4b54f66196e0bec6a91f2f61bf26f0bb2c15b92 100644 (file)
@@ -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),
index 94dc85d30123f12693169b112630abe9ead41eaf..4ef42af7a9f33611fb7fde06a790c0ec38aa1505 100644 (file)
@@ -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"
index 71e4247a7991c7bbd8a96f6361aa3f9a383782f0..3d58cefc57354774a83942c0078bab85028026b1 100644 (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) {
index 277561300c0a997ffa914f693c9d23245caef66e..53ba291cab776598e82cef2ae86f69f71925e0cb 100644 (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),
index 7d1520b469ffb33418a449e3841f8b75bfec0cfe..f9d11e803e35bc5715933c70bde028037e1e605e 100644 (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)
index b62458ec3ca0737650702ecc3d9c4f0d8c39a0c2..eb9452877a553769bafafa9059eebe595707396a 100644 (file)
@@ -50,3 +50,4 @@
 #InhibitorsMax=8192
 #SessionsMax=8192
 #StopIdleSessionSec=infinity
+#DesignatedMaintenanceTime=
index cac6a3035731c05fc0e52a1832b4332b51c06686..fbcfc9ab1b493895cec3c002a7db2960aeecf10d 100644 (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);
index fcc45c6e252f140544e0ac82982e0614a797325a..d0b7d0184250775caafbcf679888a02471f7dc4c 100644 (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");
index 35e203cb1271a2e20838fb6f57f565fe4709434d..f1860db4485df491ae0d544045a8b5fdad45b9dd 100644 (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,
index c5b4cb4e8cbb89a1b7ab5bcbcce8ca61ddf7642b..96597b1d9dfae68dc760bb9e870d9996573e2049 100644 (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 */
index d6cdd9748f5e597d112f7f28521f2b5b975ceed7..87e96a3a174f31f207be9ae91444e291907ef62d 100644 (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),
index 95cf00fc81bbf9f40bc749600841586ecff02498..00dd05bff7775bffa05b53fbb47cb30a1c31d720 100644 (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. */
index 0ca76ac23dbf58831e6313342c3fd0e2c8bf3187..5bb6ccacf7f93804912a56853dcb5269fbc1044d 100644 (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;
 
index cc2b8c2cc4600dc79a0919df2c3bfdd792b37cdb..00405f4705770d7fabb76ba86fa257e4efacbf39 100644 (file)
@@ -35,6 +35,7 @@ enum action {
         ACTION_RUNLEVEL,
         ACTION_CANCEL_SHUTDOWN,
         ACTION_SHOW_SHUTDOWN,
+        ACTION_SYSTEMCTL_SHOW_SHUTDOWN,
         _ACTION_MAX,
         _ACTION_INVALID = -EINVAL,
 };