Browse Source

Rework of runtime interface (#1498)

Also have special error handling for when branches are invalid and
ENOENT would be returned for getattr and readdir so users understand
what is going on and the runtime interface can still be used to fix
the problem.
pull/1499/head
trapexit 3 months ago
committed by GitHub
parent
commit
2b500f194a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      Makefile
  2. 2
      mkdocs/docs/faq/usage_and_functionality.md
  3. 8
      mkdocs/docs/faq/why_isnt_it_working.md
  4. 180
      mkdocs/docs/runtime_interface.md
  5. 114
      mkdocs/docs/runtime_interfaces.md
  6. 2
      mkdocs/docs/setup/upgrade.md
  7. 2
      mkdocs/mkdocs.yml
  8. 86
      src/config.cpp
  9. 10
      src/config.hpp
  10. 32
      src/fs_attr_linux.icpp
  11. 2
      src/fs_clonepath.cpp
  12. 2
      src/fs_copyfile.cpp
  13. 2
      src/fs_ficlone_linux.icpp
  14. 20
      src/fs_ioctl.hpp
  15. 12
      src/fs_llistxattr.hpp
  16. 44
      src/fuse_getattr.cpp
  17. 2
      src/fuse_getattr.hpp
  18. 92
      src/fuse_getxattr.cpp
  19. 6
      src/fuse_getxattr.hpp
  20. 381
      src/fuse_ioctl.cpp
  21. 192
      src/fuse_listxattr.cpp
  22. 1
      src/fuse_listxattr.hpp
  23. 2
      src/fuse_open.cpp
  24. 45
      src/fuse_readdir.cpp
  25. 2
      src/fuse_removexattr.cpp
  26. 328
      src/fuse_setxattr.cpp
  27. 2
      src/fuse_setxattr.hpp
  28. 42
      src/fuse_statx_supported.icpp
  29. 26
      src/gidcache.cpp
  30. 12
      src/gidcache.hpp
  31. 14
      src/mergerfs.cpp
  32. 47
      src/mergerfs_api.cpp
  33. 86
      src/mergerfs_collect_info.cpp
  34. 23
      src/mergerfs_ioctl.hpp
  35. 2311
      src/nonstd/string.hpp
  36. 49
      src/str.cpp
  37. 14
      src/str.hpp

7
Makefile

@ -293,5 +293,12 @@ release-static:
--cleanup \
--branch=$(shell git branch --show-current)
tags:
rm -fv TAGS
find . -name "*.c" -print | etags --append -
find . -name "*.h" -print | etags --append -
find . -name "*.cpp" -print | etags --append -
find . -name "*.hpp" -print | etags --append -
-include $(DEPS)

2
mkdocs/docs/faq/usage_and_functionality.md

@ -26,7 +26,7 @@ Yes. See previous question's answer.
This is true for planned removal by unmounting mergerfs and changing
the config, changes made to mergerfs at
[runtime](../runtime_interfaces.md), umounting of the branch's
[runtime](../runtime_interface.md), umounting of the branch's
filesystem on the fly (whether on purpose or due to error), etc.

8
mkdocs/docs/faq/why_isnt_it_working.md

@ -7,9 +7,9 @@ mount time. You can not simply modify the [source of the
configuration](../quickstart.md#usage) and have those settings applied
any more than you would for other filesystems. It is the user's
responsibility to [restart](../setup/upgrade.md) mergerfs to pick up
the changes or use the [runtime interface](../runtime_interfaces.md).
the changes or use the [runtime interface](../runtime_interface.md).
NOTE: the [runtime interface](../runtime_interfaces.md) is **just**
NOTE: the [runtime interface](../runtime_interface.md) is **just**
for runtime changes. It does **NOT** save those changed values
anywhere.
@ -64,7 +64,7 @@ theoretical space available. Not the practical usable space.
It probably is. The policies rather straight forward and well tested.
First, confirm the policy is configured as expected by using the
[runtime interface](../runtime_interfaces.md).
[runtime interface](../runtime_interface.md).
```shell
$ sudo getfattr -n user.mergerfs.category.create /mnt/mergerfs/.mergerfs
@ -246,4 +246,4 @@ possible options rather than being given to the kernel where those
options in the `mount` command and /proc/mounts come from.
If you want to see the options of a running instance of mergerfs you
can use the [runtime interface](../runtime_interfaces.md).
can use the [runtime interface](../runtime_interface.md).

180
mkdocs/docs/runtime_interface.md

@ -0,0 +1,180 @@
# Runtime Interface
`mergerfs` has runtime interfaces allowing users to query certain
filesystem information, get and set config, and trigger certain
activities while it is running.
The interface is provided via the POSIX extended attributes filesystem
API which is a namespaced `key=value` pair store associated with a
file. Since `mergerfs` primarily uses `key=value` pairs for config it
fits well and is a known and reasonably well supported API.
There are two targets for `xattr` calls. One is a pseudo file used for
getting and setting config and issuing certain commands. The other are
files found on the filesystem for querying certain `mergerfs` specific
information about them.
## .mergerfs pseudo file
```
<mountpoint>/.mergerfs
```
`mergerfs` provides this pseudo file for the runtime modification of
certain options and issuing commands. The file will not show up in
`readdir` but can be `stat`'ed and viewed/manipulated via
[listxattr](http://linux.die.net/man/2/listxattr),
[getxattr](https://linux.die.net/man/2/getxattr), and
[setxattr](https://linux.die.net/man/2/setxattr) calls.
Any changes made at runtime are **NOT** persisted. If you wish for
values to persist they must be included as options wherever you
configure the mounting of mergerfs (/etc/fstab, systemd, etc.).
### Command Line Tooling
Extended attributes is prevelant enough that there are common tools
available for interacting with them.
In Debian / Ubuntu distributions you can get the tools
[getfattr](https://linux.die.net/man/1/getfattr) and
[setfattr](https://linux.die.net/man/1/setfattr) from
[attr](https://linux.die.net/man/5/attr) package.
```
$ sudo apt-get install attr
```
### Config
#### Keys
Use `getfattr -d /mountpoint/.mergerfs` to see all supported
configuration keys. It is effectively the same as the
[options](config/options.md) prefixed with `user.mergerfs.`. Some are
informational or only can be set at startup and therefore read-only.
Example: option `cache.files` would be `user.mergerfs.cache.files`.
#### Values
Same as the [command line options](config/options.md).
#### Getting
`getfattr -n user.mergerfs.branches /mountpoint/.mergerfs`
`ENOATTR` will be returned if the key doesn't exist as normal with
[getxattr](https://linux.die.net/man/2/getxattr).
#### Setting
`setfattr -n user.mergerfs.branches -v VALUE /mountpoint/.mergerfs`
[setxattr](https://linux.die.net/man/2/setxattr) will return `EROFS`
(Read-only filesystem) on read-only keys. `ENOATTR` will be returned
if the key does not exist. If the value attempting to be set is not
valid `EINVAL` will be returned.
#### user.mergerfs.branches
`branches` has the ability to understand some simple instructions to
make manipulation of the list easier. The `[list]` is simply what is
described in the [branches](config/branches.md) docs.
| Value | Description |
| -------- | -------------------------- |
| [list] | set |
| +<[list] | prepend to existing list |
| +>[list] | append to existing list |
| -[list] | remove all values provided |
| -< | remove first in list |
| -> | remove last in list |
**NOTE:** if the value of `branches` is set to something invalid /
non-existant `mergerfs` will return a bogus entry when the mount point
directory is `stat`'ed and create a fake file entry when listing the
directory telling the user "error: no valid mergerfs branch found,
check config". This is done to ensure the user understands the
situation and continue to be able to access the xattr interface.
#### Example
```
[trapexit:/mnt/mergerfs] $ getfattr -d .mergerfs
user.mergerfs.branches="/mnt/a=RW:/mnt/b=RW"
user.mergerfs.minfreespace="4294967295"
user.mergerfs.moveonenospc="false"
...
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.category.create .mergerfs
user.mergerfs.category.search="mfs"
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.category.create -v pfrd .mergerfs
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.category.create .mergerfs
user.mergerfs.category.search="prfd"
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.branches -v "'+</mnt/c=RO .mergerfs
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.branches .mergerfs
user.mergerfs.branches="/mnt/c=RO:/mnt/a=RW:/mnt/b=RW"
```
### Commands
There are a number of commands / behaviors which can be triggerd by
writing ([setfattr](https://linux.die.net/man/1/setfattr),
[setxattr](https://linux.die.net/man/2/setxattr)) particular xattr
keys of `/mountpoint/.mergerfs`. These keys do not show up in key
listings.
Commands can take an argument however currently no command uses or
allows a value.
| Key | Value | Description |
| --- | ----- | ----------- |
| user.mergerfs.cmd.gc | (empty) |Trigger a thorough garbage collection of certain pools of resources. The xattr value is not used. |
| user.mergerfs.cmd.gc1 | (empty) | Trigger a simple garbage collection of certain pools of resources. This is also done on a timer. |
| user.mergerfs.cmd.invalidate-all-nodes | (empty) | Attempts to invalidate FUSE file nodes. Primarily used for debugging. |
| user.mergerfs.cmd.invalidate-gid-cache | (empty) | Invalidates all entries in gid cache. Primarily used for debugging but can also be useful in cases where gid supplemental groups change. |
| user.mergerfs.cmd.clear-gid-cache | (empty) | Clears all entries in the gid cache. Primarily used for debugging but can also be useful in cases where gid supplemental groups change. |
```
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.cmd.gc /mnt/mergerfs/.mergerfs
[trapexit:/mnt/mergerfs] $ journalctl -t mergerfs | tail -n1
Jul 20 15:36:18 hostname mergerfs[1931348]: running basic garbage collection
```
### file / directory xattrs
There is certain information `mergerfs` knows or calculates about a
file that can be useful in building tooling or debugging.
The keys below can be queried (`getfattr -n key`,
[getxattr](http://linux.die.net/man/2/getxattr)) to get the described
information. These keys will not show up in any listing (`getfattr
-d`, [listxattr](https://linux.die.net/man/2/listxattr)). Attempting
to set them will result in an error.
| Key | Return Value |
| --- | ------------ |
| user.mergerfs.basepath | The base mount point for the file given the current `getattr` policy. |
| user.mergerfs.relpath | The relative path of the file from the perspective of the mount point. |
| user.mergerfs.fullpath | The full path of the original file given the `getattr` policy. |
| user.mergerfs.allpaths | A NULL ('\0') separated list of full paths to all files found. |
```
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.fullpath test
user.mergerfs.fullpath="/mnt/a/test"
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.allpaths .
user.mergerfs.allpaths="/mnt/a\000/mnt/b\000/mnt/c"
```

114
mkdocs/docs/runtime_interfaces.md

@ -1,114 +0,0 @@
# Runtime Interfaces
## Runtime Config
### .mergerfs pseudo file
```
<mountpoint>/.mergerfs
```
There is a pseudo file available at the mount point which allows for
the runtime modification of certain **mergerfs** options. The file
will not show up in **readdir** but can be **stat**'ed and manipulated
via [{list,get,set}xattrs](http://linux.die.net/man/2/listxattr)
calls.
Any changes made at runtime are **not** persisted. If you wish for
values to persist they must be included as options wherever you
configure the mounting of mergerfs (/etc/fstab).
#### Keys
Use `getfattr -d /mountpoint/.mergerfs` or `xattr -l
/mountpoint/.mergerfs` to see all supported keys. Some are
informational and therefore read-only. `setxattr` will return EINVAL
(invalid argument) on read-only keys.
#### Values
Same as the command line.
##### user.mergerfs.branches
Used to query or modify the list of branches. When modifying there are
several shortcuts to easy manipulation of the list.
| Value | Description |
| -------- | -------------------------- |
| [list] | set |
| +<[list] | prepend |
| +>[list] | append |
| -[list] | remove all values provided |
| -< | remove first in list |
| -> | remove last in list |
`xattr -w user.mergerfs.branches +</mnt/drive3 /mnt/pool/.mergerfs`
The `=NC`, `=RO`, `=RW` syntax works just as on the command line.
##### Example
```
[trapexit:/mnt/mergerfs] $ getfattr -d .mergerfs
user.mergerfs.branches="/mnt/a=RW:/mnt/b=RW"
user.mergerfs.minfreespace="4294967295"
user.mergerfs.moveonenospc="false"
...
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.category.search .mergerfs
user.mergerfs.category.search="ff"
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.category.search -v newest .mergerfs
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.category.search .mergerfs
user.mergerfs.category.search="newest"
```
### file / directory xattrs
While they won't show up when using `getfattr` **mergerfs** offers a
number of special xattrs to query information about the files
served. To access the values you will need to issue a
[getxattr](http://linux.die.net/man/2/getxattr) for one of the
following:
- **user.mergerfs.basepath**: the base mount point for the file given the current getattr policy
- **user.mergerfs.relpath**: the relative path of the file from the perspective of the mount point
- **user.mergerfs.fullpath**: the full path of the original file given the getattr policy
- **user.mergerfs.allpaths**: a NUL ('\0') separated list of full paths to all files found
## Signals
- USR1: This will cause mergerfs to send invalidation notifications to
the kernel for all files. This will cause all unused files to be
released from memory.
- USR2: Trigger a general cleanup of currently unused memory. A more
thorough version of what happens every ~15 minutes.
## ioctl
Found in `fuse_ioctl.cpp`:
```C++
typedef char IOCTL_BUF[4096];
#define IOCTL_APP_TYPE 0xDF
#define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF)
#define IOCTL_GC _IO(IOCTL_APP_TYPE,1)
#define IOCTL_GC1 _IO(IOCTL_APP_TYPE,2)
#define IOCTL_INVALIDATE_ALL_NODES _IO(IOCTL_APP_TYPE,3)
```
- IOCTL_FILE_INFO: Same as the "file / directory xattrs" mentioned
above. Use a buffer size of 4096 bytes. Pass in a string of
"basepath", "relpath", "fullpath", or "allpaths". Receive details in
same buffer.
- IOCTL_GC: Triggers a thorough garbage collection of excess
memory. Same as SIGUSR2.
- IOCTL_GC1: Triggers a simple garbage collection of excess
memory. Same as what happens every 15 minutes normally.
- IOCTL_INVALIDATE_ALL_NODES: Same as SIGUSR1. Send invalidation
notifications to the kernel for all files causing unused files to be
released from memory.

2
mkdocs/docs/setup/upgrade.md

@ -26,4 +26,4 @@ issue: `umount -l <mergerfs_mountpoint>`. Or you can let mergerfs do
it by setting the option `lazy-umount-mountpoint=true`.
If the intent is to change settings at runtime then the [runtime
interface](../runtime_interfaces.md) should be used.
interface](../runtime_interface.md) should be used.

2
mkdocs/mkdocs.yml

@ -95,7 +95,7 @@ nav:
- config/export-support.md
- config/kernel-permissions-check.md
- error_handling_and_logging.md
- runtime_interfaces.md
- runtime_interface.md
- remote_filesystems.md
- tips_notes.md
- known_issues_bugs.md

86
src/config.cpp

@ -23,6 +23,7 @@
#include "str.hpp"
#include "to_string.hpp"
#include "version.hpp"
#include "nonstd/string.hpp"
#include <algorithm>
#include <cstdint>
@ -39,7 +40,6 @@
#define IFERT(S) if(S == s_) return true
const std::string CONTROLFILE = "/.mergerfs";
constexpr static const char CACHE_FILES_PROCESS_NAMES_DEFAULT[] =
"rtorrent|"
"qbittorrent-nox";
@ -47,7 +47,6 @@ constexpr static const char CACHE_FILES_PROCESS_NAMES_DEFAULT[] =
Config Config::_singleton;
namespace l
{
static
@ -277,6 +276,45 @@ Config::keys_xattr(std::string &s_) const
}
}
ssize_t
Config::keys_listxattr_size() const
{
ssize_t rv;
rv = 0;
for(const auto &[key,val] : _map)
{
rv += sizeof("user.mergerfs.");
rv += key.size();
}
return rv;
}
ssize_t
Config::keys_listxattr(char *list_,
size_t size_) const
{
char *list = list_;
ssize_t size = size_;
if(size_ == 0)
return keys_listxattr_size();
for(const auto &[key,val] : _map)
{
auto rv = fmt::format_to_n(list,size,
"user.mergerfs.{}\0",
key);
if(rv.out >= (list + size))
return -ERANGE;
list += rv.size;
size -= rv.size;
}
return (list - list_);
}
int
Config::get(const std::string &key_,
std::string *val_) const
@ -390,6 +428,50 @@ Config::finish_initializing()
_initialized = true;
}
bool
Config::is_rootdir(const char *fusepath_)
{
return str::eq(fusepath_,"/");
}
bool
Config::is_ctrl_file(const char *fusepath_)
{
return str::eq(fusepath_,"/.mergerfs");
}
bool
Config::is_mergerfs_xattr(const char *attrname_)
{
return str::startswith(attrname_,"user.mergerfs.");
}
bool
Config::is_cmd_xattr(const std::string_view &attrname_)
{
return nonstd::string::starts_with(attrname_,"user.mergerfs.cmd.");
}
std::string
Config::prune_ctrl_xattr(const std::string &s_)
{
const size_t offset = (sizeof("user.mergerfs.") - 1);
if(offset < s_.size())
return s_.substr(offset);
return {};
}
std::string_view
Config::prune_cmd_xattr(const std::string_view &s_)
{
constexpr size_t offset = (sizeof("user.mergerfs.cmd.") - 1);
if(offset < s_.size())
return s_.substr(offset);
return {};
}
std::ostream&
operator<<(std::ostream &os_,
const Config &c_)

10
src/config.hpp

@ -184,6 +184,8 @@ public:
bool has_key(const std::string &key) const;
void keys(std::string &s) const;
void keys_xattr(std::string &s) const;
ssize_t keys_listxattr(char *list, size_t size) const;
ssize_t keys_listxattr_size() const;
public:
int get(const std::string &key, std::string *val) const;
@ -195,6 +197,14 @@ public:
int from_stream(std::istream &istrm, ErrVec *errs);
int from_file(const std::string &filepath, ErrVec *errs);
public:
static bool is_rootdir(const char *fusepath);
static bool is_ctrl_file(const char *fusepath);
static bool is_mergerfs_xattr(const char *attrname);
static bool is_cmd_xattr(const std::string_view &attrname);
static std::string prune_ctrl_xattr(const std::string &s);
static std::string_view prune_cmd_xattr(const std::string_view &s);
private:
Str2TFStrMap _map;

32
src/fs_attr_linux.icpp

@ -36,8 +36,8 @@ _get_fs_ioc_flags(const int fd,
int rv;
rv = fs::ioctl(fd,FS_IOC_GETFLAGS,(void*)&flags);
if((rv == -1) && (errno == EINVAL))
errno = ENOTSUP;
if(rv == -EINVAL)
rv = ENOTSUP;
return rv;
}
@ -53,15 +53,13 @@ _get_fs_ioc_flags(const string &file,
fd = fs::open(file,openflags);
if(fd == -1)
return -1;
return -errno;
rv = ::_get_fs_ioc_flags(fd,flags);
if(rv == -1)
if(rv < 0)
{
int error = errno;
fs::close(fd);
errno = error;
return -1;
return rv;
}
return fs::close(fd);
@ -75,8 +73,8 @@ _set_fs_ioc_flags(const int fd,
int rv;
rv = fs::ioctl(fd,FS_IOC_SETFLAGS,(void*)&flags);
if((rv == -1) && (errno == EINVAL))
errno = ENOTSUP;
if(rv == -EINVAL)
rv = ENOTSUP;
return rv;
}
@ -92,15 +90,13 @@ _set_fs_ioc_flags(const string &file,
fd = fs::open(file,openflags);
if(fd == -1)
return -1;
return -errno;
rv = ::_set_fs_ioc_flags(fd,flags);
if(rv == -1)
if(rv < 0)
{
int error = errno;
fs::close(fd);
errno = error;
return -1;
return rv;
}
return fs::close(fd);
@ -115,8 +111,8 @@ fs::attr::copy(const int fdin,
int flags;
rv = ::_get_fs_ioc_flags(fdin,flags);
if(rv == -1)
return -1;
if(rv < 0)
return rv;
if(flags_ & FS_ATTR_CLEAR_IMMUTABLE)
flags = (flags & ~FS_IMMUTABLE_FL);
@ -132,8 +128,8 @@ fs::attr::copy(const string &from,
int flags;
rv = ::_get_fs_ioc_flags(from,flags);
if(rv == -1)
return -1;
if(rv < 0)
return rv;
return ::_set_fs_ioc_flags(to,flags);
}

2
src/fs_clonepath.cpp

@ -100,7 +100,7 @@ namespace fs
// it may not support it... it's fine...
rv = fs::attr::copy(frompath,topath);
if(return_metadata_errors_ && (rv == -1) && !l::ignorable_error(errno))
if(return_metadata_errors_ && (rv < 0) && !l::ignorable_error(-rv))
return -1;
// it may not support it... it's fine...

2
src/fs_copyfile.cpp

@ -54,7 +54,7 @@ fs::copyfile(const int src_fd_,
return -1;
rv = fs::attr::copy(src_fd_,dst_fd_,FS_ATTR_CLEAR_IMMUTABLE);
if((rv == -1) && !::_ignorable_error(errno))
if((rv < 0) && !::_ignorable_error(-rv))
return -1;
rv = fs::fchown_check_on_error(dst_fd_,src_st_);

2
src/fs_ficlone_linux.icpp

@ -31,7 +31,7 @@ namespace fs
#ifdef FICLONE
return fs::ioctl(dst_fd_,FICLONE,src_fd_);
#else
return (errno=EOPNOTSUPP,-1);
return -EOPNOTSUPP;
#endif
}
}

20
src/fs_ioctl.hpp

@ -18,6 +18,8 @@
#pragma once
#include "errno.hpp"
#include <sys/ioctl.h>
@ -29,7 +31,11 @@ namespace fs
ioctl(const int fd_,
const unsigned long request_)
{
return ::ioctl(fd_,request_);
int rv;
rv = ::ioctl(fd_,request_);
return ((rv == -1) ? -errno : rv);
}
static
@ -39,7 +45,11 @@ namespace fs
const unsigned long request_,
void *data_)
{
return ::ioctl(fd_,request_,data_);
int rv;
rv = ::ioctl(fd_,request_,data_);
return ((rv == -1) ? -errno : rv);
}
static
@ -49,6 +59,10 @@ namespace fs
const unsigned long request_,
const int int_)
{
return ::ioctl(fd_,request_,int_);
int rv;
rv = ::ioctl(fd_,request_,int_);
return ((rv == -1) ? -errno : rv);
}
}

12
src/fs_llistxattr.hpp

@ -30,21 +30,25 @@ namespace fs
{
static
inline
int
ssize_t
llistxattr(const char *path_,
char *list_,
const size_t size_)
{
#ifdef USE_XATTR
return ::llistxattr(path_,list_,size_);
ssize_t rv;
rv = ::llistxattr(path_,list_,size_);
return ((rv == -1) ? -errno : rv);
#else
return (errno=ENOTSUP,-1);
return -ENOTSUP;
#endif
}
static
inline
int
ssize_t
llistxattr(const std::string &path_,
char *list_,
const size_t size_)

44
src/fuse_getattr.cpp

@ -14,6 +14,8 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_getattr.hpp"
#include "config.hpp"
#include "errno.hpp"
#include "fs_fstat.hpp"
@ -23,6 +25,7 @@
#include "fs_stat.hpp"
#include "fuse_fgetattr.hpp"
#include "state.hpp"
#include "str.hpp"
#include "symlinkify.hpp"
#include "ugid.hpp"
@ -71,6 +74,27 @@ namespace l
return;
}
static
int
getattr_fake_root(struct stat *st_)
{
st_->st_dev = 0;
st_->st_ino = 0;
st_->st_mode = (S_IFDIR|S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
st_->st_nlink = 2;
st_->st_uid = 0;
st_->st_gid = 0;
st_->st_rdev = 0;
st_->st_size = 0;
st_->st_blksize = 512;
st_->st_blocks = 0;
st_->st_atime = 0;
st_->st_mtime = 0;
st_->st_ctime = 0;
return 0;
}
static
int
getattr_controlfile(struct stat *st_)
@ -166,6 +190,8 @@ namespace l
cfg->symlinkify,
cfg->symlinkify_timeout,
cfg->follow_symlinks);
if((rv < 0) && Config::is_rootdir(fusepath_))
return l::getattr_fake_root(st_);
timeout_->entry = ((rv >= 0) ?
cfg->cache_entry :
@ -176,16 +202,14 @@ namespace l
}
}
namespace FUSE
int
FUSE::getattr(const char *fusepath_,
struct stat *st_,
fuse_timeouts_t *timeout_)
{
int
getattr(const char *fusepath_,
struct stat *st_,
fuse_timeouts_t *timeout_)
{
if(fusepath_ == CONTROLFILE)
return l::getattr_controlfile(st_);
if(Config::is_ctrl_file(fusepath_))
return l::getattr_controlfile(st_);
return l::getattr(fusepath_,st_,timeout_);
}
return l::getattr(fusepath_,st_,timeout_);
}

2
src/fuse_getattr.hpp

@ -16,6 +16,8 @@
#pragma once
#include "fuse.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

92
src/fuse_getxattr.cpp

@ -14,6 +14,8 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_getxattr.hpp"
#include "config.hpp"
#include "errno.hpp"
#include "fs_findallfiles.hpp"
@ -61,36 +63,32 @@ namespace l
static
int
getxattr_controlfile(Config::Read &cfg_,
const char *attrname_,
char *buf_,
const size_t count_)
getxattr_ctrl_file(Config::Read &cfg_,
const char *attrname_,
char *buf_,
const size_t count_)
{
int rv;
size_t len;
std::string key;
std::string val;
StrVec attr;
if(!str::startswith(attrname_,"user.mergerfs."))
if(!Config::is_mergerfs_xattr(attrname_))
return -ENOATTR;
key = &attrname_[14];
key = Config::prune_ctrl_xattr(attrname_);
rv = cfg_->get(key,&val);
if(rv < 0)
return rv;
len = val.size();
if(count_ == 0)
return len;
return val.size();
if(count_ < len)
if(count_ < val.size())
return -ERANGE;
memcpy(buf_,val.c_str(),len);
memcpy(buf_,val.c_str(),val.size());
return (int)len;
return (int)val.size();
}
static
@ -142,17 +140,17 @@ namespace l
char *buf_,
const size_t count_)
{
StrVec attr;
std::string key;
str::split(attrname_,'.',&attr);
key = Config::prune_ctrl_xattr(attrname_);
if(attr[2] == "basepath")
if(key == "basepath")
return l::getxattr_from_string(buf_,count_,basepath_);
else if(attr[2] == "relpath")
if(key == "relpath")
return l::getxattr_from_string(buf_,count_,fusepath_);
else if(attr[2] == "fullpath")
if(key == "fullpath")
return l::getxattr_from_string(buf_,count_,fullpath_);
else if(attr[2] == "allpaths")
if(key == "allpaths")
return l::getxattr_user_mergerfs_allpaths(branches_,fusepath_,buf_,count_);
return -ENOATTR;
@ -177,7 +175,7 @@ namespace l
fullpath = fs::path::make(branches[0]->path,fusepath_);
if(str::startswith(attrname_,"user.mergerfs."))
if(Config::is_mergerfs_xattr(attrname_))
return l::getxattr_user_mergerfs(branches[0]->path,
fusepath_,
fullpath,
@ -190,37 +188,35 @@ namespace l
}
}
namespace FUSE
int
FUSE::getxattr(const char *fusepath_,
const char *attrname_,
char *attrvalue_,
size_t attrvalue_size_)
{
int
getxattr(const char *fusepath_,
const char *attrname_,
char *buf_,
size_t count_)
{
Config::Read cfg;
Config::Read cfg;
if(fusepath_ == CONTROLFILE)
return l::getxattr_controlfile(cfg,
attrname_,
buf_,
count_);
if(Config::is_ctrl_file(fusepath_))
return l::getxattr_ctrl_file(cfg,
attrname_,
attrvalue_,
attrvalue_size_);
if((cfg->security_capability == false) &&
l::is_attrname_security_capability(attrname_))
return -ENOATTR;
if((cfg->security_capability == false) &&
l::is_attrname_security_capability(attrname_))
return -ENOATTR;
if(cfg->xattr.to_int())
return -cfg->xattr.to_int();
if(cfg->xattr.to_int())
return -cfg->xattr.to_int();
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return l::getxattr(cfg->func.getxattr.policy,
cfg->branches,
fusepath_,
attrname_,
buf_,
count_);
}
return l::getxattr(cfg->func.getxattr.policy,
cfg->branches,
fusepath_,
attrname_,
attrvalue_,
attrvalue_size_);
}

6
src/fuse_getxattr.hpp

@ -16,12 +16,14 @@
#pragma once
#include <cstddef>
namespace FUSE
{
int
getxattr(const char *fusepath,
const char *attrname,
char *buf,
size_t count);
char *attrvalue,
size_t attrvalue_size);
}

381
src/fuse_ioctl.cpp

@ -14,6 +14,11 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_ioctl.hpp"
#include "fuse_getxattr.hpp"
#include "fuse_setxattr.hpp"
#include "config.hpp"
#include "dirinfo.hpp"
#include "endian.hpp"
@ -73,295 +78,123 @@
https://lwn.net/Articles/575846/
*/
namespace l
static
int
_ioctl(const int fd_,
const uint32_t cmd_,
void *data_,
uint32_t *out_bufsz_)
{
int rv;
switch(cmd_)
{
case FS_IOC_GETFLAGS:
case FS_IOC_SETFLAGS:
case FS_IOC_GETVERSION:
case FS_IOC_SETVERSION:
if(endian::is_big() && (sizeof(long) != sizeof(int)))
return -ENOTTY;
if((data_ != NULL) && (*out_bufsz_ > 4))
*out_bufsz_ = 4;
break;
}
rv = fs::ioctl(fd_,cmd_,data_);
return rv;
}
static
int
_ioctl_file(const fuse_file_info_t *ffi_,
const uint32_t cmd_,
void *data_,
uint32_t *out_bufsz_)
{
static
int
ioctl(const int fd_,
const uint32_t cmd_,
void *data_,
uint32_t *out_bufsz_)
{
int rv;
switch(cmd_)
{
case FS_IOC_GETFLAGS:
case FS_IOC_SETFLAGS:
case FS_IOC_GETVERSION:
case FS_IOC_SETVERSION:
if(endian::is_big() && (sizeof(long) != sizeof(int)))
return -ENOTTY;
if((data_ != NULL) && (*out_bufsz_ > 4))
*out_bufsz_ = 4;
break;
}
rv = fs::ioctl(fd_,cmd_,data_);
return ((rv == -1) ? -errno : rv);
}
static
int
ioctl_file(const fuse_file_info_t *ffi_,
const uint32_t cmd_,
void *data_,
uint32_t *out_bufsz_)
{
FileInfo *fi = reinterpret_cast<FileInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return l::ioctl(fi->fd,cmd_,data_,out_bufsz_);
}
FileInfo *fi = reinterpret_cast<FileInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return ::_ioctl(fi->fd,cmd_,data_,out_bufsz_);
}
#ifndef O_NOATIME
#define O_NOATIME 0
#endif
static
int
ioctl_dir_base(const Policy::Search &searchFunc_,
const Branches &branches_,
const char *fusepath_,
const uint32_t cmd_,
void *data_,
uint32_t *out_bufsz_)
{
int fd;
int rv;
std::string fullpath;
std::vector<Branch*> branches;
static
int
_ioctl_dir_base(const Policy::Search &searchFunc_,
const Branches &branches_,
const char *fusepath_,
const uint32_t cmd_,
void *data_,
uint32_t *out_bufsz_)
{
int fd;
int rv;
std::string fullpath;
std::vector<Branch*> branches;
rv = searchFunc_(branches_,fusepath_,branches);
if(rv == -1)
return -errno;
rv = searchFunc_(branches_,fusepath_,branches);
if(rv == -1)
return -errno;
fullpath = fs::path::make(branches[0]->path,fusepath_);
fullpath = fs::path::make(branches[0]->path,fusepath_);
fd = fs::open(fullpath,O_RDONLY|O_NOATIME|O_NONBLOCK);
if(fd == -1)
return -errno;
fd = fs::open(fullpath,O_RDONLY|O_NOATIME|O_NONBLOCK);
if(fd == -1)
return -errno;
rv = l::ioctl(fd,cmd_,data_,out_bufsz_);
rv = ::_ioctl(fd,cmd_,data_,out_bufsz_);
fs::close(fd);
fs::close(fd);
return rv;
}
return rv;
}
static
int
ioctl_dir(const fuse_file_info_t *ffi_,
const uint32_t cmd_,
void *data_,
uint32_t *out_bufsz_)
{
Config::Read cfg;
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return l::ioctl_dir_base(cfg->func.open.policy,
cfg->branches,
di->fusepath.c_str(),
cmd_,
data_,
out_bufsz_);
}
static
int
strcpy(const std::string &s_,
void *data_)
{
char *data = (char*)data_;
if(s_.size() >= (sizeof(IOCTL_BUF) - 1))
return -ERANGE;
memcpy(data,s_.c_str(),s_.size());
data[s_.size()] = '\0';
return s_.size();
}
static
int
file_basepath(const Policy::Search &searchFunc_,
const Branches &branches_,
const char *fusepath_,
void *data_)
{
int rv;
std::vector<Branch*> branches;
rv = searchFunc_(branches_,fusepath_,branches);
if(rv == -1)
return -errno;
return l::strcpy(branches[0]->path,data_);
}
static
int
file_basepath(const fuse_file_info_t *ffi_,
void *data_)
{
Config::Read cfg;
std::string &fusepath = reinterpret_cast<FH*>(ffi_->fh)->fusepath;
return l::file_basepath(cfg->func.open.policy,
cfg->branches,
fusepath.c_str(),
data_);
}
static
int
file_relpath(const fuse_file_info_t *ffi_,
void *data_)
{
std::string &fusepath = reinterpret_cast<FH*>(ffi_->fh)->fusepath;
return l::strcpy(fusepath,data_);
}
static
int
file_fullpath(const Policy::Search &searchFunc_,
const Branches &ibranches_,
const std::string &fusepath_,
void *data_)
{
int rv;
StrVec basepaths;
std::string fullpath;
std::vector<Branch*> obranches;
rv = searchFunc_(ibranches_,fusepath_,obranches);
if(rv == -1)
return -errno;
fullpath = fs::path::make(obranches[0]->path,fusepath_);
return l::strcpy(fullpath,data_);
}
static
int
file_fullpath(const fuse_file_info_t *ffi_,
void *data_)
{
Config::Read cfg;
std::string &fusepath = reinterpret_cast<FH*>(ffi_->fh)->fusepath;
return l::file_fullpath(cfg->func.open.policy,
cfg->branches,
fusepath,
data_);
}
static
int
file_allpaths(const fuse_file_info_t *ffi_,
void *data_)
{
Config::Read cfg;
std::string concated;
StrVec paths;
StrVec branches;
std::string &fusepath = reinterpret_cast<FH*>(ffi_->fh)->fusepath;
cfg->branches->to_paths(branches);
fs::findallfiles(branches,fusepath.c_str(),&paths);
concated = str::join(paths,'\0');
return l::strcpy(concated,data_);
}
static
int
file_info(const fuse_file_info_t *ffi_,
void *data_)
{
char *key = (char*)data_;
if(!strcmp("basepath",key))
return l::file_basepath(ffi_,data_);
if(!strcmp("relpath",key))
return l::file_relpath(ffi_,data_);
if(!strcmp("fullpath",key))
return l::file_fullpath(ffi_,data_);
if(!strcmp("allpaths",key))
return l::file_allpaths(ffi_,data_);
return -ENOATTR;
}
static
bool
is_mergerfs_ioctl_cmd(const unsigned long cmd_)
{
return (_IOC_TYPE(cmd_) == IOCTL_APP_TYPE);
}
static
bool
is_btrfs_ioctl_cmd(const unsigned long cmd_)
{
return (_IOC_TYPE(cmd_) == BTRFS_IOCTL_MAGIC);
}
static
int
ioctl_custom(const fuse_file_info_t *ffi_,
unsigned long cmd_,
void *data_)
{
switch(cmd_)
{
case IOCTL_FILE_INFO:
return l::file_info(ffi_,data_);
case IOCTL_GC:
fuse_gc();
return 0;
case IOCTL_GC1:
fuse_gc1();
return 0;
case IOCTL_INVALIDATE_ALL_NODES:
fuse_invalidate_all_nodes();
return 0;
case IOCTL_INVALIDATE_GID_CACHE:
GIDCache::invalidate_all();
break;
}
static
int
_ioctl_dir(const fuse_file_info_t *ffi_,
const uint32_t cmd_,
void *data_,
uint32_t *out_bufsz_)
{
Config::Read cfg;
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return ::_ioctl_dir_base(cfg->func.open.policy,
cfg->branches,
di->fusepath.c_str(),
cmd_,
data_,
out_bufsz_);
}
return -ENOTTY;
}
static
bool
_is_btrfs_ioctl_cmd(const unsigned long cmd_)
{
return (_IOC_TYPE(cmd_) == BTRFS_IOCTL_MAGIC);
}
namespace FUSE
int
FUSE::ioctl(const fuse_file_info_t *ffi_,
unsigned long cmd_,
void *arg_,
unsigned int flags_,
void *data_,
uint32_t *out_bufsz_)
{
int
ioctl(const fuse_file_info_t *ffi_,
unsigned long cmd_,
void *arg_,
unsigned int flags_,
void *data_,
uint32_t *out_bufsz_)
{
if(l::is_btrfs_ioctl_cmd(cmd_))
return -ENOTTY;
if(l::is_mergerfs_ioctl_cmd(cmd_))
return l::ioctl_custom(ffi_,cmd_,data_);
if(flags_ & FUSE_IOCTL_DIR)
return l::ioctl_dir(ffi_,cmd_,data_,out_bufsz_);
return l::ioctl_file(ffi_,cmd_,data_,out_bufsz_);
}
if(::_is_btrfs_ioctl_cmd(cmd_))
return -ENOTTY;
if(flags_ & FUSE_IOCTL_DIR)
return ::_ioctl_dir(ffi_,cmd_,data_,out_bufsz_);
return ::_ioctl_file(ffi_,cmd_,data_,out_bufsz_);
}

192
src/fuse_listxattr.cpp

@ -14,98 +14,136 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "category.hpp"
#include "fuse_listxattr.hpp"
#include "config.hpp"
#include "errno.hpp"
#include "fs_llistxattr.hpp"
#include "fs_path.hpp"
#include "ugid.hpp"
#include "xattr.hpp"
#include "fuse.h"
#include <optional>
#include <string>
#include <string.h>
#include <cstring>
static
ssize_t
_listxattr_size(const std::vector<Branch*> &branches_,
const char *fusepath_)
{
ssize_t rv;
ssize_t size;
std::string fullpath;
size = 0;
for(const auto branch : branches_)
{
fullpath = fs::path::make(branch->path,fusepath_);
rv = fs::llistxattr(fullpath,NULL,0);
if(rv < 0)
continue;
size += rv;
}
using std::string;
return rv;
}
static
ssize_t
_listxattr(const std::vector<Branch*> &branches_,
const char *fusepath_,
char *list_,
size_t size_)
{
ssize_t rv;
ssize_t size;
std::string fullpath;
std::optional<ssize_t> err;
if(size_ == 0)
return ::_listxattr_size(branches_,fusepath_);
size = 0;
err = -ENOENT;
for(const auto branch : branches_)
{
fullpath = fs::path::make(branch->path,fusepath_);
rv = fs::llistxattr(fullpath,list_,size_);
if(rv == -ERANGE)
return -ERANGE;
if(rv < 0)
{
if(!err.has_value())
err = rv;
continue;
}
err = 0;
list_ += rv;
size_ -= rv;
size += rv;
}
if(err < 0)
return err.value();
return size;
}
namespace l
static
int
_listxattr(const Policy::Search &searchFunc_,
const Branches &ibranches_,
const char *fusepath_,
char *list_,
const size_t size_)
{
static
int
listxattr_controlfile(Config::Read &cfg_,
char *list_,
const size_t size_)
{
string xattrs;
cfg_->keys_xattr(xattrs);
if(size_ == 0)
return xattrs.size();
if(size_ < xattrs.size())
return -ERANGE;
memcpy(list_,xattrs.c_str(),xattrs.size());
return xattrs.size();
}
static
int
listxattr(const Policy::Search &searchFunc_,
const Branches &ibranches_,
const char *fusepath_,
char *list_,
const size_t size_)
{
int rv;
std::string fullpath;
std::vector<Branch*> obranches;
rv = searchFunc_(ibranches_,fusepath_,obranches);
if(rv == -1)
return -errno;
fullpath = fs::path::make(obranches[0]->path,fusepath_);
rv = fs::llistxattr(fullpath,list_,size_);
return ((rv == -1) ? -errno : rv);
}
int rv;
std::string fullpath;
std::vector<Branch*> obranches;
rv = searchFunc_(ibranches_,fusepath_,obranches);
if(rv == -1)
return -errno;
if(size_ == 0)
return ::_listxattr_size(obranches,fusepath_);
return ::_listxattr(obranches,fusepath_,list_,size_);
}
namespace FUSE
int
FUSE::listxattr(const char *fusepath_,
char *list_,
size_t size_)
{
int
listxattr(const char *fusepath_,
char *list_,
size_t size_)
{
Config::Read cfg;
if(fusepath_ == CONTROLFILE)
return l::listxattr_controlfile(cfg,list_,size_);
switch(cfg->xattr)
{
case XAttr::ENUM::PASSTHROUGH:
break;
case XAttr::ENUM::NOATTR:
return 0;
case XAttr::ENUM::NOSYS:
return -ENOSYS;
}
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return l::listxattr(cfg->func.listxattr.policy,
cfg->branches,
fusepath_,
list_,
size_);
}
Config::Read cfg;
if(Config::is_ctrl_file(fusepath_))
return cfg->keys_listxattr(list_,size_);
switch(cfg->xattr)
{
case XAttr::ENUM::PASSTHROUGH:
break;
case XAttr::ENUM::NOATTR:
return 0;
case XAttr::ENUM::NOSYS:
return -ENOSYS;
}
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return ::_listxattr(cfg->func.listxattr.policy,
cfg->branches,
fusepath_,
list_,
size_);
}

1
src/fuse_listxattr.hpp

@ -16,6 +16,7 @@
#pragma once
#include <cstddef>
namespace FUSE
{

2
src/fuse_open.cpp

@ -14,7 +14,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fs_read.hpp"
#include "fuse_open.hpp"
#include "state.hpp"

45
src/fuse_readdir.cpp

@ -18,9 +18,17 @@
#include "fuse_readdir.hpp"
#include "config.hpp"
#include "fuse_readdir_factory.hpp"
#include "dirinfo.hpp"
#include "fuse_dirents.h"
#include "config.hpp"
#include <cstring>
#include <dirent.h>
/*
The _initialized stuff is not pretty but easiest way to deal with
the fact that mergerfs is doing arg parsing and setting up of things
@ -90,15 +98,40 @@ FUSE::ReadDir::from_string(std::string const &str_)
return 0;
}
static
int
_handle_ENOENT(const fuse_file_info_t *ffi_,
fuse_dirents_t *buf_)
{
dirent de;
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
if(di->fusepath != "/")
return -ENOENT;
de = {0};
de.d_ino = 0;
de.d_off = 0;
de.d_type = DT_REG;
strcpy(de.d_name,"error: no valid mergerfs branch found, check config");
de.d_reclen = sizeof(de);
fuse_dirents_add(buf_,&de,::strlen(de.d_name));
return 0;
}
/*
Yeah... if not initialized it will crash... ¯\_()_/¯
This will be resolved once initialization of internal objects and
handling of input is better seperated.
handling of input is better separated.
*/
int
FUSE::ReadDir::operator()(fuse_file_info_t const *ffi_,
FUSE::ReadDir::operator()(const fuse_file_info_t *ffi_,
fuse_dirents_t *buf_)
{
int rv;
std::shared_ptr<FUSE::ReadDirBase> readdir;
{
@ -106,5 +139,9 @@ FUSE::ReadDir::operator()(fuse_file_info_t const *ffi_,
readdir = _readdir;
}
return (*readdir)(ffi_,buf_);
rv = (*readdir)(ffi_,buf_);
if(rv == -ENOENT)
return ::_handle_ENOENT(ffi_,buf_);
return rv;
}

2
src/fuse_removexattr.cpp

@ -101,7 +101,7 @@ namespace FUSE
{
Config::Read cfg;
if(fusepath_ == CONTROLFILE)
if(Config::is_ctrl_file(fusepath_))
return -ENOATTR;
if(cfg->xattr.to_int())

328
src/fuse_setxattr.cpp

@ -14,187 +14,215 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_setxattr.hpp"
#include "config.hpp"
#include "errno.hpp"
#include "fs_glob.hpp"
#include "fs_lsetxattr.hpp"
#include "fs_path.hpp"
#include "fs_statvfs_cache.hpp"
#include "gidcache.hpp"
#include "num.hpp"
#include "policy_rv.hpp"
#include "str.hpp"
#include "ugid.hpp"
#include "syslog.hpp"
#include "fuse.h"
#include <string>
#include <vector>
#include <string.h>
#include <cstring>
static const char SECURITY_CAPABILITY[] = "security.capability";
using std::string;
using std::vector;
namespace l
static
int
_setxattr_cmd_xattr(const std::string_view &attrname_,
const std::string_view &attrval_)
{
static
bool
is_attrname_security_capability(const char *attrname_)
{
return (strcmp(attrname_,SECURITY_CAPABILITY) == 0);
}
std::string_view cmd;
cmd = Config::prune_cmd_xattr(attrname_);
SysLog::info("command requested: {}={}",cmd,attrval_);
static
int
setxattr_controlfile(const string &attrname_,
const string &attrval_,
const int flags_)
{
int rv;
string key;
Config::Write cfg;
if(cmd == "gc")
return (fuse_gc(),0);
if(cmd == "gc1")
return (fuse_gc1(),0);
if(cmd == "invalidate-all-nodes")
return (fuse_invalidate_all_nodes(),0);
if(cmd == "invalidate-gid-cache")
return (GIDCache::invalidate_all(),0);
if(cmd == "clear-gid-cache")
return (GIDCache::clear_all(),0);
if(!str::startswith(attrname_,"user.mergerfs."))
return -ENOATTR;
return -ENOATTR;
}
key = &attrname_[14];
static
bool
_is_attrname_security_capability(const char *attrname_)
{
return str::eq(attrname_,SECURITY_CAPABILITY);
}
if(cfg->has_key(key) == false)
return -ENOATTR;
static
int
_setxattr_ctrl_file(const char *attrname_,
const char *attrval_,
size_t attrvalsize_,
const int flags_)
{
int rv;
std::string key;
Config::Write cfg;
if((flags_ & XATTR_CREATE) == XATTR_CREATE)
return -EEXIST;
if(!Config::is_mergerfs_xattr(attrname_))
return -ENOATTR;
rv = cfg->set(key,attrval_);
if(rv < 0)
return rv;
if(Config::is_cmd_xattr(attrname_))
return ::_setxattr_cmd_xattr(attrname_,
std::string_view{attrval_,attrvalsize_});
fs::statvfs_cache_timeout(cfg->cache_statfs);
key = Config::prune_ctrl_xattr(attrname_);
if(cfg->has_key(key) == false)
return -ENOATTR;
if((flags_ & XATTR_CREATE) == XATTR_CREATE)
return -EEXIST;
rv = cfg->set(key,attrval_);
if(rv < 0)
return rv;
}
static
void
setxattr_loop_core(const string &basepath_,
const char *fusepath_,
const char *attrname_,
const char *attrval_,
const size_t attrvalsize_,
const int flags_,
PolicyRV *prv_)
{
string fullpath;
fullpath = fs::path::make(basepath_,fusepath_);
errno = 0;
fs::lsetxattr(fullpath,attrname_,attrval_,attrvalsize_,flags_);
prv_->insert(errno,basepath_);
}
static
void
setxattr_loop(const std::vector<Branch*> &branches_,
const char *fusepath_,
const char *attrname_,
const char *attrval_,
const size_t attrvalsize_,
const int flags_,
PolicyRV *prv_)
{
for(auto &branch : branches_)
{
l::setxattr_loop_core(branch->path,
fusepath_,
attrname_,
attrval_,attrvalsize_,
flags_,
prv_);
}
}
static
int
setxattr(const Policy::Action &setxattrPolicy_,
const Policy::Search &getxattrPolicy_,
const Branches &branches_,
const char *fusepath_,
const char *attrname_,
const char *attrval_,
const size_t attrvalsize_,
const int flags_)
{
int rv;
PolicyRV prv;
std::vector<Branch*> branches;
rv = setxattrPolicy_(branches_,fusepath_,branches);
if(rv == -1)
return -errno;
l::setxattr_loop(branches,fusepath_,attrname_,attrval_,attrvalsize_,flags_,&prv);
if(prv.errors.empty())
return 0;
if(prv.successes.empty())
return prv.errors[0].rv;
branches.clear();
rv = getxattrPolicy_(branches_,fusepath_,branches);
if(rv == -1)
return -errno;
return prv.get_error(branches[0]->path);
}
int
setxattr(const char *fusepath_,
const char *attrname_,
const char *attrval_,
size_t attrvalsize_,
int flags_)
{
Config::Read cfg;
if((cfg->security_capability == false) &&
l::is_attrname_security_capability(attrname_))
return -ENOATTR;
if(cfg->xattr.to_int())
return -cfg->xattr.to_int();
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return l::setxattr(cfg->func.setxattr.policy,
cfg->func.getxattr.policy,
cfg->branches,
fusepath_,
attrname_,
attrval_,
attrvalsize_,
flags_);
}
fs::statvfs_cache_timeout(cfg->cache_statfs);
return rv;
}
namespace FUSE
static
void
_setxattr_loop_core(const std::string &basepath_,
const char *fusepath_,
const char *attrname_,
const char *attrval_,
const size_t attrvalsize_,
const int flags_,
PolicyRV *prv_)
{
int
setxattr(const char *fusepath_,
const char *attrname_,
const char *attrval_,
size_t attrvalsize_,
int flags_)
{
if(fusepath_ == CONTROLFILE)
return l::setxattr_controlfile(attrname_,
string(attrval_,attrvalsize_),
flags_);
return l::setxattr(fusepath_,attrname_,attrval_,attrvalsize_,flags_);
}
std::string fullpath;
fullpath = fs::path::make(basepath_,fusepath_);
errno = 0;
fs::lsetxattr(fullpath,attrname_,attrval_,attrvalsize_,flags_);
prv_->insert(errno,basepath_);
}
static
void
_setxattr_loop(const std::vector<Branch*> &branches_,
const char *fusepath_,
const char *attrname_,
const char *attrval_,
const size_t attrvalsize_,
const int flags_,
PolicyRV *prv_)
{
for(auto &branch : branches_)
{
::_setxattr_loop_core(branch->path,
fusepath_,
attrname_,
attrval_,attrvalsize_,
flags_,
prv_);
}
}
static
int
_setxattr(const Policy::Action &setxattrPolicy_,
const Policy::Search &getxattrPolicy_,
const Branches &branches_,
const char *fusepath_,
const char *attrname_,
const char *attrval_,
const size_t attrvalsize_,
const int flags_)
{
int rv;
PolicyRV prv;
std::vector<Branch*> branches;
rv = setxattrPolicy_(branches_,fusepath_,branches);
if(rv == -1)
return -errno;
::_setxattr_loop(branches,fusepath_,attrname_,attrval_,attrvalsize_,flags_,&prv);
if(prv.errors.empty())
return 0;
if(prv.successes.empty())
return prv.errors[0].rv;
branches.clear();
rv = getxattrPolicy_(branches_,fusepath_,branches);
if(rv == -1)
return -errno;
return prv.get_error(branches[0]->path);
}
static
int
_setxattr(const char *fusepath_,
const char *attrname_,
const char *attrval_,
size_t attrvalsize_,
int flags_)
{
Config::Read cfg;
if((cfg->security_capability == false) &&
::_is_attrname_security_capability(attrname_))
return -ENOATTR;
if(cfg->xattr.to_int())
return -cfg->xattr.to_int();
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return ::_setxattr(cfg->func.setxattr.policy,
cfg->func.getxattr.policy,
cfg->branches,
fusepath_,
attrname_,
attrval_,
attrvalsize_,
flags_);
}
int
FUSE::setxattr(const char *fusepath_,
const char *attrname_,
const char *attrval_,
size_t attrvalsize_,
int flags_)
{
if(Config::is_ctrl_file(fusepath_))
return ::_setxattr_ctrl_file(attrname_,
attrval_,
attrvalsize_,
flags_);
return ::_setxattr(fusepath_,attrname_,attrval_,attrvalsize_,flags_);
}

2
src/fuse_setxattr.hpp

@ -16,6 +16,8 @@
#pragma once
#include <cstddef>
namespace FUSE
{

42
src/fuse_statx_supported.icpp

@ -1,3 +1,22 @@
/*
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 "fuse_statx.hpp"
#include "config.hpp"
@ -6,6 +25,7 @@
#include "fs_inode.hpp"
#include "fs_path.hpp"
#include "fs_statx.hpp"
#include "str.hpp"
#include "symlinkify.hpp"
#include "ugid.hpp"
@ -52,6 +72,24 @@ _set_stat_if_leads_to_reg(const std::string &path_,
return;
}
static
int
_statx_fake_root(struct fuse_statx *st_)
{
st_->ino = 0;
st_->mode = (S_IFDIR|S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
st_->nlink = 2;
st_->uid = 0;
st_->gid = 0;
st_->size = 0;
st_->blocks = 0;
st_->atime.tv_sec = 0;
st_->mtime.tv_sec = 0;
st_->ctime.tv_sec = 0;
return 0;
}
static
int
_statx_controlfile(struct fuse_statx *st_)
@ -151,6 +189,8 @@ _statx(const char *fusepath_,
cfg->symlinkify,
cfg->symlinkify_timeout,
cfg->follow_symlinks);
if((rv < 0) && Config::is_rootdir(fusepath_))
return ::_statx_fake_root(st_);
timeout_->entry = ((rv >= 0) ?
cfg->cache_entry :
@ -167,7 +207,7 @@ FUSE::statx(const char *fusepath_,
struct fuse_statx *st_,
fuse_timeouts_t *timeout_)
{
if(fusepath_ == CONTROLFILE)
if(Config::is_ctrl_file(fusepath_))
return ::_statx_controlfile(st_);
return ::_statx(fusepath_,flags_|AT_STATX_DONT_SYNC,mask_,st_,timeout_);

26
src/gidcache.cpp

@ -16,6 +16,8 @@
#include "gidcache.hpp"
#include "syslog.hpp"
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
@ -130,27 +132,47 @@ GIDCache::initgroups(const uid_t uid_,
void
GIDCache::invalidate_all()
{
size_t size;
size = _records.size();
_records.visit_all([](auto &x)
{
x.second.last_update = 0;
});
SysLog::info("gid cache invalidated, {} entries",size);
}
void
GIDCache::clear_all()
{
size_t size;
size = _records.size();
_records.clear();
SysLog::info("gid cache cleared, {} entries",size);
}
void
GIDCache::clear_unused()
{
int erased = 0;
time_t now = ::time(NULL);
auto erase_func =
[=](auto &x)
[now,&erased](auto &x)
{
return ((now - x.second.last_update) > GIDCache::remove_timeout);
bool should_erase;
time_t time_delta;
time_delta = (now - x.second.last_update);
should_erase = (time_delta > GIDCache::remove_timeout);
erased += should_erase;
return should_erase;
};
_records.erase_if(erase_func);
SysLog::info("cleared {} unused gid cache entries",erased);
}

12
src/gidcache.hpp

@ -21,20 +21,8 @@
#include <sys/types.h>
#include <unistd.h>
#include <array>
#include <vector>
#define MAXGIDS 32
#define MAXRECS 256
// GIDCache is a global, per thread cache of uid to gid + supplemental
// groups mapping for use when threads change credentials. This is
// needed due to the high cost of querying such information. The cache
// instance should always be thread local and live the lifetime of the
// app. The constructor will register the instance so they can each be
// told to invalidate the cache on demand. A second instance on the
// same thread will cause an assert to be triggered.
struct GIDRecord
{
std::vector<gid_t> gids;

14
src/mergerfs.cpp

@ -237,17 +237,17 @@ namespace l
void
usr1_signal_handler(int signal_)
{
SysLog::info("Received SIGUSR1 - invalidating all nodes");
fuse_invalidate_all_nodes();
// SysLog::info("Received SIGUSR1 - invalidating all nodes");
// fuse_invalidate_all_nodes();
}
static
void
usr2_signal_handler(int signal_)
{
SysLog::info("Received SIGUSR2 - triggering thorough gc");
fuse_gc();
GIDCache::clear_all();
// SysLog::info("Received SIGUSR2 - triggering thorough gc");
// fuse_gc();
// GIDCache::clear_all();
}
static
@ -284,8 +284,6 @@ namespace l
fuse_operations ops;
SysLog::open();
SysLog::info("mergerfs v{} started",MERGERFS_VERSION);
SysLog::info("Go to https://trapexit.github.io/mergerfs/support for support");
memset(&ops,0,sizeof(fuse_operations));
@ -309,6 +307,8 @@ namespace l
return 1;
}
SysLog::info("mergerfs v{} started",MERGERFS_VERSION);
SysLog::info("Go to https://trapexit.github.io/mergerfs/support for support");
l::warn_if_not_root();
MaintenanceThread::push_job([](int count_)

47
src/mergerfs_api.cpp

@ -1,68 +1,71 @@
#include "mergerfs_api.hpp"
#include "fs_close.hpp"
#include "fs_ioctl.hpp"
#include "fs_open.hpp"
#include "mergerfs_ioctl.hpp"
#include "fs_lgetxattr.hpp"
#include "str.hpp"
#include "scope_guard.hpp"
#include <string.h>
#include <array>
#include <cstring>
typedef std::array<char,64*1024> mfs_api_buf_t;
static
int
mergerfs::api::allpaths(const std::string &input_path_,
std::vector<std::string> &output_paths_)
_lgetxattr(const std::string &input_path_,
const std::string &key_,
std::string &value_)
{
int rv;
IOCTL_BUF buf;
std::string key;
mfs_api_buf_t buf;
rv = fs::lgetxattr(input_path_,"user.mergerfs.allpaths",buf,sizeof(buf));
key = "user.mergerfs." + key_;
rv = fs::lgetxattr(input_path_,key,buf.data(),buf.size());
if(rv < 0)
return rv;
str::split_on_null(buf,rv,&output_paths_);
value_.clear();
value_.reserve(rv);
value_.append(buf.data(),(size_t)rv);
return 0;
return rv;
}
int
_lgetxattr(const std::string &input_path_,
const std::string &key_,
std::string &value_)
mergerfs::api::allpaths(const std::string &input_path_,
std::vector<std::string> &output_paths_)
{
int rv;
IOCTL_BUF buf;
std::string val;
rv = fs::lgetxattr(input_path_,key_,buf,sizeof(buf));
rv = ::_lgetxattr(input_path_,"allpaths",val);
if(rv < 0)
return rv;
value_ = buf;
str::split_on_null(val.data(),val.size(),&output_paths_);
return rv;
return 0;
}
int
mergerfs::api::basepath(const std::string &input_path_,
std::string &basepath_)
{
return ::_lgetxattr(input_path_,"user.mergerfs.basepath",basepath_);
return ::_lgetxattr(input_path_,"basepath",basepath_);
}
int
mergerfs::api::relpath(const std::string &input_path_,
std::string &relpath_)
{
return ::_lgetxattr(input_path_,"user.mergerfs.relpath",relpath_);
return ::_lgetxattr(input_path_,"relpath",relpath_);
}
int
mergerfs::api::fullpath(const std::string &input_path_,
std::string &fullpath_)
{
return ::_lgetxattr(input_path_,"user.mergerfs.fullpath",fullpath_);
return ::_lgetxattr(input_path_,"fullpath",fullpath_);
}

86
src/mergerfs_collect_info.cpp

@ -6,6 +6,7 @@
#include "CLI11.hpp"
#include "fmt/core.h"
#include "fmt/ranges.h"
#include "scope_guard.hpp"
#include "subprocess.hpp"
@ -27,6 +28,29 @@ _write_str(const std::string &output_,
::fwrite(str_.c_str(),1,str_.size(),f);
}
template<typename ARGS>
static
void
_run(const ARGS &args_,
const std::string &output_)
{
std::string hdr;
hdr = fmt::format("=== {}\n",fmt::join(args_," "));
try
{
_write_str(output_,hdr);
subprocess::call(args_,
subprocess::output{output_.c_str()});
}
catch(...)
{
::_write_str(output_,"error: command failed to run\n");
}
_write_str(output_,"\n\n");
}
static
void
_lsblk(const std::string &output_)
@ -38,8 +62,7 @@ _lsblk(const std::string &output_)
"-o","NAME,FSTYPE,FSSIZE,SIZE,MOUNTPOINTS,RM,RO,ROTA"
};
subprocess::call(args,
subprocess::output{output_.c_str()});
::_run(args,output_);
}
static
@ -52,8 +75,7 @@ _mounts(const std::string &output_)
"/proc/mounts"
};
subprocess::call(args,
subprocess::output{output_.c_str()});
::_run(args,output_);
}
static
@ -72,8 +94,7 @@ _mount_point_stats(const std::string &output_)
for(const auto &path : allpaths)
{
auto args = {"stat",path.c_str()};
subprocess::call(args,
subprocess::output{output_.c_str()});
::_run(args,output_);
}
}
}
@ -88,14 +109,7 @@ _mergerfs_version(const std::string &output_)
"--version"
};
try
{
subprocess::call(args,
subprocess::output{output_.c_str()});
}
catch(...)
{
}
::_run(args,output_);
}
static
@ -108,14 +122,7 @@ _uname(const std::string &output_)
"-a"
};
try
{
subprocess::call(args,
subprocess::output{output_.c_str()});
}
catch(...)
{
}
::_run(args,output_);
}
static
@ -128,14 +135,7 @@ _lsb_release(const std::string &output_)
"-a"
};
try
{
subprocess::call(args,
subprocess::output{output_.c_str()});
}
catch(...)
{
}
::_run(args,output_);
}
static
@ -148,14 +148,7 @@ _df(const std::string &output_)
"-h"
};
try
{
subprocess::call(args,
subprocess::output{output_.c_str()});
}
catch(...)
{
}
::_run(args,output_);
}
static
@ -168,14 +161,7 @@ _fstab(const std::string &output_)
"/etc/fstab"
};
try
{
subprocess::call(args,
subprocess::output{output_.c_str()});
}
catch(...)
{
}
::_run(args,output_);
}
@ -202,21 +188,13 @@ mergerfs::collect_info::main(int argc_,
fmt::print("* Please have mergerfs mounted before running this tool.\n");
fs::unlink(output_filepath);
::_write_str(output_filepath,"::mergerfs --version::\n");
::_mergerfs_version(output_filepath);
::_write_str(output_filepath,"\n::uname -a::\n");
::_uname(output_filepath);
::_write_str(output_filepath,"\n::lsb_release -a::\n");
::_lsb_release(output_filepath);
::_write_str(output_filepath,"\n::df -h::\n");
::_df(output_filepath);
::_write_str(output_filepath,"\n::lsblk::\n");
::_lsblk(output_filepath);
::_write_str(output_filepath,"\n::cat /proc/mounts::\n");
::_mounts(output_filepath);
::_write_str(output_filepath,"\n::mount point stats::\n");
::_mount_point_stats(output_filepath);
::_write_str(output_filepath,"\n::cat /etc/fstab::\n");
::_fstab(output_filepath);
fmt::print("* Upload the following file to your"

23
src/mergerfs_ioctl.hpp

@ -1,5 +1,7 @@
#pragma once
#include "int_types.h"
#include <sys/ioctl.h>
#ifndef _IOC_TYPE
@ -10,11 +12,16 @@
#define _IOC_SIZEBITS 14
#endif
#define IOCTL_BUF_SIZE ((1 << _IOC_SIZEBITS) - 1)
typedef char IOCTL_BUF[IOCTL_BUF_SIZE];
#define IOCTL_APP_TYPE 0xDF
#define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF)
#define IOCTL_GC _IO(IOCTL_APP_TYPE,1)
#define IOCTL_GC1 _IO(IOCTL_APP_TYPE,2)
#define IOCTL_INVALIDATE_ALL_NODES _IO(IOCTL_APP_TYPE,3)
#define IOCTL_INVALIDATE_GID_CACHE _IO(IOCTL_APP_TYPE,4)
#define MERGERFS_IOCTL_BUF_SIZE ((1 << _IOC_SIZEBITS) - 1)
#pragma pack(push,1)
struct mergerfs_ioctl_t
{
u32 size;
char buf[MERGERFS_IOCTL_BUF_SIZE - sizeof(u32)];
};
#pragma pack(pop)
#define MERGERFS_IOCTL_APP_TYPE 0xDF
#define MERGERFS_IOCTL_GET _IOWR(MERGERFS_IOCTL_APP_TYPE,0,mergerfs_ioctl_t)
#define MERGERFS_IOCTL_SET _IOWR(MERGERFS_IOCTL_APP_TYPE,1,mergerfs_ioctl_t)

2311
src/nonstd/string.hpp
File diff suppressed because it is too large
View File

49
src/str.cpp

@ -14,9 +14,13 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "str.hpp"
#include <cstring>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <fnmatch.h>
@ -104,6 +108,28 @@ namespace str
}
}
void
splitkv(const std::string_view &str_,
const char delimiter_,
std::string &key_,
std::string_view &val_)
{
size_t pos;
pos = str_.find(delimiter_);
if(pos != std::string_view::npos)
{
key_ = str_.substr(0,pos);
val_ = str_.substr(pos + 1,
str_.size() - pos + 1);
}
else
{
key_ = str_;
val_ = std::string_view{};
}
}
void
splitkv(const string &str_,
const char delimiter_,
@ -272,3 +298,26 @@ namespace str
return rv;
}
}
bool
str::eq(const char *s0_,
const char *s1_)
{
return (strcmp(s0_,s1_) == 0);
}
bool
str::startswith(const char *s_,
const char *p_)
{
while(*p_)
{
if(*p_ != *s_)
return false;
p_++;
s_++;
}
return true;
}

14
src/str.hpp

@ -56,6 +56,12 @@ namespace str
std::string *key,
std::string *value);
void
splitkv(const std::string_view &str,
const char delimiter,
std::string &key,
std::string_view &val);
std::string
join(const std::vector<std::string> &vec,
const size_t substridx,
@ -91,10 +97,18 @@ namespace str
startswith(const std::string &str_,
const std::string &prefix_);
bool
startswith(const char *str,
const char *prefix);
bool
endswith(const std::string &str_,
const std::string &suffix_);
std::string
trim(const std::string &str);
bool
eq(const char *s0,
const char *s1);
}
Loading…
Cancel
Save