FUSE: pass Branches::Ptr by value with empty-branches guards

Previously most FUSE ops took a reference to the live cfg.branches
object. With atomic_load now producing a shared_ptr snapshot, pass
it by value: the op works against a single stable view of the
configuration for its whole lifetime, immune to concurrent
reconfiguration.

Also bail out with -ENOENT when the policy returns an empty branch
set rather than dereferencing branches[0]. This was reachable if a
policy rejected every branch (e.g. everything readonly while
looking for a writable target).

Covers access, chmod, chown, create, getattr, getxattr, ioctl,
link, listxattr, mkdir, mknod, open, readdir (cor/cosr),
readlink, removexattr, rename, rmdir, setxattr, statfs, statx,
symlink, truncate, unlink, utimens.

While here, in _getattr only dereference st_->st_mode when lstat
succeeded (it is otherwise uninitialized).
This commit is contained in:
Antonio S. Musumeci
2026-04-18 13:47:23 -05:00
committed by trapexit
parent 98648ae44b
commit 0c17492c78
25 changed files with 110 additions and 45 deletions
+3 -1
View File
@@ -30,7 +30,7 @@
static
int
_access(const Policy::Search &searchFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const int mask_)
{
@@ -42,6 +42,8 @@ _access(const Policy::Search &searchFunc_,
rv = searchFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
fullpath = branches[0]->path / fusepath_;
+5 -1
View File
@@ -64,7 +64,7 @@ static
int
_chmod(const Policy::Action &actionFunc_,
const Policy::Search &searchFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const mode_t mode_)
{
@@ -75,6 +75,8 @@ _chmod(const Policy::Action &actionFunc_,
rv = actionFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
::_chmod_loop(branches,fusepath_,mode_,&prv);
if(prv.errors.empty())
@@ -86,6 +88,8 @@ _chmod(const Policy::Action &actionFunc_,
rv = searchFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
return prv.get_error(branches[0]->path);
}
+5 -1
View File
@@ -66,7 +66,7 @@ static
int
_chown(const Policy::Action &actionFunc_,
const Policy::Search &searchFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const uid_t uid_,
const gid_t gid_)
@@ -78,6 +78,8 @@ _chown(const Policy::Action &actionFunc_,
rv = actionFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
::_chown_loop(branches,fusepath_,uid_,gid_,&prv);
@@ -90,6 +92,8 @@ _chown(const Policy::Action &actionFunc_,
rv = searchFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
return prv.get_error(branches[0]->path);
}
+5 -1
View File
@@ -207,7 +207,7 @@ int
_create(const ugid_t ugid_,
const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
fuse_file_info_t *ffi_,
const mode_t mode_,
@@ -224,10 +224,14 @@ _create(const ugid_t ugid_,
rv = searchFunc_(branches_,fusedirpath,existingpaths);
if(rv < 0)
return rv;
if(existingpaths.empty())
return -ENOENT;
rv = createFunc_(branches_,fusedirpath,createpaths);
if(rv < 0)
return rv;
if(createpaths.empty())
return -ENOENT;
rv = fs::clonepath(existingpaths[0]->path,
createpaths[0]->path,
+5 -3
View File
@@ -120,7 +120,7 @@ _getattr_controlfile(struct stat *st_)
static
int
_getattr(const Policy::Search &searchFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
struct stat *st_,
const bool symlinkify_,
@@ -134,6 +134,8 @@ _getattr(const Policy::Search &searchFunc_,
rv = searchFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
fullpath = branches[0]->path / fusepath_;
@@ -144,12 +146,12 @@ _getattr(const Policy::Search &searchFunc_,
break;
case FollowSymlinks::ENUM::DIRECTORY:
rv = fs::lstat(fullpath,st_);
if(S_ISLNK(st_->st_mode))
if((rv >= 0) && S_ISLNK(st_->st_mode))
::_set_stat_if_leads_to_dir(fullpath,st_);
break;
case FollowSymlinks::ENUM::REGULAR:
rv = fs::lstat(fullpath,st_);
if(S_ISLNK(st_->st_mode))
if((rv >= 0) && S_ISLNK(st_->st_mode))
::_set_stat_if_leads_to_reg(fullpath,st_);
break;
case FollowSymlinks::ENUM::ALL:
+5 -3
View File
@@ -97,7 +97,7 @@ _getxattr_from_string(char *destbuf_,
static
int
_getxattr_user_mergerfs_allpaths(const Branches::Ptr &branches_,
_getxattr_user_mergerfs_allpaths(const Branches::Ptr branches_,
const fs::path &fusepath_,
char *buf_,
const size_t count_)
@@ -120,7 +120,7 @@ int
_getxattr_user_mergerfs(const fs::path &basepath_,
const fs::path &fusepath_,
const fs::path &fullpath_,
const Branches &branches_,
const Branches::Ptr branches_,
const char *attrname_,
char *buf_,
const size_t count_)
@@ -144,7 +144,7 @@ _getxattr_user_mergerfs(const fs::path &basepath_,
static
int
_getxattr(const Policy::Search &searchFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const char *attrname_,
char *buf_,
@@ -157,6 +157,8 @@ _getxattr(const Policy::Search &searchFunc_,
rv = searchFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
fullpath = branches[0]->path / fusepath_;
+4 -1
View File
@@ -32,6 +32,7 @@
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "mergerfs_ioctl.hpp"
#include "state.hpp"
#include "str.hpp"
#include <string>
@@ -128,7 +129,7 @@ _ioctl_file(const fuse_req_ctx_t *ctx_,
static
int
_ioctl_dir_base(const Policy::Search &searchFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const u32 cmd_,
void *data_,
@@ -142,6 +143,8 @@ _ioctl_dir_base(const Policy::Search &searchFunc_,
rv = searchFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
fullpath = branches[0]->path / fusepath_;
+12 -4
View File
@@ -74,7 +74,7 @@ static
int
_link_create_path(const Policy::Search &searchFunc_,
const Policy::Action &actionFunc_,
const Branches &ibranches_,
const Branches::Ptr ibranches_,
const fs::path &oldfusepath_,
const fs::path &newfusepath_)
{
@@ -86,12 +86,16 @@ _link_create_path(const Policy::Search &searchFunc_,
rv = actionFunc_(ibranches_,oldfusepath_,oldbranches);
if(rv < 0)
return rv;
if(oldbranches.empty())
return -ENOENT;
newfusedirpath = newfusepath_.parent_path();
rv = searchFunc_(ibranches_,newfusedirpath,newbranches);
if(rv < 0)
return rv;
if(newbranches.empty())
return -ENOENT;
return ::_link_create_path_loop(oldbranches,newbranches[0],
oldfusepath_,newfusepath_,
@@ -148,7 +152,7 @@ _link_preserve_path_loop(const std::vector<Branch*> &oldbranches_,
static
int
_link_preserve_path(const Policy::Action &actionFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &oldfusepath_,
const fs::path &newfusepath_,
struct stat *st_)
@@ -159,6 +163,8 @@ _link_preserve_path(const Policy::Action &actionFunc_,
rv = actionFunc_(branches_,oldfusepath_,oldbranches);
if(rv < 0)
return rv;
if(oldbranches.empty())
return -ENOENT;
return ::_link_preserve_path_loop(oldbranches,
oldfusepath_,
@@ -231,8 +237,8 @@ _link_exdev_rel_symlink(const fuse_req_ctx_t *ctx_,
static
int
_link_exdev_abs_base_symlink(const fuse_req_ctx_t *ctx_,
const Policy::Search &openPolicy_,
const Branches::Ptr &ibranches_,
const Policy::Search &openPolicy_,
const Branches::Ptr ibranches_,
const fs::path &oldpath_,
const fs::path &newpath_,
struct stat *st_,
@@ -245,6 +251,8 @@ _link_exdev_abs_base_symlink(const fuse_req_ctx_t *ctx_,
rv = openPolicy_(ibranches_,oldpath_,obranches);
if(rv < 0)
return rv;
if(obranches.empty())
return -ENOENT;
target = obranches[0]->path / oldpath_;
+1 -1
View File
@@ -121,7 +121,7 @@ _listxattr(const std::vector<Branch*> &branches_,
static
int
_listxattr(const Policy::Search &searchFunc_,
const Branches &ibranches_,
const Branches::Ptr ibranches_,
const fs::path &fusepath_,
char *list_,
const size_t size_)
+5 -1
View File
@@ -103,7 +103,7 @@ int
_mkdir(const ugid_t ugid_,
const Policy::Search &getattrPolicy_,
const Policy::Create &mkdirPolicy_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const mode_t mode_,
const mode_t umask_)
@@ -118,10 +118,14 @@ _mkdir(const ugid_t ugid_,
rv = getattrPolicy_(branches_,fusedirpath,existingbranches);
if(rv < 0)
return rv;
if(existingbranches.empty())
return -ENOENT;
rv = mkdirPolicy_(branches_,fusedirpath,createbranches);
if(rv < 0)
return rv;
if(createbranches.empty())
return -ENOENT;
return ::_mkdir_loop(ugid_,
existingbranches[0],
+5 -1
View File
@@ -108,7 +108,7 @@ int
_mknod(const ugid_t ugid_,
const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const mode_t mode_,
const mode_t umask_,
@@ -124,10 +124,14 @@ _mknod(const ugid_t ugid_,
rv = searchFunc_(branches_,fusedirpath,existingbranches);
if(rv < 0)
return rv;
if(existingbranches.empty())
return -ENOENT;
rv = createFunc_(branches_,fusedirpath,createbranches);
if(rv < 0)
return rv;
if(createbranches.empty())
return -ENOENT;
return ::_mknod_loop(ugid_,
existingbranches[0]->path,
+8 -6
View File
@@ -25,9 +25,9 @@
simultaneously attempt to open the same file (same nodeid), and we
need to:
1. Share a single canonical FileInfo entry in the open_files map for all
opens of the same file while still giving each FUSE handle its own
FileInfo object and file descriptor
1. Share a single canonical FileInfo entry in the open_files map for
all opens of the same file while still giving each FUSE handle
its own FileInfo object and file descriptor
2. Properly reference count to know when to release resources
3. Handle race conditions without deadlocks or resource leaks
4. Support FUSE passthrough mode for performance
@@ -45,8 +45,8 @@
FileInfo by re-opening the canonical file descriptor
3. If it doesn't exist, we create a new FileInfo and try to insert it
4. If insertion fails (another thread inserted first), we clean up
and retry (because to use passthrough it MUST be the same underlying
file as the backing id was created)
and retry (because to use passthrough it MUST be the same
underlying file as the backing id was created)
THE RETRY LOOP
--------------
@@ -308,7 +308,7 @@ _open_fd(const int fd_,
static
int
_open(const Policy::Search &searchFunc_,
const Branches &ibranches_,
const Branches::Ptr ibranches_,
const fs::path &fusepath_,
fuse_file_info_t *ffi_,
const bool link_cow_,
@@ -321,6 +321,8 @@ _open(const Policy::Search &searchFunc_,
rv = searchFunc_(ibranches_,fusepath_,obranches);
if(rv < 0)
return rv;
if(obranches.empty())
return -ENOENT;
filepath = obranches[0]->path / fusepath_;
+1 -1
View File
@@ -47,7 +47,7 @@ static
inline
int
_concurrent_readdir(ThreadPool &tp_,
const Branches::Ptr &branches_,
const Branches::Ptr branches_,
const fs::path &rel_dirpath_,
fuse_dirents_t *dirents_)
{
+1 -1
View File
@@ -45,7 +45,7 @@ static
inline
int
_readdir(ThreadPool &tp_,
const Branches::Ptr &branches_,
const Branches::Ptr branches_,
const fs::path &rel_dirpath_,
fuse_dirents_t *dirents_)
{
+1 -1
View File
@@ -101,7 +101,7 @@ _readlink_core(const fs::path &basepath_,
static
int
_readlink(const Policy::Search &searchFunc_,
const Branches &ibranches_,
const Branches::Ptr ibranches_,
const fs::path &fusepath_,
char *buf_,
const size_t bufsize_,
+5 -1
View File
@@ -63,7 +63,7 @@ static
int
_removexattr(const Policy::Action &actionFunc_,
const Policy::Search &searchFunc_,
const Branches &ibranches_,
const Branches::Ptr ibranches_,
const fs::path &fusepath_,
const char *attrname_)
{
@@ -74,6 +74,8 @@ _removexattr(const Policy::Action &actionFunc_,
rv = actionFunc_(ibranches_,fusepath_,obranches);
if(rv < 0)
return rv;
if(obranches.empty())
return -ENOENT;
::_removexattr_loop(obranches,fusepath_,attrname_,&prv);
if(prv.errors.empty())
@@ -85,6 +87,8 @@ _removexattr(const Policy::Action &actionFunc_,
rv = searchFunc_(ibranches_,fusepath_,obranches);
if(rv < 0)
return rv;
if(obranches.empty())
return -ENOENT;
return prv.get_error(obranches[0]->path);
}
+12 -8
View File
@@ -70,8 +70,8 @@ _remove(const StrVec &toremove_)
static
int
_rename_create_path(const Policy::Search &searchPolicy_,
const Policy::Action &actionPolicy_,
const Branches::Ptr &branches_,
const Policy::Action &actionPolicy_,
const Branches::Ptr branches_,
const fs::path &oldfusepath_,
const fs::path &newfusepath_)
{
@@ -86,10 +86,14 @@ _rename_create_path(const Policy::Search &searchPolicy_,
rv = actionPolicy_(branches_,oldfusepath_,oldbranches);
if(rv < 0)
return rv;
if(oldbranches.empty())
return -ENOENT;
rv = searchPolicy_(branches_,newfusepath_.parent_path(),newbranches);
if(rv < 0)
return rv;
if(newbranches.empty())
return -ENOENT;
for(auto &branch : *branches_)
{
@@ -127,7 +131,7 @@ _rename_create_path(const Policy::Search &searchPolicy_,
static
int
_rename_preserve_path(const Policy::Action &actionPolicy_,
const Branches::Ptr &branches_,
const Branches::Ptr branches_,
const fs::path &oldfusepath_,
const fs::path &newfusepath_)
{
@@ -198,7 +202,7 @@ _rename_exdev_rename_back(const std::vector<Branch*> &branches_,
static
int
_rename_exdev_rename_target(const Policy::Action &actionPolicy_,
const Branches::Ptr &ibranches_,
const Branches::Ptr ibranches_,
const fs::path &oldfusepath_,
std::vector<Branch*> &obranches_)
{
@@ -245,8 +249,8 @@ _rename_exdev_rename_target(const Policy::Action &actionPolicy_,
static
int
_rename_exdev_rel_symlink(const fuse_req_ctx_t *ctx_,
const Policy::Action &actionPolicy_,
const Branches::Ptr &branches_,
const Policy::Action &actionPolicy_,
const Branches::Ptr branches_,
const fs::path &oldfusepath_,
const fs::path &newfusepath_)
{
@@ -274,8 +278,8 @@ _rename_exdev_rel_symlink(const fuse_req_ctx_t *ctx_,
static
int
_rename_exdev_abs_symlink(const fuse_req_ctx_t *ctx_,
const Policy::Action &actionPolicy_,
const Branches::Ptr &branches_,
const Policy::Action &actionPolicy_,
const Branches::Ptr branches_,
const fs::path &mount_,
const fs::path &oldfusepath_,
const fs::path &newfusepath_)
+1 -1
View File
@@ -78,7 +78,7 @@ _rmdir_loop(const std::vector<Branch*> &branches_,
static
int
_rmdir(const Policy::Action &actionFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const FollowSymlinks followsymlinks_,
const fs::path &fusepath_)
{
+5 -1
View File
@@ -145,7 +145,7 @@ static
int
_setxattr(const Policy::Action &setxattrPolicy_,
const Policy::Search &getxattrPolicy_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const char *attrname_,
const char *attrval_,
@@ -159,6 +159,8 @@ _setxattr(const Policy::Action &setxattrPolicy_,
rv = setxattrPolicy_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
::_setxattr_loop(branches,fusepath_,attrname_,attrval_,attrvalsize_,flags_,&prv);
if(prv.errors.empty())
@@ -170,6 +172,8 @@ _setxattr(const Policy::Action &setxattrPolicy_,
rv = getxattrPolicy_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
return prv.get_error(branches[0]->path);
}
+2 -2
View File
@@ -76,7 +76,7 @@ _should_ignore(const StatFSIgnore ignore_,
static
int
_statfs(const Branches::Ptr &branches_,
_statfs(const Branches::Ptr branches_,
const fs::path &fusepath_,
const StatFS mode_,
const StatFSIgnore ignore_,
@@ -147,7 +147,7 @@ FUSE::statfs(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
struct statvfs *st_)
{
const fs::path fusepath{fusepath_};
const fs::path fusepath{fusepath_};
return ::_statfs(cfg.branches,
fusepath,
+3 -1
View File
@@ -116,7 +116,7 @@ _statx_controlfile(struct fuse_statx *st_)
static
int
_statx(const Policy::Search &searchFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const uint32_t flags_,
const uint32_t mask_,
@@ -132,6 +132,8 @@ _statx(const Policy::Search &searchFunc_,
rv = searchFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
fullpath = branches[0]->path / fusepath_;
+5 -1
View File
@@ -97,7 +97,7 @@ int
_symlink(const ugid_t ugid_,
const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const char *target_,
const fs::path &linkpath_,
struct stat *st_)
@@ -112,10 +112,14 @@ _symlink(const ugid_t ugid_,
rv = searchFunc_(branches_,newdirpath,existingbranches);
if(rv < 0)
return rv;
if(existingbranches.empty())
return -ENOENT;
rv = createFunc_(branches_,newdirpath,newbranches);
if(rv < 0)
return rv;
if(newbranches.empty())
return -ENOENT;
return ::_symlink_loop(ugid_,
existingbranches[0]->path,
+5 -1
View File
@@ -64,7 +64,7 @@ static
int
_truncate(const Policy::Action &actionFunc_,
const Policy::Search &searchFunc_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const off_t size_)
{
@@ -75,6 +75,8 @@ _truncate(const Policy::Action &actionFunc_,
rv = actionFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
::_truncate_loop(branches,fusepath_,size_,&prv);
if(prv.errors.empty())
@@ -86,6 +88,8 @@ _truncate(const Policy::Action &actionFunc_,
rv = searchFunc_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
return prv.get_error(branches[0]->path);
}
+1 -1
View File
@@ -52,7 +52,7 @@ _unlink_loop(const std::vector<Branch*> &branches_,
static
int
_unlink(const Policy::Action &unlinkPolicy_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_)
{
int rv;
+5 -1
View File
@@ -63,7 +63,7 @@ static
int
_utimens(const Policy::Action &utimensPolicy_,
const Policy::Search &getattrPolicy_,
const Branches &branches_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const timespec ts_[2])
{
@@ -74,6 +74,8 @@ _utimens(const Policy::Action &utimensPolicy_,
rv = utimensPolicy_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
::_utimens_loop(branches,fusepath_,ts_,&prv);
if(prv.errors.empty())
@@ -85,6 +87,8 @@ _utimens(const Policy::Action &utimensPolicy_,
rv = getattrPolicy_(branches_,fusepath_,branches);
if(rv < 0)
return rv;
if(branches.empty())
return -ENOENT;
return prv.get_error(branches[0]->path);
}