Implement bespoke branch policies

Consolidate branch policy selection behind the BranchPolicy interfaces and update filesystem operations to use the bespoke policy implementations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Antonio S. Musumeci 2026-05-20 21:17:04 -04:00
parent 42ab831d97
commit 96d8232853
451 changed files with 18495 additions and 7562 deletions

3
.gitignore vendored
View file

@ -31,3 +31,6 @@ rpmbuild/
# mkdocs files
mkdocs/venv
__pycache__/
# claude code harness
.claude/

View file

@ -24,6 +24,7 @@
#include "tofrom_string.hpp"
#include <cstdint>
#include <cstddef>
#include <memory>
#include <shared_mutex>
#include <string>
@ -78,10 +79,60 @@ public:
{
}
public:
class ConstIterator
{
private:
Branches::Ptr _impl;
size_t _idx = 0;
public:
ConstIterator() = default;
ConstIterator(Branches::Ptr impl_,
const size_t idx_)
: _impl(impl_),
_idx(idx_)
{
}
public:
const Branch& operator*() const
{
return (*_impl)[_idx];
}
ConstIterator& operator++()
{
++_idx;
return *this;
}
bool operator!=(const ConstIterator &rhs_) const
{
if(rhs_._impl == nullptr)
return (_impl != nullptr && _idx < _impl->size());
if(_impl == nullptr)
return (rhs_._impl != nullptr && rhs_._idx < rhs_._impl->size());
return ((_impl != rhs_._impl) || (_idx != rhs_._idx));
}
};
public:
int from_string(const std::string_view str) final;
std::string to_string(void) const final;
public:
ConstIterator begin() const
{
std::shared_lock<std::shared_mutex> lk(_mutex);
return ConstIterator(_impl,0);
}
ConstIterator end() const
{
return ConstIterator();
}
public:
operator Ptr() const
{

View file

@ -18,8 +18,8 @@
#include "category.hpp"
#include "errno.hpp"
#include "str.hpp"
#include <set>
#include <string>
int
@ -34,16 +34,45 @@ Category::Base::from_string(const std::string_view s_)
return rv;
}
{
std::lock_guard<std::mutex> lk(_last_set_mutex);
_last_set = std::string(s_);
}
return 0;
}
std::string
Category::Base::to_string(void) const
{
std::set<std::string> rv;
// Snapshot the last category-wide input under the lock so a
// concurrent from_string can't tear the string mid-read.
std::string last;
{
std::lock_guard<std::mutex> lk(_last_set_mutex);
last = _last_set;
}
// Collect current constituent names. Each FuncWrapper::to_string is
// itself mutex-protected.
std::set<std::string> names;
for(const auto func : funcs)
rv.insert(func->to_string());
names.insert(func->to_string());
return str::join(rv,',');
if(names.size() == 1)
{
// All children agree. Prefer _last_set (round-trippable through
// the category-wide setter even when it maps to the agreed
// name); otherwise fall back to the agreed name.
if(!last.empty())
return last;
return *names.begin();
}
// Children diverge — either no category-wide assignment ever ran,
// or a per-function override has changed a child since the last
// category-wide set. Report "mixed" rather than a stale _last_set,
// since per-function .to_string()s are the source of truth for the
// actual policy in effect.
return std::string("mixed");
}

View file

@ -19,10 +19,10 @@
#pragma once
#include "tofrom_string.hpp"
#include "funcs.hpp"
#include "func.hpp"
#include <mutex>
#include <string>
#include <vector>
namespace Category
{
@ -34,6 +34,13 @@ namespace Category
protected:
std::vector<ToFromString*> funcs;
// The last value successfully applied via from_string. Categories
// can map a single user input to different per-function default
// classes, so the constituent name()s may disagree. Returning the
// last-set value here keeps to_string output round-trippable, but
// only if no per-function override has since diverged the children.
std::string _last_set;
mutable std::mutex _last_set_mutex;
};
class Action final : public Base
@ -42,18 +49,27 @@ namespace Category
Action();
public:
Action(Funcs &funcs_)
Action(ToFromString &chmod_,
ToFromString &chown_,
ToFromString &link_,
ToFromString &removexattr_,
ToFromString &rename_,
ToFromString &rmdir_,
ToFromString &setxattr_,
ToFromString &truncate_,
ToFromString &unlink_,
ToFromString &utimens_)
{
funcs.push_back(&funcs_.chmod);
funcs.push_back(&funcs_.chown);
funcs.push_back(&funcs_.link);
funcs.push_back(&funcs_.removexattr);
funcs.push_back(&funcs_.rename);
funcs.push_back(&funcs_.rmdir);
funcs.push_back(&funcs_.setxattr);
funcs.push_back(&funcs_.truncate);
funcs.push_back(&funcs_.unlink);
funcs.push_back(&funcs_.utimens);
funcs.push_back(&chmod_);
funcs.push_back(&chown_);
funcs.push_back(&link_);
funcs.push_back(&removexattr_);
funcs.push_back(&rename_);
funcs.push_back(&rmdir_);
funcs.push_back(&setxattr_);
funcs.push_back(&truncate_);
funcs.push_back(&unlink_);
funcs.push_back(&utimens_);
}
};
@ -63,12 +79,15 @@ namespace Category
Create();
public:
Create(Funcs &funcs_)
Create(ToFromString &create_,
ToFromString &mkdir_,
ToFromString &mknod_,
ToFromString &symlink_)
{
funcs.push_back(&funcs_.create);
funcs.push_back(&funcs_.mkdir);
funcs.push_back(&funcs_.mknod);
funcs.push_back(&funcs_.symlink);
funcs.push_back(&create_);
funcs.push_back(&mkdir_);
funcs.push_back(&mknod_);
funcs.push_back(&symlink_);
}
};
@ -78,14 +97,21 @@ namespace Category
Search();
public:
Search(Funcs &funcs_)
Search(ToFromString &access_,
ToFromString &getattr_,
ToFromString &getxattr_,
ToFromString &ioctl_,
ToFromString &listxattr_,
ToFromString &open_,
ToFromString &readlink_)
{
funcs.push_back(&funcs_.access);
funcs.push_back(&funcs_.getattr);
funcs.push_back(&funcs_.getxattr);
funcs.push_back(&funcs_.listxattr);
funcs.push_back(&funcs_.open);
funcs.push_back(&funcs_.readlink);
funcs.push_back(&access_);
funcs.push_back(&getattr_);
funcs.push_back(&getxattr_);
funcs.push_back(&ioctl_);
funcs.push_back(&listxattr_);
funcs.push_back(&open_);
funcs.push_back(&readlink_);
}
};
}
@ -96,10 +122,45 @@ private:
Categories();
public:
Categories(Funcs &funcs_)
: action(funcs_),
create(funcs_),
search(funcs_)
Categories(ToFromString &access_,
ToFromString &chmod_,
ToFromString &chown_,
ToFromString &link_,
ToFromString &getattr_,
ToFromString &getxattr_,
ToFromString &ioctl_,
ToFromString &listxattr_,
ToFromString &open_,
ToFromString &readlink_,
ToFromString &rename_,
ToFromString &removexattr_,
ToFromString &rmdir_,
ToFromString &setxattr_,
ToFromString &truncate_,
ToFromString &unlink_,
ToFromString &utimens_,
ToFromString &create_,
ToFromString &mkdir_,
ToFromString &mknod_,
ToFromString &symlink_)
: action(chmod_,
chown_,
link_,
removexattr_,
rename_,
rmdir_,
setxattr_,
truncate_,
unlink_,
utimens_),
create(create_,mkdir_,mknod_,symlink_),
search(access_,
getattr_,
getxattr_,
ioctl_,
listxattr_,
open_,
readlink_)
{}
public:

View file

