#include "misc.h" #include "posix.h" #include #include #include #include #include #include #include #include #include #include /*** PRIVATE DATA *************************************************************/ enum directory_flags { DIRECTORY_DELETE_ON_CLOSE = 0x01u, }; struct fx_directory_p { enum directory_flags d_flags; int d_fd; fx_path *d_path_rel; fx_path *d_path_abs; }; struct fx_directory_iterator_p { struct fx_directory_p *_p; FTS *fts; FTSENT *ent; fx_directory_iterator_flags flags; fx_directory *root; fx_directory_entry entry; }; /*** PRIVATE FUNCTIONS ********************************************************/ static const fx_path *directory_get_path(const struct fx_directory_p *dir) { return dir->d_path_abs; } static const fx_path *directory_get_rel_path(const struct fx_directory_p *dir) { return dir->d_path_rel; } static const char *directory_get_path_cstr(const struct fx_directory_p *dir) { return fx_path_ptr(dir->d_path_abs); } static const char *directory_get_rel_path_cstr(const struct fx_directory_p *dir) { return fx_path_ptr(dir->d_path_rel); } static fx_result directory_delete(fx_directory *dir, struct fx_directory_p *dir_p) { enum fx_status status = FX_SUCCESS; fx_iterator *it = fx_directory_begin(dir, FX_DIRECTORY_ITERATE_PARENT_LAST); while (FX_OK(fx_iterator_get_status(it))) { fx_iterator_erase(it); } status = fx_iterator_get_status(it); if (!FX_OK(status) && status != FX_ERR_NO_DATA) { return FX_RESULT_STATUS(status); } status = fx_path_unlink(dir_p->d_path_abs); if (!FX_OK(status)) { return FX_RESULT_STATUS(status); } return FX_RESULT_SUCCESS; } static bool directory_path_exists( const struct fx_directory_p *root, const fx_path *path) { const fx_path *parts[] = { root ? root->d_path_abs : NULL, path, }; fx_path *abs_path = fx_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return false; } bool result = fx_path_exists(abs_path); fx_path_unref(abs_path); return result; } static bool directory_path_is_file( const struct fx_directory_p *root, const fx_path *path) { const fx_path *parts[] = { root ? root->d_path_abs : NULL, path, }; fx_path *abs_path = fx_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return false; } bool result = fx_path_is_file(abs_path); fx_path_unref(abs_path); return result; } static bool directory_path_is_directory( const struct fx_directory_p *root, const fx_path *path) { const fx_path *parts[] = { root ? root->d_path_abs : NULL, path, }; fx_path *abs_path = fx_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return false; } bool result = fx_path_is_directory(abs_path); fx_path_unref(abs_path); return result; } static fx_result directory_path_stat( const struct fx_directory_p *root, const fx_path *path, struct fx_file_info *out) { const fx_path *parts[] = { root ? root->d_path_abs : NULL, path, }; fx_path *abs_path = fx_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return FX_RESULT_ERR(NO_MEMORY); } enum fx_status status = fx_path_stat(abs_path, out); fx_path_unref(abs_path); return FX_RESULT_STATUS(status); } static fx_result directory_path_unlink( const struct fx_directory_p *root, const fx_path *path) { const fx_path *parts[] = { root ? root->d_path_abs : NULL, path, }; fx_path *abs_path = fx_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return FX_RESULT_ERR(NO_MEMORY); } enum fx_status status = fx_path_unlink(abs_path); fx_path_unref(abs_path); return FX_RESULT_STATUS(status); } static fx_result create_directory(struct fx_directory_p *root, const char *path) { int root_fd = root ? root->d_fd : -1; int err; if (root_fd == -1) { err = mkdir(path, 0755); } else { err = mkdirat(root_fd, path, 0755); } if (err == 0 || errno == EEXIST) { return FX_RESULT_SUCCESS; } return fx_result_from_errno_with_subfilepath( errno, path, directory_get_rel_path_cstr(root), FX_ERR_IO_FAILURE); } static fx_result create_directory_hierarchy( struct fx_directory_p *root, const char *path) { int root_fd = root ? root->d_fd : AT_FDCWD; char *path_buf = fx_strdup(path); if (!path_buf) { return FX_RESULT_ERR(NO_MEMORY); } fx_result result = FX_RESULT_SUCCESS; for (size_t i = 0; path_buf[i]; i++) { if (path_buf[i] != '/') { continue; } path_buf[i] = 0; int err = mkdirat(root_fd, path_buf, 0755); if (err != 0 && errno != EEXIST) { result = fx_result_from_errno_with_subfilepath( errno, path_buf, directory_get_rel_path_cstr(root), FX_ERR_IO_FAILURE); break; } path_buf[i] = '/'; } int err = mkdirat(root_fd, path_buf, 0755); if (err != 0 && errno != EEXIST) { result = fx_result_from_errno_with_subfilepath( errno, path_buf, directory_get_rel_path_cstr(root), FX_ERR_IO_FAILURE); } free(path_buf); return result; } static fx_result directory_open( struct fx_directory_p *root, const fx_path *path, fx_directory_open_flags flags, fx_directory **out) { enum fx_status status = FX_SUCCESS; int root_fd = root ? root->d_fd : AT_FDCWD; const char *path_cstr = fx_path_ptr(path); if (root) { while (*path_cstr == '/') { path_cstr++; } } fx_result result = FX_RESULT_SUCCESS; if ((flags & FX_DIRECTORY_OPEN_CREATE_INTERMEDIATE) == FX_DIRECTORY_OPEN_CREATE_INTERMEDIATE) { result = create_directory_hierarchy(root, path_cstr); } else if ((flags & FX_DIRECTORY_OPEN_CREATE) == FX_DIRECTORY_OPEN_CREATE) { result = create_directory(root, path_cstr); } if (fx_result_is_error(result)) { return fx_result_propagate(result); } int fd = openat(root_fd, path_cstr, O_DIRECTORY); if (fd == -1) { status = fx_status_from_errno(errno, FX_ERR_IO_FAILURE); return FX_RESULT_STATUS(status); } fx_directory *dir = fx_object_create(FX_TYPE_DIRECTORY); fx_path *cwd = NULL; struct fx_directory_p *p = fx_object_get_private(dir, FX_TYPE_DIRECTORY); if (!root) { cwd = fx_path_create_cwd(); } const fx_path *parts[] = { root ? root->d_path_abs : cwd, path, }; fx_path *new_path = fx_path_join(parts, sizeof parts / sizeof parts[0]); if (!new_path) { return FX_RESULT_ERR(NO_MEMORY); } if (cwd) { fx_path_unref(cwd); } p->d_path_abs = new_path; p->d_path_rel = fx_path_duplicate(path); p->d_fd = fd; if (flags & FX_DIRECTORY_OPEN_DELETE_ON_CLOSE) { p->d_flags = DIRECTORY_DELETE_ON_CLOSE; } *out = dir; return FX_RESULT_SUCCESS; } /*** PUBLIC FUNCTIONS *********************************************************/ fx_result fx_directory_open( fx_directory *root, const fx_path *path, fx_directory_open_flags flags, fx_directory **out) { FX_CLASS_DISPATCH_STATIC( FX_TYPE_DIRECTORY, directory_open, root, path, flags, out); } fx_result fx_directory_open_temp(fx_directory **out) { 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_directory *dir = NULL; fx_result status = fx_directory_open( FX_DIRECTORY_ROOT, rpath, FX_DIRECTORY_OPEN_CREATE, &dir); struct fx_directory_p *p = fx_object_get_private(dir, FX_TYPE_DIRECTORY); if (fx_error_get_status_code(status) == FX_ERR_NAME_EXISTS) { fx_path_unref(rpath); continue; } if (dir) { p->d_flags |= DIRECTORY_DELETE_ON_CLOSE; } fx_path_unlink(rpath); fx_path_unref(rpath); return status; } } const fx_path *fx_directory_get_path(const fx_directory *dir) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DIRECTORY, directory_get_path, dir); } const fx_path *fx_directory_get_rel_path(const fx_directory *dir) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DIRECTORY, directory_get_rel_path, dir); } const char *fx_directory_get_path_cstr(const fx_directory *dir) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DIRECTORY, directory_get_path_cstr, dir); } const char *fx_directory_get_rel_path_cstr(const fx_directory *dir) { FX_CLASS_DISPATCH_STATIC_0( FX_TYPE_DIRECTORY, directory_get_rel_path_cstr, dir); } fx_result fx_directory_delete(fx_directory *dir) { struct fx_directory_p *p = fx_object_get_private(dir, FX_TYPE_DIRECTORY); p->d_flags |= DIRECTORY_DELETE_ON_CLOSE; /* TODO allow object release functions to return a fx_result value */ fx_directory_unref(dir); return FX_RESULT_SUCCESS; } bool fx_directory_path_exists(const fx_directory *root, const fx_path *path) { FX_CLASS_DISPATCH_STATIC(FX_TYPE_DIRECTORY, directory_path_exists, root, path); } bool fx_directory_path_is_file(const fx_directory *root, const fx_path *path) { FX_CLASS_DISPATCH_STATIC( FX_TYPE_DIRECTORY, directory_path_is_file, root, path); } bool fx_directory_path_is_directory(const fx_directory *root, const fx_path *path) { FX_CLASS_DISPATCH_STATIC( FX_TYPE_DIRECTORY, directory_path_is_directory, root, path); } fx_result fx_directory_path_stat( const fx_directory *root, const fx_path *path, struct fx_file_info *out) { FX_CLASS_DISPATCH_STATIC( FX_TYPE_DIRECTORY, directory_path_stat, root, path, out); } fx_result fx_directory_path_unlink(const fx_directory *root, const fx_path *path) { FX_CLASS_DISPATCH_STATIC(FX_TYPE_DIRECTORY, directory_path_unlink, root, path); } /*** VIRTUAL FUNCTIONS ********************************************************/ static void directory_init(fx_object *obj, void *priv) { struct fx_directory_p *dir = priv; } static void directory_fini(fx_object *obj, void *priv) { struct fx_directory_p *dir = priv; close(dir->d_fd); if (dir->d_flags & DIRECTORY_DELETE_ON_CLOSE) { directory_delete(obj, dir); } fx_path_unref(dir->d_path_abs); } /*** ITERATOR FUNCTIONS *******************************************************/ static int ftsent_compare(const FTSENT **one, const FTSENT **two) { return (strcmp((*one)->fts_name, (*two)->fts_name)); } static void update_iterator_data(struct fx_directory_iterator_p *it) { if (it->entry.filepath) { fx_path_unref((fx_path *)it->entry.filepath); it->entry.filepath = NULL; } FTSENT *ent = it->ent; fx_path *path = fx_path_create_from_cstr( ent->fts_path + fx_path_length(it->_p->d_path_abs) + 1); it->entry.filename = ent->fts_name; it->entry.filepath = path; memset(&it->entry.info, 0x0, sizeof it->entry.info); it->entry.info.length = ent->fts_statp->st_size; if (S_ISREG(ent->fts_statp->st_mode)) { it->entry.info.attrib |= FX_FILE_ATTRIB_NORMAL; } if (S_ISDIR(ent->fts_statp->st_mode)) { it->entry.info.attrib |= FX_FILE_ATTRIB_DIRECTORY; } if (S_ISBLK(ent->fts_statp->st_mode)) { it->entry.info.attrib |= FX_FILE_ATTRIB_BLOCK_DEVICE; } if (S_ISCHR(ent->fts_statp->st_mode)) { it->entry.info.attrib |= FX_FILE_ATTRIB_CHAR_DEVICE; } if (S_ISLNK(ent->fts_statp->st_mode)) { it->entry.info.attrib |= FX_FILE_ATTRIB_SYMLINK; } } static void iterator_fini(fx_object *obj, void *priv) { struct fx_directory_iterator_p *it = priv; if (it->entry.filepath) { fx_path_unref((fx_path *)it->entry.filepath); it->entry.filepath = NULL; } if (it->fts) { fts_close(it->fts); } } fx_iterator *fx_directory_begin( fx_directory *directory, enum fx_directory_iterator_flags flags) { fx_iterator *it_obj = fx_object_create(FX_TYPE_DIRECTORY_ITERATOR); struct fx_directory_iterator_p *it = fx_object_get_private(it_obj, FX_TYPE_DIRECTORY_ITERATOR); it->flags = flags; it->root = directory; it->_p = fx_object_get_private(directory, FX_TYPE_DIRECTORY); int fts_flags = FTS_COMFOLLOW | FTS_NOCHDIR; const char *path_list[] = { fx_path_ptr(it->_p->d_path_abs), NULL, }; it->fts = fts_open((char *const *)path_list, fts_flags, ftsent_compare); bool done = false; while (!done) { it->ent = fts_read(it->fts); if (!it->ent) { fx_iterator_set_status(it_obj, FX_ERR_NO_DATA); return it_obj; } if (it->ent->fts_level == 0) { continue; } switch (it->ent->fts_info) { case FTS_DOT: continue; case FTS_F: done = true; break; case FTS_D: if (it->flags & FX_DIRECTORY_ITERATE_PARENT_LAST) { continue; } done = true; break; case FTS_DP: if (it->flags & FX_DIRECTORY_ITERATE_PARENT_FIRST) { continue; } done = true; break; default: done = true; break; } } update_iterator_data(it); return it_obj; } static fx_iterator *iterator_begin(fx_object *obj) { return fx_directory_begin(obj, FX_DIRECTORY_ITERATE_PARENT_FIRST); } static const fx_iterator *iterator_cbegin(const fx_object *obj) { return fx_directory_begin((fx_object *)obj, FX_DIRECTORY_ITERATE_PARENT_FIRST); } static enum fx_status iterator_move_next(const fx_iterator *obj) { struct fx_directory_iterator_p *it = fx_object_get_private(obj, FX_TYPE_DIRECTORY_ITERATOR); if (!it || !it->fts) { return FX_ERR_NO_DATA; } bool done = false; while (!done) { it->ent = fts_read(it->fts); if (!it->ent) { return FX_ERR_NO_DATA; } if (it->ent->fts_level == 0) { continue; } switch (it->ent->fts_info) { case FTS_DOT: continue; case FTS_F: done = true; break; case FTS_D: if (it->flags & FX_DIRECTORY_ITERATE_PARENT_LAST) { continue; } done = true; break; case FTS_DP: if (it->flags & FX_DIRECTORY_ITERATE_PARENT_FIRST) { continue; } done = true; break; default: done = true; break; } } update_iterator_data(it); return FX_SUCCESS; } static enum fx_status iterator_erase(fx_iterator *obj) { struct fx_directory_iterator_p *it = fx_object_get_private(obj, FX_TYPE_DIRECTORY_ITERATOR); fx_result result = fx_directory_path_unlink(it->root, it->entry.filepath); if (fx_result_is_error(result)) { enum fx_status status = fx_error_get_status_code(result); fx_error_discard(result); return status; } return iterator_move_next(obj); } static fx_iterator_value iterator_get_value(fx_iterator *obj) { struct fx_directory_iterator_p *it = fx_object_get_private(obj, FX_TYPE_DIRECTORY_ITERATOR); return FX_ITERATOR_VALUE_PTR(&it->entry); } static const fx_iterator_value iterator_get_cvalue(const fx_iterator *obj) { struct fx_directory_iterator_p *it = fx_object_get_private(obj, FX_TYPE_DIRECTORY_ITERATOR); return FX_ITERATOR_VALUE_CPTR(&it->entry); } /*** CLASS DEFINITION *********************************************************/ // ---- fx_directory DEFINITION FX_TYPE_CLASS_DEFINITION_BEGIN(fx_directory) 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_iterable, FX_TYPE_ITERABLE) FX_INTERFACE_ENTRY(it_begin) = iterator_begin; FX_INTERFACE_ENTRY(it_cbegin) = iterator_cbegin; FX_TYPE_CLASS_INTERFACE_END(fx_iterable, FX_TYPE_ITERABLE) FX_TYPE_CLASS_DEFINITION_END(fx_directory) FX_TYPE_DEFINITION_BEGIN(fx_directory) FX_TYPE_ID(0x10d36546, 0x7f96, 0x464b, 0xbc4d, 0xe504b283fa45); FX_TYPE_CLASS(fx_directory_class); FX_TYPE_IMPLEMENTS(FX_TYPE_ITERABLE); FX_TYPE_INSTANCE_PRIVATE(struct fx_directory_p); FX_TYPE_INSTANCE_INIT(directory_init); FX_TYPE_INSTANCE_FINI(directory_fini); FX_TYPE_DEFINITION_END(fx_directory) // ---- fx_directory_iterator DEFINITION FX_TYPE_CLASS_DEFINITION_BEGIN(fx_directory_iterator) 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_iterator, FX_TYPE_ITERATOR) FX_INTERFACE_ENTRY(it_move_next) = iterator_move_next; FX_INTERFACE_ENTRY(it_erase) = iterator_erase; FX_INTERFACE_ENTRY(it_get_value) = iterator_get_value; FX_INTERFACE_ENTRY(it_get_cvalue) = iterator_get_cvalue; FX_TYPE_CLASS_INTERFACE_END(fx_iterator, FX_TYPE_ITERATOR) FX_TYPE_CLASS_DEFINITION_END(fx_directory_iterator) FX_TYPE_DEFINITION_BEGIN(fx_directory_iterator) FX_TYPE_ID(0xc707fce6, 0xc895, 0x4925, 0x8700, 0xa60641dee0cc); FX_TYPE_EXTENDS(FX_TYPE_ITERATOR); FX_TYPE_CLASS(fx_directory_iterator_class); FX_TYPE_INSTANCE_PRIVATE(struct fx_directory_iterator_p); FX_TYPE_DEFINITION_END(fx_directory_iterator)