/* gsextfs.cxx */ #include "gzextdef.h" #include #include #include #include #include #include extern "C" { #include #include } #ifndef HAVE_EXT2FS_DIRENT_NAME_LEN static inline int ext2fs_dirent_name_len(const struct ext2_dir_entry *entry) { return entry->name_len & 0xff; } #endif #include "gzextio.h" #include #include #include using namespace std; typedef list stringlist; typedef map pathcache; typedef map inodecache; typedef map dircache; struct file_state { pthread_mutex_t lock; }; typedef map filemap; static struct options { const char* filename; int show_help; } options; struct gzfs_priv { char* filename; ext2_filsys fs; pthread_mutex_t path_lock; pathcache path_cache; pthread_mutex_t inode_lock; inodecache inode_cache; pthread_mutex_t dir_lock; dircache dir_cache; pthread_mutex_t file_lock; filemap file_table; }; #define GZFS_PRIV(p) ((struct gzfs_priv*)(p->private_data)) /*** ext2 helpers ***/ static bool lookup_path(struct gzfs_priv* priv, const char* pathname, ext2_ino_t& inum) { const char* p; const char* q; ext2_ino_t dir; ext2_ino_t subdir; if (!*pathname) { inum = EXT2_ROOT_INO; return true; } pthread_mutex_lock(&priv->path_lock); pathcache::iterator itr = priv->path_cache.find(pathname); if (itr != priv->path_cache.end()) { inum = itr->second; pthread_mutex_unlock(&priv->path_lock); return true; } dir = EXT2_ROOT_INO; p = pathname; q = strchr(p, '/'); while (q) { if (ext2fs_lookup(priv->fs, dir, p, q - p, NULL, &subdir) != 0) { pthread_mutex_unlock(&priv->path_lock); return false; } dir = subdir; p = q + 1; q = strchr(p, '/'); } if (ext2fs_lookup(priv->fs, dir, p, strlen(p), NULL, &inum) != 0) { pthread_mutex_unlock(&priv->path_lock); return false; } priv->path_cache[pathname] = inum; pthread_mutex_unlock(&priv->path_lock); return true; } static bool lookup_inode(struct gzfs_priv* priv, ext2_ino_t inum, ext2_inode& inode) { pthread_mutex_lock(&priv->inode_lock); inodecache::iterator itr = priv->inode_cache.find(inum); if (itr != priv->inode_cache.end()) { inode = itr->second; pthread_mutex_unlock(&priv->inode_lock); return true; } if (ext2fs_read_inode(priv->fs, inum, &inode) != 0) { pthread_mutex_unlock(&priv->inode_lock); return false; } priv->inode_cache[inum] = inode; pthread_mutex_unlock(&priv->inode_lock); return true; } static int dir_iterator(struct ext2_dir_entry* dirent, int offset, int blocksize, char* buf, void* priv) { stringlist* filenames = (stringlist*)priv; filenames->push_back(string(dirent->name, ext2fs_dirent_name_len(dirent))); return 0; } static bool lookup_dir(struct gzfs_priv* priv, ext2_ino_t inum, stringlist& filenames) { pthread_mutex_lock(&priv->dir_lock); dircache::iterator itr = priv->dir_cache.find(inum); if (itr != priv->dir_cache.end()) { filenames = itr->second; pthread_mutex_unlock(&priv->dir_lock); return true; } if (ext2fs_dir_iterate(priv->fs, inum, 0, NULL, dir_iterator, &filenames) != 0) { pthread_mutex_unlock(&priv->dir_lock); return false; } priv->dir_cache[inum] = filenames; pthread_mutex_unlock(&priv->dir_lock); return true; } /*** FUSE operations ***/ static int gzfs_getattr(const char* path, struct stat* st) { struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context()); ext2_ino_t inum; struct ext2_inode inode; if (*path != '/') { return -1; } if (!lookup_path(priv, path + 1, inum)) { return -1; } if (!lookup_inode(priv, inum, inode)) { return -1; } st->st_ino = inum; st->st_mode = inode.i_mode; st->st_nlink = inode.i_links_count; st->st_uid = inode.i_uid; st->st_gid = inode.i_gid; st->st_size = inode.i_size; st->st_blksize = 4096 /* XXX */; st->st_blocks = inode.i_blocks; st->st_atime = inode.i_atime; st->st_mtime = inode.i_mtime; st->st_ctime = inode.i_ctime; return 0; } static int gzfs_readlink(const char* path, char* buf, size_t size) { struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context()); ext2_ino_t inum; struct ext2_inode inode; if (*path != '/') { return -1; } if (!lookup_path(priv, path+1, inum)) { return -1; } if (!lookup_inode(priv, inum, inode)) { return -1; } /* XXX: The below needs tested thoroughly. */ char linkbuf[PATH_MAX + 1]; size_t linksize; if (ext2fs_inode_has_valid_blocks(&inode)) { ext2_file_t file; unsigned int nread; if (ext2fs_file_open(priv->fs, inum, 0, &file) != 0) { return -1; } if (ext2fs_file_read(file, linkbuf, sizeof(linkbuf), &nread) != 0) { ext2fs_file_close(file); return -1; } linksize = nread; ext2fs_file_close(file); } else { memcpy(linkbuf, (const char*)inode.i_block, sizeof(inode.i_block)); linksize = sizeof(inode.i_block); } if (size > linksize) { size = linksize; } memcpy(buf, linkbuf, size); return 0; } static int gzfs_open(const char* path, struct fuse_file_info* ffi) { struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context()); ext2_ino_t inum; ext2_file_t file; if (*path != '/') { return -1; } if (!lookup_path(priv, path + 1, inum)) { return -1; } if (ext2fs_file_open(priv->fs, inum, 0, &file) != 0) { return -1; } file_state* state = new file_state; pthread_mutex_init(&state->lock, NULL); pthread_mutex_lock(&priv->file_lock); priv->file_table[file] = state; pthread_mutex_unlock(&priv->file_lock); ffi->fh = (uint64_t)file; return 0; } static int gzfs_read(const char* path, char* buf, size_t size, off_t off, struct fuse_file_info* ffi) { struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context()); __u64 pos; unsigned int nread; ext2_file_t file = (ext2_file_t)ffi->fh; pthread_mutex_lock(&priv->file_lock); file_state* state = priv->file_table.find(file)->second; pthread_mutex_unlock(&priv->file_lock); pthread_mutex_lock(&state->lock); if (ext2fs_file_llseek(file, off, SEEK_SET, &pos) != 0) { pthread_mutex_unlock(&state->lock); return -1; } if (ext2fs_file_read(file, buf, size, &nread) != 0) { pthread_mutex_unlock(&state->lock); return -1; } pthread_mutex_unlock(&state->lock); return nread; } static int gzfs_statfs(const char* param1, struct statvfs* param2) { return -1; } static int gzfs_flush(const char* param1, struct fuse_file_info* ffi) { return 0; } static int gzfs_release(const char* param1, struct fuse_file_info* ffi) { struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context()); ext2_file_t file = (ext2_file_t)ffi->fh; pthread_mutex_lock(&priv->file_lock); filemap::iterator itr = priv->file_table.find(file); file_state* state = itr->second; priv->file_table.erase(itr); pthread_mutex_unlock(&priv->file_lock); pthread_mutex_destroy(&state->lock); delete state; ext2fs_file_close(file); return 0; } static int gzfs_readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t off, struct fuse_file_info* ffi) { struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context()); ext2_ino_t inum; if (*path != '/') { return -1; } if (!lookup_path(priv, path+1, inum)) { return -1; } stringlist filenames; if (!lookup_dir(priv, inum, filenames)) { return -1; } stringlist::iterator itr; for (itr = filenames.begin(); itr != filenames.end(); ++itr) { const string& name = *itr; if (filler(buf, name.c_str(), NULL, 0) != 0) { break; } } return 0; } static void* gzfs_init(struct fuse_conn_info *conn) { errcode_t rc; struct gzfs_priv* priv = new gzfs_priv; priv->filename = strdup(options.filename); pthread_mutex_init(&priv->path_lock, NULL); pthread_mutex_init(&priv->inode_lock, NULL); pthread_mutex_init(&priv->dir_lock, NULL); pthread_mutex_init(&priv->file_lock, NULL); rc = ext2fs_open(priv->filename, 0 /* flags */, 0 /* superblock */, 4096 /* block_size */, const_cast(gzext_io_manager), &priv->fs); if (rc != 0) { fprintf(stderr, "Failed to open ext2 filesystem on %s\n", priv->filename); exit(1); } return priv; } static void gzfs_destroy(void* param) { struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context()); ext2fs_close(priv->fs); delete priv; } static const struct fuse_operations gzfs_oper = { .getattr = gzfs_getattr, .readlink = gzfs_readlink, .getdir = NULL, .mknod = NULL, .mkdir = NULL, .unlink = NULL, .rmdir = NULL, .symlink = NULL, .rename = NULL, .link = NULL, .chmod = NULL, .chown = NULL, .truncate = NULL, .utime = NULL, .open = gzfs_open, .read = gzfs_read, .write = NULL, .statfs = gzfs_statfs, .flush = gzfs_flush, .release = gzfs_release, .fsync = NULL, .setxattr = NULL, .getxattr = NULL, .listxattr = NULL, .removexattr = NULL, .opendir = NULL, .readdir = gzfs_readdir, .releasedir = NULL, .fsyncdir = NULL, .init = gzfs_init, .destroy = gzfs_destroy, .access = NULL, .create = NULL, .ftruncate = NULL, .fgetattr = NULL, .lock = NULL, .utimens = NULL, .bmap = NULL, .flag_nullpath_ok = 0, .flag_nopath = 0, .flag_utime_omit_ok = 0, .flag_reserved = 0, .ioctl = NULL, .poll = NULL, .write_buf = NULL, .read_buf = NULL, .flock = NULL, .fallocate = NULL, }; #define OPTION(t,p) \ { t, offsetof(struct options, p), 1 } static const struct fuse_opt option_spec[] = { OPTION("--filename=%s", filename), OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END }; static void usage(const char* argv0) { fprintf(stderr, "Usage: %s --filename= ... \n", argv0); exit(1); } int main(int argc, char** argv) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); if ((getuid() == 0) || (geteuid() == 0)) { fprintf(stderr, "Refusing to run as root.\n"); exit(1); } if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) { return 1; } if (options.show_help) { usage(argv[0]); } if (!options.filename) { usage(argv[0]); } char* real_filename = NULL; if (options.filename[0] == '/') { real_filename = strdup(options.filename); } else { real_filename = (char*)malloc(PATH_MAX + 1); if (realpath(options.filename, real_filename) == NULL) { fprintf(stderr, "Failed to resolve %s\n", options.filename); exit(1); } options.filename = real_filename; } return fuse_main(args.argc, args.argv, &gzfs_oper, NULL); }