diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c index 3e6b3b9004..20fe8a3eda 100644 --- a/psutil/_psutil_linux.c +++ b/psutil/_psutil_linux.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -357,12 +358,152 @@ psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { } #endif /* PSUTIL_HAVE_CPU_AFFINITY */ +#ifdef SYSTEMD_LINUX + +/* Systemd function signatures that will be loaded */ +int (*sd_booted)(void); +int (*sd_get_sessions)(char ***); +int (*sd_session_get_leader)(const char *, pid_t *); +int (*sd_session_get_remote_host)(const char *,char **); +int (*sd_session_get_start_time)(const char *, uint64_t *); +int (*sd_session_get_tty)(const char *, char **); +int (*sd_session_get_username)(const char *, char **); + +#define dlsym_check(__h, __fn) do { \ + __fn = dlsym(__h, #__fn); \ + if (dlerror() != NULL || __fn == NULL) { \ + dlclose(__h); \ + return NULL; \ + } \ +} while (0) + +void * +load_systemd() { + void *handle = dlopen("libsystemd.so.0", RTLD_LAZY); + if (dlerror() != NULL || handle == NULL) + return NULL; + + dlsym_check(handle, sd_booted); + dlsym_check(handle, sd_get_sessions); + dlsym_check(handle, sd_session_get_leader); + dlsym_check(handle, sd_session_get_remote_host); + dlsym_check(handle, sd_session_get_start_time); + dlsym_check(handle, sd_session_get_tty); + dlsym_check(handle, sd_session_get_username); + + return handle; +} /* * Return currently connected users as a list of tuples. */ +static PyObject * +psutil_users_systemd(PyObject *self, PyObject *args) { + char **sessions_list = NULL; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_user_proc = NULL; + double tstamp = 0.0; + pid_t pid = 0; + + if (py_retlist == NULL) + return NULL; + int sessions = sd_get_sessions(&sessions_list); + for (int i = 0; i < sessions; i++) { + const char *session_id = sessions_list[i]; + py_tuple = NULL; + py_user_proc = NULL; + py_user_proc = Py_True; + + char *username = NULL; + if (sd_session_get_username(session_id, &username) < 0) + goto error; + py_username = PyUnicode_DecodeFSDefault(username); + free(username); + if (! py_username) + goto error; + + char *tty = NULL; + if (sd_session_get_tty(session_id, &tty) < 0) { + py_tty = PyUnicode_DecodeFSDefault("n/a"); + } else { + py_tty = PyUnicode_DecodeFSDefault(tty); + free(tty); + } + if (! py_tty) + goto error; + + char *hostname = NULL; + if (sd_session_get_remote_host(session_id, &hostname) < 0) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(hostname); + free(hostname); + if (! py_hostname) + goto error; + + uint64_t usec = 0; + if (sd_session_get_start_time(session_id, &usec) < 0) + goto error; + tstamp = (double)usec / 1000000.0; + + if (sd_session_get_leader(session_id, &pid) < 0) + goto error; + + py_tuple = Py_BuildValue( + "OOOdO" _Py_PARSE_PID, + py_username, // username + py_tty, // tty + py_hostname, // hostname + tstamp, // tstamp + py_user_proc, // (bool) user process + pid // process id + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + free (sessions_list[i]); + } + free(sessions_list); + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + free(sessions_list); + return NULL; +} + +static PyObject *psutil_users_utmp(PyObject *, PyObject *); + static PyObject * psutil_users(PyObject *self, PyObject *args) { + void *handle = load_systemd(); + if (handle && sd_booted()) + return psutil_users_systemd(self, args); + else + return psutil_users_utmp(self, args); +} + +/* + * Return currently connected users as a list of tuples. + */ +static PyObject * +psutil_users_utmp(PyObject *self, PyObject *args) { +#else +static PyObject * +psutil_users(PyObject *self, PyObject *args) { +#endif struct utmp *ut; PyObject *py_retlist = PyList_New(0); PyObject *py_tuple = NULL; diff --git a/setup.py b/setup.py index eef7bf4558..881e7547e5 100755 --- a/setup.py +++ b/setup.py @@ -193,6 +193,25 @@ def unix_can_compile(c_code): shutil.rmtree(tempdir) +def get_systemd_version(): + try: + r = subprocess.run( + ["systemctl", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + except FileNotFoundError: + return 0 + if r.returncode != 0: + return 0 + out = r.stdout.split() + if len(out) < 2: + return 0 + version = out[1] + try: + return int(version) + except ValueError: + return 0 + + if WINDOWS: def get_winver(): maj, min = sys.getwindowsversion()[0:2] @@ -294,6 +313,11 @@ def get_winver(): if not unix_can_compile("#include "): macros.append(("PSUTIL_ETHTOOL_MISSING_TYPES", 1)) + # Systemd >= 254 can replace utmp. See: + # https://github.com/thkukuk/utmpx/blob/main/utmp-to-logind.md + if get_systemd_version() >= 254: + macros.append(("SYSTEMD_LINUX", 1)) + macros.append(("PSUTIL_LINUX", 1)) ext = Extension( 'psutil._psutil_linux',