@ -85,7 +85,27 @@ Config::Config()
cache_statfs(0),
cache_symlinks(false),
cache_writeback(false),
category(func),
category(access,
chmod,
chown,
link,
_getattr_statx,
getxattr,
ioctl,
listxattr,
open,
readlink,
rename,
removexattr,
rmdir,
setxattr,
truncate,
unlink,
utimens,
create,
mkdir,
mknod,
symlink),
config_file(),
direct_io_allow_mmap(true),
dropcacheonclose(false),
@ -93,7 +113,6 @@ Config::Config()
flushonclose(FlushOnClose::ENUM::OPENED_FOR_WRITE),
follow_symlinks(FollowSymlinks::ENUM::NEVER),
fsname(),
func(),
fuse_msg_size("1M"),
handle_killpriv(true),
handle_killpriv_v2(true),
@ -219,27 +238,29 @@ Config::Config()
_map["flush-on-close"] = &flushonclose;
_map["follow-symlinks"] = &follow_symlinks;
_map["fsname"] = &fsname;
_map["func.access"] = &func.access;
_map["func.chmod"] = &func.chmod;
_map["func.chown"] = &func.chown;
_map["func.create"] = &func.create;
_map["func.getattr"] = &func.getattr;
_map["func.getxattr"] = &func.getxattr;
_map["func.link"] = &func.link;
_map["func.listxattr"] = &func.listxattr;
_map["func.mkdir"] = &func.mkdir;
_map["func.mknod"] = &func.mknod;
_map["func.open"] = &func.open;
_map["func.access"] = &access;
_map["func.chmod"] = &chmod;
_map["func.chown"] = &chown;
_map["func.create"] = &create;
_map["func.getattr"] = &_getattr_statx;
_map["func.getxattr"] = &getxattr;
_map["func.ioctl"] = &ioctl;
_map["func.link"] = &link;
_map["func.listxattr"] = &listxattr;
_map["func.mkdir"] = &mkdir;
_map["func.mknod"] = &mknod;
_map["func.open"] = &open;
_map["func.readdir"] = &readdir;
_map["func.readlink"] = &func.readlink;
_map["func.removexattr"] = &func.removexattr;
_map["func.rename"] = &func.rename;
_map["func.rmdir"] = &func.rmdir;
_map["func.setxattr"] = &func.setxattr;
_map["func.symlink"] = &func.symlink;
_map["func.truncate"] = &func.truncate;
_map["func.unlink"] = &func.unlink;
_map["func.utimens"] = &func.utimens;
_map["func.readlink"] = &readlink;
_map["func.removexattr"] = &removexattr;
_map["func.rename"] = &rename;
_map["func.rmdir"] = &rmdir;
_map["func.setxattr"] = &setxattr;
_map["func.statx"] = &_getattr_statx;
_map["func.symlink"] = &symlink;
_map["func.truncate"] = &truncate;
_map["func.unlink"] = &unlink;
_map["func.utimens"] = &utimens;
_map["fuse-msg-size"] = &fuse_msg_size;
_map["gid"] = &_gid;
_map["gid-cache.expire-timeout"] = &_dummy;
@ -448,6 +469,34 @@ Config::finish_initializing()
_initialized = true;
}
void
Config::initialize_funcs()
{
access.initialize();
chmod.initialize();
chown.initialize();
create.initialize();
getattr.initialize();
getxattr.initialize();
ioctl.initialize();
link.initialize();
listxattr.initialize();
mkdir.initialize();
mknod.initialize();
open.initialize();
readlink.initialize();
removexattr.initialize();
rename.initialize();
rmdir.initialize();
setxattr.initialize();
statx.initialize();
symlink.initialize();
truncate.initialize();
unlink.initialize();
utimens.initialize();
readdir.initialize();
}
bool
Config::is_rootdir(const fs::path &fusepath_)
{

View file

@ -18,6 +18,29 @@
#pragma once
#include "func_access.hpp"
#include "func_chmod.hpp"
#include "func_chown.hpp"
#include "func_create.hpp"
#include "func_getattr.hpp"
#include "func_getxattr.hpp"
#include "func_ioctl.hpp"
#include "func_link.hpp"
#include "func_listxattr.hpp"
#include "func_mkdir.hpp"
#include "func_mknod.hpp"
#include "func_open.hpp"
#include "func_readlink.hpp"
#include "func_removexattr.hpp"
#include "func_rename.hpp"
#include "func_rmdir.hpp"
#include "func_setxattr.hpp"
#include "func_statx.hpp"
#include "func_symlink.hpp"
#include "func_truncate.hpp"
#include "func_unlink.hpp"
#include "func_utimens.hpp"
#include "branches.hpp"
#include "category.hpp"
#include "config_cachefiles.hpp"
@ -25,6 +48,7 @@
#include "config_dummy.hpp"
#include "config_flushonclose.hpp"
#include "config_follow_symlinks.hpp"
#include "config_getattr_statx.hpp"
#include "config_inodecalc.hpp"
#include "config_link_exdev.hpp"
#include "config_log_file.hpp"
@ -43,10 +67,8 @@
#include "enum.hpp"
#include "errno.hpp"
#include "fs_path.hpp"
#include "funcs.hpp"
#include "fuse_cfg.hpp"
#include "fuse_readdir.hpp"
#include "policy.hpp"
#include "tofrom_ref.hpp"
#include "tofrom_wrapper.hpp"
#include "syslog.hpp"
@ -107,6 +129,31 @@ public:
Config();
public:
Func2::Access access{"ff"};
Func2::Chmod chmod{"all"};
Func2::Chown chown{"all"};
Func2::Create create{"pfrd"};
Func2::GetAttr getattr{"cdfo"};
Func2::Getxattr getxattr{"ff"};
Func2::Ioctl ioctl{"ff"};
Func2::Link link{"epall"};
Func2::Listxattr listxattr{"ff"};
Func2::Mkdir mkdir{"pfrd"};
Func2::Mknod mknod{"ff"};
Func2::Open open{"ff"};
Func2::Readlink readlink{"ff"};
Func2::Removexattr removexattr{"all"};
Func2::Rename rename{"epall"};
Func2::Rmdir rmdir{"all"};
Func2::Setxattr setxattr{"all"};
Func2::Statx statx{"cdfo"};
Func2::Symlink symlink{"pfrd"};
Func2::Truncate truncate{"all"};
Func2::Unlink unlink{"all"};
Func2::Utimens utimens{"all"};
ConfigGetAttrStatx _getattr_statx{getattr,statx};
ConfigBOOL allow_idmap;
ConfigBOOL async_read;
Branches branches;
@ -129,7 +176,6 @@ public:
FlushOnClose flushonclose;
FollowSymlinks follow_symlinks;
ConfigSTR fsname;
Funcs func;
ConfigPageSize fuse_msg_size;
ConfigBOOL handle_killpriv;
ConfigBOOL handle_killpriv_v2;
@ -189,6 +235,7 @@ private:
public:
void finish_initializing();
void initialize_funcs();
public:
friend std::ostream& operator<<(std::ostream &s,

View file

@ -19,12 +19,6 @@
#pragma once
#include "enum.hpp"
#include "follow_symlinks_enum.hpp"
enum class FollowSymlinksEnum
{
NEVER,
DIRECTORY,
REGULAR,
ALL
};
typedef Enum<FollowSymlinksEnum> FollowSymlinks;

View file

@ -0,0 +1,53 @@
#pragma once
#include "config.hpp"
#include "tofrom_string.hpp"
class ConfigGetAttrStatx : public ToFromString
{
private:
Func2::GetAttr &_getattr;
Func2::Statx &_statx;
public:
ConfigGetAttrStatx() = delete;
ConfigGetAttrStatx(Func2::GetAttr &getattr_,
Func2::Statx &statx_)
: _getattr(getattr_),
_statx(statx_)
{
}
std::string
to_string() const
{
std::string g = _getattr.to_string();
std::string s = _statx.to_string();
if(g == s)
return g;
return g + "," + s;
}
int
from_string(const std::string_view str_)
{
int rv;
if(!Func2::GetAttrFactory::valid(std::string(str_)))
return -EINVAL;
if(!Func2::StatxFactory::valid(std::string(str_)))
return -EINVAL;
rv = _getattr.from_string(str_);
if(rv < 0)
return rv;
rv = _statx.from_string(str_);
if(rv < 0)
return rv;
return 0;
}
};

View file

@ -17,7 +17,7 @@
*/
#include "config_moveonenospc.hpp"
#include "ef.hpp"
#include "errno.hpp"
#include "from_string.hpp"
@ -25,34 +25,16 @@
int
MoveOnENOSPC::from_string(const std::string_view s_)
{
int rv;
std::string s;
Policy::CreateImpl *tmp;
rv = str::from(s_,&enabled);
if((rv == 0) && (enabled == true))
s = "pfrd";
ef((rv == 0) && (enabled == false))
return (enabled = false,0);
ef(rv != 0)
s = s_;
else
bool tmp;
const int rv = str::from(s_,&tmp);
if(rv != 0)
return -EINVAL;
tmp = Policies::Create::find(s);
if(tmp == NULL)
return -EINVAL;
policy = tmp;
enabled = true;
enabled = tmp;
return 0;
}
std::string
MoveOnENOSPC::to_string(void) const
{
if(enabled)
return policy.name();
return "false";
return (enabled ? "true" : "false");
}

View file

@ -18,8 +18,6 @@
#pragma once
#include "policy.hpp"
#include "policies.hpp"
#include "tofrom_string.hpp"
#include <string>
@ -29,8 +27,7 @@ class MoveOnENOSPC : public ToFromString
{
public:
MoveOnENOSPC(const bool enabled_)
: enabled(enabled_),
policy(&Policies::Create::pfrd)
: enabled(enabled_)
{
}
@ -40,5 +37,4 @@ public:
public:
bool enabled;
Policy::Create policy;
};

View file

@ -1,59 +0,0 @@
/*
ISC License
Copyright (c) 2026, Antonio SJ Musumeci <trapexit@spawn.link>
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.
*/
#pragma once
#include "errno.hpp"
#include <optional>
struct Err
{
private:
std::optional<int> _err;
public:
Err()
{
}
operator int()
{
return (_err.has_value() ? _err.value() : -ENOENT);
}
Err&
operator=(int v_)
{
if(!_err.has_value())
_err = ((v_ >= 0) ? 0 : v_);
else if(v_ >= 0)
_err = 0;
return *this;
}
bool
operator==(int v_)
{
if(_err.has_value())
return (_err.value() == v_);
return false;
}
};

View file

@ -0,0 +1,9 @@
#pragma once
enum class FollowSymlinksEnum
{
NEVER,
DIRECTORY,
REGULAR,
ALL
};

View file

@ -19,8 +19,7 @@
#pragma once
#include "to_neg_errno.hpp"
#include <string>
#include "to_cstr.hpp"
#include <sys/stat.h>
#include <sys/types.h>
@ -29,25 +28,17 @@
namespace fs
{
template<typename PathType>
static
inline
int
lstat(const char *path_,
struct stat *st_)
lstat(const PathType &path_,
struct stat *st_)
{
int rv;
rv = ::lstat(path_,st_);
rv = ::lstat(to_cstr(path_),st_);
return ::to_neg_errno(rv);
}
static
inline
int
lstat(const std::string &path_,
struct stat *st_)
{
return fs::lstat(path_.c_str(),st_);
}
}

View file

@ -18,6 +18,7 @@
#pragma once
#include "fs_acl.hpp"
#include "fs_mknod.hpp"
#include "ugid.hpp"
@ -31,9 +32,13 @@ namespace fs
int
mknod_as(const ugid_t ugid_,
const T &path_,
const mode_t mode_,
const dev_t dev_)
mode_t mode_,
dev_t dev_,
mode_t umask_)
{
if(not fs::acl::dir_has_defaults(path_))
mode_ &= ~umask_;
const ugid::SetGuard _(ugid_);
return fs::mknod(path_,mode_,dev_);
@ -41,6 +46,7 @@ namespace fs
}
#elif defined __FreeBSD__
#include "fs_lchown.hpp"
#include "fs_unlink.hpp"
namespace fs
{
@ -50,16 +56,29 @@ namespace fs
int
mknod_as(const ugid_t ugid_,
const T &path_,
const mode_t mode_,
const dev_t dev_)
mode_t mode_,
dev_t dev_,
mode_t umask_)
{
int rv;
if(not fs::acl::dir_has_defaults(path_))
mode_ &= ~umask_;
rv = fs::mknod(path_,mode_,dev_);
if(rv < 0)
return rv;
fs::lchown(path_,ugid_.uid,ugid_.gid);
const int lrv = fs::lchown(path_,ugid_.uid,ugid_.gid);
if(lrv < 0)
{
// Clean up the just-created node so a retry sees -ENOENT
// instead of -EEXIST and userspace doesn't observe a phantom.
fs::unlink(path_);
return lrv;
}
return rv;
return 0;
}
}
#else

