summaryrefslogtreecommitdiffstats
path: root/src/login
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:40 +0000
commitfc53809803cd2bc2434e312b19a18fa36776da12 (patch)
treeb4b43bd6538f51965ce32856e9c053d0f90919c8 /src/login
parentAdding upstream version 255.5. (diff)
downloadsystemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz
systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/login')
-rw-r--r--src/login/inhibit.c12
-rw-r--r--src/login/loginctl.c222
-rw-r--r--src/login/logind-action.c145
-rw-r--r--src/login/logind-action.h21
-rw-r--r--src/login/logind-core.c32
-rw-r--r--src/login/logind-dbus.c1183
-rw-r--r--src/login/logind-dbus.h18
-rw-r--r--src/login/logind-device.h2
-rw-r--r--src/login/logind-gperf.gperf1
-rw-r--r--src/login/logind-inhibit.c16
-rw-r--r--src/login/logind-inhibit.h4
-rw-r--r--src/login/logind-polkit.c5
-rw-r--r--src/login/logind-seat-dbus.c5
-rw-r--r--src/login/logind-seat.c13
-rw-r--r--src/login/logind-seat.h4
-rw-r--r--src/login/logind-session-dbus.c149
-rw-r--r--src/login/logind-session-dbus.h1
-rw-r--r--src/login/logind-session-device.c54
-rw-r--r--src/login/logind-session-device.h2
-rw-r--r--src/login/logind-session.c452
-rw-r--r--src/login/logind-session.h74
-rw-r--r--src/login/logind-user-dbus.c16
-rw-r--r--src/login/logind-user.c345
-rw-r--r--src/login/logind-user.h38
-rw-r--r--src/login/logind-wall.c12
-rw-r--r--src/login/logind.c274
-rw-r--r--src/login/logind.conf.in1
-rw-r--r--src/login/logind.h4
-rw-r--r--src/login/meson.build6
-rw-r--r--src/login/org.freedesktop.login1.conf36
-rw-r--r--src/login/pam_systemd.c184
-rw-r--r--src/login/pam_systemd_loadkey.c6
-rw-r--r--src/login/test-login-tables.c17
-rw-r--r--src/login/user-runtime-dir.c5
34 files changed, 2100 insertions, 1259 deletions
diff --git a/src/login/inhibit.c b/src/login/inhibit.c
index ad73c4b..4682830 100644
--- a/src/login/inhibit.c
+++ b/src/login/inhibit.c
@@ -111,7 +111,7 @@ static int print_inhibitors(sd_bus *bus) {
if (r < 0)
return bus_log_parse_error(r);
- if (table_get_rows(table) > 1) {
+ if (!table_isempty(table)) {
r = table_set_sort(table, (size_t) 1, (size_t) 0, (size_t) 5, (size_t) 6);
if (r < 0)
return table_log_sort_error(r);
@@ -124,10 +124,10 @@ static int print_inhibitors(sd_bus *bus) {
}
if (arg_legend) {
- if (table_get_rows(table) > 1)
- printf("\n%zu inhibitors listed.\n", table_get_rows(table) - 1);
- else
+ if (table_isempty(table))
printf("No inhibitors.\n");
+ else
+ printf("\n%zu inhibitors listed.\n", table_get_rows(table) - 1);
}
return 0;
@@ -257,9 +257,7 @@ static int run(int argc, char *argv[]) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
- log_show_color(true);
- log_parse_environment();
- log_open();
+ log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
diff --git a/src/login/loginctl.c b/src/login/loginctl.c
index 7fc6efc..cf3bff4 100644
--- a/src/login/loginctl.c
+++ b/src/login/loginctl.c
@@ -44,6 +44,7 @@ static BusPrintPropertyFlags arg_print_flags = 0;
static bool arg_full = false;
static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static const char *arg_kill_whom = NULL;
static int arg_signal = SIGTERM;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
@@ -113,80 +114,115 @@ static OutputFlags get_output_flags(void) {
colors_enabled() * OUTPUT_COLOR;
}
-static int show_table(Table *table, const char *word) {
+static int list_table_print(Table *table, const char *type) {
int r;
assert(table);
- assert(word);
+ assert(type);
- if (table_get_rows(table) > 1 || OUTPUT_MODE_IS_JSON(arg_output)) {
- r = table_set_sort(table, (size_t) 0);
- if (r < 0)
- return table_log_sort_error(r);
+ r = table_set_sort(table, (size_t) 0);
+ if (r < 0)
+ return table_log_sort_error(r);
- table_set_header(table, arg_legend);
+ r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
+ if (r < 0)
+ return r;
- if (OUTPUT_MODE_IS_JSON(arg_output))
- r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | JSON_FORMAT_COLOR_AUTO);
+ if (arg_legend) {
+ if (table_isempty(table))
+ printf("No %s.\n", type);
else
- r = table_print(table, NULL);
- if (r < 0)
- return table_log_print_error(r);
+ printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type);
}
- if (arg_legend) {
- if (table_get_rows(table) > 1)
- printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word);
+ return 0;
+}
+
+static int list_sessions_table_add(Table *table, sd_bus_message *reply) {
+ int r;
+
+ assert(table);
+ assert(reply);
+
+ r = sd_bus_message_enter_container(reply, 'a', "(sussussbto)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ for (;;) {
+ const char *session_id, *user, *seat, *class, *tty;
+ uint32_t uid, leader_pid;
+ int idle;
+ uint64_t idle_timestamp_monotonic;
+
+ r = sd_bus_message_read(reply, "(sussussbto)",
+ &session_id,
+ &uid,
+ &user,
+ &seat,
+ &leader_pid,
+ &class,
+ &tty,
+ &idle,
+ &idle_timestamp_monotonic,
+ /* object = */ NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ r = table_add_many(table,
+ TABLE_STRING, session_id,
+ TABLE_UID, (uid_t) uid,
+ TABLE_STRING, user,
+ TABLE_STRING, empty_to_null(seat),
+ TABLE_PID, (pid_t) leader_pid,
+ TABLE_STRING, class,
+ TABLE_STRING, empty_to_null(tty),
+ TABLE_BOOLEAN, idle);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (idle)
+ r = table_add_cell(table, NULL, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, &idle_timestamp_monotonic);
else
- printf("No %s.\n", word);
+ r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+ if (r < 0)
+ return table_log_add_error(r);
}
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
return 0;
}
-static int list_sessions(int argc, char *argv[], void *userdata) {
+static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, sd_bus *bus) {
static const struct bus_properties_map map[] = {
+ { "Leader", "u", NULL, offsetof(SessionStatusInfo, leader) },
+ { "Class", "s", NULL, offsetof(SessionStatusInfo, class) },
+ { "TTY", "s", NULL, offsetof(SessionStatusInfo, tty) },
{ "IdleHint", "b", NULL, offsetof(SessionStatusInfo, idle_hint) },
{ "IdleSinceHintMonotonic", "t", NULL, offsetof(SessionStatusInfo, idle_hint_timestamp.monotonic) },
- { "State", "s", NULL, offsetof(SessionStatusInfo, state) },
- { "TTY", "s", NULL, offsetof(SessionStatusInfo, tty) },
{},
};
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- _cleanup_(table_unrefp) Table *table = NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
int r;
- assert(argv);
-
- pager_open(arg_pager_flags);
-
- r = bus_call_method(bus, bus_login_mgr, "ListSessions", &error, &reply, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to list sessions: %s", bus_error_message(&error, r));
+ assert(table);
+ assert(reply);
+ assert(bus);
r = sd_bus_message_enter_container(reply, 'a', "(susso)");
if (r < 0)
return bus_log_parse_error(r);
- table = table_new("session", "uid", "user", "seat", "tty", "state", "idle", "since");
- if (!table)
- return log_oom();
-
- /* Right-align the first two fields (since they are numeric) */
- (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100);
- (void) table_set_align_percent(table, TABLE_HEADER_CELL(1), 100);
-
- (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
-
for (;;) {
_cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
const char *id, *user, *seat, *object;
uint32_t uid;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
SessionStatusInfo i = {};
r = sd_bus_message_read(reply, "(susso)", &id, &uid, &user, &seat, &object);
@@ -209,8 +245,9 @@ static int list_sessions(int argc, char *argv[], void *userdata) {
TABLE_UID, (uid_t) uid,
TABLE_STRING, user,
TABLE_STRING, empty_to_null(seat),
+ TABLE_PID, i.leader,
+ TABLE_STRING, i.class,
TABLE_STRING, empty_to_null(i.tty),
- TABLE_STRING, i.state,
TABLE_BOOLEAN, i.idle_hint);
if (r < 0)
return table_log_add_error(r);
@@ -227,7 +264,49 @@ static int list_sessions(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
- return show_table(table, "sessions");
+ return 0;
+}
+
+static int list_sessions(int argc, char *argv[], void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(table_unrefp) Table *table = NULL;
+ bool use_ex = true;
+ int r;
+
+ assert(argv);
+
+ r = bus_call_method(bus, bus_login_mgr, "ListSessionsEx", &error, &reply, NULL);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+ sd_bus_error_free(&error);
+
+ use_ex = false;
+ r = bus_call_method(bus, bus_login_mgr, "ListSessions", &error, &reply, NULL);
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to list sessions: %s", bus_error_message(&error, r));
+ }
+
+ table = table_new("session", "uid", "user", "seat", "leader", "class", "tty", "idle", "since");
+ if (!table)
+ return log_oom();
+
+ /* Right-align the first two fields (since they are numeric) */
+ (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100);
+ (void) table_set_align_percent(table, TABLE_HEADER_CELL(1), 100);
+
+ (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ if (use_ex)
+ r = list_sessions_table_add(table, reply);
+ else
+ r = list_sessions_table_add_fallback(table, reply, bus);
+ if (r < 0)
+ return r;
+
+ return list_table_print(table, "sessions");
}
static int list_users(int argc, char *argv[], void *userdata) {
@@ -246,8 +325,6 @@ static int list_users(int argc, char *argv[], void *userdata) {
assert(argv);
- pager_open(arg_pager_flags);
-
r = bus_call_method(bus, bus_login_mgr, "ListUsers", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r));
@@ -305,7 +382,7 @@ static int list_users(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
- return show_table(table, "users");
+ return list_table_print(table, "users");
}
static int list_seats(int argc, char *argv[], void *userdata) {
@@ -317,8 +394,6 @@ static int list_seats(int argc, char *argv[], void *userdata) {
assert(argv);
- pager_open(arg_pager_flags);
-
r = bus_call_method(bus, bus_login_mgr, "ListSeats", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to list seats: %s", bus_error_message(&error, r));
@@ -351,7 +426,7 @@ static int list_seats(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
- return show_table(table, "seats");
+ return list_table_print(table, "seats");
}
static int show_unit_cgroup(
@@ -658,16 +733,15 @@ static int print_session_status_info(sd_bus *bus, const char *path) {
show_journal_by_unit(
stdout,
i.scope,
- NULL,
+ /* namespace = */ NULL,
arg_output,
- 0,
+ /* n_columns = */ 0,
i.timestamp.monotonic,
arg_lines,
- 0,
get_output_flags() | OUTPUT_BEGIN_NEWLINE,
SD_JOURNAL_LOCAL_ONLY,
- true,
- NULL);
+ /* system_unit = */ true,
+ /* ellipsized = */ NULL);
}
return 0;
@@ -764,16 +838,15 @@ static int print_user_status_info(sd_bus *bus, const char *path) {
show_journal_by_unit(
stdout,
i.slice,
- NULL,
+ /* namespace = */ NULL,
arg_output,
- 0,
+ /* n_columns = */ 0,
i.timestamp.monotonic,
arg_lines,
- 0,
get_output_flags() | OUTPUT_BEGIN_NEWLINE,
SD_JOURNAL_LOCAL_ONLY,
- true,
- NULL);
+ /* system_unit = */ true,
+ /* ellipsized = */ NULL);
}
return 0;
@@ -953,7 +1026,6 @@ static int get_bus_path_by_id(
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- _cleanup_free_ char *p = NULL;
const char *path;
int r;
@@ -972,12 +1044,7 @@ static int get_bus_path_by_id(
if (r < 0)
return bus_log_parse_error(r);
- p = strdup(path);
- if (!p)
- return log_oom();
-
- *ret = TAKE_PTR(p);
- return 0;
+ return strdup_to(ret, path);
}
static int show_session(int argc, char *argv[], void *userdata) {
@@ -1442,7 +1509,10 @@ static int help(int argc, char *argv[], void *userdata) {
" --kill-whom=WHOM Whom to send signal to\n"
" -s --signal=SIGNAL Which signal to send\n"
" -n --lines=INTEGER Number of journal entries to show\n"
- " -o --output=STRING Change journal output mode (short, short-precise,\n"
+ " --json=MODE Generate JSON output for list-sessions/users/seats\n"
+ " (takes one of pretty, short, or off)\n"
+ " -j Same as --json=pretty on tty, --json=short otherwise\n"
+ " -o --output=MODE Change journal output mode (short, short-precise,\n"
" short-iso, short-iso-precise, short-full,\n"
" short-monotonic, short-unix, short-delta,\n"
" json, json-pretty, json-sse, json-seq, cat,\n"
@@ -1464,6 +1534,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_VALUE,
ARG_NO_PAGER,
ARG_NO_LEGEND,
+ ARG_JSON,
ARG_KILL_WHOM,
ARG_NO_ASK_PASSWORD,
};
@@ -1477,6 +1548,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "full", no_argument, NULL, 'l' },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "json", required_argument, NULL, ARG_JSON },
{ "kill-whom", required_argument, NULL, ARG_KILL_WHOM },
{ "signal", required_argument, NULL, 's' },
{ "host", required_argument, NULL, 'H' },
@@ -1492,7 +1564,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:j", options, NULL)) >= 0)
switch (c) {
@@ -1546,7 +1618,19 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_output < 0)
return log_error_errno(arg_output, "Unknown output '%s'.", optarg);
- if (OUTPUT_MODE_IS_JSON(arg_output))
+ break;
+
+ case 'j':
+ arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
+ arg_legend = false;
+ break;
+
+ case ARG_JSON:
+ r = parse_json_argument(optarg, &arg_json_format_flags);
+ if (r <= 0)
+ return r;
+
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
arg_legend = false;
break;
diff --git a/src/login/logind-action.c b/src/login/logind-action.c
index 8269f52..9325d91 100644
--- a/src/login/logind-action.c
+++ b/src/login/logind-action.c
@@ -6,6 +6,7 @@
#include "alloc-util.h"
#include "bus-error.h"
+#include "bus-unit-util.h"
#include "bus-util.h"
#include "conf-parser.h"
#include "format-util.h"
@@ -133,6 +134,63 @@ const HandleActionData* handle_action_lookup(HandleAction action) {
return &handle_action_data_table[action];
}
+static bool handle_action_sleep_supported(HandleAction action) {
+ assert(HANDLE_ACTION_IS_SLEEP(action) && action != HANDLE_SLEEP);
+ return sleep_supported(ASSERT_PTR(handle_action_lookup(action))->sleep_operation) > 0;
+}
+
+/* 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_ELEMENT(i, 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(Manager *m) {
+ assert(m);
+
+ FOREACH_ELEMENT(i, sleep_actions) {
+ HandleActionSleepMask action_mask = 1U << *i;
+ const HandleActionData *a;
+ _cleanup_free_ char *load_state = NULL;
+
+ if (!FLAGS_SET(m->handle_action_sleep_mask, action_mask))
+ continue;
+
+ a = ASSERT_PTR(handle_action_lookup(*i));
+
+ if (sleep_supported(a->sleep_operation) <= 0)
+ continue;
+
+ (void) unit_load_state(m->bus, a->target, &load_state);
+ if (streq_ptr(load_state, "loaded"))
+ return *i;
+ }
+
+ return _HANDLE_ACTION_INVALID;
+}
+
static int handle_action_execute(
Manager *m,
HandleAction handle,
@@ -158,6 +216,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),
@@ -169,7 +228,7 @@ static int handle_action_execute(
handle_action_to_string(m->delayed_action->handle),
handle_action_to_string(handle));
- inhibit_operation = handle_action_lookup(handle)->inhibit_what;
+ inhibit_operation = ASSERT_PTR(handle_action_lookup(handle))->inhibit_what;
/* If the actual operation is inhibited, warn and fail */
if (inhibit_what_is_valid(inhibit_operation) &&
@@ -208,21 +267,21 @@ 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_SUSPEND)
- supported = sleep_supported(SLEEP_SUSPEND) > 0;
- else if (handle == HANDLE_HIBERNATE)
- supported = sleep_supported(SLEEP_HIBERNATE) > 0;
- else if (handle == HANDLE_HYBRID_SLEEP)
- supported = sleep_supported(SLEEP_HYBRID_SLEEP) > 0;
- else if (handle == HANDLE_SUSPEND_THEN_HIBERNATE)
- supported = sleep_supported(SLEEP_SUSPEND_THEN_HIBERNATE) > 0;
- else
- assert_not_reached();
+ if (handle == HANDLE_SLEEP) {
+ HandleAction a;
+
+ a = handle_action_sleep_select(m);
+ 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 = handle_action_sleep_supported(handle);
if (!supported && handle != HANDLE_SUSPEND) {
supported = sleep_supported(SLEEP_SUSPEND) > 0;
@@ -302,8 +361,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 +383,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;
+}
diff --git a/src/login/logind-action.h b/src/login/logind-action.h
index dbca963..c78c18c 100644
--- a/src/login/logind-action.h
+++ b/src/login/logind-action.h
@@ -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(Manager *m);
+
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);
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
index f15008e..71e4247 100644
--- a/src/login/logind-core.c
+++ b/src/login/logind-core.c
@@ -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;
@@ -80,9 +82,12 @@ void manager_reset_config(Manager *m) {
int manager_parse_config_file(Manager *m) {
assert(m);
- return config_parse_config_file("logind.conf", "Login\0",
- config_item_perf_lookup, logind_gperf_lookup,
- CONFIG_PARSE_WARN, m);
+ return config_parse_standard_file_with_dropins(
+ "systemd/logind.conf",
+ "Login\0",
+ config_item_perf_lookup, logind_gperf_lookup,
+ CONFIG_PARSE_WARN,
+ /* userdata= */ m);
}
int manager_add_device(Manager *m, const char *sysfs, bool master, Device **ret_device) {
@@ -116,7 +121,7 @@ int manager_add_seat(Manager *m, const char *id, Seat **ret_seat) {
s = hashmap_get(m->seats, id);
if (!s) {
- r = seat_new(&s, m, id);
+ r = seat_new(m, id, &s);
if (r < 0)
return r;
}
@@ -136,7 +141,7 @@ int manager_add_session(Manager *m, const char *id, Session **ret_session) {
s = hashmap_get(m->sessions, id);
if (!s) {
- r = session_new(&s, m, id);
+ r = session_new(m, id, &s);
if (r < 0)
return r;
}
@@ -160,7 +165,7 @@ int manager_add_user(
u = hashmap_get(m->users, UID_TO_PTR(ur->uid));
if (!u) {
- r = user_new(&u, m, ur);
+ r = user_new(m, ur, &u);
if (r < 0)
return r;
}
@@ -216,7 +221,7 @@ int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **ret) {
i = hashmap_get(m->inhibitors, id);
if (!i) {
- r = inhibitor_new(&i, m, id);
+ r = inhibitor_new(m, id, &i);
if (r < 0)
return r;
}
@@ -366,10 +371,8 @@ int manager_get_session_by_pidref(Manager *m, const PidRef *pid, Session **ret)
return r;
} else {
r = cg_pidref_get_unit(pid, &unit);
- if (r < 0)
- return r;
-
- s = hashmap_get(m->session_units, unit);
+ if (r >= 0)
+ s = hashmap_get(m->session_units, unit);
}
if (ret)
@@ -411,6 +414,9 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
dual_timestamp k;
int ih;
+ if (!SESSION_CLASS_CAN_IDLE(s->class))
+ continue;
+
ih = session_get_idle_hint(s, &k);
if (ih < 0)
return ih;
@@ -586,7 +592,7 @@ static int manager_count_external_displays(Manager *m) {
return r;
FOREACH_DEVICE(e, d) {
- const char *status, *enabled, *dash, *nn, *subsys;
+ const char *status, *enabled, *dash, *nn;
sd_device *p;
if (sd_device_get_parent(d, &p) < 0)
@@ -595,7 +601,7 @@ static int manager_count_external_displays(Manager *m) {
/* If the parent shares the same subsystem as the
* device we are looking at then it is a connector,
* which is what we are interested in. */
- if (sd_device_get_subsystem(p, &subsys) < 0 || !streq(subsys, "drm"))
+ if (!device_in_subsystem(p, "drm"))
continue;
if (sd_device_get_sysname(d, &nn) < 0)
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index cd2db2d..a657b6e 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -49,6 +49,7 @@
#include "sleep-config.h"
#include "special.h"
#include "serialize.h"
+#include "signal-util.h"
#include "stdio-util.h"
#include "strv.h"
#include "terminal-util.h"
@@ -70,9 +71,7 @@
#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled"
-static int update_schedule_file(Manager *m);
static void reset_scheduled_shutdown(Manager *m);
-static int manager_setup_shutdown_timers(Manager* m);
static int get_sender_session(
Manager *m,
@@ -141,9 +140,9 @@ int manager_get_session_from_creds(
assert(m);
assert(ret);
- if (SEAT_IS_SELF(name)) /* the caller's own session */
+ if (SESSION_IS_SELF(name)) /* the caller's own session */
return get_sender_session(m, message, false, error, ret);
- if (SEAT_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */
+ if (SESSION_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */
return get_sender_session(m, message, true, error, ret);
session = hashmap_get(m->sessions, name);
@@ -236,7 +235,6 @@ int manager_get_seat_from_creds(
static int return_test_polkit(
sd_bus_message *message,
- int capability,
const char *action,
const char **details,
uid_t good_user,
@@ -246,7 +244,7 @@ static int return_test_polkit(
bool challenge;
int r;
- r = bus_test_polkit(message, capability, action, details, good_user, &challenge, e);
+ r = bus_test_polkit(message, action, details, good_user, &challenge, e);
if (r < 0)
return r;
@@ -342,6 +340,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 +382,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;
@@ -568,6 +590,60 @@ static int method_list_sessions(sd_bus_message *message, void *userdata, sd_bus_
return sd_bus_send(NULL, reply, NULL);
}
+static int method_list_sessions_ex(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(sussussbto)");
+ if (r < 0)
+ return r;
+
+ Session *s;
+ HASHMAP_FOREACH(s, m->sessions) {
+ _cleanup_free_ char *path = NULL;
+ dual_timestamp idle_ts;
+ bool idle;
+
+ assert(s->user);
+
+ path = session_bus_path(s);
+ if (!path)
+ return -ENOMEM;
+
+ r = session_get_idle_hint(s, &idle_ts);
+ if (r < 0)
+ return r;
+ idle = r > 0;
+
+ r = sd_bus_message_append(reply, "(sussussbto)",
+ s->id,
+ (uint32_t) s->user->user_record->uid,
+ s->user->user_record->user_name,
+ s->seat ? s->seat->id : "",
+ (uint32_t) s->leader.pid,
+ session_class_to_string(s->class),
+ s->tty,
+ idle,
+ idle_ts.monotonic,
+ path);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
static int method_list_users(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Manager *m = ASSERT_PTR(userdata);
@@ -682,8 +758,8 @@ static int create_session(
void *userdata,
sd_bus_error *error,
uid_t uid,
- pid_t pid,
- int pidfd,
+ pid_t leader_pid,
+ int leader_pidfd,
const char *service,
const char *type,
const char *class,
@@ -716,32 +792,18 @@ static int create_session(
if (flags != 0)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero.");
- if (pidfd >= 0) {
- r = pidref_set_pidfd(&leader, pidfd);
- if (r < 0)
- return r;
- } else if (pid == 0) {
- _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
- pid_t p;
-
- r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds);
- if (r < 0)
- return r;
-
- r = sd_bus_creds_get_pid(creds, &p);
- if (r < 0)
- return r;
-
- r = pidref_set_pid(&leader, p);
- if (r < 0)
- return r;
- } else {
- assert(pid > 0);
+ if (leader_pidfd >= 0)
+ r = pidref_set_pidfd(&leader, leader_pidfd);
+ else if (leader_pid == 0)
+ r = bus_query_sender_pidref(message, &leader);
+ else {
+ if (leader_pid < 0)
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Leader PID is not valid");
- r = pidref_set_pid(&leader, pid);
- if (r < 0)
- return r;
+ r = pidref_set_pid(&leader, leader_pid);
}
+ if (r < 0)
+ return r;
if (leader.pid == 1 || leader.pid == getpid_cached())
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID");
@@ -842,25 +904,22 @@ static int create_session(
c = SESSION_USER;
}
- /* Check if we are already in a logind session. Or if we are in user@.service
- * which is a special PAM session that avoids creating a logind session. */
- r = manager_get_user_by_pid(m, leader.pid, NULL);
+ /* Check if we are already in a logind session, and if so refuse. */
+ r = manager_get_session_by_pidref(m, &leader, /* ret_session= */ NULL);
if (r < 0)
- return r;
+ return log_debug_errno(
+ r,
+ "Failed to check if process " PID_FMT " is already in a session: %m",
+ leader.pid);
if (r > 0)
return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY,
"Already running in a session or user slice");
- /*
- * Old gdm and lightdm start the user-session on the same VT as
- * the greeter session. But they destroy the greeter session
- * after the user-session and want the user-session to take
- * over the VT. We need to support this for
- * backwards-compatibility, so make sure we allow new sessions
- * on a VT that a greeter is running on. Furthermore, to allow
- * re-logins, we have to allow a greeter to take over a used VT for
- * the exact same reasons.
- */
+ /* Old gdm and lightdm start the user-session on the same VT as the greeter session. But they destroy
+ * the greeter session after the user-session and want the user-session to take over the VT. We need
+ * to support this for backwards-compatibility, so make sure we allow new sessions on a VT that a
+ * greeter is running on. Furthermore, to allow re-logins, we have to allow a greeter to take over a
+ * used VT for the exact same reasons. */
if (c != SESSION_GREETER &&
vtnr > 0 &&
vtnr < MALLOC_ELEMENTSOF(m->seat0->positions) &&
@@ -920,58 +979,54 @@ static int create_session(
goto fail;
session->original_type = session->type = t;
- session->class = c;
session->remote = remote;
session->vtnr = vtnr;
+ session->class = c;
+
+ /* Once the first session that is of a pinning class shows up we'll change the GC mode for the user
+ * from USER_GC_BY_ANY to USER_GC_BY_PIN, so that the user goes away once the last pinning session
+ * goes away. Background: we want that user@.service – when started manually – remains around (which
+ * itself is a non-pinning session), but gets stopped when the last pinning session goes away. */
+
+ if (SESSION_CLASS_PIN_USER(c))
+ user->gc_mode = USER_GC_BY_PIN;
if (!isempty(tty)) {
- session->tty = strdup(tty);
- if (!session->tty) {
- r = -ENOMEM;
+ r = strdup_to(&session->tty, tty);
+ if (r < 0)
goto fail;
- }
session->tty_validity = TTY_FROM_PAM;
}
if (!isempty(display)) {
- session->display = strdup(display);
- if (!session->display) {
- r = -ENOMEM;
+ r = strdup_to(&session->display, display);
+ if (r < 0)
goto fail;
- }
}
if (!isempty(remote_user)) {
- session->remote_user = strdup(remote_user);
- if (!session->remote_user) {
- r = -ENOMEM;
+ r = strdup_to(&session->remote_user, remote_user);
+ if (r < 0)
goto fail;
- }
}
if (!isempty(remote_host)) {
- session->remote_host = strdup(remote_host);
- if (!session->remote_host) {
- r = -ENOMEM;
+ r = strdup_to(&session->remote_host, remote_host);
+ if (r < 0)
goto fail;
- }
}
if (!isempty(service)) {
- session->service = strdup(service);
- if (!session->service) {
- r = -ENOMEM;
+ r = strdup_to(&session->service, service);
+ if (r < 0)
goto fail;
- }
}
if (!isempty(desktop)) {
- session->desktop = strdup(desktop);
- if (!session->desktop) {
- r = -ENOMEM;
+ r = strdup_to(&session->desktop, desktop);
+ if (r < 0)
goto fail;
- }
}
if (seat) {
@@ -994,8 +1049,14 @@ static int create_session(
session->create_message = sd_bus_message_ref(message);
- /* Now, let's wait until the slice unit and stuff got created. We send the reply back from
- * session_send_create_reply(). */
+ /* Now call into session_send_create_reply(), which will reply to this method call for us. Or it
+ * won't – in case we just spawned a session scope and/or user service manager, and they aren't ready
+ * yet. We'll call session_create_reply() again once the session scope or the user service manager is
+ * ready, where the function will check again if a reply is then ready to be sent, and then do so if
+ * all is complete - or wait again. */
+ r = session_send_create_reply(session, /* error= */ NULL);
+ if (r < 0)
+ return r;
return 1;
@@ -1011,32 +1072,32 @@ fail:
static int method_create_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
const char *service, *type, *class, *cseat, *tty, *display, *remote_user, *remote_host, *desktop;
- pid_t leader;
+ pid_t leader_pid;
+ uint32_t vtnr;
uid_t uid;
- int remote;
- uint32_t vtnr = 0;
- int r;
+ int remote, r;
assert(message);
assert_cc(sizeof(pid_t) == sizeof(uint32_t));
assert_cc(sizeof(uid_t) == sizeof(uint32_t));
- r = sd_bus_message_read(message,
- "uusssssussbss",
- &uid,
- &leader,
- &service,
- &type,
- &class,
- &desktop,
- &cseat,
- &vtnr,
- &tty,
- &display,
- &remote,
- &remote_user,
- &remote_host);
+ r = sd_bus_message_read(
+ message,
+ "uusssssussbss",
+ &uid,
+ &leader_pid,
+ &service,
+ &type,
+ &class,
+ &desktop,
+ &cseat,
+ &vtnr,
+ &tty,
+ &display,
+ &remote,
+ &remote_user,
+ &remote_host);
if (r < 0)
return r;
@@ -1045,7 +1106,7 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
userdata,
error,
uid,
- leader,
+ leader_pid,
/* pidfd = */ -EBADF,
service,
type,
@@ -1063,29 +1124,28 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
static int method_create_session_pidfd(sd_bus_message *message, void *userdata, sd_bus_error *error) {
const char *service, *type, *class, *cseat, *tty, *display, *remote_user, *remote_host, *desktop;
- int leaderfd = -EBADF;
- uid_t uid;
- int remote;
- uint32_t vtnr = 0;
uint64_t flags;
- int r;
+ uint32_t vtnr;
+ uid_t uid;
+ int leader_fd = -EBADF, remote, r;
- r = sd_bus_message_read(message,
- "uhsssssussbsst",
- &uid,
- &leaderfd,
- &service,
- &type,
- &class,
- &desktop,
- &cseat,
- &vtnr,
- &tty,
- &display,
- &remote,
- &remote_user,
- &remote_host,
- &flags);
+ r = sd_bus_message_read(
+ message,
+ "uhsssssussbsst",
+ &uid,
+ &leader_fd,
+ &service,
+ &type,
+ &class,
+ &desktop,
+ &cseat,
+ &vtnr,
+ &tty,
+ &display,
+ &remote,
+ &remote_user,
+ &remote_host,
+ &flags);
if (r < 0)
return r;
@@ -1094,8 +1154,8 @@ static int method_create_session_pidfd(sd_bus_message *message, void *userdata,
userdata,
error,
uid,
- /* pid = */ 0,
- leaderfd,
+ /* leader_pid = */ 0,
+ leader_fd,
service,
type,
class,
@@ -1112,7 +1172,7 @@ static int method_create_session_pidfd(sd_bus_message *message, void *userdata,
static int method_release_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = ASSERT_PTR(userdata);
- Session *session;
+ Session *session, *sender_session;
const char *name;
int r;
@@ -1126,6 +1186,14 @@ static int method_release_session(sd_bus_message *message, void *userdata, sd_bu
if (r < 0)
return r;
+ r = get_sender_session(m, message, /* consult_display= */ false, error, &sender_session);
+ if (r < 0)
+ return r;
+
+ if (session != sender_session)
+ return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED,
+ "Refused to release session, since it doesn't match the one of the client");
+
r = session_release(session);
if (r < 0)
return r;
@@ -1221,11 +1289,8 @@ static int method_lock_sessions(sd_bus_message *message, void *userdata, sd_bus_
r = bus_verify_polkit_async(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.lock-sessions",
- NULL,
- false,
- UID_INVALID,
+ /* details= */ NULL,
&m->polkit_registry,
error);
if (r < 0)
@@ -1336,28 +1401,25 @@ static int method_terminate_seat(sd_bus_message *message, void *userdata, sd_bus
}
static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bus_error *error) {
- _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
- _cleanup_free_ char *cc = NULL;
Manager *m = ASSERT_PTR(userdata);
- int r, b, interactive;
- struct passwd *pw;
- const char *path;
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
uint32_t uid, auth_uid;
+ int r, enable, interactive;
assert(message);
- r = sd_bus_message_read(message, "ubb", &uid, &b, &interactive);
+ r = sd_bus_message_read(message, "ubb", &uid, &enable, &interactive);
if (r < 0)
return r;
- r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID |
- SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, &creds);
+ r = sd_bus_query_sender_creds(message,
+ SD_BUS_CREDS_EUID|SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT,
+ &creds);
if (r < 0)
return r;
if (!uid_is_valid(uid)) {
- /* Note that we get the owner UID of the session or user unit,
- * not the actual client UID here! */
+ /* Note that we get the owner UID of the session or user unit, not the actual client UID here! */
r = sd_bus_creds_get_owner_uid(creds, &uid);
if (r < 0)
return r;
@@ -1368,19 +1430,19 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu
if (r < 0)
return r;
- errno = 0;
- pw = getpwuid(uid);
- if (!pw)
- return errno_or_else(ENOENT);
+ _cleanup_free_ struct passwd *pw = NULL;
- r = bus_verify_polkit_async(
+ r = getpwuid_malloc(uid, &pw);
+ if (r < 0)
+ return r;
+
+ r = bus_verify_polkit_async_full(
message,
- CAP_SYS_ADMIN,
uid == auth_uid ? "org.freedesktop.login1.set-self-linger" :
"org.freedesktop.login1.set-user-linger",
- NULL,
- interactive,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0)
@@ -1393,24 +1455,30 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu
if (r < 0)
return r;
- cc = cescape(pw->pw_name);
- if (!cc)
+ _cleanup_free_ char *escaped = NULL;
+ const char *path;
+ User *u;
+
+ escaped = cescape(pw->pw_name);
+ if (!escaped)
return -ENOMEM;
- path = strjoina("/var/lib/systemd/linger/", cc);
- if (b) {
- User *u;
+ path = strjoina("/var/lib/systemd/linger/", escaped);
+ if (enable) {
r = touch(path);
if (r < 0)
return r;
- if (manager_add_user_by_uid(m, uid, &u) >= 0)
- user_start(u);
+ if (manager_add_user_by_uid(m, uid, &u) >= 0) {
+ r = user_start(u);
+ if (r < 0) {
+ user_add_to_gc_queue(u);
+ return r;
+ }
+ }
} else {
- User *u;
-
r = unlink(path);
if (r < 0 && errno != ENOENT)
return -errno;
@@ -1541,13 +1609,12 @@ static int method_attach_device(sd_bus_message *message, void *userdata, sd_bus_
} else if (!seat_name_is_valid(seat)) /* Note that a seat does not have to exist yet for this operation to succeed */
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Seat name %s is not valid", seat);
- r = bus_verify_polkit_async(
+ r = bus_verify_polkit_async_full(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.attach-device",
- NULL,
- interactive,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0)
@@ -1572,13 +1639,12 @@ static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_
if (r < 0)
return r;
- r = bus_verify_polkit_async(
+ r = bus_verify_polkit_async_full(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.flush-devices",
- NULL,
- interactive,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0)
@@ -1604,7 +1670,7 @@ static int have_multiple_sessions(
/* Check for other users' sessions. Greeter sessions do not
* count, and non-login sessions do not count either. */
HASHMAP_FOREACH(session, m->sessions)
- if (session->class == SESSION_USER &&
+ if (IN_SET(session->class, SESSION_USER, SESSION_USER_EARLY) &&
session->user->user_record->uid != uid)
return true;
@@ -1713,16 +1779,36 @@ static int send_prepare_for(Manager *m, const HandleActionData *a, bool _active)
return RET_GATHER(k, r);
}
+static int strdup_job(sd_bus_message *reply, char **ret) {
+ const char *j;
+ char *job;
+ int r;
+
+ assert(reply);
+ assert(ret);
+
+ r = sd_bus_message_read_basic(reply, 'o', &j);
+ if (r < 0)
+ return r;
+
+ job = strdup(j);
+ if (!job)
+ return -ENOMEM;
+
+ *ret = job;
+ return 0;
+}
+
static int execute_shutdown_or_sleep(
Manager *m,
const HandleActionData *a,
sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- const char *p;
int r;
assert(m);
+ assert(!m->action_job);
assert(a);
if (a->inhibit_what == INHIBIT_SHUTDOWN)
@@ -1736,15 +1822,11 @@ static int execute_shutdown_or_sleep(
&reply,
"ss", a->target, "replace-irreversibly");
if (r < 0)
- goto error;
-
- r = sd_bus_message_read(reply, "o", &p);
- if (r < 0)
- goto error;
+ goto fail;
- r = free_and_strdup(&m->action_job, p);
+ r = strdup_job(reply, &m->action_job);
if (r < 0)
- goto error;
+ goto fail;
m->delayed_action = a;
@@ -1753,7 +1835,7 @@ static int execute_shutdown_or_sleep(
return 0;
-error:
+fail:
/* Tell people that they now may take a lock again */
(void) send_prepare_for(m, a, false);
@@ -1914,13 +1996,12 @@ static int verify_shutdown_creds(
interactive = flags & SD_LOGIND_INTERACTIVE;
if (multiple_sessions) {
- r = bus_verify_polkit_async(
+ r = bus_verify_polkit_async_full(
message,
- CAP_SYS_BOOT,
a->polkit_action_multiple_sessions,
- NULL,
- interactive,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0)
@@ -1935,12 +2016,12 @@ static int verify_shutdown_creds(
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED,
"Access denied to root due to active block inhibitor");
- r = bus_verify_polkit_async(message,
- CAP_SYS_BOOT,
+ r = bus_verify_polkit_async_full(
+ message,
a->polkit_action_ignore_inhibit,
- NULL,
- interactive,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0)
@@ -1950,12 +2031,12 @@ static int verify_shutdown_creds(
}
if (!multiple_sessions && !blocked) {
- r = bus_verify_polkit_async(message,
- CAP_SYS_BOOT,
+ r = bus_verify_polkit_async_full(
+ message,
a->polkit_action,
- NULL,
- interactive,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0)
@@ -1993,7 +2074,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 +2083,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,11 +2098,11 @@ 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 (flags & SD_LOGIND_REBOOT_VIA_KEXEC)
+ if (action != HANDLE_REBOOT) {
+ if (FLAGS_SET(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");
- if ((flags & SD_LOGIND_SOFT_REBOOT) || (flags & SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP))
+ if (flags & (SD_LOGIND_SOFT_REBOOT|SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS,
"Soft reboot option is only applicable with reboot operations");
}
@@ -2038,20 +2119,32 @@ static int method_do_shutdown_or_sleep(
flags = interactive ? SD_LOGIND_INTERACTIVE : 0;
}
- if ((flags & SD_LOGIND_SOFT_REBOOT) ||
- ((flags & SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP) && path_is_os_tree("/run/nextroot") > 0))
+ const HandleActionData *a = NULL;
+
+ if (FLAGS_SET(flags, SD_LOGIND_SOFT_REBOOT) ||
+ (FLAGS_SET(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())
+ else if (FLAGS_SET(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;
+
+ selected = handle_action_sleep_select(m);
+ 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));
- if (a->sleep_operation >= 0) {
+ } 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;
@@ -2074,6 +2167,14 @@ static int method_do_shutdown_or_sleep(
return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
"Not running on EFI and resume= is not set, or noresume is set. No available method to resume from hibernation");
+ case SLEEP_RESUME_DEVICE_MISSING:
+ return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
+ "Specified resume device is missing or is not an active swap device");
+
+ case SLEEP_RESUME_MISCONFIGURED:
+ return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
+ "Invalid resume config: resume= is not populated yet resume_offset= is");
+
case SLEEP_NOT_ENOUGH_SWAP_SPACE:
return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
"Not enough suitable swap space for hibernation available on compatible block devices and file systems");
@@ -2082,18 +2183,25 @@ 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)
return r;
+ if (m->delayed_action)
+ return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
+ "Action %s already in progress, refusing requested %s operation.",
+ handle_action_to_string(m->delayed_action->handle),
+ handle_action_to_string(a->handle));
+
/* reset case we're shorting a scheduled shutdown */
m->unlink_nologin = false;
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 +2217,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 +2227,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 +2237,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 +2247,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 +2257,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 +2267,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,17 +2277,27 @@ 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,
void *userdata) {
- Manager *m = userdata;
+ Manager *m = ASSERT_PTR(userdata);
log_info("Creating /run/nologin, blocking further logins...");
@@ -2194,79 +2312,25 @@ static usec_t nologin_timeout_usec(usec_t elapse) {
return LESS_BY(elapse, 5 * USEC_PER_MINUTE);
}
-void manager_load_scheduled_shutdown(Manager *m) {
- _cleanup_fclose_ FILE *f = NULL;
- _cleanup_free_ char *usec = NULL,
- *warn_wall = NULL,
- *mode = NULL,
- *wall_message = NULL,
- *uid = NULL,
- *tty = NULL;
- int r;
-
+static void reset_scheduled_shutdown(Manager *m) {
assert(m);
- r = parse_env_file(f, SHUTDOWN_SCHEDULE_FILE,
- "USEC", &usec,
- "WARN_WALL", &warn_wall,
- "MODE", &mode,
- "WALL_MESSAGE", &wall_message,
- "UID", &uid,
- "TTY", &tty);
-
- /* reset will delete the file */
- reset_scheduled_shutdown(m);
-
- if (r == -ENOENT)
- return;
- if (r < 0)
- return (void) log_debug_errno(r, "Failed to parse " SHUTDOWN_SCHEDULE_FILE ": %m");
-
- HandleAction handle = handle_action_from_string(mode);
- if (handle < 0)
- return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse scheduled shutdown type: %s", mode);
-
- if (!usec)
- return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "USEC is required");
- if (deserialize_usec(usec, &m->scheduled_shutdown_timeout) < 0)
- return;
-
- /* assign parsed type only after we know usec is also valid */
- m->scheduled_shutdown_action = handle_action_lookup(handle);
-
- if (warn_wall) {
- r = parse_boolean(warn_wall);
- if (r < 0)
- log_debug_errno(r, "Failed to parse enabling wall messages");
- else
- m->enable_wall_messages = r;
- }
+ m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source);
+ 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);
- if (wall_message) {
- _cleanup_free_ char *unescaped = NULL;
- r = cunescape(wall_message, 0, &unescaped);
- if (r < 0)
- log_debug_errno(r, "Failed to parse wall message: %s", wall_message);
- else
- free_and_replace(m->wall_message, unescaped);
- }
+ 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);
+ m->shutdown_dry_run = false;
- if (uid) {
- r = parse_uid(uid, &m->scheduled_shutdown_uid);
- if (r < 0)
- log_debug_errno(r, "Failed to parse wall uid: %s", uid);
+ if (m->unlink_nologin) {
+ (void) unlink_or_warn("/run/nologin");
+ m->unlink_nologin = false;
}
- free_and_replace(m->scheduled_shutdown_tty, tty);
-
- r = manager_setup_shutdown_timers(m);
- if (r < 0)
- return reset_scheduled_shutdown(m);
-
- (void) manager_setup_wall_message_timer(m);
- (void) update_schedule_file(m);
-
- return;
+ (void) unlink(SHUTDOWN_SCHEDULE_FILE);
}
static int update_schedule_file(Manager *m) {
@@ -2275,7 +2339,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 +2353,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)
@@ -2319,44 +2383,23 @@ fail:
return log_error_errno(r, "Failed to write information about scheduled shutdowns: %m");
}
-static void reset_scheduled_shutdown(Manager *m) {
- assert(m);
-
- m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source);
- 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_timeout = USEC_INFINITY;
- m->scheduled_shutdown_uid = UID_INVALID;
- m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty);
- m->shutdown_dry_run = false;
-
- if (m->unlink_nologin) {
- (void) unlink_or_warn("/run/nologin");
- m->unlink_nologin = false;
- }
-
- (void) unlink(SHUTDOWN_SCHEDULE_FILE);
-}
-
static int manager_scheduled_shutdown_handler(
sd_event_source *s,
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) {
- r = -EALREADY;
- log_error("Scheduled shutdown to %s failed: shutdown or sleep operation already in progress", a->target);
+ r = log_error_errno(SYNTHETIC_ERRNO(EALREADY),
+ "Scheduled shutdown to %s failed: shutdown or sleep operation already in progress.",
+ a->target);
goto error;
}
@@ -2373,7 +2416,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;
@@ -2386,6 +2429,111 @@ error:
return r;
}
+static int manager_setup_shutdown_timers(Manager* m) {
+ int r;
+
+ assert(m);
+
+ r = event_reset_time(m->event, &m->scheduled_shutdown_timeout_source,
+ CLOCK_REALTIME,
+ m->scheduled_shutdown_timeout, 0,
+ manager_scheduled_shutdown_handler, m,
+ 0, "scheduled-shutdown-timeout", true);
+ if (r < 0)
+ goto fail;
+
+ r = event_reset_time(m->event, &m->nologin_timeout_source,
+ CLOCK_REALTIME,
+ nologin_timeout_usec(m->scheduled_shutdown_timeout), 0,
+ nologin_timeout_handler, m,
+ 0, "nologin-timeout", true);
+ if (r < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source);
+ m->nologin_timeout_source = sd_event_source_unref(m->nologin_timeout_source);
+
+ return r;
+}
+
+void manager_load_scheduled_shutdown(Manager *m) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *usec = NULL,
+ *warn_wall = NULL,
+ *mode = NULL,
+ *wall_message = NULL,
+ *uid = NULL,
+ *tty = NULL;
+ int r;
+
+ assert(m);
+
+ r = parse_env_file(f, SHUTDOWN_SCHEDULE_FILE,
+ "USEC", &usec,
+ "WARN_WALL", &warn_wall,
+ "MODE", &mode,
+ "WALL_MESSAGE", &wall_message,
+ "UID", &uid,
+ "TTY", &tty);
+
+ /* reset will delete the file */
+ reset_scheduled_shutdown(m);
+
+ if (r == -ENOENT)
+ return;
+ if (r < 0)
+ return (void) log_debug_errno(r, "Failed to parse " SHUTDOWN_SCHEDULE_FILE ": %m");
+
+ HandleAction handle = handle_action_from_string(mode);
+ if (handle < 0)
+ return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse scheduled shutdown type: %s", mode);
+
+ if (!usec)
+ return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "USEC is required");
+ if (deserialize_usec(usec, &m->scheduled_shutdown_timeout) < 0)
+ return;
+
+ /* assign parsed type only after we know usec is also valid */
+ m->scheduled_shutdown_action = handle;
+
+ if (warn_wall) {
+ r = parse_boolean(warn_wall);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse enabling wall messages");
+ else
+ m->enable_wall_messages = r;
+ }
+
+ if (wall_message) {
+ _cleanup_free_ char *unescaped = NULL;
+ r = cunescape(wall_message, 0, &unescaped);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse wall message: %s", wall_message);
+ else
+ free_and_replace(m->wall_message, unescaped);
+ }
+
+ if (uid) {
+ r = parse_uid(uid, &m->scheduled_shutdown_uid);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse wall uid: %s", uid);
+ }
+
+ free_and_replace(m->scheduled_shutdown_tty, tty);
+
+ r = manager_setup_shutdown_timers(m);
+ if (r < 0)
+ return reset_scheduled_shutdown(m);
+
+ (void) manager_setup_wall_message_timer(m);
+ (void) update_schedule_file(m);
+
+ return;
+}
+
static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = ASSERT_PTR(userdata);
HandleAction handle;
@@ -2410,15 +2558,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;
@@ -2438,34 +2585,6 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
return sd_bus_reply_method_return(message, NULL);
}
-static int manager_setup_shutdown_timers(Manager* m) {
- int r;
-
- r = event_reset_time(m->event, &m->scheduled_shutdown_timeout_source,
- CLOCK_REALTIME,
- m->scheduled_shutdown_timeout, 0,
- manager_scheduled_shutdown_handler, m,
- 0, "scheduled-shutdown-timeout", true);
- if (r < 0)
- goto fail;
-
- r = event_reset_time(m->event, &m->nologin_timeout_source,
- CLOCK_REALTIME,
- nologin_timeout_usec(m->scheduled_shutdown_timeout), 0,
- nologin_timeout_handler, m,
- 0, "nologin-timeout", true);
- if (r < 0)
- goto fail;
-
- return 0;
-
-fail:
- m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source);
- m->nologin_timeout_source = sd_event_source_unref(m->nologin_timeout_source);
-
- return r;
-}
-
static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = ASSERT_PTR(userdata);
const HandleActionData *a;
@@ -2474,22 +2593,18 @@ 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");
r = bus_verify_polkit_async(
message,
- CAP_SYS_BOOT,
a->polkit_action,
- NULL,
- false,
- UID_INVALID,
+ /* details= */ NULL,
&m->polkit_registry,
error);
if (r < 0)
@@ -2528,28 +2643,46 @@ 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;
+ bool multiple_sessions, challenge, blocked, check_unit_state = true;
+ 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);
+ if (selected < 0)
+ return sd_bus_reply_method_return(message, "s", "na");
+
+ check_unit_state = false; /* Already handled by handle_action_sleep_select */
+
+ assert_se(a = handle_action_lookup(selected));
- if (a->sleep_operation >= 0) {
+ } 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,27 +2699,27 @@ 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 (check_unit_state && 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;
}
}
if (multiple_sessions) {
- r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action_multiple_sessions, NULL, UID_INVALID, &challenge, error);
+ r = bus_test_polkit(
+ message,
+ a->polkit_action_multiple_sessions,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ &challenge,
+ error);
if (r < 0)
return r;
@@ -2599,7 +2732,13 @@ static int method_can_shutdown_or_sleep(
}
if (blocked) {
- r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action_ignore_inhibit, NULL, UID_INVALID, &challenge, error);
+ r = bus_test_polkit(
+ message,
+ a->polkit_action_ignore_inhibit,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ &challenge,
+ error);
if (r < 0)
return r;
@@ -2617,7 +2756,13 @@ static int method_can_shutdown_or_sleep(
/* If neither inhibit nor multiple sessions
* apply then just check the normal policy */
- r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action, NULL, UID_INVALID, &challenge, error);
+ r = bus_test_polkit(
+ message,
+ a->polkit_action,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ &challenge,
+ error);
if (r < 0)
return r;
@@ -2636,57 +2781,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(
@@ -2726,6 +2863,9 @@ static int method_set_reboot_parameter(
if (r < 0)
return r;
+ if (!reboot_parameter_is_valid(arg))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid reboot parameter '%s'.", arg);
+
r = detect_container();
if (r < 0)
return r;
@@ -2733,14 +2873,12 @@ static int method_set_reboot_parameter(
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED,
"Reboot parameter not supported in containers, refusing.");
- r = bus_verify_polkit_async(message,
- CAP_SYS_ADMIN,
- "org.freedesktop.login1.set-reboot-parameter",
- NULL,
- false,
- UID_INVALID,
- &m->polkit_registry,
- error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.login1.set-reboot-parameter",
+ /* details= */ NULL,
+ &m->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -2771,10 +2909,9 @@ static int method_can_reboot_parameter(
return return_test_polkit(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.set-reboot-parameter",
- NULL,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
error);
}
@@ -2852,14 +2989,12 @@ static int method_set_reboot_to_firmware_setup(
/* non-EFI case: $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP is set to on */
use_efi = false;
- r = bus_verify_polkit_async(message,
- CAP_SYS_ADMIN,
- "org.freedesktop.login1.set-reboot-to-firmware-setup",
- NULL,
- false,
- UID_INVALID,
- &m->polkit_registry,
- error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.login1.set-reboot-to-firmware-setup",
+ /* details= */ NULL,
+ &m->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -2916,10 +3051,9 @@ static int method_can_reboot_to_firmware_setup(
return return_test_polkit(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.set-reboot-to-firmware-setup",
- NULL,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
error);
}
@@ -3016,14 +3150,12 @@ static int method_set_reboot_to_boot_loader_menu(
/* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU is set to on */
use_efi = false;
- r = bus_verify_polkit_async(message,
- CAP_SYS_ADMIN,
- "org.freedesktop.login1.set-reboot-to-boot-loader-menu",
- NULL,
- false,
- UID_INVALID,
- &m->polkit_registry,
- error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.login1.set-reboot-to-boot-loader-menu",
+ /* details= */ NULL,
+ &m->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -3091,10 +3223,9 @@ static int method_can_reboot_to_boot_loader_menu(
return return_test_polkit(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.set-reboot-to-boot-loader-menu",
- NULL,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
error);
}
@@ -3215,14 +3346,12 @@ static int method_set_reboot_to_boot_loader_entry(
/* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY is set to on */
use_efi = false;
- r = bus_verify_polkit_async(message,
- CAP_SYS_ADMIN,
- "org.freedesktop.login1.set-reboot-to-boot-loader-entry",
- NULL,
- false,
- UID_INVALID,
- &m->polkit_registry,
- error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.login1.set-reboot-to-boot-loader-entry",
+ /* details= */ NULL,
+ &m->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -3283,10 +3412,9 @@ static int method_can_reboot_to_boot_loader_entry(
return return_test_polkit(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.set-reboot-to-boot-loader-entry",
- NULL,
- UID_INVALID,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
error);
}
@@ -3357,14 +3485,12 @@ static int method_set_wall_message(
m->enable_wall_messages == enable_wall_messages)
goto done;
- r = bus_verify_polkit_async(message,
- CAP_SYS_ADMIN,
- "org.freedesktop.login1.set-wall-message",
- NULL,
- false,
- UID_INVALID,
- &m->polkit_registry,
- error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.login1.set-wall-message",
+ /* details= */ NULL,
+ &m->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -3389,7 +3515,6 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error
Manager *m = ASSERT_PTR(userdata);
InhibitMode mm;
InhibitWhat w;
- pid_t pid;
uid_t uid;
int r;
@@ -3424,7 +3549,6 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error
r = bus_verify_polkit_async(
message,
- CAP_SYS_BOOT,
w == INHIBIT_SHUTDOWN ? (mm == INHIBIT_BLOCK ? "org.freedesktop.login1.inhibit-block-shutdown" : "org.freedesktop.login1.inhibit-delay-shutdown") :
w == INHIBIT_SLEEP ? (mm == INHIBIT_BLOCK ? "org.freedesktop.login1.inhibit-block-sleep" : "org.freedesktop.login1.inhibit-delay-sleep") :
w == INHIBIT_IDLE ? "org.freedesktop.login1.inhibit-block-idle" :
@@ -3433,9 +3557,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error
w == INHIBIT_HANDLE_REBOOT_KEY ? "org.freedesktop.login1.inhibit-handle-reboot-key" :
w == INHIBIT_HANDLE_HIBERNATE_KEY ? "org.freedesktop.login1.inhibit-handle-hibernate-key" :
"org.freedesktop.login1.inhibit-handle-lid-switch",
- NULL,
- false,
- UID_INVALID,
+ /* details= */ NULL,
&m->polkit_registry,
error);
if (r < 0)
@@ -3443,7 +3565,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
- r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID, &creds);
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD, &creds);
if (r < 0)
return r;
@@ -3451,14 +3573,10 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error
if (r < 0)
return r;
- r = sd_bus_creds_get_pid(creds, &pid);
+ r = bus_creds_get_pidref(creds, &pidref);
if (r < 0)
return r;
- r = pidref_set_pid(&pidref, pid);
- if (r < 0)
- return sd_bus_error_set_errnof(error, r, "Failed pin source process "PID_FMT": %m", pid);
-
if (hashmap_size(m->inhibitors) >= m->inhibitors_max)
return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
"Maximum number of inhibitors (%" PRIu64 ") reached, refusing further inhibitors.",
@@ -3521,6 +3639,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),
@@ -3581,6 +3700,11 @@ static const sd_bus_vtable manager_vtable[] = {
SD_BUS_RESULT("a(susso)", sessions),
method_list_sessions,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ListSessionsEx",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("a(sussussbto)", sessions),
+ method_list_sessions_ex,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("ListUsers",
SD_BUS_NO_ARGS,
SD_BUS_RESULT("a(uso)", users),
@@ -3651,7 +3775,7 @@ static const sd_bus_vtable manager_vtable[] = {
SD_BUS_ARGS("s", session_id),
SD_BUS_NO_RESULT,
method_release_session,
- 0),
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("ActivateSession",
SD_BUS_ARGS("s", session_id),
SD_BUS_NO_RESULT,
@@ -3792,6 +3916,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 +3956,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,
@@ -3928,30 +4062,32 @@ const BusObjectImplementation manager_object = {
&user_object),
};
-static int session_jobs_reply(Session *s, uint32_t jid, const char *unit, const char *result) {
+static void session_jobs_reply(Session *s, uint32_t jid, const char *unit, const char *result) {
assert(s);
assert(unit);
if (!s->started)
- return 0;
+ return;
if (result && !streq(result, "done")) {
_cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED,
"Job %u for unit '%s' failed with '%s'", jid, unit, result);
- return session_send_create_reply(s, &e);
+
+ (void) session_send_create_reply(s, &e);
+ (void) session_send_upgrade_reply(s, &e);
+ return;
}
- return session_send_create_reply(s, NULL);
+ (void) session_send_create_reply(s, /* error= */ NULL);
+ (void) session_send_upgrade_reply(s, /* error= */ NULL);
}
int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
- const char *path, *result, *unit;
Manager *m = ASSERT_PTR(userdata);
- Session *session;
+ const char *path, *result, *unit;
uint32_t id;
- User *user;
int r;
assert(message);
@@ -3974,11 +4110,14 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err
return 0;
}
+ Session *session;
+ User *user;
+
session = hashmap_get(m->session_units, unit);
if (session) {
if (streq_ptr(path, session->scope_job)) {
session->scope_job = mfree(session->scope_job);
- (void) session_jobs_reply(session, id, unit, result);
+ session_jobs_reply(session, id, unit, result);
session_save(session);
user_save(session->user);
@@ -3989,13 +4128,20 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err
user = hashmap_get(m->user_units, unit);
if (user) {
- if (streq_ptr(path, user->service_job)) {
- user->service_job = mfree(user->service_job);
-
- LIST_FOREACH(sessions_by_user, s, user->sessions)
- (void) session_jobs_reply(s, id, unit, NULL /* don't propagate user service failures to the client */);
-
- user_save(user);
+ /* If the user is stopping, we're tracking stop jobs here. So don't send reply. */
+ if (!user->stopping) {
+ char **user_job;
+ FOREACH_ARGUMENT(user_job, &user->runtime_dir_job, &user->service_manager_job)
+ if (streq_ptr(path, *user_job)) {
+ *user_job = mfree(*user_job);
+
+ LIST_FOREACH(sessions_by_user, s, user->sessions)
+ /* Don't propagate user service failures to the client */
+ session_jobs_reply(s, id, unit, /* error = */ NULL);
+
+ user_save(user);
+ break;
+ }
}
user_add_to_gc_queue(user);
@@ -4102,49 +4248,34 @@ int manager_send_changed(Manager *manager, const char *property, ...) {
l);
}
-static int strdup_job(sd_bus_message *reply, char **job) {
- const char *j;
- char *copy;
- int r;
-
- r = sd_bus_message_read(reply, "o", &j);
- if (r < 0)
- return r;
-
- copy = strdup(j);
- if (!copy)
- return -ENOMEM;
-
- *job = copy;
- return 1;
-}
-
int manager_start_scope(
Manager *manager,
const char *scope,
const PidRef *pidref,
+ bool allow_pidfd,
const char *slice,
const char *description,
- char **wants,
- char **after,
+ const char * const *requires,
+ const char * const *extra_after,
const char *requires_mounts_for,
sd_bus_message *more_properties,
sd_bus_error *error,
- char **job) {
+ char **ret_job) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
int r;
assert(manager);
assert(scope);
assert(pidref_is_set(pidref));
- assert(job);
+ assert(ret_job);
r = bus_message_new_method_call(manager->bus, &m, bus_systemd_mgr, "StartTransientUnit");
if (r < 0)
return r;
- r = sd_bus_message_append(m, "ss", strempty(scope), "fail");
+ r = sd_bus_message_append(m, "ss", scope, "fail");
if (r < 0)
return r;
@@ -4164,13 +4295,17 @@ int manager_start_scope(
return r;
}
- STRV_FOREACH(i, wants) {
- r = sd_bus_message_append(m, "(sv)", "Wants", "as", 1, *i);
+ STRV_FOREACH(i, requires) {
+ r = sd_bus_message_append(m, "(sv)", "Requires", "as", 1, *i);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "(sv)", "After", "as", 1, *i);
if (r < 0)
return r;
}
- STRV_FOREACH(i, after) {
+ STRV_FOREACH(i, extra_after) {
r = sd_bus_message_append(m, "(sv)", "After", "as", 1, *i);
if (r < 0)
return r;
@@ -4188,7 +4323,7 @@ int manager_start_scope(
if (r < 0)
return r;
- r = bus_append_scope_pidref(m, pidref);
+ r = bus_append_scope_pidref(m, pidref, allow_pidfd);
if (r < 0)
return r;
@@ -4218,20 +4353,39 @@ int manager_start_scope(
if (r < 0)
return r;
- r = sd_bus_call(manager->bus, m, 0, error, &reply);
- if (r < 0)
- return r;
+ r = sd_bus_call(manager->bus, m, 0, &e, &reply);
+ if (r < 0) {
+ /* If this failed with a property we couldn't write, this is quite likely because the server
+ * doesn't support PIDFDs yet, let's try without. */
+ if (allow_pidfd &&
+ sd_bus_error_has_names(&e, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY))
+ return manager_start_scope(
+ manager,
+ scope,
+ pidref,
+ /* allow_pidfd = */ false,
+ slice,
+ description,
+ requires,
+ extra_after,
+ requires_mounts_for,
+ more_properties,
+ error,
+ ret_job);
+
+ return sd_bus_error_move(error, &e);
+ }
- return strdup_job(reply, job);
+ return strdup_job(reply, ret_job);
}
-int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) {
+int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **ret_job) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
int r;
assert(manager);
assert(unit);
- assert(job);
+ assert(ret_job);
r = bus_call_method(
manager->bus,
@@ -4243,11 +4397,18 @@ int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error,
if (r < 0)
return r;
- return strdup_job(reply, job);
+ return strdup_job(reply, ret_job);
}
-int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode, sd_bus_error *error, char **ret_job) {
+int manager_stop_unit(
+ Manager *manager,
+ const char *unit,
+ const char *job_mode,
+ sd_bus_error *ret_error,
+ char **ret_job) {
+
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(manager);
@@ -4258,22 +4419,24 @@ int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode,
manager->bus,
bus_systemd_mgr,
"StopUnit",
- error,
+ &error,
&reply,
"ss", unit, job_mode ?: "fail");
if (r < 0) {
- if (sd_bus_error_has_names(error, BUS_ERROR_NO_SUCH_UNIT,
- BUS_ERROR_LOAD_FAILED)) {
-
+ if (sd_bus_error_has_names(&error, BUS_ERROR_NO_SUCH_UNIT, BUS_ERROR_LOAD_FAILED)) {
*ret_job = NULL;
- sd_bus_error_free(error);
return 0;
}
+ sd_bus_error_move(ret_error, &error);
return r;
}
- return strdup_job(reply, ret_job);
+ r = strdup_job(reply, ret_job);
+ if (r < 0)
+ return r;
+
+ return 1;
}
int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *ret_error) {
@@ -4313,6 +4476,7 @@ int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *ret
int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, sd_bus_error *error) {
assert(manager);
assert(unit);
+ assert(SIGNAL_VALID(signo));
return bus_call_method(
manager->bus,
@@ -4320,7 +4484,10 @@ int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo
"KillUnit",
error,
NULL,
- "ssi", unit, who == KILL_LEADER ? "main" : "all", signo);
+ "ssi",
+ unit,
+ who == KILL_LEADER ? "main" : "all",
+ signo);
}
int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *ret_error) {
diff --git a/src/login/logind-dbus.h b/src/login/logind-dbus.h
index c9d5923..8459d04 100644
--- a/src/login/logind-dbus.h
+++ b/src/login/logind-dbus.h
@@ -24,9 +24,21 @@ int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error
int manager_send_changed(Manager *manager, const char *property, ...) _sentinel_;
-int manager_start_scope(Manager *manager, const char *scope, const PidRef *pidref, const char *slice, const char *description, char **wants, char **after, const char *requires_mounts_for, sd_bus_message *more_properties, sd_bus_error *error, char **job);
-int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job);
-int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode, sd_bus_error *error, char **job);
+int manager_start_scope(
+ Manager *manager,
+ const char *scope,
+ const PidRef *pidref,
+ bool allow_pidfd,
+ const char *slice,
+ const char *description,
+ const char * const *requires,
+ const char * const *extra_after,
+ const char *requires_mounts_for,
+ sd_bus_message *more_properties,
+ sd_bus_error *error,
+ char **ret_job);
+int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **ret_job);
+int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode, sd_bus_error *error, char **ret_job);
int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error);
int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, sd_bus_error *error);
int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *error);
diff --git a/src/login/logind-device.h b/src/login/logind-device.h
index 0d89613..f96c4db 100644
--- a/src/login/logind-device.h
+++ b/src/login/logind-device.h
@@ -16,7 +16,7 @@ struct Device {
dual_timestamp timestamp;
- LIST_FIELDS(struct Device, devices);
+ LIST_FIELDS(Device, devices);
LIST_HEAD(SessionDevice, session_devices);
};
diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf
index c95a3b2..7d1520b 100644
--- a/src/login/logind-gperf.gperf
+++ b/src/login/logind-gperf.gperf
@@ -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)
diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c
index 1566dab..203665c 100644
--- a/src/login/logind-inhibit.c
+++ b/src/login/logind-inhibit.c
@@ -29,13 +29,13 @@
static void inhibitor_remove_fifo(Inhibitor *i);
-int inhibitor_new(Inhibitor **ret, Manager *m, const char* id) {
+int inhibitor_new(Manager *m, const char* id, Inhibitor **ret) {
_cleanup_(inhibitor_freep) Inhibitor *i = NULL;
int r;
- assert(ret);
assert(m);
assert(id);
+ assert(ret);
i = new(Inhibitor, 1);
if (!i)
@@ -43,6 +43,8 @@ int inhibitor_new(Inhibitor **ret, Manager *m, const char* id) {
*i = (Inhibitor) {
.manager = m,
+ .id = strdup(id),
+ .state_file = path_join("/run/systemd/inhibit/", id),
.what = _INHIBIT_WHAT_INVALID,
.mode = _INHIBIT_MODE_INVALID,
.uid = UID_INVALID,
@@ -50,12 +52,9 @@ int inhibitor_new(Inhibitor **ret, Manager *m, const char* id) {
.pid = PIDREF_NULL,
};
- i->state_file = path_join("/run/systemd/inhibit", id);
- if (!i->state_file)
+ if (!i->id || !i->state_file)
return -ENOMEM;
- i->id = basename(i->state_file);
-
r = hashmap_put(m->inhibitors, i->id, i);
if (r < 0)
return r;
@@ -79,8 +78,9 @@ Inhibitor* inhibitor_free(Inhibitor *i) {
/* Note that we don't remove neither the state file nor the fifo path here, since we want both to
* survive daemon restarts */
- free(i->state_file);
free(i->fifo_path);
+ free(i->state_file);
+ free(i->id);
pidref_done(&i->pid);
@@ -270,7 +270,7 @@ int inhibitor_load(Inhibitor *i) {
if (i->fifo_path) {
_cleanup_close_ int fd = -EBADF;
- /* Let's re-open the FIFO on both sides, and close the writing side right away */
+ /* Let's reopen the FIFO on both sides, and close the writing side right away */
fd = inhibitor_create_fifo(i);
if (fd < 0)
return log_error_errno(fd, "Failed to reopen FIFO: %m");
diff --git a/src/login/logind-inhibit.h b/src/login/logind-inhibit.h
index c34c225..8f822de 100644
--- a/src/login/logind-inhibit.h
+++ b/src/login/logind-inhibit.h
@@ -32,7 +32,7 @@ struct Inhibitor {
sd_event_source *event_source;
- const char *id;
+ char *id;
char *state_file;
bool started;
@@ -51,7 +51,7 @@ struct Inhibitor {
int fifo_fd;
};
-int inhibitor_new(Inhibitor **ret, Manager *m, const char* id);
+int inhibitor_new(Manager *m, const char* id, Inhibitor **ret);
Inhibitor* inhibitor_free(Inhibitor *i);
DEFINE_TRIVIAL_CLEANUP_FUNC(Inhibitor*, inhibitor_free);
diff --git a/src/login/logind-polkit.c b/src/login/logind-polkit.c
index e4efd64..2f1523a 100644
--- a/src/login/logind-polkit.c
+++ b/src/login/logind-polkit.c
@@ -9,11 +9,8 @@ int check_polkit_chvt(sd_bus_message *message, Manager *manager, sd_bus_error *e
#if ENABLE_POLKIT
return bus_verify_polkit_async(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.chvt",
- NULL,
- false,
- UID_INVALID,
+ /* details= */ NULL,
&manager->polkit_registry,
error);
#else
diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c
index 877b9c1..0a395c6 100644
--- a/src/login/logind-seat-dbus.c
+++ b/src/login/logind-seat-dbus.c
@@ -134,11 +134,8 @@ int bus_seat_method_terminate(sd_bus_message *message, void *userdata, sd_bus_er
r = bus_verify_polkit_async(
message,
- CAP_KILL,
"org.freedesktop.login1.manage",
- NULL,
- false,
- UID_INVALID,
+ /* details= */ NULL,
&s->manager->polkit_registry,
error);
if (r < 0)
diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c
index 8d875d2..bed1f7d 100644
--- a/src/login/logind-seat.c
+++ b/src/login/logind-seat.c
@@ -25,13 +25,13 @@
#include "terminal-util.h"
#include "tmpfile-util.h"
-int seat_new(Seat** ret, Manager *m, const char *id) {
+int seat_new(Manager *m, const char *id, Seat **ret) {
_cleanup_(seat_freep) Seat *s = NULL;
int r;
- assert(ret);
assert(m);
assert(id);
+ assert(ret);
if (!seat_name_is_valid(id))
return -EINVAL;
@@ -42,14 +42,12 @@ int seat_new(Seat** ret, Manager *m, const char *id) {
*s = (Seat) {
.manager = m,
+ .id = strdup(id),
+ .state_file = path_join("/run/systemd/seats/", id),
};
-
- s->state_file = path_join("/run/systemd/seats", id);
- if (!s->state_file)
+ if (!s->id || !s->state_file)
return -ENOMEM;
- s->id = basename(s->state_file);
-
r = hashmap_put(m->seats, s->id, s);
if (r < 0)
return r;
@@ -77,6 +75,7 @@ Seat* seat_free(Seat *s) {
free(s->positions);
free(s->state_file);
+ free(s->id);
return mfree(s);
}
diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h
index 2d18e75..76a69e6 100644
--- a/src/login/logind-seat.h
+++ b/src/login/logind-seat.h
@@ -26,10 +26,10 @@ struct Seat {
LIST_FIELDS(Seat, gc_queue);
};
-int seat_new(Seat **ret, Manager *m, const char *id);
+int seat_new(Manager *m, const char *id, Seat **ret);
Seat* seat_free(Seat *s);
-DEFINE_TRIVIAL_CLEANUP_FUNC(Seat *, seat_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Seat*, seat_free);
int seat_save(Seat *s);
int seat_load(Seat *s);
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
index a136ae4..f20ef4f 100644
--- a/src/login/logind-session-dbus.c
+++ b/src/login/logind-session-dbus.c
@@ -158,13 +158,12 @@ int bus_session_method_terminate(sd_bus_message *message, void *userdata, sd_bus
assert(message);
- r = bus_verify_polkit_async(
+ r = bus_verify_polkit_async_full(
message,
- CAP_KILL,
"org.freedesktop.login1.manage",
- NULL,
- false,
+ /* details= */ NULL,
s->user->user_record->uid,
+ /* flags= */ 0,
&s->manager->polkit_registry,
error);
if (r < 0)
@@ -204,13 +203,12 @@ int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_erro
assert(message);
- r = bus_verify_polkit_async(
+ r = bus_verify_polkit_async_full(
message,
- CAP_SYS_ADMIN,
"org.freedesktop.login1.lock-sessions",
- NULL,
- false,
+ /* details= */ NULL,
s->user->user_record->uid,
+ /* flags= */ 0,
&s->manager->polkit_registry,
error);
if (r < 0)
@@ -218,7 +216,9 @@ int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_erro
if (r == 0)
return 1; /* Will call us back */
- r = session_send_lock(s, strstr(sd_bus_message_get_member(message), "Lock"));
+ r = session_send_lock(s, /* lock= */ strstr(sd_bus_message_get_member(message), "Lock"));
+ if (r == -ENOTTY)
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen.");
if (r < 0)
return r;
@@ -250,7 +250,7 @@ static int method_set_idle_hint(sd_bus_message *message, void *userdata, sd_bus_
r = session_set_idle_hint(s, b);
if (r == -ENOTTY)
- return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical sessions.");
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical and non-user sessions.");
if (r < 0)
return r;
@@ -280,7 +280,11 @@ static int method_set_locked_hint(sd_bus_message *message, void *userdata, sd_bu
if (uid != 0 && uid != s->user->user_record->uid)
return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may set locked hint");
- session_set_locked_hint(s, b);
+ r = session_set_locked_hint(s, b);
+ if (r == -ENOTTY)
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen.");
+ if (r < 0)
+ return r;
return sd_bus_reply_method_return(message, NULL);
}
@@ -309,13 +313,12 @@ int bus_session_method_kill(sd_bus_message *message, void *userdata, sd_bus_erro
if (!SIGNAL_VALID(signo))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo);
- r = bus_verify_polkit_async(
+ r = bus_verify_polkit_async_full(
message,
- CAP_KILL,
"org.freedesktop.login1.manage",
- NULL,
- false,
+ /* details= */ NULL,
s->user->user_record->uid,
+ /* flags= */ 0,
&s->manager->polkit_registry,
error);
if (r < 0)
@@ -390,6 +393,9 @@ static int method_set_type(sd_bus_message *message, void *userdata, sd_bus_error
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid session type '%s'", t);
+ if (!SESSION_CLASS_CAN_CHANGE_TYPE(s->class))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support changing type.");
+
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You must be in control of this session to set type");
@@ -398,6 +404,62 @@ static int method_set_type(sd_bus_message *message, void *userdata, sd_bus_error
return sd_bus_reply_method_return(message, NULL);
}
+static int method_set_class(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ Session *s = ASSERT_PTR(userdata);
+ SessionClass class;
+ const char *c;
+ uid_t uid;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &c);
+ if (r < 0)
+ return r;
+
+ class = session_class_from_string(c);
+ if (class < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid session class '%s'", c);
+
+ /* For now, we'll allow only upgrades user-incomplete → user */
+ if (class != SESSION_USER)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Class may only be set to 'user'");
+
+ if (s->class == SESSION_USER) /* No change, shortcut */
+ return sd_bus_reply_method_return(message, NULL);
+ if (s->class != SESSION_USER_INCOMPLETE)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Only sessions with class 'user-incomplete' may change class");
+
+ if (s->upgrade_message)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Set session class operation already in progress");
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_euid(creds, &uid);
+ if (r < 0)
+ return r;
+
+ if (uid != 0 && uid != s->user->user_record->uid)
+ return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may change its class");
+
+ session_set_class(s, class);
+
+ s->upgrade_message = sd_bus_message_ref(message);
+
+ r = session_send_upgrade_reply(s, /* error= */ NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
static int method_set_display(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Session *s = ASSERT_PTR(userdata);
const char *display;
@@ -473,6 +535,9 @@ static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_er
if (!DEVICE_MAJOR_VALID(major) || !DEVICE_MINOR_VALID(minor))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Device major/minor is not valid.");
+ if (!SESSION_CLASS_CAN_TAKE_DEVICE(s->class))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support taking device control.");
+
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You are not in control of this session");
@@ -768,6 +833,9 @@ int session_send_lock(Session *s, bool lock) {
assert(s);
+ if (!SESSION_CLASS_CAN_LOCK(s->class))
+ return -ENOTTY;
+
p = session_bus_path(s);
if (!p)
return -ENOMEM;
@@ -789,6 +857,9 @@ int session_send_lock_all(Manager *m, bool lock) {
HASHMAP_FOREACH(session, m->sessions) {
int k;
+ if (!SESSION_CLASS_CAN_LOCK(session->class))
+ continue;
+
k = session_send_lock(session, lock);
if (k < 0)
r = k;
@@ -797,20 +868,23 @@ int session_send_lock_all(Manager *m, bool lock) {
return r;
}
-static bool session_ready(Session *s) {
+static bool session_job_pending(Session *s) {
assert(s);
+ assert(s->user);
- /* Returns true when the session is ready, i.e. all jobs we enqueued for it are done (regardless if successful or not) */
+ /* Check if we have some jobs enqueued and not finished yet. Each time we get JobRemoved signal about
+ * relevant units, session_send_create_reply and hence us is called (see match_job_removed).
+ * Note that we don't care about job result here. */
- return !s->scope_job &&
- !s->user->service_job;
+ return s->scope_job ||
+ s->user->runtime_dir_job ||
+ (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) && s->user->service_manager_job);
}
int session_send_create_reply(Session *s, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
_cleanup_close_ int fifo_fd = -EBADF;
_cleanup_free_ char *p = NULL;
- int r;
assert(s);
@@ -820,7 +894,9 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
if (!s->create_message)
return 0;
- if (!sd_bus_error_is_set(error) && !session_ready(s))
+ /* If error occurred, return it immediately. Otherwise let's wait for all jobs to finish before
+ * continuing. */
+ if (!sd_bus_error_is_set(error) && session_job_pending(s))
return 0;
c = TAKE_PTR(s->create_message);
@@ -831,10 +907,6 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
if (fifo_fd < 0)
return fifo_fd;
- r = session_watch_pidfd(s);
- if (r < 0)
- return r;
-
/* Update the session state file before we notify the client about the result. */
session_save(s);
@@ -865,6 +937,26 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
false);
}
+int session_send_upgrade_reply(Session *s, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
+ assert(s);
+
+ if (!s->upgrade_message)
+ return 0;
+
+ /* See comments in session_send_create_reply */
+ if (!sd_bus_error_is_set(error) && session_job_pending(s))
+ return 0;
+
+ c = TAKE_PTR(s->upgrade_message);
+ if (error)
+ return sd_bus_reply_method_error(c, error);
+
+ session_save(s);
+
+ return sd_bus_reply_method_return(c, NULL);
+}
+
static const sd_bus_vtable session_vtable[] = {
SD_BUS_VTABLE_START(0),
@@ -885,7 +977,7 @@ static const sd_bus_vtable session_vtable[] = {
SD_BUS_PROPERTY("Leader", "u", bus_property_get_pid, offsetof(Session, leader.pid), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Audit", "u", NULL, offsetof(Session, audit_id), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Session, type), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
- SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Session, class), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Session, class), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Active", "b", property_get_active, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("State", "s", property_get_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("IdleHint", "b", property_get_idle_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
@@ -943,6 +1035,11 @@ static const sd_bus_vtable session_vtable[] = {
SD_BUS_NO_RESULT,
method_set_type,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetClass",
+ SD_BUS_ARGS("s", class),
+ SD_BUS_NO_RESULT,
+ method_set_class,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("SetDisplay",
SD_BUS_ARGS("s", display),
SD_BUS_NO_RESULT,
diff --git a/src/login/logind-session-dbus.h b/src/login/logind-session-dbus.h
index 751ca86..a026562 100644
--- a/src/login/logind-session-dbus.h
+++ b/src/login/logind-session-dbus.h
@@ -16,6 +16,7 @@ int session_send_lock(Session *s, bool lock);
int session_send_lock_all(Manager *m, bool lock);
int session_send_create_reply(Session *s, sd_bus_error *error);
+int session_send_upgrade_reply(Session *s, sd_bus_error *error);
int bus_session_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_error *error);
diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c
index 44d8d52..c924f1e 100644
--- a/src/login/logind-session-device.c
+++ b/src/login/logind-session-device.c
@@ -11,6 +11,7 @@
#include "alloc-util.h"
#include "bus-util.h"
#include "daemon-util.h"
+#include "device-util.h"
#include "fd-util.h"
#include "logind-session-dbus.h"
#include "logind-session-device.h"
@@ -114,7 +115,8 @@ static int sd_drmdropmaster(int fd) {
}
static int session_device_open(SessionDevice *sd, bool active) {
- int fd, r;
+ _cleanup_close_ int fd = -EBADF;
+ int r;
assert(sd);
assert(sd->type != DEVICE_TYPE_UNKNOWN);
@@ -132,10 +134,8 @@ static int session_device_open(SessionDevice *sd, bool active) {
/* Weird legacy DRM semantics might return an error even though we're master. No way to detect
* that so fail at all times and let caller retry in inactive state. */
r = sd_drmsetmaster(fd);
- if (r < 0) {
- (void) close_nointr(fd);
+ if (r < 0)
return r;
- }
} else
/* DRM-Master is granted to the first user who opens a device automatically (ughh,
* racy!). Hence, we just drop DRM-Master in case we were the first. */
@@ -153,7 +153,7 @@ static int session_device_open(SessionDevice *sd, bool active) {
break;
}
- return fd;
+ return TAKE_FD(fd);
}
static int session_device_start(SessionDevice *sd) {
@@ -239,22 +239,21 @@ static void session_device_stop(SessionDevice *sd) {
}
static DeviceType detect_device_type(sd_device *dev) {
- const char *sysname, *subsystem;
- DeviceType type = DEVICE_TYPE_UNKNOWN;
+ const char *sysname;
- if (sd_device_get_sysname(dev, &sysname) < 0 ||
- sd_device_get_subsystem(dev, &subsystem) < 0)
- return type;
+ if (sd_device_get_sysname(dev, &sysname) < 0)
+ return DEVICE_TYPE_UNKNOWN;
- if (streq(subsystem, "drm")) {
+ if (device_in_subsystem(dev, "drm")) {
if (startswith(sysname, "card"))
- type = DEVICE_TYPE_DRM;
- } else if (streq(subsystem, "input")) {
+ return DEVICE_TYPE_DRM;
+
+ } else if (device_in_subsystem(dev, "input")) {
if (startswith(sysname, "event"))
- type = DEVICE_TYPE_EVDEV;
+ return DEVICE_TYPE_EVDEV;
}
- return type;
+ return DEVICE_TYPE_UNKNOWN;
}
static int session_device_verify(SessionDevice *sd) {
@@ -315,31 +314,28 @@ static int session_device_verify(SessionDevice *sd) {
if (sd->device->seat != sd->session->seat)
return -EPERM;
- sd->node = strdup(node);
- if (!sd->node)
- return -ENOMEM;
-
- return 0;
+ return strdup_to(&sd->node, node);
}
-int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
+int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **ret) {
SessionDevice *sd;
int r;
assert(s);
- assert(out);
if (!s->seat)
return -EPERM;
- sd = new0(SessionDevice, 1);
+ sd = new(SessionDevice, 1);
if (!sd)
return -ENOMEM;
- sd->session = s;
- sd->dev = dev;
- sd->fd = -EBADF;
- sd->type = DEVICE_TYPE_UNKNOWN;
+ *sd = (SessionDevice) {
+ .session = s,
+ .dev = dev,
+ .fd = -EBADF,
+ .type = DEVICE_TYPE_UNKNOWN,
+ };
r = session_device_verify(sd);
if (r < 0)
@@ -370,7 +366,9 @@ int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **
LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
- *out = sd;
+ if (ret)
+ *ret = sd;
+
return 0;
error:
diff --git a/src/login/logind-session-device.h b/src/login/logind-session-device.h
index 04654d1..06a8f80 100644
--- a/src/login/logind-session-device.h
+++ b/src/login/logind-session-device.h
@@ -27,7 +27,7 @@ struct SessionDevice {
LIST_FIELDS(struct SessionDevice, sd_by_device);
};
-int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out);
+int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **ret);
SessionDevice *session_device_free(SessionDevice *sd);
DEFINE_TRIVIAL_CLEANUP_FUNC(SessionDevice*, session_device_free);
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 3988e55..4713aa0 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -15,6 +15,7 @@
#include "audit-util.h"
#include "bus-error.h"
#include "bus-util.h"
+#include "daemon-util.h"
#include "devnum-util.h"
#include "env-file.h"
#include "escape.h"
@@ -37,7 +38,7 @@
#include "strv.h"
#include "terminal-util.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#define RELEASE_USEC (20*USEC_PER_SEC)
@@ -45,13 +46,13 @@
static void session_remove_fifo(Session *s);
static void session_restore_vt(Session *s);
-int session_new(Session **ret, Manager *m, const char *id) {
+int session_new(Manager *m, const char *id, Session **ret) {
_cleanup_(session_freep) Session *s = NULL;
int r;
- assert(ret);
assert(m);
assert(id);
+ assert(ret);
if (!session_id_valid(id))
return -EINVAL;
@@ -62,19 +63,17 @@ int session_new(Session **ret, Manager *m, const char *id) {
*s = (Session) {
.manager = m,
+ .id = strdup(id),
+ .state_file = path_join("/run/systemd/sessions/", id),
.fifo_fd = -EBADF,
.vtfd = -EBADF,
.audit_id = AUDIT_SESSION_INVALID,
.tty_validity = _TTY_VALIDITY_INVALID,
.leader = PIDREF_NULL,
};
-
- s->state_file = path_join("/run/systemd/sessions", id);
- if (!s->state_file)
+ if (!s->id || !s->state_file)
return -ENOMEM;
- s->id = basename(s->state_file);
-
s->devices = hashmap_new(&devt_hash_ops);
if (!s->devices)
return -ENOMEM;
@@ -87,12 +86,53 @@ int session_new(Session **ret, Manager *m, const char *id) {
return 0;
}
-static void session_reset_leader(Session *s) {
+static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(s->leader.fd == fd);
+ session_stop(s, /* force= */ false);
+
+ return 1;
+}
+
+static int session_watch_pidfd(Session *s) {
+ int r;
+
+ assert(s);
+ assert(s->manager);
+ assert(pidref_is_set(&s->leader));
+
+ if (s->leader.fd < 0)
+ return 0;
+
+ r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd");
+
+ return 0;
+}
+
+static void session_reset_leader(Session *s, bool keep_fdstore) {
assert(s);
+ if (!keep_fdstore) {
+ /* Clear fdstore if we're asked to, no matter if s->leader is set or not, so that when
+ * initially deserializing leader fd we clear the old fd too. */
+ (void) notify_remove_fd_warnf("session-%s-leader-fd", s->id);
+ s->leader_fd_saved = false;
+ }
+
if (!pidref_is_set(&s->leader))
return;
+ s->leader_pidfd_event_source = sd_event_source_disable_unref(s->leader_pidfd_event_source);
+
(void) hashmap_remove_value(s->manager->sessions_by_leader, &s->leader, s);
return pidref_done(&s->leader);
@@ -104,10 +144,12 @@ Session* session_free(Session *s) {
if (!s)
return NULL;
+ sd_event_source_unref(s->stop_on_idle_event_source);
+
if (s->in_gc_queue)
LIST_REMOVE(gc_queue, s->manager->session_gc_queue, s);
- s->timer_event_source = sd_event_source_unref(s->timer_event_source);
+ sd_event_source_unref(s->timer_event_source);
session_drop_controller(s);
@@ -142,9 +184,10 @@ Session* session_free(Session *s) {
free(s->scope_job);
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ true);
sd_bus_message_unref(s->create_message);
+ sd_bus_message_unref(s->upgrade_message);
free(s->tty);
free(s->display);
@@ -160,10 +203,9 @@ Session* session_free(Session *s) {
/* Note that we remove neither the state file nor the fifo path here, since we want both to survive
* daemon restarts */
- free(s->state_file);
free(s->fifo_path);
-
- sd_event_source_unref(s->stop_on_idle_event_source);
+ free(s->state_file);
+ free(s->id);
return mfree(s);
}
@@ -188,15 +230,27 @@ int session_set_leader_consume(Session *s, PidRef _leader) {
if (pidref_equal(&s->leader, &pidref))
return 0;
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ false);
s->leader = TAKE_PIDREF(pidref);
+ r = session_watch_pidfd(s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch leader pidfd for session '%s': %m", s->id);
+
r = hashmap_ensure_put(&s->manager->sessions_by_leader, &pidref_hash_ops, &s->leader, s);
if (r < 0)
return r;
assert(r > 0);
+ if (s->leader.fd >= 0) {
+ r = notify_push_fdf(s->leader.fd, "session-%s-leader-fd", s->id);
+ if (r < 0)
+ log_warning_errno(r, "Failed to push leader pidfd for session '%s', ignoring: %m", s->id);
+ else
+ s->leader_fd_saved = true;
+ }
+
(void) audit_session_from_pid(s->leader.pid, &s->audit_id);
return 1;
@@ -240,16 +294,18 @@ int session_save(Session *s) {
"# This is private data. Do not parse.\n"
"UID="UID_FMT"\n"
"USER=%s\n"
- "ACTIVE=%i\n"
- "IS_DISPLAY=%i\n"
+ "ACTIVE=%s\n"
+ "IS_DISPLAY=%s\n"
"STATE=%s\n"
- "REMOTE=%i\n",
+ "REMOTE=%s\n"
+ "LEADER_FD_SAVED=%s\n",
s->user->user_record->uid,
s->user->user_record->user_name,
- session_is_active(s),
- s->user->display == s,
+ one_zero(session_is_active(s)),
+ one_zero(s->user->display == s),
session_state_to_string(session_get_state(s)),
- s->remote);
+ one_zero(s->remote),
+ one_zero(s->leader_fd_saved));
if (s->type >= 0)
fprintf(f, "TYPE=%s\n", session_type_to_string(s->type));
@@ -377,33 +433,27 @@ static int session_load_devices(Session *s, const char *devices) {
for (const char *p = devices;;) {
_cleanup_free_ char *word = NULL;
- SessionDevice *sd;
dev_t dev;
int k;
k = extract_first_word(&p, &word, NULL, 0);
- if (k == 0)
- break;
- if (k < 0) {
- r = k;
+ if (k <= 0) {
+ RET_GATHER(r, k);
break;
}
k = parse_devnum(word, &dev);
if (k < 0) {
- r = k;
+ RET_GATHER(r, k);
continue;
}
/* The file descriptors for loaded devices will be reattached later. */
- k = session_device_new(s, dev, false, &sd);
- if (k < 0)
- r = k;
+ RET_GATHER(r, session_device_new(s, dev, /* open_device = */ false, /* ret = */ NULL));
}
if (r < 0)
- log_error_errno(r, "Loading session devices for session %s failed: %m", s->id);
-
+ log_error_errno(r, "Failed to load some session devices for session '%s': %m", s->id);
return r;
}
@@ -414,7 +464,8 @@ int session_load(Session *s) {
*vtnr = NULL,
*state = NULL,
*position = NULL,
- *leader = NULL,
+ *leader_pid = NULL,
+ *leader_fd_saved = NULL,
*type = NULL,
*original_type = NULL,
*class = NULL,
@@ -431,32 +482,33 @@ int session_load(Session *s) {
assert(s);
r = parse_env_file(NULL, s->state_file,
- "REMOTE", &remote,
- "SCOPE", &s->scope,
- "SCOPE_JOB", &s->scope_job,
- "FIFO", &s->fifo_path,
- "SEAT", &seat,
- "TTY", &s->tty,
- "TTY_VALIDITY", &tty_validity,
- "DISPLAY", &s->display,
- "REMOTE_HOST", &s->remote_host,
- "REMOTE_USER", &s->remote_user,
- "SERVICE", &s->service,
- "DESKTOP", &s->desktop,
- "VTNR", &vtnr,
- "STATE", &state,
- "POSITION", &position,
- "LEADER", &leader,
- "TYPE", &type,
- "ORIGINAL_TYPE", &original_type,
- "CLASS", &class,
- "UID", &uid,
- "REALTIME", &realtime,
- "MONOTONIC", &monotonic,
- "CONTROLLER", &controller,
- "ACTIVE", &active,
- "DEVICES", &devices,
- "IS_DISPLAY", &is_display);
+ "REMOTE", &remote,
+ "SCOPE", &s->scope,
+ "SCOPE_JOB", &s->scope_job,
+ "FIFO", &s->fifo_path,
+ "SEAT", &seat,
+ "TTY", &s->tty,
+ "TTY_VALIDITY", &tty_validity,
+ "DISPLAY", &s->display,
+ "REMOTE_HOST", &s->remote_host,
+ "REMOTE_USER", &s->remote_user,
+ "SERVICE", &s->service,
+ "DESKTOP", &s->desktop,
+ "VTNR", &vtnr,
+ "STATE", &state,
+ "POSITION", &position,
+ "LEADER", &leader_pid,
+ "LEADER_FD_SAVED", &leader_fd_saved,
+ "TYPE", &type,
+ "ORIGINAL_TYPE", &original_type,
+ "CLASS", &class,
+ "UID", &uid,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
+ "CONTROLLER", &controller,
+ "ACTIVE", &active,
+ "DEVICES", &devices,
+ "IS_DISPLAY", &is_display);
if (r < 0)
return log_error_errno(r, "Failed to read %s: %m", s->state_file);
@@ -523,19 +575,6 @@ int session_load(Session *s) {
s->tty_validity = v;
}
- if (leader) {
- _cleanup_(pidref_done) PidRef p = PIDREF_NULL;
-
- r = pidref_set_pidstr(&p, leader);
- if (r < 0)
- log_debug_errno(r, "Failed to parse leader PID of session: %s", leader);
- else {
- r = session_set_leader_consume(s, TAKE_PIDREF(p));
- if (r < 0)
- log_warning_errno(r, "Failed to set session leader PID, ignoring: %m");
- }
- }
-
if (type) {
SessionType t;
@@ -610,6 +649,33 @@ int session_load(Session *s) {
session_restore_vt(s);
}
+ if (leader_pid) {
+ assert(!pidref_is_set(&s->leader));
+
+ r = parse_pid(leader_pid, &s->deserialized_pid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse LEADER=%s: %m", leader_pid);
+
+ if (leader_fd_saved) {
+ r = parse_boolean(leader_fd_saved);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse LEADER_FD_SAVED=%s: %m", leader_fd_saved);
+ s->leader_fd_saved = r > 0;
+
+ if (s->leader_fd_saved)
+ /* The leader fd will be acquired from fdstore later */
+ return 0;
+ }
+
+ _cleanup_(pidref_done) PidRef p = PIDREF_NULL;
+
+ r = pidref_set_pid(&p, s->deserialized_pid);
+ if (r >= 0)
+ r = session_set_leader_consume(s, TAKE_PIDREF(p));
+ if (r < 0)
+ log_warning_errno(r, "Failed to set leader PID for session '%s': %m", s->id);
+ }
+
return r;
}
@@ -651,60 +717,56 @@ int session_activate(Session *s) {
}
static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_error *error) {
+ _cleanup_free_ char *scope = NULL;
+ const char *description;
int r;
assert(s);
assert(s->user);
- if (!s->scope) {
- _cleanup_strv_free_ char **after = NULL;
- _cleanup_free_ char *scope = NULL;
- const char *description;
-
- s->scope_job = mfree(s->scope_job);
-
- scope = strjoin("session-", s->id, ".scope");
- if (!scope)
- return log_oom();
-
- description = strjoina("Session ", s->id, " of User ", s->user->user_record->user_name);
-
- /* We usually want to order session scopes after systemd-user-sessions.service since the
- * latter unit is used as login session barrier for unprivileged users. However the barrier
- * doesn't apply for root as sysadmin should always be able to log in (and without waiting
- * for any timeout to expire) in case something goes wrong during the boot process. Since
- * ordering after systemd-user-sessions.service and the user instance is optional we make use
- * of STRV_IGNORE with strv_new() to skip these order constraints when needed. */
- after = strv_new("systemd-logind.service",
- s->user->runtime_dir_service,
- !uid_is_system(s->user->user_record->uid) ? "systemd-user-sessions.service" : STRV_IGNORE,
- s->user->service);
- if (!after)
- return log_oom();
-
- r = manager_start_scope(
- s->manager,
- scope,
- &s->leader,
- s->user->slice,
- description,
- /* These two have StopWhenUnneeded= set, hence add a dep towards them */
- STRV_MAKE(s->user->runtime_dir_service,
- s->user->service),
- after,
- user_record_home_directory(s->user->user_record),
- properties,
- error,
- &s->scope_job);
- if (r < 0)
- return log_error_errno(r, "Failed to start session scope %s: %s",
- scope, bus_error_message(error, r));
+ if (!SESSION_CLASS_WANTS_SCOPE(s->class))
+ return 0;
- s->scope = TAKE_PTR(scope);
- }
+ if (s->scope)
+ goto finish;
- (void) hashmap_put(s->manager->session_units, s->scope, s);
+ s->scope_job = mfree(s->scope_job);
+ scope = strjoin("session-", s->id, ".scope");
+ if (!scope)
+ return log_oom();
+
+ description = strjoina("Session ", s->id, " of User ", s->user->user_record->user_name);
+
+ r = manager_start_scope(
+ s->manager,
+ scope,
+ &s->leader,
+ /* allow_pidfd = */ true,
+ s->user->slice,
+ description,
+ /* These should have been pulled in explicitly in user_start(). Just to be sure. */
+ STRV_MAKE_CONST(s->user->runtime_dir_unit,
+ SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) ? s->user->service_manager_unit : NULL),
+ /* We usually want to order session scopes after systemd-user-sessions.service
+ * since the unit is used as login session barrier for unprivileged users. However
+ * the barrier doesn't apply for root as sysadmin should always be able to log in
+ * (and without waiting for any timeout to expire) in case something goes wrong
+ * during the boot process. */
+ STRV_MAKE_CONST("systemd-logind.service",
+ SESSION_CLASS_IS_EARLY(s->class) ? NULL : "systemd-user-sessions.service"),
+ user_record_home_directory(s->user->user_record),
+ properties,
+ error,
+ &s->scope_job);
+ if (r < 0)
+ return log_error_errno(r, "Failed to start session scope %s: %s",
+ scope, bus_error_message(error, r));
+
+ s->scope = TAKE_PTR(scope);
+
+finish:
+ (void) hashmap_put(s->manager->session_units, s->scope, s);
return 0;
}
@@ -744,7 +806,7 @@ static int session_setup_stop_on_idle_timer(Session *s) {
assert(s);
- if (s->manager->stop_idle_session_usec == USEC_INFINITY)
+ if (s->manager->stop_idle_session_usec == USEC_INFINITY || !SESSION_CLASS_CAN_STOP_ON_IDLE(s->class))
return 0;
r = sd_event_add_time_relative(
@@ -804,17 +866,17 @@ int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
user_elect_display(s->user);
/* Save data */
- session_save(s);
- user_save(s->user);
+ (void) session_save(s);
+ (void) user_save(s->user);
if (s->seat)
- seat_save(s->seat);
+ (void) seat_save(s->seat);
/* Send signals */
session_send_signal(s, true);
user_send_changed(s->user, "Display", NULL);
if (s->seat && s->seat->active == s)
- seat_send_changed(s->seat, "ActiveSession", NULL);
+ (void) seat_send_changed(s->seat, "ActiveSession", NULL);
return 0;
}
@@ -900,8 +962,8 @@ int session_stop(Session *s, bool force) {
user_elect_display(s->user);
- session_save(s);
- user_save(s->user);
+ (void) session_save(s);
+ (void) user_save(s->user);
return r;
}
@@ -923,7 +985,6 @@ int session_finalize(Session *s) {
LOG_MESSAGE("Removed session %s.", s->id));
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
- s->leader_pidfd_event_source = sd_event_source_unref(s->leader_pidfd_event_source);
if (s->seat)
seat_evict_position(s->seat, s);
@@ -948,10 +1009,10 @@ int session_finalize(Session *s) {
seat_save(s->seat);
}
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ false);
- user_save(s->user);
- user_send_changed(s->user, "Display", NULL);
+ (void) user_save(s->user);
+ (void) user_send_changed(s->user, "Display", NULL);
return 0;
}
@@ -1042,19 +1103,21 @@ int session_get_idle_hint(Session *s, dual_timestamp *t) {
return s->idle_hint;
}
- /* For sessions with an explicitly configured tty, let's check its atime */
- if (s->tty) {
- r = get_tty_atime(s->tty, &atime);
- if (r >= 0)
- goto found_atime;
- }
+ if (s->type == SESSION_TTY) {
+ /* For sessions with an explicitly configured tty, let's check its atime */
+ if (s->tty) {
+ r = get_tty_atime(s->tty, &atime);
+ if (r >= 0)
+ goto found_atime;
+ }
- /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of
- * the leader */
- if (pidref_is_set(&s->leader)) {
- r = get_process_ctty_atime(s->leader.pid, &atime);
- if (r >= 0)
- goto found_atime;
+ /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of
+ * the leader */
+ if (pidref_is_set(&s->leader)) {
+ r = get_process_ctty_atime(s->leader.pid, &atime);
+ if (r >= 0)
+ goto found_atime;
+ }
}
if (t)
@@ -1081,7 +1144,9 @@ found_atime:
int session_set_idle_hint(Session *s, bool b) {
assert(s);
- if (!SESSION_TYPE_IS_GRAPHICAL(s->type))
+ if (!SESSION_CLASS_CAN_IDLE(s->class)) /* Only some session classes know the idle concept at all */
+ return -ENOTTY;
+ if (!SESSION_TYPE_IS_GRAPHICAL(s->type)) /* And only graphical session types can set the field explicitly */
return -ENOTTY;
if (s->idle_hint == b)
@@ -1107,15 +1172,20 @@ int session_get_locked_hint(Session *s) {
return s->locked_hint;
}
-void session_set_locked_hint(Session *s, bool b) {
+int session_set_locked_hint(Session *s, bool b) {
assert(s);
+ if (!SESSION_CLASS_CAN_LOCK(s->class))
+ return -ENOTTY;
+
if (s->locked_hint == b)
- return;
+ return 0;
s->locked_hint = b;
+ (void) session_save(s);
+ (void) session_send_changed(s, "LockedHint", NULL);
- session_send_changed(s, "LockedHint", NULL);
+ return 1;
}
void session_set_type(Session *s, SessionType t) {
@@ -1125,9 +1195,22 @@ void session_set_type(Session *s, SessionType t) {
return;
s->type = t;
- session_save(s);
+ (void) session_save(s);
+ (void) session_send_changed(s, "Type", NULL);
+}
+
+void session_set_class(Session *s, SessionClass c) {
+ assert(s);
+
+ if (s->class == c)
+ return;
- session_send_changed(s, "Type", NULL);
+ s->class = c;
+ (void) session_save(s);
+ (void) session_send_changed(s, "Class", NULL);
+
+ /* This class change might mean we need the per-user session manager now. Try to start it. */
+ (void) user_start_service_manager(s->user);
}
int session_set_display(Session *s, const char *display) {
@@ -1140,9 +1223,8 @@ int session_set_display(Session *s, const char *display) {
if (r <= 0) /* 0 means the strings were equal */
return r;
- session_save(s);
-
- session_send_changed(s, "Display", NULL);
+ (void) session_save(s);
+ (void) session_send_changed(s, "Display", NULL);
return 1;
}
@@ -1157,9 +1239,8 @@ int session_set_tty(Session *s, const char *tty) {
if (r <= 0) /* 0 means the strings were equal */
return r;
- session_save(s);
-
- session_send_changed(s, "TTY", NULL);
+ (void) session_save(s);
+ (void) session_send_changed(s, "TTY", NULL);
return 1;
}
@@ -1224,41 +1305,7 @@ static void session_remove_fifo(Session *s) {
s->fifo_event_source = sd_event_source_unref(s->fifo_event_source);
s->fifo_fd = safe_close(s->fifo_fd);
-
- if (s->fifo_path) {
- (void) unlink(s->fifo_path);
- s->fifo_path = mfree(s->fifo_path);
- }
-}
-
-static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
- Session *s = ASSERT_PTR(userdata);
-
- assert(s->leader.fd == fd);
- session_stop(s, /* force= */ false);
-
- return 1;
-}
-
-int session_watch_pidfd(Session *s) {
- int r;
-
- assert(s);
-
- if (s->leader.fd < 0)
- return 0;
-
- r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s);
- if (r < 0)
- return r;
-
- r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT);
- if (r < 0)
- return r;
-
- (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd");
-
- return 0;
+ s->fifo_path = unlink_and_free(s->fifo_path);
}
bool session_may_gc(Session *s, bool drop_not_started) {
@@ -1273,15 +1320,17 @@ bool session_may_gc(Session *s, bool drop_not_started) {
return true;
r = pidref_is_alive(&s->leader);
+ if (r == -ESRCH)
+ /* Session has no leader. This is probably because the leader vanished before deserializing
+ * pidfd from FD store. */
+ return true;
if (r < 0)
- log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not.", s->leader.pid);
+ log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not: %m", s->leader.pid);
if (r > 0)
return false;
- if (s->fifo_fd >= 0) {
- if (pipe_eof(s->fifo_fd) <= 0)
- return false;
- }
+ if (s->fifo_fd >= 0 && pipe_eof(s->fifo_fd) <= 0)
+ return false;
if (s->scope_job) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -1561,7 +1610,7 @@ int session_set_controller(Session *s, const char *sender, bool force, bool prep
session_release_controller(s, true);
s->controller = TAKE_PTR(name);
- session_save(s);
+ (void) session_save(s);
return 0;
}
@@ -1575,7 +1624,7 @@ void session_drop_controller(Session *s) {
s->track = sd_bus_track_unref(s->track);
session_set_type(s, s->original_type);
session_release_controller(s, false);
- session_save(s);
+ (void) session_save(s);
session_restore_vt(s);
}
@@ -1600,10 +1649,15 @@ static const char* const session_type_table[_SESSION_TYPE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);
static const char* const session_class_table[_SESSION_CLASS_MAX] = {
- [SESSION_USER] = "user",
- [SESSION_GREETER] = "greeter",
- [SESSION_LOCK_SCREEN] = "lock-screen",
- [SESSION_BACKGROUND] = "background",
+ [SESSION_USER] = "user",
+ [SESSION_USER_EARLY] = "user-early",
+ [SESSION_USER_INCOMPLETE] = "user-incomplete",
+ [SESSION_GREETER] = "greeter",
+ [SESSION_LOCK_SCREEN] = "lock-screen",
+ [SESSION_BACKGROUND] = "background",
+ [SESSION_BACKGROUND_LIGHT] = "background-light",
+ [SESSION_MANAGER] = "manager",
+ [SESSION_MANAGER_EARLY] = "manager-early",
};
DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index 8b63843..c187bcd 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -20,14 +20,50 @@ typedef enum SessionState {
} SessionState;
typedef enum SessionClass {
- SESSION_USER,
- SESSION_GREETER,
- SESSION_LOCK_SCREEN,
- SESSION_BACKGROUND,
+ SESSION_USER, /* A regular user session */
+ SESSION_USER_EARLY, /* A user session, that is not ordered after systemd-user-sessions.service (i.e. for root) */
+ SESSION_USER_INCOMPLETE, /* A user session that is only half-way set up and doesn't pull in the service manager, and can be upgraded to a full user session later */
+ SESSION_GREETER, /* A login greeter pseudo-session */
+ SESSION_LOCK_SCREEN, /* A lock screen */
+ SESSION_BACKGROUND, /* Things like cron jobs, which are non-interactive */
+ SESSION_BACKGROUND_LIGHT, /* Like SESSION_BACKGROUND, but without the service manager */
+ SESSION_MANAGER, /* The service manager */
+ SESSION_MANAGER_EARLY, /* The service manager for root (which is allowed to run before systemd-user-sessions.service) */
_SESSION_CLASS_MAX,
_SESSION_CLASS_INVALID = -EINVAL,
} SessionClass;
+/* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. It's
+ * generally set for root sessions, but no one else. */
+#define SESSION_CLASS_IS_EARLY(class) IN_SET((class), SESSION_USER_EARLY, SESSION_MANAGER_EARLY)
+
+/* Which session classes want their own scope units? (all of them, except the manager, which comes in its own service unit already */
+#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_INCOMPLETE, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND, SESSION_BACKGROUND_LIGHT)
+
+/* Which session classes want their own per-user service manager? */
+#define SESSION_CLASS_WANTS_SERVICE_MANAGER(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND)
+
+/* Which session classes can pin our user tracking? */
+#define SESSION_CLASS_PIN_USER(class) (!IN_SET((class), SESSION_MANAGER, SESSION_MANAGER_EARLY))
+
+/* Which session classes decide whether system is idle? (should only cover sessions that have input, and are not idle screens themselves)*/
+#define SESSION_CLASS_CAN_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER))
+
+/* Which session classes have a lock screen concept? */
+#define SESSION_CLASS_CAN_LOCK(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY))
+
+/* Which sessions are candidates to become "display" sessions */
+#define SESSION_CLASS_CAN_DISPLAY(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER))
+
+/* Which sessions classes should be subject to stop-in-idle */
+#define SESSION_CLASS_CAN_STOP_ON_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY))
+
+/* Which session classes can take control of devices */
+#define SESSION_CLASS_CAN_TAKE_DEVICE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN))
+
+/* Which session classes allow changing session types */
+#define SESSION_CLASS_CAN_CHANGE_TYPE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN))
+
typedef enum SessionType {
SESSION_UNSPECIFIED,
SESSION_TTY,
@@ -59,7 +95,8 @@ typedef enum TTYValidity {
struct Session {
Manager *manager;
- const char *id;
+ char *id;
+
unsigned position;
SessionType type;
SessionType original_type;
@@ -89,6 +126,8 @@ struct Session {
int vtfd;
PidRef leader;
+ bool leader_fd_saved; /* pidfd of leader uploaded to fdstore */
+ pid_t deserialized_pid; /* PID deserialized from state file (for verification when pidfd is used) */
uint32_t audit_id;
int fifo_fd;
@@ -97,18 +136,19 @@ struct Session {
sd_event_source *fifo_event_source;
sd_event_source *leader_pidfd_event_source;
- bool idle_hint;
- dual_timestamp idle_hint_timestamp;
+ bool in_gc_queue;
+ bool started;
+ bool stopping;
- bool locked_hint;
+ bool was_active;
- bool in_gc_queue:1;
- bool started:1;
- bool stopping:1;
+ bool locked_hint;
- bool was_active:1;
+ bool idle_hint;
+ dual_timestamp idle_hint_timestamp;
- sd_bus_message *create_message;
+ sd_bus_message *create_message; /* The D-Bus message used to create the session, which we haven't responded to yet */
+ sd_bus_message *upgrade_message; /* The D-Bus message used to upgrade the session class user-incomplete → user, which we haven't responded to yet */
/* Set up when a client requested to release the session via the bus */
sd_event_source *timer_event_source;
@@ -125,10 +165,10 @@ struct Session {
LIST_FIELDS(Session, gc_queue);
};
-int session_new(Session **ret, Manager *m, const char *id);
+int session_new(Manager *m, const char *id, Session **ret);
Session* session_free(Session *s);
-DEFINE_TRIVIAL_CLEANUP_FUNC(Session *, session_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Session*, session_free);
void session_set_user(Session *s, User *u);
int session_set_leader_consume(Session *s, PidRef _leader);
@@ -139,12 +179,12 @@ bool session_is_active(Session *s);
int session_get_idle_hint(Session *s, dual_timestamp *t);
int session_set_idle_hint(Session *s, bool b);
int session_get_locked_hint(Session *s);
-void session_set_locked_hint(Session *s, bool b);
+int session_set_locked_hint(Session *s, bool b);
void session_set_type(Session *s, SessionType t);
+void session_set_class(Session *s, SessionClass c);
int session_set_display(Session *s, const char *display);
int session_set_tty(Session *s, const char *tty);
int session_create_fifo(Session *s);
-int session_watch_pidfd(Session *s);
int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error);
int session_stop(Session *s, bool force);
int session_finalize(Session *s);
diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c
index 88649b2..ba83dc5 100644
--- a/src/login/logind-user-dbus.c
+++ b/src/login/logind-user-dbus.c
@@ -192,13 +192,12 @@ int bus_user_method_terminate(sd_bus_message *message, void *userdata, sd_bus_er
assert(message);
- r = bus_verify_polkit_async(
+ r = bus_verify_polkit_async_full(
message,
- CAP_KILL,
"org.freedesktop.login1.manage",
- NULL,
- false,
+ /* details= */ NULL,
u->user_record->uid,
+ /* flags= */ 0,
&u->manager->polkit_registry,
error);
if (r < 0)
@@ -220,13 +219,12 @@ int bus_user_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *
assert(message);
- r = bus_verify_polkit_async(
+ r = bus_verify_polkit_async_full(
message,
- CAP_KILL,
"org.freedesktop.login1.manage",
- NULL,
- false,
+ /* details= */ NULL,
u->user_record->uid,
+ /* flags= */ 0,
&u->manager->polkit_registry,
error);
if (r < 0)
@@ -358,7 +356,7 @@ static const sd_bus_vtable user_vtable[] = {
SD_BUS_PROPERTY("Name", "s", property_get_name, 0, SD_BUS_VTABLE_PROPERTY_CONST),
BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(User, timestamp), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimePath", "s", NULL, offsetof(User, runtime_path), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("Service", "s", NULL, offsetof(User, service), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Service", "s", NULL, offsetof(User, service_manager_unit), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Slice", "s", NULL, offsetof(User, slice), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Display", "(so)", property_get_display, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0),
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index c613307..8066b3e 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -33,21 +33,18 @@
#include "string-table.h"
#include "strv.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "unit-name.h"
#include "user-util.h"
-int user_new(User **ret,
- Manager *m,
- UserRecord *ur) {
-
+int user_new(Manager *m, UserRecord *ur, User **ret) {
_cleanup_(user_freep) User *u = NULL;
char lu[DECIMAL_STR_MAX(uid_t) + 1];
int r;
- assert(ret);
assert(m);
assert(ur);
+ assert(ret);
if (!ur->user_name)
return -EINVAL;
@@ -63,6 +60,7 @@ int user_new(User **ret,
.manager = m,
.user_record = user_record_ref(ur),
.last_session_timestamp = USEC_INFINITY,
+ .gc_mode = USER_GC_BY_ANY,
};
if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0)
@@ -76,11 +74,11 @@ int user_new(User **ret,
if (r < 0)
return r;
- r = unit_name_build("user", lu, ".service", &u->service);
+ r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_unit);
if (r < 0)
return r;
- r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_service);
+ r = unit_name_build("user", lu, ".service", &u->service_manager_unit);
if (r < 0)
return r;
@@ -92,11 +90,11 @@ int user_new(User **ret,
if (r < 0)
return r;
- r = hashmap_put(m->user_units, u->service, u);
+ r = hashmap_put(m->user_units, u->runtime_dir_unit, u);
if (r < 0)
return r;
- r = hashmap_put(m->user_units, u->runtime_dir_service, u);
+ r = hashmap_put(m->user_units, u->service_manager_unit, u);
if (r < 0)
return r;
@@ -114,26 +112,29 @@ User *user_free(User *u) {
while (u->sessions)
session_free(u->sessions);
- if (u->service)
- hashmap_remove_value(u->manager->user_units, u->service, u);
-
- if (u->runtime_dir_service)
- hashmap_remove_value(u->manager->user_units, u->runtime_dir_service, u);
+ sd_event_source_unref(u->timer_event_source);
- if (u->slice)
- hashmap_remove_value(u->manager->user_units, u->slice, u);
+ if (u->service_manager_unit) {
+ (void) hashmap_remove_value(u->manager->user_units, u->service_manager_unit, u);
+ free(u->service_manager_job);
+ free(u->service_manager_unit);
+ }
- hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u);
+ if (u->runtime_dir_unit) {
+ (void) hashmap_remove_value(u->manager->user_units, u->runtime_dir_unit, u);
+ free(u->runtime_dir_job);
+ free(u->runtime_dir_unit);
+ }
- sd_event_source_unref(u->timer_event_source);
+ if (u->slice) {
+ (void) hashmap_remove_value(u->manager->user_units, u->slice, u);
+ free(u->slice);
+ }
- u->service_job = mfree(u->service_job);
+ (void) hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u);
- u->service = mfree(u->service);
- u->runtime_dir_service = mfree(u->runtime_dir_service);
- u->slice = mfree(u->slice);
- u->runtime_path = mfree(u->runtime_path);
- u->state_file = mfree(u->state_file);
+ free(u->runtime_path);
+ free(u->state_file);
user_record_unref(u->user_record);
@@ -162,17 +163,22 @@ static int user_save_internal(User *u) {
"# This is private data. Do not parse.\n"
"NAME=%s\n"
"STATE=%s\n" /* friendly user-facing state */
- "STOPPING=%s\n", /* low-level state */
+ "STOPPING=%s\n" /* low-level state */
+ "GC_MODE=%s\n",
u->user_record->user_name,
user_state_to_string(user_get_state(u)),
- yes_no(u->stopping));
+ yes_no(u->stopping),
+ user_gc_mode_to_string(u->gc_mode));
/* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */
if (u->runtime_path)
fprintf(f, "RUNTIME=%s\n", u->runtime_path);
- if (u->service_job)
- fprintf(f, "SERVICE_JOB=%s\n", u->service_job);
+ if (u->runtime_dir_job)
+ fprintf(f, "RUNTIME_DIR_JOB=%s\n", u->runtime_dir_job);
+
+ if (u->service_manager_job)
+ fprintf(f, "SERVICE_JOB=%s\n", u->service_manager_job);
if (u->display)
fprintf(f, "DISPLAY=%s\n", u->display->id);
@@ -302,17 +308,19 @@ int user_save(User *u) {
}
int user_load(User *u) {
- _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL;
+ _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL, *gc_mode = NULL;
int r;
assert(u);
r = parse_env_file(NULL, u->state_file,
- "SERVICE_JOB", &u->service_job,
+ "RUNTIME_DIR_JOB", &u->runtime_dir_job,
+ "SERVICE_JOB", &u->service_manager_job,
"STOPPING", &stopping,
"REALTIME", &realtime,
"MONOTONIC", &monotonic,
- "LAST_SESSION_TIMESTAMP", &last_session_timestamp);
+ "LAST_SESSION_TIMESTAMP", &last_session_timestamp,
+ "GC_MODE", &gc_mode);
if (r == -ENOENT)
return 0;
if (r < 0)
@@ -322,8 +330,11 @@ int user_load(User *u) {
r = parse_boolean(stopping);
if (r < 0)
log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping);
- else
+ else {
u->stopping = r;
+ if (u->stopping && !u->runtime_dir_job)
+ log_debug("User '%s' is stopping, but no job is being tracked.", u->user_record->user_name);
+ }
}
if (realtime)
@@ -333,25 +344,74 @@ int user_load(User *u) {
if (last_session_timestamp)
(void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp);
+ u->gc_mode = user_gc_mode_from_string(gc_mode);
+ if (u->gc_mode < 0)
+ u->gc_mode = USER_GC_BY_PIN;
+
return 0;
}
-static void user_start_service(User *u) {
+static int user_start_runtime_dir(User *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(u);
+ assert(!u->stopping);
+ assert(u->manager);
+ assert(u->runtime_dir_unit);
- /* Start the service containing the "systemd --user" instance (user@.service). Note that we don't explicitly
- * start the per-user slice or the systemd-runtime-dir@.service instance, as those are pulled in both by
- * user@.service and the session scopes as dependencies. */
+ u->runtime_dir_job = mfree(u->runtime_dir_job);
- u->service_job = mfree(u->service_job);
-
- r = manager_start_unit(u->manager, u->service, &error, &u->service_job);
+ r = manager_start_unit(u->manager, u->runtime_dir_unit, &error, &u->runtime_dir_job);
if (r < 0)
- log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to start user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
+ return log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_ERR,
+ r, "Failed to start user service '%s': %s",
+ u->runtime_dir_unit, bus_error_message(&error, r));
+
+ return 0;
+}
+
+static bool user_wants_service_manager(const User *u) {
+ assert(u);
+
+ LIST_FOREACH(sessions_by_user, s, u->sessions)
+ if (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class))
+ return true;
+
+ if (user_check_linger_file(u) > 0)
+ return true;
+
+ return false;
+}
+
+int user_start_service_manager(User *u) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(u);
+ assert(!u->stopping);
+ assert(u->manager);
+ assert(u->service_manager_unit);
+
+ if (u->service_manager_started)
+ return 1;
+
+ /* Only start user service manager if there's at least one session which wants it */
+ if (!user_wants_service_manager(u))
+ return 0;
+
+ u->service_manager_job = mfree(u->service_manager_job);
+
+ r = manager_start_unit(u->manager, u->service_manager_unit, &error, &u->service_manager_job);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED))
+ return 0;
+
+ return log_error_errno(r, "Failed to start user service '%s': %s",
+ u->service_manager_unit, bus_error_message(&error, r));
+ }
+
+ return (u->service_manager_started = true);
}
static int update_slice_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
@@ -412,12 +472,14 @@ static int user_update_slice(User *u) {
{ "IOWeight", u->user_record->io_weight },
};
- for (size_t i = 0; i < ELEMENTSOF(settings); i++)
- if (settings[i].value != UINT64_MAX) {
- r = sd_bus_message_append(m, "(sv)", settings[i].name, "t", settings[i].value);
- if (r < 0)
- return bus_log_create_error(r);
- }
+ FOREACH_ELEMENT(st, settings) {
+ if (st->value == UINT64_MAX)
+ continue;
+
+ r = sd_bus_message_append(m, "(sv)", st->name, "t", st->value);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
r = sd_bus_message_close_container(m);
if (r < 0)
@@ -434,33 +496,47 @@ static int user_update_slice(User *u) {
}
int user_start(User *u) {
+ int r;
+
assert(u);
- if (u->started && !u->stopping)
+ if (u->service_manager_started) {
+ /* Everything is up. No action needed. */
+ assert(u->started && !u->stopping);
return 0;
+ }
- /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. We have to clear
- * that flag before queueing the start-jobs again. If they succeed, the user object can be re-used just fine
- * (pid1 takes care of job-ordering and proper restart), but if they fail, we want to force another user_stop()
- * so possibly pending units are stopped. */
- u->stopping = false;
+ if (!u->started || u->stopping) {
+ /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued.
+ * We have to clear that flag before queueing the start-jobs again. If they succeed, the
+ * user object can be reused just fine (pid1 takes care of job-ordering and proper restart),
+ * but if they fail, we want to force another user_stop() so possibly pending units are
+ * stopped. */
+ u->stopping = false;
- if (!u->started)
- log_debug("Starting services for new user %s.", u->user_record->user_name);
+ if (!u->started)
+ log_debug("Tracking new user %s.", u->user_record->user_name);
- /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up
- * systemd --user. We need to do user_save_internal() because we have not "officially" started yet. */
- user_save_internal(u);
+ /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it
+ * while starting up systemd --user. We need to do user_save_internal() because we have not
+ * "officially" started yet. */
+ user_save_internal(u);
- /* Set slice parameters */
- (void) user_update_slice(u);
+ /* Set slice parameters */
+ (void) user_update_slice(u);
- /* Start user@UID.service */
- user_start_service(u);
+ (void) user_start_runtime_dir(u);
+ }
+
+ /* Start user@UID.service if needed. */
+ r = user_start_service_manager(u);
+ if (r < 0)
+ return r;
if (!u->started) {
if (!dual_timestamp_is_set(&u->timestamp))
dual_timestamp_now(&u->timestamp);
+
user_send_signal(u, true);
u->started = true;
}
@@ -476,16 +552,22 @@ static void user_stop_service(User *u, bool force) {
int r;
assert(u);
- assert(u->service);
+ assert(u->manager);
+ assert(u->runtime_dir_unit);
+
+ /* Note that we only stop user-runtime-dir@.service here, and let BindsTo= deal with the user@.service
+ * instance. However, we still need to clear service_manager_job here, so that if the stop is
+ * interrupted, the new sessions won't be confused by leftovers. */
- /* The reverse of user_start_service(). Note that we only stop user@UID.service here, and let StopWhenUnneeded=
- * deal with the slice and the user-runtime-dir@.service instance. */
+ u->service_manager_job = mfree(u->service_manager_job);
+ u->service_manager_started = false;
- u->service_job = mfree(u->service_job);
+ u->runtime_dir_job = mfree(u->runtime_dir_job);
- r = manager_stop_unit(u->manager, u->service, force ? "replace" : "fail", &error, &u->service_job);
+ r = manager_stop_unit(u->manager, u->runtime_dir_unit, force ? "replace" : "fail", &error, &u->runtime_dir_job);
if (r < 0)
- log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
+ log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s",
+ u->runtime_dir_unit, bus_error_message(&error, r));
}
int user_stop(User *u, bool force) {
@@ -506,13 +588,8 @@ int user_stop(User *u, bool force) {
return 0;
}
- LIST_FOREACH(sessions_by_user, s, u->sessions) {
- int k;
-
- k = session_stop(s, force);
- if (k < 0)
- r = k;
- }
+ LIST_FOREACH(sessions_by_user, s, u->sessions)
+ RET_GATHER(r, session_stop(s, force));
user_stop_service(u, force);
@@ -524,7 +601,7 @@ int user_stop(User *u, bool force) {
}
int user_finalize(User *u) {
- int r = 0, k;
+ int r = 0;
assert(u);
@@ -532,13 +609,10 @@ int user_finalize(User *u) {
* done. This is called as a result of an earlier user_done() when all jobs are completed. */
if (u->started)
- log_debug("User %s logged out.", u->user_record->user_name);
+ log_debug("User %s exited.", u->user_record->user_name);
- LIST_FOREACH(sessions_by_user, s, u->sessions) {
- k = session_finalize(s);
- if (k < 0)
- r = k;
- }
+ LIST_FOREACH(sessions_by_user, s, u->sessions)
+ RET_GATHER(r, session_finalize(s));
/* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs
* are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to
@@ -546,11 +620,8 @@ int user_finalize(User *u) {
* cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because
* a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up,
* and do it only for normal users. */
- if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) {
- k = clean_ipc_by_uid(u->user_record->uid);
- if (k < 0)
- r = k;
- }
+ if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid))
+ RET_GATHER(r, clean_ipc_by_uid(u->user_record->uid));
(void) unlink(u->state_file);
user_add_to_gc_queue(u);
@@ -573,6 +644,9 @@ int user_get_idle_hint(User *u, dual_timestamp *t) {
dual_timestamp k;
int ih;
+ if (!SESSION_CLASS_CAN_IDLE(s->class))
+ continue;
+
ih = session_get_idle_hint(s, &k);
if (ih < 0)
return ih;
@@ -598,9 +672,12 @@ int user_get_idle_hint(User *u, dual_timestamp *t) {
return idle_hint;
}
-int user_check_linger_file(User *u) {
+int user_check_linger_file(const User *u) {
_cleanup_free_ char *cc = NULL;
- char *p = NULL;
+ const char *p;
+
+ assert(u);
+ assert(u->user_record);
cc = cescape(u->user_record->user_name);
if (!cc)
@@ -620,11 +697,11 @@ int user_check_linger_file(User *u) {
static bool user_unit_active(User *u) {
int r;
- assert(u->service);
- assert(u->runtime_dir_service);
assert(u->slice);
+ assert(u->runtime_dir_unit);
+ assert(u->service_manager_unit);
- FOREACH_STRING(i, u->service, u->runtime_dir_service, u->slice) {
+ FOREACH_STRING(i, u->slice, u->runtime_dir_unit, u->service_manager_unit) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = manager_unit_is_active(u->manager, i, &error);
@@ -649,6 +726,29 @@ static usec_t user_get_stop_delay(User *u) {
return u->manager->user_stop_delay;
}
+static bool user_pinned_by_sessions(User *u) {
+ assert(u);
+
+ /* Returns true if at least one session exists that shall keep the user tracking alive. That
+ * generally means one session that isn't the service manager still exists. */
+
+ switch (u->gc_mode) {
+
+ case USER_GC_BY_ANY:
+ return u->sessions;
+
+ case USER_GC_BY_PIN:
+ LIST_FOREACH(sessions_by_user, i, u->sessions)
+ if (SESSION_CLASS_PIN_USER(i->class))
+ return true;
+
+ return false;
+
+ default:
+ assert_not_reached();
+ }
+}
+
bool user_may_gc(User *u, bool drop_not_started) {
int r;
@@ -657,7 +757,7 @@ bool user_may_gc(User *u, bool drop_not_started) {
if (drop_not_started && !u->started)
return true;
- if (u->sessions)
+ if (user_pinned_by_sessions(u))
return false;
if (u->last_session_timestamp != USEC_INFINITY) {
@@ -681,18 +781,23 @@ bool user_may_gc(User *u, bool drop_not_started) {
return false;
/* Check if our job is still pending */
- if (u->service_job) {
+ const char *j;
+ FOREACH_ARGUMENT(j, u->runtime_dir_job, u->service_manager_job) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- r = manager_job_is_active(u->manager, u->service_job, &error);
+ if (!j)
+ continue;
+
+ r = manager_job_is_active(u->manager, j, &error);
if (r < 0)
- log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", u->service_job, bus_error_message(&error, r));
+ log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s",
+ j, bus_error_message(&error, r));
if (r != 0)
return false;
}
- /* Note that we don't care if the three units we manage for each user object are up or not, as we are managing
- * their state rather than tracking it. */
+ /* Note that we don't care if the three units we manage for each user object are up or not, as we are
+ * managing their state rather than tracking it. */
return true;
}
@@ -713,25 +818,29 @@ UserState user_get_state(User *u) {
if (u->stopping)
return USER_CLOSING;
- if (!u->started || u->service_job)
+ if (!u->started || u->runtime_dir_job)
return USER_OPENING;
- if (u->sessions) {
- bool all_closing = true;
+ bool any = false, all_closing = true;
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ SessionState state;
- LIST_FOREACH(sessions_by_user, i, u->sessions) {
- SessionState state;
+ /* Ignore sessions that don't pin the user, i.e. are not supposed to have an effect on user state */
+ if (!SESSION_CLASS_PIN_USER(i->class))
+ continue;
- state = session_get_state(i);
- if (state == SESSION_ACTIVE)
- return USER_ACTIVE;
- if (state != SESSION_CLOSING)
- all_closing = false;
- }
+ state = session_get_state(i);
+ if (state == SESSION_ACTIVE)
+ return USER_ACTIVE;
+ if (state != SESSION_CLOSING)
+ all_closing = false;
- return all_closing ? USER_CLOSING : USER_ONLINE;
+ any = true;
}
+ if (any)
+ return all_closing ? USER_CLOSING : USER_ONLINE;
+
if (user_check_linger_file(u) > 0 && user_unit_active(u))
return USER_LINGERING;
@@ -748,7 +857,7 @@ static bool elect_display_filter(Session *s) {
/* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */
assert(s);
- return IN_SET(s->class, SESSION_USER, SESSION_GREETER) && s->started && !s->stopping;
+ return SESSION_CLASS_CAN_DISPLAY(s->class) && s->started && !s->stopping;
}
static int elect_display_compare(Session *s1, Session *s2) {
@@ -780,6 +889,9 @@ static int elect_display_compare(Session *s1, Session *s2) {
if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER))
return (s1->class != SESSION_USER) - (s2->class != SESSION_USER);
+ if ((s1->class != SESSION_USER_EARLY) != (s2->class != SESSION_USER_EARLY))
+ return (s1->class != SESSION_USER_EARLY) - (s2->class != SESSION_USER_EARLY);
+
if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID))
return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID);
@@ -823,7 +935,7 @@ void user_update_last_session_timer(User *u) {
assert(u);
- if (u->sessions) {
+ if (user_pinned_by_sessions(u)) {
/* There are sessions, turn off the timer */
u->last_session_timestamp = USEC_INFINITY;
u->timer_event_source = sd_event_source_unref(u->timer_event_source);
@@ -871,6 +983,13 @@ static const char* const user_state_table[_USER_STATE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);
+static const char* const user_gc_mode_table[_USER_GC_MODE_MAX] = {
+ [USER_GC_BY_PIN] = "pin",
+ [USER_GC_BY_ANY] = "any",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(user_gc_mode, UserGCMode);
+
int config_parse_tmpfs_size(
const char* unit,
const char *filename,
diff --git a/src/login/logind-user.h b/src/login/logind-user.h
index 21b9f8f..5c82f49 100644
--- a/src/login/logind-user.h
+++ b/src/login/logind-user.h
@@ -19,6 +19,13 @@ typedef enum UserState {
_USER_STATE_INVALID = -EINVAL,
} UserState;
+typedef enum UserGCMode {
+ USER_GC_BY_ANY, /* any session pins this user */
+ USER_GC_BY_PIN, /* only sessions with an explicitly pinning class pin this user */
+ _USER_GC_MODE_MAX,
+ _USER_GC_MODE_INVALID = -EINVAL,
+} UserGCMode;
+
struct User {
Manager *manager;
@@ -27,11 +34,17 @@ struct User {
char *state_file;
char *runtime_path;
- char *slice; /* user-UID.slice */
- char *service; /* user@UID.service */
- char *runtime_dir_service; /* user-runtime-dir@UID.service */
+ /* user-UID.slice */
+ char *slice;
+
+ /* user-runtime-dir@UID.service */
+ char *runtime_dir_unit;
+ char *runtime_dir_job;
- char *service_job;
+ /* user@UID.service */
+ bool service_manager_started;
+ char *service_manager_unit;
+ char *service_manager_job;
Session *display;
@@ -41,23 +54,27 @@ struct User {
/* Set up when the last session of the user logs out */
sd_event_source *timer_event_source;
+ UserGCMode gc_mode;
bool in_gc_queue:1;
- bool started:1; /* Whenever the user being started, has been started or is being stopped again. */
+ bool started:1; /* Whenever the user being started, has been started or is being stopped again
+ (tracked through user-runtime-dir@.service) */
bool stopping:1; /* Whenever the user is being stopped or has been stopped. */
LIST_HEAD(Session, sessions);
LIST_FIELDS(User, gc_queue);
};
-int user_new(User **out, Manager *m, UserRecord *ur);
+int user_new(Manager *m, UserRecord *ur, User **ret);
User *user_free(User *u);
-DEFINE_TRIVIAL_CLEANUP_FUNC(User *, user_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(User*, user_free);
+
+int user_start_service_manager(User *u);
+int user_start(User *u);
bool user_may_gc(User *u, bool drop_not_started);
void user_add_to_gc_queue(User *u);
-int user_start(User *u);
int user_stop(User *u, bool force);
int user_finalize(User *u);
UserState user_get_state(User *u);
@@ -65,11 +82,14 @@ int user_get_idle_hint(User *u, dual_timestamp *t);
int user_save(User *u);
int user_load(User *u);
int user_kill(User *u, int signo);
-int user_check_linger_file(User *u);
+int user_check_linger_file(const User *u);
void user_elect_display(User *u);
void user_update_last_session_timer(User *u);
const char* user_state_to_string(UserState s) _const_;
UserState user_state_from_string(const char *s) _pure_;
+const char* user_gc_mode_to_string(UserGCMode m) _const_;
+UserGCMode user_gc_mode_from_string(const char *s) _pure_;
+
CONFIG_PARSER_PROTOTYPE(config_parse_compat_user_tasks_max);
diff --git a/src/login/logind-wall.c b/src/login/logind-wall.c
index 97b74e9..ff70a59 100644
--- a/src/login/logind-wall.c
+++ b/src/login/logind-wall.c
@@ -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)
diff --git a/src/login/logind.c b/src/login/logind.c
index 88e05bb..ac4b860 100644
--- a/src/login/logind.c
+++ b/src/login/logind.c
@@ -18,6 +18,7 @@
#include "constants.h"
#include "daemon-util.h"
#include "device-util.h"
+#include "devnum-util.h"
#include "dirent-util.h"
#include "escape.h"
#include "fd-util.h"
@@ -65,17 +66,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;
@@ -153,7 +155,7 @@ static Manager* manager_free(Manager *m) {
if (m->unlink_nologin)
(void) unlink_or_warn("/run/nologin");
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
sd_event_unref(m->event);
@@ -190,12 +192,12 @@ static int manager_enumerate_devices(Manager *m) {
if (r < 0)
return r;
- FOREACH_DEVICE(e, d) {
- int k;
+ r = 0;
- k = manager_process_seat_device(m, d);
- if (k < 0)
- r = k;
+ FOREACH_DEVICE(e, d) {
+ if (device_is_processed(d) <= 0)
+ continue;
+ RET_GATHER(r, manager_process_seat_device(m, d));
}
return r;
@@ -224,12 +226,12 @@ static int manager_enumerate_buttons(Manager *m) {
if (r < 0)
return r;
- FOREACH_DEVICE(e, d) {
- int k;
+ r = 0;
- k = manager_process_button_device(m, d);
- if (k < 0)
- r = k;
+ FOREACH_DEVICE(e, d) {
+ if (device_is_processed(d) <= 0)
+ continue;
+ RET_GATHER(r, manager_process_button_device(m, d));
}
return r;
@@ -250,12 +252,11 @@ static int manager_enumerate_seats(Manager *m) {
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/seats: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/seats/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
Seat *s;
- int k;
if (!dirent_is_file(de))
continue;
@@ -268,9 +269,7 @@ static int manager_enumerate_seats(Manager *m) {
continue;
}
- k = seat_load(s);
- if (k < 0)
- r = k;
+ RET_GATHER(r, seat_load(s));
}
return r;
@@ -291,19 +290,21 @@ static int manager_enumerate_linger_users(Manager *m) {
}
FOREACH_DIRENT(de, d, return -errno) {
- int k;
_cleanup_free_ char *n = NULL;
+ int k;
if (!dirent_is_file(de))
continue;
+
k = cunescape(de->d_name, 0, &n);
if (k < 0) {
- r = log_warning_errno(k, "Failed to unescape username '%s', ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to unescape username '%s', ignoring: %m", de->d_name));
continue;
}
+
k = manager_add_user_by_name(m, n, NULL);
if (k < 0)
- r = log_warning_errno(k, "Couldn't add lingering user %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Couldn't add lingering user %s, ignoring: %m", de->d_name));
}
return r;
@@ -311,7 +312,7 @@ static int manager_enumerate_linger_users(Manager *m) {
static int manager_enumerate_users(Manager *m) {
_cleanup_closedir_ DIR *d = NULL;
- int r, k;
+ int r;
assert(m);
@@ -324,148 +325,205 @@ static int manager_enumerate_users(Manager *m) {
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/users: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/users/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
User *u;
uid_t uid;
+ int k;
if (!dirent_is_file(de))
continue;
k = parse_uid(de->d_name, &uid);
if (k < 0) {
- r = log_warning_errno(k, "Failed to parse filename /run/systemd/users/%s as UID.", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to parse filename /run/systemd/users/%s as UID, ignoring: %m", de->d_name));
continue;
}
k = manager_add_user_by_uid(m, uid, &u);
if (k < 0) {
- r = log_warning_errno(k, "Failed to add user by file name %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to add user by filename %s, ignoring: %m", de->d_name));
continue;
}
user_add_to_gc_queue(u);
- k = user_load(u);
- if (k < 0)
- r = k;
+ RET_GATHER(r, user_load(u));
}
return r;
}
-static int parse_fdname(const char *fdname, char **session_id, dev_t *dev) {
+static int parse_fdname(const char *fdname, char **ret_session_id, dev_t *ret_devno) {
_cleanup_strv_free_ char **parts = NULL;
_cleanup_free_ char *id = NULL;
- unsigned major, minor;
int r;
+ assert(ret_session_id);
+ assert(ret_devno);
+
parts = strv_split(fdname, "-");
if (!parts)
return -ENOMEM;
- if (strv_length(parts) != 5)
- return -EINVAL;
- if (!streq(parts[0], "session"))
+ if (_unlikely_(!streq(parts[0], "session")))
return -EINVAL;
id = strdup(parts[1]);
if (!id)
return -ENOMEM;
- if (!streq(parts[2], "device"))
+ if (streq(parts[2], "leader")) {
+ *ret_session_id = TAKE_PTR(id);
+ *ret_devno = 0;
+
+ return 0;
+ }
+
+ if (_unlikely_(!streq(parts[2], "device")))
return -EINVAL;
+ unsigned major, minor;
+
r = safe_atou(parts[3], &major);
if (r < 0)
return r;
+
r = safe_atou(parts[4], &minor);
if (r < 0)
return r;
- *dev = makedev(major, minor);
- *session_id = TAKE_PTR(id);
+ *ret_session_id = TAKE_PTR(id);
+ *ret_devno = makedev(major, minor);
return 0;
}
-static int deliver_fd(Manager *m, const char *fdname, int fd) {
- _cleanup_free_ char *id = NULL;
+static int deliver_session_device_fd(Session *s, const char *fdname, int fd, dev_t devno) {
SessionDevice *sd;
struct stat st;
- Session *s;
- dev_t dev;
- int r;
- assert(m);
+ assert(s);
+ assert(fdname);
assert(fd >= 0);
-
- r = parse_fdname(fdname, &id, &dev);
- if (r < 0)
- return log_debug_errno(r, "Failed to parse fd name %s: %m", fdname);
-
- s = hashmap_get(m->sessions, id);
- if (!s)
- /* If the session doesn't exist anymore, the associated session device attached to this fd
- * doesn't either. Let's simply close this fd. */
- return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Failed to attach fd for unknown session: %s", id);
+ assert(devno > 0);
if (fstat(fd, &st) < 0)
/* The device is allowed to go away at a random point, in which case fstat() failing is
* expected. */
- return log_debug_errno(errno, "Failed to stat device fd for session %s: %m", id);
+ return log_debug_errno(errno, "Failed to stat device fd '%s' for session '%s': %m",
+ fdname, s->id);
- if (!S_ISCHR(st.st_mode) || st.st_rdev != dev)
- return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "Device fd doesn't point to the expected character device node");
+ if (!S_ISCHR(st.st_mode) || st.st_rdev != devno)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
+ "Device fd '%s' doesn't point to the expected character device node.",
+ fdname);
- sd = hashmap_get(s->devices, &dev);
+ sd = hashmap_get(s->devices, &devno);
if (!sd)
/* Weird, we got an fd for a session device which wasn't recorded in the session state
* file... */
- return log_warning_errno(SYNTHETIC_ERRNO(ENODEV), "Got fd for missing session device [%u:%u] in session %s",
- major(dev), minor(dev), s->id);
+ return log_warning_errno(SYNTHETIC_ERRNO(ENODEV),
+ "Got session device fd '%s' [" DEVNUM_FORMAT_STR "], but not present in session state.",
+ fdname, DEVNUM_FORMAT_VAL(devno));
- log_debug("Attaching fd to session device [%u:%u] for session %s",
- major(dev), minor(dev), s->id);
+ log_debug("Attaching session device fd '%s' [" DEVNUM_FORMAT_STR "] to session '%s'.",
+ fdname, DEVNUM_FORMAT_VAL(devno), s->id);
session_device_attach_fd(sd, fd, s->was_active);
return 0;
}
-static int manager_attach_fds(Manager *m) {
- _cleanup_strv_free_ char **fdnames = NULL;
- int n;
+static int deliver_session_leader_fd_consume(Session *s, const char *fdname, int fd) {
+ _cleanup_(pidref_done) PidRef leader_fdstore = PIDREF_NULL;
+ int r;
- /* Upon restart, PID1 will send us back all fds of session devices that we previously opened. Each
- * file descriptor is associated with a given session. The session ids are passed through FDNAMES. */
+ assert(s);
+ assert(fdname);
+ assert(fd >= 0);
- assert(m);
+ if (!pid_is_valid(s->deserialized_pid)) {
+ r = log_warning_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
+ "Got leader pidfd for session '%s', but LEADER= is not set, refusing.",
+ s->id);
+ goto fail_close;
+ }
- n = sd_listen_fds_with_names(true, &fdnames);
- if (n < 0)
- return log_warning_errno(n, "Failed to acquire passed fd list: %m");
- if (n == 0)
- return 0;
+ if (!s->leader_fd_saved)
+ log_warning("Got leader pidfd for session '%s', but not recorded in session state, proceeding anyway.",
+ s->id);
+ else
+ assert(!pidref_is_set(&s->leader));
+
+ r = pidref_set_pidfd_take(&leader_fdstore, fd);
+ if (r < 0) {
+ if (r == -ESRCH)
+ log_debug_errno(r, "Leader of session '%s' is gone while deserializing.", s->id);
+ else
+ log_warning_errno(r, "Failed to create reference to leader of session '%s': %m", s->id);
+ goto fail_close;
+ }
- for (int i = 0; i < n; i++) {
- int fd = SD_LISTEN_FDS_START + i;
+ if (leader_fdstore.pid != s->deserialized_pid)
+ log_warning("Leader from pidfd (" PID_FMT ") doesn't match with LEADER=" PID_FMT " for session '%s', proceeding anyway.",
+ leader_fdstore.pid, s->deserialized_pid, s->id);
- if (deliver_fd(m, fdnames[i], fd) >= 0)
- continue;
+ r = session_set_leader_consume(s, TAKE_PIDREF(leader_fdstore));
+ if (r < 0)
+ return log_warning_errno(r, "Failed to attach leader pidfd for session '%s': %m", s->id);
+
+ return 0;
+
+fail_close:
+ close_and_notify_warn(fd, fdname);
+ return r;
+}
+
+static int manager_attach_session_fd_one_consume(Manager *m, const char *fdname, int fd) {
+ _cleanup_free_ char *id = NULL;
+ dev_t devno = 0; /* Explicit initialization to appease gcc */
+ Session *s;
+ int r;
+
+ assert(m);
+ assert(fdname);
+ assert(fd >= 0);
- /* Hmm, we couldn't deliver the fd to any session device object? If so, let's close the fd
- * and remove it from fdstore. */
- close_and_notify_warn(fd, fdnames[i]);
+ r = parse_fdname(fdname, &id, &devno);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse stored fd name '%s': %m", fdname);
+ goto fail_close;
}
- return 0;
+ s = hashmap_get(m->sessions, id);
+ if (!s) {
+ /* If the session doesn't exist anymore, let's simply close this fd. */
+ r = log_debug_errno(SYNTHETIC_ERRNO(ENXIO),
+ "Cannot attach fd '%s' to unknown session '%s', ignoring.", fdname, id);
+ goto fail_close;
+ }
+
+ if (devno > 0) {
+ r = deliver_session_device_fd(s, fdname, fd, devno);
+ if (r < 0)
+ goto fail_close;
+ return 0;
+ }
+
+ /* Takes ownership of fd on both success and failure */
+ return deliver_session_leader_fd_consume(s, fdname, fd);
+
+fail_close:
+ close_and_notify_warn(fd, fdname);
+ return r;
}
static int manager_enumerate_sessions(Manager *m) {
+ _cleanup_strv_free_ char **fdnames = NULL;
_cleanup_closedir_ DIR *d = NULL;
- int r = 0, k;
+ int r = 0, n;
assert(m);
@@ -475,18 +533,19 @@ static int manager_enumerate_sessions(Manager *m) {
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/sessions: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/sessions/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
- struct Session *s;
+ Session *s;
+ int k;
if (!dirent_is_file(de))
continue;
k = manager_add_session(m, de->d_name, &s);
if (k < 0) {
- r = log_warning_errno(k, "Failed to add session by file name %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to add session by filename %s, ignoring: %m", de->d_name));
continue;
}
@@ -494,12 +553,18 @@ static int manager_enumerate_sessions(Manager *m) {
k = session_load(s);
if (k < 0)
- r = k;
+ RET_GATHER(r, log_warning_errno(k, "Failed to deserialize session '%s', ignoring: %m", s->id));
}
- /* We might be restarted and PID1 could have sent us back the session device fds we previously
- * saved. */
- (void) manager_attach_fds(m);
+ n = sd_listen_fds_with_names(/* unset_environment = */ true, &fdnames);
+ if (n < 0)
+ return log_error_errno(n, "Failed to acquire passed fd list: %m");
+
+ for (int i = 0; i < n; i++) {
+ int fd = SD_LISTEN_FDS_START + i;
+
+ RET_GATHER(r, manager_attach_session_fd_one_consume(m, fdnames[i], fd));
+ }
return r;
}
@@ -515,25 +580,23 @@ static int manager_enumerate_inhibitors(Manager *m) {
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/inhibit: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/inhibit/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
- int k;
Inhibitor *i;
+ int k;
if (!dirent_is_file(de))
continue;
k = manager_add_inhibitor(m, de->d_name, &i);
if (k < 0) {
- r = log_warning_errno(k, "Couldn't add inhibitor %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Couldn't add inhibitor %s, ignoring: %m", de->d_name));
continue;
}
- k = inhibitor_load(i);
- if (k < 0)
- r = k;
+ RET_GATHER(r, inhibitor_load(i));
}
return r;
@@ -775,7 +838,7 @@ static int manager_connect_console(Manager *m) {
SIGRTMIN, SIGRTMAX);
assert_se(ignore_signals(SIGRTMIN + 1) >= 0);
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN, -1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN) >= 0);
r = sd_event_add_signal(m->event, NULL, SIGRTMIN, manager_vt_switch, m);
if (r < 0)
@@ -986,7 +1049,7 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us
m->event,
&m->idle_action_event_source,
CLOCK_MONOTONIC,
- elapse, USEC_PER_SEC*30,
+ elapse, MIN(USEC_PER_SEC*30, m->idle_action_usec), /* accuracy of 30s, but don't have an accuracy lower than the idle action timeout */
manager_dispatch_idle_action, m);
if (r < 0)
return log_error_errno(r, "Failed to add idle event source: %m");
@@ -1011,10 +1074,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa
Manager *m = userdata;
int r;
- (void) sd_notifyf(/* unset= */ false,
- "RELOADING=1\n"
- "STATUS=Reloading configuration...\n"
- "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC));
+ (void) notify_reloading();
manager_reset_config(m);
r = manager_parse_config_file(m);
@@ -1187,7 +1247,7 @@ static int run(int argc, char *argv[]) {
(void) mkdir_label("/run/systemd/users", 0755);
(void) mkdir_label("/run/systemd/sessions", 0755);
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, SIGCHLD, SIGRTMIN+18, -1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, SIGCHLD, SIGRTMIN+18) >= 0);
r = manager_new(&m);
if (r < 0)
diff --git a/src/login/logind.conf.in b/src/login/logind.conf.in
index e5fe924..b62458e 100644
--- a/src/login/logind.conf.in
+++ b/src/login/logind.conf.in
@@ -24,6 +24,7 @@
#KillExcludeUsers=root
#InhibitDelayMaxSec=5
#UserStopDelaySec=10
+#SleepOperation=suspend-then-hibernate suspend
#HandlePowerKey=poweroff
#HandlePowerKeyLongPress=ignore
#HandleRebootKey=reboot
diff --git a/src/login/logind.h b/src/login/logind.h
index 7532d37..cac6a30 100644
--- a/src/login/logind.h
+++ b/src/login/logind.h
@@ -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;
diff --git a/src/login/meson.build b/src/login/meson.build
index b5bb150..43db031 100644
--- a/src/login/meson.build
+++ b/src/login/meson.build
@@ -65,9 +65,9 @@ executables += [
'conditions' : ['ENABLE_LOGIND'],
'sources' : loginctl_sources,
'dependencies' : [
- liblz4,
- libxz,
- libzstd,
+ liblz4_cflags,
+ libxz_cflags,
+ libzstd_cflags,
threads,
],
},
diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf
index 8ba094b..dff944f 100644
--- a/src/login/org.freedesktop.login1.conf
+++ b/src/login/org.freedesktop.login1.conf
@@ -64,6 +64,10 @@
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Manager"
+ send_member="ListSessionsEx"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
send_member="ListUsers"/>
<allow send_destination="org.freedesktop.login1"
@@ -184,6 +188,10 @@
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Manager"
+ send_member="Sleep"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
send_member="CanPowerOff"/>
<allow send_destination="org.freedesktop.login1"
@@ -212,6 +220,10 @@
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Manager"
+ send_member="CanSleep"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
send_member="ScheduleShutdown"/>
<allow send_destination="org.freedesktop.login1"
@@ -263,6 +275,10 @@
send_member="FlushDevices"/>
<allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ReleaseSession"/>
+
+ <allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Seat"
send_member="Terminate"/>
@@ -324,6 +340,10 @@
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Session"
+ send_member="SetClass"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
send_member="TakeDevice"/>
<allow send_destination="org.freedesktop.login1"
@@ -339,14 +359,6 @@
send_member="SetBrightness"/>
<allow send_destination="org.freedesktop.login1"
- send_interface="org.freedesktop.login1.User"
- send_member="Terminate"/>
-
- <allow send_destination="org.freedesktop.login1"
- send_interface="org.freedesktop.login1.User"
- send_member="Kill"/>
-
- <allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Session"
send_member="SetDisplay"/>
@@ -354,6 +366,14 @@
send_interface="org.freedesktop.login1.Session"
send_member="SetTTY"/>
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.User"
+ send_member="Terminate"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.User"
+ send_member="Kill"/>
+
<allow receive_sender="org.freedesktop.login1"/>
</policy>
diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
index bf45974..a711c89 100644
--- a/src/login/pam_systemd.c
+++ b/src/login/pam_systemd.c
@@ -10,6 +10,9 @@
#include <security/pam_modules.h>
#include <security/pam_modutil.h>
#include <sys/file.h>
+#if HAVE_PIDFD_OPEN
+#include <sys/pidfd.h>
+#endif
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
@@ -525,6 +528,26 @@ static const char* getenv_harder(pam_handle_t *handle, const char *key, const ch
return fallback;
}
+static bool getenv_harder_bool(pam_handle_t *handle, const char *key, bool fallback) {
+ const char *v;
+ int r;
+
+ assert(handle);
+ assert(key);
+
+ v = getenv_harder(handle, key, NULL);
+ if (isempty(v))
+ return fallback;
+
+ r = parse_boolean(v);
+ if (r < 0) {
+ pam_syslog(handle, LOG_ERR, "Boolean environment variable value of '%s' is not valid: %s", key, v);
+ return fallback;
+ }
+
+ return r;
+}
+
static int update_environment(pam_handle_t *handle, const char *key, const char *value) {
int r;
@@ -606,6 +629,7 @@ static int apply_user_record_settings(
bool debug,
uint64_t default_capability_bounding_set,
uint64_t default_capability_ambient_set) {
+ _cleanup_strv_free_ char **langs = NULL;
int r;
assert(handle);
@@ -617,48 +641,25 @@ static int apply_user_record_settings(
}
STRV_FOREACH(i, ur->environment) {
- _cleanup_free_ char *n = NULL;
- const char *e;
-
- assert_se(e = strchr(*i, '=')); /* environment was already validated while parsing JSON record, this thus must hold */
-
- n = strndup(*i, e - *i);
- if (!n)
- return pam_log_oom(handle);
-
- if (pam_getenv(handle, n)) {
- pam_debug_syslog(handle, debug,
- "PAM environment variable $%s already set, not changing based on record.", *i);
- continue;
- }
-
r = pam_putenv_and_log(handle, *i, debug);
if (r != PAM_SUCCESS)
return r;
}
if (ur->email_address) {
- if (pam_getenv(handle, "EMAIL"))
- pam_debug_syslog(handle, debug,
- "PAM environment variable $EMAIL already set, not changing based on user record.");
- else {
- _cleanup_free_ char *joined = NULL;
+ _cleanup_free_ char *joined = NULL;
- joined = strjoin("EMAIL=", ur->email_address);
- if (!joined)
- return pam_log_oom(handle);
+ joined = strjoin("EMAIL=", ur->email_address);
+ if (!joined)
+ return pam_log_oom(handle);
- r = pam_putenv_and_log(handle, joined, debug);
- if (r != PAM_SUCCESS)
- return r;
- }
+ r = pam_putenv_and_log(handle, joined, debug);
+ if (r != PAM_SUCCESS)
+ return r;
}
if (ur->time_zone) {
- if (pam_getenv(handle, "TZ"))
- pam_debug_syslog(handle, debug,
- "PAM environment variable $TZ already set, not changing based on user record.");
- else if (!timezone_is_valid(ur->time_zone, LOG_DEBUG))
+ if (!timezone_is_valid(ur->time_zone, LOG_DEBUG))
pam_debug_syslog(handle, debug,
"Time zone specified in user record is not valid locally, not setting $TZ.");
else {
@@ -674,21 +675,38 @@ static int apply_user_record_settings(
}
}
- if (ur->preferred_language) {
- if (pam_getenv(handle, "LANG"))
- pam_debug_syslog(handle, debug,
- "PAM environment variable $LANG already set, not changing based on user record.");
- else if (locale_is_installed(ur->preferred_language) <= 0)
- pam_debug_syslog(handle, debug,
- "Preferred language specified in user record is not valid or not installed, not setting $LANG.");
- else {
- _cleanup_free_ char *joined = NULL;
+ r = user_record_languages(ur, &langs);
+ if (r < 0)
+ pam_syslog_errno(handle, LOG_ERR, r,
+ "Failed to acquire user's language preferences, ignoring: %m");
+ else if (strv_isempty(langs))
+ ; /* User has no preference set so we do nothing */
+ else if (locale_is_installed(langs[0]) <= 0)
+ pam_debug_syslog(handle, debug,
+ "Preferred languages specified in user record are not installed locally, not setting $LANG or $LANGUAGE.");
+ else {
+ _cleanup_free_ char *lang = NULL;
+
+ lang = strjoin("LANG=", langs[0]);
+ if (!lang)
+ return pam_log_oom(handle);
+
+ r = pam_putenv_and_log(handle, lang, debug);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ if (strv_length(langs) > 1) {
+ _cleanup_free_ char *joined = NULL, *language = NULL;
- joined = strjoin("LANG=", ur->preferred_language);
+ joined = strv_join(langs, ":");
if (!joined)
return pam_log_oom(handle);
- r = pam_putenv_and_log(handle, joined, debug);
+ language = strjoin("LANGUAGE=", joined);
+ if (!language)
+ return pam_log_oom(handle);
+
+ r = pam_putenv_and_log(handle, language, debug);
if (r != PAM_SUCCESS)
return r;
}
@@ -908,12 +926,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
int session_fd = -EBADF, existing, r;
- bool debug = false, remote;
+ bool debug = false, remote, incomplete;
uint32_t vtnr = 0;
uid_t original_uid;
assert(handle);
+ pam_log_setup();
+
if (parse_argv(handle,
argc, argv,
&class_pam,
@@ -934,57 +954,39 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (!logind_running())
goto success;
- /* Make sure we don't enter a loop by talking to
- * systemd-logind when it is actually waiting for the
- * background to finish start-up. If the service is
- * "systemd-user" we simply set XDG_RUNTIME_DIR and
- * leave. */
-
- r = pam_get_item(handle, PAM_SERVICE, (const void**) &service);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM service: @PAMERR@");
- if (streq_ptr(service, "systemd-user")) {
- char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
-
- xsprintf(rt, "/run/user/"UID_FMT, ur->uid);
- r = configure_runtime_directory(handle, ur, rt);
- if (r != PAM_SUCCESS)
- return r;
-
- goto success;
- }
-
- /* Otherwise, we ask logind to create a session for us */
-
- r = pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_XDISPLAY: @PAMERR@");
- r = pam_get_item(handle, PAM_TTY, (const void**) &tty);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_TTY: @PAMERR@");
- r = pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_RUSER: @PAMERR@");
- r = pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_RHOST: @PAMERR@");
+ r = pam_get_item_many(
+ handle,
+ PAM_SERVICE, &service,
+ PAM_XDISPLAY, &display,
+ PAM_TTY, &tty,
+ PAM_RUSER, &remote_user,
+ PAM_RHOST, &remote_host);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
seat = getenv_harder(handle, "XDG_SEAT", NULL);
cvtnr = getenv_harder(handle, "XDG_VTNR", NULL);
type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
+ incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false);
- tty = strempty(tty);
+ if (streq_ptr(service, "systemd-user")) {
+ /* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to
+ * 'manager' if not set, simply for robustness reasons. */
+ type = "unspecified";
+ class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ?
+ "manager-early" : "manager";
+ tty = NULL;
- if (strchr(tty, ':')) {
+ } else if (tty && strchr(tty, ':')) {
/* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
* and don't pretend that an X display was a tty. */
if (isempty(display))
display = tty;
tty = NULL;
- } else if (streq(tty, "cron")) {
+ } else if (streq_ptr(tty, "cron")) {
/* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but
* probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set
* (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked
@@ -993,10 +995,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
class = "background";
tty = NULL;
- } else if (streq(tty, "ssh")) {
+ } else if (streq_ptr(tty, "ssh")) {
/* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
* details look for "PAM_TTY_KLUDGE" in the openssh sources). */
- type ="tty";
+ type = "tty";
class = "user";
tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
* associated with a pty — won't be tracked by their tty in logind. This is because ssh
@@ -1004,7 +1006,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
* much later (this is because it doesn't know yet if it needs one at all, as whether to
* register a pty or not is negotiated much later in the protocol). */
- } else
+ } else if (tty)
/* Chop off leading /dev prefix that some clients specify, but others do not. */
tty = skip_dev_prefix(tty);
@@ -1029,7 +1031,16 @@ _public_ PAM_EXTERN int pam_sm_open_session(
!isempty(tty) ? "tty" : "unspecified";
if (isempty(class))
- class = streq(type, "unspecified") ? "background" : "user";
+ class = streq(type, "unspecified") ? "background" :
+ ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) &&
+ streq(type, "tty")) ? "user-early" : "user");
+
+ if (incomplete) {
+ if (streq(class, "user"))
+ class = "user-incomplete";
+ else
+ pam_syslog_pam_error(handle, LOG_WARNING, 0, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", class);
+ }
remote = !isempty(remote_host) && !is_localhost(remote_host);
@@ -1144,6 +1155,9 @@ _public_ PAM_EXTERN int pam_sm_open_session(
"id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
+ /* Please update manager_default_environment() in core/manager.c accordingly if more session envvars
+ * shall be added. */
+
r = update_environment(handle, "XDG_SESSION_ID", id);
if (r != PAM_SUCCESS)
return r;
@@ -1221,6 +1235,8 @@ _public_ PAM_EXTERN int pam_sm_close_session(
assert(handle);
+ pam_log_setup();
+
if (parse_argv(handle,
argc, argv,
NULL,
diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c
index 3b4e911..3b11d76 100644
--- a/src/login/pam_systemd_loadkey.c
+++ b/src/login/pam_systemd_loadkey.c
@@ -18,13 +18,15 @@
* This can be overridden by the keyname= parameter. */
static const char DEFAULT_KEYNAME[] = "cryptsetup";
-_public_ int pam_sm_authenticate(
+_public_ PAM_EXTERN int pam_sm_authenticate(
pam_handle_t *handle,
int flags,
int argc, const char **argv) {
assert(handle);
+ pam_log_setup();
+
/* Parse argv. */
assert(argc >= 0);
@@ -89,7 +91,7 @@ _public_ int pam_sm_authenticate(
return PAM_SUCCESS;
}
-_public_ int pam_sm_setcred(
+_public_ PAM_EXTERN int pam_sm_setcred(
pam_handle_t *handle,
int flags,
int argc, const char **argv) {
diff --git a/src/login/test-login-tables.c b/src/login/test-login-tables.c
index 3c5ec04..1278af7 100644
--- a/src/login/test-login-tables.c
+++ b/src/login/test-login-tables.c
@@ -2,9 +2,24 @@
#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;
+
+ 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 +31,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;
}
diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c
index ad04b04..575f8eb 100644
--- a/src/login/user-runtime-dir.c
+++ b/src/login/user-runtime-dir.c
@@ -67,7 +67,7 @@ static int user_mkdir_runtime_path(
if (r < 0)
return log_error_errno(r, "Failed to create /run/user: %m");
- if (path_is_mount_point(runtime_path, NULL, 0) > 0)
+ if (path_is_mount_point(runtime_path) > 0)
log_debug("%s is already a mount point", runtime_path);
else {
char options[sizeof("mode=0700,uid=,gid=,size=,nr_inodes=,smackfsroot=*")
@@ -190,8 +190,7 @@ static int do_umount(const char *user) {
static int run(int argc, char *argv[]) {
int r;
- log_parse_environment();
- log_open();
+ log_setup();
if (argc != 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),