Browse Source

Rework config, centralize fuse config

config
Antonio SJ Musumeci 1 week ago
parent
commit
78e68bcace
  1. 38
      libfuse/include/fuse_cfg.hpp
  2. 5
      libfuse/include/fuse_common.h
  3. 31
      libfuse/include/fuse_config.hpp
  4. 10
      libfuse/include/fuse_msgbuf.hpp
  5. 0
      libfuse/include/int_types.h
  6. 75
      libfuse/lib/fuse.cpp
  7. 21
      libfuse/lib/fuse_cfg.cpp
  8. 73
      libfuse/lib/fuse_config.cpp
  9. 5
      libfuse/lib/fuse_i.h
  10. 10
      libfuse/lib/fuse_loop.cpp
  11. 162
      libfuse/lib/fuse_lowlevel.cpp
  12. 31
      libfuse/lib/fuse_msgbuf.cpp
  13. 65
      libfuse/lib/mount_generic.c
  14. 8
      mkdocs/docs/config/cache.md
  15. 30
      mkdocs/docs/config/deprecated_options.md
  16. 41
      mkdocs/docs/config/options.md
  17. 51
      mkdocs/docs/config/passthrough.md
  18. 8
      mkdocs/docs/config/proxy-ioprio.md
  19. 92
      mkdocs/docs/faq/configuration_and_policies.md
  20. 9
      mkdocs/docs/faq/recommendations_and_warnings.md
  21. 2
      mkdocs/docs/known_issues_bugs.md
  22. 10
      mkdocs/docs/remote_filesystems.md
  23. 4
      mkdocs/docs/tooling.md
  24. 4
      mkdocs/mkdocs.yml
  25. 553
      src/config.cpp
  26. 91
      src/config.hpp
  27. 6
      src/config_cachefiles.cpp
  28. 1
      src/config_cachefiles.hpp
  29. 24
      src/config_dummy.hpp
  30. 40
      src/config_noforget.hpp
  31. 22
      src/config_passthrough_io.cpp
  32. 4
      src/config_passthrough_io.hpp
  33. 27
      src/fuse_create.cpp
  34. 19
      src/fuse_init.cpp
  35. 31
      src/fuse_open.cpp
  36. 482
      src/mergerfs.cpp
  37. 263
      src/option_parser.cpp
  38. 5
      src/option_parser.hpp
  39. 16
      src/str.cpp
  40. 5
      src/str.hpp
  41. 39
      src/tofrom_ref.hpp
  42. 2
      src/tofrom_string.hpp
  43. 2
      src/tofrom_wrapper.hpp

38
libfuse/include/fuse_cfg.hpp

@ -0,0 +1,38 @@
#pragma once
#include "int_types.h"
#include <climits>
#include <string>
#define FUSE_CFG_INVALID_ID -1
#define FUSE_CFG_INVALID_UMASK -1
struct fuse_cfg_t
{
s64 uid = FUSE_CFG_INVALID_ID;
bool valid_uid() const;
s64 gid = FUSE_CFG_INVALID_ID;
bool valid_gid() const;
s64 umask = FUSE_CFG_INVALID_UMASK;
bool valid_umask() const;
s64 remember_nodes = 0;
bool debug = false;
int max_background = 0;
int congestion_threshold = 0;
u32 max_pages = 32;
int passthrough_max_stack_depth = 1;
int read_thread_count = 0;
int process_thread_count = -1;
int process_thread_queue_depth = 2;
std::string pin_threads = "false";
};
extern fuse_cfg_t fuse_cfg;

5
libfuse/include/fuse_common.h

@ -139,13 +139,8 @@ struct fuse_conn_info
{
unsigned proto_major;
unsigned proto_minor;
unsigned max_write;
unsigned max_readahead;
uint64_t capable;
uint64_t want;
unsigned max_background;
unsigned congestion_threshold;
uint16_t max_pages;
};
struct fuse_session;

31
libfuse/include/fuse_config.hpp

