#include "io.h" #include "fastfetch.h" #include "util/stringUtils.h" #include "common/time.h" #include #include #include #include #ifndef __APPLE__ #include #else #include #endif #if FF_HAVE_WORDEXP #include #elif FF_HAVE_GLOB #warning " is not available, use instead" #include #else #warning "Neither nor is available" #endif static void createSubfolders(const char* fileName) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); 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; } ssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data) { int FF_AUTO_CLOSE_FD fd = open(fileName, O_RDONLY | O_CLOEXEC); if(fd == -1) return -1; return ffReadFDData(fd, dataSize, data); } ssize_t ffReadFileDataRelative(int dfd, const char* fileName, size_t dataSize, void* data) { int FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC); if(fd == -1) return -1; return ffReadFDData(fd, dataSize, data); } bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer) { int FF_AUTO_CLOSE_FD fd = open(fileName, O_RDONLY | O_CLOEXEC); if(fd == -1) return false; return ffAppendFDBuffer(fd, buffer); } bool ffAppendFileBufferRelative(int dfd, const char* fileName, FFstrbuf* buffer) { int FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC); if(fd == -1) return false; return ffAppendFDBuffer(fd, buffer); } bool ffPathExpandEnv(FF_MAYBE_UNUSED const char* in, FF_MAYBE_UNUSED FFstrbuf* out) { bool result = false; #if FF_HAVE_WORDEXP // https://github.com/termux/termux-packages/pull/7056 wordexp_t exp; if (wordexp(in, &exp, 0) != 0) 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); #elif FF_HAVE_GLOB glob_t gb; if (glob(in, GLOB_NOSORT | GLOB_TILDE, NULL, &gb) != 0) return false; if (gb.gl_matchc >= 1) { result = true; ffStrbufSetS(out, gb.gl_pathv[gb.gl_matchc > 1 ? ffTimeGetNow() % gb.gl_matchc : 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) { FF_AUTO_CLOSE_FD int dfd = open(folder->chars, O_RDONLY); if (dfd < 0) return; DIR* dir = fdopendir(dfd); if(dir == NULL) return; 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); } closedir(dir); } void ffListFilesRecursively(const char* path, bool pretty) { FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path); ffStrbufEnsureEndsWithC(&folder, '/'); listFilesRecursively(folder.length, &folder, 0, NULL, pretty); }