474 lines
10 KiB
C
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); }
|