ohmu
·
2026-10-15
1#include "FFPlatform_private.h"
2#include "common/FFstrbuf.h"
3#include "common/arrutil.h"
4#include "common/strutil.h"
5#include "common/io.h"
6#include "common/path.h"
7#include "common/mallocHelper.h"
8
9#include <unistd.h>
10#include <pwd.h>
11#include <limits.h>
12#include <sys/utsname.h>
13#include <paths.h>
14
15#ifdef __APPLE__
16 #include <mach-o/dyld.h>
17 #include <sys/sysctl.h>
18#elif defined(__FreeBSD__) || defined(__NetBSD__)
19 #include <sys/sysctl.h>
20#elif defined(__OpenBSD__)
21 #include <sys/sysctl.h>
22 #include <sys/stat.h>
23 #include "common/path.h"
24#elif defined(__HAIKU__)
25 #include <image.h>
26 #include <OS.h>
27#endif
28
29static void getExePath(FFPlatform* platform) {
30 char exePath[PATH_MAX];
31#if defined(__linux__) || defined(__GNU__)
32 ssize_t exePathLen = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
33 if (exePathLen > 0) {
34 exePath[exePathLen] = '\0';
35 }
36#elif defined(__APPLE__)
37 uint32_t exePathLen = sizeof(exePath);
38 if (_NSGetExecutablePath(exePath, &exePathLen) == 0) {
39 exePathLen = (uint32_t) strlen(exePath);
40 } else {
41 exePathLen = 0;
42 }
43#elif defined(__FreeBSD__) || defined(__NetBSD__)
44 size_t exePathLen = sizeof(exePath);
45 if (sysctl(
46 (int[]) { CTL_KERN,
47 #ifdef __FreeBSD__
48 KERN_PROC,
49 KERN_PROC_PATHNAME,
50 (pid_t) platform->pid
51 #else
52 KERN_PROC_ARGS,
53 (pid_t) platform->pid,
54 KERN_PROC_PATHNAME
55 #endif
56 },
57 4,
58 exePath,
59 &exePathLen,
60 nullptr,
61 0) < 0)
62 exePathLen = 0;
63 else {
64 exePathLen--; // remove terminating NUL
65 }
66#elif defined(__OpenBSD__)
67#if FF_HAVE_GETEXECPATH
68 // https://man.openbsd.org/getexecpath.3
69 size_t exePathLen = getexecpath(exePath, sizeof(exePath)) == 0 ? strlen(exePath) : 0;
70#else
71 // OpenBSD (before 8.0) doesn't have a reliable way to get the executable path.
72 // Current implementation uses argv[0], which can be easily spoofed.
73 // See #2195
74 size_t exePathLen = 0;
75 {
76 char argvBuf[ARG_MAX];
77 size_t argvSize = sizeof(argvBuf);
78 int argvMib[] = { CTL_KERN, KERN_PROC_ARGS, (pid_t) platform->pid, KERN_PROC_ARGV };
79 if (sysctl(argvMib, ARRAY_SIZE(argvMib), argvBuf, &argvSize, nullptr, 0) == 0) {
80 // The buffer is filled with an array of char pointers followed by the strings themselves
81 char** argv = (char**) argvBuf;
82 if (argv[0] && (char*) argv[0] >= argvBuf && (char*) argv[0] < argvBuf + argvSize) {
83 char* arg0 = argv[0];
84 if (arg0[0]) {
85 if (strchr(arg0, '/') != nullptr) // likely a path (absolute or relative)
86 {
87 exePathLen = strlen(arg0);
88 if (exePathLen < ARRAY_SIZE(exePath)) {
89 memcpy(exePath, arg0, exePathLen);
90 exePath[exePathLen] = '\0';
91 } else {
92 exePathLen = 0;
93 }
94 } else {
95 FF_STRBUF_AUTO_DESTROY tmpPath = ffStrbufCreate();
96 if (ffFindExecutableInPath(arg0, &tmpPath) == nullptr && tmpPath.length < ARRAY_SIZE(exePath)) {
97 memcpy(exePath, tmpPath.chars, tmpPath.length + 1);
98 exePathLen = tmpPath.length;
99 }
100 }
101
102 if (exePathLen > 0) {
103 struct stat st;
104 if (stat(exePath, &st) == 0 && S_ISREG(st.st_mode)) {
105 // Replicate kvm_getfiles()'s live path: {CTL_KERN, KERN_FILE, KERN_FILE_BYPID, pid, esize, count}
106 int fileMib[6] = { CTL_KERN, KERN_FILE, KERN_FILE_BYPID, (pid_t) platform->pid, (int) sizeof(struct kinfo_file), 0 };
107 size_t fileSize = 0;
108 if (sysctl(fileMib, ARRAY_SIZE(fileMib), nullptr, &fileSize, nullptr, 0) == 0) {
109 fileSize += fileSize / 8; // add 12.5% for the table growing between the two calls
110 size_t fileCapacity = fileSize;
111 FF_AUTO_FREE struct kinfo_file* kf = (struct kinfo_file*) malloc(fileCapacity);
112 if (kf) {
113 int rv;
114 // The table only grows, so the loop terminates as long as it grows by less
115 // than 12.5% per retry. Cap it anyway: spinning on a pathological growth
116 // rate would hang fastfetch, and giving up only costs the executable check
117 // below, which already assumes the path is correct when the list is missing.
118 int retries = 5;
119 while (true) {
120 fileMib[5] = (int) (fileCapacity / sizeof(struct kinfo_file));
121 fileSize = fileCapacity; // sysctl takes the size of the buffer as input
122 rv = sysctl(fileMib, ARRAY_SIZE(fileMib), kf, &fileSize, nullptr, 0);
123 if (rv == 0 || errno != ENOMEM || retries-- == 0) {
124 break;
125 }
126 // The file table grew between the two calls. The buffer has to grow
127 // before the retry: the next call advertises fileCapacity as the size
128 // of kf, and the kernel would copy that many bytes into the old
129 // allocation.
130 // OpenBSD writes back the size it needs, FreeBSD only the number of
131 // bytes it managed to copy, so take the larger one and add ~10% on
132 // top -- the growth libprocstat uses for this very sysctl.
133 size_t newCapacity = fileSize > fileCapacity ? fileSize : fileCapacity;
134 newCapacity += newCapacity / 8;
135 struct kinfo_file* newKf = (struct kinfo_file*) realloc(kf, newCapacity);
136 if (newKf == nullptr) {
137 rv = -1;
138 break;
139 }
140 kf = newKf;
141 fileCapacity = newCapacity;
142 }
143
144 if (rv == 0) {
145 int cntp = (int) (fileSize / sizeof(struct kinfo_file));
146 int i;
147 for (i = 0; i < cntp; i++) {
148 if (kf[i].fd_fd == KERN_FILE_TEXT) {
149 // KERN_FILE_TEXT is the executable file, not a shared library, and should be unique in the list.
150 if (st.st_dev != (dev_t) kf[i].va_fsid || st.st_ino != (ino_t) kf[i].va_fileid) {
151 i = -1;
152 }
153 break;
154 }
155 }
156 if (i < 0) {
157 exePathLen = 0;
158 }
159 }
160 // If we can't get the list of open files, we can't verify that the file is actually the executable
161 // Assume it is
162 }
163 }
164 } else {
165 exePathLen = 0;
166 }
167 }
168 }
169 }
170 }
171 }
172#endif
173#elif defined(__sun)
174 ssize_t exePathLen = readlink("/proc/self/path/a.out", exePath, sizeof(exePath) - 1);
175 if (exePathLen > 0) {
176 exePath[exePathLen] = '\0';
177 }
178#elif defined(__HAIKU__)
179 size_t exePathLen = 0;
180 image_info info;
181 int32 cookie = 0;
182
183 while (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) {
184 if (info.type == B_APP_IMAGE) {
185 exePathLen = strlcpy(exePath, info.name, sizeof(exePath));
186 break;
187 }
188 }
189#endif
190 if (exePathLen > 0) {
191 ffStrbufEnsureFree(&platform->exePath, PATH_MAX);
192 if (realpath(exePath, platform->exePath.chars)) {
193 ffStrbufRecalculateLength(&platform->exePath);
194 } else {
195 ffStrbufSetNS(&platform->exePath, (uint32_t) exePathLen, exePath);
196 }
197 }
198}
199
200static void platformPathAddEnv(FFlist* dirs, const char* env) {
201 const char* envValue = getenv(env);
202 if (!ffStrSet(envValue)) {
203 return;
204 }
205
206 FF_STRBUF_AUTO_DESTROY value = ffStrbufCreateA(64);
207 ffStrbufAppendS(&value, envValue);
208
209 uint32_t startIndex = 0;
210 while (startIndex < value.length) {
211 uint32_t colonIndex = ffStrbufNextIndexC(&value, startIndex, ':');
212 value.chars[colonIndex] = '\0';
213
214 if (!ffStrSet(value.chars + startIndex)) {
215 startIndex = colonIndex + 1;
216 continue;
217 }
218
219 ffPlatformPathAddAbsolute(dirs, value.chars + startIndex);
220
221 startIndex = colonIndex + 1;
222 }
223}
224
225static void getHomeDir(FFPlatform* platform, const struct passwd* pwd) {
226 const char* home = pwd ? pwd->pw_dir : getenv("HOME");
227 ffStrbufAppendS(&platform->homeDir, home);
228 ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
229}
230
231static void getCacheDir(FFPlatform* platform) {
232 const char* cache = getenv("XDG_CACHE_HOME");
233 if (ffStrSet(cache)) {
234 ffStrbufAppendS(&platform->cacheDir, cache);
235 ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');
236 } else {
237 ffStrbufAppend(&platform->cacheDir, &platform->homeDir);
238 ffStrbufAppendS(&platform->cacheDir, ".cache/");
239 }
240}
241
242static void getConfigDirs(FFPlatform* platform) {
243 // Always make sure `${XDG_CONFIG_HOME:-$HOME/.config}` is the first entry
244 platformPathAddEnv(&platform->configDirs, "XDG_CONFIG_HOME");
245 ffPlatformPathAddHome(&platform->configDirs, platform, ".config/");
246
247#if defined(__APPLE__)
248 ffPlatformPathAddHome(&platform->configDirs, platform, "Library/Preferences/");
249 ffPlatformPathAddHome(&platform->configDirs, platform, "Library/Application Support/");
250#endif
251#if defined(__HAIKU__)
252 ffPlatformPathAddHome(&platform->configDirs, platform, "config/settings/");
253#endif
254
255 ffPlatformPathAddHome(&platform->configDirs, platform, "");
256 platformPathAddEnv(&platform->configDirs, "XDG_CONFIG_DIRS");
257
258#if !defined(__APPLE__)
259 ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_ETC "/xdg/");
260#endif
261
262 ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_ETC "/");
263 ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_INSTALL_SYSCONF "/");
264}
265
266static void getDataDirs(FFPlatform* platform) {
267 platformPathAddEnv(&platform->dataDirs, "XDG_DATA_HOME");
268 ffPlatformPathAddHome(&platform->dataDirs, platform, ".local/share/");
269
270 // Add ${currentExePath}/../share
271 if (platform->exePath.length > 0) {
272 FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&platform->exePath);
273 ffStrbufSubstrBeforeLastC(&path, '/');
274 ffStrbufSubstrBeforeLastC(&path, '/');
275 ffStrbufAppendS(&path, "/share");
276 ffPlatformPathAddAbsolute(&platform->dataDirs, path.chars);
277 }
278
279#ifdef __APPLE__
280 ffPlatformPathAddHome(&platform->dataDirs, platform, "Library/Application Support/");
281#endif
282
283 ffPlatformPathAddHome(&platform->dataDirs, platform, "");
284 platformPathAddEnv(&platform->dataDirs, "XDG_DATA_DIRS");
285#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
286 ffPlatformPathAddAbsolute(&platform->dataDirs, FF_PATH_PKG_BASE "/share/");
287#endif
288 ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/local/share/");
289 ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/share/");
290}
291
292static void getUserName(FFPlatform* platform, const struct passwd* pwd) {
293 if (pwd) {
294 ffStrbufSetS(&platform->userName, pwd->pw_name);
295 ffStrbufSetS(&platform->fullUserName, pwd->pw_gecos);
296 ffStrbufTrimSpace(&platform->fullUserName);
297 } else {
298 ffStrbufSetS(&platform->userName, getenv("USER"));
299 }
300}
301
302static void getHostName(FFPlatform* platform, const struct utsname* uts) {
303 ffStrbufAppendS(&platform->hostName, uts->nodename);
304}
305
306static void getUserShell(FFPlatform* platform, const struct passwd* pwd) {
307 const char* shell = pwd ? pwd->pw_shell : getenv("SHELL");
308
309 ffStrbufAppendS(&platform->userShell, shell);
310}
311
312static void getSysinfo(FFPlatformSysinfo* info, const struct utsname* uts) {
313 ffStrbufAppendS(&info->name, uts->sysname);
314 ffStrbufAppendS(&info->release, uts->release);
315 ffStrbufAppendS(&info->version, uts->version);
316#ifdef __HAIKU__
317 /* historical reason */
318 if (ffStrEquals(uts->machine, "BePC")) {
319 ffStrbufSetStatic(&info->architecture, "i386");
320 } else
321#endif
322 ffStrbufAppendS(&info->architecture, uts->machine);
323
324 // _SC_PAGESIZE is one of the sysconf names <unistd.h> requires every implementation to define,
325 // and the only failure POSIX defines for sysconf is an invalid name, so this does not fail.
326 // It replaces the former sysctl(CTL_HW, HW_PAGESIZE) call on FreeBSD / macOS / OpenBSD / NetBSD.
327 info->pageSizeShift = (uint32_t) __builtin_ctzl((unsigned long) sysconf(_SC_PAGESIZE));
328}
329
330static void getCwd(FFPlatform* platform) {
331 char cwd[PATH_MAX];
332 if (getcwd(cwd, sizeof(cwd)) != nullptr) {
333 ffStrbufSetS(&platform->cwd, cwd);
334 ffStrbufEnsureEndsWithC(&platform->cwd, '/');
335 }
336}
337
338void ffPlatformInitImpl(FFPlatform* platform) {
339 platform->pid = (uint32_t) getpid();
340 platform->uid = getuid();
341#if !__ANDROID__
342 struct passwd* pwd = getpwuid(platform->uid);
343#else
344 // On Android, /etc/passwd is empty, and getpwuid() will return `/data` for pw_dir
345 struct passwd* pwd = nullptr;
346#endif
347
348 struct utsname uts;
349 if (uname(&uts) < 0) {
350 memset(&uts, 0, sizeof(uts));
351 }
352
353 getExePath(platform);
354 getCwd(platform);
355 getHomeDir(platform, pwd);
356 getCacheDir(platform);
357 getConfigDirs(platform);
358 getDataDirs(platform);
359
360 getUserName(platform, pwd);
361 getHostName(platform, &uts);
362 getUserShell(platform, pwd);
363
364 getSysinfo(&platform->sysinfo, &uts);
365}