@ -1,31 +0,0 @@
/*
ISC License
Copyright (c) 2023, 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 <string>
int fuse_config_get_read_thread_count();
int fuse_config_get_process_thread_count();
int fuse_config_get_process_thread_queue_depth();
std::string fuse_config_get_pin_threads();
void fuse_config_set_read_thread_count(int const);
void fuse_config_set_process_thread_count(int const);
void fuse_config_set_process_thread_queue_depth(int const);
void fuse_config_set_pin_threads(std::string const);

10
libfuse/include/fuse_msgbuf.hpp

@ -18,11 +18,10 @@
#pragma once
#include "int_types.h"
#include "fuse_msgbuf_t.h"
#include "extern_c.h"
EXTERN_C_BEGIN
u32 msgbuf_get_pagesize();
void msgbuf_set_bufsize(const uint32_t size);
uint64_t msgbuf_get_bufsize();
@ -35,8 +34,3 @@ void msgbuf_gc_10percent();
uint64_t msgbuf_alloc_count();
uint64_t msgbuf_avail_count();
void msgbuf_page_align(fuse_msgbuf_t *msgbuf);
void msgbuf_write_align(fuse_msgbuf_t *msgbuf);
EXTERN_C_END

0
src/int_types.h → libfuse/include/int_types.h

75
libfuse/lib/fuse.cpp

@ -19,6 +19,7 @@
#include "mutex.hpp"
#include "node.hpp"
#include "fuse_cfg.hpp"
#include "fuse_req.h"
#include "fuse_dirents.hpp"
#include "fuse_i.h"
@ -70,16 +71,6 @@ static int g_LOG_METRICS = 0;
struct fuse_config
{
unsigned int uid;
unsigned int gid;
unsigned int umask;
int remember;
int debug;
int nogc;
int set_mode;
int set_uid;
int set_gid;
int help;
};
struct lock_queue_element
@ -620,7 +611,7 @@ inline
int
remember_nodes()
{
return (f.conf.remember > 0);
return (fuse_cfg.remember_nodes > 0);
}
static
@ -703,7 +694,7 @@ find_node(uint64_t parent,
goto out_err;
node->nodeid = generate_nodeid(&f.nodeid_gen);
if(f.conf.remember)
if(fuse_cfg.remember_nodes)
inc_nlookup(node);
if(hash_name(node,parent,name) == -1)
@ -1264,12 +1255,12 @@ void
set_stat(uint64_t nodeid,
struct stat *stbuf)
{
if(f.conf.set_mode)
stbuf->st_mode = (stbuf->st_mode & S_IFMT) | (0777 & ~f.conf.umask);
if(f.conf.set_uid)
stbuf->st_uid = f.conf.uid;
if(f.conf.set_gid)
stbuf->st_gid = f.conf.gid;
if(fuse_cfg.valid_uid())
stbuf->st_uid = fuse_cfg.uid;
if(fuse_cfg.valid_gid())
stbuf->st_gid = fuse_cfg.gid;
if(fuse_cfg.valid_umask())
stbuf->st_mode = (stbuf->st_mode & S_IFMT) | (0777 & ~fuse_cfg.umask);
}
static
@ -3449,7 +3440,7 @@ fuse_prune_some_remembered_nodes(int *offset_)
checked++;
age = (now - fn->time);
if(f.conf.remember > age)
if(fuse_cfg.remember_nodes > age)
break;
assert(fn->node->nlookup == 1);
@ -3597,38 +3588,9 @@ enum
static const struct fuse_opt fuse_lib_opts[] =
{
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP),
FUSE_LIB_OPT("debug", debug,1),
FUSE_LIB_OPT("-d", debug,1),
FUSE_LIB_OPT("nogc", nogc,1),
FUSE_LIB_OPT("umask=", set_mode,1),
FUSE_LIB_OPT("umask=%o", umask,0),
FUSE_LIB_OPT("uid=", set_uid,1),
FUSE_LIB_OPT("uid=%d", uid,0),
FUSE_LIB_OPT("gid=", set_gid,1),
FUSE_LIB_OPT("gid=%d", gid,0),
FUSE_LIB_OPT("noforget", remember,-1),
FUSE_LIB_OPT("remember=%u", remember,0),
FUSE_OPT_END
};
static void fuse_lib_help(void)
{
fprintf(stderr,
" -o umask=M set file permissions (octal)\n"
" -o uid=N set file owner\n"
" -o gid=N set file group\n"
" -o noforget never forget cached inodes\n"
" -o remember=T remember cached inodes for T seconds (0s)\n"
" -o threads=NUM number of worker threads. 0 = autodetect.\n"
" Negative values autodetect then divide by\n"
" absolute value. default = 0\n"
"\n");
}
static
int
fuse_lib_opt_proc(void *data,
@ -3636,24 +3598,9 @@ fuse_lib_opt_proc(void *data,
int key,
struct fuse_args *outargs)
{
(void)arg; (void)outargs;
if(key == KEY_HELP)
{
struct fuse_config *conf = (struct fuse_config *)data;
fuse_lib_help();
conf->help = 1;
}
return 1;
}
int
fuse_is_lib_option(const char *opt)
{
return fuse_lowlevel_is_lib_option(opt) || fuse_opt_match(fuse_lib_opts,opt);
}
static
int
node_table_init(struct node_table *t)
@ -3872,7 +3819,7 @@ fuse_new(struct fuse_chan *ch,
if(fuse_opt_parse(args,&f.conf,fuse_lib_opts,fuse_lib_opt_proc) == -1)
goto out_free_fs;
g_LOG_METRICS = f.conf.debug;
g_LOG_METRICS = fuse_cfg.debug;
f.se = fuse_lowlevel_new_common(args,&llop,sizeof(llop),&f);
if(f.se == NULL)

21
libfuse/lib/fuse_cfg.cpp

@ -0,0 +1,21 @@
#include "fuse_cfg.hpp"
fuse_cfg_t fuse_cfg;
bool
fuse_cfg_t::valid_uid() const
{
return (uid != FUSE_CFG_INVALID_ID);
}
bool
fuse_cfg_t::valid_gid() const
{
return (gid != FUSE_CFG_INVALID_ID);
}
bool
fuse_cfg_t::valid_umask() const
{
return (umask != FUSE_CFG_INVALID_UMASK);
}

73
libfuse/lib/fuse_config.cpp

@ -1,73 +0,0 @@
/*
ISC License
Copyright (c) 2023, 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 <string>
static int g_READ_THREAD_COUNT = -1;
static int g_PROCESS_THREAD_COUNT = -1;
static int g_PROCESS_THREAD_QUEUE_DEPTH = -1;
static std::string g_PIN_THREADS = {};
int
fuse_config_get_read_thread_count()
{
return g_READ_THREAD_COUNT;
}
void
fuse_config_set_read_thread_count(int const v_)
{
g_READ_THREAD_COUNT = v_;
}
int
fuse_config_get_process_thread_count()
{
return g_PROCESS_THREAD_COUNT;
}
void
fuse_config_set_process_thread_count(int const v_)
{
g_PROCESS_THREAD_COUNT = v_;
}
int
fuse_config_get_process_thread_queue_depth()
{
return g_PROCESS_THREAD_QUEUE_DEPTH;
}
void
fuse_config_set_process_thread_queue_depth(int const v_)
{
g_PROCESS_THREAD_QUEUE_DEPTH = v_;
}
std::string
fuse_config_get_pin_threads()
{
return g_PIN_THREADS;
}
void
fuse_config_set_pin_threads(std::string const v_)
{
g_PIN_THREADS = v_;
}

5
libfuse/lib/fuse_i.h

@ -45,11 +45,6 @@ struct fuse_notify_req
struct fuse_ll
{
unsigned passthrough_max_stack_depth;
int debug;
int no_remote_posix_lock;
int no_remote_flock;
int big_writes;
struct fuse_lowlevel_ops op;
void *userdata;
uid_t owner;

10
libfuse/lib/fuse_loop.cpp

@ -14,7 +14,7 @@
#include "fuse_kernel.h"
#include "fuse_lowlevel.h"
#include "fuse_config.hpp"
#include "fuse_cfg.hpp"
#include "fuse_msgbuf.hpp"
#include "fuse_ll.hpp"
@ -336,10 +336,10 @@ fuse_loop_mt(struct fuse *f_)
fuse_populate_maintenance_thread(f_);
res = fuse_session_loop_mt(fuse_get_session(),
fuse_config_get_read_thread_count(),
fuse_config_get_process_thread_count(),
fuse_config_get_process_thread_queue_depth(),
fuse_config_get_pin_threads());
fuse_cfg.read_thread_count,
fuse_cfg.process_thread_count,
fuse_cfg.process_thread_queue_depth,
fuse_cfg.pin_threads);
MaintenanceThread::stop();

162
libfuse/lib/fuse_lowlevel.cpp

@ -14,6 +14,7 @@
#include "lfmp.h"
#include "debug.hpp"
#include "fuse_cfg.hpp"
#include "fuse_i.h"
#include "fuse_kernel.h"
#include "fuse_msgbuf.hpp"
@ -43,7 +44,7 @@
#define OFFSET_MAX 0x7fffffffffffffffLL
#define container_of(ptr, type, member) ({ \
const decltype( ((type*)0)->member ) *__mptr = (ptr); \
const decltype( ((type*)0)->member ) *__mptr = (ptr); \
(type *)( (char*)__mptr - offsetof(type,member) );})
static size_t pagesize;
@ -1117,11 +1118,17 @@ do_init(fuse_req_t *req,
struct fuse_in_header *hdr_)
{
struct fuse_init_out outarg = {0};
struct fuse_init_in *arg = (struct fuse_init_in *) &hdr_[1];
struct fuse_init_in *arg = (struct fuse_init_in *)&hdr_[1];
struct fuse_ll *f = req->f;
size_t bufsize = fuse_chan_bufsize(req->ch);
size_t bufsize;
uint64_t inargflags;
uint64_t outargflags;
u32 max_write;
u32 max_readahead;
max_write = UINT_MAX;
max_readahead = UINT_MAX;
bufsize = fuse_chan_bufsize(req->ch);
inargflags = 0;
outargflags = 0;
@ -1158,8 +1165,8 @@ do_init(fuse_req_t *req,
if(inargflags & FUSE_INIT_EXT)
inargflags |= (((uint64_t)arg->flags2) << 32);
if(arg->max_readahead < f->conn.max_readahead)
f->conn.max_readahead = arg->max_readahead;
if(arg->max_readahead < max_readahead)
max_readahead = arg->max_readahead;
if(inargflags & FUSE_ASYNC_READ)
f->conn.capable |= FUSE_CAP_ASYNC_READ;
@ -1209,15 +1216,15 @@ do_init(fuse_req_t *req,
else
{
f->conn.want &= ~FUSE_CAP_ASYNC_READ;
f->conn.max_readahead = 0;
max_readahead = 0;
}
if(req->f->conn.proto_minor >= 18)
f->conn.capable |= FUSE_CAP_IOCTL_DIR;
if(f->op.getlk && f->op.setlk && !f->no_remote_posix_lock)
if(f->op.getlk && f->op.setlk)
f->conn.want |= FUSE_CAP_POSIX_LOCKS;
if(f->op.flock && !f->no_remote_flock)
if(f->op.flock)
f->conn.want |= FUSE_CAP_FLOCK_LOCKS;
if(bufsize < FUSE_MIN_READ_BUFFER)
@ -1228,8 +1235,8 @@ do_init(fuse_req_t *req,
}
bufsize -= pagesize;
if(bufsize < f->conn.max_write)
f->conn.max_write = bufsize;
if(bufsize < max_write)
max_write = bufsize;
f->got_init = 1;
if(f->op.init)
@ -1238,10 +1245,11 @@ do_init(fuse_req_t *req,
outargflags = outarg.flags;
if((inargflags & FUSE_MAX_PAGES) && (f->conn.want & FUSE_CAP_MAX_PAGES))
{
outargflags |= FUSE_MAX_PAGES;
outarg.max_pages = f->conn.max_pages;
outargflags |= FUSE_MAX_PAGES;
outarg.max_pages = fuse_cfg.max_pages;
msgbuf_set_bufsize(outarg.max_pages + 1);
msgbuf_set_bufsize(outarg.max_pages);
max_write = (msgbuf_get_pagesize() * outarg.max_pages);
}
if(f->conn.want & FUSE_CAP_ASYNC_READ)
@ -1288,7 +1296,7 @@ do_init(fuse_req_t *req,
if(f->conn.want & FUSE_CAP_PASSTHROUGH)
{
outargflags |= FUSE_PASSTHROUGH;
outarg.max_stack_depth = f->passthrough_max_stack_depth;
outarg.max_stack_depth = fuse_cfg.passthrough_max_stack_depth;
}
if(inargflags & FUSE_INIT_EXT)
@ -1299,21 +1307,19 @@ do_init(fuse_req_t *req,
outarg.flags = outargflags;
outarg.max_readahead = f->conn.max_readahead;
outarg.max_write = f->conn.max_write;
outarg.max_readahead = max_readahead;
outarg.max_write = max_write;
if(f->conn.proto_minor >= 13)
{
if(f->conn.max_background >= (1 << 16))
f->conn.max_background = (1 << 16) - 1;
if(f->conn.congestion_threshold > f->conn.max_background)
f->conn.congestion_threshold = f->conn.max_background;
if(!f->conn.congestion_threshold)
{
f->conn.congestion_threshold = f->conn.max_background * 3 / 4;
}
outarg.max_background = f->conn.max_background;
outarg.congestion_threshold = f->conn.congestion_threshold;
if(fuse_cfg.max_background >= (1 << 16))
fuse_cfg.max_background = ((1 << 16) - 1);
if(fuse_cfg.congestion_threshold > fuse_cfg.max_background)
fuse_cfg.congestion_threshold = fuse_cfg.max_background;
if(!fuse_cfg.congestion_threshold)
fuse_cfg.congestion_threshold = (fuse_cfg.max_background * 3 / 4);
outarg.max_background = fuse_cfg.max_background;
outarg.congestion_threshold = fuse_cfg.congestion_threshold;
}
if(f->conn.proto_minor >= 23)
@ -1780,86 +1786,6 @@ fuse_ll_funcs[FUSE_OPCODE_LEN] =
#define FUSE_MAXOPS (sizeof(fuse_ll_funcs) / sizeof(fuse_ll_funcs[0]))
enum {
KEY_HELP,
KEY_VERSION,
};
static const struct fuse_opt fuse_ll_opts[] =
{
{ "debug", offsetof(struct fuse_ll, debug), 1 },
{ "-d", offsetof(struct fuse_ll, debug), 1 },
{ "max_readahead=%u", offsetof(struct fuse_ll, conn.max_readahead), 0 },
{ "max_background=%u", offsetof(struct fuse_ll, conn.max_background), 0 },
{ "congestion_threshold=%u",
offsetof(struct fuse_ll, conn.congestion_threshold), 0 },
{ "no_remote_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1},
{ "no_remote_lock", offsetof(struct fuse_ll, no_remote_flock), 1},
{ "no_remote_flock", offsetof(struct fuse_ll, no_remote_flock), 1},
{ "no_remote_posix_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1},
{ "passthrough-max-stack-depth=%u", offsetof(struct fuse_ll, passthrough_max_stack_depth), 0},
FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("-V", KEY_VERSION),
FUSE_OPT_KEY("--version", KEY_VERSION),
FUSE_OPT_END
};
static
void
fuse_ll_version(void)
{
fprintf(stderr, "using FUSE kernel interface version %i.%i\n",
FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
}
static
void
fuse_ll_help(void)
{
fprintf(stderr,
" -o max_readahead=N set maximum readahead\n"
" -o max_background=N set number of maximum background requests\n"
" -o congestion_threshold=N set kernel's congestion threshold\n"
" -o no_remote_lock disable remote file locking\n"
" -o no_remote_flock disable remote file locking (BSD)\n"
" -o no_remote_posix_lock disable remove file locking (POSIX)\n"
);
}
static
int
fuse_ll_opt_proc(void *data,
const char *arg,
int key,
struct fuse_args *outargs)
{
(void) data; (void) outargs;
switch (key)
{
case KEY_HELP:
fuse_ll_help();
break;
case KEY_VERSION:
fuse_ll_version();
break;
default:
fprintf(stderr, "fuse: unknown option `%s'\n", arg);
}
return -1;
}
int
fuse_lowlevel_is_lib_option(const char *opt)
{
return fuse_opt_match(fuse_ll_opts, opt);
}
static
void
fuse_ll_destroy(void *data)
@ -2032,6 +1958,23 @@ fuse_ll_buf_process_read_init(struct fuse_session *se_,
return;
}
static const struct fuse_opt fuse_ll_opts[] =
{
FUSE_OPT_END
};
static
int
fuse_ll_opt_proc(void *data_,
const char *arg_,
int key_,
struct fuse_args *outargs_)
{
fmt::print(stderr, "* ERROR: unknown option '{}'\n", arg_);
return -1;
}
/*
* always call fuse_lowlevel_new_common() internally, to work around a
* misfeature in the FreeBSD runtime linker, which links the old
@ -2060,9 +2003,6 @@ fuse_lowlevel_new_common(struct fuse_args *args,
goto out;
}
f->conn.max_write = UINT_MAX;
f->conn.max_readahead = UINT_MAX;
f->passthrough_max_stack_depth = 2;
list_init_nreq(&f->notify_list);
f->notify_ctr = 1;
mutex_init(&f->lock);
@ -2075,7 +2015,7 @@ fuse_lowlevel_new_common(struct fuse_args *args,
goto out_free;
}
if(fuse_opt_parse(args, f, fuse_ll_opts, fuse_ll_opt_proc) == -1)
if(fuse_opt_parse(args,NULL,fuse_ll_opts,fuse_ll_opt_proc) == -1)
goto out_key_destroy;
memcpy(&f->op, op, op_size);

31
libfuse/lib/fuse_msgbuf.cpp

@ -16,6 +16,7 @@
*/
#include "fuse_msgbuf.hpp"
#include "fuse.h"
#include "fuse_kernel.h"
@ -42,22 +43,34 @@ msgbuf_get_bufsize()
return g_BUFSIZE;
}
u32
msgbuf_get_pagesize()
{
return g_PAGESIZE;
}
// +1 page so that it is used for the "header" of the allocation as
// well as to allow for offseting for write requests to be page
// aligned. +1 again for fuse header as the max_pages value is for the
// body.
void
msgbuf_set_bufsize(const uint32_t size_in_pages_)
{
g_BUFSIZE = ((size_in_pages_ + 1) * g_PAGESIZE);
g_BUFSIZE = ((size_in_pages_ + 2) * g_PAGESIZE);
}
static
void
msgbuf_page_align(fuse_msgbuf_t *msgbuf_)
_msgbuf_page_align(fuse_msgbuf_t *msgbuf_)
{
msgbuf_->mem = (char*)msgbuf_;
msgbuf_->mem += g_PAGESIZE;
msgbuf_->size = (g_BUFSIZE - g_PAGESIZE);
}
static
void
msgbuf_write_align(fuse_msgbuf_t *msgbuf_)
_msgbuf_write_align(fuse_msgbuf_t *msgbuf_)
{
msgbuf_->mem = (char*)msgbuf_;
msgbuf_->mem += g_PAGESIZE;
@ -72,8 +85,8 @@ void
_msgbuf_constructor()
{
g_PAGESIZE = sysconf(_SC_PAGESIZE);
// FUSE_MAX_MAX_PAGES for payload + 1 for message header
msgbuf_set_bufsize(FUSE_DEFAULT_MAX_MAX_PAGES + 1);
msgbuf_set_bufsize(FUSE_DEFAULT_MAX_MAX_PAGES);
}
static
@ -86,7 +99,7 @@ _msgbuf_destructor()
static
void*
page_aligned_malloc(const uint64_t size_)
_page_aligned_malloc(const uint64_t size_)
{
int rv;
void *buf = NULL;
@ -111,7 +124,7 @@ _msgbuf_alloc(msgbuf_setup_func_t setup_func_)
{
g_MUTEX.unlock();
msgbuf = (fuse_msgbuf_t*)page_aligned_malloc(g_BUFSIZE);
msgbuf = (fuse_msgbuf_t*)_page_aligned_malloc(g_BUFSIZE);
if(msgbuf == NULL)
return NULL;
@ -135,13 +148,13 @@ _msgbuf_alloc(msgbuf_setup_func_t setup_func_)
fuse_msgbuf_t*
msgbuf_alloc()
{
return _msgbuf_alloc(msgbuf_write_align);
return _msgbuf_alloc(_msgbuf_write_align);
}
fuse_msgbuf_t*
msgbuf_alloc_page_aligned()
{
return _msgbuf_alloc(msgbuf_page_align);
return _msgbuf_alloc(_msgbuf_page_align);
}
static

65
libfuse/lib/mount_generic.c

@ -191,36 +191,37 @@ static int fuse_mount_opt_proc(void *data, const char *arg, int key,
{
struct mount_opts *mo = data;
switch (key) {
case KEY_RO:
arg = "ro";
/* fall through */
case KEY_KERN_FLAG:
set_mount_flag(arg, &mo->flags);
return 0;
case KEY_KERN_OPT:
return fuse_opt_add_opt(&mo->kernel_opts, arg);
case KEY_FUSERMOUNT_OPT:
return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
case KEY_SUBTYPE_OPT:
return fuse_opt_add_opt(&mo->subtype_opt, arg);
case KEY_MTAB_OPT:
return fuse_opt_add_opt(&mo->mtab_opts, arg);
case KEY_HELP:
mount_help();
mo->ishelp = 1;
break;
case KEY_VERSION:
mount_version();
mo->ishelp = 1;
break;
}
switch (key)
{
case KEY_RO:
arg = "ro";
/* fall through */
case KEY_KERN_FLAG:
set_mount_flag(arg, &mo->flags);
return 0;
case KEY_KERN_OPT:
return fuse_opt_add_opt(&mo->kernel_opts, arg);
case KEY_FUSERMOUNT_OPT:
return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
case KEY_SUBTYPE_OPT:
return fuse_opt_add_opt(&mo->subtype_opt, arg);
case KEY_MTAB_OPT:
return fuse_opt_add_opt(&mo->mtab_opts, arg);
case KEY_HELP:
mount_help();
mo->ishelp = 1;
break;
case KEY_VERSION:
mount_version();
mo->ishelp = 1;
break;
}
return 1;
}
@ -542,8 +543,8 @@ int fuse_kern_mount(const char *mountpoint, struct fuse_args *args)
memset(&mo, 0, sizeof(mo));
mo.flags = MS_NOSUID | MS_NODEV;
if (args &&
fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
if(args &&
fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
return -1;
res = 0;

8
mkdocs/docs/config/cache.md

@ -41,10 +41,10 @@ transparently enable page caching when mmap is requested. This means
it should be safe to set `cache.files=off`. However, on Linux v6.5 and
below you will need to configure `cache.files` as you need.
If [passthrough](passthrough.md) is enabled so must be page
caching. mergerfs will set `cache.files=auto-full` if `passthrough` is
enabled. And when using `passthrough` the there is no double page
caching since it is in fact passing through the IO.
If [passthrough.io](passthrough.md) is enabled so must page
caching. mergerfs will set `cache.files=auto-full` if `passthrough.io`
is enabled. And when using `passthrough.io` the there is no double
page caching since it is in fact passing through the IO.
[^1]: This is not unique to mergerfs and affects all FUSE

30
mkdocs/docs/config/deprecated_options.md

@ -3,19 +3,21 @@
These are old, deprecated options which may no longer have any
function or have been replaced. **They should not be used.**
* **direct_io**: Bypass page cache. Use `cache.files=off`
instead.
* **kernel_cache**: Do not invalidate data cache on file open. Use
`cache.files=full` instead.
* **auto_cache**: Invalidate data cache if file mtime or
size change. Use `cache.files=auto-full` instead. (default: false)
* **async_read**: Perform reads asynchronously. Use
`async_read=true` instead.
* **sync_read**: Perform reads synchronously. Use
`async_read=false` instead.
* **splice_read**: Does nothing.
* **splice_write**: Does nothing.
* **splice_move**: Does nothing.
* **allow_other**: mergerfs v2.35.0 and above sets this FUSE option
automatically if running as root.
* **use_ino**: Effectively replaced with `inodecalc`.
* **async_read**: Use `async_read=true`.
* **atomic_o_trunc**: Does nothing.
* **big_writes**: Does nothing.
* **attr_timeout**: Use `cache.attr`.
* **auto_cache**: Use `cache.files=auto-full`.
* **direct_io**: Use `cache.files=off`.
* **entry_timeout**: Use `cache.entry`.
* **hard_remove**: Does nothing.
* **kernel_cache**: Use `cache.files=full`.
* **negative_entry**: Use `cache.negative_entry`.
* **nonempty**: Does nothing.
* **splice_move**: Does nothing.
* **splice_read**: Does nothing.
* **splice_write**: Does nothing.
* **sync_read**: Use `async_read=false`.
* **use_ino**: Use `inodecalc`.

41
mkdocs/docs/config/options.md

@ -30,7 +30,7 @@ config file.
## mount options
* **config**: Path to a config file. Same arguments as below in
key=val / ini style format.
`key` or `key=val` style format. `#` used for comments.
* **[branches](branches.md)**: Colon delimited list of branches. Used
primarily in config file.
* **[branches-mount-timeout](branches-mount-timeout.md)=UINT**: Number
@ -95,7 +95,7 @@ config file.
xattrs. Default is to passthrough xattr requests. 'noattr' will
short circuit as if nothing exists. 'nosys' will respond with ENOSYS
as if xattrs are not supported or disabled. (default: passthrough)
* **[link_cow](link_cow.md)=BOOL**: When enabled if a regular file is
* **[link-cow](link_cow.md)=BOOL**: When enabled if a regular file is
opened which has a link count > 1 it will copy the file to a
temporary file and rename over the original. Breaking the link and
providing a basic copy-on-write function similar to
@ -130,11 +130,11 @@ config file.
* **[fuse_msg_size](fuse_msg_size.md)=PAGESIZE**: Set the max number of
pages per FUSE message. Only available on Linux >= 4.20 and ignored
otherwise. (min: 1; max: 65535; default: "1M")
* **[threads](threads.md)=INT**: Number of threads to use. When used
alone (`process-thread-count=-1`) it sets the number of threads
reading and processing FUSE messages. When used together it sets the
number of threads reading from FUSE. When set to zero it will
attempt to discover and use the number of logical cores. If the
* **[read-thread-count](threads.md)=INT**: Number of threads to
use. When used alone (`process-thread-count=-1`) it sets the number
of threads reading and processing FUSE messages. When used together
it sets the number of threads reading from FUSE. When set to zero it
will attempt to discover and use the number of logical cores. If the
thread count is set negative it will look up the number of cores
then divide by the absolute value. ie. threads=-2 on an 8 core
machine will result in 8 / 2 = 4 threads. There will always be at
@ -142,7 +142,6 @@ config file.
`process-thread-count` then it will try to pick reasonable values
based on CPU thread count. NOTE: higher number of threads increases
parallelism but usually decreases throughput. (default: 0)
* **[read-thread-count](threads.md)=INT**: Alias for `threads`.
* **[process-thread-count](threads.md)=INT**: Enables separate thread
pool to asynchronously process FUSE requests. In this mode
`read-thread-count` refers to the number of threads reading FUSE
@ -153,7 +152,7 @@ config file.
time. Meaning the total memory usage of the queues is queue depth
multiplied by the number of process threads plus read thread
count. 0 sets the depth to the same as the process thread
count. (default: 0)
count. (default: 2)
* **[pin-threads](pin-threads.md)=STR**: Selects a strategy to pin
threads to CPUs (default: unset)
* **[flush-on-close](flush-on-close.md)=never|always|opened-for-write**:
@ -170,9 +169,9 @@ config file.
* **[func.FUNC](functions_categories_policies.md)=POLICY**: Sets the
specific FUSE function's policy. See below for the list of value
types. Example: **func.getattr=newest**
* **[func.readdir](func_readdir.md)=seq|cosr|cor|cosr:INT|cor:INT**: Sets `readdir`
policy. INT value sets the number of threads to use for
concurrency. (default: seq)
* **[func.readdir](func_readdir.md)=seq|cosr|cor|cosr:INT|cor:INT**:
Sets `readdir` policy. INT value sets the number of threads to use
for concurrency. (default: seq)
* **[category.action](functions_categories_policies.md)=POLICY**: Sets
policy of all FUSE functions in the action category. (default:
epall)
@ -206,15 +205,23 @@ config file.
with `cache.files=per-process` (if the process is not in
`process-names`) or `cache.files=off`. (Is a kernel feature added in
v6.2) (default: true)
* **[passthrough](passthrough.md)**: Enable [FUSE IO
* **[passthrough.io](passthrough.md)=ENUM**: Enable [FUSE IO
passthrough](https://kernelnewbies.org/Linux_6.9#Faster_FUSE_I.2FO)
if available. (default: off)
* **gid-cache-expire-timeout**: Number of seconds till supplemental
group data is refreshed in the [GID
* **passthrough.max-stack-depth=INT**: Set to `1` another filesystem
can be stacked on mergerfs. Set to `2` to have mergerfs stacked over
another filesystem. (default: 1)
* **gid-cache.expire-timeout=INT**: Number of seconds till
supplemental group data is refreshed in the [GID
cache](../known_issues_bugs.md#supplemental-user-groups). (default:
3600)
* **gid-cache-remove-timeout**: Number of seconds to wait till cached
data is removed due to lack of usage. (default: 43200)
* **gid-cache.remove-timeout=INT**: Number of seconds to wait till
cached data is removed due to lack of usage. (default: 43200)
* **remember-nodes=INT**: The number of seconds to keep the internal
representation of a file once the OS tells mergerfs it is no longer
needed. Really only needed for [exporting mergerfs via
NFS](../remote_filesystems.md) (default: 0)
* **noforget**: Effectively sets `remember-nodes` to infinity.
**NOTE:** Options are evaluated in the order listed so if the options
are **func.rmdir=rand,category.action=ff** the **action** category

51
mkdocs/docs/config/passthrough.md

@ -1,4 +1,4 @@
# passthrough
# passthrough.io
* default: `off`
* arguments:
@ -14,7 +14,7 @@ to act as an active proxy for all read and write requests. This
results in, at times, significant overhead compared to direct
interaction with the underlying filesystems. Not because `mergerfs` is
doing anything particularly slow but because the additional
communication and data transfers required. With the `passthrough`
communication and data transfers required. With the `passthrough.io`
feature `mergerfs` is able to instruct the kernel to perform reads and
writes directly on the underlying file. Bypassing `mergerfs` entirely
(specifically and only for reads and writes) and thereby providing
@ -37,48 +37,47 @@ features related to read/write are affected.
requests being sent to `mergerfs` there is little reason to have
larger message sizes since the only other message larger than 1
page, directory reading, currently has a small fixed buffer size.
* parallel-direct-writes: Not only is `direct-io` and `passthrough`
* parallel-direct-writes: Not only is `direct-io` and `passthrough.io`
mutually exclusive but since `mergerfs` no longer receives write
requests in passthrough mode then there is no parallel writes
possible.
* [cache.writeback](cache.md): FUSE's writeback caching is
incompatible with `passthrough`. If `cache.writeback=true` when
enabling `passthrough` it will be reset to `false`.
* [cache.files](cache.md): Must be enabled for `passthrough` to
incompatible with `passthrough.io`. If `cache.writeback=true` when
enabling `passthrough.io` it will be reset to `false`.
* [cache.files](cache.md): Must be enabled for `passthrough.io` to
work. When `cache.files=off` FUSE's `direct-io` mode is enabled
which overrides `passthrough.` Meaning `cache.files` should be set
to `partial`, `full` or `auto-full` to use `passthrough`. If
`cache.files` is set to `off` when using `passthrough` it will
which overrides `passthrough.io`. Meaning `cache.files` should be
set to `partial`, `full` or `auto-full` to use `passthrough.io`. If
`cache.files` is set to `off` when using `passthrough.io` it will
reset it to `auto-full`.
Technically `mergerfs` has the ability to choose options like
`passthrough`, `direct-io`, and page caching independently for every
file opened. However, at the moment there is no use case for picking
and choosing which to enable outside `cache.files=per-process` (which
is largely unnecessary on Linux v6.6 and above. See
`passthrough.io`, `direct-io`, and page caching independently for
every file opened. However, at the moment there is no use case for
picking and choosing which to enable outside `cache.files=per-process`
(which is largely unnecessary on Linux v6.6 and above. See
[direct-io-allow-mmap](options.md)) If such a use case arises please
[reach out to the author](../support.md) to discuss.
Unlike [preload.so](../tooling.md#preloadso), `passthrough` will work for
any software interacting with `mergerfs`. However, `passthrough`
requires Linux v6.9 or above to work.
Unlike [preload.so](../tooling.md#preloadso), `passthrough.io` will
work for any software interacting with `mergerfs`. However,
`passthrough.io` requires Linux v6.9 or above to work.
**NOTE:** This feature will **ONLY** work if `mergerfs` is running as
`root` as currently only `root` is allowed to leverage the kernel
feature.
**NOTE:** If a file has been opened and `passthrough` enabled, while
that file is open, if another open request is made `mergerfs` must
also enable `passthrough` for the second open request. This is a
limitation of how the passthrough feature works. Though there is no
known usecase where this is useful.
**NOTE:** If a file has been opened and `passthrough.io` enabled,
while that file is open, if another open request is made `mergerfs`
must also enable `passthrough.io` for the second open request. This is
a limitation of how the passthrough feature works.
**NOTE:** In order to add `passthrough` feature to `mergerfs` it was
necessary to remove the "feature" where mergerfs could open the same
file on different branches. Such as using `func.open=rand` and having
multiple files at the same relative path across different
**NOTE:** In order to add `passthrough.io` feature to `mergerfs` it
was necessary to remove the "feature" where mergerfs could open the
same file on different branches. Such as using `func.open=rand` and
having multiple files at the same relative path across different
branches. This "feature" was very very rarely used and it was
impossible to support `passthrough` without changing the behavior.
impossible to support `passthrough.io` without changing that behavior.
## Alternatives

8
mkdocs/docs/config/proxy-ioprio.md

@ -33,11 +33,11 @@ proxying may be added in a future release.
## Conflicts With Other Options
If using [IO passthrough](passthrough.md) there is no reason to set
this value to `true`. However, since `passthrough` leads to mergerfs
IO calls being bypassed entirely will have no impact on performance or
behavior regardless.
this value to `true`. However, since `passthrough.io` leads to
mergerfs IO calls being bypassed entirely will have no impact on
performance or behavior regardless.
When using `passthrough` the IO priority of the client app would be
When using `passthrough.io` the IO priority of the client app would be
used directly given the IO is passed through.

92
mkdocs/docs/faq/configuration_and_policies.md

@ -18,37 +18,71 @@ mergerfs [does not impact](usage_and_functionality.md) the underlying
filesystems and can be added or removed without any impact it is
extremely easy to test and experiment with different settings.
Additional reading:
* [Config/Options](../config/options.md)
## What create policy should I use?
If it isn't clear to you then you should use `pfrd` as described in
the [quickstart guide](../quickstart.md).
## Why is pfrd the default create policy?
Originally the default was `epmfs` however it was found to cause
significant confusion among new users who expected mergerfs to always
choose a branch with available space. For mergerfs v2.41.0 it was
decided to change the default to the less restrictive policy `pfrd`.
significant confusion among new users who assumed mergerfs to behave
like other software which always choose a branch with most available
space without any filtering or restrictions. The reason for using
`epmfs` was to allow people to use mergerfs without impacting the
layout of their data across the branches. Keeping the total entropy of
the system in check. This, however, was not understood by users who
did not read the documentation and caused significant support burdens.
For mergerfs v2.41.0 it was decided to change the default to `pfrd` to
minimize the support burden.
`pfrd` was chosen because it prioritizes placement to branches based
on free space (percentage wise) without overloading a specific branch
as `mfs`, `lus`, or other policies could when a singular branch has
significantly more free space. Since it is impossible to know the
usage and access patterns of the filesystem spreading out files across
the branches also spreads the load and therefore good for general use
cases making it a good default.
Additional reading:
* [functions, categories and policies](../config/functions_categories_policies.md)
## How can I ensure files are collocated on the same branch?
Many people like the idea of ensuring related files, such as all the
files to a TV show season or songs in an album, are stored on the same
storage device. However, most people have no actual need for this
storage device. However, most people have no technical need for this
behavior.
1. If you backup your data it is extremely likely your backup solution
can restore only those files you are missing.
2. Software such as **Sonarr** can manage the downloading and post
processing of bespoke episodes which may be missing in a
season. Either by downloading the episode individually if available
or by downloading a full season.
can restore only those files you are missing. There is no need to
store data in related units to ensure restoring data is easy.
2. Software such as [Sonarr](https://sonarr.tv) can manage the
downloading and post processing of bespoke files / episodes which
may be missing. Either by downloading the episode individually if
available or by downloading a full season.
3. There is no benefit to keeping files collocated with regard to
drive spinup, caching, or other secondary concern.
drive spinup, caching, or other secondary concern. In fact
depending on usage patterns placing the data on the same underlying
device could harm performance.
The main use case for wanting collocation is where the branch is going
to be removed from the pool and you wish to have all data from some
logical set on that device. Such as you intend to take a drive out of
the pool to take on a trip and want a whole show on the
drive. However, even in these situations you typically end up needing
to curate the files anyway because it has show A but not show B.
to be removed from the pool and used in isolation temporarily and you
wish to have all data from some logical set on that device. Such as
you intend to take a drive out of the pool to take on a trip and want
a whole show or artist's works on the drive. However, even in these
situations you typically end up needing to curate the files anyway
because it has show A but not show B. In such cases it is more
convient to use a separate devices and sync what you wish to it.
All that said you can accomplish collocation to varying degrees using
the following methods:
@ -56,33 +90,37 @@ the following methods:
1. Use
[mergerfs.consolidate](https://github.com/trapexit/mergerfs-tools/blob/master/src/mergerfs.consolidate)
when consolidation is needed.
2. Use a `msp` create policy.
2. Use a `msp` create policy to do a best effort collocation without
needing explicit directory curation.
3. Use `epmfs` or other `ep` create policy and manually create paths
on the branches directly.
4. Use a `ep` `create` policy and `rand` for `mkdir`.
on the branches directly as you need/desire.
4. Use a `ep` `create` policy and `pfrd` or `rand` for `mkdir`.
## How can I balance files across the pool?
Similar to collocation there is generally little reason to balance
files.
files across the pool branches.
1. Since prediction of a filesystem's death or loss of data is near
impossible there is little reason to balance in hopes of limiting
data loss.
1. Since prediction of a device's/filesystem's death or loss of data
is near impossible there is little reason to balance in hopes of
limiting data loss.
2. While performance could be impacted by having too much reading or
writing happen to singular underlying filesystems balancing won't
help unless you have the ability to manage the access patterns to
the pool.
3. Over time most configurations will lead to a random distribution of
files across the branches which is effectively "balancing."
3. Over time the churn of adding and removing files will lead to a
random distribution of files across the branches which is
effectively "balancing" if using create policies which do not favor
any particular branch.
If you wish to move files around or balance the pool you can:
All that said if you wish to move files around or balance the pool you
can:
1. Use `rand` or `pfrd` create policies and just use your system as
normal.
2. Write simple scripts using rsync or similar to move files around as
you wish.
2. Write scripts using rsync or similar to move files around as you
wish.
3. Use
[mergerfs.balance](https://github.com/trapexit/mergerfs-tools/blob/master/src/mergerfs.balance). Keep
in mind that this tool is really just an example of how to

9
mkdocs/docs/faq/recommendations_and_warnings.md

@ -2,6 +2,9 @@
## What should mergerfs NOT be used for?
* Situations where you need large amounts of contiguous space beyond
that available on any singular device. Such as putting 10GiB file on
2 6GiB filesystems.
* databases: Even if the database stored data in separate files
(mergerfs wouldn't offer much otherwise) the higher latency of the
indirection will really harm performance. If it is a lightly used
@ -13,9 +16,9 @@
should stick with RAID. However, it is fine to put a filesystem
which is on a RAID setup in mergerfs.
**However, if using [passthrough](../config/passthrough.md) the above
situations are less likely to be a concern. Best to do testing for
your specific use case.**
**However, if using [passthrough](../config/passthrough.md) the
performance related issues above are less likely to be a concern. Best
to do testing for your specific use case.**
## It's mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them?

2
mkdocs/docs/known_issues_bugs.md

@ -8,7 +8,7 @@
block to change credentials as required by numerous filesystem
functions. This impacts performance.
* FreeBSD's FUSE implementation is lacking many features of Linux.
* passthrough
* IO passthrough
* statx
* lazy umount
* oom_score_adj

10
mkdocs/docs/remote_filesystems.md

@ -76,19 +76,21 @@ but if you run into problems it may be worth trying Samba/SMB.
**mergerfs settings:**
* `noforget`
* `noforget` or `remember-nodes=SECONDS`
* `inodecalc=path-hash`
* `lazy-umount-mountpoint=false`
`noforget` is needed because NFS uses the `name_to_handle_at` and
`noforget` (or `remember-nodes` with a large value such as `86400` for
a full day) is needed because NFS uses the `name_to_handle_at` and
`open_by_handle_at` functions which allow a program to keep a
reference to a file without technically having it open in the typical
sense. The problem is that FUSE has no way to know that NFS has a
handle that it will later use to open the file again. As a result, it
is possible for the kernel to tell mergerfs to forget about the file
node and should NFS ever ask for that node's details in the future it
would have nothing to respond with. Keeping nodes around forever is
not ideal but at the moment the only way to manage the situation.
would have nothing to respond with. Keeping nodes around forever (or
however long is set with `remember-nodes`) is not ideal but at the
moment the only way to manage the situation.
`inodecalc=path-hash` is needed because NFS is sensitive to
out-of-band changes. FUSE doesn't care if a file's inode value changes

4
mkdocs/docs/tooling.md

@ -50,8 +50,8 @@ OPTIONS:
EXPERIMENTAL
For some time there has been work to enable
[passthrough](config/passthrough.md) IO in FUSE. Passthrough IO would
For some time there has been work to enable IO
[passthrough](config/passthrough.md) in FUSE. IO Passthrough would
allow for near native performance with regards to reads and writes (at
the expense of certain mergerfs features.) In Linux v6.9 that feature
made its way into the kernel however in a somewhat limited form which

4
mkdocs/mkdocs.yml

@ -108,9 +108,9 @@ nav:
- extended_usage_patterns.md
- FAQ:
- faq/why_isnt_it_working.md
- faq/reliability_and_scalability.md
- faq/usage_and_functionality.md
- faq/configuration_and_policies.md
- faq/usage_and_functionality.md
- faq/reliability_and_scalability.md
- faq/compatibility_and_integration.md
- faq/recommendations_and_warnings.md
- faq/technical_behavior_and_limitations.md

553
src/config.cpp

@ -39,213 +39,258 @@
#define MINFREESPACE_DEFAULT (4294967295ULL)
#define IFERT(S) if(S == s_) return true
constexpr static const char CACHE_FILES_PROCESS_NAMES_DEFAULT[] =
"rtorrent|"
"qbittorrent-nox";
Config cfg;
namespace l
Config::CfgConfigFile::CfgConfigFile()
{
static
bool
readonly(const std::string &s_)
{
IFERT("allow-idmap");
IFERT("async_read");
IFERT("branches-mount-timeout");
IFERT("branches-mount-timeout-fail");
IFERT("cache.symlinks");
IFERT("cache.writeback");
IFERT("direct-io-allow-mmap");
IFERT("export-support");
IFERT("fsname");
IFERT("fuse_msg_size");
IFERT("kernel-permissions-check");
IFERT("mount");
IFERT("nullrw");
IFERT("pid");
IFERT("pin-threads");
IFERT("process-thread-count");
IFERT("process-thread-queue-depth");
IFERT("read-thread-count");
IFERT("readdirplus");
IFERT("scheduling-priority");
IFERT("srcmounts");
IFERT("threads");
IFERT("version");
return false;
}
}
Config::Config()
: allow_idmap(false),
async_read(true),
auto_cache(false),
minfreespace(MINFREESPACE_DEFAULT),
branches(minfreespace),
branches_mount_timeout(0),
branches_mount_timeout_fail(false),
cache_attr(1),
cache_entry(1),
cache_files(CacheFiles::ENUM::OFF),
cache_files_process_names(CACHE_FILES_PROCESS_NAMES_DEFAULT),
cache_negative_entry(0),
cache_readdir(false),
cache_statfs(0),
cache_symlinks(false),
category(func),
direct_io(false),
direct_io_allow_mmap(true),
dropcacheonclose(false),
export_support(true),
flushonclose(FlushOnClose::ENUM::OPENED_FOR_WRITE),
follow_symlinks(FollowSymlinks::ENUM::NEVER),
fsname(),
func(),
fuse_msg_size("1M"),
gid_cache_expire_timeout(60 * 60),
gid_cache_remove_timeout(60 * 60 * 12),
ignorepponrename(false),
inodecalc("hybrid-hash"),
lazy_umount_mountpoint(false),
link_cow(false),
link_exdev(LinkEXDEV::ENUM::PASSTHROUGH),
log_metrics(false),
mountpoint(),
moveonenospc(true),
nfsopenhack(NFSOpenHack::ENUM::OFF),
nullrw(false),
parallel_direct_writes(true),
posix_acl(false),
proxy_ioprio(false),
readahead(0),
readdir("seq"),
readdirplus(false),
rename_exdev(RenameEXDEV::ENUM::PASSTHROUGH),
scheduling_priority(-10),
security_capability(true),
srcmounts(branches),
statfs(StatFS::ENUM::BASE),
statfs_ignore(StatFSIgnore::ENUM::NONE),
symlinkify(false),
symlinkify_timeout(3600),
fuse_read_thread_count(0),
fuse_process_thread_count(-1),
fuse_process_thread_queue_depth(2),
fuse_pin_threads("false"),
version(MERGERFS_VERSION),
writeback_cache(false),
xattr(XAttr::ENUM::PASSTHROUGH),
_initialized(false)
int
Config::CfgConfigFile::from_string(const std::string_view s_)
{
_map["allow-idmap"] = &allow_idmap;
_map["async_read"] = &async_read;
_map["auto_cache"] = &auto_cache;
_map["branches"] = &branches;
_map["branches-mount-timeout"] = &branches_mount_timeout;
_map["branches-mount-timeout-fail"] = &branches_mount_timeout_fail;
_map["cache.attr"] = &cache_attr;
_map["cache.entry"] = &cache_entry;
_map["cache.files"] = &cache_files;
_map["cache.files.process-names"] = &cache_files_process_names;
_map["cache.negative_entry"] = &cache_negative_entry;
_map["cache.readdir"] = &cache_readdir;
_map["cache.statfs"] = &cache_statfs;
_map["cache.symlinks"] = &cache_symlinks;
_map["cache.writeback"] = &writeback_cache;
_map["category.action"] = &category.action;
_map["category.create"] = &category.create;
_map["category.search"] = &category.search;
_map["direct_io"] = &direct_io;
_map["direct-io-allow-mmap"] = &direct_io_allow_mmap;
_map["dropcacheonclose"] = &dropcacheonclose;
_map["export-support"] = &export_support;
_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.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["fuse_msg_size"] = &fuse_msg_size;
_map["gid-cache-expire-timeout"] = &gid_cache_expire_timeout;
_map["gid-cache-remove-timeout"] = &gid_cache_remove_timeout;
_map["handle-killpriv"] = &handle_killpriv;
_map["handle-killpriv-v2"] = &handle_killpriv_v2;
_map["ignorepponrename"] = &ignorepponrename;
_map["inodecalc"] = &inodecalc;
_map["kernel_cache"] = &kernel_cache;
_map["kernel-permissions-check"] = &kernel_permissions_check;
_map["lazy-umount-mountpoint"] = &lazy_umount_mountpoint;
_map["link_cow"] = &link_cow;
_map["link-exdev"] = &link_exdev;
_map["log.metrics"] = &log_metrics;
_map["minfreespace"] = &minfreespace;
_map["mount"] = &mountpoint;
_map["moveonenospc"] = &moveonenospc;
_map["nfsopenhack"] = &nfsopenhack;
_map["nullrw"] = &nullrw;
_map["passthrough"] = &passthrough;
_map["pid"] = &pid;
_map["parallel-direct-writes"] = &parallel_direct_writes;
_map["pin-threads"] = &fuse_pin_threads;
_map["posix_acl"] = &posix_acl;
_map["proxy-ioprio"] = &proxy_ioprio;
_map["readahead"] = &readahead;
_map["readdirplus"] = &readdirplus;
_map["rename-exdev"] = &rename_exdev;
_map["scheduling-priority"] = &scheduling_priority;
_map["security_capability"] = &security_capability;
_map["srcmounts"] = &srcmounts;
_map["statfs"] = &statfs;
_map["statfs_ignore"] = &statfs_ignore;
_map["symlinkify"] = &symlinkify;
_map["symlinkify_timeout"] = &symlinkify_timeout;
_map["threads"] = &fuse_read_thread_count;
_map["read-thread-count"] = &fuse_read_thread_count;
_map["process-thread-count"] = &fuse_process_thread_count;
_map["process-thread-queue-depth"] = &fuse_process_thread_queue_depth;
_map["version"] = &version;
_map["xattr"] = &xattr;
int rv;
fs::path cfg_file;
if(_depth > 5)
return -ELOOP;
_depth++;
cfg_file = (s_.empty() ? _cfg_file : s_);
rv = cfg.from_file(cfg_file);
if(rv == 0)
_cfg_file = cfg_file;
_depth--;
return rv;
}
Config&
Config::operator=(const Config &cfg_)
std::string
Config::CfgConfigFile::to_string() const
{
int rv;
std::string val;
for(auto &kv : _map)
{
rv = cfg_.get(kv.first,&val);
if(rv)
continue;
return _cfg_file;
}
kv.second->from_string(val);
}
return *this;
Config::Config()
:
allow_idmap(false),
async_read(true),
branches(minfreespace),
branches_mount_timeout(0),
branches_mount_timeout_fail(false),
cache_attr(1),
cache_entry(1),
cache_files(CacheFiles::ENUM::OFF),
cache_files_process_names(CACHE_FILES_PROCESS_NAMES_DEFAULT),
cache_negative_entry(0),
cache_readdir(false),
cache_statfs(0),
cache_symlinks(false),
cache_writeback(false),
category(func),
config_file(),
congestion_threshold(fuse_cfg.congestion_threshold,0),
debug(fuse_cfg.debug,false),
direct_io_allow_mmap(true),
dropcacheonclose(false),
export_support(true),
flushonclose(FlushOnClose::ENUM::OPENED_FOR_WRITE),
follow_symlinks(FollowSymlinks::ENUM::NEVER),
fsname(),
func(),
fuse_msg_size("1M"),
gid(fuse_cfg.gid,FUSE_CFG_INVALID_ID),
gid_cache_expire_timeout(60 * 60),
gid_cache_remove_timeout(60 * 60 * 12),
handle_killpriv(true),
handle_killpriv_v2(true),
ignorepponrename(false),
inodecalc("hybrid-hash"),
kernel_permissions_check(true),
lazy_umount_mountpoint(false),
link_cow(false),
link_exdev(LinkEXDEV::ENUM::PASSTHROUGH),
log_metrics(false),
max_background(fuse_cfg.max_background,0),
minfreespace(MINFREESPACE_DEFAULT),
mountpoint(),
moveonenospc(true),
nfsopenhack(NFSOpenHack::ENUM::OFF),
noforget(),
nullrw(false),
parallel_direct_writes(true),
passthrough_io(PassthroughIO::ENUM::OFF),
passthrough_max_stack_depth(fuse_cfg.passthrough_max_stack_depth,1),
pin_threads(fuse_cfg.pin_threads,"false"),
posix_acl(false),
process_thread_count(fuse_cfg.process_thread_count,-1),
process_thread_queue_depth(fuse_cfg.process_thread_queue_depth,2),
proxy_ioprio(false),
read_thread_count(fuse_cfg.read_thread_count,0),
readahead(0),
readdir("seq"),
remember_nodes(fuse_cfg.remember_nodes,0),
rename_exdev(RenameEXDEV::ENUM::PASSTHROUGH),
scheduling_priority(-10),
security_capability(true),
srcmounts(branches),
statfs(StatFS::ENUM::BASE),
statfs_ignore(StatFSIgnore::ENUM::NONE),
symlinkify(false),
symlinkify_timeout(3600),
threads(fuse_cfg.read_thread_count),
uid(fuse_cfg.uid,FUSE_CFG_INVALID_ID),
umask(fuse_cfg.umask,FUSE_CFG_INVALID_UMASK),
version(MERGERFS_VERSION),
xattr(XAttr::ENUM::PASSTHROUGH),
_initialized(false)
{
allow_idmap.ro =
async_read.ro =
branches_mount_timeout.ro =
branches_mount_timeout_fail.ro =
cache_symlinks.ro =
cache_writeback.ro =
congestion_threshold.ro =
direct_io_allow_mmap.ro =
export_support.ro =
fsname.ro =
fuse_msg_size.ro =
handle_killpriv.ro =
handle_killpriv_v2.ro =
kernel_permissions_check.ro =
max_background.ro =
mountpoint.ro =
nullrw.ro =
pid.ro =
pin_threads.ro =
process_thread_count.ro =
process_thread_queue_depth.ro =
read_thread_count.ro =
scheduling_priority.ro =
srcmounts.ro =
version.ro =
true;
congestion_threshold.ro =
gid.display =
max_background.ro =
threads.display =
uid.display =
umask.display =
false;
_map["allow-idmap"] = &allow_idmap;
_map["async-read"] = &async_read;
_map["atomic-o-trunc"] = &_dummy;
_map["auto-cache"] = &_dummy;
_map["big-writes"] = &_dummy;
_map["branches"] = &branches;
_map["branches-mount-timeout"] = &branches_mount_timeout;
_map["branches-mount-timeout-fail"] = &branches_mount_timeout_fail;
_map["cache.attr"] = &cache_attr;
_map["cache.entry"] = &cache_entry;
_map["cache.files"] = &cache_files;
_map["cache.files.process-names"] = &cache_files_process_names;
_map["cache.negative-entry"] = &cache_negative_entry;
_map["cache.open"] = &_dummy;
_map["cache.readdir"] = &cache_readdir;
_map["cache.statfs"] = &cache_statfs;
_map["cache.symlinks"] = &cache_symlinks;
_map["cache.writeback"] = &cache_writeback;
_map["category.action"] = &category.action;
_map["category.create"] = &category.create;
_map["category.search"] = &category.search;
_map["config"] = &config_file;
_map["debug"] = &debug;
_map["direct-io-allow-mmap"] = &direct_io_allow_mmap;
_map["direct-io"] = &_dummy;
_map["dropcacheonclose"] = &dropcacheonclose;
_map["export-support"] = &export_support;
_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.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["fuse_msg_size"] = &fuse_msg_size;
_map["gid"] = &gid;
_map["gid-cache.expire-timeout"] = &gid_cache_expire_timeout;
_map["gid-cache.remove-timeout"] = &gid_cache_remove_timeout;
_map["handle-killpriv"] = &handle_killpriv;
_map["handle-killpriv-v2"] = &handle_killpriv_v2;
_map["hard-remove"] = &_dummy;
_map["ignorepponrename"] = &ignorepponrename;
_map["inodecalc"] = &inodecalc;
_map["kernel-permissions-check"] = &kernel_permissions_check;
_map["kernel-cache"] = &_dummy;
_map["lazy-umount-mountpoint"] = &lazy_umount_mountpoint;
_map["link-exdev"] = &link_exdev;
_map["link-cow"] = &link_cow;
_map["log.metrics"] = &log_metrics;
_map["minfreespace"] = &minfreespace;
_map["mount"] = &mountpoint;
_map["moveonenospc"] = &moveonenospc;
_map["negative-entry"] = &_dummy;
_map["nfsopenhack"] = &nfsopenhack;
_map["no-splice-move"] = &_dummy;
_map["no-splice-read"] = &_dummy;
_map["no-splice-write"] = &_dummy;
_map["noforget"] = &noforget;
_map["nonempty"] = &_dummy;
_map["nullrw"] = &nullrw;
_map["parallel-direct-writes"] = &parallel_direct_writes;
_map["passthrough.io"] = &passthrough_io;
_map["passthrough.max-stack-depth"] = &passthrough_max_stack_depth;
_map["pid"] = &pid;
_map["pin-threads"] = &pin_threads;
_map["posix_acl"] = &posix_acl;
_map["process-thread-count"] = &process_thread_count;
_map["process-thread-queue-depth"] = &process_thread_queue_depth;
_map["proxy-ioprio"] = &proxy_ioprio;
_map["read-thread-count"] = &read_thread_count;
_map["readahead"] = &readahead;
_map["remember-nodes"] = &remember_nodes;
_map["rename-exdev"] = &rename_exdev;
_map["scheduling-priority"] = &scheduling_priority;
_map["security-capability"] = &security_capability;
_map["splice-move"] = &_dummy;
_map["splice-read"] = &_dummy;
_map["splice-write"] = &_dummy;
_map["srcmounts"] = &srcmounts;
_map["statfs"] = &statfs;
_map["statfs-ignore"] = &statfs_ignore;
_map["symlinkify"] = &symlinkify;
_map["symlinkify-timeout"] = &symlinkify_timeout;
_map["threads"] = &threads;
_map["uid"] = &uid;
_map["umask"] = &umask;
_map["use-ino"] = &_dummy;
_map["version"] = &version;
_map["xattr"] = &xattr;
}
bool
@ -275,6 +320,9 @@ Config::keys_xattr(std::string &s_) const
s_.reserve(1024);
for(const auto &[key,val] : _map)
{
if(val->display == false)
continue;
s_ += "user.mergerfs.";
s_ += key;
s_ += '\0';
@ -289,6 +337,9 @@ Config::keys_listxattr_size() const
rv = 0;
for(const auto &[key,val] : _map)
{
if(val->display == false)
continue;
rv += sizeof("user.mergerfs.");
rv += key.size();
}
@ -308,6 +359,9 @@ Config::keys_listxattr(char *list_,
for(const auto &[key,val] : _map)
{
if(val->display == false)
continue;
auto rv = fmt::format_to_n(list,size,
"user.mergerfs.{}\0",
key);
@ -324,9 +378,12 @@ int
Config::get(const std::string &key_,
std::string *val_) const
{
std::string key;
Str2TFStrMap::const_iterator i;
i = _map.find(key_);
key = str::replace(key_,'_','-');
i = _map.find(key);
if(i == _map.end())
return -ENOATTR;
@ -336,26 +393,21 @@ Config::get(const std::string &key_,
}
int
Config::set_raw(const std::string &key_,
const std::string &value_)
Config::set(const std::string &key_,
const std::string &value_)
{
std::string key;
Str2TFStrMap::iterator i;
i = _map.find(key_);
key = str::replace(key_,'_','-');
i = _map.find(key);
if(i == _map.end())
return -ENOATTR;
return i->second->from_string(value_);
}
int
Config::set(const std::string &key_,
const std::string &value_)
{
if(_initialized && l::readonly(key_))
if(_initialized && i->second->ro)
return -EROFS;
return set_raw(key_,value_);
return i->second->from_string(value_);
}
int
@ -372,16 +424,13 @@ Config::set(const std::string &kv_)
}
int
Config::from_stream(std::istream &istrm_,
ErrVec *errs_)
Config::from_stream(std::istream &istrm_)
{
int rv;
std::string line;
std::string key;
std::string val;
Config newcfg;
newcfg = *this;
Config::ErrVec new_errs;
while(std::getline(istrm_,line,'\n'))
{
@ -393,22 +442,21 @@ Config::from_stream(std::istream &istrm_,
key = str::trim(key);
val = str::trim(val);
rv = newcfg.set(key,val);
rv = set(key,val);
if(rv < 0)
errs_->push_back({rv,key});
new_errs.push_back({-rv,key+'='+val});
}
if(!errs_->empty())
return -EINVAL;
*this = newcfg;
rv = (new_errs.empty() ? 0 : -EINVAL);
errs.insert(errs.end(),
new_errs.begin(),
new_errs.end());
return 0;
return rv;
}
int
Config::from_file(const std::string &filepath_,
ErrVec *errs_)
Config::from_file(const std::string &filepath_)
{
int rv;
std::ifstream ifstrm;
@ -416,11 +464,11 @@ Config::from_file(const std::string &filepath_,
ifstrm.open(filepath_);
if(!ifstrm.good())
{
errs_->push_back({-errno,filepath_});
errs.push_back({errno,filepath_});
return -errno;
}
rv = from_stream(ifstrm,errs_);
rv = from_stream(ifstrm);
ifstrm.close();
@ -477,52 +525,31 @@ Config::prune_cmd_xattr(const std::string_view &s_)
return {};
}
std::ostream&
operator<<(std::ostream &os_,
const Config &c_)
{
for(const auto &[key,val] : c_._map)
os_ << key << '=' << val->to_string() << std::endl;
return os_;
}
static
std::string
err2str(const int err_)
Config::Err::to_string() const
{
switch(err_)
std::string s;
switch(err)
{
case 0:
return std::string();
case -EINVAL:
return "invalid value";
case -ENOATTR:
return "unknown option";
case -EROFS:
return "read-only option";
break;
case EINVAL:
s = "invalid value";
break;
case ENOATTR:
s = "unknown option";
break;
case EROFS:
s = "read-only option";
break;
case ELOOP:
s = "too many levels of config";
break;
default:
return strerror(-err_);
}
return std::string();
}
std::ostream&
operator<<(std::ostream &os_,
const Config::ErrVec &ev_)
{
std::string errstr;
for(auto &err : ev_)
{
os_ << "* ERROR: ";
errstr = err2str(err.err);
if(!errstr.empty())
os_ << errstr << " - ";
os_ << err.str << std::endl;
s = strerror(err);
break;
}
return os_;
return fmt::format("{} - {}",s,str);
}

91
src/config.hpp

@ -19,6 +19,7 @@
#include "branches.hpp"
#include "category.hpp"
#include "config_cachefiles.hpp"
#include "config_dummy.hpp"
#include "config_flushonclose.hpp"
#include "config_follow_symlinks.hpp"
#include "config_gidcache.hpp"
@ -27,8 +28,9 @@
#include "config_log_metrics.hpp"
#include "config_moveonenospc.hpp"
#include "config_nfsopenhack.hpp"
#include "config_noforget.hpp"
#include "config_pagesize.hpp"
#include "config_passthrough.hpp"
#include "config_passthrough_io.hpp"
#include "config_pid.hpp"
#include "config_proxy_ioprio.hpp"
#include "config_rename_exdev.hpp"
@ -40,10 +42,13 @@
#include "errno.hpp"
#include "fs_path.hpp"
#include "funcs.hpp"
#include "fuse_cfg.hpp"
#include "fuse_readdir.hpp"
#include "policy.hpp"
#include "rwlock.hpp"
#include "tofrom_ref.hpp"
#include "tofrom_wrapper.hpp"
#include "syslog.hpp"
#include "fuse.h"
@ -55,13 +60,14 @@
#include <sys/stat.h>
typedef ToFromWrapper<bool> ConfigBOOL;
typedef ToFromWrapper<uint64_t> ConfigUINT64;
typedef ToFromWrapper<int64_t> ConfigS64;
typedef ToFromWrapper<int> ConfigINT;
typedef ToFromWrapper<std::string> ConfigSTR;
typedef ToFromWrapper<fs::path> ConfigPath;
typedef std::map<std::string,ToFromString*> Str2TFStrMap;
typedef ToFromWrapper<bool> ConfigBOOL;
typedef ToFromWrapper<uint64_t> ConfigUINT64;
typedef ToFromWrapper<int64_t> ConfigS64;
typedef ToFromWrapper<int> ConfigINT;
typedef ToFromWrapper<std::string> ConfigSTR;
typedef ToFromWrapper<fs::path> ConfigPath;
typedef std::map<std::string,ToFromString*> Str2TFStrMap;
typedef ROToFromWrapper<std::string> ConfigROSTR;
extern const std::string CONTROLFILE;
@ -72,10 +78,30 @@ public:
{
int err;
std::string str;
std::string to_string() const;
};
typedef std::vector<Err> ErrVec;
public:
ErrVec errs;
public:
class CfgConfigFile : public ToFromString
{
private:
fs::path _cfg_file;
int _depth = 0;
public:
CfgConfigFile();
public:
int from_string(const std::string_view s_) final;
std::string to_string(void) const final;
};
public:
Config();
@ -85,8 +111,6 @@ public:
public:
ConfigBOOL allow_idmap;
ConfigBOOL async_read;
ConfigBOOL auto_cache;
ConfigUINT64 minfreespace;
Branches branches;
ConfigUINT64 branches_mount_timeout;
ConfigBOOL branches_mount_timeout_fail;
@ -98,8 +122,11 @@ public:
ConfigBOOL cache_readdir;
ConfigUINT64 cache_statfs;
ConfigBOOL cache_symlinks;
ConfigBOOL cache_writeback;
Categories category;
ConfigBOOL direct_io;
CfgConfigFile config_file;
TFSRef<int> congestion_threshold;
TFSRef<bool> debug;
ConfigBOOL direct_io_allow_mmap;
ConfigBOOL dropcacheonclose;
ConfigBOOL export_support;
@ -108,30 +135,38 @@ public:
ConfigSTR fsname;
Funcs func;
ConfigPageSize fuse_msg_size;
TFSRef<s64> gid;
GIDCacheExpireTimeout gid_cache_expire_timeout;
GIDCacheRemoveTimeout gid_cache_remove_timeout;
ConfigBOOL handle_killpriv = true;
ConfigBOOL handle_killpriv_v2 = true;
ConfigBOOL handle_killpriv;
ConfigBOOL handle_killpriv_v2;
ConfigBOOL ignorepponrename;
InodeCalc inodecalc;
ConfigBOOL kernel_cache;
ConfigBOOL kernel_permissions_check = true;
ConfigBOOL kernel_permissions_check;
ConfigBOOL lazy_umount_mountpoint;
ConfigBOOL link_cow;
LinkEXDEV link_exdev;
LogMetrics log_metrics;
TFSRef<int> max_background;
ConfigUINT64 minfreespace;
ConfigPath mountpoint;
MoveOnENOSPC moveonenospc;
NFSOpenHack nfsopenhack;
CfgNoforget noforget;
ConfigBOOL nullrw;
Passthrough passthrough = Passthrough::ENUM::OFF;
ConfigBOOL parallel_direct_writes;
PassthroughIO passthrough_io;
TFSRef<int> passthrough_max_stack_depth;
ConfigGetPid pid;
TFSRef<std::string> pin_threads;
ConfigBOOL posix_acl;
TFSRef<int> process_thread_count;
TFSRef<int> process_thread_queue_depth;
ProxyIOPrio proxy_ioprio;
TFSRef<int> read_thread_count;
ConfigUINT64 readahead;
FUSE::ReadDir readdir;
ConfigBOOL readdirplus;
TFSRef<s64> remember_nodes;
RenameEXDEV rename_exdev;
ConfigINT scheduling_priority;
ConfigBOOL security_capability;
@ -140,14 +175,15 @@ public:
StatFSIgnore statfs_ignore;
ConfigBOOL symlinkify;
ConfigS64 symlinkify_timeout;
ConfigINT fuse_read_thread_count;
ConfigINT fuse_process_thread_count;
ConfigINT fuse_process_thread_queue_depth;
ConfigSTR fuse_pin_threads;
ConfigSTR version;
ConfigBOOL writeback_cache;
TFSRef<int> threads;
TFSRef<s64> uid;
TFSRef<s64> umask;
ConfigROSTR version;
XAttr xattr;
private:
CfgDummy _dummy;
private:
bool _initialized;
@ -167,13 +203,12 @@ public:
public:
int get(const std::string &key, std::string *val) const;
int set_raw(const std::string &key, const std::string &val);
int set(const std::string &key, const std::string &val);
int set(const std::string &kv);
public:
int from_stream(std::istream &istrm, ErrVec *errs);
int from_file(const std::string &filepath, ErrVec *errs);
int from_stream(std::istream &istrm);
int from_file(const std::string &filepath);
public:
static bool is_rootdir(const fs::path &fusepath);
@ -185,10 +220,6 @@ public:
private:
Str2TFStrMap _map;
public:
friend class Read;
friend class Write;
};
std::ostream& operator<<(std::ostream &s,const Config::ErrVec &ev);

6
src/config_cachefiles.cpp

@ -26,8 +26,6 @@ CacheFiles::to_string() const
{
switch(_data)
{
case CacheFiles::ENUM::LIBFUSE:
return "libfuse";
case CacheFiles::ENUM::OFF:
return "off";
case CacheFiles::ENUM::PARTIAL:
@ -47,9 +45,7 @@ template<>
int
CacheFiles::from_string(const std::string_view s_)
{
if(s_ == "libfuse")
_data = CacheFiles::ENUM::LIBFUSE;
ef(s_ == "off")
if(s_ == "off")
_data = CacheFiles::ENUM::OFF;
ef(s_ == "partial")
_data = CacheFiles::ENUM::PARTIAL;

1
src/config_cachefiles.hpp

@ -23,7 +23,6 @@
enum class CacheFilesEnum
{
LIBFUSE,
OFF,
PARTIAL,
FULL,

24
src/config_dummy.hpp

@ -0,0 +1,24 @@
#pragma once
#include "tofrom_string.hpp"
class CfgDummy : public ToFromString
{
public:
CfgDummy()
{
display = false;
}
std::string
to_string() const
{
return {};
}
int
from_string(const std::string_view)
{
return 0;
}
};

40
src/config_noforget.hpp

@ -0,0 +1,40 @@
#pragma once
#include "tofrom_string.hpp"
#include "from_string.hpp"
#include "fuse_cfg.hpp"
class CfgNoforget : public ToFromString
{
public:
int
from_string(const std::string_view s_)
{
int rv;
bool b = false;
rv = str::from(s_,&b);
if((b == true) || s_.empty())
{
fuse_cfg.remember_nodes = -1;
return 0;
}
if(rv)
return rv;
fuse_cfg.remember_nodes = 0;
return 0;
}
std::string
to_string() const
{
if(fuse_cfg.remember_nodes == -1)
return "true";
return "false";
}
};

22
src/config_passthrough.cpp → src/config_passthrough_io.cpp

@ -16,23 +16,23 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config_passthrough.hpp"
#include "config_passthrough_io.hpp"
#include "ef.hpp"
#include "errno.hpp"
template<>
std::string
Passthrough::to_string() const
PassthroughIO::to_string() const
{
switch(_data)
{
case Passthrough::ENUM::OFF:
case PassthroughIO::ENUM::OFF:
return "off";
case Passthrough::ENUM::RO:
case PassthroughIO::ENUM::RO:
return "ro";
case Passthrough::ENUM::WO:
case PassthroughIO::ENUM::WO:
return "wo";
case Passthrough::ENUM::RW:
case PassthroughIO::ENUM::RW:
return "rw";
}
@ -41,16 +41,16 @@ Passthrough::to_string() const
template<>
int
Passthrough::from_string(const std::string_view s_)
PassthroughIO::from_string(const std::string_view s_)
{
if(s_ == "off")
_data = Passthrough::ENUM::OFF;
_data = PassthroughIO::ENUM::OFF;
ef(s_ == "ro")
_data = Passthrough::ENUM::RO;
_data = PassthroughIO::ENUM::RO;
ef(s_ == "wo")
_data = Passthrough::ENUM::WO;
_data = PassthroughIO::ENUM::WO;
ef(s_ == "rw")
_data = Passthrough::ENUM::RW;
_data = PassthroughIO::ENUM::RW;
else
return -EINVAL;

4
src/config_passthrough.hpp → src/config_passthrough_io.hpp

@ -21,7 +21,7 @@
#include "enum.hpp"
enum class PassthroughEnum
enum class PassthroughIOEnum
{
OFF = 0,
RO,
@ -29,4 +29,4 @@ enum class PassthroughEnum
RW
};
typedef Enum<PassthroughEnum> Passthrough;
typedef Enum<PassthroughIOEnum> PassthroughIO;

27
src/fuse_create.cpp

@ -46,7 +46,7 @@
*/
static
void
_tweak_flags_writeback_cache(int *flags_)
_tweak_flags_cache_writeback(int *flags_)
{
if((*flags_ & O_ACCMODE) == O_WRONLY)
*flags_ = ((*flags_ & ~O_ACCMODE) | O_RDWR);
@ -87,11 +87,6 @@ _config_to_ffi_flags(Config &cfg_,
{
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;
@ -215,8 +210,8 @@ _create(const Policy::Search &searchFunc_,
constexpr
const
uint64_t
_(const PassthroughEnum e_,
const uint64_t m_)
_(const PassthroughIOEnum e_,
const uint64_t m_)
{
return ((((uint64_t)e_) << 32) | (m_ & O_ACCMODE));
}
@ -234,8 +229,8 @@ _create_for_insert_lambda(const fuse_req_ctx_t *ctx_,
const ugid::Set ugid(ctx_->uid,ctx_->gid);
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
if(cfg.writeback_cache)
::_tweak_flags_writeback_cache(&ffi_->flags);
if(cfg.cache_writeback)
::_tweak_flags_cache_writeback(&ffi_->flags);
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,
ffi_->flags);
@ -266,13 +261,13 @@ _create_for_insert_lambda(const fuse_req_ctx_t *ctx_,
of_->ref_count = 1;
of_->fi = fi;
switch(_(cfg.passthrough,ffi_->flags))
switch(_(cfg.passthrough_io,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):
case _(PassthroughIO::ENUM::RO,O_RDONLY):
case _(PassthroughIO::ENUM::WO,O_WRONLY):
case _(PassthroughIO::ENUM::RW,O_RDONLY):
case _(PassthroughIO::ENUM::RW,O_WRONLY):
case _(PassthroughIO::ENUM::RW,O_RDWR):
break;
default:
return 0;

19
src/fuse_init.cpp

@ -131,7 +131,7 @@ _want_if_capable_max_pages(fuse_conn_info *conn_,
if(::_capable(conn_,FUSE_CAP_MAX_PAGES))
{
::_want(conn_,FUSE_CAP_MAX_PAGES);
conn_->max_pages = cfg_.fuse_msg_size;
fuse_cfg.max_pages = cfg_.fuse_msg_size;
SysLog::info("requesting max pages size of {}",
(uint64_t)cfg_.fuse_msg_size);
}
@ -204,8 +204,8 @@ FUSE::init(fuse_conn_info *conn_)
::_want_if_capable(conn_,FUSE_CAP_PARALLEL_DIROPS);
::_want_if_capable(conn_,FUSE_CAP_PASSTHROUGH);
::_want_if_capable(conn_,FUSE_CAP_POSIX_ACL,&cfg.posix_acl);
::_want_if_capable(conn_,FUSE_CAP_READDIR_PLUS,&cfg.readdirplus);
::_want_if_capable(conn_,FUSE_CAP_WRITEBACK_CACHE,&cfg.writeback_cache);
// ::_want_if_capable(conn_,FUSE_CAP_READDIR_PLUS,&cfg.readdirplus);
::_want_if_capable(conn_,FUSE_CAP_WRITEBACK_CACHE,&cfg.cache_writeback);
::_want_if_capable(conn_,FUSE_CAP_ALLOW_IDMAP,&cfg.allow_idmap);
// ::_want_if_capable(conn_,FUSE_CAP_READDIR_PLUS_AUTO);
::_want_if_capable_max_pages(conn_,cfg);
@ -214,21 +214,22 @@ FUSE::init(fuse_conn_info *conn_)
::_spawn_thread_to_set_readahead();
if(!(conn_->capable & FUSE_CAP_PASSTHROUGH) && (cfg.passthrough != Passthrough::ENUM::OFF))
if(!(conn_->capable & FUSE_CAP_PASSTHROUGH) &&
(cfg.passthrough_io != PassthroughIO::ENUM::OFF))
{
SysLog::warning("passthrough enabled but not supported by kernel. disabling.");
cfg.passthrough = Passthrough::ENUM::OFF;
cfg.passthrough_io = PassthroughIO::ENUM::OFF;
}
if((cfg.passthrough != Passthrough::ENUM::OFF) &&
(cfg.cache_files == CacheFiles::ENUM::OFF))
if((cfg.passthrough_io != PassthroughIO::ENUM::OFF) &&
(cfg.cache_files == CacheFiles::ENUM::OFF))
{
SysLog::warning("passthrough enabled and cache.files disabled:"
" passthrough will not function");
}
if((cfg.passthrough != Passthrough::ENUM::OFF) &&
(cfg.writeback_cache == true))
if((cfg.passthrough_io != PassthroughIO::ENUM::OFF) &&
(cfg.cache_writeback == true))
{
SysLog::warning("passthrough and cache.writeback are incompatible.");
}

31
src/fuse_open.cpp

@ -111,7 +111,7 @@ _nfsopenhack(const fs::path &fullpath_,
*/
static
void
_tweak_flags_writeback_cache(int *flags_)
_tweak_flags_cache_writeback(int *flags_)
{
if((*flags_ & O_ACCMODE) == O_WRONLY)
*flags_ = ((*flags_ & ~O_ACCMODE) | O_RDWR);
@ -145,11 +145,6 @@ _config_to_ffi_flags(Config &cfg_,
{
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;
@ -272,8 +267,8 @@ _open(const Policy::Search &searchFunc_,
constexpr
const
uint64_t
_(const PassthroughEnum e_,
const uint64_t m_)
_(const PassthroughIOEnum e_,
const uint64_t m_)
{
return ((((uint64_t)e_) << 32) | (m_ & O_ACCMODE));
}
@ -291,8 +286,8 @@ _open_for_insert_lambda(const fuse_req_ctx_t *ctx_,
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
if(cfg.writeback_cache)
::_tweak_flags_writeback_cache(&ffi_->flags);
if(cfg.cache_writeback)
::_tweak_flags_cache_writeback(&ffi_->flags);
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,
ffi_->flags);
@ -312,13 +307,13 @@ _open_for_insert_lambda(const fuse_req_ctx_t *ctx_,
of_->ref_count = 1;
of_->fi = fi;
switch(_(cfg.passthrough,ffi_->flags))
switch(_(cfg.passthrough_io,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):
case _(PassthroughIO::ENUM::RO,O_RDONLY):
case _(PassthroughIO::ENUM::WO,O_WRONLY):
case _(PassthroughIO::ENUM::RW,O_RDONLY):
case _(PassthroughIO::ENUM::RW,O_WRONLY):
case _(PassthroughIO::ENUM::RW,O_RDWR):
break;
default:
return 0;
@ -347,8 +342,8 @@ _open_for_update_lambda(const fuse_req_ctx_t *ctx_,
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
if(cfg.writeback_cache)
::_tweak_flags_writeback_cache(&ffi_->flags);
if(cfg.cache_writeback)
::_tweak_flags_cache_writeback(&ffi_->flags);
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,
ffi_->flags);

482
src/mergerfs.cpp

@ -18,6 +18,7 @@
#include "mergerfs_fsck.hpp"
#include "mergerfs_collect_info.hpp"
#include "config.hpp"
#include "fs_path.hpp"
#include "fs_readahead.hpp"
#include "fs_umount2.hpp"
@ -89,272 +90,279 @@
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <unordered_set>
namespace l
static
void
_get_fuse_operations(struct fuse_operations &ops_,
const bool nullrw_)
{
static
void
get_fuse_operations(struct fuse_operations &ops_,
const bool nullrw_)
{
ops_.access = FUSE::access;
ops_.bmap = FUSE::bmap;
ops_.chmod = FUSE::chmod;
ops_.chown = FUSE::chown;
ops_.copy_file_range = FUSE::copy_file_range;
ops_.create = FUSE::create;
ops_.destroy = FUSE::destroy;
ops_.fallocate = FUSE::fallocate;
ops_.fchmod = FUSE::fchmod;
ops_.fchown = FUSE::fchown;
ops_.fgetattr = FUSE::fgetattr;
ops_.flock = FUSE::flock;
ops_.flush = FUSE::flush;
ops_.fsync = FUSE::fsync;
ops_.fsyncdir = FUSE::fsyncdir;
ops_.ftruncate = FUSE::ftruncate;
ops_.futimens = FUSE::futimens;
ops_.getattr = FUSE::getattr;
ops_.getxattr = FUSE::getxattr;
ops_.init = FUSE::init;
ops_.ioctl = FUSE::ioctl;
ops_.link = FUSE::link;
ops_.listxattr = FUSE::listxattr;
ops_.lock = FUSE::lock;
ops_.mkdir = FUSE::mkdir;
ops_.mknod = FUSE::mknod;
ops_.open = FUSE::open;
ops_.opendir = FUSE::opendir;
ops_.poll = FUSE::poll;;
ops_.read = (nullrw_ ? FUSE::read_null : FUSE::read);
ops_.readdir = FUSE::readdir;
ops_.readdir_plus = FUSE::readdir_plus;
ops_.readlink = FUSE::readlink;
ops_.release = FUSE::release;
ops_.releasedir = FUSE::releasedir;
ops_.removemapping = FUSE::removemapping;
ops_.removexattr = FUSE::removexattr;
ops_.rename = FUSE::rename;
ops_.rmdir = FUSE::rmdir;
ops_.setupmapping = FUSE::setupmapping;
ops_.setxattr = FUSE::setxattr;
ops_.statfs = FUSE::statfs;
ops_.statx = FUSE::statx;
ops_.statx_fh = FUSE::statx_fh;
ops_.symlink = FUSE::symlink;
ops_.syncfs = FUSE::syncfs;
ops_.tmpfile = FUSE::tmpfile;
ops_.truncate = FUSE::truncate;
ops_.unlink = FUSE::unlink;
ops_.utimens = FUSE::utimens;
ops_.write = (nullrw_ ? FUSE::write_null : FUSE::write);
ops_.access = FUSE::access;
ops_.bmap = FUSE::bmap;
ops_.chmod = FUSE::chmod;
ops_.chown = FUSE::chown;
ops_.copy_file_range = FUSE::copy_file_range;
ops_.create = FUSE::create;
ops_.destroy = FUSE::destroy;
ops_.fallocate = FUSE::fallocate;
ops_.fchmod = FUSE::fchmod;
ops_.fchown = FUSE::fchown;
ops_.fgetattr = FUSE::fgetattr;
ops_.flock = FUSE::flock;
ops_.flush = FUSE::flush;
ops_.fsync = FUSE::fsync;
ops_.fsyncdir = FUSE::fsyncdir;
ops_.ftruncate = FUSE::ftruncate;
ops_.futimens = FUSE::futimens;
ops_.getattr = FUSE::getattr;
ops_.getxattr = FUSE::getxattr;
ops_.init = FUSE::init;
ops_.ioctl = FUSE::ioctl;
ops_.link = FUSE::link;
ops_.listxattr = FUSE::listxattr;
ops_.lock = FUSE::lock;
ops_.mkdir = FUSE::mkdir;
ops_.mknod = FUSE::mknod;
ops_.open = FUSE::open;
ops_.opendir = FUSE::opendir;
ops_.poll = FUSE::poll;;
ops_.read = (nullrw_ ? FUSE::read_null : FUSE::read);
ops_.readdir = FUSE::readdir;
ops_.readdir_plus = FUSE::readdir_plus;
ops_.readlink = FUSE::readlink;
ops_.release = FUSE::release;
ops_.releasedir = FUSE::releasedir;
ops_.removemapping = FUSE::removemapping;
ops_.removexattr = FUSE::removexattr;
ops_.rename = FUSE::rename;
ops_.rmdir = FUSE::rmdir;
ops_.setupmapping = FUSE::setupmapping;
ops_.setxattr = FUSE::setxattr;
ops_.statfs = FUSE::statfs;
ops_.statx = FUSE::statx;
ops_.statx_fh = FUSE::statx_fh;
ops_.symlink = FUSE::symlink;
ops_.syncfs = FUSE::syncfs;
ops_.tmpfile = FUSE::tmpfile;
ops_.truncate = FUSE::truncate;
ops_.unlink = FUSE::unlink;
ops_.utimens = FUSE::utimens;
ops_.write = (nullrw_ ? FUSE::write_null : FUSE::write);
return;
}
static
void
_setup_resources(const int scheduling_priority_)
{
std::srand(time(NULL));
resources::reset_umask();
resources::maxout_rlimit_nofile();
resources::maxout_rlimit_fsize();
resources::setpriority(scheduling_priority_);
}
static
void
_set_oom_score_adj()
{
int rv;
int orig;
int score;
if(!oom::has_oom_score_adj())
return;
}
static
void
setup_resources(const int scheduling_priority_)
{
std::srand(time(NULL));
resources::reset_umask();
resources::maxout_rlimit_nofile();
resources::maxout_rlimit_fsize();
resources::setpriority(scheduling_priority_);
}
static
void
set_oom_score_adj()
{
int rv;
int orig;
int score;
score = -990;
if(!oom::has_oom_score_adj())
return;
orig = oom::get_oom_score_adj();
rv = oom::set_oom_score_adj(score);
score = -990;
(void)rv;
SysLog::info("set oom_score_adj to {}, originally {}",
score,
orig);
}
orig = oom::get_oom_score_adj();
rv = oom::set_oom_score_adj(score);
static
bool
_wait_for_mount()
{
int failures;
std::vector<fs::path> paths;
std::chrono::milliseconds timeout;
(void)rv;
SysLog::info("set oom_score_adj to {}, originally {}",
score,
orig);
}
paths = cfg.branches->to_paths();
static
bool
wait_for_mount(const Config &cfg_)
{
int failures;
std::vector<fs::path> paths;
std::chrono::milliseconds timeout;
paths = cfg_.branches->to_paths();
SysLog::info("Waiting {} seconds for {} branches to mount",
(uint64_t)cfg_.branches_mount_timeout,
paths.size());
timeout = std::chrono::milliseconds(cfg_.branches_mount_timeout * 1000);
failures = fs::wait_for_mount(cfg_.mountpoint,
paths,
timeout);
if(failures)
{
if(cfg_.branches_mount_timeout_fail)
{
SysLog::error("{} of {} branches were not mounted"
" within the timeout of {}s. Exiting",
failures,
paths.size(),
(uint64_t)cfg_.branches_mount_timeout);
return true;
}
SysLog::warning("Continuing to mount mergerfs despite {} branches not "
"being different from the mountpoint filesystem",
failures);
}
else
{
SysLog::info("All {} branches are mounted",
paths.size());
}
return false;
}
static
void
lazy_umount(const fs::path &target_)
{
int rv;
rv = fs::umount_lazy(target_);
switch(rv)
{
case 0:
SysLog::notice("{} has been successfully lazily unmounted",
target_.string());
break;
case -EINVAL:
SysLog::notice("{} was not a mount point needing to be unmounted",
target_.string());
break;
default:
SysLog::error("Error unmounting {}: {} - {}",
target_.string(),
-rv,
strerror(-rv));
break;
}
}
static
void
usr1_signal_handler(int signal_)
{
// SysLog::info("Received SIGUSR1 - invalidating all nodes");
// fuse_invalidate_all_nodes();
}
SysLog::info("Waiting {} seconds for {} branches to mount",
(uint64_t)cfg.branches_mount_timeout,
paths.size());
static
void
usr2_signal_handler(int signal_)
{
// SysLog::info("Received SIGUSR2 - triggering thorough gc");
// fuse_gc();
// GIDCache::clear_all();
}
static
void
setup_signal_handlers()
{
std::signal(SIGUSR1,l::usr1_signal_handler);
std::signal(SIGUSR2,l::usr2_signal_handler);
}
timeout = std::chrono::milliseconds(cfg.branches_mount_timeout * 1000);
failures = fs::wait_for_mount(cfg.mountpoint,
paths,
timeout);
if(failures)
{
if(cfg.branches_mount_timeout_fail)
{
SysLog::error("{} of {} branches were not mounted"
" within the timeout of {}s. Exiting",
failures,
paths.size(),
(uint64_t)cfg.branches_mount_timeout);
return true;
}
SysLog::warning("Continuing to mount mergerfs despite {} branches not "
"being different from the mountpoint filesystem",
failures);
}
else
{
SysLog::info("All {} branches are mounted",
paths.size());
}
static
void
warn_if_not_root()
{
uid_t uid;
return false;
}
uid = geteuid();
if(uid == 0)
return;
static
void
_lazy_umount(const fs::path &target_)
{
int rv;
constexpr const char s[] = "mergerfs is not running as root and may not work correctly\n";
fmt::print(stderr,"warning: {}",s);
SysLog::warning(s);
}
rv = fs::umount_lazy(target_);
switch(rv)
{
case 0:
SysLog::notice("{} has been successfully lazily unmounted",
target_.string());
break;
case -EINVAL:
SysLog::notice("{} was not a mount point needing to be unmounted",
target_.string());
break;
default:
SysLog::error("Error unmounting {}: {} - {}",
target_.string(),
-rv,
strerror(-rv));
break;
}
}
int
main(int argc_,
char **argv_)
{
int rv;
Config::ErrVec errs;
fuse_args args;
fuse_operations ops;
static
void
_usr1_signal_handler(int signal_)
{
// 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::open();
static
void
_setup_signal_handlers()
{
std::signal(SIGUSR1,::_usr1_signal_handler);
std::signal(SIGUSR2,::_usr2_signal_handler);
}
memset(&ops,0,sizeof(fuse_operations));
static
void
_warn_if_not_root()
{
uid_t uid;
args.argc = argc_;
args.argv = argv_;
args.allocated = 0;
uid = geteuid();
if(uid == 0)
return;
options::parse(&args,&errs);
if(errs.size())
{
std::cerr << errs << std::endl;
return 1;
}
constexpr const char s[] =
"mergerfs is not running as root"
" and may not work correctly";
fmt::println(stderr,"* WARNING: {}",s);
SysLog::warning(s);
}
int
_main(int argc_,
char **argv_)
{
int rv;
fuse_args args;
fuse_operations ops;
SysLog::open();
memset(&ops,0,sizeof(fuse_operations));
args.argc = argc_;
args.argv = argv_;
args.allocated = false;
if(cfg.branches_mount_timeout > 0)
{
bool failure;
SysLog::info("mergerfs v{} started",MERGERFS_VERSION);
SysLog::info("Go to https://trapexit.github.io/mergerfs/support for support");
failure = l::wait_for_mount(cfg);
if(failure)
return 1;
}
options::parse(&args);
if(!cfg.errs.empty())
{
for(auto &err : cfg.errs)
{
std::string s = err.to_string();
SysLog::error("error: {}",s);
fmt::println(stderr,"* ERROR: {}",s);
}
SysLog::info("mergerfs v{} started",MERGERFS_VERSION);
SysLog::info("Go to https://trapexit.github.io/mergerfs/support for support");
l::warn_if_not_root();
return 1;
}
MaintenanceThread::push_job([](int count_)
if(cfg.branches_mount_timeout > 0)
{
if((count_ % 60) == 0)
GIDCache::clear_unused();
});
l::setup_resources(cfg.scheduling_priority);
l::setup_signal_handlers();
l::set_oom_score_adj();
l::get_fuse_operations(ops,cfg.nullrw);
bool failure;
failure = ::_wait_for_mount();
if(failure)
return 1;
}
::_warn_if_not_root();
MaintenanceThread::push_job([](int count_)
{
if((count_ % 60) == 0)
GIDCache::clear_unused();
});
::_setup_resources(cfg.scheduling_priority);
::_setup_signal_handlers();
::_set_oom_score_adj();
::_get_fuse_operations(ops,cfg.nullrw);
if(cfg.lazy_umount_mountpoint)
l::lazy_umount(cfg.mountpoint);
if(cfg.lazy_umount_mountpoint)
::_lazy_umount(cfg.mountpoint);
rv = fuse_main(args.argc,
args.argv,
&ops);
rv = fuse_main(args.argc,
args.argv,
&ops);
SysLog::info("exiting main loop with return code {}",rv);
SysLog::info("exiting main loop with return code {}",rv);
SysLog::close();
SysLog::close();
return rv;
}
return rv;
}
static
@ -372,7 +380,7 @@ _pick_app_and_run(int argc_,
if(appname == "mergerfs.collect-info")
return mergerfs::collect_info::main(argc_,argv_);
return l::main(argc_,argv_);
return ::_main(argc_,argv_);
}
int

263
src/option_parser.cpp

@ -31,7 +31,6 @@
#include "version.hpp"
#include "fuse.h"
#include "fuse_config.hpp"
#include <array>
#include <fstream>
@ -53,7 +52,6 @@ enum
MERGERFS_OPT_VERSION
};
static
void
_set_option(const std::string &option_,
@ -79,30 +77,19 @@ _set_kv_option(const std::string &key_,
static
void
_set_fuse_threads(Config &cfg_)
_set_fsname(fuse_args *args_)
{
fuse_config_set_read_thread_count(cfg_.fuse_read_thread_count);
fuse_config_set_process_thread_count(cfg_.fuse_process_thread_count);
fuse_config_set_process_thread_queue_depth(cfg_.fuse_process_thread_queue_depth);
fuse_config_set_pin_threads(cfg_.fuse_pin_threads);
}
static
void
_set_fsname(Config &cfg_,
fuse_args *args_)
{
if(cfg_.fsname->empty())
if(cfg.fsname->empty())
{
std::vector<std::string> paths;
cfg_.branches->to_paths(paths);
cfg.branches->to_paths(paths);
if(paths.size() > 0)
cfg_.fsname = str::remove_common_prefix_and_join(paths,':');
cfg.fsname = str::remove_common_prefix_and_join(paths,':');
}
::_set_kv_option("fsname",cfg_.fsname,args_);
::_set_kv_option("fsname",cfg.fsname,args_);
}
static
@ -114,10 +101,9 @@ _set_subtype(fuse_args *args_)
static
void
_set_default_options(fuse_args *args_,
Config &cfg_)
_set_default_options(fuse_args *args_)
{
if(cfg_.kernel_permissions_check)
if(cfg.kernel_permissions_check)
::_set_option("default_permissions",args_);
if(geteuid() == 0)
@ -126,84 +112,28 @@ _set_default_options(fuse_args *args_,
SysLog::notice("not auto setting allow_other since not running as root");
}
static
bool
_should_ignore(const std::string &key_)
{
constexpr const std::array<std::string_view,13> ignored_keys =
{
"atomic_o_trunc",
"big_writes",
"cache.open",
"defaults",
"hard_remove",
"no_splice_move",
"no_splice_read",
"no_splice_write",
"nonempty",
"splice_move",
"splice_read",
"splice_write",
"use_ino",
};
for(const auto &key : ignored_keys)
{
if(key == key_)
return true;
}
return false;
}
static
int
_parse_and_process_kv_arg(Config &cfg_,
Config::ErrVec *errs_,
const std::string &key_,
_parse_and_process_kv_arg(const std::string &key_,
const std::string &val_)
{
int rv;
std::string key(key_);
std::string val(val_);
rv = 0;
if(key == "config")
return ((cfg_.from_file(val_,errs_) < 0) ? 1 : 0);
ef(key == "attr_timeout")
key = "cache.attr";
ef(key == "entry_timeout")
key = "cache.entry";
ef(key == "negative_entry")
key = "cache.negative_entry";
ef(key == "direct_io" && val.empty())
val = "true";
ef(key == "kernel_cache" && val.empty())
val = "true";
ef(key == "auto_cache" && val.empty())
val = "true";
ef(key == "async_read" && val.empty())
val = "true";
ef(key == "sync_read" && val.empty())
{key = "async_read", val = "false";}
ef(::_should_ignore(key_))
return 0;
if(cfg_.has_key(key) == false)
if(cfg.has_key(key) == false)
return 1;
rv = cfg_.set_raw(key,val);
if(rv)
errs_->push_back({rv,key+'='+val});
rv = cfg.set(key,val);
if(rv < 0)
cfg.errs.push_back({-rv,key+'='+val});
return 0;
}
static
int
_process_opt(Config &cfg_,
Config::ErrVec *errs_,
const std::string &arg_)
_process_opt(const std::string &arg_)
{
std::string key;
std::string val;
@ -212,77 +142,76 @@ _process_opt(Config &cfg_,
key = str::trim(key);
val = str::trim(val);
return ::_parse_and_process_kv_arg(cfg_,errs_,key,val);
return ::_parse_and_process_kv_arg(key,val);
}
static
int
_process_branches(Config &cfg_,
Config::ErrVec *errs_,
const char *arg_)
_process_branches(const char *arg_)
{
int rv;
std::string arg;
arg = arg_;
rv = cfg_.set_raw("branches",arg);
if(rv)
errs_->push_back({rv,"branches="+arg});
rv = cfg.set("branches",arg);
if(rv < 0)
cfg.errs.push_back({-rv,"branches="+arg});
return 0;
}
static
int
_process_mount(Config &cfg_,
Config::ErrVec *errs_,
const char *arg_)
_process_mount(const char *arg_)
{
int rv;
std::string arg;
arg = arg_;
rv = cfg_.set_raw("mount",arg);
if(rv)
errs_->push_back({rv,"mount="+arg});
rv = cfg.set("mount",arg);
if(rv < 0)
cfg.errs.push_back({-rv,"mount="+arg});
return 1;
}
static
void
_postprocess_passthrough(Config &cfg_)
_usage(void)
{
if(cfg_.passthrough == Passthrough::ENUM::OFF)
return;
if(cfg_.cache_files == CacheFiles::ENUM::OFF)
{
SysLog::warning("'cache.files' can not be 'off' when using 'passthrough'."
" Setting 'cache.files=full'");
cfg_.cache_files = CacheFiles::ENUM::FULL;
}
if(cfg_.writeback_cache == true)
{
SysLog::warning("'cache.writeback' can not be enabled when using 'passthrough'."
" Setting 'cache.writeback=false'");
cfg_.writeback_cache = false;
}
if(cfg_.moveonenospc.enabled == true)
{
SysLog::warning("`moveonenospc` will not function when `passthrough` is enabled");
}
fmt::print("Usage: mergerfs [options] <branches> <mountpoint>\n"
"\n"
"Visit https://trapexit.github.io/mergerfs for options.\n\n");
}
static
void
_usage(void)
_version(void)
{
fmt::print("Usage: mergerfs [options] <branches> <mountpoint>\n"
fmt::print("mergerfs v{}\n\n"
"https://github.com/trapexit/mergerfs\n"
"https://trapexit.github.io/mergerfs\n"
"https://github.com/trapexit/support\n"
"\n"
"Visit https://trapexit.github.io/mergerfs for options.\n\n");
"ISC License (ISC)\n"
"\n"
"Copyright 2025, Antonio SJ Musumeci <trapexit@spawn.link>\n"
"\n"
"Permission to use, copy, modify, and/or distribute this software for\n"
"any purpose with or without fee is hereby granted, provided that the\n"
"above copyright notice and this permission notice appear in all\n"
"copies.\n"
"\n"
"THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\n"
"WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\n"
"WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\n"
"AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL\n"
"DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR\n"
"PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n"
"TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n"
"PERFORMANCE OF THIS SOFTWARE.\n\n"
,
(MERGERFS_VERSION[0] ? MERGERFS_VERSION : "unknown"));
}
static
@ -292,44 +221,25 @@ _option_processor(void *data_,
int key_,
fuse_args *outargs_)
{
Config::ErrVec *errs = (Config::ErrVec*)data_;
(void)data_;
switch(key_)
{
case FUSE_OPT_KEY_OPT:
return ::_process_opt(cfg,errs,arg_);
return ::_process_opt(arg_);
case FUSE_OPT_KEY_NONOPT:
if(cfg.branches->empty())
return ::_process_branches(cfg,errs,arg_);
return ::_process_branches(arg_);
else
return ::_process_mount(cfg,errs,arg_);
return ::_process_mount(arg_);
case MERGERFS_OPT_HELP:
::_usage();
exit(0);
case MERGERFS_OPT_VERSION:
fmt::print("mergerfs v{}\n\n"
"https://github.com/trapexit/mergerfs\n"
"https://trapexit.github.io/mergerfs\n"
"https://github.com/trapexit/support\n\n"
"ISC License (ISC)\n\n"
"Copyright 2025, Antonio SJ Musumeci <trapexit@spawn.link>\n\n"
"Permission to use, copy, modify, and/or distribute this software for\n"
"any purpose with or without fee is hereby granted, provided that the\n"
"above copyright notice and this permission notice appear in all\n"
"copies.\n\n"
"THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\n"
"WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\n"
"WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\n"
"AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL\n"
"DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR\n"
"PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n"
"TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n"
"PERFORMANCE OF THIS SOFTWARE.\n\n"
,
(MERGERFS_VERSION[0] ? MERGERFS_VERSION : "unknown"));
::_version();
exit(0);
default:
@ -341,15 +251,14 @@ _option_processor(void *data_,
static
void
_check_for_mount_loop(Config &cfg_,
Config::ErrVec *errs_)
_check_for_mount_loop()
{
fs::path mount;
std::vector<fs::path> branches;
std::error_code ec;
mount = *cfg_.mountpoint;
branches = cfg_.branches->to_paths();
mount = *cfg.mountpoint;
branches = cfg.branches->to_paths();
for(const auto &branch : branches)
{
if(std::filesystem::equivalent(branch,mount,ec))
@ -358,51 +267,61 @@ _check_for_mount_loop(Config &cfg_,
errstr = fmt::format("branches can not include the mountpoint: {}",
branch.string());
errs_->push_back({0,errstr});
cfg.errs.push_back({0,errstr});
}
}
}
static
void
_print_warnings(Config &cfg_)
_postprocess_and_print_warnings()
{
if(cfg_.passthrough != Passthrough::ENUM::OFF)
if(fuse_cfg.uid != FUSE_CFG_INVALID_ID)
SysLog::warning("overwriting 'uid' is untested and unsupported,"
" use at your own risk");
if(fuse_cfg.gid != FUSE_CFG_INVALID_ID)
SysLog::warning("overwriting 'gid' is untested and unsupported,"
" use at your own risk");
if(fuse_cfg.umask != FUSE_CFG_INVALID_UMASK)
SysLog::warning("overwriting 'umask' is untested and unsupported,"
" use at your own risk");
if(cfg.passthrough_io != PassthroughIO::ENUM::OFF)
{
if(cfg_.cache_files == CacheFiles::ENUM::OFF)
if(cfg.cache_files == CacheFiles::ENUM::OFF)
{
SysLog::warning("'cache.files' can not be 'off' when using 'passthrough'."
" Setting 'cache.files=auto-full'");
cfg_.cache_files = CacheFiles::ENUM::AUTO_FULL;
cfg.cache_files = CacheFiles::ENUM::AUTO_FULL;
}
if(cfg_.writeback_cache == true)
if(cfg.cache_writeback == true)
{
SysLog::warning("'cache.writeback' can not be enabled when using 'passthrough'."
" Setting 'cache.writeback=false'");
cfg_.writeback_cache = false;
cfg.cache_writeback = false;
}
if(cfg_.moveonenospc.enabled == true)
if(cfg.moveonenospc.enabled == true)
{
SysLog::warning("`moveonenospc` will not function when `passthrough` is enabled");
SysLog::warning("'moveonenospc' will not function when 'passthrough' is enabled");
}
}
}
static
void
_cleanup_options(Config &cfg_)
_cleanup_options()
{
if(!cfg_.symlinkify)
cfg_.symlinkify_timeout = -1;
if(!cfg.symlinkify)
cfg.symlinkify_timeout = -1;
}
namespace options
{
void
parse(fuse_args *args_,
Config::ErrVec *errs_)
parse(fuse_args *args_)
{
const struct fuse_opt opts[] =
{
@ -415,23 +334,21 @@ namespace options
};
fuse_opt_parse(args_,
errs_,
NULL,
opts,
::_option_processor);
if(cfg.branches->empty())
errs_->push_back({0,"branches not set"});
cfg.errs.push_back({EINVAL,"branches not set"});
if(cfg.mountpoint->empty())
errs_->push_back({0,"mountpoint not set"});
cfg.errs.push_back({EINVAL,"mountpoint not set"});
::_postprocess_passthrough(cfg);
::_check_for_mount_loop(cfg,errs_);
::_set_default_options(args_,cfg);
::_set_fsname(cfg,args_);
::_postprocess_and_print_warnings();
::_check_for_mount_loop();
::_set_default_options(args_);
::_set_fsname(args_);
::_set_subtype(args_);
::_set_fuse_threads(cfg);
::_print_warnings(cfg);
::_cleanup_options(cfg);
::_cleanup_options();
cfg.finish_initializing();
}

5
src/option_parser.hpp

@ -16,14 +16,11 @@
#pragma once
#include "config.hpp"
#include "fuse.h"
namespace options
{
void
parse(fuse_args *args,
Config::ErrVec *errs);
parse(fuse_args *args);
}

16
src/str.cpp

@ -16,6 +16,7 @@
#include "str.hpp"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <set>
@ -331,3 +332,18 @@ str::startswith(const char *s_,
return true;
}
std::string
str::replace(const std::string &s_,
const char src_,
const char dst_)
{
std::string s(s_);
std::replace(s.begin(),
s.end(),
src_,
dst_);
return s;
}

5
src/str.hpp

@ -101,4 +101,9 @@ namespace str
bool
eq(const char *s0,
const char *s1);
std::string
replace(const std::string &s,
const char src,
const char dst);
}

39
src/tofrom_ref.hpp

@ -0,0 +1,39 @@
#pragma once
#include "to_string.hpp"
#include "from_string.hpp"
#include "tofrom_string.hpp"
template<typename T>
class TFSRef : public ToFromString
{
public:
int
from_string(const std::string_view s_) final
{
return str::from(s_,&_data);
}
std::string
to_string(void) const final
{
return str::to(_data);
}
public:
TFSRef(T &data_)
: _data(data_)
{
}
TFSRef(T &data_,
const T val_)
: _data(data_)
{
_data = val_;
}
private:
T &_data;
};

2
src/tofrom_string.hpp

@ -26,4 +26,6 @@ class ToFromString
public:
virtual std::string to_string() const = 0;
virtual int from_string(const std::string_view) = 0;
bool display = true;
bool ro = false;
};

2
src/tofrom_wrapper.hpp

@ -102,7 +102,7 @@ public:
int
from_string(const std::string_view s_) final
{
return -EINVAL;
return -EROFS;
}
std::string

Loading…
Cancel
Save