Skip to content

Speedup nixos package count #817

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 133 additions & 9 deletions src/detection/packages/packages_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,28 +118,152 @@ static uint32_t countFilesRecursive(FFstrbuf* baseDir, const char* dirname, cons
return sum;
}

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 (c >= '0' && c <= '9')
state = DIGIT;
break;
case DIGIT:
if (c >= '0' && c <= '9')
continue;
if (c == '.')
state = DOT;
else
state = START;
break;
case DOT:
if (c >= '0' && c <= '9')
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;
ffStrbufInit(&cacheContent);
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;
ffStrbufInit(&cacheContent);
ffStrbufAppend(&cacheContent, hash);
ffStrbufAppendC(&cacheContent, '\n');
ffStrbufAppendF(&cacheContent, "%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 output = ffStrbufCreateA(128);
FF_STRBUF_AUTO_DESTROY cacheDir;
ffStrbufInit(&cacheDir);
ffStrbufAppend(&cacheDir, &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 command = ffStrbufCreateA(255);
ffStrbufAppendS(&command, "for x in $(nix-store --query --requisites ");
ffStrbufAppendS(&command, path);
ffStrbufAppendS(&command, "); do if [ -d $x ]; then echo $x ; fi ; done | cut -d- -f2- | egrep '([0-9]{1,}\\.)+[0-9]{1,}' | egrep -v '\\-doc$|\\-man$|\\-info$|\\-dev$|\\-bin$|^nixos-system-nixos-' | uniq | wc -l");

FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024);

ffProcessAppendStdOut(&output, (char* const[]) {
"sh",
"-c",
command.chars,
"nix-store",
"--query",
"--requisites",
path,
NULL
});

return (uint32_t) strtol(output.chars, NULL, 10);
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;
}

static uint32_t getNixPackages(FFstrbuf* baseDir, const char* dirname)
Expand Down