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.
This commit is contained in:
Lennart Poettering 2012-05-05 00:34:48 +02:00
parent a26336da87
commit eecd1362f7
16 changed files with 843 additions and 66 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/systemd-inhibit
/systemd-remount-fs
/build-aux
/test-watchdog

View File

@ -481,7 +481,8 @@ MANPAGES = \
man/systemd-machine-id-setup.1 \
man/systemd-detect-virt.1 \
man/journald.conf.5 \
man/journalctl.1
man/journalctl.1 \
man/systemd-inhibit.1
MANPAGES_ALIAS = \
man/reboot.8 \
@ -2666,6 +2667,20 @@ loginctl_LDADD = \
rootbin_PROGRAMS += \
loginctl
systemd_inhibit_SOURCES = \
src/login/inhibit.c
systemd_inhibit_CFLAGS = \
$(AM_CFLAGS) \
$(DBUS_CFLAGS)
systemd_inhibit_LDADD = \
libsystemd-shared.la \
libsystemd-dbus.la
rootbin_PROGRAMS += \
systemd-inhibit
test_login_SOURCES = \
src/login/test-login.c

10
TODO
View File

@ -25,6 +25,16 @@ Features:
* improve !/proc/*/loginuid situation: make /proc/*/loginuid less dependent on CONFIG_AUDIT,
or use the users cgroup information when /proc/*/loginuid is not available.
* pam_systemd: try to get old session id from cgroup, if audit sessionid cannot be determined
* logind: auto-suspend, auto-shutdown:
IdleAction=(none|suspend|hibernate|poweroff)
IdleActionDelay=...
SessionIdleMode=(explicit|ignore|login)
ForceShutdown=(yes|no)
* logind: use "sleep" as generic term for "suspend", "hibernate", ...
* services which create their own subcgroups break cgroup-empty notification (needs to be fixed in the kernel)
* don't delete /tmp/systemd-namespace-* before a process is gone down

View File

@ -146,6 +146,20 @@
defaults to
<literal>cpu</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>InhibitDelayMaxSec=</varname></term>
<listitem><para>Specifies the maximum
time a suspend or reboot is delayed
due to an inhibitor lock of type
<literal>delay</literal> being taken
before it is ignored and the operation
executed anyway. Defaults to
5s.</para></listitem>
</varlistentry>
</variablelist>
<para>Note that setting

179
man/systemd-inhibit.xml Normal file
View File

@ -0,0 +1,179 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
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/>.
-->
<refentry id="systemd-inhibit">
<refentryinfo>
<title>systemd-inhibit</title>
<productname>systemd</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Lennart</firstname>
<surname>Poettering</surname>
<email>lennart@poettering.net</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-inhibit</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-inhibit</refname>
<refpurpose>Execute a program with an inhibition lock taken</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>systemd-inhibit <arg choice="opt" rep="repeat">OPTIONS</arg> <arg>COMMAND</arg> <arg choice="opt" rep="repeat">ARGUMENTS</arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-inhibit <arg choice="opt" rep="repeat">OPTIONS</arg> --list</command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-inhibit</command> may be used
to execute a program with a shutdown, suspend or idle
inhibitor lock taken. The lock will be acquired before
the specified command line is executed and released
afterwards.</para>
<para>Inhibitor locks may be used to block or delay
suspend and shutdown requests from the user, as well
as automatic idle handling of the OS. This may be used
to avoid system suspends while an optical disc is
being recorded, or similar operations that should not
be interrupted.</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--h</option></term>
<term><option>--help</option></term>
<listitem><para>Prints a short help
text and exits.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--version</option></term>
<listitem><para>Prints a short version
string and exits.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--what=</option></term>
<listitem><para>Takes a colon
separated list of one or more
operations to inhibit:
<literal>shutdown</literal>,
<literal>suspend</literal>,
<literal>idle</literal>, for
inhibiting reboot/power-off/halt/kexec,
suspending/hibernating, resp. the
automatic idle
detection.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--who=</option></term>
<listitem><para>Takes a short human
readable descriptive string for the
program taking the lock. If not passed
defaults to the command line
string.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--why=</option></term>
<listitem><para>Takes a short human
readable descriptive string for the
reason for taking the lock. Defaults
to "Unknown reason".</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--mode=</option></term>
<listitem><para>Takes either
<literal>block</literal> or
<literal>delay</literal> and describes
how the lock is applied. If
<literal>block</literal> is used (the
default), the lock prohibits any of
the requested operations without time
limit, and only privileged users may
override it. If
<literal>delay</literal> is used, the
lock can only delay the requested
operations for a limited time. If the
time elapses the lock is ignored and
the operation executed. The time limit
may be specified in
<citerefentry><refentrytitle>systemd-logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--list</option></term>
<listitem><para>Lists all active
inhibition locks instead of acquiring
one.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>Returns the exit status of the executed program.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

353
src/login/inhibit.c Normal file
View File

@ -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;
}

View File

@ -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,33 +1780,27 @@ 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);
delayed =
m->inhibit_delay_max > 0 &&
manager_is_inhibited(m, INHIBIT_SHUTDOWN, INHIBIT_DELAY, NULL);
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);
}
freply = dbus_connection_send_with_reply_and_block(connection, forward, -1, &error);
dbus_message_unref(forward);
if (!freply)
return bus_send_error_reply(connection, message, &error, -EIO);
dbus_message_unref(freply);
reply = dbus_message_new_method_return(message);
if (!reply)
goto oom;
@ -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;
}

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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:

View File

@ -14,3 +14,4 @@
#KillExcludeUsers=root
#Controllers=
#ResetControllers=cpu
#InhibitDelayMaxSec=5

View File

@ -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);

View File

@ -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>

View File

@ -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,

View File

@ -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);