From: Ludwig Nussel Date: Mon, 6 May 2024 13:55:16 +0000 (+0200) Subject: logind: implement maintenance time X-Git-Tag: v257-rc1~1087 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e10c3d8724b0a5d07871c9de71565ac91dd55b7;p=thirdparty%2Fsystemd.git 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. --- diff --git a/man/logind.conf.xml b/man/logind.conf.xml index c52431fd413..9ec6cb1c400 100644 --- a/man/logind.conf.xml +++ b/man/logind.conf.xml @@ -393,6 +393,20 @@ + + + DesignatedMaintenanceTime= + + + + Specifies a default calendar event for scheduled shutdowns. So when using e.g. the command + shutdown -r 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 + systemd.time7. + + + + diff --git a/man/org.freedesktop.login1.xml b/man/org.freedesktop.login1.xml index d9b9b0e1b3b..ad9b00e8eb0 100644 --- a/man/org.freedesktop.login1.xml +++ b/man/org.freedesktop.login1.xml @@ -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 { + + @@ -517,6 +520,8 @@ node /org/freedesktop/login1 { + + @@ -688,7 +693,10 @@ node /org/freedesktop/login1 { challenge is returned, the operation is available but only after authorization. ScheduleShutdown() schedules a shutdown operation type at - time usec in microseconds since the UNIX epoch. type can be one + time usec in microseconds since the UNIX epoch. Alternatively, if + usec UINT64_MAX and a maintenance window is + configured, systemd-logind will use the next time of the maintenance window + instead. type can be one of poweroff, dry-poweroff, reboot, dry-reboot, halt, and dry-halt. (The dry- variants do not actually execute the shutdown action.) @@ -1579,7 +1587,8 @@ node /org/freedesktop/login1/session/1 { CreateSessionWithPIDFD() were added in version 255. Sleep(), CanSleep(), - SleepOperation, and + SleepOperation, + DesignatedMaintenanceTime, and ListSessionsEx() were added in version 256. diff --git a/man/systemctl.xml b/man/systemctl.xml index 70fd91f45a6..768a30627fd 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -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 systemd.time7 section "PARSING TIMESTAMPS". Specially, if show is given, the currently scheduled - action will be shown, which can be canceled by passing an empty string or cancel. + action will be shown, which can be canceled by passing an empty string or cancel. + auto will schedule the action according to maintenance window or one minute in + the future. diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index de12ec5e9eb..a4b54f66196 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -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), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 94dc85d3012..4ef42af7a9f 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -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" diff --git a/src/login/logind-core.c b/src/login/logind-core.c index 71e4247a799..3d58cefc573 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -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) { diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 277561300c0..53ba291cab7 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -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), diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf index 7d1520b469f..f9d11e803e3 100644 --- a/src/login/logind-gperf.gperf +++ b/src/login/logind-gperf.gperf @@ -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) diff --git a/src/login/logind.conf.in b/src/login/logind.conf.in index b62458ec3ca..eb9452877a5 100644 --- a/src/login/logind.conf.in +++ b/src/login/logind.conf.in @@ -50,3 +50,4 @@ #InhibitorsMax=8192 #SessionsMax=8192 #StopIdleSessionSec=infinity +#DesignatedMaintenanceTime= diff --git a/src/login/logind.h b/src/login/logind.h index cac6a303573..fbcfc9ab1b4 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -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); diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index fcc45c6e252..d0b7d018425 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -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"); diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index 35e203cb127..f1860db4485 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -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, diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index c5b4cb4e8cb..96597b1d9df 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -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 */ diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c index d6cdd9748f5..87e96a3a174 100644 --- a/src/systemctl/systemctl-logind.c +++ b/src/systemctl/systemctl-logind.c @@ -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), diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index 95cf00fc81b..00dd05bff77 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -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. */ diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 0ca76ac23db..5bb6ccacf7f 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -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; diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index cc2b8c2cc46..00405f47057 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -35,6 +35,7 @@ enum action { ACTION_RUNLEVEL, ACTION_CANCEL_SHUTDOWN, ACTION_SHOW_SHUTDOWN, + ACTION_SYSTEMCTL_SHOW_SHUTDOWN, _ACTION_MAX, _ACTION_INVALID = -EINVAL, };