From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/packages/packages_linux.c | 651 ++++++++++++++++++++++++++++++++ 1 file changed, 651 insertions(+) create mode 100644 src/detection/packages/packages_linux.c (limited to 'src/detection/packages/packages_linux.c') diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c new file mode 100644 index 0000000..79de8cb --- /dev/null +++ b/src/detection/packages/packages_linux.c @@ -0,0 +1,651 @@ +#include "packages.h" +#include "common/io.h" +#include "common/parsing.h" +#include "common/properties.h" +#include "common/settings.h" +#include "common/strutil.h" +#include "detection/os/os.h" + +static uint32_t getNumElements(FFstrbuf* baseDir, const char* dirname, bool isdir) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + uint32_t num_elements = ffPackagesGetNumElements(baseDir->chars, isdir); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; +} + +static uint32_t getNumStringsImpl(const char* filename, const char* needle) { + FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); + if (!ffReadFileBuffer(filename, &content)) { + return 0; + } + + uint32_t count = 0; + char* iter = content.chars; + size_t needleLength = strlen(needle); + while ((iter = memmem(iter, content.length - (size_t) (iter - content.chars), needle, needleLength)) != NULL) { + ++count; + iter += needleLength; + } + + return count; +} + +static uint32_t getNumStrings(FFstrbuf* baseDir, const char* filename, const char* needle, const char* packageId) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, filename); + + FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); + + uint32_t num_elements; + if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements)) { + ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; + } + + num_elements = getNumStringsImpl(baseDir->chars, needle); + ffStrbufSubstrBefore(baseDir, baseDirLength); + + ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); + + return num_elements; +} + +static uint32_t getSQLite3Int(FFstrbuf* baseDir, const char* dbPath, const char* query, const char* packageId) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dbPath); + + FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); + + uint32_t num_elements; + if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements)) { + ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; + } + + num_elements = (uint32_t) ffSettingsGetSQLite3Int(baseDir->chars, query); + ffStrbufSubstrBefore(baseDir, baseDirLength); + + ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); + + return num_elements; +} + +static uint32_t countFilesRecursiveImpl(FFstrbuf* baseDirPath, const char* filename) { + uint32_t baseDirPathLength = baseDirPath->length; + + ffStrbufAppendC(baseDirPath, '/'); + ffStrbufAppendS(baseDirPath, filename); + bool exists = ffPathExists(baseDirPath->chars, FF_PATHTYPE_FILE); + ffStrbufSubstrBefore(baseDirPath, baseDirPathLength); + if (exists) { + return 1; + } + + DIR* dirp = opendir(baseDirPath->chars); + if (dirp == NULL) { + return 0; + } + + ffStrbufAppendC(baseDirPath, '/'); + baseDirPathLength = baseDirPath->length; + + uint32_t sum = 0; + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + // According to the PMS, neither category nor package name can begin with '.', so no need to check for . or .. specifically + if (entry->d_type != DT_DIR || entry->d_name[0] == '.') { + continue; + } + + ffStrbufAppendS(baseDirPath, entry->d_name); + sum += countFilesRecursiveImpl(baseDirPath, filename); + ffStrbufSubstrBefore(baseDirPath, baseDirPathLength); + } + + closedir(dirp); + return sum; +} + +static uint32_t countFilesRecursive(FFstrbuf* baseDir, const char* dirname, const char* filename) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + uint32_t sum = countFilesRecursiveImpl(baseDir, filename); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return sum; +} + +static uint32_t getNumElementsBySuffix(FFstrbuf* baseDir, const char* dirname, const char* suffix) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); + ffStrbufSubstrBefore(baseDir, baseDirLength); + if (dirp == NULL) { + return 0; + } + + uint32_t count = 0; + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] != '.' && ffStrEndsWithIgnCase(entry->d_name, suffix)) { + ++count; + } + } + return count; +} + +static uint32_t getXBPSImpl(FFstrbuf* baseDir) { + DIR* dir = opendir(baseDir->chars); + if (dir == NULL) { + return 0; + } + + uint32_t result = 0; + + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type != DT_REG || !ffStrStartsWithIgnCase(entry->d_name, "pkgdb-")) { + continue; + } + + ffStrbufAppendC(baseDir, '/'); + ffStrbufAppendS(baseDir, entry->d_name); + result = getNumStringsImpl(baseDir->chars, "installed"); + break; + } + + closedir(dir); + return result; +} + +static uint32_t getXBPS(FFstrbuf* baseDir, const char* dirname) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + uint32_t result = getXBPSImpl(baseDir); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return result; +} + +static uint32_t getSnap(FFstrbuf* baseDir) { + uint32_t result = getNumElements(baseDir, "/snap", true); + + if (result == 0) { + result = getNumElements(baseDir, "/var/lib/snapd/snap", true); + } + + // Accounting for the /snap/bin folder + return result > 0 ? result - 1 : 0; +} + +#ifdef FF_HAVE_RPM + #include "common/library.h" + #include + #include + #include + #include + +static uint32_t getRpmFromLibrpm(void) { + FF_LIBRARY_LOAD(rpm, 0, "librpm" FF_LIBRARY_EXTENSION, 12) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmReadConfigFiles, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsCreate, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsInitIterator, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbGetIteratorCount, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbFreeIterator, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsFree, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmlogSetMask, 0) + + // Don't print any error messages + ffrpmlogSetMask(RPMLOG_MASK(RPMLOG_EMERG)); + + if (ffrpmReadConfigFiles(NULL, NULL) != 0) { + return 0; + } + + rpmts ts = ffrpmtsCreate(); + if (ts == NULL) { + return 0; + } + + rpmdbMatchIterator mi = ffrpmtsInitIterator(ts, RPMDBI_LABEL, NULL, 0); + if (mi == NULL) { + ffrpmtsFree(ts); + return 0; + } + + int count = ffrpmdbGetIteratorCount(mi); + + ffrpmdbFreeIterator(mi); + ffrpmtsFree(ts); + + return count > 0 ? (uint32_t) count : 0; +} + +#endif // FF_HAVE_RPM + +static uint32_t getAMPackages(FFstrbuf* baseDir) { + uint32_t baseLength = baseDir->length; + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); + if (!dirp) { + return 0; + } + + uint32_t result = 0; + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + if (entry->d_type == DT_DIR) { + ffStrbufAppendF(baseDir, "/%s/remove", entry->d_name); + if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) { + ++result; + } + ffStrbufSubstrBefore(baseDir, baseLength); + } + } + return result; +} + +static uint32_t getAMSystem(FFstrbuf* baseDir) { + // #771 + uint32_t baseDirLength = baseDir->length; + + ffStrbufAppendS(baseDir, "/opt"); + uint32_t optDirLength = baseDir->length; + + uint32_t result = 0; + + ffStrbufAppendS(baseDir, "/am/APP-MANAGER"); + if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) { + ++result; // `am` itself is counted as a package too + ffStrbufSubstrBefore(baseDir, optDirLength); + result = getAMPackages(baseDir); + } + + ffStrbufSubstrBefore(baseDir, baseDirLength); + return result; +} + +static uint32_t getAMUser(void) { + if (instance.state.platform.configDirs.length == 0) { + return 0; + } + + // check if $XDG_CONFIG_HOME/appman/appman-config exists + FFstrbuf* baseDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs); + uint32_t baseLen = baseDir->length; + ffStrbufAppendS(baseDir, "appman/appman-config"); + FF_STRBUF_AUTO_DESTROY packagesPath = ffStrbufCreate(); + if (ffReadFileBuffer(baseDir->chars, &packagesPath)) { + ffStrbufTrimRightSpace(&packagesPath); + } + ffStrbufSubstrBefore(baseDir, baseLen); + + return packagesPath.length > 0 ? getAMPackages(&packagesPath) : 0; +} + +static int compareHash(const void* a, const void* b) { + return memcmp(a, b, 32); +} + +static uint32_t getGuixPackagesImpl(char* filename) { + FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); + if (!ffAppendFileBuffer(filename, &content)) { + return 0; + } + + // Count number of unique /gnu/store/ paths in PROFILE/manifest based on their hash value. + // Contains packages explicitly installed and their propagated inputs. + char* pend = content.chars; + + for (const char* pattern = content.chars; (pattern = strstr(pattern, "/gnu/store/")); pattern += 32) { + pattern += strlen("/gnu/store/"); + memmove(pend, pattern, 32); + pend += 32; + } + + if (pend == content.chars) { + return 0; + } + + qsort(content.chars, (size_t) (pend - content.chars) / 32, 32, compareHash); + + uint32_t count = 1; + for (const char* p = content.chars + 32; p < pend; p += 32) { + count += compareHash(p - 32, p) != 0; + } + + return count; +} + +static uint32_t getGuixPackages(FFstrbuf* baseDir, const char* dirname) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + ffStrbufAppendS(baseDir, "/manifest"); + uint32_t num_elements = getGuixPackagesImpl(baseDir->chars); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; +} + +static inline uint32_t getFlatpakRuntimePackagesArch(FFstrbuf* baseDir) { + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); + if (dirp == NULL) { + return 0; + } + + uint32_t num_elements = 0; + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { + num_elements += getNumElements(baseDir, entry->d_name, true); + } + } + + return num_elements; +} + +static inline uint32_t getFlatpakRuntimePackages(FFstrbuf* baseDir) { + ffStrbufAppendS(baseDir, "runtime/"); + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); + if (dirp == NULL) { + return 0; + } + + uint32_t runtimeDirLength = baseDir->length; + uint32_t num_elements = 0; + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { + // `flatpak list` ignores `.Locale` and `.Debug` packages, and maybe others + const char* dot = strrchr(entry->d_name, '.'); + if (__builtin_expect(!dot, false)) { + continue; + } + dot++; + + if (ffStrEquals(dot, "Locale") || ffStrEquals(dot, "Debug")) { + continue; + } + + ffStrbufAppendS(baseDir, entry->d_name); + ffStrbufAppendC(baseDir, '/'); + num_elements += getFlatpakRuntimePackagesArch(baseDir); + ffStrbufSubstrBefore(baseDir, runtimeDirLength); + } + } + + return num_elements; +} + +static inline uint32_t getFlatpakAppPackages(FFstrbuf* baseDir) { + ffStrbufAppendS(baseDir, "app/"); + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); + if (dirp == NULL) { + return 0; + } + + uint32_t appDirLength = baseDir->length; + uint32_t num_elements = 0; + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { + ffStrbufAppendS(baseDir, entry->d_name); + ffStrbufAppendS(baseDir, "/current"); + if (ffPathExists(baseDir->chars, FF_PATHTYPE_ANY)) { // Exclude deleted apps, #1856 + ++num_elements; + } + ffStrbufSubstrBefore(baseDir, appDirLength); + } + } + return num_elements; +} + +static uint32_t getFlatpakPackages(FFstrbuf* baseDir, const char* dirname) { + uint32_t num_elements = 0; + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + ffStrbufAppendS(baseDir, "/flatpak/"); + uint32_t flatpakDirLength = baseDir->length; + + num_elements += getFlatpakAppPackages(baseDir); + ffStrbufSubstrBefore(baseDir, flatpakDirLength); + + num_elements += getFlatpakRuntimePackages(baseDir); + + ffStrbufSubstrBefore(baseDir, baseDirLength); + + return num_elements; +} + +static uint32_t getPacmanPackages(FFstrbuf* baseDir) { + FF_STRBUF_AUTO_DESTROY dbPath = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY rootDir = ffStrbufCreate(); + + // Get path to pacman.conf + uint32_t baseDirLen = baseDir->length; + ffStrbufAppendS(baseDir, "/etc/pacman.conf"); + + bool confFound = ffParsePropFileValues(baseDir->chars, 2, (FFpropquery[]) { + { "DBPath =", &dbPath }, + { "RootDir =", &rootDir }, + }); + ffStrbufSubstrBefore(baseDir, baseDirLen); + + if (confFound) { + if (dbPath.length > 0) { + // If DBPath is specified, use it + ffStrbufEnsureEndsWithC(&dbPath, '/'); + ffStrbufAppendS(&dbPath, "local"); + } else if (rootDir.length > 0) { + // ... otherwise, use RootDir + ffStrbufDestroy(&dbPath); + ffStrbufInitMove(&dbPath, &rootDir); + ffStrbufEnsureEndsWithC(&dbPath, '/'); + ffStrbufAppendS(&dbPath, "var/lib/pacman/local"); + } + } + + if (dbPath.length == 0) { + ffStrbufSetStatic(&dbPath, "/var/lib/pacman/local"); + } + + return getNumElements(baseDir, dbPath.chars, true); +} + +static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) { + if (FF_PACKAGES_IS_ENABLED(options, APK)) { + packageCounts->apk += getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk"); + } + if (FF_PACKAGES_IS_ENABLED(options, DPKG)) { + packageCounts->dpkg += getNumStrings(baseDir, "/var/lib/dpkg/status", "Status: install ok installed", "dpkg"); + } + if (FF_PACKAGES_IS_ENABLED(options, LPKG)) { + packageCounts->lpkg += getNumStrings(baseDir, "/opt/Loc-OS-LPKG/installed-lpkg/Listinstalled-lpkg.list", "\n", "lpkg"); + } + if (FF_PACKAGES_IS_ENABLED(options, EMERGE)) { + packageCounts->emerge += countFilesRecursive(baseDir, "/var/db/pkg", "SIZE"); + } + if (FF_PACKAGES_IS_ENABLED(options, EOPKG)) { + packageCounts->eopkg += getNumElements(baseDir, "/var/lib/eopkg/package", true); + } + if (FF_PACKAGES_IS_ENABLED(options, FLATPAK)) { + packageCounts->flatpakSystem += getFlatpakPackages(baseDir, "/var/lib"); + } + if (FF_PACKAGES_IS_ENABLED(options, KISS)) { + packageCounts->kiss += getNumElements(baseDir, "/var/db/kiss/installed", true); + } + if (FF_PACKAGES_IS_ENABLED(options, NIX)) { + packageCounts->nixDefault += ffPackagesGetNix(baseDir, "/nix/var/nix/profiles/default"); + packageCounts->nixSystem += ffPackagesGetNix(baseDir, "/run/current-system"); + } + if (FF_PACKAGES_IS_ENABLED(options, PACMAN)) { + packageCounts->pacman += getPacmanPackages(baseDir); + } + if (FF_PACKAGES_IS_ENABLED(options, LPKGBUILD)) { + packageCounts->lpkgbuild += getNumElements(baseDir, "/opt/Loc-OS-LPKG/lpkgbuild/remove", false); + } + if (FF_PACKAGES_IS_ENABLED(options, PKGTOOL)) { + packageCounts->pkgtool += getNumElements(baseDir, "/var/log/packages", false); + } + if (FF_PACKAGES_IS_ENABLED(options, RPM)) { + // `Sigmd5` is the only table that doesn't contain the virtual `gpg-pubkey` package + packageCounts->rpm += getSQLite3Int(baseDir, "/var/lib/rpm/rpmdb.sqlite", "SELECT count(*) FROM Sigmd5", "rpm"); + } + if (FF_PACKAGES_IS_ENABLED(options, SNAP)) { + packageCounts->snap += getSnap(baseDir); + } + if (FF_PACKAGES_IS_ENABLED(options, XBPS)) { + packageCounts->xbps += getXBPS(baseDir, "/var/db/xbps"); + } + if (FF_PACKAGES_IS_ENABLED(options, BREW)) { + packageCounts->brewCask += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Caskroom", true); + packageCounts->brew += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Cellar", true); + } + if (FF_PACKAGES_IS_ENABLED(options, PALUDIS)) { + packageCounts->paludis += countFilesRecursive(baseDir, "/var/db/paludis/repositories", "environment.bz2"); + } + if (FF_PACKAGES_IS_ENABLED(options, OPKG)) { + packageCounts->opkg += getNumStrings(baseDir, "/usr/lib/opkg/status", "Package:", "opkg"); // openwrt + } + if (FF_PACKAGES_IS_ENABLED(options, AM)) { + packageCounts->amSystem = getAMSystem(baseDir); + } + if (FF_PACKAGES_IS_ENABLED(options, SORCERY)) { + packageCounts->sorcery += getNumStrings(baseDir, "/var/state/sorcery/packages", ":installed:", "sorcery"); + } + if (FF_PACKAGES_IS_ENABLED(options, GUIX)) { + packageCounts->guixSystem += getGuixPackages(baseDir, "/run/current-system/profile"); + } + if (FF_PACKAGES_IS_ENABLED(options, LINGLONG)) { + packageCounts->linglong += getNumElements(baseDir, "/var/lib/linglong/layers", true); + } + if (FF_PACKAGES_IS_ENABLED(options, PACSTALL)) { + packageCounts->pacstall += getNumElements(baseDir, "/var/lib/pacstall/metadata", false); + } + if (FF_PACKAGES_IS_ENABLED(options, PISI)) { + packageCounts->pisi += getNumElements(baseDir, "/var/lib/pisi/package", true); + } + if (FF_PACKAGES_IS_ENABLED(options, PKGSRC)) { + packageCounts->pkgsrc += getNumElements(baseDir, "/usr/pkg/pkgdb", DT_DIR); + } + if (FF_PACKAGES_IS_ENABLED(options, MOSS)) { + packageCounts->moss += getSQLite3Int(baseDir, "/.moss/db/state", "SELECT COUNT(*) FROM state_selections WHERE state_id = (SELECT MAX(id) FROM state)", "moss"); + } + if (FF_PACKAGES_IS_ENABLED(options, CARDS)) { + packageCounts->cards += getNumElements(baseDir, "/var/lib/pkg/DB", true); + } +} + +static void getPackageCountsRegular(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) { + getPackageCounts(baseDir, packageCounts, options); + + if (FF_PACKAGES_IS_ENABLED(options, PACMAN)) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, FASTFETCH_TARGET_DIR_ETC "/pacman-mirrors.conf"); + if (ffParsePropFile(baseDir->chars, "Branch =", &packageCounts->pacmanBranch) && packageCounts->pacmanBranch.length == 0) { + ffStrbufAppendS(&packageCounts->pacmanBranch, "stable"); + } + ffStrbufSubstrBefore(baseDir, baseDirLength); + } +} + +static void getPackageCountsBedrock(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) { + uint32_t baseDirLength = baseDir->length; + + ffStrbufAppendS(baseDir, "/bedrock/strata"); + + FF_AUTO_CLOSE_DIR DIR* dir = opendir(baseDir->chars); + if (dir == NULL) { + ffStrbufSubstrBefore(baseDir, baseDirLength); + return; + } + + ffStrbufAppendC(baseDir, '/'); + uint32_t baseDirLength2 = baseDir->length; + + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type != DT_DIR) { + continue; + } + if (entry->d_name[0] == '.') { + continue; + } + + ffStrbufAppendS(baseDir, entry->d_name); + getPackageCounts(baseDir, packageCounts, options); + ffStrbufSubstrBefore(baseDir, baseDirLength2); + } + + ffStrbufSubstrBefore(baseDir, baseDirLength); +} + +void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { + FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(512); + ffStrbufAppendS(&baseDir, FASTFETCH_TARGET_DIR_ROOT); + + if (ffStrbufIgnCaseEqualS(&ffDetectOS()->id, "bedrock")) { + getPackageCountsBedrock(&baseDir, result, options); + } else { + getPackageCountsRegular(&baseDir, result, options); + } + +// If SQL failed, we can still try with librpm. +// This is needed on openSUSE, which seems to use a proprietary database file +// This method doesn't work on bedrock, so we do it here. +#ifdef FF_HAVE_RPM + if (FF_PACKAGES_IS_ENABLED(options, RPM) && result->rpm == 0) { + result->rpm = getRpmFromLibrpm(); + } +#endif + + ffStrbufSet(&baseDir, &instance.state.platform.homeDir); + if (FF_PACKAGES_IS_ENABLED(options, NIX)) { + // Count packages from $HOME/.nix-profile + result->nixUser += ffPackagesGetNix(&baseDir, ".nix-profile"); + + // Check in $XDG_STATE_HOME/nix/profile + FF_STRBUF_AUTO_DESTROY stateHome = ffStrbufCreate(); + const char* stateHomeEnv = getenv("XDG_STATE_HOME"); + if (ffStrSet(stateHomeEnv)) { + ffStrbufSetS(&stateHome, stateHomeEnv); + ffStrbufEnsureEndsWithC(&stateHome, '/'); + } else { + ffStrbufSet(&stateHome, &instance.state.platform.homeDir); + ffStrbufAppendS(&stateHome, ".local/state/"); + } + result->nixUser += ffPackagesGetNix(&stateHome, "nix/profile"); + + // Check in /etc/profiles/per-user/$USER + FF_STRBUF_AUTO_DESTROY userPkgsDir = ffStrbufCreateStatic("/etc/profiles/per-user/"); + result->nixUser += ffPackagesGetNix(&userPkgsDir, instance.state.platform.userName.chars); + } + + if (FF_PACKAGES_IS_ENABLED(options, GUIX)) { + result->guixUser += getGuixPackages(&baseDir, ".guix-profile"); + result->guixHome += getGuixPackages(&baseDir, ".guix-home/profile"); + } + + if (FF_PACKAGES_IS_ENABLED(options, FLATPAK)) { + result->flatpakUser = getFlatpakPackages(&baseDir, "/.local/share"); + } + + if (FF_PACKAGES_IS_ENABLED(options, AM)) { + result->amUser = getAMUser(); + } + + if (FF_PACKAGES_IS_ENABLED(options, SOAR)) { + result->soar += getSQLite3Int(&baseDir, ".local/share/soar/db/soar.db", "SELECT COUNT(DISTINCT pkg_id || pkg_name) FROM packages WHERE is_installed = true", "soar"); + } + + if (FF_PACKAGES_IS_ENABLED(options, APPIMAGE)) { + result->appimage += getNumElementsBySuffix(&baseDir, "/AppImages", ".appimage"); + result->appimage += getNumElementsBySuffix(&baseDir, "/Applications", ".appimage"); + } +} -- cgit v1.2.3