View file

@ -21,28 +21,20 @@
#include "base_types.h"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_close.hpp"
#include "fs_copyfile.hpp"
#include "fs_file_size.hpp"
#include "fs_findonfs.hpp"
#include "fs_getfl.hpp"
#include "fs_has_space.hpp"
#include "fs_mktemp.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "fs_rename.hpp"
#include "fs_stat.hpp"
#include "fs_unlink.hpp"
#include "policy.hpp"
#include "rnd.hpp"
#include <string>
#include <vector>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
static
@ -59,13 +51,61 @@ _cleanup_flags(const int flags_)
return rv;
}
// Pick a writable destination branch using PFRD (proportional to free
// space). Self-contained for moveonenospc — mirrors the same algorithm
// used by Func2::CreatePFRD but on a fresh selection (no clonepath/open).
static
int
_movefile_and_open(const Policy::Create &createFunc_,
const Branches::Ptr &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
int origfd_)
_pick_pfrd_branch(const Branches &branches_,
Branch *&chosen_)
{
int err = ENOENT;
Branches::Ptr branches = branches_;
fs::info_t info;
struct W { Branch *b; uint64_t weight; };
std::vector<W> writable;
uint64_t sum = 0;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
{ if(err == ENOENT) err = EROFS; continue; }
const int irv = fs::info(branch.path,&info);
if(irv < 0)
{ if(err == ENOENT) err = ENOENT; continue; }
if(info.readonly)
{ if(err == ENOENT) err = EROFS; continue; }
if(info.spaceavail < branch.minfreespace())
{ if(err == ENOENT) err = ENOSPC; continue; }
writable.push_back({&branch,info.spaceavail});
sum += info.spaceavail;
}
if(writable.empty())
return -err;
if(sum == 0)
return -ENOSPC;
const uint64_t threshold = RND::rand64(sum);
uint64_t idx = 0;
for(const auto &w : writable)
{
if(w.weight == 0) continue;
idx += w.weight;
if(idx > threshold) { chosen_ = w.b; return 0; }
}
chosen_ = writable.back().b;
return 0;
}
static
int
_movefile_and_open(const Branches &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
int origfd_)
{
int rv;
int dstfd_flags;
@ -75,14 +115,14 @@ _movefile_and_open(const Policy::Create &createFunc_,
fs::path src_branch;
fs::path src_filepath;
fs::path dst_filepath;
std::vector<Branch*> dst_branch;
Branch *dst_branch = nullptr;
src_branch = branchpath_;
rv = createFunc_(branches_,fusepath_,dst_branch);
rv = ::_pick_pfrd_branch(branches_,dst_branch);
if(rv < 0)
return rv;
if(dst_branch.empty())
if(dst_branch == nullptr)
return -ENOSPC;
origfd_flags = fs::getfl(origfd_);
@ -93,17 +133,17 @@ _movefile_and_open(const Policy::Create &createFunc_,
if(src_size < 0)
return src_size;
if(fs::has_space(dst_branch[0]->path,src_size) == false)
if(fs::has_space(dst_branch->path,src_size) == false)
return -ENOSPC;
fusedir = fusepath_.parent_path();
rv = fs::clonepath(src_branch,dst_branch[0]->path,fusedir);
rv = fs::clonepath(src_branch,dst_branch->path,fusedir);
if(rv < 0)
return -ENOSPC;
src_filepath = src_branch / fusepath_;
dst_filepath = dst_branch[0]->path / fusepath_;
dst_filepath = dst_branch->path / fusepath_;
rv = fs::copyfile(src_filepath,dst_filepath,{.cleanup_failure = true});
if(rv < 0)
@ -120,29 +160,19 @@ _movefile_and_open(const Policy::Create &createFunc_,
}
int
fs::movefile_and_open(const Policy::Create &policy_,
const Branches::Ptr &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
const int origfd_)
fs::movefile_and_open(const Branches &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
const int origfd_)
{
return ::_movefile_and_open(policy_,
branches_,
branchpath_,
fusepath_,
origfd_);
return ::_movefile_and_open(branches_,branchpath_,fusepath_,origfd_);
}
int
fs::movefile_and_open_as_root(const Policy::Create &policy_,
const Branches::Ptr &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
const int origfd_)
fs::movefile_and_open_as_root(const Branches &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
const int origfd_)
{
return fs::movefile_and_open(policy_,
branches_,
branchpath_,
fusepath_,
origfd_);
return fs::movefile_and_open(branches_,branchpath_,fusepath_,origfd_);
}

View file

@ -20,24 +20,19 @@
#include "branches.hpp"
#include "fs_path.hpp"
#include "policy.hpp"
#include <string>
namespace fs
{
int
movefile_and_open(const Policy::Create &policy,
const Branches::Ptr &branches_,
const fs::path &branchpath,
const fs::path &fusepath,
const int origfd);
movefile_and_open(const Branches &branches,
const fs::path &branchpath,
const fs::path &fusepath,
const int origfd);
int
movefile_and_open_as_root(const Policy::Create &policy,
const Branches::Ptr &branches_,
const fs::path &branchpath,
const fs::path &fusepath,
int origfd);
movefile_and_open_as_root(const Branches &branches,
const fs::path &branchpath,
const fs::path &fusepath,
int origfd);
}

0
src/fs_stat.cpp Normal file
View file

View file

@ -18,7 +18,10 @@
#pragma once
#include "follow_symlinks_enum.hpp"
#include "to_neg_errno.hpp"
#include "to_cstr.hpp"
#include "fs_lstat.hpp"
#include <string>
@ -29,25 +32,66 @@
namespace fs
{
template<typename PathType>
static
inline
int
stat(const char *path_,
struct stat *st_)
stat(const PathType &path_,
struct stat *st_)
{
int rv;
rv = ::stat(path_,st_);
rv = ::stat(to_cstr(path_),st_);
return ::to_neg_errno(rv);
}
template<typename PathType>
static
inline
int
stat(const std::string &path_,
struct stat *st_)
stat(const PathType &path_,
struct stat *st_,
FollowSymlinksEnum follow_)
{
return fs::stat(path_.c_str(),st_);
int rv;
switch(follow_)
{
default:
case FollowSymlinksEnum::NEVER:
rv = fs::lstat(path_,st_);
return rv;
case FollowSymlinksEnum::DIRECTORY:
rv = fs::lstat(path_,st_);
if((rv >= 0) && S_ISLNK(st_->st_mode))
{
struct stat st{};
// If the link's target lookup fails (e.g. dangling link),
// keep the link's own lstat data so callers like ls -l can
// still display the entry.
if((fs::stat(path_,&st) >= 0) && S_ISDIR(st.st_mode))
*st_ = st;
}
return rv;
case FollowSymlinksEnum::REGULAR:
rv = fs::lstat(path_,st_);
if((rv >= 0) && S_ISLNK(st_->st_mode))
{
struct stat st{};
if((fs::stat(path_,&st) >= 0) && S_ISREG(st.st_mode))
*st_ = st;
}
return rv;
case FollowSymlinksEnum::ALL:
rv = fs::stat(path_,st_);
if(rv < 0)
rv = fs::lstat(path_,st_);
return rv;
}
return -ENOENT;
}
}

View file

@ -18,6 +18,8 @@
#pragma once
#include "follow_symlinks_enum.hpp"
#include "to_cstr.hpp"
#include "to_neg_errno.hpp"
#include "fuse_kernel.h"
@ -33,13 +35,15 @@
#include "supported_statx.hpp"
namespace fs
{
template<typename PathType>
static
inline
int
statx(const int dirfd_,
const char *pathname_,
const PathType &pathname_,
const int flags_,
const unsigned int mask_,
struct fuse_statx *st_)
@ -48,7 +52,7 @@ namespace fs
int rv;
rv = ::statx(dirfd_,
pathname_,
to_cstr(pathname_),
flags_,
mask_,
(struct statx*)st_);
@ -59,19 +63,64 @@ namespace fs
#endif
}
template<typename PathType>
static
inline
int
statx(const int dirfd_,
const std::string &pathname_,
const PathType &pathname_,
const int flags_,
const unsigned int mask_,
struct fuse_statx *st_)
struct fuse_statx *st_,
FollowSymlinksEnum followsymlinks_)
{
return fs::statx(dirfd_,
pathname_.c_str(),
flags_,
mask_,
st_);
int rv;
// statx(2) recognises AT_SYMLINK_NOFOLLOW; follow-the-link is the
// default when that flag is absent. AT_SYMLINK_FOLLOW (a linkat(2)
// flag) is not meaningful here and is omitted.
const int follow_flags = (flags_ & ~AT_SYMLINK_NOFOLLOW);
const int nofollow_flags = (flags_ | AT_SYMLINK_NOFOLLOW);
switch(followsymlinks_)
{
case FollowSymlinksEnum::NEVER:
rv = fs::statx(dirfd_,pathname_,nofollow_flags,mask_,st_);
return rv;
case FollowSymlinksEnum::DIRECTORY:
rv = fs::statx(dirfd_,pathname_,nofollow_flags,mask_,st_);
if((rv >= 0) && S_ISLNK(st_->mode))
{
struct fuse_statx st{};
// Request the same mask as the caller: STATX_TYPE alone
// would leave the rest of `st` unspecified and clobber the
// caller's data when we copy back. On follow failure
// (dangling link, etc.), keep the lstat data so callers
// can still display the entry.
if((fs::statx(dirfd_,pathname_,follow_flags,mask_,&st) >= 0) &&
S_ISDIR(st.mode))
*st_ = st;
}
return rv;
case FollowSymlinksEnum::REGULAR:
rv = fs::statx(dirfd_,pathname_,nofollow_flags,mask_,st_);
if((rv >= 0) && S_ISLNK(st_->mode))
{
struct fuse_statx st{};
if((fs::statx(dirfd_,pathname_,follow_flags,mask_,&st) >= 0) &&
S_ISREG(st.mode))
*st_ = st;
}
return rv;
case FollowSymlinksEnum::ALL:
rv = fs::statx(dirfd_,pathname_,follow_flags,mask_,st_);
if(rv < 0)
rv = fs::statx(dirfd_,pathname_,nofollow_flags,mask_,st_);
return rv;
}
return -ENOENT;
}
}

36
src/fsproxy.cpp Normal file
View file

@ -0,0 +1,36 @@
#include "fsproxy.hpp"
#include "fs_close.hpp"
int
FSProxy::_spawn_proxy_if_needed(const uid_t uid_,
const gid_t gid_)
{
int rv;
FSProxy::Process process;
if(_proxies.count({uid_,gid_}))
return 0;
rv = pipe(process.req_pipe);
if(rv == -1)
return rv;
rv = pipe(process.res_pipe);
if(rv == -1)
return rv;
process.pid = fork();
if(process.pid != 0)
{
fs::close(process.req_pipe[1]);
fs::close(process.res_pipe[0]);
// listen on req in loop for instructions
return 0;
}
fs::close(process.req_pipe[0]);
fs::close(process.res_pipe[1]);
_proxies[UGID{uid_,gid_}] = process;
return 0;
}

54
src/fsproxy.hpp Normal file
View file

@ -0,0 +1,54 @@
#pragma once
#include <unordered_map>
#include <fcntl.h>
#include <unistd.h>
class FSProxy
{
public:
struct UGID
{
bool
operator==(const UGID &other_) const
{
return ((uid == other_.uid) &&
(gid == other_.gid));
}
uid_t uid;
gid_t gid;
};
struct UGIDHash
{
size_t
operator()(const UGID &key_) const
{
size_t h0 = std::hash<uid_t>{}(key_.uid);
size_t h1 = std::hash<uid_t>{}(key_.gid);
return (h0 ^ (h1 << 1));
}
};
struct Process
{
int pid;
int req_pipe[2];
int res_pipe[2];
};
public:
int create(uid_t uid,
gid_t gid,
const char *pathname,
mode_t mode);
private:
int _spawn_proxy_if_needed(uid_t,gid_t);
private:
std::unordered_map<UGID,Process,UGIDHash> _proxies;
};

View file

@ -1,77 +0,0 @@
/*
ISC License
Copyright (c) 2026, Antonio SJ Musumeci <trapexit@spawn.link>
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 "func.hpp"
int
Func::Base::Action::from_string(const std::string_view policyname_)
{
auto *tmp = Policies::Action::find(policyname_);
if(not tmp)
return -EINVAL;
policy = tmp;
return 0;
}
std::string
Func::Base::Action::to_string(void) const
{
return policy.name();
}
int
Func::Base::Create::from_string(const std::string_view policyname_)
{
auto *tmp = Policies::Create::find(policyname_);
if(not tmp)
return -EINVAL;
policy = tmp;
return 0;
}
std::string
Func::Base::Create::to_string(void) const
{
return policy.name();
}
int
Func::Base::Search::from_string(const std::string_view policyname_)
{
auto *tmp = Policies::Search::find(policyname_);
if(not tmp)
return -EINVAL;
policy = tmp;
return 0;
}
std::string
Func::Base::Search::to_string(void) const
{
return policy.name();
}

View file

@ -1,184 +0,0 @@
/*
ISC License
Copyright (c) 2026, Antonio SJ Musumeci <trapexit@spawn.link>
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.
*/
#pragma once
#include "policy.hpp"
#include "policies.hpp"
#include "tofrom_string.hpp"
#include <string>
namespace Func
{
namespace Base
{
class Action : public ToFromString
{
public:
Action(Policy::ActionImpl *policy_)
: policy(policy_)
{}
public:
int from_string(const std::string_view s) final;
std::string to_string() const final;
public:
Policy::Action policy;
};
class Create : public ToFromString
{
public:
Create(Policy::CreateImpl *policy_)
: policy(policy_)
{}
public:
int from_string(const std::string_view s) final;
std::string to_string() const final;
public:
Policy::Create policy;
};
class Search : public ToFromString
{
public:
Search(Policy::SearchImpl *policy_)
: policy(policy_)
{}
public:
int from_string(const std::string_view s) final;
std::string to_string() const final;
public:
Policy::Search policy;
};
class ActionDefault : public Action
{
public:
ActionDefault()
: Func::Base::Action(&Policies::Action::epall)
{
}
};
class CreateDefault : public Create
{
public:
CreateDefault()
: Func::Base::Create(&Policies::Create::pfrd)
{
}
};
class SearchDefault : public Search
{
public:
SearchDefault()
: Func::Base::Search(&Policies::Search::ff)
{
}
};
}
class Access final : public Base::SearchDefault
{
};
class Chmod final : public Base::ActionDefault
{
};
class Chown final : public Base::ActionDefault
{
};
class Create final : public Base::CreateDefault
{
};
class GetAttr final : public Base::SearchDefault
{
};
class GetXAttr final : public Base::SearchDefault
{
};
class Link final : public Base::ActionDefault
{
};
class ListXAttr final : public Base::SearchDefault
{
};
class Mkdir final : public Base::CreateDefault
{
};
class Mknod final : public Base::CreateDefault
{
};
class Open final : public Base::SearchDefault
{
};
class Readlink final : public Base::SearchDefault
{
};
class RemoveXAttr final : public Base::ActionDefault
{
};
class Rename final : public Base::ActionDefault
{
};
class Rmdir final : public Base::ActionDefault
{
};
class SetXAttr final : public Base::ActionDefault
{
};
class Symlink final : public Base::CreateDefault
{
};
class Truncate final : public Base::ActionDefault
{
};
class Unlink final : public Base::ActionDefault
{
};
class Utimens final : public Base::ActionDefault
{
};
}

16
src/func_access.hpp Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include "func_access_base.hpp"
#include "func_access_factory.hpp"
#include "func_wrapper.hpp"
namespace Func2
{
using Access = FuncWrapper<Func2::AccessBase,
Func2::AccessFactory,
int,
const Branches&,
const fs::path&,
const int&>;
}

26
src/func_access_all.cpp Normal file
View file

@ -0,0 +1,26 @@
#include "func_access_all.hpp"
#include "errno.hpp"
#include "fs_eaccess.hpp"
std::string_view Func2::AccessAll::name() const { return "all"; }
// "all": every branch must satisfy eaccess (including the file existing
// there). Returns the first failure.
int
Func2::AccessAll::operator()(const Branches &branches_,
const fs::path &fusepath_,
const int mode_)
{
bool any = false;
for(const auto &branch : branches_)
{
any = true;
const fs::path fullpath = branch.path / fusepath_;
const int rv = fs::eaccess(fullpath,mode_);
if(rv < 0)
return rv;
}
if(!any)
return -ENOENT;
return 0;
}

10
src/func_access_all.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include "func_access_base.hpp"
namespace Func2 {
class AccessAll : public AccessBase {
public:
AccessAll() {}
std::string_view name() const;
int operator()(const Branches&,const fs::path&,const int);
};
}

24
src/func_access_base.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include "branches.hpp"
#include "fs_path.hpp"
namespace Func2
{
class AccessBase
{
public:
AccessBase() {}
~AccessBase() {}
public:
virtual std::string_view name() const = 0;
public:
virtual int operator()(const Branches &branches,
const fs::path &fusepath,
const int mode) = 0;
};
}

26
src/func_access_epall.cpp Normal file
View file

@ -0,0 +1,26 @@
#include "func_access_epall.hpp"
#include "errno.hpp"
#include "fs_eaccess.hpp"
std::string_view Func2::AccessEPAll::name() const { return "epall"; }
int
Func2::AccessEPAll::operator()(const Branches &branches_,
const fs::path &fusepath_,
const int mode_)
{
bool found = false;
for(const auto &branch : branches_)
{
const fs::path fullpath = branch.path / fusepath_;
const int rv = fs::eaccess(fullpath,mode_);
if(rv == -ENOENT)
continue;
found = true;
if(rv < 0)
return rv;
}
if(!found)
return -ENOENT;
return 0;
}

10
src/func_access_epall.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include "func_access_base.hpp"
namespace Func2 {
class AccessEPAll : public AccessBase {
public:
AccessEPAll() {}
std::string_view name() const;
int operator()(const Branches&,const fs::path&,const int);
};
}

12
src/func_access_erofs.cpp Normal file
View file

@ -0,0 +1,12 @@
#include "func_access_erofs.hpp"
#include "errno.hpp"
std::string_view Func2::AccessEROFS::name() const { return "erofs"; }
int
Func2::AccessEROFS::operator()(const Branches&,
const fs::path&,
const int)
{
return -EROFS;
}

10
src/func_access_erofs.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include "func_access_base.hpp"
namespace Func2 {
class AccessEROFS : public AccessBase {
public:
AccessEROFS() {}
std::string_view name() const;
int operator()(const Branches&,const fs::path&,const int);
};
}

View file

@ -0,0 +1,24 @@
#include "func_access_factory.hpp"
#include "func_access_all.hpp"
#include "func_access_epall.hpp"
#include "func_access_erofs.hpp"
#include "func_access_ff.hpp"
bool
Func2::AccessFactory::valid(const std::string str_)
{
return (bool)make(str_);
}
std::shared_ptr<Func2::AccessBase>
Func2::AccessFactory::make(const std::string_view str_)
{
if(str_ == "all") return std::make_shared<Func2::AccessAll>();
if(str_ == "epall") return std::make_shared<Func2::AccessEPAll>();
if(str_ == "erofs") return std::make_shared<Func2::AccessEROFS>();
if(str_ == "ff") return std::make_shared<Func2::AccessFF>();
return {};
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "func_access_base.hpp"
namespace Func2
{
class AccessFactory
{
public:
static bool valid(const std::string str);
static std::shared_ptr<AccessBase> make(const std::string_view str);
};
}

32
src/func_access_ff.cpp Normal file
View file

@ -0,0 +1,32 @@
#include "func_access_ff.hpp"
#include "errno.hpp"
#include "fs_eaccess.hpp"
std::string_view
Func2::AccessFF::name() const
{
return "ff";
}
int
Func2::AccessFF::operator()(const Branches &branches_,
const fs::path &fusepath_,
const int mode_)
{
fs::path fullpath;
for(const auto &branch : branches_)
{
fullpath = branch.path / fusepath_;
const int rv = fs::eaccess(fullpath,mode_);
if(rv == -ENOENT)
continue;
return rv;
}
return -ENOENT;
}

21
src/func_access_ff.hpp Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include "func_access_base.hpp"
namespace Func2
{
class AccessFF : public AccessBase
{
public:
AccessFF() {}
~AccessFF() {}
public:
std::string_view name() const;
public:
int operator()(const Branches &branches,
const fs::path &fusepath,
const int mode);
};
}

17
src/func_chmod.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "func_chmod_base.hpp"
#include "func_chmod_factory.hpp"
#include "func_wrapper.hpp"
namespace Func2
{
using Chmod = FuncWrapper<Func2::ChmodBase,
Func2::ChmodFactory,
int,
const Branches&,
const fs::path&,
const mode_t&>;
}

42
src/func_chmod_all.cpp Normal file
View file

@ -0,0 +1,42 @@
#include "func_chmod_all.hpp"
#include "errno.hpp"
#include "fs_lchmod.hpp"
std::string_view
Func2::ChmodAll::name() const
{
return "all";
}
int
Func2::ChmodAll::operator()(const Branches &branches_,
const fs::path &fusepath_,
const mode_t mode_)
{
int err;
bool found;
fs::path fullpath;
err = 0;
found = false;
for(const auto &branch : branches_)
{
if(branch.ro())
continue;
fullpath = branch.path / fusepath_;
const int rv = fs::lchmod(fullpath,mode_);
if(rv == -ENOENT)
continue;
found = true;
err = rv;
}
if(!found)
return -ENOENT;
return err;
}

21
src/func_chmod_all.hpp Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include "func_chmod_base.hpp"
namespace Func2
{
class ChmodAll : public ChmodBase
{
public:
ChmodAll() {}
~ChmodAll() {}
public:
std::string_view name() const;
public:
int operator()(const Branches &branches,
const fs::path &fusepath,
const mode_t mode);
};
}

26
src/func_chmod_base.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include "branches.hpp"
#include "fs_path.hpp"
#include <sys/types.h>
namespace Func2
{
class ChmodBase
{
public:
ChmodBase() {}
~ChmodBase() {}
public:
virtual std::string_view name() const = 0;
public:
virtual int operator()(const Branches &branches,
const fs::path &fusepath,
const mode_t mode) = 0;
};
}

10
src/func_chmod_erofs.cpp Normal file
View file

@ -0,0 +1,10 @@
#include "func_chmod_erofs.hpp"
#include "errno.hpp"
std::string_view Func2::ChmodEROFS::name() const { return "erofs"; }
int
Func2::ChmodEROFS::operator()(const Branches&,const fs::path&,const mode_t)
{
return -EROFS;
}

10
src/func_chmod_erofs.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include "func_chmod_base.hpp"
namespace Func2 {
class ChmodEROFS : public ChmodBase {
public:
ChmodEROFS() {}
std::string_view name() const;
int operator()(const Branches&,const fs::path&,const mode_t);
};
}

View file

@ -0,0 +1,20 @@
#include "func_chmod_factory.hpp"
#include "func_chmod_all.hpp"
#include "func_chmod_erofs.hpp"
bool
Func2::ChmodFactory::valid(const std::string str_)
{
return (bool)make(str_);
}
std::shared_ptr<Func2::ChmodBase>
Func2::ChmodFactory::make(const std::string_view str_)
{
if(str_ == "erofs") return std::make_shared<Func2::ChmodEROFS>();
if(str_ == "all" || str_ == "epall") return std::make_shared<Func2::ChmodAll>();
return {};
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "func_chmod_base.hpp"
namespace Func2
{
class ChmodFactory
{
public:
static bool valid(const std::string str);
static std::shared_ptr<ChmodBase> make(const std::string_view str);
};
}

18
src/func_chown.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include "func_chown_base.hpp"
#include "func_chown_factory.hpp"
#include "func_wrapper.hpp"
namespace Func2
{
using Chown = FuncWrapper<Func2::ChownBase,
Func2::ChownFactory,
int,
const Branches&,
const fs::path&,
const uid_t&,
const gid_t&>;
}

43
src/func_chown_all.cpp Normal file
View file

@ -0,0 +1,43 @@
#include "func_chown_all.hpp"
#include "errno.hpp"
#include "fs_lchown.hpp"
std::string_view
Func2::ChownAll::name() const
{
return "all";
}
int
Func2::ChownAll::operator()(const Branches &branches_,
const fs::path &fusepath_,
const uid_t uid_,
const gid_t gid_)
{
int err;
bool found;
fs::path fullpath;
err = 0;
found = false;
for(const auto &branch : branches_)
{
if(branch.ro())
continue;
fullpath = branch.path / fusepath_;
const int rv = fs::lchown(fullpath,uid_,gid_);
if(rv == -ENOENT)
continue;
found = true;
err = rv;
}
if(!found)
return -ENOENT;
return err;
}

22
src/func_chown_all.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include "func_chown_base.hpp"
namespace Func2
{
class ChownAll : public ChownBase
{
public:
ChownAll() {}
~ChownAll() {}
public:
std::string_view name() const;
public:
int operator()(const Branches &branches,
const fs::path &fusepath,
const uid_t uid,
const gid_t gid);
};
}

27
src/func_chown_base.hpp Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "branches.hpp"
#include "fs_path.hpp"
#include <sys/types.h>
namespace Func2
{
class ChownBase
{
public:
ChownBase() {}
~ChownBase() {}
public:
virtual std::string_view name() const = 0;
public:
virtual int operator()(const Branches &branches,
const fs::path &fusepath,
const uid_t uid,
const gid_t gid) = 0;
};
}

10
src/func_chown_erofs.cpp Normal file
View file

@ -0,0 +1,10 @@
#include "func_chown_erofs.hpp"
#include "errno.hpp"
std::string_view Func2::ChownEROFS::name() const { return "erofs"; }
int
Func2::ChownEROFS::operator()(const Branches&,const fs::path&,const uid_t,const gid_t)
{
return -EROFS;
}

10
src/func_chown_erofs.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include "func_chown_base.hpp"
namespace Func2 {
class ChownEROFS : public ChownBase {
public:
ChownEROFS() {}
std::string_view name() const;
int operator()(const Branches&,const fs::path&,const uid_t,const gid_t);
};
}

View file

@ -0,0 +1,20 @@
#include "func_chown_factory.hpp"
#include "func_chown_all.hpp"
#include "func_chown_erofs.hpp"
bool
Func2::ChownFactory::valid(const std::string str_)
{
return (bool)make(str_);
}
std::shared_ptr<Func2::ChownBase>
Func2::ChownFactory::make(const std::string_view str_)
{
if(str_ == "erofs") return std::make_shared<Func2::ChownEROFS>();
if(str_ == "all" || str_ == "epall") return std::make_shared<Func2::ChownAll>();
return {};
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "func_chown_base.hpp"
namespace Func2
{
class ChownFactory
{
public:
static bool valid(const std::string str);
static std::shared_ptr<ChownBase> make(const std::string_view str);
};
}

20
src/func_create.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include "func_create_base.hpp"
#include "func_create_factory.hpp"
#include "func_wrapper.hpp"
namespace Func2
{
using Create = FuncWrapper<Func2::CreateBase,
Func2::CreateFactory,
int,
const ugid_t&,
const Branches&,
const fs::path&,
fuse_file_info_t*&,
const mode_t&,
const mode_t&>;
}

131
src/func_create_all.cpp Normal file
View file

@ -0,0 +1,131 @@
#include "func_create_all.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateAll::name() const
{
return "all";
}
bool
Func2::CreateAll::path_preserving() const
{
return false;
}
int
Func2::CreateAll::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
chosen = &branch;
break;
}
if(!chosen)
return -error;
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

25
src/func_create_all.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateAll : public CreateBase
{
public:
CreateAll() {}
~CreateAll() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

30
src/func_create_base.hpp Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include "branches.hpp"
#include "fs_path.hpp"
#include "ugid.hpp"
#include "fuse.h"
namespace Func2
{
class CreateBase
{
public:
CreateBase() {}
~CreateBase() {}
public:
virtual std::string_view name() const = 0;
virtual bool path_preserving() const = 0;
public:
virtual int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask) = 0;
};
}

113
src/func_create_epall.cpp Normal file
View file

@ -0,0 +1,113 @@
#include "func_create_epall.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateEPALL::name() const
{
return "epall";
}
bool
Func2::CreateEPALL::path_preserving() const
{
return true;
}
int
Func2::CreateEPALL::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
error = ENOENT;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,fusepath_.parent_path()))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
chosen = &branch;
break;
}
if(!chosen)
return -error;
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_epall.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateEPALL : public CreateBase
{
public:
CreateEPALL() {}
~CreateEPALL() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

113
src/func_create_epff.cpp Normal file
View file

@ -0,0 +1,113 @@
#include "func_create_epff.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateEPFF::name() const
{
return "epff";
}
bool
Func2::CreateEPFF::path_preserving() const
{
return true;
}
int
Func2::CreateEPFF::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
error = ENOENT;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,fusepath_.parent_path()))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
chosen = &branch;
break;
}
if(!chosen)
return -error;
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_epff.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateEPFF : public CreateBase
{
public:
CreateEPFF() {}
~CreateEPFF() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

119
src/func_create_eplfs.cpp Normal file
View file

@ -0,0 +1,119 @@
#include "func_create_eplfs.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include <limits>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateEPLFS::name() const
{
return "eplfs";
}
bool
Func2::CreateEPLFS::path_preserving() const
{
return true;
}
int
Func2::CreateEPLFS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
u64 lfs;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
error = ENOENT;
lfs = std::numeric_limits<u64>::max();
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,fusepath_.parent_path()))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
if(info.spaceavail > lfs)
continue;
lfs = info.spaceavail;
chosen = &branch;
}
if(!chosen)
return -error;
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_eplfs.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateEPLFS : public CreateBase
{
public:
CreateEPLFS() {}
~CreateEPLFS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

119
src/func_create_eplus.cpp Normal file
View file

@ -0,0 +1,119 @@
#include "func_create_eplus.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include <limits>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateEPLUS::name() const
{
return "eplus";
}
bool
Func2::CreateEPLUS::path_preserving() const
{
return true;
}
int
Func2::CreateEPLUS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
u64 lus;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
error = ENOENT;
lus = std::numeric_limits<u64>::max();
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,fusepath_.parent_path()))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
if(info.spaceused > lus)
continue;
lus = info.spaceused;
chosen = &branch;
}
if(!chosen)
return -error;
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_eplus.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateEPLUS : public CreateBase
{
public:
CreateEPLUS() {}
~CreateEPLUS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

118
src/func_create_epmfs.cpp Normal file
View file

@ -0,0 +1,118 @@
#include "func_create_epmfs.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateEPMFS::name() const
{
return "epmfs";
}
bool
Func2::CreateEPMFS::path_preserving() const
{
return true;
}
int
Func2::CreateEPMFS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
u64 mfs;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
error = ENOENT;
mfs = 0;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,fusepath_.parent_path()))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
if(info.spaceavail < mfs)
continue;
mfs = info.spaceavail;
chosen = &branch;
}
if(!chosen)
return -error;
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_epmfs.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateEPMFS : public CreateBase
{
public:
CreateEPMFS() {}
~CreateEPMFS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

141
src/func_create_eppfrd.cpp Normal file
View file

@ -0,0 +1,141 @@
#include "func_create_eppfrd.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "rnd.hpp"
#include <vector>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateEPPFRD::name() const
{
return "eppfrd";
}
bool
Func2::CreateEPPFRD::path_preserving() const
{
return true;
}
int
Func2::CreateEPPFRD::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
u64 sum;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
struct W { Branch *b; u64 weight; };
std::vector<W> writable;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
error = ENOENT;
sum = 0;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,fusepath_.parent_path()))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
writable.push_back({&branch,info.spaceavail});
sum += info.spaceavail;
}
if(writable.empty())
return -error;
if(sum == 0)
return -ENOSPC;
{
u64 idx;
u64 threshold;
idx = 0;
threshold = RND::rand64(sum);
for(const auto &w : writable)
{
if(w.weight == 0)
continue;
idx += w.weight;
if(idx > threshold)
{
chosen = w.b;
break;
}
}
if(!chosen)
chosen = writable.back().b;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateEPPFRD : public CreateBase
{
public:
CreateEPPFRD() {}
~CreateEPPFRD() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

116
src/func_create_eprand.cpp Normal file
View file

@ -0,0 +1,116 @@
#include "func_create_eprand.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "rnd.hpp"
#include <vector>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateEPRAND::name() const
{
return "eprand";
}
bool
Func2::CreateEPRAND::path_preserving() const
{
return true;
}
int
Func2::CreateEPRAND::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
std::vector<Branch*> writable;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
error = ENOENT;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,fusepath_.parent_path()))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
writable.push_back(&branch);
}
if(writable.empty())
return -error;
chosen = writable[RND::rand64(writable.size())];
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateEPRAND : public CreateBase
{
public:
CreateEPRAND() {}
~CreateEPRAND() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

35
src/func_create_erofs.cpp Normal file
View file

@ -0,0 +1,35 @@
#include "func_create_erofs.hpp"
#include "errno.hpp"
std::string_view
Func2::CreateEROFS::name() const
{
return "erofs";
}
bool
Func2::CreateEROFS::path_preserving() const
{
return false;
}
int
Func2::CreateEROFS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
(void)ugid_;
(void)branches_;
(void)fusepath_;
(void)ffi_;
(void)mode_;
(void)umask_;
return -EROFS;
}

