feature: implement linux file handling functions including open, close, read, write, and permissions management

This commit is contained in:
MechSlayer 2025-09-09 03:58:17 +02:00
parent 710f231a47
commit 7cd3fe2905
2 changed files with 497 additions and 0 deletions

View file

@ -0,0 +1,484 @@
#include "drang/alloc.h"
#include "internal.h"
#include <drang/error.h>
#include <errno.h>
#include <errno_convert.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
static int mode_to_open_flags(const drang_fs_mode_t mode)
{
int oflags = 0;
if (mode & drang_fs_mode_read && mode & drang_fs_mode_write) {
oflags |= O_RDWR;
} else if (mode & drang_fs_mode_read) {
oflags |= O_RDONLY;
} else if (mode & drang_fs_mode_write) {
oflags |= O_WRONLY;
}
if (mode & drang_fs_mode_append) {
oflags |= O_APPEND;
}
if (mode & drang_fs_mode_create) {
oflags |= O_CREAT;
}
if (mode & drang_fs_mode_truncate) {
oflags |= O_TRUNC;
}
if (mode & drang_fs_mode_exclusive) {
oflags |= O_EXCL;
}
if (oflags == 0)
return 0;
oflags |= O_LARGEFILE; // Ensure large file support
return oflags;
}
static int drang_permissions_to_linux(const drang_fs_permissions_t permissions)
{
int result = 0;
if (permissions & DRANG_FS_PERM_USER_READ) result |= S_IRUSR;
if (permissions & DRANG_FS_PERM_USER_WRITE) result |= S_IWUSR;
if (permissions & DRANG_FS_PERM_USER_EXEC) result |= S_IXUSR;
if (permissions & DRANG_FS_PERM_GROUP_READ) result |= S_IRGRP;
if (permissions & DRANG_FS_PERM_GROUP_WRITE) result |= S_IWGRP;
if (permissions & DRANG_FS_PERM_GROUP_EXEC) result |= S_IXGRP;
if (permissions & DRANG_FS_PERM_OTHER_READ) result |= S_IROTH;
if (permissions & DRANG_FS_PERM_OTHER_WRITE) result |= S_IWOTH;
if (permissions & DRANG_FS_PERM_OTHER_EXEC) result |= S_IXOTH;
return result;
}
int drang_fs_open(const char *path, drang_fs_mode_t mode, drang_fs_file_t **out_file)
{
struct drang_fs_file* file = NULL;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_file, DRANG_EINVAL);
const int oflags = mode_to_open_flags(mode);
DRANG_CHECK(oflags != 0, DRANG_EINVAL);
file = DRANG_TRY_CALLOC_T(struct drang_fs_file);
DRANG_TRY(drang_rwlock_init(&file->lock));
const int fd = (oflags & O_CREAT) ? open(path, oflags, 0666) : open(path, oflags);
if (fd == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
file->fd = fd;
file->mode = mode;
DRANG_RETURN_IN(out_file, file);
DRANG_CATCH(_)
{
if (file != NULL) {
drang_rwlock_fini(&file->lock);
drang_free(file);
}
}
DRANG_END_TRY()
}
int drang_fs_close(drang_fs_file_t *file)
{
bool locked = false;
if (file == NULL) return DRANG_EOK;
DRANG_BEGIN_TRY()
DRANG_TRY(drang_fs_flush(file));
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const int result = close(file->fd);
if (result == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_rwlock_wrunlock(&file->lock);
drang_rwlock_fini(&file->lock);
drang_free(file);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
// TODO: Implement buffering
int drang_fs_read(drang_fs_file_t *file, void *buffer, size_t count, size_t size, size_t *bytes_read)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_CHECK(file->mode & drang_fs_mode_read, DRANG_EPERM);
DRANG_CHECK(count > 0 && size > 0, DRANG_EINVAL);
size_t total_bytes = 0;
DRANG_CHECK(!DRANG_MUL_OVERFLOW(count, size, &total_bytes), DRANG_EOVERFLOW);
DRANG_TRY(drang_rwlock_rdlock(&file->lock));
locked = true;
const ssize_t res = read(file->fd, buffer, total_bytes);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
if (bytes_read != NULL) {
*bytes_read = (size_t)res;
}
drang_rwlock_rdunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_rdunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_write(
drang_fs_file_t *file, const void *buffer, size_t count, size_t size, size_t *bytes_written)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_CHECK(file->mode & (drang_fs_mode_write | drang_fs_mode_append), DRANG_EPERM);
DRANG_CHECK(count > 0 && size > 0, DRANG_EINVAL);
size_t total_bytes = 0;
DRANG_CHECK(!DRANG_MUL_OVERFLOW(count, size, &total_bytes), DRANG_EOVERFLOW);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const ssize_t res = write(file->fd, buffer, total_bytes);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
if (bytes_written != NULL) {
*bytes_written = (size_t)res;
}
drang_rwlock_wrunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_seek(drang_fs_file_t *file, int64_t offset, drang_seek_origin_t origin)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
int whence = 0;
switch (origin) {
case drang_seek_origin_begin:
whence = SEEK_SET;
break;
case drang_seek_origin_current:
whence = SEEK_CUR;
break;
case drang_seek_origin_end:
whence = SEEK_END;
break;
default:
DRANG_FAIL(DRANG_EINVAL);
}
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const off_t res = lseek(file->fd, (off_t)offset, whence);
if (res == (off_t)-1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_rwlock_wrunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_tell(drang_fs_file_t *file, uint64_t *position)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(position, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_rdlock(&file->lock));
locked = true;
const off_t res = lseek(file->fd, 0, SEEK_CUR);
if (res == (off_t)-1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
*position = (uint64_t)res;
drang_rwlock_rdunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_rdunlock(&file->lock);
}
}
DRANG_END_TRY()
}
// TODO: Implement buffering
int drang_fs_flush(drang_fs_file_t *file)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const int res = fsync(file->fd);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_rwlock_wrunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_truncate(drang_fs_file_t *file, uint64_t size)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_CHECK(file->mode & drang_fs_mode_write, DRANG_EPERM);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const int res = ftruncate(file->fd, (off_t)size);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_rwlock_wrunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_create(const char *path, drang_fs_permissions_t permissions)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
const int linux_perms = drang_permissions_to_linux(permissions);
const int fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_LARGEFILE, linux_perms);
if (fd == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
const int res = close(fd);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_remove(const char *path)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
const int res = unlink(path);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_move(const char *old_path, const char *new_path)
{
int src_fd = -1;
int dst_fd = -1;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(old_path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(new_path, DRANG_EINVAL);
if (rename(old_path, new_path) == 0) {
DRANG_RETURN();
}
if (errno == EXDEV) { // Cross-device link, need to copy+delete
struct stat src_stat = {0};
if (stat(old_path, &src_stat) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
src_fd = open(old_path, O_RDONLY | O_LARGEFILE);
if (src_fd == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
dst_fd = open(new_path, O_CREAT | O_EXCL | O_WRONLY | O_LARGEFILE, src_stat.st_mode);
if (dst_fd == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
char buffer[DRANG_LINUX_FS_BUFFER_SIZE];
ssize_t bytes_read = 0;
while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0) {
const ssize_t write_res = write(dst_fd, buffer, (size_t)bytes_read);
if (write_res == -1 || write_res != bytes_read) {
DRANG_FAIL(drang_errno_to_error(errno));
}
}
if (bytes_read == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
if (close(src_fd) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
src_fd = -1;
if (close(dst_fd) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
dst_fd = -1;
if (unlink(old_path) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_RETURN();
}
DRANG_FAIL(drang_errno_to_error(errno));
DRANG_CATCH(_)
{
if (src_fd != -1) {
close(src_fd);
}
if (dst_fd != -1) {
close(dst_fd);
unlink(new_path);
}
}
DRANG_END_TRY()
}
bool drang_fs_exists(const char *path)
{
if (path == NULL) {
return false;
}
struct stat buffer;
return (stat(path, &buffer) == 0);
}
int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_stat, DRANG_EINVAL);
struct stat st;
if (stat(path, &st) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
if (S_ISREG(st.st_mode)) {
out_stat->type = drang_fs_type_file;
} else if (S_ISDIR(st.st_mode)) {
out_stat->type = drang_fs_type_directory;
} else if (S_ISLNK(st.st_mode)) {
out_stat->type = drang_fs_type_symlink;
} else {
out_stat->type = drang_fs_type_unknown;
}
out_stat->size = (uint64_t)st.st_size;
out_stat->permissions = 0;
if (st.st_mode & S_IRUSR) out_stat->permissions |= DRANG_FS_PERM_USER_READ;
if (st.st_mode & S_IWUSR) out_stat->permissions |= DRANG_FS_PERM_USER_WRITE;
if (st.st_mode & S_IXUSR) out_stat->permissions |= DRANG_FS_PERM_USER_EXEC;
if (st.st_mode & S_IRGRP) out_stat->permissions |= DRANG_FS_PERM_GROUP_READ;
if (st.st_mode & S_IWGRP) out_stat->permissions |= DRANG_FS_PERM_GROUP_WRITE;
if (st.st_mode & S_IXGRP) out_stat->permissions |= DRANG_FS_PERM_GROUP_EXEC;
if (st.st_mode & S_IROTH) out_stat->permissions |= DRANG_FS_PERM_OTHER_READ;
if (st.st_mode & S_IWOTH) out_stat->permissions |= DRANG_FS_PERM_OTHER_WRITE;
if (st.st_mode & S_IXOTH) out_stat->permissions |= DRANG_FS_PERM_OTHER_EXEC;
out_stat->created_time = (drang_fs_time_t)st.st_ctim.tv_sec * 1000000000 + st.st_ctim.tv_nsec;
out_stat->modified_time = (drang_fs_time_t)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
out_stat->accessed_time = (drang_fs_time_t)st.st_atim.tv_sec * 1000000000 + st.st_atim.tv_nsec;
DRANG_END_TRY_IGNORE()
}
int drang_fs_set_permissions(const char *path, drang_fs_permissions_t permissions)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
const int linux_perms = drang_permissions_to_linux(permissions);
if (chmod(path, linux_perms) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <drang/fs.h>
#include <unistd.h>
#include "linux/sync/internal.h"
#define DRANG_LINUX_FS_BUFFER_SIZE (4096)
struct drang_fs_file
{
int fd;
drang_fs_mode_t mode;
struct drang_rwlock lock;
};