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