]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #29853 from YHNdnzj/sleep-automated
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 6 Dec 2023 21:25:13 +0000 (22:25 +0100)
committerGitHub <noreply@github.com>
Wed, 6 Dec 2023 21:25:13 +0000 (22:25 +0100)
logind: support Sleep() that automatically choose a sleep operation

21 files changed:
TODO
man/logind.conf.xml
man/org.freedesktop.login1.xml
man/systemctl.xml
src/login/logind-action.c
src/login/logind-action.h
src/login/logind-core.c
src/login/logind-dbus.c
src/login/logind-gperf.gperf
src/login/logind-wall.c
src/login/logind.c
src/login/logind.conf.in
src/login/logind.h
src/login/test-login-tables.c
src/systemctl/systemctl-logind.c
src/systemctl/systemctl-start-special.c
src/systemctl/systemctl-start-unit.c
src/systemctl/systemctl.c
src/systemctl/systemctl.h
test/units/testsuite-21.sh
test/units/testsuite-35.sh

diff --git a/TODO b/TODO
index 8646d99b1ded6e11898a37f6676f9eebfae3ce80..c75f69e650eea7e5adeb936784e43463a6070ad6 100644 (file)
--- a/TODO
+++ b/TODO
@@ -233,13 +233,6 @@ Features:
   dir. Similar for $XDG_RUNTIME_DIR. Start project@%i.target. Use LogField= to
   add a field identifying the project.
 
-* logind: add a new dbus call Sleep() which automatically redirects to one of
-  Suspend(), Hibernate(), SuspendThenHibernate() depending on what is
-  available, and also subject to some local configuration in
-  logind.conf. Should default to SuspendThenHibernate() if available, and then
-  fallback to Suspend() and finally Hibernate() if not. Then expose this as
-  "systemctl sleep", and tell DEs to default to this.
-
 * in sd-boot and sd-stub measure the SMBIOS vendor strings to some PCR (at
   least some subset of them that look like systemd stuff), because apparently
   some firmware does not, but systemd honours it. avoid duplicate measurement
index 72f657ced4eb4bc4f788c1cc67dba3c9172ace95..2b5c11b916658f333f2fa53af9b0bf1861b8931f 100644 (file)
         <term><varname>IdleAction=</varname></term>
 
         <listitem><para>Configures the action to take when the system
-        is idle. Takes one of
-        <literal>ignore</literal>,
-        <literal>poweroff</literal>,
-        <literal>reboot</literal>,
-        <literal>halt</literal>,
-        <literal>kexec</literal>,
-        <literal>suspend</literal>,
-        <literal>hibernate</literal>,
-        <literal>hybrid-sleep</literal>,
-        <literal>suspend-then-hibernate</literal>, and
-        <literal>lock</literal>.
-        Defaults to <literal>ignore</literal>.</para>
+        is idle. Takes one of <literal>ignore</literal>, <literal>poweroff</literal>, <literal>reboot</literal>,
+        <literal>halt</literal>, <literal>kexec</literal>, <literal>suspend</literal>, <literal>hibernate</literal>,
+        <literal>hybrid-sleep</literal>, <literal>suspend-then-hibernate</literal>, <literal>sleep</literal>,
+        and <literal>lock</literal>. Defaults to <literal>ignore</literal>.</para>
 
         <para>Note that this requires that user sessions correctly
         report the idle status to the system. The system will execute
         <xi:include href="version-info.xml" xpointer="v240"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>SleepOperation=</varname></term>
+
+        <listitem><para>Takes a list of sleep operations. Possible values are <literal>suspend</literal>,
+        <literal>hibernate</literal>, <literal>hybrid-sleep</literal>, and <literal>suspend-then-hibernate</literal>.
+        Controls the candidate sleep operations for the <literal>sleep</literal> action. When <literal>sleep</literal>
+        action is performed, the specified sleep operations are checked in a fixed order (<literal>suspend-then-hibernate</literal>
+        → <literal>hybrid-sleep</literal> → <literal>suspend</literal> → <literal>hibernate</literal>), and
+        the first one supported by the machine is used to put the system into sleep. Defaults to
+        <literal>suspend-then-hibernate suspend hibernate</literal>.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>HandlePowerKey=</varname></term>
         <term><varname>HandlePowerKeyLongPress=</varname></term>
 
         <listitem><para>Controls how logind shall handle the system power, reboot and sleep keys and the lid
         switch to trigger actions such as system power-off, reboot or suspend. Can be one of
-        <literal>ignore</literal>, <literal>poweroff</literal>, <literal>reboot</literal>,
-        <literal>halt</literal>, <literal>kexec</literal>, <literal>suspend</literal>,
-        <literal>hibernate</literal>, <literal>hybrid-sleep</literal>,
-        <literal>suspend-then-hibernate</literal>, <literal>lock</literal>, and
+        <literal>ignore</literal>, <literal>poweroff</literal>, <literal>reboot</literal>, <literal>halt</literal>,
+        <literal>kexec</literal>, <literal>suspend</literal>, <literal>hibernate</literal>, <literal>hybrid-sleep</literal>,
+        <literal>suspend-then-hibernate</literal>, <literal>sleep</literal>, <literal>lock</literal>, and
         <literal>factory-reset</literal>.  If <literal>ignore</literal>, <command>systemd-logind</command>
         will never handle these keys. If <literal>lock</literal>, all running sessions will be screen-locked;
         otherwise, the specified action will be taken in the respective event. Only input devices with the
