summaryrefslogtreecommitdiffstats
path: root/src/login
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:49:52 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:49:52 +0000
commit55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch)
tree33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/login
parentInitial commit. (diff)
downloadsystemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz
systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/login')
-rw-r--r--src/login/inhibit.c318
-rw-r--r--src/login/loginctl.c1653
-rw-r--r--src/login/logind-action.c331
-rw-r--r--src/login/logind-action.h72
-rw-r--r--src/login/logind-brightness.c250
-rw-r--r--src/login/logind-brightness.h9
-rw-r--r--src/login/logind-button.c526
-rw-r--r--src/login/logind-button.h26
-rw-r--r--src/login/logind-core.c850
-rw-r--r--src/login/logind-dbus.c4406
-rw-r--r--src/login/logind-dbus.h37
-rw-r--r--src/login/logind-device.c104
-rw-r--r--src/login/logind-device.h25
-rw-r--r--src/login/logind-gperf.gperf53
-rw-r--r--src/login/logind-inhibit.c532
-rw-r--r--src/login/logind-inhibit.h79
-rw-r--r--src/login/logind-polkit.c24
-rw-r--r--src/login/logind-polkit.h9
-rw-r--r--src/login/logind-seat-dbus.c442
-rw-r--r--src/login/logind-seat-dbus.h16
-rw-r--r--src/login/logind-seat.c682
-rw-r--r--src/login/logind-seat.h74
-rw-r--r--src/login/logind-session-dbus.c994
-rw-r--r--src/login/logind-session-dbus.h23
-rw-r--r--src/login/logind-session-device.c507
-rw-r--r--src/login/logind-session-device.h41
-rw-r--r--src/login/logind-session.c1624
-rw-r--r--src/login/logind-session.h185
-rw-r--r--src/login/logind-user-dbus.c421
-rw-r--r--src/login/logind-user-dbus.h16
-rw-r--r--src/login/logind-user.c940
-rw-r--r--src/login/logind-user.h75
-rw-r--r--src/login/logind-wall.c166
-rw-r--r--src/login/logind.c1206
-rw-r--r--src/login/logind.conf.in51
-rw-r--r--src/login/logind.h188
-rw-r--r--src/login/meson.build152
-rw-r--r--src/login/org.freedesktop.login1.conf360
-rw-r--r--src/login/org.freedesktop.login1.policy415
-rw-r--r--src/login/org.freedesktop.login1.service14
-rw-r--r--src/login/pam_systemd.c1266
-rw-r--r--src/login/pam_systemd.sym8
-rw-r--r--src/login/pam_systemd_loadkey.c98
-rw-r--r--src/login/pam_systemd_loadkey.sym8
-rw-r--r--src/login/sysfs-show.c164
-rw-r--r--src/login/sysfs-show.h8
-rw-r--r--src/login/systemd-user.in23
-rw-r--r--src/login/test-inhibit.c85
-rw-r--r--src/login/test-login-shared.c16
-rw-r--r--src/login/test-login-tables.c20
-rw-r--r--src/login/test-session-properties.c193
-rw-r--r--src/login/user-runtime-dir.c216
52 files changed, 20001 insertions, 0 deletions
diff --git a/src/login/inhibit.c b/src/login/inhibit.c
new file mode 100644
index 0000000..ad73c4b
--- /dev/null
+++ b/src/login/inhibit.c
@@ -0,0 +1,318 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "build.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-util.h"
+#include "fd-util.h"
+#include "format-table.h"
+#include "format-util.h"
+#include "main-func.h"
+#include "pager.h"
+#include "pretty-print.h"
+#include "process-util.h"
+#include "signal-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "user-util.h"
+
+static const char* arg_what = "idle:sleep:shutdown";
+static const char* arg_who = NULL;
+static const char* arg_why = "Unknown reason";
+static const char* arg_mode = NULL;
+static PagerFlags arg_pager_flags = 0;
+static bool arg_legend = true;
+
+static enum {
+ ACTION_INHIBIT,
+ ACTION_LIST
+} arg_action = ACTION_INHIBIT;
+
+static int inhibit(sd_bus *bus, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+ int fd;
+
+ r = bus_call_method(bus, bus_login_mgr, "Inhibit", error, &reply, "ssss", arg_what, arg_who, arg_why, arg_mode);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_UNIX_FD, &fd);
+ if (r < 0)
+ return r;
+
+ return RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3));
+}
+
+static int print_inhibitors(sd_bus *bus) {
+ _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;
+ int r;
+
+ pager_open(arg_pager_flags);
+
+ r = bus_call_method(bus, bus_login_mgr, "ListInhibitors", &error, &reply, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Could not get active inhibitors: %s", bus_error_message(&error, r));
+
+ table = table_new("who", "uid", "user", "pid", "comm", "what", "why", "mode");
+ if (!table)
+ return log_oom();
+
+ /* If there's not enough space, shorten the "WHY" column, as it's little more than an explaining comment. */
+ (void) table_set_weight(table, TABLE_HEADER_CELL(6), 20);
+ (void) table_set_maximum_width(table, TABLE_HEADER_CELL(0), columns()/2);
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssuu)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ for (;;) {
+ _cleanup_free_ char *comm = NULL, *u = NULL;
+ const char *what, *who, *why, *mode;
+ uint32_t uid, pid;
+
+ r = sd_bus_message_read(reply, "(ssssuu)", &what, &who, &why, &mode, &uid, &pid);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ if (arg_mode && !streq(mode, arg_mode))
+ continue;
+
+ (void) pid_get_comm(pid, &comm);
+ u = uid_to_name(uid);
+
+ r = table_add_many(table,
+ TABLE_STRING, who,
+ TABLE_UID, (uid_t) uid,
+ TABLE_STRING, strna(u),
+ TABLE_PID, (pid_t) pid,
+ TABLE_STRING, strna(comm),
+ TABLE_STRING, what,
+ TABLE_STRING, why,
+ TABLE_STRING, mode);
+ 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);
+
+ if (table_get_rows(table) > 1) {
+ 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);
+
+ table_set_header(table, arg_legend);
+
+ r = table_print(table, NULL);
+ if (r < 0)
+ return table_log_print_error(r);
+ }
+
+ if (arg_legend) {
+ if (table_get_rows(table) > 1)
+ printf("\n%zu inhibitors listed.\n", table_get_rows(table) - 1);
+ else
+ printf("No inhibitors.\n");
+ }
+
+ return 0;
+}
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-inhibit", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s [OPTIONS...] COMMAND ...\n"
+ "\n%sExecute a process while inhibiting shutdown/sleep/idle.%s\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --no-legend Do not show the headers and footers\n"
+ " --what=WHAT Operations to inhibit, colon separated list of:\n"
+ " shutdown, sleep, idle, handle-power-key,\n"
+ " handle-suspend-key, handle-hibernate-key,\n"
+ " handle-lid-switch\n"
+ " --who=STRING A descriptive string who is inhibiting\n"
+ " --why=STRING A descriptive string why is being inhibited\n"
+ " --mode=MODE One of block or delay\n"
+ " --list List active inhibitors\n"
+ "\nSee the %s for details.\n",
+ program_invocation_short_name,
+ ansi_highlight(),
+ ansi_normal(),
+ link);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_WHAT,
+ ARG_WHO,
+ ARG_WHY,
+ ARG_MODE,
+ ARG_LIST,
+ ARG_NO_PAGER,
+ ARG_NO_LEGEND,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "what", required_argument, NULL, ARG_WHAT },
+ { "who", required_argument, NULL, ARG_WHO },
+ { "why", required_argument, NULL, ARG_WHY },
+ { "mode", required_argument, NULL, ARG_MODE },
+ { "list", no_argument, NULL, ARG_LIST },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
+ while ((c = getopt_long(argc, argv, "+h", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_WHAT:
+ arg_what = optarg;
+ break;
+
+ case ARG_WHO:
+ arg_who = optarg;
+ break;
+
+ case ARG_WHY:
+ arg_why = optarg;
+ break;
+
+ case ARG_MODE:
+ arg_mode = optarg;
+ break;
+
+ case ARG_LIST:
+ arg_action = ACTION_LIST;
+ break;
+
+ case ARG_NO_PAGER:
+ arg_pager_flags |= PAGER_DISABLE;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (arg_action == ACTION_INHIBIT && optind == argc)
+ arg_action = ACTION_LIST;
+
+ else if (arg_action == ACTION_INHIBIT && optind >= argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Missing command line to execute.");
+
+ return 1;
+}
+
+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();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ r = sd_bus_default_system(&bus);
+ if (r < 0)
+ return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL);
+
+ if (arg_action == ACTION_LIST)
+ return print_inhibitors(bus);
+ else {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_strv_free_ char **arguments = NULL;
+ _cleanup_free_ char *w = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ pid_t pid;
+
+ /* Ignore SIGINT and allow the forked process to receive it */
+ (void) ignore_signals(SIGINT);
+
+ if (!arg_who) {
+ w = strv_join(argv + optind, " ");
+ if (!w)
+ return log_oom();
+
+ arg_who = w;
+ }
+
+ if (!arg_mode)
+ arg_mode = "block";
+
+ fd = inhibit(bus, &error);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to inhibit: %s", bus_error_message(&error, fd));
+
+ arguments = strv_copy(argv + optind);
+ if (!arguments)
+ return log_oom();
+
+ r = safe_fork("(inhibit)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* Child */
+ execvp(arguments[0], arguments);
+ log_open();
+ log_error_errno(errno, "Failed to execute %s: %m", argv[optind]);
+ _exit(EXIT_FAILURE);
+ }
+
+ return wait_for_terminate_and_check(argv[optind], pid, WAIT_LOG);
+ }
+}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
diff --git a/src/login/loginctl.c b/src/login/loginctl.c
new file mode 100644
index 0000000..7fc6efc
--- /dev/null
+++ b/src/login/loginctl.c
@@ -0,0 +1,1653 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <getopt.h>
+#include <locale.h>
+#include <unistd.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "build.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-map-properties.h"
+#include "bus-print-properties.h"
+#include "bus-unit-procs.h"
+#include "cgroup-show.h"
+#include "cgroup-util.h"
+#include "format-table.h"
+#include "log.h"
+#include "logs-show.h"
+#include "macro.h"
+#include "main-func.h"
+#include "memory-util.h"
+#include "pager.h"
+#include "parse-argument.h"
+#include "parse-util.h"
+#include "pretty-print.h"
+#include "process-util.h"
+#include "rlimit-util.h"
+#include "sigbus.h"
+#include "signal-util.h"
+#include "spawn-polkit-agent.h"
+#include "string-table.h"
+#include "strv.h"
+#include "sysfs-show.h"
+#include "terminal-util.h"
+#include "unit-name.h"
+#include "user-util.h"
+#include "verbs.h"
+
+static char **arg_property = NULL;
+static BusPrintPropertyFlags arg_print_flags = 0;
+static bool arg_full = false;
+static PagerFlags arg_pager_flags = 0;
+static bool arg_legend = true;
+static const char *arg_kill_whom = NULL;
+static int arg_signal = SIGTERM;
+static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
+static char *arg_host = NULL;
+static bool arg_ask_password = true;
+static unsigned arg_lines = 10;
+static OutputMode arg_output = OUTPUT_SHORT;
+
+STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep);
+
+typedef struct SessionStatusInfo {
+ const char *id;
+ uid_t uid;
+ const char *name;
+ dual_timestamp timestamp;
+ unsigned vtnr;
+ const char *seat;
+ const char *tty;
+ const char *display;
+ bool remote;
+ const char *remote_host;
+ const char *remote_user;
+ const char *service;
+ pid_t leader;
+ const char *type;
+ const char *class;
+ const char *state;
+ const char *scope;
+ const char *desktop;
+ bool idle_hint;
+ dual_timestamp idle_hint_timestamp;
+} SessionStatusInfo;
+
+typedef struct UserStatusInfo {
+ uid_t uid;
+ bool linger;
+ const char *name;
+ dual_timestamp timestamp;
+ const char *state;
+ char **sessions;
+ const char *display;
+ const char *slice;
+} UserStatusInfo;
+
+typedef struct SeatStatusInfo {
+ const char *id;
+ const char *active_session;
+ char **sessions;
+} SeatStatusInfo;
+
+static void user_status_info_done(UserStatusInfo *info) {
+ assert(info);
+
+ strv_free(info->sessions);
+}
+
+static void seat_status_info_done(SeatStatusInfo *info) {
+ assert(info);
+
+ strv_free(info->sessions);
+}
+
+static OutputFlags get_output_flags(void) {
+ return
+ FLAGS_SET(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY) * OUTPUT_SHOW_ALL |
+ (arg_full || !on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
+ colors_enabled() * OUTPUT_COLOR;
+}
+
+static int show_table(Table *table, const char *word) {
+ int r;
+
+ assert(table);
+ assert(word);
+
+ 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);
+
+ table_set_header(table, arg_legend);
+
+ 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);
+ else
+ r = table_print(table, NULL);
+ if (r < 0)
+ return table_log_print_error(r);
+ }
+
+ if (arg_legend) {
+ if (table_get_rows(table) > 1)
+ printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word);
+ else
+ printf("No %s.\n", word);
+ }
+
+ return 0;
+}
+
+static int list_sessions(int argc, char *argv[], void *userdata) {
+
+ static const struct bus_properties_map map[] = {
+ { "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));
+
+ 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;
+ 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);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ r = bus_map_all_properties(bus, "org.freedesktop.login1", object, map, BUS_MAP_BOOLEAN_AS_BOOL, &e, &m, &i);
+ if (r < 0) {
+ log_full_errno(sd_bus_error_has_name(&e, SD_BUS_ERROR_UNKNOWN_OBJECT) ? LOG_DEBUG : LOG_WARNING,
+ r,
+ "Failed to get properties of session %s, ignoring: %s",
+ id, bus_error_message(&e, r));
+ continue;
+ }
+
+ r = table_add_many(table,
+ TABLE_STRING, id,
+ TABLE_UID, (uid_t) uid,
+ TABLE_STRING, user,
+ TABLE_STRING, empty_to_null(seat),
+ 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);
+
+ if (i.idle_hint)
+ r = table_add_cell(table, NULL, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, &i.idle_hint_timestamp.monotonic);
+ else
+ 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 show_table(table, "sessions");
+}
+
+static int list_users(int argc, char *argv[], void *userdata) {
+
+ static const struct bus_properties_map property_map[] = {
+ { "Linger", "b", NULL, offsetof(UserStatusInfo, linger) },
+ { "State", "s", NULL, offsetof(UserStatusInfo, state) },
+ {},
+ };
+
+ _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, "ListUsers", &error, &reply, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_enter_container(reply, 'a', "(uso)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ table = table_new("uid", "user", "linger", "state");
+ if (!table)
+ return log_oom();
+
+ (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100);
+ (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ for (;;) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error_property = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_property = NULL;
+ _cleanup_(user_status_info_done) UserStatusInfo info = {};
+ const char *user, *object;
+ uint32_t uid;
+
+ r = sd_bus_message_read(reply, "(uso)", &uid, &user, &object);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ r = bus_map_all_properties(bus,
+ "org.freedesktop.login1",
+ object,
+ property_map,
+ BUS_MAP_BOOLEAN_AS_BOOL,
+ &error_property,
+ &reply_property,
+ &info);
+ if (r < 0) {
+ log_full_errno(sd_bus_error_has_name(&error_property, SD_BUS_ERROR_UNKNOWN_OBJECT) ? LOG_DEBUG : LOG_WARNING,
+ r,
+ "Failed to get properties of user %s, ignoring: %s",
+ user, bus_error_message(&error_property, r));
+ continue;
+ }
+
+ r = table_add_many(table,
+ TABLE_UID, (uid_t) uid,
+ TABLE_STRING, user,
+ TABLE_BOOLEAN, info.linger,
+ TABLE_STRING, info.state);
+ 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 show_table(table, "users");
+}
+
+static int list_seats(int argc, char *argv[], void *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;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ 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));
+
+ r = sd_bus_message_enter_container(reply, 'a', "(so)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ table = table_new("seat");
+ if (!table)
+ return log_oom();
+
+ (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ for (;;) {
+ const char *seat;
+
+ r = sd_bus_message_read(reply, "(so)", &seat, NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, seat);
+ 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 show_table(table, "seats");
+}
+
+static int show_unit_cgroup(
+ sd_bus *bus,
+ const char *unit,
+ pid_t leader,
+ const char *prefix) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *cgroup = NULL;
+ unsigned c;
+ int r;
+
+ assert(bus);
+ assert(unit);
+ assert(prefix);
+
+ r = show_cgroup_get_unit_path_and_warn(bus, unit, &cgroup);
+ if (r < 0)
+ return r;
+
+ if (isempty(cgroup))
+ return 0;
+
+ c = columns();
+ if (c > 18)
+ c -= 18;
+
+ r = unit_show_processes(bus, unit, cgroup, prefix, c, get_output_flags(), &error);
+ if (r == -EBADR) {
+ if (arg_transport == BUS_TRANSPORT_REMOTE)
+ return 0;
+
+ /* Fallback for older systemd versions where the GetUnitProcesses() call is not yet available */
+
+ if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup) != 0 && leader <= 0)
+ return 0;
+
+ show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, prefix, c, &leader, leader > 0, get_output_flags());
+ } else if (r < 0)
+ return log_error_errno(r, "Failed to dump process list: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int prop_map_first_of_struct(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ const char *contents;
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ r = sd_bus_message_peek_type(m, NULL, &contents);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, contents);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_basic(m, contents[0], userdata);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_skip(m, contents+1);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int prop_map_sessions_strv(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ const char *name;
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ r = sd_bus_message_enter_container(m, 'a', "(so)");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_read(m, "(so)", &name, NULL)) > 0) {
+ r = strv_extend(userdata, name);
+ if (r < 0)
+ return r;
+ }
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_exit_container(m);
+}
+
+static int mark_session(char **sessions, const char *target_session) {
+ assert(sessions);
+ assert(target_session);
+
+ STRV_FOREACH(i, sessions)
+ if (streq(*i, target_session)) {
+ _cleanup_free_ char *marked = NULL;
+
+ marked = strjoin("*", target_session);
+ if (!marked)
+ return log_oom();
+
+ return free_and_replace(*i, marked);
+ }
+
+ return 0;
+}
+
+static int print_session_status_info(sd_bus *bus, const char *path) {
+
+ static const struct bus_properties_map map[] = {
+ { "Id", "s", NULL, offsetof(SessionStatusInfo, id) },
+ { "Name", "s", NULL, offsetof(SessionStatusInfo, name) },
+ { "TTY", "s", NULL, offsetof(SessionStatusInfo, tty) },
+ { "Display", "s", NULL, offsetof(SessionStatusInfo, display) },
+ { "RemoteHost", "s", NULL, offsetof(SessionStatusInfo, remote_host) },
+ { "RemoteUser", "s", NULL, offsetof(SessionStatusInfo, remote_user) },
+ { "Service", "s", NULL, offsetof(SessionStatusInfo, service) },
+ { "Desktop", "s", NULL, offsetof(SessionStatusInfo, desktop) },
+ { "Type", "s", NULL, offsetof(SessionStatusInfo, type) },
+ { "Class", "s", NULL, offsetof(SessionStatusInfo, class) },
+ { "Scope", "s", NULL, offsetof(SessionStatusInfo, scope) },
+ { "State", "s", NULL, offsetof(SessionStatusInfo, state) },
+ { "VTNr", "u", NULL, offsetof(SessionStatusInfo, vtnr) },
+ { "Leader", "u", NULL, offsetof(SessionStatusInfo, leader) },
+ { "Remote", "b", NULL, offsetof(SessionStatusInfo, remote) },
+ { "Timestamp", "t", NULL, offsetof(SessionStatusInfo, timestamp.realtime) },
+ { "TimestampMonotonic", "t", NULL, offsetof(SessionStatusInfo, timestamp.monotonic) },
+ { "IdleHint", "b", NULL, offsetof(SessionStatusInfo, idle_hint) },
+ { "IdleSinceHint", "t", NULL, offsetof(SessionStatusInfo, idle_hint_timestamp.realtime) },
+ { "IdleSinceHintMonotonic", "t", NULL, offsetof(SessionStatusInfo, idle_hint_timestamp.monotonic) },
+ { "User", "(uo)", prop_map_first_of_struct, offsetof(SessionStatusInfo, uid) },
+ { "Seat", "(so)", prop_map_first_of_struct, offsetof(SessionStatusInfo, seat) },
+ {}
+ };
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_(table_unrefp) Table *table = NULL;
+ SessionStatusInfo i = {};
+ int r;
+
+ r = bus_map_all_properties(bus, "org.freedesktop.login1", path, map, BUS_MAP_BOOLEAN_AS_BOOL, &error, &m, &i);
+ if (r < 0)
+ return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
+
+ table = table_new_vertical();
+ if (!table)
+ return log_oom();
+
+ (void) table_set_ersatz_string(table, TABLE_ERSATZ_NA);
+
+ if (dual_timestamp_is_set(&i.timestamp)) {
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Since");
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_cell_stringf(table, NULL, "%s; %s",
+ FORMAT_TIMESTAMP(i.timestamp.realtime),
+ FORMAT_TIMESTAMP_RELATIVE_MONOTONIC(i.timestamp.monotonic));
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ r = table_add_many(table,
+ TABLE_FIELD, "State",
+ TABLE_STRING, i.state);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (i.leader > 0) {
+ _cleanup_free_ char *name = NULL;
+
+ (void) pid_get_comm(i.leader, &name);
+
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Leader");
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_cell_stringf(table, NULL, PID_FMT "%s%s%s",
+ i.leader,
+ !isempty(name) ? " (" : "",
+ strempty(name),
+ !isempty(name) ? ")" : "");
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+
+ if (!isempty(i.seat)) {
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Seat");
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (i.vtnr > 0)
+ r = table_add_cell_stringf(table, NULL, "%s; vc%u", i.seat, i.vtnr);
+ else
+ r = table_add_cell(table, NULL, TABLE_STRING, i.seat);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ if (!isempty(i.tty))
+ r = table_add_many(table,
+ TABLE_FIELD, "TTY",
+ TABLE_STRING, i.tty);
+ else if (!isempty(i.display))
+ r = table_add_many(table,
+ TABLE_FIELD, "Display",
+ TABLE_STRING, i.display);
+ else
+ r = 0;
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Remote");
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (i.remote_host && i.remote_user)
+ r = table_add_cell_stringf(table, NULL, "%s@%s", i.remote_user, i.remote_host);
+ else if (i.remote_host)
+ r = table_add_cell(table, NULL, TABLE_STRING, i.remote_host);
+ else if (i.remote_user)
+ r = table_add_cell_stringf(table, NULL, "user %s", i.remote_user);
+ else
+ r = table_add_cell(table, NULL, TABLE_BOOLEAN, &i.remote);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (i.service) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Service",
+ TABLE_STRING, i.service);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ if (i.type) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Type",
+ TABLE_STRING, i.type);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ if (i.class) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Class",
+ TABLE_STRING, i.class);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ if (!isempty(i.desktop)) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Desktop",
+ TABLE_STRING, i.desktop);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Idle");
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (i.idle_hint && dual_timestamp_is_set(&i.idle_hint_timestamp))
+ r = table_add_cell_stringf(table, NULL, "%s since %s (%s)",
+ yes_no(i.idle_hint),
+ FORMAT_TIMESTAMP(i.idle_hint_timestamp.realtime),
+ FORMAT_TIMESTAMP_RELATIVE_MONOTONIC(i.idle_hint_timestamp.monotonic));
+ else
+ r = table_add_cell(table, NULL, TABLE_BOOLEAN, &i.idle_hint);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (i.scope) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Unit",
+ TABLE_SET_MINIMUM_WIDTH, STRLEN("Display"), /* For alignment with show_unit_cgroup */
+ TABLE_STRING, i.scope);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ /* We don't use the table to show the header, in order to make the width of the column stable. */
+ printf("%s%s - %s (" UID_FMT ")%s\n", ansi_highlight(), i.id, i.name, i.uid, ansi_normal());
+
+ r = table_print(table, NULL);
+ if (r < 0)
+ return table_log_print_error(r);
+
+ if (i.scope) {
+ show_unit_cgroup(bus, i.scope, i.leader, /* prefix = */ strrepa(" ", STRLEN("Display: ")));
+
+ if (arg_transport == BUS_TRANSPORT_LOCAL)
+ show_journal_by_unit(
+ stdout,
+ i.scope,
+ NULL,
+ arg_output,
+ 0,
+ i.timestamp.monotonic,
+ arg_lines,
+ 0,
+ get_output_flags() | OUTPUT_BEGIN_NEWLINE,
+ SD_JOURNAL_LOCAL_ONLY,
+ true,
+ NULL);
+ }
+
+ return 0;
+}
+
+static int print_user_status_info(sd_bus *bus, const char *path) {
+
+ static const struct bus_properties_map map[] = {
+ { "Name", "s", NULL, offsetof(UserStatusInfo, name) },
+ { "Linger", "b", NULL, offsetof(UserStatusInfo, linger) },
+ { "Slice", "s", NULL, offsetof(UserStatusInfo, slice) },
+ { "State", "s", NULL, offsetof(UserStatusInfo, state) },
+ { "UID", "u", NULL, offsetof(UserStatusInfo, uid) },
+ { "Timestamp", "t", NULL, offsetof(UserStatusInfo, timestamp.realtime) },
+ { "TimestampMonotonic", "t", NULL, offsetof(UserStatusInfo, timestamp.monotonic) },
+ { "Display", "(so)", prop_map_first_of_struct, offsetof(UserStatusInfo, display) },
+ { "Sessions", "a(so)", prop_map_sessions_strv, offsetof(UserStatusInfo, sessions) },
+ {}
+ };
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_(user_status_info_done) UserStatusInfo i = {};
+ _cleanup_(table_unrefp) Table *table = NULL;
+ int r;
+
+ r = bus_map_all_properties(bus, "org.freedesktop.login1", path, map, BUS_MAP_BOOLEAN_AS_BOOL, &error, &m, &i);
+ if (r < 0)
+ return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
+
+ table = table_new_vertical();
+ if (!table)
+ return log_oom();
+
+ (void) table_set_ersatz_string(table, TABLE_ERSATZ_NA);
+
+ if (dual_timestamp_is_set(&i.timestamp)) {
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Since");
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_cell_stringf(table, NULL, "%s; %s",
+ FORMAT_TIMESTAMP(i.timestamp.realtime),
+ FORMAT_TIMESTAMP_RELATIVE_MONOTONIC(i.timestamp.monotonic));
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ r = table_add_many(table,
+ TABLE_FIELD, "State",
+ TABLE_STRING, i.state);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (!strv_isempty(i.sessions)) {
+ _cleanup_strv_free_ char **sessions = TAKE_PTR(i.sessions);
+
+ r = mark_session(sessions, i.display);
+ if (r < 0)
+ return r;
+
+ r = table_add_many(table,
+ TABLE_FIELD, "Sessions",
+ TABLE_STRV_WRAPPED, sessions);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ r = table_add_many(table,
+ TABLE_FIELD, "Linger",
+ TABLE_BOOLEAN, i.linger);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (i.slice) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Unit",
+ TABLE_SET_MINIMUM_WIDTH, STRLEN("Sessions"), /* For alignment with show_unit_cgroup */
+ TABLE_STRING, i.slice);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ printf("%s%s (" UID_FMT ")%s\n", ansi_highlight(), i.name, i.uid, ansi_normal());
+
+ r = table_print(table, NULL);
+ if (r < 0)
+ return table_log_print_error(r);
+
+ if (i.slice) {
+ show_unit_cgroup(bus, i.slice, /* leader = */ 0, /* prefix = */ strrepa(" ", STRLEN("Sessions: ")));
+
+ if (arg_transport == BUS_TRANSPORT_LOCAL)
+ show_journal_by_unit(
+ stdout,
+ i.slice,
+ NULL,
+ arg_output,
+ 0,
+ i.timestamp.monotonic,
+ arg_lines,
+ 0,
+ get_output_flags() | OUTPUT_BEGIN_NEWLINE,
+ SD_JOURNAL_LOCAL_ONLY,
+ true,
+ NULL);
+ }
+
+ return 0;
+}
+
+static int print_seat_status_info(sd_bus *bus, const char *path) {
+
+ static const struct bus_properties_map map[] = {
+ { "Id", "s", NULL, offsetof(SeatStatusInfo, id) },
+ { "ActiveSession", "(so)", prop_map_first_of_struct, offsetof(SeatStatusInfo, active_session) },
+ { "Sessions", "a(so)", prop_map_sessions_strv, offsetof(SeatStatusInfo, sessions) },
+ {}
+ };
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_(seat_status_info_done) SeatStatusInfo i = {};
+ _cleanup_(table_unrefp) Table *table = NULL;
+ int r;
+
+ r = bus_map_all_properties(bus, "org.freedesktop.login1", path, map, 0, &error, &m, &i);
+ if (r < 0)
+ return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
+
+ table = table_new_vertical();
+ if (!table)
+ return log_oom();
+
+ (void) table_set_ersatz_string(table, TABLE_ERSATZ_NA);
+
+ if (!strv_isempty(i.sessions)) {
+ _cleanup_strv_free_ char **sessions = TAKE_PTR(i.sessions);
+
+ r = mark_session(sessions, i.active_session);
+ if (r < 0)
+ return r;
+
+ r = table_add_many(table,
+ TABLE_FIELD, "Sessions",
+ TABLE_STRV_WRAPPED, sessions);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ if (arg_transport == BUS_TRANSPORT_LOCAL) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Devices",
+ TABLE_SET_MINIMUM_WIDTH, STRLEN("Sessions"), /* For alignment with show_sysfs */
+ TABLE_EMPTY);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ printf("%s%s%s\n", ansi_highlight(), i.id, ansi_normal());
+
+ r = table_print(table, NULL);
+ if (r < 0)
+ return table_log_print_error(r);
+
+ if (arg_transport == BUS_TRANSPORT_LOCAL) {
+ unsigned c = columns();
+ if (c > 21)
+ c -= 21;
+
+ show_sysfs(i.id, strrepa(" ", STRLEN("Sessions:")), c, get_output_flags());
+ }
+
+ return 0;
+}
+
+static int print_property(const char *name, const char *expected_value, sd_bus_message *m, BusPrintPropertyFlags flags) {
+ char type;
+ const char *contents;
+ int r;
+
+ assert(name);
+ assert(m);
+
+ r = sd_bus_message_peek_type(m, &type, &contents);
+ if (r < 0)
+ return r;
+
+ switch (type) {
+
+ case SD_BUS_TYPE_STRUCT:
+
+ if (contents[0] == SD_BUS_TYPE_STRING && STR_IN_SET(name, "Display", "Seat", "ActiveSession")) {
+ const char *s;
+
+ r = sd_bus_message_read(m, "(so)", &s, NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ bus_print_property_value(name, expected_value, flags, s);
+
+ return 1;
+
+ } else if (contents[0] == SD_BUS_TYPE_UINT32 && streq(name, "User")) {
+ uint32_t uid;
+
+ r = sd_bus_message_read(m, "(uo)", &uid, NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (!uid_is_valid(uid))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid user ID: " UID_FMT,
+ uid);
+
+ bus_print_property_valuef(name, expected_value, flags, UID_FMT, uid);
+ return 1;
+ }
+ break;
+
+ case SD_BUS_TYPE_ARRAY:
+
+ if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Sessions")) {
+ const char *s;
+ bool space = false;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(so)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (!FLAGS_SET(flags, BUS_PRINT_PROPERTY_ONLY_VALUE))
+ printf("%s=", name);
+
+ while ((r = sd_bus_message_read(m, "(so)", &s, NULL)) > 0) {
+ printf("%s%s", space ? " " : "", s);
+ space = true;
+ }
+
+ if (space || !FLAGS_SET(flags, BUS_PRINT_PROPERTY_ONLY_VALUE))
+ printf("\n");
+
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return 1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static int show_properties(sd_bus *bus, const char *path) {
+ int r;
+
+ assert(bus);
+ assert(path);
+
+ r = bus_print_all_properties(
+ bus,
+ "org.freedesktop.login1",
+ path,
+ print_property,
+ arg_property,
+ arg_print_flags,
+ NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return 0;
+}
+
+static int get_bus_path_by_id(
+ sd_bus *bus,
+ const char *type,
+ const char *method,
+ const char *id,
+ char **ret) {
+
+ _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;
+
+ assert(bus);
+ assert(type);
+ assert(STR_IN_SET(type, "session", "seat"));
+ assert(method);
+ assert(id);
+ assert(ret);
+
+ r = bus_call_method(bus, bus_login_mgr, method, &error, &reply, "s", id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get path for %s '%s': %s", type, id, bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "o", &path);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ p = strdup(path);
+ if (!p)
+ return log_oom();
+
+ *ret = TAKE_PTR(p);
+ return 0;
+}
+
+static int show_session(int argc, char *argv[], void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ bool properties;
+ int r;
+
+ assert(argv);
+
+ properties = !strstr(argv[0], "status");
+
+ pager_open(arg_pager_flags);
+
+ if (argc <= 1) {
+ _cleanup_free_ char *path = NULL;
+
+ /* If no argument is specified inspect the manager itself */
+ if (properties)
+ return show_properties(bus, "/org/freedesktop/login1");
+
+ r = get_bus_path_by_id(bus, "session", "GetSession", "auto", &path);
+ if (r < 0)
+ return r;
+
+ return print_session_status_info(bus, path);
+ }
+
+ for (int i = 1, first = true; i < argc; i++, first = false) {
+ _cleanup_free_ char *path = NULL;
+
+ r = get_bus_path_by_id(bus, "session", "GetSession", argv[i], &path);
+ if (r < 0)
+ return r;
+
+ if (!first)
+ putchar('\n');
+
+ if (properties)
+ r = show_properties(bus, path);
+ else
+ r = print_session_status_info(bus, path);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int show_user(int argc, char *argv[], void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ bool properties;
+ int r;
+
+ assert(argv);
+
+ properties = !strstr(argv[0], "status");
+
+ pager_open(arg_pager_flags);
+
+ if (argc <= 1) {
+ /* If no argument is specified inspect the manager itself */
+ if (properties)
+ return show_properties(bus, "/org/freedesktop/login1");
+
+ return print_user_status_info(bus, "/org/freedesktop/login1/user/self");
+ }
+
+ for (int i = 1, first = true; i < argc; i++, first = false) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ const char *path;
+ uid_t uid;
+
+ r = get_user_creds((const char**) (argv+i), &uid, NULL, NULL, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to look up user %s: %m", argv[i]);
+
+ r = bus_call_method(bus, bus_login_mgr, "GetUser", &error, &reply, "u", (uint32_t) uid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get user: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "o", &path);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (!first)
+ putchar('\n');
+
+ if (properties)
+ r = show_properties(bus, path);
+ else
+ r = print_user_status_info(bus, path);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int show_seat(int argc, char *argv[], void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ bool properties;
+ int r;
+
+ assert(argv);
+
+ properties = !strstr(argv[0], "status");
+
+ pager_open(arg_pager_flags);
+
+ if (argc <= 1) {
+ _cleanup_free_ char *path = NULL;
+
+ /* If no argument is specified inspect the manager itself */
+ if (properties)
+ return show_properties(bus, "/org/freedesktop/login1");
+
+ r = get_bus_path_by_id(bus, "seat", "GetSeat", "auto", &path);
+ if (r < 0)
+ return r;
+
+ return print_seat_status_info(bus, path);
+ }
+
+ for (int i = 1, first = true; i < argc; i++, first = false) {
+ _cleanup_free_ char *path = NULL;
+
+ r = get_bus_path_by_id(bus, "seat", "GetSeat", argv[i], &path);
+ if (r < 0)
+ return r;
+
+ if (!first)
+ putchar('\n');
+
+ if (properties)
+ r = show_properties(bus, path);
+ else
+ r = print_seat_status_info(bus, path);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int activate(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ if (argc < 2) {
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.login1",
+ "/org/freedesktop/login1/session/auto",
+ "org.freedesktop.login1.Session",
+ streq(argv[0], "lock-session") ? "Lock" :
+ streq(argv[0], "unlock-session") ? "Unlock" :
+ streq(argv[0], "terminate-session") ? "Terminate" :
+ "Activate",
+ &error, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
+
+ return 0;
+ }
+
+ for (int i = 1; i < argc; i++) {
+ r = bus_call_method(
+ bus,
+ bus_login_mgr,
+ streq(argv[0], "lock-session") ? "LockSession" :
+ streq(argv[0], "unlock-session") ? "UnlockSession" :
+ streq(argv[0], "terminate-session") ? "TerminateSession" :
+ "ActivateSession",
+ &error, NULL,
+ "s", argv[i]);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int kill_session(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ if (!arg_kill_whom)
+ arg_kill_whom = "all";
+
+ for (int i = 1; i < argc; i++) {
+ r = bus_call_method(
+ bus,
+ bus_login_mgr,
+ "KillSession",
+ &error, NULL,
+ "ssi", argv[i], arg_kill_whom, arg_signal);
+ if (r < 0)
+ return log_error_errno(r, "Could not kill session: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int enable_linger(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ char* short_argv[3];
+ bool b;
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ b = streq(argv[0], "enable-linger");
+
+ if (argc < 2) {
+ /* No argument? Let's use an empty user name,
+ * then logind will use our user. */
+
+ short_argv[0] = argv[0];
+ short_argv[1] = (char*) "";
+ short_argv[2] = NULL;
+ argv = short_argv;
+ argc = 2;
+ }
+
+ for (int i = 1; i < argc; i++) {
+ uid_t uid;
+
+ if (isempty(argv[i]))
+ uid = UID_INVALID;
+ else {
+ r = get_user_creds((const char**) (argv+i), &uid, NULL, NULL, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to look up user %s: %m", argv[i]);
+ }
+
+ r = bus_call_method(
+ bus,
+ bus_login_mgr,
+ "SetUserLinger",
+ &error, NULL,
+ "ubb", (uint32_t) uid, b, true);
+ if (r < 0)
+ return log_error_errno(r, "Could not enable linger: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int terminate_user(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ for (int i = 1; i < argc; i++) {
+ uid_t uid;
+
+ if (isempty(argv[i]))
+ uid = getuid();
+ else {
+ const char *u = argv[i];
+
+ r = get_user_creds(&u, &uid, NULL, NULL, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to look up user %s: %m", argv[i]);
+ }
+
+ r = bus_call_method(bus, bus_login_mgr, "TerminateUser", &error, NULL, "u", (uint32_t) uid);
+ if (r < 0)
+ return log_error_errno(r, "Could not terminate user: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int kill_user(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ if (!arg_kill_whom)
+ arg_kill_whom = "all";
+
+ for (int i = 1; i < argc; i++) {
+ uid_t uid;
+
+ if (isempty(argv[i]))
+ uid = getuid();
+ else {
+ const char *u = argv[i];
+
+ r = get_user_creds(&u, &uid, NULL, NULL, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to look up user %s: %m", argv[i]);
+ }
+
+ r = bus_call_method(
+ bus,
+ bus_login_mgr,
+ "KillUser",
+ &error, NULL,
+ "ui", (uint32_t) uid, arg_signal);
+ if (r < 0)
+ return log_error_errno(r, "Could not kill user: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int attach(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ for (int i = 2; i < argc; i++) {
+
+ r = bus_call_method(
+ bus,
+ bus_login_mgr,
+ "AttachDevice",
+ &error, NULL,
+ "ssb", argv[1], argv[i], true);
+ if (r < 0)
+ return log_error_errno(r, "Could not attach device: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int flush_devices(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ r = bus_call_method(bus, bus_login_mgr, "FlushDevices", &error, NULL, "b", true);
+ if (r < 0)
+ return log_error_errno(r, "Could not flush devices: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int lock_sessions(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ r = bus_call_method(
+ bus,
+ bus_login_mgr,
+ streq(argv[0], "lock-sessions") ? "LockSessions" : "UnlockSessions",
+ &error, NULL,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Could not lock sessions: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int terminate_seat(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ assert(argv);
+
+ polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ for (int i = 1; i < argc; i++) {
+
+ r = bus_call_method(bus, bus_login_mgr, "TerminateSeat", &error, NULL, "s", argv[i]);
+ if (r < 0)
+ return log_error_errno(r, "Could not terminate seat: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int help(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ pager_open(arg_pager_flags);
+
+ r = terminal_urlify_man("loginctl", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...] COMMAND ...\n\n"
+ "%5$sSend control commands to or query the login manager.%6$s\n"
+ "\n%3$sSession Commands:%4$s\n"
+ " list-sessions List sessions\n"
+ " session-status [ID...] Show session status\n"
+ " show-session [ID...] Show properties of sessions or the manager\n"
+ " activate [ID] Activate a session\n"
+ " lock-session [ID...] Screen lock one or more sessions\n"
+ " unlock-session [ID...] Screen unlock one or more sessions\n"
+ " lock-sessions Screen lock all current sessions\n"
+ " unlock-sessions Screen unlock all current sessions\n"
+ " terminate-session ID... Terminate one or more sessions\n"
+ " kill-session ID... Send signal to processes of a session\n"
+ "\n%3$sUser Commands:%4$s\n"
+ " list-users List users\n"
+ " user-status [USER...] Show user status\n"
+ " show-user [USER...] Show properties of users or the manager\n"
+ " enable-linger [USER...] Enable linger state of one or more users\n"
+ " disable-linger [USER...] Disable linger state of one or more users\n"
+ " terminate-user USER... Terminate all sessions of one or more users\n"
+ " kill-user USER... Send signal to processes of a user\n"
+ "\n%3$sSeat Commands:%4$s\n"
+ " list-seats List seats\n"
+ " seat-status [NAME...] Show seat status\n"
+ " show-seat [NAME...] Show properties of seats or the manager\n"
+ " attach NAME DEVICE... Attach one or more devices to a seat\n"
+ " flush-devices Flush all device associations\n"
+ " terminate-seat NAME... Terminate all sessions on one or more seats\n"
+ "\n%3$sOptions:%4$s\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --no-legend Do not show the headers and footers\n"
+ " --no-ask-password Don't prompt for password\n"
+ " -H --host=[USER@]HOST Operate on remote host\n"
+ " -M --machine=CONTAINER Operate on local container\n"
+ " -p --property=NAME Show only properties by this name\n"
+ " -P NAME Equivalent to --value --property=NAME\n"
+ " -a --all Show all properties, including empty ones\n"
+ " --value When showing properties, only print the value\n"
+ " -l --full Do not ellipsize output\n"
+ " --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"
+ " short-iso, short-iso-precise, short-full,\n"
+ " short-monotonic, short-unix, short-delta,\n"
+ " json, json-pretty, json-sse, json-seq, cat,\n"
+ " verbose, export, with-unit)\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_VALUE,
+ ARG_NO_PAGER,
+ ARG_NO_LEGEND,
+ ARG_KILL_WHOM,
+ ARG_NO_ASK_PASSWORD,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "property", required_argument, NULL, 'p' },
+ { "all", no_argument, NULL, 'a' },
+ { "value", no_argument, NULL, ARG_VALUE },
+ { "full", no_argument, NULL, 'l' },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "kill-whom", required_argument, NULL, ARG_KILL_WHOM },
+ { "signal", required_argument, NULL, 's' },
+ { "host", required_argument, NULL, 'H' },
+ { "machine", required_argument, NULL, 'M' },
+ { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
+ { "lines", required_argument, NULL, 'n' },
+ { "output", required_argument, NULL, 'o' },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ return help(0, NULL, NULL);
+
+ case ARG_VERSION:
+ return version();
+
+ case 'P':
+ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true);
+ _fallthrough_;
+
+ case 'p': {
+ r = strv_extend(&arg_property, optarg);
+ if (r < 0)
+ return log_oom();
+
+ /* If the user asked for a particular
+ * property, show it to them, even if it is
+ * empty. */
+ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true);
+ break;
+ }
+
+ case 'a':
+ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true);
+ break;
+
+ case ARG_VALUE:
+ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true);
+ break;
+
+ case 'l':
+ arg_full = true;
+ break;
+
+ case 'n':
+ if (safe_atou(optarg, &arg_lines) < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to parse lines '%s'", optarg);
+ break;
+
+ case 'o':
+ if (streq(optarg, "help")) {
+ DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX);
+ return 0;
+ }
+
+ arg_output = output_mode_from_string(optarg);
+ if (arg_output < 0)
+ return log_error_errno(arg_output, "Unknown output '%s'.", optarg);
+
+ if (OUTPUT_MODE_IS_JSON(arg_output))
+ arg_legend = false;
+
+ break;
+
+ case ARG_NO_PAGER:
+ arg_pager_flags |= PAGER_DISABLE;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ break;
+
+ case ARG_NO_ASK_PASSWORD:
+ arg_ask_password = false;
+ break;
+
+ case ARG_KILL_WHOM:
+ arg_kill_whom = optarg;
+ break;
+
+ case 's':
+ r = parse_signal_argument(optarg, &arg_signal);
+ if (r <= 0)
+ return r;
+ break;
+
+ case 'H':
+ arg_transport = BUS_TRANSPORT_REMOTE;
+ arg_host = optarg;
+ break;
+
+ case 'M':
+ arg_transport = BUS_TRANSPORT_MACHINE;
+ arg_host = optarg;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1;
+}
+
+static int loginctl_main(int argc, char *argv[], sd_bus *bus) {
+ static const Verb verbs[] = {
+ { "help", VERB_ANY, VERB_ANY, 0, help },
+ { "list-sessions", VERB_ANY, 1, VERB_DEFAULT, list_sessions },
+ { "session-status", VERB_ANY, VERB_ANY, 0, show_session },
+ { "show-session", VERB_ANY, VERB_ANY, 0, show_session },
+ { "activate", VERB_ANY, 2, 0, activate },
+ { "lock-session", VERB_ANY, VERB_ANY, 0, activate },
+ { "unlock-session", VERB_ANY, VERB_ANY, 0, activate },
+ { "lock-sessions", VERB_ANY, 1, 0, lock_sessions },
+ { "unlock-sessions", VERB_ANY, 1, 0, lock_sessions },
+ { "terminate-session", 2, VERB_ANY, 0, activate },
+ { "kill-session", 2, VERB_ANY, 0, kill_session },
+ { "list-users", VERB_ANY, 1, 0, list_users },
+ { "user-status", VERB_ANY, VERB_ANY, 0, show_user },
+ { "show-user", VERB_ANY, VERB_ANY, 0, show_user },
+ { "enable-linger", VERB_ANY, VERB_ANY, 0, enable_linger },
+ { "disable-linger", VERB_ANY, VERB_ANY, 0, enable_linger },
+ { "terminate-user", 2, VERB_ANY, 0, terminate_user },
+ { "kill-user", 2, VERB_ANY, 0, kill_user },
+ { "list-seats", VERB_ANY, 1, 0, list_seats },
+ { "seat-status", VERB_ANY, VERB_ANY, 0, show_seat },
+ { "show-seat", VERB_ANY, VERB_ANY, 0, show_seat },
+ { "attach", 3, VERB_ANY, 0, attach },
+ { "flush-devices", VERB_ANY, 1, 0, flush_devices },
+ { "terminate-seat", 2, VERB_ANY, 0, terminate_seat },
+ {}
+ };
+
+ return dispatch_verb(argc, argv, verbs, bus);
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ setlocale(LC_ALL, "");
+ log_setup();
+
+ /* The journal merging logic potentially needs a lot of fds. */
+ (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
+
+ sigbus_install();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus);
+ if (r < 0)
+ return bus_log_connect_error(r, arg_transport);
+
+ (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
+
+ return loginctl_main(argc, argv, bus);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/login/logind-action.c b/src/login/logind-action.c
new file mode 100644
index 0000000..8269f52
--- /dev/null
+++ b/src/login/logind-action.c
@@ -0,0 +1,331 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "conf-parser.h"
+#include "format-util.h"
+#include "logind-action.h"
+#include "logind-dbus.h"
+#include "logind-session-dbus.h"
+#include "process-util.h"
+#include "special.h"
+#include "string-table.h"
+#include "terminal-util.h"
+#include "user-util.h"
+
+static const HandleActionData handle_action_data_table[_HANDLE_ACTION_MAX] = {
+ [HANDLE_POWEROFF] = {
+ .handle = HANDLE_POWEROFF,
+ .target = SPECIAL_POWEROFF_TARGET,
+ .inhibit_what = INHIBIT_SHUTDOWN,
+ .polkit_action = "org.freedesktop.login1.power-off",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.power-off-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.power-off-ignore-inhibit",
+ .sleep_operation = _SLEEP_OPERATION_INVALID,
+ .message_id = SD_MESSAGE_SHUTDOWN_STR,
+ .message = "System is powering down",
+ .log_verb = "power-off",
+ },
+ [HANDLE_REBOOT] = {
+ .handle = HANDLE_REBOOT,
+ .target = SPECIAL_REBOOT_TARGET,
+ .inhibit_what = INHIBIT_SHUTDOWN,
+ .polkit_action = "org.freedesktop.login1.reboot",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.reboot-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.reboot-ignore-inhibit",
+ .sleep_operation = _SLEEP_OPERATION_INVALID,
+ .message_id = SD_MESSAGE_SHUTDOWN_STR,
+ .message = "System is rebooting",
+ .log_verb = "reboot",
+ },
+ [HANDLE_HALT] = {
+ .handle = HANDLE_HALT,
+ .target = SPECIAL_HALT_TARGET,
+ .inhibit_what = INHIBIT_SHUTDOWN,
+ .polkit_action = "org.freedesktop.login1.halt",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.halt-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.halt-ignore-inhibit",
+ .sleep_operation = _SLEEP_OPERATION_INVALID,
+ .message_id = SD_MESSAGE_SHUTDOWN_STR,
+ .message = "System is halting",
+ .log_verb = "halt",
+ },
+ [HANDLE_KEXEC] = {
+ .handle = HANDLE_KEXEC,
+ .target = SPECIAL_KEXEC_TARGET,
+ .inhibit_what = INHIBIT_SHUTDOWN,
+ .polkit_action = "org.freedesktop.login1.reboot",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.reboot-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.reboot-ignore-inhibit",
+ .sleep_operation = _SLEEP_OPERATION_INVALID,
+ .message_id = SD_MESSAGE_SHUTDOWN_STR,
+ .message = "System is rebooting with kexec",
+ .log_verb = "kexec",
+ },
+ [HANDLE_SOFT_REBOOT] = {
+ .handle = HANDLE_SOFT_REBOOT,
+ .target = SPECIAL_SOFT_REBOOT_TARGET,
+ .inhibit_what = INHIBIT_SHUTDOWN,
+ .polkit_action = "org.freedesktop.login1.reboot",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.reboot-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.reboot-ignore-inhibit",
+ .sleep_operation = _SLEEP_OPERATION_INVALID,
+ .message_id = SD_MESSAGE_SHUTDOWN_STR,
+ .message = "System userspace is rebooting",
+ .log_verb = "soft-reboot",
+ },
+ [HANDLE_SUSPEND] = {
+ .handle = HANDLE_SUSPEND,
+ .target = SPECIAL_SUSPEND_TARGET,
+ .inhibit_what = INHIBIT_SLEEP,
+ .polkit_action = "org.freedesktop.login1.suspend",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.suspend-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.suspend-ignore-inhibit",
+ .sleep_operation = SLEEP_SUSPEND,
+ },
+ [HANDLE_HIBERNATE] = {
+ .handle = HANDLE_HIBERNATE,
+ .target = SPECIAL_HIBERNATE_TARGET,
+ .inhibit_what = INHIBIT_SLEEP,
+ .polkit_action = "org.freedesktop.login1.hibernate",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.hibernate-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.hibernate-ignore-inhibit",
+ .sleep_operation = SLEEP_HIBERNATE,
+ },
+ [HANDLE_HYBRID_SLEEP] = {
+ .handle = HANDLE_HYBRID_SLEEP,
+ .target = SPECIAL_HYBRID_SLEEP_TARGET,
+ .inhibit_what = INHIBIT_SLEEP,
+ .polkit_action = "org.freedesktop.login1.hibernate",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.hibernate-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.hibernate-ignore-inhibit",
+ .sleep_operation = SLEEP_HYBRID_SLEEP,
+ },
+ [HANDLE_SUSPEND_THEN_HIBERNATE] = {
+ .handle = HANDLE_SUSPEND_THEN_HIBERNATE,
+ .target = SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET,
+ .inhibit_what = INHIBIT_SLEEP,
+ .polkit_action = "org.freedesktop.login1.hibernate",
+ .polkit_action_multiple_sessions = "org.freedesktop.login1.hibernate-multiple-sessions",
+ .polkit_action_ignore_inhibit = "org.freedesktop.login1.hibernate-ignore-inhibit",
+ .sleep_operation = SLEEP_SUSPEND_THEN_HIBERNATE,
+ },
+ [HANDLE_FACTORY_RESET] = {
+ .handle = HANDLE_FACTORY_RESET,
+ .target = SPECIAL_FACTORY_RESET_TARGET,
+ .inhibit_what = _INHIBIT_WHAT_INVALID,
+ .sleep_operation = _SLEEP_OPERATION_INVALID,
+ .message_id = SD_MESSAGE_FACTORY_RESET_STR,
+ .message = "System is performing factory reset",
+ },
+};
+
+const HandleActionData* handle_action_lookup(HandleAction action) {
+
+ if (action < 0 || (size_t) action >= ELEMENTSOF(handle_action_data_table))
+ return NULL;
+
+ return &handle_action_data_table[action];
+}
+
+static int handle_action_execute(
+ Manager *m,
+ HandleAction handle,
+ bool ignore_inhibited,
+ bool is_edge) {
+
+ static const char * const message_table[_HANDLE_ACTION_MAX] = {
+ [HANDLE_POWEROFF] = "Powering off...",
+ [HANDLE_REBOOT] = "Rebooting...",
+ [HANDLE_HALT] = "Halting...",
+ [HANDLE_KEXEC] = "Rebooting via kexec...",
+ [HANDLE_SOFT_REBOOT] = "Rebooting userspace...",
+ [HANDLE_SUSPEND] = "Suspending...",
+ [HANDLE_HIBERNATE] = "Hibernating...",
+ [HANDLE_HYBRID_SLEEP] = "Hibernating and suspending...",
+ [HANDLE_SUSPEND_THEN_HIBERNATE] = "Suspending, then hibernating...",
+ [HANDLE_FACTORY_RESET] = "Performing factory reset...",
+ };
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ InhibitWhat inhibit_operation;
+ Inhibitor *offending = NULL;
+ int r;
+
+ assert(m);
+
+ if (handle == HANDLE_KEXEC && access(KEXEC, X_OK) < 0)
+ return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Requested %s operation not supported, ignoring.", handle_action_to_string(handle));
+
+ if (m->delayed_action)
+ return log_debug_errno(SYNTHETIC_ERRNO(EALREADY),
+ "Action %s already in progress, ignoring requested %s operation.",
+ handle_action_to_string(m->delayed_action->handle),
+ handle_action_to_string(handle));
+
+ inhibit_operation = handle_action_lookup(handle)->inhibit_what;
+
+ /* If the actual operation is inhibited, warn and fail */
+ if (inhibit_what_is_valid(inhibit_operation) &&
+ !ignore_inhibited &&
+ manager_is_inhibited(m, inhibit_operation, INHIBIT_BLOCK, NULL, false, false, 0, &offending)) {
+ _cleanup_free_ char *comm = NULL, *u = NULL;
+
+ (void) pidref_get_comm(&offending->pid, &comm);
+ u = uid_to_name(offending->uid);
+
+ /* If this is just a recheck of the lid switch then don't warn about anything */
+ log_full(is_edge ? LOG_ERR : LOG_DEBUG,
+ "Refusing %s operation, %s is inhibited by UID "UID_FMT"/%s, PID "PID_FMT"/%s.",
+ handle_action_to_string(handle),
+ inhibit_what_to_string(inhibit_operation),
+ offending->uid, strna(u),
+ offending->pid.pid, strna(comm));
+
+ return is_edge ? -EPERM : 0;
+ }
+
+ log_info("%s", message_table[handle]);
+
+ r = bus_manager_shutdown_or_sleep_now_or_later(m, handle_action_lookup(handle), &error);
+ if (r < 0)
+ return log_error_errno(r, "Failed to execute %s operation: %s",
+ handle_action_to_string(handle),
+ bus_error_message(&error, r));
+
+ return 1;
+}
+
+static int handle_action_sleep_execute(
+ Manager *m,
+ HandleAction handle,
+ 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 (!supported && handle != HANDLE_SUSPEND) {
+ supported = sleep_supported(SLEEP_SUSPEND) > 0;
+ if (supported) {
+ log_notice("Requested %s operation is not supported, using regular suspend instead.",
+ handle_action_to_string(handle));
+ handle = HANDLE_SUSPEND;
+ }
+ }
+
+ if (!supported)
+ return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Requested %s operation not supported, ignoring.", handle_action_to_string(handle));
+
+ return handle_action_execute(m, handle, ignore_inhibited, is_edge);
+}
+
+int manager_handle_action(
+ Manager *m,
+ InhibitWhat inhibit_key,
+ HandleAction handle,
+ bool ignore_inhibited,
+ bool is_edge) {
+
+ assert(m);
+ assert(handle_action_valid(handle));
+
+ /* If the key handling is turned off, don't do anything */
+ if (handle == HANDLE_IGNORE) {
+ log_debug("Handling of %s (%s) is disabled, taking no action.",
+ inhibit_key == 0 ? "idle timeout" : inhibit_what_to_string(inhibit_key),
+ is_edge ? "edge" : "level");
+ return 0;
+ }
+
+ if (inhibit_key == INHIBIT_HANDLE_LID_SWITCH) {
+ /* If the last system suspend or startup is too close, let's not suspend for now, to give
+ * USB docking stations some time to settle so that we can properly watch its displays. */
+ if (m->lid_switch_ignore_event_source) {
+ log_debug("Ignoring lid switch request, system startup or resume too close.");
+ return 0;
+ }
+ }
+
+ /* If the key handling is inhibited, don't do anything */
+ if (inhibit_key > 0) {
+ if (manager_is_inhibited(m, inhibit_key, INHIBIT_BLOCK, NULL, true, false, 0, NULL)) {
+ log_debug("Refusing %s operation, %s is inhibited.",
+ handle_action_to_string(handle),
+ inhibit_what_to_string(inhibit_key));
+ return 0;
+ }
+ }
+
+ /* Locking is handled differently from the rest. */
+ if (handle == HANDLE_LOCK) {
+ if (!is_edge)
+ return 0;
+
+ log_info("Locking sessions...");
+ session_send_lock_all(m, true);
+ return 1;
+ }
+
+ if (HANDLE_ACTION_IS_SLEEP(handle))
+ return handle_action_sleep_execute(m, handle, ignore_inhibited, is_edge);
+
+ return handle_action_execute(m, handle, ignore_inhibited, is_edge);
+}
+
+static const char* const handle_action_verb_table[_HANDLE_ACTION_MAX] = {
+ [HANDLE_IGNORE] = "do nothing",
+ [HANDLE_POWEROFF] = "power off",
+ [HANDLE_REBOOT] = "reboot",
+ [HANDLE_HALT] = "halt",
+ [HANDLE_KEXEC] = "kexec",
+ [HANDLE_SOFT_REBOOT] = "soft-reboot",
+ [HANDLE_SUSPEND] = "suspend",
+ [HANDLE_HIBERNATE] = "hibernate",
+ [HANDLE_HYBRID_SLEEP] = "enter hybrid sleep",
+ [HANDLE_SUSPEND_THEN_HIBERNATE] = "suspend and later hibernate",
+ [HANDLE_FACTORY_RESET] = "perform a factory reset",
+ [HANDLE_LOCK] = "be locked",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(handle_action_verb, HandleAction);
+
+/* These strings are sent out by PrepareForShutdownWithMetadata signals as metadata, so the values cannot
+ * change as they are public APIs. */
+static const char* const handle_action_table[_HANDLE_ACTION_MAX] = {
+ [HANDLE_IGNORE] = "ignore",
+ [HANDLE_POWEROFF] = "poweroff",
+ [HANDLE_REBOOT] = "reboot",
+ [HANDLE_HALT] = "halt",
+ [HANDLE_KEXEC] = "kexec",
+ [HANDLE_SOFT_REBOOT] = "soft-reboot",
+ [HANDLE_SUSPEND] = "suspend",
+ [HANDLE_HIBERNATE] = "hibernate",
+ [HANDLE_HYBRID_SLEEP] = "hybrid-sleep",
+ [HANDLE_SUSPEND_THEN_HIBERNATE] = "suspend-then-hibernate",
+ [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");
diff --git a/src/login/logind-action.h b/src/login/logind-action.h
new file mode 100644
index 0000000..dbca963
--- /dev/null
+++ b/src/login/logind-action.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "conf-parser.h"
+
+typedef enum HandleAction {
+ HANDLE_IGNORE,
+ HANDLE_POWEROFF,
+ _HANDLE_ACTION_SHUTDOWN_FIRST = HANDLE_POWEROFF,
+ HANDLE_REBOOT,
+ HANDLE_HALT,
+ 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_LOCK,
+ HANDLE_FACTORY_RESET,
+ _HANDLE_ACTION_MAX,
+ _HANDLE_ACTION_INVALID = -EINVAL,
+} HandleAction;
+
+typedef struct HandleActionData HandleActionData;
+
+#include "logind-inhibit.h"
+#include "logind.h"
+#include "sleep-config.h"
+
+static inline bool handle_action_valid(HandleAction a) {
+ return a >= 0 && a < _HANDLE_ACTION_MAX;
+}
+
+static inline bool HANDLE_ACTION_IS_SHUTDOWN(HandleAction a) {
+ return a >= _HANDLE_ACTION_SHUTDOWN_FIRST && a <= _HANDLE_ACTION_SHUTDOWN_LAST;
+}
+
+static inline bool HANDLE_ACTION_IS_SLEEP(HandleAction a) {
+ return a >= _HANDLE_ACTION_SLEEP_FIRST && a <= _HANDLE_ACTION_SLEEP_LAST;
+}
+
+struct HandleActionData {
+ HandleAction handle;
+ const char *target;
+ InhibitWhat inhibit_what;
+ const char *polkit_action;
+ const char *polkit_action_multiple_sessions;
+ const char *polkit_action_ignore_inhibit;
+ SleepOperation sleep_operation;
+ const char* message_id;
+ const char* message;
+ const char* log_verb;
+};
+
+int manager_handle_action(
+ Manager *m,
+ InhibitWhat inhibit_key,
+ HandleAction handle,
+ bool ignore_inhibited,
+ bool is_edge);
+
+const char* handle_action_verb_to_string(HandleAction h) _const_;
+
+const char* handle_action_to_string(HandleAction h) _const_;
+HandleAction handle_action_from_string(const char *s) _pure_;
+
+const HandleActionData* handle_action_lookup(HandleAction handle);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_handle_action);
diff --git a/src/login/logind-brightness.c b/src/login/logind-brightness.c
new file mode 100644
index 0000000..40bcb39
--- /dev/null
+++ b/src/login/logind-brightness.c
@@ -0,0 +1,250 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bus-util.h"
+#include "device-util.h"
+#include "hash-funcs.h"
+#include "logind-brightness.h"
+#include "logind.h"
+#include "process-util.h"
+#include "stdio-util.h"
+
+/* Brightness and LED devices tend to be very slow to write to (often being I2C and such). Writes to the
+ * sysfs attributes are synchronous, and hence will freeze our process on access. We can't really have that,
+ * hence we add some complexity: whenever we need to write to the brightness attribute, we do so in a forked
+ * off process, which terminates when it is done. Watching that process allows us to watch completion of the
+ * write operation.
+ *
+ * To make this even more complex: clients are likely to send us many write requests in a short time-frame
+ * (because they implement reactive brightness sliders on screen). Let's coalesce writes to make this
+ * efficient: whenever we get requests to change brightness while we are still writing to the brightness
+ * attribute, let's remember the request and restart a new one when the initial operation finished. When we
+ * get another request while one is ongoing and one is pending we'll replace the pending one with the new
+ * one.
+ *
+ * The bus messages are answered when the first write operation finishes that started either due to the
+ * request or due to a later request that overrode the requested one.
+ *
+ * Yes, this is complex, but I don't see an easier way if we want to be both efficient and still support
+ * completion notification. */
+
+typedef struct BrightnessWriter {
+ Manager *manager;
+
+ sd_device *device;
+ char *path;
+
+ pid_t child;
+
+ uint32_t brightness;
+ bool again;
+
+ Set *current_messages;
+ Set *pending_messages;
+
+ sd_event_source* child_event_source;
+} BrightnessWriter;
+
+static BrightnessWriter* brightness_writer_free(BrightnessWriter *w) {
+ if (!w)
+ return NULL;
+
+ if (w->manager && w->path)
+ (void) hashmap_remove_value(w->manager->brightness_writers, w->path, w);
+
+ sd_device_unref(w->device);
+ free(w->path);
+
+ set_free(w->current_messages);
+ set_free(w->pending_messages);
+
+ w->child_event_source = sd_event_source_unref(w->child_event_source);
+
+ return mfree(w);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(BrightnessWriter*, brightness_writer_free);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ brightness_writer_hash_ops,
+ char,
+ string_hash_func,
+ string_compare_func,
+ BrightnessWriter,
+ brightness_writer_free);
+
+static void brightness_writer_reply(BrightnessWriter *w, int error) {
+ int r;
+
+ assert(w);
+
+ for (;;) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ m = set_steal_first(w->current_messages);
+ if (!m)
+ break;
+
+ if (error == 0)
+ r = sd_bus_reply_method_return(m, NULL);
+ else
+ r = sd_bus_reply_method_errnof(m, error, "Failed to write to brightness device: %m");
+ if (r < 0)
+ log_warning_errno(r, "Failed to send method reply, ignoring: %m");
+ }
+}
+
+static int brightness_writer_fork(BrightnessWriter *w);
+
+static int on_brightness_writer_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
+ BrightnessWriter *w = ASSERT_PTR(userdata);
+ int r;
+
+ assert(s);
+ assert(si);
+
+ assert(si->si_pid == w->child);
+ w->child = 0;
+ w->child_event_source = sd_event_source_unref(w->child_event_source);
+
+ brightness_writer_reply(w,
+ si->si_code == CLD_EXITED &&
+ si->si_status == EXIT_SUCCESS ? 0 : -EPROTO);
+
+ if (w->again) {
+ /* Another request to change the brightness has been queued. Act on it, but make the pending
+ * messages the current ones. */
+ w->again = false;
+ set_free(w->current_messages);
+ w->current_messages = TAKE_PTR(w->pending_messages);
+
+ r = brightness_writer_fork(w);
+ if (r >= 0)
+ return 0;
+
+ brightness_writer_reply(w, r);
+ }
+
+ brightness_writer_free(w);
+ return 0;
+}
+
+static int brightness_writer_fork(BrightnessWriter *w) {
+ int r;
+
+ assert(w);
+ assert(w->manager);
+ assert(w->child == 0);
+ assert(!w->child_event_source);
+
+ r = safe_fork("(sd-bright)", FORK_DEATHSIG_SIGKILL|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_REOPEN_LOG, &w->child);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ char brs[DECIMAL_STR_MAX(uint32_t)+1];
+
+ /* Child */
+ xsprintf(brs, "%" PRIu32, w->brightness);
+
+ r = sd_device_set_sysattr_value(w->device, "brightness", brs);
+ if (r < 0) {
+ log_device_error_errno(w->device, r, "Failed to write brightness to device: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ r = sd_event_add_child(w->manager->event, &w->child_event_source, w->child, WEXITED, on_brightness_writer_exit, w);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch brightness writer child " PID_FMT ": %m", w->child);
+
+ return 0;
+}
+
+static int set_add_message(Set **set, sd_bus_message *message) {
+ int r;
+
+ assert(set);
+
+ if (!message)
+ return 0;
+
+ r = sd_bus_message_get_expect_reply(message);
+ if (r <= 0)
+ return r;
+
+ r = set_ensure_put(set, &bus_message_hash_ops, message);
+ if (r <= 0)
+ return r;
+ sd_bus_message_ref(message);
+
+ return 1;
+}
+
+int manager_write_brightness(
+ Manager *m,
+ sd_device *device,
+ uint32_t brightness,
+ sd_bus_message *message) {
+
+ _cleanup_(brightness_writer_freep) BrightnessWriter *w = NULL;
+ BrightnessWriter *existing;
+ const char *path;
+ int r;
+
+ assert(m);
+ assert(device);
+
+ r = sd_device_get_syspath(device, &path);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to get sysfs path for brightness device: %m");
+
+ existing = hashmap_get(m->brightness_writers, path);
+ if (existing) {
+ /* There's already a writer for this device. Let's update it with the new brightness, and add
+ * our message to the set of message to reply when done. */
+
+ r = set_add_message(&existing->pending_messages, message);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add message to set: %m");
+
+ /* We override any previously requested brightness here: we coalesce writes, and the newest
+ * requested brightness is the one we'll put into effect. */
+ existing->brightness = brightness;
+ existing->again = true; /* request another iteration of the writer when the current one is
+ * complete */
+ return 0;
+ }
+
+ w = new(BrightnessWriter, 1);
+ if (!w)
+ return log_oom();
+
+ *w = (BrightnessWriter) {
+ .device = sd_device_ref(device),
+ .path = strdup(path),
+ .brightness = brightness,
+ };
+
+ if (!w->path)
+ return log_oom();
+
+ r = hashmap_ensure_put(&m->brightness_writers, &brightness_writer_hash_ops, w->path, w);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ return log_error_errno(r, "Failed to add brightness writer to hashmap: %m");
+
+ w->manager = m;
+
+ r = set_add_message(&w->current_messages, message);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add message to set: %m");
+
+ r = brightness_writer_fork(w);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(w);
+ return 0;
+}
diff --git a/src/login/logind-brightness.h b/src/login/logind-brightness.h
new file mode 100644
index 0000000..f1c7775
--- /dev/null
+++ b/src/login/logind-brightness.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+#include "sd-device.h"
+
+#include "logind.h"
+
+int manager_write_brightness(Manager *m, sd_device *device, uint32_t brightness, sd_bus_message *message);
diff --git a/src/login/logind-button.c b/src/login/logind-button.c
new file mode 100644
index 0000000..14835ae
--- /dev/null
+++ b/src/login/logind-button.c
@@ -0,0 +1,526 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "async.h"
+#include "fd-util.h"
+#include "logind-button.h"
+#include "logind-dbus.h"
+#include "missing_input.h"
+#include "string-util.h"
+
+#define CONST_MAX5(a, b, c, d, e) CONST_MAX(CONST_MAX(a, b), CONST_MAX(CONST_MAX(c, d), e))
+
+#define ULONG_BITS (sizeof(unsigned long)*8)
+
+#define LONG_PRESS_DURATION (5 * USEC_PER_SEC)
+
+static bool bitset_get(const unsigned long *bits, unsigned i) {
+ return (bits[i / ULONG_BITS] >> (i % ULONG_BITS)) & 1UL;
+}
+
+static void bitset_put(unsigned long *bits, unsigned i) {
+ bits[i / ULONG_BITS] |= (unsigned long) 1 << (i % ULONG_BITS);
+}
+
+Button* button_new(Manager *m, const char *name) {
+ Button *b;
+
+ assert(m);
+ assert(name);
+
+ b = new0(Button, 1);
+ if (!b)
+ return NULL;
+
+ b->name = strdup(name);
+ if (!b->name)
+ return mfree(b);
+
+ if (hashmap_put(m->buttons, b->name, b) < 0) {
+ free(b->name);
+ return mfree(b);
+ }
+
+ b->manager = m;
+ b->fd = -EBADF;
+
+ return b;
+}
+
+Button *button_free(Button *b) {
+ if (!b)
+ return NULL;
+
+ hashmap_remove(b->manager->buttons, b->name);
+
+ sd_event_source_unref(b->io_event_source);
+ sd_event_source_unref(b->check_event_source);
+
+ asynchronous_close(b->fd);
+
+ free(b->name);
+ free(b->seat);
+
+ return mfree(b);
+}
+
+int button_set_seat(Button *b, const char *sn) {
+ assert(b);
+
+ return free_and_strdup(&b->seat, sn);
+}
+
+static void button_lid_switch_handle_action(Manager *manager, bool is_edge) {
+ HandleAction handle_action;
+
+ assert(manager);
+
+ /* If we are docked or on external power, handle the lid switch
+ * differently */
+ if (manager_is_docked_or_external_displays(manager))
+ handle_action = manager->handle_lid_switch_docked;
+ else if (handle_action_valid(manager->handle_lid_switch_ep) && manager_is_on_external_power())
+ handle_action = manager->handle_lid_switch_ep;
+ else
+ handle_action = manager->handle_lid_switch;
+
+ manager_handle_action(manager, INHIBIT_HANDLE_LID_SWITCH, handle_action, manager->lid_switch_ignore_inhibited, is_edge);
+}
+
+static int button_recheck(sd_event_source *e, void *userdata) {
+ Button *b = ASSERT_PTR(userdata);
+
+ assert(b->lid_closed);
+
+ button_lid_switch_handle_action(b->manager, false);
+ return 1;
+}
+
+static int button_install_check_event_source(Button *b) {
+ int r;
+ assert(b);
+
+ /* Install a post handler, so that we keep rechecking as long as the lid is closed. */
+
+ if (b->check_event_source)
+ return 0;
+
+ r = sd_event_add_post(b->manager->event, &b->check_event_source, button_recheck, b);
+ if (r < 0)
+ return r;
+
+ return sd_event_source_set_priority(b->check_event_source, SD_EVENT_PRIORITY_IDLE+1);
+}
+
+static int long_press_of_power_key_handler(sd_event_source *e, uint64_t usec, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(e);
+
+ m->power_key_long_press_event_source = sd_event_source_unref(m->power_key_long_press_event_source);
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Power key pressed long."),
+ "MESSAGE_ID=" SD_MESSAGE_POWER_KEY_LONG_PRESS_STR);
+
+ manager_handle_action(m, INHIBIT_HANDLE_POWER_KEY, m->handle_power_key_long_press, m->power_key_ignore_inhibited, true);
+ return 0;
+}
+
+static int long_press_of_reboot_key_handler(sd_event_source *e, uint64_t usec, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(e);
+
+ m->reboot_key_long_press_event_source = sd_event_source_unref(m->reboot_key_long_press_event_source);
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Reboot key pressed long."),
+ "MESSAGE_ID=" SD_MESSAGE_REBOOT_KEY_LONG_PRESS_STR);
+
+ manager_handle_action(m, INHIBIT_HANDLE_REBOOT_KEY, m->handle_reboot_key_long_press, m->reboot_key_ignore_inhibited, true);
+ return 0;
+}
+
+static int long_press_of_suspend_key_handler(sd_event_source *e, uint64_t usec, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(e);
+
+ m->suspend_key_long_press_event_source = sd_event_source_unref(m->suspend_key_long_press_event_source);
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Suspend key pressed long."),
+ "MESSAGE_ID=" SD_MESSAGE_SUSPEND_KEY_LONG_PRESS_STR);
+
+ manager_handle_action(m, INHIBIT_HANDLE_SUSPEND_KEY, m->handle_suspend_key_long_press, m->suspend_key_ignore_inhibited, true);
+ return 0;
+}
+
+static int long_press_of_hibernate_key_handler(sd_event_source *e, uint64_t usec, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(e);
+
+ m->hibernate_key_long_press_event_source = sd_event_source_unref(m->hibernate_key_long_press_event_source);
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Hibernate key pressed long."),
+ "MESSAGE_ID=" SD_MESSAGE_HIBERNATE_KEY_LONG_PRESS_STR);
+
+ manager_handle_action(m, INHIBIT_HANDLE_HIBERNATE_KEY, m->handle_hibernate_key_long_press, m->hibernate_key_ignore_inhibited, true);
+ return 0;
+}
+
+static void start_long_press(Manager *m, sd_event_source **e, sd_event_time_handler_t callback) {
+ int r;
+
+ assert(m);
+ assert(e);
+
+ if (*e)
+ return;
+
+ r = sd_event_add_time_relative(
+ m->event,
+ e,
+ CLOCK_MONOTONIC,
+ LONG_PRESS_DURATION, 0,
+ callback, m);
+ if (r < 0)
+ log_warning_errno(r, "Failed to add long press timer event, ignoring: %m");
+}
+
+static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Button *b = ASSERT_PTR(userdata);
+ struct input_event ev;
+ ssize_t l;
+
+ assert(s);
+ assert(fd == b->fd);
+
+ l = read(b->fd, &ev, sizeof(ev));
+ if (l < 0)
+ return errno != EAGAIN ? -errno : 0;
+ if ((size_t) l < sizeof(ev))
+ return -EIO;
+
+ if (ev.type == EV_KEY && ev.value > 0) {
+
+ switch (ev.code) {
+
+ case KEY_POWER:
+ case KEY_POWER2:
+ if (b->manager->handle_power_key_long_press != HANDLE_IGNORE && b->manager->handle_power_key_long_press != b->manager->handle_power_key) {
+ log_debug("Power key pressed. Further action depends on the key press duration.");
+ start_long_press(b->manager, &b->manager->power_key_long_press_event_source, long_press_of_power_key_handler);
+ } else {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Power key pressed short."),
+ "MESSAGE_ID=" SD_MESSAGE_POWER_KEY_STR);
+ manager_handle_action(b->manager, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, true);
+ }
+ break;
+
+ /* The kernel naming is a bit confusing here:
+ KEY_RESTART was probably introduced for media playback purposes, but
+ is now being predominantly used to indicate device reboot.
+ */
+
+ case KEY_RESTART:
+ if (b->manager->handle_reboot_key_long_press != HANDLE_IGNORE && b->manager->handle_reboot_key_long_press != b->manager->handle_reboot_key) {
+ log_debug("Reboot key pressed. Further action depends on the key press duration.");
+ start_long_press(b->manager, &b->manager->reboot_key_long_press_event_source, long_press_of_reboot_key_handler);
+ } else {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Reboot key pressed short."),
+ "MESSAGE_ID=" SD_MESSAGE_REBOOT_KEY_STR);
+ manager_handle_action(b->manager, INHIBIT_HANDLE_REBOOT_KEY, b->manager->handle_reboot_key, b->manager->reboot_key_ignore_inhibited, true);
+ }
+ break;
+
+ /* The kernel naming is a bit confusing here:
+
+ KEY_SLEEP = suspend-to-ram, which everybody else calls "suspend"
+ KEY_SUSPEND = suspend-to-disk, which everybody else calls "hibernate"
+ */
+
+ case KEY_SLEEP:
+ if (b->manager->handle_suspend_key_long_press != HANDLE_IGNORE && b->manager->handle_suspend_key_long_press != b->manager->handle_suspend_key) {
+ log_debug("Suspend key pressed. Further action depends on the key press duration.");
+ start_long_press(b->manager, &b->manager->suspend_key_long_press_event_source, long_press_of_suspend_key_handler);
+ } else {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Suspend key pressed short."),
+ "MESSAGE_ID=" SD_MESSAGE_SUSPEND_KEY_STR);
+ manager_handle_action(b->manager, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, true);
+ }
+ break;
+
+ case KEY_SUSPEND:
+ if (b->manager->handle_hibernate_key_long_press != HANDLE_IGNORE && b->manager->handle_hibernate_key_long_press != b->manager->handle_hibernate_key) {
+ log_debug("Hibernate key pressed. Further action depends on the key press duration.");
+ start_long_press(b->manager, &b->manager->hibernate_key_long_press_event_source, long_press_of_hibernate_key_handler);
+ } else {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Hibernate key pressed short."),
+ "MESSAGE_ID=" SD_MESSAGE_HIBERNATE_KEY_STR);
+ manager_handle_action(b->manager, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, true);
+ }
+ break;
+ }
+
+ } else if (ev.type == EV_KEY && ev.value == 0) {
+
+ switch (ev.code) {
+
+ case KEY_POWER:
+ case KEY_POWER2:
+ if (b->manager->power_key_long_press_event_source) {
+ /* Long press event timer is still pending and key release
+ event happened. This means that key press duration was
+ insufficient to trigger a long press event
+ */
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Power key pressed short."),
+ "MESSAGE_ID=" SD_MESSAGE_POWER_KEY_STR);
+
+ b->manager->power_key_long_press_event_source = sd_event_source_unref(b->manager->power_key_long_press_event_source);
+
+ manager_handle_action(b->manager, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, true);
+ }
+ break;
+
+ case KEY_RESTART:
+ if (b->manager->reboot_key_long_press_event_source) {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Reboot key pressed short."),
+ "MESSAGE_ID=" SD_MESSAGE_REBOOT_KEY_STR);
+
+ b->manager->reboot_key_long_press_event_source = sd_event_source_unref(b->manager->reboot_key_long_press_event_source);
+
+ manager_handle_action(b->manager, INHIBIT_HANDLE_REBOOT_KEY, b->manager->handle_reboot_key, b->manager->reboot_key_ignore_inhibited, true);
+ }
+ break;
+
+ case KEY_SLEEP:
+ if (b->manager->suspend_key_long_press_event_source) {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Suspend key pressed short."),
+ "MESSAGE_ID=" SD_MESSAGE_SUSPEND_KEY_STR);
+
+ b->manager->suspend_key_long_press_event_source = sd_event_source_unref(b->manager->suspend_key_long_press_event_source);
+
+ manager_handle_action(b->manager, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, true);
+ }
+ break;
+ case KEY_SUSPEND:
+ if (b->manager->hibernate_key_long_press_event_source) {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Hibernate key pressed short."),
+ "MESSAGE_ID=" SD_MESSAGE_HIBERNATE_KEY_STR);
+
+ b->manager->hibernate_key_long_press_event_source = sd_event_source_unref(b->manager->hibernate_key_long_press_event_source);
+
+ manager_handle_action(b->manager, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, true);
+ }
+ break;
+ }
+
+ } else if (ev.type == EV_SW && ev.value > 0) {
+
+ if (ev.code == SW_LID) {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Lid closed."),
+ "MESSAGE_ID=" SD_MESSAGE_LID_CLOSED_STR);
+
+ b->lid_closed = true;
+ button_lid_switch_handle_action(b->manager, true);
+ button_install_check_event_source(b);
+ manager_send_changed(b->manager, "LidClosed", NULL);
+
+ } else if (ev.code == SW_DOCK) {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("System docked."),
+ "MESSAGE_ID=" SD_MESSAGE_SYSTEM_DOCKED_STR);
+
+ b->docked = true;
+ }
+
+ } else if (ev.type == EV_SW && ev.value == 0) {
+
+ if (ev.code == SW_LID) {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Lid opened."),
+ "MESSAGE_ID=" SD_MESSAGE_LID_OPENED_STR);
+
+ b->lid_closed = false;
+ b->check_event_source = sd_event_source_unref(b->check_event_source);
+ manager_send_changed(b->manager, "LidClosed", NULL);
+
+ } else if (ev.code == SW_DOCK) {
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("System undocked."),
+ "MESSAGE_ID=" SD_MESSAGE_SYSTEM_UNDOCKED_STR);
+
+ b->docked = false;
+ }
+ }
+
+ return 0;
+}
+
+static int button_suitable(int fd) {
+ unsigned long types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1];
+
+ assert(fd >= 0);
+
+ if (ioctl(fd, EVIOCGBIT(EV_SYN, sizeof types), types) < 0)
+ return -errno;
+
+ if (bitset_get(types, EV_KEY)) {
+ unsigned long keys[CONST_MAX5(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND, KEY_RESTART)/ULONG_BITS+1];
+
+ if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof keys), keys) < 0)
+ return -errno;
+
+ if (bitset_get(keys, KEY_POWER) ||
+ bitset_get(keys, KEY_POWER2) ||
+ bitset_get(keys, KEY_SLEEP) ||
+ bitset_get(keys, KEY_SUSPEND) ||
+ bitset_get(keys, KEY_RESTART))
+ return true;
+ }
+
+ if (bitset_get(types, EV_SW)) {
+ unsigned long switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1];
+
+ if (ioctl(fd, EVIOCGBIT(EV_SW, sizeof switches), switches) < 0)
+ return -errno;
+
+ if (bitset_get(switches, SW_LID) ||
+ bitset_get(switches, SW_DOCK))
+ return true;
+ }
+
+ return false;
+}
+
+static int button_set_mask(const char *name, int fd) {
+ unsigned long
+ types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1] = {},
+ keys[CONST_MAX5(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND, KEY_RESTART)/ULONG_BITS+1] = {},
+ switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {};
+ struct input_mask mask;
+
+ assert(name);
+ assert(fd >= 0);
+
+ bitset_put(types, EV_KEY);
+ bitset_put(types, EV_SW);
+
+ mask = (struct input_mask) {
+ .type = EV_SYN,
+ .codes_size = sizeof(types),
+ .codes_ptr = PTR_TO_UINT64(types),
+ };
+
+ if (ioctl(fd, EVIOCSMASK, &mask) < 0)
+ /* Log only at debug level if the kernel doesn't do EVIOCSMASK yet */
+ return log_full_errno(IN_SET(errno, ENOTTY, EOPNOTSUPP, EINVAL) ? LOG_DEBUG : LOG_WARNING,
+ errno, "Failed to set EV_SYN event mask on /dev/input/%s: %m", name);
+
+ bitset_put(keys, KEY_POWER);
+ bitset_put(keys, KEY_POWER2);
+ bitset_put(keys, KEY_SLEEP);
+ bitset_put(keys, KEY_SUSPEND);
+ bitset_put(keys, KEY_RESTART);
+
+ mask = (struct input_mask) {
+ .type = EV_KEY,
+ .codes_size = sizeof(keys),
+ .codes_ptr = PTR_TO_UINT64(keys),
+ };
+
+ if (ioctl(fd, EVIOCSMASK, &mask) < 0)
+ return log_warning_errno(errno, "Failed to set EV_KEY event mask on /dev/input/%s: %m", name);
+
+ bitset_put(switches, SW_LID);
+ bitset_put(switches, SW_DOCK);
+
+ mask = (struct input_mask) {
+ .type = EV_SW,
+ .codes_size = sizeof(switches),
+ .codes_ptr = PTR_TO_UINT64(switches),
+ };
+
+ if (ioctl(fd, EVIOCSMASK, &mask) < 0)
+ return log_warning_errno(errno, "Failed to set EV_SW event mask on /dev/input/%s: %m", name);
+
+ return 0;
+}
+
+int button_open(Button *b) {
+ _cleanup_(asynchronous_closep) int fd = -EBADF;
+ const char *p;
+ char name[256];
+ int r;
+
+ assert(b);
+
+ b->fd = asynchronous_close(b->fd);
+
+ p = strjoina("/dev/input/", b->name);
+
+ fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (fd < 0)
+ return log_warning_errno(errno, "Failed to open %s: %m", p);
+
+ r = button_suitable(fd);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to determine whether input device %s is relevant to us: %m", p);
+ if (r == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL),
+ "Device %s does not expose keys or switches relevant to us, ignoring.", p);
+
+ if (ioctl(fd, EVIOCGNAME(sizeof name), name) < 0)
+ return log_error_errno(errno, "Failed to get input name for %s: %m", p);
+
+ (void) button_set_mask(b->name, fd);
+
+ b->io_event_source = sd_event_source_unref(b->io_event_source);
+ r = sd_event_add_io(b->manager->event, &b->io_event_source, fd, EPOLLIN, button_dispatch, b);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add button event for %s: %m", p);
+
+ b->fd = TAKE_FD(fd);
+ log_info("Watching system buttons on %s (%s)", p, name);
+ return 0;
+}
+
+int button_check_switches(Button *b) {
+ unsigned long switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {};
+ assert(b);
+
+ if (b->fd < 0)
+ return -EINVAL;
+
+ if (ioctl(b->fd, EVIOCGSW(sizeof(switches)), switches) < 0)
+ return -errno;
+
+ b->lid_closed = bitset_get(switches, SW_LID);
+ b->docked = bitset_get(switches, SW_DOCK);
+ manager_send_changed(b->manager, "LidClosed", NULL);
+
+ if (b->lid_closed)
+ button_install_check_event_source(b);
+
+ return 0;
+}
diff --git a/src/login/logind-button.h b/src/login/logind-button.h
new file mode 100644
index 0000000..6c39471
--- /dev/null
+++ b/src/login/logind-button.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct Button Button;
+
+#include "logind.h"
+
+struct Button {
+ Manager *manager;
+
+ sd_event_source *io_event_source;
+ sd_event_source *check_event_source;
+
+ char *name;
+ char *seat;
+ int fd;
+
+ bool lid_closed;
+ bool docked;
+};
+
+Button* button_new(Manager *m, const char *name);
+Button *button_free(Button *b);
+int button_open(Button *b);
+int button_set_seat(Button *b, const char *sn);
+int button_check_switches(Button *b);
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
new file mode 100644
index 0000000..f15008e
--- /dev/null
+++ b/src/login/logind-core.c
@@ -0,0 +1,850 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <linux/vt.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "battery-util.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-util.h"
+#include "cgroup-util.h"
+#include "conf-parser.h"
+#include "device-util.h"
+#include "efi-loader.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "limits-util.h"
+#include "logind.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "stdio-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "udev-util.h"
+#include "user-util.h"
+#include "userdb.h"
+#include "utmp-wtmp.h"
+
+void manager_reset_config(Manager *m) {
+ assert(m);
+
+ m->n_autovts = 6;
+ m->reserve_vt = 6;
+ m->remove_ipc = true;
+ m->inhibit_delay_max = 5 * USEC_PER_SEC;
+ m->user_stop_delay = 10 * USEC_PER_SEC;
+
+ m->handle_power_key = HANDLE_POWEROFF;
+ m->handle_power_key_long_press = HANDLE_IGNORE;
+ m->handle_reboot_key = HANDLE_REBOOT;
+ m->handle_reboot_key_long_press = HANDLE_POWEROFF;
+ m->handle_suspend_key = HANDLE_SUSPEND;
+ m->handle_suspend_key_long_press = HANDLE_HIBERNATE;
+ m->handle_hibernate_key = HANDLE_HIBERNATE;
+ m->handle_hibernate_key_long_press = HANDLE_IGNORE;
+
+ m->handle_lid_switch = HANDLE_SUSPEND;
+ m->handle_lid_switch_ep = _HANDLE_ACTION_INVALID;
+ m->handle_lid_switch_docked = HANDLE_IGNORE;
+
+ m->power_key_ignore_inhibited = false;
+ m->suspend_key_ignore_inhibited = false;
+ m->hibernate_key_ignore_inhibited = false;
+ m->lid_switch_ignore_inhibited = true;
+ m->reboot_key_ignore_inhibited = false;
+
+ m->holdoff_timeout_usec = 30 * USEC_PER_SEC;
+
+ m->idle_action_usec = 30 * USEC_PER_MINUTE;
+ m->idle_action = HANDLE_IGNORE;
+
+ m->runtime_dir_size = physical_memory_scale(10U, 100U); /* 10% */
+ m->runtime_dir_inodes = DIV_ROUND_UP(m->runtime_dir_size, 4096); /* 4k per inode */
+ m->sessions_max = 8192;
+ m->inhibitors_max = 8192;
+
+ m->kill_user_processes = KILL_USER_PROCESSES;
+
+ m->kill_only_users = strv_free(m->kill_only_users);
+ m->kill_exclude_users = strv_free(m->kill_exclude_users);
+
+ m->stop_idle_session_usec = USEC_INFINITY;
+}
+
+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);
+}
+
+int manager_add_device(Manager *m, const char *sysfs, bool master, Device **ret_device) {
+ Device *d;
+
+ assert(m);
+ assert(sysfs);
+
+ d = hashmap_get(m->devices, sysfs);
+ if (d)
+ /* we support adding master-flags, but not removing them */
+ d->master = d->master || master;
+ else {
+ d = device_new(m, sysfs, master);
+ if (!d)
+ return -ENOMEM;
+ }
+
+ if (ret_device)
+ *ret_device = d;
+
+ return 0;
+}
+
+int manager_add_seat(Manager *m, const char *id, Seat **ret_seat) {
+ Seat *s;
+ int r;
+
+ assert(m);
+ assert(id);
+
+ s = hashmap_get(m->seats, id);
+ if (!s) {
+ r = seat_new(&s, m, id);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret_seat)
+ *ret_seat = s;
+
+ return 0;
+}
+
+int manager_add_session(Manager *m, const char *id, Session **ret_session) {
+ Session *s;
+ int r;
+
+ assert(m);
+ assert(id);
+
+ s = hashmap_get(m->sessions, id);
+ if (!s) {
+ r = session_new(&s, m, id);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret_session)
+ *ret_session = s;
+
+ return 0;
+}
+
+int manager_add_user(
+ Manager *m,
+ UserRecord *ur,
+ User **ret_user) {
+
+ User *u;
+ int r;
+
+ assert(m);
+ assert(ur);
+
+ u = hashmap_get(m->users, UID_TO_PTR(ur->uid));
+ if (!u) {
+ r = user_new(&u, m, ur);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret_user)
+ *ret_user = u;
+
+ return 0;
+}
+
+int manager_add_user_by_name(
+ Manager *m,
+ const char *name,
+ User **ret_user) {
+
+ _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+ int r;
+
+ assert(m);
+ assert(name);
+
+ r = userdb_by_name(name, USERDB_SUPPRESS_SHADOW, &ur);
+ if (r < 0)
+ return r;
+
+ return manager_add_user(m, ur, ret_user);
+}
+
+int manager_add_user_by_uid(
+ Manager *m,
+ uid_t uid,
+ User **ret_user) {
+
+ _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+ int r;
+
+ assert(m);
+ assert(uid_is_valid(uid));
+
+ r = userdb_by_uid(uid, USERDB_SUPPRESS_SHADOW, &ur);
+ if (r < 0)
+ return r;
+
+ return manager_add_user(m, ur, ret_user);
+}
+
+int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **ret) {
+ Inhibitor *i;
+ int r;
+
+ assert(m);
+ assert(id);
+
+ i = hashmap_get(m->inhibitors, id);
+ if (!i) {
+ r = inhibitor_new(&i, m, id);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret)
+ *ret = i;
+
+ return 0;
+}
+
+int manager_add_button(Manager *m, const char *name, Button **ret_button) {
+ Button *b;
+
+ assert(m);
+ assert(name);
+
+ b = hashmap_get(m->buttons, name);
+ if (!b) {
+ b = button_new(m, name);
+ if (!b)
+ return -ENOMEM;
+ }
+
+ if (ret_button)
+ *ret_button = b;
+
+ return 0;
+}
+
+int manager_process_seat_device(Manager *m, sd_device *d) {
+ Device *device;
+ int r;
+
+ assert(m);
+
+ if (device_for_action(d, SD_DEVICE_REMOVE) ||
+ sd_device_has_current_tag(d, "seat") <= 0) {
+ const char *syspath;
+
+ r = sd_device_get_syspath(d, &syspath);
+ if (r < 0)
+ return 0;
+
+ device = hashmap_get(m->devices, syspath);
+ if (!device)
+ return 0;
+
+ seat_add_to_gc_queue(device->seat);
+ device_free(device);
+
+ } else {
+ const char *sn, *syspath;
+ bool master;
+ Seat *seat;
+
+ if (sd_device_get_property_value(d, "ID_SEAT", &sn) < 0 || isempty(sn))
+ sn = "seat0";
+
+ if (!seat_name_is_valid(sn)) {
+ log_device_warning(d, "Device with invalid seat name %s found, ignoring.", sn);
+ return 0;
+ }
+
+ seat = hashmap_get(m->seats, sn);
+ master = sd_device_has_current_tag(d, "master-of-seat") > 0;
+
+ /* Ignore non-master devices for unknown seats */
+ if (!master && !seat)
+ return 0;
+
+ r = sd_device_get_syspath(d, &syspath);
+ if (r < 0)
+ return r;
+
+ r = manager_add_device(m, syspath, master, &device);
+ if (r < 0)
+ return r;
+
+ if (!seat) {
+ r = manager_add_seat(m, sn, &seat);
+ if (r < 0) {
+ if (!device->seat)
+ device_free(device);
+
+ return r;
+ }
+ }
+
+ device_attach(device, seat);
+ seat_start(seat);
+ }
+
+ return 0;
+}
+
+int manager_process_button_device(Manager *m, sd_device *d) {
+ const char *sysname;
+ Button *b;
+ int r;
+
+ assert(m);
+
+ r = sd_device_get_sysname(d, &sysname);
+ if (r < 0)
+ return r;
+
+ if (device_for_action(d, SD_DEVICE_REMOVE) ||
+ sd_device_has_current_tag(d, "power-switch") <= 0)
+
+ button_free(hashmap_get(m->buttons, sysname));
+
+ else {
+ const char *sn;
+
+ r = manager_add_button(m, sysname, &b);
+ if (r < 0)
+ return r;
+
+ if (sd_device_get_property_value(d, "ID_SEAT", &sn) < 0 || isempty(sn))
+ sn = "seat0";
+
+ button_set_seat(b, sn);
+
+ r = button_open(b);
+ if (r < 0) /* event device doesn't have any keys or switches relevant to us? (or any other error
+ * opening the device?) let's close the button again. */
+ button_free(b);
+ }
+
+ return 0;
+}
+
+int manager_get_session_by_pidref(Manager *m, const PidRef *pid, Session **ret) {
+ _cleanup_free_ char *unit = NULL;
+ Session *s;
+ int r;
+
+ assert(m);
+
+ if (!pidref_is_set(pid))
+ return -EINVAL;
+
+ s = hashmap_get(m->sessions_by_leader, pid);
+ if (s) {
+ r = pidref_verify(pid);
+ if (r < 0)
+ return r;
+ } else {
+ r = cg_pidref_get_unit(pid, &unit);
+ if (r < 0)
+ return r;
+
+ s = hashmap_get(m->session_units, unit);
+ }
+
+ if (ret)
+ *ret = s;
+
+ return !!s;
+}
+
+int manager_get_user_by_pid(Manager *m, pid_t pid, User **ret) {
+ _cleanup_free_ char *unit = NULL;
+ User *u = NULL;
+ int r;
+
+ assert(m);
+
+ if (!pid_is_valid(pid))
+ return -EINVAL;
+
+ r = cg_pid_get_slice(pid, &unit);
+ if (r >= 0)
+ u = hashmap_get(m->user_units, unit);
+
+ if (ret)
+ *ret = u;
+
+ return !!u;
+}
+
+int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
+ Session *s;
+ bool idle_hint;
+ dual_timestamp ts = DUAL_TIMESTAMP_NULL;
+
+ assert(m);
+
+ idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t, false, false, 0, NULL);
+
+ HASHMAP_FOREACH(s, m->sessions) {
+ dual_timestamp k;
+ int ih;
+
+ ih = session_get_idle_hint(s, &k);
+ if (ih < 0)
+ return ih;
+
+ if (!ih) {
+ if (!idle_hint) {
+ if (k.monotonic < ts.monotonic)
+ ts = k;
+ } else {
+ idle_hint = false;
+ ts = k;
+ }
+ } else if (idle_hint) {
+
+ if (k.monotonic > ts.monotonic)
+ ts = k;
+ }
+ }
+
+ if (t)
+ *t = ts;
+
+ return idle_hint;
+}
+
+bool manager_shall_kill(Manager *m, const char *user) {
+ assert(m);
+ assert(user);
+
+ if (!m->kill_exclude_users && streq(user, "root"))
+ return false;
+
+ if (strv_contains(m->kill_exclude_users, user))
+ return false;
+
+ if (!strv_isempty(m->kill_only_users))
+ return strv_contains(m->kill_only_users, user);
+
+ return m->kill_user_processes;
+}
+
+int config_parse_n_autovts(
+ 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) {
+
+ unsigned *n = ASSERT_PTR(data);
+ unsigned o;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = safe_atou(rvalue, &o);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse number of autovts, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (o > 15) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "A maximum of 15 autovts are supported, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *n = o;
+ return 0;
+}
+
+static int vt_is_busy(unsigned vtnr) {
+ struct vt_stat vt_stat;
+ int r;
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(vtnr >= 1);
+
+ /* VT_GETSTATE "cannot return state for more than 16 VTs, since v_state is short" */
+ assert(vtnr <= 15);
+
+ /* We explicitly open /dev/tty1 here instead of /dev/tty0. If
+ * we'd open the latter we'd open the foreground tty which
+ * hence would be unconditionally busy. By opening /dev/tty1
+ * we avoid this. Since tty1 is special and needs to be an
+ * explicitly loaded getty or DM this is safe. */
+
+ fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0)
+ r = -errno;
+ else
+ r = !!(vt_stat.v_state & (1 << vtnr));
+
+ return r;
+}
+
+int manager_spawn_autovt(Manager *m, unsigned vtnr) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ char name[sizeof("autovt@tty.service") + DECIMAL_STR_MAX(unsigned)];
+ int r;
+
+ assert(m);
+ assert(vtnr >= 1);
+
+ if (vtnr > m->n_autovts &&
+ vtnr != m->reserve_vt)
+ return 0;
+
+ if (vtnr != m->reserve_vt) {
+ /* If this is the reserved TTY, we'll start the getty
+ * on it in any case, but otherwise only if it is not
+ * busy. */
+
+ r = vt_is_busy(vtnr);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ return -EBUSY;
+ }
+
+ xsprintf(name, "autovt@tty%u.service", vtnr);
+ r = bus_call_method(m->bus, bus_systemd_mgr, "StartUnit", &error, NULL, "ss", name, "fail");
+ if (r < 0)
+ return log_error_errno(r, "Failed to start %s: %s", name, bus_error_message(&error, r));
+
+ return 0;
+}
+
+bool manager_is_lid_closed(Manager *m) {
+ Button *b;
+
+ HASHMAP_FOREACH(b, m->buttons)
+ if (b->lid_closed)
+ return true;
+
+ return false;
+}
+
+static bool manager_is_docked(Manager *m) {
+ Button *b;
+
+ HASHMAP_FOREACH(b, m->buttons)
+ if (b->docked)
+ return true;
+
+ return false;
+}
+
+static int manager_count_external_displays(Manager *m) {
+ _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+ int r, n = 0;
+
+ r = sd_device_enumerator_new(&e);
+ if (r < 0)
+ return r;
+
+ r = sd_device_enumerator_allow_uninitialized(e);
+ if (r < 0)
+ return r;
+
+ r = sd_device_enumerator_add_match_subsystem(e, "drm", true);
+ if (r < 0)
+ return r;
+
+ FOREACH_DEVICE(e, d) {
+ const char *status, *enabled, *dash, *nn, *subsys;
+ sd_device *p;
+
+ if (sd_device_get_parent(d, &p) < 0)
+ continue;
+
+ /* 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"))
+ continue;
+
+ if (sd_device_get_sysname(d, &nn) < 0)
+ continue;
+
+ /* Ignore internal displays: the type is encoded in the sysfs name, as the second dash
+ * separated item (the first is the card name, the last the connector number). We implement a
+ * deny list of external displays here, rather than an allow list of internal ones, to ensure
+ * we don't block suspends too eagerly. */
+ dash = strchr(nn, '-');
+ if (!dash)
+ continue;
+
+ dash++;
+ if (!STARTSWITH_SET(dash,
+ "VGA-", "DVI-I-", "DVI-D-", "DVI-A-"
+ "Composite-", "SVIDEO-", "Component-",
+ "DIN-", "DP-", "HDMI-A-", "HDMI-B-", "TV-"))
+ continue;
+
+ /* Ignore ports that are not enabled */
+ if (sd_device_get_sysattr_value(d, "enabled", &enabled) < 0 || !streq(enabled, "enabled"))
+ continue;
+
+ /* We count any connector which is not explicitly
+ * "disconnected" as connected. */
+ if (sd_device_get_sysattr_value(d, "status", &status) < 0 || !streq(status, "disconnected"))
+ n++;
+ }
+
+ return n;
+}
+
+bool manager_is_docked_or_external_displays(Manager *m) {
+ int n;
+
+ /* If we are docked don't react to lid closing */
+ if (manager_is_docked(m)) {
+ log_debug("System is docked.");
+ return true;
+ }
+
+ /* If we have more than one display connected,
+ * assume that we are docked. */
+ n = manager_count_external_displays(m);
+ if (n < 0)
+ log_warning_errno(n, "Display counting failed: %m");
+ else if (n >= 1) {
+ log_debug("External (%i) displays connected.", n);
+ return true;
+ }
+
+ return false;
+}
+
+bool manager_is_on_external_power(void) {
+ int r;
+
+ /* For now we only check for AC power, but 'external power' can apply to anything that isn't an internal
+ * battery */
+ r = on_ac_power();
+ if (r < 0)
+ log_warning_errno(r, "Failed to read AC power status: %m");
+
+ return r != 0; /* Treat failure as 'on AC' */
+}
+
+bool manager_all_buttons_ignored(Manager *m) {
+ assert(m);
+
+ if (m->handle_power_key != HANDLE_IGNORE)
+ return false;
+ if (m->handle_power_key_long_press != HANDLE_IGNORE)
+ return false;
+ if (m->handle_suspend_key != HANDLE_IGNORE)
+ return false;
+ if (m->handle_suspend_key_long_press != HANDLE_IGNORE)
+ return false;
+ if (m->handle_hibernate_key != HANDLE_IGNORE)
+ return false;
+ if (m->handle_hibernate_key_long_press != HANDLE_IGNORE)
+ return false;
+ if (m->handle_reboot_key != HANDLE_IGNORE)
+ return false;
+ if (m->handle_reboot_key_long_press != HANDLE_IGNORE)
+ return false;
+ if (m->handle_lid_switch != HANDLE_IGNORE)
+ return false;
+ if (!IN_SET(m->handle_lid_switch_ep, _HANDLE_ACTION_INVALID, HANDLE_IGNORE))
+ return false;
+ if (m->handle_lid_switch_docked != HANDLE_IGNORE)
+ return false;
+
+ return true;
+}
+
+int manager_read_utmp(Manager *m) {
+#if ENABLE_UTMP
+ int r;
+ _unused_ _cleanup_(utxent_cleanup) bool utmpx = false;
+
+ assert(m);
+
+ if (utmpxname(_PATH_UTMPX) < 0)
+ return log_error_errno(errno, "Failed to set utmp path to " _PATH_UTMPX ": %m");
+
+ utmpx = utxent_start();
+
+ for (;;) {
+ _cleanup_free_ char *t = NULL;
+ struct utmpx *u;
+ const char *c;
+ Session *s;
+
+ errno = 0;
+ u = getutxent();
+ if (!u) {
+ if (errno == ENOENT)
+ log_debug_errno(errno, _PATH_UTMPX " does not exist, ignoring.");
+ else if (errno != 0)
+ log_warning_errno(errno, "Failed to read " _PATH_UTMPX ", ignoring: %m");
+ return 0;
+ }
+
+ if (u->ut_type != USER_PROCESS)
+ continue;
+
+ if (!pid_is_valid(u->ut_pid))
+ continue;
+
+ t = strndup(u->ut_line, sizeof(u->ut_line));
+ if (!t)
+ return log_oom();
+
+ c = path_startswith(t, "/dev/");
+ if (c) {
+ r = free_and_strdup(&t, c);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (isempty(t))
+ continue;
+
+ if (manager_get_session_by_pidref(m, &PIDREF_MAKE_FROM_PID(u->ut_pid), &s) <= 0)
+ continue;
+
+ if (s->tty_validity == TTY_FROM_UTMP && !streq_ptr(s->tty, t)) {
+ /* This may happen on multiplexed SSH connection (i.e. 'SSH connection sharing'). In
+ * this case PAM and utmp sessions don't match. In such a case let's invalidate the TTY
+ * information and never acquire it again. */
+
+ s->tty = mfree(s->tty);
+ s->tty_validity = TTY_UTMP_INCONSISTENT;
+ log_debug("Session '%s' has inconsistent TTY information, dropping TTY information.", s->id);
+ continue;
+ }
+
+ /* Never override what we figured out once */
+ if (s->tty || s->tty_validity >= 0)
+ continue;
+
+ s->tty = TAKE_PTR(t);
+ s->tty_validity = TTY_FROM_UTMP;
+ log_debug("Acquired TTY information '%s' from utmp for session '%s'.", s->tty, s->id);
+ }
+
+#else
+ return 0;
+#endif
+}
+
+#if ENABLE_UTMP
+static int manager_dispatch_utmp(sd_event_source *s, const struct inotify_event *event, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ /* If there's indication the file itself might have been removed or became otherwise unavailable, then let's
+ * reestablish the watch on whatever there's now. */
+ if ((event->mask & (IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF|IN_Q_OVERFLOW|IN_UNMOUNT)) != 0)
+ manager_connect_utmp(m);
+
+ (void) manager_read_utmp(m);
+ return 0;
+}
+#endif
+
+void manager_connect_utmp(Manager *m) {
+#if ENABLE_UTMP
+ sd_event_source *s = NULL;
+ int r;
+
+ assert(m);
+
+ /* Watch utmp for changes via inotify. We do this to deal with tools such as ssh, which will register the PAM
+ * session early, and acquire a TTY only much later for the connection. Thus during PAM the TTY won't be known
+ * yet. ssh will register itself with utmp when it finally acquired the TTY. Hence, let's make use of this, and
+ * watch utmp for the TTY asynchronously. We use the PAM session's leader PID as key, to find the right entry.
+ *
+ * Yes, relying on utmp is pretty ugly, but it's good enough for informational purposes, as well as idle
+ * detection (which, for tty sessions, relies on the TTY used) */
+
+ r = sd_event_add_inotify(m->event, &s, _PATH_UTMPX, IN_MODIFY|IN_MOVE_SELF|IN_DELETE_SELF|IN_ATTRIB, manager_dispatch_utmp, m);
+ if (r < 0)
+ log_full_errno(r == -ENOENT ? LOG_DEBUG: LOG_WARNING, r, "Failed to create inotify watch on " _PATH_UTMPX ", ignoring: %m");
+ else {
+ r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
+ if (r < 0)
+ log_warning_errno(r, "Failed to adjust utmp event source priority, ignoring: %m");
+
+ (void) sd_event_source_set_description(s, "utmp");
+ }
+
+ sd_event_source_unref(m->utmp_event_source);
+ m->utmp_event_source = s;
+#endif
+}
+
+void manager_reconnect_utmp(Manager *m) {
+#if ENABLE_UTMP
+ assert(m);
+
+ if (m->utmp_event_source)
+ return;
+
+ manager_connect_utmp(m);
+#endif
+}
+
+int manager_read_efi_boot_loader_entries(Manager *m) {
+#if ENABLE_EFI
+ int r;
+
+ assert(m);
+ if (m->efi_boot_loader_entries_set)
+ return 0;
+
+ r = efi_loader_get_entries(&m->efi_boot_loader_entries);
+ if (r < 0) {
+ if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r)) {
+ log_debug_errno(r, "Boot loader reported no entries.");
+ m->efi_boot_loader_entries_set = true;
+ return 0;
+ }
+ return log_error_errno(r, "Failed to determine entries reported by boot loader: %m");
+ }
+
+ m->efi_boot_loader_entries_set = true;
+ return 1;
+#else
+ return 0;
+#endif
+}
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
new file mode 100644
index 0000000..ec1f2f3
--- /dev/null
+++ b/src/login/logind-dbus.c
@@ -0,0 +1,4406 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "sd-device.h"
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "audit-util.h"
+#include "bootspec.h"
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-get-properties.h"
+#include "bus-locator.h"
+#include "bus-polkit.h"
+#include "bus-unit-util.h"
+#include "bus-util.h"
+#include "cgroup-util.h"
+#include "device-util.h"
+#include "dirent-util.h"
+#include "efi-api.h"
+#include "efi-loader.h"
+#include "efivars.h"
+#include "env-file.h"
+#include "env-util.h"
+#include "escape.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "fileio-label.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "logind-action.h"
+#include "logind-dbus.h"
+#include "logind-polkit.h"
+#include "logind-seat-dbus.h"
+#include "logind-session-dbus.h"
+#include "logind-user-dbus.h"
+#include "logind.h"
+#include "missing_capability.h"
+#include "mkdir-label.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "reboot-util.h"
+#include "selinux-util.h"
+#include "sleep-config.h"
+#include "special.h"
+#include "serialize.h"
+#include "stdio-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "tmpfile-util.h"
+#include "unit-name.h"
+#include "user-util.h"
+#include "utmp-wtmp.h"
+#include "virt.h"
+#include "wall.h"
+
+/* As a random fun fact sysvinit had a 252 (256-(strlen(" \r\n")+1))
+ * character limit for the wall message.
+ * https://git.savannah.nongnu.org/cgit/sysvinit.git/tree/src/shutdown.c#n72
+ * There is no real technical need for that but doesn't make sense
+ * to store arbitrary amounts either. As we are not stingy here, we
+ * allow 4k.
+ */
+#define WALL_MESSAGE_MAX 4096U
+
+#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,
+ sd_bus_message *message,
+ bool consult_display,
+ sd_bus_error *error,
+ Session **ret) {
+
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ Session *session = NULL;
+ const char *name;
+ int r;
+
+ /* Acquire the sender's session. This first checks if the sending process is inside a session itself,
+ * and returns that. If not and 'consult_display' is true, this returns the display session of the
+ * owning user of the caller. */
+
+ r = sd_bus_query_sender_creds(message,
+ SD_BUS_CREDS_SESSION|SD_BUS_CREDS_AUGMENT|
+ (consult_display ? SD_BUS_CREDS_OWNER_UID : 0), &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_session(creds, &name);
+ if (r < 0) {
+ if (r != -ENXIO)
+ return r;
+
+ if (consult_display) {
+ uid_t uid;
+
+ r = sd_bus_creds_get_owner_uid(creds, &uid);
+ if (r < 0) {
+ if (r != -ENXIO)
+ return r;
+ } else {
+ User *user;
+
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
+ if (user)
+ session = user->display;
+ }
+ }
+ } else
+ session = hashmap_get(m->sessions, name);
+
+ if (!session)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SESSION_FOR_PID,
+ consult_display ?
+ "Caller does not belong to any known session and doesn't own any suitable session." :
+ "Caller does not belong to any known session.");
+
+ *ret = session;
+ return 0;
+}
+
+int manager_get_session_from_creds(
+ Manager *m,
+ sd_bus_message *message,
+ const char *name,
+ sd_bus_error *error,
+ Session **ret) {
+
+ Session *session;
+
+ assert(m);
+ assert(ret);
+
+ if (SEAT_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 */
+ return get_sender_session(m, message, true, error, ret);
+
+ session = hashmap_get(m->sessions, name);
+ if (!session)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SESSION, "No session '%s' known", name);
+
+ *ret = session;
+ return 0;
+}
+
+static int get_sender_user(Manager *m, sd_bus_message *message, sd_bus_error *error, User **ret) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ uid_t uid;
+ User *user;
+ int r;
+
+ /* Note that we get the owner UID of the session, not the actual client UID here! */
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_owner_uid(creds, &uid);
+ if (r < 0) {
+ if (r != -ENXIO)
+ return r;
+
+ user = NULL;
+ } else
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
+
+ if (!user)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_USER_FOR_PID,
+ "Caller does not belong to any logged in or lingering user");
+
+ *ret = user;
+ return 0;
+}
+
+int manager_get_user_from_creds(Manager *m, sd_bus_message *message, uid_t uid, sd_bus_error *error, User **ret) {
+ User *user;
+
+ assert(m);
+ assert(ret);
+
+ if (!uid_is_valid(uid))
+ return get_sender_user(m, message, error, ret);
+
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
+ if (!user)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER,
+ "User ID "UID_FMT" is not logged in or lingering", uid);
+
+ *ret = user;
+ return 0;
+}
+
+int manager_get_seat_from_creds(
+ Manager *m,
+ sd_bus_message *message,
+ const char *name,
+ sd_bus_error *error,
+ Seat **ret) {
+
+ Seat *seat;
+ int r;
+
+ assert(m);
+ assert(ret);
+
+ if (SEAT_IS_SELF(name) || SEAT_IS_AUTO(name)) {
+ Session *session;
+
+ /* Use these special seat names as session names */
+ r = manager_get_session_from_creds(m, message, name, error, &session);
+ if (r < 0)
+ return r;
+
+ seat = session->seat;
+ if (!seat)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, "Session '%s' has no seat.", session->id);
+ } else {
+ seat = hashmap_get(m->seats, name);
+ if (!seat)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, "No seat '%s' known", name);
+ }
+
+ *ret = seat;
+ return 0;
+}
+
+static int return_test_polkit(
+ sd_bus_message *message,
+ int capability,
+ const char *action,
+ const char **details,
+ uid_t good_user,
+ sd_bus_error *e) {
+
+ const char *result;
+ bool challenge;
+ int r;
+
+ r = bus_test_polkit(message, capability, action, details, good_user, &challenge, e);
+ if (r < 0)
+ return r;
+
+ if (r > 0)
+ result = "yes";
+ else if (challenge)
+ result = "challenge";
+ else
+ result = "no";
+
+ return sd_bus_reply_method_return(message, "s", result);
+}
+
+static int property_get_idle_hint(
+ 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);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "b", manager_get_idle_hint(m, NULL) > 0);
+}
+
+static int property_get_idle_since_hint(
+ 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);
+ dual_timestamp t = DUAL_TIMESTAMP_NULL;
+
+ assert(bus);
+ assert(reply);
+
+ manager_get_idle_hint(m, &t);
+
+ return sd_bus_message_append(reply, "t", streq(property, "IdleSinceHint") ? t.realtime : t.monotonic);
+}
+
+static int property_get_inhibited(
+ 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);
+ InhibitWhat w;
+
+ assert(bus);
+ assert(reply);
+
+ w = manager_inhibit_what(m, streq(property, "BlockInhibited") ? INHIBIT_BLOCK : INHIBIT_DELAY);
+
+ return sd_bus_message_append(reply, "s", inhibit_what_to_string(w));
+}
+
+static int property_get_preparing(
+ 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);
+ bool b = false;
+
+ assert(bus);
+ assert(reply);
+
+ if (m->delayed_action) {
+ if (streq(property, "PreparingForShutdown"))
+ b = m->delayed_action->inhibit_what & INHIBIT_SHUTDOWN;
+ else
+ b = m->delayed_action->inhibit_what & INHIBIT_SLEEP;
+ }
+
+ return sd_bus_message_append(reply, "b", b);
+}
+
+static int property_get_scheduled_shutdown(
+ 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);
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = sd_bus_message_open_container(reply, 'r', "st");
+ 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);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_close_container(reply);
+}
+
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_handle_action, handle_action, HandleAction);
+static BUS_DEFINE_PROPERTY_GET(property_get_docked, "b", Manager, manager_is_docked_or_external_displays);
+static BUS_DEFINE_PROPERTY_GET(property_get_lid_closed, "b", Manager, manager_is_lid_closed);
+static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_on_external_power, "b", manager_is_on_external_power());
+static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_compat_user_tasks_max, "t", CGROUP_LIMIT_MAX);
+static BUS_DEFINE_PROPERTY_GET_REF(property_get_hashmap_size, "t", Hashmap *, (uint64_t) hashmap_size);
+
+static int method_get_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *p = NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ const char *name;
+ Session *session;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_session_from_creds(m, message, name, error, &session);
+ if (r < 0)
+ return r;
+
+ p = session_bus_path(session);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "o", p);
+}
+
+/* Get login session of a process. This is not what you are looking for these days,
+ * as apps may instead belong to a user service unit. This includes terminal
+ * emulators and hence command-line apps. */
+static int method_get_session_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ _cleanup_free_ char *p = NULL;
+ Session *session = NULL;
+ pid_t pid;
+ int r;
+
+ assert(message);
+
+ assert_cc(sizeof(pid_t) == sizeof(uint32_t));
+
+ r = sd_bus_message_read(message, "u", &pid);
+ if (r < 0)
+ return r;
+ if (pid < 0)
+ return -EINVAL;
+
+ if (pid == 0) {
+ r = manager_get_session_from_creds(m, message, NULL, error, &session);
+ if (r < 0)
+ return r;
+ } else {
+ r = manager_get_session_by_pidref(m, &PIDREF_MAKE_FROM_PID(pid), &session);
+ if (r < 0)
+ return r;
+
+ if (!session)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SESSION_FOR_PID,
+ "PID "PID_FMT" does not belong to any known session", pid);
+ }
+
+ p = session_bus_path(session);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "o", p);
+}
+
+static int method_get_user(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *p = NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ uint32_t uid;
+ User *user;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "u", &uid);
+ if (r < 0)
+ return r;
+
+ r = manager_get_user_from_creds(m, message, uid, error, &user);
+ if (r < 0)
+ return r;
+
+ p = user_bus_path(user);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "o", p);
+}
+
+static int method_get_user_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *p = NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ User *user = NULL;
+ pid_t pid;
+ int r;
+
+ assert(message);
+
+ assert_cc(sizeof(pid_t) == sizeof(uint32_t));
+
+ r = sd_bus_message_read(message, "u", &pid);
+ if (r < 0)
+ return r;
+ if (pid < 0)
+ return -EINVAL;
+
+ if (pid == 0) {
+ r = manager_get_user_from_creds(m, message, UID_INVALID, error, &user);
+ if (r < 0)
+ return r;
+ } else {
+ r = manager_get_user_by_pid(m, pid, &user);
+ if (r < 0)
+ return r;
+ if (!user)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_USER_FOR_PID,
+ "PID "PID_FMT" does not belong to any logged in user or lingering user",
+ pid);
+ }
+
+ p = user_bus_path(user);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "o", p);
+}
+
+static int method_get_seat(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *p = NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ const char *name;
+ Seat *seat;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_seat_from_creds(m, message, name, error, &seat);
+ if (r < 0)
+ return r;
+
+ p = seat_bus_path(seat);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "o", p);
+}
+
+static int method_list_sessions(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);
+ Session *session;
+ 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', "(susso)");
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(session, m->sessions) {
+ _cleanup_free_ char *p = NULL;
+
+ p = session_bus_path(session);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(reply, "(susso)",
+ session->id,
+ (uint32_t) session->user->user_record->uid,
+ session->user->user_record->user_name,
+ session->seat ? session->seat->id : "",
+ p);
+ 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);
+ User *user;
+ 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', "(uso)");
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(user, m->users) {
+ _cleanup_free_ char *p = NULL;
+
+ p = user_bus_path(user);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(reply, "(uso)",
+ (uint32_t) user->user_record->uid,
+ user->user_record->user_name,
+ p);
+ 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_seats(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);
+ Seat *seat;
+ 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', "(so)");
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(seat, m->seats) {
+ _cleanup_free_ char *p = NULL;
+
+ p = seat_bus_path(seat);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(reply, "(so)", seat->id, p);
+ 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_inhibitors(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);
+ Inhibitor *inhibitor;
+ 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', "(ssssuu)");
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(inhibitor, m->inhibitors) {
+
+ r = sd_bus_message_append(reply, "(ssssuu)",
+ strempty(inhibit_what_to_string(inhibitor->what)),
+ strempty(inhibitor->who),
+ strempty(inhibitor->why),
+ strempty(inhibit_mode_to_string(inhibitor->mode)),
+ (uint32_t) inhibitor->uid,
+ (uint32_t) inhibitor->pid.pid);
+ 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 create_session(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error,
+ uid_t uid,
+ pid_t pid,
+ int pidfd,
+ const char *service,
+ const char *type,
+ const char *class,
+ const char *desktop,
+ const char *cseat,
+ uint32_t vtnr,
+ const char *tty,
+ const char *display,
+ int remote,
+ const char *remote_user,
+ const char *remote_host,
+ uint64_t flags) {
+
+ _cleanup_(pidref_done) PidRef leader = PIDREF_NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ _cleanup_free_ char *id = NULL;
+ Session *session = NULL;
+ uint32_t audit_id = 0;
+ User *user = NULL;
+ Seat *seat = NULL;
+ SessionType t;
+ SessionClass c;
+ int r;
+
+ assert(message);
+
+ if (!uid_is_valid(uid))
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid UID");
+
+ 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);
+
+ r = pidref_set_pid(&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");
+
+ if (isempty(type))
+ t = _SESSION_TYPE_INVALID;
+ else {
+ t = session_type_from_string(type);
+ if (t < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid session type %s", type);
+ }
+
+ if (isempty(class))
+ c = _SESSION_CLASS_INVALID;
+ else {
+ c = session_class_from_string(class);
+ if (c < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid session class %s", class);
+ }
+
+ if (isempty(desktop))
+ desktop = NULL;
+ else {
+ if (!string_is_safe(desktop))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid desktop string %s", desktop);
+ }
+
+ if (isempty(cseat))
+ seat = NULL;
+ else {
+ seat = hashmap_get(m->seats, cseat);
+ if (!seat)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT,
+ "No seat '%s' known", cseat);
+ }
+
+ if (tty_is_vc(tty)) {
+ int v;
+
+ if (!seat)
+ seat = m->seat0;
+ else if (seat != m->seat0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "TTY %s is virtual console but seat %s is not seat0", tty, seat->id);
+
+ v = vtnr_from_tty(tty);
+ if (v <= 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Cannot determine VT number from virtual console TTY %s", tty);
+
+ if (vtnr == 0)
+ vtnr = (uint32_t) v;
+ else if (vtnr != (uint32_t) v)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Specified TTY and VT number do not match");
+
+ } else if (tty_is_console(tty)) {
+
+ if (!seat)
+ seat = m->seat0;
+ else if (seat != m->seat0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Console TTY specified but seat is not seat0");
+
+ if (vtnr != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Console TTY specified but VT number is not 0");
+ }
+
+ if (seat) {
+ if (seat_has_vts(seat)) {
+ if (vtnr <= 0 || vtnr > 63)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "VT number out of range");
+ } else {
+ if (vtnr != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Seat has no VTs but VT number not 0");
+ }
+ }
+
+ if (t == _SESSION_TYPE_INVALID) {
+ if (!isempty(display))
+ t = SESSION_X11;
+ else if (!isempty(tty))
+ t = SESSION_TTY;
+ else
+ t = SESSION_UNSPECIFIED;
+ }
+
+ if (c == _SESSION_CLASS_INVALID) {
+ if (t == SESSION_UNSPECIFIED)
+ c = SESSION_BACKGROUND;
+ else
+ 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);
+ if (r < 0)
+ return r;
+ 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.
+ */
+ if (c != SESSION_GREETER &&
+ vtnr > 0 &&
+ vtnr < MALLOC_ELEMENTSOF(m->seat0->positions) &&
+ m->seat0->positions[vtnr] &&
+ m->seat0->positions[vtnr]->class != SESSION_GREETER)
+ return sd_bus_error_set(error, BUS_ERROR_SESSION_BUSY, "Already occupied by a session");
+
+ if (hashmap_size(m->sessions) >= m->sessions_max)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
+ "Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.",
+ m->sessions_max);
+
+ (void) audit_session_from_pid(leader.pid, &audit_id);
+ if (audit_session_is_valid(audit_id)) {
+ /* Keep our session IDs and the audit session IDs in sync */
+
+ if (asprintf(&id, "%"PRIu32, audit_id) < 0)
+ return -ENOMEM;
+
+ /* Wut? There's already a session by this name and we didn't find it above? Weird, then let's
+ * not trust the audit data and let's better register a new ID */
+ if (hashmap_contains(m->sessions, id)) {
+ log_warning("Existing logind session ID %s used by new audit session, ignoring.", id);
+ audit_id = AUDIT_SESSION_INVALID;
+ id = mfree(id);
+ }
+ }
+
+ if (!id) {
+ do {
+ id = mfree(id);
+
+ if (asprintf(&id, "c%" PRIu64, ++m->session_counter) < 0)
+ return -ENOMEM;
+
+ } while (hashmap_contains(m->sessions, id));
+ }
+
+ /* The generated names should not clash with 'auto' or 'self' */
+ assert(!SESSION_IS_SELF(id));
+ assert(!SESSION_IS_AUTO(id));
+
+ /* If we are not watching utmp already, try again */
+ manager_reconnect_utmp(m);
+
+ r = manager_add_user_by_uid(m, uid, &user);
+ if (r < 0)
+ goto fail;
+
+ r = manager_add_session(m, id, &session);
+ if (r < 0)
+ goto fail;
+
+ session_set_user(session, user);
+ r = session_set_leader_consume(session, TAKE_PIDREF(leader));
+ if (r < 0)
+ goto fail;
+
+ session->original_type = session->type = t;
+ session->class = c;
+ session->remote = remote;
+ session->vtnr = vtnr;
+
+ if (!isempty(tty)) {
+ session->tty = strdup(tty);
+ if (!session->tty) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ session->tty_validity = TTY_FROM_PAM;
+ }
+
+ if (!isempty(display)) {
+ session->display = strdup(display);
+ if (!session->display) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!isempty(remote_user)) {
+ session->remote_user = strdup(remote_user);
+ if (!session->remote_user) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!isempty(remote_host)) {
+ session->remote_host = strdup(remote_host);
+ if (!session->remote_host) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!isempty(service)) {
+ session->service = strdup(service);
+ if (!session->service) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!isempty(desktop)) {
+ session->desktop = strdup(desktop);
+ if (!session->desktop) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (seat) {
+ r = seat_attach_session(seat, session);
+ if (r < 0)
+ goto fail;
+ }
+
+ r = sd_bus_message_enter_container(message, 'a', "(sv)");
+ if (r < 0)
+ goto fail;
+
+ r = session_start(session, message, error);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ goto fail;
+
+ 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(). */
+
+ return 1;
+
+fail:
+ if (session)
+ session_add_to_gc_queue(session);
+
+ if (user)
+ user_add_to_gc_queue(user);
+
+ return r;
+}
+
+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;
+ uid_t uid;
+ int remote;
+ uint32_t vtnr = 0;
+ int 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);
+ if (r < 0)
+ return r;
+
+ return create_session(
+ message,
+ userdata,
+ error,
+ uid,
+ leader,
+ /* pidfd = */ -EBADF,
+ service,
+ type,
+ class,
+ desktop,
+ cseat,
+ vtnr,
+ tty,
+ display,
+ remote,
+ remote_user,
+ remote_host,
+ /* flags = */ 0);
+}
+
+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;
+
+ r = sd_bus_message_read(message,
+ "uhsssssussbsst",
+ &uid,
+ &leaderfd,
+ &service,
+ &type,
+ &class,
+ &desktop,
+ &cseat,
+ &vtnr,
+ &tty,
+ &display,
+ &remote,
+ &remote_user,
+ &remote_host,
+ &flags);
+ if (r < 0)
+ return r;
+
+ return create_session(
+ message,
+ userdata,
+ error,
+ uid,
+ /* pid = */ 0,
+ leaderfd,
+ service,
+ type,
+ class,
+ desktop,
+ cseat,
+ vtnr,
+ tty,
+ display,
+ remote,
+ remote_user,
+ remote_host,
+ flags);
+}
+
+static int method_release_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ Session *session;
+ const char *name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_session_from_creds(m, message, name, error, &session);
+ if (r < 0)
+ return r;
+
+ r = session_release(session);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_activate_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ Session *session;
+ const char *name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_session_from_creds(m, message, name, error, &session);
+ if (r < 0)
+ return r;
+
+ /* PolicyKit is done by bus_session_method_activate() */
+
+ return bus_session_method_activate(message, session, error);
+}
+
+static int method_activate_session_on_seat(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ const char *session_name, *seat_name;
+ Manager *m = ASSERT_PTR(userdata);
+ Session *session;
+ Seat *seat;
+ int r;
+
+ assert(message);
+
+ /* Same as ActivateSession() but refuses to work if the seat doesn't match */
+
+ r = sd_bus_message_read(message, "ss", &session_name, &seat_name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_session_from_creds(m, message, session_name, error, &session);
+ if (r < 0)
+ return r;
+
+ r = manager_get_seat_from_creds(m, message, seat_name, error, &seat);
+ if (r < 0)
+ return r;
+
+ if (session->seat != seat)
+ return sd_bus_error_setf(error, BUS_ERROR_SESSION_NOT_ON_SEAT,
+ "Session %s not on seat %s", session_name, seat_name);
+
+ r = check_polkit_chvt(message, m, error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = session_activate(session);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_lock_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ Session *session;
+ const char *name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_session_from_creds(m, message, name, error, &session);
+ if (r < 0)
+ return r;
+
+ return bus_session_method_lock(message, session, error);
+}
+
+static int method_lock_sessions(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.lock-sessions",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = session_send_lock_all(m, streq(sd_bus_message_get_member(message), "LockSessions"));
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_kill_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ const char *name;
+ Manager *m = ASSERT_PTR(userdata);
+ Session *session;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_session_from_creds(m, message, name, error, &session);
+ if (r < 0)
+ return r;
+
+ return bus_session_method_kill(message, session, error);
+}
+
+static int method_kill_user(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ uint32_t uid;
+ User *user;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "u", &uid);
+ if (r < 0)
+ return r;
+
+ r = manager_get_user_from_creds(m, message, uid, error, &user);
+ if (r < 0)
+ return r;
+
+ return bus_user_method_kill(message, user, error);
+}
+
+static int method_terminate_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ const char *name;
+ Session *session;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_session_from_creds(m, message, name, error, &session);
+ if (r < 0)
+ return r;
+
+ return bus_session_method_terminate(message, session, error);
+}
+
+static int method_terminate_user(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ uint32_t uid;
+ User *user;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "u", &uid);
+ if (r < 0)
+ return r;
+
+ r = manager_get_user_from_creds(m, message, uid, error, &user);
+ if (r < 0)
+ return r;
+
+ return bus_user_method_terminate(message, user, error);
+}
+
+static int method_terminate_seat(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ const char *name;
+ Seat *seat;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = manager_get_seat_from_creds(m, message, name, error, &seat);
+ if (r < 0)
+ return r;
+
+ return bus_seat_method_terminate(message, seat, error);
+}
+
+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;
+ uint32_t uid, auth_uid;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "ubb", &uid, &b, &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);
+ 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! */
+ r = sd_bus_creds_get_owner_uid(creds, &uid);
+ if (r < 0)
+ return r;
+ }
+
+ /* owner_uid is racy, so for authorization we must use euid */
+ r = sd_bus_creds_get_euid(creds, &auth_uid);
+ if (r < 0)
+ return r;
+
+ errno = 0;
+ pw = getpwuid(uid);
+ if (!pw)
+ return errno_or_else(ENOENT);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ uid == auth_uid ? "org.freedesktop.login1.set-self-linger" :
+ "org.freedesktop.login1.set-user-linger",
+ NULL,
+ interactive,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ (void) mkdir_p_label("/var/lib/systemd", 0755);
+ r = mkdir_safe_label("/var/lib/systemd/linger", 0755, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ return r;
+
+ cc = cescape(pw->pw_name);
+ if (!cc)
+ return -ENOMEM;
+
+ path = strjoina("/var/lib/systemd/linger/", cc);
+ if (b) {
+ User *u;
+
+ r = touch(path);
+ if (r < 0)
+ return r;
+
+ if (manager_add_user_by_uid(m, uid, &u) >= 0)
+ user_start(u);
+
+ } else {
+ User *u;
+
+ r = unlink(path);
+ if (r < 0 && errno != ENOENT)
+ return -errno;
+
+ u = hashmap_get(m->users, UID_TO_PTR(uid));
+ if (u)
+ user_add_to_gc_queue(u);
+ }
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int trigger_device(Manager *m, sd_device *parent) {
+ _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+ int r;
+
+ assert(m);
+
+ r = sd_device_enumerator_new(&e);
+ if (r < 0)
+ return r;
+
+ r = sd_device_enumerator_allow_uninitialized(e);
+ if (r < 0)
+ return r;
+
+ if (parent) {
+ r = sd_device_enumerator_add_match_parent(e, parent);
+ if (r < 0)
+ return r;
+ }
+
+ FOREACH_DEVICE(e, d) {
+ r = sd_device_trigger(d, SD_DEVICE_CHANGE);
+ if (r < 0)
+ log_device_debug_errno(d, r, "Failed to trigger device, ignoring: %m");
+ }
+
+ return 0;
+}
+
+static int attach_device(Manager *m, const char *seat, const char *sysfs, sd_bus_error *error) {
+ _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+ _cleanup_free_ char *rule = NULL, *file = NULL;
+ const char *id_for_seat;
+ int r;
+
+ assert(m);
+ assert(seat);
+ assert(sysfs);
+
+ r = sd_device_new_from_syspath(&d, sysfs);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to open device '%s': %m", sysfs);
+
+ if (sd_device_has_current_tag(d, "seat") <= 0)
+ return sd_bus_error_set_errnof(error, ENODEV, "Device '%s' lacks 'seat' udev tag.", sysfs);
+
+ if (sd_device_get_property_value(d, "ID_FOR_SEAT", &id_for_seat) < 0)
+ return sd_bus_error_set_errnof(error, ENODEV, "Device '%s' lacks 'ID_FOR_SEAT' udev property.", sysfs);
+
+ if (asprintf(&file, "/etc/udev/rules.d/72-seat-%s.rules", id_for_seat) < 0)
+ return -ENOMEM;
+
+ if (asprintf(&rule, "TAG==\"seat\", ENV{ID_FOR_SEAT}==\"%s\", ENV{ID_SEAT}=\"%s\"", id_for_seat, seat) < 0)
+ return -ENOMEM;
+
+ (void) mkdir_p_label("/etc/udev/rules.d", 0755);
+ r = write_string_file_atomic_label(file, rule);
+ if (r < 0)
+ return r;
+
+ return trigger_device(m, d);
+}
+
+static int flush_devices(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
+
+ assert(m);
+
+ d = opendir("/etc/udev/rules.d");
+ if (!d) {
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Failed to open /etc/udev/rules.d: %m");
+ } else
+ FOREACH_DIRENT_ALL(de, d, break) {
+ if (!dirent_is_file(de))
+ continue;
+
+ if (!startswith(de->d_name, "72-seat-"))
+ continue;
+
+ if (!endswith(de->d_name, ".rules"))
+ continue;
+
+ if (unlinkat(dirfd(d), de->d_name, 0) < 0)
+ log_warning_errno(errno, "Failed to unlink %s: %m", de->d_name);
+ }
+
+ return trigger_device(m, NULL);
+}
+
+static int method_attach_device(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ const char *sysfs, *seat;
+ Manager *m = ASSERT_PTR(userdata);
+ int interactive, r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "ssb", &seat, &sysfs, &interactive);
+ if (r < 0)
+ return r;
+
+ if (!path_is_normalized(sysfs))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not normalized", sysfs);
+ if (!path_startswith(sysfs, "/sys"))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not in /sys", sysfs);
+
+ if (SEAT_IS_SELF(seat) || SEAT_IS_AUTO(seat)) {
+ Seat *found;
+
+ r = manager_get_seat_from_creds(m, message, seat, error, &found);
+ if (r < 0)
+ return r;
+
+ seat = found->id;
+
+ } 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(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.attach-device",
+ NULL,
+ interactive,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ r = attach_device(m, seat, sysfs, error);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ int interactive, r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "b", &interactive);
+ if (r < 0)
+ return r;
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.flush-devices",
+ NULL,
+ interactive,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ r = flush_devices(m);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int have_multiple_sessions(
+ Manager *m,
+ uid_t uid) {
+
+ Session *session;
+
+ assert(m);
+
+ /* 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 &&
+ session->user->user_record->uid != uid)
+ return true;
+
+ return false;
+}
+
+static int bus_manager_log_shutdown(
+ Manager *m,
+ const HandleActionData *a) {
+ assert(m);
+ assert(a);
+
+ const char *message = a->message ?: "System is shutting down";
+ const char *log_verb = a->log_verb ? strjoina("SHUTDOWN=", a->log_verb) : NULL;
+
+ return log_struct(LOG_NOTICE,
+ "MESSAGE_ID=%s", a->message_id ?: SD_MESSAGE_SHUTDOWN_STR,
+ LOG_MESSAGE("%s%s%s%s.",
+ message,
+ m->wall_message ? " (" : "",
+ strempty(m->wall_message),
+ m->wall_message ? ")" : ""),
+ log_verb);
+}
+
+static int lid_switch_ignore_handler(sd_event_source *e, uint64_t usec, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(e);
+
+ m->lid_switch_ignore_event_source = sd_event_source_unref(m->lid_switch_ignore_event_source);
+ return 0;
+}
+
+int manager_set_lid_switch_ignore(Manager *m, usec_t until) {
+ int r;
+
+ assert(m);
+
+ if (until <= now(CLOCK_MONOTONIC))
+ return 0;
+
+ /* We want to ignore the lid switch for a while after each
+ * suspend, and after boot-up. Hence let's install a timer for
+ * this. As long as the event source exists we ignore the lid
+ * switch. */
+
+ if (m->lid_switch_ignore_event_source) {
+ usec_t u;
+
+ r = sd_event_source_get_time(m->lid_switch_ignore_event_source, &u);
+ if (r < 0)
+ return r;
+
+ if (until <= u)
+ return 0;
+
+ r = sd_event_source_set_time(m->lid_switch_ignore_event_source, until);
+ } else
+ r = sd_event_add_time(
+ m->event,
+ &m->lid_switch_ignore_event_source,
+ CLOCK_MONOTONIC,
+ until, 0,
+ lid_switch_ignore_handler, m);
+
+ return r;
+}
+
+static int send_prepare_for(Manager *m, const HandleActionData *a, bool _active) {
+ int k = 0, r, active = _active;
+
+ assert(m);
+ assert(a);
+ assert(IN_SET(a->inhibit_what, INHIBIT_SHUTDOWN, INHIBIT_SLEEP));
+
+ /* We need to send both old and new signal for backward compatibility. The newer one allows clients
+ * to know which type of reboot is going to happen, as they might be doing different actions (e.g.:
+ * on soft-reboot), and it is sent first, so that clients know that if they receive the old one
+ * first then they don't have to wait for the new one, as it means it's not supported. So, do not
+ * change the order here, as it is an API. */
+ if (a->inhibit_what == INHIBIT_SHUTDOWN) {
+ k = sd_bus_emit_signal(m->bus,
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ "PrepareForShutdownWithMetadata",
+ "ba{sv}",
+ active,
+ 1,
+ "type",
+ "s",
+ handle_action_to_string(a->handle));
+ if (k < 0)
+ log_debug_errno(k, "Failed to emit PrepareForShutdownWithMetadata(): %m");
+ }
+
+ r = sd_bus_emit_signal(m->bus,
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ a->inhibit_what == INHIBIT_SHUTDOWN ? "PrepareForShutdown" : "PrepareForSleep",
+ "b",
+ active);
+ if (r < 0)
+ log_debug_errno(r, "Failed to emit PrepareForShutdown(): %m");
+
+ return RET_GATHER(k, r);
+}
+
+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(a);
+
+ if (a->inhibit_what == INHIBIT_SHUTDOWN)
+ bus_manager_log_shutdown(m, a);
+
+ r = bus_call_method(
+ m->bus,
+ bus_systemd_mgr,
+ "StartUnit",
+ error,
+ &reply,
+ "ss", a->target, "replace-irreversibly");
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read(reply, "o", &p);
+ if (r < 0)
+ goto error;
+
+ r = free_and_strdup(&m->action_job, p);
+ if (r < 0)
+ goto error;
+
+ m->delayed_action = a;
+
+ /* Make sure the lid switch is ignored for a while */
+ manager_set_lid_switch_ignore(m, usec_add(now(CLOCK_MONOTONIC), m->holdoff_timeout_usec));
+
+ return 0;
+
+error:
+ /* Tell people that they now may take a lock again */
+ (void) send_prepare_for(m, a, false);
+
+ return r;
+}
+
+int manager_dispatch_delayed(Manager *manager, bool timeout) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ Inhibitor *offending = NULL;
+ int r;
+
+ assert(manager);
+
+ if (!manager->delayed_action || manager->action_job)
+ return 0;
+
+ if (manager_is_inhibited(manager, manager->delayed_action->inhibit_what, INHIBIT_DELAY, NULL, false, false, 0, &offending)) {
+ _cleanup_free_ char *comm = NULL, *u = NULL;
+
+ if (!timeout)
+ return 0;
+
+ (void) pidref_get_comm(&offending->pid, &comm);
+ u = uid_to_name(offending->uid);
+
+ log_notice("Delay lock is active (UID "UID_FMT"/%s, PID "PID_FMT"/%s) but inhibitor timeout is reached.",
+ offending->uid, strna(u),
+ offending->pid.pid, strna(comm));
+ }
+
+ /* Actually do the operation */
+ r = execute_shutdown_or_sleep(manager, manager->delayed_action, &error);
+ if (r < 0) {
+ log_warning("Error during inhibitor-delayed operation (already returned success to client): %s",
+ bus_error_message(&error, r));
+
+ manager->delayed_action = NULL;
+ }
+
+ return 1; /* We did some work. */
+}
+
+static int manager_inhibit_timeout_handler(
+ sd_event_source *s,
+ uint64_t usec,
+ void *userdata) {
+
+ Manager *manager = ASSERT_PTR(userdata);
+
+ assert(manager->inhibit_timeout_source == s);
+
+ return manager_dispatch_delayed(manager, true);
+}
+
+static int delay_shutdown_or_sleep(
+ Manager *m,
+ const HandleActionData *a) {
+
+ int r;
+
+ assert(m);
+ assert(a);
+
+ if (m->inhibit_timeout_source) {
+ r = sd_event_source_set_time_relative(m->inhibit_timeout_source, m->inhibit_delay_max);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_source_set_time_relative() failed: %m");
+
+ r = sd_event_source_set_enabled(m->inhibit_timeout_source, SD_EVENT_ONESHOT);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_source_set_enabled() failed: %m");
+ } else {
+ r = sd_event_add_time_relative(
+ m->event,
+ &m->inhibit_timeout_source,
+ CLOCK_MONOTONIC, m->inhibit_delay_max, 0,
+ manager_inhibit_timeout_handler, m);
+ if (r < 0)
+ return r;
+ }
+
+ m->delayed_action = a;
+
+ return 0;
+}
+
+int bus_manager_shutdown_or_sleep_now_or_later(
+ Manager *m,
+ const HandleActionData *a,
+ sd_bus_error *error) {
+
+ _cleanup_free_ char *load_state = NULL;
+ bool delayed;
+ int r;
+
+ assert(m);
+ assert(a);
+ assert(!m->action_job);
+
+ r = unit_load_state(m->bus, a->target, &load_state);
+ if (r < 0)
+ return r;
+
+ if (!streq(load_state, "loaded"))
+ return log_notice_errno(SYNTHETIC_ERRNO(EACCES),
+ "Unit %s is %s, refusing operation.",
+ a->target, load_state);
+
+ /* Tell everybody to prepare for shutdown/sleep */
+ (void) send_prepare_for(m, a, true);
+
+ delayed =
+ m->inhibit_delay_max > 0 &&
+ manager_is_inhibited(m, a->inhibit_what, INHIBIT_DELAY, NULL, false, false, 0, NULL);
+
+ if (delayed)
+ /* Shutdown is delayed, keep in mind what we
+ * want to do, and start a timeout */
+ r = delay_shutdown_or_sleep(m, a);
+ else
+ /* Shutdown is not delayed, execute it
+ * immediately */
+ r = execute_shutdown_or_sleep(m, a, error);
+
+ return r;
+}
+
+static int verify_shutdown_creds(
+ Manager *m,
+ sd_bus_message *message,
+ const HandleActionData *a,
+ uint64_t flags,
+ sd_bus_error *error) {
+
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ bool multiple_sessions, blocked, interactive;
+ uid_t uid;
+ int r;
+
+ assert(m);
+ assert(a);
+ assert(message);
+
+ 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;
+
+ r = have_multiple_sessions(m, uid);
+ if (r < 0)
+ return r;
+
+ multiple_sessions = r > 0;
+ blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL);
+ interactive = flags & SD_LOGIND_INTERACTIVE;
+
+ if (multiple_sessions) {
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_BOOT,
+ a->polkit_action_multiple_sessions,
+ NULL,
+ interactive,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ }
+
+ if (blocked) {
+ /* We don't check polkit for root here, because you can't be more privileged than root */
+ if (uid == 0 && (flags & SD_LOGIND_ROOT_CHECK_INHIBITORS))
+ 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,
+ a->polkit_action_ignore_inhibit,
+ NULL,
+ interactive,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ }
+
+ if (!multiple_sessions && !blocked) {
+ r = bus_verify_polkit_async(message,
+ CAP_SYS_BOOT,
+ a->polkit_action,
+ NULL,
+ interactive,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ }
+
+ return 0;
+}
+
+static int setup_wall_message_timer(Manager *m, sd_bus_message* message) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ int r;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_TTY|SD_BUS_CREDS_UID, &creds);
+ if (r >= 0) {
+ const char *tty = NULL;
+
+ (void) sd_bus_creds_get_uid(creds, &m->scheduled_shutdown_uid);
+ (void) sd_bus_creds_get_tty(creds, &tty);
+
+ r = free_and_strdup(&m->scheduled_shutdown_tty, tty);
+ if (r < 0)
+ return log_oom();
+ }
+
+ r = manager_setup_wall_message_timer(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int method_do_shutdown_or_sleep(
+ Manager *m,
+ sd_bus_message *message,
+ const HandleActionData *a,
+ bool with_flags,
+ sd_bus_error *error) {
+
+ uint64_t flags;
+ int r;
+
+ assert(m);
+ assert(message);
+ assert(a);
+
+ if (with_flags) {
+ /* New style method: with flags parameter (and interactive bool in the bus message header) */
+ r = sd_bus_message_read(message, "t", &flags);
+ if (r < 0)
+ return r;
+ if ((flags & ~SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC) != 0)
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid flags parameter");
+
+ if (FLAGS_SET(flags, (SD_LOGIND_REBOOT_VIA_KEXEC|SD_LOGIND_SOFT_REBOOT)))
+ 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)
+ 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))
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Soft reboot option is only applicable with reboot operations");
+ }
+ } else {
+ /* Old style method: no flags parameter, but interactive bool passed as boolean in
+ * payload. Let's convert this argument to the new-style flags parameter for our internal
+ * use. */
+ int interactive;
+
+ r = sd_bus_message_read(message, "b", &interactive);
+ if (r < 0)
+ return r;
+
+ 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))
+ a = handle_action_lookup(HANDLE_SOFT_REBOOT);
+ else if ((flags & SD_LOGIND_REBOOT_VIA_KEXEC) && kexec_loaded())
+ a = handle_action_lookup(HANDLE_KEXEC);
+
+ /* Don't allow multiple jobs being executed at the same time */
+ if (m->delayed_action)
+ return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
+ "There's already a shutdown or sleep operation in progress");
+
+ if (a->sleep_operation >= 0) {
+ SleepSupport support;
+
+ r = sleep_supported_full(a->sleep_operation, &support);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ switch (support) {
+
+ case SLEEP_DISABLED:
+ return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
+ "Sleep verb '%s' is disabled by config",
+ sleep_operation_to_string(a->sleep_operation));
+
+ case SLEEP_NOT_CONFIGURED:
+ case SLEEP_STATE_OR_MODE_NOT_SUPPORTED:
+ case SLEEP_ALARM_NOT_SUPPORTED:
+ return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
+ "Sleep verb '%s' is not configured or configuration is not supported by kernel",
+ sleep_operation_to_string(a->sleep_operation));
+
+ case SLEEP_RESUME_NOT_SUPPORTED:
+ return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
+ "Not running on EFI and resume= is not set. No available method to resume from hibernation");
+
+ 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");
+
+ default:
+ assert_not_reached();
+
+ }
+ }
+
+ r = verify_shutdown_creds(m, message, a, flags, error);
+ if (r != 0)
+ return r;
+
+ /* 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;
+
+ (void) setup_wall_message_timer(m, message);
+
+ r = bus_manager_shutdown_or_sleep_now_or_later(m, a, error);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ return method_do_shutdown_or_sleep(
+ m, message,
+ handle_action_lookup(HANDLE_POWEROFF),
+ sd_bus_message_is_method_call(message, NULL, "PowerOffWithFlags"),
+ error);
+}
+
+static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ return method_do_shutdown_or_sleep(
+ m, message,
+ handle_action_lookup(HANDLE_REBOOT),
+ sd_bus_message_is_method_call(message, NULL, "RebootWithFlags"),
+ error);
+}
+
+static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ return method_do_shutdown_or_sleep(
+ m, message,
+ handle_action_lookup(HANDLE_HALT),
+ sd_bus_message_is_method_call(message, NULL, "HaltWithFlags"),
+ error);
+}
+
+static int method_suspend(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ return method_do_shutdown_or_sleep(
+ m, message,
+ handle_action_lookup(HANDLE_SUSPEND),
+ sd_bus_message_is_method_call(message, NULL, "SuspendWithFlags"),
+ error);
+}
+
+static int method_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ return method_do_shutdown_or_sleep(
+ m, message,
+ handle_action_lookup(HANDLE_HIBERNATE),
+ sd_bus_message_is_method_call(message, NULL, "HibernateWithFlags"),
+ error);
+}
+
+static int method_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ return method_do_shutdown_or_sleep(
+ m, message,
+ handle_action_lookup(HANDLE_HYBRID_SLEEP),
+ sd_bus_message_is_method_call(message, NULL, "HybridSleepWithFlags"),
+ error);
+}
+
+static int method_suspend_then_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ return method_do_shutdown_or_sleep(
+ m, message,
+ handle_action_lookup(HANDLE_SUSPEND_THEN_HIBERNATE),
+ sd_bus_message_is_method_call(message, NULL, "SuspendThenHibernateWithFlags"),
+ error);
+}
+
+static int nologin_timeout_handler(
+ sd_event_source *s,
+ uint64_t usec,
+ void *userdata) {
+
+ Manager *m = userdata;
+
+ log_info("Creating /run/nologin, blocking further logins...");
+
+ m->unlink_nologin =
+ create_shutdown_run_nologin_or_warn() >= 0;
+
+ return 0;
+}
+
+static usec_t nologin_timeout_usec(usec_t elapse) {
+ /* Issue /run/nologin five minutes before shutdown */
+ 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;
+
+ 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;
+ }
+
+ 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 update_schedule_file(Manager *m) {
+ _cleanup_(unlink_and_freep) char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(m);
+ assert(m->scheduled_shutdown_action);
+
+ r = mkdir_parents_label(SHUTDOWN_SCHEDULE_FILE, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create shutdown subdirectory: %m");
+
+ r = fopen_temporary(SHUTDOWN_SCHEDULE_FILE, &f, &temp_path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to save information about scheduled shutdowns: %m");
+
+ (void) fchmod(fileno(f), 0644);
+
+ 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, "UID", UID_FMT, m->scheduled_shutdown_uid);
+
+ if (m->scheduled_shutdown_tty)
+ serialize_item_format(f, "TTY", "%s", m->scheduled_shutdown_tty);
+
+ if (!isempty(m->wall_message)) {
+ r = serialize_item_escaped(f, "WALL_MESSAGE", m->wall_message);
+ if (r < 0)
+ goto fail;
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, SHUTDOWN_SCHEDULE_FILE) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ temp_path = mfree(temp_path);
+ return 0;
+
+fail:
+ (void) unlink(SHUTDOWN_SCHEDULE_FILE);
+
+ 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);
+ int r;
+
+ a = m->scheduled_shutdown_action;
+ assert(a);
+
+ /* 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);
+ goto error;
+ }
+
+ if (m->shutdown_dry_run) {
+ /* We do not process delay inhibitors here. Otherwise, we
+ * would have to be considered "in progress" (like the check
+ * above) for some seconds after our admin has seen the final
+ * wall message. */
+
+ bus_manager_log_shutdown(m, a);
+ log_info("Running in dry run, suppressing action.");
+ reset_scheduled_shutdown(m);
+
+ return 0;
+ }
+
+ r = bus_manager_shutdown_or_sleep_now_or_later(m, m->scheduled_shutdown_action, &error);
+ if (r < 0) {
+ log_error_errno(r, "Scheduled shutdown to %s failed: %m", a->target);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ reset_scheduled_shutdown(m);
+ return r;
+}
+
+static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ HandleAction handle;
+ const HandleActionData *a;
+ uint64_t elapse;
+ char *type;
+ int r;
+ bool dry_run = false;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "st", &type, &elapse);
+ if (r < 0)
+ return r;
+
+ if (startswith(type, "dry-")) {
+ type += 4;
+ dry_run = true;
+ }
+
+ handle = handle_action_from_string(type);
+ 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(a->polkit_action);
+
+ r = verify_shutdown_creds(m, message, a, 0, error);
+ if (r != 0)
+ return r;
+
+ m->scheduled_shutdown_action = a;
+ m->shutdown_dry_run = dry_run;
+ m->scheduled_shutdown_timeout = elapse;
+
+ r = manager_setup_shutdown_timers(m);
+ if (r < 0)
+ return r;
+
+ r = setup_wall_message_timer(m, message);
+ if (r >= 0)
+ r = update_schedule_file(m);
+
+ if (r < 0) {
+ reset_scheduled_shutdown(m);
+ return r;
+ }
+
+ 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;
+ bool cancelled;
+ int r;
+
+ assert(message);
+
+ cancelled = m->scheduled_shutdown_action
+ && !IN_SET(m->scheduled_shutdown_action->handle, HANDLE_IGNORE, _HANDLE_ACTION_INVALID);
+ if (!cancelled)
+ return sd_bus_reply_method_return(message, "b", false);
+
+ a = 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,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ if (m->enable_wall_messages) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ const char *tty = NULL;
+ uid_t uid = 0;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_TTY|SD_BUS_CREDS_UID, &creds);
+ if (r >= 0) {
+ (void) sd_bus_creds_get_uid(creds, &uid);
+ (void) sd_bus_creds_get_tty(creds, &tty);
+ }
+
+ _cleanup_free_ char *username = uid_to_name(uid);
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("System shutdown has been cancelled"),
+ "ACTION=%s", handle_action_to_string(a->handle),
+ "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_CANCELED_STR,
+ username ? "OPERATOR=%s" : NULL, username);
+
+ (void) wall("System shutdown has been cancelled",
+ username, tty, logind_wall_tty_filter, m);
+ }
+
+ reset_scheduled_shutdown(m);
+
+ return sd_bus_reply_method_return(message, "b", true);
+}
+
+static int method_can_shutdown_or_sleep(
+ Manager *m,
+ sd_bus_message *message,
+ const HandleActionData *a,
+ sd_bus_error *error) {
+
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ bool multiple_sessions, challenge, blocked;
+ const char *result = NULL;
+ uid_t uid;
+ int r;
+
+ assert(m);
+ assert(message);
+ assert(a);
+
+ if (a->sleep_operation >= 0) {
+ SleepSupport support;
+
+ 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");
+ }
+
+ 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;
+
+ r = have_multiple_sessions(m, uid);
+ if (r < 0)
+ return r;
+
+ 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;
+
+ 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;
+
+ 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);
+ if (r < 0)
+ return r;
+
+ if (r > 0)
+ result = "yes";
+ else if (challenge)
+ result = "challenge";
+ else
+ result = "no";
+ }
+
+ if (blocked) {
+ r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action_ignore_inhibit, NULL, UID_INVALID, &challenge, error);
+ if (r < 0)
+ return r;
+
+ if (r > 0) {
+ if (!result)
+ result = "yes";
+ } else if (challenge) {
+ if (!result || streq(result, "yes"))
+ result = "challenge";
+ } else
+ result = "no";
+ }
+
+ if (!multiple_sessions && !blocked) {
+ /* 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);
+ if (r < 0)
+ return r;
+
+ if (r > 0)
+ result = "yes";
+ else if (challenge)
+ result = "challenge";
+ else
+ result = "no";
+ }
+
+ finish:
+ return sd_bus_reply_method_return(message, "s", result);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+static int property_get_reboot_parameter(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+ _cleanup_free_ char *parameter = NULL;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ r = read_reboot_parameter(&parameter);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_append(reply, "s", parameter);
+}
+
+static int method_set_reboot_parameter(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = ASSERT_PTR(userdata);
+ const char *arg;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &arg);
+ if (r < 0)
+ return r;
+
+ r = detect_container();
+ if (r < 0)
+ return r;
+ if (r > 0)
+ 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);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ r = update_reboot_parameter_and_warn(arg, false);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_can_reboot_parameter(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _unused_ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = detect_container();
+ if (r < 0)
+ return r;
+ if (r > 0) /* Inside containers, specifying a reboot parameter, doesn't make much sense */
+ return sd_bus_reply_method_return(message, "s", "na");
+
+ return return_test_polkit(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-parameter",
+ NULL,
+ UID_INVALID,
+ error);
+}
+
+static int property_get_reboot_to_firmware_setup(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_FIRMWARE_SETUP");
+ if (r == -ENXIO) {
+ /* EFI case: let's see what is currently configured in the EFI variables */
+ r = efi_get_reboot_to_firmware();
+ if (r < 0 && r != -EOPNOTSUPP)
+ log_warning_errno(r, "Failed to determine reboot-to-firmware-setup state: %m");
+ } else if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP: %m");
+ else if (r > 0) {
+ /* Non-EFI case: let's see whether /run/systemd/reboot-to-firmware-setup exists. */
+ if (access("/run/systemd/reboot-to-firmware-setup", F_OK) < 0) {
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Failed to check whether /run/systemd/reboot-to-firmware-setup exists: %m");
+
+ r = false;
+ } else
+ r = true;
+ }
+
+ return sd_bus_message_append(reply, "b", r > 0);
+}
+
+static int method_set_reboot_to_firmware_setup(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = ASSERT_PTR(userdata);
+ bool use_efi;
+ int b, r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "b", &b);
+ if (r < 0)
+ return r;
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_FIRMWARE_SETUP");
+ if (r == -ENXIO) {
+ /* EFI case: let's see what the firmware supports */
+
+ r = efi_reboot_to_firmware_supported();
+ if (r == -EOPNOTSUPP)
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Firmware does not support boot into firmware.");
+ if (r < 0)
+ return r;
+
+ use_efi = true;
+
+ } else if (r <= 0) {
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP is set to off */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP: %m");
+
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Firmware does not support boot into firmware.");
+ } else
+ /* 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);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ if (use_efi) {
+ r = efi_set_reboot_to_firmware(b);
+ if (r < 0)
+ return r;
+ } else {
+ if (b) {
+ r = touch("/run/systemd/reboot-to-firmware-setup");
+ if (r < 0)
+ return r;
+ } else {
+ if (unlink("/run/systemd/reboot-to-firmware-setup") < 0 && errno != ENOENT)
+ return -errno;
+ }
+ }
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_can_reboot_to_firmware_setup(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _unused_ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_FIRMWARE_SETUP");
+ if (r == -ENXIO) {
+ /* EFI case: let's see what the firmware supports */
+
+ r = efi_reboot_to_firmware_supported();
+ if (r < 0) {
+ if (r != -EOPNOTSUPP)
+ log_warning_errno(r, "Failed to determine whether reboot to firmware is supported: %m");
+
+ return sd_bus_reply_method_return(message, "s", "na");
+ }
+
+ } else if (r <= 0) {
+ /* Non-EFI case: let's trust $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP: %m");
+
+ return sd_bus_reply_method_return(message, "s", "na");
+ }
+
+ return return_test_polkit(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-firmware-setup",
+ NULL,
+ UID_INVALID,
+ error);
+}
+
+static int property_get_reboot_to_boot_loader_menu(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ uint64_t x = UINT64_MAX;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU");
+ if (r == -ENXIO) {
+ /* EFI case: returns the current value of LoaderConfigTimeoutOneShot. Three cases are distinguished:
+ *
+ * 1. Variable not set, boot into boot loader menu is not enabled (we return UINT64_MAX to the user)
+ * 2. Variable set to "0", boot into boot loader menu is enabled with no timeout (we return 0 to the user)
+ * 3. Variable set to numeric value formatted in ASCII, boot into boot loader menu with the specified timeout in seconds
+ */
+
+ r = efi_loader_get_config_timeout_one_shot(&x);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read LoaderConfigTimeoutOneShot variable, ignoring: %m");
+ }
+
+ } else if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU: %m");
+ else if (r > 0) {
+ _cleanup_free_ char *v = NULL;
+
+ /* Non-EFI case, let's process /run/systemd/reboot-to-boot-loader-menu. */
+
+ r = read_one_line_file("/run/systemd/reboot-to-boot-loader-menu", &v);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read /run/systemd/reboot-to-boot-loader-menu: %m");
+ } else {
+ r = safe_atou64(v, &x);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse /run/systemd/reboot-to-boot-loader-menu: %m");
+ }
+ }
+
+ return sd_bus_message_append(reply, "t", x);
+}
+
+static int method_set_reboot_to_boot_loader_menu(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = ASSERT_PTR(userdata);
+ bool use_efi;
+ uint64_t x;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "t", &x);
+ if (r < 0)
+ return r;
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU");
+ if (r == -ENXIO) {
+ uint64_t features;
+
+ /* EFI case: let's see if booting into boot loader menu is supported. */
+
+ r = efi_loader_get_features(&features);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether reboot to boot loader menu is supported: %m");
+ if (r < 0 || !FLAGS_SET(features, EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Boot loader does not support boot into boot loader menu.");
+
+ use_efi = true;
+
+ } else if (r <= 0) {
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU is set to off */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU: %m");
+
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Boot loader does not support boot into boot loader menu.");
+ } else
+ /* 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);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ if (use_efi) {
+ if (x == UINT64_MAX)
+ r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot), NULL, 0);
+ else {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 1];
+ xsprintf(buf, "%" PRIu64, DIV_ROUND_UP(x, USEC_PER_SEC)); /* second granularity */
+
+ r = efi_set_variable_string(EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot), buf);
+ }
+ if (r < 0)
+ return r;
+ } else {
+ if (x == UINT64_MAX) {
+ if (unlink("/run/systemd/reboot-to-boot-loader-menu") < 0 && errno != ENOENT)
+ return -errno;
+ } else {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 1];
+
+ xsprintf(buf, "%" PRIu64, x); /* μs granularity */
+
+ r = write_string_file_atomic_label("/run/systemd/reboot-to-boot-loader-menu", buf);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_can_reboot_to_boot_loader_menu(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _unused_ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU");
+ if (r == -ENXIO) {
+ uint64_t features = 0;
+
+ /* EFI case, let's see if booting into boot loader menu is supported. */
+
+ r = efi_loader_get_features(&features);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether reboot to boot loader menu is supported: %m");
+ if (r < 0 || !FLAGS_SET(features, EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT))
+ return sd_bus_reply_method_return(message, "s", "na");
+
+ } else if (r <= 0) {
+ /* Non-EFI case: let's trust $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU: %m");
+
+ return sd_bus_reply_method_return(message, "s", "na");
+ }
+
+ return return_test_polkit(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-boot-loader-menu",
+ NULL,
+ UID_INVALID,
+ error);
+}
+
+static int property_get_reboot_to_boot_loader_entry(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_free_ char *v = NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ const char *x = NULL;
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY");
+ if (r == -ENXIO) {
+ /* EFI case: let's read the LoaderEntryOneShot variable */
+
+ r = efi_loader_update_entry_one_shot_cache(&m->efi_loader_entry_one_shot, &m->efi_loader_entry_one_shot_stat);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read LoaderEntryOneShot variable, ignoring: %m");
+ } else
+ x = m->efi_loader_entry_one_shot;
+
+ } else if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY: %m");
+ else if (r > 0) {
+
+ /* Non-EFI case, let's process /run/systemd/reboot-to-boot-loader-entry. */
+
+ r = read_one_line_file("/run/systemd/reboot-to-boot-loader-entry", &v);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read /run/systemd/reboot-to-boot-loader-entry, ignoring: %m");
+ } else if (!efi_loader_entry_name_valid(v))
+ log_warning("/run/systemd/reboot-to-boot-loader-entry is not valid, ignoring.");
+ else
+ x = v;
+ }
+
+ return sd_bus_message_append(reply, "s", x);
+}
+
+static int boot_loader_entry_exists(Manager *m, const char *id) {
+ _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
+ int r;
+
+ assert(m);
+ assert(id);
+
+ r = boot_config_load_auto(&config, NULL, NULL);
+ if (r < 0 && r != -ENOKEY) /* don't complain if no GPT is found, hence skip ENOKEY */
+ return r;
+
+ r = manager_read_efi_boot_loader_entries(m);
+ if (r >= 0)
+ (void) boot_config_augment_from_loader(&config, m->efi_boot_loader_entries, /* auto_only= */ true);
+
+ return !!boot_config_find_entry(&config, id);
+}
+
+static int method_set_reboot_to_boot_loader_entry(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = ASSERT_PTR(userdata);
+ bool use_efi;
+ const char *v;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &v);
+ if (r < 0)
+ return r;
+
+ if (isempty(v))
+ v = NULL;
+ else if (efi_loader_entry_name_valid(v)) {
+ r = boot_loader_entry_exists(m, v);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Boot loader entry '%s' is not known.", v);
+ } else
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Boot loader entry name '%s' is not valid, refusing.", v);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY");
+ if (r == -ENXIO) {
+ uint64_t features;
+
+ /* EFI case: let's see if booting into boot loader entry is supported. */
+
+ r = efi_loader_get_features(&features);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether reboot into boot loader entry is supported: %m");
+ if (r < 0 || !FLAGS_SET(features, EFI_LOADER_FEATURE_ENTRY_ONESHOT))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Loader does not support boot into boot loader entry.");
+
+ use_efi = true;
+
+ } else if (r <= 0) {
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY is set to off */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY: %m");
+
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Loader does not support boot into boot loader entry.");
+ } else
+ /* 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);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ if (use_efi) {
+ if (isempty(v))
+ /* Delete item */
+ r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderEntryOneShot), NULL, 0);
+ else
+ r = efi_set_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), v);
+ if (r < 0)
+ return r;
+ } else {
+ if (isempty(v)) {
+ if (unlink("/run/systemd/reboot-to-boot-loader-entry") < 0 && errno != ENOENT)
+ return -errno;
+ } else {
+ r = write_string_file_atomic_label("/run/systemd/reboot-boot-to-loader-entry", v);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_can_reboot_to_boot_loader_entry(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _unused_ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY");
+ if (r == -ENXIO) {
+ uint64_t features = 0;
+
+ /* EFI case, let's see if booting into boot loader entry is supported. */
+
+ r = efi_loader_get_features(&features);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether reboot to boot loader entry is supported: %m");
+ if (r < 0 || !FLAGS_SET(features, EFI_LOADER_FEATURE_ENTRY_ONESHOT))
+ return sd_bus_reply_method_return(message, "s", "na");
+
+ } else if (r <= 0) {
+ /* Non-EFI case: let's trust $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY: %m");
+
+ return sd_bus_reply_method_return(message, "s", "na");
+ }
+
+ return return_test_polkit(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-boot-loader-entry",
+ NULL,
+ UID_INVALID,
+ error);
+}
+
+static int property_get_boot_loader_entries(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ size_t i;
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = boot_config_load_auto(&config, NULL, NULL);
+ if (r < 0 && r != -ENOKEY) /* don't complain if there's no GPT found */
+ return r;
+
+ r = manager_read_efi_boot_loader_entries(m);
+ if (r >= 0)
+ (void) boot_config_augment_from_loader(&config, m->efi_boot_loader_entries, /* auto_only= */ true);
+
+ r = sd_bus_message_open_container(reply, 'a', "s");
+ if (r < 0)
+ return r;
+
+ for (i = 0; i < config.n_entries; i++) {
+ BootEntry *e = config.entries + i;
+
+ r = sd_bus_message_append(reply, "s", e->id);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
+static int method_set_wall_message(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ int r;
+ Manager *m = ASSERT_PTR(userdata);
+ char *wall_message;
+ int enable_wall_messages;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "sb", &wall_message, &enable_wall_messages);
+ if (r < 0)
+ return r;
+
+ if (strlen(wall_message) > WALL_MESSAGE_MAX)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Wall message too long, maximum permitted length is %u characters.",
+ WALL_MESSAGE_MAX);
+
+ /* Short-circuit the operation if the desired state is already in place, to
+ * avoid an unnecessary polkit permission check. */
+ if (streq_ptr(m->wall_message, empty_to_null(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);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = free_and_strdup(&m->wall_message, empty_to_null(wall_message));
+ if (r < 0)
+ return log_oom();
+
+ m->enable_wall_messages = enable_wall_messages;
+
+ done:
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ const char *who, *why, *what, *mode;
+ _cleanup_free_ char *id = NULL;
+ _cleanup_close_ int fifo_fd = -EBADF;
+ Manager *m = ASSERT_PTR(userdata);
+ InhibitMode mm;
+ InhibitWhat w;
+ pid_t pid;
+ uid_t uid;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "ssss", &what, &who, &why, &mode);
+ if (r < 0)
+ return r;
+
+ w = inhibit_what_from_string(what);
+ if (w <= 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid what specification %s", what);
+
+ mm = inhibit_mode_from_string(mode);
+ if (mm < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid mode specification %s", mode);
+
+ /* Delay is only supported for shutdown/sleep */
+ if (mm == INHIBIT_DELAY && (w & ~(INHIBIT_SHUTDOWN|INHIBIT_SLEEP)))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Delay inhibitors only supported for shutdown and sleep");
+
+ /* Don't allow taking delay locks while we are already
+ * executing the operation. We shouldn't create the impression
+ * that the lock was successful if the machine is about to go
+ * down/suspend any moment. */
+ if (m->delayed_action && m->delayed_action->inhibit_what & w)
+ return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
+ "The operation inhibition has been requested for is already running");
+
+ 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" :
+ w == INHIBIT_HANDLE_POWER_KEY ? "org.freedesktop.login1.inhibit-handle-power-key" :
+ w == INHIBIT_HANDLE_SUSPEND_KEY ? "org.freedesktop.login1.inhibit-handle-suspend-key" :
+ 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,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ 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);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_euid(creds, &uid);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_pid(creds, &pid);
+ 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.",
+ m->inhibitors_max);
+
+ do {
+ id = mfree(id);
+
+ if (asprintf(&id, "%" PRIu64, ++m->inhibit_counter) < 0)
+ return -ENOMEM;
+
+ } while (hashmap_get(m->inhibitors, id));
+
+ _cleanup_(inhibitor_freep) Inhibitor *i = NULL;
+ r = manager_add_inhibitor(m, id, &i);
+ if (r < 0)
+ return r;
+
+ i->what = w;
+ i->mode = mm;
+ i->pid = TAKE_PIDREF(pidref);
+ i->uid = uid;
+ i->why = strdup(why);
+ i->who = strdup(who);
+
+ if (!i->why || !i->who)
+ return -ENOMEM;
+
+ fifo_fd = inhibitor_create_fifo(i);
+ if (fifo_fd < 0)
+ return fifo_fd;
+
+ r = inhibitor_start(i);
+ if (r < 0)
+ return r;
+ TAKE_PTR(i);
+
+ return sd_bus_reply_method_return(message, "h", fifo_fd);
+}
+
+static const sd_bus_vtable manager_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ SD_BUS_WRITABLE_PROPERTY("EnableWallMessages", "b", bus_property_get_bool, bus_property_set_bool, offsetof(Manager, enable_wall_messages), 0),
+ SD_BUS_WRITABLE_PROPERTY("WallMessage", "s", NULL, NULL, offsetof(Manager, wall_message), 0),
+
+ SD_BUS_PROPERTY("NAutoVTs", "u", NULL, offsetof(Manager, n_autovts), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("KillOnlyUsers", "as", NULL, offsetof(Manager, kill_only_users), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("KillExcludeUsers", "as", NULL, offsetof(Manager, kill_exclude_users), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("KillUserProcesses", "b", bus_property_get_bool, offsetof(Manager, kill_user_processes), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RebootParameter", "s", property_get_reboot_parameter, 0, 0),
+ SD_BUS_PROPERTY("RebootToFirmwareSetup", "b", property_get_reboot_to_firmware_setup, 0, 0),
+ SD_BUS_PROPERTY("RebootToBootLoaderMenu", "t", property_get_reboot_to_boot_loader_menu, 0, 0),
+ SD_BUS_PROPERTY("RebootToBootLoaderEntry", "s", property_get_reboot_to_boot_loader_entry, 0, 0),
+ SD_BUS_PROPERTY("BootLoaderEntries", "as", property_get_boot_loader_entries, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("IdleHint", "b", property_get_idle_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IdleSinceHint", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IdleSinceHintMonotonic", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("BlockInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ 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("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),
+ SD_BUS_PROPERTY("HandleRebootKeyLongPress", "s", property_get_handle_action, offsetof(Manager, handle_reboot_key_long_press), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HandleSuspendKey", "s", property_get_handle_action, offsetof(Manager, handle_suspend_key), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HandleSuspendKeyLongPress", "s", property_get_handle_action, offsetof(Manager, handle_suspend_key_long_press), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HandleHibernateKey", "s", property_get_handle_action, offsetof(Manager, handle_hibernate_key), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HandleHibernateKeyLongPress", "s", property_get_handle_action, offsetof(Manager, handle_hibernate_key_long_press), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HandleLidSwitch", "s", property_get_handle_action, offsetof(Manager, handle_lid_switch), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HandleLidSwitchExternalPower", "s", property_get_handle_action, offsetof(Manager, handle_lid_switch_ep), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HandleLidSwitchDocked", "s", property_get_handle_action, offsetof(Manager, handle_lid_switch_docked), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("HoldoffTimeoutUSec", "t", NULL, offsetof(Manager, holdoff_timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("IdleAction", "s", property_get_handle_action, offsetof(Manager, idle_action), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("IdleActionUSec", "t", NULL, offsetof(Manager, idle_action_usec), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("PreparingForShutdown", "b", property_get_preparing, 0, 0),
+ SD_BUS_PROPERTY("PreparingForSleep", "b", property_get_preparing, 0, 0),
+ SD_BUS_PROPERTY("ScheduledShutdown", "(st)", property_get_scheduled_shutdown, 0, 0),
+ SD_BUS_PROPERTY("Docked", "b", property_get_docked, 0, 0),
+ SD_BUS_PROPERTY("LidClosed", "b", property_get_lid_closed, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("OnExternalPower", "b", property_get_on_external_power, 0, 0),
+ SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(Manager, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RuntimeDirectorySize", "t", NULL, offsetof(Manager, runtime_dir_size), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RuntimeDirectoryInodesMax", "t", NULL, offsetof(Manager, runtime_dir_inodes), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("InhibitorsMax", "t", NULL, offsetof(Manager, inhibitors_max), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("NCurrentInhibitors", "t", property_get_hashmap_size, offsetof(Manager, inhibitors), 0),
+ SD_BUS_PROPERTY("SessionsMax", "t", NULL, offsetof(Manager, sessions_max), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("NCurrentSessions", "t", property_get_hashmap_size, offsetof(Manager, sessions), 0),
+ SD_BUS_PROPERTY("UserTasksMax", "t", property_get_compat_user_tasks_max, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+ SD_BUS_PROPERTY("StopIdleSessionUSec", "t", NULL, offsetof(Manager, stop_idle_session_usec), SD_BUS_VTABLE_PROPERTY_CONST),
+
+ SD_BUS_METHOD_WITH_ARGS("GetSession",
+ SD_BUS_ARGS("s", session_id),
+ SD_BUS_RESULT("o", object_path),
+ method_get_session,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetSessionByPID",
+ SD_BUS_ARGS("u", pid),
+ SD_BUS_RESULT("o", object_path),
+ method_get_session_by_pid,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetUser",
+ SD_BUS_ARGS("u", uid),
+ SD_BUS_RESULT("o", object_path),
+ method_get_user,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetUserByPID",
+ SD_BUS_ARGS("u", pid),
+ SD_BUS_RESULT("o", object_path),
+ method_get_user_by_pid,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetSeat",
+ SD_BUS_ARGS("s", seat_id),
+ SD_BUS_RESULT("o", object_path),
+ method_get_seat,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ListSessions",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("a(susso)", sessions),
+ method_list_sessions,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ListUsers",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("a(uso)", users),
+ method_list_users,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ListSeats",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("a(so)", seats),
+ method_list_seats,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ListInhibitors",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("a(ssssuu)", inhibitors),
+ method_list_inhibitors,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CreateSession",
+ SD_BUS_ARGS("u", uid,
+ "u", pid,
+ "s", service,
+ "s", type,
+ "s", class,
+ "s", desktop,
+ "s", seat_id,
+ "u", vtnr,
+ "s", tty,
+ "s", display,
+ "b", remote,
+ "s", remote_user,
+ "s", remote_host,
+ "a(sv)", properties),
+ SD_BUS_RESULT("s", session_id,
+ "o", object_path,
+ "s", runtime_path,
+ "h", fifo_fd,
+ "u", uid,
+ "s", seat_id,
+ "u", vtnr,
+ "b", existing),
+ method_create_session,
+ 0),
+ SD_BUS_METHOD_WITH_ARGS("CreateSessionWithPIDFD",
+ SD_BUS_ARGS("u", uid,
+ "h", pidfd,
+ "s", service,
+ "s", type,
+ "s", class,
+ "s", desktop,
+ "s", seat_id,
+ "u", vtnr,
+ "s", tty,
+ "s", display,
+ "b", remote,
+ "s", remote_user,
+ "s", remote_host,
+ "t", flags,
+ "a(sv)", properties),
+ SD_BUS_RESULT("s", session_id,
+ "o", object_path,
+ "s", runtime_path,
+ "h", fifo_fd,
+ "u", uid,
+ "s", seat_id,
+ "u", vtnr,
+ "b", existing),
+ method_create_session_pidfd,
+ 0),
+ SD_BUS_METHOD_WITH_ARGS("ReleaseSession",
+ SD_BUS_ARGS("s", session_id),
+ SD_BUS_NO_RESULT,
+ method_release_session,
+ 0),
+ SD_BUS_METHOD_WITH_ARGS("ActivateSession",
+ SD_BUS_ARGS("s", session_id),
+ SD_BUS_NO_RESULT,
+ method_activate_session,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ActivateSessionOnSeat",
+ SD_BUS_ARGS("s", session_id, "s", seat_id),
+ SD_BUS_NO_RESULT,
+ method_activate_session_on_seat,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("LockSession",
+ SD_BUS_ARGS("s", session_id),
+ SD_BUS_NO_RESULT,
+ method_lock_session,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("UnlockSession",
+ SD_BUS_ARGS("s", session_id),
+ SD_BUS_NO_RESULT,
+ method_lock_session,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("LockSessions",
+ NULL,
+ NULL,
+ method_lock_sessions,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("UnlockSessions",
+ NULL,
+ NULL,
+ method_lock_sessions,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("KillSession",
+ SD_BUS_ARGS("s", session_id, "s", who, "i", signal_number),
+ SD_BUS_NO_RESULT,
+ method_kill_session,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("KillUser",
+ SD_BUS_ARGS("u", uid, "i", signal_number),
+ SD_BUS_NO_RESULT,
+ method_kill_user,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("TerminateSession",
+ SD_BUS_ARGS("s", session_id),
+ SD_BUS_NO_RESULT,
+ method_terminate_session,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("TerminateUser",
+ SD_BUS_ARGS("u", uid),
+ SD_BUS_NO_RESULT,
+ method_terminate_user,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("TerminateSeat",
+ SD_BUS_ARGS("s", seat_id),
+ SD_BUS_NO_RESULT,
+ method_terminate_seat,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetUserLinger",
+ SD_BUS_ARGS("u", uid, "b", enable, "b", interactive),
+ SD_BUS_NO_RESULT,
+ method_set_user_linger,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("AttachDevice",
+ SD_BUS_ARGS("s", seat_id, "s", sysfs_path, "b", interactive),
+ SD_BUS_NO_RESULT,
+ method_attach_device,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("FlushDevices",
+ SD_BUS_ARGS("b", interactive),
+ SD_BUS_NO_RESULT,
+ method_flush_devices,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("PowerOff",
+ SD_BUS_ARGS("b", interactive),
+ SD_BUS_NO_RESULT,
+ method_poweroff,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("PowerOffWithFlags",
+ SD_BUS_ARGS("t", flags),
+ SD_BUS_NO_RESULT,
+ method_poweroff,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Reboot",
+ SD_BUS_ARGS("b", interactive),
+ SD_BUS_NO_RESULT,
+ method_reboot,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("RebootWithFlags",
+ SD_BUS_ARGS("t", flags),
+ SD_BUS_NO_RESULT,
+ method_reboot,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Halt",
+ SD_BUS_ARGS("b", interactive),
+ SD_BUS_NO_RESULT,
+ method_halt,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("HaltWithFlags",
+ SD_BUS_ARGS("t", flags),
+ SD_BUS_NO_RESULT,
+ method_halt,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Suspend",
+ SD_BUS_ARGS("b", interactive),
+ SD_BUS_NO_RESULT,
+ method_suspend,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SuspendWithFlags",
+ SD_BUS_ARGS("t", flags),
+ SD_BUS_NO_RESULT,
+ method_suspend,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Hibernate",
+ SD_BUS_ARGS("b", interactive),
+ SD_BUS_NO_RESULT,
+ method_hibernate,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("HibernateWithFlags",
+ SD_BUS_ARGS("t", flags),
+ SD_BUS_NO_RESULT,
+ method_hibernate,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("HybridSleep",
+ SD_BUS_ARGS("b", interactive),
+ SD_BUS_NO_RESULT,
+ method_hybrid_sleep,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("HybridSleepWithFlags",
+ SD_BUS_ARGS("t", flags),
+ SD_BUS_NO_RESULT,
+ method_hybrid_sleep,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SuspendThenHibernate",
+ SD_BUS_ARGS("b", interactive),
+ SD_BUS_NO_RESULT,
+ method_suspend_then_hibernate,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SuspendThenHibernateWithFlags",
+ SD_BUS_ARGS("t", flags),
+ SD_BUS_NO_RESULT,
+ method_suspend_then_hibernate,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanPowerOff",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_poweroff,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanReboot",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_reboot,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanHalt",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_halt,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanSuspend",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_suspend,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanHibernate",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_hibernate,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanHybridSleep",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_hybrid_sleep,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanSuspendThenHibernate",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_suspend_then_hibernate,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ScheduleShutdown",
+ SD_BUS_ARGS("s", type, "t", usec),
+ SD_BUS_NO_RESULT,
+ method_schedule_shutdown,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CancelScheduledShutdown",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("b", cancelled),
+ method_cancel_scheduled_shutdown,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Inhibit",
+ SD_BUS_ARGS("s", what, "s", who, "s", why, "s", mode),
+ SD_BUS_RESULT("h", pipe_fd),
+ method_inhibit,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanRebootParameter",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_reboot_parameter,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetRebootParameter",
+ SD_BUS_ARGS("s", parameter),
+ SD_BUS_NO_RESULT,
+ method_set_reboot_parameter,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanRebootToFirmwareSetup",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_reboot_to_firmware_setup,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetRebootToFirmwareSetup",
+ SD_BUS_ARGS("b", enable),
+ SD_BUS_NO_RESULT,
+ method_set_reboot_to_firmware_setup,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanRebootToBootLoaderMenu",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_reboot_to_boot_loader_menu,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetRebootToBootLoaderMenu",
+ SD_BUS_ARGS("t", timeout),
+ SD_BUS_NO_RESULT,
+ method_set_reboot_to_boot_loader_menu,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("CanRebootToBootLoaderEntry",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", result),
+ method_can_reboot_to_boot_loader_entry,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetRebootToBootLoaderEntry",
+ SD_BUS_ARGS("s", boot_loader_entry),
+ SD_BUS_NO_RESULT,
+ method_set_reboot_to_boot_loader_entry,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetWallMessage",
+ SD_BUS_ARGS("s", wall_message, "b", enable),
+ SD_BUS_NO_RESULT,
+ method_set_wall_message,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
+ SD_BUS_SIGNAL_WITH_ARGS("SessionNew",
+ SD_BUS_ARGS("s", session_id, "o", object_path),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("SessionRemoved",
+ SD_BUS_ARGS("s", session_id, "o", object_path),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("UserNew",
+ SD_BUS_ARGS("u", uid, "o", object_path),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("UserRemoved",
+ SD_BUS_ARGS("u", uid, "o", object_path),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("SeatNew",
+ SD_BUS_ARGS("s", seat_id, "o", object_path),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("SeatRemoved",
+ SD_BUS_ARGS("s", seat_id, "o", object_path),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("PrepareForShutdown",
+ SD_BUS_ARGS("b", start),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("PrepareForShutdownWithMetadata",
+ SD_BUS_ARGS("b", start, "a{sv}", metadata),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("PrepareForSleep",
+ SD_BUS_ARGS("b", start),
+ 0),
+
+ SD_BUS_VTABLE_END
+};
+
+const BusObjectImplementation manager_object = {
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ .vtables = BUS_VTABLES(manager_vtable),
+ .children = BUS_IMPLEMENTATIONS(&seat_object,
+ &session_object,
+ &user_object),
+};
+
+static int session_jobs_reply(Session *s, uint32_t jid, const char *unit, const char *result) {
+ assert(s);
+ assert(unit);
+
+ if (!s->started)
+ return 0;
+
+ 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);
+ }
+
+ return session_send_create_reply(s, 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;
+ uint32_t id;
+ User *user;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "uoss", &id, &path, &unit, &result);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ return 0;
+ }
+
+ if (m->action_job && streq(m->action_job, path)) {
+ assert(m->delayed_action);
+ log_info("Operation '%s' finished.", handle_action_to_string(m->delayed_action->handle));
+
+ /* Tell people that they now may take a lock again */
+ (void) send_prepare_for(m, m->delayed_action, false);
+
+ m->action_job = mfree(m->action_job);
+ m->delayed_action = NULL;
+ return 0;
+ }
+
+ 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_save(session);
+ user_save(session->user);
+ }
+
+ session_add_to_gc_queue(session);
+ }
+
+ 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);
+ }
+
+ user_add_to_gc_queue(user);
+ }
+
+ return 0;
+}
+
+int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ const char *path, *unit;
+ Manager *m = ASSERT_PTR(userdata);
+ Session *session;
+ User *user;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "so", &unit, &path);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ return 0;
+ }
+
+ session = hashmap_get(m->session_units, unit);
+ if (session)
+ session_add_to_gc_queue(session);
+
+ user = hashmap_get(m->user_units, unit);
+ if (user)
+ user_add_to_gc_queue(user);
+
+ return 0;
+}
+
+int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *unit = NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ const char *path;
+ Session *session;
+ User *user;
+ int r;
+
+ assert(message);
+
+ path = sd_bus_message_get_path(message);
+ if (!path)
+ return 0;
+
+ r = unit_name_from_dbus_path(path, &unit);
+ if (r == -EINVAL) /* not a unit */
+ return 0;
+ if (r < 0) {
+ log_oom();
+ return 0;
+ }
+
+ session = hashmap_get(m->session_units, unit);
+ if (session)
+ session_add_to_gc_queue(session);
+
+ user = hashmap_get(m->user_units, unit);
+ if (user)
+ user_add_to_gc_queue(user);
+
+ return 0;
+}
+
+int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ Session *session;
+ int b, r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "b", &b);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ return 0;
+ }
+
+ if (b)
+ return 0;
+
+ /* systemd finished reloading, let's recheck all our sessions */
+ log_debug("System manager has been reloaded, rechecking sessions...");
+
+ HASHMAP_FOREACH(session, m->sessions)
+ session_add_to_gc_queue(session);
+
+ return 0;
+}
+
+int manager_send_changed(Manager *manager, const char *property, ...) {
+ char **l;
+
+ assert(manager);
+
+ l = strv_from_stdarg_alloca(property);
+
+ return sd_bus_emit_properties_changed_strv(
+ manager->bus,
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ 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,
+ 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) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ int r;
+
+ assert(manager);
+ assert(scope);
+ assert(pidref_is_set(pidref));
+ assert(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");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, 'a', "(sv)");
+ if (r < 0)
+ return r;
+
+ if (!isempty(slice)) {
+ r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice);
+ if (r < 0)
+ return r;
+ }
+
+ if (!isempty(description)) {
+ r = sd_bus_message_append(m, "(sv)", "Description", "s", description);
+ if (r < 0)
+ return r;
+ }
+
+ STRV_FOREACH(i, wants) {
+ r = sd_bus_message_append(m, "(sv)", "Wants", "as", 1, *i);
+ if (r < 0)
+ return r;
+ }
+
+ STRV_FOREACH(i, after) {
+ r = sd_bus_message_append(m, "(sv)", "After", "as", 1, *i);
+ if (r < 0)
+ return r;
+ }
+
+ if (!empty_or_root(requires_mounts_for)) {
+ r = sd_bus_message_append(m, "(sv)", "RequiresMountsFor", "as", 1, requires_mounts_for);
+ if (r < 0)
+ return r;
+ }
+
+ /* Make sure that the session shells are terminated with SIGHUP since bash and friends tend to ignore
+ * SIGTERM */
+ r = sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", true);
+ if (r < 0)
+ return r;
+
+ r = bus_append_scope_pidref(m, pidref);
+ if (r < 0)
+ return r;
+
+ /* For login session scopes, if a process is OOM killed by the kernel, *don't* terminate the rest of
+ the scope */
+ r = sd_bus_message_append(m, "(sv)", "OOMPolicy", "s", "continue");
+ if (r < 0)
+ return r;
+
+ /* disable TasksMax= for the session scope, rely on the slice setting for it */
+ r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_MAX);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ if (more_properties) {
+ /* If TasksMax also appears here, it will overwrite the default value set above */
+ r = sd_bus_message_copy(m, more_properties, true);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "a(sa(sv))", 0);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call(manager->bus, m, 0, error, &reply);
+ if (r < 0)
+ return r;
+
+ return strdup_job(reply, job);
+}
+
+int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ assert(manager);
+ assert(unit);
+ assert(job);
+
+ r = bus_call_method(
+ manager->bus,
+ bus_systemd_mgr,
+ "StartUnit",
+ error,
+ &reply,
+ "ss", unit, "replace");
+ if (r < 0)
+ return r;
+
+ return strdup_job(reply, job);
+}
+
+int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode, sd_bus_error *error, char **ret_job) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ assert(manager);
+ assert(unit);
+ assert(ret_job);
+
+ r = bus_call_method(
+ manager->bus,
+ bus_systemd_mgr,
+ "StopUnit",
+ 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)) {
+
+ *ret_job = NULL;
+ sd_bus_error_free(error);
+ return 0;
+ }
+
+ return r;
+ }
+
+ return strdup_job(reply, ret_job);
+}
+
+int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *ret_error) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ assert(manager);
+ assert(scope);
+
+ path = unit_dbus_path_from_name(scope);
+ if (!path)
+ return -ENOMEM;
+
+ r = sd_bus_call_method(
+ manager->bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Scope",
+ "Abandon",
+ &error,
+ NULL,
+ NULL);
+ if (r < 0) {
+ if (sd_bus_error_has_names(&error, BUS_ERROR_NO_SUCH_UNIT,
+ BUS_ERROR_LOAD_FAILED,
+ BUS_ERROR_SCOPE_NOT_RUNNING))
+ return 0;
+
+ sd_bus_error_move(ret_error, &error);
+ return r;
+ }
+
+ return 1;
+}
+
+int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, sd_bus_error *error) {
+ assert(manager);
+ assert(unit);
+
+ return bus_call_method(
+ manager->bus,
+ bus_systemd_mgr,
+ "KillUnit",
+ error,
+ NULL,
+ "ssi", unit, who == KILL_LEADER ? "main" : "all", signo);
+}
+
+int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *ret_error) {
+ _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 *path = NULL;
+ const char *state;
+ int r;
+
+ assert(manager);
+ assert(unit);
+
+ path = unit_dbus_path_from_name(unit);
+ if (!path)
+ return -ENOMEM;
+
+ r = sd_bus_get_property(
+ manager->bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit",
+ "ActiveState",
+ &error,
+ &reply,
+ "s");
+ if (r < 0) {
+ /* systemd might have dropped off momentarily, let's
+ * not make this an error */
+ if (sd_bus_error_has_names(&error, SD_BUS_ERROR_NO_REPLY,
+ SD_BUS_ERROR_DISCONNECTED))
+ return true;
+
+ /* If the unit is already unloaded then it's not
+ * active */
+ if (sd_bus_error_has_names(&error, BUS_ERROR_NO_SUCH_UNIT,
+ BUS_ERROR_LOAD_FAILED))
+ return false;
+
+ sd_bus_error_move(ret_error, &error);
+ return r;
+ }
+
+ r = sd_bus_message_read(reply, "s", &state);
+ if (r < 0)
+ return r;
+
+ return !STR_IN_SET(state, "inactive", "failed");
+}
+
+int manager_job_is_active(Manager *manager, const char *path, sd_bus_error *ret_error) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ assert(manager);
+ assert(path);
+
+ r = sd_bus_get_property(
+ manager->bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Job",
+ "State",
+ &error,
+ &reply,
+ "s");
+ if (r < 0) {
+ if (sd_bus_error_has_names(&error, SD_BUS_ERROR_NO_REPLY,
+ SD_BUS_ERROR_DISCONNECTED))
+ return true;
+
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT))
+ return false;
+
+ sd_bus_error_move(ret_error, &error);
+ return r;
+ }
+
+ /* We don't actually care about the state really. The fact
+ * that we could read the job state is enough for us */
+
+ return true;
+}
diff --git a/src/login/logind-dbus.h b/src/login/logind-dbus.h
new file mode 100644
index 0000000..c9d5923
--- /dev/null
+++ b/src/login/logind-dbus.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "bus-object.h"
+#include "logind-action.h"
+#include "logind-session.h"
+#include "logind-user.h"
+#include "logind.h"
+
+int manager_get_session_from_creds(Manager *m, sd_bus_message *message, const char *name, sd_bus_error *error, Session **ret);
+int manager_get_user_from_creds(Manager *m, sd_bus_message *message, uid_t uid, sd_bus_error *error, User **ret);
+int manager_get_seat_from_creds(Manager *m, sd_bus_message *message, const char *name, sd_bus_error *error, Seat **ret);
+
+int manager_dispatch_delayed(Manager *manager, bool timeout);
+
+int bus_manager_shutdown_or_sleep_now_or_later(Manager *m, const HandleActionData *a, sd_bus_error *error);
+
+int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error);
+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_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);
+int manager_job_is_active(Manager *manager, const char *path, sd_bus_error *error);
+
+void manager_load_scheduled_shutdown(Manager *m);
+
+extern const BusObjectImplementation manager_object;
diff --git a/src/login/logind-device.c b/src/login/logind-device.c
new file mode 100644
index 0000000..376cd0d
--- /dev/null
+++ b/src/login/logind-device.c
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <string.h>
+
+#include "alloc-util.h"
+#include "logind-device.h"
+#include "logind-seat-dbus.h"
+
+Device* device_new(Manager *m, const char *sysfs, bool master) {
+ Device *d;
+
+ assert(m);
+ assert(sysfs);
+
+ d = new0(Device, 1);
+ if (!d)
+ return NULL;
+
+ d->sysfs = strdup(sysfs);
+ if (!d->sysfs)
+ return mfree(d);
+
+ if (hashmap_put(m->devices, d->sysfs, d) < 0) {
+ free(d->sysfs);
+ return mfree(d);
+ }
+
+ d->manager = m;
+ d->master = master;
+ dual_timestamp_now(&d->timestamp);
+
+ return d;
+}
+
+static void device_detach(Device *d) {
+ Seat *s;
+ SessionDevice *sd;
+
+ assert(d);
+
+ if (!d->seat)
+ return;
+
+ while ((sd = d->session_devices))
+ session_device_free(sd);
+
+ s = d->seat;
+ LIST_REMOVE(devices, d->seat->devices, d);
+ d->seat = NULL;
+
+ if (!seat_has_master_device(s)) {
+ seat_add_to_gc_queue(s);
+ seat_send_changed(s, "CanGraphical", NULL);
+ }
+}
+
+void device_free(Device *d) {
+ assert(d);
+
+ device_detach(d);
+
+ hashmap_remove(d->manager->devices, d->sysfs);
+
+ free(d->sysfs);
+ free(d);
+}
+
+void device_attach(Device *d, Seat *s) {
+ bool had_master;
+
+ assert(d);
+ assert(s);
+
+ if (d->seat == s)
+ return;
+
+ if (d->seat)
+ device_detach(d);
+
+ d->seat = s;
+ had_master = seat_has_master_device(s);
+
+ /* We keep the device list sorted by the "master" flag. That is, master
+ * devices are at the front, other devices at the tail. As there is no
+ * way to easily add devices at the list-tail, we need to iterate the
+ * list to find the first non-master device when adding non-master
+ * devices. We assume there is only a few (normally 1) master devices
+ * per seat, so we iterate only a few times. */
+
+ if (d->master || !s->devices)
+ LIST_PREPEND(devices, s->devices, d);
+ else
+ LIST_FOREACH(devices, i, s->devices) {
+ if (!i->devices_next || !i->master) {
+ LIST_INSERT_AFTER(devices, s->devices, i, d);
+ break;
+ }
+ }
+
+ if (!had_master && d->master && s->started) {
+ seat_save(s);
+ seat_send_changed(s, "CanGraphical", NULL);
+ }
+}
diff --git a/src/login/logind-device.h b/src/login/logind-device.h
new file mode 100644
index 0000000..0d89613
--- /dev/null
+++ b/src/login/logind-device.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct Device Device;
+
+#include "list.h"
+#include "logind-seat.h"
+#include "logind-session-device.h"
+
+struct Device {
+ Manager *manager;
+
+ char *sysfs;
+ Seat *seat;
+ bool master;
+
+ dual_timestamp timestamp;
+
+ LIST_FIELDS(struct Device, devices);
+ LIST_HEAD(SessionDevice, session_devices);
+};
+
+Device* device_new(Manager *m, const char *sysfs, bool master);
+void device_free(Device *d);
+void device_attach(Device *d, Seat *s);
diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf
new file mode 100644
index 0000000..c95a3b2
--- /dev/null
+++ b/src/login/logind-gperf.gperf
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+%{
+#if __GNUC__ >= 7
+_Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
+#endif
+#include <stddef.h>
+#include "conf-parser.h"
+#include "logind.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name logind_gperf_hash
+%define lookup-function-name logind_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Login.NAutoVTs, config_parse_n_autovts, 0, offsetof(Manager, n_autovts)
+Login.ReserveVT, config_parse_unsigned, 0, offsetof(Manager, reserve_vt)
+Login.KillUserProcesses, config_parse_bool, 0, offsetof(Manager, kill_user_processes)
+Login.KillOnlyUsers, config_parse_strv, 0, offsetof(Manager, kill_only_users)
+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.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)
+Login.HandleRebootKeyLongPress, config_parse_handle_action, 0, offsetof(Manager, handle_reboot_key_long_press)
+Login.HandleSuspendKey, config_parse_handle_action, 0, offsetof(Manager, handle_suspend_key)
+Login.HandleSuspendKeyLongPress, config_parse_handle_action, 0, offsetof(Manager, handle_suspend_key_long_press)
+Login.HandleHibernateKey, config_parse_handle_action, 0, offsetof(Manager, handle_hibernate_key)
+Login.HandleHibernateKeyLongPress, config_parse_handle_action, 0, offsetof(Manager, handle_hibernate_key_long_press)
+Login.HandleLidSwitch, config_parse_handle_action, 0, offsetof(Manager, handle_lid_switch)
+Login.HandleLidSwitchExternalPower, config_parse_handle_action, 0, offsetof(Manager, handle_lid_switch_ep)
+Login.HandleLidSwitchDocked, config_parse_handle_action, 0, offsetof(Manager, handle_lid_switch_docked)
+Login.PowerKeyIgnoreInhibited, config_parse_bool, 0, offsetof(Manager, power_key_ignore_inhibited)
+Login.SuspendKeyIgnoreInhibited, config_parse_bool, 0, offsetof(Manager, suspend_key_ignore_inhibited)
+Login.HibernateKeyIgnoreInhibited, config_parse_bool, 0, offsetof(Manager, hibernate_key_ignore_inhibited)
+Login.LidSwitchIgnoreInhibited, config_parse_bool, 0, offsetof(Manager, lid_switch_ignore_inhibited)
+Login.RebootKeyIgnoreInhibited, config_parse_bool, 0, offsetof(Manager, reboot_key_ignore_inhibited)
+Login.HoldoffTimeoutSec, config_parse_sec, 0, offsetof(Manager, holdoff_timeout_usec)
+Login.IdleAction, config_parse_handle_action, 0, offsetof(Manager, idle_action)
+Login.IdleActionSec, config_parse_sec, 0, offsetof(Manager, idle_action_usec)
+Login.RuntimeDirectorySize, config_parse_tmpfs_size, 0, offsetof(Manager, runtime_dir_size)
+Login.RuntimeDirectoryInodesMax, config_parse_iec_uint64, 0, offsetof(Manager, runtime_dir_inodes)
+Login.RemoveIPC, config_parse_bool, 0, offsetof(Manager, remove_ipc)
+Login.InhibitorsMax, config_parse_uint64, 0, offsetof(Manager, inhibitors_max)
+Login.SessionsMax, config_parse_uint64, 0, offsetof(Manager, sessions_max)
+Login.UserTasksMax, config_parse_compat_user_tasks_max, 0, 0
+Login.StopIdleSessionSec, config_parse_sec_fix_0, 0, offsetof(Manager, stop_idle_session_usec)
diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c
new file mode 100644
index 0000000..1566dab
--- /dev/null
+++ b/src/login/logind-inhibit.c
@@ -0,0 +1,532 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "env-file.h"
+#include "errno-list.h"
+#include "errno-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "io-util.h"
+#include "logind-dbus.h"
+#include "logind-inhibit.h"
+#include "missing_threads.h"
+#include "mkdir-label.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "tmpfile-util.h"
+#include "user-util.h"
+
+static void inhibitor_remove_fifo(Inhibitor *i);
+
+int inhibitor_new(Inhibitor **ret, Manager *m, const char* id) {
+ _cleanup_(inhibitor_freep) Inhibitor *i = NULL;
+ int r;
+
+ assert(ret);
+ assert(m);
+ assert(id);
+
+ i = new(Inhibitor, 1);
+ if (!i)
+ return -ENOMEM;
+
+ *i = (Inhibitor) {
+ .manager = m,
+ .what = _INHIBIT_WHAT_INVALID,
+ .mode = _INHIBIT_MODE_INVALID,
+ .uid = UID_INVALID,
+ .fifo_fd = -EBADF,
+ .pid = PIDREF_NULL,
+ };
+
+ i->state_file = path_join("/run/systemd/inhibit", id);
+ if (!i->state_file)
+ return -ENOMEM;
+
+ i->id = basename(i->state_file);
+
+ r = hashmap_put(m->inhibitors, i->id, i);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(i);
+ return 0;
+}
+
+Inhibitor* inhibitor_free(Inhibitor *i) {
+
+ if (!i)
+ return NULL;
+
+ free(i->who);
+ free(i->why);
+
+ sd_event_source_unref(i->event_source);
+ safe_close(i->fifo_fd);
+
+ hashmap_remove(i->manager->inhibitors, i->id);
+
+ /* 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);
+
+ pidref_done(&i->pid);
+
+ return mfree(i);
+}
+
+static int inhibitor_save(Inhibitor *i) {
+ _cleanup_(unlink_and_freep) char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(i);
+
+ r = mkdir_safe_label("/run/systemd/inhibit", 0755, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ goto fail;
+
+ r = fopen_temporary(i->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "WHAT=%s\n"
+ "MODE=%s\n"
+ "UID="UID_FMT"\n"
+ "PID="PID_FMT"\n",
+ inhibit_what_to_string(i->what),
+ inhibit_mode_to_string(i->mode),
+ i->uid,
+ i->pid.pid);
+
+ if (i->who) {
+ _cleanup_free_ char *cc = NULL;
+
+ cc = cescape(i->who);
+ if (!cc) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "WHO=%s\n", cc);
+ }
+
+ if (i->why) {
+ _cleanup_free_ char *cc = NULL;
+
+ cc = cescape(i->why);
+ if (!cc) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "WHY=%s\n", cc);
+ }
+
+ if (i->fifo_path)
+ fprintf(f, "FIFO=%s\n", i->fifo_path);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, i->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ temp_path = mfree(temp_path);
+ return 0;
+
+fail:
+ (void) unlink(i->state_file);
+
+ return log_error_errno(r, "Failed to save inhibit data %s: %m", i->state_file);
+}
+
+static int bus_manager_send_inhibited_change(Inhibitor *i) {
+ const char *property;
+
+ assert(i);
+
+ property = i->mode == INHIBIT_BLOCK ? "BlockInhibited" : "DelayInhibited";
+
+ return manager_send_changed(i->manager, property, NULL);
+}
+
+int inhibitor_start(Inhibitor *i) {
+ assert(i);
+
+ if (i->started)
+ return 0;
+
+ dual_timestamp_now(&i->since);
+
+ log_debug("Inhibitor %s (%s) pid="PID_FMT" uid="UID_FMT" mode=%s started.",
+ strna(i->who), strna(i->why),
+ i->pid.pid, i->uid,
+ inhibit_mode_to_string(i->mode));
+
+ i->started = true;
+
+ inhibitor_save(i);
+
+ bus_manager_send_inhibited_change(i);
+
+ return 0;
+}
+
+void inhibitor_stop(Inhibitor *i) {
+ assert(i);
+
+ if (i->started)
+ log_debug("Inhibitor %s (%s) pid="PID_FMT" uid="UID_FMT" mode=%s stopped.",
+ strna(i->who), strna(i->why),
+ i->pid.pid, i->uid,
+ inhibit_mode_to_string(i->mode));
+
+ inhibitor_remove_fifo(i);
+
+ if (i->state_file)
+ (void) unlink(i->state_file);
+
+ i->started = false;
+
+ bus_manager_send_inhibited_change(i);
+}
+
+int inhibitor_load(Inhibitor *i) {
+ _cleanup_free_ char *what = NULL, *uid = NULL, *pid = NULL, *who = NULL, *why = NULL, *mode = NULL;
+ InhibitWhat w;
+ InhibitMode mm;
+ char *cc;
+ ssize_t l;
+ int r;
+
+ r = parse_env_file(NULL, i->state_file,
+ "WHAT", &what,
+ "UID", &uid,
+ "PID", &pid,
+ "WHO", &who,
+ "WHY", &why,
+ "MODE", &mode,
+ "FIFO", &i->fifo_path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read %s: %m", i->state_file);
+
+ w = what ? inhibit_what_from_string(what) : 0;
+ if (w >= 0)
+ i->what = w;
+
+ mm = mode ? inhibit_mode_from_string(mode) : INHIBIT_BLOCK;
+ if (mm >= 0)
+ i->mode = mm;
+
+ if (uid) {
+ r = parse_uid(uid, &i->uid);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse UID of inhibitor: %s", uid);
+ }
+
+ if (pid) {
+ pidref_done(&i->pid);
+ r = pidref_set_pidstr(&i->pid, pid);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse PID of inhibitor: %s", pid);
+ }
+
+ if (who) {
+ l = cunescape(who, 0, &cc);
+ if (l < 0)
+ return log_debug_errno(l, "Failed to unescape \"who\" of inhibitor: %m");
+
+ free_and_replace(i->who, cc);
+ }
+
+ if (why) {
+ l = cunescape(why, 0, &cc);
+ if (l < 0)
+ return log_debug_errno(l, "Failed to unescape \"why\" of inhibitor: %m");
+
+ free_and_replace(i->why, cc);
+ }
+
+ 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 */
+ fd = inhibitor_create_fifo(i);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to reopen FIFO: %m");
+ }
+
+ return 0;
+}
+
+static int inhibitor_dispatch_fifo(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Inhibitor *i = ASSERT_PTR(userdata);
+
+ assert(s);
+ assert(fd == i->fifo_fd);
+
+ inhibitor_stop(i);
+ inhibitor_free(i);
+
+ return 0;
+}
+
+int inhibitor_create_fifo(Inhibitor *i) {
+ int r;
+
+ assert(i);
+
+ /* Create FIFO */
+ if (!i->fifo_path) {
+ r = mkdir_safe_label("/run/systemd/inhibit", 0755, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ return r;
+
+ i->fifo_path = strjoin("/run/systemd/inhibit/", i->id, ".ref");
+ if (!i->fifo_path)
+ return -ENOMEM;
+
+ if (mkfifo(i->fifo_path, 0600) < 0 && errno != EEXIST)
+ return -errno;
+ }
+
+ /* Open reading side */
+ if (i->fifo_fd < 0) {
+ i->fifo_fd = open(i->fifo_path, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (i->fifo_fd < 0)
+ return -errno;
+ }
+
+ if (!i->event_source) {
+ r = sd_event_add_io(i->manager->event, &i->event_source, i->fifo_fd, 0, inhibitor_dispatch_fifo, i);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(i->event_source, SD_EVENT_PRIORITY_IDLE-10);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(i->event_source, "inhibitor-ref");
+ }
+
+ /* Open writing side */
+ return RET_NERRNO(open(i->fifo_path, O_WRONLY|O_CLOEXEC|O_NONBLOCK));
+}
+
+static void inhibitor_remove_fifo(Inhibitor *i) {
+ assert(i);
+
+ i->event_source = sd_event_source_unref(i->event_source);
+ i->fifo_fd = safe_close(i->fifo_fd);
+
+ if (i->fifo_path) {
+ (void) unlink(i->fifo_path);
+ i->fifo_path = mfree(i->fifo_path);
+ }
+}
+
+bool inhibitor_is_orphan(Inhibitor *i) {
+ assert(i);
+
+ if (!i->started)
+ return true;
+
+ if (!i->fifo_path)
+ return true;
+
+ if (i->fifo_fd < 0)
+ return true;
+
+ if (pipe_eof(i->fifo_fd) != 0)
+ return true;
+
+ return false;
+}
+
+InhibitWhat manager_inhibit_what(Manager *m, InhibitMode mm) {
+ Inhibitor *i;
+ InhibitWhat what = 0;
+
+ assert(m);
+
+ HASHMAP_FOREACH(i, m->inhibitors)
+ if (i->mode == mm && i->started)
+ what |= i->what;
+
+ return what;
+}
+
+static int pidref_is_active_session(Manager *m, const PidRef *pid) {
+ Session *s;
+ int r;
+
+ assert(m);
+ assert(pid);
+
+ /* Get client session. This is not what you are looking for these days.
+ * FIXME #6852 */
+ r = manager_get_session_by_pidref(m, pid, &s);
+ if (r < 0)
+ return r;
+
+ /* If there's no session assigned to it, then it's globally active on all ttys */
+ if (r == 0)
+ return 1;
+
+ return session_is_active(s);
+}
+
+bool manager_is_inhibited(
+ Manager *m,
+ InhibitWhat w,
+ InhibitMode mm,
+ dual_timestamp *since,
+ bool ignore_inactive,
+ bool ignore_uid,
+ uid_t uid,
+ Inhibitor **offending) {
+
+ Inhibitor *i;
+ struct dual_timestamp ts = DUAL_TIMESTAMP_NULL;
+ bool inhibited = false;
+
+ assert(m);
+ assert(w > 0);
+ assert(w < _INHIBIT_WHAT_MAX);
+
+ HASHMAP_FOREACH(i, m->inhibitors) {
+ if (!i->started)
+ continue;
+
+ if (!(i->what & w))
+ continue;
+
+ if (i->mode != mm)
+ continue;
+
+ if (ignore_inactive && pidref_is_active_session(m, &i->pid) <= 0)
+ continue;
+
+ if (ignore_uid && i->uid == uid)
+ continue;
+
+ if (!inhibited ||
+ i->since.monotonic < ts.monotonic)
+ ts = i->since;
+
+ inhibited = true;
+
+ if (offending)
+ *offending = i;
+ }
+
+ if (since)
+ *since = ts;
+
+ return inhibited;
+}
+
+const char *inhibit_what_to_string(InhibitWhat w) {
+ static thread_local char buffer[STRLEN(
+ "shutdown:"
+ "sleep:"
+ "idle:"
+ "handle-power-key:"
+ "handle-suspend-key:"
+ "handle-hibernate-key:"
+ "handle-lid-switch:"
+ "handle-reboot-key")+1];
+ char *p;
+
+ if (!inhibit_what_is_valid(w))
+ return NULL;
+
+ p = buffer;
+ if (w & INHIBIT_SHUTDOWN)
+ p = stpcpy(p, "shutdown:");
+ if (w & INHIBIT_SLEEP)
+ p = stpcpy(p, "sleep:");
+ if (w & INHIBIT_IDLE)
+ p = stpcpy(p, "idle:");
+ if (w & INHIBIT_HANDLE_POWER_KEY)
+ p = stpcpy(p, "handle-power-key:");
+ if (w & INHIBIT_HANDLE_SUSPEND_KEY)
+ p = stpcpy(p, "handle-suspend-key:");
+ if (w & INHIBIT_HANDLE_HIBERNATE_KEY)
+ p = stpcpy(p, "handle-hibernate-key:");
+ if (w & INHIBIT_HANDLE_LID_SWITCH)
+ p = stpcpy(p, "handle-lid-switch:");
+ if (w & INHIBIT_HANDLE_REBOOT_KEY)
+ p = stpcpy(p, "handle-reboot-key:");
+
+ if (p > buffer)
+ *(p-1) = 0;
+ else
+ *p = 0;
+
+ return buffer;
+}
+
+int inhibit_what_from_string(const char *s) {
+ InhibitWhat what = 0;
+
+ for (const char *p = s;;) {
+ _cleanup_free_ char *word = NULL;
+ int r;
+
+ /* A sanity check that our return values fit in an int */
+ assert_cc((int) _INHIBIT_WHAT_MAX == _INHIBIT_WHAT_MAX);
+
+ r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return what;
+
+ if (streq(word, "shutdown"))
+ what |= INHIBIT_SHUTDOWN;
+ else if (streq(word, "sleep"))
+ what |= INHIBIT_SLEEP;
+ else if (streq(word, "idle"))
+ what |= INHIBIT_IDLE;
+ else if (streq(word, "handle-power-key"))
+ what |= INHIBIT_HANDLE_POWER_KEY;
+ else if (streq(word, "handle-suspend-key"))
+ what |= INHIBIT_HANDLE_SUSPEND_KEY;
+ else if (streq(word, "handle-hibernate-key"))
+ what |= INHIBIT_HANDLE_HIBERNATE_KEY;
+ else if (streq(word, "handle-lid-switch"))
+ what |= INHIBIT_HANDLE_LID_SWITCH;
+ else if (streq(word, "handle-reboot-key"))
+ what |= INHIBIT_HANDLE_REBOOT_KEY;
+ else
+ return _INHIBIT_WHAT_INVALID;
+ }
+}
+
+static const char* const inhibit_mode_table[_INHIBIT_MODE_MAX] = {
+ [INHIBIT_BLOCK] = "block",
+ [INHIBIT_DELAY] = "delay"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(inhibit_mode, InhibitMode);
diff --git a/src/login/logind-inhibit.h b/src/login/logind-inhibit.h
new file mode 100644
index 0000000..c34c225
--- /dev/null
+++ b/src/login/logind-inhibit.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "pidref.h"
+
+typedef struct Inhibitor Inhibitor;
+
+typedef enum InhibitWhat {
+ INHIBIT_SHUTDOWN = 1 << 0,
+ INHIBIT_SLEEP = 1 << 1,
+ INHIBIT_IDLE = 1 << 2,
+ INHIBIT_HANDLE_POWER_KEY = 1 << 3,
+ INHIBIT_HANDLE_SUSPEND_KEY = 1 << 4,
+ INHIBIT_HANDLE_HIBERNATE_KEY = 1 << 5,
+ INHIBIT_HANDLE_LID_SWITCH = 1 << 6,
+ INHIBIT_HANDLE_REBOOT_KEY = 1 << 7,
+ _INHIBIT_WHAT_MAX = 1 << 8,
+ _INHIBIT_WHAT_INVALID = -EINVAL,
+} InhibitWhat;
+
+typedef enum InhibitMode {
+ INHIBIT_BLOCK,
+ INHIBIT_DELAY,
+ _INHIBIT_MODE_MAX,
+ _INHIBIT_MODE_INVALID = -EINVAL,
+} InhibitMode;
+
+#include "logind.h"
+
+struct Inhibitor {
+ Manager *manager;
+
+ sd_event_source *event_source;
+
+ const char *id;
+ char *state_file;
+
+ bool started;
+
+ InhibitWhat what;
+ char *who;
+ char *why;
+ InhibitMode mode;
+
+ PidRef pid;
+ uid_t uid;
+
+ dual_timestamp since;
+
+ char *fifo_path;
+ int fifo_fd;
+};
+
+int inhibitor_new(Inhibitor **ret, Manager *m, const char* id);
+Inhibitor* inhibitor_free(Inhibitor *i);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Inhibitor*, inhibitor_free);
+
+int inhibitor_load(Inhibitor *i);
+
+int inhibitor_start(Inhibitor *i);
+void inhibitor_stop(Inhibitor *i);
+
+int inhibitor_create_fifo(Inhibitor *i);
+
+bool inhibitor_is_orphan(Inhibitor *i);
+
+InhibitWhat manager_inhibit_what(Manager *m, InhibitMode mm);
+bool manager_is_inhibited(Manager *m, InhibitWhat w, InhibitMode mm, dual_timestamp *since, bool ignore_inactive, bool ignore_uid, uid_t uid, Inhibitor **offending);
+
+static inline bool inhibit_what_is_valid(InhibitWhat w) {
+ return w > 0 && w < _INHIBIT_WHAT_MAX;
+}
+
+const char *inhibit_what_to_string(InhibitWhat k);
+int inhibit_what_from_string(const char *s);
+
+const char *inhibit_mode_to_string(InhibitMode k);
+InhibitMode inhibit_mode_from_string(const char *s);
diff --git a/src/login/logind-polkit.c b/src/login/logind-polkit.c
new file mode 100644
index 0000000..e4efd64
--- /dev/null
+++ b/src/login/logind-polkit.c
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bus-polkit.h"
+#include "logind-polkit.h"
+#include "missing_capability.h"
+#include "user-util.h"
+
+int check_polkit_chvt(sd_bus_message *message, Manager *manager, sd_bus_error *error) {
+#if ENABLE_POLKIT
+ return bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.chvt",
+ NULL,
+ false,
+ UID_INVALID,
+ &manager->polkit_registry,
+ error);
+#else
+ /* Allow chvt when polkit is not present. This allows a service to start a graphical session as a
+ * non-root user when polkit is not compiled in, more closely matching the default polkit policy */
+ return 1;
+#endif
+}
diff --git a/src/login/logind-polkit.h b/src/login/logind-polkit.h
new file mode 100644
index 0000000..9ec01a3
--- /dev/null
+++ b/src/login/logind-polkit.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "bus-object.h"
+#include "logind.h"
+
+int check_polkit_chvt(sd_bus_message *message, Manager *manager, sd_bus_error *error);
diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c
new file mode 100644
index 0000000..877b9c1
--- /dev/null
+++ b/src/login/logind-seat-dbus.c
@@ -0,0 +1,442 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "alloc-util.h"
+#include "bus-common-errors.h"
+#include "bus-get-properties.h"
+#include "bus-label.h"
+#include "bus-polkit.h"
+#include "bus-util.h"
+#include "logind-dbus.h"
+#include "logind-polkit.h"
+#include "logind-seat-dbus.h"
+#include "logind-seat.h"
+#include "logind-session-dbus.h"
+#include "logind.h"
+#include "missing_capability.h"
+#include "strv.h"
+#include "user-util.h"
+
+static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_const_true, "b", true);
+static BUS_DEFINE_PROPERTY_GET(property_get_can_tty, "b", Seat, seat_can_tty);
+static BUS_DEFINE_PROPERTY_GET(property_get_can_graphical, "b", Seat, seat_can_graphical);
+
+static int property_get_active_session(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_free_ char *p = NULL;
+ Seat *s = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ p = s->active ? session_bus_path(s->active) : strdup("/");
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_message_append(reply, "(so)", s->active ? s->active->id : "", p);
+}
+
+static int property_get_sessions(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Seat *s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = sd_bus_message_open_container(reply, 'a', "(so)");
+ if (r < 0)
+ return r;
+
+ LIST_FOREACH(sessions_by_seat, session, s->sessions) {
+ _cleanup_free_ char *p = NULL;
+
+ p = session_bus_path(session);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(reply, "(so)", session->id, p);
+ if (r < 0)
+ return r;
+
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int property_get_idle_hint(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Seat *s = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "b", seat_get_idle_hint(s, NULL) > 0);
+}
+
+static int property_get_idle_since_hint(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Seat *s = ASSERT_PTR(userdata);
+ dual_timestamp t;
+ uint64_t u;
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = seat_get_idle_hint(s, &t);
+ if (r < 0)
+ return r;
+
+ u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+
+ return sd_bus_message_append(reply, "t", u);
+}
+
+int bus_seat_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Seat *s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_KILL,
+ "org.freedesktop.login1.manage",
+ NULL,
+ false,
+ UID_INVALID,
+ &s->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = seat_stop_sessions(s, /* force = */ true);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_activate_session(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Seat *s = ASSERT_PTR(userdata);
+ const char *name;
+ Session *session;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ session = hashmap_get(s->manager->sessions, name);
+ if (!session)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SESSION, "No session '%s' known", name);
+
+ if (session->seat != s)
+ return sd_bus_error_setf(error, BUS_ERROR_SESSION_NOT_ON_SEAT, "Session %s not on seat %s", name, s->id);
+
+ r = check_polkit_chvt(message, s->manager, error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = session_activate(session);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_switch_to(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Seat *s = ASSERT_PTR(userdata);
+ unsigned to;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "u", &to);
+ if (r < 0)
+ return r;
+
+ if (to <= 0)
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid virtual terminal");
+
+ r = check_polkit_chvt(message, s->manager, error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = seat_switch_to(s, to);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_switch_to_next(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Seat *s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = check_polkit_chvt(message, s->manager, error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = seat_switch_to_next(s);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_switch_to_previous(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Seat *s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = check_polkit_chvt(message, s->manager, error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = seat_switch_to_previous(s);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int seat_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
+ _cleanup_free_ char *e = NULL;
+ sd_bus_message *message;
+ Manager *m = ASSERT_PTR(userdata);
+ const char *p;
+ Seat *seat;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(interface);
+ assert(found);
+
+ p = startswith(path, "/org/freedesktop/login1/seat/");
+ if (!p)
+ return 0;
+
+ e = bus_label_unescape(p);
+ if (!e)
+ return -ENOMEM;
+
+ message = sd_bus_get_current_message(bus);
+
+ r = manager_get_seat_from_creds(m, message, e, error, &seat);
+ if (r == -ENXIO) {
+ sd_bus_error_free(error);
+ return 0;
+ }
+ if (r < 0)
+ return r;
+
+ *found = seat;
+ return 1;
+}
+
+char *seat_bus_path(Seat *s) {
+ _cleanup_free_ char *t = NULL;
+
+ assert(s);
+
+ t = bus_label_escape(s->id);
+ if (!t)
+ return NULL;
+
+ return strjoin("/org/freedesktop/login1/seat/", t);
+}
+
+static int seat_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+ _cleanup_strv_free_ char **l = NULL;
+ sd_bus_message *message;
+ Manager *m = userdata;
+ Seat *seat;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(nodes);
+
+ HASHMAP_FOREACH(seat, m->seats) {
+ char *p;
+
+ p = seat_bus_path(seat);
+ if (!p)
+ return -ENOMEM;
+
+ r = strv_consume(&l, p);
+ if (r < 0)
+ return r;
+ }
+
+ message = sd_bus_get_current_message(bus);
+ if (message) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, &creds);
+ if (r >= 0) {
+ bool may_auto = false;
+ const char *name;
+
+ r = sd_bus_creds_get_session(creds, &name);
+ if (r >= 0) {
+ Session *session;
+
+ session = hashmap_get(m->sessions, name);
+ if (session && session->seat) {
+ r = strv_extend(&l, "/org/freedesktop/login1/seat/self");
+ if (r < 0)
+ return r;
+
+ may_auto = true;
+ }
+ }
+
+ if (!may_auto) {
+ uid_t uid;
+
+ r = sd_bus_creds_get_owner_uid(creds, &uid);
+ if (r >= 0) {
+ User *user;
+
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
+ may_auto = user && user->display && user->display->seat;
+ }
+ }
+
+ if (may_auto) {
+ r = strv_extend(&l, "/org/freedesktop/login1/seat/auto");
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+
+ *nodes = TAKE_PTR(l);
+ return 1;
+}
+
+int seat_send_signal(Seat *s, bool new_seat) {
+ _cleanup_free_ char *p = NULL;
+
+ assert(s);
+
+ p = seat_bus_path(s);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_emit_signal(
+ s->manager->bus,
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ new_seat ? "SeatNew" : "SeatRemoved",
+ "so", s->id, p);
+}
+
+int seat_send_changed(Seat *s, const char *properties, ...) {
+ _cleanup_free_ char *p = NULL;
+ char **l;
+
+ assert(s);
+
+ if (!s->started)
+ return 0;
+
+ p = seat_bus_path(s);
+ if (!p)
+ return -ENOMEM;
+
+ l = strv_from_stdarg_alloca(properties);
+
+ return sd_bus_emit_properties_changed_strv(s->manager->bus, p, "org.freedesktop.login1.Seat", l);
+}
+
+static const sd_bus_vtable seat_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ SD_BUS_PROPERTY("Id", "s", NULL, offsetof(Seat, id), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ActiveSession", "(so)", property_get_active_session, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("CanMultiSession", "b", property_get_const_true, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+ SD_BUS_PROPERTY("CanTTY", "b", property_get_can_tty, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("CanGraphical", "b", property_get_can_graphical, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("Sessions", "a(so)", property_get_sessions, 0, 0),
+ SD_BUS_PROPERTY("IdleHint", "b", property_get_idle_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IdleSinceHint", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IdleSinceHintMonotonic", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+
+ SD_BUS_METHOD("Terminate", NULL, NULL, bus_seat_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED),
+
+ SD_BUS_METHOD_WITH_ARGS("ActivateSession",
+ SD_BUS_ARGS("s", session_id),
+ SD_BUS_NO_RESULT,
+ method_activate_session,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SwitchTo",
+ SD_BUS_ARGS("u", vtnr),
+ SD_BUS_NO_RESULT,
+ method_switch_to,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
+ SD_BUS_METHOD("SwitchToNext", NULL, NULL, method_switch_to_next, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SwitchToPrevious", NULL, NULL, method_switch_to_previous, SD_BUS_VTABLE_UNPRIVILEGED),
+
+ SD_BUS_VTABLE_END
+};
+
+const BusObjectImplementation seat_object = {
+ "/org/freedesktop/login1/seat",
+ "org.freedesktop.login1.Seat",
+ .fallback_vtables = BUS_FALLBACK_VTABLES({seat_vtable, seat_object_find}),
+ .node_enumerator = seat_node_enumerator,
+};
diff --git a/src/login/logind-seat-dbus.h b/src/login/logind-seat-dbus.h
new file mode 100644
index 0000000..258db91
--- /dev/null
+++ b/src/login/logind-seat-dbus.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "bus-object.h"
+#include "logind-seat.h"
+
+extern const BusObjectImplementation seat_object;
+
+char *seat_bus_path(Seat *s);
+
+int seat_send_signal(Seat *s, bool new_seat);
+int seat_send_changed(Seat *s, const char *properties, ...) _sentinel_;
+
+int bus_seat_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error);
diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c
new file mode 100644
index 0000000..8d875d2
--- /dev/null
+++ b/src/login/logind-seat.c
@@ -0,0 +1,682 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "devnode-acl.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "logind-seat-dbus.h"
+#include "logind-seat.h"
+#include "logind-session-dbus.h"
+#include "mkdir-label.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "tmpfile-util.h"
+
+int seat_new(Seat** ret, Manager *m, const char *id) {
+ _cleanup_(seat_freep) Seat *s = NULL;
+ int r;
+
+ assert(ret);
+ assert(m);
+ assert(id);
+
+ if (!seat_name_is_valid(id))
+ return -EINVAL;
+
+ s = new(Seat, 1);
+ if (!s)
+ return -ENOMEM;
+
+ *s = (Seat) {
+ .manager = m,
+ };
+
+ s->state_file = path_join("/run/systemd/seats", id);
+ if (!s->state_file)
+ return -ENOMEM;
+
+ s->id = basename(s->state_file);
+
+ r = hashmap_put(m->seats, s->id, s);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(s);
+ return 0;
+}
+
+Seat* seat_free(Seat *s) {
+ if (!s)
+ return NULL;
+
+ if (s->in_gc_queue)
+ LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
+
+ while (s->sessions)
+ session_free(s->sessions);
+
+ assert(!s->active);
+
+ while (s->devices)
+ device_free(s->devices);
+
+ hashmap_remove(s->manager->seats, s->id);
+
+ free(s->positions);
+ free(s->state_file);
+
+ return mfree(s);
+}
+
+int seat_save(Seat *s) {
+ _cleanup_(unlink_and_freep) char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(s);
+
+ if (!s->started)
+ return 0;
+
+ r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ goto fail;
+
+ r = fopen_temporary(s->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "IS_SEAT0=%i\n"
+ "CAN_MULTI_SESSION=1\n"
+ "CAN_TTY=%i\n"
+ "CAN_GRAPHICAL=%i\n",
+ seat_is_seat0(s),
+ seat_can_tty(s),
+ seat_can_graphical(s));
+
+ if (s->active) {
+ assert(s->active->user);
+
+ fprintf(f,
+ "ACTIVE=%s\n"
+ "ACTIVE_UID="UID_FMT"\n",
+ s->active->id,
+ s->active->user->user_record->uid);
+ }
+
+ if (s->sessions) {
+ fputs("SESSIONS=", f);
+ LIST_FOREACH(sessions_by_seat, i, s->sessions) {
+ fprintf(f,
+ "%s%c",
+ i->id,
+ i->sessions_by_seat_next ? ' ' : '\n');
+ }
+
+ fputs("UIDS=", f);
+ LIST_FOREACH(sessions_by_seat, i, s->sessions)
+ fprintf(f,
+ UID_FMT"%c",
+ i->user->user_record->uid,
+ i->sessions_by_seat_next ? ' ' : '\n');
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, s->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ temp_path = mfree(temp_path);
+ return 0;
+
+fail:
+ (void) unlink(s->state_file);
+ return log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
+}
+
+int seat_load(Seat *s) {
+ assert(s);
+
+ /* There isn't actually anything to read here ... */
+
+ return 0;
+}
+
+static int vt_allocate(unsigned vtnr) {
+ char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned)];
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(vtnr >= 1);
+
+ xsprintf(p, "/dev/tty%u", vtnr);
+ fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ return 0;
+}
+
+int seat_preallocate_vts(Seat *s) {
+ int r = 0;
+ unsigned i;
+
+ assert(s);
+ assert(s->manager);
+
+ if (s->manager->n_autovts <= 0)
+ return 0;
+
+ if (!seat_has_vts(s))
+ return 0;
+
+ log_debug("Preallocating VTs...");
+
+ for (i = 1; i <= s->manager->n_autovts; i++) {
+ int q;
+
+ q = vt_allocate(i);
+ if (q < 0)
+ r = log_error_errno(q, "Failed to preallocate VT %u: %m", i);
+ }
+
+ return r;
+}
+
+int seat_apply_acls(Seat *s, Session *old_active) {
+ int r;
+
+ assert(s);
+
+ r = devnode_acl_all(s->id,
+ false,
+ !!old_active, old_active ? old_active->user->user_record->uid : 0,
+ !!s->active, s->active ? s->active->user->user_record->uid : 0);
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to apply ACLs: %m");
+
+ return 0;
+}
+
+int seat_set_active(Seat *s, Session *session) {
+ Session *old_active;
+
+ assert(s);
+ assert(!session || session->seat == s);
+
+ /* When logind receives the SIGRTMIN signal from the kernel, it will
+ * execute session_leave_vt and stop all devices of the session; at
+ * this time, if the session is active and there is no change in the
+ * session, then the session does not have the permissions of the device,
+ * and the machine will have a black screen and suspended animation.
+ * Therefore, if the active session has executed session_leave_vt ,
+ * A resume is required here. */
+ if (session == s->active) {
+ if (session) {
+ log_debug("Active session remains unchanged, resuming session devices.");
+ session_device_resume_all(session);
+ }
+ return 0;
+ }
+
+ old_active = s->active;
+ s->active = session;
+
+ if (old_active) {
+ session_device_pause_all(old_active);
+ session_send_changed(old_active, "Active", NULL);
+ }
+
+ (void) seat_apply_acls(s, old_active);
+
+ if (session && session->started) {
+ session_send_changed(session, "Active", NULL);
+ session_device_resume_all(session);
+ }
+
+ if (!session || session->started)
+ seat_send_changed(s, "ActiveSession", NULL);
+
+ seat_save(s);
+
+ if (session) {
+ session_save(session);
+ user_save(session->user);
+ }
+
+ if (old_active) {
+ session_save(old_active);
+ if (!session || session->user != old_active->user)
+ user_save(old_active->user);
+ }
+
+ return 0;
+}
+
+static Session* seat_get_position(Seat *s, unsigned pos) {
+ assert(s);
+
+ if (pos >= MALLOC_ELEMENTSOF(s->positions))
+ return NULL;
+
+ return s->positions[pos];
+}
+
+int seat_switch_to(Seat *s, unsigned num) {
+ Session *session;
+
+ /* Public session positions skip 0 (there is only F1-F12). Maybe it
+ * will get reassigned in the future, so return error for now. */
+ if (num == 0)
+ return -EINVAL;
+
+ session = seat_get_position(s, num);
+ if (!session) {
+ /* allow switching to unused VTs to trigger auto-activate */
+ if (seat_has_vts(s) && num < 64)
+ return chvt(num);
+
+ return -EINVAL;
+ }
+
+ return session_activate(session);
+}
+
+int seat_switch_to_next(Seat *s) {
+ unsigned start, i;
+ Session *session;
+
+ if (MALLOC_ELEMENTSOF(s->positions) == 0)
+ return -EINVAL;
+
+ start = 1;
+ if (s->active && s->active->position > 0)
+ start = s->active->position;
+
+ for (i = start + 1; i < MALLOC_ELEMENTSOF(s->positions); ++i) {
+ session = seat_get_position(s, i);
+ if (session)
+ return session_activate(session);
+ }
+
+ for (i = 1; i < start; ++i) {
+ session = seat_get_position(s, i);
+ if (session)
+ return session_activate(session);
+ }
+
+ return -EINVAL;
+}
+
+int seat_switch_to_previous(Seat *s) {
+ if (MALLOC_ELEMENTSOF(s->positions) == 0)
+ return -EINVAL;
+
+ size_t start = s->active && s->active->position > 0 ? s->active->position : 1;
+
+ for (size_t i = start - 1; i > 0; i--) {
+ Session *session = seat_get_position(s, i);
+ if (session)
+ return session_activate(session);
+ }
+
+ for (size_t i = MALLOC_ELEMENTSOF(s->positions) - 1; i > start; i--) {
+ Session *session = seat_get_position(s, i);
+ if (session)
+ return session_activate(session);
+ }
+
+ return -EINVAL;
+}
+
+int seat_active_vt_changed(Seat *s, unsigned vtnr) {
+ Session *new_active = NULL;
+ int r;
+
+ assert(s);
+ assert(vtnr >= 1);
+
+ if (!seat_has_vts(s))
+ return -EINVAL;
+
+ log_debug("VT changed to %u", vtnr);
+
+ /* we might have earlier closing sessions on the same VT, so try to
+ * find a running one first */
+ LIST_FOREACH(sessions_by_seat, i, s->sessions)
+ if (i->vtnr == vtnr && !i->stopping) {
+ new_active = i;
+ break;
+ }
+
+ if (!new_active)
+ /* no running one? then we can't decide which one is the
+ * active one, let the first one win */
+ LIST_FOREACH(sessions_by_seat, i, s->sessions)
+ if (i->vtnr == vtnr) {
+ new_active = i;
+ break;
+ }
+
+ r = seat_set_active(s, new_active);
+ manager_spawn_autovt(s->manager, vtnr);
+
+ return r;
+}
+
+int seat_read_active_vt(Seat *s) {
+ char t[64];
+ ssize_t k;
+ int vtnr;
+
+ assert(s);
+
+ if (!seat_has_vts(s))
+ return 0;
+
+ if (lseek(s->manager->console_active_fd, SEEK_SET, 0) < 0)
+ return log_error_errno(errno, "lseek on console_active_fd failed: %m");
+
+ errno = 0;
+ k = read(s->manager->console_active_fd, t, sizeof(t)-1);
+ if (k <= 0)
+ return log_error_errno(errno ?: EIO,
+ "Failed to read current console: %s", STRERROR_OR_EOF(errno));
+
+ t[k] = 0;
+ truncate_nl(t);
+
+ vtnr = vtnr_from_tty(t);
+ if (vtnr < 0) {
+ log_error_errno(vtnr, "Hm, /sys/class/tty/tty0/active is badly formatted: %m");
+ return -EIO;
+ }
+
+ return seat_active_vt_changed(s, vtnr);
+}
+
+int seat_start(Seat *s) {
+ assert(s);
+
+ if (s->started)
+ return 0;
+
+ log_struct(LOG_INFO,
+ "MESSAGE_ID=" SD_MESSAGE_SEAT_START_STR,
+ "SEAT_ID=%s", s->id,
+ LOG_MESSAGE("New seat %s.", s->id));
+
+ /* Initialize VT magic stuff */
+ seat_preallocate_vts(s);
+
+ /* Read current VT */
+ seat_read_active_vt(s);
+
+ s->started = true;
+
+ /* Save seat data */
+ seat_save(s);
+
+ seat_send_signal(s, true);
+
+ return 0;
+}
+
+int seat_stop(Seat *s, bool force) {
+ int r;
+
+ assert(s);
+
+ if (s->started)
+ log_struct(LOG_INFO,
+ "MESSAGE_ID=" SD_MESSAGE_SEAT_STOP_STR,
+ "SEAT_ID=%s", s->id,
+ LOG_MESSAGE("Removed seat %s.", s->id));
+
+ r = seat_stop_sessions(s, force);
+
+ (void) unlink(s->state_file);
+ seat_add_to_gc_queue(s);
+
+ if (s->started)
+ seat_send_signal(s, false);
+
+ s->started = false;
+
+ return r;
+}
+
+int seat_stop_sessions(Seat *s, bool force) {
+ int r = 0, k;
+
+ assert(s);
+
+ LIST_FOREACH(sessions_by_seat, session, s->sessions) {
+ k = session_stop(session, force);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+void seat_evict_position(Seat *s, Session *session) {
+ unsigned pos = session->position;
+
+ session->position = 0;
+
+ if (pos == 0)
+ return;
+
+ if (pos < MALLOC_ELEMENTSOF(s->positions) && s->positions[pos] == session) {
+ s->positions[pos] = NULL;
+
+ /* There might be another session claiming the same
+ * position (eg., during gdm->session transition), so let's look
+ * for it and set it on the free slot. */
+ LIST_FOREACH(sessions_by_seat, iter, s->sessions)
+ if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
+ s->positions[pos] = iter;
+ break;
+ }
+ }
+}
+
+void seat_claim_position(Seat *s, Session *session, unsigned pos) {
+ /* with VTs, the position is always the same as the VTnr */
+ if (seat_has_vts(s))
+ pos = session->vtnr;
+
+ if (!GREEDY_REALLOC0(s->positions, pos + 1))
+ return;
+
+ seat_evict_position(s, session);
+
+ session->position = pos;
+ if (pos > 0)
+ s->positions[pos] = session;
+}
+
+static void seat_assign_position(Seat *s, Session *session) {
+ unsigned pos;
+
+ if (session->position > 0)
+ return;
+
+ for (pos = 1; pos < MALLOC_ELEMENTSOF(s->positions); ++pos)
+ if (!s->positions[pos])
+ break;
+
+ seat_claim_position(s, session, pos);
+}
+
+int seat_attach_session(Seat *s, Session *session) {
+ assert(s);
+ assert(session);
+ assert(!session->seat);
+
+ if (!seat_has_vts(s) != !session->vtnr)
+ return -EINVAL;
+
+ session->seat = s;
+ LIST_PREPEND(sessions_by_seat, s->sessions, session);
+ seat_assign_position(s, session);
+
+ /* On seats with VTs, the VT logic defines which session is active. On
+ * seats without VTs, we automatically activate new sessions. */
+ if (!seat_has_vts(s))
+ seat_set_active(s, session);
+
+ return 0;
+}
+
+void seat_complete_switch(Seat *s) {
+ Session *session;
+
+ assert(s);
+
+ /* if no session-switch is pending or if it got canceled, do nothing */
+ if (!s->pending_switch)
+ return;
+
+ session = TAKE_PTR(s->pending_switch);
+
+ seat_set_active(s, session);
+}
+
+bool seat_has_vts(Seat *s) {
+ assert(s);
+
+ return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
+}
+
+bool seat_is_seat0(Seat *s) {
+ assert(s);
+
+ return s->manager->seat0 == s;
+}
+
+bool seat_can_tty(Seat *s) {
+ assert(s);
+
+ return seat_has_vts(s);
+}
+
+bool seat_has_master_device(Seat *s) {
+ assert(s);
+
+ /* device list is ordered by "master" flag */
+ return !!s->devices && s->devices->master;
+}
+
+bool seat_can_graphical(Seat *s) {
+ assert(s);
+
+ return seat_has_master_device(s);
+}
+
+int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
+ bool idle_hint = true;
+ dual_timestamp ts = DUAL_TIMESTAMP_NULL;
+
+ assert(s);
+
+ LIST_FOREACH(sessions_by_seat, session, s->sessions) {
+ dual_timestamp k;
+ int ih;
+
+ ih = session_get_idle_hint(session, &k);
+ if (ih < 0)
+ return ih;
+
+ if (!ih) {
+ if (!idle_hint) {
+ if (k.monotonic > ts.monotonic)
+ ts = k;
+ } else {
+ idle_hint = false;
+ ts = k;
+ }
+ } else if (idle_hint) {
+
+ if (k.monotonic > ts.monotonic)
+ ts = k;
+ }
+ }
+
+ if (t)
+ *t = ts;
+
+ return idle_hint;
+}
+
+bool seat_may_gc(Seat *s, bool drop_not_started) {
+ assert(s);
+
+ if (drop_not_started && !s->started)
+ return true;
+
+ if (seat_is_seat0(s))
+ return false;
+
+ return !seat_has_master_device(s);
+}
+
+void seat_add_to_gc_queue(Seat *s) {
+ assert(s);
+
+ if (s->in_gc_queue)
+ return;
+
+ LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
+ s->in_gc_queue = true;
+}
+
+static bool seat_name_valid_char(char c) {
+ return
+ ascii_isalpha(c) ||
+ ascii_isdigit(c) ||
+ IN_SET(c, '-', '_');
+}
+
+bool seat_name_is_valid(const char *name) {
+ const char *p;
+
+ assert(name);
+
+ if (!startswith(name, "seat"))
+ return false;
+
+ if (!name[4])
+ return false;
+
+ for (p = name; *p; p++)
+ if (!seat_name_valid_char(*p))
+ return false;
+
+ if (strlen(name) > 255)
+ return false;
+
+ return true;
+}
diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h
new file mode 100644
index 0000000..2d18e75
--- /dev/null
+++ b/src/login/logind-seat.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct Seat Seat;
+
+#include "list.h"
+#include "logind-session.h"
+
+struct Seat {
+ Manager *manager;
+ char *id;
+
+ char *state_file;
+
+ LIST_HEAD(Device, devices);
+
+ Session *active;
+ Session *pending_switch;
+ LIST_HEAD(Session, sessions);
+
+ Session **positions;
+
+ bool in_gc_queue:1;
+ bool started:1;
+
+ LIST_FIELDS(Seat, gc_queue);
+};
+
+int seat_new(Seat **ret, Manager *m, const char *id);
+Seat* seat_free(Seat *s);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Seat *, seat_free);
+
+int seat_save(Seat *s);
+int seat_load(Seat *s);
+
+int seat_apply_acls(Seat *s, Session *old_active);
+int seat_set_active(Seat *s, Session *session);
+int seat_switch_to(Seat *s, unsigned num);
+int seat_switch_to_next(Seat *s);
+int seat_switch_to_previous(Seat *s);
+int seat_active_vt_changed(Seat *s, unsigned vtnr);
+int seat_read_active_vt(Seat *s);
+int seat_preallocate_vts(Seat *s);
+
+int seat_attach_session(Seat *s, Session *session);
+void seat_complete_switch(Seat *s);
+void seat_evict_position(Seat *s, Session *session);
+void seat_claim_position(Seat *s, Session *session, unsigned pos);
+
+bool seat_has_vts(Seat *s);
+bool seat_is_seat0(Seat *s);
+bool seat_can_tty(Seat *s);
+bool seat_has_master_device(Seat *s);
+bool seat_can_graphical(Seat *s);
+
+int seat_get_idle_hint(Seat *s, dual_timestamp *t);
+
+int seat_start(Seat *s);
+int seat_stop(Seat *s, bool force);
+int seat_stop_sessions(Seat *s, bool force);
+
+bool seat_may_gc(Seat *s, bool drop_not_started);
+void seat_add_to_gc_queue(Seat *s);
+
+bool seat_name_is_valid(const char *name);
+
+static inline bool SEAT_IS_SELF(const char *name) {
+ return isempty(name) || streq(name, "self");
+}
+
+static inline bool SEAT_IS_AUTO(const char *name) {
+ return streq_ptr(name, "auto");
+}
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
new file mode 100644
index 0000000..a136ae4
--- /dev/null
+++ b/src/login/logind-session-dbus.c
@@ -0,0 +1,994 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "alloc-util.h"
+#include "bus-common-errors.h"
+#include "bus-get-properties.h"
+#include "bus-label.h"
+#include "bus-polkit.h"
+#include "bus-util.h"
+#include "devnum-util.h"
+#include "fd-util.h"
+#include "logind-brightness.h"
+#include "logind-dbus.h"
+#include "logind-polkit.h"
+#include "logind-seat-dbus.h"
+#include "logind-session-dbus.h"
+#include "logind-session-device.h"
+#include "logind-session.h"
+#include "logind-user-dbus.h"
+#include "logind.h"
+#include "missing_capability.h"
+#include "path-util.h"
+#include "signal-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "user-util.h"
+
+static int property_get_user(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_free_ char *p = NULL;
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ p = user_bus_path(s->user);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_message_append(reply, "(uo)", (uint32_t) s->user->user_record->uid, p);
+}
+
+static int property_get_name(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "s", s->user->user_record->user_name);
+}
+
+static int property_get_seat(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_free_ char *p = NULL;
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ p = s->seat ? seat_bus_path(s->seat) : strdup("/");
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_message_append(reply, "(so)", s->seat ? s->seat->id : "", p);
+}
+
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, session_type, SessionType);
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, session_class, SessionClass);
+static BUS_DEFINE_PROPERTY_GET(property_get_active, "b", Session, session_is_active);
+static BUS_DEFINE_PROPERTY_GET2(property_get_state, "s", Session, session_get_state, session_state_to_string);
+
+static int property_get_idle_hint(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "b", session_get_idle_hint(s, NULL) > 0);
+}
+
+static int property_get_idle_since_hint(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Session *s = ASSERT_PTR(userdata);
+ dual_timestamp t = DUAL_TIMESTAMP_NULL;
+ uint64_t u;
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = session_get_idle_hint(s, &t);
+ if (r < 0)
+ return r;
+
+ u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+
+ return sd_bus_message_append(reply, "t", u);
+}
+
+static int property_get_locked_hint(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "b", session_get_locked_hint(s) > 0);
+}
+
+int bus_session_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_KILL,
+ "org.freedesktop.login1.manage",
+ NULL,
+ false,
+ s->user->user_record->uid,
+ &s->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = session_stop(s, /* force = */ true);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_session_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = check_polkit_chvt(message, s->manager, error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = session_activate(s);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.lock-sessions",
+ NULL,
+ false,
+ s->user->user_record->uid,
+ &s->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = session_send_lock(s, strstr(sd_bus_message_get_member(message), "Lock"));
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_set_idle_hint(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);
+ uid_t uid;
+ int r, b;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "b", &b);
+ if (r < 0)
+ return r;
+
+ 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 set idle hint");
+
+ 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.");
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_set_locked_hint(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);
+ uid_t uid;
+ int r, b;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "b", &b);
+ if (r < 0)
+ return r;
+
+ 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 set locked hint");
+
+ session_set_locked_hint(s, b);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_session_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ const char *swho;
+ int32_t signo;
+ KillWho who;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "si", &swho, &signo);
+ if (r < 0)
+ return r;
+
+ if (isempty(swho))
+ who = KILL_ALL;
+ else {
+ who = kill_who_from_string(swho);
+ if (who < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho);
+ }
+
+ if (!SIGNAL_VALID(signo))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_KILL,
+ "org.freedesktop.login1.manage",
+ NULL,
+ false,
+ s->user->user_record->uid,
+ &s->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = session_kill(s, who, signo);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_take_control(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);
+ int r, force;
+ uid_t uid;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "b", &force);
+ if (r < 0)
+ return r;
+
+ 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 && (force || uid != s->user->user_record->uid))
+ return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may take control");
+
+ r = session_set_controller(s, sd_bus_message_get_sender(message), force, true);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_release_control(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(message);
+
+ 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");
+
+ session_drop_controller(s);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_set_type(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ const char *t;
+ SessionType type;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &t);
+ if (r < 0)
+ return r;
+
+ type = session_type_from_string(t);
+ if (type < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid session type '%s'", t);
+
+ 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");
+
+ session_set_type(s, type);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_set_display(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ const char *display;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &display);
+ if (r < 0)
+ return r;
+
+ 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 display");
+
+ if (!SESSION_TYPE_IS_GRAPHICAL(s->type))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Setting display is only supported for graphical sessions");
+
+ r = session_set_display(s, display);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_set_tty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ int fd, r, flags;
+ _cleanup_free_ char *q = NULL;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "h", &fd);
+ if (r < 0)
+ return r;
+
+ 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 tty");
+
+ assert(fd >= 0);
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0)
+ return -errno;
+ if ((flags & O_ACCMODE) != O_RDWR)
+ return -EACCES;
+ if (FLAGS_SET(flags, O_PATH))
+ return -ENOTTY;
+
+ r = getttyname_malloc(fd, &q);
+ if (r < 0)
+ return r;
+
+ r = session_set_tty(s, q);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ uint32_t major, minor;
+ _cleanup_(session_device_freep) SessionDevice *sd = NULL;
+ dev_t dev;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "uu", &major, &minor);
+ if (r < 0)
+ return r;
+
+ 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_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");
+
+ dev = makedev(major, minor);
+ sd = hashmap_get(s->devices, &dev);
+ if (sd)
+ /* We don't allow retrieving a device multiple times.
+ * The related ReleaseDevice call is not ref-counted.
+ * The caller should use dup() if it requires more
+ * than one fd (it would be functionally
+ * equivalent). */
+ return sd_bus_error_set(error, BUS_ERROR_DEVICE_IS_TAKEN, "Device already taken");
+
+ r = session_device_new(s, dev, true, &sd);
+ if (r < 0)
+ return r;
+
+ r = session_device_save(sd);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_reply_method_return(message, "hb", sd->fd, !sd->active);
+ if (r < 0)
+ return r;
+
+ session_save(s);
+ TAKE_PTR(sd);
+
+ return 1;
+}
+
+static int method_release_device(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ uint32_t major, minor;
+ SessionDevice *sd;
+ dev_t dev;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "uu", &major, &minor);
+ if (r < 0)
+ return r;
+
+ 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_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");
+
+ dev = makedev(major, minor);
+ sd = hashmap_get(s->devices, &dev);
+ if (!sd)
+ return sd_bus_error_set(error, BUS_ERROR_DEVICE_NOT_TAKEN, "Device not taken");
+
+ session_device_free(sd);
+ session_save(s);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_pause_device_complete(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ uint32_t major, minor;
+ SessionDevice *sd;
+ dev_t dev;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "uu", &major, &minor);
+ if (r < 0)
+ return r;
+
+ 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_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");
+
+ dev = makedev(major, minor);
+ sd = hashmap_get(s->devices, &dev);
+ if (!sd)
+ return sd_bus_error_set(error, BUS_ERROR_DEVICE_NOT_TAKEN, "Device not taken");
+
+ session_device_complete_pause(sd);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_set_brightness(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+ const char *subsystem, *name, *seat;
+ Session *s = ASSERT_PTR(userdata);
+ uint32_t brightness;
+ uid_t uid;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "ssu", &subsystem, &name, &brightness);
+ if (r < 0)
+ return r;
+
+ if (!STR_IN_SET(subsystem, "backlight", "leds"))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Subsystem type %s not supported, must be one of 'backlight' or 'leds'.", subsystem);
+ if (!filename_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not a valid device name %s, refusing.", name);
+
+ if (!s->seat)
+ return sd_bus_error_set(error, BUS_ERROR_NOT_YOUR_DEVICE, "Your session has no seat, refusing.");
+ if (s->seat->active != s)
+ return sd_bus_error_set(error, BUS_ERROR_NOT_YOUR_DEVICE, "Session is not in foreground, refusing.");
+
+ 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 brightness.");
+
+ r = sd_device_new_from_subsystem_sysname(&d, subsystem, name);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to open device %s:%s: %m", subsystem, name);
+
+ if (sd_device_get_property_value(d, "ID_SEAT", &seat) >= 0 && !streq_ptr(seat, s->seat->id))
+ return sd_bus_error_setf(error, BUS_ERROR_NOT_YOUR_DEVICE, "Device %s:%s does not belong to your seat %s, refusing.", subsystem, name, s->seat->id);
+
+ r = manager_write_brightness(s->manager, d, brightness, message);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int session_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
+ _cleanup_free_ char *e = NULL;
+ sd_bus_message *message;
+ Manager *m = ASSERT_PTR(userdata);
+ Session *session;
+ const char *p;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(interface);
+ assert(found);
+
+ p = startswith(path, "/org/freedesktop/login1/session/");
+ if (!p)
+ return 0;
+
+ e = bus_label_unescape(p);
+ if (!e)
+ return -ENOMEM;
+
+ message = sd_bus_get_current_message(bus);
+
+ r = manager_get_session_from_creds(m, message, e, error, &session);
+ if (r == -ENXIO) {
+ sd_bus_error_free(error);
+ return 0;
+ }
+ if (r < 0)
+ return r;
+
+ *found = session;
+ return 1;
+}
+
+char *session_bus_path(Session *s) {
+ _cleanup_free_ char *t = NULL;
+
+ assert(s);
+
+ t = bus_label_escape(s->id);
+ if (!t)
+ return NULL;
+
+ return strjoin("/org/freedesktop/login1/session/", t);
+}
+
+static int session_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+ _cleanup_strv_free_ char **l = NULL;
+ sd_bus_message *message;
+ Manager *m = userdata;
+ Session *session;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(nodes);
+
+ HASHMAP_FOREACH(session, m->sessions) {
+ char *p;
+
+ p = session_bus_path(session);
+ if (!p)
+ return -ENOMEM;
+
+ r = strv_consume(&l, p);
+ if (r < 0)
+ return r;
+ }
+
+ message = sd_bus_get_current_message(bus);
+ if (message) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, &creds);
+ if (r >= 0) {
+ bool may_auto = false;
+ const char *name;
+
+ r = sd_bus_creds_get_session(creds, &name);
+ if (r >= 0) {
+ session = hashmap_get(m->sessions, name);
+ if (session) {
+ r = strv_extend(&l, "/org/freedesktop/login1/session/self");
+ if (r < 0)
+ return r;
+
+ may_auto = true;
+ }
+ }
+
+ if (!may_auto) {
+ uid_t uid;
+
+ r = sd_bus_creds_get_owner_uid(creds, &uid);
+ if (r >= 0) {
+ User *user;
+
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
+ may_auto = user && user->display;
+ }
+ }
+
+ if (may_auto) {
+ r = strv_extend(&l, "/org/freedesktop/login1/session/auto");
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+
+ *nodes = TAKE_PTR(l);
+ return 1;
+}
+
+int session_send_signal(Session *s, bool new_session) {
+ _cleanup_free_ char *p = NULL;
+
+ assert(s);
+
+ p = session_bus_path(s);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_emit_signal(
+ s->manager->bus,
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ new_session ? "SessionNew" : "SessionRemoved",
+ "so", s->id, p);
+}
+
+int session_send_changed(Session *s, const char *properties, ...) {
+ _cleanup_free_ char *p = NULL;
+ char **l;
+
+ assert(s);
+
+ if (!s->started)
+ return 0;
+
+ p = session_bus_path(s);
+ if (!p)
+ return -ENOMEM;
+
+ l = strv_from_stdarg_alloca(properties);
+
+ return sd_bus_emit_properties_changed_strv(s->manager->bus, p, "org.freedesktop.login1.Session", l);
+}
+
+int session_send_lock(Session *s, bool lock) {
+ _cleanup_free_ char *p = NULL;
+
+ assert(s);
+
+ p = session_bus_path(s);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_emit_signal(
+ s->manager->bus,
+ p,
+ "org.freedesktop.login1.Session",
+ lock ? "Lock" : "Unlock",
+ NULL);
+}
+
+int session_send_lock_all(Manager *m, bool lock) {
+ Session *session;
+ int r = 0;
+
+ assert(m);
+
+ HASHMAP_FOREACH(session, m->sessions) {
+ int k;
+
+ k = session_send_lock(session, lock);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static bool session_ready(Session *s) {
+ assert(s);
+
+ /* Returns true when the session is ready, i.e. all jobs we enqueued for it are done (regardless if successful or not) */
+
+ return !s->scope_job &&
+ !s->user->service_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);
+
+ /* This is called after the session scope and the user service were successfully created, and finishes where
+ * bus_manager_create_session() left off. */
+
+ if (!s->create_message)
+ return 0;
+
+ if (!sd_bus_error_is_set(error) && !session_ready(s))
+ return 0;
+
+ c = TAKE_PTR(s->create_message);
+ if (error)
+ return sd_bus_reply_method_error(c, error);
+
+ fifo_fd = session_create_fifo(s);
+ 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);
+
+ p = session_bus_path(s);
+ if (!p)
+ return -ENOMEM;
+
+ log_debug("Sending reply about created session: "
+ "id=%s object_path=%s uid=%u runtime_path=%s "
+ "session_fd=%d seat=%s vtnr=%u",
+ s->id,
+ p,
+ (uint32_t) s->user->user_record->uid,
+ s->user->runtime_path,
+ fifo_fd,
+ s->seat ? s->seat->id : "",
+ (uint32_t) s->vtnr);
+
+ return sd_bus_reply_method_return(
+ c, "soshusub",
+ s->id,
+ p,
+ s->user->runtime_path,
+ fifo_fd,
+ (uint32_t) s->user->user_record->uid,
+ s->seat ? s->seat->id : "",
+ (uint32_t) s->vtnr,
+ false);
+}
+
+static const sd_bus_vtable session_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ SD_BUS_PROPERTY("Id", "s", NULL, offsetof(Session, id), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("User", "(uo)", property_get_user, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Name", "s", property_get_name, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Session, timestamp), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("VTNr", "u", NULL, offsetof(Session, vtnr), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Seat", "(so)", property_get_seat, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("TTY", "s", NULL, offsetof(Session, tty), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("Display", "s", NULL, offsetof(Session, display), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("Remote", "b", bus_property_get_bool, offsetof(Session, remote), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RemoteHost", "s", NULL, offsetof(Session, remote_host), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RemoteUser", "s", NULL, offsetof(Session, remote_user), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Session, service), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Desktop", "s", NULL, offsetof(Session, desktop), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Session, scope), SD_BUS_VTABLE_PROPERTY_CONST),
+ 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("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),
+ SD_BUS_PROPERTY("IdleSinceHint", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IdleSinceHintMonotonic", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("LockedHint", "b", property_get_locked_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+
+ SD_BUS_METHOD("Terminate",
+ NULL,
+ NULL,
+ bus_session_method_terminate,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Activate",
+ NULL,
+ NULL,
+ bus_session_method_activate,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Lock",
+ NULL,
+ NULL,
+ bus_session_method_lock,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Unlock",
+ NULL,
+ NULL,
+ bus_session_method_lock,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetIdleHint",
+ SD_BUS_ARGS("b", idle),
+ SD_BUS_NO_RESULT,
+ method_set_idle_hint,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetLockedHint",
+ SD_BUS_ARGS("b", locked),
+ SD_BUS_NO_RESULT,
+ method_set_locked_hint,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Kill",
+ SD_BUS_ARGS("s", who, "i", signal_number),
+ SD_BUS_NO_RESULT,
+ bus_session_method_kill,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("TakeControl",
+ SD_BUS_ARGS("b", force),
+ SD_BUS_NO_RESULT,
+ method_take_control,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("ReleaseControl",
+ NULL,
+ NULL,
+ method_release_control,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetType",
+ SD_BUS_ARGS("s", type),
+ SD_BUS_NO_RESULT,
+ method_set_type,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetDisplay",
+ SD_BUS_ARGS("s", display),
+ SD_BUS_NO_RESULT,
+ method_set_display,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetTTY",
+ SD_BUS_ARGS("h", tty_fd),
+ SD_BUS_NO_RESULT,
+ method_set_tty,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("TakeDevice",
+ SD_BUS_ARGS("u", major, "u", minor),
+ SD_BUS_RESULT("h", fd, "b", inactive),
+ method_take_device,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ReleaseDevice",
+ SD_BUS_ARGS("u", major, "u", minor),
+ SD_BUS_NO_RESULT,
+ method_release_device,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("PauseDeviceComplete",
+ SD_BUS_ARGS("u", major, "u", minor),
+ SD_BUS_NO_RESULT,
+ method_pause_device_complete,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetBrightness",
+ SD_BUS_ARGS("s", subsystem, "s", name, "u", brightness),
+ SD_BUS_NO_RESULT,
+ method_set_brightness,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
+ SD_BUS_SIGNAL_WITH_ARGS("PauseDevice",
+ SD_BUS_ARGS("u", major, "u", minor, "s", type),
+ 0),
+ SD_BUS_SIGNAL_WITH_ARGS("ResumeDevice",
+ SD_BUS_ARGS("u", major, "u", minor, "h", fd),
+ 0),
+ SD_BUS_SIGNAL("Lock", NULL, 0),
+ SD_BUS_SIGNAL("Unlock", NULL, 0),
+
+ SD_BUS_VTABLE_END
+};
+
+const BusObjectImplementation session_object = {
+ "/org/freedesktop/login1/session",
+ "org.freedesktop.login1.Session",
+ .fallback_vtables = BUS_FALLBACK_VTABLES({session_vtable, session_object_find}),
+ .node_enumerator = session_node_enumerator,
+};
diff --git a/src/login/logind-session-dbus.h b/src/login/logind-session-dbus.h
new file mode 100644
index 0000000..751ca86
--- /dev/null
+++ b/src/login/logind-session-dbus.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "bus-object.h"
+#include "logind-session.h"
+
+extern const BusObjectImplementation session_object;
+
+char *session_bus_path(Session *s);
+
+int session_send_signal(Session *s, bool new_session);
+int session_send_changed(Session *s, const char *properties, ...) _sentinel_;
+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 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);
+int bus_session_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_session_method_kill(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
new file mode 100644
index 0000000..44d8d52
--- /dev/null
+++ b/src/login/logind-session-device.c
@@ -0,0 +1,507 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include "sd-device.h"
+#include "sd-daemon.h"
+
+#include "alloc-util.h"
+#include "bus-util.h"
+#include "daemon-util.h"
+#include "fd-util.h"
+#include "logind-session-dbus.h"
+#include "logind-session-device.h"
+#include "missing_drm.h"
+#include "missing_input.h"
+#include "parse-util.h"
+
+enum SessionDeviceNotifications {
+ SESSION_DEVICE_RESUME,
+ SESSION_DEVICE_TRY_PAUSE,
+ SESSION_DEVICE_PAUSE,
+ SESSION_DEVICE_RELEASE,
+};
+
+static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_free_ char *path = NULL;
+ const char *t = NULL;
+ uint32_t major, minor;
+ int r;
+
+ assert(sd);
+
+ major = major(sd->dev);
+ minor = minor(sd->dev);
+
+ if (!sd->session->controller)
+ return 0;
+
+ path = session_bus_path(sd->session);
+ if (!path)
+ return -ENOMEM;
+
+ r = sd_bus_message_new_signal(
+ sd->session->manager->bus,
+ &m, path,
+ "org.freedesktop.login1.Session",
+ type == SESSION_DEVICE_RESUME ? "ResumeDevice" : "PauseDevice");
+ if (!m)
+ return r;
+
+ r = sd_bus_message_set_destination(m, sd->session->controller);
+ if (r < 0)
+ return r;
+
+ switch (type) {
+
+ case SESSION_DEVICE_RESUME:
+ r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
+ if (r < 0)
+ return r;
+ break;
+
+ case SESSION_DEVICE_TRY_PAUSE:
+ t = "pause";
+ break;
+
+ case SESSION_DEVICE_PAUSE:
+ t = "force";
+ break;
+
+ case SESSION_DEVICE_RELEASE:
+ t = "gone";
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (t) {
+ r = sd_bus_message_append(m, "uus", major, minor, t);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_send(sd->session->manager->bus, m, NULL);
+}
+
+static void sd_eviocrevoke(int fd) {
+ static bool warned = false;
+
+ assert(fd >= 0);
+
+ if (ioctl(fd, EVIOCREVOKE, NULL) < 0) {
+
+ if (errno == EINVAL && !warned) {
+ log_warning_errno(errno, "Kernel does not support evdev-revocation: %m");
+ warned = true;
+ }
+ }
+}
+
+static int sd_drmsetmaster(int fd) {
+ assert(fd >= 0);
+ return RET_NERRNO(ioctl(fd, DRM_IOCTL_SET_MASTER, 0));
+}
+
+static int sd_drmdropmaster(int fd) {
+ assert(fd >= 0);
+ return RET_NERRNO(ioctl(fd, DRM_IOCTL_DROP_MASTER, 0));
+}
+
+static int session_device_open(SessionDevice *sd, bool active) {
+ int fd, r;
+
+ assert(sd);
+ assert(sd->type != DEVICE_TYPE_UNKNOWN);
+ assert(sd->node);
+
+ /* open device and try to get a udev_device from it */
+ fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (fd < 0)
+ return -errno;
+
+ switch (sd->type) {
+
+ case DEVICE_TYPE_DRM:
+ if (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);
+ 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. */
+ (void) sd_drmdropmaster(fd);
+ break;
+
+ case DEVICE_TYPE_EVDEV:
+ if (!active)
+ sd_eviocrevoke(fd);
+ break;
+
+ case DEVICE_TYPE_UNKNOWN:
+ default:
+ /* fallback for devices without synchronizations */
+ break;
+ }
+
+ return fd;
+}
+
+static int session_device_start(SessionDevice *sd) {
+ int r;
+
+ assert(sd);
+ assert(session_is_active(sd->session));
+
+ if (sd->active)
+ return 0;
+
+ switch (sd->type) {
+
+ case DEVICE_TYPE_DRM:
+ if (sd->fd < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADF),
+ "Failed to re-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
+
+ /* Device is kept open. Simply call drmSetMaster() and hope there is no-one else. In case it fails, we
+ * keep the device paused. Maybe at some point we have a drmStealMaster(). */
+ r = sd_drmsetmaster(sd->fd);
+ if (r < 0)
+ return r;
+ break;
+
+ case DEVICE_TYPE_EVDEV:
+ /* Evdev devices are revoked while inactive. Reopen it and we are fine. */
+ r = session_device_open(sd, true);
+ if (r < 0)
+ return r;
+
+ /* For evdev devices, the file descriptor might be left uninitialized. This might happen while resuming
+ * into a session and logind has been restarted right before. */
+ close_and_replace(sd->fd, r);
+ break;
+
+ case DEVICE_TYPE_UNKNOWN:
+ default:
+ /* fallback for devices without synchronizations */
+ break;
+ }
+
+ sd->active = true;
+ return 0;
+}
+
+static void session_device_stop(SessionDevice *sd) {
+ assert(sd);
+
+ if (!sd->active)
+ return;
+
+ switch (sd->type) {
+
+ case DEVICE_TYPE_DRM:
+ if (sd->fd < 0) {
+ log_error("Failed to de-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
+ return;
+ }
+
+ /* On DRM devices we simply drop DRM-Master but keep it open.
+ * This allows the user to keep resources allocated. The
+ * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
+ * circumventing this. */
+ sd_drmdropmaster(sd->fd);
+ break;
+
+ case DEVICE_TYPE_EVDEV:
+ /* Revoke access on evdev file-descriptors during deactivation.
+ * This will basically prevent any operations on the fd and
+ * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
+ * protection this way. */
+ sd_eviocrevoke(sd->fd);
+ break;
+
+ case DEVICE_TYPE_UNKNOWN:
+ default:
+ /* fallback for devices without synchronization */
+ break;
+ }
+
+ sd->active = false;
+}
+
+static DeviceType detect_device_type(sd_device *dev) {
+ const char *sysname, *subsystem;
+ DeviceType type = DEVICE_TYPE_UNKNOWN;
+
+ if (sd_device_get_sysname(dev, &sysname) < 0 ||
+ sd_device_get_subsystem(dev, &subsystem) < 0)
+ return type;
+
+ if (streq(subsystem, "drm")) {
+ if (startswith(sysname, "card"))
+ type = DEVICE_TYPE_DRM;
+ } else if (streq(subsystem, "input")) {
+ if (startswith(sysname, "event"))
+ type = DEVICE_TYPE_EVDEV;
+ }
+
+ return type;
+}
+
+static int session_device_verify(SessionDevice *sd) {
+ _cleanup_(sd_device_unrefp) sd_device *p = NULL;
+ const char *sp, *node;
+ sd_device *dev;
+ int r;
+
+ r = sd_device_new_from_devnum(&p, 'c', sd->dev);
+ if (r < 0)
+ return r;
+
+ dev = p;
+
+ if (sd_device_get_syspath(dev, &sp) < 0 ||
+ sd_device_get_devname(dev, &node) < 0)
+ return -EINVAL;
+
+ /* detect device type so we can find the correct sysfs parent */
+ sd->type = detect_device_type(dev);
+
+ /* Prevent opening unsupported devices. Especially devices of
+ * subsystem "input" must be opened via the evdev node as
+ * we require EVIOCREVOKE. */
+ switch (sd->type) {
+ case DEVICE_TYPE_EVDEV:
+ /* for evdev devices we need the parent node as device */
+ if (sd_device_get_parent_with_subsystem_devtype(p, "input", NULL, &dev) < 0)
+ return -ENODEV;
+ if (sd_device_get_syspath(dev, &sp) < 0)
+ return -ENODEV;
+ break;
+
+ case DEVICE_TYPE_DRM:
+ break;
+
+ case DEVICE_TYPE_UNKNOWN:
+ default:
+ return -ENODEV;
+ }
+
+ /* search for an existing seat device and return it if available */
+ sd->device = hashmap_get(sd->session->manager->devices, sp);
+ if (!sd->device) {
+ /* The caller might have gotten the udev event before we were
+ * able to process it. Hence, fake the "add" event and let the
+ * logind-manager handle the new device. */
+ r = manager_process_seat_device(sd->session->manager, dev);
+ if (r < 0)
+ return r;
+
+ /* if it's still not available, then the device is invalid */
+ sd->device = hashmap_get(sd->session->manager->devices, sp);
+ if (!sd->device)
+ return -ENODEV;
+ }
+
+ if (sd->device->seat != sd->session->seat)
+ return -EPERM;
+
+ sd->node = strdup(node);
+ if (!sd->node)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
+ SessionDevice *sd;
+ int r;
+
+ assert(s);
+ assert(out);
+
+ if (!s->seat)
+ return -EPERM;
+
+ sd = new0(SessionDevice, 1);
+ if (!sd)
+ return -ENOMEM;
+
+ sd->session = s;
+ sd->dev = dev;
+ sd->fd = -EBADF;
+ sd->type = DEVICE_TYPE_UNKNOWN;
+
+ r = session_device_verify(sd);
+ if (r < 0)
+ goto error;
+
+ r = hashmap_put(s->devices, &sd->dev, sd);
+ if (r < 0)
+ goto error;
+
+ if (open_device) {
+ /* Open the device for the first time. We need a valid fd to pass back
+ * to the caller. If the session is not active, this _might_ immediately
+ * revoke access and thus invalidate the fd. But this is still needed
+ * to pass a valid fd back. */
+ sd->active = session_is_active(s);
+ r = session_device_open(sd, sd->active);
+ if (r < 0) {
+ /* EINVAL _may_ mean a master is active; retry inactive */
+ if (sd->active && r == -EINVAL) {
+ sd->active = false;
+ r = session_device_open(sd, false);
+ }
+ if (r < 0)
+ goto error;
+ }
+ sd->fd = r;
+ }
+
+ LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
+
+ *out = sd;
+ return 0;
+
+error:
+ hashmap_remove(s->devices, &sd->dev);
+ free(sd->node);
+ free(sd);
+ return r;
+}
+
+SessionDevice *session_device_free(SessionDevice *sd) {
+ if (!sd)
+ return NULL;
+
+ /* Make sure to remove the pushed fd. */
+ if (sd->pushed_fd)
+ (void) notify_remove_fd_warnf("session-%s-device-%u-%u", sd->session->id, major(sd->dev), minor(sd->dev));
+
+ session_device_stop(sd);
+ session_device_notify(sd, SESSION_DEVICE_RELEASE);
+ safe_close(sd->fd);
+
+ LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
+
+ hashmap_remove(sd->session->devices, &sd->dev);
+
+ free(sd->node);
+
+ return mfree(sd);
+}
+
+void session_device_complete_pause(SessionDevice *sd) {
+ SessionDevice *iter;
+
+ if (!sd->active)
+ return;
+
+ session_device_stop(sd);
+
+ /* if not all devices are paused, wait for further completion events */
+ HASHMAP_FOREACH(iter, sd->session->devices)
+ if (iter->active)
+ return;
+
+ /* complete any pending session switch */
+ seat_complete_switch(sd->session->seat);
+}
+
+void session_device_resume_all(Session *s) {
+ SessionDevice *sd;
+
+ assert(s);
+
+ HASHMAP_FOREACH(sd, s->devices) {
+ if (sd->active)
+ continue;
+
+ if (session_device_start(sd) < 0)
+ continue;
+ if (session_device_save(sd) < 0)
+ continue;
+
+ session_device_notify(sd, SESSION_DEVICE_RESUME);
+ }
+}
+
+void session_device_pause_all(Session *s) {
+ SessionDevice *sd;
+
+ assert(s);
+
+ HASHMAP_FOREACH(sd, s->devices) {
+ if (!sd->active)
+ continue;
+
+ session_device_stop(sd);
+ session_device_notify(sd, SESSION_DEVICE_PAUSE);
+ }
+}
+
+unsigned session_device_try_pause_all(Session *s) {
+ unsigned num_pending = 0;
+ SessionDevice *sd;
+
+ assert(s);
+
+ HASHMAP_FOREACH(sd, s->devices) {
+ if (!sd->active)
+ continue;
+
+ session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
+ num_pending++;
+ }
+
+ return num_pending;
+}
+
+int session_device_save(SessionDevice *sd) {
+ const char *id;
+ int r;
+
+ assert(sd);
+
+ /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
+ * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
+ * don't have to handle them differently later.
+ *
+ * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
+ * is revoked. */
+
+ if (sd->pushed_fd)
+ return 0;
+
+ /* Session ID does not contain separators. */
+ id = sd->session->id;
+ assert(*(id + strcspn(id, "-\n")) == '\0');
+
+ r = notify_push_fdf(sd->fd, "session-%s-device-%u-%u", id, major(sd->dev), minor(sd->dev));
+ if (r < 0)
+ return r;
+
+ sd->pushed_fd = true;
+ return 1;
+}
+
+void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
+ assert(fd >= 0);
+ assert(sd);
+ assert(sd->fd < 0);
+ assert(!sd->active);
+
+ sd->fd = fd;
+ sd->pushed_fd = true;
+ sd->active = active;
+}
diff --git a/src/login/logind-session-device.h b/src/login/logind-session-device.h
new file mode 100644
index 0000000..04654d1
--- /dev/null
+++ b/src/login/logind-session-device.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef enum DeviceType DeviceType;
+typedef struct SessionDevice SessionDevice;
+
+#include "list.h"
+#include "logind.h"
+
+enum DeviceType {
+ DEVICE_TYPE_UNKNOWN,
+ DEVICE_TYPE_DRM,
+ DEVICE_TYPE_EVDEV,
+};
+
+struct SessionDevice {
+ Session *session;
+ Device *device;
+
+ dev_t dev;
+ char *node;
+ int fd;
+ DeviceType type:3;
+ bool active:1;
+ bool pushed_fd:1;
+
+ LIST_FIELDS(struct SessionDevice, sd_by_device);
+};
+
+int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out);
+SessionDevice *session_device_free(SessionDevice *sd);
+DEFINE_TRIVIAL_CLEANUP_FUNC(SessionDevice*, session_device_free);
+
+void session_device_complete_pause(SessionDevice *sd);
+
+void session_device_resume_all(Session *s);
+void session_device_pause_all(Session *s);
+unsigned session_device_try_pause_all(Session *s);
+
+int session_device_save(SessionDevice *sd);
+void session_device_attach_fd(SessionDevice *sd, int fd, bool active);
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
new file mode 100644
index 0000000..3988e55
--- /dev/null
+++ b/src/login/logind-session.c
@@ -0,0 +1,1624 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/kd.h>
+#include <linux/vt.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "audit-util.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "devnum-util.h"
+#include "env-file.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "io-util.h"
+#include "logind-dbus.h"
+#include "logind-seat-dbus.h"
+#include "logind-session-dbus.h"
+#include "logind-session.h"
+#include "logind-user-dbus.h"
+#include "mkdir-label.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "serialize.h"
+#include "string-table.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "tmpfile-util.h"
+#include "uid-alloc-range.h"
+#include "user-util.h"
+
+#define RELEASE_USEC (20*USEC_PER_SEC)
+
+static void session_remove_fifo(Session *s);
+static void session_restore_vt(Session *s);
+
+int session_new(Session **ret, Manager *m, const char *id) {
+ _cleanup_(session_freep) Session *s = NULL;
+ int r;
+
+ assert(ret);
+ assert(m);
+ assert(id);
+
+ if (!session_id_valid(id))
+ return -EINVAL;
+
+ s = new(Session, 1);
+ if (!s)
+ return -ENOMEM;
+
+ *s = (Session) {
+ .manager = m,
+ .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)
+ return -ENOMEM;
+
+ s->id = basename(s->state_file);
+
+ s->devices = hashmap_new(&devt_hash_ops);
+ if (!s->devices)
+ return -ENOMEM;
+
+ r = hashmap_put(m->sessions, s->id, s);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(s);
+ return 0;
+}
+
+static void session_reset_leader(Session *s) {
+ assert(s);
+
+ if (!pidref_is_set(&s->leader))
+ return;
+
+ (void) hashmap_remove_value(s->manager->sessions_by_leader, &s->leader, s);
+
+ return pidref_done(&s->leader);
+}
+
+Session* session_free(Session *s) {
+ SessionDevice *sd;
+
+ if (!s)
+ return NULL;
+
+ 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);
+
+ session_drop_controller(s);
+
+ while ((sd = hashmap_first(s->devices)))
+ session_device_free(sd);
+
+ hashmap_free(s->devices);
+
+ if (s->user) {
+ LIST_REMOVE(sessions_by_user, s->user->sessions, s);
+
+ if (s->user->display == s)
+ s->user->display = NULL;
+
+ user_update_last_session_timer(s->user);
+ }
+
+ if (s->seat) {
+ if (s->seat->active == s)
+ s->seat->active = NULL;
+ if (s->seat->pending_switch == s)
+ s->seat->pending_switch = NULL;
+
+ seat_evict_position(s->seat, s);
+ LIST_REMOVE(sessions_by_seat, s->seat->sessions, s);
+ }
+
+ if (s->scope) {
+ hashmap_remove(s->manager->session_units, s->scope);
+ free(s->scope);
+ }
+
+ free(s->scope_job);
+
+ session_reset_leader(s);
+
+ sd_bus_message_unref(s->create_message);
+
+ free(s->tty);
+ free(s->display);
+ free(s->remote_host);
+ free(s->remote_user);
+ free(s->service);
+ free(s->desktop);
+
+ hashmap_remove(s->manager->sessions, s->id);
+
+ sd_event_source_unref(s->fifo_event_source);
+ safe_close(s->fifo_fd);
+
+ /* 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);
+
+ return mfree(s);
+}
+
+void session_set_user(Session *s, User *u) {
+ assert(s);
+ assert(!s->user);
+
+ s->user = u;
+ LIST_PREPEND(sessions_by_user, u->sessions, s);
+
+ user_update_last_session_timer(u);
+}
+
+int session_set_leader_consume(Session *s, PidRef _leader) {
+ _cleanup_(pidref_done) PidRef pidref = _leader;
+ int r;
+
+ assert(s);
+ assert(pidref_is_set(&pidref));
+
+ if (pidref_equal(&s->leader, &pidref))
+ return 0;
+
+ session_reset_leader(s);
+
+ s->leader = TAKE_PIDREF(pidref);
+
+ r = hashmap_ensure_put(&s->manager->sessions_by_leader, &pidref_hash_ops, &s->leader, s);
+ if (r < 0)
+ return r;
+ assert(r > 0);
+
+ (void) audit_session_from_pid(s->leader.pid, &s->audit_id);
+
+ return 1;
+}
+
+static void session_save_devices(Session *s, FILE *f) {
+ SessionDevice *sd;
+
+ if (!hashmap_isempty(s->devices)) {
+ fprintf(f, "DEVICES=");
+ HASHMAP_FOREACH(sd, s->devices)
+ fprintf(f, DEVNUM_FORMAT_STR " ", DEVNUM_FORMAT_VAL(sd->dev));
+ fprintf(f, "\n");
+ }
+}
+
+int session_save(Session *s) {
+ _cleanup_(unlink_and_freep) char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(s);
+
+ if (!s->user)
+ return -ESTALE;
+
+ if (!s->started)
+ return 0;
+
+ r = mkdir_safe_label("/run/systemd/sessions", 0755, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ goto fail;
+
+ r = fopen_temporary(s->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "UID="UID_FMT"\n"
+ "USER=%s\n"
+ "ACTIVE=%i\n"
+ "IS_DISPLAY=%i\n"
+ "STATE=%s\n"
+ "REMOTE=%i\n",
+ s->user->user_record->uid,
+ s->user->user_record->user_name,
+ session_is_active(s),
+ s->user->display == s,
+ session_state_to_string(session_get_state(s)),
+ s->remote);
+
+ if (s->type >= 0)
+ fprintf(f, "TYPE=%s\n", session_type_to_string(s->type));
+
+ if (s->original_type >= 0)
+ fprintf(f, "ORIGINAL_TYPE=%s\n", session_type_to_string(s->original_type));
+
+ if (s->class >= 0)
+ fprintf(f, "CLASS=%s\n", session_class_to_string(s->class));
+
+ if (s->scope)
+ fprintf(f, "SCOPE=%s\n", s->scope);
+ if (s->scope_job)
+ fprintf(f, "SCOPE_JOB=%s\n", s->scope_job);
+
+ if (s->fifo_path)
+ fprintf(f, "FIFO=%s\n", s->fifo_path);
+
+ if (s->seat)
+ fprintf(f, "SEAT=%s\n", s->seat->id);
+
+ if (s->tty)
+ fprintf(f, "TTY=%s\n", s->tty);
+
+ if (s->tty_validity >= 0)
+ fprintf(f, "TTY_VALIDITY=%s\n", tty_validity_to_string(s->tty_validity));
+
+ if (s->display)
+ fprintf(f, "DISPLAY=%s\n", s->display);
+
+ if (s->remote_host) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = cescape(s->remote_host);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "REMOTE_HOST=%s\n", escaped);
+ }
+
+ if (s->remote_user) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = cescape(s->remote_user);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "REMOTE_USER=%s\n", escaped);
+ }
+
+ if (s->service) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = cescape(s->service);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "SERVICE=%s\n", escaped);
+ }
+
+ if (s->desktop) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = cescape(s->desktop);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "DESKTOP=%s\n", escaped);
+ }
+
+ if (s->seat && seat_has_vts(s->seat))
+ fprintf(f, "VTNR=%u\n", s->vtnr);
+
+ if (!s->vtnr)
+ fprintf(f, "POSITION=%u\n", s->position);
+
+ if (pidref_is_set(&s->leader))
+ fprintf(f, "LEADER="PID_FMT"\n", s->leader.pid);
+
+ if (audit_session_is_valid(s->audit_id))
+ fprintf(f, "AUDIT=%"PRIu32"\n", s->audit_id);
+
+ if (dual_timestamp_is_set(&s->timestamp))
+ fprintf(f,
+ "REALTIME="USEC_FMT"\n"
+ "MONOTONIC="USEC_FMT"\n",
+ s->timestamp.realtime,
+ s->timestamp.monotonic);
+
+ if (s->controller) {
+ fprintf(f, "CONTROLLER=%s\n", s->controller);
+ session_save_devices(s, f);
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, s->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ temp_path = mfree(temp_path);
+ return 0;
+
+fail:
+ (void) unlink(s->state_file);
+
+ return log_error_errno(r, "Failed to save session data %s: %m", s->state_file);
+}
+
+static int session_load_devices(Session *s, const char *devices) {
+ int r = 0;
+
+ assert(s);
+
+ 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;
+ break;
+ }
+
+ k = parse_devnum(word, &dev);
+ if (k < 0) {
+ 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;
+ }
+
+ if (r < 0)
+ log_error_errno(r, "Loading session devices for session %s failed: %m", s->id);
+
+ return r;
+}
+
+int session_load(Session *s) {
+ _cleanup_free_ char *remote = NULL,
+ *seat = NULL,
+ *tty_validity = NULL,
+ *vtnr = NULL,
+ *state = NULL,
+ *position = NULL,
+ *leader = NULL,
+ *type = NULL,
+ *original_type = NULL,
+ *class = NULL,
+ *uid = NULL,
+ *realtime = NULL,
+ *monotonic = NULL,
+ *controller = NULL,
+ *active = NULL,
+ *devices = NULL,
+ *is_display = NULL;
+
+ int k, r;
+
+ 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);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read %s: %m", s->state_file);
+
+ if (!s->user) {
+ uid_t u;
+ User *user;
+
+ if (!uid)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "UID not specified for session %s",
+ s->id);
+
+ r = parse_uid(uid, &u);
+ if (r < 0) {
+ log_error("Failed to parse UID value %s for session %s.", uid, s->id);
+ return r;
+ }
+
+ user = hashmap_get(s->manager->users, UID_TO_PTR(u));
+ if (!user)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "User of session %s not known.",
+ s->id);
+
+ session_set_user(s, user);
+ }
+
+ if (remote) {
+ k = parse_boolean(remote);
+ if (k >= 0)
+ s->remote = k;
+ }
+
+ if (vtnr)
+ safe_atou(vtnr, &s->vtnr);
+
+ if (seat && !s->seat) {
+ Seat *o;
+
+ o = hashmap_get(s->manager->seats, seat);
+ if (o)
+ r = seat_attach_session(o, s);
+ if (!o || r < 0)
+ log_error("Cannot attach session %s to seat %s", s->id, seat);
+ }
+
+ if (!s->seat || !seat_has_vts(s->seat))
+ s->vtnr = 0;
+
+ if (position && s->seat) {
+ unsigned npos;
+
+ safe_atou(position, &npos);
+ seat_claim_position(s->seat, s, npos);
+ }
+
+ if (tty_validity) {
+ TTYValidity v;
+
+ v = tty_validity_from_string(tty_validity);
+ if (v < 0)
+ log_debug("Failed to parse TTY validity: %s", tty_validity);
+ else
+ 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;
+
+ t = session_type_from_string(type);
+ if (t >= 0)
+ s->type = t;
+ }
+
+ if (original_type) {
+ SessionType ot;
+
+ ot = session_type_from_string(original_type);
+ if (ot >= 0)
+ s->original_type = ot;
+ } else
+ /* Pre-v246 compat: initialize original_type if not set in the state file */
+ s->original_type = s->type;
+
+ if (class) {
+ SessionClass c;
+
+ c = session_class_from_string(class);
+ if (c >= 0)
+ s->class = c;
+ }
+
+ if (streq_ptr(state, "closing"))
+ s->stopping = true;
+
+ if (s->fifo_path) {
+ int fd;
+
+ /* If we open an unopened pipe for reading we will not
+ get an EOF. to trigger an EOF we hence open it for
+ writing, but close it right away which then will
+ trigger the EOF. This will happen immediately if no
+ other process has the FIFO open for writing, i. e.
+ when the session died before logind (re)started. */
+
+ fd = session_create_fifo(s);
+ safe_close(fd);
+ }
+
+ if (realtime)
+ (void) deserialize_usec(realtime, &s->timestamp.realtime);
+ if (monotonic)
+ (void) deserialize_usec(monotonic, &s->timestamp.monotonic);
+
+ if (active) {
+ k = parse_boolean(active);
+ if (k >= 0)
+ s->was_active = k;
+ }
+
+ if (is_display) {
+ /* Note that when enumerating users are loaded before sessions, hence the display session to use is
+ * something we have to store along with the session and not the user, as in that case we couldn't
+ * apply it at the time we load the user. */
+
+ k = parse_boolean(is_display);
+ if (k < 0)
+ log_warning_errno(k, "Failed to parse IS_DISPLAY session property: %m");
+ else if (k > 0)
+ s->user->display = s;
+ }
+
+ if (controller) {
+ if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0) {
+ session_set_controller(s, controller, false, false);
+ session_load_devices(s, devices);
+ } else
+ session_restore_vt(s);
+ }
+
+ return r;
+}
+
+int session_activate(Session *s) {
+ unsigned num_pending;
+
+ assert(s);
+ assert(s->user);
+
+ if (!s->seat)
+ return -EOPNOTSUPP;
+
+ if (s->seat->active == s)
+ return 0;
+
+ /* on seats with VTs, we let VTs manage session-switching */
+ if (seat_has_vts(s->seat)) {
+ if (s->vtnr == 0)
+ return -EOPNOTSUPP;
+
+ return chvt(s->vtnr);
+ }
+
+ /* On seats without VTs, we implement session-switching in logind. We
+ * try to pause all session-devices and wait until the session
+ * controller acknowledged them. Once all devices are asleep, we simply
+ * switch the active session and be done.
+ * We save the session we want to switch to in seat->pending_switch and
+ * seat_complete_switch() will perform the final switch. */
+
+ s->seat->pending_switch = s;
+
+ /* if no devices are running, immediately perform the session switch */
+ num_pending = session_device_try_pause_all(s);
+ if (!num_pending)
+ seat_complete_switch(s->seat);
+
+ return 0;
+}
+
+static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_error *error) {
+ 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));
+
+ s->scope = TAKE_PTR(scope);
+ }
+
+ (void) hashmap_put(s->manager->session_units, s->scope, s);
+
+ return 0;
+}
+
+static int session_dispatch_stop_on_idle(sd_event_source *source, uint64_t t, void *userdata) {
+ Session *s = userdata;
+ dual_timestamp ts;
+ int r, idle;
+
+ assert(s);
+
+ if (s->stopping)
+ return 0;
+
+ idle = session_get_idle_hint(s, &ts);
+ if (idle) {
+ log_info("Session \"%s\" of user \"%s\" is idle, stopping.", s->id, s->user->user_record->user_name);
+
+ return session_stop(s, /* force */ true);
+ }
+
+ r = sd_event_source_set_time(
+ source,
+ usec_add(dual_timestamp_is_set(&ts) ? ts.monotonic : now(CLOCK_MONOTONIC),
+ s->manager->stop_idle_session_usec));
+ if (r < 0)
+ return log_error_errno(r, "Failed to configure stop on idle session event source: %m");
+
+ r = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable stop on idle session event source: %m");
+
+ return 1;
+}
+
+static int session_setup_stop_on_idle_timer(Session *s) {
+ int r;
+
+ assert(s);
+
+ if (s->manager->stop_idle_session_usec == USEC_INFINITY)
+ return 0;
+
+ r = sd_event_add_time_relative(
+ s->manager->event,
+ &s->stop_on_idle_event_source,
+ CLOCK_MONOTONIC,
+ s->manager->stop_idle_session_usec,
+ 0,
+ session_dispatch_stop_on_idle, s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add stop on idle session event source: %m");
+
+ return 0;
+}
+
+int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
+ int r;
+
+ assert(s);
+
+ if (!s->user)
+ return -ESTALE;
+
+ if (s->stopping)
+ return -EINVAL;
+
+ if (s->started)
+ return 0;
+
+ r = user_start(s->user);
+ if (r < 0)
+ return r;
+
+ r = session_start_scope(s, properties, error);
+ if (r < 0)
+ return r;
+
+ r = session_setup_stop_on_idle_timer(s);
+ if (r < 0)
+ return r;
+
+ log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO,
+ "MESSAGE_ID=" SD_MESSAGE_SESSION_START_STR,
+ "SESSION_ID=%s", s->id,
+ "USER_ID=%s", s->user->user_record->user_name,
+ "LEADER="PID_FMT, s->leader.pid,
+ LOG_MESSAGE("New session %s of user %s.", s->id, s->user->user_record->user_name));
+
+ if (!dual_timestamp_is_set(&s->timestamp))
+ dual_timestamp_now(&s->timestamp);
+
+ if (s->seat)
+ seat_read_active_vt(s->seat);
+
+ s->started = true;
+
+ user_elect_display(s->user);
+
+ /* Save data */
+ session_save(s);
+ user_save(s->user);
+ if (s->seat)
+ 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);
+
+ return 0;
+}
+
+static int session_stop_scope(Session *s, bool force) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(s);
+
+ if (!s->scope)
+ return 0;
+
+ /* Let's always abandon the scope first. This tells systemd that we are not interested anymore, and everything
+ * that is left in the scope is "left-over". Informing systemd about this has the benefit that it will log
+ * when killing any processes left after this point. */
+ r = manager_abandon_scope(s->manager, s->scope, &error);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to abandon session scope, ignoring: %s", bus_error_message(&error, r));
+ sd_bus_error_free(&error);
+ }
+
+ s->scope_job = mfree(s->scope_job);
+
+ /* Optionally, let's kill everything that's left now. */
+ if (force ||
+ (s->user->user_record->kill_processes != 0 &&
+ (s->user->user_record->kill_processes > 0 ||
+ manager_shall_kill(s->manager, s->user->user_record->user_name)))) {
+
+ r = manager_stop_unit(s->manager, s->scope, force ? "replace" : "fail", &error, &s->scope_job);
+ if (r < 0) {
+ if (force)
+ return log_error_errno(r, "Failed to stop session scope: %s", bus_error_message(&error, r));
+
+ log_warning_errno(r, "Failed to stop session scope, ignoring: %s", bus_error_message(&error, r));
+ }
+ } else {
+
+ /* With no killing, this session is allowed to persist in "closing" state indefinitely.
+ * Therefore session stop and session removal may be two distinct events.
+ * Session stop is quite significant on its own, let's log it. */
+ log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO,
+ "SESSION_ID=%s", s->id,
+ "USER_ID=%s", s->user->user_record->user_name,
+ "LEADER="PID_FMT, s->leader.pid,
+ LOG_MESSAGE("Session %s logged out. Waiting for processes to exit.", s->id));
+ }
+
+ return 0;
+}
+
+int session_stop(Session *s, bool force) {
+ int r;
+
+ assert(s);
+
+ /* This is called whenever we begin with tearing down a session record. It's called in four cases: explicit API
+ * request via the bus (either directly for the session object or for the seat or user object this session
+ * belongs to; 'force' is true), or due to automatic GC (i.e. scope vanished; 'force' is false), or because the
+ * session FIFO saw an EOF ('force' is false), or because the release timer hit ('force' is false). */
+
+ if (!s->user)
+ return -ESTALE;
+ if (!s->started)
+ return 0;
+ if (s->stopping)
+ return 0;
+
+ 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);
+
+ /* We are going down, don't care about FIFOs anymore */
+ session_remove_fifo(s);
+
+ /* Kill cgroup */
+ r = session_stop_scope(s, force);
+
+ s->stopping = true;
+
+ user_elect_display(s->user);
+
+ session_save(s);
+ user_save(s->user);
+
+ return r;
+}
+
+int session_finalize(Session *s) {
+ SessionDevice *sd;
+
+ assert(s);
+
+ if (!s->user)
+ return -ESTALE;
+
+ if (s->started)
+ log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO,
+ "MESSAGE_ID=" SD_MESSAGE_SESSION_STOP_STR,
+ "SESSION_ID=%s", s->id,
+ "USER_ID=%s", s->user->user_record->user_name,
+ "LEADER="PID_FMT, s->leader.pid,
+ 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);
+
+ /* Kill session devices */
+ while ((sd = hashmap_first(s->devices)))
+ session_device_free(sd);
+
+ (void) unlink(s->state_file);
+ session_add_to_gc_queue(s);
+ user_add_to_gc_queue(s->user);
+
+ if (s->started) {
+ session_send_signal(s, false);
+ s->started = false;
+ }
+
+ if (s->seat) {
+ if (s->seat->active == s)
+ seat_set_active(s->seat, NULL);
+
+ seat_save(s->seat);
+ }
+
+ session_reset_leader(s);
+
+ user_save(s->user);
+ user_send_changed(s->user, "Display", NULL);
+
+ return 0;
+}
+
+static int release_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) {
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(es);
+
+ session_stop(s, /* force = */ false);
+ return 0;
+}
+
+int session_release(Session *s) {
+ assert(s);
+
+ if (!s->started || s->stopping)
+ return 0;
+
+ if (s->timer_event_source)
+ return 0;
+
+ return sd_event_add_time_relative(
+ s->manager->event,
+ &s->timer_event_source,
+ CLOCK_MONOTONIC,
+ RELEASE_USEC, 0,
+ release_timeout_callback, s);
+}
+
+bool session_is_active(Session *s) {
+ assert(s);
+
+ if (!s->seat)
+ return true;
+
+ return s->seat->active == s;
+}
+
+static int get_tty_atime(const char *tty, usec_t *atime) {
+ _cleanup_free_ char *p = NULL;
+ struct stat st;
+
+ assert(tty);
+ assert(atime);
+
+ if (!path_is_absolute(tty)) {
+ p = path_join("/dev", tty);
+ if (!p)
+ return -ENOMEM;
+
+ tty = p;
+ } else if (!path_startswith(tty, "/dev/"))
+ return -ENOENT;
+
+ if (lstat(tty, &st) < 0)
+ return -errno;
+
+ *atime = timespec_load(&st.st_atim);
+ return 0;
+}
+
+static int get_process_ctty_atime(pid_t pid, usec_t *atime) {
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ assert(pid > 0);
+ assert(atime);
+
+ r = get_ctty(pid, NULL, &p);
+ if (r < 0)
+ return r;
+
+ return get_tty_atime(p, atime);
+}
+
+int session_get_idle_hint(Session *s, dual_timestamp *t) {
+ usec_t atime = 0, dtime = 0;
+ int r;
+
+ assert(s);
+
+ /* Graphical sessions have an explicit idle hint */
+ if (SESSION_TYPE_IS_GRAPHICAL(s->type)) {
+ if (t)
+ *t = s->idle_hint_timestamp;
+
+ 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;
+ }
+
+ /* 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)
+ *t = DUAL_TIMESTAMP_NULL;
+
+ return false;
+
+found_atime:
+ if (t)
+ dual_timestamp_from_realtime(t, atime);
+
+ if (s->manager->idle_action_usec > 0 && s->manager->stop_idle_session_usec != USEC_INFINITY)
+ dtime = MIN(s->manager->idle_action_usec, s->manager->stop_idle_session_usec);
+ else if (s->manager->idle_action_usec > 0)
+ dtime = s->manager->idle_action_usec;
+ else if (s->manager->stop_idle_session_usec != USEC_INFINITY)
+ dtime = s->manager->stop_idle_session_usec;
+ else
+ return false;
+
+ return usec_add(atime, dtime) <= now(CLOCK_REALTIME);
+}
+
+int session_set_idle_hint(Session *s, bool b) {
+ assert(s);
+
+ if (!SESSION_TYPE_IS_GRAPHICAL(s->type))
+ return -ENOTTY;
+
+ if (s->idle_hint == b)
+ return 0;
+
+ s->idle_hint = b;
+ dual_timestamp_now(&s->idle_hint_timestamp);
+
+ session_send_changed(s, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic", NULL);
+
+ if (s->seat)
+ seat_send_changed(s->seat, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic", NULL);
+
+ user_send_changed(s->user, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic", NULL);
+ manager_send_changed(s->manager, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic", NULL);
+
+ return 1;
+}
+
+int session_get_locked_hint(Session *s) {
+ assert(s);
+
+ return s->locked_hint;
+}
+
+void session_set_locked_hint(Session *s, bool b) {
+ assert(s);
+
+ if (s->locked_hint == b)
+ return;
+
+ s->locked_hint = b;
+
+ session_send_changed(s, "LockedHint", NULL);
+}
+
+void session_set_type(Session *s, SessionType t) {
+ assert(s);
+
+ if (s->type == t)
+ return;
+
+ s->type = t;
+ session_save(s);
+
+ session_send_changed(s, "Type", NULL);
+}
+
+int session_set_display(Session *s, const char *display) {
+ int r;
+
+ assert(s);
+ assert(display);
+
+ r = free_and_strdup(&s->display, display);
+ if (r <= 0) /* 0 means the strings were equal */
+ return r;
+
+ session_save(s);
+
+ session_send_changed(s, "Display", NULL);
+
+ return 1;
+}
+
+int session_set_tty(Session *s, const char *tty) {
+ int r;
+
+ assert(s);
+ assert(tty);
+
+ r = free_and_strdup(&s->tty, tty);
+ if (r <= 0) /* 0 means the strings were equal */
+ return r;
+
+ session_save(s);
+
+ session_send_changed(s, "TTY", NULL);
+
+ return 1;
+}
+
+static int session_dispatch_fifo(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(s->fifo_fd == fd);
+
+ /* EOF on the FIFO means the session died abnormally. */
+
+ session_remove_fifo(s);
+ session_stop(s, /* force = */ false);
+
+ return 1;
+}
+
+int session_create_fifo(Session *s) {
+ int r;
+
+ assert(s);
+
+ /* Create FIFO */
+ if (!s->fifo_path) {
+ r = mkdir_safe_label("/run/systemd/sessions", 0755, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ return r;
+
+ s->fifo_path = strjoin("/run/systemd/sessions/", s->id, ".ref");
+ if (!s->fifo_path)
+ return -ENOMEM;
+
+ if (mkfifo(s->fifo_path, 0600) < 0 && errno != EEXIST)
+ return -errno;
+ }
+
+ /* Open reading side */
+ if (s->fifo_fd < 0) {
+ s->fifo_fd = open(s->fifo_path, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (s->fifo_fd < 0)
+ return -errno;
+ }
+
+ if (!s->fifo_event_source) {
+ r = sd_event_add_io(s->manager->event, &s->fifo_event_source, s->fifo_fd, 0, session_dispatch_fifo, s);
+ if (r < 0)
+ return r;
+
+ /* Let's make sure we noticed dead sessions before we process new bus requests (which might
+ * create new sessions). */
+ r = sd_event_source_set_priority(s->fifo_event_source, SD_EVENT_PRIORITY_NORMAL-10);
+ if (r < 0)
+ return r;
+ }
+
+ /* Open writing side */
+ return RET_NERRNO(open(s->fifo_path, O_WRONLY|O_CLOEXEC|O_NONBLOCK));
+}
+
+static void session_remove_fifo(Session *s) {
+ assert(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;
+}
+
+bool session_may_gc(Session *s, bool drop_not_started) {
+ int r;
+
+ assert(s);
+
+ if (drop_not_started && !s->started)
+ return true;
+
+ if (!s->user)
+ return true;
+
+ r = pidref_is_alive(&s->leader);
+ if (r < 0)
+ log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not.", s->leader.pid);
+ if (r > 0)
+ return false;
+
+ if (s->fifo_fd >= 0) {
+ if (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;
+
+ r = manager_job_is_active(s->manager, s->scope_job, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", s->scope_job, bus_error_message(&error, r));
+ if (r != 0)
+ return false;
+ }
+
+ if (s->scope) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = manager_unit_is_active(s->manager, s->scope, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", s->scope, bus_error_message(&error, r));
+ if (r != 0)
+ return false;
+ }
+
+ return true;
+}
+
+void session_add_to_gc_queue(Session *s) {
+ assert(s);
+
+ if (s->in_gc_queue)
+ return;
+
+ LIST_PREPEND(gc_queue, s->manager->session_gc_queue, s);
+ s->in_gc_queue = true;
+}
+
+SessionState session_get_state(Session *s) {
+ assert(s);
+
+ /* always check closing first */
+ if (s->stopping || s->timer_event_source)
+ return SESSION_CLOSING;
+
+ if (s->scope_job || (!pidref_is_set(&s->leader) && s->fifo_fd < 0))
+ return SESSION_OPENING;
+
+ if (session_is_active(s))
+ return SESSION_ACTIVE;
+
+ return SESSION_ONLINE;
+}
+
+int session_kill(Session *s, KillWho who, int signo) {
+ assert(s);
+
+ if (!s->scope)
+ return -ESRCH;
+
+ return manager_kill_unit(s->manager, s->scope, who, signo, NULL);
+}
+
+static int session_open_vt(Session *s, bool reopen) {
+ _cleanup_close_ int fd = -EBADF;
+ char path[sizeof("/dev/tty") + DECIMAL_STR_MAX(s->vtnr)];
+
+ assert(s);
+
+ if (s->vtnr < 1)
+ return -ENODEV;
+
+ if (!reopen && s->vtfd >= 0)
+ return s->vtfd;
+
+ sprintf(path, "/dev/tty%u", s->vtnr);
+
+ fd = open_terminal(path, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY);
+ if (fd < 0)
+ return log_error_errno(fd, "Cannot open VT %s of session %s: %m", path, s->id);
+
+ close_and_replace(s->vtfd, fd);
+ return s->vtfd;
+}
+
+static int session_prepare_vt(Session *s) {
+ int vt, r;
+ struct vt_mode mode = {};
+
+ assert(s);
+
+ if (s->vtnr < 1)
+ return 0;
+
+ vt = session_open_vt(s, /* reopen = */ false);
+ if (vt < 0)
+ return vt;
+
+ r = fchown(vt, s->user->user_record->uid, -1);
+ if (r < 0) {
+ r = log_error_errno(errno,
+ "Cannot change owner of /dev/tty%u: %m",
+ s->vtnr);
+ goto error;
+ }
+
+ r = ioctl(vt, KDSKBMODE, K_OFF);
+ if (r < 0) {
+ r = log_error_errno(errno,
+ "Cannot set K_OFF on /dev/tty%u: %m",
+ s->vtnr);
+ goto error;
+ }
+
+ r = ioctl(vt, KDSETMODE, KD_GRAPHICS);
+ if (r < 0) {
+ r = log_error_errno(errno,
+ "Cannot set KD_GRAPHICS on /dev/tty%u: %m",
+ s->vtnr);
+ goto error;
+ }
+
+ /* Oh, thanks to the VT layer, VT_AUTO does not work with KD_GRAPHICS.
+ * So we need a dummy handler here which just acknowledges *all* VT
+ * switch requests. */
+ mode.mode = VT_PROCESS;
+ mode.relsig = SIGRTMIN;
+ mode.acqsig = SIGRTMIN + 1;
+ r = ioctl(vt, VT_SETMODE, &mode);
+ if (r < 0) {
+ r = log_error_errno(errno,
+ "Cannot set VT_PROCESS on /dev/tty%u: %m",
+ s->vtnr);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ session_restore_vt(s);
+ return r;
+}
+
+static void session_restore_vt(Session *s) {
+ int r;
+
+ assert(s);
+
+ if (s->vtfd < 0)
+ return;
+
+ r = vt_restore(s->vtfd);
+ if (r == -EIO) {
+ /* It might happen if the controlling process exited before or while we were
+ * restoring the VT as it would leave the old file-descriptor in a hung-up
+ * state. In this case let's retry with a fresh handle to the virtual terminal. */
+
+ /* We do a little dance to avoid having the terminal be available
+ * for reuse before we've cleaned it up. */
+
+ int fd = session_open_vt(s, /* reopen = */ true);
+ if (fd >= 0)
+ r = vt_restore(fd);
+ }
+ if (r < 0)
+ log_warning_errno(r, "Failed to restore VT, ignoring: %m");
+
+ s->vtfd = safe_close(s->vtfd);
+}
+
+void session_leave_vt(Session *s) {
+ int r;
+
+ assert(s);
+
+ /* This is called whenever we get a VT-switch signal from the kernel.
+ * We acknowledge all of them unconditionally. Note that session are
+ * free to overwrite those handlers and we only register them for
+ * sessions with controllers. Legacy sessions are not affected.
+ * However, if we switch from a non-legacy to a legacy session, we must
+ * make sure to pause all device before acknowledging the switch. We
+ * process the real switch only after we are notified via sysfs, so the
+ * legacy session might have already started using the devices. If we
+ * don't pause the devices before the switch, we might confuse the
+ * session we switch to. */
+
+ if (s->vtfd < 0)
+ return;
+
+ session_device_pause_all(s);
+ r = vt_release(s->vtfd, /* restore = */ false);
+ if (r == -EIO) {
+ /* Handle the same VT hung-up case as in session_restore_vt */
+
+ int fd = session_open_vt(s, /* reopen = */ true);
+ if (fd >= 0)
+ r = vt_release(fd, /* restore = */ false);
+ }
+ if (r < 0)
+ log_debug_errno(r, "Cannot release VT of session %s: %m", s->id);
+}
+
+bool session_is_controller(Session *s, const char *sender) {
+ return streq_ptr(ASSERT_PTR(s)->controller, sender);
+}
+
+static void session_release_controller(Session *s, bool notify) {
+ _unused_ _cleanup_free_ char *name = NULL;
+ SessionDevice *sd;
+
+ assert(s);
+
+ if (!s->controller)
+ return;
+
+ name = s->controller;
+
+ /* By resetting the controller before releasing the devices, we won't send notification signals.
+ * This avoids sending useless notifications if the controller is released on disconnects. */
+ if (!notify)
+ s->controller = NULL;
+
+ while ((sd = hashmap_first(s->devices)))
+ session_device_free(sd);
+
+ s->controller = NULL;
+ s->track = sd_bus_track_unref(s->track);
+}
+
+static int on_bus_track(sd_bus_track *track, void *userdata) {
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(track);
+
+ session_drop_controller(s);
+
+ return 0;
+}
+
+int session_set_controller(Session *s, const char *sender, bool force, bool prepare) {
+ _cleanup_free_ char *name = NULL;
+ int r;
+
+ assert(s);
+ assert(sender);
+
+ if (session_is_controller(s, sender))
+ return 0;
+ if (s->controller && !force)
+ return -EBUSY;
+
+ name = strdup(sender);
+ if (!name)
+ return -ENOMEM;
+
+ s->track = sd_bus_track_unref(s->track);
+ r = sd_bus_track_new(s->manager->bus, &s->track, on_bus_track, s);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_track_add_name(s->track, name);
+ if (r < 0)
+ return r;
+
+ /* When setting a session controller, we forcibly mute the VT and set
+ * it into graphics-mode. Applications can override that by changing
+ * VT state after calling TakeControl(). However, this serves as a good
+ * default and well-behaving controllers can now ignore VTs entirely.
+ * Note that we reset the VT on ReleaseControl() and if the controller
+ * exits.
+ * If logind crashes/restarts, we restore the controller during restart
+ * (without preparing the VT since the controller has probably overridden
+ * VT state by now) or reset the VT in case it crashed/exited, too. */
+ if (prepare) {
+ r = session_prepare_vt(s);
+ if (r < 0) {
+ s->track = sd_bus_track_unref(s->track);
+ return r;
+ }
+ }
+
+ session_release_controller(s, true);
+ s->controller = TAKE_PTR(name);
+ session_save(s);
+
+ return 0;
+}
+
+void session_drop_controller(Session *s) {
+ assert(s);
+
+ if (!s->controller)
+ return;
+
+ s->track = sd_bus_track_unref(s->track);
+ session_set_type(s, s->original_type);
+ session_release_controller(s, false);
+ session_save(s);
+ session_restore_vt(s);
+}
+
+static const char* const session_state_table[_SESSION_STATE_MAX] = {
+ [SESSION_OPENING] = "opening",
+ [SESSION_ONLINE] = "online",
+ [SESSION_ACTIVE] = "active",
+ [SESSION_CLOSING] = "closing",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(session_state, SessionState);
+
+static const char* const session_type_table[_SESSION_TYPE_MAX] = {
+ [SESSION_UNSPECIFIED] = "unspecified",
+ [SESSION_TTY] = "tty",
+ [SESSION_X11] = "x11",
+ [SESSION_WAYLAND] = "wayland",
+ [SESSION_MIR] = "mir",
+ [SESSION_WEB] = "web",
+};
+
+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",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass);
+
+static const char* const kill_who_table[_KILL_WHO_MAX] = {
+ [KILL_LEADER] = "leader",
+ [KILL_ALL] = "all",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);
+
+static const char* const tty_validity_table[_TTY_VALIDITY_MAX] = {
+ [TTY_FROM_PAM] = "from-pam",
+ [TTY_FROM_UTMP] = "from-utmp",
+ [TTY_UTMP_INCONSISTENT] = "utmp-inconsistent",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(tty_validity, TTYValidity);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
new file mode 100644
index 0000000..8b63843
--- /dev/null
+++ b/src/login/logind-session.h
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct Session Session;
+typedef enum KillWho KillWho;
+
+#include "list.h"
+#include "login-util.h"
+#include "logind-user.h"
+#include "pidref.h"
+#include "string-util.h"
+
+typedef enum SessionState {
+ SESSION_OPENING, /* Session scope is being created */
+ SESSION_ONLINE, /* Logged in */
+ SESSION_ACTIVE, /* Logged in and in the fg */
+ SESSION_CLOSING, /* Logged out, but scope is still there */
+ _SESSION_STATE_MAX,
+ _SESSION_STATE_INVALID = -EINVAL,
+} SessionState;
+
+typedef enum SessionClass {
+ SESSION_USER,
+ SESSION_GREETER,
+ SESSION_LOCK_SCREEN,
+ SESSION_BACKGROUND,
+ _SESSION_CLASS_MAX,
+ _SESSION_CLASS_INVALID = -EINVAL,
+} SessionClass;
+
+typedef enum SessionType {
+ SESSION_UNSPECIFIED,
+ SESSION_TTY,
+ SESSION_X11,
+ SESSION_WAYLAND,
+ SESSION_MIR,
+ SESSION_WEB,
+ _SESSION_TYPE_MAX,
+ _SESSION_TYPE_INVALID = -EINVAL,
+} SessionType;
+
+#define SESSION_TYPE_IS_GRAPHICAL(type) IN_SET(type, SESSION_X11, SESSION_WAYLAND, SESSION_MIR)
+
+enum KillWho {
+ KILL_LEADER,
+ KILL_ALL,
+ _KILL_WHO_MAX,
+ _KILL_WHO_INVALID = -EINVAL,
+};
+
+typedef enum TTYValidity {
+ TTY_FROM_PAM,
+ TTY_FROM_UTMP,
+ TTY_UTMP_INCONSISTENT, /* may happen on ssh sessions with multiplexed TTYs */
+ _TTY_VALIDITY_MAX,
+ _TTY_VALIDITY_INVALID = -EINVAL,
+} TTYValidity;
+
+struct Session {
+ Manager *manager;
+
+ const char *id;
+ unsigned position;
+ SessionType type;
+ SessionType original_type;
+ SessionClass class;
+
+ char *state_file;
+
+ User *user;
+
+ dual_timestamp timestamp;
+
+ char *display;
+ char *tty;
+ TTYValidity tty_validity;
+
+ bool remote;
+ char *remote_user;
+ char *remote_host;
+ char *service;
+ char *desktop;
+
+ char *scope;
+ char *scope_job;
+
+ Seat *seat;
+ unsigned vtnr;
+ int vtfd;
+
+ PidRef leader;
+ uint32_t audit_id;
+
+ int fifo_fd;
+ char *fifo_path;
+
+ sd_event_source *fifo_event_source;
+ sd_event_source *leader_pidfd_event_source;
+
+ bool idle_hint;
+ dual_timestamp idle_hint_timestamp;
+
+ bool locked_hint;
+
+ bool in_gc_queue:1;
+ bool started:1;
+ bool stopping:1;
+
+ bool was_active:1;
+
+ sd_bus_message *create_message;
+
+ /* Set up when a client requested to release the session via the bus */
+ sd_event_source *timer_event_source;
+
+ char *controller;
+ Hashmap *devices;
+ sd_bus_track *track;
+
+ sd_event_source *stop_on_idle_event_source;
+
+ LIST_FIELDS(Session, sessions_by_user);
+ LIST_FIELDS(Session, sessions_by_seat);
+
+ LIST_FIELDS(Session, gc_queue);
+};
+
+int session_new(Session **ret, Manager *m, const char *id);
+Session* session_free(Session *s);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Session *, session_free);
+
+void session_set_user(Session *s, User *u);
+int session_set_leader_consume(Session *s, PidRef _leader);
+bool session_may_gc(Session *s, bool drop_not_started);
+void session_add_to_gc_queue(Session *s);
+int session_activate(Session *s);
+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);
+void session_set_type(Session *s, SessionType t);
+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);
+int session_release(Session *s);
+int session_save(Session *s);
+int session_load(Session *s);
+int session_kill(Session *s, KillWho who, int signo);
+
+SessionState session_get_state(Session *u);
+
+const char* session_state_to_string(SessionState t) _const_;
+SessionState session_state_from_string(const char *s) _pure_;
+
+const char* session_type_to_string(SessionType t) _const_;
+SessionType session_type_from_string(const char *s) _pure_;
+
+const char* session_class_to_string(SessionClass t) _const_;
+SessionClass session_class_from_string(const char *s) _pure_;
+
+const char *kill_who_to_string(KillWho k) _const_;
+KillWho kill_who_from_string(const char *s) _pure_;
+
+const char* tty_validity_to_string(TTYValidity t) _const_;
+TTYValidity tty_validity_from_string(const char *s) _pure_;
+
+void session_leave_vt(Session *s);
+
+bool session_is_controller(Session *s, const char *sender);
+int session_set_controller(Session *s, const char *sender, bool force, bool prepare);
+void session_drop_controller(Session *s);
+
+static inline bool SESSION_IS_SELF(const char *name) {
+ return isempty(name) || streq(name, "self");
+}
+
+static inline bool SESSION_IS_AUTO(const char *name) {
+ return streq_ptr(name, "auto");
+}
diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c
new file mode 100644
index 0000000..88649b2
--- /dev/null
+++ b/src/login/logind-user-dbus.c
@@ -0,0 +1,421 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "alloc-util.h"
+#include "bus-get-properties.h"
+#include "bus-polkit.h"
+#include "bus-util.h"
+#include "format-util.h"
+#include "logind-dbus.h"
+#include "logind-session-dbus.h"
+#include "logind-user-dbus.h"
+#include "logind-user.h"
+#include "logind.h"
+#include "missing_capability.h"
+#include "signal-util.h"
+#include "strv.h"
+#include "user-util.h"
+
+static int property_get_uid(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ User *u = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "u", (uint32_t) u->user_record->uid);
+}
+
+static int property_get_gid(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ User *u = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "u", (uint32_t) u->user_record->gid);
+}
+
+static int property_get_name(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ User *u = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "s", u->user_record->user_name);
+}
+
+static BUS_DEFINE_PROPERTY_GET2(property_get_state, "s", User, user_get_state, user_state_to_string);
+
+static int property_get_display(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_free_ char *p = NULL;
+ User *u = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ p = u->display ? session_bus_path(u->display) : strdup("/");
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_message_append(reply, "(so)", u->display ? u->display->id : "", p);
+}
+
+static int property_get_sessions(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ User *u = ASSERT_PTR(userdata);
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = sd_bus_message_open_container(reply, 'a', "(so)");
+ if (r < 0)
+ return r;
+
+ LIST_FOREACH(sessions_by_user, session, u->sessions) {
+ _cleanup_free_ char *p = NULL;
+
+ p = session_bus_path(session);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(reply, "(so)", session->id, p);
+ if (r < 0)
+ return r;
+
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
+static int property_get_idle_hint(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ User *u = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "b", user_get_idle_hint(u, NULL) > 0);
+}
+
+static int property_get_idle_since_hint(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ User *u = ASSERT_PTR(userdata);
+ dual_timestamp t = DUAL_TIMESTAMP_NULL;
+ uint64_t k;
+
+ assert(bus);
+ assert(reply);
+
+ (void) user_get_idle_hint(u, &t);
+ k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+
+ return sd_bus_message_append(reply, "t", k);
+}
+
+static int property_get_linger(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ User *u = ASSERT_PTR(userdata);
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = user_check_linger_file(u);
+
+ return sd_bus_message_append(reply, "b", r > 0);
+}
+
+int bus_user_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ User *u = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_KILL,
+ "org.freedesktop.login1.manage",
+ NULL,
+ false,
+ u->user_record->uid,
+ &u->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = user_stop(u, /* force = */ true);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_user_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ User *u = ASSERT_PTR(userdata);
+ int32_t signo;
+ int r;
+
+ assert(message);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_KILL,
+ "org.freedesktop.login1.manage",
+ NULL,
+ false,
+ u->user_record->uid,
+ &u->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = sd_bus_message_read(message, "i", &signo);
+ if (r < 0)
+ return r;
+
+ if (!SIGNAL_VALID(signo))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo);
+
+ r = user_kill(u, signo);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int user_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ uid_t uid;
+ User *user;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(interface);
+ assert(found);
+
+ if (streq(path, "/org/freedesktop/login1/user/self")) {
+ sd_bus_message *message;
+
+ message = sd_bus_get_current_message(bus);
+
+ r = manager_get_user_from_creds(m, message, UID_INVALID, error, &user);
+ if (r == -ENXIO) {
+ sd_bus_error_free(error);
+ return 0;
+ }
+ if (r < 0)
+ return r;
+ } else {
+ const char *p;
+
+ p = startswith(path, "/org/freedesktop/login1/user/_");
+ if (!p)
+ return 0;
+
+ r = parse_uid(p, &uid);
+ if (r < 0)
+ return 0;
+
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
+ if (!user)
+ return 0;
+ }
+
+ *found = user;
+ return 1;
+}
+
+char *user_bus_path(User *u) {
+ char *s;
+
+ assert(u);
+
+ if (asprintf(&s, "/org/freedesktop/login1/user/_"UID_FMT, u->user_record->uid) < 0)
+ return NULL;
+
+ return s;
+}
+
+static int user_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+ _cleanup_strv_free_ char **l = NULL;
+ sd_bus_message *message;
+ Manager *m = userdata;
+ User *user;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(nodes);
+
+ HASHMAP_FOREACH(user, m->users) {
+ char *p;
+
+ p = user_bus_path(user);
+ if (!p)
+ return -ENOMEM;
+
+ r = strv_consume(&l, p);
+ if (r < 0)
+ return r;
+ }
+
+ message = sd_bus_get_current_message(bus);
+ if (message) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, &creds);
+ if (r >= 0) {
+ uid_t uid;
+
+ r = sd_bus_creds_get_owner_uid(creds, &uid);
+ if (r >= 0) {
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
+ if (user) {
+ r = strv_extend(&l, "/org/freedesktop/login1/user/self");
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+ }
+
+ *nodes = TAKE_PTR(l);
+
+ return 1;
+}
+
+static const sd_bus_vtable user_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ SD_BUS_PROPERTY("UID", "u", property_get_uid, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("GID", "u", property_get_gid, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ 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("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),
+ SD_BUS_PROPERTY("Sessions", "a(so)", property_get_sessions, 0, 0),
+ SD_BUS_PROPERTY("IdleHint", "b", property_get_idle_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IdleSinceHint", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IdleSinceHintMonotonic", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("Linger", "b", property_get_linger, 0, 0),
+
+ SD_BUS_METHOD("Terminate", NULL, NULL, bus_user_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Kill",
+ SD_BUS_ARGS("i", signal_number),
+ SD_BUS_NO_RESULT,
+ bus_user_method_kill,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
+ SD_BUS_VTABLE_END
+};
+
+const BusObjectImplementation user_object = {
+ "/org/freedesktop/login1/user",
+ "org.freedesktop.login1.User",
+ .fallback_vtables = BUS_FALLBACK_VTABLES({user_vtable, user_object_find}),
+ .node_enumerator = user_node_enumerator,
+};
+
+int user_send_signal(User *u, bool new_user) {
+ _cleanup_free_ char *p = NULL;
+
+ assert(u);
+
+ p = user_bus_path(u);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_emit_signal(
+ u->manager->bus,
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ new_user ? "UserNew" : "UserRemoved",
+ "uo", (uint32_t) u->user_record->uid, p);
+}
+
+int user_send_changed(User *u, const char *properties, ...) {
+ _cleanup_free_ char *p = NULL;
+ char **l;
+
+ assert(u);
+
+ if (!u->started)
+ return 0;
+
+ p = user_bus_path(u);
+ if (!p)
+ return -ENOMEM;
+
+ l = strv_from_stdarg_alloca(properties);
+
+ return sd_bus_emit_properties_changed_strv(u->manager->bus, p, "org.freedesktop.login1.User", l);
+}
diff --git a/src/login/logind-user-dbus.h b/src/login/logind-user-dbus.h
new file mode 100644
index 0000000..d2f24ce
--- /dev/null
+++ b/src/login/logind-user-dbus.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "logind-user.h"
+
+extern const BusObjectImplementation user_object;
+
+char *user_bus_path(User *s);
+
+int user_send_signal(User *u, bool new_user);
+int user_send_changed(User *u, const char *properties, ...) _sentinel_;
+
+int bus_user_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_user_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error);
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
new file mode 100644
index 0000000..c613307
--- /dev/null
+++ b/src/login/logind-user.c
@@ -0,0 +1,940 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-util.h"
+#include "cgroup-util.h"
+#include "clean-ipc.h"
+#include "env-file.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "label-util.h"
+#include "limits-util.h"
+#include "logind-dbus.h"
+#include "logind-user-dbus.h"
+#include "logind-user.h"
+#include "mkdir-label.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "percent-util.h"
+#include "rm-rf.h"
+#include "serialize.h"
+#include "special.h"
+#include "stdio-util.h"
+#include "string-table.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "uid-alloc-range.h"
+#include "unit-name.h"
+#include "user-util.h"
+
+int user_new(User **ret,
+ Manager *m,
+ UserRecord *ur) {
+
+ _cleanup_(user_freep) User *u = NULL;
+ char lu[DECIMAL_STR_MAX(uid_t) + 1];
+ int r;
+
+ assert(ret);
+ assert(m);
+ assert(ur);
+
+ if (!ur->user_name)
+ return -EINVAL;
+
+ if (!uid_is_valid(ur->uid))
+ return -EINVAL;
+
+ u = new(User, 1);
+ if (!u)
+ return -ENOMEM;
+
+ *u = (User) {
+ .manager = m,
+ .user_record = user_record_ref(ur),
+ .last_session_timestamp = USEC_INFINITY,
+ };
+
+ if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0)
+ return -ENOMEM;
+
+ if (asprintf(&u->runtime_path, "/run/user/" UID_FMT, ur->uid) < 0)
+ return -ENOMEM;
+
+ xsprintf(lu, UID_FMT, ur->uid);
+ r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &u->slice);
+ if (r < 0)
+ return r;
+
+ r = unit_name_build("user", lu, ".service", &u->service);
+ if (r < 0)
+ return r;
+
+ r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_service);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(m->users, UID_TO_PTR(ur->uid), u);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(m->user_units, u->slice, u);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(m->user_units, u->service, u);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(m->user_units, u->runtime_dir_service, u);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(u);
+ return 0;
+}
+
+User *user_free(User *u) {
+ if (!u)
+ return NULL;
+
+ if (u->in_gc_queue)
+ LIST_REMOVE(gc_queue, u->manager->user_gc_queue, 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);
+
+ if (u->slice)
+ hashmap_remove_value(u->manager->user_units, u->slice, u);
+
+ hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u);
+
+ sd_event_source_unref(u->timer_event_source);
+
+ u->service_job = mfree(u->service_job);
+
+ 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);
+
+ user_record_unref(u->user_record);
+
+ return mfree(u);
+}
+
+static int user_save_internal(User *u) {
+ _cleanup_(unlink_and_freep) char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(u);
+ assert(u->state_file);
+
+ r = mkdir_safe_label("/run/systemd/users", 0755, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ goto fail;
+
+ r = fopen_temporary(u->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "NAME=%s\n"
+ "STATE=%s\n" /* friendly user-facing state */
+ "STOPPING=%s\n", /* low-level state */
+ u->user_record->user_name,
+ user_state_to_string(user_get_state(u)),
+ yes_no(u->stopping));
+
+ /* 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->display)
+ fprintf(f, "DISPLAY=%s\n", u->display->id);
+
+ if (dual_timestamp_is_set(&u->timestamp))
+ fprintf(f,
+ "REALTIME="USEC_FMT"\n"
+ "MONOTONIC="USEC_FMT"\n",
+ u->timestamp.realtime,
+ u->timestamp.monotonic);
+
+ if (u->last_session_timestamp != USEC_INFINITY)
+ fprintf(f, "LAST_SESSION_TIMESTAMP=" USEC_FMT "\n",
+ u->last_session_timestamp);
+
+ if (u->sessions) {
+ bool first;
+
+ fputs("SESSIONS=", f);
+ first = true;
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ if (first)
+ first = false;
+ else
+ fputc(' ', f);
+
+ fputs(i->id, f);
+ }
+
+ fputs("\nSEATS=", f);
+ first = true;
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ if (!i->seat)
+ continue;
+
+ if (first)
+ first = false;
+ else
+ fputc(' ', f);
+
+ fputs(i->seat->id, f);
+ }
+
+ fputs("\nACTIVE_SESSIONS=", f);
+ first = true;
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ if (!session_is_active(i))
+ continue;
+
+ if (first)
+ first = false;
+ else
+ fputc(' ', f);
+
+ fputs(i->id, f);
+ }
+
+ fputs("\nONLINE_SESSIONS=", f);
+ first = true;
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ if (session_get_state(i) == SESSION_CLOSING)
+ continue;
+
+ if (first)
+ first = false;
+ else
+ fputc(' ', f);
+
+ fputs(i->id, f);
+ }
+
+ fputs("\nACTIVE_SEATS=", f);
+ first = true;
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ if (!session_is_active(i) || !i->seat)
+ continue;
+
+ if (first)
+ first = false;
+ else
+ fputc(' ', f);
+
+ fputs(i->seat->id, f);
+ }
+
+ fputs("\nONLINE_SEATS=", f);
+ first = true;
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ if (session_get_state(i) == SESSION_CLOSING || !i->seat)
+ continue;
+
+ if (first)
+ first = false;
+ else
+ fputc(' ', f);
+
+ fputs(i->seat->id, f);
+ }
+ fputc('\n', f);
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, u->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ temp_path = mfree(temp_path);
+ return 0;
+
+fail:
+ (void) unlink(u->state_file);
+
+ return log_error_errno(r, "Failed to save user data %s: %m", u->state_file);
+}
+
+int user_save(User *u) {
+ assert(u);
+
+ if (!u->started)
+ return 0;
+
+ return user_save_internal(u);
+}
+
+int user_load(User *u) {
+ _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL;
+ int r;
+
+ assert(u);
+
+ r = parse_env_file(NULL, u->state_file,
+ "SERVICE_JOB", &u->service_job,
+ "STOPPING", &stopping,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
+ "LAST_SESSION_TIMESTAMP", &last_session_timestamp);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read %s: %m", u->state_file);
+
+ if (stopping) {
+ r = parse_boolean(stopping);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping);
+ else
+ u->stopping = r;
+ }
+
+ if (realtime)
+ (void) deserialize_usec(realtime, &u->timestamp.realtime);
+ if (monotonic)
+ (void) deserialize_usec(monotonic, &u->timestamp.monotonic);
+ if (last_session_timestamp)
+ (void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp);
+
+ return 0;
+}
+
+static void user_start_service(User *u) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(u);
+
+ /* 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->service_job = mfree(u->service_job);
+
+ r = manager_start_unit(u->manager, u->service, &error, &u->service_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));
+}
+
+static int update_slice_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ _cleanup_(user_record_unrefp) UserRecord *ur = ASSERT_PTR(userdata);
+ const sd_bus_error *e;
+ int r;
+
+ assert(m);
+
+ e = sd_bus_message_get_error(m);
+ if (e) {
+ r = sd_bus_error_get_errno(e);
+ log_warning_errno(r,
+ "Failed to update slice of %s, ignoring: %s",
+ ur->user_name,
+ bus_error_message(e, r));
+
+ return 0;
+ }
+
+ log_debug("Successfully set slice parameters of %s.", ur->user_name);
+ return 0;
+}
+
+static int user_update_slice(User *u) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert(u);
+
+ if (u->user_record->tasks_max == UINT64_MAX &&
+ u->user_record->memory_high == UINT64_MAX &&
+ u->user_record->memory_max == UINT64_MAX &&
+ u->user_record->cpu_weight == UINT64_MAX &&
+ u->user_record->io_weight == UINT64_MAX)
+ return 0;
+
+ r = bus_message_new_method_call(u->manager->bus, &m, bus_systemd_mgr, "SetUnitProperties");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "sb", u->slice, true);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "(sv)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ const struct {
+ const char *name;
+ uint64_t value;
+ } settings[] = {
+ { "TasksMax", u->user_record->tasks_max },
+ { "MemoryMax", u->user_record->memory_max },
+ { "MemoryHigh", u->user_record->memory_high },
+ { "CPUWeight", u->user_record->cpu_weight },
+ { "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);
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_call_async(u->manager->bus, NULL, m, update_slice_callback, u->user_record, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to change user slice properties: %m");
+
+ /* Ref the user record pointer, so that the slot keeps it pinned */
+ user_record_ref(u->user_record);
+
+ return 0;
+}
+
+int user_start(User *u) {
+ assert(u);
+
+ if (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)
+ log_debug("Starting services for 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);
+
+ /* Set slice parameters */
+ (void) user_update_slice(u);
+
+ /* Start user@UID.service */
+ user_start_service(u);
+
+ if (!u->started) {
+ if (!dual_timestamp_is_set(&u->timestamp))
+ dual_timestamp_now(&u->timestamp);
+ user_send_signal(u, true);
+ u->started = true;
+ }
+
+ /* Save new user data */
+ user_save(u);
+
+ return 0;
+}
+
+static void user_stop_service(User *u, bool force) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(u);
+ assert(u->service);
+
+ /* 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_job = mfree(u->service_job);
+
+ r = manager_stop_unit(u->manager, u->service, force ? "replace" : "fail", &error, &u->service_job);
+ if (r < 0)
+ log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
+}
+
+int user_stop(User *u, bool force) {
+ int r = 0;
+
+ assert(u);
+
+ /* This is called whenever we begin with tearing down a user record. It's called in two cases: explicit API
+ * request to do so via the bus (in which case 'force' is true) and automatically due to GC, if there's no
+ * session left pinning it (in which case 'force' is false). Note that this just initiates tearing down of the
+ * user, the User object will remain in memory until user_finalize() is called, see below. */
+
+ if (!u->started)
+ return 0;
+
+ if (u->stopping) { /* Stop jobs have already been queued */
+ user_save(u);
+ return 0;
+ }
+
+ LIST_FOREACH(sessions_by_user, s, u->sessions) {
+ int k;
+
+ k = session_stop(s, force);
+ if (k < 0)
+ r = k;
+ }
+
+ user_stop_service(u, force);
+
+ u->stopping = true;
+
+ user_save(u);
+
+ return r;
+}
+
+int user_finalize(User *u) {
+ int r = 0, k;
+
+ assert(u);
+
+ /* Called when the user is really ready to be freed, i.e. when all unit stop jobs and suchlike for it are
+ * 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);
+
+ LIST_FOREACH(sessions_by_user, s, u->sessions) {
+ k = session_finalize(s);
+ if (k < 0)
+ r = k;
+ }
+
+ /* 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
+ * system components. Since enable RemoveIPC= globally for all users, we need to be a bit careful with such
+ * 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;
+ }
+
+ (void) unlink(u->state_file);
+ user_add_to_gc_queue(u);
+
+ if (u->started) {
+ user_send_signal(u, false);
+ u->started = false;
+ }
+
+ return r;
+}
+
+int user_get_idle_hint(User *u, dual_timestamp *t) {
+ bool idle_hint = true;
+ dual_timestamp ts = DUAL_TIMESTAMP_NULL;
+
+ assert(u);
+
+ LIST_FOREACH(sessions_by_user, s, u->sessions) {
+ dual_timestamp k;
+ int ih;
+
+ ih = session_get_idle_hint(s, &k);
+ if (ih < 0)
+ return ih;
+
+ if (!ih) {
+ if (!idle_hint) {
+ if (k.monotonic < ts.monotonic)
+ ts = k;
+ } else {
+ idle_hint = false;
+ ts = k;
+ }
+ } else if (idle_hint) {
+
+ if (k.monotonic > ts.monotonic)
+ ts = k;
+ }
+ }
+
+ if (t)
+ *t = ts;
+
+ return idle_hint;
+}
+
+int user_check_linger_file(User *u) {
+ _cleanup_free_ char *cc = NULL;
+ char *p = NULL;
+
+ cc = cescape(u->user_record->user_name);
+ if (!cc)
+ return -ENOMEM;
+
+ p = strjoina("/var/lib/systemd/linger/", cc);
+ if (access(p, F_OK) < 0) {
+ if (errno != ENOENT)
+ return -errno;
+
+ return false;
+ }
+
+ return true;
+}
+
+static bool user_unit_active(User *u) {
+ int r;
+
+ assert(u->service);
+ assert(u->runtime_dir_service);
+ assert(u->slice);
+
+ FOREACH_STRING(i, u->service, u->runtime_dir_service, u->slice) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = manager_unit_is_active(u->manager, i, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", i, bus_error_message(&error, r));
+ if (r != 0)
+ return true;
+ }
+
+ return false;
+}
+
+static usec_t user_get_stop_delay(User *u) {
+ assert(u);
+
+ if (u->user_record->stop_delay_usec != UINT64_MAX)
+ return u->user_record->stop_delay_usec;
+
+ if (user_record_removable(u->user_record) > 0)
+ return 0; /* For removable users lower the stop delay to zero */
+
+ return u->manager->user_stop_delay;
+}
+
+bool user_may_gc(User *u, bool drop_not_started) {
+ int r;
+
+ assert(u);
+
+ if (drop_not_started && !u->started)
+ return true;
+
+ if (u->sessions)
+ return false;
+
+ if (u->last_session_timestamp != USEC_INFINITY) {
+ usec_t user_stop_delay;
+
+ /* All sessions have been closed. Let's see if we shall leave the user record around for a bit */
+
+ user_stop_delay = user_get_stop_delay(u);
+
+ if (user_stop_delay == USEC_INFINITY)
+ return false; /* Leave it around forever! */
+ if (user_stop_delay > 0 &&
+ now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, user_stop_delay))
+ return false; /* Leave it around for a bit longer. */
+ }
+
+ /* Is this a user that shall stay around forever ("linger")? Before we say "no" to GC'ing for lingering users, let's check
+ * if any of the three units that we maintain for this user is still around. If none of them is,
+ * there's no need to keep this user around even if lingering is enabled. */
+ if (user_check_linger_file(u) > 0 && user_unit_active(u))
+ return false;
+
+ /* Check if our job is still pending */
+ if (u->service_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 (r < 0)
+ log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", u->service_job, 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. */
+
+ return true;
+}
+
+void user_add_to_gc_queue(User *u) {
+ assert(u);
+
+ if (u->in_gc_queue)
+ return;
+
+ LIST_PREPEND(gc_queue, u->manager->user_gc_queue, u);
+ u->in_gc_queue = true;
+}
+
+UserState user_get_state(User *u) {
+ assert(u);
+
+ if (u->stopping)
+ return USER_CLOSING;
+
+ if (!u->started || u->service_job)
+ return USER_OPENING;
+
+ if (u->sessions) {
+ bool all_closing = true;
+
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ SessionState state;
+
+ 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;
+ }
+
+ if (user_check_linger_file(u) > 0 && user_unit_active(u))
+ return USER_LINGERING;
+
+ return USER_CLOSING;
+}
+
+int user_kill(User *u, int signo) {
+ assert(u);
+
+ return manager_kill_unit(u->manager, u->slice, KILL_ALL, signo, NULL);
+}
+
+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;
+}
+
+static int elect_display_compare(Session *s1, Session *s2) {
+ /* Indexed by SessionType. Lower numbers mean more preferred. */
+ static const int type_ranks[_SESSION_TYPE_MAX] = {
+ [SESSION_UNSPECIFIED] = 0,
+ [SESSION_TTY] = -2,
+ [SESSION_X11] = -3,
+ [SESSION_WAYLAND] = -3,
+ [SESSION_MIR] = -3,
+ [SESSION_WEB] = -1,
+ };
+
+ /* Calculate the partial order relationship between s1 and s2,
+ * returning < 0 if s1 is preferred as the user’s ‘primary session’,
+ * 0 if s1 and s2 are equally preferred or incomparable, or > 0 if s2
+ * is preferred.
+ *
+ * s1 or s2 may be NULL. */
+ if (!s1 && !s2)
+ return 0;
+
+ if ((s1 == NULL) != (s2 == NULL))
+ return (s1 == NULL) - (s2 == NULL);
+
+ if (s1->stopping != s2->stopping)
+ return s1->stopping - s2->stopping;
+
+ if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER))
+ return (s1->class != SESSION_USER) - (s2->class != SESSION_USER);
+
+ if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID))
+ return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID);
+
+ if (s1->type != s2->type)
+ return type_ranks[s1->type] - type_ranks[s2->type];
+
+ return 0;
+}
+
+void user_elect_display(User *u) {
+ assert(u);
+
+ /* This elects a primary session for each user, which we call the "display". We try to keep the assignment
+ * stable, but we "upgrade" to better choices. */
+ log_debug("Electing new display for user %s", u->user_record->user_name);
+
+ LIST_FOREACH(sessions_by_user, s, u->sessions) {
+ if (!elect_display_filter(s)) {
+ log_debug("Ignoring session %s", s->id);
+ continue;
+ }
+
+ if (elect_display_compare(s, u->display) < 0) {
+ log_debug("Choosing session %s in preference to %s", s->id, u->display ? u->display->id : "-");
+ u->display = s;
+ }
+ }
+}
+
+static int user_stop_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) {
+ User *u = ASSERT_PTR(userdata);
+
+ user_add_to_gc_queue(u);
+
+ return 0;
+}
+
+void user_update_last_session_timer(User *u) {
+ usec_t user_stop_delay;
+ int r;
+
+ assert(u);
+
+ if (u->sessions) {
+ /* 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);
+ return;
+ }
+
+ if (u->last_session_timestamp != USEC_INFINITY)
+ return; /* Timer already started */
+
+ u->last_session_timestamp = now(CLOCK_MONOTONIC);
+
+ assert(!u->timer_event_source);
+
+ user_stop_delay = user_get_stop_delay(u);
+ if (!timestamp_is_set(user_stop_delay))
+ return;
+
+ if (sd_event_get_state(u->manager->event) == SD_EVENT_FINISHED) {
+ log_debug("Not allocating user stop timeout, since we are already exiting.");
+ return;
+ }
+
+ r = sd_event_add_time(u->manager->event,
+ &u->timer_event_source,
+ CLOCK_MONOTONIC,
+ usec_add(u->last_session_timestamp, user_stop_delay), 0,
+ user_stop_timeout_callback, u);
+ if (r < 0)
+ log_warning_errno(r, "Failed to enqueue user stop event source, ignoring: %m");
+
+ if (DEBUG_LOGGING)
+ log_debug("Last session of user '%s' logged out, terminating user context in %s.",
+ u->user_record->user_name,
+ FORMAT_TIMESPAN(user_stop_delay, USEC_PER_MSEC));
+}
+
+static const char* const user_state_table[_USER_STATE_MAX] = {
+ [USER_OFFLINE] = "offline",
+ [USER_OPENING] = "opening",
+ [USER_LINGERING] = "lingering",
+ [USER_ONLINE] = "online",
+ [USER_ACTIVE] = "active",
+ [USER_CLOSING] = "closing"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);
+
+int config_parse_tmpfs_size(
+ 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) {
+
+ uint64_t *sz = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ /* First, try to parse as percentage */
+ r = parse_permyriad(rvalue);
+ if (r > 0)
+ *sz = physical_memory_scale(r, 10000U);
+ else {
+ uint64_t k;
+
+ /* If the passed argument was not a percentage, or out of range, parse as byte size */
+
+ r = parse_size(rvalue, 1024, &k);
+ if (r >= 0 && (k <= 0 || (uint64_t) (size_t) k != k))
+ r = -ERANGE;
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse size value '%s', ignoring: %m", rvalue);
+ return 0;
+ }
+
+ *sz = PAGE_ALIGN((size_t) k);
+ }
+
+ return 0;
+}
+
+int config_parse_compat_user_tasks_max(
+ 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) {
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ log_syntax(unit, LOG_NOTICE, filename, line, 0,
+ "Support for option %s= has been removed.",
+ lvalue);
+ log_info("Hint: try creating /etc/systemd/system/user-.slice.d/50-limits.conf with:\n"
+ " [Slice]\n"
+ " TasksMax=%s",
+ rvalue);
+ return 0;
+}
diff --git a/src/login/logind-user.h b/src/login/logind-user.h
new file mode 100644
index 0000000..21b9f8f
--- /dev/null
+++ b/src/login/logind-user.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct User User;
+
+#include "conf-parser.h"
+#include "list.h"
+#include "logind.h"
+#include "user-record.h"
+
+typedef enum UserState {
+ USER_OFFLINE, /* Not logged in at all */
+ USER_OPENING, /* Is logging in */
+ USER_LINGERING, /* Lingering has been enabled by the admin for this user */
+ USER_ONLINE, /* User logged in */
+ USER_ACTIVE, /* User logged in and has a session in the fg */
+ USER_CLOSING, /* User logged out, but processes still remain and lingering is not enabled */
+ _USER_STATE_MAX,
+ _USER_STATE_INVALID = -EINVAL,
+} UserState;
+
+struct User {
+ Manager *manager;
+
+ UserRecord *user_record;
+
+ 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 */
+
+ char *service_job;
+
+ Session *display;
+
+ dual_timestamp timestamp; /* When this User object was 'started' the first time */
+ usec_t last_session_timestamp; /* When the number of sessions of this user went from 1 to 0 the last time */
+
+ /* Set up when the last session of the user logs out */
+ sd_event_source *timer_event_source;
+
+ bool in_gc_queue:1;
+
+ bool started:1; /* Whenever the user being started, has been started or is being stopped again. */
+ 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);
+User *user_free(User *u);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(User *, user_free);
+
+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);
+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);
+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_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_compat_user_tasks_max);
diff --git a/src/login/logind-wall.c b/src/login/logind-wall.c
new file mode 100644
index 0000000..97b74e9
--- /dev/null
+++ b/src/login/logind-wall.c
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "audit-util.h"
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "event-util.h"
+#include "format-util.h"
+#include "logind.h"
+#include "path-util.h"
+#include "special.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "user-util.h"
+#include "wall.h"
+
+static usec_t when_wall(usec_t n, usec_t elapse) {
+ static const int wall_timers[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 25, 40, 55, 70, 100, 130, 150, 180,
+ };
+
+ /* If the time is already passed, then don't announce */
+ if (n >= elapse)
+ return 0;
+
+ usec_t left = elapse - n;
+
+ for (unsigned i = 1; i < ELEMENTSOF(wall_timers); i++)
+ if (wall_timers[i] * USEC_PER_MINUTE >= left)
+ return left - wall_timers[i-1] * USEC_PER_MINUTE;
+
+ return left % USEC_PER_HOUR;
+}
+
+bool logind_wall_tty_filter(const char *tty, bool is_local, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(m->scheduled_shutdown_action);
+
+ const char *p = path_startswith(tty, "/dev/");
+ if (!p)
+ return true;
+
+ /* Do not send information about events which do not destroy local sessions to local terminals. We
+ * 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))
+ return false;
+
+ return !streq_ptr(p, m->scheduled_shutdown_tty);
+}
+
+static int warn_wall(Manager *m, usec_t n) {
+ assert(m);
+
+ if (!m->scheduled_shutdown_action)
+ return 0;
+
+ bool left = m->scheduled_shutdown_timeout > n;
+
+ _cleanup_free_ char *l = NULL;
+ 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),
+ left ? "at " : "now",
+ left ? FORMAT_TIMESTAMP(m->scheduled_shutdown_timeout) : "") < 0) {
+
+ log_oom();
+ return 1; /* We're out-of-memory for now, but let's try to print the message later */
+ }
+
+ _cleanup_free_ char *username = uid_to_name(m->scheduled_shutdown_uid);
+
+ int level = left ? LOG_INFO : LOG_NOTICE;
+
+ log_struct(level,
+ LOG_MESSAGE("%s", l),
+ "ACTION=%s", handle_action_to_string(m->scheduled_shutdown_action->handle),
+ "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_SCHEDULED_STR,
+ username ? "OPERATOR=%s" : NULL, username);
+
+ if (m->enable_wall_messages)
+ (void) wall(l, username, m->scheduled_shutdown_tty, logind_wall_tty_filter, m);
+
+ return 1;
+}
+
+static int wall_message_timeout_handler(
+ sd_event_source *s,
+ uint64_t usec,
+ void *userdata) {
+
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(s == m->wall_message_timeout_source);
+
+ usec_t n = now(CLOCK_REALTIME);
+
+ r = warn_wall(m, n);
+ if (r == 0)
+ return 0;
+
+ usec_t next = when_wall(n, m->scheduled_shutdown_timeout);
+ if (next > 0) {
+ r = sd_event_source_set_time(s, n + next);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_source_set_time() failed. %m");
+
+ r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_source_set_enabled() failed. %m");
+ }
+
+ return 0;
+}
+
+int manager_setup_wall_message_timer(Manager *m) {
+ int r;
+
+ assert(m);
+
+ usec_t n = now(CLOCK_REALTIME);
+ usec_t elapse = m->scheduled_shutdown_timeout;
+
+ /* wall message handling */
+
+ if (!m->scheduled_shutdown_action)
+ return 0;
+
+ if (elapse > 0 && elapse < n)
+ return 0;
+
+ /* Warn immediately if less than 15 minutes are left */
+ if (elapse == 0 || elapse - n < 15 * USEC_PER_MINUTE) {
+ r = warn_wall(m, n);
+ if (r == 0)
+ return 0;
+ }
+
+ elapse = when_wall(n, elapse);
+ if (elapse == 0)
+ return 0;
+
+ r = event_reset_time(m->event, &m->wall_message_timeout_source,
+ CLOCK_REALTIME,
+ n + elapse, 0,
+ wall_message_timeout_handler, m,
+ 0, "wall-message-timer", true);
+
+ if (r < 0) {
+ m->wall_message_timeout_source = sd_event_source_unref(m->wall_message_timeout_source);
+ return log_error_errno(r, "Failed to set up wall message timer: %m");
+ }
+
+ return 0;
+}
diff --git a/src/login/logind.c b/src/login/logind.c
new file mode 100644
index 0000000..88e05bb
--- /dev/null
+++ b/src/login/logind.c
@@ -0,0 +1,1206 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sd-daemon.h"
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-log-control-api.h"
+#include "bus-polkit.h"
+#include "cgroup-util.h"
+#include "common-signal.h"
+#include "constants.h"
+#include "daemon-util.h"
+#include "device-util.h"
+#include "dirent-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "logind-dbus.h"
+#include "logind-seat-dbus.h"
+#include "logind-session-dbus.h"
+#include "logind-user-dbus.h"
+#include "logind.h"
+#include "main-func.h"
+#include "mkdir-label.h"
+#include "parse-util.h"
+#include "process-util.h"
+#include "selinux-util.h"
+#include "service-util.h"
+#include "signal-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "udev-util.h"
+#include "user-util.h"
+
+static Manager* manager_free(Manager *m);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(device_hash_ops, char, string_hash_func, string_compare_func, Device, device_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(seat_hash_ops, char, string_hash_func, string_compare_func, Seat, seat_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(session_hash_ops, char, string_hash_func, string_compare_func, Session, session_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(user_hash_ops, void, trivial_hash_func, trivial_compare_func, User, user_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(inhibitor_hash_ops, char, string_hash_func, string_compare_func, Inhibitor, inhibitor_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(button_hash_ops, char, string_hash_func, string_compare_func, Button, button_free);
+
+static int manager_new(Manager **ret) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ int r;
+
+ assert(ret);
+
+ m = new(Manager, 1);
+ if (!m)
+ return -ENOMEM;
+
+ *m = (Manager) {
+ .console_active_fd = -EBADF,
+ .reserve_vt_fd = -EBADF,
+ .enable_wall_messages = true,
+ .idle_action_not_before_usec = now(CLOCK_MONOTONIC),
+ };
+
+ 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);
+
+ m->user_units = hashmap_new(&string_hash_ops);
+ m->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;
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(m->event, NULL, SIGRTMIN+18, sigrtmin18_handler, NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL);
+ if (r < 0)
+ log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m");
+
+ (void) sd_event_set_watchdog(m->event, true);
+
+ manager_reset_config(m);
+
+ *ret = TAKE_PTR(m);
+ return 0;
+}
+
+static Manager* manager_free(Manager *m) {
+ if (!m)
+ return NULL;
+
+ hashmap_free(m->devices);
+ hashmap_free(m->seats);
+ hashmap_free(m->sessions);
+
+ /* All records should have been removed by session_free */
+ assert(hashmap_isempty(m->sessions_by_leader));
+ hashmap_free(m->sessions_by_leader);
+
+ hashmap_free(m->users);
+ hashmap_free(m->inhibitors);
+ hashmap_free(m->buttons);
+ hashmap_free(m->brightness_writers);
+
+ hashmap_free(m->user_units);
+ hashmap_free(m->session_units);
+
+ sd_event_source_unref(m->idle_action_event_source);
+ sd_event_source_unref(m->inhibit_timeout_source);
+ sd_event_source_unref(m->scheduled_shutdown_timeout_source);
+ sd_event_source_unref(m->nologin_timeout_source);
+ sd_event_source_unref(m->wall_message_timeout_source);
+
+ sd_event_source_unref(m->console_active_event_source);
+ sd_event_source_unref(m->lid_switch_ignore_event_source);
+
+ sd_event_source_unref(m->reboot_key_long_press_event_source);
+
+#if ENABLE_UTMP
+ sd_event_source_unref(m->utmp_event_source);
+#endif
+
+ safe_close(m->console_active_fd);
+
+ sd_device_monitor_unref(m->device_seat_monitor);
+ sd_device_monitor_unref(m->device_monitor);
+ sd_device_monitor_unref(m->device_vcsa_monitor);
+ sd_device_monitor_unref(m->device_button_monitor);
+
+ if (m->unlink_nologin)
+ (void) unlink_or_warn("/run/nologin");
+
+ bus_verify_polkit_async_registry_free(m->polkit_registry);
+
+ sd_bus_flush_close_unref(m->bus);
+ sd_event_unref(m->event);
+
+ safe_close(m->reserve_vt_fd);
+
+ strv_free(m->kill_only_users);
+ strv_free(m->kill_exclude_users);
+
+ free(m->scheduled_shutdown_tty);
+ free(m->wall_message);
+ free(m->action_job);
+
+ strv_free(m->efi_boot_loader_entries);
+ free(m->efi_loader_entry_one_shot);
+
+ return mfree(m);
+}
+
+static int manager_enumerate_devices(Manager *m) {
+ _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+ int r;
+
+ assert(m);
+
+ /* Loads devices from udev and creates seats for them as
+ * necessary */
+
+ r = sd_device_enumerator_new(&e);
+ if (r < 0)
+ return r;
+
+ r = sd_device_enumerator_add_match_tag(e, "master-of-seat");
+ if (r < 0)
+ return r;
+
+ FOREACH_DEVICE(e, d) {
+ int k;
+
+ k = manager_process_seat_device(m, d);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static int manager_enumerate_buttons(Manager *m) {
+ _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+ int r;
+
+ assert(m);
+
+ /* Loads buttons from udev */
+
+ if (manager_all_buttons_ignored(m))
+ return 0;
+
+ r = sd_device_enumerator_new(&e);
+ if (r < 0)
+ return r;
+
+ r = sd_device_enumerator_add_match_subsystem(e, "input", true);
+ if (r < 0)
+ return r;
+
+ r = sd_device_enumerator_add_match_tag(e, "power-switch");
+ if (r < 0)
+ return r;
+
+ FOREACH_DEVICE(e, d) {
+ int k;
+
+ k = manager_process_button_device(m, d);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static int manager_enumerate_seats(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
+ int r = 0;
+
+ assert(m);
+
+ /* This loads data about seats stored on disk, but does not
+ * actually create any seats. Removes data of seats that no
+ * longer exist. */
+
+ d = opendir("/run/systemd/seats");
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+
+ 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;
+
+ s = hashmap_get(m->seats, de->d_name);
+ if (!s) {
+ if (unlinkat(dirfd(d), de->d_name, 0) < 0)
+ log_warning_errno(errno, "Failed to remove /run/systemd/seats/%s, ignoring: %m",
+ de->d_name);
+ continue;
+ }
+
+ k = seat_load(s);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static int manager_enumerate_linger_users(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
+ int r = 0;
+
+ assert(m);
+
+ d = opendir("/var/lib/systemd/linger");
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_error_errno(errno, "Failed to open /var/lib/systemd/linger/: %m");
+ }
+
+ FOREACH_DIRENT(de, d, return -errno) {
+ int k;
+ _cleanup_free_ char *n = NULL;
+
+ 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);
+ 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);
+ }
+
+ return r;
+}
+
+static int manager_enumerate_users(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
+ int r, k;
+
+ assert(m);
+
+ /* Add lingering users */
+ r = manager_enumerate_linger_users(m);
+
+ /* Read in user data stored on disk */
+ d = opendir("/run/systemd/users");
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_error_errno(errno, "Failed to open /run/systemd/users: %m");
+ }
+
+ FOREACH_DIRENT(de, d, return -errno) {
+ User *u;
+ uid_t uid;
+
+ 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);
+ 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);
+ continue;
+ }
+
+ user_add_to_gc_queue(u);
+
+ k = user_load(u);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static int parse_fdname(const char *fdname, char **session_id, dev_t *dev) {
+ _cleanup_strv_free_ char **parts = NULL;
+ _cleanup_free_ char *id = NULL;
+ unsigned major, minor;
+ int r;
+
+ parts = strv_split(fdname, "-");
+ if (!parts)
+ return -ENOMEM;
+ if (strv_length(parts) != 5)
+ return -EINVAL;
+
+ if (!streq(parts[0], "session"))
+ return -EINVAL;
+
+ id = strdup(parts[1]);
+ if (!id)
+ return -ENOMEM;
+
+ if (!streq(parts[2], "device"))
+ return -EINVAL;
+
+ 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);
+
+ return 0;
+}
+
+static int deliver_fd(Manager *m, const char *fdname, int fd) {
+ _cleanup_free_ char *id = NULL;
+ SessionDevice *sd;
+ struct stat st;
+ Session *s;
+ dev_t dev;
+ int r;
+
+ assert(m);
+ 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);
+
+ 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);
+
+ 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");
+
+ sd = hashmap_get(s->devices, &dev);
+ 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);
+
+ log_debug("Attaching fd to session device [%u:%u] for session %s",
+ major(dev), minor(dev), 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;
+
+ /* 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(m);
+
+ 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;
+
+ for (int i = 0; i < n; i++) {
+ int fd = SD_LISTEN_FDS_START + i;
+
+ if (deliver_fd(m, fdnames[i], fd) >= 0)
+ continue;
+
+ /* 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]);
+ }
+
+ return 0;
+}
+
+static int manager_enumerate_sessions(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
+ int r = 0, k;
+
+ assert(m);
+
+ /* Read in session data stored on disk */
+ d = opendir("/run/systemd/sessions");
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_error_errno(errno, "Failed to open /run/systemd/sessions: %m");
+ }
+
+ FOREACH_DIRENT(de, d, return -errno) {
+ struct Session *s;
+
+ 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);
+ continue;
+ }
+
+ session_add_to_gc_queue(s);
+
+ k = session_load(s);
+ if (k < 0)
+ r = k;
+ }
+
+ /* We might be restarted and PID1 could have sent us back the session device fds we previously
+ * saved. */
+ (void) manager_attach_fds(m);
+
+ return r;
+}
+
+static int manager_enumerate_inhibitors(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
+ int r = 0;
+
+ assert(m);
+
+ d = opendir("/run/systemd/inhibit");
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_error_errno(errno, "Failed to open /run/systemd/inhibit: %m");
+ }
+
+ FOREACH_DIRENT(de, d, return -errno) {
+ int k;
+ Inhibitor *i;
+
+ 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);
+ continue;
+ }
+
+ k = inhibitor_load(i);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static int manager_dispatch_seat_udev(sd_device_monitor *monitor, sd_device *device, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(device);
+
+ manager_process_seat_device(m, device);
+ return 0;
+}
+
+static int manager_dispatch_device_udev(sd_device_monitor *monitor, sd_device *device, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(device);
+
+ manager_process_seat_device(m, device);
+ return 0;
+}
+
+static int manager_dispatch_vcsa_udev(sd_device_monitor *monitor, sd_device *device, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+ const char *name;
+
+ assert(device);
+
+ /* Whenever a VCSA device is removed try to reallocate our
+ * VTs, to make sure our auto VTs never go away. */
+
+ if (sd_device_get_sysname(device, &name) >= 0 &&
+ startswith(name, "vcsa") &&
+ device_for_action(device, SD_DEVICE_REMOVE))
+ seat_preallocate_vts(m->seat0);
+
+ return 0;
+}
+
+static int manager_dispatch_button_udev(sd_device_monitor *monitor, sd_device *device, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(device);
+
+ manager_process_button_device(m, device);
+ return 0;
+}
+
+static int manager_dispatch_console(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(m->seat0);
+ assert(m->console_active_fd == fd);
+
+ seat_read_active_vt(m->seat0);
+ return 0;
+}
+
+static int manager_reserve_vt(Manager *m) {
+ _cleanup_free_ char *p = NULL;
+
+ assert(m);
+
+ if (m->reserve_vt <= 0)
+ return 0;
+
+ if (asprintf(&p, "/dev/tty%u", m->reserve_vt) < 0)
+ return log_oom();
+
+ m->reserve_vt_fd = open(p, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
+ if (m->reserve_vt_fd < 0) {
+
+ /* Don't complain on VT-less systems */
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Failed to pin reserved VT: %m");
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int manager_connect_bus(Manager *m) {
+ int r;
+
+ assert(m);
+ assert(!m->bus);
+
+ r = sd_bus_default_system(&m->bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to system bus: %m");
+
+ r = bus_add_implementation(m->bus, &manager_object, m);
+ if (r < 0)
+ return r;
+
+ r = bus_log_control_api_register(m->bus);
+ if (r < 0)
+ return r;
+
+ r = bus_match_signal_async(m->bus, NULL, bus_systemd_mgr, "JobRemoved", match_job_removed, NULL, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request match for JobRemoved: %m");
+
+ r = bus_match_signal_async(m->bus, NULL, bus_systemd_mgr, "UnitRemoved", match_unit_removed, NULL, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request match for UnitRemoved: %m");
+
+ r = sd_bus_match_signal_async(
+ m->bus,
+ NULL,
+ "org.freedesktop.systemd1",
+ NULL,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ match_properties_changed, NULL, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request match for PropertiesChanged: %m");
+
+ r = bus_match_signal_async(m->bus, NULL, bus_systemd_mgr, "Reloading", match_reloading, NULL, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request match for Reloading: %m");
+
+ r = bus_call_method_async(m->bus, NULL, bus_systemd_mgr, "Subscribe", NULL, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable subscription: %m");
+
+ r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.login1", 0, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request name: %m");
+
+ r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach bus to event loop: %m");
+
+ return 0;
+}
+
+static int manager_vt_switch(sd_event_source *src, const struct signalfd_siginfo *si, void *data) {
+ Manager *m = ASSERT_PTR(data);
+ Session *active;
+
+ /*
+ * We got a VT-switch signal and we have to acknowledge it immediately.
+ * Preferably, we'd just use m->seat0->active->vtfd, but unfortunately,
+ * old user-space might run multiple sessions on a single VT, *sigh*.
+ * Therefore, we have to iterate all sessions and find one with a vtfd
+ * on the requested VT.
+ * As only VTs with active controllers have VT_PROCESS set, our current
+ * notion of the active VT might be wrong (for instance if the switch
+ * happens while we setup VT_PROCESS). Therefore, read the current VT
+ * first and then use s->active->vtnr as reference. Note that this is
+ * not racy, as no further VT-switch can happen as long as we're in
+ * synchronous VT_PROCESS mode.
+ */
+
+ assert(m->seat0);
+
+ seat_read_active_vt(m->seat0);
+
+ active = m->seat0->active;
+ if (!active || active->vtnr < 1) {
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ /* We are requested to acknowledge the VT-switch signal by the kernel but
+ * there's no registered sessions for the current VT. Normally this
+ * shouldn't happen but something wrong might have happened when we tried
+ * to release the VT. Better be safe than sorry, and try to release the VT
+ * one more time otherwise the user will be locked with the current VT. */
+
+ log_warning("Received VT_PROCESS signal without a registered session, restoring VT.");
+
+ /* At this point we only have the kernel mapping for referring to the current VT. */
+ fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
+ if (fd < 0) {
+ log_warning_errno(fd, "Failed to open current VT, ignoring: %m");
+ return 0;
+ }
+
+ r = vt_release(fd, /* restore = */ true);
+ if (r < 0)
+ log_warning_errno(r, "Failed to release current VT, ignoring: %m");
+
+ return 0;
+ }
+
+ if (active->vtfd >= 0)
+ session_leave_vt(active);
+ else
+ LIST_FOREACH(sessions_by_seat, iter, m->seat0->sessions)
+ if (iter->vtnr == active->vtnr && iter->vtfd >= 0) {
+ session_leave_vt(iter);
+ break;
+ }
+
+ return 0;
+}
+
+static int manager_connect_console(Manager *m) {
+ int r;
+
+ assert(m);
+ assert(m->console_active_fd < 0);
+
+ /* On certain systems (such as S390, Xen, and containers) /dev/tty0 does not exist (as there is no VC), so
+ * don't fail if we can't open it. */
+
+ if (access("/dev/tty0", F_OK) < 0)
+ return 0;
+
+ m->console_active_fd = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (m->console_active_fd < 0) {
+
+ /* On some systems /dev/tty0 may exist even though /sys/class/tty/tty0 does not. These are broken, but
+ * common. Let's complain but continue anyway. */
+ if (errno == ENOENT) {
+ log_warning_errno(errno, "System has /dev/tty0 but not /sys/class/tty/tty0/active which is broken, ignoring: %m");
+ return 0;
+ }
+
+ return log_error_errno(errno, "Failed to open /sys/class/tty/tty0/active: %m");
+ }
+
+ r = sd_event_add_io(m->event, &m->console_active_event_source, m->console_active_fd, 0, manager_dispatch_console, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch foreground console: %m");
+
+ /*
+ * SIGRTMIN is used as global VT-release signal, SIGRTMIN + 1 is used
+ * as VT-acquire signal. We ignore any acquire-events (yes, we still
+ * have to provide a valid signal-number for it!) and acknowledge all
+ * release events immediately.
+ */
+
+ if (SIGRTMIN + 1 > SIGRTMAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Not enough real-time signals available: %i-%i",
+ SIGRTMIN, SIGRTMAX);
+
+ assert_se(ignore_signals(SIGRTMIN + 1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN, -1) >= 0);
+
+ r = sd_event_add_signal(m->event, NULL, SIGRTMIN, manager_vt_switch, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to subscribe to signal: %m");
+
+ return 0;
+}
+
+static int manager_connect_udev(Manager *m) {
+ int r;
+
+ assert(m);
+ assert(!m->device_seat_monitor);
+ assert(!m->device_monitor);
+ assert(!m->device_vcsa_monitor);
+ assert(!m->device_button_monitor);
+
+ r = sd_device_monitor_new(&m->device_seat_monitor);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_filter_add_match_tag(m->device_seat_monitor, "master-of-seat");
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_attach_event(m->device_seat_monitor, m->event);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_start(m->device_seat_monitor, manager_dispatch_seat_udev, m);
+ if (r < 0)
+ return r;
+
+ (void) sd_device_monitor_set_description(m->device_seat_monitor, "seat");
+
+ r = sd_device_monitor_new(&m->device_monitor);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "input", NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "graphics", NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "drm", NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_attach_event(m->device_monitor, m->event);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_start(m->device_monitor, manager_dispatch_device_udev, m);
+ if (r < 0)
+ return r;
+
+ (void) sd_device_monitor_set_description(m->device_monitor, "input,graphics,drm");
+
+ /* Don't watch keys if nobody cares */
+ if (!manager_all_buttons_ignored(m)) {
+ r = sd_device_monitor_new(&m->device_button_monitor);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_filter_add_match_tag(m->device_button_monitor, "power-switch");
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_button_monitor, "input", NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_attach_event(m->device_button_monitor, m->event);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_start(m->device_button_monitor, manager_dispatch_button_udev, m);
+ if (r < 0)
+ return r;
+
+ (void) sd_device_monitor_set_description(m->device_button_monitor, "button");
+ }
+
+ /* Don't bother watching VCSA devices, if nobody cares */
+ if (m->n_autovts > 0 && m->console_active_fd >= 0) {
+
+ r = sd_device_monitor_new(&m->device_vcsa_monitor);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_vcsa_monitor, "vc", NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_attach_event(m->device_vcsa_monitor, m->event);
+ if (r < 0)
+ return r;
+
+ r = sd_device_monitor_start(m->device_vcsa_monitor, manager_dispatch_vcsa_udev, m);
+ if (r < 0)
+ return r;
+
+ (void) sd_device_monitor_set_description(m->device_vcsa_monitor, "vcsa");
+ }
+
+ return 0;
+}
+
+static void manager_gc(Manager *m, bool drop_not_started) {
+ Seat *seat;
+ Session *session;
+ User *user;
+
+ assert(m);
+
+ while ((seat = LIST_POP(gc_queue, m->seat_gc_queue))) {
+ seat->in_gc_queue = false;
+
+ if (seat_may_gc(seat, drop_not_started)) {
+ seat_stop(seat, /* force = */ false);
+ seat_free(seat);
+ }
+ }
+
+ while ((session = LIST_POP(gc_queue, m->session_gc_queue))) {
+ session->in_gc_queue = false;
+
+ /* First, if we are not closing yet, initiate stopping. */
+ if (session_may_gc(session, drop_not_started) &&
+ session_get_state(session) != SESSION_CLOSING)
+ (void) session_stop(session, /* force = */ false);
+
+ /* Normally, this should make the session referenced again, if it doesn't then let's get rid
+ * of it immediately. */
+ if (session_may_gc(session, drop_not_started)) {
+ (void) session_finalize(session);
+ session_free(session);
+ }
+ }
+
+ while ((user = LIST_POP(gc_queue, m->user_gc_queue))) {
+ user->in_gc_queue = false;
+
+ /* First step: queue stop jobs */
+ if (user_may_gc(user, drop_not_started))
+ (void) user_stop(user, false);
+
+ /* Second step: finalize user */
+ if (user_may_gc(user, drop_not_started)) {
+ (void) user_finalize(user);
+ user_free(user);
+ }
+ }
+}
+
+static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+ struct dual_timestamp since;
+ usec_t n, elapse;
+ int r;
+
+ if (m->idle_action == HANDLE_IGNORE ||
+ m->idle_action_usec <= 0)
+ return 0;
+
+ n = now(CLOCK_MONOTONIC);
+
+ r = manager_get_idle_hint(m, &since);
+ if (r <= 0) {
+ /* Not idle. Let's check if after a timeout it might be idle then. */
+ elapse = n + m->idle_action_usec;
+ m->was_idle = false;
+ } else {
+
+ /* Idle! Let's see if it's time to do something, or if
+ * we shall sleep for longer. */
+
+ if (n >= since.monotonic + m->idle_action_usec &&
+ (m->idle_action_not_before_usec <= 0 || n >= m->idle_action_not_before_usec + m->idle_action_usec)) {
+ bool is_edge = false;
+
+ /* We weren't idle previously or some activity happened while we were sleeping, and now we are
+ * idle. Let's remember that for the next time and make this an edge transition. */
+ if (!m->was_idle || since.monotonic >= m->idle_action_not_before_usec) {
+ is_edge = true;
+ m->was_idle = true;
+ }
+
+ if (m->idle_action == HANDLE_LOCK && !is_edge)
+ /* We are idle and we were before so we are actually not taking any action. */
+ log_debug("System idle.");
+ else
+ log_info("System idle. Will %s now.", handle_action_verb_to_string(m->idle_action));
+
+ manager_handle_action(m, 0, m->idle_action, false, is_edge);
+ m->idle_action_not_before_usec = n;
+ }
+
+ elapse = MAX(since.monotonic, m->idle_action_not_before_usec) + m->idle_action_usec;
+ }
+
+ if (!m->idle_action_event_source) {
+
+ r = sd_event_add_time(
+ m->event,
+ &m->idle_action_event_source,
+ CLOCK_MONOTONIC,
+ elapse, USEC_PER_SEC*30,
+ manager_dispatch_idle_action, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add idle event source: %m");
+
+ r = sd_event_source_set_priority(m->idle_action_event_source, SD_EVENT_PRIORITY_IDLE+10);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set idle event source priority: %m");
+ } else {
+ r = sd_event_source_set_time(m->idle_action_event_source, elapse);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set idle event timer: %m");
+
+ r = sd_event_source_set_enabled(m->idle_action_event_source, SD_EVENT_ONESHOT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable idle event timer: %m");
+ }
+
+ return 0;
+}
+
+static int manager_dispatch_reload_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *m = userdata;
+ int r;
+
+ (void) sd_notifyf(/* unset= */ false,
+ "RELOADING=1\n"
+ "STATUS=Reloading configuration...\n"
+ "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC));
+
+ manager_reset_config(m);
+ r = manager_parse_config_file(m);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse config file, using defaults: %m");
+ else
+ log_info("Config file reloaded.");
+
+ (void) sd_notify(/* unset= */ false, NOTIFY_READY);
+ return 0;
+}
+
+static int manager_startup(Manager *m) {
+ int r;
+ Seat *seat;
+ Session *session;
+ User *user;
+ Button *button;
+ Inhibitor *inhibitor;
+
+ assert(m);
+
+ r = sd_event_add_signal(m->event, NULL, SIGHUP, manager_dispatch_reload_signal, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register SIGHUP handler: %m");
+
+ /* Connect to utmp */
+ manager_connect_utmp(m);
+
+ /* Connect to console */
+ r = manager_connect_console(m);
+ if (r < 0)
+ return r;
+
+ /* Connect to udev */
+ r = manager_connect_udev(m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create udev watchers: %m");
+
+ /* Connect to the bus */
+ r = manager_connect_bus(m);
+ if (r < 0)
+ return r;
+
+ /* Instantiate magic seat 0 */
+ r = manager_add_seat(m, "seat0", &m->seat0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add seat0: %m");
+
+ r = manager_set_lid_switch_ignore(m, 0 + m->holdoff_timeout_usec);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set up lid switch ignore event source: %m");
+
+ /* Deserialize state */
+ r = manager_enumerate_devices(m);
+ if (r < 0)
+ log_warning_errno(r, "Device enumeration failed: %m");
+
+ r = manager_enumerate_seats(m);
+ if (r < 0)
+ log_warning_errno(r, "Seat enumeration failed: %m");
+
+ r = manager_enumerate_users(m);
+ if (r < 0)
+ log_warning_errno(r, "User enumeration failed: %m");
+
+ r = manager_enumerate_sessions(m);
+ if (r < 0)
+ log_warning_errno(r, "Session enumeration failed: %m");
+
+ r = manager_enumerate_inhibitors(m);
+ if (r < 0)
+ log_warning_errno(r, "Inhibitor enumeration failed: %m");
+
+ r = manager_enumerate_buttons(m);
+ if (r < 0)
+ log_warning_errno(r, "Button enumeration failed: %m");
+
+ manager_load_scheduled_shutdown(m);
+
+ /* Remove stale objects before we start them */
+ manager_gc(m, false);
+
+ /* Reserve the special reserved VT */
+ manager_reserve_vt(m);
+
+ /* Read in utmp if it exists */
+ manager_read_utmp(m);
+
+ /* And start everything */
+ HASHMAP_FOREACH(seat, m->seats)
+ (void) seat_start(seat);
+
+ HASHMAP_FOREACH(user, m->users)
+ (void) user_start(user);
+
+ HASHMAP_FOREACH(session, m->sessions)
+ (void) session_start(session, NULL, NULL);
+
+ HASHMAP_FOREACH(inhibitor, m->inhibitors) {
+ (void) inhibitor_start(inhibitor);
+
+ /* Let's see if the inhibitor is dead now, then remove it */
+ if (inhibitor_is_orphan(inhibitor)) {
+ inhibitor_stop(inhibitor);
+ inhibitor_free(inhibitor);
+ }
+ }
+
+ HASHMAP_FOREACH(button, m->buttons)
+ button_check_switches(button);
+
+ manager_dispatch_idle_action(NULL, 0, m);
+
+ return 0;
+}
+
+static int manager_run(Manager *m) {
+ int r;
+
+ assert(m);
+
+ for (;;) {
+ r = sd_event_get_state(m->event);
+ if (r < 0)
+ return r;
+ if (r == SD_EVENT_FINISHED)
+ return 0;
+
+ manager_gc(m, true);
+
+ r = manager_dispatch_delayed(m, false);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ r = sd_event_run(m->event, UINT64_MAX);
+ if (r < 0)
+ return r;
+ }
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL;
+ int r;
+
+ log_set_facility(LOG_AUTH);
+ log_setup();
+
+ r = service_parse_argv("systemd-logind.service",
+ "Manager for user logins and devices and privileged operations.",
+ BUS_IMPLEMENTATIONS(&manager_object,
+ &log_control_object),
+ argc, argv);
+ if (r <= 0)
+ return r;
+
+ umask(0022);
+
+ r = mac_init();
+ if (r < 0)
+ return r;
+
+ /* Always create the directories people can create inotify watches in. Note that some applications
+ * might check for the existence of /run/systemd/seats/ to determine whether logind is available, so
+ * please always make sure these directories are created early on and unconditionally. */
+ (void) mkdir_label("/run/systemd/seats", 0755);
+ (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);
+
+ r = manager_new(&m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate manager object: %m");
+
+ (void) manager_parse_config_file(m);
+
+ r = manager_startup(m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to fully start up daemon: %m");
+
+ notify_message = notify_start(NOTIFY_READY, NOTIFY_STOPPING);
+ return manager_run(m);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/login/logind.conf.in b/src/login/logind.conf.in
new file mode 100644
index 0000000..e5fe924
--- /dev/null
+++ b/src/login/logind.conf.in
@@ -0,0 +1,51 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# Entries in this file show the compile time defaults. Local configuration
+# should be created by either modifying this file (or a copy of it placed in
+# /etc/ if the original file is shipped in /usr/), or by creating "drop-ins" in
+# the /etc/systemd/logind.conf.d/ directory. The latter is generally
+# recommended. Defaults can be restored by simply deleting the main
+# configuration file and all drop-ins located in /etc/.
+#
+# Use 'systemd-analyze cat-config systemd/logind.conf' to display the full config.
+#
+# See logind.conf(5) for details.
+
+[Login]
+#NAutoVTs=6
+#ReserveVT=6
+#KillUserProcesses={{ "yes" if KILL_USER_PROCESSES else "no" }}
+#KillOnlyUsers=
+#KillExcludeUsers=root
+#InhibitDelayMaxSec=5
+#UserStopDelaySec=10
+#HandlePowerKey=poweroff
+#HandlePowerKeyLongPress=ignore
+#HandleRebootKey=reboot
+#HandleRebootKeyLongPress=poweroff
+#HandleSuspendKey=suspend
+#HandleSuspendKeyLongPress=hibernate
+#HandleHibernateKey=hibernate
+#HandleHibernateKeyLongPress=ignore
+#HandleLidSwitch=suspend
+#HandleLidSwitchExternalPower=suspend
+#HandleLidSwitchDocked=ignore
+#PowerKeyIgnoreInhibited=no
+#SuspendKeyIgnoreInhibited=no
+#HibernateKeyIgnoreInhibited=no
+#LidSwitchIgnoreInhibited=yes
+#RebootKeyIgnoreInhibited=no
+#HoldoffTimeoutSec=30s
+#IdleAction=ignore
+#IdleActionSec=30min
+#RuntimeDirectorySize=10%
+#RuntimeDirectoryInodesMax=
+#RemoveIPC=yes
+#InhibitorsMax=8192
+#SessionsMax=8192
+#StopIdleSessionSec=infinity
diff --git a/src/login/logind.h b/src/login/logind.h
new file mode 100644
index 0000000..7532d37
--- /dev/null
+++ b/src/login/logind.h
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <sys/stat.h>
+
+#include "sd-bus.h"
+#include "sd-device.h"
+#include "sd-event.h"
+
+#include "conf-parser.h"
+#include "hashmap.h"
+#include "list.h"
+#include "set.h"
+#include "time-util.h"
+#include "user-record.h"
+
+typedef struct Manager Manager;
+
+#include "logind-action.h"
+#include "logind-button.h"
+#include "logind-device.h"
+#include "logind-inhibit.h"
+
+struct Manager {
+ sd_event *event;
+ sd_bus *bus;
+
+ Hashmap *devices;
+ Hashmap *seats;
+ Hashmap *sessions;
+ Hashmap *sessions_by_leader;
+ Hashmap *users; /* indexed by UID */
+ Hashmap *inhibitors;
+ Hashmap *buttons;
+ Hashmap *brightness_writers;
+
+ LIST_HEAD(Seat, seat_gc_queue);
+ LIST_HEAD(Session, session_gc_queue);
+ LIST_HEAD(User, user_gc_queue);
+
+ sd_device_monitor *device_seat_monitor, *device_monitor, *device_vcsa_monitor, *device_button_monitor;
+
+ sd_event_source *console_active_event_source;
+
+#if ENABLE_UTMP
+ sd_event_source *utmp_event_source;
+#endif
+
+ int console_active_fd;
+
+ unsigned n_autovts;
+
+ unsigned reserve_vt;
+ int reserve_vt_fd;
+
+ Seat *seat0;
+
+ char **kill_only_users, **kill_exclude_users;
+ bool kill_user_processes;
+
+ uint64_t session_counter;
+ uint64_t inhibit_counter;
+
+ Hashmap *session_units;
+ Hashmap *user_units;
+
+ usec_t inhibit_delay_max;
+ usec_t user_stop_delay;
+
+ /* If a shutdown/suspend was delayed due to an inhibitor this contains the action we are supposed to
+ * start after the delay is over */
+ const HandleActionData *delayed_action;
+
+ /* If a shutdown/suspend is currently executed, then this is the job of it */
+ char *action_job;
+ sd_event_source *inhibit_timeout_source;
+
+ const HandleActionData *scheduled_shutdown_action;
+ usec_t scheduled_shutdown_timeout;
+ sd_event_source *scheduled_shutdown_timeout_source;
+ uid_t scheduled_shutdown_uid;
+ char *scheduled_shutdown_tty;
+ sd_event_source *nologin_timeout_source;
+ bool unlink_nologin;
+
+ char *wall_message;
+ bool enable_wall_messages;
+ sd_event_source *wall_message_timeout_source;
+
+ bool shutdown_dry_run;
+
+ sd_event_source *idle_action_event_source;
+ usec_t idle_action_usec;
+ usec_t idle_action_not_before_usec;
+ HandleAction idle_action;
+ bool was_idle;
+
+ usec_t stop_idle_session_usec;
+
+ HandleAction handle_power_key;
+ HandleAction handle_power_key_long_press;
+ HandleAction handle_reboot_key;
+ HandleAction handle_reboot_key_long_press;
+ HandleAction handle_suspend_key;
+ HandleAction handle_suspend_key_long_press;
+ HandleAction handle_hibernate_key;
+ HandleAction handle_hibernate_key_long_press;
+
+ HandleAction handle_lid_switch;
+ HandleAction handle_lid_switch_ep;
+ HandleAction handle_lid_switch_docked;
+
+ bool power_key_ignore_inhibited;
+ bool suspend_key_ignore_inhibited;
+ bool hibernate_key_ignore_inhibited;
+ bool lid_switch_ignore_inhibited;
+ bool reboot_key_ignore_inhibited;
+
+ bool remove_ipc;
+
+ Hashmap *polkit_registry;
+
+ usec_t holdoff_timeout_usec;
+ sd_event_source *lid_switch_ignore_event_source;
+
+ sd_event_source *power_key_long_press_event_source;
+ sd_event_source *reboot_key_long_press_event_source;
+ sd_event_source *suspend_key_long_press_event_source;
+ sd_event_source *hibernate_key_long_press_event_source;
+
+ uint64_t runtime_dir_size;
+ uint64_t runtime_dir_inodes;
+ uint64_t sessions_max;
+ uint64_t inhibitors_max;
+
+ char **efi_boot_loader_entries;
+ bool efi_boot_loader_entries_set;
+
+ char *efi_loader_entry_one_shot;
+ struct stat efi_loader_entry_one_shot_stat;
+};
+
+void manager_reset_config(Manager *m);
+int manager_parse_config_file(Manager *m);
+
+int manager_add_device(Manager *m, const char *sysfs, bool master, Device **ret_device);
+int manager_add_button(Manager *m, const char *name, Button **ret_button);
+int manager_add_seat(Manager *m, const char *id, Seat **ret_seat);
+int manager_add_session(Manager *m, const char *id, Session **ret_session);
+int manager_add_user(Manager *m, UserRecord *ur, User **ret_user);
+int manager_add_user_by_name(Manager *m, const char *name, User **ret_user);
+int manager_add_user_by_uid(Manager *m, uid_t uid, User **ret_user);
+int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **ret_inhibitor);
+
+int manager_process_seat_device(Manager *m, sd_device *d);
+int manager_process_button_device(Manager *m, sd_device *d);
+
+int manager_spawn_autovt(Manager *m, unsigned vtnr);
+
+bool manager_shall_kill(Manager *m, const char *user);
+
+int manager_get_idle_hint(Manager *m, dual_timestamp *t);
+
+int manager_get_user_by_pid(Manager *m, pid_t pid, User **user);
+int manager_get_session_by_pidref(Manager *m, const PidRef *pid, Session **ret);
+
+bool manager_is_lid_closed(Manager *m);
+bool manager_is_docked_or_external_displays(Manager *m);
+bool manager_is_on_external_power(void);
+bool manager_all_buttons_ignored(Manager *m);
+
+int manager_read_utmp(Manager *m);
+void manager_connect_utmp(Manager *m);
+void manager_reconnect_utmp(Manager *m);
+
+/* gperf lookup function */
+const struct ConfigPerfItem* logind_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+
+int manager_set_lid_switch_ignore(Manager *m, usec_t until);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_n_autovts);
+CONFIG_PARSER_PROTOTYPE(config_parse_tmpfs_size);
+
+int manager_setup_wall_message_timer(Manager *m);
+bool logind_wall_tty_filter(const char *tty, bool is_local, void *userdata);
+
+int manager_read_efi_boot_loader_entries(Manager *m);
diff --git a/src/login/meson.build b/src/login/meson.build
new file mode 100644
index 0000000..b5bb150
--- /dev/null
+++ b/src/login/meson.build
@@ -0,0 +1,152 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+systemd_logind_sources = files(
+ 'logind.c',
+)
+
+logind_gperf_c = custom_target(
+ 'logind_gperf.c',
+ input : 'logind-gperf.gperf',
+ output : 'logind-gperf.c',
+ command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@'])
+
+liblogind_core_sources = files(
+ 'logind-action.c',
+ 'logind-brightness.c',
+ 'logind-button.c',
+ 'logind-core.c',
+ 'logind-dbus.c',
+ 'logind-device.c',
+ 'logind-inhibit.c',
+ 'logind-polkit.c',
+ 'logind-seat-dbus.c',
+ 'logind-seat.c',
+ 'logind-session-dbus.c',
+ 'logind-session-device.c',
+ 'logind-session.c',
+ 'logind-user-dbus.c',
+ 'logind-user.c',
+ 'logind-wall.c',
+)
+
+liblogind_core_sources += [logind_gperf_c]
+
+liblogind_core = static_library(
+ 'logind-core',
+ liblogind_core_sources,
+ include_directories : includes,
+ dependencies : [libacl,
+ userspace],
+ build_by_default : false)
+
+loginctl_sources = files(
+ 'loginctl.c',
+ 'sysfs-show.c',
+)
+
+executables += [
+ libexec_template + {
+ 'name' : 'systemd-logind',
+ 'dbus' : true,
+ 'conditions' : ['ENABLE_LOGIND'],
+ 'sources' : systemd_logind_sources,
+ 'link_with' : [
+ liblogind_core,
+ libshared,
+ ],
+ 'dependencies' : [
+ libacl,
+ threads,
+ ],
+ },
+ executable_template + {
+ 'name' : 'loginctl',
+ 'public' : true,
+ 'conditions' : ['ENABLE_LOGIND'],
+ 'sources' : loginctl_sources,
+ 'dependencies' : [
+ liblz4,
+ libxz,
+ libzstd,
+ threads,
+ ],
+ },
+ executable_template + {
+ 'name' : 'systemd-inhibit',
+ 'public' : true,
+ 'conditions' : ['ENABLE_LOGIND'],
+ 'sources' : files('inhibit.c'),
+ },
+ libexec_template + {
+ 'name' : 'systemd-user-runtime-dir',
+ 'conditions' : ['ENABLE_LOGIND'],
+ 'sources' : files('user-runtime-dir.c'),
+ },
+ test_template + {
+ 'sources' : files('test-inhibit.c'),
+ 'type' : 'manual',
+ },
+ test_template + {
+ 'sources' : files('test-login-tables.c'),
+ 'link_with' : [
+ liblogind_core,
+ libshared,
+ ],
+ 'dependencies' : threads,
+ },
+ test_template + {
+ 'sources' : files('test-session-properties.c'),
+ 'type' : 'manual',
+ },
+]
+
+simple_tests += files(
+ 'test-login-shared.c'
+)
+
+modules += [
+ pam_template + {
+ 'name' : 'pam_systemd',
+ 'conditions' : [
+ 'ENABLE_LOGIND',
+ 'HAVE_PAM',
+ ],
+ 'sources' : files('pam_systemd.c'),
+ 'version-script' : meson.current_source_dir() / 'pam_systemd.sym',
+ },
+ pam_template + {
+ 'name' : 'pam_systemd_loadkey',
+ 'conditions' : [
+ 'HAVE_PAM',
+ ],
+ 'sources' : files('pam_systemd_loadkey.c'),
+ 'version-script' : meson.current_source_dir() / 'pam_systemd_loadkey.sym',
+ },
+]
+
+enable_logind = conf.get('ENABLE_LOGIND') == 1
+
+custom_target(
+ 'logind.conf',
+ input : 'logind.conf.in',
+ output : 'logind.conf',
+ command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
+ install : enable_logind and install_sysconfdir_samples and pkgsysconfdir != 'no',
+ install_dir : pkgconfigfiledir)
+
+custom_target(
+ 'systemd-user',
+ input : 'systemd-user.in',
+ output : 'systemd-user',
+ command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
+ install : enable_logind and pamconfdir != 'no',
+ install_dir : pamconfdir)
+
+if enable_logind
+ install_data('org.freedesktop.login1.conf',
+ install_dir : dbuspolicydir)
+ install_data('org.freedesktop.login1.service',
+ install_dir : dbussystemservicedir)
+ install_data('org.freedesktop.login1.policy',
+ install_dir : polkitpolicydir)
+endif
diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf
new file mode 100644
index 0000000..8ba094b
--- /dev/null
+++ b/src/login/org.freedesktop.login1.conf
@@ -0,0 +1,360 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+-->
+
+<busconfig>
+
+ <policy user="root">
+ <allow own="org.freedesktop.login1"/>
+ <allow send_destination="org.freedesktop.login1"/>
+ <allow receive_sender="org.freedesktop.login1"/>
+ </policy>
+
+ <policy context="default">
+ <deny send_destination="org.freedesktop.login1"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.DBus.Introspectable"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.DBus.Peer"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.DBus.Properties"
+ send_member="Get"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.DBus.Properties"
+ send_member="GetAll"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="GetSession"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="GetSessionByPID"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="GetUser"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="GetUserByPID"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="GetSeat"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ListSessions"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ListUsers"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ListSeats"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ListInhibitors"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="Inhibit"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SetUserLinger"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ActivateSession"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ActivateSessionOnSeat"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="LockSession"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="UnlockSession"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="LockSessions"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="UnlockSessions"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="KillSession"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="KillUser"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="TerminateSession"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="TerminateUser"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="TerminateSeat"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="PowerOff"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="PowerOffWithFlags"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="Reboot"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="RebootWithFlags"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="Halt"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="HaltWithFlags"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="Suspend"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SuspendWithFlags"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="Hibernate"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="HibernateWithFlags"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="HybridSleep"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="HybridSleepWithFlags"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SuspendThenHibernate"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SuspendThenHibernateWithFlags"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanPowerOff"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanReboot"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanHalt"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanSuspend"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanHibernate"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanHybridSleep"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanSuspendThenHibernate"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ScheduleShutdown"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CancelScheduledShutdown"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanRebootParameter"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SetRebootParameter"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanRebootToFirmwareSetup"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SetRebootToFirmwareSetup"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanRebootToBootLoaderMenu"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SetRebootToBootLoaderMenu"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanRebootToBootLoaderEntry"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SetRebootToBootLoaderEntry"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SetWallMessage"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="AttachDevice"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="FlushDevices"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Seat"
+ send_member="Terminate"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Seat"
+ send_member="ActivateSession"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Seat"
+ send_member="SwitchTo"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Seat"
+ send_member="SwitchToPrevious"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Seat"
+ send_member="SwitchToNext"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="Terminate"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="Activate"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="Lock"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="Unlock"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="SetIdleHint"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="SetLockedHint"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="Kill"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="TakeControl"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="ReleaseControl"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="SetType"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="TakeDevice"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="ReleaseDevice"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="PauseDeviceComplete"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ 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"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="SetTTY"/>
+
+ <allow receive_sender="org.freedesktop.login1"/>
+ </policy>
+
+</busconfig>
diff --git a/src/login/org.freedesktop.login1.policy b/src/login/org.freedesktop.login1.policy
new file mode 100644
index 0000000..012ee14
--- /dev/null
+++ b/src/login/org.freedesktop.login1.policy
@@ -0,0 +1,415 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+-->
+
+<policyconfig>
+
+ <vendor>The systemd Project</vendor>
+ <vendor_url>https://systemd.io</vendor_url>
+
+ <action id="org.freedesktop.login1.inhibit-block-shutdown">
+ <description gettext-domain="systemd">Allow applications to inhibit system shutdown</description>
+ <message gettext-domain="systemd">Authentication is required for an application to inhibit system shutdown.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.inhibit-delay-shutdown org.freedesktop.login1.inhibit-block-sleep org.freedesktop.login1.inhibit-delay-sleep org.freedesktop.login1.inhibit-block-idle</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-delay-shutdown">
+ <description gettext-domain="systemd">Allow applications to delay system shutdown</description>
+ <message gettext-domain="systemd">Authentication is required for an application to delay system shutdown.</message>
+ <defaults>
+ <allow_any>yes</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.inhibit-delay-sleep</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-block-sleep">
+ <description gettext-domain="systemd">Allow applications to inhibit system sleep</description>
+ <message gettext-domain="systemd">Authentication is required for an application to inhibit system sleep.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.inhibit-delay-sleep org.freedesktop.login1.inhibit-block-idle</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-delay-sleep">
+ <description gettext-domain="systemd">Allow applications to delay system sleep</description>
+ <message gettext-domain="systemd">Authentication is required for an application to delay system sleep.</message>
+ <defaults>
+ <allow_any>yes</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-block-idle">
+ <description gettext-domain="systemd">Allow applications to inhibit automatic system suspend</description>
+ <message gettext-domain="systemd">Authentication is required for an application to inhibit automatic system suspend.</message>
+ <defaults>
+ <allow_any>yes</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-handle-power-key">
+ <description gettext-domain="systemd">Allow applications to inhibit system handling of the power key</description>
+ <message gettext-domain="systemd">Authentication is required for an application to inhibit system handling of the power key.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.inhibit-handle-suspend-key org.freedesktop.login1.inhibit-handle-hibernate-key org.freedesktop.login1.inhibit-handle-lid-switch</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-handle-suspend-key">
+ <description gettext-domain="systemd">Allow applications to inhibit system handling of the suspend key</description>
+ <message gettext-domain="systemd">Authentication is required for an application to inhibit system handling of the suspend key.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.inhibit-handle-hibernate-key org.freedesktop.login1.inhibit-handle-lid-switch</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-handle-hibernate-key">
+ <description gettext-domain="systemd">Allow applications to inhibit system handling of the hibernate key</description>
+ <message gettext-domain="systemd">Authentication is required for an application to inhibit system handling of the hibernate key.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-handle-lid-switch">
+ <description gettext-domain="systemd">Allow applications to inhibit system handling of the lid switch</description>
+ <message gettext-domain="systemd">Authentication is required for an application to inhibit system handling of the lid switch.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.inhibit-handle-reboot-key">
+ <description gettext-domain="systemd">Allow applications to inhibit system handling of the reboot key</description>
+ <message gettext-domain="systemd">Authentication is required for an application to inhibit system handling of the reboot key.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.inhibit-handle-suspend-key org.freedesktop.login1.inhibit-handle-hibernate-key org.freedesktop.login1.inhibit-handle-lid-switch</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.set-self-linger">
+ <description gettext-domain="systemd">Allow non-logged-in user to run programs</description>
+ <message gettext-domain="systemd">Explicit request is required to run programs as a non-logged-in user.</message>
+ <defaults>
+ <allow_any>yes</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.set-user-linger">
+ <description gettext-domain="systemd">Allow non-logged-in users to run programs</description>
+ <message gettext-domain="systemd">Authentication is required to run programs as a non-logged-in user.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.attach-device">
+ <description gettext-domain="systemd">Allow attaching devices to seats</description>
+ <message gettext-domain="systemd">Authentication is required to attach a device to a seat.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.flush-devices</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.flush-devices">
+ <description gettext-domain="systemd">Flush device to seat attachments</description>
+ <message gettext-domain="systemd">Authentication is required to reset how devices are attached to seats.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.power-off">
+ <description gettext-domain="systemd">Power off the system</description>
+ <message gettext-domain="systemd">Authentication is required to power off the system.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.set-wall-message</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.power-off-multiple-sessions">
+ <description gettext-domain="systemd">Power off the system while other users are logged in</description>
+ <message gettext-domain="systemd">Authentication is required to power off the system while other users are logged in.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.power-off</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.power-off-ignore-inhibit">
+ <description gettext-domain="systemd">Power off the system while an application is inhibiting this</description>
+ <message gettext-domain="systemd">Authentication is required to power off the system while an application is inhibiting this.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.power-off</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.reboot">
+ <description gettext-domain="systemd">Reboot the system</description>
+ <message gettext-domain="systemd">Authentication is required to reboot the system.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.set-wall-message</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.reboot-multiple-sessions">
+ <description gettext-domain="systemd">Reboot the system while other users are logged in</description>
+ <message gettext-domain="systemd">Authentication is required to reboot the system while other users are logged in.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.reboot</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.reboot-ignore-inhibit">
+ <description gettext-domain="systemd">Reboot the system while an application is inhibiting this</description>
+ <message gettext-domain="systemd">Authentication is required to reboot the system while an application is inhibiting this.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.reboot</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.halt">
+ <description gettext-domain="systemd">Halt the system</description>
+ <message gettext-domain="systemd">Authentication is required to halt the system.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.set-wall-message</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.halt-multiple-sessions">
+ <description gettext-domain="systemd">Halt the system while other users are logged in</description>
+ <message gettext-domain="systemd">Authentication is required to halt the system while other users are logged in.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.halt</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.halt-ignore-inhibit">
+ <description gettext-domain="systemd">Halt the system while an application is inhibiting this</description>
+ <message gettext-domain="systemd">Authentication is required to halt the system while an application is inhibiting this.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.halt</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.suspend">
+ <description gettext-domain="systemd">Suspend the system</description>
+ <message gettext-domain="systemd">Authentication is required to suspend the system.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.suspend-multiple-sessions">
+ <description gettext-domain="systemd">Suspend the system while other users are logged in</description>
+ <message gettext-domain="systemd">Authentication is required to suspend the system while other users are logged in.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.suspend</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.suspend-ignore-inhibit">
+ <description gettext-domain="systemd">Suspend the system while an application is inhibiting this</description>
+ <message gettext-domain="systemd">Authentication is required to suspend the system while an application is inhibiting this.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.suspend</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.hibernate">
+ <description gettext-domain="systemd">Hibernate the system</description>
+ <message gettext-domain="systemd">Authentication is required to hibernate the system.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.hibernate-multiple-sessions">
+ <description gettext-domain="systemd">Hibernate the system while other users are logged in</description>
+ <message gettext-domain="systemd">Authentication is required to hibernate the system while other users are logged in.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.hibernate</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.hibernate-ignore-inhibit">
+ <description gettext-domain="systemd">Hibernate the system while an application is inhibiting this</description>
+ <message gettext-domain="systemd">Authentication is required to hibernate the system while an application is inhibiting this.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.hibernate</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.manage">
+ <description gettext-domain="systemd">Manage active sessions, users and seats</description>
+ <message gettext-domain="systemd">Authentication is required to manage active sessions, users and seats.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.lock-sessions">
+ <description gettext-domain="systemd">Lock or unlock active sessions</description>
+ <message gettext-domain="systemd">Authentication is required to lock or unlock active sessions.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.set-reboot-parameter">
+ <description gettext-domain="systemd">Set the reboot "reason" in the kernel</description>
+ <message gettext-domain="systemd">Authentication is required to set the reboot "reason" in the kernel.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.reboot</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.set-reboot-to-firmware-setup">
+ <description gettext-domain="systemd">Indicate to the firmware to boot to setup interface</description>
+ <message gettext-domain="systemd">Authentication is required to indicate to the firmware to boot to setup interface.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.reboot</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.set-reboot-to-boot-loader-menu">
+ <description gettext-domain="systemd">Indicate to the boot loader to boot to the boot loader menu</description>
+ <message gettext-domain="systemd">Authentication is required to indicate to the boot loader to boot to the boot loader menu.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.reboot</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.set-reboot-to-boot-loader-entry">
+ <description gettext-domain="systemd">Indicate to the boot loader to boot a specific entry</description>
+ <message gettext-domain="systemd">Authentication is required to indicate to the boot loader to boot into a specific boot loader entry.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.reboot</annotate>
+ </action>
+
+ <action id="org.freedesktop.login1.set-wall-message">
+ <description gettext-domain="systemd">Set a wall message</description>
+ <message gettext-domain="systemd">Authentication is required to set a wall message</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.login1.chvt">
+ <description gettext-domain="systemd">Change Session</description>
+ <message gettext-domain="systemd">Authentication is required to change the virtual terminal.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+</policyconfig>
diff --git a/src/login/org.freedesktop.login1.service b/src/login/org.freedesktop.login1.service
new file mode 100644
index 0000000..6d443cf
--- /dev/null
+++ b/src/login/org.freedesktop.login1.service
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[D-BUS Service]
+Name=org.freedesktop.login1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.freedesktop.login1.service
diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
new file mode 100644
index 0000000..bf45974
--- /dev/null
+++ b/src/login/pam_systemd.c
@@ -0,0 +1,1266 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <security/_pam_macros.h>
+#include <security/pam_ext.h>
+#include <security/pam_misc.h>
+#include <security/pam_modules.h>
+#include <security/pam_modutil.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "audit-util.h"
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-internal.h"
+#include "bus-locator.h"
+#include "cap-list.h"
+#include "capability-util.h"
+#include "cgroup-setup.h"
+#include "devnum-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "hostname-util.h"
+#include "locale-util.h"
+#include "login-util.h"
+#include "macro.h"
+#include "missing_syscall.h"
+#include "pam-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "percent-util.h"
+#include "process-util.h"
+#include "rlimit-util.h"
+#include "socket-util.h"
+#include "stdio-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "user-util.h"
+#include "userdb.h"
+
+#define LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
+
+static int parse_caps(
+ pam_handle_t *handle,
+ const char *value,
+ uint64_t *caps) {
+
+ bool subtract;
+ int r;
+
+ assert(handle);
+ assert(value);
+
+ if (value[0] == '~') {
+ subtract = true;
+ value++;
+ } else
+ subtract = false;
+
+ for (;;) {
+ _cleanup_free_ char *s = NULL;
+ uint64_t b, m;
+ int c;
+
+ /* We can't use spaces as separators here, as PAM's simplistic argument parser doesn't allow
+ * spaces inside of arguments. We use commas instead (which is similar to cap_from_text(),
+ * which also uses commas). */
+ r = extract_first_word(&value, &s, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ c = capability_from_name(s);
+ if (c < 0) {
+ pam_syslog(handle, LOG_WARNING, "Unknown capability, ignoring: %s", s);
+ continue;
+ }
+
+ m = UINT64_C(1) << c;
+
+ if (!caps)
+ continue;
+
+ if (*caps == UINT64_MAX)
+ b = subtract ? all_capabilities() : 0;
+ else
+ b = *caps;
+
+ if (subtract)
+ *caps = b & ~m;
+ else
+ *caps = b | m;
+ }
+
+ return 0;
+}
+
+static int parse_argv(
+ pam_handle_t *handle,
+ int argc, const char **argv,
+ const char **class,
+ const char **type,
+ const char **desktop,
+ bool *debug,
+ uint64_t *default_capability_bounding_set,
+ uint64_t *default_capability_ambient_set) {
+
+ int r;
+
+ assert(handle);
+ assert(argc >= 0);
+ assert(argc == 0 || argv);
+
+ for (int i = 0; i < argc; i++) {
+ const char *p;
+
+ if ((p = startswith(argv[i], "class="))) {
+ if (class)
+ *class = p;
+
+ } else if ((p = startswith(argv[i], "type="))) {
+ if (type)
+ *type = p;
+
+ } else if ((p = startswith(argv[i], "desktop="))) {
+ if (desktop)
+ *desktop = p;
+
+ } else if (streq(argv[i], "debug")) {
+ if (debug)
+ *debug = true;
+
+ } else if ((p = startswith(argv[i], "debug="))) {
+ r = parse_boolean(p);
+ if (r < 0)
+ pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p);
+ else if (debug)
+ *debug = r;
+
+ } else if ((p = startswith(argv[i], "default-capability-bounding-set="))) {
+ r = parse_caps(handle, p, default_capability_bounding_set);
+ if (r < 0)
+ pam_syslog(handle, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p);
+
+ } else if ((p = startswith(argv[i], "default-capability-ambient-set="))) {
+ r = parse_caps(handle, p, default_capability_ambient_set);
+ if (r < 0)
+ pam_syslog(handle, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p);
+
+ } else
+ pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]);
+ }
+
+ return 0;
+}
+
+static int acquire_user_record(
+ pam_handle_t *handle,
+ UserRecord **ret_record) {
+
+ _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+ const char *username = NULL, *json = NULL;
+ _cleanup_free_ char *field = NULL;
+ int r;
+
+ assert(handle);
+
+ r = pam_get_user(handle, &username, NULL);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@");
+
+ if (isempty(username))
+ return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not valid.");
+
+ /* If pam_systemd_homed (or some other module) already acquired the user record we can reuse it
+ * here. */
+ field = strjoin("systemd-user-record-", username);
+ if (!field)
+ return pam_log_oom(handle);
+
+ r = pam_get_data(handle, field, (const void**) &json);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@");
+ if (r == PAM_SUCCESS && json) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+ /* Parse cached record */
+ r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
+ if (r < 0)
+ return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse JSON user record: %m");
+
+ ur = user_record_new();
+ if (!ur)
+ return pam_log_oom(handle);
+
+ r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
+ if (r < 0)
+ return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m");
+
+ /* Safety check if cached record actually matches what we are looking for */
+ if (!streq_ptr(username, ur->user_name))
+ return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR,
+ "Acquired user record does not match user name.");
+ } else {
+ _cleanup_free_ char *formatted = NULL;
+
+ /* Request the record ourselves */
+ r = userdb_by_name(username, 0, &ur);
+ if (r < 0) {
+ pam_syslog_errno(handle, LOG_ERR, r, "Failed to get user record: %m");
+ return PAM_USER_UNKNOWN;
+ }
+
+ r = json_variant_format(ur->json, 0, &formatted);
+ if (r < 0)
+ return pam_syslog_errno(handle, LOG_ERR, r, "Failed to format user JSON: %m");
+
+ /* And cache it for everyone else */
+ r = pam_set_data(handle, field, formatted, pam_cleanup_free);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r,
+ "Failed to set PAM user record data '%s': @PAMERR@", field);
+ TAKE_PTR(formatted);
+ }
+
+ if (!uid_is_valid(ur->uid))
+ return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR,
+ "Acquired user record does not have a UID.");
+
+ if (ret_record)
+ *ret_record = TAKE_PTR(ur);
+
+ return PAM_SUCCESS;
+}
+
+static bool display_is_local(const char *display) {
+ assert(display);
+
+ return
+ display[0] == ':' &&
+ ascii_isdigit(display[1]);
+}
+
+static int socket_from_display(const char *display) {
+ _cleanup_free_ char *f = NULL;
+ size_t k;
+ char *c;
+ union sockaddr_union sa;
+ socklen_t sa_len;
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ assert(display);
+
+ if (!display_is_local(display))
+ return -EINVAL;
+
+ k = strspn(display+1, "0123456789");
+
+ /* Try abstract socket first. */
+ f = new(char, STRLEN("@/tmp/.X11-unix/X") + k + 1);
+ if (!f)
+ return -ENOMEM;
+
+ c = stpcpy(f, "@/tmp/.X11-unix/X");
+ memcpy(c, display+1, k);
+ c[k] = 0;
+
+ r = sockaddr_un_set_path(&sa.un, f);
+ if (r < 0)
+ return r;
+ sa_len = r;
+
+ fd = RET_NERRNO(socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0));
+ if (fd < 0)
+ return fd;
+
+ r = RET_NERRNO(connect(fd, &sa.sa, sa_len));
+ if (r >= 0)
+ return TAKE_FD(fd);
+ if (r != -ECONNREFUSED)
+ return r;
+
+ /* Try also non-abstract socket. */
+ r = sockaddr_un_set_path(&sa.un, f + 1);
+ if (r < 0)
+ return r;
+ sa_len = r;
+
+ r = RET_NERRNO(connect(fd, &sa.sa, sa_len));
+ if (r >= 0)
+ return TAKE_FD(fd);
+ return r;
+}
+
+static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
+ _cleanup_free_ char *sys_path = NULL, *tty = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ struct ucred ucred;
+ int v, r;
+ dev_t display_ctty;
+
+ assert(display);
+ assert(vtnr);
+
+ /* We deduce the X11 socket from the display name, then use
+ * SO_PEERCRED to determine the X11 server process, ask for
+ * the controlling tty of that and if it's a VC then we know
+ * the seat and the virtual terminal. Sounds ugly, is only
+ * semi-ugly. */
+
+ fd = socket_from_display(display);
+ if (fd < 0)
+ return fd;
+
+ r = getpeercred(fd, &ucred);
+ if (r < 0)
+ return r;
+
+ r = get_ctty_devnr(ucred.pid, &display_ctty);
+ if (r < 0)
+ return r;
+
+ if (asprintf(&sys_path, "/sys/dev/char/" DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(display_ctty)) < 0)
+ return -ENOMEM;
+ r = readlink_value(sys_path, &tty);
+ if (r < 0)
+ return r;
+
+ v = vtnr_from_tty(tty);
+ if (v < 0)
+ return v;
+ else if (v == 0)
+ return -ENOENT;
+
+ if (seat)
+ *seat = "seat0";
+ *vtnr = (uint32_t) v;
+
+ return 0;
+}
+
+static int export_legacy_dbus_address(
+ pam_handle_t *handle,
+ const char *runtime) {
+
+ const char *s;
+ _cleanup_free_ char *t = NULL;
+ int r = PAM_BUF_ERR;
+
+ /* We need to export $DBUS_SESSION_BUS_ADDRESS because various applications will not connect
+ * correctly to the bus without it. This setting matches what dbus.socket does for the user
+ * session using 'systemctl --user set-environment'. We want to have the same configuration
+ * in processes started from the PAM session.
+ *
+ * The setting of the address is guarded by the access() check because it is also possible to compile
+ * dbus without --enable-user-session, in which case this socket is not used, and
+ * $DBUS_SESSION_BUS_ADDRESS should not be set. An alternative approach would to not do the access()
+ * check here, and let applications try on their own, by using "unix:path=%s/bus;autolaunch:". But we
+ * expect the socket to be present by the time we do this check, so we can just as well check once
+ * here. */
+
+ s = strjoina(runtime, "/bus");
+ if (access(s, F_OK) < 0)
+ return PAM_SUCCESS;
+
+ if (asprintf(&t, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
+ return pam_log_oom(handle);
+
+ r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, 0);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set bus variable: @PAMERR@");
+
+ return PAM_SUCCESS;
+}
+
+static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
+ uint64_t val;
+ int r;
+
+ if (isempty(limit))
+ return PAM_SUCCESS;
+
+ if (streq(limit, "infinity")) {
+ r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", UINT64_MAX);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+
+ return PAM_SUCCESS;
+ }
+
+ r = parse_permyriad(limit);
+ if (r >= 0) {
+ r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", UINT32_SCALE_FROM_PERMYRIAD(r));
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+
+ return PAM_SUCCESS;
+ }
+
+ r = parse_size(limit, 1024, &val);
+ if (r >= 0) {
+ r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+
+ return PAM_SUCCESS;
+ }
+
+ pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit);
+ return PAM_SUCCESS;
+}
+
+static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
+ usec_t val;
+ int r;
+
+ /* No need to parse "infinity" here, it will be set by default later in scope_init() */
+ if (isempty(limit) || streq(limit, "infinity"))
+ return PAM_SUCCESS;
+
+ r = parse_sec(limit, &val);
+ if (r >= 0) {
+ r = sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+ } else
+ pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit);
+
+ return PAM_SUCCESS;
+}
+
+static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
+ uint64_t val;
+ int r;
+
+ /* No need to parse "infinity" here, it will be set unconditionally later in manager_start_scope() */
+ if (isempty(limit) || streq(limit, "infinity"))
+ return PAM_SUCCESS;
+
+ r = safe_atou64(limit, &val);
+ if (r >= 0) {
+ r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", val);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+ } else
+ pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit);
+
+ return PAM_SUCCESS;
+}
+
+static int append_session_cpu_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
+ uint64_t val;
+ int r;
+
+ if (isempty(limit))
+ return PAM_SUCCESS;
+
+ r = cg_cpu_weight_parse(limit, &val);
+ if (r < 0)
+ pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit);
+ else {
+ r = sd_bus_message_append(m, "(sv)", "CPUWeight", "t", val);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int append_session_io_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
+ uint64_t val;
+ int r;
+
+ if (isempty(limit))
+ return PAM_SUCCESS;
+
+ r = cg_weight_parse(limit, &val);
+ if (r < 0)
+ pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit);
+ else {
+ r = sd_bus_message_append(m, "(sv)", "IOWeight", "t", val);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+ }
+
+ return PAM_SUCCESS;
+}
+
+static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) {
+ const char *v;
+
+ assert(handle);
+ assert(key);
+
+ /* Looks for an environment variable, preferably in the environment block associated with the
+ * specified PAM handle, falling back to the process' block instead. Why check both? Because we want
+ * to permit configuration of session properties from unit files that invoke PAM services, so that
+ * PAM services don't have to be reworked to set systemd-specific properties, but these properties
+ * can still be set from the unit file Environment= block. */
+
+ v = pam_getenv(handle, key);
+ if (!isempty(v))
+ return v;
+
+ /* We use secure_getenv() here, since we might get loaded into su/sudo, which are SUID. Ideally
+ * they'd clean up the environment before invoking foreign code (such as PAM modules), but alas they
+ * currently don't (to be precise, they clean up the environment they pass to their children, but
+ * not their own environ[]). */
+ v = secure_getenv(key);
+ if (!isempty(v))
+ return v;
+
+ return fallback;
+}
+
+static int update_environment(pam_handle_t *handle, const char *key, const char *value) {
+ int r;
+
+ assert(handle);
+ assert(key);
+
+ /* Updates the environment, but only if there's actually a value set. Also, log about errors */
+
+ if (isempty(value))
+ return PAM_SUCCESS;
+
+ r = pam_misc_setenv(handle, key, value, 0);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r,
+ "Failed to set environment variable %s: @PAMERR@", key);
+
+ return PAM_SUCCESS;
+}
+
+static bool validate_runtime_directory(pam_handle_t *handle, const char *path, uid_t uid) {
+ struct stat st;
+
+ assert(handle);
+ assert(path);
+
+ /* Some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually
+ * set up properly for us. This is supposed to provide a careful safety net for supporting su/sudo
+ * type transitions: in that case the UID changes, but the session and thus the user owning it
+ * doesn't change. Since the $XDG_RUNTIME_DIR lifecycle is bound to the session's user being logged
+ * in at least once we should be particularly careful when setting the environment variable, since
+ * otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */
+
+ if (!path_is_absolute(path)) {
+ pam_syslog(handle, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path);
+ goto fail;
+ }
+
+ if (lstat(path, &st) < 0) {
+ pam_syslog_errno(handle, LOG_ERR, errno, "Failed to stat() runtime directory '%s': %m", path);
+ goto fail;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path);
+ goto fail;
+ }
+
+ if (st.st_uid != uid) {
+ pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid);
+ goto fail;
+ }
+
+ return true;
+
+fail:
+ pam_syslog(handle, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order.");
+ return false;
+}
+
+static int pam_putenv_and_log(pam_handle_t *handle, const char *e, bool debug) {
+ int r;
+
+ assert(handle);
+ assert(e);
+
+ r = pam_putenv(handle, e);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r,
+ "Failed to set PAM environment variable %s: @PAMERR@", e);
+
+ pam_debug_syslog(handle, debug, "PAM environment variable %s set based on user record.", e);
+
+ return PAM_SUCCESS;
+}
+
+static int apply_user_record_settings(
+ pam_handle_t *handle,
+ UserRecord *ur,
+ bool debug,
+ uint64_t default_capability_bounding_set,
+ uint64_t default_capability_ambient_set) {
+ int r;
+
+ assert(handle);
+ assert(ur);
+
+ if (ur->umask != MODE_INVALID) {
+ umask(ur->umask);
+ pam_debug_syslog(handle, debug, "Set user umask to %04o based on user record.", ur->umask);
+ }
+
+ 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;
+
+ 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;
+ }
+ }
+
+ 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))
+ pam_debug_syslog(handle, debug,
+ "Time zone specified in user record is not valid locally, not setting $TZ.");
+ else {
+ _cleanup_free_ char *joined = NULL;
+
+ joined = strjoin("TZ=:", ur->time_zone);
+ if (!joined)
+ return pam_log_oom(handle);
+
+ r = pam_putenv_and_log(handle, joined, debug);
+ if (r != PAM_SUCCESS)
+ return r;
+ }
+ }
+
+ 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;
+
+ joined = strjoin("LANG=", ur->preferred_language);
+ if (!joined)
+ return pam_log_oom(handle);
+
+ r = pam_putenv_and_log(handle, joined, debug);
+ if (r != PAM_SUCCESS)
+ return r;
+ }
+ }
+
+ if (nice_is_valid(ur->nice_level)) {
+ if (nice(ur->nice_level) < 0)
+ pam_syslog_errno(handle, LOG_ERR, errno,
+ "Failed to set nice level to %i, ignoring: %m", ur->nice_level);
+ else
+ pam_debug_syslog(handle, debug,
+ "Nice level set to %i, based on user record.", ur->nice_level);
+ }
+
+ for (int rl = 0; rl < _RLIMIT_MAX; rl++) {
+
+ if (!ur->rlimits[rl])
+ continue;
+
+ r = setrlimit_closest(rl, ur->rlimits[rl]);
+ if (r < 0)
+ pam_syslog_errno(handle, LOG_ERR, r,
+ "Failed to set resource limit %s, ignoring: %m", rlimit_to_string(rl));
+ else
+ pam_debug_syslog(handle, debug,
+ "Resource limit %s set, based on user record.", rlimit_to_string(rl));
+ }
+
+ uint64_t a, b;
+ a = user_record_capability_ambient_set(ur);
+ if (a == UINT64_MAX)
+ a = default_capability_ambient_set;
+
+ b = user_record_capability_bounding_set(ur);
+ if (b == UINT64_MAX)
+ b = default_capability_bounding_set;
+
+ if (a != UINT64_MAX && a != 0) {
+ a &= b;
+
+ r = capability_ambient_set_apply(a, /* also_inherit= */ true);
+ if (r < 0)
+ pam_syslog_errno(handle, LOG_ERR, r,
+ "Failed to set ambient capabilities, ignoring: %m");
+ }
+
+ if (b != UINT64_MAX && !cap_test_all(b)) {
+ r = capability_bounding_set_drop(b, /* right_now= */ false);
+ if (r < 0)
+ pam_syslog_errno(handle, LOG_ERR, r,
+ "Failed to set bounding capabilities, ignoring: %m");
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int configure_runtime_directory(
+ pam_handle_t *handle,
+ UserRecord *ur,
+ const char *rt) {
+
+ int r;
+
+ assert(handle);
+ assert(ur);
+ assert(rt);
+
+ if (!validate_runtime_directory(handle, rt, ur->uid))
+ return PAM_SUCCESS;
+
+ r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set runtime dir: @PAMERR@");
+
+ return export_legacy_dbus_address(handle, rt);
+}
+
+static uint64_t pick_default_capability_ambient_set(
+ UserRecord *ur,
+ const char *service,
+ const char *seat) {
+
+ /* If not configured otherwise, let's enable CAP_WAKE_ALARM for regular users when logging in on a
+ * seat (i.e. when they are present physically on the device), or when invoked for the systemd --user
+ * instances. This allows desktops to install CAP_WAKE_ALARM to implement alarm clock apps without
+ * much fuss. */
+
+ return ur &&
+ user_record_disposition(ur) == USER_REGULAR &&
+ (streq_ptr(service, "systemd-user") || !isempty(seat)) ? (UINT64_C(1) << CAP_WAKE_ALARM) : UINT64_MAX;
+}
+
+typedef struct SessionContext {
+ const uid_t uid;
+ const pid_t pid;
+ const char *service;
+ const char *type;
+ const char *class;
+ const char *desktop;
+ const char *seat;
+ const uint32_t vtnr;
+ const char *tty;
+ const char *display;
+ const bool remote;
+ const char *remote_user;
+ const char *remote_host;
+ const char *memory_max;
+ const char *tasks_max;
+ const char *cpu_weight;
+ const char *io_weight;
+ const char *runtime_max_sec;
+} SessionContext;
+
+static int create_session_message(
+ sd_bus *bus,
+ pam_handle_t *handle,
+ const SessionContext *context,
+ bool avoid_pidfd,
+ sd_bus_message **ret) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_close_ int pidfd = -EBADF;
+ int r;
+
+ assert(bus);
+ assert(handle);
+ assert(context);
+ assert(ret);
+
+ if (!avoid_pidfd) {
+ pidfd = pidfd_open(getpid_cached(), 0);
+ if (pidfd < 0 && !ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
+ return -errno;
+ }
+
+ r = bus_message_new_method_call(bus, &m, bus_login_mgr, pidfd >= 0 ? "CreateSessionWithPIDFD" : "CreateSession");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m,
+ pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss",
+ (uint32_t) context->uid,
+ pidfd >= 0 ? pidfd : context->pid,
+ context->service,
+ context->type,
+ context->class,
+ context->desktop,
+ context->seat,
+ context->vtnr,
+ context->tty,
+ context->display,
+ context->remote,
+ context->remote_user,
+ context->remote_host);
+ if (r < 0)
+ return r;
+
+ if (pidfd >= 0) {
+ r = sd_bus_message_append(m, "t", UINT64_C(0));
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_open_container(m, 'a', "(sv)");
+ if (r < 0)
+ return r;
+
+ r = append_session_memory_max(handle, m, context->memory_max);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = append_session_runtime_max_sec(handle, m, context->runtime_max_sec);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = append_session_tasks_max(handle, m, context->tasks_max);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = append_session_cpu_weight(handle, m, context->cpu_weight);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = append_session_io_weight(handle, m, context->io_weight);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(m);
+ return 0;
+}
+
+_public_ PAM_EXTERN int pam_sm_open_session(
+ pam_handle_t *handle,
+ int flags,
+ int argc, const char **argv) {
+
+ /* Let's release the D-Bus connection once this function exits, after all the session might live
+ * quite a long time, and we are not going to process the bus connection in that time, so let's
+ * better close before the daemon kicks us off because we are not processing anything. */
+ _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ const char
+ *id, *object_path, *runtime_path,
+ *service = NULL,
+ *tty = NULL, *display = NULL,
+ *remote_user = NULL, *remote_host = NULL,
+ *seat = NULL,
+ *type = NULL, *class = NULL,
+ *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL,
+ *memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL;
+ uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX;
+ _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;
+ uint32_t vtnr = 0;
+ uid_t original_uid;
+
+ assert(handle);
+
+ if (parse_argv(handle,
+ argc, argv,
+ &class_pam,
+ &type_pam,
+ &desktop_pam,
+ &debug,
+ &default_capability_bounding_set,
+ &default_capability_ambient_set) < 0)
+ return PAM_SESSION_ERR;
+
+ pam_debug_syslog(handle, debug, "pam-systemd initializing");
+
+ r = acquire_user_record(handle, &ur);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ /* Make most of this a NOP on non-logind systems */
+ 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@");
+
+ 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);
+
+ tty = strempty(tty);
+
+ if (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")) {
+ /* 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
+ * off processes.) */
+ type = "unspecified";
+ class = "background";
+ tty = NULL;
+
+ } else if (streq(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";
+ 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
+ * does the PAM session registration early for new connections, and registers a pty only
+ * 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
+ /* Chop off leading /dev prefix that some clients specify, but others do not. */
+ tty = skip_dev_prefix(tty);
+
+ /* If this fails vtnr will be 0, that's intended */
+ if (!isempty(cvtnr))
+ (void) safe_atou32(cvtnr, &vtnr);
+
+ if (!isempty(display) && !vtnr) {
+ if (isempty(seat))
+ (void) get_seat_from_display(display, &seat, &vtnr);
+ else if (streq(seat, "seat0"))
+ (void) get_seat_from_display(display, NULL, &vtnr);
+ }
+
+ if (seat && !streq(seat, "seat0") && vtnr != 0) {
+ pam_debug_syslog(handle, debug, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
+ vtnr = 0;
+ }
+
+ if (isempty(type))
+ type = !isempty(display) ? "x11" :
+ !isempty(tty) ? "tty" : "unspecified";
+
+ if (isempty(class))
+ class = streq(type, "unspecified") ? "background" : "user";
+
+ remote = !isempty(remote_host) && !is_localhost(remote_host);
+
+ r = pam_get_data(handle, "systemd.memory_max", (const void **)&memory_max);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.memory_max data: @PAMERR@");
+ r = pam_get_data(handle, "systemd.tasks_max", (const void **)&tasks_max);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.tasks_max data: @PAMERR@");
+ r = pam_get_data(handle, "systemd.cpu_weight", (const void **)&cpu_weight);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.cpu_weight data: @PAMERR@");
+ r = pam_get_data(handle, "systemd.io_weight", (const void **)&io_weight);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.io_weight data: @PAMERR@");
+ r = pam_get_data(handle, "systemd.runtime_max_sec", (const void **)&runtime_max_sec);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.runtime_max_sec data: @PAMERR@");
+
+ /* Talk to logind over the message bus */
+ r = pam_acquire_bus_connection(handle, "pam-systemd", &bus, &d);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ pam_debug_syslog(handle, debug,
+ "Asking logind to create session: "
+ "uid="UID_FMT" pid="PID_FMT" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32" tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
+ ur->uid, getpid_cached(),
+ strempty(service),
+ type, class, strempty(desktop),
+ strempty(seat), vtnr, strempty(tty), strempty(display),
+ yes_no(remote), strempty(remote_user), strempty(remote_host));
+ pam_debug_syslog(handle, debug,
+ "Session limits: "
+ "memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s",
+ strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec));
+
+ const SessionContext context = {
+ .uid = ur->uid,
+ .pid = 0,
+ .service = service,
+ .type = type,
+ .class = class,
+ .desktop = desktop,
+ .seat = seat,
+ .vtnr = vtnr,
+ .tty = tty,
+ .display = display,
+ .remote = remote,
+ .remote_user = remote_user,
+ .remote_host = remote_host,
+ .memory_max = memory_max,
+ .tasks_max = tasks_max,
+ .cpu_weight = cpu_weight,
+ .io_weight = io_weight,
+ .runtime_max_sec = runtime_max_sec,
+ };
+
+ r = create_session_message(bus,
+ handle,
+ &context,
+ /* avoid_pidfd = */ false,
+ &m);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+
+ r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+ if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+ sd_bus_error_free(&error);
+ pam_debug_syslog(handle, debug,
+ "CreateSessionWithPIDFD() API is not available, retrying with CreateSession().");
+
+ m = sd_bus_message_unref(m);
+ r = create_session_message(bus,
+ handle,
+ &context,
+ /* avoid_pidfd = */ true,
+ &m);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+
+ r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+ }
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
+ /* We are already in a session, don't do anything */
+ pam_debug_syslog(handle, debug,
+ "Not creating session: %s", bus_error_message(&error, r));
+ goto success;
+ }
+
+ pam_syslog(handle, LOG_ERR,
+ "Failed to create session: %s", bus_error_message(&error, r));
+ return PAM_SESSION_ERR;
+ }
+
+ r = sd_bus_message_read(reply,
+ "soshusub",
+ &id,
+ &object_path,
+ &runtime_path,
+ &session_fd,
+ &original_uid,
+ &seat,
+ &vtnr,
+ &existing);
+ if (r < 0)
+ return pam_bus_log_parse_error(handle, r);
+
+ pam_debug_syslog(handle, debug,
+ "Reply from logind: "
+ "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);
+
+ r = update_environment(handle, "XDG_SESSION_ID", id);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ if (original_uid == ur->uid) {
+ /* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the
+ * original user of the session. We do this in order not to result in privileged apps
+ * clobbering the runtime directory unnecessarily. */
+
+ r = configure_runtime_directory(handle, ur, runtime_path);
+ if (r != PAM_SUCCESS)
+ return r;
+ }
+
+ /* Most likely we got the session/type/class from environment variables, but might have gotten the data
+ * somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this
+ * data is inherited into the session processes, and programs can rely on them to be initialized. */
+
+ r = update_environment(handle, "XDG_SESSION_TYPE", type);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = update_environment(handle, "XDG_SESSION_CLASS", class);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = update_environment(handle, "XDG_SESSION_DESKTOP", desktop);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = update_environment(handle, "XDG_SEAT", seat);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ if (vtnr > 0) {
+ char buf[DECIMAL_STR_MAX(vtnr)];
+ sprintf(buf, "%u", vtnr);
+
+ r = update_environment(handle, "XDG_VTNR", buf);
+ if (r != PAM_SUCCESS)
+ return r;
+ }
+
+ r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install existing flag: @PAMERR@");
+
+ if (session_fd >= 0) {
+ _cleanup_close_ int fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0)
+ return pam_syslog_errno(handle, LOG_ERR, errno, "Failed to dup session fd: %m");
+
+ r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(fd), NULL);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install session fd: @PAMERR@");
+ TAKE_FD(fd);
+ }
+
+success:
+ if (default_capability_ambient_set == UINT64_MAX)
+ default_capability_ambient_set = pick_default_capability_ambient_set(ur, service, seat);
+
+ return apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
+}
+
+_public_ PAM_EXTERN int pam_sm_close_session(
+ pam_handle_t *handle,
+ int flags,
+ int argc, const char **argv) {
+
+ const void *existing = NULL;
+ bool debug = false;
+ const char *id;
+ int r;
+
+ assert(handle);
+
+ if (parse_argv(handle,
+ argc, argv,
+ NULL,
+ NULL,
+ NULL,
+ &debug,
+ NULL,
+ NULL) < 0)
+ return PAM_SESSION_ERR;
+
+ pam_debug_syslog(handle, debug, "pam-systemd shutting down");
+
+ /* Only release session if it wasn't pre-existing when we
+ * tried to create it */
+ r = pam_get_data(handle, "systemd.existing", &existing);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r,
+ "Failed to get PAM systemd.existing data: @PAMERR@");
+
+ id = pam_getenv(handle, "XDG_SESSION_ID");
+ if (id && !existing) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+
+ /* Before we go and close the FIFO we need to tell logind that this is a clean session
+ * shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */
+
+ r = pam_acquire_bus_connection(handle, "pam-systemd", &bus, NULL);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = bus_call_method(bus, bus_login_mgr, "ReleaseSession", &error, NULL, "s", id);
+ if (r < 0)
+ return pam_syslog_pam_error(handle, LOG_ERR, PAM_SESSION_ERR,
+ "Failed to release session: %s", bus_error_message(&error, r));
+ }
+
+ /* Note that we are knowingly leaking the FIFO fd here. This way, logind can watch us die. If we
+ * closed it here it would not have any clue when that is completed. Given that one cannot really
+ * have multiple PAM sessions open from the same process this means we will leak one FD at max. */
+
+ return PAM_SUCCESS;
+}
diff --git a/src/login/pam_systemd.sym b/src/login/pam_systemd.sym
new file mode 100644
index 0000000..130cf6a
--- /dev/null
+++ b/src/login/pam_systemd.sym
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+{
+global:
+ pam_sm_close_session;
+ pam_sm_open_session;
+local: *;
+};
diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c
new file mode 100644
index 0000000..3b4e911
--- /dev/null
+++ b/src/login/pam_systemd_loadkey.c
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <security/_pam_macros.h>
+#include <security/pam_ext.h>
+#include <security/pam_misc.h>
+#include <security/pam_modules.h>
+#include <security/pam_modutil.h>
+
+#include "keyring-util.h"
+#include "macro.h"
+#include "missing_syscall.h"
+#include "nulstr-util.h"
+#include "pam-util.h"
+#include "strv.h"
+
+/* By default, this module retrieves the key stored by systemd-cryptsetup.
+ * This can be overridden by the keyname= parameter. */
+static const char DEFAULT_KEYNAME[] = "cryptsetup";
+
+_public_ int pam_sm_authenticate(
+ pam_handle_t *handle,
+ int flags,
+ int argc, const char **argv) {
+
+ assert(handle);
+
+ /* Parse argv. */
+
+ assert(argc >= 0);
+ assert(argc == 0 || argv);
+
+ const char *keyname = DEFAULT_KEYNAME;
+ bool debug = false;
+
+ for (int i = 0; i < argc; i++) {
+ const char *p;
+
+ if ((p = startswith(argv[i], "keyname=")))
+ keyname = p;
+ else if (streq(argv[i], "debug"))
+ debug = true;
+ else
+ pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]);
+ }
+
+ pam_debug_syslog(handle, debug, "pam-systemd-loadkey initializing");
+
+ /* Retrieve the key. */
+
+ key_serial_t serial;
+ serial = request_key("user", keyname, NULL, 0);
+ if (serial < 0) {
+ if (errno == ENOKEY) {
+ pam_debug_syslog(handle, debug, "Key not found: %s", keyname);
+ return PAM_AUTHINFO_UNAVAIL;
+ } else if (errno == EKEYEXPIRED) {
+ pam_debug_syslog(handle, debug, "Key expired: %s", keyname);
+ return PAM_AUTHINFO_UNAVAIL;
+ } else
+ return pam_syslog_errno(handle, LOG_ERR, errno, "Failed to look up the key: %m");
+ }
+
+ _cleanup_(erase_and_freep) void *p = NULL;
+ size_t n;
+ int r;
+
+ r = keyring_read(serial, &p, &n);
+ if (r < 0)
+ return pam_syslog_errno(handle, LOG_ERR, r, "Failed to read the key: %m");
+
+ /* Split the key by NUL. Set the last item as authtok. */
+
+ _cleanup_(strv_free_erasep) char **passwords = strv_parse_nulstr(p, n);
+ if (!passwords)
+ return pam_log_oom(handle);
+
+ size_t passwords_len = strv_length(passwords);
+ if (passwords_len == 0) {
+ pam_debug_syslog(handle, debug, "Key is empty");
+ return PAM_AUTHINFO_UNAVAIL;
+ } else if (passwords_len > 1)
+ pam_debug_syslog(handle, debug, "Multiple passwords found in the key. Using the last one");
+
+ r = pam_set_item(handle, PAM_AUTHTOK, passwords[passwords_len - 1]);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@");
+
+ return PAM_SUCCESS;
+}
+
+_public_ int pam_sm_setcred(
+ pam_handle_t *handle,
+ int flags,
+ int argc, const char **argv) {
+
+ return PAM_SUCCESS;
+}
diff --git a/src/login/pam_systemd_loadkey.sym b/src/login/pam_systemd_loadkey.sym
new file mode 100644
index 0000000..d611dc1
--- /dev/null
+++ b/src/login/pam_systemd_loadkey.sym
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+{
+global:
+ pam_sm_authenticate;
+ pam_sm_setcred;
+local: *;
+};
diff --git a/src/login/sysfs-show.c b/src/login/sysfs-show.c
new file mode 100644
index 0000000..0a8c02a
--- /dev/null
+++ b/src/login/sysfs-show.c
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "device-enumerator-private.h"
+#include "glyph-util.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "sysfs-show.h"
+#include "terminal-util.h"
+
+static int show_sysfs_one(
+ const char *seat,
+ sd_device **dev_list,
+ size_t *i_dev,
+ size_t n_dev,
+ const char *sub,
+ const char *prefix,
+ unsigned n_columns,
+ OutputFlags flags) {
+
+ size_t max_width;
+ int r;
+
+ assert(seat);
+ assert(dev_list);
+ assert(i_dev);
+ assert(prefix);
+
+ if (flags & OUTPUT_FULL_WIDTH)
+ max_width = SIZE_MAX;
+ else if (n_columns < 10)
+ max_width = 10;
+ else
+ max_width = n_columns;
+
+ while (*i_dev < n_dev) {
+ const char *sysfs, *sn, *name = NULL, *subsystem, *sysname;
+ _cleanup_free_ char *k = NULL, *l = NULL;
+ size_t lookahead;
+ bool is_master;
+
+ if (sd_device_get_syspath(dev_list[*i_dev], &sysfs) < 0 ||
+ !path_startswith(sysfs, sub))
+ return 0;
+
+ if (sd_device_get_property_value(dev_list[*i_dev], "ID_SEAT", &sn) < 0 || isempty(sn))
+ sn = "seat0";
+
+ /* Explicitly also check for tag 'seat' here */
+ if (!streq(seat, sn) ||
+ sd_device_has_current_tag(dev_list[*i_dev], "seat") <= 0 ||
+ sd_device_get_subsystem(dev_list[*i_dev], &subsystem) < 0 ||
+ sd_device_get_sysname(dev_list[*i_dev], &sysname) < 0) {
+ (*i_dev)++;
+ continue;
+ }
+
+ is_master = sd_device_has_current_tag(dev_list[*i_dev], "master-of-seat") > 0;
+
+ if (sd_device_get_sysattr_value(dev_list[*i_dev], "name", &name) < 0)
+ (void) sd_device_get_sysattr_value(dev_list[*i_dev], "id", &name);
+
+ /* Look if there's more coming after this */
+ for (lookahead = *i_dev + 1; lookahead < n_dev; lookahead++) {
+ const char *lookahead_sysfs;
+
+ if (sd_device_get_syspath(dev_list[lookahead], &lookahead_sysfs) < 0)
+ continue;
+
+ if (path_startswith(lookahead_sysfs, sub) &&
+ !path_startswith(lookahead_sysfs, sysfs)) {
+ const char *lookahead_sn;
+
+ if (sd_device_get_property_value(dev_list[lookahead], "ID_SEAT", &lookahead_sn) < 0 ||
+ isempty(lookahead_sn))
+ lookahead_sn = "seat0";
+
+ if (streq(seat, lookahead_sn) && sd_device_has_current_tag(dev_list[lookahead], "seat") > 0)
+ break;
+ }
+ }
+
+ k = ellipsize(sysfs, max_width, 20);
+ if (!k)
+ return -ENOMEM;
+
+ printf("%s%s%s\n", prefix, special_glyph(lookahead < n_dev ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT), k);
+
+ if (asprintf(&l,
+ "%s%s:%s%s%s%s",
+ is_master ? "[MASTER] " : "",
+ subsystem, sysname,
+ name ? " \"" : "", strempty(name), name ? "\"" : "") < 0)
+ return -ENOMEM;
+
+ free(k);
+ k = ellipsize(l, max_width, 70);
+ if (!k)
+ return -ENOMEM;
+
+ printf("%s%s%s\n", prefix, lookahead < n_dev ? special_glyph(SPECIAL_GLYPH_TREE_VERTICAL) : " ", k);
+
+ if (++(*i_dev) < n_dev) {
+ _cleanup_free_ char *p = NULL;
+
+ p = strjoin(prefix, lookahead < n_dev ? special_glyph(SPECIAL_GLYPH_TREE_VERTICAL) : " ");
+ if (!p)
+ return -ENOMEM;
+
+ r = show_sysfs_one(seat, dev_list, i_dev, n_dev, sysfs, p,
+ n_columns == UINT_MAX || n_columns < 2 ? n_columns : n_columns - 2,
+ flags);
+ if (r < 0)
+ return r;
+ }
+
+ }
+
+ return 0;
+}
+
+int show_sysfs(const char *seat, const char *prefix, unsigned n_columns, OutputFlags flags) {
+ _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+ size_t n_dev = 0, i = 0;
+ sd_device **dev_list;
+ int r;
+
+ if (n_columns <= 0)
+ n_columns = columns();
+
+ prefix = strempty(prefix);
+
+ if (isempty(seat))
+ seat = "seat0";
+
+ r = sd_device_enumerator_new(&e);
+ if (r < 0)
+ return r;
+
+ r = sd_device_enumerator_allow_uninitialized(e);
+ if (r < 0)
+ return r;
+
+ r = sd_device_enumerator_add_match_tag(e, streq(seat, "seat0") ? "seat" : seat);
+ if (r < 0)
+ return r;
+
+ r = device_enumerator_scan_devices(e);
+ if (r < 0)
+ return r;
+
+ dev_list = device_enumerator_get_devices(e, &n_dev);
+
+ if (dev_list && n_dev > 0)
+ show_sysfs_one(seat, dev_list, &i, n_dev, "/", prefix, n_columns, flags);
+ else
+ printf("%s%s%s\n", prefix, special_glyph(SPECIAL_GLYPH_TREE_RIGHT), "(none)");
+
+ return 0;
+}
diff --git a/src/login/sysfs-show.h b/src/login/sysfs-show.h
new file mode 100644
index 0000000..32ccbf3
--- /dev/null
+++ b/src/login/sysfs-show.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#include "output-mode.h"
+
+int show_sysfs(const char *seat, const char *prefix, unsigned columns, OutputFlags flags);
diff --git a/src/login/systemd-user.in b/src/login/systemd-user.in
new file mode 100644
index 0000000..8a3c9e0
--- /dev/null
+++ b/src/login/systemd-user.in
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# This file is part of systemd.
+#
+# Used by systemd --user instances.
+
+{% if ENABLE_HOMED %}
+-account sufficient pam_systemd_home.so
+{% endif %}
+account sufficient pam_unix.so no_pass_expiry
+account required pam_permit.so
+
+{% if HAVE_SELINUX %}
+session required pam_selinux.so close
+session required pam_selinux.so nottys open
+{% endif %}
+session required pam_loginuid.so
+session optional pam_keyinit.so force revoke
+session required pam_namespace.so
+{% if ENABLE_HOMED %}
+-session optional pam_systemd_home.so
+{% endif %}
+session optional pam_umask.so silent
+session optional pam_systemd.so
diff --git a/src/login/test-inhibit.c b/src/login/test-inhibit.c
new file mode 100644
index 0000000..abb80d9
--- /dev/null
+++ b/src/login/test-inhibit.c
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "sd-bus.h"
+
+#include "bus-locator.h"
+#include "bus-util.h"
+#include "fd-util.h"
+#include "macro.h"
+#include "tests.h"
+
+static int inhibit(sd_bus *bus, const char *what) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *who = "Test Tool", *reason = "Just because!", *mode = "block";
+ int fd;
+ int r;
+
+ r = bus_call_method(bus, bus_login_mgr, "Inhibit", &error, &reply, "ssss", what, who, reason, mode);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_UNIX_FD, &fd);
+ assert_se(r >= 0);
+ assert_se(fd >= 0);
+
+ return fcntl(fd, F_DUPFD_CLOEXEC, 3);
+}
+
+static void print_inhibitors(sd_bus *bus) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *what, *who, *why, *mode;
+ uint32_t uid, pid;
+ unsigned n = 0;
+ int r;
+
+ r = bus_call_method(bus, bus_login_mgr, "ListInhibitors", &error, &reply, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssuu)");
+ assert_se(r >= 0);
+
+ while ((r = sd_bus_message_read(reply, "(ssssuu)", &what, &who, &why, &mode, &uid, &pid)) > 0) {
+ printf("what=<%s> who=<%s> why=<%s> mode=<%s> uid=<%"PRIu32"> pid=<%"PRIu32">\n",
+ what, who, why, mode, uid, pid);
+
+ n++;
+ }
+ assert_se(r >= 0);
+
+ printf("%u inhibitors\n", n);
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ int fd1, fd2;
+ int r;
+
+ test_setup_logging(LOG_DEBUG);
+
+ r = sd_bus_open_system(&bus);
+ assert_se(r >= 0);
+
+ print_inhibitors(bus);
+
+ fd1 = inhibit(bus, "sleep");
+ assert_se(fd1 >= 0);
+ print_inhibitors(bus);
+
+ fd2 = inhibit(bus, "idle:shutdown");
+ assert_se(fd2 >= 0);
+ print_inhibitors(bus);
+
+ safe_close(fd1);
+ sleep(1);
+ print_inhibitors(bus);
+
+ safe_close(fd2);
+ sleep(1);
+ print_inhibitors(bus);
+
+ return 0;
+}
diff --git a/src/login/test-login-shared.c b/src/login/test-login-shared.c
new file mode 100644
index 0000000..17cd479
--- /dev/null
+++ b/src/login/test-login-shared.c
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "login-util.h"
+#include "macro.h"
+#include "tests.h"
+
+TEST(session_id_valid) {
+ assert_se(session_id_valid("c1"));
+ assert_se(session_id_valid("1234"));
+
+ assert_se(!session_id_valid("1-2"));
+ assert_se(!session_id_valid(""));
+ assert_se(!session_id_valid("\tid"));
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
diff --git a/src/login/test-login-tables.c b/src/login/test-login-tables.c
new file mode 100644
index 0000000..3c5ec04
--- /dev/null
+++ b/src/login/test-login-tables.c
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "logind-action.h"
+#include "logind-session.h"
+#include "test-tables.h"
+#include "tests.h"
+
+int main(int argc, char **argv) {
+ test_setup_logging(LOG_DEBUG);
+
+ test_table(handle_action, HANDLE_ACTION);
+ test_table(inhibit_mode, INHIBIT_MODE);
+ test_table(kill_who, KILL_WHO);
+ test_table(session_class, SESSION_CLASS);
+ test_table(session_state, SESSION_STATE);
+ test_table(session_type, SESSION_TYPE);
+ test_table(user_state, USER_STATE);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/login/test-session-properties.c b/src/login/test-session-properties.c
new file mode 100644
index 0000000..b5b5f60
--- /dev/null
+++ b/src/login/test-session-properties.c
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* Usage:
+ * ./test-session-properties <SESSION-OBJECT-PATH> [<TTY>]
+ * e.g.,
+ * ./test-session-properties /org/freedesktop/login1/session/_32 /dev/tty2
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "bus-common-errors.h"
+#include "bus-locator.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "tests.h"
+
+static const char *arg_tty = NULL;
+
+static BusLocator session;
+
+/* Tests org.freedesktop.logind.Session SetType */
+TEST(set_type) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus* bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char* types[] = {"tty", "x11", "wayland", "mir", "web"};
+ _cleanup_free_ char *type = NULL, *type2 = NULL;
+
+ assert_se(sd_bus_open_system(&bus) >= 0);
+
+ /* Default type is set */
+ assert_se(bus_get_property_string(bus, &session, "Type", NULL, &type) >= 0);
+ assert_se(streq(type, "tty"));
+
+ /* Type can only be set by the session controller (which we're not ATM) */
+ assert_se(bus_call_method(bus, &session, "SetType", &error, NULL, "s", "x11") < 0);
+ assert_se(sd_bus_error_has_name(&error, BUS_ERROR_NOT_IN_CONTROL));
+
+ assert_se(bus_call_method(bus, &session, "TakeControl", NULL, NULL, "b", true) >= 0);
+
+ /* All defined session types can be set */
+ for (size_t i = 0; i < ELEMENTSOF(types); i++) {
+ type = mfree(type);
+ assert_se(bus_call_method(bus, &session, "SetType", NULL, NULL, "s", types[i]) >= 0);
+ assert_se(bus_get_property_string(bus, &session, "Type", NULL, &type) >= 0);
+ assert_se(streq(type, types[i]));
+ }
+
+ /* An unknown type is rejected */
+ sd_bus_error_free(&error);
+ assert_se(bus_call_method(bus, &session, "SetType", &error, NULL, "s", "hello") < 0);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS));
+ assert_se(bus_get_property_string(bus, &session, "Type", NULL, &type2) >= 0);
+
+ /* Type is reset to the original value when we release control of the session */
+ assert_se(!streq(type, "tty"));
+ assert_se(bus_call_method(bus, &session, "ReleaseControl", NULL, NULL, NULL) >= 0);
+ type = mfree(type);
+ assert_se(bus_get_property_string(bus, &session, "Type", NULL, &type) >= 0);
+ assert_se(streq(type, "tty"));
+}
+
+/* Tests org.freedesktop.logind.Session SetDisplay */
+TEST(set_display) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus* bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *display = NULL;
+
+ assert_se(sd_bus_open_system(&bus) >= 0);
+
+ /* Display is unset by default */
+ assert_se(bus_get_property_string(bus, &session, "Display", NULL, &display) >= 0);
+ assert_se(isempty(display));
+
+ /* Display can only be set by the session controller (which we're not ATM) */
+ assert_se(bus_call_method(bus, &session, "SetDisplay", &error, NULL, "s", ":0") < 0);
+ assert_se(sd_bus_error_has_name(&error, BUS_ERROR_NOT_IN_CONTROL));
+
+ assert_se(bus_call_method(bus, &session, "TakeControl", NULL, NULL, "b", true) >= 0);
+
+ /* Display can only be set on a graphical session */
+ assert_se(bus_call_method(bus, &session, "SetType", NULL, NULL, "s", "tty") >= 0);
+ sd_bus_error_free(&error);
+ assert_se(bus_call_method(bus, &session, "SetDisplay", &error, NULL, "s", ":0") < 0);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_NOT_SUPPORTED));
+
+ assert_se(bus_call_method(bus, &session, "SetType", NULL, NULL, "s", "x11") >= 0);
+
+ /* Non-empty display can be set */
+ assert_se(bus_call_method(bus, &session, "SetDisplay", NULL, NULL, "s", ":0") >= 0);
+ display = mfree(display);
+ assert_se(bus_get_property_string(bus, &session, "Display", NULL, &display) >= 0);
+ assert_se(streq(display, ":0"));
+
+ /* Empty display can be set too */
+ assert_se(bus_call_method(bus, &session, "SetDisplay", NULL, NULL, "s", "") >= 0);
+ display = mfree(display);
+ assert_se(bus_get_property_string(bus, &session, "Display", NULL, &display) >= 0);
+ assert_se(isempty(display));
+}
+
+/* Tests org.freedesktop.logind.Session SetTTY */
+TEST(set_tty) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus* bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *tty = NULL;
+ int fd;
+
+ if (!arg_tty)
+ return;
+
+ fd = open(arg_tty, O_RDWR|O_CLOEXEC|O_NOCTTY);
+ assert_se(fd >= 0);
+
+ assert_se(sd_bus_open_system(&bus) >= 0);
+
+ /* tty can only be set by the session controller (which we're not ATM) */
+ assert_se(bus_call_method(bus, &session, "SetTTY", &error, NULL, "h", fd) < 0);
+ assert_se(sd_bus_error_has_name(&error, BUS_ERROR_NOT_IN_CONTROL));
+
+ assert_se(bus_call_method(bus, &session, "TakeControl", NULL, NULL, "b", true) >= 0);
+
+ /* tty can be set */
+ assert_se(bus_call_method(bus, &session, "SetTTY", NULL, NULL, "h", fd) >= 0);
+ tty = mfree(tty);
+ assert_se(bus_get_property_string(bus, &session, "TTY", NULL, &tty) >= 0);
+ assert_se(streq(tty, "tty2"));
+}
+
+/* Tests org.freedesktop.logind.Session SetIdleHint */
+TEST(set_idle_hint) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus* bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int idle_hint;
+ time_t stamp, idle_since1, idle_since2;
+
+ assert_se(sd_bus_open_system(&bus) >= 0);
+
+ /* Idle hint is not set by default */
+ assert_se(bus_get_property_trivial(bus, &session, "IdleHint", NULL, 'b', &idle_hint) >= 0);
+ assert_se(!idle_hint);
+
+ assert_se(bus_call_method(bus, &session, "TakeControl", NULL, NULL, "b", true) >= 0);
+
+ /* Idle hint can only be set on a graphical session */
+ assert_se(bus_call_method(bus, &session, "SetType", NULL, NULL, "s", "tty") >= 0);
+ assert_se(bus_call_method(bus, &session, "SetIdleHint", &error, NULL, "b", true) < 0);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_NOT_SUPPORTED));
+
+ assert_se(bus_call_method(bus, &session, "SetType", NULL, NULL, "s", "x11") >= 0);
+
+ stamp = now(CLOCK_MONOTONIC);
+
+ /* Idle hint can be set */
+ assert_se(bus_call_method(bus, &session, "SetIdleHint", NULL, NULL, "b", true) >= 0);
+ assert_se(bus_get_property_trivial(bus, &session, "IdleHint", NULL, 'b', &idle_hint) >= 0);
+ assert_se(idle_hint);
+ assert_se(bus_get_property_trivial(bus, &session, "IdleSinceHintMonotonic", NULL, 't', &idle_since1) >= 0);
+ assert_se(idle_since1 >= stamp);
+
+ /* Repeated setting doesn't change anything */
+ assert_se(bus_call_method(bus, &session, "SetIdleHint", NULL, NULL, "b", true) >= 0);
+ assert_se(bus_get_property_trivial(bus, &session, "IdleHint", NULL, 'b', &idle_hint) >= 0);
+ assert_se(idle_hint);
+ assert_se(bus_get_property_trivial(bus, &session, "IdleSinceHintMonotonic", NULL, 't', &idle_since2) >= 0);
+ assert_se(idle_since2 == idle_since1);
+
+ /* Idle hint can be unset */
+ assert_se(bus_call_method(bus, &session, "SetIdleHint", NULL, NULL, "b", false) >= 0);
+ assert_se(bus_get_property_trivial(bus, &session, "IdleHint", NULL, 'b', &idle_hint) >= 0);
+ assert_se(!idle_hint);
+}
+
+static int intro(void) {
+ if (saved_argc <= 1)
+ return EXIT_FAILURE;
+
+ session = (BusLocator) {
+ .destination = "org.freedesktop.login1",
+ .path = saved_argv[1],
+ .interface = "org.freedesktop.login1.Session",
+ };
+
+ if (saved_argc > 2)
+ arg_tty = saved_argv[2];
+
+ return EXIT_SUCCESS;
+}
+
+DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro);
diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c
new file mode 100644
index 0000000..ad04b04
--- /dev/null
+++ b/src/login/user-runtime-dir.c
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdint.h>
+#include <sys/mount.h>
+
+#include "sd-bus.h"
+
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "dev-setup.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "label-util.h"
+#include "limits-util.h"
+#include "main-func.h"
+#include "mkdir-label.h"
+#include "mount-util.h"
+#include "mountpoint-util.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "selinux-util.h"
+#include "smack-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "user-util.h"
+
+static int acquire_runtime_dir_properties(uint64_t *size, uint64_t *inodes) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ r = sd_bus_default_system(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to system bus: %m");
+
+ r = bus_get_property_trivial(bus, bus_login_mgr, "RuntimeDirectorySize", &error, 't', size);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to acquire runtime directory size, ignoring: %s", bus_error_message(&error, r));
+ *size = physical_memory_scale(10U, 100U); /* 10% */
+ }
+
+ r = bus_get_property_trivial(bus, bus_login_mgr, "RuntimeDirectoryInodesMax", &error, 't', inodes);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to acquire number of inodes for runtime directory, ignoring: %s", bus_error_message(&error, r));
+ *inodes = DIV_ROUND_UP(*size, 4096);
+ }
+
+ return 0;
+}
+
+static int user_mkdir_runtime_path(
+ const char *runtime_path,
+ uid_t uid,
+ gid_t gid,
+ uint64_t runtime_dir_size,
+ uint64_t runtime_dir_inodes) {
+
+ int r;
+
+ assert(runtime_path);
+ assert(path_is_absolute(runtime_path));
+ assert(uid_is_valid(uid));
+ assert(gid_is_valid(gid));
+
+ r = mkdir_safe_label("/run/user", 0755, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create /run/user: %m");
+
+ if (path_is_mount_point(runtime_path, NULL, 0) > 0)
+ log_debug("%s is already a mount point", runtime_path);
+ else {
+ char options[sizeof("mode=0700,uid=,gid=,size=,nr_inodes=,smackfsroot=*")
+ + DECIMAL_STR_MAX(uid_t)
+ + DECIMAL_STR_MAX(gid_t)
+ + DECIMAL_STR_MAX(uint64_t)
+ + DECIMAL_STR_MAX(uint64_t)];
+
+ xsprintf(options,
+ "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%" PRIu64 ",nr_inodes=%" PRIu64 "%s",
+ uid, gid, runtime_dir_size, runtime_dir_inodes,
+ mac_smack_use() ? ",smackfsroot=*" : "");
+
+ r = mkdir_label(runtime_path, 0700);
+ if (r < 0 && r != -EEXIST)
+ return log_error_errno(r, "Failed to create %s: %m", runtime_path);
+
+ r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, options);
+ if (r < 0) {
+ if (!ERRNO_IS_PRIVILEGE(r)) {
+ log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path);
+ goto fail;
+ }
+
+ log_debug_errno(r,
+ "Failed to mount per-user tmpfs directory %s.\n"
+ "Assuming containerized execution, ignoring: %m", runtime_path);
+
+ r = chmod_and_chown(runtime_path, 0700, uid, gid);
+ if (r < 0) {
+ log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path);
+ goto fail;
+ }
+ }
+
+ r = label_fix(runtime_path, 0);
+ if (r < 0)
+ log_warning_errno(r, "Failed to fix label of \"%s\", ignoring: %m", runtime_path);
+ }
+
+ return 0;
+
+fail:
+ /* Try to clean up, but ignore errors */
+ (void) rmdir(runtime_path);
+ return r;
+}
+
+static int user_remove_runtime_path(const char *runtime_path) {
+ int r;
+
+ assert(runtime_path);
+ assert(path_is_absolute(runtime_path));
+
+ r = rm_rf(runtime_path, 0);
+ if (r < 0)
+ log_debug_errno(r, "Failed to remove runtime directory %s (before unmounting), ignoring: %m", runtime_path);
+
+ /* Ignore cases where the directory isn't mounted, as that's quite possible, if we lacked the permissions to
+ * mount something */
+ r = umount2(runtime_path, MNT_DETACH);
+ if (r < 0 && !IN_SET(errno, EINVAL, ENOENT))
+ log_debug_errno(errno, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path);
+
+ r = rm_rf(runtime_path, REMOVE_ROOT);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", runtime_path);
+
+ return 0;
+}
+
+static int do_mount(const char *user) {
+ char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
+ uint64_t runtime_dir_size, runtime_dir_inodes;
+ uid_t uid;
+ gid_t gid;
+ int r;
+
+ r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r,
+ r == -ESRCH ? "No such user \"%s\"" :
+ r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
+ : "Failed to look up user \"%s\": %m",
+ user);
+
+ r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes);
+ if (r < 0)
+ return r;
+
+ xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
+
+ log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, uid, gid);
+ return user_mkdir_runtime_path(runtime_path, uid, gid, runtime_dir_size, runtime_dir_inodes);
+}
+
+static int do_umount(const char *user) {
+ char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
+ uid_t uid;
+ int r;
+
+ /* The user may be already removed. So, first try to parse the string by parse_uid(),
+ * and if it fails, fall back to get_user_creds(). */
+ if (parse_uid(user, &uid) < 0) {
+ r = get_user_creds(&user, &uid, NULL, NULL, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r,
+ r == -ESRCH ? "No such user \"%s\"" :
+ r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
+ : "Failed to look up user \"%s\": %m",
+ user);
+ }
+
+ xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
+
+ log_debug("Will remove %s", runtime_path);
+ return user_remove_runtime_path(runtime_path);
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ if (argc != 3)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "This program takes two arguments.");
+ if (!STR_IN_SET(argv[1], "start", "stop"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "First argument must be either \"start\" or \"stop\".");
+
+ umask(0022);
+
+ r = mac_init();
+ if (r < 0)
+ return r;
+
+ if (streq(argv[1], "start"))
+ return do_mount(argv[2]);
+ if (streq(argv[1], "stop"))
+ return do_umount(argv[2]);
+ assert_not_reached();
+}
+
+DEFINE_MAIN_FUNCTION(run);