#include "Fuse.h" #include #include #include "FuseErrnoException.h" #include "Filesystem.h" #include #include #include #include using std::vector; using std::string; namespace bf = boost::filesystem; using namespace cpputils::logging; using namespace fspp::fuse; #define FUSE_OBJ ((Fuse *) fuse_get_context()->private_data) // Remove the following line, if you don't want to output each fuse operation on the console //#define FSPP_LOG 1 namespace { int fusepp_getattr(const char *path, struct stat *stbuf) { int rs = FUSE_OBJ->getattr(bf::path(path), stbuf); return rs; } int fusepp_fgetattr(const char *path, struct stat *stbuf, fuse_file_info *fileinfo) { return FUSE_OBJ->fgetattr(bf::path(path), stbuf, fileinfo); } int fusepp_readlink(const char *path, char *buf, size_t size) { return FUSE_OBJ->readlink(bf::path(path), buf, size); } int fusepp_mknod(const char *path, mode_t mode, dev_t rdev) { return FUSE_OBJ->mknod(bf::path(path), mode, rdev); } int fusepp_mkdir(const char *path, mode_t mode) { return FUSE_OBJ->mkdir(bf::path(path), mode); } int fusepp_unlink(const char *path) { return FUSE_OBJ->unlink(bf::path(path)); } int fusepp_rmdir(const char *path) { return FUSE_OBJ->rmdir(bf::path(path)); } int fusepp_symlink(const char *to, const char *from) { return FUSE_OBJ->symlink(bf::path(to), bf::path(from)); } int fusepp_rename(const char *from, const char *to) { return FUSE_OBJ->rename(bf::path(from), bf::path(to)); } int fusepp_link(const char *from, const char *to) { return FUSE_OBJ->link(bf::path(from), bf::path(to)); } int fusepp_chmod(const char *path, mode_t mode) { return FUSE_OBJ->chmod(bf::path(path), mode); } int fusepp_chown(const char *path, uid_t uid, gid_t gid) { return FUSE_OBJ->chown(bf::path(path), uid, gid); } int fusepp_truncate(const char *path, off_t size) { return FUSE_OBJ->truncate(bf::path(path), size); } int fusepp_ftruncate(const char *path, off_t size, fuse_file_info *fileinfo) { return FUSE_OBJ->ftruncate(bf::path(path), size, fileinfo); } int fusepp_utimens(const char *path, const timespec times[2]) { return FUSE_OBJ->utimens(bf::path(path), times); } int fusepp_open(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->open(bf::path(path), fileinfo); } int fusepp_release(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->release(bf::path(path), fileinfo); } int fusepp_read(const char *path, char *buf, size_t size, off_t offset, fuse_file_info *fileinfo) { return FUSE_OBJ->read(bf::path(path), buf, size, offset, fileinfo); } int fusepp_write(const char *path, const char *buf, size_t size, off_t offset, fuse_file_info *fileinfo) { return FUSE_OBJ->write(bf::path(path), buf, size, offset, fileinfo); } int fusepp_statfs(const char *path, struct statvfs *fsstat) { return FUSE_OBJ->statfs(bf::path(path), fsstat); } int fusepp_flush(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->flush(bf::path(path), fileinfo); } int fusepp_fsync(const char *path, int datasync, fuse_file_info *fileinfo) { return FUSE_OBJ->fsync(bf::path(path), datasync, fileinfo); } //int fusepp_setxattr(const char*, const char*, const char*, size_t, int) //int fusepp_getxattr(const char*, const char*, char*, size_t) //int fusepp_listxattr(const char*, char*, size_t) //int fusepp_removexattr(const char*, const char*) int fusepp_opendir(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->opendir(bf::path(path), fileinfo); } int fusepp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, fuse_file_info *fileinfo) { return FUSE_OBJ->readdir(bf::path(path), buf, filler, offset, fileinfo); } int fusepp_releasedir(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->releasedir(bf::path(path), fileinfo); } int fusepp_fsyncdir(const char *path, int datasync, fuse_file_info *fileinfo) { return FUSE_OBJ->fsyncdir(bf::path(path), datasync, fileinfo); } void* fusepp_init(fuse_conn_info *conn) { auto f = FUSE_OBJ; f->init(conn); return f; } void fusepp_destroy(void *userdata) { auto f = FUSE_OBJ; ASSERT(userdata == f, "Wrong userdata set"); UNUSED(userdata); //In case the assert is disabled f->destroy(); } int fusepp_access(const char *path, int mask) { return FUSE_OBJ->access(bf::path(path), mask); } int fusepp_create(const char *path, mode_t mode, fuse_file_info *fileinfo) { return FUSE_OBJ->create(bf::path(path), mode, fileinfo); } /*int fusepp_lock(const char*, fuse_file_info*, int cmd, flock*) int fusepp_bmap(const char*, size_t blocksize, uint64_t *idx) int fusepp_ioctl(const char*, int cmd, void *arg, fuse_file_info*, unsigned int flags, void *data) int fusepp_poll(const char*, fuse_file_info*, fuse_pollhandle *ph, unsigned *reventsp) int fusepp_write_buf(const char*, fuse_bufvec *buf, off_t off, fuse_file_info*) int fusepp_read_buf(const chas*, struct fuse_bufvec **bufp, size_t size, off_T off, fuse_file_info*) int fusepp_flock(const char*, fuse_file_info*, int op) int fusepp_fallocate(const char*, int, off_t, off_t, fuse_file_info*)*/ fuse_operations *operations() { static std::unique_ptr singleton(nullptr); if (!singleton) { singleton = std::make_unique(); singleton->getattr = &fusepp_getattr; singleton->fgetattr = &fusepp_fgetattr; singleton->readlink = &fusepp_readlink; singleton->mknod = &fusepp_mknod; singleton->mkdir = &fusepp_mkdir; singleton->unlink = &fusepp_unlink; singleton->rmdir = &fusepp_rmdir; singleton->symlink = &fusepp_symlink; singleton->rename = &fusepp_rename; singleton->link = &fusepp_link; singleton->chmod = &fusepp_chmod; singleton->chown = &fusepp_chown; singleton->truncate = &fusepp_truncate; singleton->utimens = &fusepp_utimens; singleton->open = &fusepp_open; singleton->read = &fusepp_read; singleton->write = &fusepp_write; singleton->statfs = &fusepp_statfs; singleton->flush = &fusepp_flush; singleton->release = &fusepp_release; singleton->fsync = &fusepp_fsync; /*#ifdef HAVE_SYS_XATTR_H singleton->setxattr = &fusepp_setxattr; singleton->getxattr = &fusepp_getxattr; singleton->listxattr = &fusepp_listxattr; singleton->removexattr = &fusepp_removexattr; #endif*/ singleton->opendir = &fusepp_opendir; singleton->readdir = &fusepp_readdir; singleton->releasedir = &fusepp_releasedir; singleton->fsyncdir = &fusepp_fsyncdir; singleton->init = &fusepp_init; singleton->destroy = &fusepp_destroy; singleton->access = &fusepp_access; singleton->create = &fusepp_create; singleton->ftruncate = &fusepp_ftruncate; } return singleton.get(); } } Fuse::~Fuse() { } Fuse::Fuse(Filesystem *fs) :_fs(fs), _mountdir(), _running(false) { } void Fuse::run(int argc, char **argv) { vector _argv(argv, argv + argc); _mountdir = argv[1]; fuse_main(_argv.size(), _argv.data(), operations(), (void*)this); } bool Fuse::running() const { return _running; } void Fuse::stop() { //TODO Find better way to unmount (i.e. don't use external fusermount). Unmounting by kill(getpid(), SIGINT) worked, but left the mount directory transport endpoint as not connected. int ret = system(("fusermount -z -u " + _mountdir.native()).c_str()); // "-z" takes care that if the filesystem can't be unmounted right now because something is opened, it will be unmounted as soon as it can be. if (ret != 0) { LOG(ERROR) << "Could not unmount filesystem"; } } int Fuse::getattr(const bf::path &path, struct stat *stbuf) { #ifdef FSPP_LOG LOG(DEBUG) << "getattr(" << path << ", _, _)"; #endif try { _fs->lstat(path, stbuf); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::getattr: " << e.what(); return -EIO; } catch(fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::fgetattr(const bf::path &path, struct stat *stbuf, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "fgetattr(" << path << ", _, _)\n"; #endif // On FreeBSD, trying to do anything with the mountpoint ends up // opening it, and then using the FD for an fgetattr. So in the // special case of a path of "/", I need to do a getattr on the // underlying root directory instead of doing the fgetattr(). // TODO Check if necessary if (path.native() == "/") { return getattr(path, stbuf); } try { _fs->fstat(fileinfo->fh, stbuf); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::fgetattr: " << e.what(); return -EIO; } catch(fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::readlink(const bf::path &path, char *buf, size_t size) { #ifdef FSPP_LOG LOG(DEBUG) << "readlink(" << path << ", _, " << size << ")"; #endif try { _fs->readSymlink(path, buf, size); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::readlink: " << e.what(); return -EIO; } catch (fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::mknod(const bf::path &path, mode_t mode, dev_t rdev) { UNUSED(rdev); UNUSED(mode); UNUSED(path); LOG(WARN) << "Called non-implemented mknod(" << path << ", " << mode << ", _)"; return ENOSYS; } int Fuse::mkdir(const bf::path &path, mode_t mode) { #ifdef FSPP_LOG LOG(DEBUG) << "mkdir(" << path << ", " << mode << ")"; #endif try { auto context = fuse_get_context(); _fs->mkdir(path, mode, context->uid, context->gid); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::mkdir: " << e.what(); return -EIO; } catch(fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::unlink(const bf::path &path) { #ifdef FSPP_LOG LOG(DEBUG) << "unlink(" << path << ")"; #endif try { _fs->unlink(path); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::unlink: " << e.what(); return -EIO; } catch(fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::rmdir(const bf::path &path) { #ifdef FSPP_LOG LOG(DEBUG) << "rmdir(" << path << ")"; #endif try { _fs->rmdir(path); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::rmdir: " << e.what(); return -EIO; } catch(fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::symlink(const bf::path &from, const bf::path &to) { #ifdef FSPP_LOG LOG(DEBUG) << "symlink(" << from << ", " << to << ")"; #endif try { auto context = fuse_get_context(); _fs->createSymlink(from, to, context->uid, context->gid); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::symlink: " << e.what(); return -EIO; } catch(fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::rename(const bf::path &from, const bf::path &to) { #ifdef FSPP_LOG LOG(DEBUG) << "rename(" << from << ", " << to << ")"; #endif try { _fs->rename(from, to); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::rename: " << e.what(); return -EIO; } catch(fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } //TODO int Fuse::link(const bf::path &from, const bf::path &to) { LOG(WARN) << "NOT IMPLEMENTED: link(" << from << ", " << to << ")"; //auto real_from = _impl->RootDir() / from; //auto real_to = _impl->RootDir() / to; //int retstat = ::link(real_from.c_str(), real_to.c_str()); //return errcode_map(retstat); return ENOSYS; } int Fuse::chmod(const bf::path &path, mode_t mode) { #ifdef FSPP_LOG LOG(DEBUG) << "chmod(" << path << ", " << mode << ")"; #endif try { _fs->chmod(path, mode); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::chmod: " << e.what(); return -EIO; } catch (fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::chown(const bf::path &path, uid_t uid, gid_t gid) { #ifdef FSPP_LOG LOG(DEBUG) << "chown(" << path << ", " << uid << ", " << gid << ")"; #endif try { _fs->chown(path, uid, gid); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::chown: " << e.what(); return -EIO; } catch (fspp::fuse::FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::truncate(const bf::path &path, off_t size) { #ifdef FSPP_LOG LOG(DEBUG) << "truncate(" << path << ", " << size << ")"; #endif try { _fs->truncate(path, size); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::truncate: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::ftruncate(const bf::path &path, off_t size, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "ftruncate(" << path << ", " << size << ")"; #endif UNUSED(path); try { _fs->ftruncate(fileinfo->fh, size); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::ftruncate: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } //TODO int Fuse::utimens(const bf::path &path, const timespec times[2]) { #ifdef FSPP_LOG LOG(DEBUG) << "utimens(" << path << ", _)"; #endif try { _fs->utimens(path, times); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::utimens: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::open(const bf::path &path, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "open(" << path << ", _)"; #endif try { fileinfo->fh = _fs->openFile(path, fileinfo->flags); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::open: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::release(const bf::path &path, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "release(" << path << ", _)"; #endif UNUSED(path); try { _fs->closeFile(fileinfo->fh); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::release: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::read(const bf::path &path, char *buf, size_t size, off_t offset, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "read(" << path << ", _, " << size << ", " << offset << ", _ )"; #endif UNUSED(path); try { return _fs->read(fileinfo->fh, buf, size, offset); } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::read: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::write(const bf::path &path, const char *buf, size_t size, off_t offset, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "write(" << path << ", _, " << size << ", " << offset << ", _)"; #endif UNUSED(path); try { _fs->write(fileinfo->fh, buf, size, offset); return size; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::write: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } //TODO int Fuse::statfs(const bf::path &path, struct statvfs *fsstat) { #ifdef FSPP_LOG LOG(DEBUG) << "statfs(" << path << ", _)"; #endif try { _fs->statfs(path, fsstat); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::statfs: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } //TODO int Fuse::flush(const bf::path &path, fuse_file_info *fileinfo) { #ifdef FSPP_LOG //TODO Implement it LOG(WARN) << "Called non-implemented flush(" << path << ", _)"; #endif UNUSED(path); try { _fs->flush(fileinfo->fh); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::flush: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::fsync(const bf::path &path, int datasync, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "fsync(" << path << ", " << datasync << ", _)"; #endif UNUSED(path); try { if (datasync) { _fs->fdatasync(fileinfo->fh); } else { _fs->fsync(fileinfo->fh); } return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::fsync: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::opendir(const bf::path &path, fuse_file_info *fileinfo) { UNUSED(path); UNUSED(fileinfo); //LOG(DEBUG) << "opendir(" << path << ", _)"; //We don't need opendir, because readdir works directly on the path return 0; } int Fuse::readdir(const bf::path &path, void *buf, fuse_fill_dir_t filler, off_t offset, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "readdir(" << path << ", _, _, " << offset << ", _)"; #endif UNUSED(fileinfo); UNUSED(offset); try { auto entries = _fs->readDir(path); struct stat stbuf; for (const auto &entry : *entries) { //We could pass more file metadata to filler() in its third parameter, //but it doesn't help performance since fuse ignores everything in stbuf //except for file-type bits in st_mode and (if used) st_ino. //It does getattr() calls on all entries nevertheless. if (entry.type == Dir::EntryType::DIR) { stbuf.st_mode = S_IFDIR; } else if (entry.type == Dir::EntryType::FILE) { stbuf.st_mode = S_IFREG; } else if (entry.type == Dir::EntryType::SYMLINK) { stbuf.st_mode = S_IFLNK; } else { ASSERT(false, "Unknown entry type"); } if (filler(buf, entry.name.c_str(), &stbuf, 0) != 0) { return -ENOMEM; } } return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::readdir: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::releasedir(const bf::path &path, fuse_file_info *fileinfo) { UNUSED(path); UNUSED(fileinfo); //LOG(DEBUG) << "releasedir(" << path << ", _)"; //We don't need releasedir, because readdir works directly on the path return 0; } //TODO int Fuse::fsyncdir(const bf::path &path, int datasync, fuse_file_info *fileinfo) { UNUSED(fileinfo); UNUSED(datasync); UNUSED(path); //LOG(WARN) << "Called non-implemented fsyncdir(" << path << ", " << datasync << ", _)"; return 0; } void Fuse::init(fuse_conn_info *conn) { UNUSED(conn); _running = true; #ifdef FSPP_LOG cpputils::logging::setLevel(DEBUG); #endif LOG(INFO) << "Filesystem started."; } void Fuse::destroy() { _running = false; LOG(INFO) << "Filesystem stopped."; } int Fuse::access(const bf::path &path, int mask) { #ifdef FSPP_LOG LOG(DEBUG) << "access(" << path << ", " << mask << ")"; #endif try { _fs->access(path, mask); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::access: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } } int Fuse::create(const bf::path &path, mode_t mode, fuse_file_info *fileinfo) { #ifdef FSPP_LOG LOG(DEBUG) << "create(" << path << ", " << mode << ", _)"; #endif try { auto context = fuse_get_context(); fileinfo->fh = _fs->createAndOpenFile(path, mode, context->uid, context->gid); return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERROR) << "AssertFailed in Fuse::create: " << e.what(); return -EIO; } catch (FuseErrnoException &e) { return -e.getErrno(); } }