#include #include #include // ============================= // // FILESYSTEM DRIVER REGISTERING // // ============================= // typedef struct fs_driver { const char* name; fs_driver_ops_t *ops; struct fs_driver *next; } fs_driver_t; fs_driver_t *drivers = 0; void register_fs_driver(const char* name, fs_driver_ops_t *ops) { fs_driver_t *d = (fs_driver_t*)malloc(sizeof(fs_driver_t)); ASSERT(d != 0); // should we fail in a more graceful manner ? TODO d->name = name; d->ops = ops; d->next = drivers; drivers = d; } // ================================== // // CREATING AND DELETING FILE SYSTEMS // // ================================== // fs_t *make_fs(const char* drv_name, fs_handle_t *source, const char* opts) { // Look for driver fs_driver_t *d = 0; for(fs_driver_t *i = drivers; i != 0; i = i->next) { if (drv_name != 0 && strcmp(i->name, drv_name) == 0) d = i; if (drv_name == 0 && source != 0 && i->ops->detect && i->ops->detect(source)) d = i; if (d != 0) break; } if (d == 0) return 0; // driver not found // Open file system fs_t *fs = (fs_t*)malloc(sizeof(fs_t)); if (fs == 0) return 0; fs->refs = 1; fs->root.refs = 1; // root node is never disposed of (done by fs->shutdown) fs->root.fs = fs; fs->root.parent = 0; fs->root.children = 0; if (d->ops->make(source, opts, fs)) { return fs; } else { free(fs); return 0; } } bool fs_add_source(fs_t *fs, fs_handle_t *source, const char* opts) { return fs->ops->add_source && fs->ops->add_source(fs->data, source, opts); } void ref_fs(fs_t *fs) { fs->refs++; } void unref_fs(fs_t *fs) { fs->refs--; if (fs->refs == 0) { // don't unref root node, don't call dispose on it // (done by fs->shutdown) fs->ops->shutdown(fs->data); free(fs); } } // ====================================================== // // WALKING IN THE FILE SYSTEM CREATING AND DELETING NODES // void ref_fs_node(fs_node_t *n) { mutex_lock(&n->lock); n->refs++; mutex_unlock(&n->lock); } void unref_fs_node(fs_node_t *n) { mutex_lock(&n->lock); n->refs--; if (n->refs == 0) { ASSERT(n != &n->fs->root); ASSERT(n->parent != 0); ASSERT(n->name != 0); mutex_lock(&n->parent->lock); hashtbl_remove(n->parent->children, n->name); if (n->ops->dispose) n->ops->dispose(n->data); mutex_unlock(&n->parent->lock); unref_fs_node(n->parent); unref_fs(n->fs); if (n->children != 0) delete_hashtbl(n->children); free(n->name); free(n); } else { mutex_unlock(&n->lock); } } fs_node_t* fs_walk_one(fs_node_t* from, const char* file) { mutex_lock(&from->lock); if (from->children != 0) { fs_node_t *n = (fs_node_t*)hashtbl_find(from->children, file); if (n != 0) { ref_fs_node(n); mutex_unlock(&from->lock); return n; } } bool walk_ok = false, add_ok = false; fs_node_t *n = (fs_node_t*)malloc(sizeof(fs_node_t)); if (n == 0) goto error; n->fs = from->fs; n->refs = 1; n->lock = MUTEX_UNLOCKED; n->parent = from; n->children = 0; n->name = strdup(file); if (n->name == 0) goto error; walk_ok = from->ops->walk && from->ops->walk(from->data, file, n); if (!walk_ok) goto error; if (from->children == 0) { from->children = create_hashtbl(str_key_eq_fun, str_hash_fun, 0); if (from->children == 0) goto error; } add_ok = hashtbl_add(from->children, n->name, n); if (!add_ok) goto error; mutex_unlock(&from->lock); ref_fs_node(n->parent); ref_fs(n->fs); return n; error: if (walk_ok) n->ops->dispose(n->data); if (n != 0 && n->name != 0) free(n->name); if (n != 0) free(n); mutex_unlock(&from->lock); return 0; } fs_node_t* fs_walk_path(fs_node_t* from, const char* path) { if (*path == '/') path++; fs_node_t *n = from; ref_fs_node(n); while (n && (*path)) { if (*path == '/') { // we don't want multiple slashes, so fail unref_fs_node(n); return 0; } const char* d = strchr(path, '/'); if (d == 0) { fs_node_t *n2 = fs_walk_one(n, path); unref_fs_node(n); return n2; } else { size_t nlen = d - path; if (nlen >= DIR_MAX - 1) { unref_fs_node(n); return 0; // sorry, path item too long } char name_buf[DIR_MAX]; strncpy(name_buf, path, nlen); name_buf[nlen] = 0; fs_node_t *n2 = fs_walk_one(n, name_buf); unref_fs_node(n); n = n2; path = d + 1; } } return n; } fs_node_t* fs_walk_path_except_last(fs_node_t* from, const char* path, char* last_file_buf) { // This function does NOT walk into the last component of the path (it may not even exist), // instead it isolates it in the buffer last_file // This buffer is expected to be of size DIR_MAX (defined in fs.h) if (*path == '/') path++; if (*path == 0) return 0; // no last component ! fs_node_t *n = from; ref_fs_node(n); while (n && (*path)) { if (*path == '/') { // we don't want multiple slashes, so fail unref_fs_node(n); return 0; } const char* d = strchr(path, '/'); if (d == 0) { if (strlen(path) >= DIR_MAX - 1) { unref_fs_node(n); return 0; // sorry, path item too long } strcpy(last_file_buf, path); return n; } else { size_t nlen = d - path; if (nlen >= DIR_MAX - 1) { unref_fs_node(n); return 0; // sorry, path item too long } strncpy(last_file_buf, path, nlen); last_file_buf[nlen] = 0; if (*(d+1) == 0) { // trailing slash ! return n; } else { fs_node_t *n2 = fs_walk_one(n, last_file_buf); unref_fs_node(n); n = n2; path = d + 1; } } } return n; } // =========================== // // DOING THINGS IN FILESYSTEMS // bool fs_create(fs_t *fs, const char* file, int type) { char name[DIR_MAX]; fs_node_t *n = fs_walk_path_except_last(&fs->root, file, name); if (n == 0) return false; mutex_lock(&n->lock); bool ret = n->ops->create && n->ops->create(n->data, name, type); mutex_unlock(&n->lock); unref_fs_node(n); return ret; } bool fs_delete(fs_t *fs, const char* file) { char name[DIR_MAX]; fs_node_t* n = fs_walk_path_except_last(&fs->root, file, name); if (n == 0) return false; if (n->children != 0) { fs_node_t* x = (fs_node_t*)hashtbl_find(n->children, name); if (x != 0) return false; } mutex_lock(&n->lock); bool ret = n->ops->delete && n->ops->delete(n->data, name); mutex_unlock(&n->lock); unref_fs_node(n); return ret; } bool fs_move(fs_t *fs, const char* from, const char* to) { char old_name[DIR_MAX]; fs_node_t *old_parent = fs_walk_path_except_last(&fs->root, from, old_name); if (old_parent == 0) return false; char new_name[DIR_MAX]; fs_node_t *new_parent = fs_walk_path_except_last(&fs->root, to, new_name); if (new_parent == 0) { unref_fs_node(old_parent); return false; } bool ret = false; if (!old_parent->ops->move) goto end; mutex_lock(&old_parent->lock); mutex_lock(&new_parent->lock); fs_node_t *the_node = (old_parent->children != 0 ? (fs_node_t*)hashtbl_find(old_parent->children, old_name) : 0); if (the_node) { mutex_lock(&the_node->lock); char* new_name_dup = strdup(new_name); if (new_name_dup == 0) goto unlock_end; // we failed bool add_ok = hashtbl_add(new_parent->children, new_name_dup, the_node); if (!add_ok) { free(new_name_dup); goto unlock_end; } ret = old_parent->ops->move(old_parent->data, old_name, new_parent, new_name); if (ret) { // adjust node parameters hashtbl_remove(old_parent->children, old_name); free(the_node->name); the_node->name = new_name_dup; the_node->parent = new_parent; } else { hashtbl_remove(new_parent->children, new_name_dup); free(new_name_dup); } unlock_end: mutex_unlock(&the_node->lock); } else { ret = old_parent->ops->move(old_parent->data, old_name, new_parent, new_name); } mutex_unlock(&old_parent->lock); mutex_unlock(&new_parent->lock); end: unref_fs_node(old_parent); unref_fs_node(new_parent); return ret; } bool fs_stat(fs_t *fs, const char* file, stat_t *st) { fs_node_t* n = fs_walk_path(&fs->root, file); if (n == 0) return false; mutex_lock(&n->lock); bool ret = n->ops->stat && n->ops->stat(n->data, st); mutex_unlock(&n->lock); unref_fs_node(n); return ret; } int fs_ioctl(fs_t *fs, const char* file, int command, void* data) { fs_node_t* n = fs_walk_path(&fs->root, file); if (n == 0) return false; mutex_lock(&n->lock); int ret = (n->ops->ioctl ? n->ops->ioctl(n->data, command, data) : -1); mutex_unlock(&n->lock); unref_fs_node(n); return ret; } // =================== // // OPERATIONS ON FILES // // =================== // fs_handle_t* fs_open(fs_t *fs, const char* file, int mode) { fs_node_t *n = fs_walk_path(&fs->root, file); if (n == 0 && (mode & FM_CREATE)) { if (fs_create(fs, file, FT_REGULAR)) { n = fs_walk_path(&fs->root, file); } } if (n == 0) return 0; mutex_lock(&n->lock); mode &= ~FM_CREATE; fs_handle_t *h = (fs_handle_t*)malloc(sizeof(fs_handle_t)); if (h == 0) goto error; h->refs = 1; h->lock = MUTEX_UNLOCKED; h->fs = fs; h->node = n; bool open_ok = n->ops->open(n->data, mode, h); if (!open_ok) goto error; // our reference to node n is transferred to the file handle mutex_unlock(&n->lock); ref_fs(fs); return h; error: mutex_unlock(&n->lock); unref_fs_node(n); if (h != 0) free(h); return 0; } void ref_file(fs_handle_t *file) { mutex_lock(&file->lock); file->refs++; mutex_unlock(&file->lock); } void unref_file(fs_handle_t *file) { mutex_lock(&file->lock); file->refs--; if (file->refs == 0) { file->ops->close(file->data); unref_fs_node(file->node); unref_fs(file->fs); free(file); } else { mutex_unlock(&file->lock); } } int file_get_mode(fs_handle_t *f) { return f->mode; } size_t file_read(fs_handle_t *f, size_t offset, size_t len, char* buf) { if (!(f->mode & FM_READ)) return 0; if (f->ops->read == 0) return 0; mutex_lock(&f->lock); size_t ret = f->ops->read(f->data, offset, len, buf); mutex_unlock(&f->lock); return ret; } size_t file_write(fs_handle_t *f, size_t offset, size_t len, const char* buf) { if (!(f->mode & FM_WRITE)) return 0; if (f->ops->write == 0) return 0; mutex_lock(&f->lock); size_t ret = f->ops->write(f->data, offset, len, buf); mutex_unlock(&f->lock); return ret; } bool file_stat(fs_handle_t *f, stat_t *st) { mutex_lock(&f->node->lock); bool ret = f->node->ops->stat && f->node->ops->stat(f->node->data, st); mutex_unlock(&f->node->lock); return ret; } bool file_readdir(fs_handle_t *f, dirent_t *d) { if (!(f->mode & FM_READDIR)) return 0; mutex_lock(&f->lock); bool ret = f->ops->readdir && f->ops->readdir(f->data, d); mutex_unlock(&f->lock); return ret; } /* vim: set ts=4 sw=4 tw=0 noet :*/