mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 15:53:51 +01:00
354 lines
9.3 KiB
C
354 lines
9.3 KiB
C
#include "common/io.h"
|
|
#include "fastfetch.h"
|
|
#include "common/strutil.h"
|
|
#include "common/time.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <termios.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#ifndef __APPLE__
|
|
#include <poll.h>
|
|
#else
|
|
#include <sys/select.h>
|
|
#define st_mtim st_mtimespec // `struct stat` spells it `st_mtimespec` on Apple
|
|
#endif
|
|
|
|
#if FF_HAVE_WORDEXP
|
|
#include <wordexp.h>
|
|
#else
|
|
#include <glob.h>
|
|
#endif
|
|
|
|
static void createSubfolders(const char* fileName) {
|
|
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
|
|
|
|
const char* token = nullptr;
|
|
while ((token = strchr(fileName, '/')) != nullptr) {
|
|
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;
|
|
|
|
FF_AUTO_CLOSE_FD int 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
|
|
,
|
|
nullptr,
|
|
&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, nullptr, nullptr, &(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 nullptr;
|
|
}
|
|
|
|
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 == nullptr) {
|
|
close(dfd);
|
|
return; // Should not happen
|
|
}
|
|
|
|
uint32_t folderLength = folder->length;
|
|
|
|
if (pretty && folderName != nullptr) {
|
|
for (uint8_t i = 0; i < indentation - 1; i++) {
|
|
fputs(" | ", stdout);
|
|
}
|
|
printf("%s/\n", folderName);
|
|
}
|
|
|
|
struct dirent* entry;
|
|
|
|
while ((entry = readdir(dir)) != nullptr) {
|
|
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, nullptr, pretty);
|
|
}
|
|
|
|
FFNativeFD ffGetNullFD(void) {
|
|
static FFNativeFD hNullFile = -1;
|
|
if (hNullFile != -1) {
|
|
return hNullFile;
|
|
}
|
|
hNullFile = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
|
return hNullFile;
|
|
}
|
|
|
|
bool ffIsTerminal(int fd) {
|
|
return isatty(fd) != 0;
|
|
}
|
|
|
|
bool ffRemoveFile(const char* fileName) {
|
|
return unlink(fileName) == 0;
|
|
}
|
|
|
|
uint64_t ffPathGetMtime(const char* path) {
|
|
struct stat st;
|
|
if (stat(path, &st) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
// An mtime at or before the Unix epoch means the filesystem did not fill it in. Reporting it
|
|
// as-is would hand the caller a value indistinguishable from "unknown", so treat it as such.
|
|
if (st.st_mtim.tv_sec <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
return (uint64_t) st.st_mtim.tv_sec * 1000ull + (uint64_t) st.st_mtim.tv_nsec / 1000000ull;
|
|
}
|