From a3a14ffb499712f36bbb24ba7684c8bd3ba337ca Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Thu, 12 Dec 2024 16:08:50 +0100 Subject: [PATCH] examples: Add memfs_ll.cc This is an initial implementation and quickly hacked together within a few hours - issues expected. This also increase to C++17 as memfs_ll makes use of more recent features. Background to create this was actually to be able to test large file names (3 * 1024B), which couldn't be achieved with passthrough file system as non of the underlying file systems seems to support that. Signed-off-by: Bernd Schubert --- example/memfs_ll.cc | 1156 +++++++++++++++++++++++++++++++++++++++++++ example/meson.build | 3 + meson.build | 2 +- 3 files changed, 1160 insertions(+), 1 deletion(-) create mode 100644 example/memfs_ll.cc diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc new file mode 100644 index 0000000..c7b9663 --- /dev/null +++ b/example/memfs_ll.cc @@ -0,0 +1,1156 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2024 DataDirect Networks. + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +#include +#define FUSE_USE_VERSION 317 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MEMFS_ATTR_TIMEOUT 0.0 +#define MEMFS_ENTRY_TIMEOUT 0.0 + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +class Inodes; +class Inode; +class Dentry; + +static void memfs_panic(std::string_view message); + +struct DirHandle { + std::vector > entries; + size_t offset; + + DirHandle(const std::vector > + &entries) + : entries(entries) + , offset(0) + { + } +}; + +class Inode { + private: + uint64_t ino; // Unique inode number + std::string name; + bool is_dir_; + time_t ctime; + time_t mtime; + time_t atime; + mode_t mode; + std::vector content; + std::vector dentries; + mutable std::mutex mutex; + uint64_t nlookup; + mutable std::mutex attr_mutex; + std::atomic nlink; + uid_t uid; + gid_t gid; + + friend class Inodes; + + public: + Inode(uint64_t ino, const std::string &n, bool dir) + : ino(ino) + , name(n) + , is_dir_(dir) + , ctime(time(NULL)) + , mtime(ctime) + , atime(ctime) + , mode(dir ? S_IFDIR | 0755 : S_IFREG | 0644) + , nlookup(1) + , nlink(dir ? 2 : 1) + , uid(0) + , gid(0) + { + } + + uint64_t get_ino() const + { + return ino; + } + + // Method to lock the mutex + void lock() const + { + mutex.lock(); + } + + // Method to unlock the mutex + void unlock() const + { + mutex.unlock(); + } + + void inc_lookup() + { + std::lock_guard lock(mutex); + nlookup++; + } + + uint64_t dec_lookup(uint64_t count) + { + std::unique_lock lock(mutex); + if (nlookup < count) { + lock.unlock(); + memfs_panic("Lookup count mismatch detected"); + } + nlookup -= count; + return nlookup; + } + + const std::string &get_name() const + { + return name; + } + bool is_dir() const + { + return is_dir_; + } + time_t get_ctime() const + { + return ctime; + } + time_t get_mtime() const + { + return mtime; + } + mode_t get_mode() const + { + return mode; + } + + size_t content_size() const + { + return content.size(); + } + + void read_content(char *buf, size_t size, off_t offset) const + { + size_t bytes_to_read = std::min(size, content.size() - offset); + std::copy(content.begin() + offset, + content.begin() + offset + bytes_to_read, buf); + } + + void write_content(const char *buf, size_t size, off_t offset) + { + std::lock_guard lock(mutex); + if (offset + size > content.size()) { + content.resize(offset + size); + } + std::copy(buf, buf + size, content.begin() + offset); + mtime = time(NULL); + } + + void set_uid(uid_t _uid) + { + std::lock_guard lock(attr_mutex); + uid = _uid; + } + + void set_gid(gid_t _gid) + { + std::lock_guard lock(attr_mutex); + gid = _gid; + } + + void set_mode(mode_t new_mode) + { + std::lock_guard lock(attr_mutex); + mode = new_mode; + } + + void set_atime(const struct timespec &_atime) + { + std::lock_guard lock(attr_mutex); + atime = _atime.tv_sec; + } + + void set_mtime(const struct timespec &_mtime) + { + std::lock_guard lock(attr_mutex); + mtime = _mtime.tv_sec; + } + + void truncate(off_t size) + { + std::lock_guard lock(mutex); + std::lock_guard attr_lock(attr_mutex); + if (size < content.size()) { + content.resize(size); + } else if (size > content.size()) { + content.resize(size, 0); + } + mtime = time(NULL); + } + + void get_attr(struct stat *stbuf) const + { + std::lock_guard lock(attr_mutex); + stbuf->st_ino = ino; + stbuf->st_mode = mode; + stbuf->st_nlink = nlink; + stbuf->st_uid = uid; + stbuf->st_gid = gid; + stbuf->st_size = content.size(); + stbuf->st_blocks = DIV_ROUND_UP(content.size(), 512); + stbuf->st_atime = atime; + stbuf->st_mtime = mtime; + stbuf->st_ctime = ctime; + } + + bool is_empty() const + { + return dentries.empty(); + } + + void inc_nlink() + { + nlink++; + } + + nlink_t dec_nlink() + { + nlink_t old_value = + nlink.fetch_sub(1, std::memory_order_relaxed); + if (old_value == 0) { + memfs_panic("Attempting to decrement nlink below zero"); + } + return old_value - 1; + } + + /** + * Methods that need Dentry knowledge + */ + int add_child_locked(const std::string &name, Dentry *child_dentry); + int add_child(const std::string &name, Dentry *child_dentry); + int remove_child(const std::string &name); + std::vector > + get_children() const; + Dentry *find_child_locked(const std::string &name) const; + Dentry *find_child(const std::string &name) const; +}; + +class Dentry { + public: + std::string name; + Inode *inode; + + Dentry(const std::string &n, Inode *i) + : name(n) + , inode(i) + { + } + + uint64_t get_ino() const + { + return inode->get_ino(); + } + bool is_dir() const + { + return inode->is_dir(); + } + const std::string &get_name() const + { + return name; + } + + time_t get_ctime() const + { + return inode->get_ctime(); + } + time_t get_mtime() const + { + return inode->get_mtime(); + } + mode_t get_mode() const + { + return inode->get_mode(); + } + size_t content_size() const + { + return inode->content_size(); + } + + Inode *get_inode() const + { + return inode; + } + + void inc_lookup() + { + inode->inc_lookup(); + } +}; + +class Inodes { + private: + std::unordered_map > inodes; + mutable std::shared_mutex inodes_mutex; + std::atomic next_ino{ FUSE_ROOT_ID + 1 }; + std::mutex mutex; + + public: + Inodes() + { + auto root = std::make_unique(FUSE_ROOT_ID, "/", true); + root->mode = S_IFDIR | 0755; + root->nlink = 2; // . and .. + inodes[FUSE_ROOT_ID] = std::move(root); + } + + // New lock method + void lock() + { + inodes_mutex.lock(); + } + + // New unlock method + void unlock() + { + inodes_mutex.unlock(); + } + + void erase_locked(Inode *inode) + { + if (inode) { + inodes.erase(inode->get_ino()); + } + } + + void erase(Inode *inode) + { + std::unique_lock lock(inodes_mutex); + erase_locked(inode); + } + + Inode *find_locked(fuse_ino_t ino) + { + auto it = inodes.find(ino); + if (it == inodes.end()) { + return nullptr; + } + return it->second.get(); + } + + Inode *find(fuse_ino_t ino) + { + std::shared_lock lock(inodes_mutex); + return find_locked(ino); + } + + Inode *create(const std::string &name, bool is_dir, mode_t mode) + { + std::unique_lock lock(inodes_mutex); + + uint64_t ino = next_ino.fetch_add(1, std::memory_order_relaxed); + auto new_inode = std::make_unique(ino, name, is_dir); + new_inode->set_mode(mode); + + auto [it, inserted] = inodes.emplace(ino, std::move(new_inode)); + + if (!inserted) { + // This should never happen, but let's handle it just in case + return nullptr; + } + + return it->second.get(); + } + + size_t size() + { + std::lock_guard lock(mutex); + return inodes.size(); + } +}; + +int Inode::add_child_locked(const std::string &name, Dentry *child_dentry) +{ + if (!is_dir_) { + return ENOTDIR; + } + + // Check if a child with this name already exists + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + if (it != dentries.end()) { + return EEXIST; + } + + dentries.push_back(child_dentry); + if (child_dentry->is_dir()) { + nlink++; + } + return 0; +} + +int Inode::add_child(const std::string &name, Dentry *child_dentry) +{ + std::lock_guard lock(mutex); + return add_child_locked(name, child_dentry); +} + +int Inode::remove_child(const std::string &name) +{ + if (!is_dir_) { + return ENOTDIR; + } + + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + if (it == dentries.end()) { + return ENOENT; + } + + Dentry *child_dentry = *it; + dentries.erase(it); + + if (child_dentry->is_dir()) { + nlink--; + } + + delete child_dentry; + return 0; +} +Dentry *Inode::find_child_locked(const std::string &name) const +{ + if (!is_dir_) { + return nullptr; + } + + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + return (it != dentries.end()) ? *it : nullptr; +} + +Dentry *Inode::find_child(const std::string &name) const +{ + std::lock_guard lock(mutex); + return find_child_locked(name); +} + +std::vector > Inode::get_children() const +{ + if (!is_dir_) { + return {}; // Return an empty vector if this is not a directory + } + + std::vector > children; + children.reserve(dentries.size()); + + for (size_t i = 0; i < dentries.size(); ++i) { + const Dentry *dentry = dentries[i]; + std::string name = dentry->get_name(); + children.emplace_back(name, dentry); + } + + return children; +} +static Inodes Inodes; + +static void memfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + + if (!parentInode) { + fuse_reply_err(req, ENOENT); + return; + } + + if (!parentInode->is_dir()) { + fuse_reply_err(req, ENOTDIR); + return; + } + + Dentry *child = parentInode->find_child(name); + if (!child) { + fuse_reply_err(req, ENOENT); + return; + } + + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + e.ino = child->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + e.attr.st_ino = child->get_ino(); + e.attr.st_mode = child->get_mode(); + e.attr.st_nlink = child->is_dir() ? 2 : 1; + + child->inc_lookup(); + + fuse_reply_entry(req, &e); +} + +static void memfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + fuse_ino_t actual_ino = fi ? fi->fh : ino; + if (actual_ino == 0) { + fuse_reply_err(req, EBADF); + return; + } + + auto *inode_data = Inodes.find(actual_ino); + if (!inode_data) { + fuse_reply_err(req, ENOENT); + return; + } + + struct stat stbuf; + inode_data->get_attr(&stbuf); + stbuf.st_ino = actual_ino; // Ensure the correct inode number is set + + fuse_reply_attr(req, &stbuf, MEMFS_ATTR_TIMEOUT); +} + +static void memfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + if (parentInode->find_child(name)) { + fuse_reply_err(req, EEXIST); + return; + } + + Inode *new_inode = Inodes.create(name, false, mode); + if (!new_inode) { + fuse_reply_err(req, EIO); + return; + } + + // Create a new Dentry and add it to the parent + Dentry *new_dentry = new Dentry(name, new_inode); + + //std::cout << "Debug: Created new Dentry at address " + // << (void *)new_dentry << ", name: '" << name + // << "', inode address: " << (void *)new_inode << std::endl; + + parentInode->add_child(name, new_dentry); + + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + e.ino = new_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + new_inode->get_attr(&e.attr); + + fi->fh = e.ino; + fuse_reply_create(req, &e, fi); +} + +static void memfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t offset, + [[maybe_unused]] struct fuse_file_info *fi) +{ + Inode *inode = Inodes.find(ino); + if (!inode) { + fuse_reply_err(req, ENOENT); + return; + } + + if (inode->is_dir()) { + fuse_reply_err(req, EISDIR); + return; + } + + inode->write_content(buf, size, offset); + fuse_reply_write(req, size); +} + +static void memfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, [[maybe_unused]] struct fuse_file_info *fi) +{ + Inode *inode = Inodes.find(ino); + if (!inode || inode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + inode->lock(); + + if (offset >= inode->content_size()) { + fuse_reply_buf(req, nullptr, 0); + inode->unlock(); + return; + } + + std::vector content( + std::min(size, inode->content_size() - offset)); + inode->read_content(content.data(), content.size(), offset); + + inode->unlock(); + + fuse_reply_buf(req, content.data(), content.size()); +} + +static void memfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *inode_data = Inodes.find(ino); + if (!inode_data || inode_data->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + // Use the inode number as the file handle + fi->fh = ino; + fuse_reply_open(req, fi); +} + +static void memfs_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *inode = Inodes.find(ino); + if (!inode || !inode->is_dir()) { + fuse_reply_err(req, ENOTDIR); + return; + } + + // Create a new DirHandle + auto dir_handle = new DirHandle(inode->get_children()); + + // Store the pointer to the DirHandle in fi->fh + fi->fh = reinterpret_cast(dir_handle); + + fuse_reply_open(req, fi); +} + +static void memfs_readdir(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + auto *dir_handle = reinterpret_cast(fi->fh); + if (!dir_handle) { + fuse_reply_err(req, EBADF); + return; + } + + char *buffer = new char[size]; + size_t buf_size = 0; + + for (off_t i = offset; + i < static_cast(dir_handle->entries.size()); ++i) { + const auto &entry = dir_handle->entries[i]; + const std::string &name = entry.first; + const Dentry *dentry = entry.second; + + struct stat stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = dentry->get_inode()->get_ino(); + dentry->get_inode()->get_attr(&stbuf); + + size_t entry_size = fuse_add_direntry(req, nullptr, 0, + name.c_str(), nullptr, 0); + if (buf_size + entry_size > size) { + break; + } + + fuse_add_direntry(req, buffer + buf_size, size - buf_size, + name.c_str(), &stbuf, i + 1); + buf_size += entry_size; + } + + fuse_reply_buf(req, buffer, buf_size); + delete[] buffer; +} + +static void memfs_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + // No need to remove file handle + (void)fi; + (void)ino; + fuse_reply_err(req, 0); +} + +static void memfs_releasedir(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *dir_handle = reinterpret_cast(fi->fh); + delete dir_handle; + fuse_reply_err(req, 0); +} + +static void memfs_panic(std::string_view message) +{ + std::cerr << "MEMFS PANIC: " << message << std::endl; + std::abort(); +} + +static void memfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + Inodes.lock(); + Inode *inode = Inodes.find_locked(ino); + uint64_t res; + if (inode) { + res = inode->dec_lookup(nlookup); + if (res == 0) + Inodes.erase_locked(inode); + } + Inodes.unlock(); + fuse_reply_none(req); +} + +static void memfs_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + for (size_t i = 0; i < count; i++) { + fuse_ino_t ino = forgets[i].ino; + uint64_t nlookup = forgets[i].nlookup; + auto *inode_data = Inodes.find(ino); + if (inode_data) { + inode_data->dec_lookup(nlookup); + } + } + fuse_reply_none(req); +} + +static void memfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi) +{ + fuse_ino_t actual_ino = fi ? fi->fh : ino; + if (actual_ino == 0) { + fuse_reply_err(req, EBADF); + return; + } + + auto *inode_data = Inodes.find(actual_ino); + if (!inode_data) { + fuse_reply_err(req, ENOENT); + return; + } + + inode_data->lock(); + + if (to_set & FUSE_SET_ATTR_MODE) + inode_data->set_mode(attr->st_mode); + if (to_set & FUSE_SET_ATTR_UID) + inode_data->set_uid(attr->st_uid); + if (to_set & FUSE_SET_ATTR_GID) + inode_data->set_gid(attr->st_gid); + if (to_set & FUSE_SET_ATTR_SIZE) + inode_data->truncate(attr->st_size); + if (to_set & FUSE_SET_ATTR_ATIME) + inode_data->set_atime(attr->st_atim); + if (to_set & FUSE_SET_ATTR_MTIME) + inode_data->set_mtime(attr->st_mtim); + + struct stat st; + inode_data->get_attr(&st); + inode_data->unlock(); + + fuse_reply_attr(req, &st, MEMFS_ATTR_TIMEOUT); +} + +static void memfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + int error = 0; + Inode *parentInode = nullptr; + Inode *new_inode = nullptr; + Dentry *new_dentry = nullptr; + struct fuse_entry_param e; + + parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + error = ENOENT; + goto out; + } + + new_inode = Inodes.create(name, true, mode | S_IFDIR); + if (!new_inode) { + error = EIO; + goto out; + } + + new_dentry = new Dentry(name, new_inode); + error = parentInode->add_child(name, new_dentry); + if (error != 0) { + goto out_cleanup; + } + + memset(&e, 0, sizeof(e)); + e.ino = new_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + new_inode->get_attr(&e.attr); + +out: + if (error == 0) { + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, error); + } + return; + +out_cleanup: + delete new_dentry; + Inodes.erase_locked(new_inode); + goto out; +} + +static void memfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + parentInode->lock(); + + auto child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + + Inode *child = child_dentry->get_inode(); + if (!child || !child->is_dir() || !child->is_empty()) { + parentInode->unlock(); + fuse_reply_err(req, child ? (child->is_empty() ? ENOTDIR : + ENOTEMPTY) : + ENOENT); + return; + } + + parentInode->remove_child(name); + child->dec_nlink(); // This should handle removal if nlink reaches 0 + + parentInode->unlock(); + + fuse_reply_err(req, 0); +} + +static void memfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + parentInode->lock(); + + auto child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + + Inode *child = child_dentry->get_inode(); + if (!child || child->is_dir()) { + parentInode->unlock(); + fuse_reply_err(req, child ? EISDIR : ENOENT); + return; + } + + parentInode->remove_child(name); + child->dec_nlink(); + + parentInode->unlock(); + + fuse_reply_err(req, 0); +} + +static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + int error = 0; + Inode *parentInode = nullptr; + Inode *newparentInode = nullptr; + Dentry *child_dentry = nullptr; + Dentry *existing_dentry = nullptr; + + if (flags & (RENAME_EXCHANGE | RENAME_NOREPLACE)) { + fuse_reply_err(req, EINVAL); + return; + } + + Inodes.lock(); + + parentInode = Inodes.find(parent); + newparentInode = Inodes.find(newparent); + if (!parentInode || !parentInode->is_dir() || !newparentInode || + !newparentInode->is_dir()) { + error = ENOENT; + goto out_unlock_global; + } + + parentInode->lock(); + if (parent != newparent) { + newparentInode->lock(); + } + + child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + error = ENOENT; + goto out_unlock; + } + + existing_dentry = newparentInode->find_child_locked(newname); + if (existing_dentry) { + if (existing_dentry->is_dir()) { + if (!existing_dentry->get_inode()->is_empty()) { + error = ENOTEMPTY; + goto out_unlock; + } + newparentInode->dec_nlink(); + } + newparentInode->remove_child(newname); + existing_dentry->get_inode()->dec_nlink(); + } + + parentInode->remove_child(name); + child_dentry->name = newname; + newparentInode->add_child(newname, child_dentry); + +out_unlock: + parentInode->unlock(); + if (parent != newparent) { + newparentInode->unlock(); + } + +out_unlock_global: + Inodes.unlock(); + fuse_reply_err(req, error); +} + +static void memfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname) +{ + int error = 0; + Inode *src_inode = nullptr; + Inode *parent_inode = nullptr; + struct fuse_entry_param e; + std::unique_ptr new_dentry; + + Inodes.lock(); + + src_inode = Inodes.find(ino); + if (!src_inode) { + error = ENOENT; + goto out_unlock_global; + } + + parent_inode = Inodes.find(newparent); + if (!parent_inode || !parent_inode->is_dir()) { + error = ENOENT; + goto out_unlock_global; + } + + parent_inode->lock(); + + // Check if the new name already exists in the parent directory + if (parent_inode->find_child_locked(newname) != nullptr) { + error = EEXIST; + goto out_unlock_parent; + } + + src_inode->inc_nlink(); + + new_dentry = std::make_unique(newname, src_inode); + parent_inode->add_child(newname, new_dentry.get()); + + memset(&e, 0, sizeof(e)); + e.ino = ino; + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + src_inode->get_attr(&e.attr); + +out_unlock_parent: + parent_inode->unlock(); + +out_unlock_global: + Inodes.unlock(); + + if (error == 0) { + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, error); + } +} + +static void memfs_statfs(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino) +{ + struct statvfs stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + + stbuf.f_bsize = 4096; + stbuf.f_frsize = 4096; + stbuf.f_namemax = PATH_MAX; // Maximum filename length + + stbuf.f_files = Inodes.size(); // Total inodes (files + directories) + + stbuf.f_ffree = std::numeric_limits::max() - + stbuf.f_files; // Free inodes + + // Set total and free blocks + // For simplicity, we'll set a fixed total number of blocks and calculate free blocks based on used inodes + stbuf.f_blocks = 1000000; // arbitrary number, needs to be a parameter + stbuf.f_bfree = stbuf.f_blocks - + (stbuf.f_files * + 10); // Assume each file uses 10 blocks on average + stbuf.f_bavail = stbuf.f_bfree; + + stbuf.f_fsid = 0; + + // Set flags + stbuf.f_flag = ST_NOSUID; + + fuse_reply_statfs(req, &stbuf); +} + +static const struct fuse_lowlevel_ops memfs_oper = { + .init = nullptr, + .destroy = nullptr, + .lookup = memfs_lookup, + .forget = memfs_forget, + .getattr = memfs_getattr, + .setattr = memfs_setattr, + .readlink = nullptr, + .mknod = nullptr, + .mkdir = memfs_mkdir, + .unlink = memfs_unlink, + .rmdir = memfs_rmdir, + .symlink = nullptr, + .rename = memfs_rename, + .link = memfs_link, + .open = memfs_open, + .read = memfs_read, + .write = memfs_write, + .flush = nullptr, + .release = memfs_release, + .fsync = nullptr, + .opendir = memfs_opendir, + .readdir = memfs_readdir, + .releasedir = memfs_releasedir, + .fsyncdir = nullptr, + .statfs = memfs_statfs, + .setxattr = nullptr, + .getxattr = nullptr, + .listxattr = nullptr, + .removexattr = nullptr, + .access = nullptr, + .create = memfs_create, + .getlk = nullptr, + .setlk = nullptr, + .bmap = nullptr, + .ioctl = nullptr, + .poll = nullptr, + .write_buf = nullptr, + .retrieve_reply = nullptr, + .forget_multi = memfs_forget_multi, + .flock = nullptr, + .fallocate = nullptr, + .readdirplus = nullptr, + .copy_file_range = nullptr, + .lseek = nullptr, + .tmpfile = nullptr, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + int ret = -1; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + + if (config == NULL) { + std::cerr << "fuse_loop_cfg_create failed" << std::endl; + exit(EXIT_FAILURE); + } + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + printf("File-system specific options:\n" + " -o opt,[opt...] mount options\n" + " -h --help print help\n" + "\n"); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if (opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + se = fuse_session_new(&args, &memfs_oper, sizeof(memfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + ret = fuse_session_loop_mt(se, config); + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/meson.build b/example/meson.build index 300d0c7..5c02cc1 100644 --- a/example/meson.build +++ b/example/meson.build @@ -35,6 +35,9 @@ if not platform.endswith('bsd') and platform != 'dragonfly' and add_languages('c executable('passthrough_hp', 'passthrough_hp.cc', dependencies: [ thread_dep, libfuse_dep ], install: false) + executable('memfs_ll', 'memfs_ll.cc', + dependencies: [ thread_dep, libfuse_dep ], + install: false) endif # TODO: Link passthrough_fh with ulockmgr if available diff --git a/meson.build b/meson.build index 4b570d1..f05b94f 100644 --- a/meson.build +++ b/meson.build @@ -3,7 +3,7 @@ project('libfuse3', ['c'], version: '3.17.0', default_options: [ 'buildtype=debugoptimized', 'c_std=gnu11', - 'cpp_std=c++11', + 'cpp_std=c++17', 'warning_level=2', ]) -- 2.30.2