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