index 877bf463a07fd5e599f79393aa5f47f8d53d2714..12d8695c27d3fe474f068c9574b7dcdd6bf21d07 100644 (file)
@@ -141,6 +141,7 @@ node /org/freedesktop/login1 {
       HybridSleepWithFlags(in  t flags);
       SuspendThenHibernate(in  b interactive);
       SuspendThenHibernateWithFlags(in  t flags);
+      Sleep(in  t flags);
       CanPowerOff(out s result);
       CanReboot(out s result);
       CanHalt(out s result);
@@ -148,6 +149,7 @@ node /org/freedesktop/login1 {
       CanHibernate(out s result);
       CanHybridSleep(out s result);
       CanSuspendThenHibernate(out s result);
+      CanSleep(out s result);
       ScheduleShutdown(in  s type,
                        in  t usec);
       CancelScheduledShutdown(out b cancelled);
@@ -218,6 +220,8 @@ node /org/freedesktop/login1 {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t UserStopDelayUSec = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as SleepOperation = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s HandlePowerKey = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s HandlePowerKeyLongPress = '...';
@@ -280,6 +284,8 @@ node /org/freedesktop/login1 {
 };
     </programlisting>
 
+    <!--property SleepOperation is not documented!-->
+
     <!--property HandlePowerKeyLongPress is not documented!-->
 
     <!--property HandleRebootKey is not documented!-->
@@ -378,6 +384,8 @@ node /org/freedesktop/login1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="SuspendThenHibernateWithFlags()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="Sleep()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="CanPowerOff()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="CanReboot()"/>
@@ -392,6 +400,8 @@ node /org/freedesktop/login1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="CanSuspendThenHibernate()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="CanSleep()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="ScheduleShutdown()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="CancelScheduledShutdown()"/>
@@ -470,6 +480,8 @@ node /org/freedesktop/login1 {
 
     <variablelist class="dbus-property" generated="True" extra-ref="UserStopDelayUSec"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="SleepOperation"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="HandlePowerKey"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="HandlePowerKeyLongPress"/>
@@ -609,17 +621,20 @@ node /org/freedesktop/login1 {
       off, rebooted, halted (shut down without turning off power), suspended (the system state is saved to
       RAM and the CPU is turned off), or hibernated (the system state is saved to disk and the machine is
       powered down). <function>HybridSleep()</function> results in the system entering a hybrid-sleep mode,
-      i.e. the system is both hibernated and suspended.  <function>SuspendThenHibernate()</function> results
+      i.e. the system is both hibernated and suspended. <function>SuspendThenHibernate()</function> results
       in the system being suspended, then later woken using an RTC timer and hibernated. The only argument is
       the polkit interactivity boolean <varname>interactive</varname> (see below). The main purpose of these
       calls is that they enforce polkit policy and hence allow powering off/rebooting/suspending/hibernating
-      even by unprivileged users. They also enforce inhibition locks for non-privileged users. UIs should
-      expose these calls as the primary mechanism to poweroff/reboot/suspend/hibernate the machine. Methods
+      even by unprivileged users. They also enforce inhibition locks for non-privileged users.
+      <function>Sleep()</function> automatically selects the most suitable sleep operation supported by the
+      machine. The candidate sleep operations to check for support can be configured through <varname>SleepOperation=</varname>
+      setting in <citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+      UIs should expose these calls as the primary mechanism to poweroff/reboot/suspend/hibernate the machine. Methods
       <function>PowerOffWithFlags()</function>, <function>RebootWithFlags()</function>,
       <function>HaltWithFlags()</function>, <function>SuspendWithFlags()</function>,
-      <function>HibernateWithFlags()</function>, <function>HybridSleepWithFlags()</function> and
-      <function>SuspendThenHibernateWithFlags()</function> add <varname>flags</varname> to allow for
-      extendability, defined as follows:</para>
+      <function>HibernateWithFlags()</function>, <function>HybridSleepWithFlags()</function>,
+      <function>SuspendThenHibernateWithFlags()</function>, and <function>Sleep()</function> take
+      <varname>flags</varname> to allow for extendability, defined as follows:</para>
       <programlisting>
 #define SD_LOGIND_ROOT_CHECK_INHIBITORS          (UINT64_C(1) &lt;&lt; 0)
 #define SD_LOGIND_KEXEC_REBOOT                   (UINT64_C(1) &lt;&lt; 1)
@@ -649,20 +664,18 @@ node /org/freedesktop/login1 {
       <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for the
       corresponding command line interface.</para>
 
-      <para><function>CanPowerOff()</function>, <function>CanReboot()</function>,
-      <function>CanHalt()</function>, <function>CanSuspend()</function>, <function>CanHibernate()</function>,
-      <function>CanHybridSleep()</function>, <function>CanSuspendThenHibernate()</function>,
+      <para><function>CanPowerOff()</function>, <function>CanReboot()</function>, <function>CanHalt()</function>,
+      <function>CanSuspend()</function>, <function>CanHibernate()</function>, <function>CanHybridSleep()</function>,
+      <function>CanSuspendThenHibernate()</function>, <function>CanSleep()</function>,
       <function>CanRebootParameter()</function>, <function>CanRebootToFirmwareSetup()</function>,
-      <function>CanRebootToBootLoaderMenu()</function>, and
-      <function>CanRebootToBootLoaderEntry()</function> test whether the system supports the respective
-      operation and whether the calling user is allowed to execute it. Returns one of <literal>na</literal>,
-      <literal>yes</literal>, <literal>no</literal>, and <literal>challenge</literal>. If
-      <literal>na</literal> is returned, the operation is not available because hardware, kernel, or drivers
-      do not support it. If <literal>yes</literal> is returned, the operation is supported and the user may
-      execute the operation without further authentication. If <literal>no</literal> is returned, the
-      operation is available but the user is not allowed to execute the operation. If
-      <literal>challenge</literal> is returned, the operation is available but only after
-      authorization.</para>
+      <function>CanRebootToBootLoaderMenu()</function>, and <function>CanRebootToBootLoaderEntry()</function>
+      test whether the system supports the respective operation and whether the calling user is allowed to
+      execute it. Returns one of <literal>na</literal>, <literal>yes</literal>, <literal>no</literal>, and
+      <literal>challenge</literal>. If <literal>na</literal> is returned, the operation is not available because
+      hardware, kernel, or drivers do not support it. If <literal>yes</literal> is returned, the operation is
+      supported and the user may execute the operation without further authentication. If <literal>no</literal>
+      is returned, the operation is available but the user is not allowed to execute the operation. If
+      <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
@@ -819,8 +832,8 @@ node /org/freedesktop/login1 {
       <interfacename>org.freedesktop.login1.hibernate-ignore-inhibit</interfacename>,
       respectively depending on whether there are other sessions around or active inhibits are present.
       <function>HybridSleep()</function> and <function>SuspendThenHibernate()</function>
-      use the same privileges as <function>Hibernate()</function>.
-      <function>SetRebootParameter()</function> requires
+      use the same privileges as <function>Hibernate()</function>. <function>Sleep()</function> uses
+      the inhibits of the auto-selected sleep operation. <function>SetRebootParameter()</function> requires
       <interfacename>org.freedesktop.login1.set-reboot-parameter</interfacename>.</para>
 
       <para><function>SetRebootToFirmwareSetup</function> requires
@@ -1546,6 +1559,9 @@ node /org/freedesktop/login1/session/1 {
       <para><varname>StopIdleSessionUSec</varname> was added in version 252.</para>
       <para><function>PrepareForShutdownWithMetadata</function> and
       <function>CreateSessionWithPIDFD()</function> were added in version 255.</para>
+      <para><function>Sleep()</function>,
+      <function>CanSleep()</function>, and
+      <varname>SleepOperation</varname> were added in version 256.</para>
     </refsect2>
     <refsect2>
       <title>Session Objects</title>
index 1d791b44fd32d8bb1d4cc2e8f0a784beec7fc70f..6029434ae6b7e56b2ae32d2643852c6abc1473fe 100644 (file)
@@ -1755,6 +1755,24 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><command>sleep</command></term>
+
+          <listitem>
+            <para>Put the system to sleep, through <command>suspend</command>, <command>hibernate</command>,
+            <command>hybrid-sleep</command>, or <command>suspend-then-hibernate</command>. The sleep operation
+            to use is automatically selected by <citerefentry>
+            <refentrytitle>systemd-logind.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+            By default, <command>suspend-then-hibernate</command> is used, and falls back to <command>suspend</command>
+            and then <command>hibernate</command> if not supported. Refer to <varname>SleepOperation=</varname>
+            setting in <citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+            for more details. This command is asynchronous, and will return after the sleep operation is
+            successfully enqueued. It will not wait for the sleep/resume cycle to complete.</para>
+
+            <xi:include href="version-info.xml" xpointer="v256"/>
+          </listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><command>suspend</command></term>
 
index e678edd66fb2910ed534b161bbf8c57604cb55fa..b9cb9b818dcc7e3bab3bec167d23966806e0404a 100644 (file)
@@ -133,6 +133,49 @@ const HandleActionData* handle_action_lookup(HandleAction action) {
         return &handle_action_data_table[action];
 }
 
+/* The order in which we try each sleep operation. We should typically prefer operations without a delay,
+ * i.e. s2h and suspend, and use hibernation at last since it requires minimum hardware support.
+ * hybrid-sleep is disabled by default, and thus should be ordered before suspend if manually chosen by user,
+ * since it implies suspend and will probably never be selected by us otherwise. */
+static const HandleAction sleep_actions[] = {
+        HANDLE_SUSPEND_THEN_HIBERNATE,
+        HANDLE_HYBRID_SLEEP,
+        HANDLE_SUSPEND,
+        HANDLE_HIBERNATE,
+};
+
+int handle_action_get_enabled_sleep_actions(HandleActionSleepMask mask, char ***ret) {
+        _cleanup_strv_free_ char **actions = NULL;
+        int r;
+
+        assert(ret);
+
+        FOREACH_ARRAY(i, sleep_actions, ELEMENTSOF(sleep_actions))
+                if (FLAGS_SET(mask, 1U << *i)) {
+                        r = strv_extend(&actions, handle_action_to_string(*i));
+                        if (r < 0)
+                                return r;
+                }
+
+        *ret = TAKE_PTR(actions);
+        return 0;
+}
+
+HandleAction handle_action_sleep_select(HandleActionSleepMask mask) {
+
+        FOREACH_ARRAY(i, sleep_actions, ELEMENTSOF(sleep_actions)) {
+                HandleActionSleepMask a = 1U << *i;
+
+                if (!FLAGS_SET(mask, a))
+                        continue;
+
+                if (sleep_supported(handle_action_lookup(*i)->sleep_operation) > 0)
+                        return *i;
+        }
+
+        return _HANDLE_ACTION_INVALID;
+}
+
 static int handle_action_execute(
                 Manager *m,
                 HandleAction handle,
@@ -158,6 +201,7 @@ static int handle_action_execute(
         int r;
 
         assert(m);
+        assert(!IN_SET(handle, HANDLE_IGNORE, HANDLE_LOCK, HANDLE_SLEEP));
 
         if (handle == HANDLE_KEXEC && access(KEXEC, X_OK) < 0)
                 return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
@@ -208,11 +252,22 @@ static int handle_action_sleep_execute(
                 bool ignore_inhibited,
                 bool is_edge) {
 
-        bool supported;
-
         assert(m);
         assert(HANDLE_ACTION_IS_SLEEP(handle));
 
+        if (handle == HANDLE_SLEEP) {
+                HandleAction a;
+
+                a = handle_action_sleep_select(m->handle_action_sleep_mask);
+                if (a < 0)
+                        return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                                 "None of the configured sleep operations are supported, ignoring.");
+
+                return handle_action_sleep_execute(m, a, ignore_inhibited, is_edge);
+        }
+
+        bool supported;
+
         if (handle == HANDLE_SUSPEND)
                 supported = sleep_supported(SLEEP_SUSPEND) > 0;
         else if (handle == HANDLE_HIBERNATE)
@@ -302,8 +357,9 @@ static const char* const handle_action_verb_table[_HANDLE_ACTION_MAX] = {
         [HANDLE_SOFT_REBOOT]            = "soft-reboot",
         [HANDLE_SUSPEND]                = "suspend",
         [HANDLE_HIBERNATE]              = "hibernate",
-        [HANDLE_HYBRID_SLEEP]           = "enter hybrid sleep",
+        [HANDLE_HYBRID_SLEEP]           = "hybrid sleep",
         [HANDLE_SUSPEND_THEN_HIBERNATE] = "suspend and later hibernate",
+        [HANDLE_SLEEP]                  = "sleep",
         [HANDLE_FACTORY_RESET]          = "perform a factory reset",
         [HANDLE_LOCK]                   = "be locked",
 };
@@ -323,9 +379,66 @@ static const char* const handle_action_table[_HANDLE_ACTION_MAX] = {
         [HANDLE_HIBERNATE]              = "hibernate",
         [HANDLE_HYBRID_SLEEP]           = "hybrid-sleep",
         [HANDLE_SUSPEND_THEN_HIBERNATE] = "suspend-then-hibernate",
+        [HANDLE_SLEEP]                  = "sleep",
         [HANDLE_FACTORY_RESET]          = "factory-reset",
         [HANDLE_LOCK]                   = "lock",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(handle_action, HandleAction);
 DEFINE_CONFIG_PARSE_ENUM(config_parse_handle_action, handle_action, HandleAction, "Failed to parse handle action setting");
+
+int config_parse_handle_action_sleep(
+                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) {
+
+        HandleActionSleepMask *mask = ASSERT_PTR(data);
+        _cleanup_strv_free_ char **actions = NULL;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        if (isempty(rvalue))
+                goto empty;
+
+        if (strv_split_full(&actions, rvalue, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE) < 0)
+                return log_oom();
+
+        *mask = 0;
+
+        STRV_FOREACH(action, actions) {
+                HandleAction a;
+
+                a = handle_action_from_string(*action);
+                if (a < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, a,
+                                   "Failed to parse SleepOperation '%s', ignoring: %m", *action);
+                        continue;
+                }
+
+                if (!HANDLE_ACTION_IS_SLEEP(a) || a == HANDLE_SLEEP) {
+                        log_syntax(unit, LOG_WARNING, filename, line, 0,
+                                   "HandleAction '%s' is not a sleep operation, ignoring: %m", *action);
+                        continue;
+                }
+
+                *mask |= 1U << a;
+        }
+
+        if (*mask == 0)
+                goto empty;
+
+        return 0;
+
+empty:
+        *mask = HANDLE_ACTION_SLEEP_MASK_DEFAULT;
+        return 0;
+}
index dbca963f2d53ea1437db41a701798424ee7dec3c..5ee86486ec3c652e1499d7f0304d74c4e4cd57ae 100644 (file)
@@ -5,6 +5,7 @@
 
 typedef enum HandleAction {
         HANDLE_IGNORE,
+
         HANDLE_POWEROFF,
         _HANDLE_ACTION_SHUTDOWN_FIRST = HANDLE_POWEROFF,
         HANDLE_REBOOT,
@@ -12,20 +13,33 @@ typedef enum HandleAction {
         HANDLE_KEXEC,
         HANDLE_SOFT_REBOOT,
         _HANDLE_ACTION_SHUTDOWN_LAST = HANDLE_SOFT_REBOOT,
+
         HANDLE_SUSPEND,
         _HANDLE_ACTION_SLEEP_FIRST = HANDLE_SUSPEND,
         HANDLE_HIBERNATE,
         HANDLE_HYBRID_SLEEP,
         HANDLE_SUSPEND_THEN_HIBERNATE,
-        _HANDLE_ACTION_SLEEP_LAST = HANDLE_SUSPEND_THEN_HIBERNATE,
+        HANDLE_SLEEP, /* A "high-level" action that automatically choose an appropriate low-level sleep action */
+        _HANDLE_ACTION_SLEEP_LAST = HANDLE_SLEEP,
+
         HANDLE_LOCK,
         HANDLE_FACTORY_RESET,
+
         _HANDLE_ACTION_MAX,
         _HANDLE_ACTION_INVALID = -EINVAL,
 } HandleAction;
 
 typedef struct HandleActionData HandleActionData;
 
+typedef enum HandleActionSleepMask {
+        HANDLE_SLEEP_SUSPEND_MASK                = 1U << HANDLE_SUSPEND,
+        HANDLE_SLEEP_HIBERNATE_MASK              = 1U << HANDLE_HIBERNATE,
+        HANDLE_SLEEP_HYBRID_SLEEP_MASK           = 1U << HANDLE_HYBRID_SLEEP,
+        HANDLE_SLEEP_SUSPEND_THEN_HIBERNATE_MASK = 1U << HANDLE_SUSPEND_THEN_HIBERNATE,
+} HandleActionSleepMask;
+
+#define HANDLE_ACTION_SLEEP_MASK_DEFAULT (HANDLE_SLEEP_SUSPEND_THEN_HIBERNATE_MASK|HANDLE_SLEEP_SUSPEND_MASK|HANDLE_SLEEP_HIBERNATE_MASK)
+
 #include "logind-inhibit.h"
 #include "logind.h"
 #include "sleep-config.h"
@@ -55,6 +69,9 @@ struct HandleActionData {
         const char* log_verb;
 };
 
+int handle_action_get_enabled_sleep_actions(HandleActionSleepMask mask, char ***ret);
+HandleAction handle_action_sleep_select(HandleActionSleepMask mask);
+
 int manager_handle_action(
                 Manager *m,
                 InhibitWhat inhibit_key,
@@ -70,3 +87,5 @@ HandleAction handle_action_from_string(const char *s) _pure_;
 const HandleActionData* handle_action_lookup(HandleAction handle);
 
 CONFIG_PARSER_PROTOTYPE(config_parse_handle_action);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_handle_action_sleep);
index f15008e0df2e116095b927c1a547490353eb2060..becc21b0de6936cc6a56ec88ea83b13553d6409b 100644 (file)
@@ -40,6 +40,8 @@ void manager_reset_config(Manager *m) {
         m->inhibit_delay_max = 5 * USEC_PER_SEC;
         m->user_stop_delay = 10 * USEC_PER_SEC;
 
+        m->handle_action_sleep_mask = HANDLE_ACTION_SLEEP_MASK_DEFAULT;
+
         m->handle_power_key = HANDLE_POWEROFF;
         m->handle_power_key_long_press = HANDLE_IGNORE;
         m->handle_reboot_key = HANDLE_REBOOT;
index ae9b2cbf363303a974bbc2bf048e03b26b1a02c9..34992b5681059a00215754bd9aa1a589e61a444a 100644 (file)
@@ -342,6 +342,29 @@ static int property_get_preparing(
         return sd_bus_message_append(reply, "b", b);
 }
 
+static int property_get_sleep_operations(
+                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_strv_free_ char **actions = NULL;
+        int r;
+
+        assert(bus);
+        assert(reply);
+
+        r = handle_action_get_enabled_sleep_actions(m->handle_action_sleep_mask, &actions);
+        if (r < 0)
+                return r;
+
+        return sd_bus_message_append_strv(reply, actions);
+}
+
 static int property_get_scheduled_shutdown(
                 sd_bus *bus,
                 const char *path,
@@ -361,9 +384,10 @@ static int property_get_scheduled_shutdown(
         if (r < 0)
                 return r;
 
-        r = sd_bus_message_append(reply, "st",
-                m->scheduled_shutdown_action ? handle_action_to_string(m->scheduled_shutdown_action->handle) : NULL,
-                m->scheduled_shutdown_timeout);
+        r = sd_bus_message_append(
+                        reply, "st",
+                        handle_action_to_string(m->scheduled_shutdown_action),
+                        m->scheduled_shutdown_timeout);
         if (r < 0)
                 return r;
 
@@ -1993,7 +2017,7 @@ static int setup_wall_message_timer(Manager *m, sd_bus_message* message) {
 static int method_do_shutdown_or_sleep(
                 Manager *m,
                 sd_bus_message *message,
-                const HandleActionData *a,
+                HandleAction action,
                 bool with_flags,
                 sd_bus_error *error) {
 
@@ -2002,7 +2026,7 @@ static int method_do_shutdown_or_sleep(
 
         assert(m);
         assert(message);
-        assert(a);
+        assert(HANDLE_ACTION_IS_SHUTDOWN(action) || HANDLE_ACTION_IS_SLEEP(action));
 
         if (with_flags) {
                 /* New style method: with flags parameter (and interactive bool in the bus message header) */
@@ -2017,7 +2041,7 @@ static int method_do_shutdown_or_sleep(
                         return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS,
                                                 "Both reboot via kexec and soft reboot selected, which is not supported");
 
-                if (a->handle != HANDLE_REBOOT) {
+                if (action != HANDLE_REBOOT) {
                         if (flags & SD_LOGIND_REBOOT_VIA_KEXEC)
                                 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS,
                                                         "Reboot via kexec option is only applicable with reboot operations");
@@ -2038,20 +2062,32 @@ static int method_do_shutdown_or_sleep(
                 flags = interactive ? SD_LOGIND_INTERACTIVE : 0;
         }
 
+        const HandleActionData *a = NULL;
+
         if ((flags & SD_LOGIND_SOFT_REBOOT) ||
             ((flags & SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP) && path_is_os_tree("/run/nextroot") > 0))
                 a = handle_action_lookup(HANDLE_SOFT_REBOOT);
         else if ((flags & SD_LOGIND_REBOOT_VIA_KEXEC) && kexec_loaded())
                 a = handle_action_lookup(HANDLE_KEXEC);
 
-        /* Don't allow multiple jobs being executed at the same time */
-        if (m->delayed_action)
-                return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
-                                         "There's already a shutdown or sleep operation in progress");
+        if (action == HANDLE_SLEEP) {
+                HandleAction selected;
 
-        if (a->sleep_operation >= 0) {
+                selected = handle_action_sleep_select(m->handle_action_sleep_mask);
+                if (selected < 0)
+                        return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
+                                                "None of the configured sleep operations are supported");
+
+                assert_se(a = handle_action_lookup(selected));
+
+        } else if (HANDLE_ACTION_IS_SLEEP(action)) {
                 SleepSupport support;
 
+                assert_se(a = handle_action_lookup(action));
+
+                assert(a->sleep_operation >= 0);
+                assert(a->sleep_operation < _SLEEP_OPERATION_MAX);
+
                 r = sleep_supported_full(a->sleep_operation, &support);
                 if (r < 0)
                         return r;
@@ -2082,7 +2118,8 @@ static int method_do_shutdown_or_sleep(
                                 assert_not_reached();
 
                         }
-        }
+        } else if (!a)
+                assert_se(a = handle_action_lookup(action));
 
         r = verify_shutdown_creds(m, message, a, flags, error);
         if (r != 0)
@@ -2093,7 +2130,7 @@ static int method_do_shutdown_or_sleep(
         reset_scheduled_shutdown(m);
 
         m->scheduled_shutdown_timeout = 0;
-        m->scheduled_shutdown_action = a;
+        m->scheduled_shutdown_action = action;
 
         (void) setup_wall_message_timer(m, message);
 
@@ -2109,7 +2146,7 @@ static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error
 
         return method_do_shutdown_or_sleep(
                         m, message,
-                        handle_action_lookup(HANDLE_POWEROFF),
+                        HANDLE_POWEROFF,
                         sd_bus_message_is_method_call(message, NULL, "PowerOffWithFlags"),
                         error);
 }
@@ -2119,7 +2156,7 @@ static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *
 
         return method_do_shutdown_or_sleep(
                         m, message,
-                        handle_action_lookup(HANDLE_REBOOT),
+                        HANDLE_REBOOT,
                         sd_bus_message_is_method_call(message, NULL, "RebootWithFlags"),
                         error);
 }
@@ -2129,7 +2166,7 @@ static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *er
 
         return method_do_shutdown_or_sleep(
                         m, message,
-                        handle_action_lookup(HANDLE_HALT),
+                        HANDLE_HALT,
                         sd_bus_message_is_method_call(message, NULL, "HaltWithFlags"),
                         error);
 }
@@ -2139,7 +2176,7 @@ static int method_suspend(sd_bus_message *message, void *userdata, sd_bus_error
 
         return method_do_shutdown_or_sleep(
                         m, message,
-                        handle_action_lookup(HANDLE_SUSPEND),
+                        HANDLE_SUSPEND,
                         sd_bus_message_is_method_call(message, NULL, "SuspendWithFlags"),
                         error);
 }
@@ -2149,7 +2186,7 @@ static int method_hibernate(sd_bus_message *message, void *userdata, sd_bus_erro
 
         return method_do_shutdown_or_sleep(
                         m, message,
-                        handle_action_lookup(HANDLE_HIBERNATE),
+                        HANDLE_HIBERNATE,
                         sd_bus_message_is_method_call(message, NULL, "HibernateWithFlags"),
                         error);
 }
@@ -2159,7 +2196,7 @@ static int method_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_e
 
         return method_do_shutdown_or_sleep(
                         m, message,
-                        handle_action_lookup(HANDLE_HYBRID_SLEEP),
+                        HANDLE_HYBRID_SLEEP,
                         sd_bus_message_is_method_call(message, NULL, "HybridSleepWithFlags"),
                         error);
 }
@@ -2169,11 +2206,21 @@ static int method_suspend_then_hibernate(sd_bus_message *message, void *userdata
 
         return method_do_shutdown_or_sleep(
                         m, message,
-                        handle_action_lookup(HANDLE_SUSPEND_THEN_HIBERNATE),
+                        HANDLE_SUSPEND_THEN_HIBERNATE,
                         sd_bus_message_is_method_call(message, NULL, "SuspendThenHibernateWithFlags"),
                         error);
 }
 
+static int method_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        Manager *m = userdata;
+
+        return method_do_shutdown_or_sleep(
+                        m, message,
+                        HANDLE_SLEEP,
+                        /* with_flags = */ true,
+                        error);
+}
+
 static int nologin_timeout_handler(
                         sd_event_source *s,
                         uint64_t usec,
@@ -2232,7 +2279,7 @@ void manager_load_scheduled_shutdown(Manager *m) {
                 return;
 
         /* assign parsed type only after we know usec is also valid */
-        m->scheduled_shutdown_action = handle_action_lookup(handle);
+        m->scheduled_shutdown_action = handle;
 
         if (warn_wall) {
                 r = parse_boolean(warn_wall);
@@ -2275,7 +2322,7 @@ static int update_schedule_file(Manager *m) {
         int r;
 
         assert(m);
-        assert(m->scheduled_shutdown_action);
+        assert(handle_action_valid(m->scheduled_shutdown_action));
 
         r = mkdir_parents_label(SHUTDOWN_SCHEDULE_FILE, 0755);
         if (r < 0)
@@ -2289,7 +2336,7 @@ static int update_schedule_file(Manager *m) {
 
         serialize_usec(f, "USEC", m->scheduled_shutdown_timeout);
         serialize_item_format(f, "WARN_WALL", "%s", one_zero(m->enable_wall_messages));
-        serialize_item_format(f, "MODE", "%s", handle_action_to_string(m->scheduled_shutdown_action->handle));
+        serialize_item_format(f, "MODE", "%s", handle_action_to_string(m->scheduled_shutdown_action));
         serialize_item_format(f, "UID", UID_FMT, m->scheduled_shutdown_uid);
 
         if (m->scheduled_shutdown_tty)
@@ -2326,7 +2373,7 @@ static void reset_scheduled_shutdown(Manager *m) {
         m->wall_message_timeout_source = sd_event_source_unref(m->wall_message_timeout_source);
         m->nologin_timeout_source = sd_event_source_unref(m->nologin_timeout_source);
 
-        m->scheduled_shutdown_action = NULL;
+        m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID;
         m->scheduled_shutdown_timeout = USEC_INFINITY;
         m->scheduled_shutdown_uid = UID_INVALID;
         m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty);
@@ -2345,13 +2392,12 @@ static int manager_scheduled_shutdown_handler(
                         uint64_t usec,
                         void *userdata) {
 
-        const HandleActionData *a = NULL;
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         Manager *m = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        const HandleActionData *a;
         int r;
 
-        a = m->scheduled_shutdown_action;
-        assert(a);
+        assert_se(a = handle_action_lookup(m->scheduled_shutdown_action));
 
         /* Don't allow multiple jobs being executed at the same time */
         if (m->delayed_action) {
@@ -2373,7 +2419,7 @@ static int manager_scheduled_shutdown_handler(
                 return 0;
         }
 
-        r = bus_manager_shutdown_or_sleep_now_or_later(m, m->scheduled_shutdown_action, &error);
+        r = bus_manager_shutdown_or_sleep_now_or_later(m, a, &error);
         if (r < 0) {
                 log_error_errno(r, "Scheduled shutdown to %s failed: %m", a->target);
                 goto error;
@@ -2410,15 +2456,14 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
         if (!HANDLE_ACTION_IS_SHUTDOWN(handle))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type: %s", type);
 
-        a = handle_action_lookup(handle);
-        assert(a);
+        assert_se(a = handle_action_lookup(handle));
         assert(a->polkit_action);
 
         r = verify_shutdown_creds(m, message, a, 0, error);
         if (r != 0)
                 return r;
 
-        m->scheduled_shutdown_action = a;
+        m->scheduled_shutdown_action = handle;
         m->shutdown_dry_run = dry_run;
         m->scheduled_shutdown_timeout = elapse;
 
@@ -2474,12 +2519,11 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd
 
         assert(message);
 
-        cancelled = m->scheduled_shutdown_action
-                && !IN_SET(m->scheduled_shutdown_action->handle, HANDLE_IGNORE, _HANDLE_ACTION_INVALID);
+        cancelled = handle_action_valid(m->scheduled_shutdown_action) && m->scheduled_shutdown_action != HANDLE_IGNORE;
         if (!cancelled)
                 return sd_bus_reply_method_return(message, "b", false);
 
-        a = m->scheduled_shutdown_action;
+        assert_se(a = handle_action_lookup(m->scheduled_shutdown_action));
         if (!a->polkit_action)
                 return sd_bus_error_set(error, SD_BUS_ERROR_AUTH_FAILED, "Unsupported shutdown type");
 
@@ -2528,28 +2572,44 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd
 static int method_can_shutdown_or_sleep(
                 Manager *m,
                 sd_bus_message *message,
-                const HandleActionData *a,
+                HandleAction action,
                 sd_bus_error *error) {
 
         _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
         bool multiple_sessions, challenge, blocked;
+        const HandleActionData *a;
         const char *result = NULL;
         uid_t uid;
         int r;
 
         assert(m);
         assert(message);
-        assert(a);
+        assert(HANDLE_ACTION_IS_SHUTDOWN(action) || HANDLE_ACTION_IS_SLEEP(action));
+
+        if (action == HANDLE_SLEEP) {
+                HandleAction selected;
+
+                selected = handle_action_sleep_select(m->handle_action_sleep_mask);
+                if (selected < 0)
+                        return sd_bus_reply_method_return(message, "s", "na");
 
-        if (a->sleep_operation >= 0) {
+                assert_se(a = handle_action_lookup(selected));
+
+        } else if (HANDLE_ACTION_IS_SLEEP(action)) {
                 SleepSupport support;
 
+                assert_se(a = handle_action_lookup(action));
+
+                assert(a->sleep_operation >= 0);
+                assert(a->sleep_operation < _SLEEP_OPERATION_MAX);
+
                 r = sleep_supported_full(a->sleep_operation, &support);
                 if (r < 0)
                         return r;
                 if (r == 0)
                         return sd_bus_reply_method_return(message, "s", support == SLEEP_DISABLED ? "no" : "na");
-        }
+        } else
+                assert_se(a = handle_action_lookup(action));
 
         r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
         if (r < 0)
@@ -2566,22 +2626,16 @@ static int method_can_shutdown_or_sleep(
         multiple_sessions = r > 0;
         blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL);
 
-        HandleAction handle = handle_action_from_string(sleep_operation_to_string(a->sleep_operation));
-        if (handle >= 0) {
-                const char *target;
+        if (a->target) {
+                _cleanup_free_ char *load_state = NULL;
 
-                target = handle_action_lookup(handle)->target;
-                if (target) {
-                        _cleanup_free_ char *load_state = NULL;
-
-                        r = unit_load_state(m->bus, target, &load_state);
-                        if (r < 0)
-                                return r;
+                r = unit_load_state(m->bus, a->target, &load_state);
+                if (r < 0)
+                        return r;
 
-                        if (!streq(load_state, "loaded")) {
-                                result = "no";
-                                goto finish;
-                        }
+                if (!streq(load_state, "loaded")) {
+                        result = "no";
+                        goto finish;
                 }
         }
 
@@ -2636,57 +2690,49 @@ static int method_can_shutdown_or_sleep(
 static int method_can_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = userdata;
 
-        return method_can_shutdown_or_sleep(
-                        m, message, handle_action_lookup(HANDLE_POWEROFF),
-                        error);
+        return method_can_shutdown_or_sleep(m, message, HANDLE_POWEROFF, error);
 }
 
 static int method_can_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = userdata;
 
-        return method_can_shutdown_or_sleep(
-                        m, message, handle_action_lookup(HANDLE_REBOOT),
-                        error);
+        return method_can_shutdown_or_sleep(m, message, HANDLE_REBOOT, error);
 }
 
 static int method_can_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = userdata;
 
-        return method_can_shutdown_or_sleep(
-                        m, message, handle_action_lookup(HANDLE_HALT),
-                        error);
+        return method_can_shutdown_or_sleep(m, message, HANDLE_HALT, error);
 }
 
 static int method_can_suspend(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = userdata;
 
-        return method_can_shutdown_or_sleep(
-                        m, message, handle_action_lookup(HANDLE_SUSPEND),
-                        error);
+        return method_can_shutdown_or_sleep(m, message, HANDLE_SUSPEND, error);
 }
 
 static int method_can_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = userdata;
 
-        return method_can_shutdown_or_sleep(
-                        m, message, handle_action_lookup(HANDLE_HIBERNATE),
-                        error);
+        return method_can_shutdown_or_sleep(m, message, HANDLE_HIBERNATE, error);
 }
 
 static int method_can_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = userdata;
 
-        return method_can_shutdown_or_sleep(
-                        m, message, handle_action_lookup(HANDLE_HYBRID_SLEEP),
-                        error);
+        return method_can_shutdown_or_sleep(m, message, HANDLE_HYBRID_SLEEP, error);
 }
 
 static int method_can_suspend_then_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = userdata;
 
-        return method_can_shutdown_or_sleep(
-                        m, message, handle_action_lookup(HANDLE_SUSPEND_THEN_HIBERNATE),
-                        error);
+        return method_can_shutdown_or_sleep(m, message, HANDLE_SUSPEND_THEN_HIBERNATE, error);
+}
+
+static int method_can_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        Manager *m = userdata;
+
+        return method_can_shutdown_or_sleep(m, message, HANDLE_SLEEP, error);
 }
 
 static int property_get_reboot_parameter(
@@ -3521,6 +3567,7 @@ static const sd_bus_vtable manager_vtable[] = {
         SD_BUS_PROPERTY("DelayInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("InhibitDelayMaxUSec", "t", NULL, offsetof(Manager, inhibit_delay_max), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("UserStopDelayUSec", "t", NULL, offsetof(Manager, user_stop_delay), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("SleepOperation", "as", property_get_sleep_operations, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("HandlePowerKey", "s", property_get_handle_action, offsetof(Manager, handle_power_key), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("HandlePowerKeyLongPress", "s", property_get_handle_action, offsetof(Manager, handle_power_key_long_press), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("HandleRebootKey", "s", property_get_handle_action, offsetof(Manager, handle_reboot_key), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -3792,6 +3839,11 @@ static const sd_bus_vtable manager_vtable[] = {
                                 SD_BUS_NO_RESULT,
                                 method_suspend_then_hibernate,
                                 SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("Sleep",
+                                SD_BUS_ARGS("t", flags),
+                                SD_BUS_NO_RESULT,
+                                method_sleep,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD_WITH_ARGS("CanPowerOff",
                                 SD_BUS_NO_ARGS,
                                 SD_BUS_RESULT("s", result),
@@ -3827,6 +3879,11 @@ static const sd_bus_vtable manager_vtable[] = {
                                 SD_BUS_RESULT("s", result),
                                 method_can_suspend_then_hibernate,
                                 SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("CanSleep",
+                                SD_BUS_NO_ARGS,
+                                SD_BUS_RESULT("s", result),
+                                method_can_sleep,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD_WITH_ARGS("ScheduleShutdown",
                                 SD_BUS_ARGS("s", type, "t", usec),
                                 SD_BUS_NO_RESULT,
index c95a3b2dc3af48f3267a98b8c6157770647823f5..7d1520b469ffb33418a449e3841f8b75bfec0cfe 100644 (file)
@@ -25,6 +25,7 @@ Login.KillOnlyUsers,                config_parse_strv,                  0, offse
 Login.KillExcludeUsers,             config_parse_strv,                  0, offsetof(Manager, kill_exclude_users)
 Login.InhibitDelayMaxSec,           config_parse_sec,                   0, offsetof(Manager, inhibit_delay_max)
 Login.UserStopDelaySec,             config_parse_sec,                   0, offsetof(Manager, user_stop_delay)
+Login.SleepOperation,               config_parse_handle_action_sleep,   0, offsetof(Manager, handle_action_sleep_mask)
 Login.HandlePowerKey,               config_parse_handle_action,         0, offsetof(Manager, handle_power_key)
 Login.HandlePowerKeyLongPress,      config_parse_handle_action,         0, offsetof(Manager, handle_power_key_long_press)
 Login.HandleRebootKey,              config_parse_handle_action,         0, offsetof(Manager, handle_reboot_key)
index 97b74e9e042d5b56613848fd2b7bfec3e8bf6aea..ff70a595f1577f472548ba173247e5295fd0fbc4 100644 (file)
@@ -42,7 +42,7 @@ static usec_t when_wall(usec_t n, usec_t elapse) {
 bool logind_wall_tty_filter(const char *tty, bool is_local, void *userdata) {
         Manager *m = ASSERT_PTR(userdata);
 
-        assert(m->scheduled_shutdown_action);
+        assert(handle_action_valid(m->scheduled_shutdown_action));
 
         const char *p = path_startswith(tty, "/dev/");
         if (!p)
@@ -52,7 +52,7 @@ bool logind_wall_tty_filter(const char *tty, bool is_local, void *userdata) {
          * can assume that if the system enters sleep or hibernation, this will be visible in an obvious way
          * for any local user. And once the systems exits sleep or hibernation, the notification would be
          * just noise, in particular for auto-suspend. */
-        if (is_local && HANDLE_ACTION_IS_SLEEP(m->scheduled_shutdown_action->handle))
+        if (is_local && HANDLE_ACTION_IS_SLEEP(m->scheduled_shutdown_action))
                 return false;
 
         return !streq_ptr(p, m->scheduled_shutdown_tty);
@@ -61,7 +61,7 @@ bool logind_wall_tty_filter(const char *tty, bool is_local, void *userdata) {
 static int warn_wall(Manager *m, usec_t n) {
         assert(m);
 
-        if (!m->scheduled_shutdown_action)
+        if (!handle_action_valid(m->scheduled_shutdown_action))
                 return 0;
 
         bool left = m->scheduled_shutdown_timeout > n;
@@ -70,7 +70,7 @@ static int warn_wall(Manager *m, usec_t n) {
         if (asprintf(&l, "%s%sThe system will %s %s%s!",
                      strempty(m->wall_message),
                      isempty(m->wall_message) ? "" : "\n",
-                     handle_action_verb_to_string(m->scheduled_shutdown_action->handle),
+                     handle_action_verb_to_string(m->scheduled_shutdown_action),
                      left ? "at " : "now",
                      left ? FORMAT_TIMESTAMP(m->scheduled_shutdown_timeout) : "") < 0) {
 
@@ -84,7 +84,7 @@ static int warn_wall(Manager *m, usec_t n) {
 
         log_struct(level,
                    LOG_MESSAGE("%s", l),
-                   "ACTION=%s", handle_action_to_string(m->scheduled_shutdown_action->handle),
+                   "ACTION=%s", handle_action_to_string(m->scheduled_shutdown_action),
                    "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_SCHEDULED_STR,
                    username ? "OPERATOR=%s" : NULL, username);
 
@@ -134,7 +134,7 @@ int manager_setup_wall_message_timer(Manager *m) {
 
         /* wall message handling */
 
-        if (!m->scheduled_shutdown_action)
+        if (!handle_action_valid(m->scheduled_shutdown_action))
                 return 0;
 
         if (elapse > 0 && elapse < n)
index 88e05bb769c0c44f618ab6660aff6b75403d5786..965e2a4aefe9cfcb251b3fbdf5204f74a878dd01 100644 (file)
@@ -65,17 +65,18 @@ static int manager_new(Manager **ret) {
                 .reserve_vt_fd = -EBADF,
                 .enable_wall_messages = true,
                 .idle_action_not_before_usec = now(CLOCK_MONOTONIC),
-        };
+                .scheduled_shutdown_action = _HANDLE_ACTION_INVALID,
 
-        m->devices = hashmap_new(&device_hash_ops);
-        m->seats = hashmap_new(&seat_hash_ops);
-        m->sessions = hashmap_new(&session_hash_ops);
-        m->users = hashmap_new(&user_hash_ops);
-        m->inhibitors = hashmap_new(&inhibitor_hash_ops);
-        m->buttons = hashmap_new(&button_hash_ops);
+                .devices = hashmap_new(&device_hash_ops),
+                .seats = hashmap_new(&seat_hash_ops),
+                .sessions = hashmap_new(&session_hash_ops),
+                .users = hashmap_new(&user_hash_ops),
+                .inhibitors = hashmap_new(&inhibitor_hash_ops),
+                .buttons = hashmap_new(&button_hash_ops),
 
-        m->user_units = hashmap_new(&string_hash_ops);
-        m->session_units = hashmap_new(&string_hash_ops);
+                .user_units = hashmap_new(&string_hash_ops),
+                .session_units = hashmap_new(&string_hash_ops),
+        };
 
         if (!m->devices || !m->seats || !m->sessions || !m->users || !m->inhibitors || !m->buttons || !m->user_units || !m->session_units)
                 return -ENOMEM;
index e5fe924681134933f139b3b7cdd9159955e0aeda..b62458ec3ca0737650702ecc3d9c4f0d8c39a0c2 100644 (file)
@@ -24,6 +24,7 @@
 #KillExcludeUsers=root
 #InhibitDelayMaxSec=5
 #UserStopDelaySec=10
+#SleepOperation=suspend-then-hibernate suspend
 #HandlePowerKey=poweroff
 #HandlePowerKeyLongPress=ignore
 #HandleRebootKey=reboot
index 7532d379c01cb615305b9749a2afdc59709b5b63..cac6a3035731c05fc0e52a1832b4332b51c06686 100644 (file)
@@ -76,7 +76,7 @@ struct Manager {
         char *action_job;
         sd_event_source *inhibit_timeout_source;
 
-        const HandleActionData *scheduled_shutdown_action;
+        HandleAction scheduled_shutdown_action;
         usec_t scheduled_shutdown_timeout;
         sd_event_source *scheduled_shutdown_timeout_source;
         uid_t scheduled_shutdown_uid;
@@ -98,6 +98,8 @@ struct Manager {
 
         usec_t stop_idle_session_usec;
 
+        HandleActionSleepMask handle_action_sleep_mask;
+
         HandleAction handle_power_key;
         HandleAction handle_power_key_long_press;
         HandleAction handle_reboot_key;
index 3c5ec04271f562bce0dbb1b09875efc1c396f64a..e9ff3085ecba43439ada633c5729f973e5e95e2e 100644 (file)
@@ -2,9 +2,27 @@
 
 #include "logind-action.h"
 #include "logind-session.h"
+#include "sleep-config.h"
 #include "test-tables.h"
 #include "tests.h"
 
+static void test_sleep_handle_action(void) {
+        for (HandleAction action = _HANDLE_ACTION_SLEEP_FIRST; action < _HANDLE_ACTION_SLEEP_LAST; action++) {
+                const HandleActionData *data;
+                const char *sleep_operation_str, *handle_action_str;
+
+                if (action == HANDLE_SLEEP)
+                        continue;
+
+                assert_se(data = handle_action_lookup(action));
+
+                assert_se(handle_action_str = handle_action_to_string(action));
+                assert_se(sleep_operation_str = sleep_operation_to_string(data->sleep_operation));
+
+                assert_se(streq(handle_action_str, sleep_operation_str));
+        }
+}
+
 int main(int argc, char **argv) {
         test_setup_logging(LOG_DEBUG);
 
@@ -16,5 +34,7 @@ int main(int argc, char **argv) {
         test_table(session_type, SESSION_TYPE);
         test_table(user_state, USER_STATE);
 
+        test_sleep_handle_action();
+
         return EXIT_SUCCESS;
 }
index 268e528856a382d4fb541c8083c33cc20cb08a13..2e35413b5fd2ccacbb6f3affa196a1700bcf2f27 100644 (file)
@@ -51,6 +51,7 @@ int logind_reboot(enum action a) {
                 [ACTION_HIBERNATE]              = "Hibernate",
                 [ACTION_HYBRID_SLEEP]           = "HybridSleep",
                 [ACTION_SUSPEND_THEN_HIBERNATE] = "SuspendThenHibernate",
+                [ACTION_SLEEP]                  = "Sleep",
         };
 
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -71,7 +72,7 @@ int logind_reboot(enum action a) {
         polkit_agent_open_maybe();
         (void) logind_set_wall_message(bus);
 
-        const char *method_with_flags = strjoina(actions[a], "WithFlags");
+        const char *method_with_flags = a == ACTION_SLEEP ? actions[a] : strjoina(actions[a], "WithFlags");
 
         log_debug("%s org.freedesktop.login1.Manager %s dbus call.",
                   arg_dry_run ? "Would execute" : "Executing", method_with_flags);
@@ -103,7 +104,7 @@ int logind_reboot(enum action a) {
         }
         if (r >= 0)
                 return 0;
-        if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD))
+        if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD) || a == ACTION_SLEEP)
                 return log_error_errno(r, "Call to %s failed: %s", actions[a], bus_error_message(&error, r));
 
         /* Fall back to original methods in case there is an older version of systemd-logind */
index d93bffb75914d65633c699d2d81b6918d9ac21db..2cf746c5a6057d7b5e424b9fa67e3013c1b0f453 100644 (file)
@@ -229,6 +229,9 @@ int verb_start_special(int argc, char *argv[], void *userdata) {
                         arg_no_block = true;
                         break;
 
+                case ACTION_SLEEP:
+                        return logind_reboot(a);
+
                 case ACTION_EXIT:
                         /* Since exit is so close in behaviour to power-off/reboot, let's also make
                          * it asynchronous, in order to not confuse the user needlessly with unexpected
index 6927e9774792870c63b7ab16321b8780ccdc692e..ae7e25eedb626003d11b5780b9a3770887d37d65 100644 (file)
@@ -236,6 +236,7 @@ const struct action_metadata action_table[_ACTION_MAX] = {
         [ACTION_HIBERNATE]              = { SPECIAL_HIBERNATE_TARGET,              "hibernate",              "replace-irreversibly" },
         [ACTION_HYBRID_SLEEP]           = { SPECIAL_HYBRID_SLEEP_TARGET,           "hybrid-sleep",           "replace-irreversibly" },
         [ACTION_SUSPEND_THEN_HIBERNATE] = { SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, "suspend-then-hibernate", "replace-irreversibly" },
+        [ACTION_SLEEP]                  = { NULL, /* handled only by logind */     "sleep",                  NULL                   },
 };
 
 enum action verb_to_action(const char *verb) {
@@ -294,6 +295,8 @@ int verb_start(int argc, char *argv[], void *userdata) {
 
                 action = verb_to_action(argv[0]);
 
+                assert(action != ACTION_SLEEP);
+
                 if (action != _ACTION_INVALID) {
                         /* A command in style "systemctl reboot", "systemctl poweroff", … */
                         method = "StartUnit";
index dd6f6c9873e25e611ab703928c7ea7c79fb48c96..0fa672eb905314cac10229f772481e789f921ac5 100644 (file)
@@ -249,6 +249,8 @@ static int systemctl_help(void) {
                "  soft-reboot                         Shut down and reboot userspace\n"
                "  exit [EXIT_CODE]                    Request user instance or container exit\n"
                "  switch-root [ROOT [INIT]]           Change to a different root file system\n"
+               "  sleep                               Put the system to sleep (through one of\n"
+               "                                      the operations below)\n"
                "  suspend                             Suspend the system\n"
                "  hibernate                           Hibernate the system\n"
                "  hybrid-sleep                        Hibernate and suspend the system\n"
@@ -1179,6 +1181,7 @@ static int systemctl_main(int argc, char *argv[]) {
                 { "reboot",                VERB_ANY, 1,        VERB_ONLINE_ONLY, verb_start_system_special    },
                 { "kexec",                 VERB_ANY, 1,        VERB_ONLINE_ONLY, verb_start_system_special    },
                 { "soft-reboot",           VERB_ANY, 1,        VERB_ONLINE_ONLY, verb_start_system_special    },
+                { "sleep",                 VERB_ANY, 1,        VERB_ONLINE_ONLY, verb_start_system_special    },
                 { "suspend",               VERB_ANY, 1,        VERB_ONLINE_ONLY, verb_start_system_special    },
                 { "hibernate",             VERB_ANY, 1,        VERB_ONLINE_ONLY, verb_start_system_special    },
                 { "hybrid-sleep",          VERB_ANY, 1,        VERB_ONLINE_ONLY, verb_start_system_special    },
@@ -1323,6 +1326,7 @@ static int run(int argc, char *argv[]) {
                 break;
 
         case ACTION_EXIT:
+        case ACTION_SLEEP:
         case ACTION_SUSPEND:
         case ACTION_HIBERNATE:
         case ACTION_HYBRID_SLEEP:
index e8ba8f76a07e9b8bc8edeaf0bf464d187c496504..a42fc36a2e481297cee9bd40902a2e7c188f5caa 100644 (file)
@@ -18,6 +18,7 @@ enum action {
         ACTION_KEXEC,
         ACTION_SOFT_REBOOT,
         ACTION_EXIT,
+        ACTION_SLEEP,
         ACTION_SUSPEND,
         ACTION_HIBERNATE,
         ACTION_HYBRID_SLEEP,
index 02673ab29e15a8924cfcd620fddc04a21af2885a..0b0dfc6c8314c8f02f9440e5de0c65d62c450beb 100755 (executable)
@@ -28,7 +28,9 @@ systemctl log-level info
 # FIXME: systemd-run doesn't play well with daemon-reexec
 # See: https://github.com/systemd/systemd/issues/27204
 sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:Reexecute FIXME' /etc/dfuzzer.conf
+
 sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:SoftReboot destructive' /etc/dfuzzer.conf
+sed -i '/\[org.freedesktop.login1\]/aSleep destructive' /etc/dfuzzer.conf
 
 # TODO
 #   * check for possibly newly introduced buses?
index 36e26da885a28fabfd0cf9a773484110fdaf4a35..b250a5c18fb1527f1e793247f79783291044b091 100755 (executable)
@@ -57,6 +57,25 @@ EOF
     rm -rf /run/systemd/logind.conf.d
 }
 
+testcase_sleep_automated() {
+    assert_eq "$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager SleepOperation)" 'as 3 "suspend-then-hibernate" "suspend" "hibernate"'
+
+    mkdir -p /run/systemd/logind.conf.d
+
+    cat >/run/systemd/logind.conf.d/sleep-operations.conf <<EOF
+[Login]
+SleepOperation=suspend hybrid-sleep
+EOF
+
+    systemctl restart systemd-logind.service
+
+    assert_eq "$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager SleepOperation)" 'as 2 "hybrid-sleep" "suspend"'
+
+    busctl call org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager CanSleep
+
+    rm -rf /run/systemd/logind.conf.d
+}
+
 testcase_started() {
     local pid