master ohmu/fastfetch-openbsd / src / common / io.h
ohmu  ·  2026-10-15
  1#pragma once
  2
  3#include "common/FFstrbuf.h"
  4#include "common/FFlist.h"
  5
  6#ifdef _WIN32
  7    #include <fileapi.h>
  8    #include <handleapi.h>
  9    #include <io.h>
 10    #include "common/windows/nt.h"
 11typedef HANDLE FFNativeFD;
 12    #define FF_INVALID_FD INVALID_HANDLE_VALUE
 13#else
 14    #include <unistd.h>
 15    #include <dirent.h>
 16    #include <sys/stat.h>
 17    #include <errno.h>
 18    #include <limits.h>
 19    #include <fcntl.h>
 20typedef int FFNativeFD;
 21    #define FF_INVALID_FD (-1)
 22    // procfs's file can be changed between read calls such as /proc/meminfo and /proc/uptime.
 23    // one safe way to read correct data is reading the whole file in a single read syscall
 24    #define PROC_FILE_BUFFSIZ (32 * 1024)
 25#endif
 26
 27#ifdef _WIN32
 28    #ifndef O_CLOEXEC
 29        #define O_CLOEXEC 0
 30    #endif
 31    #ifndef O_RDONLY
 32        #define O_RDONLY 0
 33    #endif
 34    #ifndef O_DIRECTORY
 35        #define O_DIRECTORY 0200000
 36    #endif
 37
 38// Only O_RDONLY is supported
 39[[gnu::nonnull(2)]] HANDLE openat(HANDLE dfd, const char* fileName, int oflag);
 40[[gnu::nonnull(2)]] HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory);
 41#endif
 42
 43static inline bool ffIsValidNativeFD(FFNativeFD fd) {
 44#ifndef _WIN32
 45    return fd >= 0;
 46#else
 47    // https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443
 48    return fd != INVALID_HANDLE_VALUE && fd != nullptr;
 49#endif
 50}
 51
 52[[gnu::always_inline, gnu::nonnull(1)]]
 53static inline void wrapClose(FFNativeFD* pfd) {
 54    if (ffIsValidNativeFD(*pfd)) {
 55#ifndef _WIN32
 56        close(*pfd);
 57#else
 58        NtClose(*pfd);
 59#endif
 60    }
 61}
 62#define FF_AUTO_CLOSE_FD [[gnu::cleanup(wrapClose)]]
 63
 64static inline FFNativeFD FFUnixFD2NativeFD(int unixfd) {
 65#ifndef _WIN32
 66    return unixfd;
 67#else
 68    return (FFNativeFD) _get_osfhandle(unixfd);
 69#endif
 70}
 71
 72[[gnu::nonnull(3)]]
 73static inline bool ffWriteFDData(FFNativeFD fd, size_t dataSize, const void* data) {
 74#ifndef _WIN32
 75    return write(fd, data, dataSize) != -1;
 76#else
 77    DWORD written;
 78    return WriteFile(fd, data, (DWORD) dataSize, &written, nullptr) && written == dataSize;
 79#endif
 80}
 81
 82[[gnu::nonnull(2)]]
 83static inline bool ffWriteFDBuffer(FFNativeFD fd, const FFstrbuf* content) {
 84    return ffWriteFDData(fd, content->length, content->chars);
 85}
 86
 87[[gnu::nonnull(1, 3)]]
 88bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data);
 89
 90[[gnu::nonnull(1, 2)]]
 91static inline bool ffWriteFileBuffer(const char* fileName, const FFstrbuf* buffer) {
 92    return ffWriteFileData(fileName, buffer->length, buffer->chars);
 93}
 94
 95[[gnu::nonnull(3)]]
 96static inline ssize_t ffReadFDData(FFNativeFD fd, size_t dataSize, void* data) {
 97#ifndef _WIN32
 98    return read(fd, data, dataSize);
 99#else
100    DWORD bytesRead;
101    if (!ReadFile(fd, data, (DWORD) dataSize, &bytesRead, nullptr)) {
102        return -1;
103    }
104
105    return (ssize_t) bytesRead;
106#endif
107}
108
109[[gnu::nonnull(2)]] bool ffAppendFDBuffer(FFNativeFD fd, FFstrbuf* buffer);
110
111[[gnu::nonnull(1, 3)]] static inline ssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data) {
112    FF_AUTO_CLOSE_FD FFNativeFD fd =
113#ifndef _WIN32
114        open(fileName, O_RDONLY | O_CLOEXEC);
115#else
116        CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
117#endif
118
119    if (!ffIsValidNativeFD(fd)) {
120        return -1;
121    }
122
123    return ffReadFDData(fd, dataSize, data);
124}
125
126[[gnu::nonnull(2, 4)]] static inline ssize_t ffReadFileDataRelative(FFNativeFD dfd, const char* fileName, size_t dataSize, void* data) {
127    FF_AUTO_CLOSE_FD FFNativeFD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC);
128    if (!ffIsValidNativeFD(fd)) {
129        return -1;
130    }
131
132    return ffReadFDData(fd, dataSize, data);
133}
134
135// `CreateFileA` reads the path in the ANSI code page, so a UTF-8 path holding anything outside ASCII
136// names a different file, or none at all. The wide API is the one that takes what UTF-8 decodes to,
137// and the conversion is done the same way ffPathExists() below does it.
138[[gnu::nonnull(1, 2)]] static inline bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer) {
139#ifdef _WIN32
140    wchar_t fileNameW[MAX_PATH];
141    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), nullptr, fileName, (ULONG) strlen(fileName) + 1))) {
142        return false;
143    }
144    FF_AUTO_CLOSE_FD FFNativeFD fd = CreateFileW(fileNameW, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
145#else
146    FF_AUTO_CLOSE_FD FFNativeFD fd = open(fileName, O_RDONLY | O_CLOEXEC);
147#endif
148
149    if (!ffIsValidNativeFD(fd)) {
150        return false;
151    }
152
153    return ffAppendFDBuffer(fd, buffer);
154}
155
156[[gnu::nonnull(2, 3)]] static inline bool ffAppendFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer) {
157    FF_AUTO_CLOSE_FD FFNativeFD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC);
158    if (!ffIsValidNativeFD(fd)) {
159        return false;
160    }
161
162    return ffAppendFDBuffer(fd, buffer);
163}
164
165[[gnu::nonnull(2)]] static inline bool ffReadFDBuffer(FFNativeFD fd, FFstrbuf* buffer) {
166    ffStrbufClear(buffer);
167    return ffAppendFDBuffer(fd, buffer);
168}
169
170[[gnu::nonnull(1, 2)]] static inline bool ffReadFileBuffer(const char* fileName, FFstrbuf* buffer) {
171    ffStrbufClear(buffer);
172    return ffAppendFileBuffer(fileName, buffer);
173}
174
175[[gnu::nonnull(2, 3)]] static inline bool ffReadFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer) {
176    ffStrbufClear(buffer);
177    return ffAppendFileBufferRelative(dfd, fileName, buffer);
178}
179
180typedef enum FFPathType: uint8_t {
181    FF_PATHTYPE_FILE = 1 << 0,
182    FF_PATHTYPE_DIRECTORY = 1 << 1,
183    FF_PATHTYPE_ANY = FF_PATHTYPE_FILE | FF_PATHTYPE_DIRECTORY,
184} FFPathType;
185
186[[gnu::nonnull(1)]] static inline bool ffPathExists(const char* path, FFPathType pathType) {
187#ifdef _WIN32
188
189    wchar_t wPath[MAX_PATH];
190    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(wPath, (ULONG) sizeof(wPath), nullptr, path, (ULONG) strlen(path) + 1))) {
191        return false;
192    }
193
194    DWORD attr = GetFileAttributesW(wPath);
195
196    if (attr == INVALID_FILE_ATTRIBUTES) {
197        return false;
198    }
199
200    if (pathType & FF_PATHTYPE_FILE && !(attr & FILE_ATTRIBUTE_DIRECTORY)) {
201        return true;
202    }
203
204    if (pathType & FF_PATHTYPE_DIRECTORY && (attr & FILE_ATTRIBUTE_DIRECTORY)) {
205        return true;
206    }
207
208#else
209
210    if (pathType == FF_PATHTYPE_ANY) {
211        // Zero overhead
212        return access(path, F_OK) == 0;
213    } else {
214        struct stat fileStat;
215        if (stat(path, &fileStat) != 0) {
216            return false;
217        }
218
219        unsigned int mode = fileStat.st_mode & S_IFMT;
220
221        if (pathType & FF_PATHTYPE_FILE && mode != S_IFDIR) {
222            return true;
223        }
224
225        if (pathType & FF_PATHTYPE_DIRECTORY && mode == S_IFDIR) {
226            return true;
227        }
228    }
229
230#endif
231
232    return false;
233}
234
235[[gnu::nonnull(1, 2)]] bool ffPathExpandEnv(const char* in, FFstrbuf* out);
236
237#define FF_IO_TERM_RESP_WAIT_MS 1000 // Terminal may respond slowly, especially when printing large image/gif logos. Found on iTerm
238
239[[gnu::format(scanf, 3, 4), gnu::nonnull(1, 3)]] const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...);
240
241// Not thread safe!
242// Returns the previous state, which callers routinely discard (see ffUnsuppressIO), so not `nodiscard`
243bool ffSuppressIO(bool suppress);
244
245static inline void ffUnsuppressIO(bool* suppressed) {
246    if (!*suppressed) {
247        return;
248    }
249    ffSuppressIO(false);
250    *suppressed = false;
251}
252
253#define FF_SUPPRESS_IO() [[maybe_unused, gnu::cleanup(ffUnsuppressIO)]] bool io_suppressed__ = ffSuppressIO(true)
254
255[[gnu::nonnull(1)]] void ffListFilesRecursively(const char* path, bool pretty);
256
257[[gnu::nonnull(1), gnu::always_inline]] static inline void wrapFclose(FILE** pfile) {
258    if (*pfile) {
259        fclose(*pfile);
260    }
261}
262#define FF_AUTO_CLOSE_FILE [[gnu::cleanup(wrapFclose)]]
263
264[[gnu::nonnull(1), gnu::always_inline]]
265#ifndef _WIN32
266static inline void wrapClosedir(DIR** pdir) {
267    if (*pdir) {
268        closedir(*pdir);
269    }
270}
271#else
272static inline void wrapClosedir(HANDLE* pdir) {
273    if (*pdir) {
274        FindClose(*pdir);
275    }
276}
277#endif
278#define FF_AUTO_CLOSE_DIR [[gnu::cleanup(wrapClosedir)]]
279
280[[gnu::nonnull(1, 2, 3)]] static inline bool ffSearchUserConfigFile(const FFlist* configDirs, const char* fileSubpath, FFstrbuf* result) {
281    // configDirs is a list of FFstrbufs include the trailing slash
282    FF_LIST_FOR_EACH (FFstrbuf, dir, *configDirs) {
283        ffStrbufClear(result);
284        ffStrbufAppend(result, dir);
285        ffStrbufAppendS(result, fileSubpath);
286        if (ffPathExists(result->chars, FF_PATHTYPE_FILE)) {
287            return true;
288        }
289    }
290
291    return false;
292}
293
294FFNativeFD ffGetNullFD(void);
295// Whether a descriptor is attached to a real terminal, i.e. whether a human is looking at it.
296//
297// Do not use `isatty()` for this on Windows. It is `_isatty()`, which answers "is this a character
298// device" rather than "is this a console", and `NUL` -- as well as MSYS2's `/dev/null` -- is a
299// character device. A redirected run therefore looks interactive, which is how
300// `fastfetch --gen-config <path> > /dev/null` ends up on the interactive path and blocks forever.
301bool ffIsTerminal(int fd);
302// Returns whether the file was removed; callers that only want it gone discard that, so not `nodiscard`
303[[gnu::nonnull(1)]] bool ffRemoveFile(const char* fileName);
304// Modification time of a file, in milliseconds since the Unix epoch, or 0 if it can not be read.
305// The representation is uniform across platforms so that a value derived from it means the
306// same thing everywhere, which matters for callers that store it as a cache key.
307[[gnu::nonnull(1)]] uint64_t ffPathGetMtime(const char* path);