25
src/func_create_erofs.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateEROFS : public CreateBase
{
public:
CreateEROFS() {}
~CreateEROFS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

View file

@ -0,0 +1,58 @@
#include "func_create_factory.hpp"
#include "func_create_all.hpp"
#include "func_create_epall.hpp"
#include "func_create_epff.hpp"
#include "func_create_eplfs.hpp"
#include "func_create_eplus.hpp"
#include "func_create_epmfs.hpp"
#include "func_create_eppfrd.hpp"
#include "func_create_eprand.hpp"
#include "func_create_erofs.hpp"
#include "func_create_ff.hpp"
#include "func_create_lfs.hpp"
#include "func_create_lup.hpp"
#include "func_create_lus.hpp"
#include "func_create_mfs.hpp"
#include "func_create_msplfs.hpp"
#include "func_create_msplus.hpp"
#include "func_create_mspmfs.hpp"
#include "func_create_msppfrd.hpp"
#include "func_create_newest.hpp"
#include "func_create_pfrd.hpp"
#include "func_create_rand.hpp"
bool
Func2::CreateFactory::valid(const std::string str_)
{
return (bool)make(str_);
}
std::shared_ptr<Func2::CreateBase>
Func2::CreateFactory::make(const std::string_view str_)
{
if(str_ == "all") return std::make_shared<Func2::CreateAll>();
if(str_ == "epall") return std::make_shared<Func2::CreateEPALL>();
if(str_ == "epff") return std::make_shared<Func2::CreateEPFF>();
if(str_ == "eplfs") return std::make_shared<Func2::CreateEPLFS>();
if(str_ == "eplus") return std::make_shared<Func2::CreateEPLUS>();
if(str_ == "epmfs") return std::make_shared<Func2::CreateEPMFS>();
if(str_ == "eppfrd") return std::make_shared<Func2::CreateEPPFRD>();
if(str_ == "eprand") return std::make_shared<Func2::CreateEPRAND>();
if(str_ == "erofs") return std::make_shared<Func2::CreateEROFS>();
if(str_ == "ff") return std::make_shared<Func2::CreateFF>();
if(str_ == "lfs") return std::make_shared<Func2::CreateLFS>();
if(str_ == "lup") return std::make_shared<Func2::CreateLUP>();
if(str_ == "lus") return std::make_shared<Func2::CreateLUS>();
if(str_ == "mfs") return std::make_shared<Func2::CreateMFS>();
if(str_ == "msplfs") return std::make_shared<Func2::CreateMSPLFS>();
if(str_ == "msplus") return std::make_shared<Func2::CreateMSPLUS>();
if(str_ == "mspmfs") return std::make_shared<Func2::CreateMSPMFS>();
if(str_ == "msppfrd") return std::make_shared<Func2::CreateMSPPFRD>();
if(str_ == "newest") return std::make_shared<Func2::CreateNewest>();
if(str_ == "pfrd") return std::make_shared<Func2::CreatePFRD>();
if(str_ == "rand") return std::make_shared<Func2::CreateRAND>();
return {};
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <memory>
#include <string_view>
namespace Func2
{
class CreateBase;
class CreateFactory
{
public:
static bool valid(const std::string str_);
static std::shared_ptr<CreateBase> make(const std::string_view str_);
};
}

131
src/func_create_ff.cpp Normal file
View file

@ -0,0 +1,131 @@
#include "func_create_ff.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateFF::name() const
{
return "ff";
}
bool
Func2::CreateFF::path_preserving() const
{
return false;
}
int
Func2::CreateFF::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
chosen = &branch;
break;
}
if(!chosen)
return -error;
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

25
src/func_create_ff.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateFF : public CreateBase
{
public:
CreateFF() {}
~CreateFF() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

137
src/func_create_lfs.cpp Normal file
View file

@ -0,0 +1,137 @@
#include "func_create_lfs.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include <limits>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateLFS::name() const
{
return "lfs";
}
bool
Func2::CreateLFS::path_preserving() const
{
return false;
}
int
Func2::CreateLFS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
u64 lfs;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
lfs = std::numeric_limits<u64>::max();
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
if(info.spaceavail > lfs)
continue;
lfs = info.spaceavail;
chosen = &branch;
}
if(!chosen)
return -error;
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_lfs.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateLFS : public CreateBase
{
public:
CreateLFS() {}
~CreateLFS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

145
src/func_create_lup.cpp Normal file
View file

@ -0,0 +1,145 @@
#include "func_create_lup.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateLUP::name() const
{
return "lup";
}
bool
Func2::CreateLUP::path_preserving() const
{
return false;
}
int
Func2::CreateLUP::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
long double best_ratio;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
best_ratio = 2.0L;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
{
u64 used = info.spaceused;
u64 total = info.spaceused + info.spaceavail;
long double ratio;
if(total == 0)
{ used = 0; total = 1; }
ratio = (long double)used / (long double)total;
if(ratio >= best_ratio)
continue;
best_ratio = ratio;
chosen = &branch;
}
}
if(!chosen)
return -error;
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_lup.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateLUP : public CreateBase
{
public:
CreateLUP() {}
~CreateLUP() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

137
src/func_create_lus.cpp Normal file
View file

@ -0,0 +1,137 @@
#include "func_create_lus.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include <limits>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateLUS::name() const
{
return "lus";
}
bool
Func2::CreateLUS::path_preserving() const
{
return false;
}
int
Func2::CreateLUS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
u64 lus;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
lus = std::numeric_limits<u64>::max();
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
if(info.spaceused > lus)
continue;
lus = info.spaceused;
chosen = &branch;
}
if(!chosen)
return -error;
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_lus.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateLUS : public CreateBase
{
public:
CreateLUS() {}
~CreateLUS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

136
src/func_create_mfs.cpp Normal file
View file

@ -0,0 +1,136 @@
#include "func_create_mfs.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateMFS::name() const
{
return "mfs";
}
bool
Func2::CreateMFS::path_preserving() const
{
return false;
}
int
Func2::CreateMFS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
u64 mfs;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
mfs = 0;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
if(info.spaceavail < mfs)
continue;
mfs = info.spaceavail;
chosen = &branch;
}
if(!chosen)
return -error;
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_mfs.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateMFS : public CreateBase
{
public:
CreateMFS() {}
~CreateMFS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

172
src/func_create_msplfs.cpp Normal file
View file

@ -0,0 +1,172 @@
#include "func_create_msplfs.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "fs_mkdir_as.hpp"
#include <vector>
#include <limits>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateMSPLFS::name() const
{
return "msplfs";
}
bool
Func2::CreateMSPLFS::path_preserving() const
{
return true;
}
int
Func2::CreateMSPLFS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
fs::path walk;
std::vector<fs::path> segments;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
mode_t dirmode;
branches = branches_;
chosen = nullptr;
error = ENOENT;
walk = fusepath_.parent_path();
for(;;)
{
bool any;
u64 lfs;
any = false;
lfs = std::numeric_limits<u64>::max();
chosen = nullptr;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,walk))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
any = true;
if(info.spaceavail > lfs)
continue;
lfs = info.spaceavail;
chosen = &branch;
}
if(any)
break;
if(walk == "/")
break;
walk = walk.parent_path();
}
if(!chosen)
return -error;
for(fs::path p = fusepath_.parent_path();
!p.empty() && p != "/";
p = p.parent_path())
segments.push_back(p);
dirmode = (0777 & ~umask_);
for(auto it = segments.rbegin(); it != segments.rend(); ++it)
{
const Branch *src;
const fs::path full = chosen->path / *it;
if(fs::exists(full))
continue;
src = nullptr;
for(auto &b : *branches)
{
if(&b == chosen)
continue;
if(!fs::exists(b.path,*it))
continue;
src = &b;
break;
}
if(src != nullptr)
rv = fs::clonepath(src->path,chosen->path,*it);
else
rv = fs::mkdir_as(ugid_,full,dirmode);
if((rv < 0) && (rv != -EEXIST))
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateMSPLFS : public CreateBase
{
public:
CreateMSPLFS() {}
~CreateMSPLFS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

172
src/func_create_msplus.cpp Normal file
View file

@ -0,0 +1,172 @@
#include "func_create_msplus.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "fs_mkdir_as.hpp"
#include <vector>
#include <limits>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateMSPLUS::name() const
{
return "msplus";
}
bool
Func2::CreateMSPLUS::path_preserving() const
{
return true;
}
int
Func2::CreateMSPLUS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
fs::path walk;
std::vector<fs::path> segments;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
mode_t dirmode;
branches = branches_;
chosen = nullptr;
error = ENOENT;
walk = fusepath_.parent_path();
for(;;)
{
bool any;
u64 lus;
any = false;
lus = std::numeric_limits<u64>::max();
chosen = nullptr;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,walk))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
any = true;
if(info.spaceused > lus)
continue;
lus = info.spaceused;
chosen = &branch;
}
if(any)
break;
if(walk == "/")
break;
walk = walk.parent_path();
}
if(!chosen)
return -error;
for(fs::path p = fusepath_.parent_path();
!p.empty() && p != "/";
p = p.parent_path())
segments.push_back(p);
dirmode = (0777 & ~umask_);
for(auto it = segments.rbegin(); it != segments.rend(); ++it)
{
const Branch *src;
const fs::path full = chosen->path / *it;
if(fs::exists(full))
continue;
src = nullptr;
for(auto &b : *branches)
{
if(&b == chosen)
continue;
if(!fs::exists(b.path,*it))
continue;
src = &b;
break;
}
if(src != nullptr)
rv = fs::clonepath(src->path,chosen->path,*it);
else
rv = fs::mkdir_as(ugid_,full,dirmode);
if((rv < 0) && (rv != -EEXIST))
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateMSPLUS : public CreateBase
{
public:
CreateMSPLUS() {}
~CreateMSPLUS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

172
src/func_create_mspmfs.cpp Normal file
View file

@ -0,0 +1,172 @@
#include "func_create_mspmfs.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "fs_mkdir_as.hpp"
#include <vector>
#include <limits>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateMSPMFS::name() const
{
return "mspmfs";
}
bool
Func2::CreateMSPMFS::path_preserving() const
{
return true;
}
int
Func2::CreateMSPMFS::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
fs::path walk;
std::vector<fs::path> segments;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
mode_t dirmode;
branches = branches_;
chosen = nullptr;
error = ENOENT;
walk = fusepath_.parent_path();
for(;;)
{
bool any;
u64 mfs;
any = false;
mfs = 0;
chosen = nullptr;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,walk))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
any = true;
if(info.spaceavail < mfs)
continue;
mfs = info.spaceavail;
chosen = &branch;
}
if(any)
break;
if(walk == "/")
break;
walk = walk.parent_path();
}
if(!chosen)
return -error;
for(fs::path p = fusepath_.parent_path();
!p.empty() && p != "/";
p = p.parent_path())
segments.push_back(p);
dirmode = (0777 & ~umask_);
for(auto it = segments.rbegin(); it != segments.rend(); ++it)
{
const Branch *src;
const fs::path full = chosen->path / *it;
if(fs::exists(full))
continue;
src = nullptr;
for(auto &b : *branches)
{
if(&b == chosen)
continue;
if(!fs::exists(b.path,*it))
continue;
src = &b;
break;
}
if(src != nullptr)
rv = fs::clonepath(src->path,chosen->path,*it);
else
rv = fs::mkdir_as(ugid_,full,dirmode);
if((rv < 0) && (rv != -EEXIST))
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateMSPMFS : public CreateBase
{
public:
CreateMSPMFS() {}
~CreateMSPMFS() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

195
src/func_create_msppfrd.cpp Normal file
View file

@ -0,0 +1,195 @@
#include "func_create_msppfrd.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "fs_mkdir_as.hpp"
#include "rnd.hpp"
#include <vector>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateMSPPFRD::name() const
{
return "msppfrd";
}
bool
Func2::CreateMSPPFRD::path_preserving() const
{
return true;
}
int
Func2::CreateMSPPFRD::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
fs::path walk;
std::vector<fs::path> segments;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
mode_t dirmode;
branches = branches_;
chosen = nullptr;
error = ENOENT;
walk = fusepath_.parent_path();
for(;;)
{
u64 sum;
struct W { Branch *b; u64 weight; };
std::vector<W> writable;
sum = 0;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,walk))
error_and_continue(error,ENOENT);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
writable.push_back({&branch,info.spaceavail});
sum += info.spaceavail;
}
if(!writable.empty())
{
if(sum == 0)
{
chosen = writable.front().b;
}
else
{
u64 idx;
u64 threshold;
idx = 0;
threshold = RND::rand64(sum);
for(const auto &w : writable)
{
if(w.weight == 0)
continue;
idx += w.weight;
if(idx > threshold)
{
chosen = w.b;
break;
}
}
if(!chosen)
chosen = writable.back().b;
}
break;
}
if(walk == "/")
break;
walk = walk.parent_path();
}
if(!chosen)
return -error;
for(fs::path p = fusepath_.parent_path();
!p.empty() && p != "/";
p = p.parent_path())
segments.push_back(p);
dirmode = (0777 & ~umask_);
for(auto it = segments.rbegin(); it != segments.rend(); ++it)
{
const Branch *src;
const fs::path full = chosen->path / *it;
if(fs::exists(full))
continue;
src = nullptr;
for(auto &b : *branches)
{
if(&b == chosen)
continue;
if(!fs::exists(b.path,*it))
continue;
src = &b;
break;
}
if(src != nullptr)
rv = fs::clonepath(src->path,chosen->path,*it);
else
rv = fs::mkdir_as(ugid_,full,dirmode);
if((rv < 0) && (rv != -EEXIST))
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateMSPPFRD : public CreateBase
{
public:
CreateMSPPFRD() {}
~CreateMSPPFRD() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

139
src/func_create_newest.cpp Normal file
View file

@ -0,0 +1,139 @@
#include "func_create_newest.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "timespec_utils.hpp"
#include <sys/stat.h>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateNewest::name() const
{
return "newest";
}
bool
Func2::CreateNewest::path_preserving() const
{
return false;
}
int
Func2::CreateNewest::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
struct stat st;
timespec newest;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
newest = {0,0};
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
if(!fs::exists(branch.path,fusepath_,&st))
error_and_continue(error,ENOENT);
if(chosen && (st.st_mtim < newest))
continue;
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
newest = st.st_mtim;
chosen = &branch;
}
if(!chosen)
return -error;
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateNewest : public CreateBase
{
public:
CreateNewest() {}
~CreateNewest() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

159
src/func_create_pfrd.cpp Normal file
View file

@ -0,0 +1,159 @@
#include "func_create_pfrd.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "rnd.hpp"
#include <vector>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreatePFRD::name() const
{
return "pfrd";
}
bool
Func2::CreatePFRD::path_preserving() const
{
return false;
}
int
Func2::CreatePFRD::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
u64 sum;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
struct W { Branch *b; u64 weight; };
std::vector<W> writable;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
sum = 0;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
writable.push_back({&branch,info.spaceavail});
sum += info.spaceavail;
}
if(writable.empty())
return -error;
if(sum == 0)
return -ENOSPC;
{
u64 idx;
u64 threshold;
idx = 0;
threshold = RND::rand64(sum);
for(const auto &w : writable)
{
if(w.weight == 0)
continue;
idx += w.weight;
if(idx > threshold)
{
chosen = w.b;
break;
}
}
if(!chosen)
chosen = writable.back().b;
}
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

25
src/func_create_pfrd.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreatePFRD : public CreateBase
{
public:
CreatePFRD() {}
~CreatePFRD() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

134
src/func_create_rand.cpp Normal file
View file

@ -0,0 +1,134 @@
#include "func_create_rand.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"
#include "fs_info_t.hpp"
#include "fs_path.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_open_as.hpp"
#include "rnd.hpp"
#include <vector>
#define error_and_continue(CUR,ERR) \
do { \
::_calc_error(CUR,ERR); \
continue; \
} while(0)
static
inline
void
_calc_error(int &cur_,
const int err_)
{
switch(cur_)
{
default:
case ENOENT:
cur_ = err_;
break;
case ENOSPC:
if(err_ != ENOENT)
cur_ = err_;
break;
case EROFS:
if((err_ != ENOENT) && (err_ != ENOSPC))
cur_ = err_;
break;
}
}
std::string_view
Func2::CreateRAND::name() const
{
return "rand";
}
bool
Func2::CreateRAND::path_preserving() const
{
return false;
}
int
Func2::CreateRAND::operator()(const ugid_t &ugid_,
const Branches &branches_,
const fs::path &fusepath_,
fuse_file_info_t *&ffi_,
const mode_t &mode_,
const mode_t &umask_)
{
int rv;
int error;
fs::info_t info;
Branches::Ptr branches;
const Branch *chosen;
const Branch *clone_src;
std::vector<Branch*> writable;
int fd;
fs::path fullpath;
mode_t mode;
FileInfo *fi;
branches = branches_;
chosen = nullptr;
clone_src = nullptr;
error = ENOENT;
for(auto &branch : *branches)
{
if(branch.ro_or_nc())
error_and_continue(error,EROFS);
rv = fs::info(branch.path,&info);
if(rv < 0)
error_and_continue(error,ENOENT);
if(info.readonly)
error_and_continue(error,EROFS);
if(info.spaceavail < branch.minfreespace())
error_and_continue(error,ENOSPC);
writable.push_back(&branch);
}
if(writable.empty())
return -error;
chosen = writable[RND::rand64(writable.size())];
for(auto &branch : *branches)
{
if(!fs::exists(branch.path,fusepath_.parent_path()))
continue;
clone_src = &branch;
break;
}
if(!clone_src)
return -ENOENT;
if(clone_src != chosen)
{
rv = fs::clonepath(clone_src->path,chosen->path,fusepath_.parent_path());
if(rv < 0)
return rv;
}
fullpath = chosen->path / fusepath_;
mode = mode_;
if(!fs::acl::dir_has_defaults(fullpath))
mode &= ~umask_;
fd = fs::open_as(ugid_,fullpath,ffi_->flags,mode);
if(fd < 0)
return fd;
fi = new FileInfo(fd,chosen,fusepath_,ffi_->direct_io);
ffi_->fh = fi->to_fh();
return 0;
}

23
src/func_create_rand.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "func_create_base.hpp"
namespace Func2
{
class CreateRAND : public CreateBase
{
public:
CreateRAND() {}
~CreateRAND() {}
public:
std::string_view name() const;
bool path_preserving() const;
public:
int operator()(const ugid_t &ugid,
const Branches &branches,
const fs::path &fusepath,
fuse_file_info_t *&ffi,
const mode_t &mode,
const mode_t &umask);
};
}

20
src/func_getattr.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include "func_getattr_base.hpp"
#include "func_getattr_factory.hpp"
#include "func_wrapper.hpp"
namespace Func2
{
using GetAttr = FuncWrapper<Func2::GetAttrBase,
Func2::GetAttrFactory,
int,
const Branches&,
const fs::path&,
struct stat*&,
const FollowSymlinksEnum&,
const bool&,
const s64&>;
}

36
src/func_getattr_base.hpp Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include "follow_symlinks_enum.hpp"
#include "fs_path.hpp"
#include "branches.hpp"
#include "base_types.h"
#include "fuse.h"
#include <string_view>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
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 bool symlinkify,
const s64 symlinkify_timeout) = 0;
};
}

