master ohmu/fastfetch-openbsd / src / detection / lm / lm_linux.c
ohmu  ·  2026-10-15
  1#include "lm.h"
  2#include "common/debug.h"
  3#include "common/io.h"
  4#include "common/mallocHelper.h"
  5#include "common/properties.h"
  6#include "common/processing.h"
  7#include "common/strutil.h"
  8
  9#ifdef FF_HAVE_DBUS
 10    #include "common/dbus.h"
 11#endif
 12
 13#include <errno.h>
 14#include <unistd.h>
 15
 16#if __FreeBSD__
 17    #include <sys/sysctl.h>
 18    #include <sys/types.h>
 19    #include <sys/user.h>
 20#elif __OpenBSD__
 21    #include <sys/param.h>
 22    #include <sys/sysctl.h>
 23#elif __sun
 24    #include <procfs.h>
 25#elif __NetBSD__
 26    #include <sys/types.h>
 27    #include <sys/sysctl.h>
 28#endif
 29
 30static const char* getGdmVersion(FFstrbuf* version) {
 31    const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
 32    if (error || version->length == 0) {
 33        error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
 34        if (error || version->length == 0) {
 35            FF_DEBUG("Failed to get GDM version");
 36            return "Failed to get GDM version";
 37        }
 38    }
 39
 40    // GDM 44.1
 41    ffStrbufSubstrAfterFirstC(version, ' ');
 42    return nullptr;
 43}
 44
 45static const char* getSshdVersion(FFstrbuf* version) {
 46    const char* error = ffProcessAppendStdErr(version, (char* const[]) { "sshd", "-V", nullptr });
 47    if (error) {
 48        return error;
 49    }
 50
 51    // OpenSSH_9.0p1, OpenSSL 3.0.9 30 May 2023...
 52    ffStrbufSubstrBeforeFirstC(version, ',');
 53    ffStrbufSubstrAfterFirstC(version, '_');
 54    return nullptr;
 55}
 56
 57#ifdef FF_HAVE_ZLIB
 58    #include "common/path.h"
 59    #include "common/zlib.h"
 60
 61    #include <stdlib.h>
 62
 63static const char* getSddmVersion(FFstrbuf* version) {
 64    const char* error = ffZlibLoad(FF_ZLIB_GZFILE);
 65    if (error != nullptr) {
 66        FF_DEBUG("The sddm version cannot be read: %s", error);
 67        return error;
 68    }
 69
 70    gzFile file = gzopen(FF_PATH_PKG_BASE "/share/man/man1/sddm.1.gz", "rb");
 71    if (file == Z_NULL) {
 72        FF_DEBUG("gzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed: %s", strerror(errno));
 73        return "gzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed";
 74    }
 75
 76    ffStrbufEnsureFree(version, 2047);
 77    memset(version->chars, 0, version->allocated);
 78    int size = gzread(file, version->chars, version->allocated - 1);
 79    gzclose(file);
 80
 81    if (size <= 0) {
 82        FF_DEBUG("gzread(file, version->chars, version->length) failed");
 83        return "gzread(file, version->chars, version->length) failed";
 84    }
 85
 86    version->length = (uint32_t) size;
 87    uint32_t index = ffStrbufFirstIndexS(version, ".TH ");
 88    if (index == version->length) {
 89        FF_DEBUG(".TH is not found");
 90        ffStrbufClear(version);
 91        return ".TH is not found";
 92    }
 93
 94    ffStrbufSubstrBefore(version, ffStrbufNextIndexC(version, index, '\n'));
 95    ffStrbufSubstrAfter(version, index + (uint32_t) strlen(".TH "));
 96
 97    // "SDDM" 1 "May 2014" "sddm 0.20.0" "sddm"
 98    ffStrbufSubstrBeforeLastC(version, ' ');
 99    ffStrbufTrimRight(version, '"');
100    ffStrbufSubstrAfterLastC(version, ' ');
101
102    return nullptr;
103}
104#else
105static const char* getSddmVersion([[maybe_unused]] FFstrbuf* version) {
106    return "Fastfetch is built without libz support";
107}
108#endif
109
110static const char* getXfwmVersion(FFstrbuf* version) {
111    const char* error = ffProcessAppendStdOut(version, (char* const[]) { "xfwm4", "--version", nullptr });
112    if (error) {
113        return error;
114    }
115
116    // This is xfwm4 version 4.18.0 (revision 7e7473c5b) for Xfce 4.18...
117    ffStrbufSubstrAfterFirstS(version, "version ");
118    ffStrbufSubstrBeforeFirstC(version, ' ');
119
120    return nullptr;
121}
122
123static const char* getLightdmVersion(FFstrbuf* version) {
124    const char* error = ffProcessAppendStdErr(version, (char* const[]) { "lightdm", "--version", nullptr });
125    if (error) {
126        return error;
127    }
128
129    // lightdm 1.30.0
130    ffStrbufSubstrAfterFirstC(version, ' ');
131    ffStrbufTrimRight(version, '\n');
132
133    return nullptr;
134}
135
136#if __linux__ && !__ANDROID__
137#ifdef FF_HAVE_DBUS
138static const char* detectBySystemdDbus(FFLMResult* result) {
139    // This is the standard way to query the systemd session service name,
140    // and requires no $XDG_SESSION_ID being available
141    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
142    if (ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus) != nullptr) {
143        FF_DEBUG("Failed to load system DBus");
144        return "Failed to load system DBus";
145    }
146
147    if (!ffDBusGetPropertyString(
148            &dbus,
149            "org.freedesktop.login1",
150            "/org/freedesktop/login1/session/auto",
151            "org.freedesktop.login1.Session",
152            "Service",
153            &result->service)) {
154        FF_DEBUG("Failed to get systemd session Service property");
155        return "Failed to get systemd session Service property";
156    }
157
158    return nullptr;
159}
160#endif
161
162#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
163#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
164
165static const char* detectBySystemdPrivate(FFLMResult* result) {
166    FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
167
168    FF_STRBUF_AUTO_DESTROY sessionId = ffStrbufCreateS(getenv("XDG_SESSION_ID"));
169    if (sessionId.length == 0) {
170        // On some incorrectly configured systems, $XDG_SESSION_ID is not set. Try finding it ourself
171        // WARNING: This is private data. Do not parse
172        ffStrbufAppendS(&path, FF_SYSTEMD_USERS_PATH);
173        ffStrbufAppendUInt(&path, instance.state.platform.uid);
174
175        // This is actually buggy, and assumes current user is using DE
176        // `sd_pid_get_session` can be a better option, but we need to find a pid to use
177        if (!ffParsePropFile(path.chars, "DISPLAY=", &sessionId)) {
178            FF_DEBUG("Failed to get $XDG_SESSION_ID");
179            return "Failed to get $XDG_SESSION_ID";
180        }
181    }
182
183    ffStrbufClear(&path);
184    ffStrbufAppendS(&path, FF_SYSTEMD_SESSIONS_PATH);
185    ffStrbufAppend(&path, &sessionId);
186
187    // WARNING: This is private data. Do not parse
188    if (!ffParsePropFile(path.chars, "SERVICE=", &result->service)) {
189        return "Failed to parse " FF_SYSTEMD_SESSIONS_PATH "$XDG_SESSION_ID";
190    }
191
192    return nullptr;
193}
194#endif
195
196static const char* testLms(const char* psName) {
197    static const char* A[] = {
198        "atrium",
199    };
200    static const char* C[] = {
201        "cdm",
202    };
203    static const char* E[] = {
204        "entrance",
205    };
206    static const char* G[] = {
207        "gdm",
208        "gdm3",
209        "greetd",
210    };
211    static const char* L[] = {
212        "lemurs",
213        "lightdm",
214        "lxdm",
215        "ly",
216    };
217    static const char* P[] = {
218        "plasmalogin",
219    };
220    static const char* S[] = {
221        "sddm",
222        "slim",
223    };
224    static const char* T[] = {
225        "tbsm",
226    };
227    static const char* X[] = {
228        "xdm",
229        "xenodm",
230    };
231
232    switch (psName[0]) {
233        #define PS_CASE(letter, array) \
234            case letter: \
235                for (uint32_t i = 0; i < ARRAY_SIZE(array); ++i) { \
236                    if (ffStrEqualsIgnCase(psName, array[i])) { \
237                        return array[i]; \
238                    } \
239                } \
240                break;
241        PS_CASE('a', A)
242        PS_CASE('c', C)
243        PS_CASE('e', E)
244        PS_CASE('g', G)
245        PS_CASE('l', L)
246        PS_CASE('p', P)
247        PS_CASE('s', S)
248        PS_CASE('t', T)
249        PS_CASE('x', X)
250    }
251    return nullptr;
252}
253
254const char* detectByProcesses(FFLMResult* result) {
255#if __FreeBSD__
256    #ifdef __DragonFly__
257        #define ki_comm kp_comm
258    #endif
259
260    int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0 };
261    size_t length = 0;
262
263    if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
264        FF_DEBUG("sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed: %s", strerror(errno));
265        return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
266    }
267
268    FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
269    if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
270        FF_DEBUG("sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed: %s", strerror(errno));
271        return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
272    }
273
274    length /= sizeof(*procs);
275
276    for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) {
277        const char* lm = testLms(proc->ki_comm);
278        if (lm) {
279            ffStrbufSetStatic(&result->service, lm);
280            break;
281        }
282    }
283#elif __OpenBSD__
284    int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0, (int) sizeof(struct kinfo_proc), 0 };
285    size_t length = 0;
286
287    if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
288        FF_DEBUG("sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed: %s", strerror(errno));
289        return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
290    }
291
292    FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
293    request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
294    if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
295        FF_DEBUG("sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed: %s", strerror(errno));
296        return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
297    }
298
299    int count = (int) (length / sizeof(struct kinfo_proc));
300    for (int i = 0; i < count; ++i) {
301        const char* lm = testLms(procs[i].p_comm);
302        if (lm) {
303            ffStrbufSetStatic(&result->service, lm);
304            break;
305        }
306    }
307#elif __sun
308    FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
309    if (procdir == nullptr) {
310        FF_DEBUG("opendir(\"/proc\") failed: %s", strerror(errno));
311        return "opendir(\"/proc\") failed";
312    }
313
314    FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
315    ffStrbufAppendS(&procPath, "/proc/");
316
317    uint32_t procPathLength = procPath.length;
318
319    struct dirent* dirent;
320    while ((dirent = readdir(procdir)) != nullptr) {
321        if (!ffCharIsDigit(dirent->d_name[0])) {
322            continue;
323        }
324
325        ffStrbufAppendS(&procPath, dirent->d_name);
326        ffStrbufAppendS(&procPath, "/psinfo");
327        psinfo_t proc;
328        if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) {
329            ffStrbufSubstrBefore(&procPath, procPathLength);
330
331            if (proc.pr_uid != 0) {
332                continue;
333            }
334
335            const char* lm = testLms(proc.pr_fname);
336            if (lm) {
337                ffStrbufSetS(&result->service, lm);
338                break;
339            }
340        }
341    }
342#elif __linux__ || __GNU__
343    FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
344    if (procdir == nullptr) {
345        FF_DEBUG("opendir(\"/proc\") failed: %s", strerror(errno));
346        return "opendir(\"/proc\") failed";
347    }
348
349    FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
350    int procfd = dirfd(procdir);
351
352    struct dirent* dirent;
353    while ((dirent = readdir(procdir)) != nullptr) {
354        // Match only folders starting with a number (the pid folders)
355        if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
356            continue;
357        }
358
359        ffStrbufSetS(&procPath, dirent->d_name);
360        uint32_t procFolderPathLength = procPath.length;
361
362        // Don't check for processes not owned by root (login managers run as root).
363        char loginuid[32];
364        ffStrbufAppendS(&procPath, "/loginuid");
365        ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
366        if (bytesRead <= 0) {
367            continue;
368        }
369        loginuid[bytesRead] = '\0';
370
371        if (strtol(loginuid, nullptr, 10) != (long) (uint32_t) -1 /* no loginuid */) {
372            continue;
373        }
374
375        ffStrbufSubstrBefore(&procPath, procFolderPathLength);
376
377        ffStrbufAppendS(&procPath, "/comm");
378        char comm[256];
379        bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(comm) - 1, comm);
380        if (bytesRead <= 0) {
381            continue;
382        }
383        if (comm[bytesRead - 1] == '\n') {
384            --bytesRead;
385        }
386        comm[bytesRead] = '\0';
387
388        const char* lm = testLms(comm);
389        if (lm) {
390            ffStrbufSetS(&result->service, lm);
391            break;
392        }
393    }
394#elif __NetBSD__
395    int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_UID, 0, sizeof(struct kinfo_proc2), INT_MAX };
396
397    size_t size = 0;
398    if (sysctl(request, ARRAY_SIZE(request), nullptr, &size, nullptr, 0) != 0) {
399        FF_DEBUG("sysctl(KERN_PROC_UID, nullptr) failed: %s", strerror(errno));
400        return "sysctl(KERN_PROC_UID, nullptr) failed";
401    }
402
403    FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);
404
405    if (sysctl(request, ARRAY_SIZE(request), procs, &size, nullptr, 0) != 0) {
406        FF_DEBUG("sysctl(KERN_PROC_UID, procs) failed: %s", strerror(errno));
407        return "sysctl(KERN_PROC_UID, procs) failed";
408    }
409
410    for (struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) {
411        const char* lm = testLms(proc->p_comm);
412        if (lm) {
413            ffStrbufSetStatic(&result->service, lm);
414            break;
415        }
416    }
417#endif
418
419    if (result->service.length == 0) {
420        FF_DEBUG("Failed to detect login manager by processes");
421        return "Failed to detect login manager by processes";
422    }
423
424    return nullptr;
425}
426
427const char* ffDetectLM(FFLMResult* result) {
428    const char* error = "";
429#if __linux__
430    #ifdef FF_HAVE_DBUS
431        error = detectBySystemdDbus(result);
432    #endif
433    if (error != nullptr) {
434        error = detectBySystemdPrivate(result);
435    }
436#endif
437
438    if (error != nullptr) {
439        error = detectByProcesses(result);
440    }
441
442    if (error != nullptr) {
443        return error;
444    }
445
446    switch (result->service.chars[0]) {
447        case 'a':
448            if (ffStrEqualsIgnCase(result->service.chars, "atrium")) {
449                ffStrbufSetStatic(&result->prettyName, "Atrium");
450            }
451            break;
452        case 'c':
453            if (ffStrEqualsIgnCase(result->service.chars, "cdm")) {
454                ffStrbufSetStatic(&result->prettyName, "CDM");
455            }
456            break;
457        case 'e':
458            if (ffStrEqualsIgnCase(result->service.chars, "entrance")) {
459                ffStrbufSetStatic(&result->prettyName, "Entrance");
460            }
461            break;
462        case 'g':
463            if (ffStrbufStartsWithIgnCaseS(&result->service, "gdm")) {
464                ffStrbufSetStatic(&result->prettyName, "GDM");
465            } else if (ffStrEqualsIgnCase(result->service.chars, "greetd")) {
466                ffStrbufSetStatic(&result->prettyName, "greetd");
467            }
468            break;
469        case 'l':
470            if (ffStrEqualsIgnCase(result->service.chars, "lemurs")) {
471                ffStrbufSetStatic(&result->prettyName, "Lemurs");
472            } else if (ffStrEqualsIgnCase(result->service.chars, "lightdm")) {
473                ffStrbufSetStatic(&result->prettyName, "LightDM");
474            } else if (ffStrEqualsIgnCase(result->service.chars, "lxdm")) {
475                ffStrbufSetStatic(&result->prettyName, "LXDM");
476            } else if (ffStrEqualsIgnCase(result->service.chars, "ly")) {
477                ffStrbufSetStatic(&result->prettyName, "Ly");
478            }
479            break;
480        case 'p':
481            if (ffStrEqualsIgnCase(result->service.chars, "plasmalogin")) {
482                ffStrbufSetStatic(&result->prettyName, "Plasma Login");
483            }
484            break;
485        case 's':
486            if (ffStrEqualsIgnCase(result->service.chars, "sddm")) {
487                ffStrbufSetStatic(&result->prettyName, "SDDM");
488            } else if (ffStrEqualsIgnCase(result->service.chars, "slim")) {
489                ffStrbufSetStatic(&result->prettyName, "SLiM");
490            }
491            break;
492        case 't':
493            if (ffStrEqualsIgnCase(result->service.chars, "tbsm")) {
494                ffStrbufSetStatic(&result->prettyName, "TBSM");
495            }
496            break;
497        case 'x':
498            if (ffStrEqualsIgnCase(result->service.chars, "xdm")) {
499                ffStrbufSetStatic(&result->prettyName, "XDM");
500            } else if (ffStrEqualsIgnCase(result->service.chars, "xenodm")) {
501                ffStrbufSetStatic(&result->prettyName, "Xenodm");
502            }
503            break;
504    }
505
506    if (result->prettyName.length == 0) {
507        ffStrbufSet(&result->prettyName, &result->service);
508    }
509
510    if (instance.config.general.detectVersion) {
511        if (ffStrbufStartsWithS(&result->service, "gdm")) {
512            getGdmVersion(&result->version);
513        } else if (ffStrbufStartsWithS(&result->service, "sddm")) {
514            getSddmVersion(&result->version);
515        } else if (ffStrbufStartsWithS(&result->service, "xfwm")) {
516            getXfwmVersion(&result->version);
517        } else if (ffStrbufStartsWithS(&result->service, "lightdm")) {
518            getLightdmVersion(&result->version);
519        } else if (ffStrbufStartsWithS(&result->service, "sshd")) {
520            getSshdVersion(&result->version);
521        }
522    }
523
524    return nullptr;
525}