#include "misc.h" #include "posix.h" #include #include #include #include #include #include #include #include #include #include enum directory_flags { DIRECTORY_DELETE_ON_CLOSE = 0x01u, }; struct b_directory { struct b_dsref base; enum directory_flags flags; int fd; struct b_path *rel_path; struct b_path *abs_path; }; struct z__b_directory_iterator { FTS *fts; FTSENT *ent; }; static void directory_release(struct b_dsref *obj); static struct b_dsref_type directory_type = { .t_name = "corelib::directory", .t_flags = B_DSREF_FUNDAMENTAL, .t_id = B_DSREF_TYPE_PATH, .t_instance_size = sizeof(struct b_directory), .t_release = directory_release, }; static b_result create_directory(struct b_directory *root, const char *path) { int root_fd = root ? root->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 B_RESULT_SUCCESS; } return b_result_from_errno_with_subfilepath( errno, path, b_directory_get_rel_path_cstr(root), B_ERR_IO_FAILURE); } static b_result create_directory_hierarchy(struct b_directory *root, const char *path) { int root_fd = root->fd; char *path_buf = b_strdup(path); if (!path_buf) { return B_RESULT_ERR(NO_MEMORY); } b_result result = B_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 = b_result_from_errno_with_subfilepath( errno, path_buf, b_directory_get_rel_path_cstr(root), B_ERR_IO_FAILURE); break; } path_buf[i] = '/'; } int err = mkdirat(root_fd, path_buf, 0755); if (err != 0 && errno != EEXIST) { result = b_result_from_errno_with_subfilepath( errno, path_buf, b_directory_get_rel_path_cstr(root), B_ERR_IO_FAILURE); } free(path_buf); return result; } b_result b_directory_open( struct b_directory *root, const struct b_path *path, b_directory_open_flags flags, struct b_directory **out) { enum b_status status = B_SUCCESS; int root_fd = root ? root->fd : AT_FDCWD; const char *path_cstr = b_path_ptr(path); if (root) { while (*path_cstr == '/') { path_cstr++; } } b_result result = B_RESULT_SUCCESS; if ((flags & B_DIRECTORY_OPEN_CREATE_INTERMEDIATE) == B_DIRECTORY_OPEN_CREATE_INTERMEDIATE) { result = create_directory_hierarchy(root, path_cstr); } else if ((flags & B_DIRECTORY_OPEN_CREATE) == B_DIRECTORY_OPEN_CREATE) { result = create_directory(root, path_cstr); } if (b_result_is_error(result)) { return b_result_propagate(result); } int fd = openat(root_fd, path_cstr, O_DIRECTORY); if (fd == -1) { status = b_status_from_errno(errno, B_ERR_IO_FAILURE); return B_RESULT_STATUS(status); } struct b_directory *dir = (struct b_directory *)b_dsref_type_instantiate(&directory_type); struct b_path *cwd = NULL; if (!root) { cwd = b_path_create_cwd(); } const b_path *parts[] = { root ? root->abs_path : cwd, path, }; b_path *new_path = b_path_join(parts, sizeof parts / sizeof parts[0]); if (!new_path) { return B_RESULT_ERR(NO_MEMORY); } if (cwd) { b_path_release(cwd); } dir->abs_path = new_path; dir->rel_path = b_path_duplicate(path); dir->fd = fd; if (flags & B_DIRECTORY_OPEN_DELETE_ON_CLOSE) { dir->flags = DIRECTORY_DELETE_ON_CLOSE; } *out = dir; return B_RESULT_SUCCESS; } b_result b_directory_open_temp(struct b_directory **out) { 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); struct b_directory *dir = NULL; b_result status = b_directory_open( B_DIRECTORY_ROOT, rpath, B_DIRECTORY_OPEN_CREATE, &dir); if (b_error_get_status_code(status) == B_ERR_NAME_EXISTS) { b_path_release(rpath); continue; } if (dir) { dir->flags |= DIRECTORY_DELETE_ON_CLOSE; } b_path_unlink(rpath); b_path_release(rpath); return status; } } const struct b_path *b_directory_get_path(const struct b_directory *dir) { if (!dir) { return NULL; } return dir->abs_path; } const struct b_path *b_directory_get_rel_path(const struct b_directory *dir) { if (!dir) { return NULL; } return dir->rel_path; } const char *b_directory_get_path_cstr(const struct b_directory *dir) { if (!dir) { return NULL; } return b_path_ptr(dir->abs_path); } const char *b_directory_get_rel_path_cstr(const struct b_directory *dir) { if (!dir) { return NULL; } return b_path_ptr(dir->rel_path); } static b_result directory_delete(struct b_directory *dir) { enum b_status status = B_SUCCESS; struct b_directory_iterator it; b_directory_iterator_begin(dir, &it, B_DIRECTORY_ITERATE_PARENT_LAST); while (b_directory_iterator_is_valid(&it)) { status = b_directory_iterator_erase(&it); if (!B_OK(status)) { return B_RESULT_STATUS(status); } } status = b_path_unlink(dir->abs_path); if (!B_OK(status)) { return B_RESULT_STATUS(status); } return B_RESULT_SUCCESS; } b_result b_directory_delete(struct b_directory *dir) { dir->flags |= DIRECTORY_DELETE_ON_CLOSE; /* TODO allow object release functions to return a b_result value */ b_directory_release(dir); return B_RESULT_SUCCESS; } bool b_directory_path_exists( const struct b_directory *root, const struct b_path *path) { const struct b_path *parts[] = { root ? root->abs_path : NULL, path, }; struct b_path *abs_path = b_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return false; } bool result = b_path_exists(abs_path); b_path_release(abs_path); return result; } bool b_directory_path_is_file( const struct b_directory *root, const struct b_path *path) { const struct b_path *parts[] = { root ? root->abs_path : NULL, path, }; struct b_path *abs_path = b_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return false; } bool result = b_path_is_file(abs_path); b_path_release(abs_path); return result; } bool b_directory_path_is_directory( const struct b_directory *root, const struct b_path *path) { const struct b_path *parts[] = { root ? root->abs_path : NULL, path, }; struct b_path *abs_path = b_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return false; } bool result = b_path_is_directory(abs_path); b_path_release(abs_path); return result; } b_result b_directory_path_stat( const struct b_directory *root, const struct b_path *path, struct b_file_info *out) { const struct b_path *parts[] = { root ? root->abs_path : NULL, path, }; struct b_path *abs_path = b_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return B_RESULT_ERR(NO_MEMORY); } enum b_status status = b_path_stat(abs_path, out); b_path_release(abs_path); return B_RESULT_STATUS(status); } b_result b_directory_path_unlink( const struct b_directory *root, const struct b_path *path) { const struct b_path *parts[] = { root ? root->abs_path : NULL, path, }; struct b_path *abs_path = b_path_join(parts, sizeof parts / sizeof parts[0]); if (!abs_path) { return B_RESULT_ERR(NO_MEMORY); } enum b_status status = b_path_unlink(abs_path); b_path_release(abs_path); return B_RESULT_STATUS(status); } static int ftsent_compare(const FTSENT **one, const FTSENT **two) { return (strcmp((*one)->fts_name, (*two)->fts_name)); } static void update_iterator_data(struct b_directory_iterator *it) { struct z__b_directory_iterator *it_data = it->_z; if (it->filepath) { b_path_release((struct b_path *)it->filepath); it->filepath = NULL; } FTSENT *ent = it_data->ent; struct b_path *path = b_path_create_from_cstr( ent->fts_path + b_path_length(it->root->abs_path) + 1); it->filename = ent->fts_name; it->filepath = path; memset(&it->info, 0x0, sizeof it->info); it->info.length = ent->fts_statp->st_size; if (S_ISREG(ent->fts_statp->st_mode)) { it->info.attrib |= B_FILE_ATTRIB_NORMAL; } if (S_ISDIR(ent->fts_statp->st_mode)) { it->info.attrib |= B_FILE_ATTRIB_DIRECTORY; } if (S_ISBLK(ent->fts_statp->st_mode)) { it->info.attrib |= B_FILE_ATTRIB_BLOCK_DEVICE; } if (S_ISCHR(ent->fts_statp->st_mode)) { it->info.attrib |= B_FILE_ATTRIB_CHAR_DEVICE; } if (S_ISLNK(ent->fts_statp->st_mode)) { it->info.attrib |= B_FILE_ATTRIB_SYMLINK; } } static void cleanup_iterator(struct b_directory_iterator *it) { if (it->filepath) { b_path_release((struct b_path *)it->filepath); it->filepath = NULL; } struct z__b_directory_iterator *it_data = it->_z; memset(it, 0x0, sizeof *it); if (!it_data) { return; } if (it_data->fts) { fts_close(it_data->fts); } free(it_data); } int b_directory_iterator_begin( struct b_directory *directory, struct b_directory_iterator *it, enum b_directory_iterator_flags flags) { memset(it, 0x0, sizeof *it); it->flags = flags; it->root = directory; struct z__b_directory_iterator *it_data = malloc(sizeof *it_data); if (!it_data) { return -1; } memset(it_data, 0x0, sizeof *it_data); it->_z = it_data; int fts_flags = FTS_COMFOLLOW | FTS_NOCHDIR; const char *path_list[] = { b_path_ptr(directory->abs_path), NULL, }; it_data->fts = fts_open((char *const *)path_list, fts_flags, ftsent_compare); bool done = false; while (!done) { it_data->ent = fts_read(it_data->fts); if (!it_data->ent) { cleanup_iterator(it); return -1; } if (it_data->ent->fts_level == 0) { continue; } switch (it_data->ent->fts_info) { case FTS_DOT: continue; case FTS_F: done = true; break; case FTS_D: if (it->flags & B_DIRECTORY_ITERATE_PARENT_LAST) { continue; } done = true; break; case FTS_DP: if (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST) { continue; } done = true; break; default: done = true; break; } } update_iterator_data(it); return 0; } bool b_directory_iterator_next(struct b_directory_iterator *it) { struct z__b_directory_iterator *it_data = it->_z; if (!it_data || !it_data->fts) { return false; } bool done = false; while (!done) { it_data->ent = fts_read(it_data->fts); if (!it_data->ent) { cleanup_iterator(it); return false; } if (it_data->ent->fts_level == 0) { continue; } switch (it_data->ent->fts_info) { case FTS_DOT: continue; case FTS_F: done = true; break; case FTS_D: if (it->flags & B_DIRECTORY_ITERATE_PARENT_LAST) { continue; } done = true; break; case FTS_DP: if (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST) { continue; } done = true; break; default: done = true; break; } } update_iterator_data(it); return true; } enum b_status b_directory_iterator_erase(struct b_directory_iterator *it) { b_result result = b_directory_path_unlink(it->root, it->filepath); if (b_result_is_error(result)) { enum b_status status = b_error_get_status_code(result); b_error_release(result); return status; } b_directory_iterator_next(it); return B_SUCCESS; } bool b_directory_iterator_is_valid(const struct b_directory_iterator *it) { if (!it->_z) { return false; } if (!it->_z->ent) { return false; } return true; } static void directory_release(struct b_dsref *obj) { struct b_directory *dir = B_DIRECTORY(obj); close(dir->fd); if (dir->flags & DIRECTORY_DELETE_ON_CLOSE) { directory_delete(dir); } b_path_release(dir->abs_path); }