#include "misc.h" #include "posix.h" #include #include #include #include #include #include #include #include #include #include #include #include struct b_file { struct b_dsref base; enum b_file_mode mode; int fd; struct b_path *path; }; static void file_release(struct b_dsref *obj); static struct b_dsref_type file_type = { .t_name = "corelib::file", .t_flags = B_DSREF_FUNDAMENTAL, .t_id = B_DSREF_TYPE_FILE, .t_instance_size = sizeof(struct b_file), .t_release = file_release, }; #define CHECK_FLAG(v, f) (((v) & (f)) == (f)) 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; } b_result b_file_open( struct b_directory *root, const struct b_path *path, enum b_file_mode mode, struct b_file **out) { struct b_path *file_path = b_path_retain((struct b_path *)path); unsigned int flags = b_mode_to_unix_mode(mode); if (flags == (unsigned int)-1) { b_path_release(file_path); return B_RESULT_ERR(INVALID_ARGUMENT); } struct b_path *root_path = NULL; if (root) { root_path = b_path_retain( (struct b_path *)b_directory_get_path(root)); } else { root_path = b_path_create_cwd(); } const struct b_path *parts[] = { root_path, file_path, }; struct b_path *abs_path = b_path_join(parts, sizeof parts / sizeof parts[0]); b_path_release(root_path); b_path_release(file_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_release(abs_path); return B_RESULT_STATUS( b_status_from_errno(errno, B_ERR_IO_FAILURE)); } struct b_file *file = (struct b_file *)b_dsref_type_instantiate(&file_type); if (!file) { close(fd); b_path_release(abs_path); return B_RESULT_ERR(NO_MEMORY); } file->fd = fd; file->path = abs_path; file->mode = mode; *out = file; return B_RESULT_SUCCESS; } b_result b_file_open_temp(enum b_file_mode mode, struct 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); struct 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_release(rpath); continue; } b_path_unlink(rpath); b_path_release(rpath); return status; } } b_result b_file_open_shadow( struct b_file *original, enum b_file_mode mode, struct b_file **out) { mode |= B_FILE_SHADOW | B_FILE_DELETE_ON_CLOSE | B_FILE_CREATE; struct b_path *dir; b_path_get_directory(original->path, &dir); struct b_string *filename = b_string_create(); b_path_get_filename(original->path, filename); b_string_prepend_cstr(filename, ".~"); struct b_path *shadow_filename = b_path_create_from_cstr(b_string_ptr(filename)); b_string_release(filename); const struct b_path *parts[] = { dir, shadow_filename, }; struct b_path *shadow_filepath = b_path_join(parts, sizeof parts / sizeof parts[0]); b_path_release(dir); b_path_release(shadow_filename); if (b_path_exists(shadow_filepath)) { b_path_unlink(shadow_filepath); } struct b_file *shadow_file; b_result status = b_file_open( B_DIRECTORY_ROOT, shadow_filepath, mode, &shadow_file); b_path_release(shadow_filepath); if (b_result_is_error(status)) { return status; } *out = shadow_file; return B_RESULT_SUCCESS; } static enum b_status stream_close(struct b_stream *stream) { struct b_file *file = stream->s_ptr; b_file_release(file); return B_SUCCESS; } static enum b_status stream_getc(struct b_stream *stream, int *out) { struct b_file *file = stream->s_ptr; char c; size_t nr_read = 0; enum b_status status = b_file_read(file, stream->s_cursor, sizeof c, &c, &nr_read); if (status != B_SUCCESS) { return status; } if (nr_read == 0) { return B_ERR_NO_DATA; } stream->s_cursor += nr_read; *out = c; return B_SUCCESS; } static enum b_status stream_read( struct b_stream *stream, unsigned char *buf, size_t max, size_t *nr_read) { struct b_file *file = stream->s_ptr; enum b_status status = b_file_read(file, stream->s_cursor, max, buf, nr_read); stream->s_cursor += *nr_read; return status; } static enum b_status stream_write( struct b_stream *stream, const unsigned char *buf, size_t count, size_t *nr_written) { struct b_file *file = stream->s_ptr; enum b_status status = b_file_write(file, stream->s_cursor, count, buf, nr_written); stream->s_cursor += *nr_written; return status; } static enum b_status stream_seek( struct 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 *file = stream->s_ptr; enum b_status status = b_file_seek(file, offset, basis); if (!B_OK(status)) { return status; } return b_file_cursor(file, &stream->s_cursor); } enum b_status b_file_open_stream(struct b_file *file, struct b_stream **out) { struct b_stream *stream = malloc(sizeof *stream); if (!stream) { return B_ERR_NO_MEMORY; } memset(stream, 0x0, sizeof *stream); if (file->mode & B_FILE_READ_ONLY) { stream->s_mode |= B_STREAM_READ; } if (file->mode & B_FILE_WRITE_ONLY) { stream->s_mode |= B_STREAM_WRITE; } if (file->mode & B_FILE_BINARY) { stream->s_mode |= B_STREAM_BINARY; } stream->s_ptr = b_file_retain(file); stream->s_close = stream_close; stream->s_getc = stream_getc; stream->s_read = stream_read; stream->s_write = stream_write; stream->s_seek = stream_seek; *out = stream; return B_SUCCESS; } enum b_status b_file_stat(struct b_file *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); } enum b_status b_file_size(struct b_file *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; } enum b_status b_file_cursor(struct b_file *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; } enum b_status b_file_resize(struct b_file *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); } enum b_status b_file_seek( struct b_file *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 == 0) { return B_SUCCESS; } return b_status_from_errno(errno, B_ERR_IO_FAILURE); } enum b_status b_file_swap_shadow(struct b_file *main_file, struct b_file *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; } struct b_path *dir_path; b_path_get_directory(main_file->path, &dir_path); struct b_path *tmp_path = NULL; while (1) { char tmp_name[16]; z__b_io_generate_tmp_filename(tmp_name, sizeof tmp_name); struct b_path *tmp_name_p = b_path_create_from_cstr(tmp_name); const struct b_path *parts[] = { dir_path, tmp_name_p, }; tmp_path = b_path_join(parts, sizeof parts / sizeof parts[0]); b_path_release(tmp_name_p); if (!b_path_exists(tmp_path)) { break; } b_path_release(tmp_path); tmp_path = NULL; } b_path_release(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_release(tmp_path); int fd = main_file->fd; main_file->fd = shadow_file->fd; shadow_file->fd = fd; return B_SUCCESS; } enum b_status b_file_read( struct b_file *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; } enum b_status b_file_write( struct b_file *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; } static void file_release(struct b_dsref *obj) { struct b_file *file = (struct b_file *)obj; close(file->fd); if (file->mode & B_FILE_DELETE_ON_CLOSE) { b_path_unlink(file->path); } b_path_release(file->path); }