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}