#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; }