Skip to content

Commit 20fe497

Browse files
committed
Use logind instead of utmp because of Y2038
Bi-arch systems line x86-64 present the Y2038 problem, where an overflow can be produced because some glibc compatibility decissions (see https://github.com/thkukuk/utmpx/blob/main/Y2038.md for more information) This patch uses logind from systemd instead of utmp on Linux systems, if the systemd version is support the new API (>= 254). Signed-off-by: Alberto Planas <[email protected]>
1 parent 77e5b74 commit 20fe497

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

psutil/_psutil_linux.c

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#include <stdlib.h>
1515
#include <mntent.h>
1616
#include <features.h>
17+
#ifdef SYSTEMD_LINUX
18+
#include <dlfcn.h>
19+
#endif
1720
#include <utmp.h>
1821
#include <sched.h>
1922
#include <linux/version.h>
@@ -357,12 +360,152 @@ psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) {
357360
}
358361
#endif /* PSUTIL_HAVE_CPU_AFFINITY */
359362

363+
#ifdef SYSTEMD_LINUX
364+
365+
/* Systemd function signatures that will be loaded */
366+
int (*sd_booted)(void);
367+
int (*sd_get_sessions)(char ***);
368+
int (*sd_session_get_leader)(const char *, pid_t *);
369+
int (*sd_session_get_remote_host)(const char *,char **);
370+
int (*sd_session_get_start_time)(const char *, uint64_t *);
371+
int (*sd_session_get_tty)(const char *, char **);
372+
int (*sd_session_get_username)(const char *, char **);
373+
374+
#define dlsym_check(__h, __fn) do { \
375+
__fn = dlsym(__h, #__fn); \
376+
if (dlerror() != NULL || __fn == NULL) { \
377+
dlclose(__h); \
378+
return NULL; \
379+
} \
380+
} while (0)
381+
382+
void *
383+
load_systemd() {
384+
void *handle = dlopen("libsystemd.so.0", RTLD_LAZY);
385+
if (dlerror() != NULL || handle == NULL)
386+
return NULL;
387+
388+
dlsym_check(handle, sd_booted);
389+
dlsym_check(handle, sd_get_sessions);
390+
dlsym_check(handle, sd_session_get_leader);
391+
dlsym_check(handle, sd_session_get_remote_host);
392+
dlsym_check(handle, sd_session_get_start_time);
393+
dlsym_check(handle, sd_session_get_tty);
394+
dlsym_check(handle, sd_session_get_username);
395+
396+
return handle;
397+
}
398+
399+
/*
400+
* Return currently connected users as a list of tuples.
401+
*/
402+
static PyObject *
403+
psutil_users_systemd(PyObject *self, PyObject *args) {
404+
char **sessions_list = NULL;
405+
PyObject *py_retlist = PyList_New(0);
406+
PyObject *py_tuple = NULL;
407+
PyObject *py_username = NULL;
408+
PyObject *py_tty = NULL;
409+
PyObject *py_hostname = NULL;
410+
PyObject *py_user_proc = NULL;
411+
double tstamp = 0.0;
412+
pid_t pid = 0;
413+
414+
if (py_retlist == NULL)
415+
return NULL;
416+
int sessions = sd_get_sessions(&sessions_list);
417+
for (int i = 0; i < sessions; i++) {
418+
const char *session_id = sessions_list[i];
419+
py_tuple = NULL;
420+
py_user_proc = NULL;
421+
py_user_proc = Py_True;
422+
423+
char *username = NULL;
424+
if (sd_session_get_username(session_id, &username) < 0)
425+
goto error;
426+
py_username = PyUnicode_DecodeFSDefault(username);
427+
free(username);
428+
if (! py_username)
429+
goto error;
430+
431+
char *tty = NULL;
432+
if (sd_session_get_tty(session_id, &tty) < 0) {
433+
py_tty = PyUnicode_DecodeFSDefault("n/a");
434+
} else {
435+
py_tty = PyUnicode_DecodeFSDefault(tty);
436+
free(tty);
437+
}
438+
if (! py_tty)
439+
goto error;
440+
441+
char *hostname = NULL;
442+
if (sd_session_get_remote_host(session_id, &hostname) < 0)
443+
goto error;
444+
py_hostname = PyUnicode_DecodeFSDefault(hostname);
445+
free(hostname);
446+
if (! py_hostname)
447+
goto error;
448+
449+
uint64_t usec = 0;
450+
if (sd_session_get_start_time(session_id, &usec) < 0)
451+
goto error;
452+
tstamp = (double)usec / 1000000.0;
453+
454+
if (sd_session_get_leader(session_id, &pid) < 0)
455+
goto error;
456+
457+
py_tuple = Py_BuildValue(
458+
"OOOdO" _Py_PARSE_PID,
459+
py_username, // username
460+
py_tty, // tty
461+
py_hostname, // hostname
462+
tstamp, // tstamp
463+
py_user_proc, // (bool) user process
464+
pid // process id
465+
);
466+
if (! py_tuple)
467+
goto error;
468+
if (PyList_Append(py_retlist, py_tuple))
469+
goto error;
470+
Py_CLEAR(py_username);
471+
Py_CLEAR(py_tty);
472+
Py_CLEAR(py_hostname);
473+
Py_CLEAR(py_tuple);
474+
free (sessions_list[i]);
475+
}
476+
free(sessions_list);
477+
return py_retlist;
478+
479+
error:
480+
Py_XDECREF(py_username);
481+
Py_XDECREF(py_tty);
482+
Py_XDECREF(py_hostname);
483+
Py_XDECREF(py_tuple);
484+
Py_DECREF(py_retlist);
485+
free(sessions_list);
486+
return NULL;
487+
}
488+
489+
static PyObject *psutil_users_utmp(PyObject *, PyObject *);
490+
491+
static PyObject *
492+
psutil_users(PyObject *self, PyObject *args) {
493+
void *handle = load_systemd();
494+
if (handle && sd_booted())
495+
return psutil_users_systemd(self, args);
496+
else
497+
return psutil_users_utmp(self, args);
498+
}
360499

