/** * @file memfs-fuse.c * * @copyright 2015-2021 Bill Zissimopoulos */ /* * This file is part of WinFsp. * * You can redistribute it and/or modify it under the terms of the GNU * General Public License version 3 as published by the Free Software * Foundation. * * Licensees holding a valid commercial license may use this software * in accordance with the commercial license agreement provided in * conjunction with the software. The terms and conditions of any such * commercial license agreement shall govern, supersede, and render * ineffective any application of the GPLv3 license to this software, * notwithstanding of any reference thereto in the software or * associated repository. */ #include #include #include #include #include #include #include #include #include #include #include "compat.h" class memfs { public: memfs() : _ino(1), _root(std::make_shared(_ino, S_IFDIR | 00777, 0, 0)) { } int main(int argc, char *argv[]) { static fuse_operations ops = { getattr, 0, // getdir readlink, mknod, mkdir, unlink, rmdir, symlink, rename, link, chmod, chown, truncate, 0, // utime open, read, write, statfs, flush, release, 0, // fsync setxattr, getxattr, listxattr, removexattr, opendir, readdir, releasedir, 0, // fsyncdir init, 0, // destroy 0, // access 0, // create ftruncate, fgetattr, 0, // lock utimens, 0, // bmap 0, // flag_nullpath_ok 0, // flag_nopath 0, // flag_utime_omit_ok 0, // flag_reserved 0, // ioctl 0, // poll 0, // write_buf 0, // read_buf 0, // flock 0, // fallocate 0, // reserved00 0, // reserved01 0, // reserved02 0, // statfs_x 0, // setvolname 0, // exchange 0, // getxtimes 0, // setbkuptime 0, // setchgtime setcrtime, #if defined(FSP_FUSE_USE_STAT_EX) chflags, #else 0, // chflags #endif 0, // setattr_x 0, // fsetattr_x }; return fuse_main(argc, argv, &ops, this); } private: struct node_t { node_t(fuse_ino_t ino, fuse_mode_t mode, fuse_uid_t uid, fuse_gid_t gid, fuse_dev_t dev = 0) : stat() { stat.st_ino = ino; stat.st_mode = mode; stat.st_nlink = 1; stat.st_uid = uid; stat.st_gid = gid; stat.st_rdev = dev; stat.st_atim = stat.st_mtim = stat.st_ctim = stat.st_birthtim = now(); } void resize(size_t size, bool capacity) { if (capacity) { const size_t unit = 64 * 1024; size_t newcap = (size + unit - 1) / unit * unit; size_t oldcap = data.capacity(); if (newcap > oldcap) data.reserve(newcap); else if (newcap < oldcap) { data.resize(newcap); data.shrink_to_fit(); } } data.resize(size); stat.st_size = size; } struct fuse_stat stat; std::vector data; std::unordered_map> childmap; std::unordered_map> xattrmap; }; static fuse_timespec now() { using namespace std::chrono; auto now = system_clock::now(); auto sec = floor(now); auto nsec = floor(now) - floor(sec); return fuse_timespec { static_cast(sec.time_since_epoch().count()), /* std::chrono epoch is UNIX epoch in C++20 */ static_cast(nsec.count()), }; } static memfs *getself() { return static_cast(fuse_get_context()->private_data); } static int getattr(const char *path, struct fuse_stat *stbuf) { return fgetattr(path, stbuf, nullptr); } static int fgetattr(const char *path, struct fuse_stat *stbuf, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path, fi); if (!node) return -ENOENT; *stbuf = node->stat; return 0; } static int readlink(const char *path, char *buf, size_t size) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; if (S_IFLNK != (node->stat.st_mode & S_IFMT)) return EINVAL; size = (std::min)(size - 1, node->data.size()); std::memcpy(buf, node->data.data(), size); buf[size] = '\0'; return 0; } static int mknod(const char *path, fuse_mode_t mode, fuse_dev_t dev) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->make_node(path, mode, dev); } static int mkdir(const char *path, fuse_mode_t mode) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->make_node(path, S_IFDIR | (mode & 07777), 0); } static int unlink(const char *path) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->remove_node(path, false); } static int rmdir(const char *path) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->remove_node(path, true); } static int symlink(const char *dstpath, const char *srcpath) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->make_node(srcpath, S_IFLNK | 00777, 0, dstpath); } static int rename(const char *oldpath, const char *newpath) { auto self = getself(); std::lock_guard lock(self->_mutex); auto oldlookup = self->lookup_node(oldpath); auto oldprnt = std::get<0>(oldlookup); auto oldname = std::get<1>(oldlookup); auto oldnode = std::get<2>(oldlookup); if (!oldnode) return -ENOENT; auto newlookup = self->lookup_node(newpath); auto newprnt = std::get<0>(newlookup); auto newname = std::get<1>(newlookup); auto newnode = std::get<2>(newlookup); if (!newprnt) return -ENOENT; if (newname.empty()) // guard against directory loop creation return -EINVAL; if (oldprnt == newprnt && oldname == newname) return 0; if (newnode) { if (int errc = self->remove_node(newpath, S_IFDIR == (oldnode->stat.st_mode & S_IFMT))) return errc; } oldprnt->childmap.erase(oldname); newprnt->childmap[newname] = oldnode; return 0; } static int link(const char *oldpath, const char *newpath) { auto self = getself(); std::lock_guard lock(self->_mutex); auto oldlookup = self->lookup_node(oldpath); auto oldnode = std::get<2>(oldlookup); if (!oldnode) return -ENOENT; auto newlookup = self->lookup_node(newpath); auto newprnt = std::get<0>(newlookup); auto newname = std::get<1>(newlookup); auto newnode = std::get<2>(newlookup); if (!newprnt) return -ENOENT; if (newnode) return -EEXIST; oldnode->stat.st_nlink++; newprnt->childmap[newname] = oldnode; oldnode->stat.st_ctim = newprnt->stat.st_ctim = newprnt->stat.st_mtim = now(); return 0; } static int chmod(const char *path, fuse_mode_t mode) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; node->stat.st_mode = (node->stat.st_mode & S_IFMT) | (mode & 07777); node->stat.st_ctim = now(); return 0; } static int chown(const char *path, fuse_uid_t uid, fuse_gid_t gid) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; if (-1 != uid) node->stat.st_uid = uid; if (-1 != gid) node->stat.st_gid = gid; node->stat.st_ctim = now(); return 0; } static int truncate(const char *path, fuse_off_t size) { return ftruncate(path, size, nullptr); } static int ftruncate(const char *path, fuse_off_t size, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path, fi); if (!node) return -ENOENT; if (SIZE_MAX < size) return -EFBIG; node->resize(static_cast(size), true); node->stat.st_ctim = node->stat.st_mtim = now(); #if defined(FSP_FUSE_USE_STAT_EX) node->stat.st_flags |= UF_ARCHIVE; #endif return 0; } static int open(const char *path, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->open_node(path, false, fi); } static int read(const char *path, char *buf, size_t size, fuse_off_t off, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path, fi); if (!node) return -ENOENT; fuse_off_t endoff = (std::min)( off + static_cast(size), static_cast(node->data.size())); if (off > endoff) return 0; std::memcpy(buf, node->data.data() + off, static_cast(endoff - off)); node->stat.st_atim = now(); return static_cast(endoff - off); } static int write(const char *path, const char *buf, size_t size, fuse_off_t off, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path, fi); if (!node) return -ENOENT; fuse_off_t endoff = off + static_cast(size); if (SIZE_MAX < endoff) return -EFBIG; if (node->data.size() < endoff) node->resize(static_cast(endoff), true); std::memcpy(node->data.data() + off, buf, static_cast(endoff - off)); node->stat.st_ctim = node->stat.st_mtim = now(); #if defined(FSP_FUSE_USE_STAT_EX) node->stat.st_flags |= UF_ARCHIVE; #endif return static_cast(endoff - off); } static int statfs(const char *path, struct fuse_statvfs *stbuf) { std::memset(stbuf, 0, sizeof *stbuf); return 0; } static int flush(const char *path, struct fuse_file_info *fi) { return -ENOSYS; } static int release(const char *path, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->close_node(fi); } static int setxattr(const char *path, const char *name0, const char *value, size_t size, int flags) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; if (0 == std::strcmp("com.apple.ResourceFork", name0)) return -ENOTSUP; std::string name = name0; if (XATTR_CREATE == flags) { if (node->xattrmap.end() != node->xattrmap.find(name)) return -EEXIST; } else if (XATTR_REPLACE == flags) { if (node->xattrmap.end() == node->xattrmap.find(name)) return -ENOATTR; } node->xattrmap[name].assign(value, value + size); return 0; } static int getxattr(const char *path, const char *name0, char *value, size_t size) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; if (0 == std::strcmp("com.apple.ResourceFork", name0)) return -ENOTSUP; std::string name = name0; auto iter = node->xattrmap.find(name); if (node->xattrmap.end() == iter) return -ENOATTR; if (0 != size) { if (iter->second.size() > size) return -ERANGE; std::memcpy(value, iter->second.data(), iter->second.size()); } return static_cast(iter->second.size()); } static int listxattr(const char *path, char *namebuf, size_t size) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; size_t copysize = 0; for (auto elem : node->xattrmap) { size_t namesize = elem.first.size() + 1; if (0 != size) { if (copysize + namesize > size) return -ERANGE; std::memcpy(namebuf + copysize, elem.first.c_str(), namesize); copysize += namesize; } } return static_cast(copysize); } static int removexattr(const char *path, const char *name0) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; if (0 == std::strcmp("com.apple.ResourceFork", name0)) return -ENOTSUP; std::string name = name0; return node->xattrmap.erase(name) ? 0 : -ENOATTR; } static int opendir(const char *path, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->open_node(path, true, fi); } static int readdir(const char *path, void *buf, fuse_fill_dir_t filler, fuse_off_t off, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path, fi); if (!node) return -ENOENT; filler(buf, ".", &node->stat, 0); filler(buf, "..", nullptr, 0); for (auto elem : node->childmap) if (0 != filler(buf, elem.first.c_str(), &elem.second->stat, 0)) break; return 0; } static int releasedir(const char *path, struct fuse_file_info *fi) { auto self = getself(); std::lock_guard lock(self->_mutex); return self->close_node(fi); } static void *init(struct fuse_conn_info *conn) { #if defined(FSP_FUSE_CAP_READDIR_PLUS) conn->want |= (conn->capable & FSP_FUSE_CAP_READDIR_PLUS); #endif #if defined(FSP_FUSE_USE_STAT_EX) && defined(FSP_FUSE_CAP_STAT_EX) conn->want |= (conn->capable & FSP_FUSE_CAP_STAT_EX); #endif return getself(); } static int utimens(const char *path, const struct fuse_timespec tmsp[2]) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; if (tmsp) { node->stat.st_ctim = now(); node->stat.st_atim = tmsp[0]; node->stat.st_mtim = tmsp[1]; } else node->stat.st_ctim = node->stat.st_atim = node->stat.st_mtim = now(); return 0; } static int setcrtime(const char *path, const struct fuse_timespec *tmsp) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; if (tmsp) { node->stat.st_ctim = now(); node->stat.st_birthtim = tmsp[0]; } else node->stat.st_ctim = node->stat.st_birthtim = now(); return 0; } #if defined(FSP_FUSE_USE_STAT_EX) static int chflags(const char *path, uint32_t flags) { auto self = getself(); std::lock_guard lock(self->_mutex); auto node = self->get_node(path); if (!node) return -ENOENT; node->stat.st_flags = flags; node->stat.st_ctim = now(); return 0; } #endif std::tuple, std::string, std::shared_ptr> lookup_node(const char *path, node_t *ancestor = nullptr) { auto prnt = _root; std::string name; auto node = prnt; for (const char *part = path, *p; *part; part = p + !!(*p)) { for (p = part; *p && '/' != *p; p++) ; if (part == p) continue; prnt = node; if (!node) break; name.assign(part, p); auto iter = node->childmap.find(name); node = node->childmap.end() != iter ? iter->second : nullptr; if (ancestor && node.get() == ancestor) { name.assign(""); // special case loop condition break; } } return std::make_tuple(prnt, name, node); } int make_node(const char *path, fuse_mode_t mode, fuse_dev_t dev, const char *data = nullptr) { auto lookup = lookup_node(path); auto prnt = std::get<0>(lookup); auto name = std::get<1>(lookup); auto node = std::get<2>(lookup); if (!prnt) return -ENOENT; if (node) return -EEXIST; fuse_context *context = fuse_get_context(); node = std::make_shared(++_ino, mode, context->uid, context->gid, dev); #if defined(FSP_FUSE_USE_STAT_EX) if (S_IFDIR != (mode & S_IFMT)) node->stat.st_flags |= UF_ARCHIVE; #endif if (data) { node->resize(std::strlen(data), false); std::memcpy(node->data.data(), data, node->data.size()); } prnt->childmap[name] = node; prnt->stat.st_ctim = prnt->stat.st_mtim = node->stat.st_ctim; return 0; } int remove_node(const char *path, bool dir) { auto lookup = lookup_node(path); auto prnt = std::get<0>(lookup); auto name = std::get<1>(lookup); auto node = std::get<2>(lookup); if (!node) return -ENOENT; if (!dir && S_IFDIR == (node->stat.st_mode & S_IFMT)) return -EISDIR; if (dir && S_IFDIR != (node->stat.st_mode & S_IFMT)) return -ENOTDIR; if (0 < node->childmap.size()) return -ENOTEMPTY; node->stat.st_nlink--; prnt->childmap.erase(name); node->stat.st_ctim = prnt->stat.st_ctim = prnt->stat.st_mtim = now(); return 0; } int open_node(const char *path, bool dir, struct fuse_file_info *fi) { auto node = std::get<2>(lookup_node(path)); if (!node) return -ENOENT; if (!dir && S_IFDIR == (node->stat.st_mode & S_IFMT)) return -EISDIR; if (dir && S_IFDIR != (node->stat.st_mode & S_IFMT)) return -ENOTDIR; // A file descriptor is a raw pointer to a shared_ptr. // This has the effect of incrementing the shared_ptr // refcount, thus keeping an open node around even // if the node is unlinked. fi->fh = (uint64_t)(uintptr_t)new std::shared_ptr(node); return 0; } int close_node(struct fuse_file_info *fi) { delete (std::shared_ptr *)(uintptr_t)fi->fh; return 0; } std::shared_ptr get_node(const char *path, struct fuse_file_info *fi = nullptr) { if (!fi) return std::get<2>(lookup_node(path)); else return *(std::shared_ptr *)(uintptr_t)fi->fh; } private: std::mutex _mutex; fuse_ino_t _ino; std::shared_ptr _root; }; int main(int argc, char *argv[]) { return memfs().main(argc, argv); }