#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 b_status stream_close(b_stream *); static enum b_status stream_getc(b_stream *, int *); static enum b_status stream_read(b_stream *, void *, size_t, size_t *); static enum b_status stream_write(b_stream *, const void *, size_t, size_t *); static enum b_status stream_seek(b_stream *, long long, b_stream_seek_origin); static enum b_status stream_tell(const b_stream *, size_t *); /*** PRIVATE DATA *************************************************************/ struct b_file_p { enum b_file_mode mode; int fd; b_path *path; }; /*** PRIVATE FUNCTIONS ********************************************************/ static unsigned int b_mode_to_unix_mode(enum b_file_mode mode) { unsigned int result = 0; if (CHECK_FLAG(mode, B_FILE_READ_WRITE)) { result |= O_RDWR; } else if (CHECK_FLAG(mode, B_FILE_READ_ONLY)) { result |= O_RDONLY; } else if (CHECK_FLAG(mode, B_FILE_WRITE_ONLY)) { result |= O_WRONLY; } else { return (unsigned int)-1; } if (CHECK_FLAG(mode, B_FILE_TRUNCATE)) { result |= O_TRUNC; } if (CHECK_FLAG(mode, B_FILE_CREATE)) { result |= O_CREAT; } if (CHECK_FLAG(mode, B_FILE_CREATE_ONLY)) { result |= O_EXCL; } return result; } static b_result file_open_shadow( struct b_file_p *original, enum b_file_mode mode, b_file **out) { mode |= B_FILE_SHADOW | B_FILE_DELETE_ON_CLOSE | B_FILE_CREATE; b_path *dir; b_path_get_directory(original->path, &dir); b_string *filename = b_string_create(); b_path_get_filename(original->path, filename); b_string_prepend_cstr(filename, ".~"); b_path *shadow_filename = b_path_create_from_cstr(b_string_ptr(filename)); b_string_unref(filename); const b_path *parts[] = { dir, shadow_filename, }; b_path *shadow_filepath = b_path_join(parts, sizeof parts / sizeof parts[0]); b_path_unref(dir); b_path_unref(shadow_filename); if (b_path_exists(shadow_filepath)) { b_path_unlink(shadow_filepath); } b_file *shadow_file; b_result status = b_file_open( B_DIRECTORY_ROOT, shadow_filepath, mode, &shadow_file); b_path_unref(shadow_filepath); if (b_result_is_error(status)) { return status; } *out = shadow_file; return B_RESULT_SUCCESS; } static const b_path *file_path(const struct b_file_p *file) { return file->path; } static enum b_status file_stat(struct b_file_p *file, struct b_file_info *out) { struct stat st; int err = fstat(file->fd, &st); if (err != 0) { return b_status_from_errno(errno, B_ERR_IO_FAILURE); } memset(out, 0x0, sizeof *out); return b_file_info_from_stat(&st, out); } static enum b_status file_size(struct b_file_p *file, size_t *out_len) { off_t cur = lseek(file->fd, 0, SEEK_CUR); if (cur == (off_t)-1) { return b_status_from_errno(errno, B_ERR_IO_FAILURE); } off_t len = lseek(file->fd, 0, SEEK_END); if (len == (off_t)-1) { return b_status_from_errno(errno, B_ERR_IO_FAILURE); } cur = lseek(file->fd, cur, SEEK_SET); if (cur == (off_t)-1) { return b_status_from_errno(errno, B_ERR_IO_FAILURE); } *out_len = (size_t)len; return B_SUCCESS; } static enum b_status file_cursor(struct b_file_p *file, size_t *out_pos) { off_t cur = lseek(file->fd, 0, SEEK_CUR); if (cur == (off_t)-1) { return b_status_from_errno(errno, B_ERR_IO_FAILURE); } *out_pos = (size_t)cur; return B_SUCCESS; } static enum b_status file_resize(struct b_file_p *file, size_t len) { int err = ftruncate(file->fd, len); if (err == 0) { return B_SUCCESS; } return b_status_from_errno(errno, B_ERR_IO_FAILURE); } static enum b_status file_seek( struct b_file_p *file, long long offset, enum b_seek_basis basis) { int whence; switch (basis) { case B_SEEK_BEGINNING: whence = SEEK_SET; break; case B_SEEK_CURRENT: whence = SEEK_CUR; break; case B_SEEK_END: whence = SEEK_END; break; default: return B_ERR_INVALID_ARGUMENT; } int err = lseek(file->fd, offset, whence); if (err == (off_t)-1) { return b_status_from_errno(errno, B_ERR_IO_FAILURE); } return B_SUCCESS; } static enum b_status file_swap_shadow( struct b_file_p *main_file, struct b_file_p *shadow_file) { if (main_file->mode & B_FILE_SHADOW) { return B_ERR_NOT_SUPPORTED; } if (!(shadow_file->mode & B_FILE_SHADOW)) { return B_ERR_NOT_SUPPORTED; } b_path *dir_path; b_path_get_directory(main_file->path, &dir_path); b_path *tmp_path = NULL; while (1) { char tmp_name[16]; z__b_io_generate_tmp_filename(tmp_name, sizeof tmp_name); b_path *tmp_name_p = b_path_create_from_cstr(tmp_name); const b_path *parts[] = { dir_path, tmp_name_p, }; tmp_path = b_path_join(parts, sizeof parts / sizeof parts[0]); b_path_unref(tmp_name_p); if (!b_path_exists(tmp_path)) { break; } b_path_unref(tmp_path); tmp_path = NULL; } b_path_unref(dir_path); int err; err = rename(b_path_ptr(main_file->path), b_path_ptr(tmp_path)); err = rename(b_path_ptr(shadow_file->path), b_path_ptr(main_file->path)); err = rename(b_path_ptr(tmp_path), b_path_ptr(shadow_file->path)); b_path_unref(tmp_path); int fd = main_file->fd; main_file->fd = shadow_file->fd; shadow_file->fd = fd; return B_SUCCESS; } static enum b_status file_read( struct b_file_p *file, size_t offset, size_t len, void *buf, size_t *nr_read) { if (offset != B_OFFSET_CURRENT) { lseek(file->fd, offset, SEEK_SET); } long r = read(file->fd, buf, len); enum b_status status = B_SUCCESS; if (r < 0) { status = b_status_from_errno(errno, B_ERR_IO_FAILURE); } *nr_read = r; return status; } static enum b_status file_write( struct b_file_p *file, size_t offset, size_t len, const void *buf, size_t *nr_written) { if (offset != B_OFFSET_CURRENT) { lseek(file->fd, offset, SEEK_SET); } long w = write(file->fd, buf, len); enum b_status status = B_SUCCESS; if (w < 0) { status = b_status_from_errno(errno, B_ERR_IO_FAILURE); } *nr_written = w; return status; } /*** STREAM FUNCTIONS *********************************************************/ static enum b_status stream_close(b_stream *stream) { return B_SUCCESS; } static enum b_status stream_getc(b_stream *stream, int *out) { struct b_file_p *file = b_object_get_private(stream, B_TYPE_FILE); char c; size_t nr_read = 0; enum b_status status = file_read(file, B_OFFSET_CURRENT, sizeof c, &c, &nr_read); if (status != B_SUCCESS) { return status; } if (nr_read == 0) { return B_ERR_NO_DATA; } *out = c; return B_SUCCESS; } static enum b_status stream_read( b_stream *stream, void *buf, size_t max, size_t *nr_read) { struct b_file_p *file = b_object_get_private(stream, B_TYPE_FILE); enum b_status status = file_read(file, B_OFFSET_CURRENT, max, buf, nr_read); return status; } static enum b_status stream_write( b_stream *stream, const void *buf, size_t count, size_t *nr_written) { struct b_file_p *file = b_object_get_private(stream, B_TYPE_FILE); enum b_status status = file_write(file, B_OFFSET_CURRENT, count, buf, nr_written); return status; } static enum b_status stream_seek( b_stream *stream, long long offset, b_stream_seek_origin origin) { b_seek_basis basis; switch (origin) { case B_STREAM_SEEK_START: basis = B_SEEK_BEGINNING; break; case B_STREAM_SEEK_CURRENT: basis = B_SEEK_CURRENT; break; case B_STREAM_SEEK_END: basis = B_SEEK_END; break; default: return B_ERR_INVALID_ARGUMENT; } struct b_file_p *file = b_object_get_private(stream, B_TYPE_FILE); return file_seek(file, offset, basis); } static enum b_status stream_tell(const b_stream *stream, size_t *pos) { const struct b_file_p *file = b_object_get_private(stream, B_TYPE_FILE); off_t v = lseek(file->fd, 0, SEEK_CUR); if (v == (off_t)-1) { return b_status_from_errno(errno, B_ERR_IO_FAILURE); } *pos = v; return B_SUCCESS; } /*** PUBLIC FUNCTIONS *********************************************************/ b_result b_file_open( b_directory *root, const b_path *path, enum b_file_mode mode, b_file **out) { const b_path *file_path = path; unsigned int flags = b_mode_to_unix_mode(mode); if (flags == (unsigned int)-1) { return B_RESULT_ERR(INVALID_ARGUMENT); } const b_path *root_path = NULL; bool free_root_path = false; if (root) { root_path = b_directory_get_path(root); } else { root_path = b_path_create_cwd(); free_root_path = true; } const b_path *parts[] = { root_path, file_path, }; b_path *abs_path = b_path_join(parts, sizeof parts / sizeof parts[0]); if (free_root_path) { b_path_unref((b_path *)root_path); } if (!abs_path) { return B_RESULT_ERR(NO_MEMORY); } int fd = open(b_path_ptr(abs_path), flags, 0644); if (fd == -1) { b_path_unref(abs_path); return B_RESULT_STATUS( b_status_from_errno(errno, B_ERR_IO_FAILURE)); } b_file *file = b_object_create(B_TYPE_FILE); if (!file) { close(fd); b_path_unref(abs_path); return B_RESULT_ERR(NO_MEMORY); } struct b_file_p *p = b_object_get_private(file, B_TYPE_FILE); b_stream_cfg *cfg = b_object_get_protected(file, B_TYPE_STREAM); if (mode & B_FILE_READ_ONLY) { cfg->s_mode |= B_STREAM_READ; } if (mode & B_FILE_WRITE_ONLY) { cfg->s_mode |= B_STREAM_WRITE; } if (mode & B_FILE_BINARY) { cfg->s_mode |= B_STREAM_BINARY; } p->fd = fd; p->path = abs_path; p->mode = mode; *out = file; return B_RESULT_SUCCESS; } b_result b_file_open_temp(enum b_file_mode mode, b_file **out) { mode |= B_FILE_DELETE_ON_CLOSE; char name[16]; char path[128]; while (1) { z__b_io_generate_tmp_filename(name, sizeof name); snprintf(path, sizeof path, "/tmp/%s", name); b_path *rpath = b_path_create_from_cstr(path); b_result status = b_file_open( B_DIRECTORY_ROOT, rpath, mode | B_FILE_CREATE_ONLY, out); if (b_error_get_status_code(status) == B_ERR_NAME_EXISTS) { b_path_unref(rpath); continue; } b_path_unlink(rpath); b_path_unref(rpath); return status; } } b_result b_file_open_shadow(b_file *original, enum b_file_mode mode, b_file **out) { B_CLASS_DISPATCH_STATIC(B_TYPE_FILE, file_open_shadow, original, mode, out); } const b_path *b_file_path(const b_file *file) { B_CLASS_DISPATCH_STATIC_0(B_TYPE_FILE, file_path, file); } enum b_status b_file_stat(b_file *file, struct b_file_info *out) { B_CLASS_DISPATCH_STATIC(B_TYPE_FILE, file_stat, file, out); } enum b_status b_file_size(b_file *file, size_t *out_len) { B_CLASS_DISPATCH_STATIC(B_TYPE_FILE, file_size, file, out_len); } enum b_status b_file_cursor(b_file *file, size_t *out_pos) { B_CLASS_DISPATCH_STATIC(B_TYPE_FILE, file_cursor, file, out_pos); } enum b_status b_file_resize(b_file *file, size_t len) { B_CLASS_DISPATCH_STATIC(B_TYPE_FILE, file_resize, file, len); } enum b_status b_file_seek(b_file *file, long long offset, enum b_seek_basis basis) { B_CLASS_DISPATCH_STATIC(B_TYPE_FILE, file_seek, file, offset, basis); } enum b_status b_file_swap_shadow(b_file *main_file, b_file *shadow_file) { struct b_file_p *main_p = b_object_get_private(main_file, B_TYPE_FILE); struct b_file_p *shadow_p = b_object_get_private(main_file, B_TYPE_FILE); return file_swap_shadow(main_p, shadow_p); } enum b_status b_file_read( b_file *file, size_t offset, size_t len, void *buf, size_t *nr_read) { B_CLASS_DISPATCH_STATIC( B_TYPE_FILE, file_read, file, offset, len, buf, nr_read); } enum b_status b_file_write( b_file *file, size_t offset, size_t len, const void *buf, size_t *nr_written) { B_CLASS_DISPATCH_STATIC( B_TYPE_FILE, file_write, file, offset, len, buf, nr_written); } /*** VIRTUAL FUNCTIONS ********************************************************/ static void file_init(b_object *obj, void *priv) { struct b_file_p *file = priv; } static void file_fini(b_object *obj, void *priv) { struct b_file_p *file = priv; close(file->fd); if (file->mode & B_FILE_DELETE_ON_CLOSE) { b_path_unlink(file->path); } b_path_unref(file->path); } /*** CLASS DEFINITION *********************************************************/ B_TYPE_CLASS_DEFINITION_BEGIN(b_file) B_TYPE_CLASS_INTERFACE_BEGIN(b_object, B_TYPE_OBJECT) B_INTERFACE_ENTRY(to_string) = NULL; B_TYPE_CLASS_INTERFACE_END(b_object, B_TYPE_OBJECT) B_TYPE_CLASS_INTERFACE_BEGIN(b_stream, B_TYPE_STREAM) B_INTERFACE_ENTRY(s_close) = stream_close; B_INTERFACE_ENTRY(s_getc) = stream_getc; B_INTERFACE_ENTRY(s_read) = stream_read; B_INTERFACE_ENTRY(s_write) = stream_write; B_INTERFACE_ENTRY(s_seek) = stream_seek; B_INTERFACE_ENTRY(s_tell) = stream_tell; B_TYPE_CLASS_INTERFACE_END(b_stream, B_TYPE_STREAM) B_TYPE_CLASS_DEFINITION_END(b_file) B_TYPE_DEFINITION_BEGIN(b_file) B_TYPE_ID(0x495a73f6, 0xb8c3, 0x4e17, 0xb5f4, 0x6fc321f67c7b); B_TYPE_EXTENDS(B_TYPE_STREAM); B_TYPE_CLASS(b_file_class); B_TYPE_INSTANCE_PRIVATE(struct b_file_p); B_TYPE_INSTANCE_INIT(file_init); B_TYPE_INSTANCE_FINI(file_fini); B_TYPE_DEFINITION_END(b_file)