master ohmu/fastfetch-openbsd / src / detection / packages / packages.c
ohmu  ·  2026-10-15
  1#include "packages.h"
  2#include "common/io.h"
  3
  4#include <inttypes.h>
  5#include <stddef.h>
  6
  7void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options);
  8
  9const char* ffDetectPackages(FFPackagesResult* result, FFPackagesOptions* options) {
 10    ffDetectPackagesImpl(result, options);
 11
 12    for (uint32_t i = 0; i < offsetof(FFPackagesResult, all) / sizeof(uint32_t); ++i) {
 13        result->all += ((uint32_t*) result)[i];
 14    }
 15
 16    return nullptr;
 17}
 18
 19bool ffPackagesReadCacheKey(FFstrbuf* cacheDir, FFstrbuf* cacheContent, uint64_t cacheKey, const char* packageId, uint32_t* result) {
 20    if (__builtin_expect(cacheKey == 0, false)) {
 21        // The caller could not compute a cache key (its data source is unreadable). Nothing may be
 22        // cached under an unknown key, and reporting 0 would be a lie. Leaving `cacheContent` empty
 23        // makes the matching ffPackagesWriteCache() call a no-op.
 24        return false;
 25    }
 26
 27    ffStrbufSet(cacheDir, &instance.state.platform.cacheDir);
 28    ffStrbufEnsureEndsWithC(cacheDir, '/');
 29    ffStrbufAppendF(cacheDir, "fastfetch/packages/%s.txt", packageId);
 30
 31    if (ffReadFileBuffer(cacheDir->chars, cacheContent)) {
 32        uint64_t key_cached;
 33        uint32_t num_cached;
 34        if (sscanf(cacheContent->chars, "%" SCNu64 " %" SCNu32, &key_cached, &num_cached) == 2 &&
 35            key_cached == cacheKey && num_cached > 0) {
 36            *result = num_cached;
 37            return true;
 38        }
 39    }
 40
 41    ffStrbufSetF(cacheContent, "%" PRIu64 " ", cacheKey);
 42
 43    return false;
 44}
 45
 46bool ffPackagesReadCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, const char* filePath, const char* packageId, uint32_t* result) {
 47    const uint64_t mtime_current = ffPathGetMtime(filePath);
 48    if (__builtin_expect(mtime_current == 0, false)) {
 49        // A missing database legitimately means "no packages installed", and must not be cached.
 50        // A database whose modification time could not be read must not be cached either, and
 51        // reporting 0 for it would be a lie. ffPathGetMtime() reports 0 for both, so tell them
 52        // apart before deciding which of the two applies.
 53        if (ffPathExists(filePath, FF_PATHTYPE_FILE)) {
 54            return false;
 55        }
 56
 57        *result = 0;
 58        return true;
 59    }
 60
 61    return ffPackagesReadCacheKey(cacheDir, cacheContent, mtime_current, packageId, result);
 62}
 63
 64bool ffPackagesWriteCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, uint32_t num_elements) {
 65    if (__builtin_expect(cacheContent->length == 0, false)) {
 66        return false;
 67    }
 68
 69    ffStrbufAppendF(cacheContent, "%" PRIu32, num_elements);
 70    return ffWriteFileBuffer(cacheDir->chars, cacheContent);
 71}
 72
 73#if __linux__
 74#include <sys/syscall.h>
 75
 76struct linux_dirent64 {
 77    uint64_t       d_ino;    /* 64-bit inode number */
 78    int64_t        d_off;    /* Not an offset; see getdents() */
 79    unsigned short d_reclen; /* Size of this dirent */
 80    unsigned char  d_type;   /* File type */
 81    char           d_name[]; /* Filename (null-terminated) */
 82};
 83
 84uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir) {
 85    FF_AUTO_CLOSE_FD int fd = open(dirname, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 86    if (fd < 0) {
 87        return 0;
 88    }
 89
 90    alignas(struct linux_dirent64) uint8_t bytes[64 * 1024];
 91
 92    uint32_t num_elements = 0;
 93    const size_t nameOffset = offsetof(struct linux_dirent64, d_name);
 94
 95    for (;;) {
 96        long bytesRead = syscall(SYS_getdents64, fd, bytes, sizeof(bytes));
 97        if (bytesRead < 0) {
 98            if (errno == EINTR) {
 99                continue;
100            }
101            break;
102        }
103        if (bytesRead == 0) {
104            break;
105        }
106
107        size_t remaining = (size_t) bytesRead;
108        struct linux_dirent64* entry = (struct linux_dirent64*) bytes;
109
110        while (remaining >= nameOffset + 1) {
111            if (entry->d_reclen < nameOffset + 1 || entry->d_reclen > remaining) {
112                remaining = 0;
113                break;
114            }
115
116            bool ok = false;
117            if (entry->d_name[0] != '.') {
118                if (__builtin_expect(entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK, true)) {
119                    ok = entry->d_type == (isdir ? DT_DIR : DT_REG);
120                } else {
121                    struct stat stbuf;
122                    if (fstatat(fd, entry->d_name, &stbuf, 0) == 0) {
123                        ok = isdir ? S_ISDIR(stbuf.st_mode) : S_ISREG(stbuf.st_mode);
124                    }
125                }
126            }
127
128            num_elements += ok;
129            size_t recordLength = entry->d_reclen;
130            remaining -= recordLength;
131            entry = (struct linux_dirent64*) ((uint8_t*) entry + recordLength);
132        }
133
134        if (remaining != 0) {
135            break;
136        }
137    }
138
139    return num_elements;
140}
141
142#elif __APPLE__
143
144#pragma clang diagnostic ignored "-Wdeprecated-declarations" // syscall
145
146#include <sys/syscall.h>
147
148uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir) {
149    FF_AUTO_CLOSE_FD int fd = open(dirname, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
150    if (fd < 0) {
151        return 0;
152    }
153
154    alignas(struct dirent) uint8_t bytes[64 * 1024];
155    off_t seek = 0;
156    uint32_t num_elements = 0;
157    const size_t nameOffset = offsetof(struct dirent, d_name);
158
159    for (;;) {
160        // getdirentries64 stores its EOF indicator in the final four bytes.
161        uint32_t* eofFlag = (uint32_t*) (bytes + sizeof(bytes) - sizeof(uint32_t));
162        *eofFlag = 0;
163
164        long bytesRead = syscall(SYS_getdirentries64, fd, bytes, sizeof(bytes), &seek);
165        if (bytesRead < 0) {
166            if (errno == EINTR) {
167                continue;
168            }
169            break;
170        }
171        if (bytesRead == 0 || (size_t) bytesRead > sizeof(bytes)) {
172            break;
173        }
174
175        size_t remaining = (size_t) bytesRead;
176        struct dirent* entry = (struct dirent*) bytes;
177        while (remaining >= nameOffset + 1) {
178            assert(((uintptr_t) entry) % alignof(struct dirent) == 0);
179
180            if (entry->d_reclen < nameOffset + 1 || entry->d_reclen > remaining ||
181                entry->d_namlen >= entry->d_reclen - nameOffset) {
182                remaining = 0;
183                break;
184            }
185
186            const char* name = (const char*) entry + nameOffset;
187            if (entry->d_ino != 0 && name[0] != '.') {
188                bool ok = false;
189                if (__builtin_expect(entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK, true)) {
190                    ok = entry->d_type == (isdir ? DT_DIR : DT_REG);
191                } else {
192                    struct stat stbuf;
193                    if (fstatat(fd, name, &stbuf, 0) == 0) {
194                        ok = isdir ? S_ISDIR(stbuf.st_mode) : S_ISREG(stbuf.st_mode);
195                    }
196                }
197
198                num_elements += ok;
199            }
200
201            remaining -= entry->d_reclen;
202            entry = (struct dirent*) ((uint8_t*) entry + entry->d_reclen);
203        }
204
205        if ((size_t) bytesRead <= sizeof(bytes) - sizeof(uint32_t) && *eofFlag == 1) {
206            break;
207        }
208    }
209
210    return num_elements;
211}
212
213#elif !_WIN32
214uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir) {
215    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(dirname);
216    if (dirp == nullptr) {
217        return 0;
218    }
219
220    uint32_t num_elements = 0;
221
222    struct dirent* entry;
223    while ((entry = readdir(dirp)) != nullptr) {
224        bool ok = false;
225
226        if (entry->d_name[0] != '.') {
227    #if !defined(__sun) && !defined(__HAIKU__)
228            if (entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK) {
229                ok = entry->d_type == (isdir ? DT_DIR : DT_REG);
230            } else
231    #endif
232            {
233                struct stat stbuf;
234                if (fstatat(dirfd(dirp), entry->d_name, &stbuf, 0) == 0) {
235                    ok = isdir ? S_ISDIR(stbuf.st_mode) : S_ISREG(stbuf.st_mode);
236                }
237            }
238        }
239
240        if (ok) {
241            ++num_elements;
242        }
243    }
244
245    return num_elements;
246}
247#endif