From 7a5e94be743c1e922d5995b747c430a7eb305eeb Mon Sep 17 00:00:00 2001 From: Antonio SJ Musumeci Date: Sun, 27 Jul 2025 16:59:59 -0500 Subject: [PATCH] fs_stat.hpp --- Makefile | 1 + libfuse/lib/ffi.h | 11 +++++ libfuse/lib/fuse.cpp | 9 ++++ src/branches.hpp | 7 +++ src/config.cpp | 2 + src/config.hpp | 7 ++- src/config_follow_symlinks.hpp | 8 +-- src/follow_symlinks_enum.hpp | 9 ++++ src/from_string.cpp | 47 ++++++++++++++++++ src/from_string.hpp | 1 + src/fs_stat.cpp | 70 ++++++++++++++++++++++++++ src/fs_stat.hpp | 18 +++++++ src/fs_statx.cpp | 90 ++++++++++++++++++++++++++++++++++ src/fs_statx.hpp | 62 ++++++++++++++++++++++- src/func_getattr.hpp | 76 ++++++++++++++++++++++++++++ src/func_getattr_base.hpp | 35 +++++++++++++ src/func_getattr_combine.cpp | 64 ++++++++++++++++++++++++ src/func_getattr_combine.hpp | 21 ++++++++ src/func_getattr_factory.cpp | 39 +++++++++++++++ src/func_getattr_factory.hpp | 13 +++++ src/func_getattr_ff.cpp | 42 ++++++++++++++++ src/func_getattr_ff.hpp | 21 ++++++++ src/func_getattr_newest.cpp | 58 ++++++++++++++++++++++ src/func_getattr_newest.hpp | 21 ++++++++ src/fuse_statx_supported.icpp | 70 +++++--------------------- src/option_parser.cpp | 60 ++++++++++++++++++----- src/symlinkify.hpp | 42 +++++++++++++--- src/timespec_utils.hpp | 38 ++++++++++++++ src/to_string.cpp | 6 +++ src/to_string.hpp | 1 + 30 files changed, 862 insertions(+), 87 deletions(-) create mode 100644 libfuse/lib/ffi.h create mode 100644 src/follow_symlinks_enum.hpp create mode 100644 src/fs_stat.cpp create mode 100644 src/fs_statx.cpp create mode 100644 src/func_getattr.hpp create mode 100644 src/func_getattr_base.hpp create mode 100644 src/func_getattr_combine.cpp create mode 100644 src/func_getattr_combine.hpp create mode 100644 src/func_getattr_factory.cpp create mode 100644 src/func_getattr_factory.hpp create mode 100644 src/func_getattr_ff.cpp create mode 100644 src/func_getattr_ff.hpp create mode 100644 src/func_getattr_newest.cpp create mode 100644 src/func_getattr_newest.hpp create mode 100644 src/timespec_utils.hpp diff --git a/Makefile b/Makefile index a2c7a260..2afd4de8 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,7 @@ CXXFLAGS ?= ${OPT_FLAGS} CXXFLAGS := \ ${CXXFLAGS} \ -std=c++17 \ + -fno-rtti \ $(STATIC_FLAGS) \ $(LTO_FLAGS) \ -Isrc \ diff --git a/libfuse/lib/ffi.h b/libfuse/lib/ffi.h new file mode 100644 index 00000000..5fc4b4e0 --- /dev/null +++ b/libfuse/lib/ffi.h @@ -0,0 +1,11 @@ +struct ffi +{ + uint64_t nodeid; + uint64_t gen; + uid_t uid; + gid_t gid; + pid_t pid; + uint64_t fh; + char *name; + char path[0]; +}; diff --git a/libfuse/lib/fuse.cpp b/libfuse/lib/fuse.cpp index f2c93e2b..8f5fe7b2 100644 --- a/libfuse/lib/fuse.cpp +++ b/libfuse/lib/fuse.cpp @@ -1567,6 +1567,9 @@ fuse_lib_lookup(fuse_req_t req, nodeid = hdr_->nodeid; f = req_fuse_prepare(req); + fmt::print("lookup: {}\n", + nodeid); + if(name[0] == '.') { if(name[1] == '\0') @@ -1685,6 +1688,9 @@ fuse_lib_getattr(fuse_req_t req, arg = (fuse_getattr_in*)fuse_hdr_arg(hdr_); f = req_fuse_prepare(req); + fmt::print("getattr: {}\n", + hdr_->nodeid); + fh = 0; if(arg->getattr_flags & FUSE_GETATTR_FH) fh = arg->fh; @@ -1789,6 +1795,9 @@ fuse_lib_statx(fuse_req_t req_, struct fuse *f = req_fuse_prepare(req_); fuse_statx_in *inarg; + fmt::print("statx: {}\n", + hdr_->nodeid); + inarg = (fuse_statx_in*)fuse_hdr_arg(hdr_); if(inarg->getattr_flags & FUSE_GETATTR_FH) diff --git a/src/branches.hpp b/src/branches.hpp index 31487f40..b6e3833a 100644 --- a/src/branches.hpp +++ b/src/branches.hpp @@ -80,6 +80,13 @@ public: operator Ptr() const { std::lock_guard lg(_mutex); return _impl; } Ptr operator->() const { std::lock_guard lg(_mutex); return _impl; } +public: + Impl::iterator begin() { return _impl->begin(); } + Impl::iterator end() { return _impl->end(); } + Impl::const_iterator begin() const { return _impl->begin(); } + Impl::const_iterator end() const { return _impl->end(); } + + public: void find_and_set_mode_ro(); }; diff --git a/src/config.cpp b/src/config.cpp index d5408bb4..bc80f88b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -223,6 +223,8 @@ Config::Config() _map["process-thread-queue-depth"] = &fuse_process_thread_queue_depth; _map["version"] = &version; _map["xattr"] = &xattr; + + _map["func2.getattr"] = &getattr; } Config& diff --git a/src/config.hpp b/src/config.hpp index 879bf2e4..70318fc3 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -16,6 +16,8 @@ #pragma once +#include "func_getattr.hpp" + #include "branches.hpp" #include "category.hpp" #include "config_cachefiles.hpp" @@ -56,6 +58,7 @@ typedef ToFromWrapper ConfigBOOL; typedef ToFromWrapper ConfigUINT64; +typedef ToFromWrapper ConfigS64; typedef ToFromWrapper ConfigINT; typedef ToFromWrapper ConfigSTR; typedef ToFromWrapper ConfigPath; @@ -161,7 +164,7 @@ public: StatFS statfs; StatFSIgnore statfs_ignore; ConfigBOOL symlinkify; - ConfigUINT64 symlinkify_timeout; + ConfigS64 symlinkify_timeout; ConfigINT fuse_read_thread_count; ConfigINT fuse_process_thread_count; ConfigINT fuse_process_thread_queue_depth; @@ -170,6 +173,8 @@ public: ConfigBOOL writeback_cache; XAttr xattr; + Func2::GetAttr getattr{"combine"}; + private: bool _initialized; diff --git a/src/config_follow_symlinks.hpp b/src/config_follow_symlinks.hpp index 1ebbdecf..dadaaffb 100644 --- a/src/config_follow_symlinks.hpp +++ b/src/config_follow_symlinks.hpp @@ -18,13 +18,7 @@ #pragma once +#include "follow_symlinks_enum.hpp" #include "enum.hpp" -enum class FollowSymlinksEnum - { - NEVER, - DIRECTORY, - REGULAR, - ALL - }; typedef Enum FollowSymlinks; diff --git a/src/follow_symlinks_enum.hpp b/src/follow_symlinks_enum.hpp new file mode 100644 index 00000000..c708b898 --- /dev/null +++ b/src/follow_symlinks_enum.hpp @@ -0,0 +1,9 @@ +#pragma once + +enum class FollowSymlinksEnum + { + NEVER, + DIRECTORY, + REGULAR, + ALL + }; diff --git a/src/from_string.cpp b/src/from_string.cpp index a4a408fc..50ad2b4a 100644 --- a/src/from_string.cpp +++ b/src/from_string.cpp @@ -65,6 +65,53 @@ namespace str return 0; } + int + from(const std::string &value_, + int64_t *int64_) + { + char *endptr; + int64_t tmp; + + tmp = ::strtoll(value_.c_str(),&endptr,10); + switch(*endptr) + { + case 'b': + case 'B': + tmp *= 1ULL; + break; + + case 'k': + case 'K': + tmp *= 1024ULL; + break; + + case 'm': + case 'M': + tmp *= (1024ULL * 1024ULL); + break; + + case 'g': + case 'G': + tmp *= (1024ULL * 1024ULL * 1024ULL); + break; + + case 't': + case 'T': + tmp *= (1024ULL * 1024ULL * 1024ULL * 1024ULL); + break; + + case '\0': + break; + + default: + return -EINVAL; + } + + *int64_ = tmp; + + return 0; + } + int from(const std::string &value_, uint64_t *uint64_) diff --git a/src/from_string.hpp b/src/from_string.hpp index 5944f6c7..fd8c4fce 100644 --- a/src/from_string.hpp +++ b/src/from_string.hpp @@ -29,6 +29,7 @@ namespace str int from(const std::string &, bool *); int from(const std::string &, int *); int from(const std::string &, uint64_t *); + int from(const std::string &, int64_t *); int from(const std::string &, std::string *); int from(const std::string &, const std::string *); int from(const std::string &, fs::Path *); diff --git a/src/fs_stat.cpp b/src/fs_stat.cpp new file mode 100644 index 00000000..c894a403 --- /dev/null +++ b/src/fs_stat.cpp @@ -0,0 +1,70 @@ +#include "fs_stat.hpp" +#include "fs_lstat.hpp" + +static +void +_set_stat_if_leads_to_dir(const char *path_, + struct stat *st_) +{ + int rv; + struct stat st; + + rv = fs::stat(path_,&st); + if(rv < 0) + return; + + if(S_ISDIR(st.st_mode)) + *st_ = st; + + return; +} + +static +void +_set_stat_if_leads_to_reg(const char *path_, + struct stat *st_) +{ + int rv; + struct stat st; + + rv = fs::stat(path_,&st); + if(rv < 0) + return; + + if(S_ISREG(st.st_mode)) + *st_ = st; + + return; +} + +int +fs::stat(const char *path_, + struct stat *st_, + FollowSymlinksEnum follow_) +{ + int rv; + + switch(follow_) + { + case FollowSymlinksEnum::NEVER: + rv = fs::lstat(path_,st_); + break; + case FollowSymlinksEnum::DIRECTORY: + rv = fs::lstat(path_,st_); + if(S_ISLNK(st_->st_mode)) + _set_stat_if_leads_to_dir(path_,st_); + break; + case FollowSymlinksEnum::REGULAR: + rv = fs::lstat(path_,st_); + if(S_ISLNK(st_->st_mode)) + _set_stat_if_leads_to_reg(path_,st_); + break; + case FollowSymlinksEnum::ALL: + rv = fs::stat(path_,st_); + if(rv != 0) + rv = fs::lstat(path_,st_); + break; + } + + return rv; +} diff --git a/src/fs_stat.hpp b/src/fs_stat.hpp index 0e85f7f3..5ffa9f08 100644 --- a/src/fs_stat.hpp +++ b/src/fs_stat.hpp @@ -18,6 +18,7 @@ #pragma once +#include "follow_symlinks_enum.hpp" #include "to_neg_errno.hpp" #include @@ -50,4 +51,21 @@ namespace fs { return fs::stat(path_.c_str(),st_); } + + int + stat(const char *path, + struct stat *st, + FollowSymlinksEnum follow); + + static + inline + int + stat(const std::string &path_, + struct stat *st_, + FollowSymlinksEnum follow_) + { + return fs::stat(path_.c_str(), + st_, + follow_); + } } diff --git a/src/fs_statx.cpp b/src/fs_statx.cpp new file mode 100644 index 00000000..9fc1753c --- /dev/null +++ b/src/fs_statx.cpp @@ -0,0 +1,90 @@ +/* + ISC License + + Copyright (c) 2025, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "fs_statx.hpp" + +static +void +_set_stat_if_leads_to_dir(const std::string &path_, + struct fuse_statx *st_) +{ + int rv; + struct fuse_statx st; + + rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,&st); + if(rv < 0) + return; + + if(S_ISDIR(st.mode)) + *st_ = st; + + return; +} + +static +void +_set_stat_if_leads_to_reg(const std::string &path_, + struct fuse_statx *st_) +{ + int rv; + struct fuse_statx st; + + rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,&st); + if(rv < 0) + return; + + if(S_ISREG(st.mode)) + *st_ = st; + + return; +} + +int +fs::statx(const int dirfd_, + const char *pathname_, + const int flags_, + const unsigned int mask_, + struct fuse_statx *st_, + FollowSymlinksEnum follow_) +{ + int rv; + + switch(follow_) + { + case FollowSymlinksEnum::NEVER: + rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); + break; + case FollowSymlinksEnum::DIRECTORY: + rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); + if((rv >= 0) && S_ISLNK(st_->mode)) + ::_set_stat_if_leads_to_dir(pathname_,st_); + break; + case FollowSymlinksEnum::REGULAR: + rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); + if((rv >= 0) && S_ISLNK(st_->mode)) + ::_set_stat_if_leads_to_reg(pathname_,st_); + break; + case FollowSymlinksEnum::ALL: + rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_FOLLOW,mask_,st_); + if(rv < 0) + rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); + break; + } + + return rv; +} diff --git a/src/fs_statx.hpp b/src/fs_statx.hpp index 9fb48eae..fba93998 100644 --- a/src/fs_statx.hpp +++ b/src/fs_statx.hpp @@ -1,6 +1,7 @@ #pragma once #include "to_neg_errno.hpp" +#include "follow_symlinks_enum.hpp" #include "fuse_kernel.h" @@ -14,7 +15,7 @@ #include #ifdef STATX_TYPE -#define MERGERFS_STATX_SUPPORTED +//#define MERGERFS_STATX_SUPPORTED #endif namespace fs @@ -54,4 +55,63 @@ namespace fs { return fs::statx(dirfd_,pathname_.c_str(),flags_,mask_,st_); } + + int + statx(const int dirfd, + const char *pathname, + const int flags, + const unsigned int mask, + struct fuse_statx *st, + FollowSymlinksEnum follow); + + static + inline + int + statx(const int dirfd_, + const std::string &pathname_, + const int flags_, + const unsigned int mask_, + struct fuse_statx *st_, + FollowSymlinksEnum follow_) + { + return fs::statx(dirfd_, + pathname_.c_str(), + flags_, + mask_, + st_, + follow_); + } + + static + inline + int + statx(const char *pathname_, + const int flags_, + const unsigned int mask_, + struct fuse_statx *st_, + FollowSymlinksEnum follow_) + { + return fs::statx(AT_FDCWD, + pathname_, + flags_, + mask_, + st_, + follow_); + } + + static + inline + int + statx(const std::string &pathname_, + const int flags_, + const unsigned int mask_, + struct fuse_statx *st_, + FollowSymlinksEnum follow_) + { + return fs::statx(pathname_.c_str(), + flags_, + mask_, + st_, + follow_); + } } diff --git a/src/func_getattr.hpp b/src/func_getattr.hpp new file mode 100644 index 00000000..d05261f2 --- /dev/null +++ b/src/func_getattr.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "func_getattr_base.hpp" +#include "func_getattr_factory.hpp" + +#include "tofrom_string.hpp" + +#include "fmt/core.h" + +#include + +#include + +namespace Func2 +{ + class GetAttr : public ToFromString + { + private: + std::shared_ptr _impl; + + public: + GetAttr() + { + } + + GetAttr(const std::string &name_) + { + _impl = Func2::GetAttrFactory::make(name_); + assert(_impl); + } + + public: + int + operator()(const Branches &branches_, + const fs::Path &fusepath_, + struct stat *st_, + const FollowSymlinksEnum follow_symlinks_, + const s64 symlinkify_timeout_) + { + std::shared_ptr p; + + p = std::atomic_load(&_impl); + + return (*p)(branches_, + fusepath_, + st_, + follow_symlinks_, + symlinkify_timeout_); + } + + public: + std::string + to_string() const + { + std::shared_ptr p; + + p = std::atomic_load(&_impl); + + return std::string(p->name()); + } + + int + from_string(const std::string &str_) + { + std::shared_ptr p; + + p = Func2::GetAttrFactory::make(str_); + if(!p) + return -EINVAL; + + _impl = std::atomic_load(&p); + + return 0; + } + }; +} diff --git a/src/func_getattr_base.hpp b/src/func_getattr_base.hpp new file mode 100644 index 00000000..86220276 --- /dev/null +++ b/src/func_getattr_base.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "follow_symlinks_enum.hpp" + +#include "fs_path.hpp" +#include "branches.hpp" +#include "int_types.h" + +#include "fuse.h" + +#include + +#include +#include +#include + +namespace Func2 +{ + class GetAttrBase + { + public: + GetAttrBase() {} + ~GetAttrBase() {} + + public: + virtual std::string_view name() const = 0; + + public: + virtual int operator()(const Branches &branches, + const fs::Path &fusepath, + struct stat *st, + const FollowSymlinksEnum follow_symlinks, + const s64 symlinkify_timeout) = 0; + }; +} diff --git a/src/func_getattr_combine.cpp b/src/func_getattr_combine.cpp new file mode 100644 index 00000000..beae03f4 --- /dev/null +++ b/src/func_getattr_combine.cpp @@ -0,0 +1,64 @@ +#include "func_getattr_combine.hpp" + +#include "fs_inode.hpp" +#include "fs_stat.hpp" +#include "symlinkify.hpp" +#include "timespec_utils.hpp" + + +std::string_view +Func2::GetAttrCombine::name() const +{ + return "combine"; +} + +int +Func2::GetAttrCombine::operator()(const Branches &branches_, + const fs::Path &fusepath_, + struct stat *st_, + const FollowSymlinksEnum follow_symlinks_, + const s64 symlinkify_timeout_) +{ + int rv; + fs::Path fullpath; + const Branch *first_branch; + + first_branch = nullptr; + for(const auto &branch : branches_) + { + struct stat st; + + fullpath = branch.path; + fullpath += fusepath_; + rv = fs::stat(fullpath.c_str(),&st,follow_symlinks_); + if(rv == -1) + continue; + + if(!first_branch) + { + *st_ = st; + first_branch = &branch; + continue; + } + + st_->st_atim = TimeSpec::newest(st_->st_atim,st.st_atim); + st_->st_ctim = TimeSpec::newest(st_->st_ctim,st.st_ctim); + st_->st_mtim = TimeSpec::newest(st_->st_mtim,st.st_mtim); + st_->st_nlink += st.st_nlink; + } + + if(!first_branch) + return -ENOENT; + + if(symlinkify_timeout_ >= 0) + { + fullpath = fs::path::make(first_branch->path,fusepath_); + symlinkify::convert_if_can_be_symlink(fullpath, + st_, + symlinkify_timeout_); + } + + fs::inode::calc(first_branch->path,fusepath_.string(),st_); + + return 0; +} diff --git a/src/func_getattr_combine.hpp b/src/func_getattr_combine.hpp new file mode 100644 index 00000000..609a712b --- /dev/null +++ b/src/func_getattr_combine.hpp @@ -0,0 +1,21 @@ +#include "func_getattr_base.hpp" + +namespace Func2 +{ + class GetAttrCombine : public GetAttrBase + { + public: + GetAttrCombine() {} + ~GetAttrCombine() {} + + public: + std::string_view name() const; + + public: + int operator()(const Branches &branches, + const fs::Path &fusepath, + struct stat *st, + const FollowSymlinksEnum follow_symlinks, + const s64 symlinkify_timeout); + }; +} diff --git a/src/func_getattr_factory.cpp b/src/func_getattr_factory.cpp new file mode 100644 index 00000000..51d13afc --- /dev/null +++ b/src/func_getattr_factory.cpp @@ -0,0 +1,39 @@ +#include "func_getattr_factory.hpp" + +#include "func_getattr_combine.hpp" +#include "func_getattr_ff.hpp" +#include "func_getattr_newest.hpp" + +#include "policies.hpp" + +bool +Func2::GetAttrFactory::valid(const std::string str_) +{ + if(str_ == "combined") + return true; + if(str_ == "ff") + return true; + if(str_ == "newest") + return true; + + if(Policies::Search::find(str_)) + return true; + + return false; +} + +std::shared_ptr +Func2::GetAttrFactory::make(const std::string str_) +{ + if(str_ == "combine") + return std::make_shared(); + if(str_ == "ff") + return std::make_shared(); + if(str_ == "newest") + return std::make_shared(); + + if(Policies::Search::find(str_)) + return std::make_shared(); + + return {}; +} diff --git a/src/func_getattr_factory.hpp b/src/func_getattr_factory.hpp new file mode 100644 index 00000000..1b353d8e --- /dev/null +++ b/src/func_getattr_factory.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "func_getattr_base.hpp" + +namespace Func2 +{ + class GetAttrFactory + { + public: + static bool valid(const std::string str); + static std::shared_ptr make(const std::string str); + }; +} diff --git a/src/func_getattr_ff.cpp b/src/func_getattr_ff.cpp new file mode 100644 index 00000000..26b34955 --- /dev/null +++ b/src/func_getattr_ff.cpp @@ -0,0 +1,42 @@ +#include "func_getattr_ff.hpp" + +#include "fs_stat.hpp" +#include "fs_inode.hpp" +#include "symlinkify.hpp" + + +std::string_view +Func2::GetAttrFF::name() const +{ + return "ff"; +} + +int +Func2::GetAttrFF::operator()(const Branches &branches_, + const fs::Path &fusepath_, + struct stat *st_, + const FollowSymlinksEnum follow_symlinks_, + const s64 symlinkify_timeout_) +{ + int rv; + fs::Path fullpath; + + for(const auto &branch : branches_) + { + fullpath = branch.path; + fullpath += fusepath_; + rv = fs::stat(fullpath.string(),st_,follow_symlinks_); + if(rv != 0) + continue; + + symlinkify::convert_if_can_be_symlink(fullpath, + st_, + symlinkify_timeout_); + + fs::inode::calc(branch.path,fusepath_.string(),st_); + + return 0; + } + + return -ENOENT; +} diff --git a/src/func_getattr_ff.hpp b/src/func_getattr_ff.hpp new file mode 100644 index 00000000..a27d432a --- /dev/null +++ b/src/func_getattr_ff.hpp @@ -0,0 +1,21 @@ +#include "func_getattr_base.hpp" + +namespace Func2 +{ + class GetAttrFF : public GetAttrBase + { + public: + GetAttrFF() {} + ~GetAttrFF() {} + + public: + std::string_view name() const; + + public: + int operator()(const Branches &branches, + const fs::Path &fusepath, + struct stat *st, + const FollowSymlinksEnum follow_symlinks, + const s64 symlinkify_timeout); + }; +} diff --git a/src/func_getattr_newest.cpp b/src/func_getattr_newest.cpp new file mode 100644 index 00000000..588b7efa --- /dev/null +++ b/src/func_getattr_newest.cpp @@ -0,0 +1,58 @@ +#include "func_getattr_newest.hpp" + +#include "fs_stat.hpp" +#include "fs_inode.hpp" +#include "timespec_utils.hpp" +#include "symlinkify.hpp" + + +std::string_view +Func2::GetAttrNewest::name() const +{ + return "newest"; +} + +int +Func2::GetAttrNewest::operator()(const Branches &branches_, + const fs::Path &fusepath_, + struct stat *st_, + const FollowSymlinksEnum follow_symlinks_, + const s64 symlinkify_timeout_) +{ + int rv; + fs::Path fullpath; + const Branch *newest_branch; + + newest_branch = nullptr; + for(const auto &branch : branches_) + { + struct stat tmp_st; + + fullpath = branch.path; + fullpath += fusepath_; + rv = fs::stat(fullpath,&tmp_st,follow_symlinks_); + if(rv == -1) + continue; + + if(!TimeSpec::is_newer(tmp_st.st_mtim,st_->st_mtim)) + continue; + + *st_ = tmp_st; + newest_branch = &branch; + } + + if(!newest_branch) + return -ENOENT; + + if(symlinkify_timeout_ >= 0) + { + fullpath = fs::path::make(newest_branch->path,fusepath_); + symlinkify::convert_if_can_be_symlink(fullpath, + st_, + symlinkify_timeout_); + } + + fs::inode::calc(newest_branch->path,fusepath_.string(),st_); + + return 0; +} diff --git a/src/func_getattr_newest.hpp b/src/func_getattr_newest.hpp new file mode 100644 index 00000000..81c1b2e1 --- /dev/null +++ b/src/func_getattr_newest.hpp @@ -0,0 +1,21 @@ +#include "func_getattr_base.hpp" + +namespace Func2 +{ + class GetAttrNewest : public GetAttrBase + { + public: + GetAttrNewest() {} + ~GetAttrNewest() {} + + public: + std::string_view name() const; + + public: + int operator()(const Branches &branches, + const fs::Path &fusepath, + struct stat *st, + const FollowSymlinksEnum follow_symlinks, + const s64 symlinkify_timeout); + }; +} diff --git a/src/fuse_statx_supported.icpp b/src/fuse_statx_supported.icpp index 33731f59..0810f8a8 100644 --- a/src/fuse_statx_supported.icpp +++ b/src/fuse_statx_supported.icpp @@ -36,41 +36,6 @@ #include -static -void -_set_stat_if_leads_to_dir(const std::string &path_, - struct fuse_statx *st_) -{ - int rv; - struct fuse_statx st; - - rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,&st); - if(rv < 0) - return; - - if(S_ISDIR(st.mode)) - *st_ = st; - - return; -} - -static -void -_set_stat_if_leads_to_reg(const std::string &path_, - struct fuse_statx *st_) -{ - int rv; - struct fuse_statx st; - - rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,&st); - if(rv < 0) - return; - - if(S_ISREG(st.mode)) - *st_ = st; - - return; -} static int @@ -134,28 +99,7 @@ _statx(const Policy::Search &searchFunc_, fullpath = fs::path::make(branches[0]->path,fusepath_); - switch(followsymlinks_) - { - case FollowSymlinks::ENUM::NEVER: - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); - break; - case FollowSymlinks::ENUM::DIRECTORY: - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); - if((rv >= 0) && S_ISLNK(st_->mode)) - ::_set_stat_if_leads_to_dir(fullpath,st_); - break; - case FollowSymlinks::ENUM::REGULAR: - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); - if((rv >= 0) && S_ISLNK(st_->mode)) - ::_set_stat_if_leads_to_reg(fullpath,st_); - break; - case FollowSymlinks::ENUM::ALL: - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_FOLLOW,mask_,st_); - if(rv < 0) - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); - break; - } - + rv = fs::statx(fullpath,flags_,mask_,st_,followsymlinks_); if(rv < 0) return rv; @@ -210,7 +154,11 @@ FUSE::statx(const char *fusepath_, if(Config::is_ctrl_file(fusepath_)) return ::_statx_controlfile(st_); - return ::_statx(fusepath_,flags_|AT_STATX_DONT_SYNC,mask_,st_,timeout_); + return ::_statx(fusepath_, + flags_|AT_STATX_DONT_SYNC, + mask_, + st_, + timeout_); } int @@ -222,5 +170,9 @@ FUSE::statx_fh(const uint64_t fh_, { FileInfo *fi = reinterpret_cast(fh_); - return ::_statx(fi->fusepath.c_str(),flags_,mask_,st_,timeout_); + return ::_statx(fi->fusepath.c_str(), + flags_|AT_STATX_DONT_SYNC, + mask_, + st_, + timeout_); } diff --git a/src/option_parser.cpp b/src/option_parser.cpp index 643c7d6f..fe5f26c8 100644 --- a/src/option_parser.cpp +++ b/src/option_parser.cpp @@ -56,7 +56,6 @@ enum MERGERFS_OPT_VERSION }; - static void _set_option(const std::string &option_, @@ -204,7 +203,7 @@ _parse_and_process_kv_arg(Config::Write &cfg_, static int -process_opt(Config::Write &cfg_, +_process_opt(Config::Write &cfg_, Config::ErrVec *errs_, const std::string &arg_) { @@ -220,7 +219,7 @@ process_opt(Config::Write &cfg_, static int -process_branches(Config::Write &cfg_, +_process_branches(Config::Write &cfg_, Config::ErrVec *errs_, const char *arg_) { @@ -237,7 +236,7 @@ process_branches(Config::Write &cfg_, static int -process_mount(Config::Write &cfg_, +_process_mount(Config::Write &cfg_, Config::ErrVec *errs_, const char *arg_) { @@ -290,10 +289,10 @@ _usage(void) static int -option_processor(void *data_, - const char *arg_, - int key_, - fuse_args *outargs_) +_option_processor(void *data_, + const char *arg_, + int key_, + fuse_args *outargs_) { Config::Write cfg; Config::ErrVec *errs = (Config::ErrVec*)data_; @@ -301,13 +300,13 @@ option_processor(void *data_, switch(key_) { case FUSE_OPT_KEY_OPT: - return process_opt(cfg,errs,arg_); + return ::_process_opt(cfg,errs,arg_); case FUSE_OPT_KEY_NONOPT: if(cfg->branches->empty()) - return process_branches(cfg,errs,arg_); + return ::_process_branches(cfg,errs,arg_); else - return process_mount(cfg,errs,arg_); + return ::_process_mount(cfg,errs,arg_); case MERGERFS_OPT_HELP: ::_usage(); @@ -367,6 +366,41 @@ _check_for_mount_loop(Config::Write &cfg_, } } +static +void +_print_warnings(Config::Write &cfg_) +{ + if(cfg_->passthrough != Passthrough::ENUM::OFF) + { + if(cfg_->cache_files == CacheFiles::ENUM::OFF) + { + SysLog::warning("'cache.files' can not be 'off' when using 'passthrough'." + " Setting 'cache.files=auto-full'"); + cfg_->cache_files = CacheFiles::ENUM::AUTO_FULL; + } + + if(cfg_->writeback_cache == true) + { + SysLog::warning("'cache.writeback' can not be enabled when using 'passthrough'." + " Setting 'cache.writeback=false'"); + cfg_->writeback_cache = false; + } + + if(cfg_->moveonenospc.enabled == true) + { + SysLog::warning("`moveonenospc` will not function when `passthrough` is enabled"); + } + } +} + +static +void +_cleanup_options(Config::Write &cfg_) +{ + if(!cfg_->symlinkify) + cfg_->symlinkify_timeout = -1; +} + namespace options { void @@ -387,7 +421,7 @@ namespace options fuse_opt_parse(args_, errs_, opts, - ::option_processor); + ::_option_processor); if(cfg->branches->empty()) errs_->push_back({0,"branches not set"}); @@ -400,6 +434,8 @@ namespace options ::_set_fsname(cfg,args_); ::_set_subtype(args_); ::_set_fuse_threads(cfg); + ::_print_warnings(cfg); + ::_cleanup_options(cfg); cfg->finish_initializing(); } diff --git a/src/symlinkify.hpp b/src/symlinkify.hpp index 3a82d4aa..590003b2 100644 --- a/src/symlinkify.hpp +++ b/src/symlinkify.hpp @@ -19,9 +19,7 @@ #pragma once #include "fuse_kernel.h" - -#include -#include +#include "int_types.h" #include @@ -32,13 +30,13 @@ namespace symlinkify inline bool can_be_symlink(const struct stat &st_, - const time_t timeout_) + const s64 timeout_) { if(S_ISDIR(st_.st_mode) || (st_.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))) return false; - const time_t now = ::time(NULL); + const s64 now = ::time(NULL); return (((now - st_.st_mtime) > timeout_) && ((now - st_.st_ctime) > timeout_)); @@ -48,13 +46,13 @@ namespace symlinkify inline bool can_be_symlink(const struct fuse_statx &st_, - const time_t timeout_) + const s64 timeout_) { if(S_ISDIR(st_.mode) || (st_.mode & (S_IWUSR|S_IWGRP|S_IWOTH))) return false; - const time_t now = ::time(NULL); + const s64 now = ::time(NULL); return (((now - st_.mtime.tv_sec) > timeout_) && ((now - st_.ctime.tv_sec) > timeout_)); @@ -81,4 +79,34 @@ namespace symlinkify st_->size = target_.size(); st_->blocks = 0; } + + static + inline + void + convert_if_can_be_symlink(const std::string &target_, + struct stat *st_, + const s64 timeout_) + { + if(timeout_ < 0) + return; + if(!symlinkify::can_be_symlink(*st_,timeout_)) + return; + + symlinkify::convert(target_,st_); + } + + static + inline + void + convert_if_can_be_symlink(const std::string &target_, + fuse_statx *st_, + const s64 timeout_) + { + if(timeout_ < 0) + return; + if(!symlinkify::can_be_symlink(*st_,timeout_)) + return; + + symlinkify::convert(target_,st_); + } } diff --git a/src/timespec_utils.hpp b/src/timespec_utils.hpp new file mode 100644 index 00000000..c4defa00 --- /dev/null +++ b/src/timespec_utils.hpp @@ -0,0 +1,38 @@ +#pragma once + +namespace TimeSpec +{ + static + inline + bool + is_newer(const timespec &t0_, + const timespec &t1_) + { + if(t0_.tv_sec > t1_.tv_sec) + return true; + if(t0_.tv_sec == t1_.tv_sec) + { + if(t0_.tv_nsec > t1_.tv_nsec) + return true; + } + + return false; + } + + static + inline + timespec + newest(const timespec &t0_, + const timespec &t1_) + { + if(t0_.tv_sec > t1_.tv_sec) + return t0_; + if(t0_.tv_sec == t1_.tv_sec) + { + if(t0_.tv_nsec > t1_.tv_nsec) + return t0_; + } + + return t1_; + } +} diff --git a/src/to_string.cpp b/src/to_string.cpp index c16cd093..11765bad 100644 --- a/src/to_string.cpp +++ b/src/to_string.cpp @@ -46,6 +46,12 @@ namespace str return fmt::format("{}",uint64_); } + std::string + to(const int64_t int64_) + { + return fmt::format("{}",int64_); + } + std::string to(const std::string &s_) { diff --git a/src/to_string.hpp b/src/to_string.hpp index d02bca07..4e4690ba 100644 --- a/src/to_string.hpp +++ b/src/to_string.hpp @@ -29,6 +29,7 @@ namespace str std::string to(const bool); std::string to(const int); std::string to(const uint64_t); + std::string to(const int64_t); std::string to(const std::string&); std::string to(const fs::Path&); }