diff options
author | Lennart Poettering <lennart@poettering.net> | 2012-05-05 00:34:48 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2012-05-05 00:36:08 +0200 |
commit | eecd1362f7f4de432483b5d77c56726c3621a83a (patch) | |
tree | e8f1f3639121d317660cc6cc0d5ffe8f8731a39f /src | |
parent | a26336da875a6657d404d1e44b86ae067c34b110 (diff) |
logind: implement delay inhibitor locks in addition to block inhibitor locks
This is useful to allow applications to synchronously save data before
the system is suspended or shut down.
Diffstat (limited to 'src')
-rw-r--r-- | src/login/inhibit.c | 353 | ||||
-rw-r--r-- | src/login/logind-dbus.c | 196 | ||||
-rw-r--r-- | src/login/logind-gperf.gperf | 1 | ||||
-rw-r--r-- | src/login/logind-inhibit.c | 47 | ||||
-rw-r--r-- | src/login/logind-inhibit.h | 15 | ||||
-rw-r--r-- | src/login/logind.c | 21 | ||||
-rw-r--r-- | src/login/logind.conf | 1 | ||||
-rw-r--r-- | src/login/logind.h | 10 | ||||
-rw-r--r-- | src/login/org.freedesktop.login1.policy.in | 32 | ||||
-rw-r--r-- | src/shared/dbus-common.c | 11 | ||||
-rw-r--r-- | src/shared/dbus-common.h | 1 |
11 files changed, 623 insertions, 65 deletions
diff --git a/src/login/inhibit.c b/src/login/inhibit.c new file mode 100644 index 0000000000..a817c84b36 --- /dev/null +++ b/src/login/inhibit.c @@ -0,0 +1,353 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <getopt.h> +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <dbus.h> +#include <unistd.h> + +#include "dbus-common.h" +#include "util.h" +#include "build.h" +#include "strv.h" + +static const char* arg_what = "idle:suspend:shutdown"; +static const char* arg_who = NULL; +static const char* arg_why = "Unknown reason"; +static const char* arg_mode = "block"; + +static enum { + ACTION_INHIBIT, + ACTION_LIST +} arg_action = ACTION_INHIBIT; + +static int inhibit(DBusConnection *bus, DBusError *error) { + DBusMessage *m = NULL, *reply = NULL; + int fd; + + assert(bus); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "Inhibit"); + if (!m) + return -ENOMEM; + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &arg_what, + DBUS_TYPE_STRING, &arg_who, + DBUS_TYPE_STRING, &arg_why, + DBUS_TYPE_STRING, &arg_mode, + DBUS_TYPE_INVALID)) { + fd = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); + if (!reply) { + fd = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, error, + DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_INVALID)){ + fd = -EIO; + goto finish; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return fd; +} + +static int print_inhibitors(DBusConnection *bus, DBusError *error) { + DBusMessage *m, *reply; + unsigned n = 0; + DBusMessageIter iter, sub, sub2; + int r; + + assert(bus); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ListInhibitors"); + if (!m) + return -ENOMEM; + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); + if (!reply) { + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter)) { + r = -ENOMEM; + goto finish; + } + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + r = -EIO; + goto finish; + } + dbus_message_iter_recurse(&iter, &sub); + + printf("%-21s %-20s %-20s %-5s %6s %6s\n", + "WHAT", + "WHO", + "WHY", + "MODE", + "UID", + "PID"); + + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *what, *who, *why, *mode; + char *ewho, *ewhy; + dbus_uint32_t uid, pid; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &what, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &who, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &why, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &mode, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &pid, false) < 0) { + r = -EIO; + goto finish; + } + + ewho = ellipsize(who, 20, 66); + ewhy = ellipsize(why, 20, 66); + + printf("%-21s %-20s %-20s %-5s %6lu %6lu\n", + what, ewho ? ewho : who, ewhy ? ewhy : why, mode, (unsigned long) uid, (unsigned long) pid); + + free(ewho); + free(ewhy); + + dbus_message_iter_next(&sub); + + n++; + } + + printf("\n%u inhibitors listed.\n", n); + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} + +static int help(void) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Execute a process while inhibiting shutdown/suspend/idle.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --what=WHAT Operations to inhibit, colon separated list of idle,\n" + " suspend, shutdown\n" + " --who=STRING A descriptive string who is inhibiting\n" + " --why=STRING A descriptive string why is being inhibited\n" + " --mode=MODE One of block or delay\n" + " --list List active inhibitors\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_WHAT, + ARG_WHO, + ARG_WHY, + ARG_MODE, + ARG_LIST, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "what", required_argument, NULL, ARG_WHAT }, + { "who", required_argument, NULL, ARG_WHO }, + { "why", required_argument, NULL, ARG_WHY }, + { "mode", required_argument, NULL, ARG_MODE }, + { "list", no_argument, NULL, ARG_LIST }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + return 0; + + case ARG_WHAT: + arg_what = optarg; + break; + + case ARG_WHO: + arg_who = optarg; + break; + + case ARG_WHY: + arg_why = optarg; + break; + + case ARG_MODE: + arg_mode = optarg; + break; + + case ARG_LIST: + arg_action = ACTION_LIST; + break; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (arg_action == ACTION_INHIBIT && optind >= argc) { + log_error("Missing command line to execute."); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, exit_code = 0; + DBusConnection *bus = NULL; + DBusError error; + int fd = -1; + + dbus_error_init(&error); + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (!bus) { + log_error("Failed to connect to bus: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (arg_action == ACTION_LIST) { + + r = print_inhibitors(bus, &error); + if (r < 0) { + log_error("Failed to list inhibitors: %s", bus_error_message_or_strerror(&error, -r)); + goto finish; + } + + } else { + char *w = NULL; + pid_t pid; + + if (!arg_who) + arg_who = w = strv_join(argv + optind, " "); + + fd = inhibit(bus, &error); + free(w); + + if (fd < 0) { + log_error("Failed to inhibit: %s", bus_error_message_or_strerror(&error, -r)); + r = fd; + goto finish; + } + + pid = fork(); + if (pid < 0) { + log_error("Failed to fork: %m"); + r = -errno; + goto finish; + } + + if (pid == 0) { + /* Child */ + + close_nointr_nofail(fd); + execvp(argv[optind], argv + optind); + log_error("Failed to execute %s: %m", argv[optind]); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate_and_warn(argv[optind], pid); + if (r >= 0) + exit_code = r; + } + +finish: + if (bus) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + dbus_error_free(&error); + + if (fd >= 0) + close_nointr_nofail(fd); + + return r < 0 ? EXIT_FAILURE : exit_code; +} diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 3fe6c872b7..a361b93dc7 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -144,10 +144,11 @@ " <arg name=\"what\" type=\"s\" direction=\"in\"/>\n" \ " <arg name=\"who\" type=\"s\" direction=\"in\"/>\n" \ " <arg name=\"why\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \ " <arg name=\"fd\" type=\"h\" direction=\"out\"/>\n" \ " </method>\n" \ " <method name=\"ListInhibitors\">\n" \ - " <arg name=\"inhibitors\" type=\"a(sssuu)\" direction=\"out\"/>\n" \ + " <arg name=\"inhibitors\" type=\"a(ssssuu)\" direction=\"out\"/>\n" \ " </method>\n" \ " <signal name=\"SessionNew\">\n" \ " <arg name=\"id\" type=\"s\"/>\n" \ @@ -173,6 +174,9 @@ " <arg name=\"id\" type=\"s\"/>\n" \ " <arg name=\"path\" type=\"o\"/>\n" \ " </signal>\n" \ + " <signal name=\"PrepareForShutdown\">\n" \ + " <arg name=\"active\" type=\"b\"/>\n" \ + " </signal>\n" \ " <property name=\"ControlGroupHierarchy\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Controllers\" type=\"as\" access=\"read\"/>\n" \ " <property name=\"ResetControllers\" type=\"as\" access=\"read\"/>\n" \ @@ -183,7 +187,9 @@ " <property name=\"IdleHint\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"IdleSinceHint\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"IdleSinceHintMonotonic\" type=\"t\" access=\"read\"/>\n" \ - " <property name=\"Inhibited\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"BlockInhibited\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"DelayInhibited\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"InhibitDelayMaxUSec\" type=\"t\" access=\"read\"/>\n" \ " </interface>\n" #define INTROSPECTION_BEGIN \ @@ -239,7 +245,7 @@ static int bus_manager_append_inhibited(DBusMessageIter *i, const char *property InhibitWhat w; const char *p; - w = manager_inhibit_what(m); + w = manager_inhibit_what(m, streq(property, "BlockInhibited") ? INHIBIT_BLOCK : INHIBIT_DELAY); p = inhibit_what_to_string(w); if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &p)) @@ -638,9 +644,10 @@ fail: static int bus_manager_inhibit(Manager *m, DBusConnection *connection, DBusMessage *message, DBusError *error, DBusMessage **_reply) { Inhibitor *i = NULL; char *id = NULL; - const char *who, *why, *what; + const char *who, *why, *what, *mode; pid_t pid; InhibitWhat w; + InhibitMode mm; unsigned long ul; int r, fifo_fd = -1; DBusMessage *reply = NULL; @@ -657,6 +664,7 @@ static int bus_manager_inhibit(Manager *m, DBusConnection *connection, DBusMessa DBUS_TYPE_STRING, &what, DBUS_TYPE_STRING, &who, DBUS_TYPE_STRING, &why, + DBUS_TYPE_STRING, &mode, DBUS_TYPE_INVALID)) { r = -EIO; goto fail; @@ -668,7 +676,16 @@ static int bus_manager_inhibit(Manager *m, DBusConnection *connection, DBusMessa goto fail; } - r = verify_polkit(connection, message, "org.freedesktop.login1.inhibit", false, NULL, error); + mm = inhibit_mode_from_string(mode); + if (mm < 0) { + r = -EINVAL; + goto fail; + } + + r = verify_polkit(connection, message, + m == INHIBIT_BLOCK ? + "org.freedesktop.login1.inhibit-block" : + "org.freedesktop.login1.inhibit-delay", false, NULL, error); if (r < 0) goto fail; @@ -701,6 +718,7 @@ static int bus_manager_inhibit(Manager *m, DBusConnection *connection, DBusMessa goto fail; i->what = w; + i->mode = mm; i->pid = pid; i->uid = (uid_t) ul; i->why = strdup(why); @@ -918,6 +936,76 @@ static int have_multiple_sessions( return false; } +static int send_start_unit(DBusConnection *connection, const char *name, DBusError *error) { + DBusMessage *message, *reply; + const char *mode = "replace"; + + assert(connection); + assert(name); + + message = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit"); + if (!message) + return -ENOMEM; + + if (!dbus_message_append_args(message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) { + dbus_message_unref(message); + return -ENOMEM; + } + + reply = dbus_connection_send_with_reply_and_block(connection, message, -1, error); + dbus_message_unref(message); + + if (!reply) + return -EIO; + + dbus_message_unref(reply); + return 0; +} + +static int send_prepare_for_shutdown(Manager *m, bool _active) { + dbus_bool_t active = _active; + DBusMessage *message; + int r = 0; + + assert(m); + + message = dbus_message_new_signal("/org/freedesktop/login1", "org.freedesktop.login1.Manager", "PrepareForShutdown"); + if (!message) + return -ENOMEM; + + if (!dbus_message_append_args(message, DBUS_TYPE_BOOLEAN, &active, DBUS_TYPE_INVALID) || + !dbus_connection_send(m->bus, message, NULL)) + r = -ENOMEM; + + dbus_message_unref(message); + return r; +} + +static int delay_shutdown(Manager *m, const char *name) { + assert(m); + + if (!m->delayed_shutdown) { + /* Tell everybody to prepare for shutdown */ + send_prepare_for_shutdown(m, true); + + /* Update timestamp for timeout */ + m->delayed_shutdown_timestamp = now(CLOCK_MONOTONIC); + } + + /* Remember what we want to do, possibly overriding what kind + * of shutdown we previously queued. */ + m->delayed_shutdown = name; + + return 0; +} + static const BusProperty bus_login_manager_properties[] = { { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_path), true }, { "Controllers", bus_property_append_strv, "as", offsetof(Manager, controllers), true }, @@ -929,7 +1017,9 @@ static const BusProperty bus_login_manager_properties[] = { { "IdleHint", bus_manager_append_idle_hint, "b", 0 }, { "IdleSinceHint", bus_manager_append_idle_hint_since, "t", 0 }, { "IdleSinceHintMonotonic", bus_manager_append_idle_hint_since, "t", 0 }, - { "Inhibited", bus_manager_append_inhibited, "s", 0 }, + { "BlockInhibited", bus_manager_append_inhibited, "s", 0 }, + { "DelayInhibited", bus_manager_append_inhibited, "s", 0 }, + { "InhibitDelayMaxUSec", bus_property_append_usec, "t", offsetof(Manager, inhibit_delay_max) }, { NULL, } }; @@ -1228,26 +1318,28 @@ static DBusHandlerResult manager_message_handler( dbus_message_iter_init_append(reply, &iter); - if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sssuu)", &sub)) + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ssssuu)", &sub)) goto oom; HASHMAP_FOREACH(inhibitor, m->inhibitors, i) { DBusMessageIter sub2; dbus_uint32_t uid, pid; - const char *what, *who, *why; + const char *what, *who, *why, *mode; if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) goto oom; - what = inhibit_what_to_string(inhibitor->what); + what = strempty(inhibit_what_to_string(inhibitor->what)); who = strempty(inhibitor->who); why = strempty(inhibitor->why); + mode = strempty(inhibit_mode_to_string(inhibitor->mode)); uid = (dbus_uint32_t) inhibitor->uid; pid = (dbus_uint32_t) inhibitor->pid; if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &what) || !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &who) || !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &why) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &mode) || !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &uid) || !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &pid)) goto oom; @@ -1641,10 +1733,8 @@ static DBusHandlerResult manager_message_handler( } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "PowerOff") || dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "Reboot")) { dbus_bool_t interactive; - bool multiple_sessions, inhibit; - DBusMessage *forward, *freply; + bool multiple_sessions, blocked, delayed; const char *name, *action; - const char *mode = "replace"; if (!dbus_message_get_args( message, @@ -1658,7 +1748,7 @@ static DBusHandlerResult manager_message_handler( return bus_send_error_reply(connection, message, &error, r); multiple_sessions = r > 0; - inhibit = manager_is_inhibited(m, INHIBIT_SHUTDOWN, NULL); + blocked = manager_is_inhibited(m, INHIBIT_SHUTDOWN, INHIBIT_BLOCK, NULL); if (multiple_sessions) { action = streq(dbus_message_get_member(message), "PowerOff") ? @@ -1670,7 +1760,7 @@ static DBusHandlerResult manager_message_handler( return bus_send_error_reply(connection, message, &error, r); } - if (inhibit) { + if (blocked) { action = streq(dbus_message_get_member(message), "PowerOff") ? "org.freedesktop.login1.power-off-ignore-inhibit" : "org.freedesktop.login1.reboot-ignore-inhibit"; @@ -1680,7 +1770,7 @@ static DBusHandlerResult manager_message_handler( return bus_send_error_reply(connection, message, &error, r); } - if (!multiple_sessions && !inhibit) { + if (!multiple_sessions && !blocked) { action = streq(dbus_message_get_member(message), "PowerOff") ? "org.freedesktop.login1.power-off" : "org.freedesktop.login1.reboot"; @@ -1690,32 +1780,26 @@ static DBusHandlerResult manager_message_handler( return bus_send_error_reply(connection, message, &error, r); } - forward = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnit"); - if (!forward) - return bus_send_error_reply(connection, message, NULL, -ENOMEM); - name = streq(dbus_message_get_member(message), "PowerOff") ? SPECIAL_POWEROFF_TARGET : SPECIAL_REBOOT_TARGET; - if (!dbus_message_append_args(forward, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &mode, - DBUS_TYPE_INVALID)) { - dbus_message_unref(forward); - return bus_send_error_reply(connection, message, NULL, -ENOMEM); - } - - freply = dbus_connection_send_with_reply_and_block(connection, forward, -1, &error); - dbus_message_unref(forward); + delayed = + m->inhibit_delay_max > 0 && + manager_is_inhibited(m, INHIBIT_SHUTDOWN, INHIBIT_DELAY, NULL); - if (!freply) - return bus_send_error_reply(connection, message, &error, -EIO); - - dbus_message_unref(freply); + if (delayed) { + /* Shutdown is delayed, keep in mind what we + * want to do, and start a timeout */ + r = delay_shutdown(m, name); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + } else { + /* Shutdown is not delayed, execute it + * immediately */ + r = send_start_unit(connection, name, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + } reply = dbus_message_new_method_return(message); if (!reply) @@ -1732,7 +1816,7 @@ static DBusHandlerResult manager_message_handler( return bus_send_error_reply(connection, message, &error, r); multiple_sessions = r > 0; - inhibit = manager_is_inhibited(m, INHIBIT_SHUTDOWN, NULL); + inhibit = manager_is_inhibited(m, INHIBIT_SHUTDOWN, INHIBIT_BLOCK, NULL); if (multiple_sessions) { action = streq(dbus_message_get_member(message), "CanPowerOff") ? @@ -1943,3 +2027,39 @@ finish: return r; } + +int manager_dispatch_delayed_shutdown(Manager *manager) { + const char *name; + DBusError error; + bool delayed; + int r; + + assert(manager); + + if (!manager->delayed_shutdown) + return 0; + + /* Continue delay? */ + delayed = + manager->delayed_shutdown_timestamp + manager->inhibit_delay_max > now(CLOCK_MONOTONIC) && + manager_is_inhibited(manager, INHIBIT_SHUTDOWN, INHIBIT_DELAY, NULL); + if (delayed) + return 0; + + /* Reset delay data */ + name = manager->delayed_shutdown; + manager->delayed_shutdown = NULL; + + /* Actually do the shutdown */ + dbus_error_init(&error); + r = send_start_unit(manager->bus, name, &error); + if (r < 0) { + log_warning("Failed to send delayed shutdown message: %s", bus_error_message_or_strerror(&error, -r)); + return r; + } + + /* Tell people about it */ + send_prepare_for_shutdown(manager, false); + + return 1; +} diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf index 940fe10e89..d8ef92a016 100644 --- a/src/login/logind-gperf.gperf +++ b/src/login/logind-gperf.gperf @@ -20,3 +20,4 @@ Login.KillOnlyUsers, config_parse_strv, 0, offsetof(Manager, kill_only_u Login.KillExcludeUsers, config_parse_strv, 0, offsetof(Manager, kill_exclude_users) Login.Controllers, config_parse_strv, 0, offsetof(Manager, controllers) Login.ResetControllers, config_parse_strv, 0, offsetof(Manager, reset_controllers) +Login.InhibitDelayMaxSec,config_parse_usec, 0, offsetof(Manager, inhibit_delay_max) diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c index 78afee3139..e4eefd0def 100644 --- a/src/login/logind-inhibit.c +++ b/src/login/logind-inhibit.c @@ -97,9 +97,11 @@ int inhibitor_save(Inhibitor *i) { fprintf(f, "# This is private data. Do not parse.\n" "WHAT=%s\n" + "MODE=%s\n" "UID=%lu\n" "PID=%lu\n", inhibit_what_to_string(i->what), + inhibit_mode_to_string(i->mode), (unsigned long) i->uid, (unsigned long) i->pid); @@ -152,9 +154,10 @@ int inhibitor_start(Inhibitor *i) { dual_timestamp_get(&i->since); - log_debug("Inhibitor %s (%s) pid=%lu uid=%lu started.", + log_debug("Inhibitor %s (%s) pid=%lu uid=%lu mode=%s started.", strna(i->who), strna(i->why), - (unsigned long) i->pid, (unsigned long) i->uid); + (unsigned long) i->pid, (unsigned long) i->uid, + inhibit_mode_to_string(i->mode)); inhibitor_save(i); @@ -169,9 +172,10 @@ int inhibitor_stop(Inhibitor *i) { assert(i); if (i->started) - log_debug("Inhibitor %s (%s) pid=%lu uid=%lu stopped.", + log_debug("Inhibitor %s (%s) pid=%lu uid=%lu mode=%s stopped.", strna(i->who), strna(i->why), - (unsigned long) i->pid, (unsigned long) i->uid); + (unsigned long) i->pid, (unsigned long) i->uid, + inhibit_mode_to_string(i->mode)); if (i->state_file) unlink(i->state_file); @@ -185,13 +189,15 @@ int inhibitor_stop(Inhibitor *i) { int inhibitor_load(Inhibitor *i) { InhibitWhat w; + InhibitMode mm; int r; char *cc, *what = NULL, *uid = NULL, *pid = NULL, *who = NULL, - *why = NULL; + *why = NULL, + *mode = NULL; r = parse_env_file(i->state_file, NEWLINE, "WHAT", &what, @@ -199,17 +205,25 @@ int inhibitor_load(Inhibitor *i) { "PID", &pid, "WHO", &who, "WHY", &why, + "MODE", &mode, "FIFO", &i->fifo_path, NULL); if (r < 0) goto finish; - w = inhibit_what_from_string(what); + w = what ? inhibit_what_from_string(what) : 0; if (w >= 0) i->what = w; - parse_uid(uid, &i->uid); - parse_pid(pid, &i->pid); + mm = mode ? inhibit_mode_from_string(mode) : INHIBIT_BLOCK; + if (mm >= 0) + i->mode = mm; + + if (uid) + parse_uid(uid, &i->uid); + + if (pid) + parse_pid(pid, &i->pid); if (who) { cc = cunescape(who); @@ -314,7 +328,7 @@ void inhibitor_remove_fifo(Inhibitor *i) { } } -InhibitWhat manager_inhibit_what(Manager *m) { +InhibitWhat manager_inhibit_what(Manager *m, InhibitMode mm) { Inhibitor *i; Iterator j; InhibitWhat what = 0; @@ -322,12 +336,13 @@ InhibitWhat manager_inhibit_what(Manager *m) { assert(m); HASHMAP_FOREACH(i, m->inhibitor_fds, j) - what |= i->what; + if (i->mode == mm) + what |= i->what; return what; } -bool manager_is_inhibited(Manager *m, InhibitWhat w, dual_timestamp *since) { +bool manager_is_inhibited(Manager *m, InhibitWhat w, InhibitMode mm, dual_timestamp *since) { Inhibitor *i; Iterator j; struct dual_timestamp ts = { 0, 0 }; @@ -340,6 +355,9 @@ bool manager_is_inhibited(Manager *m, InhibitWhat w, dual_timestamp *since) { if (!(i->what & w)) continue; + if (i->mode != mm) + continue; + if (!inhibited || i->since.monotonic < ts.monotonic) ts = i->since; @@ -391,3 +409,10 @@ InhibitWhat inhibit_what_from_string(const char *s) { return what; } + +static const char* const inhibit_mode_table[_INHIBIT_MODE_MAX] = { + [INHIBIT_BLOCK] = "block", + [INHIBIT_DELAY] = "delay" +}; + +DEFINE_STRING_TABLE_LOOKUP(inhibit_mode, InhibitMode); diff --git a/src/login/logind-inhibit.h b/src/login/logind-inhibit.h index 1d47cfc68d..823af39918 100644 --- a/src/login/logind-inhibit.h +++ b/src/login/logind-inhibit.h @@ -37,6 +37,13 @@ typedef enum InhibitWhat { _INHIBIT_WHAT_INVALID = -1 } InhibitWhat; +typedef enum InhibitMode { + INHIBIT_BLOCK, + INHIBIT_DELAY, + _INHIBIT_MODE_MAX, + _INHIBIT_MODE_INVALID = -1 +} InhibitMode; + struct Inhibitor { Manager *manager; @@ -48,6 +55,7 @@ struct Inhibitor { InhibitWhat what; char *who; char *why; + InhibitMode mode; pid_t pid; uid_t uid; @@ -70,10 +78,13 @@ int inhibitor_stop(Inhibitor *i); int inhibitor_create_fifo(Inhibitor *i); void inhibitor_remove_fifo(Inhibitor *i); -InhibitWhat manager_inhibit_what(Manager *m); -bool manager_is_inhibited(Manager *m, InhibitWhat w, dual_timestamp *since); +InhibitWhat manager_inhibit_what(Manager *m, InhibitMode mm); +bool manager_is_inhibited(Manager *m, InhibitWhat w, InhibitMode mm, dual_timestamp *since); const char *inhibit_what_to_string(InhibitWhat k); InhibitWhat inhibit_what_from_string(const char *s); +const char *inhibit_mode_to_string(InhibitMode k); +InhibitMode inhibit_mode_from_string(const char *s); + #endif diff --git a/src/login/logind.c b/src/login/logind.c index 6b7012ec73..5860bfc8d7 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -50,6 +50,7 @@ Manager *manager_new(void) { m->udev_vcsa_fd = -1; m->epoll_fd = -1; m->n_autovts = 6; + m->inhibit_delay_max = 5 * USEC_PER_SEC; m->devices = hashmap_new(string_hash_func, string_compare_func); m->seats = hashmap_new(string_hash_func, string_compare_func); @@ -1163,7 +1164,7 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) { assert(m); - idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, t); + idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t); HASHMAP_FOREACH(s, m->sessions, i) { dual_timestamp k; @@ -1264,15 +1265,28 @@ int manager_run(Manager *m) { for (;;) { struct epoll_event event; int n; + int msec = -1; manager_gc(m, true); + if (manager_dispatch_delayed_shutdown(m) > 0) + continue; + if (dbus_connection_dispatch(m->bus) != DBUS_DISPATCH_COMPLETE) continue; manager_gc(m, true); - n = epoll_wait(m->epoll_fd, &event, 1, -1); + if (m->delayed_shutdown) { + usec_t x, y; + + x = now(CLOCK_MONOTONIC); + y = m->delayed_shutdown_timestamp + m->inhibit_delay_max; + + msec = x >= y ? 0 : (int) ((y - x) / USEC_PER_MSEC); + } + + n = epoll_wait(m->epoll_fd, &event, 1, msec); if (n < 0) { if (errno == EINTR || errno == EAGAIN) continue; @@ -1281,6 +1295,9 @@ int manager_run(Manager *m) { return -errno; } + if (n == 0) + continue; + switch (event.data.u32) { case FD_SEAT_UDEV: diff --git a/src/login/logind.conf b/src/login/logind.conf index 6a2cefa576..c0779a7685 100644 --- a/src/login/logind.conf +++ b/src/login/logind.conf @@ -14,3 +14,4 @@ #KillExcludeUsers=root #Controllers= #ResetControllers=cpu +#InhibitDelayMaxSec=5 diff --git a/src/login/logind.h b/src/login/logind.h index 4e9dcd5fed..2c0545206d 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -81,6 +81,14 @@ struct Manager { Hashmap *cgroups; Hashmap *session_fds; Hashmap *inhibitor_fds; + + /* If a shutdown was delayed due to a inhibitor this contains + the unit name we are supposed to start after the delay is + over */ + const char *delayed_shutdown; + usec_t delayed_shutdown_timestamp; + + usec_t inhibit_delay_max; }; enum { @@ -132,6 +140,8 @@ DBusHandlerResult bus_message_filter(DBusConnection *c, DBusMessage *message, vo int manager_send_changed(Manager *manager, const char *properties); +int manager_dispatch_delayed_shutdown(Manager *manager); + /* gperf lookup function */ const struct ConfigPerfItem* logind_gperf_lookup(const char *key, unsigned length); diff --git a/src/login/org.freedesktop.login1.policy.in b/src/login/org.freedesktop.login1.policy.in index a2dc4025ce..76ed2bea38 100644 --- a/src/login/org.freedesktop.login1.policy.in +++ b/src/login/org.freedesktop.login1.policy.in @@ -16,9 +16,9 @@ <vendor>The systemd Project</vendor> <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url> - <action id="org.freedesktop.login1.inhibit"> + <action id="org.freedesktop.login1.inhibit-block"> <_description>Allow applications to inhibit system shutdown and suspend</_description> - <_message>Authentication is required to allow an application to inhibit system shutdown or suspend</_message> + <_message>Authentication is required to allow an application to inhibit system shutdown or suspend.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>yes</allow_inactive> @@ -26,9 +26,19 @@ </defaults> </action> + <action id="org.freedesktop.login1.inhibit-delay"> + <_description>Allow applications to delay system shutdown and suspend</_description> + <_message>Authentication is required to allow an application to delay system shutdown or suspend.</_message> + <defaults> + <allow_any>yes</allow_any> + <allow_inactive>yes</allow_inactive> + <allow_active>yes</allow_active> + </defaults> + </action> + <action id="org.freedesktop.login1.set-user-linger"> <_description>Allow non-logged-in users to run programs</_description> - <_message>Authentication is required to allow a non-logged-in user to run programs</_message> + <_message>Authentication is required to allow a non-logged-in user to run programs.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> @@ -38,7 +48,7 @@ <action id="org.freedesktop.login1.attach-device"> <_description>Allow attaching devices to seats</_description> - <_message>Authentication is required to allow attaching a device to a seat</_message> + <_message>Authentication is required to allow attaching a device to a seat.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> @@ -48,7 +58,7 @@ <action id="org.freedesktop.login1.flush-devices"> <_description>Flush device to seat attachments</_description> - <_message>Authentication is required to allow resetting how devices are attached to seats</_message> + <_message>Authentication is required to allow resetting how devices are attached to seats.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> @@ -58,7 +68,7 @@ <action id="org.freedesktop.login1.power-off"> <_description>Power off the system</_description> - <_message>Authentication is required to allow powering off the system</_message> + <_message>Authentication is required to allow powering off the system.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> @@ -68,7 +78,7 @@ <action id="org.freedesktop.login1.power-off-multiple-sessions"> <_description>Power off the system when other users are logged in</_description> - <_message>Authentication is required to allow powering off the system while other users are logged in</_message> + <_message>Authentication is required to allow powering off the system while other users are logged in.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> @@ -78,7 +88,7 @@ <action id="org.freedesktop.login1.power-off-ignore-inhibit"> <_description>Power off the system when an application asked to inhibit it</_description> - <_message>Authentication is required to allow powering off the system while an application asked to inhibit it</_message> + <_message>Authentication is required to allow powering off the system while an application asked to inhibit it.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> @@ -88,7 +98,7 @@ <action id="org.freedesktop.login1.reboot"> <_description>Reboot the system</_description> - <_message>Authentication is required to allow rebooting the system</_message> + <_message>Authentication is required to allow rebooting the system.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> @@ -98,7 +108,7 @@ <action id="org.freedesktop.login1.reboot-multiple-sessions"> <_description>Reboot the system when other users are logged in</_description> - <_message>Authentication is required to allow rebooting the system while other users are logged in</_message> + <_message>Authentication is required to allow rebooting the system while other users are logged in.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> @@ -108,7 +118,7 @@ <action id="org.freedesktop.login1.reboot-ignore-inhibit"> <_description>Reboot the system when an application asked to inhibit it</_description> - <_message>Authentication is required to allow rebooting the system while an application asked to inhibit it</_message> + <_message>Authentication is required to allow rebooting the system while an application asked to inhibit it.</_message> <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> diff --git a/src/shared/dbus-common.c b/src/shared/dbus-common.c index e161273cd8..ddb50b1eca 100644 --- a/src/shared/dbus-common.c +++ b/src/shared/dbus-common.c @@ -245,7 +245,8 @@ int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error) { } const char *bus_error_message(const DBusError *error) { - assert(error); + if (!error) + return NULL; /* Sometimes the D-Bus server is a little bit too verbose with * its error messages, so let's override them here */ @@ -255,6 +256,14 @@ const char *bus_error_message(const DBusError *error) { return error->message; } +const char *bus_error_message_or_strerror(const DBusError *error, int err) { + + if (error && dbus_error_is_set(error)) + return bus_error_message(error); + + return strerror(err); +} + DBusHandlerResult bus_default_message_handler( DBusConnection *c, DBusMessage *message, diff --git a/src/shared/dbus-common.h b/src/shared/dbus-common.h index edb8107347..9ae35df9c5 100644 --- a/src/shared/dbus-common.h +++ b/src/shared/dbus-common.h @@ -91,6 +91,7 @@ int bus_connect_system_ssh(const char *user, const char *host, DBusConnection ** int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error); const char *bus_error_message(const DBusError *error); +const char *bus_error_message_or_strerror(const DBusError *error, int err); typedef int (*BusPropertyCallback)(DBusMessageIter *iter, const char *property, void *data); typedef int (*BusPropertySetCallback)(DBusMessageIter *iter, const char *property, void *data); |