78
src/func_getattr_cdco.cpp Normal file
View file

@ -0,0 +1,78 @@
#include "func_getattr_cdco.hpp"
#include "fs_inode.hpp"
#include "fs_stat.hpp"
#include "symlinkify.hpp"
#include "timespec_utils.hpp"
std::string_view
Func2::GetAttrCDCO::name() const
{
return "cdco";
}
int
Func2::GetAttrCDCO::operator()(const Branches &branches_,
const fs::path &fusepath_,
struct stat *st_,
const FollowSymlinksEnum follow_symlinks_,
const bool symlinkify_,
const s64 symlinkify_timeout_)
{
int rv;
int err;
fs::path fullpath;
const Branch *first_branch;
err = 0;
first_branch = nullptr;
for(const auto &branch : branches_)
{
struct stat tmp_st;
fullpath = branch.path / fusepath_;
rv = fs::stat(fullpath,&tmp_st,follow_symlinks_);
if(rv < 0)
{
err = rv;
continue;
}
if(not first_branch)
{
*st_ = tmp_st;
first_branch = &branch;
continue;
}
// This upgrades the uid:gid because mergerfs now does most
// file interaction as root and relies on `default_permissions`
// to manage permissions. Want to ensure that root owned files
// can't be changed so we treat them all as root owned.
if(tmp_st.st_uid == 0)
st_->st_uid = 0;
if(tmp_st.st_gid == 0)
st_->st_gid = 0;
st_->st_atim = TimeSpec::newest(st_->st_atim,tmp_st.st_atim);
st_->st_ctim = TimeSpec::newest(st_->st_ctim,tmp_st.st_ctim);
st_->st_mtim = TimeSpec::newest(st_->st_mtim,tmp_st.st_mtim);
st_->st_nlink += tmp_st.st_nlink;
}
if(!first_branch)
return err;
if(symlinkify_)
{
fullpath = first_branch->path / fusepath_;
symlinkify::convert_if_can_be_symlink(fullpath,
st_,
symlinkify_timeout_);
}
fs::inode::calc(first_branch->path,fusepath_,st_);
return 0;
}

Some files were not shown because too many files have changed in this diff Show more