summaryrefslogtreecommitdiff
path: root/src/login
diff options
context:
space:
mode:
Diffstat (limited to 'src/login')
-rw-r--r--src/login/70-uaccess.rules.m4 (renamed from src/login/70-uaccess.rules)4
-rw-r--r--src/login/elogind-dbus.c2
-rw-r--r--src/login/elogind.c46
-rw-r--r--src/login/elogind.h3
-rw-r--r--src/login/logind-core.c216
-rw-r--r--src/login/logind-dbus.c113
-rw-r--r--src/login/logind-device.c4
-rw-r--r--src/login/logind-gperf.gperf1
-rw-r--r--src/login/logind-seat.c58
-rw-r--r--src/login/logind-seat.h6
-rw-r--r--src/login/logind-session-dbus.c23
-rw-r--r--src/login/logind-session.c252
-rw-r--r--src/login/logind-session.h25
-rw-r--r--src/login/logind-user-dbus.c2
-rw-r--r--src/login/logind-user.c395
-rw-r--r--src/login/logind-user.h22
-rw-r--r--src/login/logind-utmp.c6
-rw-r--r--src/login/logind.c79
-rw-r--r--src/login/logind.h20
-rw-r--r--src/login/meson.build13
-rw-r--r--src/login/org.freedesktop.login1.policy2
-rw-r--r--src/login/pam_elogind.c124
-rw-r--r--src/login/user-runtime-dir.c111
23 files changed, 1025 insertions, 502 deletions
diff --git a/src/login/70-uaccess.rules b/src/login/70-uaccess.rules.m4
index 3515d292a..d55e5bf5c 100644
--- a/src/login/70-uaccess.rules
+++ b/src/login/70-uaccess.rules.m4
@@ -46,6 +46,10 @@ SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x014001*", TAG+="uaccess"
# DRI video devices
SUBSYSTEM=="drm", KERNEL=="card*", TAG+="uaccess"
+m4_ifdef(`DEV_KVM_UACCESS',``
+# KVM
+SUBSYSTEM=="misc", KERNEL=="kvm", TAG+="uaccess"''
+)m4_dnl
# smart-card readers
ENV{ID_SMARTCARD_READER}=="?*", TAG+="uaccess"
diff --git a/src/login/elogind-dbus.c b/src/login/elogind-dbus.c
index f359e1867..03ec05029 100644
--- a/src/login/elogind-dbus.c
+++ b/src/login/elogind-dbus.c
@@ -83,7 +83,7 @@ static int run_helper(const char *helper, const char *arg_verb) {
arguments[0] = NULL;
arguments[1] = (char*)arg_verb;
arguments[2] = NULL;
- execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments);
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL);
r = safe_fork_full(helper, NULL, 0, FORK_RESET_SIGNALS|FORK_REOPEN_LOG, NULL);
diff --git a/src/login/elogind.c b/src/login/elogind.c
index 9fe52a94c..77622483a 100644
--- a/src/login/elogind.c
+++ b/src/login/elogind.c
@@ -156,7 +156,7 @@ static int elogind_daemonize(void) {
/// Simple tool to see, if elogind is already running
static pid_t elogind_is_already_running(bool need_pid_file) {
- _cleanup_free_ char *s = NULL;
+ _cleanup_free_ char *s = NULL, *comm = NULL;
pid_t pid;
int r;
@@ -170,8 +170,17 @@ static pid_t elogind_is_already_running(bool need_pid_file) {
if (r < 0)
goto we_are_alone;
- if ( (pid != getpid_cached()) && pid_is_alive(pid))
- return pid;
+ if ( (pid != getpid_cached()) && pid_is_alive(pid)) {
+ /* If the old elogind process currently running was forked into
+ * background, its name will be "elogind-daemon", while this
+ * process will be "elogind".
+ * Therefore check comm with startswith().
+ */
+ get_process_comm(pid, &comm);
+ if (NULL == startswith(strna(comm), program_invocation_short_name))
+ goto we_are_alone;
+ }
+ return pid;
we_are_alone:
@@ -454,27 +463,28 @@ void elogind_manager_reset_config(Manager* m) {
#endif // ENABLE_DEBUG_ELOGIND
}
-
/// Add-On for manager_startup()
-int elogind_manager_startup(Manager *m) {
- int r;
-
- assert(m);
+int elogind_manager_startup(Manager* m) {
+ int r, e = 0;
- assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, -1) >= 0);
+ /* Install our signal handler */
r = sd_event_add_signal(m->event, NULL, SIGINT, elogind_signal_handler, m);
- if (r < 0)
- return log_error_errno(r, "Failed to register SIGINT handler: %m");
+ if (r < 0) {
+ if (e == 0) e = r;
+ log_error_errno(r, "Failed to register SIGINT handler: %m");
+ }
- assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGQUIT, -1) >= 0);
r = sd_event_add_signal(m->event, NULL, SIGQUIT, elogind_signal_handler, m);
- if (r < 0)
- return log_error_errno(r, "Failed to register SIGQUIT handler: %m");
+ if (r < 0) {
+ if (e == 0) e = r;
+ log_error_errno(r, "Failed to register SIGQUIT handler: %m");
+ }
- assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGTERM, -1) >= 0);
r = sd_event_add_signal(m->event, NULL, SIGTERM, elogind_signal_handler, m);
- if (r < 0)
- return log_error_errno(r, "Failed to register SIGTERM handler: %m");
+ if (r < 0) {
+ if (e == 0) e = r;
+ log_error_errno(r, "Failed to register SIGTERM handler: %m");
+ }
- return 0;
+ return e;
}
diff --git a/src/login/elogind.h b/src/login/elogind.h
index 60b9f80dd..37bcb1e40 100644
--- a/src/login/elogind.h
+++ b/src/login/elogind.h
@@ -42,7 +42,6 @@ int elogind_manager_new(Manager* m);
void elogind_manager_reset_config(Manager* m);
/// Add-On for manager_startup()
-int elogind_manager_startup(Manager *m);
-
+int elogind_manager_startup(Manager* m);
#endif // ELOGIND_SRC_LOGIN_ELOGIN_H_INCLUDED
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
index db0df19e2..e7517794c 100644
--- a/src/login/logind-core.c
+++ b/src/login/logind-core.c
@@ -5,6 +5,9 @@
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/vt.h>
+#if ENABLE_UTMP
+#include <utmpx.h>
+#endif
#include "alloc-util.h"
#include "bus-error.h"
@@ -14,6 +17,7 @@
#include "fd-util.h"
#include "logind.h"
#include "parse-util.h"
+#include "path-util.h"
#include "process-util.h"
#include "strv.h"
#include "terminal-util.h"
@@ -29,6 +33,8 @@ void manager_reset_config(Manager *m) {
#endif // 0
m->remove_ipc = true;
m->inhibit_delay_max = 5 * USEC_PER_SEC;
+ m->user_stop_delay = 10 * USEC_PER_SEC;
+
m->handle_power_key = HANDLE_POWEROFF;
m->handle_suspend_key = HANDLE_SUSPEND;
m->handle_hibernate_key = HANDLE_HIBERNATE;
@@ -102,15 +108,16 @@ int manager_add_device(Manager *m, const char *sysfs, bool master, Device **_dev
int manager_add_seat(Manager *m, const char *id, Seat **_seat) {
Seat *s;
+ int r;
assert(m);
assert(id);
s = hashmap_get(m->seats, id);
if (!s) {
- s = seat_new(m, id);
- if (!s)
- return -ENOMEM;
+ r = seat_new(&s, m, id);
+ if (r < 0)
+ return r;
}
if (_seat)
@@ -121,15 +128,16 @@ int manager_add_seat(Manager *m, const char *id, Seat **_seat) {
int manager_add_session(Manager *m, const char *id, Session **_session) {
Session *s;
+ int r;
assert(m);
assert(id);
s = hashmap_get(m->sessions, id);
if (!s) {
- s = session_new(m, id);
- if (!s)
- return -ENOMEM;
+ r = session_new(&s, m, id);
+ if (r < 0)
+ return r;
}
if (_session)
@@ -138,7 +146,14 @@ int manager_add_session(Manager *m, const char *id, Session **_session) {
return 0;
}
-int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user) {
+int manager_add_user(
+ Manager *m,
+ uid_t uid,
+ gid_t gid,
+ const char *name,
+ const char *home,
+ User **_user) {
+
User *u;
int r;
@@ -147,7 +162,7 @@ int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **
u = hashmap_get(m->users, UID_TO_PTR(uid));
if (!u) {
- r = user_new(&u, m, uid, gid, name);
+ r = user_new(&u, m, uid, gid, name, home);
if (r < 0)
return r;
}
@@ -158,7 +173,12 @@ int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **
return 0;
}
-int manager_add_user_by_name(Manager *m, const char *name, User **_user) {
+int manager_add_user_by_name(
+ Manager *m,
+ const char *name,
+ User **_user) {
+
+ const char *home = NULL;
uid_t uid;
gid_t gid;
int r;
@@ -166,11 +186,11 @@ int manager_add_user_by_name(Manager *m, const char *name, User **_user) {
assert(m);
assert(name);
- r = get_user_creds(&name, &uid, &gid, NULL, NULL);
+ r = get_user_creds(&name, &uid, &gid, &home, NULL);
if (r < 0)
return r;
- return manager_add_user(m, uid, gid, name, _user);
+ return manager_add_user(m, uid, gid, name, home, _user);
}
int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) {
@@ -183,7 +203,7 @@ int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) {
if (!p)
return errno > 0 ? -errno : -ENOENT;
- return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user);
+ return manager_add_user(m, uid, p->pw_gid, p->pw_name, p->pw_dir, _user);
}
int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **_inhibitor) {
@@ -337,26 +357,29 @@ int manager_get_session_by_pid(Manager *m, pid_t pid, Session **ret) {
if (!pid_is_valid(pid))
return -EINVAL;
+ s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(pid));
+ if (!s) {
#if 0 /// elogind does not support systemd units, but its own session system
- r = cg_pid_get_unit(pid, &unit);
- if (r < 0)
- goto not_found;
+ r = cg_pid_get_unit(pid, &unit);
+ if (r < 0)
+ goto not_found;
- s = hashmap_get(m->session_units, unit);
- if (!s)
- goto not_found;
+ s = hashmap_get(m->session_units, unit);
+ if (!s)
+ goto not_found;
#else
- log_debug_elogind("Searching session for PID %u", pid);
- r = cg_pid_get_session(pid, &session_name);
- if (r < 0)
- goto not_found;
+ log_debug_elogind("Searching session for PID %u", pid);
+ r = cg_pid_get_session(pid, &session_name);
+ if (r < 0)
+ goto not_found;
- s = hashmap_get(m->sessions, session_name);
- log_debug_elogind("Session Name \"%s\" -> Session \"%s\"",
- session_name, s && s->id ? s->id : "NULL");
- if (NULL == s)
- goto not_found;
+ s = hashmap_get(m->sessions, session_name);
+ log_debug_elogind("Session Name \"%s\" -> Session \"%s\"",
+ session_name, s && s->id ? s->id : "NULL");
+ if (NULL == s)
+ goto not_found;
#endif // 0
+ }
if (ret)
*ret = s;
@@ -722,3 +745,142 @@ bool manager_all_buttons_ignored(Manager *m) {
return true;
}
+
+int manager_read_utmp(Manager *m) {
+#if ENABLE_UTMP
+ int r;
+
+ assert(m);
+
+ if (utmpxname(_PATH_UTMPX) < 0)
+ return log_error_errno(errno, "Failed to set utmp path to " _PATH_UTMPX ": %m");
+
+ setutxent();
+
+ for (;;) {
+ _cleanup_free_ char *t = NULL;
+ struct utmpx *u;
+ const char *c;
+ Session *s;
+
+ errno = 0;
+ u = getutxent();
+ if (!u) {
+ if (errno != 0)
+ log_warning_errno(errno, "Failed to read " _PATH_UTMPX ", ignoring: %m");
+ r = 0;
+ break;
+ }
+
+ if (u->ut_type != USER_PROCESS)
+ continue;
+
+ if (!pid_is_valid(u->ut_pid))
+ continue;
+
+ t = strndup(u->ut_line, sizeof(u->ut_line));
+ if (!t) {
+ r = log_oom();
+ break;
+ }
+
+ c = path_startswith(t, "/dev/");
+ if (c) {
+ r = free_and_strdup(&t, c);
+ if (r < 0) {
+ log_oom();
+ break;
+ }
+ }
+
+ if (isempty(t))
+ continue;
+
+ s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(u->ut_pid));
+ if (!s)
+ continue;
+
+ if (s->tty_validity == TTY_FROM_UTMP && !streq_ptr(s->tty, t)) {
+ /* This may happen on multiplexed SSH connection (i.e. 'SSH connection sharing'). In
+ * this case PAM and utmp sessions don't match. In such a case let's invalidate the TTY
+ * information and never acquire it again. */
+
+ s->tty = mfree(s->tty);
+ s->tty_validity = TTY_UTMP_INCONSISTENT;
+ log_debug("Session '%s' has inconsistent TTY information, dropping TTY information.", s->id);
+ continue;
+ }
+
+ /* Never override what we figured out once */
+ if (s->tty || s->tty_validity >= 0)
+ continue;
+
+ s->tty = TAKE_PTR(t);
+ s->tty_validity = TTY_FROM_UTMP;
+ log_debug("Acquired TTY information '%s' from utmp for session '%s'.", s->tty, s->id);
+ }
+
+ endutxent();
+ return r;
+#else
+ return 0
+#endif
+}
+
+#if ENABLE_UTMP
+static int manager_dispatch_utmp(sd_event_source *s, const struct inotify_event *event, void *userdata) {
+ Manager *m = userdata;
+
+ assert(m);
+
+ /* If there's indication the file itself might have been removed or became otherwise unavailable, then let's
+ * reestablish the watch on whatever there's now. */
+ if ((event->mask & (IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF|IN_Q_OVERFLOW|IN_UNMOUNT)) != 0)
+ manager_connect_utmp(m);
+
+ (void) manager_read_utmp(m);
+ return 0;
+}
+#endif
+
+void manager_connect_utmp(Manager *m) {
+#if ENABLE_UTMP
+ sd_event_source *s = NULL;
+ int r;
+
+ assert(m);
+
+ /* Watch utmp for changes via inotify. We do this to deal with tools such as ssh, which will register the PAM
+ * session early, and acquire a TTY only much later for the connection. Thus during PAM the TTY won't be known
+ * yet. ssh will register itself with utmp when it finally acquired the TTY. Hence, let's make use of this, and
+ * watch utmp for the TTY asynchronously. We use the PAM session's leader PID as key, to find the right entry.
+ *
+ * Yes, relying on utmp is pretty ugly, but it's good enough for informational purposes, as well as idle
+ * detection (which, for tty sessions, relies on the TTY used) */
+
+ r = sd_event_add_inotify(m->event, &s, _PATH_UTMPX, IN_MODIFY|IN_MOVE_SELF|IN_DELETE_SELF|IN_ATTRIB, manager_dispatch_utmp, m);
+ if (r < 0)
+ log_full_errno(r == -ENOENT ? LOG_DEBUG: LOG_WARNING, r, "Failed to create inotify watch on " _PATH_UTMPX ", ignoring: %m");
+ else {
+ r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
+ if (r < 0)
+ log_warning_errno(r, "Failed to adjust utmp event source priority, ignoring: %m");
+
+ (void) sd_event_source_set_description(s, "utmp");
+ }
+
+ sd_event_source_unref(m->utmp_event_source);
+ m->utmp_event_source = s;
+#endif
+}
+
+void manager_reconnect_utmp(Manager *m) {
+#if ENABLE_UTMP
+ assert(m);
+
+ if (m->utmp_event_source)
+ return;
+
+ manager_connect_utmp(m);
+#endif
+}
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index acf50e62e..44f08c583 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -775,6 +775,9 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
} while (hashmap_get(m->sessions, id));
}
+ /* If we are not watching utmp aleady, try again */
+ manager_reconnect_utmp(m);
+
r = manager_add_user_by_uid(m, uid, &user);
if (r < 0)
goto fail;
@@ -784,9 +787,8 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
goto fail;
session_set_user(session, user);
+ session_set_leader(session, leader);
- session->leader = leader;
- session->audit_id = audit_id;
session->type = t;
session->class = c;
session->remote = remote;
@@ -798,6 +800,8 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
r = -ENOMEM;
goto fail;
}
+
+ session->tty_validity = TTY_FROM_PAM;
}
if (!isempty(display)) {
@@ -848,9 +852,9 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
r = sd_bus_message_enter_container(message, 'a', "(sv)");
if (r < 0)
- return r;
+ goto fail;
- r = session_start(session, message);
+ r = session_start(session, message, error);
if (r < 0)
goto fail;
@@ -1842,8 +1846,9 @@ static int method_do_shutdown_or_sleep(
#if 0 /// Within elogind the manager m must be provided, too
r = can_sleep(sleep_verb);
if (r == -ENOSPC)
- return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
- "Not enough swap space for hibernation");
+ return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Not enough swap space for hibernation");
+ if (r == -EADV)
+ return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Resume not configured, can't hibernate");
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
"Sleep verb \"%s\" not supported", sleep_verb);
@@ -2285,6 +2290,7 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd
if (cancelled && m->enable_wall_messages) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ _cleanup_free_ char *username = NULL;
const char *tty = NULL;
uid_t uid = 0;
int r;
@@ -2295,9 +2301,10 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd
(void) sd_bus_creds_get_tty(creds, &tty);
}
+ username = uid_to_name(uid);
#if 0 /// elogind wants to allow extra cancellation messages
utmp_wall("The system shutdown has been cancelled",
- uid_to_name(uid), tty, logind_wall_tty_filter, m);
+ username, tty, logind_wall_tty_filter, m);
#else
r = asprintf(&l, "%s%sThe system shutdown has been cancelled!",
strempty(m->wall_message),
@@ -2345,7 +2352,7 @@ static int method_can_shutdown_or_sleep(
#else
r = can_sleep(m, sleep_verb);
#endif // 0
- if (IN_SET(r, 0, -ENOSPC))
+ if (IN_SET(r, 0, -ENOSPC, -EADV))
return sd_bus_reply_method_return(message, "s", "na");
if (r < 0)
return r;
@@ -2367,7 +2374,6 @@ static int method_can_shutdown_or_sleep(
blocked = manager_is_inhibited(m, w, INHIBIT_BLOCK, NULL, false, true, uid, NULL);
handle = handle_action_from_string(sleep_verb);
-
if (handle >= 0) {
#if 0 /// elogind uses its own variant, which can use the handle directly.
const char *target;
@@ -2385,7 +2391,6 @@ static int method_can_shutdown_or_sleep(
goto finish;
}
}
- }
#else
log_debug_elogind("CanShutDownOrSleep: %s [%d] %s blocked",
sleep_verb, handle, blocked ? "is" : "not");
@@ -2819,6 +2824,7 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_PROPERTY("BlockInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("DelayInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("InhibitDelayMaxUSec", "t", NULL, offsetof(Manager, inhibit_delay_max), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("UserStopDelayUSec", "t", NULL, offsetof(Manager, user_stop_delay), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandlePowerKey", "s", property_get_handle_action, offsetof(Manager, handle_power_key), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandleSuspendKey", "s", property_get_handle_action, offsetof(Manager, handle_suspend_key), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandleHibernateKey", "s", property_get_handle_action, offsetof(Manager, handle_hibernate_key), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -2900,24 +2906,20 @@ const sd_bus_vtable manager_vtable[] = {
#if 0 /// UNNEEDED by elogind
static int session_jobs_reply(Session *s, const char *unit, const char *result) {
- int r = 0;
-
assert(s);
assert(unit);
if (!s->started)
- return r;
+ return 0;
- if (streq(result, "done"))
- r = session_send_create_reply(s, NULL);
- else {
+ 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, "Start job for unit %s failed with '%s'", unit, result);
- r = session_send_create_reply(s, &e);
+ sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit '%s' failed with '%s'", unit, result);
+ return session_send_create_reply(s, &e);
}
- return r;
+ return session_send_create_reply(s, NULL);
}
int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -2950,30 +2952,29 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err
}
session = hashmap_get(m->session_units, unit);
- if (session && streq_ptr(path, session->scope_job)) {
- session->scope_job = mfree(session->scope_job);
- session_jobs_reply(session, unit, result);
+ if (session) {
+ if (streq_ptr(path, session->scope_job)) {
+ session->scope_job = mfree(session->scope_job);
+ (void) session_jobs_reply(session, unit, result);
+
+ session_save(session);
+ user_save(session->user);
+ }
- session_save(session);
- user_save(session->user);
session_add_to_gc_queue(session);
}
user = hashmap_get(m->user_units, unit);
- if (user &&
- (streq_ptr(path, user->service_job) ||
- streq_ptr(path, user->slice_job))) {
-
- if (streq_ptr(path, user->service_job))
+ if (user) {
+ if (streq_ptr(path, user->service_job)) {
user->service_job = mfree(user->service_job);
- if (streq_ptr(path, user->slice_job))
- user->slice_job = mfree(user->slice_job);
+ LIST_FOREACH(sessions_by_user, session, user->sessions)
+ (void) session_jobs_reply(session, unit, NULL /* don't propagate user service failures to the client */);
- LIST_FOREACH(sessions_by_user, session, user->sessions)
- session_jobs_reply(session, unit, result);
+ user_save(user);
+ }
- user_save(user);
user_add_to_gc_queue(user);
}
@@ -3107,13 +3108,15 @@ int manager_start_scope(
pid_t pid,
const char *slice,
const char *description,
- const char *after,
- const char *after2,
+ char **wants,
+ char **after,
+ const char *requires_mounts_for,
sd_bus_message *more_properties,
sd_bus_error *error,
char **job) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ char **i;
int r;
assert(manager);
@@ -3151,14 +3154,20 @@ int manager_start_scope(
return r;
}
- if (!isempty(after)) {
- r = sd_bus_message_append(m, "(sv)", "After", "as", 1, after);
+ STRV_FOREACH(i, wants) {
+ r = sd_bus_message_append(m, "(sv)", "Wants", "as", 1, *i);
if (r < 0)
return r;
}
- if (!isempty(after2)) {
- r = sd_bus_message_append(m, "(sv)", "After", "as", 1, after2);
+ STRV_FOREACH(i, after) {
+ r = sd_bus_message_append(m, "(sv)", "After", "as", 1, *i);
+ if (r < 0)
+ return r;
+ }
+
+ if (!empty_or_root(requires_mounts_for)) {
+ r = sd_bus_message_append(m, "(sv)", "RequiresMountsFor", "as", 1, requires_mounts_for);
if (r < 0)
return r;
}
@@ -3255,7 +3264,8 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c
return strdup_job(reply, job);
}
-int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error) {
+int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *ret_error) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *path = NULL;
int r;
@@ -3272,17 +3282,16 @@ int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *err
path,
"org.freedesktop.systemd1.Scope",
"Abandon",
- error,
+ &error,
NULL,
NULL);
if (r < 0) {
- if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) ||
- sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED) ||
- sd_bus_error_has_name(error, BUS_ERROR_SCOPE_NOT_RUNNING)) {
- sd_bus_error_free(error);
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) ||
+ sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED) ||
+ sd_bus_error_has_name(&error, BUS_ERROR_SCOPE_NOT_RUNNING))
return 0;
- }
+ sd_bus_error_move(ret_error, &error);
return r;
}
@@ -3304,7 +3313,7 @@ int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo
"ssi", unit, who == KILL_LEADER ? "main" : "all", signo);
}
-int manager_unit_is_active(Manager *manager, const char *unit) {
+int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *ret_error) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ char *path = NULL;
@@ -3340,17 +3349,18 @@ int manager_unit_is_active(Manager *manager, const char *unit) {
sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED))
return false;
+ sd_bus_error_move(ret_error, &error);
return r;
}
r = sd_bus_message_read(reply, "s", &state);
if (r < 0)
- return -EINVAL;
+ return r;
- return !streq(state, "inactive") && !streq(state, "failed");
+ return !STR_IN_SET(state, "inactive", "failed");
}
-int manager_job_is_active(Manager *manager, const char *path) {
+int manager_job_is_active(Manager *manager, const char *path, sd_bus_error *ret_error) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
int r;
@@ -3375,6 +3385,7 @@ int manager_job_is_active(Manager *manager, const char *path) {
if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT))
return false;
+ sd_bus_error_move(ret_error, &error);
return r;
}
diff --git a/src/login/logind-device.c b/src/login/logind-device.c
index 9b5b3e879..e724365b1 100644
--- a/src/login/logind-device.c
+++ b/src/login/logind-device.c
@@ -99,6 +99,8 @@ void device_attach(Device *d, Seat *s) {
}
}
- if (!had_master && d->master)
+ if (!had_master && d->master && s->started) {
+ seat_save(s);
seat_send_changed(s, "CanGraphical", NULL);
+ }
}
diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf
index c8523fbdf..fe593d5b4 100644
--- a/src/login/logind-gperf.gperf
+++ b/src/login/logind-gperf.gperf
@@ -34,6 +34,7 @@ Login.KillUserProcesses, config_parse_bool, 0, offse
Login.KillOnlyUsers, config_parse_strv, 0, offsetof(Manager, kill_only_users)
Login.KillExcludeUsers, config_parse_strv, 0, offsetof(Manager, kill_exclude_users)
Login.InhibitDelayMaxSec, config_parse_sec, 0, offsetof(Manager, inhibit_delay_max)
+Login.UserStopDelaySec, config_parse_sec, 0, offsetof(Manager, user_stop_delay)
Login.HandlePowerKey, config_parse_handle_action, 0, offsetof(Manager, handle_power_key)
Login.HandleSuspendKey, config_parse_handle_action, 0, offsetof(Manager, handle_suspend_key)
Login.HandleHibernateKey, config_parse_handle_action, 0, offsetof(Manager, handle_hibernate_key)
diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c
index 0ee5f70f5..c8fc4bb8e 100644
--- a/src/login/logind-seat.c
+++ b/src/login/logind-seat.c
@@ -21,33 +21,42 @@
#include "terminal-util.h"
#include "util.h"
-Seat *seat_new(Manager *m, const char *id) {
- Seat *s;
+int seat_new(Seat** ret, Manager *m, const char *id) {
+ _cleanup_(seat_freep) Seat *s = NULL;
+ int r;
+ assert(ret);
assert(m);
assert(id);
- s = new0(Seat, 1);
+ if (!seat_name_is_valid(id))
+ return -EINVAL;
+
+ s = new(Seat, 1);
if (!s)
- return NULL;
+ return -ENOMEM;
+
+ *s = (Seat) {
+ .manager = m,
+ };
s->state_file = strappend("/run/systemd/seats/", id);
if (!s->state_file)
- return mfree(s);
+ return -ENOMEM;
s->id = basename(s->state_file);
- s->manager = m;
- if (hashmap_put(m->seats, s->id, s) < 0) {
- free(s->state_file);
- return mfree(s);
- }
+ r = hashmap_put(m->seats, s->id, s);
+ if (r < 0)
+ return r;
- return s;
+ *ret = TAKE_PTR(s);
+ return 0;
}
-void seat_free(Seat *s) {
- assert(s);
+Seat* seat_free(Seat *s) {
+ if (!s)
+ return NULL;
if (s->in_gc_queue)
LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
@@ -64,7 +73,8 @@ void seat_free(Seat *s) {
free(s->positions);
free(s->state_file);
- free(s);
+
+ return mfree(s);
}
int seat_save(Seat *s) {
@@ -166,7 +176,7 @@ static int vt_allocate(unsigned int vtnr) {
xsprintf(p, "/dev/tty%u", vtnr);
fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
- return -errno;
+ return fd;
return 0;
}
@@ -190,10 +200,8 @@ int seat_preallocate_vts(Seat *s) {
int q;
q = vt_allocate(i);
- if (q < 0) {
- log_error_errno(q, "Failed to preallocate VT %u: %m", i);
- r = q;
- }
+ if (q < 0)
+ r = log_error_errno(q, "Failed to preallocate VT %u: %m", i);
}
return r;
@@ -212,9 +220,9 @@ int seat_apply_acls(Seat *s, Session *old_active) {
!!s->active, s->active ? s->active->user->uid : 0);
if (r < 0)
- log_error_errno(r, "Failed to apply ACLs: %m");
+ return log_error_errno(r, "Failed to apply ACLs: %m");
- return r;
+ return 0;
}
int seat_set_active(Seat *s, Session *session) {
@@ -234,7 +242,7 @@ int seat_set_active(Seat *s, Session *session) {
session_send_changed(old_active, "Active", NULL);
}
- seat_apply_acls(s, old_active);
+ (void) seat_apply_acls(s, old_active);
if (session && session->started) {
session_send_changed(session, "Active", NULL);
@@ -427,7 +435,7 @@ int seat_start(Seat *s) {
}
int seat_stop(Seat *s, bool force) {
- int r = 0;
+ int r;
assert(s);
@@ -437,9 +445,9 @@ int seat_stop(Seat *s, bool force) {
"SEAT_ID=%s", s->id,
LOG_MESSAGE("Removed seat %s.", s->id));
- seat_stop_sessions(s, force);
+ r = seat_stop_sessions(s, force);
- unlink(s->state_file);
+ (void) unlink(s->state_file);
seat_add_to_gc_queue(s);
if (s->started)
diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h
index 6d4506257..0446384ce 100644
--- a/src/login/logind-seat.h
+++ b/src/login/logind-seat.h
@@ -27,8 +27,10 @@ struct Seat {
LIST_FIELDS(Seat, gc_queue);
};
-Seat *seat_new(Manager *m, const char *id);
-void seat_free(Seat *s);
+int seat_new(Seat **ret, Manager *m, const char *id);
+Seat* seat_free(Seat *s);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Seat *, seat_free);
int seat_save(Seat *s);
int seat_load(Seat *s);
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
index 8a95fc7c7..e7f64fd61 100644
--- a/src/login/logind-session-dbus.c
+++ b/src/login/logind-session-dbus.c
@@ -689,6 +689,17 @@ int session_send_lock_all(Manager *m, bool lock) {
return r;
}
+#if 0 /// elogind does not support scope and service jobs
+static bool session_ready(Session *s) {
+ assert(s);
+
+ /* Returns true when the session is ready, i.e. all jobs we enqueued for it are done (regardless if successful or not) */
+
+ return !s->scope_job &&
+ !s->user->service_job;
+}
+#endif // 0
+
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 = -1;
@@ -696,21 +707,18 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
assert(s);
- /* This is called after the session scope and the user service
- * were successfully created, and finishes where
+ /* This is called after the session scope and the user service were successfully created, and finishes where
* bus_manager_create_session() left off. */
if (!s->create_message)
return 0;
#if 0 /// elogind does not support scope and service jobs
- if (!sd_bus_error_is_set(error) && (s->scope_job || s->user->service_job))
+ if (!sd_bus_error_is_set(error) && !session_ready(s))
return 0;
#endif // 0
- c = s->create_message;
- s->create_message = NULL;
-
+ c = TAKE_PTR(s->create_message);
if (error)
return sd_bus_reply_method_error(c, error);
@@ -718,8 +726,7 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
if (fifo_fd < 0)
return fifo_fd;
- /* Update the session state file before we notify the client
- * about the result. */
+ /* Update the session state file before we notify the client about the result. */
session_save(s);
#if 1 /// Additionally elogind saves the user state file
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index fedf58358..dce0f7377 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -5,6 +5,7 @@
#include <linux/kd.h>
#include <linux/vt.h>
#include <signal.h>
+#include <stdio_ext.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -24,7 +25,9 @@
#include "mkdir.h"
#include "parse-util.h"
#include "path-util.h"
+//#include "process-util.h"
#include "string-table.h"
+//#include "strv.h"
#include "terminal-util.h"
#include "user-util.h"
#include "util.h"
@@ -37,47 +40,52 @@
static void session_remove_fifo(Session *s);
-Session* session_new(Manager *m, const char *id) {
- Session *s;
+int session_new(Session **ret, Manager *m, const char *id) {
+ _cleanup_(session_freep) Session *s = NULL;
+ int r;
+ assert(ret);
assert(m);
assert(id);
- assert(session_id_valid(id));
- s = new0(Session, 1);
+ if (!session_id_valid(id))
+ return -EINVAL;
+
+ s = new(Session, 1);
if (!s)
- return NULL;
+ return -ENOMEM;
+
+ *s = (Session) {
+ .manager = m,
+ .fifo_fd = -1,
+ .vtfd = -1,
+ .audit_id = AUDIT_SESSION_INVALID,
+ .tty_validity = _TTY_VALIDITY_INVALID,
+ };
s->state_file = strappend("/run/systemd/sessions/", id);
if (!s->state_file)
- return mfree(s);
-
- s->devices = hashmap_new(&devt_hash_ops);
- if (!s->devices) {
- free(s->state_file);
- return mfree(s);
- }
+ return -ENOMEM;
s->id = basename(s->state_file);
- if (hashmap_put(m->sessions, s->id, s) < 0) {
- hashmap_free(s->devices);
- free(s->state_file);
- return mfree(s);
- }
+ s->devices = hashmap_new(&devt_hash_ops);
+ if (!s->devices)
+ return -ENOMEM;
- s->manager = m;
- s->fifo_fd = -1;
- s->vtfd = -1;
- s->audit_id = AUDIT_SESSION_INVALID;
+ r = hashmap_put(m->sessions, s->id, s);
+ if (r < 0)
+ return r;
- return s;
+ *ret = TAKE_PTR(s);
+ return 0;
}
-void session_free(Session *s) {
+Session* session_free(Session *s) {
SessionDevice *sd;
- assert(s);
+ if (!s)
+ return NULL;
if (s->in_gc_queue)
LIST_REMOVE(gc_queue, s->manager->session_gc_queue, s);
@@ -98,6 +106,8 @@ void session_free(Session *s) {
if (s->user->display == s)
s->user->display = NULL;
+
+ user_update_last_session_timer(s->user);
}
if (s->seat) {
@@ -110,10 +120,15 @@ void session_free(Session *s) {
LIST_REMOVE(sessions_by_seat, s->seat->sessions, s);
}
+#if 0 /// elogind does not support systemd units and scope_jobs
if (s->scope) {
hashmap_remove(s->manager->session_units, s->scope);
free(s->scope);
}
+#endif // 0
+
+ if (pid_is_valid(s->leader))
+ (void) hashmap_remove_value(s->manager->sessions_by_leader, PID_TO_PTR(s->leader), s);
#if 0 /// elogind does not support systemd scope_jobs
free(s->scope_job);
@@ -131,7 +146,8 @@ void session_free(Session *s) {
hashmap_remove(s->manager->sessions, s->id);
free(s->state_file);
- free(s);
+
+ return mfree(s);
}
void session_set_user(Session *s, User *u) {
@@ -140,6 +156,32 @@ void session_set_user(Session *s, User *u) {
s->user = u;
LIST_PREPEND(sessions_by_user, u->sessions, s);
+
+ user_update_last_session_timer(u);
+}
+
+int session_set_leader(Session *s, pid_t pid) {
+ int r;
+
+ assert(s);
+
+ if (!pid_is_valid(pid))
+ return -EINVAL;
+
+ if (s->leader == pid)
+ return 0;
+
+ r = hashmap_put(s->manager->sessions_by_leader, PID_TO_PTR(pid), s);
+ if (r < 0)
+ return r;
+
+ if (pid_is_valid(s->leader))
+ (void) hashmap_remove_value(s->manager->sessions_by_leader, PID_TO_PTR(s->leader), s);
+
+ s->leader = pid;
+ (void) audit_session_from_pid(pid, &s->audit_id);
+
+ return 1;
}
static void session_save_devices(Session *s, FILE *f) {
@@ -175,20 +217,21 @@ int session_save(Session *s) {
if (r < 0)
goto fail;
- assert(s->user);
-
- fchmod(fileno(f), 0644);
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ (void) fchmod(fileno(f), 0644);
fprintf(f,
"# This is private data. Do not parse.\n"
"UID="UID_FMT"\n"
"USER=%s\n"
"ACTIVE=%i\n"
+ "IS_DISPLAY=%i\n"
"STATE=%s\n"
"REMOTE=%i\n",
s->user->uid,
s->user->name,
session_is_active(s),
+ s->user->display == s,
session_state_to_string(session_get_state(s)),
s->remote);
@@ -214,6 +257,9 @@ int session_save(Session *s) {
if (s->tty)
fprintf(f, "TTY=%s\n", s->tty);
+ if (s->tty_validity >= 0)
+ fprintf(f, "TTY_VALIDITY=%s\n", tty_validity_to_string(s->tty_validity));
+
if (s->display)
fprintf(f, "DISPLAY=%s\n", s->display);
@@ -350,6 +396,7 @@ static int session_load_devices(Session *s, const char *devices) {
int session_load(Session *s) {
_cleanup_free_ char *remote = NULL,
*seat = NULL,
+ *tty_validity = NULL,
*vtnr = NULL,
*state = NULL,
*position = NULL,
@@ -361,7 +408,8 @@ int session_load(Session *s) {
*monotonic = NULL,
*controller = NULL,
*active = NULL,
- *devices = NULL;
+ *devices = NULL,
+ *is_display = NULL;
int k, r;
@@ -376,6 +424,7 @@ int session_load(Session *s) {
"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,
@@ -393,6 +442,7 @@ int session_load(Session *s) {
"CONTROLLER", &controller,
"ACTIVE", &active,
"DEVICES", &devices,
+ "IS_DISPLAY", &is_display,
NULL);
if (r < 0)
@@ -451,9 +501,27 @@ int session_load(Session *s) {
seat_claim_position(s->seat, s, npos);
}
+ if (tty_validity) {
+ TTYValidity v;
+
+ v = tty_validity_from_string(tty_validity);
+ if (v < 0)
+ log_debug("Failed to parse TTY validity: %s", tty_validity);
+ else
+ s->tty_validity = v;
+ }
+
if (leader) {
- if (parse_pid(leader, &s->leader) >= 0)
- (void) audit_session_from_pid(s->leader, &s->audit_id);
+ pid_t pid;
+
+ r = parse_pid(leader, &pid);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse leader PID of session: %s", leader);
+ else {
+ r = session_set_leader(s, pid);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set session leader PID, ignoring: %m");
+ }
}
if (type) {
@@ -500,6 +568,18 @@ int session_load(Session *s) {
s->was_active = k;
}
+ if (is_display) {
+ /* Note that when enumerating users are loaded before sessions, hence the display session to use is
+ * something we have to store along with the session and not the user, as in that case we couldn't
+ * apply it at the time we load the user. */
+
+ k = parse_boolean(is_display);
+ if (k < 0)
+ log_warning_errno(k, "Failed to parse IS_DISPLAY session property: %m");
+ else if (k > 0)
+ s->user->display = s;
+ }
+
if (controller) {
if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0) {
session_set_controller(s, controller, false, false);
@@ -525,7 +605,7 @@ int session_activate(Session *s) {
/* on seats with VTs, we let VTs manage session-switching */
if (seat_has_vts(s->seat)) {
- if (!s->vtnr)
+ if (s->vtnr == 0)
return -EOPNOTSUPP;
return chvt(s->vtnr);
@@ -549,17 +629,18 @@ int session_activate(Session *s) {
}
#if 0 /// UNNEEDED by elogind
-static int session_start_scope(Session *s, sd_bus_message *properties) {
+static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_error *error) {
int r;
assert(s);
assert(s->user);
if (!s->scope) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *scope, *job = 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();
@@ -572,21 +653,16 @@ static int session_start_scope(Session *s, sd_bus_message *properties) {
s->leader,
s->user->slice,
description,
- "systemd-logind.service",
- "systemd-user-sessions.service",
+ STRV_MAKE(s->user->runtime_dir_service, s->user->service), /* These two have StopWhenUnneeded= set, hence add a dep towards them */
+ STRV_MAKE("systemd-logind.service", "systemd-user-sessions.service", s->user->runtime_dir_service, s->user->service), /* And order us after some more */
+ s->user->home,
properties,
- &error,
- &job);
- if (r < 0) {
- log_error_errno(r, "Failed to start session scope %s: %s", scope, bus_error_message(&error, r));
- free(scope);
- return r;
- } else {
- s->scope = scope;
+ 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));
- free(s->scope_job);
- s->scope_job = job;
- }
+ s->scope = TAKE_PTR(scope);
}
if (s->scope)
@@ -615,7 +691,7 @@ static int session_start_cgroup(Session *s) {
}
#endif // 0
-int session_start(Session *s, sd_bus_message *properties) {
+int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
int r;
assert(s);
@@ -623,6 +699,9 @@ int session_start(Session *s, sd_bus_message *properties) {
if (!s->user)
return -ESTALE;
+ if (s->stopping)
+ return -EINVAL;
+
if (s->started)
return 0;
@@ -630,9 +709,8 @@ int session_start(Session *s, sd_bus_message *properties) {
if (r < 0)
return r;
- /* Create cgroup */
#if 0 /// elogind does its own session management
- r = session_start_scope(s, properties);
+ r = session_start_scope(s, properties, error);
#else
r = session_start_cgroup(s);
#endif // 0
@@ -687,21 +765,24 @@ static int session_stop_scope(Session *s, bool force) {
* that is left in the scope is "left-over". Informing systemd about this has the benefit that it will log
* when killing any processes left after this point. */
r = manager_abandon_scope(s->manager, s->scope, &error);
- if (r < 0)
+ if (r < 0) {
log_warning_errno(r, "Failed to abandon session scope, ignoring: %s", bus_error_message(&error, r));
+ sd_bus_error_free(&error);
+ }
+
+ s->scope_job = mfree(s->scope_job);
/* Optionally, let's kill everything that's left now. */
if (force || manager_shall_kill(s->manager, s->user->name)) {
- char *job = NULL;
- r = manager_stop_unit(s->manager, s->scope, &error, &job);
- if (r < 0)
- return log_error_errno(r, "Failed to stop session scope: %s", bus_error_message(&error, r));
+ r = manager_stop_unit(s->manager, s->scope, &error, &s->scope_job);
+ if (r < 0) {
+ if (force)
+ return log_error_errno(r, "Failed to stop session scope: %s", bus_error_message(&error, r));
- free(s->scope_job);
- s->scope_job = job;
+ log_warning_errno(r, "Failed to stop session scope, ignoring: %s", bus_error_message(&error, r));
+ }
} else {
- s->scope_job = mfree(s->scope_job);
/* With no killing, this session is allowed to persist in "closing" state indefinitely.
* Therefore session stop and session removal may be two distinct events.
@@ -739,8 +820,17 @@ int session_stop(Session *s, bool force) {
assert(s);
+ /* This is called whenever we begin with tearing down a session record. It's called in four cases: explicit API
+ * request via the bus (either directly for the session object or for the seat or user object this session
+ * belongs to; 'force' is true), or due to automatic GC (i.e. scope vanished; 'force' is false), or because the
+ * session FIFO saw an EOF ('force' is false), or because the release timer hit ('force' is false). */
+
if (!s->user)
return -ESTALE;
+ if (!s->started)
+ return 0;
+ if (s->stopping)
+ return 0;
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
@@ -839,7 +929,7 @@ int session_release(Session *s) {
return sd_event_add_time(s->manager->event,
&s->timer_event_source,
CLOCK_MONOTONIC,
- now(CLOCK_MONOTONIC) + RELEASE_USEC, 0,
+ usec_add(now(CLOCK_MONOTONIC), RELEASE_USEC), 0,
release_timeout_callback, s);
}
@@ -918,7 +1008,7 @@ int session_get_idle_hint(Session *s, dual_timestamp *t) {
/* For sessions with a leader but no explicitly configured
* tty, let's check the controlling tty of the leader */
- if (s->leader > 0) {
+ if (pid_is_valid(s->leader)) {
r = get_process_ctty_atime(s->leader, &atime);
if (r >= 0)
goto found_atime;
@@ -1002,7 +1092,8 @@ int session_create_fifo(Session *s) {
if (r < 0)
return r;
- if (asprintf(&s->fifo_path, "/run/systemd/sessions/%s.ref", s->id) < 0)
+ s->fifo_path = strjoin("/run/systemd/sessions/", s->id, ".ref");
+ if (!s->fifo_path)
return -ENOMEM;
if (mkfifo(s->fifo_path, 0600) < 0 && errno != EEXIST)
@@ -1014,7 +1105,6 @@ int session_create_fifo(Session *s) {
s->fifo_fd = open(s->fifo_path, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (s->fifo_fd < 0)
return -errno;
-
}
if (!s->fifo_event_source) {
@@ -1044,12 +1134,16 @@ static void session_remove_fifo(Session *s) {
s->fifo_fd = safe_close(s->fifo_fd);
if (s->fifo_path) {
- unlink(s->fifo_path);
+ (void) unlink(s->fifo_path);
s->fifo_path = mfree(s->fifo_path);
}
}
bool session_may_gc(Session *s, bool drop_not_started) {
+#if 0 /// UNNEEDED by elogind
+ int r;
+#endif // 0
+
assert(s);
if (drop_not_started && !s->started)
@@ -1064,11 +1158,25 @@ bool session_may_gc(Session *s, bool drop_not_started) {
}
#if 0 /// elogind supports neither scopes nor jobs
- if (s->scope_job && manager_job_is_active(s->manager, s->scope_job))
- return false;
+ if (s->scope_job) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- if (s->scope && manager_unit_is_active(s->manager, s->scope))
- return false;
+ r = manager_job_is_active(s->manager, s->scope_job, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", s->scope_job, bus_error_message(&error, r));
+ if (r != 0)
+ return false;
+ }
+
+ if (s->scope) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = manager_unit_is_active(s->manager, s->scope, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", s->scope, bus_error_message(&error, r));
+ if (r != 0)
+ return false;
+ }
#endif // 0
return true;
@@ -1395,3 +1503,11 @@ static const char* const kill_who_table[_KILL_WHO_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);
+
+static const char* const tty_validity_table[_TTY_VALIDITY_MAX] = {
+ [TTY_FROM_PAM] = "from-pam",
+ [TTY_FROM_UTMP] = "from-utmp",
+ [TTY_UTMP_INCONSISTENT] = "utmp-inconsistent",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(tty_validity, TTYValidity);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index 117f4683b..0bca7683a 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -46,6 +46,14 @@ enum KillWho {
_KILL_WHO_INVALID = -1
};
+typedef enum TTYValidity {
+ TTY_FROM_PAM,
+ TTY_FROM_UTMP,
+ TTY_UTMP_INCONSISTENT, /* may happen on ssh sessions with multiplexed TTYs */
+ _TTY_VALIDITY_MAX,
+ _TTY_VALIDITY_INVALID = -1,
+} TTYValidity;
+
struct Session {
Manager *manager;
@@ -60,8 +68,9 @@ struct Session {
dual_timestamp timestamp;
- char *tty;
char *display;
+ char *tty;
+ TTYValidity tty_validity;
bool remote;
char *remote_user;
@@ -99,6 +108,7 @@ struct Session {
sd_bus_message *create_message;
+ /* Set up when a client requested to release the session via the bus */
sd_event_source *timer_event_source;
char *controller;
@@ -111,9 +121,13 @@ struct Session {
LIST_FIELDS(Session, gc_queue);
};
-Session *session_new(Manager *m, const char *id);
-void session_free(Session *s);
+int session_new(Session **ret, Manager *m, const char *id);
+Session* session_free(Session *s);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Session *, session_free);
+
void session_set_user(Session *s, User *u);
+int session_set_leader(Session *s, pid_t pid);
bool session_may_gc(Session *s, bool drop_not_started);
void session_add_to_gc_queue(Session *s);
int session_activate(Session *s);
@@ -123,7 +137,7 @@ void 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_create_fifo(Session *s);
-int session_start(Session *s, sd_bus_message *properties);
+int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error);
int session_stop(Session *s, bool force);
int session_finalize(Session *s);
int session_release(Session *s);
@@ -157,6 +171,9 @@ SessionClass session_class_from_string(const char *s) _pure_;
const char *kill_who_to_string(KillWho k) _const_;
KillWho kill_who_from_string(const char *s) _pure_;
+const char* tty_validity_to_string(TTYValidity t) _const_;
+TTYValidity tty_validity_from_string(const char *s) _pure_;
+
int session_prepare_vt(Session *s);
void session_restore_vt(Session *s);
void session_leave_vt(Session *s);
diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c
index c662a26b9..9620fb0cf 100644
--- a/src/login/logind-user-dbus.c
+++ b/src/login/logind-user-dbus.c
@@ -109,7 +109,7 @@ static int property_get_idle_since_hint(
assert(reply);
assert(u);
- user_get_idle_hint(u, &t);
+ (void) user_get_idle_hint(u, &t);
k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
return sd_bus_message_append(reply, "t", k);
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index 38a15b224..78822c921 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -26,40 +26,54 @@
#include "special.h"
#include "stdio-util.h"
#include "string-table.h"
+//#include "strv.h"
#include "unit-name.h"
#include "user-util.h"
//#include "util.h"
/// Additional includes needed by elogind
#include "user-runtime-dir.h"
-int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name) {
+int user_new(User **ret,
+ Manager *m,
+ uid_t uid,
+ gid_t gid,
+ const char *name,
+ const char *home) {
+
_cleanup_(user_freep) User *u = NULL;
char lu[DECIMAL_STR_MAX(uid_t) + 1];
int r;
- assert(out);
+ assert(ret);
assert(m);
assert(name);
- u = new0(User, 1);
+ u = new(User, 1);
if (!u)
return -ENOMEM;
- u->manager = m;
- u->uid = uid;
- u->gid = gid;
- xsprintf(lu, UID_FMT, uid);
+ *u = (User) {
+ .manager = m,
+ .uid = uid,
+ .gid = gid,
+ .last_session_timestamp = USEC_INFINITY,
+ };
u->name = strdup(name);
if (!u->name)
return -ENOMEM;
+ u->home = strdup(home);
+ if (!u->home)
+ return -ENOMEM;
+
if (asprintf(&u->state_file, "/run/systemd/users/"UID_FMT, uid) < 0)
return -ENOMEM;
if (asprintf(&u->runtime_path, "/run/user/"UID_FMT, uid) < 0)
return -ENOMEM;
+ xsprintf(lu, UID_FMT, uid);
r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &u->slice);
if (r < 0)
return r;
@@ -68,10 +82,15 @@ int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name) {
if (r < 0)
return r;
+ r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_service);
+ if (r < 0)
+ return r;
+
r = hashmap_put(m->users, UID_TO_PTR(uid), u);
if (r < 0)
return r;
+#if 0 /// elogind does not support systemd units
r = hashmap_put(m->user_units, u->slice, u);
if (r < 0)
return r;
@@ -80,8 +99,12 @@ int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name) {
if (r < 0)
return r;
- *out = TAKE_PTR(u);
+ r = hashmap_put(m->user_units, u->runtime_dir_service, u);
+ if (r < 0)
+ return r;
+#endif // 0
+ *ret = TAKE_PTR(u);
return 0;
}
@@ -95,24 +118,32 @@ User *user_free(User *u) {
while (u->sessions)
session_free(u->sessions);
+#if 0 /// elogind does not support systemd units
if (u->service)
hashmap_remove_value(u->manager->user_units, u->service, u);
+ if (u->runtime_dir_service)
+ hashmap_remove_value(u->manager->user_units, u->runtime_dir_service, u);
+
if (u->slice)
hashmap_remove_value(u->manager->user_units, u->slice, u);
+#endif // 0
hashmap_remove_value(u->manager->users, UID_TO_PTR(u->uid), u);
+ (void) sd_event_source_unref(u->timer_event_source);
+
#if 0 /// elogind neither supports slice nor service jobs.
- u->slice_job = mfree(u->slice_job);
u->service_job = mfree(u->service_job);
#endif // 0
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);
u->name = mfree(u->name);
+ u->home = mfree(u->home);
return mfree(u);
}
@@ -139,9 +170,11 @@ static int user_save_internal(User *u) {
fprintf(f,
"# This is private data. Do not parse.\n"
"NAME=%s\n"
- "STATE=%s\n",
+ "STATE=%s\n" /* friendly user-facing state */
+ "STOPPING=%s\n", /* low-level state */
u->name,
- user_state_to_string(user_get_state(u)));
+ user_state_to_string(user_get_state(u)),
+ yes_no(u->stopping));
/* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */
if (u->runtime_path)
@@ -151,10 +184,7 @@ static int user_save_internal(User *u) {
if (u->service_job)
fprintf(f, "SERVICE_JOB=%s\n", u->service_job);
- if (u->slice_job)
- fprintf(f, "SLICE_JOB=%s\n", u->slice_job);
#endif // 0
-
if (u->display)
fprintf(f, "DISPLAY=%s\n", u->display->id);
@@ -165,6 +195,10 @@ static int user_save_internal(User *u) {
u->timestamp.realtime,
u->timestamp.monotonic);
+ if (u->last_session_timestamp != USEC_INFINITY)
+ fprintf(f, "LAST_SESSION_TIMESTAMP=" USEC_FMT "\n",
+ u->last_session_timestamp);
+
if (u->sessions) {
Session *i;
bool first;
@@ -278,120 +312,96 @@ int user_save(User *u) {
if (!u->started)
return 0;
- return user_save_internal (u);
+ return user_save_internal(u);
}
int user_load(User *u) {
- _cleanup_free_ char *display = NULL, *realtime = NULL, *monotonic = NULL;
- Session *s = NULL;
+ _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL;
int r;
assert(u);
r = parse_env_file(NULL, u->state_file, NEWLINE,
#if 0 /// elogind neither supports service nor slice jobs
- "SERVICE_JOB", &u->service_job,
- "SLICE_JOB", &u->slice_job,
+ "SERVICE_JOB", &u->service_job,
#endif // 0
- "DISPLAY", &display,
- "REALTIME", &realtime,
- "MONOTONIC", &monotonic,
+ "STOPPING", &stopping,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
+ "LAST_SESSION_TIMESTAMP", &last_session_timestamp,
NULL);
- if (r < 0) {
- if (r == -ENOENT)
- return 0;
-
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
return log_error_errno(r, "Failed to read %s: %m", u->state_file);
- }
-
- if (display)
- s = hashmap_get(u->manager->sessions, display);
- if (s && s->display && display_is_local(s->display))
- u->display = s;
+ if (stopping) {
+ r = parse_boolean(stopping);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping);
+ else
+ u->stopping = r;
+ }
if (realtime)
- timestamp_deserialize(realtime, &u->timestamp.realtime);
+ (void) timestamp_deserialize(realtime, &u->timestamp.realtime);
if (monotonic)
- timestamp_deserialize(monotonic, &u->timestamp.monotonic);
+ (void) timestamp_deserialize(monotonic, &u->timestamp.monotonic);
+ if (last_session_timestamp)
+ (void) timestamp_deserialize(last_session_timestamp, &u->last_session_timestamp);
- return r;
+ return 0;
}
-static int user_start_service(User *u) {
-#if 0 /// elogind can not ask systemd via dbus to start user services
+#if 0 /// elogind neither spawns systemd --user nor suports systemd units and services.
+static void user_start_service(User *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *job;
int r;
assert(u);
+ /* Start the service containing the "systemd --user" instance (user@.service). Note that we don't explicitly
+ * start the per-user slice or the systemd-runtime-dir@.service instance, as those are pulled in both by
+ * user@.service and the session scopes as dependencies. */
+
u->service_job = mfree(u->service_job);
- r = manager_start_unit(
- u->manager,
- u->service,
- &error,
- &job);
+ r = manager_start_unit(u->manager, u->service, &error, &u->service_job);
if (r < 0)
- /* we don't fail due to this, let's try to continue */
- log_error_errno(r, "Failed to start user service, ignoring: %s", bus_error_message(&error, r));
- else
- u->service_job = job;
-#else
- assert(u);
-
- hashmap_put(u->manager->user_units, u->service, u);
-#endif // 0
-
- return 0;
+ log_warning_errno(r, "Failed to start user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
}
+#endif // 0
int user_start(User *u) {
- int r;
-
assert(u);
if (u->started && !u->stopping)
return 0;
- /*
- * If u->stopping is set, the user is marked for removal and the slice
- * and service stop-jobs are queued. We have to clear that flag before
- * queing 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.
- * Note that we don't clear u->started, as we have no clue what state
- * the user is in on failure here. Hence, we pretend the user is
- * running so it will be properly taken down by GC. However, we clearly
- * return an error from user_start() in that case, so no further
- * reference to the user is taken.
- */
+ /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. We have to clear
+ * that flag before queing 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 0 /// elogind has to prepare the XDG_RUNTIME_DIR by itself
if (!u->started)
log_debug("Starting services for new user %s.", u->name);
-#else
- if (!u->started) {
- log_debug("Starting services for new user %s.", u->name);
- r = user_runtime_dir("start", u);
- if (r < 0)
- return r;
- }
+
+#if 1 /// elogind has to prepare the XDG_RUNTIME_DIR by itself
+ int r;
+ r = user_runtime_dir("start", u);
+ if (r < 0)
+ return r;
#endif // 1
- /* 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. */
+ /* 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);
- /* Spawn user systemd */
- r = user_start_service(u);
- if (r < 0)
- return r;
+#if 0 /// elogind does not spawn user instances of systemd
+ /* Start user@UID.service */
+ user_start_service(u);
+#endif // 0
if (!u->started) {
if (!dual_timestamp_is_set(&u->timestamp))
@@ -406,84 +416,59 @@ int user_start(User *u) {
return 0;
}
-#if 0 /// UNNEEDED by elogind
-static int user_stop_slice(User *u) {
+#if 0 /// elogind does not support user services and systemd units
+static void user_stop_service(User *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *job;
int r;
assert(u);
+ assert(u->service);
- r = manager_stop_unit(u->manager, u->slice, &error, &job);
- if (r < 0) {
- log_error("Failed to stop user slice: %s", bus_error_message(&error, r));
- return r;
- }
-
- free(u->slice_job);
- u->slice_job = job;
-
- return r;
-}
-
-static int user_stop_service(User *u) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *job;
- int r;
+ /* 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. */
- assert(u);
-
- r = manager_stop_unit(u->manager, u->service, &error, &job);
- if (r < 0) {
- log_error("Failed to stop user service: %s", bus_error_message(&error, r));
- return r;
- }
+ u->service_job = mfree(u->service_job);
- free_and_replace(u->service_job, job);
- return r;
+ r = manager_stop_unit(u->manager, u->service, &error, &u->service_job);
+ if (r < 0)
+ log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
}
#endif // 0
int user_stop(User *u, bool force) {
Session *s;
- int r = 0, k;
+ int r = 0;
assert(u);
- /* Stop jobs have already been queued */
- if (u->stopping) {
+ /* This is called whenever we begin with tearing down a user record. It's called in two cases: explicit API
+ * request to do so via the bus (in which case 'force' is true) and automatically due to GC, if there's no
+ * session left pinning it (in which case 'force' is false). Note that this just initiates tearing down of the
+ * user, the User object will remain in memory until user_finalize() is called, see below. */
+
+ if (!u->started)
+ return 0;
+
+ if (u->stopping) { /* Stop jobs have already been queued */
user_save(u);
-#if 1 /// elogind must queue this user again
- user_add_to_gc_queue(u);
-#endif // 1
- return r;
+ return 0;
}
LIST_FOREACH(sessions_by_user, s, u->sessions) {
+ int k;
+
k = session_stop(s, force);
if (k < 0)
r = k;
}
- /* Kill systemd */
#if 0 /// elogind does not support service or slice jobs
- k = user_stop_service(u);
- if (k < 0)
- r = k;
-
- /* Kill cgroup */
- k = user_stop_slice(u);
- if (k < 0)
- r = k;
+ user_stop_service(u);
#endif // 0
u->stopping = true;
user_save(u);
-#if 1 /// elogind must queue this user again
- user_add_to_gc_queue(u);
-#endif // 1
-
return r;
}
@@ -493,6 +478,9 @@ int user_finalize(User *u) {
assert(u);
+ /* Called when the user is really ready to be freed, i.e. when all unit stop jobs and suchlike for it are
+ * done. This is called as a result of an earlier user_done() when all jobs are completed. */
+
if (u->started)
log_debug("User %s logged out.", u->name);
@@ -508,7 +496,6 @@ int user_finalize(User *u) {
if (k < 0)
r = k;
#endif // 1
-
/* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs
* are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to
* system components. Since enable RemoveIPC= globally for all users, we need to be a bit careful with such
@@ -521,7 +508,7 @@ int user_finalize(User *u) {
r = k;
}
- unlink(u->state_file);
+ (void) unlink(u->state_file);
user_add_to_gc_queue(u);
if (u->started) {
@@ -577,11 +564,44 @@ int user_check_linger_file(User *u) {
return -ENOMEM;
p = strjoina("/var/lib/elogind/linger/", cc);
+ if (access(p, F_OK) < 0) {
+ if (errno != ENOENT)
+ return -errno;
+
+ return false;
+ }
+
+ return true;
+}
+
+#if 0 /// elogind does not support systemd units
+static bool user_unit_active(User *u) {
+ const char *i;
+ int r;
+
+ assert(u->service);
+ assert(u->runtime_dir_service);
+ assert(u->slice);
+
+ FOREACH_STRING(i, u->service, u->runtime_dir_service, u->slice) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = manager_unit_is_active(u->manager, i, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", u->service, bus_error_message(&error, r));
+ if (r != 0)
+ return true;
+ }
- return access(p, F_OK) >= 0;
+ return false;
}
+#endif // 0
bool user_may_gc(User *u, bool drop_not_started) {
+#if 0 /// UNNEEDED by elogind
+ int r;
+#endif // 0
+
assert(u);
if (drop_not_started && !u->started)
@@ -590,15 +610,39 @@ bool user_may_gc(User *u, bool drop_not_started) {
if (u->sessions)
return false;
+ if (u->last_session_timestamp != USEC_INFINITY) {
+ /* All sessions have been closed. Let's see if we shall leave the user record around for a bit */
+
+ if (u->manager->user_stop_delay == USEC_INFINITY)
+ return false; /* Leave it around forever! */
+ if (u->manager->user_stop_delay > 0 &&
+ now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, u->manager->user_stop_delay))
+ return false; /* Leave it around for a bit longer. */
+ }
+
+ /* Is this a user that shall stay around forever ("linger")? Before we say "no" to GC'ing for lingering users, let's check
+ * if any of the three units that we maintain for this user is still around. If none of them is,
+ * there's no need to keep this user around even if lingering is enabled. */
+#if 0 /// elogind does not support systemd units
+ if (user_check_linger_file(u) > 0 && user_unit_active(u))
+#else
if (user_check_linger_file(u) > 0)
+#endif // 0
return false;
#if 0 /// elogind neither supports service nor slice jobs
- if (u->slice_job && manager_job_is_active(u->manager, u->slice_job))
- return false;
+ /* Check if our job is still pending */
+ if (u->service_job) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- if (u->service_job && manager_job_is_active(u->manager, u->service_job))
- return false;
+ r = manager_job_is_active(u->manager, u->service_job, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", u->service_job, bus_error_message(&error, r));
+ if (r != 0)
+ return false;
+ }
+ /* Note that we don't care if the three units we manage for each user object are up or not, as we are managing
+ * their state rather than tracking it. */
#endif // 0
return true;
@@ -623,7 +667,7 @@ UserState user_get_state(User *u) {
return USER_CLOSING;
#if 0 /// elogind neither supports service nor slice jobs.
- if (!u->started || u->slice_job || u->service_job)
+ if (!u->started || u->service_job)
#else
if (!u->started)
#endif // 0
@@ -645,7 +689,11 @@ UserState user_get_state(User *u) {
return all_closing ? USER_CLOSING : USER_ONLINE;
}
+#if 0 /// elogind does not support systemd units
+ if (user_check_linger_file(u) > 0 && user_unit_active(u))
+#else
if (user_check_linger_file(u) > 0)
+#endif // 0
return USER_LINGERING;
return USER_CLOSING;
@@ -673,11 +721,10 @@ int user_kill(User *u, int signo) {
}
static bool elect_display_filter(Session *s) {
- /* Return true if the session is a candidate for the user’s ‘primary
- * session’ or ‘display’. */
+ /* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */
assert(s);
- return (s->class == SESSION_USER && !s->stopping);
+ return s->class == SESSION_USER && s->started && !s->stopping;
}
static int elect_display_compare(Session *s1, Session *s2) {
@@ -723,9 +770,8 @@ void user_elect_display(User *u) {
assert(u);
- /* This elects a primary session for each user, which we call
- * the "display". We try to keep the assignment stable, but we
- * "upgrade" to better choices. */
+ /* This elects a primary session for each user, which we call the "display". We try to keep the assignment
+ * stable, but we "upgrade" to better choices. */
log_debug("Electing new display for user %s", u->name);
LIST_FOREACH(sessions_by_user, s, u->sessions) {
@@ -741,6 +787,59 @@ void user_elect_display(User *u) {
}
}
+static int user_stop_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) {
+ User *u = userdata;
+
+ assert(u);
+ user_add_to_gc_queue(u);
+
+ return 0;
+}
+
+void user_update_last_session_timer(User *u) {
+ int r;
+
+ assert(u);
+
+ if (u->sessions) {
+ /* There are sessions, turn off the timer */
+ u->last_session_timestamp = USEC_INFINITY;
+ u->timer_event_source = sd_event_source_unref(u->timer_event_source);
+ return;
+ }
+
+ if (u->last_session_timestamp != USEC_INFINITY)
+ return; /* Timer already started */
+
+ u->last_session_timestamp = now(CLOCK_MONOTONIC);
+
+ assert(!u->timer_event_source);
+
+ if (u->manager->user_stop_delay == 0 || u->manager->user_stop_delay == USEC_INFINITY)
+ return;
+
+ if (sd_event_get_state(u->manager->event) == SD_EVENT_FINISHED) {
+ log_debug("Not allocating user stop timeout, since we are already exiting.");
+ return;
+ }
+
+ r = sd_event_add_time(u->manager->event,
+ &u->timer_event_source,
+ CLOCK_MONOTONIC,
+ usec_add(u->last_session_timestamp, u->manager->user_stop_delay), 0,
+ user_stop_timeout_callback, u);
+ if (r < 0)
+ log_warning_errno(r, "Failed to enqueue user stop event source, ignoring: %m");
+
+ if (DEBUG_LOGGING) {
+ char s[FORMAT_TIMESPAN_MAX];
+
+ log_debug("Last session of user '%s' logged out, terminating user context in %s.",
+ u->name,
+ format_timespan(s, sizeof(s), u->manager->user_stop_delay, USEC_PER_MSEC));
+ }
+}
+
static const char* const user_state_table[_USER_STATE_MAX] = {
[USER_OFFLINE] = "offline",
[USER_OPENING] = "opening",
@@ -813,7 +912,7 @@ int config_parse_compat_user_tasks_max(
log_syntax(unit, LOG_NOTICE, filename, line, 0,
"Support for option %s= has been removed.",
lvalue);
- log_info("Hint: try creating /etc/elogind/system/user-.slice/50-limits.conf with:\n"
+ log_info("Hint: try creating /etc/elogind/system/user-.slice.d/50-limits.conf with:\n"
" [Slice]\n"
" TasksMax=%s",
rvalue);
diff --git a/src/login/logind-user.h b/src/login/logind-user.h
index afb6f4b1a..ca404f31b 100644
--- a/src/login/logind-user.h
+++ b/src/login/logind-user.h
@@ -23,29 +23,36 @@ struct User {
uid_t uid;
gid_t gid;
char *name;
+ char *home;
char *state_file;
char *runtime_path;
- char *slice;
- char *service;
+
+ char *slice; /* user-UID.slice */
+ char *service; /* user@UID.service */
+ char *runtime_dir_service; /* user-runtime-dir@UID.service */
#if 0 /// UNNEEDED by elogind
char *service_job;
- char *slice_job;
#endif // 0
Session *display;
- dual_timestamp timestamp;
+ dual_timestamp timestamp; /* When this User object was 'started' the first time */
+ usec_t last_session_timestamp; /* When the number of sessions of this user went from 1 to 0 the last time */
+
+ /* Set up when the last session of the user logs out */
+ sd_event_source *timer_event_source;
bool in_gc_queue:1;
- bool started:1;
- bool stopping:1;
+
+ bool started:1; /* Whenever the user being started, has been started or is being stopped again. */
+ bool stopping:1; /* Whenever the user is being stopped or has been stopped. */
LIST_HEAD(Session, sessions);
LIST_FIELDS(User, gc_queue);
};
-int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name);
+int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name, const char *home);
User *user_free(User *u);
DEFINE_TRIVIAL_CLEANUP_FUNC(User *, user_free);
@@ -62,6 +69,7 @@ int user_load(User *u);
int user_kill(User *u, int signo);
int user_check_linger_file(User *u);
void user_elect_display(User *u);
+void user_update_last_session_timer(User *u);
extern const sd_bus_vtable user_vtable[];
int user_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
diff --git a/src/login/logind-utmp.c b/src/login/logind-utmp.c
index f18147688..d3489f5f0 100644
--- a/src/login/logind-utmp.c
+++ b/src/login/logind-utmp.c
@@ -61,7 +61,7 @@ bool logind_wall_tty_filter(const char *tty, void *userdata) {
static int warn_wall(Manager *m, usec_t n) {
char date[FORMAT_TIMESTAMP_MAX] = {};
- _cleanup_free_ char *l = NULL;
+ _cleanup_free_ char *l = NULL, *username = NULL;
usec_t left;
int r;
@@ -83,8 +83,8 @@ static int warn_wall(Manager *m, usec_t n) {
return 0;
}
- utmp_wall(l, uid_to_name(m->scheduled_shutdown_uid),
- m->scheduled_shutdown_tty, logind_wall_tty_filter, m);
+ username = uid_to_name(m->scheduled_shutdown_uid);
+ utmp_wall(l, username, m->scheduled_shutdown_tty, logind_wall_tty_filter, m);
return 1;
}
diff --git a/src/login/logind.c b/src/login/logind.c
index c7acb05cb..558733b30 100644
--- a/src/login/logind.c
+++ b/src/login/logind.c
@@ -45,28 +45,35 @@ static int manager_new(Manager **ret) {
assert(ret);
- m = new0(Manager, 1);
+ m = new(Manager, 1);
if (!m)
return -ENOMEM;
- m->console_active_fd = -1;
-#if 0 /// UNNEEDED by elogind
- m->reserve_vt_fd = -1;
+ *m = (Manager) {
+ .console_active_fd = -1,
+#if 0 /// elogind does not support autospawning of vts
+ .reserve_vt_fd = -1,
#endif // 0
+ };
m->idle_action_not_before_usec = now(CLOCK_MONOTONIC);
m->devices = hashmap_new(&string_hash_ops);
m->seats = hashmap_new(&string_hash_ops);
m->sessions = hashmap_new(&string_hash_ops);
+ m->sessions_by_leader = hashmap_new(NULL);
m->users = hashmap_new(NULL);
m->inhibitors = hashmap_new(&string_hash_ops);
m->buttons = hashmap_new(&string_hash_ops);
+#if 0 /// elogind does not support units
m->user_units = hashmap_new(&string_hash_ops);
m->session_units = hashmap_new(&string_hash_ops);
- if (!m->devices || !m->seats || !m->sessions || !m->users || !m->inhibitors || !m->buttons || !m->user_units || !m->session_units)
+ if (!m->devices || !m->seats || !m->sessions || !m->sessions_by_leader || !m->users || !m->inhibitors || !m->buttons || !m->user_units || !m->session_units)
+#else
+ if (!m->devices || !m->seats || !m->sessions || !m->sessions_by_leader || !m->users || !m->inhibitors || !m->buttons)
+#endif // 0
return -ENOMEM;
#if 1 /// elogind needs some more data
@@ -82,6 +89,7 @@ static int manager_new(Manager **ret) {
if (r < 0)
return r;
+#if 0 /// elogind uses its own signal handler, installed at elogind_manager_startup()
r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
if (r < 0)
return r;
@@ -89,6 +97,7 @@ static int manager_new(Manager **ret) {
r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
if (r < 0)
return r;
+#endif // 0
(void) sd_event_set_watchdog(m->event, true);
@@ -130,12 +139,15 @@ static Manager* manager_unref(Manager *m) {
hashmap_free(m->devices);
hashmap_free(m->seats);
hashmap_free(m->sessions);
+ hashmap_free(m->sessions_by_leader);
hashmap_free(m->users);
hashmap_free(m->inhibitors);
hashmap_free(m->buttons);
+#if 0 /// elogind does not support systemd units.
hashmap_free(m->user_units);
hashmap_free(m->session_units);
+#endif // 0
sd_event_source_unref(m->idle_action_event_source);
sd_event_source_unref(m->inhibit_timeout_source);
@@ -150,6 +162,10 @@ static Manager* manager_unref(Manager *m) {
sd_event_source_unref(m->udev_button_event_source);
sd_event_source_unref(m->lid_switch_ignore_event_source);
+#if ENABLE_UTMP
+ sd_event_source_unref(m->utmp_event_source);
+#endif
+
safe_close(m->console_active_fd);
udev_monitor_unref(m->udev_seat_monitor);
@@ -852,28 +868,28 @@ static int manager_connect_console(Manager *m) {
assert(m);
assert(m->console_active_fd < 0);
- /* On certain architectures (S390 and Xen, and containers),
- /dev/tty0 does not exist, so don't fail if we can't open
- it. */
+ /* On certain systems (such as S390, Xen, and containers) /dev/tty0 does not exist (as there is no VC), so
+ * don't fail if we can't open it. */
+
if (access("/dev/tty0", F_OK) < 0)
return 0;
m->console_active_fd = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (m->console_active_fd < 0) {
- /* On some systems the device node /dev/tty0 may exist
- * even though /sys/class/tty/tty0 does not. */
- if (errno == ENOENT)
+ /* On some systems /dev/tty0 may exist even though /sys/class/tty/tty0 does not. These are broken, but
+ * common. Let's complain but continue anyway. */
+ if (errno == ENOENT) {
+ log_warning_errno(errno, "System has /dev/tty0 but not /sys/class/tty/tty0/active which is broken, ignoring: %m");
return 0;
+ }
return log_error_errno(errno, "Failed to open /sys/class/tty/tty0/active: %m");
}
r = sd_event_add_io(m->event, &m->console_active_event_source, m->console_active_fd, 0, manager_dispatch_console, m);
- if (r < 0) {
- log_error("Failed to watch foreground console");
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch foreground console: %m");
/*
* SIGRTMIN is used as global VT-release signal, SIGRTMIN + 1 is used
@@ -892,7 +908,7 @@ static int manager_connect_console(Manager *m) {
r = sd_event_add_signal(m->event, NULL, SIGRTMIN, manager_vt_switch, m);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to subscribe to signal: %m");
return 0;
}
@@ -1018,13 +1034,13 @@ static void manager_gc(Manager *m, bool drop_not_started) {
/* First, if we are not closing yet, initiate stopping */
if (session_may_gc(session, drop_not_started) &&
session_get_state(session) != SESSION_CLOSING)
- session_stop(session, false);
+ (void) session_stop(session, false);
/* Normally, this should make the session referenced
* again, if it doesn't then let's get rid of it
* immediately */
if (session_may_gc(session, drop_not_started)) {
- session_finalize(session);
+ (void) session_finalize(session);
session_free(session);
}
}
@@ -1035,11 +1051,11 @@ static void manager_gc(Manager *m, bool drop_not_started) {
/* First step: queue stop jobs */
if (user_may_gc(user, drop_not_started))
- user_stop(user, false);
+ (void) user_stop(user, false);
/* Second step: finalize user */
if (user_may_gc(user, drop_not_started)) {
- user_finalize(user);
+ (void) user_finalize(user);
user_free(user);
}
}
@@ -1137,9 +1153,15 @@ static int manager_startup(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to register SIGHUP handler: %m");
-#if 1 /// elogind needs some extra preparations before connecting...
- elogind_manager_startup(m);
+#if 1 /// install elogind specific signal handlers
+ r = elogind_manager_startup(m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register elogind signal handlers: %m");
#endif // 1
+
+ /* Connect to utmp */
+ manager_connect_utmp(m);
+
/* Connect to console */
r = manager_connect_console(m);
if (r < 0)
@@ -1197,15 +1219,18 @@ static int manager_startup(Manager *m) {
manager_reserve_vt(m);
#endif // 0
+ /* Read in utmp if it exists */
+ manager_read_utmp(m);
+
/* And start everything */
HASHMAP_FOREACH(seat, m->seats, i)
- seat_start(seat);
+ (void) seat_start(seat);
HASHMAP_FOREACH(user, m->users, i)
- user_start(user);
+ (void) user_start(user);
HASHMAP_FOREACH(session, m->sessions, i)
- session_start(session, NULL);
+ (void) session_start(session, NULL, NULL);
HASHMAP_FOREACH(inhibitor, m->inhibitors, i)
inhibitor_start(inhibitor);
@@ -1307,7 +1332,11 @@ int main(int argc, char *argv[]) {
return log_error_errno(r, "Failed to create /run/systemd/machines : %m");
#endif // 0
+#if 0 /// elogind also blocks SIGQUIT, and installs a signal handler for it
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, -1) >= 0);
+#else
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, SIGQUIT, -1) >= 0);
+#endif // 0
r = manager_new(&m);
if (r < 0) {
diff --git a/src/login/logind.h b/src/login/logind.h
index 3f17d5d6d..6db015e82 100644
--- a/src/login/logind.h
+++ b/src/login/logind.h
@@ -37,6 +37,7 @@ struct Manager {
Hashmap *devices;
Hashmap *seats;
Hashmap *sessions;
+ Hashmap *sessions_by_leader;
Hashmap *users;
Hashmap *inhibitors;
Hashmap *buttons;
@@ -54,6 +55,10 @@ struct Manager {
sd_event_source *udev_vcsa_event_source;
sd_event_source *udev_button_event_source;
+#if ENABLE_UTMP
+ sd_event_source *utmp_event_source;
+#endif
+
#if 0 /// elogind does not support autospawning of vts
int console_active_fd;
@@ -89,10 +94,13 @@ struct Manager {
unsigned long session_counter;
unsigned long inhibit_counter;
+#if 0 /// elogind does not support units
Hashmap *session_units;
Hashmap *user_units;
+#endif // 0
usec_t inhibit_delay_max;
+ usec_t user_stop_delay;
/* If an action is currently being executed or is delayed,
* this is != 0 and encodes what is being done */
@@ -171,7 +179,7 @@ int manager_add_device(Manager *m, const char *sysfs, bool master, Device **_dev
int manager_add_button(Manager *m, const char *name, Button **_button);
int manager_add_seat(Manager *m, const char *id, Seat **_seat);
int manager_add_session(Manager *m, const char *id, Session **_session);
-int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user);
+int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, const char *home, User **_user);
int manager_add_user_by_name(Manager *m, const char *name, User **_user);
int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user);
int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **_inhibitor);
@@ -194,6 +202,10 @@ bool manager_is_docked_or_external_displays(Manager *m);
bool manager_is_on_external_power(void);
bool manager_all_buttons_ignored(Manager *m);
+int manager_read_utmp(Manager *m);
+void manager_connect_utmp(Manager *m);
+void manager_reconnect_utmp(Manager *m);
+
extern const sd_bus_vtable manager_vtable[];
#if 0 /// UNNEEDED by elogind
@@ -212,13 +224,13 @@ int bus_manager_shutdown_or_sleep_now_or_later(Manager *m, HandleAction action,
int manager_send_changed(Manager *manager, const char *property, ...) _sentinel_;
#if 0 /// UNNEEDED by elogind
-int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, const char *after, const char *after2, sd_bus_message *more_properties, sd_bus_error *error, char **job);
+int manager_start_scope(Manager *manager, const char *scope, pid_t pid, 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, sd_bus_error *error, char **job);
int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error);
int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, sd_bus_error *error);
-int manager_unit_is_active(Manager *manager, const char *unit);
-int manager_job_is_active(Manager *manager, const char *path);
+int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *error);
+int manager_job_is_active(Manager *manager, const char *path, sd_bus_error *error);
#endif // 0
/* gperf lookup function */
diff --git a/src/login/meson.build b/src/login/meson.build
index 0eac0d3cc..81f39e1ea 100644
--- a/src/login/meson.build
+++ b/src/login/meson.build
@@ -83,7 +83,6 @@ loginctl_sources = files('''
#if 0 /// UNNEEDED by elogind
# user_runtime_dir_sources = files('''
# user-runtime-dir.c
-# logind.h
# '''.split())
#endif // 0
@@ -125,10 +124,6 @@ loginctl_sources += files('''
install_data('70-power-switch.rules', install_dir : udevrulesdir)
- if conf.get('HAVE_ACL') == 1
- install_data('70-uaccess.rules', install_dir : udevrulesdir)
- endif
-
seat_rules = configure_file(
input : '71-seat.rules.in',
output : '71-seat.rules',
@@ -142,6 +137,14 @@ loginctl_sources += files('''
output : '73-seat-late.rules.m4',
configuration : substs)
#endif // 1
+ custom_target(
+ '70-uaccess.rules',
+ input : '70-uaccess.rules.m4',
+ output: '70-uaccess.rules',
+ command : [meson_apply_m4, config_h, '@INPUT@'],
+ capture : true,
+ install : conf.get('HAVE_ACL') == 1,
+ install_dir : udevrulesdir)
custom_target(
'73-seat-late.rules',
diff --git a/src/login/org.freedesktop.login1.policy b/src/login/org.freedesktop.login1.policy
index f1d1f956d..78bee24b0 100644
--- a/src/login/org.freedesktop.login1.policy
+++ b/src/login/org.freedesktop.login1.policy
@@ -343,7 +343,7 @@
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
- <allow_active>auth_admin_keep</allow_active>
+ <allow_active>yes</allow_active>
</defaults>
</action>
diff --git a/src/login/pam_elogind.c b/src/login/pam_elogind.c
index 88c5705ef..c4550d35b 100644
--- a/src/login/pam_elogind.c
+++ b/src/login/pam_elogind.c
@@ -130,7 +130,7 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_
r = socket_from_display(display, &p);
if (r < 0)
return r;
- strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
+ strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path));
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (fd < 0)
@@ -160,40 +160,6 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_
return 0;
}
-static int export_legacy_dbus_address(
- pam_handle_t *handle,
- uid_t uid,
- const char *runtime) {
-
- _cleanup_free_ char *s = NULL;
- int r = PAM_BUF_ERR;
-
- /* FIXME: We *really* should move the access() check into the
- * daemons that spawn dbus-daemon, instead of forcing
- * DBUS_SESSION_BUS_ADDRESS= here. */
-
- s = strjoin(runtime, "/bus");
- if (!s)
- goto error;
-
- if (access(s, F_OK) < 0)
- return PAM_SUCCESS;
-
- s = mfree(s);
- if (asprintf(&s, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
- goto error;
-
- r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
- if (r != PAM_SUCCESS)
- goto error;
-
- return PAM_SUCCESS;
-
-error:
- pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
- return r;
-}
-
static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
uint64_t val;
int r;
@@ -274,6 +240,36 @@ static int append_session_cg_weight(pam_handle_t *handle, sd_bus_message *m, con
return 0;
}
+static bool validate_runtime_directory(pam_handle_t *handle, const char *path, uid_t uid) {
+ struct stat st;
+
+ assert(path);
+
+ /* Just some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually set
+ * up properly for us. */
+
+ if (lstat(path, &st) < 0) {
+ pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path);
+ goto fail;
+ }
+
+ if (st.st_uid != uid) {
+ pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid);
+ goto fail;
+ }
+
+ return true;
+
+fail:
+ pam_syslog(handle, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order.");
+ return false;
+}
+
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
int flags,
@@ -334,16 +330,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (asprintf(&rt, "/run/user/"UID_FMT, pw->pw_uid) < 0)
return PAM_BUF_ERR;
- r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
- return r;
+ if (validate_runtime_directory(handle, rt, pw->pw_uid)) {
+ r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
+ if (r != PAM_SUCCESS) {
+ pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
+ return r;
+ }
}
- r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
- if (r != PAM_SUCCESS)
- return r;
-
return PAM_SUCCESS;
}
@@ -381,28 +375,32 @@ _public_ PAM_EXTERN int pam_sm_open_session(
tty = strempty(tty);
if (strchr(tty, ':')) {
- /* A tty with a colon is usually an X11 display,
- * placed there to show up in utmp. We rearrange
- * things and don't pretend that an X display was a
- * tty. */
-
+ /* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
+ * and don't pretend that an X display was a tty. */
if (isempty(display))
display = tty;
tty = NULL;
+
} else if (streq(tty, "cron")) {
- /* cron has been setting PAM_TTY to "cron" for a very
- * long time and it probably shouldn't stop doing that
- * for compatibility reasons. */
+ /* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but
+ * probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set
+ * (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked
+ * off processes.) */
type = "unspecified";
class = "background";
tty = NULL;
+
} else if (streq(tty, "ssh")) {
- /* ssh has been setting PAM_TTY to "ssh" for a very
- * long time and probably shouldn't stop doing that
- * for compatibility reasons. */
+ /* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
+ * details look for "PAM_TTY_KLUDGE" in the openssh sources). */
type ="tty";
class = "user";
- tty = NULL;
+ tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
+ * associated with a pty — won't be tracked by their tty in logind. This is because ssh
+ * does the PAM session registration early for new connections, and registers a pty only
+ * much later (this is because it doesn't know yet if it needs one at all, as whether to
+ * register a pty or not is negotiated much later in the protocol). */
+
} else
/* Chop off leading /dev prefix that some clients specify, but others do not. */
tty = skip_dev_prefix(tty);
@@ -472,7 +470,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
r = sd_bus_message_append(m, "uusssssussbss",
(uint32_t) pw->pw_uid,
- (uint32_t) getpid_cached(),
+ 0,
service,
type,
class,
@@ -561,15 +559,13 @@ _public_ PAM_EXTERN int pam_sm_open_session(
* in privileged apps clobbering the runtime directory
* unnecessarily. */
- r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
- return r;
+ if (validate_runtime_directory(handle, runtime_path, pw->pw_uid)) {
+ r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
+ if (r != PAM_SUCCESS) {
+ pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
+ return r;
+ }
}
-
- r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
- if (r != PAM_SUCCESS)
- return r;
}
if (!isempty(seat)) {
diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c
index 3cd5afee3..aff039bc7 100644
--- a/src/login/user-runtime-dir.c
+++ b/src/login/user-runtime-dir.c
@@ -3,13 +3,16 @@
#include <stdint.h>
#include <sys/mount.h>
+//#include "sd-bus.h"
+
+//#include "bus-error.h"
#include "fs-util.h"
#include "label.h"
-//#include "logind.h"
#include "mkdir.h"
#include "mount-util.h"
#include "path-util.h"
#include "rm-rf.h"
+//#include "selinux-util.h"
#include "smack-util.h"
#include "stdio-util.h"
#include "string-util.h"
@@ -19,22 +22,29 @@
#include "user-runtime-dir.h"
#if 0 /// UNNEEDED by elogind
-static int gather_configuration(size_t *runtime_dir_size) {
- Manager m = {};
+static int acquire_runtime_dir_size(uint64_t *ret) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
int r;
- manager_reset_config(&m);
+ r = sd_bus_default_system(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to system bus: %m");
- r = manager_parse_config_file(&m);
+ r = sd_bus_get_property_trivial(bus, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "RuntimeDirectorySize", &error, 't', ret);
if (r < 0)
- log_warning_errno(r, "Failed to parse logind.conf: %m");
+ return log_error_errno(r, "Failed to acquire runtime directory size: %s", bus_error_message(&error, r));
- *runtime_dir_size = m.runtime_dir_size;
return 0;
}
#endif // 0
-static int user_mkdir_runtime_path(const char *runtime_path, uid_t uid, gid_t gid, size_t runtime_dir_size) {
+static int user_mkdir_runtime_path(
+ const char *runtime_path,
+ uid_t uid,
+ gid_t gid,
+ uint64_t runtime_dir_size) {
+
int r;
assert(runtime_path);
@@ -52,10 +62,10 @@ static int user_mkdir_runtime_path(const char *runtime_path, uid_t uid, gid_t gi
char options[sizeof("mode=0700,uid=,gid=,size=,smackfsroot=*")
+ DECIMAL_STR_MAX(uid_t)
+ DECIMAL_STR_MAX(gid_t)
- + DECIMAL_STR_MAX(size_t)];
+ + DECIMAL_STR_MAX(uint64_t)];
xsprintf(options,
- "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%zu%s",
+ "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%" PRIu64 "%s",
uid, gid, runtime_dir_size,
mac_smack_use() ? ",smackfsroot=*" : "");
@@ -99,27 +109,42 @@ static int user_remove_runtime_path(const char *runtime_path) {
r = rm_rf(runtime_path, 0);
if (r < 0)
- log_error_errno(r, "Failed to remove runtime directory %s (before unmounting): %m", runtime_path);
+ log_debug_errno(r, "Failed to remove runtime directory %s (before unmounting), ignoring: %m", runtime_path);
- /* Ignore cases where the directory isn't mounted, as that's
- * quite possible, if we lacked the permissions to mount
- * something */
+ /* Ignore cases where the directory isn't mounted, as that's quite possible, if we lacked the permissions to
+ * mount something */
r = umount2(runtime_path, MNT_DETACH);
if (r < 0 && !IN_SET(errno, EINVAL, ENOENT))
- log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", runtime_path);
+ log_debug_errno(errno, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path);
r = rm_rf(runtime_path, REMOVE_ROOT);
- if (r < 0)
- log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", runtime_path);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", runtime_path);
- return r;
+ return 0;
}
#if 0 /// having a User instance, elogind can ask its manager directly.
-static int do_mount(const char *runtime_path, uid_t uid, gid_t gid) {
- size_t runtime_dir_size;
+static int do_mount(const char *user) {
+ char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
+ uint64_t runtime_dir_size;
+ uid_t uid;
+ gid_t gid;
+ int r;
- assert_se(gather_configuration(&runtime_dir_size) == 0);
+ r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r,
+ r == -ESRCH ? "No such user \"%s\"" :
+ r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
+ : "Failed to look up user \"%s\": %m",
+ user);
+
+ r = acquire_runtime_dir_size(&runtime_dir_size);
+ if (r < 0)
+ return r;
+
+ xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
#else
static int do_mount(const char *runtime_path, size_t runtime_dir_size, uid_t uid, gid_t gid) {
#endif // 0
@@ -128,17 +153,35 @@ static int do_mount(const char *runtime_path, size_t runtime_dir_size, uid_t uid
return user_mkdir_runtime_path(runtime_path, uid, gid, runtime_dir_size);
}
+#if 0 /// elogind already has the runtime path
+static int do_umount(const char *user) {
+ char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
+ uid_t uid;
+ int r;
+
+ /* The user may be already removed. So, first try to parse the string by parse_uid(),
+ * and if it fails, fallback to get_user_creds().*/
+ if (parse_uid(user, &uid) < 0) {
+ r = get_user_creds(&user, &uid, NULL, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r,
+ r == -ESRCH ? "No such user \"%s\"" :
+ r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
+ : "Failed to look up user \"%s\": %m",
+ user);
+ }
+
+ xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
+#else
static int do_umount(const char *runtime_path) {
+#endif // 0
+
log_debug("Will remove %s", runtime_path);
return user_remove_runtime_path(runtime_path);
}
#if 0 /// elogind does this internally as we have no unit chain being init.
int main(int argc, char *argv[]) {
- const char *user;
- uid_t uid;
- gid_t gid;
- char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
int r;
log_parse_environment();
@@ -153,24 +196,18 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
- umask(0022);
-
- user = argv[2];
- r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+ r = mac_selinux_init();
if (r < 0) {
- log_error_errno(r,
- r == -ESRCH ? "No such user \"%s\"" :
- r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
- : "Failed to look up user \"%s\": %m",
- user);
+ log_error_errno(r, "Could not initialize labelling: %m\n");
return EXIT_FAILURE;
}
- xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
+
+ umask(0022);
if (streq(argv[1], "start"))
- r = do_mount(runtime_path, uid, gid);
+ r = do_mount(argv[2]);
else if (streq(argv[1], "stop"))
- r = do_umount(runtime_path);
+ r = do_umount(argv[2]);
else
assert_not_reached("Unknown verb!");