master ohmu/fastfetch-openbsd / src / detection / packages / packages_nix.c
ohmu  ·  2026-10-15
  1#include "packages.h"
  2#include "common/io.h"
  3#include "common/processing.h"
  4#include "common/strutil.h"
  5
  6static bool isValidNixPkg(FFstrbuf* pkg) {
  7    if (!ffPathExists(pkg->chars, FF_PATHTYPE_DIRECTORY)) {
  8        return false;
  9    }
 10
 11    ffStrbufSubstrAfterLastC(pkg, '/');
 12    if (
 13        ffStrbufStartsWithS(pkg, "nixos-system-nixos-") ||
 14        ffStrbufEndsWithS(pkg, "-doc") ||
 15        ffStrbufEndsWithS(pkg, "-man") ||
 16        ffStrbufEndsWithS(pkg, "-info") ||
 17        ffStrbufEndsWithS(pkg, "-dev") ||
 18        ffStrbufEndsWithS(pkg, "-bin")) {
 19        return false;
 20    }
 21
 22    enum { START,
 23        DIGIT,
 24        DOT,
 25        MATCH } state = START;
 26
 27    for (uint32_t i = 0; i < pkg->length; i++) {
 28        char c = pkg->chars[i];
 29        switch (state) {
 30            case START:
 31                if (ffCharIsDigit(c)) {
 32                    state = DIGIT;
 33                }
 34                break;
 35            case DIGIT:
 36                if (ffCharIsDigit(c)) {
 37                    continue;
 38                }
 39                if (c == '.') {
 40                    state = DOT;
 41                } else {
 42                    state = START;
 43                }
 44                break;
 45            case DOT:
 46                if (ffCharIsDigit(c)) {
 47                    state = MATCH;
 48                } else {
 49                    state = START;
 50                }
 51                break;
 52            case MATCH:
 53                break;
 54        }
 55    }
 56
 57    return state == MATCH;
 58}
 59
 60static bool checkNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t* count) {
 61    if (!ffPathExists(cacheDir->chars, FF_PATHTYPE_FILE)) {
 62        return false;
 63    }
 64
 65    FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
 66    if (!ffReadFileBuffer(cacheDir->chars, &cacheContent)) {
 67        return false;
 68    }
 69
 70    // Format: <hash>\n<count>
 71    uint32_t split = ffStrbufFirstIndexC(&cacheContent, '\n');
 72    if (split == cacheContent.length) {
 73        return false;
 74    }
 75
 76    ffStrbufSetNS(hash, split, cacheContent.chars);
 77    *count = (uint32_t) atoi(cacheContent.chars + split + 1);
 78
 79    return true;
 80}
 81
 82static bool writeNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t count) {
 83    FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateCopy(hash);
 84    ffStrbufAppendF(&cacheContent, "\n%u", count);
 85    return ffWriteFileBuffer(cacheDir->chars, &cacheContent);
 86}
 87
 88static uint32_t getNixPackagesImpl(char* path) {
 89    // Nix detection is kinda slow, so we only do it if the dir exists
 90    if (!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) {
 91        return 0;
 92    }
 93
 94    FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreateCopy(&instance.state.platform.cacheDir);
 95    ffStrbufEnsureEndsWithC(&cacheDir, '/');
 96    ffStrbufAppendS(&cacheDir, "fastfetch/packages/nix");
 97    ffStrbufAppendS(&cacheDir, path);
 98
 99    // Check the hash first to determine if we need to recompute the count
100    FF_STRBUF_AUTO_DESTROY hash = ffStrbufCreateA(64);
101    FF_STRBUF_AUTO_DESTROY cacheHash = ffStrbufCreateA(64);
102    uint32_t count = 0;
103
104    ffProcessAppendStdOut(&hash, (char* const[]) { "nix-store", "--query", "--hash", path, nullptr });
105
106    if (checkNixCache(&cacheDir, &cacheHash, &count) && ffStrbufEqual(&hash, &cacheHash)) {
107        return count;
108    }
109
110    // Cache is invalid, recompute the count
111    count = 0;
112
113    // Implementation based on bash script from here:
114    // https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222
115
116    FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024);
117
118    ffProcessAppendStdOut(&output, (char* const[]) { "nix-store", "--query", "--requisites", path, nullptr });
119
120    uint32_t lineLength = 0;
121    for (uint32_t i = 0; i < output.length; i++) {
122        if (output.chars[i] != '\n') {
123            lineLength++;
124            continue;
125        }
126
127        output.chars[i] = '\0';
128        FFstrbuf line = {
129            .allocated = 0,
130            .length = lineLength,
131            .chars = output.chars + i - lineLength
132        };
133        if (isValidNixPkg(&line)) {
134            count++;
135        }
136        lineLength = 0;
137    }
138
139    writeNixCache(&cacheDir, &hash, count);
140    return count;
141}
142
143uint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname) {
144    uint32_t baseDirLength = baseDir->length;
145    ffStrbufAppendS(baseDir, dirname);
146    uint32_t num_elements = getNixPackagesImpl(baseDir->chars);
147    ffStrbufSubstrBefore(baseDir, baseDirLength);
148    return num_elements;
149}