Files
fastfetch/src/common/impl/io_unix.c
T

379 lines
9.3 KiB
C
Raw Normal View History

2026-01-06 09:48:38 +08:00
#include "common/io.h"
#include "fastfetch.h"
2026-01-06 09:48:38 +08:00
#include "common/stringUtils.h"
#include "common/time.h"
2021-04-09 14:07:05 +02:00
#include <fcntl.h>
#include <termios.h>
2023-01-28 17:19:45 +01:00
#include <dirent.h>
#include <errno.h>
#ifndef __APPLE__
#include <poll.h>
#else
#include <sys/select.h>
#endif
2024-07-30 11:06:27 +08:00
#if FF_HAVE_WORDEXP
#include <wordexp.h>
#else
2025-10-16 14:33:53 +08:00
#include <glob.h>
#endif
2021-04-09 14:07:05 +02:00
2022-04-21 15:50:45 +02:00
static void createSubfolders(const char* fileName)
{
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
2022-04-21 15:50:45 +02:00
2023-11-27 16:02:22 +09:00
char *token = NULL;
while((token = strchr(fileName, '/')) != NULL)
2022-04-21 15:50:45 +02:00
{
2023-11-27 16:02:22 +09:00
ffStrbufAppendNS(&path, (uint32_t)(token - fileName + 1), fileName);
mkdir(path.chars, S_IRWXU | S_IRGRP | S_IROTH);
fileName = token + 1;
2022-04-21 15:50:45 +02:00
}
}
2022-06-19 18:22:00 +02:00
bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data)
2022-04-21 15:50:45 +02:00
{
2023-12-04 15:14:40 +08:00
int openFlagsModes = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;
2024-10-17 15:39:11 +08:00
mode_t openFlagsRights = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
2022-04-21 15:50:45 +02:00
2023-02-23 13:58:39 +08:00
int FF_AUTO_CLOSE_FD fd = open(fileName, openFlagsModes, openFlagsRights);
if(fd == -1)
2022-04-21 15:50:45 +02:00
{
if (errno == ENOENT)
{
createSubfolders(fileName);
fd = open(fileName, openFlagsModes, openFlagsRights);
if(fd == -1)
return false;
}
else
2022-04-21 15:50:45 +02:00
return false;
}
2023-02-23 13:58:39 +08:00
return write(fd, data, dataSize) > 0;
}
2023-12-22 16:04:56 +09:00
static inline void readWithLength(int fd, FFstrbuf* buffer, uint32_t length)
2021-04-09 14:07:05 +02:00
{
2023-12-22 16:04:56 +09:00
ffStrbufEnsureFixedLengthFree(buffer, length);
ssize_t bytesRead = 0;
2023-12-22 16:04:56 +09:00
while(
length > 0 && (bytesRead = read(fd, buffer->chars + buffer->length, length)) > 0
) {
buffer->length += (uint32_t) bytesRead;
length -= (uint32_t) bytesRead;
}
2023-12-22 16:04:56 +09:00
}
2021-11-17 10:59:31 +01:00
2023-12-22 16:04:56 +09:00
static inline void readUntilEOF(int fd, FFstrbuf* buffer)
{
ffStrbufEnsureFree(buffer, 31);
uint32_t available = ffStrbufGetFree(buffer);
ssize_t bytesRead = 0;
2021-11-17 10:59:31 +01:00
while(
2023-12-22 16:04:56 +09:00
(bytesRead = read(fd, buffer->chars + buffer->length, available)) > 0
2021-11-17 10:59:31 +01:00
) {
buffer->length += (uint32_t) bytesRead;
2023-12-22 16:04:56 +09:00
if((uint32_t) bytesRead == available)
2023-09-02 10:04:57 -04:00
ffStrbufEnsureFree(buffer, buffer->allocated - 1); // Doubles capacity every round. -1 for the null byte.
2023-12-22 16:04:56 +09:00
available = ffStrbufGetFree(buffer);
2021-04-09 14:07:05 +02:00
}
2023-12-22 16:04:56 +09:00
}
2021-04-09 14:07:05 +02:00
2023-12-22 16:04:56 +09:00
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);
2021-05-08 16:24:57 +02:00
2023-12-22 16:04:56 +09:00
buffer->chars[buffer->length] = '\0';
2022-06-20 16:27:33 +02:00
2023-09-02 10:04:57 -04:00
return buffer->length > 0;
2022-06-20 16:27:33 +02:00
}
2022-06-20 16:35:15 +02:00
ssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data)
2022-06-20 16:27:33 +02:00
{
2023-12-04 15:14:40 +08:00
int FF_AUTO_CLOSE_FD fd = open(fileName, O_RDONLY | O_CLOEXEC);
2022-06-20 16:27:33 +02:00
if(fd == -1)
return -1;
2023-02-23 13:58:39 +08:00
return ffReadFDData(fd, dataSize, data);
2021-04-18 15:19:21 +02:00
}
2024-10-12 10:41:58 +08:00
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);
}
2022-06-20 16:27:33 +02:00
bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer)
2021-04-18 15:19:21 +02:00
{
2023-12-04 15:14:40 +08:00
int FF_AUTO_CLOSE_FD fd = open(fileName, O_RDONLY | O_CLOEXEC);
2021-04-18 15:19:21 +02:00
if(fd == -1)
2021-06-13 14:23:35 +02:00
return false;
2021-04-18 15:19:21 +02:00
2023-02-23 13:58:39 +08:00
return ffAppendFDBuffer(fd, buffer);
2021-04-09 14:07:05 +02:00
}
2024-10-11 16:36:08 +08:00
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);
}
2025-10-16 14:33:53 +08:00
bool ffPathExpandEnv(const char* in, FFstrbuf* out)
{
bool result = false;
2025-10-16 14:33:53 +08:00
#if FF_HAVE_WORDEXP
wordexp_t exp;
2025-10-16 14:33:53 +08:00
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);
2025-10-16 14:33:53 +08:00
#else
glob_t gb;
2025-10-16 14:33:53 +08:00
if (glob(in, GLOB_NOSORT
#ifdef GLOB_TILDE
| GLOB_TILDE
#endif
#ifdef GLOB_BRACE
| GLOB_BRACE
#endif
, NULL, &gb) != 0)
return false;
2025-10-16 14:33:53 +08:00
if (gb.gl_pathc >= 1)
{
result = true;
2025-10-16 14:33:53 +08:00
ffStrbufSetS(out, gb.gl_pathv[gb.gl_pathc > 1 ? ffTimeGetNow() % (unsigned) gb.gl_pathc : 0]);
}
globfree(&gb);
#endif
return result;
}
2024-07-31 09:40:00 +08:00
static int ftty = -1;
static struct termios oldTerm;
void restoreTerm(void)
{
2024-07-31 09:40:00 +08:00
tcsetattr(ftty, TCSAFLUSH, &oldTerm);
}
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...)
2024-07-31 09:40:00 +08:00
{
if (ftty < 0)
{
ftty = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC);
if (ftty < 0)
2024-07-31 09:40:00 +08:00
return "open(\"/dev/tty\", O_RDWR | O_NOCTTY | O_CLOEXEC) failed";
2024-07-31 09:40:00 +08:00
if(tcgetattr(ftty, &oldTerm) == -1)
return "tcgetattr(STDIN_FILENO, &oldTerm) failed";
2024-07-31 09:40:00 +08:00
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);
}
2024-07-31 09:40:00 +08:00
ffWriteFDData(ftty, strlen(request), request);
2024-06-25 16:09:19 +08:00
//Give the terminal some time to respond
#ifndef __APPLE__
2024-07-31 09:40:00 +08:00
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)
2024-10-04 15:58:17 +08:00
{
va_end(args);
return "read(STDIN_FILENO, buffer, sizeof(buffer) - 1) failed";
2024-10-04 15:58:17 +08:00
}
bytesRead += (size_t) nRead;
buffer[bytesRead] = '\0';
2024-10-06 09:29:54 +08:00
va_list cargs;
va_copy(cargs, args);
int ret = vsscanf(buffer, format, cargs);
va_end(cargs);
if (ret <= 0)
2024-10-04 15:58:17 +08:00
{
va_end(args);
return "vsscanf(buffer, format, args) failed";
2024-10-04 15:58:17 +08:00
}
if (ret >= nParams)
break;
}
va_end(args);
2023-07-26 10:27:40 +08:00
return NULL;
}
2023-02-11 12:34:57 +08:00
bool ffSuppressIO(bool suppress)
{
2025-03-23 21:56:32 +08:00
#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)
2023-02-11 12:34:57 +08:00
return true;
origOut = dup(STDOUT_FILENO);
origErr = dup(STDERR_FILENO);
2023-12-04 15:14:40 +08:00
nullFile = open("/dev/null", O_WRONLY | O_CLOEXEC);
init = true;
}
if(nullFile == -1)
2023-02-11 12:34:57 +08:00
return false;
fflush(stdout);
fflush(stderr);
dup2(suppress ? nullFile : origOut, STDOUT_FILENO);
dup2(suppress ? nullFile : origErr, STDERR_FILENO);
2023-02-11 12:34:57 +08:00
return true;
}
2023-01-28 17:19:45 +01:00
2023-11-13 16:41:10 +08:00
void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indentation, const char* folderName, bool pretty)
2023-01-28 17:19:45 +01:00
{
2025-06-19 18:11:54 +08:00
FF_AUTO_CLOSE_FD int dfd = open(folder->chars, O_RDONLY | O_CLOEXEC);
2024-06-16 01:02:48 +08:00
if (dfd < 0)
return;
DIR* dir = fdopendir(dfd);
2023-01-28 17:19:45 +01:00
if(dir == NULL)
return;
uint32_t folderLength = folder->length;
2023-11-13 16:41:10 +08:00
if(pretty && folderName != NULL)
{
for(uint8_t i = 0; i < indentation - 1; i++)
fputs(" | ", stdout);
2023-01-28 17:19:45 +01:00
printf("%s/\n", folderName);
2023-11-13 16:41:10 +08:00
}
2023-01-28 17:19:45 +01:00
struct dirent* entry;
while((entry = readdir(dir)) != NULL)
{
if(entry->d_name[0] == '.') // skip hidden files
continue;
2024-06-16 01:02:48 +08:00
bool isDir = false;
2025-02-05 02:08:28 +01:00
#if !defined(__sun) && !defined(__HAIKU__)
2024-06-16 01:02:48 +08:00
if(entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK)
isDir = entry->d_type == DT_DIR;
else
#endif
2024-06-16 01:02:48 +08:00
{
struct stat stbuf;
if (fstatat(dfd, entry->d_name, &stbuf, 0) < 0)
isDir = false;
else
isDir = S_ISDIR(stbuf.st_mode);
}
if (isDir)
2023-01-28 17:19:45 +01:00
{
ffStrbufAppendS(folder, entry->d_name);
ffStrbufAppendC(folder, '/');
2023-11-13 16:41:10 +08:00
listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry->d_name, pretty);
2023-01-28 17:19:45 +01:00
ffStrbufSubstrBefore(folder, folderLength);
continue;
}
2023-11-13 16:41:10 +08:00
if (pretty)
{
for(uint8_t i = 0; i < indentation; i++)
fputs(" | ", stdout);
}
else
{
fputs(folder->chars + baseLength, stdout);
}
2023-01-28 17:19:45 +01:00
puts(entry->d_name);
}
closedir(dir);
}
2023-11-13 16:41:10 +08:00
void ffListFilesRecursively(const char* path, bool pretty)
2023-01-28 17:19:45 +01:00
{
FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path);
2023-01-28 17:19:45 +01:00
ffStrbufEnsureEndsWithC(&folder, '/');
2023-11-13 16:41:10 +08:00
listFilesRecursively(folder.length, &folder, 0, NULL, pretty);
2023-01-28 17:19:45 +01:00
}
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;
}