#include "misc.h" #include "posix.h" #include #include #include #include #include #include #include #include #include #include #include #include #define CHECK_FLAG(v, f) (((v) & (f)) == (f)) static enum fx_status stream_close(fx_stream *); static enum fx_status stream_getc(fx_stream *, int *); static enum fx_status stream_read(fx_stream *, void *, size_t, size_t *); static enum fx_status stream_write(fx_stream *, const void *, size_t, size_t *); static enum fx_status stream_seek(fx_stream *, long long, fx_stream_seek_origin); static enum fx_status stream_tell(const fx_stream *, size_t *); /*** PRIVATE DATA *************************************************************/ struct fx_file_p { enum fx_file_mode mode; int fd; fx_path *path; }; /*** PRIVATE FUNCTIONS ********************************************************/ static unsigned int fx_mode_to_unix_mode(enum fx_file_mode mode) { unsigned int result = 0; if (CHECK_FLAG(mode, FX_FILE_READ_WRITE)) { result |= O_RDWR; } else if (CHECK_FLAG(mode, FX_FILE_READ_ONLY)) { result |= O_RDONLY; } else if (CHECK_FLAG(mode, FX_FILE_WRITE_ONLY)) { result |= O_WRONLY; } else { return (unsigned int)-1; } if (CHECK_FLAG(mode, FX_FILE_TRUNCATE)) { result |= O_TRUNC; } if (CHECK_FLAG(mode, FX_FILE_CREATE)) { result |= O_CREAT; } if (CHECK_FLAG(mode, FX_FILE_CREATE_ONLY)) { result |= O_EXCL; } return result; } static fx_result file_open_shadow( struct fx_file_p *original, enum fx_file_mode mode, fx_file **out) { mode |= FX_FILE_SHADOW | FX_FILE_DELETE_ON_CLOSE | FX_FILE_CREATE; fx_path *dir; fx_path_get_directory(original->path, &dir); fx_string *filename = fx_string_create(); fx_path_get_filename(original->path, filename); fx_string_prepend_cstr(filename, ".~"); fx_path *shadow_filename = fx_path_create_from_cstr(fx_string_ptr(filename)); fx_string_unref(filename); const fx_path *parts[] = { dir, shadow_filename, }; fx_path *shadow_filepath = fx_path_join(parts, sizeof parts / sizeof parts[0]); fx_path_unref(dir); fx_path_unref(shadow_filename); if (fx_path_exists(shadow_filepath)) { fx_path_unlink(shadow_filepath); } fx_file *shadow_file; fx_result status = fx_file_open( FX_DIRECTORY_ROOT, shadow_filepath, mode, &shadow_file); fx_path_unref(shadow_filepath); if (fx_result_is_error(status)) { return status; } *out = shadow_file; return FX_RESULT_SUCCESS; } static const fx_path *file_path(const struct fx_file_p *file) { return file->path; } static enum fx_status file_stat(struct fx_file_p *file, struct fx_file_info *out) { struct stat st; int err = fstat(file->fd, &st); if (err != 0) { return fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } memset(out, 0x0, sizeof *out); return fx_file_info_from_stat(&st, out); } static enum fx_status file_size(struct fx_file_p *file, size_t *out_len) { off_t cur = lseek(file->fd, 0, SEEK_CUR); if (cur == (off_t)-1) { return fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } off_t len = lseek(file->fd, 0, SEEK_END); if (len == (off_t)-1) { return fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } cur = lseek(file->fd, cur, SEEK_SET); if (cur == (off_t)-1) { return fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } *out_len = (size_t)len; return FX_SUCCESS; } static enum fx_status file_cursor(struct fx_file_p *file, size_t *out_pos) { off_t cur = lseek(file->fd, 0, SEEK_CUR); if (cur == (off_t)-1) { return fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } *out_pos = (size_t)cur; return FX_SUCCESS; } static enum fx_status file_resize(struct fx_file_p *file, size_t len) { int err = ftruncate(file->fd, len); if (err == 0) { return FX_SUCCESS; } return fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } static enum fx_status file_seek( struct fx_file_p *file, long long offset, enum fx_seek_basis basis) { int whence; switch (basis) { case FX_SEEK_BEGINNING: whence = SEEK_SET; break; case FX_SEEK_CURRENT: whence = SEEK_CUR; break; case FX_SEEK_END: whence = SEEK_END; break; default: return FX_ERR_INVALID_ARGUMENT; } int err = lseek(file->fd, offset, whence); if (err == (off_t)-1) { return fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } return FX_SUCCESS; } static enum fx_status file_swap_shadow( struct fx_file_p *main_file, struct fx_file_p *shadow_file) { if (main_file->mode & FX_FILE_SHADOW) { return FX_ERR_NOT_SUPPORTED; } if (!(shadow_file->mode & FX_FILE_SHADOW)) { return FX_ERR_NOT_SUPPORTED; } fx_path *dir_path; fx_path_get_directory(main_file->path, &dir_path); fx_path *tmp_path = NULL; while (1) { char tmp_name[16]; z__fx_io_generate_tmp_filename(tmp_name, sizeof tmp_name); fx_path *tmp_name_p = fx_path_create_from_cstr(tmp_name); const fx_path *parts[] = { dir_path, tmp_name_p, }; tmp_path = fx_path_join(parts, sizeof parts / sizeof parts[0]); fx_path_unref(tmp_name_p); if (!fx_path_exists(tmp_path)) { break; } fx_path_unref(tmp_path); tmp_path = NULL; } fx_path_unref(dir_path); int err; err = rename(fx_path_ptr(main_file->path), fx_path_ptr(tmp_path)); err = rename(fx_path_ptr(shadow_file->path), fx_path_ptr(main_file->path)); err = rename(fx_path_ptr(tmp_path), fx_path_ptr(shadow_file->path)); fx_path_unref(tmp_path); int fd = main_file->fd; main_file->fd = shadow_file->fd; shadow_file->fd = fd; return FX_SUCCESS; } static enum fx_status file_read( struct fx_file_p *file, size_t offset, size_t len, void *buf, size_t *nr_read) { if (offset != FX_OFFSET_CURRENT) { lseek(file->fd, offset, SEEK_SET); } long r = read(file->fd, buf, len); enum fx_status status = FX_SUCCESS; if (r < 0) { status = fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } *nr_read = r; return status; } static enum fx_status file_write( struct fx_file_p *file, size_t offset, size_t len, const void *buf, size_t *nr_written) { if (offset != FX_OFFSET_CURRENT) { lseek(file->fd, offset, SEEK_SET); } long w = write(file->fd, buf, len); enum fx_status status = FX_SUCCESS; if (w < 0) { status = fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } *nr_written = w; return status; } /*** STREAM FUNCTIONS *********************************************************/ static enum fx_status stream_close(fx_stream *stream) { return FX_SUCCESS; } static enum fx_status stream_getc(fx_stream *stream, int *out) { struct fx_file_p *file = fx_object_get_private(stream, FX_TYPE_FILE); char c; size_t nr_read = 0; enum fx_status status = file_read(file, FX_OFFSET_CURRENT, sizeof c, &c, &nr_read); if (status != FX_SUCCESS) { return status; } if (nr_read == 0) { return FX_ERR_NO_DATA; } *out = c; return FX_SUCCESS; } static enum fx_status stream_read( fx_stream *stream, void *buf, size_t max, size_t *nr_read) { struct fx_file_p *file = fx_object_get_private(stream, FX_TYPE_FILE); enum fx_status status = file_read(file, FX_OFFSET_CURRENT, max, buf, nr_read); return status; } static enum fx_status stream_write( fx_stream *stream, const void *buf, size_t count, size_t *nr_written) { struct fx_file_p *file = fx_object_get_private(stream, FX_TYPE_FILE); enum fx_status status = file_write(file, FX_OFFSET_CURRENT, count, buf, nr_written); return status; } static enum fx_status stream_seek( fx_stream *stream, long long offset, fx_stream_seek_origin origin) { fx_seek_basis basis; switch (origin) { case FX_STREAM_SEEK_START: basis = FX_SEEK_BEGINNING; break; case FX_STREAM_SEEK_CURRENT: basis = FX_SEEK_CURRENT; break; case FX_STREAM_SEEK_END: basis = FX_SEEK_END; break; default: return FX_ERR_INVALID_ARGUMENT; } struct fx_file_p *file = fx_object_get_private(stream, FX_TYPE_FILE); return file_seek(file, offset, basis); } static enum fx_status stream_tell(const fx_stream *stream, size_t *pos) { const struct fx_file_p *file = fx_object_get_private(stream, FX_TYPE_FILE); off_t v = lseek(file->fd, 0, SEEK_CUR); if (v == (off_t)-1) { return fx_status_from_errno(errno, FX_ERR_IO_FAILURE); } *pos = v; return FX_SUCCESS; } /*** PUBLIC FUNCTIONS *********************************************************/ fx_result fx_file_open( fx_directory *root, const fx_path *path, enum fx_file_mode mode, fx_file **out) { const fx_path *file_path = path; unsigned int flags = fx_mode_to_unix_mode(mode); if (flags == (unsigned int)-1) { return FX_RESULT_ERR(INVALID_ARGUMENT); } const fx_path *root_path = NULL; bool free_root_path = false; if (root) { root_path = fx_directory_get_path(root); } else { root_path = fx_path_create_cwd(); free_root_path = true; } const fx_path *parts[] = { root_path, file_path, }; fx_path *abs_path = fx_path_join(parts, sizeof parts / sizeof parts[0]); if (free_root_path) { fx_path_unref((fx_path *)root_path); } if (!abs_path) { return FX_RESULT_ERR(NO_MEMORY); } int fd = open(fx_path_ptr(abs_path), flags, 0644); if (fd == -1) { fx_path_unref(abs_path); return FX_RESULT_STATUS( fx_status_from_errno(errno, FX_ERR_IO_FAILURE)); } fx_file *file = fx_object_create(FX_TYPE_FILE); if (!file) { close(fd); fx_path_unref(abs_path); return FX_RESULT_ERR(NO_MEMORY); } struct fx_file_p *p = fx_object_get_private(file, FX_TYPE_FILE); fx_stream_cfg *cfg = fx_object_get_protected(file, FX_TYPE_STREAM); if (mode & FX_FILE_READ_ONLY) { cfg->s_mode |= FX_STREAM_READ; } if (mode & FX_FILE_WRITE_ONLY) { cfg->s_mode |= FX_STREAM_WRITE; } if (mode & FX_FILE_BINARY) { cfg->s_mode |= FX_STREAM_BINARY; } p->fd = fd; p->path = abs_path; p->mode = mode; *out = file; return FX_RESULT_SUCCESS; } fx_result fx_file_open_temp(enum fx_file_mode mode, fx_file **out) { mode |= FX_FILE_DELETE_ON_CLOSE; char name[16]; char path[128]; while (1) { z__fx_io_generate_tmp_filename(name, sizeof name); snprintf(path, sizeof path, "/tmp/%s", name); fx_path *rpath = fx_path_create_from_cstr(path); fx_result status = fx_file_open( FX_DIRECTORY_ROOT, rpath, mode | FX_FILE_CREATE_ONLY, out); if (fx_error_get_status_code(status) == FX_ERR_NAME_EXISTS) { fx_path_unref(rpath); continue; } fx_path_unlink(rpath); fx_path_unref(rpath); return status; } } fx_result fx_file_open_shadow(fx_file *original, enum fx_file_mode mode, fx_file **out) { FX_CLASS_DISPATCH_STATIC(FX_TYPE_FILE, file_open_shadow, original, mode, out); } const fx_path *fx_file_path(const fx_file *file) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_FILE, file_path, file); } enum fx_status fx_file_stat(fx_file *file, struct fx_file_info *out) { FX_CLASS_DISPATCH_STATIC(FX_TYPE_FILE, file_stat, file, out); } enum fx_status fx_file_size(fx_file *file, size_t *out_len) { FX_CLASS_DISPATCH_STATIC(FX_TYPE_FILE, file_size, file, out_len); } enum fx_status fx_file_cursor(fx_file *file, size_t *out_pos) { FX_CLASS_DISPATCH_STATIC(FX_TYPE_FILE, file_cursor, file, out_pos); } enum fx_status fx_file_resize(fx_file *file, size_t len) { FX_CLASS_DISPATCH_STATIC(FX_TYPE_FILE, file_resize, file, len); } enum fx_status fx_file_seek(fx_file *file, long long offset, enum fx_seek_basis basis) { FX_CLASS_DISPATCH_STATIC(FX_TYPE_FILE, file_seek, file, offset, basis); } enum fx_status fx_file_swap_shadow(fx_file *main_file, fx_file *shadow_file) { struct fx_file_p *main_p = fx_object_get_private(main_file, FX_TYPE_FILE); struct fx_file_p *shadow_p = fx_object_get_private(main_file, FX_TYPE_FILE); return file_swap_shadow(main_p, shadow_p); } enum fx_status fx_file_read( fx_file *file, size_t offset, size_t len, void *buf, size_t *nr_read) { FX_CLASS_DISPATCH_STATIC( FX_TYPE_FILE, file_read, file, offset, len, buf, nr_read); } enum fx_status fx_file_write( fx_file *file, size_t offset, size_t len, const void *buf, size_t *nr_written) { FX_CLASS_DISPATCH_STATIC( FX_TYPE_FILE, file_write, file, offset, len, buf, nr_written); } /*** VIRTUAL FUNCTIONS ********************************************************/ static void file_init(fx_object *obj, void *priv) { struct fx_file_p *file = priv; } static void file_fini(fx_object *obj, void *priv) { struct fx_file_p *file = priv; close(file->fd); if (file->mode & FX_FILE_DELETE_ON_CLOSE) { fx_path_unlink(file->path); } fx_path_unref(file->path); } /*** CLASS DEFINITION *********************************************************/ FX_TYPE_CLASS_DEFINITION_BEGIN(fx_file) FX_TYPE_CLASS_INTERFACE_BEGIN(fx_object, FX_TYPE_OBJECT) FX_INTERFACE_ENTRY(to_string) = NULL; FX_TYPE_CLASS_INTERFACE_END(fx_object, FX_TYPE_OBJECT) FX_TYPE_CLASS_INTERFACE_BEGIN(fx_stream, FX_TYPE_STREAM) FX_INTERFACE_ENTRY(s_close) = stream_close; FX_INTERFACE_ENTRY(s_getc) = stream_getc; FX_INTERFACE_ENTRY(s_read) = stream_read; FX_INTERFACE_ENTRY(s_write) = stream_write; FX_INTERFACE_ENTRY(s_seek) = stream_seek; FX_INTERFACE_ENTRY(s_tell) = stream_tell; FX_TYPE_CLASS_INTERFACE_END(fx_stream, FX_TYPE_STREAM) FX_TYPE_CLASS_DEFINITION_END(fx_file) FX_TYPE_DEFINITION_BEGIN(fx_file) FX_TYPE_ID(0x495a73f6, 0xb8c3, 0x4e17, 0xb5f4, 0x6fc321f67c7b); FX_TYPE_EXTENDS(FX_TYPE_STREAM); FX_TYPE_CLASS(fx_file_class); FX_TYPE_INSTANCE_PRIVATE(struct fx_file_p); FX_TYPE_INSTANCE_INIT(file_init); FX_TYPE_INSTANCE_FINI(file_fini); FX_TYPE_DEFINITION_END(fx_file)