ohmu
·
2026-10-15
1#include "packages.h"
2#include "common/io.h"
3#include "common/parsing.h"
4#include "common/properties.h"
5#include "common/settings.h"
6#include "common/strutil.h"
7#include "detection/os/os.h"
8
9static uint32_t getNumElements(FFstrbuf* baseDir, const char* dirname, bool isdir) {
10 uint32_t baseDirLength = baseDir->length;
11 ffStrbufAppendS(baseDir, dirname);
12 uint32_t num_elements = ffPackagesGetNumElements(baseDir->chars, isdir);
13 ffStrbufSubstrBefore(baseDir, baseDirLength);
14 return num_elements;
15}
16
17static uint32_t getNumStringsImpl(const char* filename, const char* needle) {
18 FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
19 if (!ffReadFileBuffer(filename, &content)) {
20 return 0;
21 }
22
23 uint32_t count = 0;
24 char* iter = content.chars;
25 size_t needleLength = strlen(needle);
26 while ((iter = memmem(iter, content.length - (size_t) (iter - content.chars), needle, needleLength)) != nullptr) {
27 ++count;
28 iter += needleLength;
29 }
30
31 return count;
32}
33
34static uint32_t getNumStrings(FFstrbuf* baseDir, const char* filename, const char* needle, const char* packageId) {
35 uint32_t baseDirLength = baseDir->length;
36 ffStrbufAppendS(baseDir, filename);
37
38 FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate();
39 FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
40
41 uint32_t num_elements;
42 if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements)) {
43 ffStrbufSubstrBefore(baseDir, baseDirLength);
44 return num_elements;
45 }
46
47 num_elements = getNumStringsImpl(baseDir->chars, needle);
48 ffStrbufSubstrBefore(baseDir, baseDirLength);
49
50 ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements);
51
52 return num_elements;
53}
54
55static uint32_t getSQLite3Int(FFstrbuf* baseDir, const char* dbPath, const char* query, const char* packageId) {
56 uint32_t baseDirLength = baseDir->length;
57 ffStrbufAppendS(baseDir, dbPath);
58
59 FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate();
60 FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
61
62 uint32_t num_elements;
63 if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements)) {
64 ffStrbufSubstrBefore(baseDir, baseDirLength);
65 return num_elements;
66 }
67
68 num_elements = (uint32_t) ffSettingsGetSQLite3Int(baseDir->chars, query);
69 ffStrbufSubstrBefore(baseDir, baseDirLength);
70
71 ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements);
72
73 return num_elements;
74}
75
76static uint32_t countFilesRecursiveImpl(FFstrbuf* baseDirPath, const char* filename) {
77 uint32_t baseDirPathLength = baseDirPath->length;
78
79 ffStrbufAppendC(baseDirPath, '/');
80 ffStrbufAppendS(baseDirPath, filename);
81 bool exists = ffPathExists(baseDirPath->chars, FF_PATHTYPE_FILE);
82 ffStrbufSubstrBefore(baseDirPath, baseDirPathLength);
83 if (exists) {
84 return 1;
85 }
86
87 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDirPath->chars);
88 if (dirp == nullptr) {
89 return 0;
90 }
91
92 ffStrbufAppendC(baseDirPath, '/');
93 baseDirPathLength = baseDirPath->length;
94
95 uint32_t sum = 0;
96
97 struct dirent* entry;
98 while ((entry = readdir(dirp)) != nullptr) {
99 // According to the PMS, neither category nor package name can begin with '.', so no need to check for . or .. specifically
100 if (entry->d_type != DT_DIR || entry->d_name[0] == '.') {
101 continue;
102 }
103
104 ffStrbufAppendS(baseDirPath, entry->d_name);
105 sum += countFilesRecursiveImpl(baseDirPath, filename);
106 ffStrbufSubstrBefore(baseDirPath, baseDirPathLength);
107 }
108
109 return sum;
110}
111
112static uint32_t countFilesRecursive(FFstrbuf* baseDir, const char* dirname, const char* filename) {
113 uint32_t baseDirLength = baseDir->length;
114 ffStrbufAppendS(baseDir, dirname);
115 uint32_t sum = countFilesRecursiveImpl(baseDir, filename);
116 ffStrbufSubstrBefore(baseDir, baseDirLength);
117 return sum;
118}
119
120static uint32_t getNumElementsBySuffix(FFstrbuf* baseDir, const char* dirname, const char* suffix) {
121 uint32_t baseDirLength = baseDir->length;
122 ffStrbufAppendS(baseDir, dirname);
123 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);
124 ffStrbufSubstrBefore(baseDir, baseDirLength);
125 if (dirp == nullptr) {
126 return 0;
127 }
128
129 uint32_t count = 0;
130 struct dirent* entry;
131 while ((entry = readdir(dirp)) != nullptr) {
132 if (entry->d_name[0] != '.' && ffStrEndsWithIgnCase(entry->d_name, suffix)) {
133 ++count;
134 }
135 }
136 return count;
137}
138
139static uint32_t getXBPSImpl(FFstrbuf* baseDir) {
140 FF_AUTO_CLOSE_DIR DIR* dir = opendir(baseDir->chars);
141 if (dir == nullptr) {
142 return 0;
143 }
144
145 uint32_t result = 0;
146
147 struct dirent* entry;
148 while ((entry = readdir(dir)) != nullptr) {
149 if (entry->d_type != DT_REG || !ffStrStartsWithIgnCase(entry->d_name, "pkgdb-")) {
150 continue;
151 }
152
153 ffStrbufAppendC(baseDir, '/');
154 ffStrbufAppendS(baseDir, entry->d_name);
155 result = getNumStringsImpl(baseDir->chars, "<string>installed</string>");
156 break;
157 }
158
159 return result;
160}
161
162static uint32_t getXBPS(FFstrbuf* baseDir, const char* dirname) {
163 uint32_t baseDirLength = baseDir->length;
164 ffStrbufAppendS(baseDir, dirname);
165 uint32_t result = getXBPSImpl(baseDir);
166 ffStrbufSubstrBefore(baseDir, baseDirLength);
167 return result;
168}
169
170static uint32_t getSnap(FFstrbuf* baseDir) {
171 uint32_t result = getNumElements(baseDir, "/snap", true);
172
173 if (result == 0) {
174 result = getNumElements(baseDir, "/var/lib/snapd/snap", true);
175 }
176
177 // Accounting for the /snap/bin folder
178 return result > 0 ? result - 1 : 0;
179}
180
181#ifdef FF_HAVE_RPM
182 #include "common/library.h"
183 #include <rpm/rpmlib.h>
184 #include <rpm/rpmts.h>
185 #include <rpm/rpmdb.h>
186 #include <rpm/rpmlog.h>
187
188static uint32_t getRpmFromLibrpm(void) {
189 FF_LIBRARY_LOAD(rpm, 0, "librpm" FF_LIBRARY_EXTENSION, 12)
190 FF_LIBRARY_LOAD_SYMBOL(rpm, rpmReadConfigFiles, 0)
191 FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsCreate, 0)
192 FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsInitIterator, 0)
193 FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbGetIteratorCount, 0)
194 FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbFreeIterator, 0)
195 FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsFree, 0)
196 FF_LIBRARY_LOAD_SYMBOL(rpm, rpmlogSetMask, 0)
197
198 // Don't print any error messages
199 ffrpmlogSetMask(RPMLOG_MASK(RPMLOG_EMERG));
200
201 if (ffrpmReadConfigFiles(nullptr, nullptr) != 0) {
202 return 0;
203 }
204
205 rpmts ts = ffrpmtsCreate();
206 if (ts == nullptr) {
207 return 0;
208 }
209
210 rpmdbMatchIterator mi = ffrpmtsInitIterator(ts, RPMDBI_LABEL, nullptr, 0);
211 if (mi == nullptr) {
212 ffrpmtsFree(ts);
213 return 0;
214 }
215
216 int count = ffrpmdbGetIteratorCount(mi);
217
218 ffrpmdbFreeIterator(mi);
219 ffrpmtsFree(ts);
220
221 return count > 0 ? (uint32_t) count : 0;
222}
223
224#endif // FF_HAVE_RPM
225
226static uint32_t getAMPackages(FFstrbuf* baseDir) {
227 uint32_t baseLength = baseDir->length;
228 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);
229 if (!dirp) {
230 return 0;
231 }
232
233 uint32_t result = 0;
234 struct dirent* entry;
235 while ((entry = readdir(dirp)) != nullptr) {
236 if (entry->d_name[0] == '.') {
237 continue;
238 }
239 if (entry->d_type == DT_DIR) {
240 ffStrbufAppendF(baseDir, "/%s/remove", entry->d_name);
241 if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) {
242 ++result;
243 }
244 ffStrbufSubstrBefore(baseDir, baseLength);
245 }
246 }
247 return result;
248}
249
250static uint32_t getAMSystem(FFstrbuf* baseDir) {
251 // #771
252 uint32_t baseDirLength = baseDir->length;
253
254 ffStrbufAppendS(baseDir, "/opt");
255 uint32_t optDirLength = baseDir->length;
256
257 uint32_t result = 0;
258
259 ffStrbufAppendS(baseDir, "/am/APP-MANAGER");
260 if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) {
261 ++result; // `am` itself is counted as a package too
262 ffStrbufSubstrBefore(baseDir, optDirLength);
263 result = getAMPackages(baseDir);
264 }
265
266 ffStrbufSubstrBefore(baseDir, baseDirLength);
267 return result;
268}
269
270static uint32_t getAMUser(void) {
271 if (instance.state.platform.configDirs.length == 0) {
272 return 0;
273 }
274
275 // check if $XDG_CONFIG_HOME/appman/appman-config exists
276 FFstrbuf* baseDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs);
277 uint32_t baseLen = baseDir->length;
278 ffStrbufAppendS(baseDir, "appman/appman-config");
279 FF_STRBUF_AUTO_DESTROY packagesPath = ffStrbufCreate();
280 if (ffReadFileBuffer(baseDir->chars, &packagesPath)) {
281 ffStrbufTrimRightSpace(&packagesPath);
282 }
283 ffStrbufSubstrBefore(baseDir, baseLen);
284
285 return packagesPath.length > 0 ? getAMPackages(&packagesPath) : 0;
286}
287
288static int compareHash(const void* a, const void* b) {
289 return memcmp(a, b, 32);
290}
291
292static uint32_t getGuixPackagesImpl(char* filename) {
293 FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
294 if (!ffAppendFileBuffer(filename, &content)) {
295 return 0;
296 }
297
298 // Count number of unique /gnu/store/ paths in PROFILE/manifest based on their hash value.
299 // Contains packages explicitly installed and their propagated inputs.
300 char* pend = content.chars;
301
302 for (const char* pattern = content.chars; (pattern = strstr(pattern, "/gnu/store/")); pattern += 32) {
303 pattern += strlen("/gnu/store/");
304 memmove(pend, pattern, 32);
305 pend += 32;
306 }
307
308 if (pend == content.chars) {
309 return 0;
310 }
311
312 qsort(content.chars, (size_t) (pend - content.chars) / 32, 32, compareHash);
313
314 uint32_t count = 1;
315 for (const char* p = content.chars + 32; p < pend; p += 32) {
316 count += compareHash(p - 32, p) != 0;
317 }
318
319 return count;
320}
321
322static uint32_t getGuixPackages(FFstrbuf* baseDir, const char* dirname) {
323 uint32_t baseDirLength = baseDir->length;
324 ffStrbufAppendS(baseDir, dirname);
325 ffStrbufAppendS(baseDir, "/manifest");
326 uint32_t num_elements = getGuixPackagesImpl(baseDir->chars);
327 ffStrbufSubstrBefore(baseDir, baseDirLength);
328 return num_elements;
329}
330
331static inline uint32_t getFlatpakRuntimePackagesArch(FFstrbuf* baseDir) {
332 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);
333 if (dirp == nullptr) {
334 return 0;
335 }
336
337 uint32_t num_elements = 0;
338
339 struct dirent* entry;
340 while ((entry = readdir(dirp)) != nullptr) {
341 if (entry->d_type == DT_DIR && entry->d_name[0] != '.') {
342 num_elements += getNumElements(baseDir, entry->d_name, true);
343 }
344 }
345
346 return num_elements;
347}
348
349static inline uint32_t getFlatpakRuntimePackages(FFstrbuf* baseDir) {
350 ffStrbufAppendS(baseDir, "runtime/");
351 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);
352 if (dirp == nullptr) {
353 return 0;
354 }
355
356 uint32_t runtimeDirLength = baseDir->length;
357 uint32_t num_elements = 0;
358
359 struct dirent* entry;
360 while ((entry = readdir(dirp)) != nullptr) {
361 if (entry->d_type == DT_DIR && entry->d_name[0] != '.') {
362 // `flatpak list` ignores `.Locale` and `.Debug` packages, and maybe others
363 const char* dot = strrchr(entry->d_name, '.');
364 if (__builtin_expect(!dot, false)) {
365 continue;
366 }
367 dot++;
368
369 if (ffStrEquals(dot, "Locale") || ffStrEquals(dot, "Debug")) {
370 continue;
371 }
372
373 ffStrbufAppendS(baseDir, entry->d_name);
374 ffStrbufAppendC(baseDir, '/');
375 num_elements += getFlatpakRuntimePackagesArch(baseDir);
376 ffStrbufSubstrBefore(baseDir, runtimeDirLength);
377 }
378 }
379
380 return num_elements;
381}
382
383static inline uint32_t getFlatpakAppPackages(FFstrbuf* baseDir) {
384 ffStrbufAppendS(baseDir, "app/");
385 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);
386 if (dirp == nullptr) {
387 return 0;
388 }
389
390 uint32_t appDirLength = baseDir->length;
391 uint32_t num_elements = 0;
392
393 struct dirent* entry;
394 while ((entry = readdir(dirp)) != nullptr) {
395 if (entry->d_type == DT_DIR && entry->d_name[0] != '.') {
396 ffStrbufAppendS(baseDir, entry->d_name);
397 ffStrbufAppendS(baseDir, "/current");
398 if (ffPathExists(baseDir->chars, FF_PATHTYPE_ANY)) { // Exclude deleted apps, #1856
399 ++num_elements;
400 }
401 ffStrbufSubstrBefore(baseDir, appDirLength);
402 }
403 }
404 return num_elements;
405}
406
407static uint32_t getFlatpakPackages(FFstrbuf* baseDir, const char* dirname) {
408 uint32_t num_elements = 0;
409 uint32_t baseDirLength = baseDir->length;
410 ffStrbufAppendS(baseDir, dirname);
411 ffStrbufAppendS(baseDir, "/flatpak/");
412 uint32_t flatpakDirLength = baseDir->length;
413
414 num_elements += getFlatpakAppPackages(baseDir);
415 ffStrbufSubstrBefore(baseDir, flatpakDirLength);
416
417 num_elements += getFlatpakRuntimePackages(baseDir);
418
419 ffStrbufSubstrBefore(baseDir, baseDirLength);
420
421 return num_elements;
422}
423
424static uint32_t getPacmanPackages(FFstrbuf* baseDir) {
425 FF_STRBUF_AUTO_DESTROY dbPath = ffStrbufCreate();
426 FF_STRBUF_AUTO_DESTROY rootDir = ffStrbufCreate();
427
428 // Get path to pacman.conf
429 uint32_t baseDirLen = baseDir->length;
430 ffStrbufAppendS(baseDir, "/etc/pacman.conf");
431
432 bool confFound = ffParsePropFileValues(baseDir->chars, 2, (FFpropquery[]) {
433 { "DBPath =", &dbPath },
434 { "RootDir =", &rootDir },
435 });
436 ffStrbufSubstrBefore(baseDir, baseDirLen);
437
438 if (confFound) {
439 if (dbPath.length > 0) {
440 // If DBPath is specified, use it
441 ffStrbufEnsureEndsWithC(&dbPath, '/');
442 ffStrbufAppendS(&dbPath, "local");
443 } else if (rootDir.length > 0) {
444 // ... otherwise, use RootDir
445 ffStrbufDestroy(&dbPath);
446 ffStrbufInitMove(&dbPath, &rootDir);
447 ffStrbufEnsureEndsWithC(&dbPath, '/');
448 ffStrbufAppendS(&dbPath, "var/lib/pacman/local");
449 }
450 }
451
452 if (dbPath.length == 0) {
453 ffStrbufSetStatic(&dbPath, "/var/lib/pacman/local");
454 }
455
456 return getNumElements(baseDir, dbPath.chars, true);
457}
458
459static uint32_t getEmergePackagesImpl(FFstrbuf* baseDir) {
460 FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars);
461 if (dirp == nullptr)
462 return 0;
463
464 uint32_t result = 0;
465
466 struct dirent *entry;
467 while ((entry = readdir(dirp)) != nullptr)
468 {
469 if (entry->d_type != DT_DIR || entry->d_name[0] == '.')
470 continue;
471
472 result += getNumElements(baseDir, entry->d_name, true);
473 }
474 return result;
475}
476
477static uint32_t getEmergePackages(FFstrbuf* baseDir, const char* dirname) {
478 uint32_t baseDirLength = baseDir->length;
479 ffStrbufAppendS(baseDir, dirname);
480 ffStrbufAppendC(baseDir, '/');
481 uint32_t result = getEmergePackagesImpl(baseDir);
482 ffStrbufSubstrBefore(baseDir, baseDirLength);
483 return result;
484}
485
486static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) {
487 if (FF_PACKAGES_IS_ENABLED(options, APK)) {
488 packageCounts->apk += getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk");
489 }
490 if (FF_PACKAGES_IS_ENABLED(options, CRUX)) {
491 packageCounts->crux += getNumStrings(baseDir, "/var/lib/pkg/db", "\n\n", "crux");
492 }
493 if (FF_PACKAGES_IS_ENABLED(options, DPKG)) {
494 packageCounts->dpkg += getNumStrings(baseDir, "/var/lib/dpkg/status", "Status: install ok installed", "dpkg");
495 }
496 if (FF_PACKAGES_IS_ENABLED(options, LPKG)) {
497 packageCounts->lpkg += getNumStrings(baseDir, "/opt/Loc-OS-LPKG/installed-lpkg/Listinstalled-lpkg.list", "\n", "lpkg");
498 }
499 if (FF_PACKAGES_IS_ENABLED(options, EMERGE)) {
500 packageCounts->emerge += getEmergePackages(baseDir, "/var/db/pkg");
501 }
502 if (FF_PACKAGES_IS_ENABLED(options, EOPKG)) {
503 packageCounts->eopkg += getNumElements(baseDir, "/var/lib/eopkg/package", true);
504 }
505 if (FF_PACKAGES_IS_ENABLED(options, FLATPAK)) {
506 packageCounts->flatpakSystem += getFlatpakPackages(baseDir, "/var/lib");
507 }
508 if (FF_PACKAGES_IS_ENABLED(options, KISS)) {
509 packageCounts->kiss += getNumElements(baseDir, "/var/db/kiss/installed", true);
510 }
511 if (FF_PACKAGES_IS_ENABLED(options, NIX)) {
512 packageCounts->nixDefault += ffPackagesGetNix(baseDir, "/nix/var/nix/profiles/default");
513 packageCounts->nixSystem += ffPackagesGetNix(baseDir, "/run/current-system");
514 }
515 if (FF_PACKAGES_IS_ENABLED(options, PACMAN)) {
516 packageCounts->pacman += getPacmanPackages(baseDir);
517 }
518 if (FF_PACKAGES_IS_ENABLED(options, LPKGBUILD)) {
519 packageCounts->lpkgbuild += getNumElements(baseDir, "/opt/Loc-OS-LPKG/lpkgbuild/remove", false);
520 }
521 if (FF_PACKAGES_IS_ENABLED(options, PKGTOOL)) {
522 packageCounts->pkgtool += getNumElements(baseDir, "/var/log/packages", false);
523 }
524 if (FF_PACKAGES_IS_ENABLED(options, PORG)) {
525 packageCounts->porg += getNumElements(baseDir, "/var/log/porg", false);
526 }
527 if (FF_PACKAGES_IS_ENABLED(options, RPM)) {
528 // `Sigmd5` is the only table that doesn't contain the virtual `gpg-pubkey` package
529 packageCounts->rpm += getSQLite3Int(baseDir, "/var/lib/rpm/rpmdb.sqlite", "SELECT count(*) FROM Sigmd5", "rpm");
530 }
531 if (FF_PACKAGES_IS_ENABLED(options, RUM)) {
532 packageCounts->rum += getSQLite3Int(baseDir, "/var/lib/rakuos/rum-rpmdb/rpmdb.sqlite", "SELECT count(*) FROM Sigmd5", "rum");
533 }
534 if (FF_PACKAGES_IS_ENABLED(options, SNAP)) {
535 packageCounts->snap += getSnap(baseDir);
536 }
537 if (FF_PACKAGES_IS_ENABLED(options, XBPS)) {
538 packageCounts->xbps += getXBPS(baseDir, "/var/db/xbps");
539 }
540 if (FF_PACKAGES_IS_ENABLED(options, BREW)) {
541 packageCounts->brewCask += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Caskroom", true);
542 packageCounts->brew += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Cellar", true);
543 }
544 if (FF_PACKAGES_IS_ENABLED(options, PALUDIS)) {
545 packageCounts->paludis += countFilesRecursive(baseDir, "/var/db/paludis/repositories", "environment.bz2");
546 }
547 if (FF_PACKAGES_IS_ENABLED(options, OPKG)) {
548 packageCounts->opkg += getNumStrings(baseDir, "/usr/lib/opkg/status", "Package:", "opkg"); // openwrt
549 }
550 if (FF_PACKAGES_IS_ENABLED(options, AM)) {
551 packageCounts->amSystem = getAMSystem(baseDir);
552 }
553 if (FF_PACKAGES_IS_ENABLED(options, SORCERY)) {
554 packageCounts->sorcery += getNumStrings(baseDir, "/var/state/sorcery/packages", ":installed:", "sorcery");
555 }
556 if (FF_PACKAGES_IS_ENABLED(options, GUIX)) {
557 packageCounts->guixSystem += getGuixPackages(baseDir, "/run/current-system/profile");
558 }
559 if (FF_PACKAGES_IS_ENABLED(options, LINGLONG)) {
560 packageCounts->linglong += getNumElements(baseDir, "/var/lib/linglong/layers", true);
561 }
562 if (FF_PACKAGES_IS_ENABLED(options, PACSTALL)) {
563 packageCounts->pacstall += getNumElements(baseDir, "/var/lib/pacstall/metadata", false);
564 }
565 if (FF_PACKAGES_IS_ENABLED(options, PISI)) {
566 packageCounts->pisi += getNumElements(baseDir, "/var/lib/pisi/package", true);
567 }
568 if (FF_PACKAGES_IS_ENABLED(options, PKGSRC)) {
569 packageCounts->pkgsrc += getNumElements(baseDir, "/usr/pkg/pkgdb", DT_DIR);
570 }
571 if (FF_PACKAGES_IS_ENABLED(options, MOSS)) {
572 packageCounts->moss += getSQLite3Int(baseDir, "/.moss/db/state", "SELECT COUNT(*) FROM state_selections WHERE state_id = (SELECT MAX(id) FROM state)", "moss");
573 }
574 if (FF_PACKAGES_IS_ENABLED(options, CARDS)) {
575 packageCounts->cards += getNumElements(baseDir, "/var/lib/pkg/DB", true);
576 }
577}
578
579static void getPackageCountsRegular(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) {
580 getPackageCounts(baseDir, packageCounts, options);
581
582 if (FF_PACKAGES_IS_ENABLED(options, PACMAN)) {
583 uint32_t baseDirLength = baseDir->length;
584 ffStrbufAppendS(baseDir, FASTFETCH_TARGET_DIR_ETC "/pacman-mirrors.conf");
585 if (ffParsePropFile(baseDir->chars, "Branch =", &packageCounts->pacmanBranch) && packageCounts->pacmanBranch.length == 0) {
586 ffStrbufAppendS(&packageCounts->pacmanBranch, "stable");
587 }
588 ffStrbufSubstrBefore(baseDir, baseDirLength);
589 }
590}
591
592static void getPackageCountsBedrock(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) {
593 uint32_t baseDirLength = baseDir->length;
594
595 ffStrbufAppendS(baseDir, "/bedrock/strata");
596
597 FF_AUTO_CLOSE_DIR DIR* dir = opendir(baseDir->chars);
598 if (dir == nullptr) {
599 ffStrbufSubstrBefore(baseDir, baseDirLength);
600 return;
601 }
602
603 ffStrbufAppendC(baseDir, '/');
604 uint32_t baseDirLength2 = baseDir->length;
605
606 struct dirent* entry;
607 while ((entry = readdir(dir)) != nullptr) {
608 if (entry->d_type != DT_DIR) {
609 continue;
610 }
611 if (entry->d_name[0] == '.') {
612 continue;
613 }
614
615 ffStrbufAppendS(baseDir, entry->d_name);
616 getPackageCounts(baseDir, packageCounts, options);
617 ffStrbufSubstrBefore(baseDir, baseDirLength2);
618 }
619
620 ffStrbufSubstrBefore(baseDir, baseDirLength);
621}
622
623static uint32_t getInstallReleasePackages(FFstrbuf* baseDir) {
624 uint32_t result = 0;
625
626 uint32_t baseDirLength = baseDir->length;
627 ffStrbufAppendS(baseDir, ".config/install_release/state.json");
628 if (ffPathExists(baseDir->chars, FF_PATHTYPE_ANY)) {
629 yyjson_doc* doc = yyjson_read_file(baseDir->chars, YYJSON_READ_NOFLAG, nullptr, nullptr);
630 if (doc != nullptr) {
631 yyjson_val* root = yyjson_doc_get_root(doc);
632 if (yyjson_is_obj(root)) {
633 result = (uint32_t) yyjson_obj_size(root);
634 }
635 yyjson_doc_free(doc);
636 }
637 }
638 ffStrbufSubstrBefore(baseDir, baseDirLength);
639 return result;
640}
641
642void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) {
643 FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(512);
644 ffStrbufAppendS(&baseDir, FASTFETCH_TARGET_DIR_ROOT);
645
646 if (ffStrbufIgnCaseEqualS(&ffDetectOS()->id, "bedrock")) {
647 getPackageCountsBedrock(&baseDir, result, options);
648 } else {
649 getPackageCountsRegular(&baseDir, result, options);
650 }
651
652// If SQL failed, we can still try with librpm.
653// This is needed on openSUSE, which seems to use a proprietary database file
654// This method doesn't work on bedrock, so we do it here.
655#ifdef FF_HAVE_RPM
656 if (FF_PACKAGES_IS_ENABLED(options, RPM) && result->rpm == 0) {
657 result->rpm = getRpmFromLibrpm();
658 }
659#endif
660
661 ffStrbufSet(&baseDir, &instance.state.platform.homeDir);
662 if (FF_PACKAGES_IS_ENABLED(options, NIX)) {
663 // Count packages from $HOME/.nix-profile
664 result->nixUser += ffPackagesGetNix(&baseDir, ".nix-profile");
665
666 // Check in $XDG_STATE_HOME/nix/profile
667 FF_STRBUF_AUTO_DESTROY stateHome = ffStrbufCreate();
668 const char* stateHomeEnv = getenv("XDG_STATE_HOME");
669 if (ffStrSet(stateHomeEnv)) {
670 ffStrbufSetS(&stateHome, stateHomeEnv);
671 ffStrbufEnsureEndsWithC(&stateHome, '/');
672 } else {
673 ffStrbufSet(&stateHome, &instance.state.platform.homeDir);
674 ffStrbufAppendS(&stateHome, ".local/state/");
675 }
676 result->nixUser += ffPackagesGetNix(&stateHome, "nix/profile");
677
678 // Check in /etc/profiles/per-user/$USER
679 FF_STRBUF_AUTO_DESTROY userPkgsDir = ffStrbufCreateStatic("/etc/profiles/per-user/");
680 result->nixUser += ffPackagesGetNix(&userPkgsDir, instance.state.platform.userName.chars);
681 }
682
683 if (FF_PACKAGES_IS_ENABLED(options, GUIX)) {
684 result->guixUser += getGuixPackages(&baseDir, ".guix-profile");
685 result->guixHome += getGuixPackages(&baseDir, ".guix-home/profile");
686 }
687
688 if (FF_PACKAGES_IS_ENABLED(options, FLATPAK)) {
689 result->flatpakUser = getFlatpakPackages(&baseDir, "/.local/share");
690 }
691
692 if (FF_PACKAGES_IS_ENABLED(options, AM)) {
693 result->amUser = getAMUser();
694 }
695
696 if (FF_PACKAGES_IS_ENABLED(options, SOAR)) {
697 result->soar += getSQLite3Int(&baseDir, ".local/share/soar/db/soar.db", "SELECT COUNT(DISTINCT pkg_id || pkg_name) FROM packages WHERE is_installed = true", "soar");
698 }
699
700 if (FF_PACKAGES_IS_ENABLED(options, APPIMAGE)) {
701 result->appimage += getNumElementsBySuffix(&baseDir, "/AppImages", ".appimage");
702 result->appimage += getNumElementsBySuffix(&baseDir, "/Applications", ".appimage");
703 }
704
705 if (FF_PACKAGES_IS_ENABLED(options, INSTALLRELEASE)) {
706 result->installrelease = getInstallReleasePackages(&baseDir);
707 }
708}