master ohmu/fastfetch-openbsd / src / detection / users / users_linux.c
ohmu  ·  2026-10-15
  1#include "common/debug.h"
  2#include "common/io.h"
  3#include "common/properties.h"
  4#include "fastfetch.h"
  5#include "users.h"
  6
  7#include <string.h>
  8#include <unistd.h>
  9
 10#if FF_HAVE_UTMPX
 11    #include <utmpx.h>
 12#else
 13    // for Android compatibility
 14    #include <utmp.h>
 15    #define utmpx utmp
 16    #define setutxent setutent
 17    #define getutxent getutent
 18#endif
 19#if __linux__ || __GNU__
 20    #include <netinet/in.h>
 21    #include <arpa/inet.h>
 22#endif
 23
 24#if __linux__
 25bool detectUserBySystemd(const FFstrbuf* pathUsers, FFlist* users) {
 26    FF_STRBUF_AUTO_DESTROY state = ffStrbufCreate();
 27    FF_STRBUF_AUTO_DESTROY userName = ffStrbufCreate();
 28    FF_STRBUF_AUTO_DESTROY loginTime = ffStrbufCreate();
 29    FF_STRBUF_AUTO_DESTROY sessions = ffStrbufCreate();
 30
 31    // WARNING: This is private data. Do not parse
 32    if (!ffParsePropFileValues(pathUsers->chars, 4, (FFpropquery[]) {
 33                                                        { "NAME=", &userName },
 34                                                        { "STATE=", &state },
 35                                                        { "REALTIME=", &loginTime },
 36                                                        { "ONLINE_SESSIONS=", &sessions },
 37                                                    }) ||
 38        !ffStrbufEqualS(&state, "active")) {
 39        return false;
 40    }
 41
 42    FFUserResult* user = FF_LIST_ADD(FFUserResult, *users);
 43    ffStrbufInitMove(&user->name, &userName);
 44    ffStrbufInit(&user->hostName);
 45    ffStrbufInit(&user->sessionName);
 46    ffStrbufInit(&user->clientIp);
 47    ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms
 48    user->loginTime = ffStrbufToUInt(&loginTime, 0);
 49
 50    FF_STRBUF_AUTO_DESTROY pathSessions = ffStrbufCreateS("/run/systemd/sessions/");
 51    const uint32_t pathSessionsBaseLen = pathSessions.length;
 52
 53    FF_STRBUF_AUTO_DESTROY tty = ffStrbufCreate();
 54    FF_STRBUF_AUTO_DESTROY remoteHost = ffStrbufCreate();
 55    FF_STRBUF_AUTO_DESTROY service = ffStrbufCreate();
 56
 57    char* token = nullptr;
 58    size_t n = 0;
 59    while (ffStrbufGetdelim(&token, &n, ' ', &sessions)) {
 60        ffStrbufSubstrBefore(&pathSessions, pathSessionsBaseLen);
 61        ffStrbufAppendS(&pathSessions, token);
 62
 63        ffStrbufClear(&remoteHost);
 64        ffStrbufClear(&service);
 65        ffStrbufClear(&tty);
 66        ffStrbufClear(&loginTime);
 67
 68        // WARNING: This is private data. Do not parse
 69        if (ffParsePropFileValues(pathSessions.chars, 4, (FFpropquery[]) {
 70                                                             { "REMOTE_HOST=", &remoteHost },
 71                                                             { "TTY=", &tty },
 72                                                             { "SERVICE=", &service },
 73                                                             { "REALTIME=", &loginTime },
 74                                                         }) &&
 75            !ffStrbufEqualS(&service, "systemd-user")) {
 76            if (remoteHost.length) {
 77                ffStrbufTrimRight(&remoteHost, ']');
 78                ffStrbufTrimLeft(&remoteHost, '[');
 79                ffStrbufInitMove(&user->hostName, &remoteHost);
 80            } else {
 81                ffStrbufSetStatic(&user->hostName, "localhost");
 82            }
 83            ffStrbufInitMove(&user->sessionName, tty.length ? &tty : &service);
 84            if (loginTime.length) {
 85                ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms
 86                user->loginTime = ffStrbufToUInt(&loginTime, 0);
 87            }
 88            break;
 89        }
 90    }
 91
 92    return true;
 93}
 94
 95const char* detectBySystemd(FFUsersOptions* options, FFlist* users) {
 96    // For some reason, debian/ubuntu no longer updates `/var/run/utmp` (#2064)
 97    // Query systemd instead
 98    FF_STRBUF_AUTO_DESTROY pathUsers = ffStrbufCreateS("/run/systemd/users/");
 99
100    if (options->myselfOnly) {
101        ffStrbufAppendUInt(&pathUsers, instance.state.platform.uid);
102        detectUserBySystemd(&pathUsers, users);
103    } else {
104        const uint32_t pathUsersBaseLen = pathUsers.length;
105        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pathUsers.chars);
106        if (!dirp) {
107            FF_DEBUG("opendir(\"%s\") failed: %s", pathUsers.chars, strerror(errno));
108            return "opendir(\"/run/systemd/users/\") failed";
109        }
110
111        struct dirent* entry;
112        while ((entry = readdir(dirp))) {
113            if (entry->d_type != DT_REG) {
114                continue;
115            }
116
117            ffStrbufAppendS(&pathUsers, entry->d_name);
118            detectUserBySystemd(&pathUsers, users);
119            ffStrbufSubstrBefore(&pathUsers, pathUsersBaseLen);
120        }
121    }
122    return nullptr;
123}
124#endif
125
126#if __linux__ || __GNU__
127static void fillUtmpIpAddr(FFUserResult* user, struct utmpx* n) {
128    bool isIpv6 = false;
129    for (int i = 1; i < 4; ++i) {
130        if (n->ut_addr_v6[i] != 0) {
131            isIpv6 = true;
132            break;
133        }
134    }
135
136    if (isIpv6) {
137        char ipv6_str[INET6_ADDRSTRLEN];
138        if (inet_ntop(AF_INET6, n->ut_addr_v6, ipv6_str, INET6_ADDRSTRLEN) != nullptr) {
139            ffStrbufSetS(&user->clientIp, ipv6_str);
140        }
141    } else if (n->ut_addr_v6[0] != 0) {
142        char ipv4_str[INET_ADDRSTRLEN];
143        if (inet_ntop(AF_INET, n->ut_addr_v6, ipv4_str, INET_ADDRSTRLEN) != nullptr) {
144            ffStrbufSetS(&user->clientIp, ipv4_str);
145        }
146    }
147}
148#else
149static void fillUtmpIpAddr([[maybe_unused]] FFUserResult* user, [[maybe_unused]] struct utmpx* n) {
150}
151#endif
152
153const char* detectByUtmp(FFUsersOptions* options, FFlist* users) {
154    struct utmpx* n = nullptr;
155    setutxent();
156
157next:
158    while ((n = getutxent())) {
159        if (n->ut_type != USER_PROCESS) {
160            continue;
161        }
162
163        if (options->myselfOnly && !ffStrbufEqualS(&instance.state.platform.userName, n->ut_user)) {
164            continue;
165        }
166
167        FF_LIST_FOR_EACH (FFUserResult, user, *users) {
168            if (ffStrbufEqualS(&user->name, n->ut_user)) {
169                uint64_t newLoginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000;
170                if (newLoginTime > user->loginTime) {
171                    ffStrbufSetS(&user->hostName, n->ut_host);
172                    ffStrbufSetS(&user->sessionName, n->ut_line);
173                    fillUtmpIpAddr(user, n);
174                    user->loginTime = newLoginTime;
175                }
176                goto next;
177            }
178        }
179
180        FFUserResult* user = FF_LIST_ADD(FFUserResult, *users);
181        ffStrbufInitS(&user->name, n->ut_user);
182        ffStrbufInitS(&user->hostName, n->ut_host);
183        ffStrbufInitS(&user->sessionName, n->ut_line);
184        ffStrbufInit(&user->clientIp);
185        fillUtmpIpAddr(user, n);
186        user->loginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000;
187    }
188
189    endutxent();
190
191    return nullptr;
192}
193
194const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) {
195    const char* err = detectByUtmp(options, users);
196    if (err) {
197        return err;
198    }
199
200#if __linux__
201    if (users->length == 0) {
202        detectBySystemd(options, users);
203    }
204#endif
205
206    return nullptr;
207}