#include "posix.h" #include #include #include #include #include #include #include #include #include #include struct b_file { struct b_object base; enum b_file_mode mode; int fd; struct b_path *path; }; static void file_release(struct b_object *obj); static struct b_object_type file_type = { .t_name = "corelib::file", .t_flags = B_OBJECT_FUNDAMENTAL, .t_id = B_OBJECT_TYPE_FILE, .t_instance_size = sizeof(struct b_file), .t_release = file_release, }; static unsigned int b_mode_to_unix_mode(enum b_file_mode mode) { unsigned int result = 0; if (mode & B_FILE_READ_WRITE) { result |= O_RDWR; } else if (mode & B_FILE_READ_ONLY) { result |= O_RDONLY; } else if (mode & B_FILE_WRITE_ONLY) { result |= O_WRONLY; } else { return 0; } if (mode & B_FILE_TRUNCATE) { result |= O_TRUNC; } if (mode & B_FILE_CREATE) { result |= O_CREAT; } if ((mode & B_FILE_CREATE_ONLY) == B_FILE_CREATE_ONLY) { result |= O_EXCL; } return result; } enum b_status 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 == 0) { b_path_release(file_path); return B_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_ERR_NO_MEMORY; } int fd = open(b_path_ptr(abs_path), flags, 0644); if (fd == -1) { b_path_release(abs_path); return b_status_from_errno(errno, B_ERR_IO_FAILURE); } struct b_file *file = (struct b_file *)b_object_type_instantiate(&file_type); if (!file) { close(fd); b_path_release(abs_path); return B_ERR_NO_MEMORY; } file->fd = fd; file->path = abs_path; file->mode = mode; *out = file; return B_SUCCESS; } static void generate_tmp_filename(char *out, size_t len) { static const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" "01234567" "89+=-_."; static const size_t alphabet_len = 67; b_random_ctx *ctx = b_random_global_ctx(); for (size_t i = 0; i < len; i++) { int v = b_random_next_int64(ctx) % alphabet_len; out[i] = alphabet[v]; } out[len - 1] = 0; } enum b_status 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) { generate_tmp_filename(name, sizeof name); snprintf(path, sizeof path, "/tmp/%s", name); struct b_path *rpath = b_path_create_from_cstr(path); enum b_status status = b_file_open( B_DIRECTORY_ROOT, rpath, mode | B_FILE_CREATE_ONLY, out); if (status == B_ERR_NAME_EXISTS) { b_path_release(rpath); continue; } b_path_unlink(rpath); b_path_release(rpath); return status; } } enum b_status 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; enum b_status status = b_file_open( B_DIRECTORY_ROOT, shadow_filepath, mode, &shadow_file); b_path_release(shadow_filepath); if (!B_OK(status)) { return status; } *out = shadow_file; 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]; 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_object *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); }