logind: utmp fix (#36958)

Fixes: #36930
This commit is contained in:
Yu Watanabe
2025-04-03 02:41:26 +09:00
committed by GitHub
7 changed files with 148 additions and 137 deletions

View File

@@ -20,6 +20,7 @@
#include "fd-util.h"
#include "limits-util.h"
#include "logind.h"
#include "logind-utmp.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
@@ -29,7 +30,6 @@
#include "udev-util.h"
#include "user-util.h"
#include "userdb.h"
#include "utmp-wtmp.h"
void manager_reset_config(Manager *m) {
assert(m);
@@ -711,137 +711,6 @@ bool manager_all_buttons_ignored(Manager *m) {
return true;
}
int manager_read_utmp(Manager *m) {
#if ENABLE_UTMP
int r;
_unused_ _cleanup_(utxent_cleanup) bool utmpx = false;
assert(m);
if (utmpxname(UTMPX_FILE) < 0)
return log_error_errno(errno, "Failed to set utmp path to " UTMPX_FILE ": %m");
utmpx = utxent_start();
for (;;) {
_cleanup_free_ char *t = NULL;
struct utmpx *u;
const char *c;
Session *s;
errno = 0;
u = getutxent();
if (!u) {
if (errno == ENOENT)
log_debug_errno(errno, UTMPX_FILE " does not exist, ignoring.");
else if (errno != 0)
log_warning_errno(errno, "Failed to read " UTMPX_FILE ", ignoring: %m");
return 0;
}
if (u->ut_type != USER_PROCESS)
continue;
if (!pid_is_valid(u->ut_pid))
continue;
t = strndup(u->ut_line, sizeof(u->ut_line));
if (!t)
return log_oom();
c = path_startswith(t, "/dev/");
if (c) {
r = free_and_strdup(&t, c);
if (r < 0)
return log_oom();
}
if (isempty(t))
continue;
if (manager_get_session_by_pidref(m, &PIDREF_MAKE_FROM_PID(u->ut_pid), &s) <= 0)
continue;
if (s->tty_validity == TTY_FROM_UTMP && !streq_ptr(s->tty, t)) {
/* This may happen on multiplexed SSH connection (i.e. 'SSH connection sharing'). In
* this case PAM and utmp sessions don't match. In such a case let's invalidate the TTY
* information and never acquire it again. */
s->tty = mfree(s->tty);
s->tty_validity = TTY_UTMP_INCONSISTENT;
log_debug("Session '%s' has inconsistent TTY information, dropping TTY information.", s->id);
continue;
}
/* Never override what we figured out once */
if (s->tty || s->tty_validity >= 0)
continue;
s->tty = TAKE_PTR(t);
s->tty_validity = TTY_FROM_UTMP;
log_debug("Acquired TTY information '%s' from utmp for session '%s'.", s->tty, s->id);
}
#else
return 0;
#endif
}
#if ENABLE_UTMP
static int manager_dispatch_utmp(sd_event_source *s, const struct inotify_event *event, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
/* If there's indication the file itself might have been removed or became otherwise unavailable, then let's
* reestablish the watch on whatever there's now. */
if ((event->mask & (IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF|IN_Q_OVERFLOW|IN_UNMOUNT)) != 0)
manager_connect_utmp(m);
(void) manager_read_utmp(m);
return 0;
}
#endif
void manager_connect_utmp(Manager *m) {
#if ENABLE_UTMP
sd_event_source *s = NULL;
int r;
assert(m);
/* Watch utmp for changes via inotify. We do this to deal with tools such as ssh, which will register the PAM
* session early, and acquire a TTY only much later for the connection. Thus during PAM the TTY won't be known
* yet. ssh will register itself with utmp when it finally acquired the TTY. Hence, let's make use of this, and
* watch utmp for the TTY asynchronously. We use the PAM session's leader PID as key, to find the right entry.
*
* Yes, relying on utmp is pretty ugly, but it's good enough for informational purposes, as well as idle
* detection (which, for tty sessions, relies on the TTY used) */
r = sd_event_add_inotify(m->event, &s, UTMPX_FILE, 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 " UTMPX_FILE ", ignoring: %m");
else {
r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
if (r < 0)
log_warning_errno(r, "Failed to adjust utmp event source priority, ignoring: %m");
(void) sd_event_source_set_description(s, "utmp");
}
sd_event_source_unref(m->utmp_event_source);
m->utmp_event_source = s;
#endif
}
void manager_reconnect_utmp(Manager *m) {
#if ENABLE_UTMP
assert(m);
if (m->utmp_event_source)
return;
manager_connect_utmp(m);
#endif
}
int manager_read_efi_boot_loader_entries(Manager *m) {
#if ENABLE_EFI

View File

@@ -32,13 +32,14 @@
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
#include "logind.h"
#include "logind-action.h"
#include "logind-dbus.h"
#include "logind-polkit.h"
#include "logind-seat-dbus.h"
#include "logind-session-dbus.h"
#include "logind-user-dbus.h"
#include "logind.h"
#include "logind-utmp.h"
#include "mkdir-label.h"
#include "parse-util.h"
#include "path-util.h"

135
src/login/logind-utmp.c Normal file
View File

@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "logind-utmp.h"
#include "path-util.h"
#include "utmp-wtmp.h"
int manager_read_utmp(Manager *m) {
#if ENABLE_UTMP
assert(m);
if (utmpxname(UTMPX_FILE) < 0)
return log_error_errno(errno, "Failed to set utmp path to " UTMPX_FILE ": %m");
_unused_ _cleanup_(utxent_cleanup) bool utmpx = utxent_start();
for (;;) {
_cleanup_free_ char *t = NULL;
struct utmpx *u;
const char *c;
Session *s;
errno = 0;
u = getutxent();
if (!u) {
if (errno == ENOENT)
log_debug_errno(errno, UTMPX_FILE " does not exist, ignoring.");
else if (errno != 0)
log_warning_errno(errno, "Failed to read " UTMPX_FILE ", ignoring: %m");
return 0;
}
if (u->ut_type != USER_PROCESS)
continue;
if (!pid_is_valid(u->ut_pid))
continue;
t = strndup(u->ut_line, sizeof(u->ut_line));
if (!t)
return log_oom();
c = path_startswith(t, "/dev/");
if (c) {
if (free_and_strdup(&t, c) < 0)
return log_oom();
}
if (isempty(t))
continue;
if (manager_get_session_by_pidref(m, &PIDREF_MAKE_FROM_PID(u->ut_pid), &s) <= 0)
continue;
if (s->tty_validity == TTY_FROM_UTMP && !streq_ptr(s->tty, t)) {
/* This may happen on multiplexed SSH connection (i.e. 'SSH connection sharing'). In
* this case PAM and utmp sessions don't match. In such a case let's invalidate the TTY
* information and never acquire it again. */
s->tty = mfree(s->tty);
s->tty_validity = TTY_UTMP_INCONSISTENT;
log_debug("Session '%s' has inconsistent TTY information, dropping TTY information.", s->id);
session_save(s);
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);
session_save(s);
}
#else
return 0;
#endif
}
#if ENABLE_UTMP
static int manager_dispatch_utmp(sd_event_source *s, const struct inotify_event *event, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
/* If there's indication the file itself might have been removed or became otherwise unavailable, then let's
* reestablish the watch on whatever there's now. */
if ((event->mask & (IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF|IN_Q_OVERFLOW|IN_UNMOUNT)) != 0)
manager_connect_utmp(m);
(void) manager_read_utmp(m);
return 0;
}
#endif
void manager_connect_utmp(Manager *m) {
#if ENABLE_UTMP
sd_event_source *s = NULL;
int r;
assert(m);
/* Watch utmp for changes via inotify. We do this to deal with tools such as ssh, which will register the PAM
* session early, and acquire a TTY only much later for the connection. Thus during PAM the TTY won't be known
* yet. ssh will register itself with utmp when it finally acquired the TTY. Hence, let's make use of this, and
* watch utmp for the TTY asynchronously. We use the PAM session's leader PID as key, to find the right entry.
*
* Yes, relying on utmp is pretty ugly, but it's good enough for informational purposes, as well as idle
* detection (which, for tty sessions, relies on the TTY used) */
r = sd_event_add_inotify(m->event, &s, UTMPX_FILE, 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 " UTMPX_FILE ", 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
}

8
src/login/logind-utmp.h Normal file
View File

@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "logind.h"
int manager_read_utmp(Manager *m);
void manager_connect_utmp(Manager *m);
void manager_reconnect_utmp(Manager *m);

View File

@@ -29,6 +29,7 @@
#include "logind-seat-dbus.h"
#include "logind-session-dbus.h"
#include "logind-user-dbus.h"
#include "logind-utmp.h"
#include "logind-varlink.h"
#include "main-func.h"
#include "mkdir-label.h"

View File

@@ -181,10 +181,6 @@ bool manager_is_docked_or_external_displays(Manager *m);
bool manager_is_on_external_power(void);
bool manager_all_buttons_ignored(Manager *m);
int manager_read_utmp(Manager *m);
void manager_connect_utmp(Manager *m);
void manager_reconnect_utmp(Manager *m);
/* gperf lookup function */
const struct ConfigPerfItem* logind_gperf_lookup(const char *key, GPERF_LEN_TYPE length);

View File

@@ -26,6 +26,7 @@ liblogind_core_sources = files(
'logind-session.c',
'logind-user-dbus.c',
'logind-user.c',
'logind-utmp.c',
'logind-varlink.c',
'logind-wall.c',
)