Add support for FUSE passthrough
Some checks are pending
CodeQL / Analyze (push) Waiting to run
mkdocs / deploy (push) Waiting to run

This commit is contained in:
trapexit 2025-06-05 23:47:42 -05:00 • committed by GitHub
parent 92312c8507
commit e310d24615
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
412 changed files with 43707 additions and 17432 deletions

View file

@ -14,6 +14,8 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "state.hpp"
#include "config.hpp"
#include "errno.hpp"
#include "fileinfo.hpp"
@ -21,7 +23,8 @@
#include "fs_clonepath.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "procfs_get_name.hpp"
#include "fuse_passthrough.hpp"
#include "procfs.hpp"
#include "ugid.hpp"
#include "fuse.h"
@ -30,184 +33,323 @@
#include <vector>
namespace l
/*
The kernel expects being able to issue read requests when running
with writeback caching enabled so we must change O_WRONLY to
O_RDWR.
With writeback caching enabled the kernel handles O_APPEND. Could
be an issue if the underlying file changes out of band but that is
true of any caching.
*/
static
void
_tweak_flags_writeback_cache(int *flags_)
{
/*
The kernel expects being able to issue read requests when running
with writeback caching enabled so we must change O_WRONLY to
O_RDWR.
if((*flags_ & O_ACCMODE) == O_WRONLY)
*flags_ = ((*flags_ & ~O_ACCMODE) | O_RDWR);
if(*flags_ & O_APPEND)
*flags_ &= ~O_APPEND;
}
With writeback caching enabled the kernel handles O_APPEND. Could
be an issue if the underlying file changes out of band but that is
true of any caching.
*/
static
void
tweak_flags_writeback_cache(int *flags_)
{
if((*flags_ & O_ACCMODE) == O_WRONLY)
*flags_ = ((*flags_ & ~O_ACCMODE) | O_RDWR);
if(*flags_ & O_APPEND)
*flags_ &= ~O_APPEND;
}
static
bool
_rdonly(const int flags_)
{
return ((flags_ & O_ACCMODE) == O_RDONLY);
}
static
bool
rdonly(const int flags_)
{
return ((flags_ & O_ACCMODE) == O_RDONLY);
}
static
bool
_calculate_flush(FlushOnClose const flushonclose_,
int const flags_)
{
switch(flushonclose_)
{
case FlushOnCloseEnum::NEVER:
return false;
case FlushOnCloseEnum::OPENED_FOR_WRITE:
return !::_rdonly(flags_);
case FlushOnCloseEnum::ALWAYS:
return true;
}
static
bool
calculate_flush(FlushOnClose const flushonclose_,
int const flags_)
{
switch(flushonclose_)
{
case FlushOnCloseEnum::NEVER:
return false;
case FlushOnCloseEnum::OPENED_FOR_WRITE:
return !l::rdonly(flags_);
case FlushOnCloseEnum::ALWAYS:
return true;
}
return true;
}
return true;
}
static
void
_config_to_ffi_flags(Config::Read &cfg_,
const int tid_,
fuse_file_info_t *ffi_)
{
switch(cfg_->cache_files)
{
case CacheFiles::ENUM::LIBFUSE:
ffi_->direct_io = cfg_->direct_io;
ffi_->keep_cache = cfg_->kernel_cache;
ffi_->auto_cache = cfg_->auto_cache;
break;
case CacheFiles::ENUM::OFF:
ffi_->direct_io = 1;
ffi_->keep_cache = 0;
ffi_->auto_cache = 0;
break;
case CacheFiles::ENUM::PARTIAL:
ffi_->direct_io = 0;
ffi_->keep_cache = 0;
ffi_->auto_cache = 0;
break;
case CacheFiles::ENUM::FULL:
ffi_->direct_io = 0;
ffi_->keep_cache = 1;
ffi_->auto_cache = 0;
break;
case CacheFiles::ENUM::AUTO_FULL:
ffi_->direct_io = 0;
ffi_->keep_cache = 0;
ffi_->auto_cache = 1;
break;
case CacheFiles::ENUM::PER_PROCESS:
std::string proc_name;
static
void
config_to_ffi_flags(Config::Read &cfg_,
const int tid_,
fuse_file_info_t *ffi_)
{
switch(cfg_->cache_files)
{
case CacheFiles::ENUM::LIBFUSE:
ffi_->direct_io = cfg_->direct_io;
ffi_->keep_cache = cfg_->kernel_cache;
ffi_->auto_cache = cfg_->auto_cache;
break;
case CacheFiles::ENUM::OFF:
ffi_->direct_io = 1;
ffi_->keep_cache = 0;
ffi_->auto_cache = 0;
break;
case CacheFiles::ENUM::PARTIAL:
ffi_->direct_io = 0;
ffi_->keep_cache = 0;
ffi_->auto_cache = 0;
break;
case CacheFiles::ENUM::FULL:
ffi_->direct_io = 0;
ffi_->keep_cache = 1;
ffi_->auto_cache = 0;
break;
case CacheFiles::ENUM::AUTO_FULL:
ffi_->direct_io = 0;
ffi_->keep_cache = 0;
ffi_->auto_cache = 1;
break;
case CacheFiles::ENUM::PER_PROCESS:
std::string proc_name;
proc_name = procfs::get_name(tid_);
if(cfg_->cache_files_process_names.count(proc_name) == 0)
{
ffi_->direct_io = 1;
ffi_->keep_cache = 0;
ffi_->auto_cache = 0;
}
else
{
ffi_->direct_io = 0;
ffi_->keep_cache = 0;
ffi_->auto_cache = 0;
}
break;
}
proc_name = procfs::get_name(tid_);
if(cfg_->cache_files_process_names.count(proc_name) == 0)
{
ffi_->direct_io = 1;
ffi_->keep_cache = 0;
ffi_->auto_cache = 0;
}
else
{
ffi_->direct_io = 0;
ffi_->keep_cache = 0;
ffi_->auto_cache = 0;
}
break;
}
if(cfg_->parallel_direct_writes == true)
ffi_->parallel_direct_writes = ffi_->direct_io;
}
if(cfg_->parallel_direct_writes == true)
ffi_->parallel_direct_writes = ffi_->direct_io;
}
static
int
_create_core(const std::string &fullpath_,
mode_t mode_,
const mode_t umask_,
const int flags_)
{
if(!fs::acl::dir_has_defaults(fullpath_))
mode_ &= ~umask_;
static
int
create_core(const std::string &fullpath_,
mode_t mode_,
const mode_t umask_,
const int flags_)
{
if(!fs::acl::dir_has_defaults(fullpath_))
mode_ &= ~umask_;
return fs::open(fullpath_,flags_,mode_);
}
return fs::open(fullpath_,flags_,mode_);
}
static
int
_create_core(const Branch *branch_,
const char *fusepath_,
fuse_file_info_t *ffi_,
const mode_t mode_,
const mode_t umask_)
{
int rv;
FileInfo *fi;
std::string fullpath;
static
int
create_core(const Branch *branch_,
const char *fusepath_,
fuse_file_info_t *ffi_,
const mode_t mode_,
const mode_t umask_)
{
int rv;
FileInfo *fi;
std::string fullpath;
fullpath = fs::path::make(branch_->path,fusepath_);
fullpath = fs::path::make(branch_->path,fusepath_);
rv = ::_create_core(fullpath,mode_,umask_,ffi_->flags);
if(rv == -1)
return -errno;
rv = l::create_core(fullpath,mode_,umask_,ffi_->flags);
if(rv == -1)
return -errno;
fi = new FileInfo(rv,branch_,fusepath_,ffi_->direct_io);
fi = new FileInfo(rv,branch_,fusepath_,ffi_->direct_io);
ffi_->fh = reinterpret_cast<uint64_t>(fi);
ffi_->fh = reinterpret_cast<uint64_t>(fi);
return 0;
}
static
int
_create(const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const char *fusepath_,
fuse_file_info_t *ffi_,
const mode_t mode_,
const mode_t umask_)
{
int rv;
std::string fullpath;
std::string fusedirpath;
std::vector<Branch*> createpaths;
std::vector<Branch*> existingpaths;
fusedirpath = fs::path::dirname(fusepath_);
rv = searchFunc_(branches_,fusedirpath,existingpaths);
if(rv == -1)
return -errno;
rv = createFunc_(branches_,fusedirpath,createpaths);
if(rv == -1)
return -errno;
rv = fs::clonepath_as_root(existingpaths[0]->path,
createpaths[0]->path,
fusedirpath);
if(rv == -1)
return -errno;
return ::_create_core(createpaths[0],
fusepath_,
ffi_,
mode_,
umask_);
}
constexpr
const
uint64_t
_(const PassthroughEnum e_,
const uint64_t m_)
{
return ((((uint64_t)e_) << 32) | (m_ & O_ACCMODE));
}
static
int
_create_for_insert_lambda(const fuse_context *fc_,
const char *fusepath_,
const mode_t mode_,
fuse_file_info_t *ffi_,
State::OpenFile *of_)
{
int rv;
FileInfo *fi;
Config::Read cfg;
const ugid::Set ugid(fc_->uid,fc_->gid);
::_config_to_ffi_flags(cfg,fc_->pid,ffi_);
if(cfg->writeback_cache)
::_tweak_flags_writeback_cache(&ffi_->flags);
ffi_->noflush = !::_calculate_flush(cfg->flushonclose,
ffi_->flags);
rv = ::_create(cfg->func.getattr.policy,
cfg->func.create.policy,
cfg->branches,
fusepath_,
ffi_,
mode_,
fc_->umask);
if(rv == -EROFS)
{
Config::Write()->branches.find_and_set_mode_ro();
rv = ::_create(cfg->func.getattr.policy,
cfg->func.create.policy,
cfg->branches,
fusepath_,
ffi_,
mode_,
fc_->umask);
}
if(rv < 0)
return rv;
fi = reinterpret_cast<FileInfo*>(ffi_->fh);
of_->ref_count = 1;
of_->fi = fi;
switch(_(cfg->passthrough,ffi_->flags))
{
case _(Passthrough::ENUM::RO,O_RDONLY):
case _(Passthrough::ENUM::WO,O_WRONLY):
case _(Passthrough::ENUM::RW,O_RDONLY):
case _(Passthrough::ENUM::RW,O_WRONLY):
case _(Passthrough::ENUM::RW,O_RDWR):
break;
default:
return 0;
}
of_->backing_id = FUSE::passthrough_open(fc_,fi->fd);
if(of_->backing_id < 0)
return 0;
}
static
int
create(const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const char *fusepath_,
fuse_file_info_t *ffi_,
const mode_t mode_,
const mode_t umask_)
{
int rv;
std::string fullpath;
std::string fusedirpath;
std::vector<Branch*> createpaths;
std::vector<Branch*> existingpaths;
ffi_->backing_id = of_->backing_id;
ffi_->passthrough = true;
ffi_->keep_cache = false;
fusedirpath = fs::path::dirname(fusepath_);
return 0;
}
rv = searchFunc_(branches_,fusedirpath,existingpaths);
if(rv == -1)
return -errno;
static
inline
constexpr
auto
_create_insert_lambda(const fuse_context *fc_,
const char *fusepath_,
const mode_t mode_,
fuse_file_info_t *ffi_,
int *_rv_)
{
return
[=](auto &val_)
{
*_rv_ = ::_create_for_insert_lambda(fc_,
fusepath_,
mode_,
ffi_,
&val_.second);
};
}
rv = createFunc_(branches_,fusedirpath,createpaths);
if(rv == -1)
return -errno;
// This function should never be called?
static
inline
constexpr
auto
_create_update_lambda()
{
return
[=](auto &val_)
{
fmt::print(stderr,"THIS SHOULD NOT HAPPEN");
abort();
};
}
rv = fs::clonepath_as_root(existingpaths[0]->path,
createpaths[0]->path,
fusedirpath);
if(rv == -1)
return -errno;
static
int
_create(const fuse_context *fc_,
const char *fusepath_,
mode_t mode_,
fuse_file_info_t *ffi_)
{
int rv;
auto &of = state.open_files;
return l::create_core(createpaths[0],
fusepath_,
ffi_,
mode_,
umask_);
}
rv = -EINVAL;
of.try_emplace_and_visit(fc_->nodeid,
::_create_insert_lambda(fc_,fusepath_,mode_,ffi_,&rv),
::_create_update_lambda());
// Can't abort an emplace_and_visit and can't assume another thread
// hasn't created an entry since this failure so erase only if
// ref_count is default (0).
if(rv < 0)
of.erase_if(fc_->nodeid,
[](auto &val_)
{
return (val_.second.ref_count <= 0);
});
return rv;
}
namespace FUSE
@ -217,38 +359,9 @@ namespace FUSE
mode_t mode_,
fuse_file_info_t *ffi_)
{
int rv;
Config::Read cfg;
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
l::config_to_ffi_flags(cfg,fc->pid,ffi_);
if(cfg->writeback_cache)
l::tweak_flags_writeback_cache(&ffi_->flags);
ffi_->noflush = !l::calculate_flush(cfg->flushonclose,
ffi_->flags);
rv = l::create(cfg->func.getattr.policy,
cfg->func.create.policy,
cfg->branches,
fusepath_,
ffi_,
mode_,
fc->umask);
if(rv == -EROFS)
{
Config::Write()->branches.find_and_set_mode_ro();
rv = l::create(cfg->func.getattr.policy,
cfg->func.create.policy,
cfg->branches,
fusepath_,
ffi_,
mode_,
fc->umask);
}
return rv;
return ::_create(fc,fusepath_,mode_,ffi_);
}
}