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/logind-dbus.c | |
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/logind-dbus.c')
-rw-r--r-- | src/login/logind-dbus.c | 1183 |
1 files changed, 675 insertions, 508 deletions
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) { |