summaryrefslogtreecommitdiffstats
path: root/src/detection/packages/packages_nix.c
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/packages/packages_nix.c
Add the files
Diffstat (limited to 'src/detection/packages/packages_nix.c')
-rw-r--r--src/detection/packages/packages_nix.c149
1 files changed, 149 insertions, 0 deletions
diff --git a/src/detection/packages/packages_nix.c b/src/detection/packages/packages_nix.c
new file mode 100644
index 0000000..d1cc264
--- /dev/null
+++ b/src/detection/packages/packages_nix.c
@@ -0,0 +1,149 @@
+#include "packages.h"
+#include "common/io.h"
+#include "common/processing.h"
+#include "common/strutil.h"
+
+static bool isValidNixPkg(FFstrbuf* pkg) {
+ if (!ffPathExists(pkg->chars, FF_PATHTYPE_DIRECTORY)) {
+ return false;
+ }
+
+ ffStrbufSubstrAfterLastC(pkg, '/');
+ if (
+ ffStrbufStartsWithS(pkg, "nixos-system-nixos-") ||
+ ffStrbufEndsWithS(pkg, "-doc") ||
+ ffStrbufEndsWithS(pkg, "-man") ||
+ ffStrbufEndsWithS(pkg, "-info") ||
+ ffStrbufEndsWithS(pkg, "-dev") ||
+ ffStrbufEndsWithS(pkg, "-bin")) {
+ return false;
+ }
+
+ enum { START,
+ DIGIT,
+ DOT,
+ MATCH } state = START;
+
+ for (uint32_t i = 0; i < pkg->length; i++) {
+ char c = pkg->chars[i];
+ switch (state) {
+ case START:
+ if (ffCharIsDigit(c)) {
+ state = DIGIT;
+ }
+ break;
+ case DIGIT:
+ if (ffCharIsDigit(c)) {
+ continue;
+ }
+ if (c == '.') {
+ state = DOT;
+ } else {
+ state = START;
+ }
+ break;
+ case DOT:
+ if (ffCharIsDigit(c)) {
+ state = MATCH;
+ } else {
+ state = START;
+ }
+ break;
+ case MATCH:
+ break;
+ }
+ }
+
+ return state == MATCH;
+}
+
+static bool checkNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t* count) {
+ if (!ffPathExists(cacheDir->chars, FF_PATHTYPE_FILE)) {
+ return false;
+ }
+
+ FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
+ if (!ffReadFileBuffer(cacheDir->chars, &cacheContent)) {
+ return false;
+ }
+
+ // Format: <hash>\n<count>
+ uint32_t split = ffStrbufFirstIndexC(&cacheContent, '\n');
+ if (split == cacheContent.length) {
+ return false;
+ }
+
+ ffStrbufSetNS(hash, split, cacheContent.chars);
+ *count = (uint32_t) atoi(cacheContent.chars + split + 1);
+
+ return true;
+}
+
+static bool writeNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t count) {
+ FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateCopy(hash);
+ ffStrbufAppendF(&cacheContent, "\n%u", count);
+ return ffWriteFileBuffer(cacheDir->chars, &cacheContent);
+}
+
+static uint32_t getNixPackagesImpl(char* path) {
+ // Nix detection is kinda slow, so we only do it if the dir exists
+ if (!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) {
+ return 0;
+ }
+
+ FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreateCopy(&instance.state.platform.cacheDir);
+ ffStrbufEnsureEndsWithC(&cacheDir, '/');
+ ffStrbufAppendS(&cacheDir, "fastfetch/packages/nix");
+ ffStrbufAppendS(&cacheDir, path);
+
+ // Check the hash first to determine if we need to recompute the count
+ FF_STRBUF_AUTO_DESTROY hash = ffStrbufCreateA(64);
+ FF_STRBUF_AUTO_DESTROY cacheHash = ffStrbufCreateA(64);
+ uint32_t count = 0;
+
+ ffProcessAppendStdOut(&hash, (char* const[]) { "nix-store", "--query", "--hash", path, NULL });
+
+ if (checkNixCache(&cacheDir, &cacheHash, &count) && ffStrbufEqual(&hash, &cacheHash)) {
+ return count;
+ }
+
+ // Cache is invalid, recompute the count
+ count = 0;
+
+ // Implementation based on bash script from here:
+ // https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222
+
+ FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024);
+
+ ffProcessAppendStdOut(&output, (char* const[]) { "nix-store", "--query", "--requisites", path, NULL });
+
+ uint32_t lineLength = 0;
+ for (uint32_t i = 0; i < output.length; i++) {
+ if (output.chars[i] != '\n') {
+ lineLength++;
+ continue;
+ }
+
+ output.chars[i] = '\0';
+ FFstrbuf line = {
+ .allocated = 0,
+ .length = lineLength,
+ .chars = output.chars + i - lineLength
+ };
+ if (isValidNixPkg(&line)) {
+ count++;
+ }
+ lineLength = 0;
+ }
+
+ writeNixCache(&cacheDir, &hash, count);
+ return count;
+}
+
+uint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname) {
+ uint32_t baseDirLength = baseDir->length;
+ ffStrbufAppendS(baseDir, dirname);
+ uint32_t num_elements = getNixPackagesImpl(baseDir->chars);
+ ffStrbufSubstrBefore(baseDir, baseDirLength);
+ return num_elements;
+}