diff --git a/man/logind.conf.xml b/man/logind.conf.xml
index c52431fd41..9ec6cb1c40 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 d9b9b0e1b3..ad9b00e8eb 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 70fd91f45a..768a30627f 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 de12ec5e9e..a4b54f6619 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 94dc85d301..4ef42af7a9 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 71e4247a79..3d58cefc57 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 277561300c..53ba291cab 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 7d1520b469..f9d11e803e 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 b62458ec3c..eb9452877a 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 cac6a30357..fbcfc9ab1b 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 fcc45c6e25..d0b7d01842 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 35e203cb12..f1860db448 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 c5b4cb4e8c..96597b1d9d 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 d6cdd9748f..87e96a3a17 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 95cf00fc81..00dd05bff7 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 0ca76ac23d..5bb6ccacf7 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 cc2b8c2cc4..00405f4705 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,
};