361500
/*
362501
* Return currently connected users as a list of tuples.
363502
*/
364503
static PyObject *
504+
psutil_users_utmp(PyObject *self, PyObject *args) {
505+
#else
506+
static PyObject *
365507
psutil_users(PyObject *self, PyObject *args) {
508+
#endif
366509
struct utmp *ut;
367510
PyObject *py_retlist = PyList_New(0);
368511
PyObject *py_tuple = NULL;

setup.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,23 @@ def unix_can_compile(c_code):
192192
shutil.rmtree(tempdir)
193193

194194

195+
def get_systemd_version():
196+
try:
197+
r = subprocess.run(["systemctl", "--version"], capture_output=True)
198+
except FileNotFoundError:
199+
return 0
200+
if r.returncode != 0:
201+
return 0
202+
out = r.stdout.split()
203+
if len(out) < 2:
204+
return 0
205+
version = out[1]
206+
try:
207+
return int(version)
208+
except ValueError:
209+
return 0
210+
211+
195212
if WINDOWS:
196213
def get_winver():
197214
maj, min = sys.getwindowsversion()[0:2]
@@ -293,6 +310,11 @@ def get_winver():
293310
if not unix_can_compile("#include <linux/ethtool.h>"):
294311
macros.append(("PSUTIL_ETHTOOL_MISSING_TYPES", 1))
295312

313+
# Systemd >= 254 can replace utmp. See:
314+
# https://github.com/thkukuk/utmpx/blob/main/utmp-to-logind.md
315+
if get_systemd_version() >= 254:
316+
macros.append(("SYSTEMD_LINUX", 1))
317+
296318
macros.append(("PSUTIL_LINUX", 1))
297319
ext = Extension(
298320
'psutil._psutil_linux',

0 commit comments

Comments
 (0)