diff --git a/Source/DrangPlatform/Source/linux/fs/file.c b/Source/DrangPlatform/Source/linux/fs/file.c new file mode 100644 index 0000000..8f81f52 --- /dev/null +++ b/Source/DrangPlatform/Source/linux/fs/file.c @@ -0,0 +1,484 @@ + +#include "drang/alloc.h" +#include "internal.h" +#include +#include +#include +#include +#include +#include + +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() +} \ No newline at end of file diff --git a/Source/DrangPlatform/Source/linux/fs/internal.h b/Source/DrangPlatform/Source/linux/fs/internal.h new file mode 100644 index 0000000..7cf40bd --- /dev/null +++ b/Source/DrangPlatform/Source/linux/fs/internal.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include +#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; +};