mirror of https://github.com/trapexit/mergerfs.git
30 changed files with 862 additions and 87 deletions
-
1Makefile
-
11libfuse/lib/ffi.h
-
9libfuse/lib/fuse.cpp
-
7src/branches.hpp
-
2src/config.cpp
-
7src/config.hpp
-
8src/config_follow_symlinks.hpp
-
9src/follow_symlinks_enum.hpp
-
47src/from_string.cpp
-
1src/from_string.hpp
-
70src/fs_stat.cpp
-
18src/fs_stat.hpp
-
90src/fs_statx.cpp
-
62src/fs_statx.hpp
-
76src/func_getattr.hpp
-
35src/func_getattr_base.hpp
-
64src/func_getattr_combine.cpp
-
21src/func_getattr_combine.hpp
-
39src/func_getattr_factory.cpp
-
13src/func_getattr_factory.hpp
-
42src/func_getattr_ff.cpp
-
21src/func_getattr_ff.hpp
-
58src/func_getattr_newest.cpp
-
21src/func_getattr_newest.hpp
-
70src/fuse_statx_supported.icpp
-
60src/option_parser.cpp
-
42src/symlinkify.hpp
-
38src/timespec_utils.hpp
-
6src/to_string.cpp
-
1src/to_string.hpp
@ -0,0 +1,11 @@ |
|||
struct ffi |
|||
{ |
|||
uint64_t nodeid; |
|||
uint64_t gen; |
|||
uid_t uid; |
|||
gid_t gid; |
|||
pid_t pid; |
|||
uint64_t fh; |
|||
char *name; |
|||
char path[0]; |
|||
}; |
@ -0,0 +1,9 @@ |
|||
#pragma once
|
|||
|
|||
enum class FollowSymlinksEnum |
|||
{ |
|||
NEVER, |
|||
DIRECTORY, |
|||
REGULAR, |
|||
ALL |
|||
}; |
@ -0,0 +1,70 @@ |
|||
#include "fs_stat.hpp"
|
|||
#include "fs_lstat.hpp"
|
|||
|
|||
static |
|||
void |
|||
_set_stat_if_leads_to_dir(const char *path_, |
|||
struct stat *st_) |
|||
{ |
|||
int rv; |
|||
struct stat st; |
|||
|
|||
rv = fs::stat(path_,&st); |
|||
if(rv < 0) |
|||
return; |
|||
|
|||
if(S_ISDIR(st.st_mode)) |
|||
*st_ = st; |
|||
|
|||
return; |
|||
} |
|||
|
|||
static |
|||
void |
|||
_set_stat_if_leads_to_reg(const char *path_, |
|||
struct stat *st_) |
|||
{ |
|||
int rv; |
|||
struct stat st; |
|||
|
|||
rv = fs::stat(path_,&st); |
|||
if(rv < 0) |
|||
return; |
|||
|
|||
if(S_ISREG(st.st_mode)) |
|||
*st_ = st; |
|||
|
|||
return; |
|||
} |
|||
|
|||
int |
|||
fs::stat(const char *path_, |
|||
struct stat *st_, |
|||
FollowSymlinksEnum follow_) |
|||
{ |
|||
int rv; |
|||
|
|||
switch(follow_) |
|||
{ |
|||
case FollowSymlinksEnum::NEVER: |
|||
rv = fs::lstat(path_,st_); |
|||
break; |
|||
case FollowSymlinksEnum::DIRECTORY: |
|||
rv = fs::lstat(path_,st_); |
|||
if(S_ISLNK(st_->st_mode)) |
|||
_set_stat_if_leads_to_dir(path_,st_); |
|||
break; |
|||
case FollowSymlinksEnum::REGULAR: |
|||
rv = fs::lstat(path_,st_); |
|||
if(S_ISLNK(st_->st_mode)) |
|||
_set_stat_if_leads_to_reg(path_,st_); |
|||
break; |
|||
case FollowSymlinksEnum::ALL: |
|||
rv = fs::stat(path_,st_); |
|||
if(rv != 0) |
|||
rv = fs::lstat(path_,st_); |
|||
break; |
|||
} |
|||
|
|||
return rv; |
|||
} |
@ -0,0 +1,90 @@ |
|||
/*
|
|||
ISC License |
|||
|
|||
Copyright (c) 2025, 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 "fs_statx.hpp"
|
|||
|
|||
static |
|||
void |
|||
_set_stat_if_leads_to_dir(const std::string &path_, |
|||
struct fuse_statx *st_) |
|||
{ |
|||
int rv; |
|||
struct fuse_statx st; |
|||
|
|||
rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,&st); |
|||
if(rv < 0) |
|||
return; |
|||
|
|||
if(S_ISDIR(st.mode)) |
|||
*st_ = st; |
|||
|
|||
return; |
|||
} |
|||
|
|||
static |
|||
void |
|||
_set_stat_if_leads_to_reg(const std::string &path_, |
|||
struct fuse_statx *st_) |
|||
{ |
|||
int rv; |
|||
struct fuse_statx st; |
|||
|
|||
rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,&st); |
|||
if(rv < 0) |
|||
return; |
|||
|
|||
if(S_ISREG(st.mode)) |
|||
*st_ = st; |
|||
|
|||
return; |
|||
} |
|||
|
|||
int |
|||
fs::statx(const int dirfd_, |
|||
const char *pathname_, |
|||
const int flags_, |
|||
const unsigned int mask_, |
|||
struct fuse_statx *st_, |
|||
FollowSymlinksEnum follow_) |
|||
{ |
|||
int rv; |
|||
|
|||
switch(follow_) |
|||
{ |
|||
case FollowSymlinksEnum::NEVER: |
|||
rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); |
|||
break; |
|||
case FollowSymlinksEnum::DIRECTORY: |
|||
rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); |
|||
if((rv >= 0) && S_ISLNK(st_->mode)) |
|||
::_set_stat_if_leads_to_dir(pathname_,st_); |
|||
break; |
|||
case FollowSymlinksEnum::REGULAR: |
|||
rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); |
|||
if((rv >= 0) && S_ISLNK(st_->mode)) |
|||
::_set_stat_if_leads_to_reg(pathname_,st_); |
|||
break; |
|||
case FollowSymlinksEnum::ALL: |
|||
rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_FOLLOW,mask_,st_); |
|||
if(rv < 0) |
|||
rv = fs::statx(AT_FDCWD,pathname_,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); |
|||
break; |
|||
} |
|||
|
|||
return rv; |
|||
} |
@ -0,0 +1,76 @@ |
|||
#pragma once
|
|||
|
|||
#include "func_getattr_base.hpp"
|
|||
#include "func_getattr_factory.hpp"
|
|||
|
|||
#include "tofrom_string.hpp"
|
|||
|
|||
#include "fmt/core.h"
|
|||
|
|||
#include <atomic>
|
|||
|
|||
#include <cassert>
|
|||
|
|||
namespace Func2 |
|||
{ |
|||
class GetAttr : public ToFromString |
|||
{ |
|||
private: |
|||
std::shared_ptr<Func2::GetAttrBase> _impl; |
|||
|
|||
public: |
|||
GetAttr() |
|||
{ |
|||
} |
|||
|
|||
GetAttr(const std::string &name_) |
|||
{ |
|||
_impl = Func2::GetAttrFactory::make(name_); |
|||
assert(_impl); |
|||
} |
|||
|
|||
public: |
|||
int |
|||
operator()(const Branches &branches_, |
|||
const fs::Path &fusepath_, |
|||
struct stat *st_, |
|||
const FollowSymlinksEnum follow_symlinks_, |
|||
const s64 symlinkify_timeout_) |
|||
{ |
|||
std::shared_ptr<Func2::GetAttrBase> p; |
|||
|
|||
p = std::atomic_load(&_impl); |
|||
|
|||
return (*p)(branches_, |
|||
fusepath_, |
|||
st_, |
|||
follow_symlinks_, |
|||
symlinkify_timeout_); |
|||
} |
|||
|
|||
public: |
|||
std::string |
|||
to_string() const |
|||
{ |
|||
std::shared_ptr<Func2::GetAttrBase> p; |
|||
|
|||
p = std::atomic_load(&_impl); |
|||
|
|||
return std::string(p->name()); |
|||
} |
|||
|
|||
int |
|||
from_string(const std::string &str_) |
|||
{ |
|||
std::shared_ptr<Func2::GetAttrBase> p; |
|||
|
|||
p = Func2::GetAttrFactory::make(str_); |
|||
if(!p) |
|||
return -EINVAL; |
|||
|
|||
_impl = std::atomic_load(&p); |
|||
|
|||
return 0; |
|||
} |
|||
}; |
|||
} |
@ -0,0 +1,35 @@ |
|||
#pragma once
|
|||
|
|||
#include "follow_symlinks_enum.hpp"
|
|||
|
|||
#include "fs_path.hpp"
|
|||
#include "branches.hpp"
|
|||
#include "int_types.h"
|
|||
|
|||
#include "fuse.h"
|
|||
|
|||
#include <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 s64 symlinkify_timeout) = 0; |
|||
}; |
|||
} |
@ -0,0 +1,64 @@ |
|||
#include "func_getattr_combine.hpp"
|
|||
|
|||
#include "fs_inode.hpp"
|
|||
#include "fs_stat.hpp"
|
|||
#include "symlinkify.hpp"
|
|||
#include "timespec_utils.hpp"
|
|||
|
|||
|
|||
std::string_view |
|||
Func2::GetAttrCombine::name() const |
|||
{ |
|||
return "combine"; |
|||
} |
|||
|
|||
int |
|||
Func2::GetAttrCombine::operator()(const Branches &branches_, |
|||
const fs::Path &fusepath_, |
|||
struct stat *st_, |
|||
const FollowSymlinksEnum follow_symlinks_, |
|||
const s64 symlinkify_timeout_) |
|||
{ |
|||
int rv; |
|||
fs::Path fullpath; |
|||
const Branch *first_branch; |
|||
|
|||
first_branch = nullptr; |
|||
for(const auto &branch : branches_) |
|||
{ |
|||
struct stat st; |
|||
|
|||
fullpath = branch.path; |
|||
fullpath += fusepath_; |
|||
rv = fs::stat(fullpath.c_str(),&st,follow_symlinks_); |
|||
if(rv == -1) |
|||
continue; |
|||
|
|||
if(!first_branch) |
|||
{ |
|||
*st_ = st; |
|||
first_branch = &branch; |
|||
continue; |
|||
} |
|||
|
|||
st_->st_atim = TimeSpec::newest(st_->st_atim,st.st_atim); |
|||
st_->st_ctim = TimeSpec::newest(st_->st_ctim,st.st_ctim); |
|||
st_->st_mtim = TimeSpec::newest(st_->st_mtim,st.st_mtim); |
|||
st_->st_nlink += st.st_nlink; |
|||
} |
|||
|
|||
if(!first_branch) |
|||
return -ENOENT; |
|||
|
|||
if(symlinkify_timeout_ >= 0) |
|||
{ |
|||
fullpath = fs::path::make(first_branch->path,fusepath_); |
|||
symlinkify::convert_if_can_be_symlink(fullpath, |
|||
st_, |
|||
symlinkify_timeout_); |
|||
} |
|||
|
|||
fs::inode::calc(first_branch->path,fusepath_.string(),st_); |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,21 @@ |
|||
#include "func_getattr_base.hpp"
|
|||
|
|||
namespace Func2 |
|||
{ |
|||
class GetAttrCombine : public GetAttrBase |
|||
{ |
|||
public: |
|||
GetAttrCombine() {} |
|||
~GetAttrCombine() {} |
|||
|
|||
public: |
|||
std::string_view name() const; |
|||
|
|||
public: |
|||
int operator()(const Branches &branches, |
|||
const fs::Path &fusepath, |
|||
struct stat *st, |
|||
const FollowSymlinksEnum follow_symlinks, |
|||
const s64 symlinkify_timeout); |
|||
}; |
|||
} |
@ -0,0 +1,39 @@ |
|||
#include "func_getattr_factory.hpp"
|
|||
|
|||
#include "func_getattr_combine.hpp"
|
|||
#include "func_getattr_ff.hpp"
|
|||
#include "func_getattr_newest.hpp"
|
|||
|
|||
#include "policies.hpp"
|
|||
|
|||
bool |
|||
Func2::GetAttrFactory::valid(const std::string str_) |
|||
{ |
|||
if(str_ == "combined") |
|||
return true; |
|||
if(str_ == "ff") |
|||
return true; |
|||
if(str_ == "newest") |
|||
return true; |
|||
|
|||
if(Policies::Search::find(str_)) |
|||
return true; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
std::shared_ptr<Func2::GetAttrBase> |
|||
Func2::GetAttrFactory::make(const std::string str_) |
|||
{ |
|||
if(str_ == "combine") |
|||
return std::make_shared<Func2::GetAttrCombine>(); |
|||
if(str_ == "ff") |
|||
return std::make_shared<Func2::GetAttrFF>(); |
|||
if(str_ == "newest") |
|||
return std::make_shared<Func2::GetAttrNewest>(); |
|||
|
|||
if(Policies::Search::find(str_)) |
|||
return std::make_shared<Func2::GetAttrCombine>(); |
|||
|
|||
return {}; |
|||
} |
@ -0,0 +1,13 @@ |
|||
#pragma once
|
|||
|
|||
#include "func_getattr_base.hpp"
|
|||
|
|||
namespace Func2 |
|||
{ |
|||
class GetAttrFactory |
|||
{ |
|||
public: |
|||
static bool valid(const std::string str); |
|||
static std::shared_ptr<GetAttrBase> make(const std::string str); |
|||
}; |
|||
} |
@ -0,0 +1,42 @@ |
|||
#include "func_getattr_ff.hpp"
|
|||
|
|||
#include "fs_stat.hpp"
|
|||
#include "fs_inode.hpp"
|
|||
#include "symlinkify.hpp"
|
|||
|
|||
|
|||
std::string_view |
|||
Func2::GetAttrFF::name() const |
|||
{ |
|||
return "ff"; |
|||
} |
|||
|
|||
int |
|||
Func2::GetAttrFF::operator()(const Branches &branches_, |
|||
const fs::Path &fusepath_, |
|||
struct stat *st_, |
|||
const FollowSymlinksEnum follow_symlinks_, |
|||
const s64 symlinkify_timeout_) |
|||
{ |
|||
int rv; |
|||
fs::Path fullpath; |
|||
|
|||
for(const auto &branch : branches_) |
|||
{ |
|||
fullpath = branch.path; |
|||
fullpath += fusepath_; |
|||
rv = fs::stat(fullpath.string(),st_,follow_symlinks_); |
|||
if(rv != 0) |
|||
continue; |
|||
|
|||
symlinkify::convert_if_can_be_symlink(fullpath, |
|||
st_, |
|||
symlinkify_timeout_); |
|||
|
|||
fs::inode::calc(branch.path,fusepath_.string(),st_); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
return -ENOENT; |
|||
} |
@ -0,0 +1,21 @@ |
|||
#include "func_getattr_base.hpp"
|
|||
|
|||
namespace Func2 |
|||
{ |
|||
class GetAttrFF : public GetAttrBase |
|||
{ |
|||
public: |
|||
GetAttrFF() {} |
|||
~GetAttrFF() {} |
|||
|
|||
public: |
|||
std::string_view name() const; |
|||
|
|||
public: |
|||
int operator()(const Branches &branches, |
|||
const fs::Path &fusepath, |
|||
struct stat *st, |
|||
const FollowSymlinksEnum follow_symlinks, |
|||
const s64 symlinkify_timeout); |
|||
}; |
|||
} |
@ -0,0 +1,58 @@ |
|||
#include "func_getattr_newest.hpp"
|
|||
|
|||
#include "fs_stat.hpp"
|
|||
#include "fs_inode.hpp"
|
|||
#include "timespec_utils.hpp"
|
|||
#include "symlinkify.hpp"
|
|||
|
|||
|
|||
std::string_view |
|||
Func2::GetAttrNewest::name() const |
|||
{ |
|||
return "newest"; |
|||
} |
|||
|
|||
int |
|||
Func2::GetAttrNewest::operator()(const Branches &branches_, |
|||
const fs::Path &fusepath_, |
|||
struct stat *st_, |
|||
const FollowSymlinksEnum follow_symlinks_, |
|||
const s64 symlinkify_timeout_) |
|||
{ |
|||
int rv; |
|||
fs::Path fullpath; |
|||
const Branch *newest_branch; |
|||
|
|||
newest_branch = nullptr; |
|||
for(const auto &branch : branches_) |
|||
{ |
|||
struct stat tmp_st; |
|||
|
|||
fullpath = branch.path; |
|||
fullpath += fusepath_; |
|||
rv = fs::stat(fullpath,&tmp_st,follow_symlinks_); |
|||
if(rv == -1) |
|||
continue; |
|||
|
|||
if(!TimeSpec::is_newer(tmp_st.st_mtim,st_->st_mtim)) |
|||
continue; |
|||
|
|||
*st_ = tmp_st; |
|||
newest_branch = &branch; |
|||
} |
|||
|
|||
if(!newest_branch) |
|||
return -ENOENT; |
|||
|
|||
if(symlinkify_timeout_ >= 0) |
|||
{ |
|||
fullpath = fs::path::make(newest_branch->path,fusepath_); |
|||
symlinkify::convert_if_can_be_symlink(fullpath, |
|||
st_, |
|||
symlinkify_timeout_); |
|||
} |
|||
|
|||
fs::inode::calc(newest_branch->path,fusepath_.string(),st_); |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,21 @@ |
|||
#include "func_getattr_base.hpp"
|
|||
|
|||
namespace Func2 |
|||
{ |
|||
class GetAttrNewest : public GetAttrBase |
|||
{ |
|||
public: |
|||
GetAttrNewest() {} |
|||
~GetAttrNewest() {} |
|||
|
|||
public: |
|||
std::string_view name() const; |
|||
|
|||
public: |
|||
int operator()(const Branches &branches, |
|||
const fs::Path &fusepath, |
|||
struct stat *st, |
|||
const FollowSymlinksEnum follow_symlinks, |
|||
const s64 symlinkify_timeout); |
|||
}; |
|||
} |
@ -0,0 +1,38 @@ |
|||
#pragma once
|
|||
|
|||
namespace TimeSpec |
|||
{ |
|||
static |
|||
inline |
|||
bool |
|||
is_newer(const timespec &t0_, |
|||
const timespec &t1_) |
|||
{ |
|||
if(t0_.tv_sec > t1_.tv_sec) |
|||
return true; |
|||
if(t0_.tv_sec == t1_.tv_sec) |
|||
{ |
|||
if(t0_.tv_nsec > t1_.tv_nsec) |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
static |
|||
inline |
|||
timespec |
|||
newest(const timespec &t0_, |
|||
const timespec &t1_) |
|||
{ |
|||
if(t0_.tv_sec > t1_.tv_sec) |
|||
return t0_; |
|||
if(t0_.tv_sec == t1_.tv_sec) |
|||
{ |
|||
if(t0_.tv_nsec > t1_.tv_nsec) |
|||
return t0_; |
|||
} |
|||
|
|||
return t1_; |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue