vessel/core/file.c
2025-06-21 16:26:31 +03:00

474 lines
10 KiB
C

#include "include/conf.h"
#include "include/file.h"
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#ifdef FILE_NOPOLL
# include <sys/select.h>
#else
# include <poll.h>
#endif /* FILE_NOPOLL */
static inline unsigned vs_file_flags_to_unix_flags(vs_FileFlags flags) {
if (flags == VS_FILEF_NONE) {
return 0;
}
unsigned oflags = 0;
/* R/W flags */
if ((flags & VS_FILEF_RD) && (flags & VS_FILEF_WR)) {
oflags |= O_RDWR;
} else if (flags & VS_FILEF_RD) {
oflags |= O_RDONLY;
} else if (flags & VS_FILEF_WR) {
oflags |= O_WRONLY;
}
/* I/O flags */
if (flags & VS_FILEF_CREAT) {
oflags |= O_CREAT;
}
if (flags & VS_FILEF_APPEND) {
oflags |= O_APPEND;
}
if (flags & VS_FILEF_NONBLOCK) {
oflags |= O_NONBLOCK;
}
if (flags & VS_FILEF_TRUNC) {
oflags |= O_TRUNC;
}
return oflags;
}
static inline mode_t vs_file_mode_to_unix_mode(vs_FileMode mode) {
if (mode == VS_FILEM_ALL) {
return 0777;
}
if (mode == VS_FILEM_NONE) {
return 0000;
}
mode_t unix_mode = 0;
/* User flags */
if (mode & VS_FILEM_ORD) {
unix_mode |= S_IRUSR;
}
if (mode & VS_FILEM_OWR) {
unix_mode |= S_IWUSR;
}
if (mode & VS_FILEM_OEX) {
unix_mode |= S_IXUSR;
}
/* Group flags */
if (mode & VS_FILEM_GRD) {
unix_mode |= S_IRGRP;
}
if (mode & VS_FILEM_GWR) {
unix_mode |= S_IWGRP;
}
if (mode & VS_FILEM_GEX) {
unix_mode |= S_IXGRP;
}
/* Set other permissions */
if (mode & VS_FILEM_ARD) {
unix_mode |= S_IROTH;
}
if (mode & VS_FILEM_AWR) {
unix_mode |= S_IWOTH;
}
if (mode & VS_FILEM_AEX) {
unix_mode |= S_IXOTH;
}
return unix_mode;
}
static inline vs_FileMode vs_unix_mode_to_file_mode(const mode_t unix_mode) {
if (unix_mode == 0777) {
return VS_FILEM_ALL;
}
if (unix_mode == 0000) {
return VS_FILEM_NONE;
}
vs_FileMode mode = VS_FILEM_NONE;
/* User flags */
if (unix_mode & S_IRUSR) {
mode |= VS_FILEM_ORD;
}
if (unix_mode & S_IWUSR) {
mode |= VS_FILEM_OWR;
}
if (unix_mode & S_IXUSR) {
mode |= VS_FILEM_OEX;
}
/* Group flags */
if (unix_mode & S_IRGRP) {
mode |= VS_FILEM_GRD;
}
if (unix_mode & S_IWGRP) {
mode |= VS_FILEM_GWR;
}
if (unix_mode & S_IXGRP) {
mode |= VS_FILEM_GEX;
}
/* Other permissions */
if (unix_mode & S_IROTH) {
mode |= VS_FILEM_ARD;
}
if (unix_mode & S_IWOTH) {
mode |= VS_FILEM_AWR;
}
if (unix_mode & S_IXOTH) {
mode |= VS_FILEM_AEX;
}
return mode;
}
bool vs_File_init(vs_File *file, vs_File *src) {
if (!file) {
return false;
}
if (src) {
file->_fd = src->_fd;
file->_fs = src->_fs;
file->_error = src->_error;
} else {
file->_fd = -1;
file->_fs = -1;
file->_error = false;
}
return true;
}
bool vs_File_open(vs_File *file, const char *filename, vs_FileFlags flags, vs_FileMode mode) {
if (!file || !filename || vs_File_isopen(file)) {
return false;
}
const int oflags = (int)vs_file_flags_to_unix_flags(flags);
if (mode == VS_FILEM_NONE) {
file->_fd = open(filename, oflags);
} else {
file->_fd = open(filename, oflags, vs_file_mode_to_unix_mode(mode));
}
if (file->_fd < 0) {
vs_File_seterr(file);
return false;
}
file->_fs = -1;
vs_File_clearerr(file);
return true;
}
static inline int vs_portable_mkstemp(char *tmpl) {
/* +1 for NULL */
static const char charset[64 + 1] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
size_t len = strlen(tmpl);
if (len < 6 || strcmp(&tmpl[len - 6], "XXXXXX") != 0) {
errno = EINVAL;
return -1;
}
char *xxxxxx = tmpl + len - 6;
for (uint8_t attempt = 0; attempt < 128; ++attempt) {
for (uint8_t idx = 0; idx < 6; ++idx) {
/* 64 = 2^6, therefore we can use optimisation a & (b - 1) */
xxxxxx[idx] = charset[(size_t)vs_get_rand() & (size_t)63];
}
int fd = open(tmpl, O_RDWR | O_CREAT | O_EXCL | O_NOCTTY, 0600);
if (fd >= 0) {
unlink(tmpl);
return fd;
}
if (errno != EEXIST) {
break;
}
}
errno = EEXIST;
return -1;
}
bool vs_File_create_tmp(vs_File *file) {
if (!file || vs_File_isopen(file)) {
return false;
}
char tmpl[] = VS_FILE_TMPDIR ".vs_tmp_XXXXXX";
int fd = vs_portable_mkstemp(tmpl);
if (fd < 0) {
vs_File_seterr(file);
return false;
}
if (fd < 0) {
vs_File_seterr(file);
return false;
}
file->_fd = fd;
file->_fs = -1;
vs_File_clearerr(file);
return true;
}
bool vs_File_isfile(vs_File *file) { return file && file->_fd >= 0 && file->_fs == -1; }
bool vs_File_issock(vs_File *file) { return file && file->_fs >= 0 && file->_fd == -1; }
bool vs_File_isopen(vs_File *file) {
if (!file) {
return false;
}
return vs_bool_xor(vs_File_isfile(file), vs_File_issock(file));
}
/*
* This is a problem we will run into while trying to support Windows,
* which is why we use a void* instead of a `int fd` or whatver since
* Windows seperates sockets and normal files.
*
* Windows sockets are of SOCKET type in winsock2.h (WinAPI) where files
* are FILE*. In my honest opinion as a developer, this is extremely stupid
* since UNIXes just use file descriptors.
*
* Windows sucks :(
*
* Size is checked just in case you try to pass some garbage data and it
* crashes. Mainly a sanity thing since generic types suck.
*/
bool vs_File_usesock(vs_File *file, vs_FileSocket sock) {
if (!file || sock < 0 || vs_File_isopen(file)) {
return false;
}
file->_fs = sock;
file->_fd = -1;
return vs_File_clearerr(file);
}
bool vs_File_iserr(vs_File *file) { return !file || file->_error; }
bool vs_File_clearerr(vs_File *file) {
if (!file) {
return false;
}
file->_error = false;
return true;
}
bool vs_File_seterr(vs_File *file) {
if (!file) {
return false;
}
file->_error = true;
return true;
}
int vs_File_poll(vs_File *file, const struct timeval *timeout) {
if (!file || !timeout || !vs_File_isopen(file)) {
return -1;
}
#ifdef FILE_NOPOLL
fd_set read_fds;
struct timeval to;
const int fdx = vs_File_isfile(file) ? file->_fd : file->_fs;
to.tv_sec = timeout->tv_sec;
to.tv_usec = timeout->tv_usec;
FD_ZERO(&read_fds);
FD_SET(fdx, &read_fds);
const int ret = select(fdx + 1, &read_fds, NULL, NULL, &to);
#else
struct pollfd pfd;
pfd.fd = vs_File_isfile(file) ? file->_fd : file->_fs;
pfd.events = POLLIN;
const int timeout_ms = (int)((timeout->tv_sec * 1000) + (timeout->tv_usec / 1000));
const int ret = poll(&pfd, 1, timeout_ms);
#endif /* FILE_NOPOLL */
return ret < 0 ? -1 : ret;
}
size_t vs_File_read(vs_File *file, void *buf, size_t count) {
if (!file || !buf || 0 == count || !vs_File_isopen(file)) {
return 0;
}
const ssize_t read_bytes =
vs_File_isfile(file) ? read(file->_fd, buf, count) : recv(file->_fs, buf, count, 0);
if (read_bytes < 0) {
vs_File_seterr(file);
return 0;
}
vs_File_clearerr(file);
return (size_t)read_bytes;
}
size_t vs_File_write(vs_File *file, const void *buf, const size_t count) {
if (!file || !buf || 0 == count || !vs_File_isopen(file)) {
return 0;
}
const ssize_t written_bytes = vs_File_isfile(file) ? write(file->_fd, buf, count)
: send(file->_fs, buf, count, MSG_NOSIGNAL);
if (written_bytes < 0) {
vs_File_seterr(file);
return 0;
}
vs_File_clearerr(file);
return (size_t)written_bytes;
}
off_t vs_File_seek(vs_File *file, off_t off, vs_FileSeekFlag flag) {
if (!file || !vs_File_isfile(file)) {
return 0;
}
int whence = 0;
const off_t offset = off;
if (flag == VS_FILES_SET) {
whence = SEEK_SET;
} else if (flag == VS_FILES_CUR) {
whence = SEEK_CUR;
} else if (flag == VS_FILES_END) {
whence = SEEK_END;
} else {
vs_File_seterr(file);
return -1;
}
const off_t lseeker = lseek(file->_fd, offset, whence);
if (lseeker == (off_t)-1) {
vs_File_seterr(file);
return -1;
}
vs_File_clearerr(file);
return lseeker;
}
uint64_t vs_File_identity(vs_File *file) {
if (!file || !vs_File_isopen(file)) {
return 0;
}
return (uint64_t)(vs_File_isfile(file) ? file->_fd : file->_fs);
}
bool vs_File_close(vs_File *file) {
if (!file || !vs_File_isopen(file)) {
return false;
}
bool ret = false;
if (vs_File_isfile(file)) {
ret = close(file->_fd) == 0;
} else {
const bool shut_down = shutdown(file->_fs, SHUT_RDWR) == 0;
const bool closed = close(file->_fs) == 0;
ret = shut_down && closed;
}
vs_File_clear(file);
if (ret) {
vs_File_clearerr(file);
return true;
}
vs_File_seterr(file);
return false;
}
bool vs_File_destroy(vs_File *file) {
if (!file) {
return false;
}
vs_File_close(file);
vs_File_clear(file);
return true;
}
bool vs_File_clear(vs_File *file) {
if (!file) {
return false;
}
file->_fd = -1;
file->_fs = -1;
file->_error = false;
return true;
}
bool vs_File_stat(vs_File *file, vs_FileStat *statbuf) {
if (!file || !statbuf || !vs_File_isfile(file)) {
return false;
}
struct stat sbuf;
if (fstat(file->_fd, &sbuf) < 0) {
return false;
}
statbuf->size = sbuf.st_size < 0 ? 0 : (size_t)sbuf.st_size;
statbuf->last_access_time = sbuf.st_atime;
statbuf->last_modification_time = sbuf.st_mtime;
statbuf->mode = vs_unix_mode_to_file_mode(sbuf.st_mode);
return true;
}
bool vs_File_ok(const char *path) { return 0 == access(path, F_OK | R_OK | W_OK); }