From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/common/impl/io_unix.c | 333 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 src/common/impl/io_unix.c (limited to 'src/common/impl/io_unix.c') diff --git a/src/common/impl/io_unix.c b/src/common/impl/io_unix.c new file mode 100644 index 0000000..e603186 --- /dev/null +++ b/src/common/impl/io_unix.c @@ -0,0 +1,333 @@ +#include "common/io.h" +#include "fastfetch.h" +#include "common/strutil.h" +#include "common/time.h" + +#include +#include +#include +#include +#ifndef __APPLE__ + #include +#else + #include +#endif + +#if FF_HAVE_WORDEXP + #include +#else + #include +#endif + +static void createSubfolders(const char* fileName) { + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); + + const char* token = NULL; + while ((token = strchr(fileName, '/')) != NULL) { + ffStrbufAppendNS(&path, (uint32_t) (token - fileName + 1), fileName); + mkdir(path.chars, S_IRWXU | S_IRGRP | S_IROTH); + fileName = token + 1; + } +} + +bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data) { + int openFlagsModes = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC; + mode_t openFlagsRights = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + int FF_AUTO_CLOSE_FD fd = open(fileName, openFlagsModes, openFlagsRights); + if (fd == -1) { + if (errno == ENOENT) { + createSubfolders(fileName); + fd = open(fileName, openFlagsModes, openFlagsRights); + if (fd == -1) { + return false; + } + } else { + return false; + } + } + + return write(fd, data, dataSize) > 0; +} + +static inline void readWithLength(int fd, FFstrbuf* buffer, uint32_t length) { + ffStrbufEnsureFixedLengthFree(buffer, length); + ssize_t bytesRead = 0; + while ( + length > 0 && (bytesRead = read(fd, buffer->chars + buffer->length, length)) > 0) { + buffer->length += (uint32_t) bytesRead; + length -= (uint32_t) bytesRead; + } +} + +static inline void readUntilEOF(int fd, FFstrbuf* buffer) { + ffStrbufEnsureFree(buffer, 31); + uint32_t available = ffStrbufGetFree(buffer); + ssize_t bytesRead = 0; + while ( + (bytesRead = read(fd, buffer->chars + buffer->length, available)) > 0) { + buffer->length += (uint32_t) bytesRead; + if ((uint32_t) bytesRead == available) { + ffStrbufEnsureFree(buffer, buffer->allocated - 1); // Doubles capacity every round. -1 for the null byte. + } + available = ffStrbufGetFree(buffer); + } +} + +bool ffAppendFDBuffer(int fd, FFstrbuf* buffer) { + struct stat fileInfo; + if (fstat(fd, &fileInfo) != 0) { + return false; + } + + if (fileInfo.st_size > 0) { + readWithLength(fd, buffer, (uint32_t) fileInfo.st_size); + } else { + readUntilEOF(fd, buffer); + } + + buffer->chars[buffer->length] = '\0'; + + return buffer->length > 0; +} + +bool ffPathExpandEnv(const char* in, FFstrbuf* out) { + bool result = false; + +#if FF_HAVE_WORDEXP + + wordexp_t exp; + if (wordexp(in, &exp, 0) != 0) { // WARN: 0 = no safety flags; command substitution allowed + return false; + } + + if (exp.we_wordc >= 1) { + result = true; + ffStrbufSetS(out, exp.we_wordv[exp.we_wordc > 1 ? ffTimeGetNow() % exp.we_wordc : 0]); + } + + wordfree(&exp); + +#else + + glob_t gb; + if (glob(in, GLOB_NOSORT + #ifdef GLOB_TILDE + | GLOB_TILDE + #endif + #ifdef GLOB_BRACE + | GLOB_BRACE + #endif + , + NULL, + &gb) != 0) + return false; + + if (gb.gl_pathc >= 1) { + result = true; + ffStrbufSetS(out, gb.gl_pathv[gb.gl_pathc > 1 ? ffTimeGetNow() % (unsigned) gb.gl_pathc : 0]); + } + + globfree(&gb); + +#endif + + return result; +} + +static int ftty = -1; +static struct termios oldTerm; +void restoreTerm(void) { + tcsetattr(ftty, TCSAFLUSH, &oldTerm); +} + +const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) { + if (ftty < 0) { + ftty = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC); + if (ftty < 0) { + return "open(\"/dev/tty\", O_RDWR | O_NOCTTY | O_CLOEXEC) failed"; + } + + if (tcgetattr(ftty, &oldTerm) == -1) { + return "tcgetattr(STDIN_FILENO, &oldTerm) failed"; + } + + struct termios newTerm = oldTerm; + newTerm.c_lflag &= (tcflag_t) ~(ICANON | ECHO); + if (tcsetattr(ftty, TCSAFLUSH, &newTerm) == -1) { + return "tcsetattr(STDIN_FILENO, TCSAFLUSH, &newTerm)"; + } + atexit(restoreTerm); + } + + ffWriteFDData(ftty, strlen(request), request); + +// Give the terminal some time to respond +#ifndef __APPLE__ + if (poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS) <= 0) { + return "poll(/dev/tty) timeout or failed"; + } +#else + { + // On macOS, poll(/dev/tty) always returns immediately + // See also https://nathancraddock.com/blog/macos-dev-tty-polling/ + fd_set rd; + FD_ZERO(&rd); + FD_SET(ftty, &rd); + if (select(ftty + 1, &rd, NULL, NULL, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 }) <= 0) { + return "select(/dev/tty) timeout or failed"; + } + } +#endif + + char buffer[1024]; + size_t bytesRead = 0; + + va_list args; + va_start(args, format); + + while (true) { + ssize_t nRead = read(ftty, buffer + bytesRead, sizeof(buffer) - bytesRead - 1); + + if (nRead <= 0) { + va_end(args); + return "read(STDIN_FILENO, buffer, sizeof(buffer) - 1) failed"; + } + + bytesRead += (size_t) nRead; + buffer[bytesRead] = '\0'; + + va_list cargs; + va_copy(cargs, args); + int ret = vsscanf(buffer, format, cargs); + va_end(cargs); + + if (ret <= 0) { + va_end(args); + return "vsscanf(buffer, format, args) failed"; + } + if (ret >= nParams) { + break; + } + } + + va_end(args); + + return NULL; +} + +bool ffSuppressIO(bool suppress) { +#ifndef NDEBUG + if (instance.config.display.debugMode) { + return false; + } +#endif + + static bool init = false; + static int origOut = -1; + static int origErr = -1; + static int nullFile = -1; + + if (!init) { + if (!suppress) { + return true; + } + + origOut = dup(STDOUT_FILENO); + origErr = dup(STDERR_FILENO); + nullFile = open("/dev/null", O_WRONLY | O_CLOEXEC); + init = true; + } + + if (nullFile == -1) { + return false; + } + + fflush(stdout); + fflush(stderr); + + dup2(suppress ? nullFile : origOut, STDOUT_FILENO); + dup2(suppress ? nullFile : origErr, STDERR_FILENO); + return true; +} + +void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indentation, const char* folderName, bool pretty) { + int dfd = open(folder->chars, O_RDONLY | O_CLOEXEC | O_DIRECTORY); // Ownership of dfd will be transformed to dir + if (dfd < 0) { + return; + } + + FF_AUTO_CLOSE_DIR DIR* dir = fdopendir(dfd); + if (dir == NULL) { + close(dfd); + return; // Should not happen + } + + uint32_t folderLength = folder->length; + + if (pretty && folderName != NULL) { + for (uint8_t i = 0; i < indentation - 1; i++) { + fputs(" | ", stdout); + } + printf("%s/\n", folderName); + } + + struct dirent* entry; + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { // skip hidden files + continue; + } + + bool isDir = false; +#if !defined(__sun) && !defined(__HAIKU__) + if (entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK) { + isDir = entry->d_type == DT_DIR; + } else +#endif + { + struct stat stbuf; + if (fstatat(dfd, entry->d_name, &stbuf, 0) < 0) { + isDir = false; + } else { + isDir = S_ISDIR(stbuf.st_mode); + } + } + if (isDir) { + ffStrbufAppendS(folder, entry->d_name); + ffStrbufAppendC(folder, '/'); + listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry->d_name, pretty); + ffStrbufSubstrBefore(folder, folderLength); + continue; + } + + if (pretty) { + for (uint8_t i = 0; i < indentation; i++) { + fputs(" | ", stdout); + } + } else { + fputs(folder->chars + baseLength, stdout); + } + + puts(entry->d_name); + } +} + +void ffListFilesRecursively(const char* path, bool pretty) { + FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path); + ffStrbufEnsureEndsWithC(&folder, '/'); + listFilesRecursively(folder.length, &folder, 0, NULL, pretty); +} + +FFNativeFD ffGetNullFD(void) { + static FFNativeFD hNullFile = -1; + if (hNullFile != -1) { + return hNullFile; + } + hNullFile = open("/dev/null", O_WRONLY | O_CLOEXEC); + return hNullFile; +} + +bool ffRemoveFile(const char* fileName) { + return unlink(fileName) == 0; +} -- cgit v1.2.3