summaryrefslogtreecommitdiffstats
path: root/src/detection/packages/packages_windows.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/packages/packages_windows.c')
-rw-r--r--src/detection/packages/packages_windows.c203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/detection/packages/packages_windows.c b/src/detection/packages/packages_windows.c
new file mode 100644
index 0000000..a3faee5
--- /dev/null
+++ b/src/detection/packages/packages_windows.c
@@ -0,0 +1,203 @@
+#include "packages.h"
+#include "common/processing.h"
+#include "common/strutil.h"
+#include "common/path.h"
+#include "common/windows/unicode.h"
+#include "common/mallocHelper.h"
+#include "common/io.h"
+
+#include <stdalign.h>
+#include <windows.h>
+#include "common/windows/nt.h"
+#include <ntstatus.h>
+#include <shlobj.h>
+
+static uint32_t getNumElements(const char* searchPath, DWORD type, const wchar_t* ignore) {
+ FF_AUTO_CLOSE_FD HANDLE dfd = CreateFileA(searchPath, FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (dfd == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ bool flag = ignore == NULL;
+ uint32_t counter = 0;
+ alignas(8) uint8_t buffer[64 * 1024];
+ BOOLEAN firstScan = TRUE;
+
+ size_t ignoreLen = ignore ? wcslen(ignore) : 0;
+
+ while (true) {
+ IO_STATUS_BLOCK ioStatus = {};
+ NTSTATUS status = NtQueryDirectoryFile(
+ dfd,
+ NULL,
+ NULL,
+ NULL,
+ &ioStatus,
+ buffer,
+ ARRAY_SIZE(buffer),
+ FileDirectoryInformation,
+ FALSE,
+ NULL,
+ firstScan);
+ firstScan = FALSE;
+
+ if (!NT_SUCCESS(status) && status != STATUS_BUFFER_OVERFLOW) {
+ break;
+ }
+
+ for (FILE_DIRECTORY_INFORMATION* entry = (FILE_DIRECTORY_INFORMATION*) buffer;
+ ;
+ entry = (FILE_DIRECTORY_INFORMATION*) ((uint8_t*) entry + entry->NextEntryOffset)) {
+ if (!(entry->FileAttributes & type)) {
+ continue;
+ }
+
+ if (!flag &&
+ ignoreLen == entry->FileNameLength / sizeof(*entry->FileName) &&
+ _wcsnicmp(entry->FileName, ignore, ignoreLen) == 0) {
+ flag = true;
+ continue;
+ }
+
+ counter++;
+
+ if (entry->NextEntryOffset == 0) {
+ break;
+ }
+ }
+
+ if (status == STATUS_SUCCESS) {
+ break; // No next page
+ }
+ }
+
+ if (type == FILE_ATTRIBUTE_DIRECTORY && counter >= 2) {
+ counter -= 2; // accounting for . and ..
+ }
+
+ return counter;
+}
+
+static inline void wrapYyjsonFree(yyjson_doc** doc) {
+ assert(doc);
+ if (*doc) {
+ yyjson_doc_free(*doc);
+ }
+}
+
+static void detectScoop(FFPackagesResult* result) {
+ FF_STRBUF_AUTO_DESTROY scoopPath = ffStrbufCreateA(MAX_PATH + 3);
+ ffStrbufAppend(&scoopPath, &instance.state.platform.homeDir);
+ ffStrbufAppendS(&scoopPath, ".config/scoop/config.json");
+
+ yyjson_val* root = NULL;
+
+ yyjson_doc* FF_A_CLEANUP(wrapYyjsonFree) doc = yyjson_read_file(scoopPath.chars, 0, NULL, NULL);
+ if (doc) {
+ root = yyjson_doc_get_root(doc);
+ if (!yyjson_is_obj(root)) {
+ root = NULL;
+ }
+ }
+
+ {
+ ffStrbufClear(&scoopPath);
+ if (root) {
+ ffStrbufSetJsonVal(&scoopPath, yyjson_obj_get(root, "root_path"));
+ }
+ if (scoopPath.length == 0) {
+ ffStrbufSet(&scoopPath, &instance.state.platform.homeDir);
+ ffStrbufAppendS(&scoopPath, "/scoop");
+ }
+ ffStrbufAppendS(&scoopPath, "/apps/");
+ result->scoopUser = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, L"scoop");
+ }
+
+ {
+ ffStrbufClear(&scoopPath);
+ if (root) {
+ ffStrbufSetJsonVal(&scoopPath, yyjson_obj_get(root, "global_path"));
+ }
+ if (scoopPath.length == 0) {
+ PWSTR pPath = NULL;
+ if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramData, KF_FLAG_DEFAULT, NULL, &pPath))) {
+ ffStrbufSetWS(&scoopPath, pPath);
+ CoTaskMemFree(pPath);
+ }
+ ffStrbufAppendS(&scoopPath, "/scoop");
+ }
+ ffStrbufAppendS(&scoopPath, "/apps/");
+ result->scoopGlobal = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, L"scoop");
+ }
+}
+
+static void detectChoco(FF_A_UNUSED FFPackagesResult* result) {
+ const char* chocoInstall = getenv("ChocolateyInstall");
+ if (!chocoInstall || chocoInstall[0] == '\0') {
+ return;
+ }
+
+ char chocoPath[MAX_PATH + 3];
+ char* pend = ffStrCopy(chocoPath, chocoInstall, ARRAY_SIZE(chocoPath));
+ ffStrCopy(pend, "/lib/", ARRAY_SIZE(chocoPath) - (size_t) (pend - chocoPath));
+ result->choco = getNumElements(chocoPath, FILE_ATTRIBUTE_DIRECTORY, L"choco");
+}
+
+static void detectPacman(FFPackagesResult* result) {
+ const char* msystemPrefix = getenv("MSYSTEM_PREFIX");
+ if (!msystemPrefix) {
+ return;
+ }
+
+ // MSYS2
+ char pacmanPath[MAX_PATH + 3];
+ char* pend = ffStrCopy(pacmanPath, msystemPrefix, ARRAY_SIZE(pacmanPath));
+ ffStrCopy(pend, "/../var/lib/pacman/local/", ARRAY_SIZE(pacmanPath) - (size_t) (pend - pacmanPath));
+ result->pacman = getNumElements(pacmanPath, FILE_ATTRIBUTE_DIRECTORY, NULL);
+}
+
+static void detectWinget(FFPackagesResult* result) {
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+ if (ffProcessAppendStdOut(&buffer, (char*[]) {
+ "winget.exe",
+ "list",
+ "--disable-interactivity",
+ NULL,
+ })) {
+ return;
+ }
+
+ uint32_t index = ffStrbufFirstIndexS(&buffer, "--\r\n"); // Ignore garbage and table headers
+ if (index == buffer.length) {
+ return;
+ }
+
+ uint32_t count = 0;
+ for (
+ index += strlen("--\r\n");
+ (index = ffStrbufNextIndexC(&buffer, index, '\n')) < buffer.length;
+ ++index) {
+ ++count;
+ }
+
+ if (buffer.chars[buffer.length - 1] != '\n') { // count last line
+ ++count;
+ }
+
+ result->winget = count;
+}
+
+void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) {
+ if (FF_PACKAGES_IS_ENABLED(options, SCOOP)) {
+ detectScoop(result);
+ }
+ if (FF_PACKAGES_IS_ENABLED(options, CHOCO)) {
+ detectChoco(result);
+ }
+ if (FF_PACKAGES_IS_ENABLED(options, PACMAN)) {
+ detectPacman(result);
+ }
+ if (FF_PACKAGES_IS_ENABLED(options, WINGET)) {
+ detectWinget(result);
+ }
+}